67 lines
2.6 KiB
JavaScript
67 lines
2.6 KiB
JavaScript
|
|
"use strict";
|
|||
|
|
|
|||
|
|
// Layer: ui/physics-shape-editor
|
|||
|
|
// Shared button labels/templates for rotator, reciprocator, and poison-block
|
|||
|
|
// segment editors. Low-level editor pointer behavior remains in placement log.
|
|||
|
|
(function (global) {
|
|||
|
|
const MODE_LABELS = Object.freeze({
|
|||
|
|
line: "📏 直線",
|
|||
|
|
free: "✎ 自由描画",
|
|||
|
|
erase: "⌫ 消しゴム",
|
|||
|
|
});
|
|||
|
|
const TEMPLATE_LABELS = Object.freeze({
|
|||
|
|
bar: "┃ 棒",
|
|||
|
|
cross: "+ 十字",
|
|||
|
|
circle: "○ 円",
|
|||
|
|
});
|
|||
|
|
const ACTION_LABELS = Object.freeze({
|
|||
|
|
undo: "↶ 一つ戻す",
|
|||
|
|
clear: "🗑 全消し",
|
|||
|
|
});
|
|||
|
|
function templateSegments(name) {
|
|||
|
|
if (name === "bar") return [[-130, 0, 130, 0]];
|
|||
|
|
if (name === "cross") return [[-118, 0, 118, 0], [0, -90, 0, 90]];
|
|||
|
|
if (name === "circle") {
|
|||
|
|
const segments = [];
|
|||
|
|
const n = 18, r = 108;
|
|||
|
|
for (let i = 0; i < n; i += 1) {
|
|||
|
|
const a = i / n * Math.PI * 2;
|
|||
|
|
const b = (i + 1) / n * Math.PI * 2;
|
|||
|
|
segments.push([Math.cos(a) * r, Math.sin(a) * r, Math.cos(b) * r, Math.sin(b) * r]);
|
|||
|
|
}
|
|||
|
|
return segments;
|
|||
|
|
}
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
function decorateToolbar(dialog, prefix = "rotator") {
|
|||
|
|
if (!dialog) return false;
|
|||
|
|
for (const btn of dialog.querySelectorAll(`[data-${prefix}-mode]`)) {
|
|||
|
|
const key = btn.dataset[`${prefix}Mode`];
|
|||
|
|
if (MODE_LABELS[key]) { btn.textContent = MODE_LABELS[key]; btn.title = MODE_LABELS[key].replace(/^[^\s]+\s*/, ""); }
|
|||
|
|
}
|
|||
|
|
for (const btn of dialog.querySelectorAll(`[data-${prefix}-template]`)) {
|
|||
|
|
const key = btn.dataset[`${prefix}Template`];
|
|||
|
|
if (TEMPLATE_LABELS[key]) { btn.textContent = TEMPLATE_LABELS[key]; btn.title = TEMPLATE_LABELS[key].replace(/^[^\s]+\s*/, ""); }
|
|||
|
|
}
|
|||
|
|
const undo = dialog.querySelector(`#${prefix}Undo`);
|
|||
|
|
const clear = dialog.querySelector(`#${prefix}Clear`);
|
|||
|
|
if (undo) undo.textContent = ACTION_LABELS.undo;
|
|||
|
|
if (clear) clear.textContent = ACTION_LABELS.clear;
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
function pointSegmentDistance(p, seg) {
|
|||
|
|
if (!p || !Array.isArray(seg) || seg.length < 4) return Infinity;
|
|||
|
|
const x1 = Number(seg[0]) || 0, y1 = Number(seg[1]) || 0;
|
|||
|
|
const x2 = Number(seg[2]) || 0, y2 = Number(seg[3]) || 0;
|
|||
|
|
const dx = x2 - x1, dy = y2 - y1;
|
|||
|
|
const lenSq = dx * dx + dy * dy;
|
|||
|
|
if (lenSq <= 0.0001) return Math.hypot(p.x - x1, p.y - y1);
|
|||
|
|
const u = Math.max(0, Math.min(1, ((p.x - x1) * dx + (p.y - y1) * dy) / lenSq));
|
|||
|
|
return Math.hypot(p.x - (x1 + dx * u), p.y - (y1 + dy * u));
|
|||
|
|
}
|
|||
|
|
global.TarinaiPhysicsShapeEditorSystem = Object.freeze({
|
|||
|
|
decorateToolbar,
|
|||
|
|
templateSegments,
|
|||
|
|
pointSegmentDistance,
|
|||
|
|
});
|
|||
|
|
})(typeof window !== "undefined" ? window : globalThis);
|