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

feature request: transform origin (or "pivot point") #15965

Open
3 of 13 tasks
trusktr opened this issue Mar 13, 2019 · 63 comments
Open
3 of 13 tasks

feature request: transform origin (or "pivot point") #15965

trusktr opened this issue Mar 13, 2019 · 63 comments
Milestone

Comments

@trusktr
Copy link
Contributor

trusktr commented Mar 13, 2019

Description of the problem

As an example, Babylon has this feature built in, called pivot points. CSS has it in the form of the transform-origin property.

These features enable rotation about a pivot point with a one-liner.

Here's an implementation idea: https://jsfiddle.net/mmalex/hd8ex0ok/ (thanks @nmalex).

I believe ideally this would be implemented inside Matrix4, and the Matrix4.compose( position, quaternion, scale ) signature would be changed to Matrix4.compose( position, quaternion, scale[, origin] ), with the origin parameter being optional for backwards compatibility.

An origin property would be added to Object3D, so that we can for example write object.origin.set(1,2,3).

updateMatrixWorld would then call this.matrix.compose( this.position, this.quaternion, this.scale, this.origin ).

Just bike shedding, but pivot could also be another name in place of origin, but I like origin better because "pivot" doesn't seem to align with "scale" (I'm thinking that scale would also happen about the origin).

Three.js version
  • Dev
  • r102
  • ...
Browser
  • All of them
  • Chrome
  • Firefox
  • Internet Explorer
OS
  • All of them
  • Windows
  • macOS
  • Linux
  • Android
  • iOS
Hardware Requirements (graphics card, VR Device, ...)

N/A

@WestLangley
Copy link
Collaborator

Can you please provide a simple fiddle demonstrating your use case?

Also, please explain in the context of your demo why the existing three.js capabilities are not adequate.

@Mugen87
Copy link
Collaborator

Mugen87 commented Mar 13, 2019

TBH, I always thought that the existing approaches in three.js feel more like workarounds. I mean using an instance of THREE.Object3D that acts as a pivot point or translating the geometry is in some sense the utilization of a practical side effect. But it's not usable in all scenarios. Sometimes, you don't want to change your scene graph or the geometry data. Having a pivotMatrix that defines the pivot point is a solution without such ramifications.

@Mugen87
Copy link
Collaborator

Mugen87 commented Mar 13, 2019

Related: https://stackoverflow.com/questions/55116131/how-can-we-change-the-rotation-origin-pivot-point-of-a-three-js-object-without/55138754#55138754

I think it might be worth to consider this issue as a valid feature request.

@looeee
Copy link
Collaborator

looeee commented Mar 13, 2019

I agree with @Mugen87 here. I spent way to long using these hacks to get pivot points from the FBX format working, and I still think that there are edge cases that are not covered since the format has the concept of geometric pivot (i.e. transformed geometry relative to the mesh) and also object level pivot points (what @trusktr means here).

Both currently are shoe-horned into the geometric pivot, but it would have been a lot easier and less error prone if threejs had native pivot points.

Since most other 3D apps seem to have this concept, the only reason I can think of for NOT adding them is if there are major caveats for doing so, such as reduced performance.

@donmccurdy
Copy link
Collaborator

donmccurdy commented Mar 13, 2019

Having a pivotMatrix that defines the pivot point is a solution without such ramifications.

There are some ramifications here. The new matrix will have to be recursively updated and updateMatrixWorld will likely become slower. I think we'd want to measure that effect on a CPU-constrained example before proceeding.

Since most other 3D apps seem to have this concept, ...

Any non-DCC examples? I know all modeling tools have this, but they have a lot of concepts that aren't reasonable to include in a realtime rendering library. I think Unity's editor allows you to set pivot points, but they're baked when you build the project and a pivot point cannot be changed at runtime without using parent nodes.

From the authors of the Godot Engine:

Some 3D DCCs support an extra object Pivot (e.g. 3DS MAX). Collada exports this as an extra node and most likely if a glTF 2.0 exporter [for 3DS MAX] existed, the same would be done. ... I don't think any mainstream 3D engine supports extra object pivots either.

@arodic
Copy link
Sponsor Contributor

arodic commented Mar 13, 2019

