feat: edit moving source trajectories
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { SOURCE_COLORS } from "./model.js";
|
||||
import { sourcePositionAtTime } from "/packages/acoustics-core/trajectory.js";
|
||||
|
||||
function finitePlane(plane) {
|
||||
return [plane.xMin, plane.xMax, plane.yMin, plane.yMax].every(Number.isFinite) &&
|
||||
@@ -45,9 +46,50 @@ function drawGrid(context, width, height, plane) {
|
||||
context.textAlign = "left";
|
||||
}
|
||||
|
||||
function drawSource(context, source, index, selected, plane, width, height) {
|
||||
if (!source.position?.every(Number.isFinite)) return;
|
||||
const point = coordinateToPixel(source.position, plane, width, height);
|
||||
function drawTrajectory(context, source, index, selected, selectedKeyframeIndex, plane, width, height) {
|
||||
if (!Array.isArray(source.trajectory) || source.trajectory.length < 2) return;
|
||||
const points = source.trajectory.map(({ position }) => coordinateToPixel(position, plane, width, height));
|
||||
const color = SOURCE_COLORS[index % SOURCE_COLORS.length];
|
||||
context.save();
|
||||
context.strokeStyle = color;
|
||||
context.globalAlpha = selected ? 0.85 : 0.28;
|
||||
context.lineWidth = selected ? 2.5 : 1.5;
|
||||
context.setLineDash([7, 5]);
|
||||
context.beginPath();
|
||||
points.forEach((point, pointIndex) => {
|
||||
if (pointIndex === 0) context.moveTo(point.x, point.y);
|
||||
else context.lineTo(point.x, point.y);
|
||||
});
|
||||
context.stroke();
|
||||
context.setLineDash([]);
|
||||
points.forEach((point, pointIndex) => {
|
||||
context.globalAlpha = selected ? 1 : 0.42;
|
||||
context.fillStyle = color;
|
||||
context.beginPath();
|
||||
context.arc(point.x, point.y, pointIndex === selectedKeyframeIndex && selected ? 6 : 4, 0, Math.PI * 2);
|
||||
context.fill();
|
||||
if (pointIndex === selectedKeyframeIndex && selected) {
|
||||
context.strokeStyle = "#fff";
|
||||
context.lineWidth = 2;
|
||||
context.beginPath();
|
||||
context.arc(point.x, point.y, 9, 0, Math.PI * 2);
|
||||
context.stroke();
|
||||
}
|
||||
});
|
||||
context.restore();
|
||||
}
|
||||
|
||||
function positionAtPreviewTime(source, previewTime) {
|
||||
try {
|
||||
return sourcePositionAtTime(source, previewTime);
|
||||
} catch {
|
||||
return source.position;
|
||||
}
|
||||
}
|
||||
|
||||
function drawSource(context, source, position, index, selected, plane, width, height) {
|
||||
if (!position?.every(Number.isFinite)) return;
|
||||
const point = coordinateToPixel(position, plane, width, height);
|
||||
if (point.x < -30 || point.x > width + 30 || point.y < -30 || point.y > height + 30) return;
|
||||
const color = SOURCE_COLORS[index % SOURCE_COLORS.length];
|
||||
context.save();
|
||||
@@ -81,8 +123,8 @@ export function createSceneCanvas(canvas, emptyElement, badgeElement, onMove) {
|
||||
const context = canvas.getContext("2d");
|
||||
let current = null;
|
||||
|
||||
function render(scene, image, plane, selectedSourceId) {
|
||||
current = { scene, image, plane, selectedSourceId };
|
||||
function render(scene, image, plane, selectedSourceId, previewTime = 0, selectedKeyframeIndex = 0) {
|
||||
current = { scene, image, plane, selectedSourceId, previewTime, selectedKeyframeIndex };
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const width = Math.max(1, rect.width);
|
||||
const height = Math.max(1, rect.height);
|
||||
@@ -111,12 +153,22 @@ export function createSceneCanvas(canvas, emptyElement, badgeElement, onMove) {
|
||||
context.fillRect(0, 0, width, height);
|
||||
}
|
||||
scene.sources.forEach((source, index) => {
|
||||
drawSource(context, source, index, source.id === selectedSourceId, plane, width, height);
|
||||
drawTrajectory(
|
||||
context, source, index, source.id === selectedSourceId,
|
||||
source.id === selectedSourceId ? selectedKeyframeIndex : -1,
|
||||
plane, width, height,
|
||||
);
|
||||
});
|
||||
scene.sources.forEach((source, index) => {
|
||||
drawSource(
|
||||
context, source, positionAtPreviewTime(source, previewTime), index,
|
||||
source.id === selectedSourceId, plane, width, height,
|
||||
);
|
||||
});
|
||||
emptyElement.classList.toggle("is-hidden", Boolean(image));
|
||||
badgeElement.textContent =
|
||||
`x ${plane.xMin.toFixed(2)}…${plane.xMax.toFixed(2)} m · ` +
|
||||
`y ${plane.yMin.toFixed(2)}…${plane.yMax.toFixed(2)} m`;
|
||||
`y ${plane.yMin.toFixed(2)}…${plane.yMax.toFixed(2)} m · t ${previewTime.toFixed(2)} s`;
|
||||
}
|
||||
|
||||
canvas.addEventListener("pointerdown", (event) => {
|
||||
@@ -128,7 +180,10 @@ export function createSceneCanvas(canvas, emptyElement, badgeElement, onMove) {
|
||||
});
|
||||
|
||||
new ResizeObserver(() => {
|
||||
if (current) render(current.scene, current.image, current.plane, current.selectedSourceId);
|
||||
if (current) render(
|
||||
current.scene, current.image, current.plane, current.selectedSourceId,
|
||||
current.previewTime, current.selectedKeyframeIndex,
|
||||
);
|
||||
}).observe(canvas);
|
||||
|
||||
return { render };
|
||||
|
||||
Reference in New Issue
Block a user