Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LWO Loader incorrect placement of geometry and mesh when used with pivot point placement #26733

Closed
WORMSS opened this issue Sep 11, 2023 · 14 comments
Labels

Comments

@WORMSS
Copy link
Contributor

WORMSS commented Sep 11, 2023

Description

Hello, I've made a few objects in Lightwave and I use the LWOLoader to load them.

Layer 1, I modeled an object around 0,0,0 and I apply rotation on that mesh, and everything works as expected.

On Layer 2, I modeled another object but I positioned it where it should be relative to Layer 1. But, being a little clever boy, I set the pivot point (Lets just say 2,1,0). In Lightwave when I tell to rotate from the pivot point, it moves as expected.
But when I use THREE to rotate the mesh, it rotates from 0,0,0 and not 2,1,0.

Anyone know how to make LWO respect the layer's Pivot Point?

Reproduction steps

Code

// code goes here

Live example

Screenshots

No response

Version

r156

Device

No response

Browser

No response

OS

No response

@WORMSS
Copy link
Contributor Author

WORMSS commented Sep 11, 2023

I've been digging deeper...
I had to learn how the LWO format works, (sadly all the specs on it point to a location on their website that no longer exists)..
Been pulling apart the LWO reader, and came across this little nugget..

pivot: this.reader.getFloat32Array( 3 ), // Note: this seems to be superflous, as the geometry is translated when pivot is present

So, I can see it actually records the pivot points.
but then you inverse translate the geometry instead of translate the mesh.

// TODO: z may need to be reversed to account for coordinate system change
geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] );

So, now the mesh is in the incorrect place but the geometry has been shifted over to make it "look" like it's in the correct place.
But it doesn't rotate from the correct place.
I don't believe there is a mechanism to move the pivot point of a THREE.Mesh to not rotate from 0,0,0 ? Correct?

@mrdoob
Copy link
Owner

mrdoob commented Sep 12, 2023

Is that the best issue title you can come up with? 🤓

@WORMSS
Copy link
Contributor Author

WORMSS commented Sep 12, 2023

I suspected I typed it in as a place holder so I could gather all the information and then make a better one..
But I actually went away and came back after several hours and I guess I never went back to fix the title.

@WORMSS WORMSS changed the title LWO Loader LWO Loader incorrect placement of geometry and mesh when used with pivot point placement Sep 12, 2023
@Mugen87
Copy link
Collaborator

Mugen87 commented Sep 12, 2023

I don't believe there is a mechanism to move the pivot point of a THREE.Mesh to not rotate from 0,0,0 ? Correct?

Correct. Pivot points like you know them from other engines/projects are not supported, see #15965.

@Mugen87
Copy link
Collaborator

Mugen87 commented Sep 12, 2023

As a workaround, instead of transforming the geometry couldn't you add a "dummy" instance of THREE.Object3D or THREE.Group and add the mesh to it? You can then offset the mesh and apply the rotation to the dummy.

@WORMSS
Copy link
Contributor Author

WORMSS commented Sep 12, 2023

Yeah, then we would just need to move the mesh so it's 0,0,0 is where the pivot point should be on its parent (or scene)
I have done a kind of proof of concept test with my existing model I am trying to develop but I know I am off on 1 of the axis somewhere.. I am guessing it's the z as apparently something in inverted?? Not tested fully, it was quite late when I was doing it and I am doing it with a model that is oddly shaped..

image
image

This is getting it "roughly" into the correct position for rotation, but not quiet yet..

I will set up a proper test environment with some simple boxes in all the orientations so I can be sure it fixes all direction scenarios

@WORMSS
Copy link
Contributor Author

WORMSS commented Sep 12, 2023

As a workaround, instead of transforming the geometry couldn't you add a "dummy" instance of THREE.Object3D or THREE.Group and add the mesh to it? You can then offset the mesh and apply the rotation to the dummy.

It's the loader that sets up the hierarchy structure during it's parsing phase..

image

BUT, I have just seen this on github... I don't remember coming across this when stepping through the code.. but maybe I was just tired..

image

I will try running this code 'raw' from github and see what happens when I do testing.. maybe it's already fixed but the CDN's don't have the fix...

@WORMSS
Copy link
Contributor Author

WORMSS commented Sep 12, 2023

image

Okay, just as another curiosity...

Green Arrow = Where Z = 0 on the actual Model
Orange Arrow = Where the pivot Point is.
Blue Arrow = Where Three is rotating the model from..

Red wireframe bar

