Skip to content

Commit 3154922

Browse files
committed
uniform axis scale handles to align better with the drei scale controls and have a better ux
1 parent 47965f6 commit 3154922

File tree

6 files changed

+117
-30
lines changed

6 files changed

+117
-30
lines changed

examples/handle-vanilla/index.ts

+3-21
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,6 @@
1-
import {
2-
BoxGeometry,
3-
Group,
4-
Mesh,
5-
MeshBasicMaterial,
6-
Object3D,
7-
Object3DEventMap,
8-
PerspectiveCamera,
9-
Scene,
10-
Vector3,
11-
WebGLRenderer,
12-
} from 'three'
1+
import { BoxGeometry, Mesh, MeshBasicMaterial, Object3DEventMap, PerspectiveCamera, Scene, WebGLRenderer } from 'three'
132
import { PointerEventsMap, forwardHtmlEvents } from '@pmndrs/pointer-events'
14-
import {
15-
OrbitHandles,
16-
PivotHandles,
17-
RotateHandles,
18-
TransformHandles,
19-
TranslateHandles,
20-
HandleStore,
21-
} from '@pmndrs/handle'
3+
import { OrbitHandles, PivotHandles, TransformHandles, HandleStore } from '@pmndrs/handle'
224
import { createXRStore } from '@pmndrs/xr'
235

246
const camera = new PerspectiveCamera(70, 1, 0.01, 100)
@@ -40,7 +22,7 @@ const transform = new TransformHandles()
4022
transform.rotation.y = Math.PI / 4
4123
transform.position.z = -2
4224
scene.add(transform)
43-
transform.bind('translate')
25+
transform.bind('scale')
4426
transform.space = 'local'
4527

