1
1
import { useKeyboardControls } from "@react-three/drei" ;
2
2
import { useFrame } from "@react-three/fiber" ;
3
3
import {
4
+ quat ,
4
5
RigidBody ,
5
6
CapsuleCollider ,
6
7
useRapier ,
7
8
RapierRigidBody ,
8
9
type RigidBodyProps ,
9
10
} from "@react-three/rapier" ;
10
- import { useEffect , useRef , useMemo , type ReactNode } from "react" ;
11
+ import { useEffect , useRef , useMemo , type ReactNode , forwardRef } from "react" ;
11
12
import * as THREE from "three" ;
12
13
import { useControls } from "leva" ;
13
14
import { useFollowCam } from "./hooks/useFollowCam" ;
@@ -20,7 +21,7 @@ import type {
20
21
21
22
export { EcctrlAnimation } from "./EcctrlAnimation" ;
22
23
23
- export default function Ecctrl ( {
24
+ const Ecctrl = forwardRef < RapierRigidBody , EcctrlProps > ( ( {
24
25
children,
25
26
debug = false ,
26
27
capsuleHalfHeight = 0.35 ,
@@ -68,15 +69,28 @@ export default function Ecctrl({
68
69
autoBalance = true ,
69
70
autoBalanceSpringK = 0.3 ,
70
71
autoBalanceDampingC = 0.03 ,
72
+ autoBalanceSpringOnY = 0.3 ,
71
73
autoBalanceDampingOnY = 0.02 ,
72
74
// Animation temporary setups
73
75
animated = false ,
74
76
// Other rigibody props from parent
75
77
...props
76
- } : EcctrlProps ) {
77
- const characterRef = useRef < RapierRigidBody > ( ) ;
78
+ } : EcctrlProps , ref ) => {
79
+ const characterRef = ref || useRef < RapierRigidBody > ( )
78
80
const characterModelRef = useRef < THREE . Group > ( ) ;
79
81
82
+ /**
83
+ * Body collider setup
84
+ */
85
+ const modelFacingVec = useMemo ( ( ) => new THREE . Vector3 ( ) , [ ] ) ;
86
+ const bodyFacingVec = useMemo ( ( ) => new THREE . Vector3 ( ) , [ ] ) ;
87
+ const bodyBalanceVec = useMemo ( ( ) => new THREE . Vector3 ( ) , [ ] ) ;
88
+ const bodyBalanceVecOnX = useMemo ( ( ) => new THREE . Vector3 ( ) , [ ] ) ;
89
+ const bodyFacingVecOnY = useMemo ( ( ) => new THREE . Vector3 ( ) , [ ] ) ;
90
+ const bodyBalanceVecOnZ = useMemo ( ( ) => new THREE . Vector3 ( ) , [ ] ) ;
91
+ const vectorY = useMemo ( ( ) => new THREE . Vector3 ( 0 , 1 , 0 ) , [ ] ) ;
92
+ const vectorZ = useMemo ( ( ) => new THREE . Vector3 ( 0 , 0 , 1 ) , [ ] ) ;
93
+
80
94
// Animation change functions
81
95
const idleAnimation = ! animated ? null : useGame ( ( state ) => state . idle ) ;
82
96
const walkAnimation = ! animated ? null : useGame ( ( state ) => state . walk ) ;
@@ -327,6 +341,12 @@ export default function Ecctrl({
327
341
max : 0.1 ,
328
342
step : 0.001 ,
329
343
} ,
344
+ autoBalanceSpringOnY : {
345
+ value : autoBalanceSpringOnY ,
346
+ min : 0 ,
347
+ max : 5 ,
348
+ step : 0.01 ,
349
+ } ,
330
350
autoBalanceDampingOnY : {
331
351
value : autoBalanceDampingOnY ,
332
352
min : 0 ,
@@ -340,6 +360,7 @@ export default function Ecctrl({
340
360
autoBalance = autoBalanceForceDebug . autoBalance ;
341
361
autoBalanceSpringK = autoBalanceForceDebug . autoBalanceSpringK ;
342
362
autoBalanceDampingC = autoBalanceForceDebug . autoBalanceDampingC ;
363
+ autoBalanceSpringOnY = autoBalanceForceDebug . autoBalanceSpringOnY ;
343
364
autoBalanceDampingOnY = autoBalanceForceDebug . autoBalanceDampingOnY ;
344
365
}
345
366
@@ -562,15 +583,32 @@ export default function Ecctrl({
562
583
* Character auto balance function
563
584
*/
564
585
const autoBalanceCharacter = ( ) => {
586
+ bodyFacingVec . set ( 0 , 0 , 1 ) . applyQuaternion ( quat ( characterRef . current . rotation ( ) ) )
587
+ bodyBalanceVec . set ( 0 , 1 , 0 ) . applyQuaternion ( quat ( characterRef . current . rotation ( ) ) )
588
+
589
+ bodyBalanceVecOnX . set ( 0 , bodyBalanceVec . y , bodyBalanceVec . z )
590
+ bodyFacingVecOnY . set ( bodyFacingVec . x , 0 , bodyFacingVec . z )
591
+ bodyBalanceVecOnZ . set ( bodyBalanceVec . x , bodyBalanceVec . y , 0 )
592
+
593
+ characterModelRef . current . getWorldDirection ( modelFacingVec )
594
+ const crossVecOnX = vectorY . clone ( ) . cross ( bodyBalanceVecOnX ) ;
595
+ const crossVecOnY = vectorZ . clone ( ) . cross ( bodyFacingVecOnY ) ;
596
+ const crossVecOnZ = vectorY . clone ( ) . cross ( bodyBalanceVecOnZ ) ;
597
+
565
598
dragAngForce . set (
566
- - autoBalanceSpringK * characterRef . current . rotation ( ) . x -
567
- characterRef . current . angvel ( ) . x * autoBalanceDampingC ,
568
- - autoBalanceSpringK * characterRef . current . rotation ( ) . y -
569
- characterRef . current . angvel ( ) . y * autoBalanceDampingOnY ,
570
- - autoBalanceSpringK * characterRef . current . rotation ( ) . z -
571
- characterRef . current . angvel ( ) . z * autoBalanceDampingC
599
+ ( crossVecOnX . x < 0 ? 1 : - 1 ) *
600
+ autoBalanceSpringK * ( bodyBalanceVecOnX . angleTo ( vectorY ) )
601
+ - characterRef . current . angvel ( ) . x * autoBalanceDampingC ,
602
+ ( crossVecOnY . y < 0 ? 1 : - 1 ) *
603
+ autoBalanceSpringOnY * ( bodyFacingVecOnY . angleTo ( vectorZ ) )
604
+ - characterRef . current . angvel ( ) . y * autoBalanceDampingOnY ,
605
+ ( crossVecOnZ . z < 0 ? 1 : - 1 ) *
606
+ autoBalanceSpringK * ( bodyBalanceVecOnZ . angleTo ( vectorY ) )
607
+ - characterRef . current . angvel ( ) . z * autoBalanceDampingC ,
572
608
) ;
573
- characterRef . current . applyTorqueImpulse ( dragAngForce , false ) ;
609
+
610
+ // Apply balance torque impulse
611
+ characterRef . current . applyTorqueImpulse ( dragAngForce , false )
574
612
} ;
575
613
576
614
useEffect ( ( ) => {
@@ -1021,7 +1059,9 @@ export default function Ecctrl({
1021
1059
</ group >
1022
1060
</ RigidBody >
1023
1061
) ;
1024
- }
1062
+ } )
1063
+
1064
+ export default Ecctrl
1025
1065
1026
1066
export interface EcctrlProps extends RigidBodyProps {
1027
1067
children ?: ReactNode ;
@@ -1071,6 +1111,7 @@ export interface EcctrlProps extends RigidBodyProps {
1071
1111
autoBalance ?: boolean ;
1072
1112
autoBalanceSpringK ?: number ;
1073
1113
autoBalanceDampingC ?: number ;
1114
+ autoBalanceSpringOnY ?: number ;
1074
1115
autoBalanceDampingOnY ?: number ;
1075
1116
// Animation temporary setups
1076
1117
animated ?: boolean ;
0 commit comments