@WORMSS
Copy link
Contributor Author

WORMSS commented Sep 12, 2023

const boom_z = objects.boom.userData.pivot[2]; // 2 = z;;
objects.boom.geometry.translate(0, 0, boom_z * 2);
objects.boom.translateZ(-boom_z * 2);

Finally got everything to align..
It seems when I try to correct ALL the axis it would mess everything up..
but because I had something visual I could actually see now I decided to try and maneuver 1 axis at a time.. and noticed actually it's ONLY z that is out.. but it's out by twice as much...

@WORMSS
Copy link
Contributor Author

WORMSS commented Sep 13, 2023

Okay, this is how I eventually fixed the pivot points as things got more nested..
This seems to have fixed everything

const { meshes } = await new LWOLoader().loadAsync(urlToLWOFile);
for ( const mesh of meshes ) {
  fixPivots(mesh);
}

function fixPivots(mesh: THREE.Mesh) {
  const [, , z] = mesh.userData.pivot;
  const parentsZ = (mesh.parent?.userData.pivot[2] /* 2 = z */ ?? 0) * 2; // THIS was the killer blow.. 
  mesh.geometry.translate(0, 0, z * 2);
  mesh.translateZ(-z * 2 + parentsZ);
  for (const child of mesh.children) {
    fixPivots(child); // Fix Fix Fix ALL the way down.
  }
}

@Mugen87
Copy link
Collaborator

Mugen87 commented Mar 2, 2024

I have fixed a similar issue in another loader by creating a custom 3D object class and implement pivot points there. In this way, you don't have to translate geometries but update the local/world matrices which is probably more what LWO expects. Maybe you can try to enhance LOWLoader like so:

class LWOMesh extends Mesh {

	constructor() {

		super();

		this.pivot = new Vector3();

	}

	updateMatrix() {

		this.matrix.compose( this.position, this.quaternion, this.scale );

		const px = this.pivot.x;
		const py = this.pivot.y;
		const pz = this.pivot.z;

		const te = this.matrix.elements;

		te[ 12 ] += px - te[ 0 ] * px - te[ 4 ] * py - te[ 8 ] * pz;
		te[ 13 ] += py - te[ 1 ] * px - te[ 5 ] * py - te[ 9 ] * pz;
		te[ 14 ] += pz - te[ 2 ] * px - te[ 6 ] * py - te[ 10 ] * pz;

		this.matrixWorldNeedsUpdate = true;

	}

}

Instead of creating instances of Mesh, you use LWOMesh and apply the pivot point from the LWO data. Looking at the code, it seems Points and LineSegments require a similar custom class.

Would be great if you could give this a shot and report back how is it going.

@WORMSS
Copy link
Contributor Author

WORMSS commented Mar 2, 2024

Not gonna lie, I totally forgot about coming back here.

I've been using my hack for so long.. I never got around to making a lwo object to test all this..

It was this and another problem I found with using the wrong property for colour with different paint types.
Again, never got around to making a simple test for it..

@Mugen87
Copy link
Collaborator

Mugen87 commented Mar 4, 2024

Without a test asset it's impossible to properly fix and test the issue. Closing for now.

@Mugen87 Mugen87 closed this as not planned Won't fix, can't repro, duplicate, stale Mar 4, 2024
@WORMSS
Copy link
Contributor Author

WORMSS commented Mar 19, 2024

@Mugen87 I DID IT....
I actually fixed it!

And the fix "eventually" ended up being super simple...

parsePoints(length: number) {
    this.currentPoints = [];
    for (var i = 0; i < length / 4; i += 3) {
-      // z -> -z to match three.js right handed coords
+     // x -> -x to match three.js right handed coords
      this.currentPoints.push(
-       this.reader.getFloat32(),
+       -this.reader.getFloat32(),
        this.reader.getFloat32(),
-       -this.reader.getFloat32(),
+       this.reader.getFloat32(),
      );
    }
  }

also changed the pivot code.

- var layer = {
-   number: this.reader.getUint16(),
-   flags: this.reader.getUint16(), // If the least significant bit of flags is set, the layer is hidden.
-   pivot: this.reader.getFloat32Array( 3 ), // Note: this seems to be superflous, as the geometry is translated when pivot is present
-   name: this.reader.getString(),
- };
+ const number = this.reader.getUint16();
+ const flags = this.reader.getUint16(); // If the least significant bit of flags is set, the layer is hidden.
+ const [x, y, z] = this.reader.getFloat32Array(3);
+ var layer = {
+   number,
+   flags, // If the least significant bit of flags is set, the layer is hidden.
+   pivot: [-x, y, z], // Note: this seems to be superflous, as the geometry is translated when pivot is present
+   name: this.reader.getString(),
+ };

