fix: keep camera geometry aligned with trajectories
This commit is contained in:
@@ -62,10 +62,30 @@ export function shiftSceneDistance(scene, experiment, nextDistance) {
|
|||||||
const previousDistance = experiment.scan.z;
|
const previousDistance = experiment.scan.z;
|
||||||
const delta = nextDistance - previousDistance;
|
const delta = nextDistance - previousDistance;
|
||||||
experiment.scan.z = nextDistance;
|
experiment.scan.z = nextDistance;
|
||||||
for (const source of scene.sources) source.position[2] += delta;
|
for (const source of scene.sources) {
|
||||||
|
source.position[2] += delta;
|
||||||
|
for (const keyframe of source.trajectory ?? []) keyframe.position[2] += delta;
|
||||||
|
}
|
||||||
if (scene.image?.plane) scene.image.plane.z += delta;
|
if (scene.image?.plane) scene.image.plane.z += delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sourceGeometryPositions(scene) {
|
||||||
|
return (scene.sources ?? []).flatMap((source) => {
|
||||||
|
const candidates = [source.position, ...(source.trajectory ?? []).map(({ position }) => position)];
|
||||||
|
const uniquePositions = new Map();
|
||||||
|
for (const position of candidates) {
|
||||||
|
if (
|
||||||
|
Array.isArray(position) &&
|
||||||
|
position.length === 3 &&
|
||||||
|
position.every(Number.isFinite)
|
||||||
|
) {
|
||||||
|
uniquePositions.set(position.join(","), position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...uniquePositions.values()];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function alignExperimentToScene(experiment, scene) {
|
export function alignExperimentToScene(experiment, scene) {
|
||||||
const plane = scene.image?.plane;
|
const plane = scene.image?.plane;
|
||||||
if (plane) {
|
if (plane) {
|
||||||
@@ -79,13 +99,7 @@ export function alignExperimentToScene(experiment, scene) {
|
|||||||
return "image-plane";
|
return "image-plane";
|
||||||
}
|
}
|
||||||
|
|
||||||
const positions = (scene.sources ?? [])
|
const positions = sourceGeometryPositions(scene);
|
||||||
.map((source) => source.position)
|
|
||||||
.filter((position) =>
|
|
||||||
Array.isArray(position) &&
|
|
||||||
position.length === 3 &&
|
|
||||||
position.every(Number.isFinite),
|
|
||||||
);
|
|
||||||
if (positions.length === 0) return "unchanged";
|
if (positions.length === 0) return "unchanged";
|
||||||
|
|
||||||
const paddedBounds = (values) => {
|
const paddedBounds = (values) => {
|
||||||
|
|||||||
@@ -249,7 +249,14 @@ test("Camera workload counts unique overlapping FFT bins", () => {
|
|||||||
test("Camera distance shift keeps the scene and scan on one z plane", () => {
|
test("Camera distance shift keeps the scene and scan on one z plane", () => {
|
||||||
const scene = {
|
const scene = {
|
||||||
sources: [
|
sources: [
|
||||||
{ id: "one", position: [0.1, -0.2, 2] },
|
{
|
||||||
|
id: "one",
|
||||||
|
position: [0.1, -0.2, 2],
|
||||||
|
trajectory: [
|
||||||
|
{ time: 0, position: [0.1, -0.2, 2] },
|
||||||
|
{ time: 1, position: [0.6, 0.3, 2.5] },
|
||||||
|
],
|
||||||
|
},
|
||||||
{ id: "two", position: [-0.3, 0.4, 2.5] },
|
{ id: "two", position: [-0.3, 0.4, 2.5] },
|
||||||
],
|
],
|
||||||
image: { plane: { z: 2, xMin: -1, xMax: 1, yMin: -1, yMax: 1 } },
|
image: { plane: { z: 2, xMin: -1, xMax: 1, yMin: -1, yMax: 1 } },
|
||||||
@@ -260,6 +267,16 @@ test("Camera distance shift keeps the scene and scan on one z plane", () => {
|
|||||||
|
|
||||||
assertEqual(experiment.scan.z, 3.25, "scan z");
|
assertEqual(experiment.scan.z, 3.25, "scan z");
|
||||||
assertEqual(scene.sources[0].position.join(","), "0.1,-0.2,3.25", "first source z shift");
|
assertEqual(scene.sources[0].position.join(","), "0.1,-0.2,3.25", "first source z shift");
|
||||||
|
assertEqual(
|
||||||
|
scene.sources[0].trajectory[0].position.join(","),
|
||||||
|
"0.1,-0.2,3.25",
|
||||||
|
"first trajectory keyframe z shift",
|
||||||
|
);
|
||||||
|
assertEqual(
|
||||||
|
scene.sources[0].trajectory[1].position.join(","),
|
||||||
|
"0.6,0.3,3.75",
|
||||||
|
"later trajectory keyframe retains relative depth",
|
||||||
|
);
|
||||||
assertEqual(scene.sources[1].position.join(","), "-0.3,0.4,3.75", "relative source depth retained");
|
assertEqual(scene.sources[1].position.join(","), "-0.3,0.4,3.75", "relative source depth retained");
|
||||||
assertEqual(scene.image.plane.z, 3.25, "background image plane z shift");
|
assertEqual(scene.image.plane.z, 3.25, "background image plane z shift");
|
||||||
});
|
});
|
||||||
@@ -302,6 +319,26 @@ test("Camera aligns an experiment to an imported scene without changing grid res
|
|||||||
assertNear(experiment.scan.yMax, 0.45, 1e-12, "source y maximum with padding");
|
assertNear(experiment.scan.yMax, 0.45, 1e-12, "source y maximum with padding");
|
||||||
assertEqual(experiment.scan.columns, 32, "columns retained after source fallback");
|
assertEqual(experiment.scan.columns, 32, "columns retained after source fallback");
|
||||||
assertEqual(experiment.scan.rows, 24, "rows retained after source fallback");
|
assertEqual(experiment.scan.rows, 24, "rows retained after source fallback");
|
||||||
|
|
||||||
|
const movingScene = {
|
||||||
|
sources: [
|
||||||
|
{
|
||||||
|
id: "moving",
|
||||||
|
position: [-2, -3, 1],
|
||||||
|
trajectory: [
|
||||||
|
{ time: 0, position: [-2, -3, 1] },
|
||||||
|
{ time: 1, position: [4, 5, 9] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ id: "fixed", position: [0, 1, 5] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
assertEqual(alignExperimentToScene(experiment, movingScene), "source-bounds", "trajectory fallback");
|
||||||
|
assertEqual(experiment.scan.z, 5, "trajectory depths contribute to median scan depth");
|
||||||
|
assertNear(experiment.scan.xMin, -2.9, 1e-12, "trajectory x minimum with padding");
|
||||||
|
assertNear(experiment.scan.xMax, 4.9, 1e-12, "trajectory x maximum with padding");
|
||||||
|
assertNear(experiment.scan.yMin, -4.2, 1e-12, "trajectory y minimum with padding");
|
||||||
|
assertNear(experiment.scan.yMax, 6.2, 1e-12, "trajectory y maximum with padding");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Camera result document is schema-safe and linked to its snapshot", async () => {
|
test("Camera result document is schema-safe and linked to its snapshot", async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user