4628
const box2 = new Mesh<BoxGeometry, MeshBasicMaterial, Object3DEventMap & PointerEventsMap>(

packages/handle/src/computations/utils.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,9 @@ export function computeHandleTransformState(
7979
}
8080

8181
//compute scale
82-
applyTransformOptionsToVector(scale, storeData.initialTargetScale, options.scale ?? true)
82+
if (typeof options.scale != 'object' || !options.scale.uniform) {
83+
applyTransformOptionsToVector(scale, storeData.initialTargetScale, options.scale ?? true)
84+
}
8385

8486
return {
8587
pointerAmount,

packages/handle/src/handles/registered.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { extractHandleTransformOptions } from './utils.js'
55

66
export class RegisteredHandle extends Group {
77
public readonly store: HandleStore<unknown>
8-
public options: Exclude<ReturnType<typeof extractHandleTransformOptions>, false> | undefined
8+
protected options: Exclude<ReturnType<typeof extractHandleTransformOptions>, false> | undefined
99
protected readonly tag: string
1010

1111
constructor(

packages/handle/src/handles/scale/axis.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export class AxisScaleHandle extends RegisteredHandle {
7878
interactionGroup.position.x = this.invert ? -0.3 : 0.3
7979
this.add(interactionGroup)
8080

81-
const interactionMesh = new Mesh(new CylinderGeometry(0.2, 0, 0.6, 4))
81+
const interactionMesh = new Mesh(new CylinderGeometry(0.2, 0, 0.5, 4))
8282
interactionMesh.pointerEventsOrder = Infinity
8383
interactionMesh.position.y = 0.04
8484
interactionGroup.add(interactionMesh)
@@ -93,7 +93,6 @@ export class AxisScaleHandle extends RegisteredHandle {
9393
unregister()
9494
cleanupHeadHover?.()
9595
cleanupLineHover?.()
96-
this.remove(visualizationHeadMesh)
9796
if (visualizationLineGroup != null) {
9897
this.remove(visualizationLineGroup)
9998
}

packages/handle/src/handles/scale/index.ts

+37-5
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,18 @@ import { computeHandlesScale } from '../utils.js'
55
import { FreeScaleHandle } from './free.js'
66
import { AxisScaleHandle } from './axis.js'
77
import { PlaneScaleHandle } from './plane.js'
8+
import { UniformAxisScaleHandle } from './uniform.js'
89

910
const vectorHelper = new Vector3()
1011

1112
export class ScaleHandles extends Group {
12-
private readonly free: FreeScaleHandle
13+
private readonly scaleX: UniformAxisScaleHandle
14+
private readonly scaleY: UniformAxisScaleHandle
15+
private readonly scaleZ: UniformAxisScaleHandle
16+
private readonly scaleNegX: UniformAxisScaleHandle
17+
private readonly scaleNegY: UniformAxisScaleHandle
18+
private readonly scaleNegZ: UniformAxisScaleHandle
19+
1320
private readonly translationX: AxisScaleHandle
1421
private readonly translationY: AxisScaleHandle
1522
private readonly translationZ: AxisScaleHandle
@@ -26,8 +33,22 @@ export class ScaleHandles extends Group {
2633
public fixed?: boolean,
2734
) {
2835
super()
29-
this.free = new FreeScaleHandle(this.context)
30-
this.add(this.free)
36+
this.scaleX = new UniformAxisScaleHandle(this.context, undefined, 'x')
37+
this.add(this.scaleX)
38+
this.scaleY = new UniformAxisScaleHandle(this.context, undefined, 'y')
39+
this.scaleY.rotation.z = Math.PI / 2
40+
this.add(this.scaleY)
41+
this.scaleZ = new UniformAxisScaleHandle(this.context, undefined, 'z')
42+
this.scaleZ.rotation.y = -Math.PI / 2
43+
this.add(this.scaleZ)
44+
this.scaleNegX = new UniformAxisScaleHandle(this.context, undefined, 'x', true)
45+
this.add(this.scaleNegX)
46+
this.scaleNegY = new UniformAxisScaleHandle(this.context, undefined, 'y', true)
47+
this.scaleNegY.rotation.z = Math.PI / 2
48+
this.add(this.scaleNegY)
49+
this.scaleNegZ = new UniformAxisScaleHandle(this.context, undefined, 'z', true)
50+
this.scaleNegZ.rotation.y = -Math.PI / 2
51+
this.add(this.scaleNegZ)
3152
this.translationX = new AxisScaleHandle(this.context, 'x')
3253
this.add(this.translationX)
3354
this.translationY = new AxisScaleHandle(this.context, 'y')
@@ -66,6 +87,13 @@ export class ScaleHandles extends Group {
6687
}
6788

6889
bind(options?: HandlesProperties) {
90+
const unbindScaleX = this.scaleX.bind(0xffffff, 0xffff00, options)
91+
const unbindScaleY = this.scaleY.bind(0xffffff, 0xffff00, options)
92+
const unbindScaleZ = this.scaleZ.bind(0xffffff, 0xffff00, options)
93+
const unbindScaleNegX = this.scaleNegX.bind(0xffffff, 0xffff00, options)
94+
const unbindScaleNegY = this.scaleNegY.bind(0xffffff, 0xffff00, options)
95+
const unbindScaleNegZ = this.scaleNegZ.bind(0xffffff, 0xffff00, options)
96+
6997
const unbindTranslationX = this.translationX.bind(0xff0000, 0xffff00, options)
7098
const unbindTranslationY = this.translationY.bind(0x00ff00, 0xffff00, options)
7199
const unbindTranslationZ = this.translationZ.bind(0x0000ff, 0xffff00, options)
@@ -75,7 +103,6 @@ export class ScaleHandles extends Group {
75103
const unbindTranslationXY = this.translationXY.bind(0x0000ff, 0xffff00, options)
76104
const unbindTranslationYZ = this.translationYZ.bind(0xff0000, 0xffff00, options)
77105
const unbindTranslationXZ = this.translationXZ.bind(0x00ff00, 0xffff00, options)
78-
const unbindFree = this.free.bind(options)
79106

80107
return () => {
81108
unbindTranslationX?.()
@@ -87,7 +114,12 @@ export class ScaleHandles extends Group {
87114
unbindTranslationXY?.()
88115
unbindTranslationYZ?.()
89116
unbindTranslationXZ?.()
90-
unbindFree?.()
117+
unbindScaleX?.()
118+
unbindScaleY?.()
119+
unbindScaleZ?.()
120+
unbindScaleNegX?.()
121+
unbindScaleNegY?.()
122+
unbindScaleNegZ?.()
91123
}
92124
}
93125
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { BoxGeometry, ColorRepresentation, CylinderGeometry, Euler, Group, Mesh, MeshBasicMaterial } from 'three'
2+
import { RegisteredHandle } from '../registered.js'
3+
import { HandlesContext } from '../context.js'
4+
import { HandlesProperties } from '../index.js'
5+
import { handleXRayMaterialProperties, setupHandlesContextHoverMaterial } from '../material.js'
6+
import { extractHandleTransformOptions } from '../utils.js'
7+
import { Axis } from '../../state.js'
8+
9+
const normalRotation = new Euler(0, 0, -Math.PI / 2)
10+
const invertedRotation = new Euler(0, 0, Math.PI / 2)
11+
12+
export class UniformAxisScaleHandle extends RegisteredHandle {
13+
constructor(
14+
context: HandlesContext,
15+
tagPrefix: string = '',
16+
private readonly actualAxis: Axis,
17+
private readonly invert: boolean = false,
18+
) {
19+
super(context, 'xyz', tagPrefix, () => ({
20+
scale: { uniform: true, ...this.options },
21+
rotate: false,
22+
translate: 'as-scale',
23+
multitouch: false,
24+
}))
25+
}
26+
27+
bind(defaultColor: ColorRepresentation, defaultHoverColor: ColorRepresentation, config?: HandlesProperties) {
28+
const options = extractHandleTransformOptions(this.actualAxis, config)
29+
if (options === false) {
30+
return undefined
31+
}
32+
this.options = options
33+
const rotation = this.invert ? invertedRotation : normalRotation
34+
35+
//visualization
36+
const headGroup = new Group()
37+
headGroup.position.x = this.invert ? -0.7 : 0.7
38+
headGroup.rotation.copy(rotation)
39+
this.add(headGroup)
40+
41+
const material = new MeshBasicMaterial(handleXRayMaterialProperties)
42+
const cleanupHeadHover = setupHandlesContextHoverMaterial(this.context, material, this.tag, {
43+
color: defaultColor,
44+
hoverColor: defaultHoverColor,
45+
opacity: 0.5,
46+
hoverOpacity: 1,
47+
})
48+
49+
const visualizationHeadMesh = new Mesh(new BoxGeometry(0.08, 0.08, 0.08), material)
50+
visualizationHeadMesh.renderOrder = Infinity
51+
visualizationHeadMesh.rotation.copy(rotation)
52+
53+
headGroup.add(visualizationHeadMesh)
54+
55+
const interactionHeadMesh = new Mesh(new BoxGeometry(0.15, 0.15, 0.15), material)
56+
interactionHeadMesh.visible = false
57+
interactionHeadMesh.pointerEventsOrder = Infinity
58+
interactionHeadMesh.rotation.copy(rotation)
59+
60+
headGroup.add(interactionHeadMesh)
61+
62+
const unregister = this.context.registerHandle(this.store, interactionHeadMesh, this.tag)
63+
64+
return () => {
65+
material.dispose()
66+
visualizationHeadMesh.geometry.dispose()
67+
unregister()
68+
cleanupHeadHover?.()
69+
this.remove(headGroup)
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)