BVHEcctrl is a lightweight, plug-and-play character controller designed for react-three-fiber
. It requires no physics engine, and utilizes three-mesh-bvh
for highly efficient, triangle-accurate collision detection and response.
🧠 Ideal for web-based 3D experiences such as games, interactive portfolios, and virtual worlds.
- No physics engine required
- Fast BVH-based collision detection
- Smooth floating capsule movement for stairs, slopes, and uneven surfaces
- Built-in support for:
StaticCollider
KinematicCollider
InstancedStaticCollider
npm install bvhecctrl
import BVHEcctrl from "bvhecctrl";
import { KeyboardControls } from "@react-three/drei";
const keyboardMap = [
{ name: "forward", keys: ["ArrowUp", "KeyW"] },
{ name: "backward", keys: ["ArrowDown", "KeyS"] },
{ name: "leftward", keys: ["ArrowLeft", "KeyA"] },
{ name: "rightward", keys: ["ArrowRight", "KeyD"] },
{ name: "jump", keys: ["Space"] },
{ name: "run", keys: ["Shift"] },
];
return (
<KeyboardControls map={keyboardMap}>
<BVHEcctrl ref={ecctrlRef}>
<CharacterModel />
</BVHEcctrl>
</KeyboardControls>
);
import {
StaticCollider,
KinematicCollider,
InstancedStaticCollider,
} from "bvhecctrl";
useFrame((_, delta) => {
if (kinematicRef.current) kinematicRef.current.rotation.y += delta;
});
return (
<>
<StaticCollider>
<MapModel />
</StaticCollider>
<KinematicCollider ref={kinematicRef}>
<MapModel />
</KinematicCollider>
<InstancedStaticCollider>
<InstancedMapModel />
</InstancedStaticCollider>
</>
);
You can use any camera control system that fits your needs — such as <OrbitControls>
, <PointerLockControls>
, <CameraControls>
, or your own custom setup.
For smooth and flexible control, I highly recommend using <CameraControls>
from @react-three/drei
. It even offers built-in camera collision support.
Here’s a simple example using <CameraControls>
to follow the character:
useFrame(() => {
camControlRef.current.moveTo(
ecctrlRef.current.group.position.x,
ecctrlRef.current.group.position.y + 0.3,
ecctrlRef.current.group.position.z,
true
);
});
Prop | Type | Default | Description |
---|---|---|---|
debug |
boolean |
false |
Enables visual debugging. |
colliderCapsuleArgs |
[number, number, number, number] |
[0.3, 0.6, 4, 8] |
Capsule shape: [radius, height, capSegments, radialSegments]. |
paused |
boolean |
false |
Freezes character physics and collision response. |
delay |
number |
1.5 |
Physics delay after initialization (in seconds). |
gravity |
number |
9.81 |
World gravity. |
fallGravityFactor |
number |
4 |
Multiplier to increase falling speed. |
maxFallSpeed |
number |
50 |
Maximum fall velocity cap. |
mass |
number |
1 |
Virtual mass, affects float behavior. |
sleepTimeout |
number |
10 |
Time before character enters sleep state when idle. |
slowMotionFactor |
number |
1 |
Global time scaling (e.g., 0.5 = half-speed). |
turnSpeed |
number |
15 |
Turn speed when changing direction. |
maxWalkSpeed |
number |
3 |
Max speed when walking. |
maxRunSpeed |
number |
5 |
Max speed when running. |
acceleration |
number |
26 |
Speed increase per second. |
deceleration |
number |
15 |
Speed decrease when input is released. |
counterVelFactor |
number |
1.5 |
Extra velocity when changing direction. |
airDragFactor |
number |
0.3 |
Simulated drag while in air. |
jumpVel |
number |
5 |
Upward velocity when jumping. |
maxSlope |
number |
1 |
Max walkable surface slope (in radians). |
floatHeight |
number |
0.2 |
Desired float height above ground. |
floatPullBackHeight |
number |
0.25 |
Additional distance below ground used to pull character down. |
floatSensorRadius |
number |
0.12 |
Radius of the sensor used to sense the ground. |
floatSpringK |
number |
1600 |
Spring constant for floating. |
floatDampingC |
number |
60 |
Damping factor for float spring (prevents oscillation). |
collisionCheckIteration |
number |
3 |
Number of collision check per frame. |
collisionPushBackVelocity |
number |
3 |
Extra velocity applied to resolve penetration. |
collisionPushBackDamping |
number |
0.1 |
Damping applied to the pushback force. |
collisionPushBackThreshold |
number |
0.001 |
Minimum depth before pushback activates. |
Prop | Type | Default | Description |
---|---|---|---|
debug |
boolean |
false |
Shows BVH bounds for debugging. |
debugVisualizeDepth |
number |
10 |
Depth level of BVH to visualize (debug only). |
restitution |
number |
0.05 |
Bounciness on impact. |
friction |
number |
0.8 |
Surface friction against character movement. |
excludeFloatHit |
boolean |
false |
If true, this collider is ignored in float sensor checks. |
excludeCollisionCheck |
boolean |
false |
If true, this collider is excluded from collision detection. |
BVHOptions |
object |
(see below) | Configuration for building the internal BVH tree. |
Default BVHOptions:
{
strategy: SAH,
verbose: false,
setBoundingBox: true,
maxDepth: 40,
maxLeafTris: 10,
indirect: false
}
// Access the character group (THREE.Group)
ecctrlRef.current.group;
// Reset current velocity to zero
ecctrlRef.current.resetLinVel();
// Set linear velocity directly
ecctrlRef.current.setLinVel(THREE.Vector3);
// Set character movement, can have one or more inputs
ecctrlRef.current.setMovement({
forward: boolean,
backward: boolean,
leftward: boolean,
rightward: boolean,
joystick: THREE.Vector2,
run: boolean,
jump: boolean,
});
import { characterStatus } from "bvhecctrl";
characterStatus.position; // THREE.Vector3
characterStatus.linvel; // THREE.Vector3
characterStatus.quaternion; // THREE.Quaternion
characterStatus.inputDir; // THREE.Vector3
characterStatus.movingDir; // THREE.Vector3
characterStatus.isOnGround; // boolean
characterStatus.isOnMovingPlatform; // boolean
characterStatus.animationStatus; // "IDLE" | "WALK" | "RUN" | "JUMP_START" | "JUMP_IDLE" | "JUMP_FALL" | "JUMP_LAND"
If you find BVHEcctrl helpful, please consider supporting the project.
Your support means a lot and will help me dedicate more time to development, improvements, and documentation.
Every bit of support helps! Whether it’s a donation, a star, or simply sharing the project. Thank you for helping me keep this project moving forward! 🙏
I’m considering offering additional features or paid extensions.
If you're interested, feel free to vote or leave feedback:
- A vanilla three.js version of BVHEcctrl
- Custom gravity support (move on spheres)
- Tutorials on building this controller from scratch
- More experimental features and playground demos
💬 Leave your thoughts on GitHub or join the discussion on the pmndrs Discord.