Skip to content

Commit

Permalink
feat: refactor and improve physics + clean error messages
Browse files Browse the repository at this point in the history
  • Loading branch information
Gugustinette committed Aug 26, 2024
1 parent c67ac15 commit bb999d5
Show file tree
Hide file tree
Showing 19 changed files with 195 additions and 126 deletions.
36 changes: 26 additions & 10 deletions apps/playground-3d/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ import MyCustomCube from './classes/MyCustomCube'
name: 'block-grass-corner',
position: { x: -10, y: -1, z: -7 },
scale: { x: 4, y: 4, z: 4 },
rotationDegree: { x: 0, y: -45, z: 0 },
})
blockGrassCorner.initCollider({
position: { x: 0, y: 1, z: 0 },
scale: { x: 2, y: 2, z: 2 },
scale: { x: 0.5, y: 0.5, z: 0.5 },
rotationDegree: { x: 0, y: 45, z: 0 },
})
scene.addComponent(blockGrassCorner)

Expand All @@ -47,24 +49,38 @@ import MyCustomCube from './classes/MyCustomCube'

const blockGrassLargeSlope = new FGLB(scene, {
name: 'block-grass-large-slope',
position: { x: -12, y: 0, z: -4 },
position: { x: -8.5, y: -1, z: -4 },
scale: { x: 2, y: 2, z: 2 },
rotationDegree: { x: 0, y: 90, z: 0 },
rotationDegree: { x: 0, y: -90, z: 0 },
})
blockGrassLargeSlope.initCollider({
position: { x: 0, y: 0.5, z: 0 },
scale: { x: 2, y: 1, z: 2 },
blockGrassLargeSlope.onLoaded(() => {
blockGrassLargeSlope.initCollider({
shape: F3dShapes.MESH,
})
})
scene.addComponent(blockGrassLargeSlope)

const blockGrassLargeSlope2 = new FGLB(scene, {
name: 'block-grass-large-slope',
position: { x: -13, y: -2, z: -3 },
scale: { x: 2, y: 2, z: 2 },
rotationDegree: { x: 0, y: -90, z: 0 },
})
blockGrassLargeSlope2.onLoaded(() => {
blockGrassLargeSlope2.initCollider({
shape: F3dShapes.MESH,
})
})
scene.addComponent(blockGrassLargeSlope2)

const blockGrassLarge = new FFBX(scene, {
name: 'block-grass-large',
position: { x: -12, y: -5, z: 0 },
scale: { x: 4, y: 4, z: 4 },
position: { x: -14, y: -8, z: 0 },
scale: { x: 8, y: 8, z: 8 },
})
blockGrassLarge.initCollider({
position: { x: 0, y: 1, z: 0 },
scale: { x: 4, y: 2, z: 4 },
position: { x: 0, y: 2, z: 0 },
scale: { x: 1, y: 0.5, z: 1 },
})
scene.addComponent(blockGrassLarge)

Expand Down
10 changes: 5 additions & 5 deletions packages/2d/src/FComponent2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export abstract class FComponent2d extends FComponent {
options = { ...DEFAULT_OPTIONS, ...options }
// Validate options
if (!options.position || !options.scale || (options.rotation === undefined && options.rotationDegree === undefined))
throw new Error('FComponent2d requires position, scale and rotation options')
throw new Error('FibboError: FComponent2d requires position, scale and rotation options')

// Set the transform values
this.position = options.position
Expand Down Expand Up @@ -262,11 +262,11 @@ export abstract class FComponent2d extends FComponent {
options = { ...DEFAULT_OPTIONS, ...options }
// Validate options
if (!options.position || !options.scale || options.rotation === undefined || !options.shape)
throw new Error('initRigidBody requires position, scale, rotation and shape options')
throw new Error('FibboError: initRigidBody requires position, scale, rotation and shape options')

// Check if the world exists
if (!this.scene.world)
throw new Error('FScene must have a world to create a rigid body')
throw new Error('FibboError: FScene must have a world to create a rigid body')

// Create a rigid body description according to the type
const rigidBodyDesc = new RAPIER.RigidBodyDesc(options.rigidBodyType as RAPIER.RigidBodyType)
Expand Down Expand Up @@ -323,11 +323,11 @@ export abstract class FComponent2d extends FComponent {
options = { ...DEFAULT_OPTIONS, ...options }
// Validate options
if (!options.position || !options.scale || options.rotation === undefined || !options.shape)
throw new Error('initCollider requires position, scale, rotation and shape options')
throw new Error('FibboError: initCollider requires position, scale, rotation and shape options')

// Check if the world exists
if (!this.scene.world)
throw new Error('FScene must have a world to create a collider')
throw new Error('FibboError: FScene must have a world to create a collider')

// Create a collider description
const colliderDesc = options.shape === F2dShapes.SQUARE
Expand Down
4 changes: 2 additions & 2 deletions packages/2d/src/FScene2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export class FScene2d extends FScene {

// Verify window and document are available
if (typeof window === 'undefined' || typeof document === 'undefined')
throw new Error('FScene must be instantiated in a browser environment')
throw new Error('FibboError: FScene must be instantiated in a browser environment')

// Create a new PIXI application
this.app = new PIXI.Application()
Expand All @@ -71,7 +71,7 @@ export class FScene2d extends FScene {
options = { ...DEFAULT_OPTIONS, ...options }
// Validate the options
if (options.domNode === undefined || options.gravity === undefined)
throw new Error('The gravity option must be defined')
throw new Error('FibboError: The gravity option must be defined')

// Store the DOM node
this.__DOM_NODE__ = options.domNode
Expand Down
2 changes: 1 addition & 1 deletion packages/2d/src/cameras/FCamera2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export abstract class FCamera2d extends FCamera {
options = { ...DEFAULT_OPTIONS, ...options }
// Validate options
if (!options.position)
throw new Error('FCamera2d requires position')
throw new Error('FibboError: FCamera2d requires position')

// Store options
this.position = options.position
Expand Down
2 changes: 1 addition & 1 deletion packages/2d/src/character/FCharacter2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export abstract class FCharacter2d extends FComponent2d {
options = { ...DEFAULT_OPTIONS, ...options }
// Validate options
if (!options.speed)
throw new Error('FCharacter2d requires speed option')
throw new Error('FibboError: FCharacter2d requires speed option')

// Store speed
this.speed = options.speed
Expand Down
91 changes: 66 additions & 25 deletions packages/3d/src/FCollider3d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface FCollider3dOptions {
position?: { x: number, y: number, z: number }
scale?: { x: number, y: number, z: number }
rotation?: { x: number, y: number, z: number }
rotationDegree?: { x: number, y: number, z: number }
shape?: F3dShapes
rigidBody?: RAPIER.RigidBody
sensor?: boolean
Expand All @@ -23,10 +24,15 @@ export class FCollider3d {
*/
collider: RAPIER.Collider
/**
* Offset for the collider.
* Position Offset for the collider.
* This is used to adjust the collider position relative to the mesh.
*/
colliderOffset: THREE.Vector3
colliderPositionOffset: THREE.Vector3
/**
* Rotation Offset for the collider.
* This is used to adjust the collider position relative to the mesh.
*/
colliderRotationOffset: THREE.Vector3

/**
* @description Creates a collider for a given component.
Expand All @@ -35,6 +41,7 @@ export class FCollider3d {
* @param options.position The position of the collider. If not defined, it will use the default position of the FComponent3d.
* @param options.scale The scale of the collider. If not defined, it will use the default scale of the FComponent3d.
* @param options.rotation The rotation of the collider. If not defined, it will use the default rotation of the FComponent3d.
* @param options.rotationDegree The rotation of the collider in degrees. If not defined, it will default to 0.
* @param options.shape The shape of the collider. If not defined, it will default to F3dShapes.CUBE.
* @param options.rigidBody The rigid body to attach the collider to. (optional)
* @param options.sensor If true, the collider will be a sensor.
Expand All @@ -51,27 +58,41 @@ export class FCollider3d {
constructor(component: FComponent3d, options?: FCollider3dOptions) {
// Apply default options
const DEFAULT_OPTIONS = {
position: new THREE.Vector3(0, 0, 0),
scale: new THREE.Vector3(component.scale.x, component.scale.y, component.scale.z),
rotation: new THREE.Vector3(component.rotation.x, component.rotation.y, component.rotation.z),
position: { x: 0, y: 0, z: 0 },
scale: { x: 1, y: 1, z: 1 },
rotation: { x: 0, y: 0, z: 0 },
rotationDegree: undefined,
shape: F3dShapes.CUBE,
rigidBody: undefined,
sensor: false,
}
options = { ...DEFAULT_OPTIONS, ...options }
// Validate options
if (!options.position || !options.scale || !options.rotation || !options.shape || options.sensor === undefined)
throw new Error('initCollider requires position, scale, rotation and shape options')
throw new Error('FibboError: initCollider requires position, scale, rotation and shape options')

// Check if the world exists
if (!component.scene.world)
throw new Error('FScene must have a world to create a rigid body')
throw new Error('FibboError: FScene must have a world to create a rigid body')

// 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)
}

// Store the collider offset
this.colliderOffset = new THREE.Vector3(options.position.x, options.position.y, options.position.z)
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)

// Devide the scale by 2 for the collider
options.scale = new THREE.Vector3(options.scale.x / 2, options.scale.y / 2, options.scale.z / 2)
// Devide the scale by 2 for the collider (RAPIER uses half-extents)
// Also interpete the scale as relative to the component's scale
options.scale = {
x: component.scale.x * (options.scale.x / 2),
y: component.scale.y * (options.scale.y / 2),
z: component.scale.z * (options.scale.z / 2),
}

// Create a collider description according to the shape given
let colliderDesc
Expand All @@ -86,19 +107,34 @@ export class FCollider3d {
colliderDesc = RAPIER.ColliderDesc.capsule(options.scale.x, options.scale.y)
break
case F3dShapes.MESH:
if (component.mesh instanceof THREE.Mesh) {
colliderDesc = RAPIER.ColliderDesc.trimesh(
component.mesh?.geometry.attributes.position.array as Float32Array,
component.mesh?.geometry.index?.array as Uint32Array,
)
}
else {
throw new TypeError('Mesh collider can only be created from a THREE.Mesh')
}
// If component.mesh isn't defined, throw an error
{
if (!component.mesh)
throw new Error('FibboError: Mesh collider can only be created from a THREE.Mesh')
// Flag to check if a THREE.Mesh was found
let found = false
// Traverse the mesh tree until we find a THREE.Mesh
component.mesh.traverse((child) => {
if (!found && child instanceof THREE.Mesh) {
colliderDesc = RAPIER.ColliderDesc.trimesh(
child.geometry.attributes.position.array as Float32Array,
child.geometry.index?.array as Uint32Array,
)
found = true
}
})
// If no THREE.Mesh was found, throw an error
if (!found)
throw new Error('FibboError: Mesh collider can only be created if a THREE.Mesh is found in the component')
break
}
default:
throw new Error(`Shape not supported : ${options.shape}`)
throw new Error(`FibboError: shape not supported : ${options.shape}`)
}
// Verify the collider description was created
if (!colliderDesc)
throw new Error('FibboError: Collider description could not be created')

// If no rigidbody given, the collider is free : set translation and rotation for the collider
if (options.rigidBody === undefined) {
// Interprete the given position as relative to the component's position
Expand All @@ -107,13 +143,18 @@ export class FCollider3d {
component.position.y + options.position.y,
component.position.z + options.position.z,
)
finalPosition.add(this.colliderOffset)

finalPosition.add(this.colliderPositionOffset)
colliderDesc.setTranslation(finalPosition.x, finalPosition.y, finalPosition.z)
colliderDesc.setRotation(
// Create quaternion from Euler angles
new THREE.Quaternion().setFromEuler(new THREE.Euler(options.rotation.x, options.rotation.y, options.rotation.z)),

// 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,
),
)
colliderDesc.setRotation(finalRotation)
}
// Set the sensor flag
colliderDesc.setSensor(options.sensor)
Expand Down
Loading

0 comments on commit bb999d5

Please sign in to comment.