The new matrix will have to be recursively updated and updateMatrixWorld will likely become slower.

Perhaps the new matrix can be calculated only if pivot point is specified, as it is optional.

@looeee
Copy link
Collaborator

looeee commented Mar 13, 2019

I think we'd want to measure that effect on a CPU-constrained example before proceeding.

Yes, this seems like the obvious next step. If this can only be achieved by reduced performance, then it's a non-starter and we'll have to continue with the hacks.

In Babylon.js it looks like the pivot is a simple pre-transform matrix. If we implement it in a similar way, such that the pivot matrix is undefined by default, then updateMatrixWorld should only be slower if the pivot is present.

On the other hand, Babylon.js is the only non-DCC tool I can find that implements pivot points in real time (with 5 minutes of research, anyway).

This would be useful for the FBX loader, and I can imagine other situations such as manually building objects with hinges where since this would be useful. But on the other hand, we've got this far using just the hacks and I don't personally have a strong need for this.

@truskr if you want to champion this idea, could you do some more research and see whether other tools beside Babylon offer real time pivots? Also, if you can identify some use cases and see if other people besides you are requesting this feature then that would help strengthen the case.

object.origin.set(1,2,3)

My preference would be to call this object.pivot.

@WestLangley
Copy link
Collaborator

@looeee Please stop referring to the existing code as hacks.

@WestLangley
Copy link
Collaborator

WestLangley commented Mar 13, 2019

I would like the OP to answer my questions above before considering adding such a feature.

Note, a user can do the following at the application level. I would not expect it to have a measurable performance impact.

THREE.Object3D.prototype.updateMatrix = function () {

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

	if ( this.pivot && this.pivot.isVector3 ) {

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

		var 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;

};

and then define the object pivot only when needed:

mesh.pivot = new THREE.Vector3( 5, 5, 5 );

With this approach, children of objects having a defined pivot are still located relative to the object's origin.

@trusktr
Copy link
Contributor Author

trusktr commented Mar 14, 2019

Basic example: Using origin/pivot to define the hinge of a door in a game. Here's a fiddle, using CSS transform-origin, but you get the idea.

@Mugen87
Copy link
Collaborator

Mugen87 commented Mar 14, 2019

Instead of asking the user to monkey-patch library code, I would prefer to include Object3D.pivot into the library.

@trusktr

This comment has been minimized.

@mrdoob
Copy link
Owner

mrdoob commented Mar 15, 2019

Sometimes, you don't want to change your scene graph or the geometry data.

I spent way to long using these hacks to get pivot points from the FBX format working

I think these are fair points.

We should measure what's the performance impact of modifying updateMatrix() so it looks like this.

THREE.Object3D.prototype.updateMatrix = function () {

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

	var pivot = this.pivot;

	if ( pivot !== null ) {

		var px = pivot.x, py = pivot.y,  pz = pivot.z;
		var 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;

};

@mrdoob mrdoob added this to the r103 milestone Mar 15, 2019
@looeee
Copy link
Collaborator

looeee commented Mar 15, 2019

We should measure what's the performance impact of modifying updateMatrix() so it looks like this.

Perhaps we can do this at the start of the next release cycle? That way we'll have a month to test it, and we can roll it back if it causes performance degradation

@mrdoob mrdoob modified the milestones: r103, r104 Mar 15, 2019
@trusktr
Copy link
Contributor Author

trusktr commented Mar 15, 2019

If it goes into the lib, is it better in Object3D than in Matrix4 and Matrix3 (for 2D)?

@WestLangley
Copy link
Collaborator

@trusktr Did you try the patch I provided for you? Is it implementing the feature you requested?

@trusktr
Copy link
Contributor Author

trusktr commented Mar 15, 2019

@WestLangley Seems like it does. Here's a fiddle rotating/scaling a cube (and its child) around its corner.

@Mugen87
Copy link
Collaborator

Mugen87 commented Mar 15, 2019

@trusktr Do you want to prepare a PR with the code from #15965 (comment)?

@WestLangley
Copy link
Collaborator

@Mugen87 You should be asking me that question.

@WestLangley
Copy link
Collaborator

[FBXLoader] would have been a lot easier and less error prone if threejs had native pivot points.

@looeee Can you please try this patch in the FBXLoader? Do animations work when pivots are specified?

@WestLangley
Copy link
Collaborator

@Mugen87 Have you been able to try this? I am finding it very unintuitive when the pivot is changed...

@Mugen87
Copy link
Collaborator

Mugen87 commented Mar 15, 2019

@WestLangley Sorry about that. I was not aware you want to do this 😅

Have you been able to try this? I am finding it very unintuitive when the pivot is changed...

I'm actually waiting on a PR so I can checkout the respective branch and test it. I know the feature from Babylon.js and I'd like to compare both behaviors.

@WestLangley
Copy link
Collaborator

I was not aware you want to do this

If the feature is supported, I will file the PR.

I do not support the feature yet, because we haven't decided what the behavior should be.

@WestLangley
Copy link
Collaborator

WestLangley commented Mar 15, 2019

This demo requires changing the pivot in real-time, and unfortunately, doing so is not intuitive at all. (One filp is easy; change the pivot and the object jumps to a different location.)

Mar-15-2019 08-22-15

A better API may be a method that rotates an object around an axis apart from the object -- such as the method I proposed in this SO answer.

@trusktr
Copy link
Contributor Author

trusktr commented Mar 15, 2019

Here's a fiddle of that rotating plane, using the above pivot.

I wanted to verify that it works with arbitrary quaternion applied too: this fiddle shows rotating around the front diagonal of a cube as expected. 👍 This shows that with a pivot, and using quaternion.setFromAxisAngle, we can make arbitrary local axes of rotation (point + axis + angle).

And this fiddle shows pivot working regardless of having a transformed (scaled and rotated) parent. 👍

I like the idea of an option to rotate around an axis, but in the SO answer it doesn't work with a transformed parent. Having to figure out a world axis in order to rotate an object may not be convenient. In the door example, it's simple to specify pivot local to the door, as opposed to calculating a world axis (and making it work with rotated parents), and it makes encapsulation easy: the local component doesn't need to worry about the outside world for the animation to work.

@WestLangley
Copy link
Collaborator

@trusktr You are not changing the pivot in real-time -- that means after the object is rendered.

How about answering my question above, and providing a demo of your use case?

If your use case is a door, you can pivot a door by aligning the door's geometry with the origin.

@marnec
Copy link

marnec commented Nov 2, 2023

THREE.Object3D.prototype.updateMatrix = function () {

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

	var pivot = this.pivot;

	if ( pivot !== null ) {

		var px = pivot.x, py = pivot.y,  pz = pivot.z;
		var 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;

};

BTW this is a very bad solution if you are using typescript and @types/three because the types lib doesn't know about pivot

@gkjohnson
Copy link
Collaborator

BTW this is a very bad solution if you are using typescript and @types/three because the types lib doesn't know about pivot

You can use module augmentation to add whichever fields you want to existing object definitions:

https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation

@mrdoob mrdoob modified the milestones: r159, r160 Nov 30, 2023
@Mugen87
Copy link
Collaborator

Mugen87 commented Dec 19, 2023

I have another concrete use case where having Object3D.pivot is required. I'm implementing a loader for M2 assets and the respective bone setup requires the definition of pivot points (see the M2CompBone spec). If the loader does not do that, the animation is broken. For comparison:

Without Object3D.pivot and updated Object3D.updateMatrix():

broken.mov

Defining Object3D.pivot, assigning the M2 pivot data and monkey-patching Object3D.updateMatrix():

fixed.mov

I'm not sure how I can implement a solution without Object3D.pivot. Here is the code where I monkey-patch Object3D.updateMatrix() and the line which defines the pivot point.

@gkjohnson
Copy link
Collaborator

It seems reasonable to make a extension class for your use case that includes support for Pivot, no? Something like M2PivotBone?

@Mugen87
Copy link
Collaborator

Mugen87 commented Dec 20, 2023

Yeah, that sounds good! I'm still hoping to see support for pivot points in Object3D in the future so it is not necessary to create custom Object3D types.

@RemusMar
Copy link
Contributor

I'm still hoping to see support for pivot points in Object3D in the future so it is not necessary to create custom Object3D types.

+1

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

No branches or pull requests