From 613de413c518c40f4fbd69bbae2e976ff5c08f52 Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Sun, 22 Feb 2026 08:37:10 +0000 Subject: [PATCH 1/3] Align mesh Y placement and remove legacy yOffset compensation --- api/mesh.js | 21 +++++++++++++++++---- ui/blocklyutil.js | 28 ++-------------------------- ui/gizmos.js | 4 +--- 3 files changed, 20 insertions(+), 33 deletions(-) diff --git a/api/mesh.js b/api/mesh.js index d6358f95..f43b23b4 100644 --- a/api/mesh.js +++ b/api/mesh.js @@ -723,6 +723,22 @@ export const flockMesh = { bb.position = new flock.BABYLON.Vector3(x, resolvedY, z); + const alignMeshBaseToY = (targetY) => { + bb.computeWorldMatrix(true); + bb.refreshBoundingInfo(); + + const minWorldY = bb.getBoundingInfo().boundingBox.minimumWorld.y; + const deltaY = targetY - minWorldY; + + if (Math.abs(deltaY) > 1e-6) { + bb.position.y += deltaY; + bb.computeWorldMatrix(true); + bb.refreshBoundingInfo(); + } + }; + + alignMeshBaseToY(resolvedY); + mesh.computeWorldMatrix(true); mesh.refreshBoundingInfo(); mesh.isPickable = true; @@ -731,7 +747,6 @@ export const flockMesh = { }); bb.metadata = bb.metadata || {}; - bb.metadata.yOffset = (bb.position.y - resolvedY) / scale; bb.metadata.modelName = modelName; flock.stopAnimationsTargetingMesh(flock.scene, mesh); @@ -751,7 +766,7 @@ export const flockMesh = { flock.waitForGroundReady().then(() => { const groundY = flock.getGroundLevelAt(x, z); bb.position.y = groundY; - bb.metadata.yOffset = (bb.position.y - groundY) / scale; + alignMeshBaseToY(groundY); if (bb.physics) { bb.physics.setTargetTransform(bb.position, bb.rotationQuaternion); } @@ -765,8 +780,6 @@ export const flockMesh = { setMetadata(descendant); }); - bb.position.y += bb.getBoundingInfo().boundingBox.extendSizeWorld.y; - const boxBody = new flock.BABYLON.PhysicsBody( bb, flock.BABYLON.PhysicsMotionType.STATIC, diff --git a/ui/blocklyutil.js b/ui/blocklyutil.js index c221c860..65dcb9c5 100644 --- a/ui/blocklyutil.js +++ b/ui/blocklyutil.js @@ -426,32 +426,8 @@ export function findParentWithBlockId(mesh) { return null; } -export function calculateYPosition(mesh, block) { - let finalY = mesh.position.y; - - if ( - mesh.metadata && - mesh.metadata.yOffset && - mesh.metadata.yOffset !== 0 && - block - ) { - console.log( - "Updating y position for mesh:", - mesh.name, - "with block:", - block.type, - ); - const scaleInput = block.getInput("SCALE"); - - if (scaleInput && scaleInput.connection.targetBlock()) { - const scale = scaleInput.connection - .targetBlock() - .getFieldValue("NUM"); - finalY -= scale * mesh.metadata.yOffset; - } - } - - return finalY; +export function calculateYPosition(mesh, _block) { + return mesh.position.y; } export function setNumberInputs(block, valuesByInputName) { diff --git a/ui/gizmos.js b/ui/gizmos.js index c5facf38..07b1efa6 100644 --- a/ui/gizmos.js +++ b/ui/gizmos.js @@ -779,9 +779,7 @@ export function toggleGizmo(gizmoType) { const block = meshMap[mesh.metadata.blockKey]; if (block) { - let meshY = calculateYPosition(mesh, block); - meshY -= - mesh.getBoundingInfo().boundingBox.extendSize.y * mesh.scaling.y; + const meshY = calculateYPosition(mesh, block); setBlockXYZ(block, mesh.position.x, meshY, mesh.position.z); } }); From 33891ef218984af50913e7e46248eb9694caf73d Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Sun, 22 Feb 2026 08:55:00 +0000 Subject: [PATCH 2/3] Fix gizmo Y sync to use mesh base height --- ui/blocklyutil.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ui/blocklyutil.js b/ui/blocklyutil.js index 65dcb9c5..a73ebec0 100644 --- a/ui/blocklyutil.js +++ b/ui/blocklyutil.js @@ -426,8 +426,16 @@ export function findParentWithBlockId(mesh) { return null; } -export function calculateYPosition(mesh, _block) { - return mesh.position.y; +export function calculateYPosition(mesh) { + if (!mesh) return 0; + + mesh.computeWorldMatrix?.(true); + mesh.refreshBoundingInfo?.(); + + const boundingInfo = mesh.getBoundingInfo?.(); + const minY = boundingInfo?.boundingBox?.minimumWorld?.y; + + return Number.isFinite(minY) ? minY : mesh.position.y; } export function setNumberInputs(block, valuesByInputName) { From 7ba8a744b236dc63712d13c21414cf28144c02ce Mon Sep 17 00:00:00 2001 From: Dr Tracy Gardner Date: Sun, 22 Feb 2026 08:55:05 +0000 Subject: [PATCH 3/3] Make positionAt use base-height Y semantics for all meshes --- api/transform.js | 64 +++++++++++++++++------------------------------- 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/api/transform.js b/api/transform.js index 2c65879b..d21a06fc 100644 --- a/api/transform.js +++ b/api/transform.js @@ -35,50 +35,30 @@ export const flockTransform = { } } - // Check if we have pivot settings in metadata - if (mesh.metadata && mesh.metadata.pivotSettings) { - const pivotSettings = mesh.metadata.pivotSettings; - const boundingBox = mesh.getBoundingInfo().boundingBox.extendSize; - - // Helper to resolve pivot values - function resolvePivotValue(value, axis) { - if (typeof value === "string") { - switch (value) { - case "MIN": - return -boundingBox[axis]; - case "MAX": - return boundingBox[axis]; - case "CENTER": - default: - return 0; - } - } else if (typeof value === "number") { - return value; - } else { - return 0; + // Use a consistent placement rule: requested Y is mesh base (minimumWorld.y) + // so imported models and primitives share the same semantics. + mesh.position.set( + x, + useY ? y : mesh.position.y, + z, + ); + + if ( + useY && + meshName !== "__active_camera__" && + typeof mesh.getBoundingInfo === "function" + ) { + mesh.computeWorldMatrix(true); + mesh.refreshBoundingInfo?.(); + const boundingInfo = mesh.getBoundingInfo(); + const minWorldY = boundingInfo?.boundingBox?.minimumWorld?.y; + + if (Number.isFinite(minWorldY)) { + const deltaY = y - minWorldY; + if (Math.abs(deltaY) > 1e-6) { + mesh.position.y += deltaY; } } - - // Calculate offset based on pivot settings - const pivotOffsetX = resolvePivotValue(pivotSettings.x, "x"); - const pivotOffsetY = resolvePivotValue(pivotSettings.y, "y"); - const pivotOffsetZ = resolvePivotValue(pivotSettings.z, "z"); - - // Apply position with pivot offset - mesh.position.set( - x - pivotOffsetX, - useY ? y - pivotOffsetY : mesh.position.y, - z - pivotOffsetZ, - ); - } else { - // Original behavior if no pivot settings - const addY = - meshName === "__active_camera__" - ? 0 - : mesh.getBoundingInfo().boundingBox.extendSize.y * - mesh.scaling.y; - let targetY = useY ? y + addY : mesh.position.y; - mesh.position.set(x, targetY, z); } // Update physics and world matrix