update Tue 07/14/2026 12:21:23.58
This commit is contained in:
+21
-21
@@ -41,7 +41,7 @@ scene.anchors ||= []; scene.buildings ||= []; scene.spawns ||= [];
|
||||
|
||||
const cv = document.getElementById('cv');
|
||||
const ctx = cv.getContext('2d');
|
||||
let view = { x: 0, z: 0, scale: 120 }; // px per metre
|
||||
let view = { x: 0, z: 0, scale: 1.2 }; // px per cm
|
||||
let sel = null; // { kind, index }
|
||||
let drag = null;
|
||||
|
||||
@@ -59,9 +59,9 @@ const s2w = (px, py) => [(px * devicePixelRatio - cv.width / 2) / (view.scale *
|
||||
|
||||
function draw() {
|
||||
ctx.clearRect(0, 0, cv.width, cv.height);
|
||||
// grid (0.25 m)
|
||||
// grid (25 cm)
|
||||
ctx.strokeStyle = '#1a2244'; ctx.lineWidth = 1;
|
||||
const step = 0.25 * view.scale * devicePixelRatio;
|
||||
const step = 25 * view.scale * devicePixelRatio;
|
||||
const ox = (cv.width / 2 - view.x * view.scale * devicePixelRatio) % step;
|
||||
const oz = (cv.height / 2 - view.z * view.scale * devicePixelRatio) % step;
|
||||
for (let x = ox; x < cv.width; x += step) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, cv.height); ctx.stroke(); }
|
||||
@@ -90,7 +90,7 @@ function draw() {
|
||||
for (const [i, a] of scene.anchors.entries()) {
|
||||
const [sx, sy] = w2s(a.position[0], a.position[2]);
|
||||
ctx.save(); ctx.translate(sx, sy); ctx.rotate(-(a.yawDeg || 0) * Math.PI / 180);
|
||||
const r = Math.max(8, (a.sizeMM / 1000) * view.scale) * devicePixelRatio;
|
||||
const r = Math.max(8, (a.sizeMM / 10) * view.scale) * devicePixelRatio;
|
||||
ctx.fillStyle = isSel('anchor', i) ? '#51eaf1' : (a.enabled === false ? '#456' : (a.mount === 'wall' ? '#f65151' : '#3bc9a7'));
|
||||
if (a.mount === 'wall') { ctx.fillRect(-r, -2.5 * devicePixelRatio, 2 * r, 5 * devicePixelRatio); // edge-on line
|
||||
ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(0, -r); ctx.strokeStyle = ctx.fillStyle; ctx.lineWidth = 2; ctx.stroke(); }
|
||||
@@ -108,10 +108,10 @@ const isSel = (k, i) => sel && sel.kind === k && sel.index === i;
|
||||
function hit(px, py) {
|
||||
const [wx, wz] = s2w(px, py);
|
||||
const near = (x, z, r) => Math.hypot(wx - x, wz - z) < r;
|
||||
for (const [i, a] of scene.anchors.entries()) if (near(a.position[0], a.position[2], 0.12)) return { kind: 'anchor', index: i };
|
||||
for (const [i, s] of scene.spawns.entries()) if (near(s.position[0], s.position[2], 0.12)) return { kind: 'spawn', index: i };
|
||||
for (const [i, a] of scene.anchors.entries()) if (near(a.position[0], a.position[2], 12)) return { kind: 'anchor', index: i };
|
||||
for (const [i, s] of scene.spawns.entries()) if (near(s.position[0], s.position[2], 12)) return { kind: 'spawn', index: i };
|
||||
for (const [i, b] of scene.buildings.entries())
|
||||
if (Math.abs(wx - b.position[0]) < b.size[0] / 2 + 0.05 && Math.abs(wz - b.position[2]) < b.size[2] / 2 + 0.05)
|
||||
if (Math.abs(wx - b.position[0]) < b.size[0] / 2 + 5 && Math.abs(wz - b.position[2]) < b.size[2] / 2 + 5)
|
||||
return { kind: 'building', index: i };
|
||||
return null;
|
||||
}
|
||||
@@ -129,8 +129,8 @@ cv.addEventListener('pointermove', (e) => {
|
||||
if (drag.kind === 'move' && sel) {
|
||||
const [wx, wz] = s2w(e.offsetX, e.offsetY);
|
||||
const o = objOf(sel);
|
||||
o.position[0] = Math.round(wx * 200) / 200; // 5 mm snap
|
||||
o.position[2] = Math.round(wz * 200) / 200;
|
||||
o.position[0] = Math.round(wx * 2) / 2; // 5 mm snap
|
||||
o.position[2] = Math.round(wz * 2) / 2;
|
||||
panel(false); draw();
|
||||
} else if (drag.kind === 'pan') {
|
||||
view.x = drag.vx - (e.offsetX - drag.sx) / view.scale;
|
||||
@@ -141,7 +141,7 @@ cv.addEventListener('pointermove', (e) => {
|
||||
cv.addEventListener('pointerup', () => drag = null);
|
||||
cv.addEventListener('wheel', (e) => {
|
||||
e.preventDefault();
|
||||
view.scale = Math.min(600, Math.max(30, view.scale * (e.deltaY < 0 ? 1.12 : 0.89)));
|
||||
view.scale = Math.min(6, Math.max(0.3, view.scale * (e.deltaY < 0 ? 1.12 : 0.89)));
|
||||
draw();
|
||||
}, { passive: false });
|
||||
|
||||
@@ -154,10 +154,10 @@ document.querySelectorAll('[data-add]').forEach(b => b.onclick = () => {
|
||||
scene.anchors.push({ markerId: id, position: [wx, 0, wz], mount: 'flat', yawDeg: 0, pitchDeg: 0, rollDeg: 0, sizeMM: 60, enabled: true });
|
||||
sel = { kind: 'anchor', index: scene.anchors.length - 1 };
|
||||
} else if (b.dataset.add === 'building') {
|
||||
scene.buildings.push({ name: 'building', position: [wx, 0, wz], size: [0.4, 0.3, 0.3], yawDeg: 0 });
|
||||
scene.buildings.push({ name: 'building', position: [wx, 0, wz], size: [40, 30, 30], yawDeg: 0 });
|
||||
sel = { kind: 'building', index: scene.buildings.length - 1 };
|
||||
} else {
|
||||
scene.spawns.push({ id: 'spawn-' + (scene.spawns.length + 1), position: [wx, 0.25, wz], enabled: true });
|
||||
scene.spawns.push({ id: 'spawn-' + (scene.spawns.length + 1), position: [wx, 25, wz], enabled: true });
|
||||
sel = { kind: 'spawn', index: scene.spawns.length - 1 };
|
||||
}
|
||||
panel(); draw();
|
||||
@@ -173,7 +173,7 @@ function field(label, val, oncommit, opts) {
|
||||
});
|
||||
if (opts?.select)
|
||||
return `<div class="row"><label>${label}</label><select id="${id}">${opts.select.map(o => `<option ${o === val ? 'selected' : ''}>${o}</option>`).join('')}</select></div>`;
|
||||
return `<div class="row"><label>${label}</label><input id="${id}" type="number" step="${opts?.step ?? 0.01}" value="${val}"></div>`;
|
||||
return `<div class="row"><label>${label}</label><input id="${id}" type="number" step="${opts?.step ?? 0.5}" value="${val}"></div>`;
|
||||
}
|
||||
function textField(label, val, oncommit) {
|
||||
const id = 'f' + Math.random().toString(36).slice(2, 8);
|
||||
@@ -187,9 +187,9 @@ function panel(rebuild = true) {
|
||||
let h = `<h2>${sel.kind} ${sel.index}</h2>`;
|
||||
if (sel.kind === 'anchor') {
|
||||
h += field('marker ID', o.markerId, v => o.markerId = v | 0, { step: 1 });
|
||||
h += field('x (m)', o.position[0], v => o.position[0] = v);
|
||||
h += field('y (m)', o.position[1], v => o.position[1] = v);
|
||||
h += field('z (m)', o.position[2], v => o.position[2] = v);
|
||||
h += field('x (cm)', o.position[0], v => o.position[0] = v);
|
||||
h += field('y (cm)', o.position[1], v => o.position[1] = v);
|
||||
h += field('z (cm)', o.position[2], v => o.position[2] = v);
|
||||
h += field('mount', o.mount, v => o.mount = v, { select: ['flat', 'wall', 'custom'] });
|
||||
h += field('yaw °', o.yawDeg, v => o.yawDeg = v, { step: 1 });
|
||||
h += field('pitch °', o.pitchDeg, v => o.pitchDeg = v, { step: 1 });
|
||||
@@ -199,17 +199,17 @@ function panel(rebuild = true) {
|
||||
h += `<p class="badge">flat = face-up on a surface · wall = vertical, yaw sets facing · custom = full yaw/pitch/roll</p>`;
|
||||
} else if (sel.kind === 'building') {
|
||||
h += textField('name', o.name || '', v => o.name = v);
|
||||
h += field('x (m)', o.position[0], v => o.position[0] = v);
|
||||
h += field('z (m)', o.position[2], v => o.position[2] = v);
|
||||
h += field('x (cm)', o.position[0], v => o.position[0] = v);
|
||||
h += field('z (cm)', o.position[2], v => o.position[2] = v);
|
||||
h += field('width', o.size[0], v => o.size[0] = v);
|
||||
h += field('height', o.size[1], v => o.size[1] = v);
|
||||
h += field('depth', o.size[2], v => o.size[2] = v);
|
||||
h += field('yaw °', o.yawDeg || 0, v => o.yawDeg = v, { step: 1 });
|
||||
} else {
|
||||
h += textField('id', o.id, v => o.id = v);
|
||||
h += field('x (m)', o.position[0], v => o.position[0] = v);
|
||||
h += field('y (m)', o.position[1], v => o.position[1] = v);
|
||||
h += field('z (m)', o.position[2], v => o.position[2] = v);
|
||||
h += field('x (cm)', o.position[0], v => o.position[0] = v);
|
||||
h += field('y (cm)', o.position[1], v => o.position[1] = v);
|
||||
h += field('z (cm)', o.position[2], v => o.position[2] = v);
|
||||
h += field('enabled', o.enabled === false ? 'no' : 'yes', v => o.enabled = v === 'yes', { select: ['yes', 'no'] });
|
||||
}
|
||||
h += `<div class="row" style="margin-top:12px"><button id="del" class="danger">Delete</button></div>`;
|
||||
|
||||
Reference in New Issue
Block a user