Skip to content

Commit 81f4c06

Browse files
feat: add touch support to follow cam
1 parent 8dcc8f8 commit 81f4c06

File tree

4 files changed

+105
-13
lines changed

4 files changed

+105
-13
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ecctrl",
3-
"version": "1.0.41",
3+
"version": "1.0.42",
44
"author": "Erdong Chen",
55
"license": "MIT",
66
"description": "A floating rigibody character controller for R3F",

readme.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ EcctrlProps: {
128128
camMaxDis: -7, // Maximum camera distance
129129
camMinDis: -0.7, // Minimum camera distance
130130
camInitDir: 0, // Camera initial position direction (in rad)
131+
camMoveSpeed = 1, // Camera moving speed multiplier
132+
camZoomSpeed = 1, // Camera zooming speed multiplier
133+
camCollisionOffset = 0.7, // Camera collision offset
131134
// Follow light setups
132135
followLightPos: { x: 20, y: 30, z: 10 }, // Follow light position
133136
// Base control setups

src/Ecctrl.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ const Ecctrl = forwardRef<RapierRigidBody, EcctrlProps>(({
3434
camMaxDis = -7,
3535
camMinDis = -0.7,
3636
camInitDir = 0, // in rad
37+
camMoveSpeed = 1,
38+
camZoomSpeed = 1,
39+
camCollisionOffset = 0.7,
3740
// Follow light setups
3841
followLightPos = { x: 20, y: 30, z: 10 },
3942
// Base control setups
@@ -396,6 +399,9 @@ const Ecctrl = forwardRef<RapierRigidBody, EcctrlProps>(({
396399
camInitDis,
397400
camMaxDis,
398401
camMinDis,
402+
camMoveSpeed,
403+
camZoomSpeed,
404+
camCollisionOffset
399405
};
400406

401407
/**
@@ -1074,8 +1080,11 @@ export interface EcctrlProps extends RigidBodyProps {
10741080
camMaxDis?: number;
10751081
camMinDis?: number;
10761082
camInitDir?: number;
1083+
camMoveSpeed?: number;
1084+
camZoomSpeed?: number;
1085+
camCollisionOffset?: number;
10771086
// Follow light setups
1078-
followLightPos?: { x: number, y: number, z: number },
1087+
followLightPos?: { x: number, y: number, z: number };
10791088
// Base control setups
10801089
maxVelLimit?: number;
10811090
turnVelMultiplier?: number;

src/hooks/useFollowCam.tsx

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,16 @@ export const useFollowCam = function (props: UseFollowCamProps) {
77
const { scene, camera } = useThree();
88
// const { rapier, world } = useRapier();
99

10+
let isMouseDown = false;
11+
let previousTouch1: Touch = null;
12+
let previousTouch2: Touch = null;
13+
1014
let originZDis = props.camInitDis;
1115
const camMaxDis = props.camMaxDis;
1216
const camMinDis = props.camMinDis;
13-
const camCollisionOff = 0.7;
17+
const camMoveSpeed = props.camMoveSpeed;
18+
const camZoomSpeed = props.camZoomSpeed;
19+
const camCollisionOffset = props.camCollisionOffset;
1420
const pivot = useMemo(() => new THREE.Object3D(), []);
1521
const followCam = useMemo(() => {
1622
const origin = new THREE.Object3D();
@@ -40,9 +46,9 @@ export const useFollowCam = function (props: UseFollowCamProps) {
4046

4147
// Mouse move event
4248
const onDocumentMouseMove = (e: MouseEvent) => {
43-
if (document.pointerLockElement) {
44-
pivot.rotation.y -= e.movementX * 0.002;
45-
const vy = followCam.rotation.x + e.movementY * 0.002;
49+
if (document.pointerLockElement || isMouseDown) {
50+
pivot.rotation.y -= e.movementX * 0.002 * camMoveSpeed;
51+
const vy = followCam.rotation.x + e.movementY * 0.002 * camMoveSpeed;
4652

4753
cameraDistance = followCam.position.length();
4854

@@ -57,18 +63,76 @@ export const useFollowCam = function (props: UseFollowCamProps) {
5763

5864
// Mouse scroll event
5965
const onDocumentMouseWheel = (e: Event) => {
60-
if (document.pointerLockElement) {
61-
const vz = originZDis - (e as WheelEvent).deltaY * 0.002;
62-
const vy = followCam.rotation.x + (e as WheelEvent).movementY * 0.002;
66+
const vz = originZDis - (e as WheelEvent).deltaY * 0.002 * camZoomSpeed;
67+
const vy = followCam.rotation.x;
68+
69+
if (vz >= camMaxDis && vz <= camMinDis) {
70+
originZDis = vz;
71+
followCam.position.z = originZDis * Math.cos(-vy);
72+
followCam.position.y = originZDis * Math.sin(-vy);
73+
}
74+
return false;
75+
};
76+
77+
// Touch start event
78+
const onTouchStart = (e: TouchEvent) => {
79+
// prevent swipe to navigate gesture
80+
e.preventDefault();
81+
e.stopImmediatePropagation();
82+
}
83+
84+
// Touch end event
85+
const onTouchEnd = (e: TouchEvent) => {
86+
previousTouch1 = null
87+
previousTouch2 = null
88+
}
89+
90+
// Touch move event
91+
const onTouchMove = (e: TouchEvent) => {
92+
const touch1 = e.targetTouches[0];
93+
const touch2 = e.targetTouches[1];
94+
95+
// One finger touch to rotate camera
96+
if (previousTouch1 && !previousTouch2) {
97+
const touch1MovementX = touch1.pageX - previousTouch1.pageX;
98+
const touch1MovementY = touch1.pageY - previousTouch1.pageY;
99+
100+
pivot.rotation.y -= touch1MovementX * 0.005 * camMoveSpeed;
101+
const vy = followCam.rotation.x + touch1MovementY * 0.005 * camMoveSpeed;
102+
103+
cameraDistance = followCam.position.length();
104+
105+
if (vy >= -0.5 && vy <= 1.5) {
106+
followCam.rotation.x = vy;
107+
followCam.position.y = -cameraDistance * Math.sin(-vy);
108+
followCam.position.z = -cameraDistance * Math.cos(-vy);
109+
}
110+
}
111+
112+
// Two fingers touch to zoom in/out camera
113+
if (previousTouch2) {
114+
const prePinchDis = Math.hypot(
115+
previousTouch1.pageX - previousTouch2.pageX,
116+
previousTouch1.pageY - previousTouch2.pageY
117+
);
118+
const pinchDis = Math.hypot(
119+
e.touches[0].pageX - e.touches[1].pageX,
120+
e.touches[0].pageY - e.touches[1].pageY
121+
);
122+
123+
const vz = originZDis - (prePinchDis - pinchDis) * 0.01 * camZoomSpeed;
124+
const vy = followCam.rotation.x;
63125

64126
if (vz >= camMaxDis && vz <= camMinDis) {
65127
originZDis = vz;
66128
followCam.position.z = originZDis * Math.cos(-vy);
67129
followCam.position.y = originZDis * Math.sin(-vy);
68130
}
69131
}
70-
return false;
71-
};
132+
133+
previousTouch1 = touch1;
134+
previousTouch2 = touch2;
135+
}
72136

73137
// Custom traverse function
74138
// Prepare intersect objects for camera collision
@@ -106,8 +170,8 @@ export const useFollowCam = function (props: UseFollowCamProps) {
106170
intersects = camRayCast.intersectObjects(intersectObjects);
107171
if (intersects.length && intersects[0].distance <= -originZDis) {
108172
smallestDistance =
109-
-intersects[0].distance * camCollisionOff < -0.7
110-
? -intersects[0].distance * camCollisionOff
173+
-intersects[0].distance * camCollisionOffset < -0.7
174+
? -intersects[0].distance * camCollisionOffset
111175
: -0.7;
112176
} else {
113177
smallestDistance = originZDis;
@@ -144,11 +208,24 @@ export const useFollowCam = function (props: UseFollowCamProps) {
144208
followCam.add(camera);
145209
pivot.add(followCam);
146210

211+
document.addEventListener("mousedown", () => { isMouseDown = true });
212+
document.addEventListener("mouseup", () => { isMouseDown = false });
147213
document.addEventListener("mousemove", onDocumentMouseMove);
148214
document.addEventListener("mousewheel", onDocumentMouseWheel);
215+
// Touch event
216+
document.addEventListener("touchstart", onTouchStart, { passive: false });
217+
document.addEventListener("touchend", onTouchEnd);
218+
document.addEventListener("touchmove", onTouchMove);
219+
149220
return () => {
221+
document.removeEventListener("mousedown", () => { isMouseDown = true });
222+
document.removeEventListener("mouseup", () => { isMouseDown = false });
150223
document.removeEventListener("mousemove", onDocumentMouseMove);
151224
document.removeEventListener("mousewheel", onDocumentMouseWheel);
225+
// Touch event
226+
document.removeEventListener("touchstart", onTouchStart);
227+
document.removeEventListener("touchend", onTouchEnd);
228+
document.removeEventListener("touchmove", onTouchMove);
152229
};
153230
});
154231

@@ -159,4 +236,7 @@ export type UseFollowCamProps = {
159236
camInitDis: number;
160237
camMaxDis: number;
161238
camMinDis: number;
239+
camMoveSpeed: number;
240+
camZoomSpeed: number;
241+
camCollisionOffset: number;
162242
};

0 commit comments

Comments
 (0)