Skip to content

Commit

Permalink
feat: physics improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Gugustinette committed Aug 28, 2024
1 parent 33d913e commit b35a591
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 67 deletions.
4 changes: 2 additions & 2 deletions apps/playground-3d/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ import MyCustomCube from './classes/MyCustomCube'
// Add "stairs"
for (let i = 0; i < 10; i++) {
const cube = new FCube(scene, {
scale: { x: 2, y: 1, z: 1 },
position: { x: 6, y: i / 4 - 0.5, z: -i },
scale: { x: 2, y: 0.2, z: 1 },
position: { x: 6, y: i / 4, z: -i },
})
cube.initCollider()
cube.setColor(0x1F1F1F)
Expand Down
28 changes: 15 additions & 13 deletions packages/3d/src/FCollider3d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ export class FCollider3d {
* Position Offset for the collider.
* This is used to adjust the collider position relative to the mesh.
*/
colliderPositionOffset: THREE.Vector3
colliderPositionOffset: { x: number, y: number, z: number }
/**
* Rotation Offset for the collider.
* This is used to adjust the collider position relative to the mesh.
*/
colliderRotationOffset: THREE.Vector3
colliderRotationOffset: { x: number, y: number, z: number }

/**
* @description Creates a collider for a given component.
Expand All @@ -48,9 +48,9 @@ export class FCollider3d {
* @example
* ```ts
* component.initCollider(scene, component, {
* position: new THREE.Vector3(0, 1, 0),
* scale: new THREE.Vector3(1, 1, 1),
* rotation: new THREE.Vector3(0, 0, 0),
* position: { x: 0, y: 1, z: 0 },
* scale: { x: 1, y: 1, z: 1 },
* rotation: { x: 0, y: 0, z: 0 },
* shape: F3dShapes.CUBE
* })
* ```
Expand All @@ -77,14 +77,16 @@ export class FCollider3d {

// If rotation degree is given, convert it to radians
if (options.rotationDegree) {
options.rotation.x = THREE.MathUtils.degToRad(options.rotationDegree.x)
options.rotation.y = THREE.MathUtils.degToRad(options.rotationDegree.y)
options.rotation.z = THREE.MathUtils.degToRad(options.rotationDegree.z)
options.rotation = {
x: THREE.MathUtils.degToRad(options.rotationDegree.x),
y: THREE.MathUtils.degToRad(options.rotationDegree.y),
z: THREE.MathUtils.degToRad(options.rotationDegree.z),
}
}

// Store the collider offset
this.colliderPositionOffset = new THREE.Vector3(options.position.x, options.position.y, options.position.z)
this.colliderRotationOffset = new THREE.Vector3(options.rotation.x, options.rotation.y, options.rotation.z)
this.colliderPositionOffset = { x: options.position.x, y: options.position.y, z: options.position.z }
this.colliderRotationOffset = { x: options.rotation.x, y: options.rotation.y, z: options.rotation.z }

// Devide the scale by 2 for the collider (RAPIER uses half-extents)
// Also interpete the scale as relative to the component's scale
Expand Down Expand Up @@ -149,9 +151,9 @@ export class FCollider3d {
// Interprete the given rotation as relative to the component's rotation
const finalRotation = new THREE.Quaternion().setFromEuler(
new THREE.Euler(
component.rotation.x + options.rotation.x / 2,
component.rotation.y + options.rotation.y / 2,
component.rotation.z + options.rotation.z / 2,
component.rotation.x + options.rotation.x,
component.rotation.y + options.rotation.y,
component.rotation.z + options.rotation.z,
),
)
colliderDesc.setRotation(finalRotation)
Expand Down
23 changes: 20 additions & 3 deletions packages/3d/src/FComponent3d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,32 @@ export abstract class FComponent3d extends FComponent {
// If the collider and mesh exist, update the mesh position and rotation according to the collider
else if (this.collider && this.mesh) {
// Get the new transforms from the collider

// Translation
const newMeshPosition = this.collider.collider.translation()
// Remove offset
newMeshPosition.x -= this.collider.colliderPositionOffset.x
newMeshPosition.y -= this.collider.colliderPositionOffset.y
newMeshPosition.z -= this.collider.colliderPositionOffset.z
const newMeshRotation = this.collider.collider.rotation()

// Rotation
const newMeshRotation = new THREE.Euler().setFromQuaternion(
new THREE.Quaternion(
this.collider.collider.rotation().x,
this.collider.collider.rotation().y,
this.collider.collider.rotation().z,
this.collider.collider.rotation().w,
),
)
// Remove offset
newMeshRotation.x -= this.collider.colliderRotationOffset.x
newMeshRotation.y -= this.collider.colliderRotationOffset.y
newMeshRotation.z -= this.collider.colliderRotationOffset.z

// Apply the new transforms to the mesh
this.mesh.position.set(newMeshPosition.x, newMeshPosition.y, newMeshPosition.z)
this.mesh.setRotationFromQuaternion(new THREE.Quaternion(newMeshRotation.x, newMeshRotation.y, newMeshRotation.z, newMeshRotation.w))
// Update position and rotation properties of the component according to the collider
this.mesh.setRotationFromEuler(newMeshRotation)
// Update position and rotation properties of the component
this.position.set(newMeshPosition.x, newMeshPosition.y, newMeshPosition.z)
this.rotation.set(newMeshRotation.x, newMeshRotation.y, newMeshRotation.z)
}
Expand Down
4 changes: 4 additions & 0 deletions packages/3d/src/cameras/FGameCamera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ export class FGameCamera extends FOrbitCamera {
const movementY = this.lastMouseMoveEvent.movementY || 0

// Rotate the camera based on mouse movement
/**
* Let's be honest, I don't know why this works.
* But it does.
*/
this.translateX(-movementX * 0.01)
this.translateY(movementY * 0.01)
this.lastMouseMoveEvent = undefined
Expand Down
2 changes: 1 addition & 1 deletion packages/3d/src/character/FCharacter3d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export abstract class FCharacter3d extends FComponent3d {

initSensor(options?: FCollider3dOptions): void {
super.initSensor({
scale: new THREE.Vector3(1.1, 1.1, 1.1),
scale: { x: 1.1, y: 1.1, z: 1.1 },
shape: F3dShapes.CAPSULE,
...options,
})
Expand Down
102 changes: 54 additions & 48 deletions packages/3d/src/character/FCharacter3dKP.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as THREE from 'three'
import RAPIER from '@dimforge/rapier3d'
import RAPIER, { QueryFilterFlags } from '@dimforge/rapier3d'
import { FKeyboard } from '@fibbojs/event'
import type { FScene3d } from '../FScene3d'
import type { FComponent3dOptions } from '../FComponent3d'
Expand All @@ -20,61 +20,18 @@ import { FCharacter3dKinematic } from './FCharacter3dKinematic'
* ```
*/
export class FCharacter3dKP extends FCharacter3dKinematic {
yVelocity: number

constructor(scene: FScene3d, options?: FComponent3dOptions) {
super(scene, options)

let yVelocity = scene.world.gravity.y

/**
* Handle movements on each frame (gravity + character movement)
* For some reason, using the onFrame method will result in weird behavior with gravity
* (e.g. the character crossing the ground)
*/
scene.onFrame((delta) => {
let worldDirection = new THREE.Vector3(0, 0, 0)
// Compute the movement direction
worldDirection.x = this.inputs.left ? 1 : this.inputs.right ? -1 : 0
worldDirection.z = this.inputs.forward ? 1 : this.inputs.backward ? -1 : 0
// Normalize the movement direction
worldDirection = worldDirection.normalize()
// Apply the camera direction to the movement direction
const cameraDirection = scene.camera.getCameraDirection()
worldDirection.applyAxisAngle(new THREE.Vector3(0, 1, 0), Math.atan2(cameraDirection.x, cameraDirection.z))

// Create movement vector
const desiredMovement = {
x: worldDirection.x * delta * 8,
y: yVelocity * delta,
z: worldDirection.z * delta * 8,
}
// Compute the desired movement
this.characterController.computeColliderMovement(
this.collider?.collider as RAPIER.Collider,
desiredMovement,
)
// Get the corrected movement
const correctedMovement = this.characterController.computedMovement()
// Apply the movement to the rigid body
this.rigidBody?.rigidBody.setNextKinematicTranslation({
x: this.rigidBody.rigidBody.translation().x + correctedMovement.x * delta * this.speed * 64,
y: this.rigidBody.rigidBody.translation().y + correctedMovement.y * delta * this.speed * 64,
z: this.rigidBody.rigidBody.translation().z + correctedMovement.z * delta * this.speed * 64,
})

// If yVelocity is not 0, apply gravity
if (yVelocity > scene.world.gravity.y) {
yVelocity += scene.world.gravity.y * delta * 4
}
else {
yVelocity = scene.world.gravity.y
}
})
this.yVelocity = scene.world.gravity.y

// Create a keyboard instance
const fKeyboard = new FKeyboard(scene)
// Bind the keyboard events
fKeyboard.on(' ', () => {
yVelocity = 10
this.yVelocity = 10
})

// Initialize the rigid body
Expand All @@ -83,6 +40,55 @@ export class FCharacter3dKP extends FCharacter3dKinematic {
this.initSensor()
}

onFrame(delta: number): void {
/**
* Handle movements on each frame (gravity + character movement)
*/

let worldDirection = new THREE.Vector3(0, 0, 0)
// Compute the movement direction
worldDirection.x = this.inputs.left ? 1 : this.inputs.right ? -1 : 0
worldDirection.z = this.inputs.forward ? 1 : this.inputs.backward ? -1 : 0
// Normalize the movement direction
worldDirection = worldDirection.normalize()
// Apply the camera direction to the movement direction
const cameraDirection = this.scene.camera.getCameraDirection()
worldDirection.applyAxisAngle(new THREE.Vector3(0, 1, 0), Math.atan2(cameraDirection.x, cameraDirection.z))

// Create movement vector
const desiredMovement = {
x: worldDirection.x * delta * 8,
y: this.yVelocity * delta,
z: worldDirection.z * delta * 8,
}
// Compute the desired movement
this.characterController.computeColliderMovement(
this.collider?.collider as RAPIER.Collider,
desiredMovement,
// I don't why this flag works, I would expect QueryFilterFlags.EXCLUDE_SENSORS to work but anyway
QueryFilterFlags.EXCLUDE_SOLIDS,
)
// Get the corrected movement
const correctedMovement = this.characterController.computedMovement()
// Apply the movement to the rigid body
this.rigidBody?.rigidBody.setNextKinematicTranslation({
x: this.rigidBody.rigidBody.translation().x + correctedMovement.x * delta * this.speed * 64,
y: this.rigidBody.rigidBody.translation().y + correctedMovement.y * delta * this.speed * 64,
z: this.rigidBody.rigidBody.translation().z + correctedMovement.z * delta * this.speed * 64,
})

// If yVelocity is not 0, apply gravity
if (this.yVelocity > this.scene.world.gravity.y) {
this.yVelocity += this.scene.world.gravity.y * delta * 4
}
else {
this.yVelocity = this.scene.world.gravity.y
}

// Call the super onFrame method
super.onFrame(delta)
}

initRigidBody(options?: FRigidBody3dOptions): void {
super.initRigidBody({
rigidBodyType: RAPIER.RigidBodyType.KinematicPositionBased,
Expand Down

0 comments on commit b35a591

Please sign in to comment.