Skip to content

Commit 292802d

Browse files
authored
Camera controls focus damping (#7313)
* Added separate focusing damping * Adjusted default focus Damping * Updated focus damping description
1 parent be27bc1 commit 292802d

File tree

6 files changed

+115
-33
lines changed

6 files changed

+115
-33
lines changed

examples/src/examples/camera/multi.controls.mjs

+12
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,18 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
5454
link: { observer, path: 'attr.enableFly' }
5555
})
5656
),
57+
jsx(
58+
LabelGroup,
59+
{ text: 'Focus damping' },
60+
jsx(SliderInput, {
61+
binding: new BindingTwoWay(),
62+
link: { observer, path: 'attr.focusDamping' },
63+
min: 0,
64+
max: 0.999,
65+
step: 0.001,
66+
precision: 3
67+
})
68+
),
5769
jsx(
5870
LabelGroup,
5971
{ text: 'Pitch range' },

examples/src/examples/camera/multi.example.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ data.set('attr', [
150150
'enableOrbit',
151151
'enablePan',
152152
'enableFly',
153+
'focusDamping',
153154
'pitchRange',
154155
'rotateSpeed',
155156
'rotateDamping',

examples/src/examples/camera/orbit.controls.mjs

+12
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
3636
link: { observer, path: 'attr.enablePan' }
3737
})
3838
),
39+
jsx(
40+
LabelGroup,
41+
{ text: 'Focus damping' },
42+
jsx(SliderInput, {
43+
binding: new BindingTwoWay(),
44+
link: { observer, path: 'attr.focusDamping' },
45+
min: 0,
46+
max: 0.999,
47+
step: 0.001,
48+
precision: 3
49+
})
50+
),
3951
jsx(
4052
LabelGroup,
4153
{ text: 'Pitch range' },

examples/src/examples/camera/orbit.example.mjs

+1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ data.set('example', {
147147

148148
data.set('attr', [
149149
'enablePan',
150+
'focusDamping',
150151
'pitchRange',
151152
'rotateSpeed',
152153
'rotateDamping',

examples/src/examples/misc/editor.example.mjs

+3-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ app.root.addChild(camera);
129129
const cameraControls = /** @type {CameraControls} */ (camera.script.create(CameraControls, {
130130
properties: {
131131
focusPoint: pc.Vec3.ZERO,
132-
sceneSize: 5
132+
sceneSize: 5,
133+
rotateDamping: 0,
134+
moveDamping: 0
133135
}
134136
}));
135137
app.on('gizmo:pointer', (/** @type {boolean} */ hasPointer) => {

scripts/esm/camera-controls.mjs

+86-32
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ const tmpQ1 = new Quat();
1818
const tmpR1 = new Ray();
1919
const tmpP1 = new Plane();
2020

21+
/** @type {AddEventListenerOptions & EventListenerOptions} */
2122
const PASSIVE = { passive: false };
2223
const ZOOM_SCALE_SCENE_MULT = 10;
24+
const EPSILON = 0.0001;
2325

2426
/**
2527
* Calculate the lerp rate.
@@ -55,7 +57,7 @@ class CameraControls extends Script {
5557

5658
/**
5759
* @private
58-
* @type {CameraComponent}
60+
* @type {CameraComponent | null}
5961
*/
6062
_camera = null;
6163

@@ -155,6 +157,12 @@ class CameraControls extends Script {
155157
*/
156158
_moving = false;
157159

160+
/**
161+
* @type {boolean}
162+
* @private
163+
*/
164+
_focusing = false;
165+
158166
/**
159167
* @type {Record<string, boolean>}
160168
* @private
@@ -222,6 +230,15 @@ class CameraControls extends Script {
222230
*/
223231
enableFly = true;
224232

233+
/**
234+
* @attribute
235+
* @title Focus Damping
236+
* @description The damping applied when calling {@link CameraControls#focus}. A higher value means
237+
* more damping. A value of 0 means no damping.
238+
* @type {number}
239+
*/
240+
focusDamping = 0.98;
241+
225242
/**
226243
* @attribute
227244
* @title Rotate Speed
@@ -236,7 +253,7 @@ class CameraControls extends Script {
236253
* @description The rotation damping. A higher value means more damping. A value of 0 means no damping.
237254
* @type {number}
238255
*/
239-
rotateDamping = 0.97;
256+
rotateDamping = 0.98;
240257

241258
/**
242259
* @attribute
@@ -334,7 +351,9 @@ class CameraControls extends Script {
334351

335352
const camera = this._camera;
336353
this.detach();
337-
this.attach(camera);
354+
if (camera) {
355+
this.attach(camera);
356+
}
338357
}
339358

340359
get element() {
@@ -547,6 +566,11 @@ class CameraControls extends Script {
547566
const startFly = this._isStartFly(event);
548567
const startOrbit = this._isStartOrbit(event);
549568

569+
if (this._focusing) {
570+
this._cancelSmoothTransform();
571+
this._focusing = false;
572+
}
573+
550574
if (startTouchPan) {
551575
// start touch pan
552576
this._lastPinchDist = this._getPinchDist();
@@ -768,6 +792,11 @@ class CameraControls extends Script {
768792

769793
// clamp movement if locked
770794
if (this._moving) {
795+
if (this._focusing) {
796+
this._cancelSmoothTransform();
797+
this._focusing = false;
798+
}
799+
771800
this._clampPosition(this._origin);
772801
}
773802
}
@@ -874,12 +903,34 @@ class CameraControls extends Script {
874903
* @param {number} dt - The delta time.
875904
*/
876905
_smoothTransform(dt) {
877-
const ar = dt === -1 ? 1 : lerpRate(this.rotateDamping, dt);
878-
const am = dt === -1 ? 1 : lerpRate(this.moveDamping, dt);
906+
const ar = dt === -1 ? 1 : lerpRate(this._focusing ? this.focusDamping : this.rotateDamping, dt);
907+
const am = dt === -1 ? 1 : lerpRate(this._focusing ? this.focusDamping : this.moveDamping, dt);
879908
this._angles.x = math.lerp(this._angles.x, this._dir.x, ar);
880909
this._angles.y = math.lerp(this._angles.y, this._dir.y, ar);
881910
this._position.lerp(this._position, this._origin, am);
882911
this._baseTransform.setTRS(this._position, tmpQ1.setFromEulerAngles(this._angles), Vec3.ONE);
912+
913+
const focusDelta = this._position.distance(this._origin) +
914+
Math.abs(this._angles.x - this._dir.x) +
915+
Math.abs(this._angles.y - this._dir.y);
916+
if (this._focusing && focusDelta < EPSILON) {
917+
this._focusing = false;
918+
}
919+
}
920+
921+
/**
922+
* @private
923+
*/
924+
_cancelSmoothZoom() {
925+
this._cameraDist = this._zoomDist;
926+
}
927+
928+
/**
929+
* @private
930+
*/
931+
_cancelSmoothTransform() {
932+
this._origin.copy(this._position);
933+
this._dir.set(this._angles.x, this._angles.y);
883934
}
884935

885936
/**
@@ -894,8 +945,8 @@ class CameraControls extends Script {
894945
/**
895946
* Focus the camera on a point.
896947
*
897-
* @param {Vec3} point - The point.
898-
* @param {Vec3} [start] - The start.
948+
* @param {Vec3} point - The focus point.
949+
* @param {Vec3} [start] - The camera start position.
899950
* @param {boolean} [smooth] - Whether to smooth the focus.
900951
*/
901952
focus(point, start, smooth = true) {
@@ -905,35 +956,39 @@ class CameraControls extends Script {
905956
if (this._flying) {
906957
return;
907958
}
908-
if (!start) {
909-
this._origin.copy(point);
910-
if (!smooth) {
911-
this._position.copy(point);
912-
}
913-
return;
914-
}
915959

916-
tmpV1.sub2(start, point);
917-
const elev = Math.atan2(tmpV1.y, Math.sqrt(tmpV1.x * tmpV1.x + tmpV1.z * tmpV1.z)) * math.RAD_TO_DEG;
918-
const azim = Math.atan2(tmpV1.x, tmpV1.z) * math.RAD_TO_DEG;
919-
this._clampAngles(this._dir.set(-elev, azim));
960+
if (start) {
961+
tmpV1.sub2(start, point);
962+
const elev = Math.atan2(tmpV1.y, Math.sqrt(tmpV1.x * tmpV1.x + tmpV1.z * tmpV1.z)) * math.RAD_TO_DEG;
963+
const azim = Math.atan2(tmpV1.x, tmpV1.z) * math.RAD_TO_DEG;
964+
this._clampAngles(this._dir.set(-elev, azim));
920965

921-
this._origin.copy(point);
966+
this._origin.copy(point);
922967

923-
this._cameraTransform.setTranslate(0, 0, 0);
968+
this._cameraTransform.setTranslate(0, 0, 0);
924969

925-
const pos = this.entity.getPosition();
926-
const rot = this.entity.getRotation();
927-
this._baseTransform.setTRS(pos, rot, Vec3.ONE);
970+
const pos = this.entity.getPosition();
971+
const rot = this.entity.getRotation();
972+
this._baseTransform.setTRS(pos, rot, Vec3.ONE);
928973

929-
this._zoomDist = this._clampZoom(tmpV1.length());
974+
this._zoomDist = this._clampZoom(tmpV1.length());
930975

931-
if (!smooth) {
932-
this._smoothZoom(-1);
933-
this._smoothTransform(-1);
976+
if (!smooth) {
977+
this._smoothZoom(-1);
978+
this._smoothTransform(-1);
979+
}
980+
981+
this._updateTransform();
982+
} else {
983+
this._origin.copy(point);
984+
if (!smooth) {
985+
this._position.copy(point);
986+
}
934987
}
935988

936-
this._updateTransform();
989+
if (smooth) {
990+
this._focusing = true;
991+
}
937992
}
938993

939994
/**
@@ -957,7 +1012,7 @@ class CameraControls extends Script {
9571012
* @param {number} [zoomDist] - The zoom distance.
9581013
* @param {boolean} [smooth] - Whether to smooth the refocus.
9591014
*/
960-
refocus(point, start = null, zoomDist, smooth = true) {
1015+
refocus(point, start, zoomDist, smooth = true) {
9611016
if (typeof zoomDist === 'number') {
9621017
this.resetZoom(zoomDist, smooth);
9631018
}
@@ -1003,9 +1058,8 @@ class CameraControls extends Script {
10031058

10041059
this._camera = null;
10051060

1006-
this._dir.x = this._angles.x;
1007-
this._dir.y = this._angles.y;
1008-
this._origin.copy(this._position);
1061+
this._cancelSmoothZoom();
1062+
this._cancelSmoothTransform();
10091063

10101064
this._pointerEvents.clear();
10111065
this._lastPinchDist = -1;

0 commit comments

Comments
 (0)