Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Feature/mesh parts control dev#2

Merged
NBForgeLab merged 11 commits intomasterfrom
feature/mesh-parts-control-dev
Feb 9, 2026
Merged

Feature/mesh parts control dev#2
NBForgeLab merged 11 commits intomasterfrom
feature/mesh-parts-control-dev

Conversation

@NBForgeLab
Copy link
Owner

@NBForgeLab NBForgeLab commented Feb 9, 2026

Summary by CodeRabbit

Release Notes

  • Improvements
    • Enhanced 3D model transformation accuracy for positioning, rotation, and scaling
    • Improved handling of mesh hierarchies with better support for complex nested object structures
    • More precise coordinate calculations for 3D object manipulation

Open with Devin

@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

📝 Walkthrough

Walkthrough

This PR introduces normalization scale tracking to the 3D rendering system. The Model3DRuntimeObject3DRenderer computes a normalization scale after stretching models into a unitary cube and stores it on MeshParts. Model3DRuntimeObjectMeshParts now tracks original per-mesh transforms and applies normalization conversions when setting or getting mesh positions, rotations, and scales.

Changes

Cohort / File(s) Summary
Normalization Scale Setup
Extensions/3D/Model3DRuntimeObject3DRenderer.ts
Stores computed normalization scale (scaleX, scaleY, scaleZ) on mesh parts after applying the scale matrix to normalize models to unit cube dimensions.
Normalization-aware Mesh Parts
Extensions/3D/Model3DRuntimeObjectMeshParts.ts
Implements per-mesh original transform tracking (position, rotation, scale); adds normalization scale storage and coordinate conversion logic; updates position/rotation/scale getters and setters to account for normalization; enhances mesh removal to clean up descendant meshes and associated transform data; introduces new setNormalizationScale() public method.

Sequence Diagram

sequenceDiagram
    participant Renderer as 3DRenderer
    participant MeshParts as MeshParts
    participant Mesh as Mesh/Group

    Renderer->>Renderer: Compute scale for unit cube
    Renderer->>MeshParts: setNormalizationScale(scaleX, scaleY, scaleZ)
    MeshParts->>MeshParts: Store normalization scale
    
    Note over Renderer,Mesh: Later mesh operations
    Renderer->>MeshParts: setMeshPosition(x, y, z)
    MeshParts->>MeshParts: Convert to normalized-space<br/>using normalization scale
    MeshParts->>MeshParts: Add to original position
    MeshParts->>Mesh: Update position
    
    Renderer->>MeshParts: getMeshPositionX/Y/Z()
    MeshParts->>MeshParts: Compute offset from original<br/>Convert back to object-space<br/>Apply normalization scale
    MeshParts->>Renderer: Return normalized position
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A cube of wonder, normalized and bright,
Transforms tracked from left to right,
Scales and rotations in harmony dance,
With rabbits ensuring each mesh gets its chance! 🎪

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feature/mesh parts control dev' is vague and uses generic naming conventions without clearly describing what the feature adds or changes to mesh parts control. Revise the title to be more specific and descriptive, such as 'Add normalization scale tracking for 3D mesh parts' or 'Implement per-mesh transform tracking and normalization in 3D rendering.'
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Docstrings were successfully generated.
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/mesh-parts-control-dev

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@NBForgeLab NBForgeLab marked this pull request as ready for review February 9, 2026 17:06
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

Caution

Docstrings generation - FAILED