that fix was slightly less nice.

But now, when I have a model that faces +z in Lightwave, it now correctly faces +z in three.js

Also all the pivot points just "magically" work and has allowed me to get rid of nasty hack I had to run on every model load.

function* flat<T>(meshes: T | T[]) {
  if (Array.isArray(meshes)) {
    yield* meshes;
  } else {
    yield meshes;
  }
}
function* getObjects(meshes: THREE.Object3D | THREE.Object3D[]): Generator<THREE.Object3D> {
  for (const mesh of flat(meshes)) {
    yield mesh;
    for (const m of mesh.children) {
      yield* getObjects(m);
    }
  }
}
function fixPivots(meshes: THREE.Object3D[]) {
  for (const mesh of getObjectsRecursive(meshes)) {
    if (!(mesh instanceof THREE.Mesh) && !(mesh instanceof THREE.Points)) {
      continue;
    }
    const [, , z] = mesh.userData?.pivot ?? [0, 0, 0];             // THIS IS
    const parentsZ = (mesh.parent?.userData?.pivot?.[2] ?? 0) * 2; // COMPLETE AND
    mesh.geometry.translate(0, 0, z * 2);                          // UTTER MADNESS
    mesh.translateZ(-z * 2 + parentsZ);                            // I HATE IT
  }
}

and my other really bizarre stuff I had to do is also much MUCH simplified
FROM THIS

export function resetMatrix(matrix: THREE.Matrix3) {
  return matrix.set(1, 0, 0, 0, 1, 0, 0, 0, 1);
}

export function resetVectorApplyMatrix(vector: THREE.Vector2, matrix: THREE.Matrix3) {
  return vector.set(0, 0).applyMatrix3(matrix);
}

export function linkPiston(cylinder: THREE.Object3D, ram: THREE.Object3D) {
  const arm = ram.parent;
  if (!arm) {
    throw new Error('Ram Parent is null');
  }
  // NEED A VERY SPECIFIC PARENT STRUCTURE TO MAKE THIS WORK
  if (!cylinder.parent?.children.includes(arm)) {
    throw new Error('Cylinder and Ram-Parent not siblings, you will need custom logic');
  }

  const siblingOffset = new THREE.Vector2(
    arm.position.z - cylinder.position.z,
    arm.position.y - cylinder.position.y,
  );

  const vector = new THREE.Vector2();
  const matrix = new THREE.Matrix3();

  let lastRotation: number | null;

  return {
    update() {
      resetMatrix(matrix);
      matrix.translate(ram.position.z, ram.position.y);   // APPLY THE
      matrix.rotate(arm.rotation.x);                      // MATRIX IN
      matrix.translate(siblingOffset.x, siblingOffset.y); // REVERSE ORDER

      resetVectorApplyMatrix(vector, matrix);

      const cylinderRot = -vector.angle() + T(0.5);

      cylinder.rotation.x = cylinderRot;
      ram.rotation.x = cylinderRot - T(0.5) - currentRotation;
    },
  };
}

TO THIS

export function linkPiston(cylinder: THREE.Object3D, ram: THREE.Object3D) {
  const tmp = new THREE.Vector3(0, 0, 0);
  return {
    update() {
      ram.lookAt(cylinder.getWorldPosition(tmp));
      cylinder.lookAt(ram.getWorldPosition(tmp));
    },
  };
}

As you can imagine, I am very much happier to get rid of this hacky code once I realised what the problem was

WORMSS added a commit to WORMSS/three.js that referenced this issue Mar 30, 2024
fix: IFFParser.js now reads x as -x instead of z to -z. This now sets the geometry and pivot points in the correct location.

This fixes mrdoob#26733
WORMSS added a commit to WORMSS/three.js that referenced this issue Mar 30, 2024
fix: IFFParser.js now reads x as -x instead of z to -z. This now sets the geometry and pivot points in the correct location.

This fixes mrdoob#26733
Mugen87 pushed a commit that referenced this issue Apr 2, 2024
* [Loader] Fix for LWOLoader geometry correction

fix: IFFParser.js now reads x as -x instead of z to -z. This now sets the geometry and pivot points in the correct location.

This fixes #26733

* fix: webgl_loader_lwo updated with corrected coordinates for meshes and lights and cameras
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants