Skip to content

Transform Matrix decay/skew when continuously updating Node.quaternion (Decomposition Precision Loss) #2187

@hrincescu

Description

@hrincescu

Platform

Android

Module

sceneview (3D only)

SceneView version

4.12.0

Device / OS

Galaxy s21 ultra - Android 15

What happened?

When updating a Node's quaternion continuously inside an onFrame loop (e.g., for a 120Hz gesture-driven object rotation), the node's visual mesh slowly warps and skews over time, drifting away from its true mathematical center and shape.
The root cause is in Node.kt. The setter for quaternion relies on reading the position and scale properties to compose a new Transform:

open var quaternion: Quaternion
    get() = transform.quaternion
    set(value) {
        transform = Transform(position, value, scale) 
    }

Because position and scale fetch their values by decomposing the underlying Filament 4x4 Matrix (transformManager.getTransform()), floating-point imprecision accumulates. Reading and rewriting the decomposed scale 120 times a second causes it to decay (e.g., from 1.0 to 0.99999...), physically skewing the rendered node over time.

What did you expect?

Updating node.quaternion should strictly update the rotation without causing floating-point decay in scale and position. Node.kt should ideally cache local position, quaternion, and scale as pristine backing fields in memory, and compose the Transform from those fields rather than decomposing the Filament 4x4 matrix every time a single property is updated.

Steps to reproduce

1. Create a Node (e.g., attach a 3D sphere or mesh to it).
2. Inside an onFrame callback, continuously update node.quaternion (e.g., spinning it rapidly or driving it via continuous touch gestures).
3. Observe the 3D model over a short period of continuous rotation; it will visually distort and stretch on its axes (most noticeable when precision is needed).
4. Proof of issue: The distortion can be completely stopped setting the transform directly with clean values:

// This decays and skews the mesh because the setter internally reads the dirty position/scale:
node.quaternion = newQuaternion

// This completely fixes it and keeps the mesh pristine, while also saving JNI calls:
node.transform = Transform(
    position = Position(0f), 
    quaternion = newQuaternion, 
    scale = Scale(1f)
)

Logs / stack trace

This is the specific block in Node.kt causing the precision loss loop:

open var quaternion: Quaternion
        get() = transform.quaternion
        set(value) {
            // Calling `position` and `scale` here triggers matrix decomposition 
            // from the C++ engine, pulling floating-point inaccuracies into the new Transform.
            transform = Transform(position, value, scale)
        }

Screenshots / screen recordings

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions