Skip to content

TransformControls: introduce scaleFromEdge property #31346

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

Closed
wants to merge 8 commits into from

Conversation

manisandro
Copy link

@manisandro manisandro commented Jul 1, 2025

This PR proposes an option to the TransformControls to scale objects while keeping the opposite edge as the fixed anchor point, instead of scaling around the center. It currently only works for simple objects, not object groups.

@Mugen87
Copy link
Collaborator

Mugen87 commented Jul 1, 2025

The feature is actually quite nice! I've tested it in misc_controls_transform and it works without issues.

@manisandro
Copy link
Author

manisandro commented Jul 1, 2025

One edge case which I've since discovered: it fails if you scale the object through its origin. Since negative scales also have other ugly side effects (translation in local space working the opposite way, csg operations with three-csg producing inverse results, etc), I'd suggest also adding an option to disallow negative scales. Shall I open a new PR for this or add it to his PR?

@Mugen87
Copy link
Collaborator

Mugen87 commented Jul 1, 2025

it fails if you scale the object through its origin.

What do you mean with failing? Isn't the scale behavior equal to the default behavior (meaning when scaleFromEdge is set to false)?

@manisandro
Copy link
Author

I mean that, with negative scales, pulling from an edge will actually use the pulled edge itself as an anchor, instead of the opposite edge.

@Mugen87
Copy link
Collaborator

Mugen87 commented Jul 1, 2025

When the scaling gets negative, the gizmo might be flipped which looks odd, imo. Instead of adding a flag, it's probably best to disallow negative scaling in TransformControls and just clamp negative values to zero.

AFAICS, the scale gizmo in Blender does not allow negative scaling as well. If wanted, you must configure the negative scale over the UI.

@manisandro
Copy link
Author

I've added a suggested way of handling this (just ignore scale movements which would make the scale negative)

@mrdoob
Copy link
Owner

mrdoob commented Jul 2, 2025

Any chance you could add a video of the feature in the first post?

@Mugen87
Copy link
Collaborator

Mugen87 commented Jul 2, 2025

Here is a live example with a custom version of TransformControls from the PR: https://jsfiddle.net/dc2gjq6o/1/

scaleFromEdge is already set to true.

@gkjohnson
Copy link
Collaborator

gkjohnson commented Jul 3, 2025

AFAICS, the scale gizmo in Blender does not allow negative scaling as well. If wanted, you must configure the negative scale over the UI.

I've just tried in blender and the widget does allow for negative scaling (note the Y-value in the UI). I don't recall a 3D DCC that disallowed negative scaling via drag:

blender-scale.mov

Since negative scales also have other ugly side effects (translation in local space working the opposite way, csg operations with three-csg producing inverse results, etc), I'd suggest also adding an option to disallow negative scales.

Vector direction reversing is a mathetmatical consequence of a negative scale. Users can choose to disallow negative scaling (via value clamping, for example) if needed for their projects but it's otherwise explicitly supported in three.js (winding direction is flipped when rendering, etc) and not something that should be considered inherently "ugly". If other projects like three-csg do not support negative scaling then an issue can be submitted at that project (there's no reason it can't be supported) but it's not a reason to broadly restrict what is typical 3d editor behavior.

@Mugen87
Copy link
Collaborator

Mugen87 commented Jul 3, 2025

I've just tried in blender and the widget does allow for negative scaling

Good catch! I've used the s key for scaling and this does not allow negative scales.

In this case, we should allow negative scale in TransformControls and accept the resulting gizmo behavior.

@manisandro
Copy link
Author

Any objections to making [1] a configurable opt-in option?

[1] da4bf71

@gkjohnson
Copy link
Collaborator

Any objections to making [1] a configurable opt-in option?

Is there any reason this can't already be done? It's trivial to clamp the scale of the object.

control.addEventListener( 'change', e => {

  e.target.object.scale.max( new THREE.Vector3( 0, 0, 0 ) );

} );

@manisandro
Copy link
Author

If you have scaleFromEdge: true, the logic to compensate the object position will use the non-clamped scales, resulting in the object getting moved around even if the scales are later clamped.

@gkjohnson
Copy link
Collaborator

gkjohnson commented Jul 4, 2025

If you have scaleFromEdge: true, the logic to compensate the object position will use the non-clamped scales, resulting in the object getting moved around even if the scales are later clamped.

This is true of any scale clamping, not just when clamping at zero. Even if a flag is added for disabling negative scaling this issue of the object drifting as you scale it by the edge if the user needs to clamp to a positive scale still occurs. Perhaps negative scaling is the only thing that needs to be prevented in some cases but it's not a generally robust solution.

In my opinion it's best to keep this feature separate from TransformControls. It can already be implemented using the existing events which provides the flexibility for users to provide any restrictions they need on the transform. Here's a demonstration showing scale clamping at a small epsilon (TransformControls seems to break with 0 scale) and at 2:

https://jsfiddle.net/L12ecmpw/

scaling.mov

@Mugen87
Copy link
Collaborator

Mugen87 commented Jul 5, 2025

Since users can implement this kind of custom scaling in an objectChange event listener, I agree with not supporting it directly in TransformControls. Granted, the event listener's code isn't trivial but I assume not much users require this kind of scaling. If we see more users requests in the future though, we can reconsider the PR.

@manisandro Thanks for your effort here. Even the PR isn't merged, it is now a informative discussion for developers who looking for a custom scaling solution.

Here is the custom event listener just for the case jsfiddle goes offline in the future:

control.addEventListener( 'objectChange', e => {

	const control = e.target;
	const { axis, mode, object } = control;

	if ( mode == 'scale' ) {

		const lowerCaseAxis = axis.toLowerCase();
		object.scale[ lowerCaseAxis ] = THREE.MathUtils.clamp( object.scale[ lowerCaseAxis ], 1e-4, 2 );

		// construct the new box
		const newBox = new THREE.Box3();
		newBox.copy( mesh.geometry.boundingBox );
		newBox.min.multiply( object.scale );
		newBox.max.multiply( object.scale );

		// determine which side the clamp to
		const negativeScaling = control.pointStart[ lowerCaseAxis ] < 0 ? - 1 : 1;

		// find the change in the box size
		const width = box.max[ lowerCaseAxis ] - box.min[ lowerCaseAxis ];
		const newWidth = newBox.max[ lowerCaseAxis ] - newBox.min[ lowerCaseAxis ];
		const scaleDiff = newWidth / width;

		// offset the size
		object.position.copy( center );
		object.position[ lowerCaseAxis ] += negativeScaling * ( width * scaleDiff - width ) * 0.5;

	}

} );

@Mugen87 Mugen87 closed this Jul 5, 2025
@Mugen87 Mugen87 added this to the r179 milestone Jul 5, 2025
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.

4 participants