No docstrings were generated.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@Extensions/3D/Model3DRuntimeObject3DRenderer.ts`:
- Around line 208-210: The function stretchModelIntoUnitaryCube currently
mutates external state by calling
this._model3DRuntimeObject._meshParts.setNormalizationScale, violating its
JSDoc; change stretchModelIntoUnitaryCube to return the computed
scaleX/scaleY/scaleZ (e.g., along with the bounding box) and remove the
setNormalizationScale call from it, then update the caller
(_updateDefaultTransformation or _updateModel) to receive those scale values and
perform this._model3DRuntimeObject._meshParts.setNormalizationScale(scaleX,
scaleY, scaleZ) there; alternatively, if you prefer not to change callers,
update the JSDoc for stretchModelIntoUnitaryCube to explicitly state it mutates
_model3DRuntimeObject._meshParts via setNormalizationScale so callers know about
the side effect.
🧹 Nitpick comments (3)
Extensions/3D/Model3DRuntimeObjectMeshParts.ts (3)

11-14: _originalMeshRotations is stored but never used in any computation.

The field is populated in buildMeshesMap (line 62), cleaned up in removeMesh/clear, but neither setMeshRotation nor getMeshRotationX/Y/Z reference it. If it's reserved for a future relative-rotation API, consider adding a comment to that effect. Otherwise, remove it to avoid confusion.

Also applies to: 20-23


263-279: Rotation uses absolute values while position and scale are relative to originals — document the asymmetry.

setMeshPosition and setMeshScale operate relative to the original transform, but setMeshRotation sets absolute rotation, discarding the mesh's original rotation from the model. This means setMeshRotation(name, 0, 0, 0) resets any authored rotation. The JSDoc notes this, but it's a potential foot-gun for users who expect uniform behavior.

If relative rotation is eventually desired, the stored (but currently unused) _originalMeshRotations could be leveraged — composing relative Euler angles is non-trivial though, so the absolute approach is understandable for now.


325-346: setMeshScale with zero original scale silently returns — consider a warning.

If originalScale is missing (line 336), the method exits silently. This is a defensive check, but an original scale of (0, 0, 0) on a component won't be caught — the multiplication will produce zero regardless of user input. Unlike the getters, the setter has no zero-check on individual components. This is likely fine in practice (a zero-scale mesh is degenerate) but worth being aware of.

Comment on lines +208 to +210
// Store the normalization scale for mesh parts positioning
// Note: Y scale is negated in the matrix but we store the absolute value
this._model3DRuntimeObject._meshParts.setNormalizationScale(scaleX, scaleY, scaleZ);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

stretchModelIntoUnitaryCube now mutates state outside threeObject, violating its JSDoc contract.

Line 146 documents: "This function doesn't mutate anything outside of threeObject." The new setNormalizationScale call on line 210 mutates _meshParts, which is outside threeObject. Update the JSDoc to reflect this side effect, or move the setNormalizationScale call to the caller (_updateDefaultTransformation or _updateModel) to preserve the contract.

Option A: Move the call to the caller

In stretchModelIntoUnitaryCube, return the scale values along with the bounding box, then call setNormalizationScale in _updateDefaultTransformation:

     // in stretchModelIntoUnitaryCube, remove:
-    // Store the normalization scale for mesh parts positioning
-    // Note: Y scale is negated in the matrix but we store the absolute value
-    this._model3DRuntimeObject._meshParts.setNormalizationScale(scaleX, scaleY, scaleZ);
     // in _updateDefaultTransformation, after stretchModelIntoUnitaryCube returns:
+    const scaleX = modelWidth < epsilon ? 1 : 1 / modelWidth;
+    const scaleY = modelHeight < epsilon ? 1 : 1 / modelHeight;
+    const scaleZ = modelDepth < epsilon ? 1 : 1 / modelDepth;
+    this._model3DRuntimeObject._meshParts.setNormalizationScale(scaleX, scaleY, scaleZ);
Option B: Update the JSDoc
-   * This function doesn't mutate anything outside of `threeObject`.
+   * This function doesn't mutate anything outside of `threeObject`,
+   * except for storing the normalization scale on the runtime object's mesh parts.
🤖 Prompt for AI Agents
In `@Extensions/3D/Model3DRuntimeObject3DRenderer.ts` around lines 208 - 210, The
function stretchModelIntoUnitaryCube currently mutates external state by calling
this._model3DRuntimeObject._meshParts.setNormalizationScale, violating its
JSDoc; change stretchModelIntoUnitaryCube to return the computed
scaleX/scaleY/scaleZ (e.g., along with the bounding box) and remove the
setNormalizationScale call from it, then update the caller
(_updateDefaultTransformation or _updateModel) to receive those scale values and
perform this._model3DRuntimeObject._meshParts.setNormalizationScale(scaleX,
scaleY, scaleZ) there; alternatively, if you prefer not to change callers,
update the JSDoc for stretchModelIntoUnitaryCube to explicitly state it mutates
_model3DRuntimeObject._meshParts via setNormalizationScale so callers know about
the side effect.

@NBForgeLab NBForgeLab merged commit 89ee1e1 into master Feb 9, 2026
1 check passed
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

Open in Devin Review

Comment on lines +161 to +165

const normalizedX = objectWidth !== 0 ? (x * this._normalizationScale.x) / objectWidth : 0;
// Y axis is flipped in the renderer, so we need to negate it
const normalizedY = objectHeight !== 0 ? (-y * this._normalizationScale.y) / objectHeight : 0;
const normalizedZ = objectDepth !== 0 ? (z * this._normalizationScale.z) / objectDepth : 0;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Mesh position conversion formula multiplies by normalization scale instead of dividing

The setMeshPosition and getMeshPosition* methods use an inverted normalization scale factor, causing mesh positions to be visually incorrect by a factor of normalizationScale^2.

Root Cause and Detailed Explanation

The transform chain from mesh local space to world space is:
worldOffset = dp * normalizationScale * objectDimension

where normalizationScale = 1/modelDimension (set at Model3DRuntimeObject3DRenderer.ts:197-199).

To convert a user-specified object-space offset x to mesh local space:
dp.x = x / (normalizationScale.x * objectWidth)

But the code at line 162 computes:

normalizedX = (x * this._normalizationScale.x) / objectWidth

which equals x * normalizationScale.x / objectWidth — multiplying by normalizationScale instead of dividing.

Concrete example: For a model 10 units wide (normalizationScale.x = 0.1), displayed at 200px (objectWidth = 200), setting x = 40 should move the mesh by 40 world units. The correct local offset is dp.x = 40 / (0.1 * 200) = 2. But the code computes dp.x = (40 * 0.1) / 200 = 0.02, resulting in a world displacement of only 0.02 * 0.1 * 200 = 0.4 instead of 40.

The getter has the symmetric inverse error (line 196: divides by normalizationScale.x instead of multiplying), so get/set roundtrips are consistent, but the actual visual displacement is wrong by a factor of modelWidth^2.

The same bug affects Y (line 164) and Z (line 165) axes, and their corresponding getters (lines 223, 249).

Impact: All mesh position manipulations via setMeshPosition will produce drastically incorrect visual results. The magnitude of the error depends on the model's original dimensions.

Suggested change
const normalizedX = objectWidth !== 0 ? (x * this._normalizationScale.x) / objectWidth : 0;
// Y axis is flipped in the renderer, so we need to negate it
const normalizedY = objectHeight !== 0 ? (-y * this._normalizationScale.y) / objectHeight : 0;
const normalizedZ = objectDepth !== 0 ? (z * this._normalizationScale.z) / objectDepth : 0;
const normalizedX = objectWidth !== 0 && this._normalizationScale.x !== 0 ? x / (this._normalizationScale.x * objectWidth) : 0;
// Y axis is flipped in the renderer, so we need to negate it
const normalizedY = objectHeight !== 0 && this._normalizationScale.y !== 0 ? -y / (this._normalizationScale.y * objectHeight) : 0;
const normalizedZ = objectDepth !== 0 && this._normalizationScale.z !== 0 ? z / (this._normalizationScale.z * objectDepth) : 0;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +195 to +198
const normalizedOffset = currentPos.x - originalPos.x;
return this._normalizationScale.x !== 0
? (normalizedOffset * objectWidth) / this._normalizationScale.x
: 0;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 getMeshPosition getters use inverted normalization scale (symmetric to setter bug)*

The getter methods for mesh position X, Y, and Z divide by normalizationScale instead of multiplying, producing incorrect object-space values.

Root Cause

The correct conversion from mesh local offset back to object space is:
x = normalizedOffset * normalizationScale.x * objectWidth

But getMeshPositionX at line 195-197 computes:

return (normalizedOffset * objectWidth) / this._normalizationScale.x

This divides by normalizationScale.x instead of multiplying. The same error exists in getMeshPositionY (line 222-224, also with Y-flip) and getMeshPositionZ (line 248-250).

While the getters are consistent with the buggy setters (roundtrip works), both are wrong relative to the actual visual position of the mesh.

Impact: Reading mesh positions returns values that don't correspond to actual world-space positions.

Prompt for agents
Fix the three getMeshPosition* methods to multiply by normalizationScale instead of dividing:

In getMeshPositionX (around line 195-198), change:
  return this._normalizationScale.x !== 0 ? (normalizedOffset * objectWidth) / this._normalizationScale.x : 0;
to:
  return normalizedOffset * this._normalizationScale.x * objectWidth;

In getMeshPositionY (around line 222-224), change:
  return this._normalizationScale.y !== 0 ? (-normalizedOffset * objectHeight) / this._normalizationScale.y : 0;
to:
  return -normalizedOffset * this._normalizationScale.y * objectHeight;

In getMeshPositionZ (around line 248-250), change:
  return this._normalizationScale.z !== 0 ? (normalizedOffset * objectDepth) / this._normalizationScale.z : 0;
to:
  return normalizedOffset * this._normalizationScale.z * objectDepth;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +208 to +210
// Store the normalization scale for mesh parts positioning
// Note: Y scale is negated in the matrix but we store the absolute value
this._model3DRuntimeObject._meshParts.setNormalizationScale(scaleX, scaleY, scaleZ);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 stretchModelIntoUnitaryCube side effect corrupts mesh normalization scale when called from Physics3D

stretchModelIntoUnitaryCube now mutates _meshParts._normalizationScale, violating its documented contract and causing incorrect mesh positioning when called from Physics3D with a different mesh shape resource.

Root Cause

The new line 210 in Model3DRuntimeObject3DRenderer.ts adds a side effect to stretchModelIntoUnitaryCube:

this._model3DRuntimeObject._meshParts.setNormalizationScale(scaleX, scaleY, scaleZ);

The function's doc comment at line 146 states: "This function doesn't mutate anything outside of threeObject." This contract is now violated.

The function is also called from Physics3DRuntimeBehavior.ts:913:

model3DRuntimeObject._renderer.stretchModelIntoUnitaryCube(modelInCube, ...);

The Physics3D behavior may use a different model resource (meshShapeResourceName at Physics3DRuntimeBehavior.ts:902), which would have different bounding box dimensions. When this happens, stretchModelIntoUnitaryCube computes normalization scales based on the physics mesh (not the visual model) and overwrites _meshParts._normalizationScale with incorrect values.

Impact: After getMeshShapeSettings is called with a custom physics mesh resource, all subsequent setMeshPosition/getMeshPosition* calls will use wrong scale factors, producing incorrect mesh positions.

Prompt for agents
Move the setNormalizationScale call out of stretchModelIntoUnitaryCube and into _updateDefaultTransformation or _updateModel instead, so that it is only called when the visual model is being set up, not when Physics3D calls stretchModelIntoUnitaryCube for physics shape computation.

For example, in _updateDefaultTransformation after the call to stretchModelIntoUnitaryCube, compute the normalization scale from the bounding box and call setNormalizationScale there. Or pass the normalization scale as a return value from stretchModelIntoUnitaryCube and let the caller decide what to do with it.

Also update the doc comment on stretchModelIntoUnitaryCube to accurately reflect any remaining side effects.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant