Stabilize multi-marker fusion (deterministic order + coarse-bucket weights) + expose inter-marker spread
This commit is contained in:
+27
-4
@@ -70,11 +70,26 @@ export class WorldFuser {
|
|||||||
const pos = new THREE.Vector3();
|
const pos = new THREE.Vector3();
|
||||||
const quat = new THREE.Quaternion();
|
const quat = new THREE.Quaternion();
|
||||||
camWorld.decompose(pos, quat, _s);
|
camWorld.decompose(pos, quat, _s);
|
||||||
cams.push({ pos, quat, w: Math.max(1, e.area) });
|
cams.push({ markerId: e.markerId, pos, quat, w: Math.max(1, e.area) });
|
||||||
}
|
}
|
||||||
if (!cams.length) return null;
|
if (!cams.length) return null;
|
||||||
|
|
||||||
// weighted position mean + incremental weighted slerp for orientation
|
/* Multi-marker fusion. Two visible markers each give an independent camera-in-world
|
||||||
|
* estimate; if they disagree (pose noise, or real-table spacing not matching the
|
||||||
|
* editor), naive area-weighting oscillates frame to frame because the areas jitter
|
||||||
|
* — this is the "steady on one marker, jittery on two" symptom. Fixes:
|
||||||
|
* 1. Deterministic order (sort by markerId) so the slerp base can't flip.
|
||||||
|
* 2. Robust weights: quantise area into coarse buckets so tiny per-frame area
|
||||||
|
* wobble doesn't shift the blend; a marker only dominates when genuinely much
|
||||||
|
* closer. Equal-ish markers then average to a STABLE midpoint, not a moving one. */
|
||||||
|
cams.sort((a, b) => a.markerId - b.markerId);
|
||||||
|
// Quantise confidence into coarse buckets from marker size (sqrt(area) ~ linear
|
||||||
|
// size). Near-equal markers land in the same bucket => equal weight => stable
|
||||||
|
// midpoint. A marker only outweighs another when it's a full bucket closer, so
|
||||||
|
// per-frame area jitter no longer shifts the blend.
|
||||||
|
for (const c of cams) c.w = Math.max(1, Math.round(Math.sqrt(c.w) / 8));
|
||||||
|
|
||||||
|
// weighted position mean + incremental weighted slerp (now order-stable)
|
||||||
let wSum = cams[0].w;
|
let wSum = cams[0].w;
|
||||||
const pos = cams[0].pos.clone().multiplyScalar(cams[0].w);
|
const pos = cams[0].pos.clone().multiplyScalar(cams[0].w);
|
||||||
const quat = cams[0].quat.clone();
|
const quat = cams[0].quat.clone();
|
||||||
@@ -89,6 +104,14 @@ export class WorldFuser {
|
|||||||
}
|
}
|
||||||
pos.multiplyScalar(1 / wSum);
|
pos.multiplyScalar(1 / wSum);
|
||||||
|
|
||||||
|
// spread = how far apart the individual marker estimates are (0 for one marker).
|
||||||
|
// Large spread (>~2cm) means the markers disagree — usually real-table spacing
|
||||||
|
// not matching the editor, which no fusion can fully hide; surfaced in dev HUD.
|
||||||
|
let spread = 0;
|
||||||
|
for (let i = 0; i < cams.length; i++)
|
||||||
|
for (let j = i + 1; j < cams.length; j++)
|
||||||
|
spread = Math.max(spread, cams[i].pos.distanceTo(cams[j].pos));
|
||||||
|
|
||||||
// ---- 1€ filter (position + orientation) ----
|
// ---- 1€ filter (position + orientation) ----
|
||||||
const now = performance.now();
|
const now = performance.now();
|
||||||
const gap = now - this.lastFuseT;
|
const gap = now - this.lastFuseT;
|
||||||
@@ -100,7 +123,7 @@ export class WorldFuser {
|
|||||||
if (!this.smoothPos || gap > 1500) {
|
if (!this.smoothPos || gap > 1500) {
|
||||||
this.smoothPos = pos.clone(); oe.prevPos = pos.clone(); oe.dPos.set(0, 0, 0);
|
this.smoothPos = pos.clone(); oe.prevPos = pos.clone(); oe.dPos.set(0, 0, 0);
|
||||||
this.smoothQuat = quat.clone(); oe.prevQuat = quat.clone(); oe.dAngRate = 0;
|
this.smoothQuat = quat.clone(); oe.prevQuat = quat.clone(); oe.dAngRate = 0;
|
||||||
return { position: this.smoothPos.clone(), quaternion: this.smoothQuat.clone(), markerCount: cams.length };
|
return { position: this.smoothPos.clone(), quaternion: this.smoothQuat.clone(), markerCount: cams.length, spread };
|
||||||
}
|
}
|
||||||
|
|
||||||
// POSITION: derivative -> speed -> dynamic cutoff -> low-pass
|
// POSITION: derivative -> speed -> dynamic cutoff -> low-pass
|
||||||
@@ -123,6 +146,6 @@ export class WorldFuser {
|
|||||||
this.smoothQuat.slerp(quat, aA);
|
this.smoothQuat.slerp(quat, aA);
|
||||||
oe.prevQuat.copy(quat);
|
oe.prevQuat.copy(quat);
|
||||||
|
|
||||||
return { position: this.smoothPos.clone(), quaternion: this.smoothQuat.clone(), markerCount: cams.length };
|
return { position: this.smoothPos.clone(), quaternion: this.smoothQuat.clone(), markerCount: cams.length, spread };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user