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/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 diff --git a/ui/blocklyutil.js b/ui/blocklyutil.js index c221c860..a73ebec0 100644 --- a/ui/blocklyutil.js +++ b/ui/blocklyutil.js @@ -426,32 +426,16 @@ export function findParentWithBlockId(mesh) { return null; } -export function calculateYPosition(mesh, block) { - let finalY = mesh.position.y; +export function calculateYPosition(mesh) { + if (!mesh) return 0; - 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"); + mesh.computeWorldMatrix?.(true); + mesh.refreshBoundingInfo?.(); - if (scaleInput && scaleInput.connection.targetBlock()) { - const scale = scaleInput.connection - .targetBlock() - .getFieldValue("NUM"); - finalY -= scale * mesh.metadata.yOffset; - } - } + const boundingInfo = mesh.getBoundingInfo?.(); + const minY = boundingInfo?.boundingBox?.minimumWorld?.y; - return finalY; + return Number.isFinite(minY) ? minY : 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); } });