From ad6be1d8acdacb65fc30f7d14b20e8213e4bb079 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Sun, 22 Feb 2026 09:14:58 +0000 Subject: [PATCH] add shared anchor transform helper scaffold --- api/anchorTransform.js | 108 +++++++++++++++++++++++++++++++++++++++++ flock.js | 6 +++ 2 files changed, 114 insertions(+) create mode 100644 api/anchorTransform.js diff --git a/api/anchorTransform.js b/api/anchorTransform.js new file mode 100644 index 00000000..a476a675 --- /dev/null +++ b/api/anchorTransform.js @@ -0,0 +1,108 @@ +let flock; + +export function setFlockReference(ref) { + flock = ref; +} + +function getDefaultPivotSettings() { + return { x: "CENTER", y: "MIN", z: "CENTER" }; +} + +function normalizePivotSetting(setting, fallback = "CENTER") { + if (typeof setting === "number" && Number.isFinite(setting)) return setting; + if (typeof setting !== "string") return fallback; + + const upper = setting.toUpperCase(); + if (upper === "MIN" || upper === "CENTER" || upper === "MAX") return upper; + return fallback; +} + +function resolveAxisPivotLocal(mesh, axis, setting) { + if (typeof setting === "number" && Number.isFinite(setting)) { + return setting; + } + + const bounds = mesh?.getBoundingInfo?.()?.boundingBox; + if (!bounds) return 0; + + const ext = bounds.extendSize?.[axis]; + const half = Number.isFinite(ext) ? ext : 0; + + if (setting === "MIN") return -half; + if (setting === "MAX") return half; + return 0; +} + +export const flockAnchorTransform = { + getPivotSettings(mesh, fallback = getDefaultPivotSettings()) { + const configured = mesh?.metadata?.pivotSettings || {}; + return { + x: normalizePivotSetting(configured.x, fallback.x), + y: normalizePivotSetting(configured.y, fallback.y), + z: normalizePivotSetting(configured.z, fallback.z), + }; + }, + + getLocalAnchorOffset(mesh, pivotSettings = null) { + if (!mesh) { + return new flock.BABYLON.Vector3(0, 0, 0); + } + + const resolved = pivotSettings || this.getPivotSettings(mesh); + + return new flock.BABYLON.Vector3( + resolveAxisPivotLocal(mesh, "x", normalizePivotSetting(resolved.x, "CENTER")), + resolveAxisPivotLocal(mesh, "y", normalizePivotSetting(resolved.y, "MIN")), + resolveAxisPivotLocal(mesh, "z", normalizePivotSetting(resolved.z, "CENTER")), + ); + }, + + resolveWorldAnchor(mesh, pivotSettings = null) { + if (!mesh) return null; + + if (!mesh.getBoundingInfo || !mesh.getBoundingInfo()) { + const p = mesh.position || flock.BABYLON.Vector3.Zero(); + return { x: p.x, y: p.y, z: p.z }; + } + + mesh.computeWorldMatrix?.(true); + + const localAnchor = this.getLocalAnchorOffset(mesh, pivotSettings); + const worldAnchor = flock.BABYLON.Vector3.TransformCoordinates( + localAnchor, + mesh.getWorldMatrix(), + ); + + return { x: worldAnchor.x, y: worldAnchor.y, z: worldAnchor.z }; + }, + + setMeshByWorldAnchor( + mesh, + target, + { useX = true, useY = true, useZ = true, pivotSettings = null } = {}, + ) { + if (!mesh) return; + + const current = this.resolveWorldAnchor(mesh, pivotSettings); + if (!current) return; + + const tx = useX ? target?.x : current.x; + const ty = useY ? target?.y : current.y; + const tz = useZ ? target?.z : current.z; + + const dx = Number.isFinite(tx) ? tx - current.x : 0; + const dy = Number.isFinite(ty) ? ty - current.y : 0; + const dz = Number.isFinite(tz) ? tz - current.z : 0; + + if (dx !== 0 || dy !== 0 || dz !== 0) { + mesh.position.addInPlace(new flock.BABYLON.Vector3(dx, dy, dz)); + mesh.computeWorldMatrix?.(true); + } + + return { + x: current.x + dx, + y: current.y + dy, + z: current.z + dz, + }; + }, +}; diff --git a/flock.js b/flock.js index 734576e7..0018cbb0 100644 --- a/flock.js +++ b/flock.js @@ -74,6 +74,10 @@ import { flockMesh, setFlockReference as setFlockMesh } from "./api/mesh"; import { flockCamera, setFlockReference as setFlockCamera } from "./api/camera"; import { flockEvents, setFlockReference as setFlockEvents } from "./api/events"; import { flockMath, setFlockReference as setFlockMath } from "./api/math"; +import { + flockAnchorTransform, + setFlockReference as setFlockAnchorTransform, +} from "./api/anchorTransform"; import { flockSensing, setFlockReference as setFlockSensing, @@ -162,6 +166,7 @@ export const flock = { ...flockEvents, ...flockSensing, ...flockMath, + ...flockAnchorTransform, // Enhanced error reporting with block context createEnhancedError(error, code) { const lines = code.split("\n"); @@ -2000,6 +2005,7 @@ export const flock = { setFlockControl(flock); setFlockEvents(flock); setFlockSensing(flock); + setFlockAnchorTransform(flock); // Add highlight layer flock.highlighter = new flock.BABYLON.HighlightLayer(