From e3ec76f8c4a29d2958ff6b15adc745e203f49e56 Mon Sep 17 00:00:00 2001 From: Jaime A Torrealba C <63722373+JaimeTorrealba@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:25:48 +0100 Subject: [PATCH] feat(app): 119 Add props to rigidbody (#129) * feat(app): 119 Add props to rigidbody * fix: fix set initial props, docs, types, demos. Add additionalMass * fix: move types * refactor(core): watcher types accessibility ### Description - Add object types support - `NonNever` - `Methods` - `CallableProps` - Improve `makePropWatcherRB` types accessibility - Improve `makePropsWatcherRB` types accessibility Co-Authored-By: Jaime A Torrealba C <63722373+JaimeTorrealba@users.noreply.github.com> --------- Co-authored-by: Nathan Mande --- docs/components/rigid-body.md | 88 ++++++++++----- docs/guide/how-does-it-work.md | 5 + package.json | 2 +- .../src/pages/basics/ApplyingForcesDemo.vue | 4 - .../src/pages/basics/RigidBodyProps.vue | 103 ++++++++++++++++++ playground/src/router/routes/basics.ts | 5 + src/components/RigidBody.vue | 37 ++++++- src/types/boolean.ts | 1 + src/types/index.ts | 2 + src/types/object.ts | 11 ++ src/types/rigid-body.ts | 60 +++++++++- src/utils/props.ts | 30 +++++ 12 files changed, 314 insertions(+), 34 deletions(-) create mode 100644 playground/src/pages/basics/RigidBodyProps.vue create mode 100644 src/types/boolean.ts create mode 100644 src/types/object.ts create mode 100644 src/utils/props.ts diff --git a/docs/components/rigid-body.md b/docs/components/rigid-body.md index 77339ba..3a2b65c 100644 --- a/docs/components/rigid-body.md +++ b/docs/components/rigid-body.md @@ -1,16 +1,23 @@ # RigidBody -:::info -The information in this page is a summary of the RigidBody instance, please check the [complete documentation](https://rapier.rs/docs/user_guides/javascript/rigid_bodies) for more info -::: +:::info The information in this page is a summary of the RigidBody instance, +please check the +[complete documentation](https://rapier.rs/docs/user_guides/javascript/rigid_bodies) +for more info ::: -The real-time simulation of rigid-bodies subjected to forces and contacts is the main feature of a physics engine for video-games, robotics, or animation. +The real-time simulation of rigid-bodies subjected to forces and contacts is the +main feature of a physics engine for video-games, robotics, or animation. -`@tresjs/rapier` provides a `RigidBody` component compatible with the `Tresjs` ecosystem, with the advantage of making the "bound" between the two worlds (physic world and our 3D scene). +`@tresjs/rapier` provides a `RigidBody` component compatible with the `Tresjs` +ecosystem, with the advantage of making the "bound" between the two worlds +(physic world and our 3D scene). ## Basic usage -To use a `RigidBody` component, the best case is to import it from `@tresjs/rapier` and then pass as [slot](https://vuejs.org/guide/components/slots.html#scoped-slots) the element that you want to attach. +To use a `RigidBody` component, the best case is to import it from +`@tresjs/rapier` and then pass as +[slot](https://vuejs.org/guide/components/slots.html#scoped-slots) the element +that you want to attach. ```html @@ -26,6 +33,7 @@ To use a `RigidBody` component, the best case is to import it from `@tresjs/rapi We can specify what kind of `RigidBody` type. `Dynamic` is the default. A basic floor example with type fixed: + ```html @@ -37,24 +45,29 @@ A basic floor example with type fixed: ### Available types -| Prop | Description | -| :--------------- | :--------------------------------------------------- | -| `Dynamic` | Indicates that the body is affected by external forces and contacts. | -| `Fixed` | Indicates the body cannot move. It acts as if it has an infinite mass and will not be affected by any force. | -| `KinematicPositionBased` | Indicates that the body position must not be altered by the physics engine. | -| `KinematicVelocityBased` | Indicates that the body velocity must not be altered by the physics engine.| +| Prop | Description | +| :----------------------- | :----------------------------------------------------------------------------------------------------------- | +| `Dynamic` | Indicates that the body is affected by external forces and contacts. | +| `Fixed` | Indicates the body cannot move. It acts as if it has an infinite mass and will not be affected by any force. | +| `KinematicPositionBased` | Indicates that the body position must not be altered by the physics engine. | +| `KinematicVelocityBased` | Indicates that the body velocity must not be altered by the physics engine. | -:::info -Both position-based and velocity-based kinematic bodies are mostly the same. Choosing between both is mostly a matter of preference between position-based control and velocity-based control. -::: +:::info Both position-based and velocity-based kinematic bodies are mostly the +same. Choosing between both is mostly a matter of preference between +position-based control and velocity-based control. ::: -More info at [Rigid-body type](https://rapier.rs/docs/user_guides/javascript/rigid_bodies#rigid-body-type) +More info at +[Rigid-body type](https://rapier.rs/docs/user_guides/javascript/rigid_bodies#rigid-body-type) -## Automatic Colliders +## Automatic Colliders -`RigidBody` comes with automatic colliders, if you need a custom Collider please check [Colliders components](/components/collider), you can specify a set of pre-defined colliders in order to fit the mesh with the best shape possible. `cuboid` is the default. +`RigidBody` comes with automatic colliders, if you need a custom Collider please +check [Colliders components](/components/collider), you can specify a set of +pre-defined colliders in order to fit the mesh with the best shape possible. +`cuboid` is the default. A basic example, a ball falling down: + ```html{1} @@ -62,13 +75,16 @@ A basic example, a ball falling down: - ``` + ### Available Automatic Colliders ## Applying forces -To use methods (like applying forces or impulses) you first need to access the element using [template ref](https://vuejs.org/guide/essentials/template-refs.html#template-refs). Then access to the `instance` +To use methods (like applying forces or impulses) you first need to access the +element using +[template ref](https://vuejs.org/guide/essentials/template-refs.html#template-refs). +Then access to the `instance` Basic example, making the cube jump with one click: @@ -82,8 +98,6 @@ const rigidCubeRef = shallowRef(null) const jumpCube = () => { if (rigidCubeRef.value) { - // if you mass is 1 your object will not move - rigidCubeRef.value.rigidBodyInfos.rigidBodyDesc.mass = 5 rigidCubeRef.value.instance.applyImpulse({ x: 0, y: 15, z: 0 }, true) } } @@ -106,7 +120,8 @@ const jumpCube = () => { ``` -More info [Forces and Impulses](https://rapier.rs/docs/user_guides/javascript/rigid_bodies#forces-and-impulses) +More info +[Forces and Impulses](https://rapier.rs/docs/user_guides/javascript/rigid_bodies#forces-and-impulses) ## Collisions @@ -118,12 +133,35 @@ SOON ## Props +| Prop | Description | Default | +| :---------------------- | :----------------------------------- | ------------------------------ | +| **type** | `rigidBody` type | `dynamic` | +| **collider** | `automatic collider | `cuboid` | +| **gravityScale** | gravity for the `rigidBody` | `1` | +| **additionalMass** | add extra mass to the `rigidBody` | `0` | +| **linearDamping** | set the linear damping | `0` | +| **angularDamping** | set the angular damping | `0` | +| **dominanceGroup** | set the dominance group | `0` | +| **linvel** | linear velocity | `x: 0, y: 0, z: 0` | +| **angvel** | angular velocity | `x: 0, y: 0, z: 0` | +| **enabledRotations** | enable rotations in specific axis | `{x: true, y: true, z: true }` | +| **enabledTranslations** | enable translations in specific axis | `{x: true, y: true, z: true }` | +| **lockTranslations** | Lock all translations | `false` | +| **lockRotations** | Lock all rotations | `false` | + +:::info The `rigidBody` instance has many other functions, please check the +[official docs](https://rapier.rs/docs/api/javascript/JavaScript3D/) for a +complete list, if you need them, you can +use[Template ref](https://vuejs.org/guide/essentials/template-refs.html#template-refs). +::: + ## Expose object + ``` { instance: rigidBodyInstance, - rigidBodyInfos, - collider: colliderInfos, + rigidBodyDesc, + context: colliderInfos, group: parentObject, } ``` diff --git a/docs/guide/how-does-it-work.md b/docs/guide/how-does-it-work.md index 17d7265..17527b4 100644 --- a/docs/guide/how-does-it-work.md +++ b/docs/guide/how-does-it-work.md @@ -1 +1,6 @@ # How does it work? + +## Caveats + +- Rapier set object to sleep... +- HMR sometimes work unexpected prefer reload page (unles reactive property is set) diff --git a/package.json b/package.json index 02b7bef..965b354 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "peerDependencies": { "@tresjs/core": ">=4.0.0", "three": ">=0.158.0", - "vue": ">=3.3.8" + "vue": ">=3.4.0" }, "dependencies": { "@dimforge/rapier3d-compat": "^0.14.0" diff --git a/playground/src/pages/basics/ApplyingForcesDemo.vue b/playground/src/pages/basics/ApplyingForcesDemo.vue index 49e1d5d..6e08067 100644 --- a/playground/src/pages/basics/ApplyingForcesDemo.vue +++ b/playground/src/pages/basics/ApplyingForcesDemo.vue @@ -18,8 +18,6 @@ const rigidSphereRef = shallowRef(null) const jumpCube = () => { if (!rigidCubeRef.value) { return } - - rigidCubeRef.value.rigidBodyDesc.mass = 5 rigidCubeRef.value.instance.applyImpulse({ x: 0, y: 15, z: 0 }, true) } @@ -27,8 +25,6 @@ const windSphere = () => { if (!rigidSphereRef.value) { return } - - rigidSphereRef.value.rigidBodyDesc.mass = 5 rigidSphereRef.value.instance.applyImpulse({ x: 5, y: 0, z: 0 }, true) } diff --git a/playground/src/pages/basics/RigidBodyProps.vue b/playground/src/pages/basics/RigidBodyProps.vue new file mode 100644 index 0000000..a624f00 --- /dev/null +++ b/playground/src/pages/basics/RigidBodyProps.vue @@ -0,0 +1,103 @@ + + + diff --git a/playground/src/router/routes/basics.ts b/playground/src/router/routes/basics.ts index ca964f3..146c6b3 100644 --- a/playground/src/router/routes/basics.ts +++ b/playground/src/router/routes/basics.ts @@ -24,4 +24,9 @@ export const basicsRoutes = [ name: 'Gravity', component: () => import('../../pages/basics/GravityDemo.vue'), }, + { + path: '/basics/props', + name: 'Props', + component: () => import('../../pages/basics/RigidBodyProps.vue'), + }, ] diff --git a/src/components/RigidBody.vue b/src/components/RigidBody.vue index e1c93e4..4ec6ff7 100644 --- a/src/components/RigidBody.vue +++ b/src/components/RigidBody.vue @@ -2,18 +2,29 @@ import { useLoop } from '@tresjs/core' import { Object3D } from 'three' import { nextTick, onUnmounted, onUpdated, provide, shallowRef, watch } from 'vue' -import type { ShallowRef } from 'vue' +import type { ShallowRef } from 'vue' import { useRapierContext } from '../composables' import { createColliderPropsFromObject, createRigidBody } from '../core' +import { makePropsWatcherRB } from '../utils/props' import { BaseCollider } from './colliders' import type { ColliderProps, ExposedRigidBody, RigidBodyContext, RigidBodyProps } from '../types' const props = withDefaults(defineProps>(), { type: 'dynamic', collider: 'cuboid', + gravityScale: 1, + additionalMass: 0, + linearDamping: 0, + angularDamping: 0, + dominanceGroup: 0, + linvel: () => ({ x: 0, y: 0, z: 0 }), + angvel: () => ({ x: 0, y: 0, z: 0 }), + enabledRotations: () => ({ x: true, y: true, z: true }), + enabledTranslations: () => ({ x: true, y: true, z: true }), + lockTranslations: false, + lockRotations: false, }) - const { onBeforeRender } = useLoop() const { world } = useRapierContext() @@ -63,6 +74,28 @@ watch(bodyGroup, async (group) => { bodyContext.value = newPhysicsState }, { once: true }) +// PROPS +makePropsWatcherRB(props, [ + 'gravityScale', + 'additionalMass', + 'linearDamping', + 'angularDamping', + 'dominanceGroup', + 'linvel', + 'angvel', + 'enabledRotations', + 'enabledTranslations', +], instance) + +watch([() => props.lockTranslations, instance], ([_lockTranslations, _]) => { + if (!instance.value) { return } + instance.value.lockTranslations(_lockTranslations, true) +}) +watch([() => props.lockRotations, instance], ([_lockRotations, _]) => { + if (!instance.value) { return } + instance.value.lockRotations(_lockRotations, true) +}) + onBeforeRender(() => { const context = bodyContext.value if (!context) { return } diff --git a/src/types/boolean.ts b/src/types/boolean.ts new file mode 100644 index 0000000..83faf18 --- /dev/null +++ b/src/types/boolean.ts @@ -0,0 +1 @@ +export interface enableBolean { x: boolean, y: boolean, z: boolean } diff --git a/src/types/index.ts b/src/types/index.ts index dd8f7d2..fe0e56a 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,4 +1,6 @@ +export * from './boolean' export * from './collider' +export * from './object' export * from './physics' export * from './rapier' export * from './rigid-body' diff --git a/src/types/object.ts b/src/types/object.ts new file mode 100644 index 0000000..cf7a9e4 --- /dev/null +++ b/src/types/object.ts @@ -0,0 +1,11 @@ +/** @description Utility type to exclude properties with the type `never` */ +export type NonNever = { + [K in keyof T as T[K] extends never ? never : K]: T[K]; +} + +/** @description Utility type to extract only **methods** of an `object` */ +export type Methods = NonNever<{ + [K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : never; +}> + +export type CallableProps = Record, (...args: any[]) => unknown> diff --git a/src/types/rigid-body.ts b/src/types/rigid-body.ts index 8765249..69087f3 100644 --- a/src/types/rigid-body.ts +++ b/src/types/rigid-body.ts @@ -1,6 +1,7 @@ import type { Collider, ColliderDesc, RigidBody, RigidBodyDesc, World } from '@dimforge/rapier3d-compat' -import type { TresObject3D } from '@tresjs/core' +import type { TresObject3D, TresVector3, VectorCoordinates } from '@tresjs/core' +import type { enableBolean } from './boolean' import type { ColliderShape } from './collider' /** @description Tres Rapier supported `RigidBody` types. */ @@ -19,7 +20,62 @@ export interface RigidBodyProps { * * Pass `false` to disable the auto colliders. */ - collider: ColliderShape | false + collider?: ColliderShape | false + /** + * @description Set the gravity of the`RigidBody`. + * @default 1 + */ + gravityScale?: number + /** + * @description add extra mass to the`RigidBody`. + * @default 1 + */ + additionalMass?: number + /** + * @description Set the gravity of the`RigidBody`. + * @default { x: 0, y: 0, z: 0 } + */ + linvel?: TresVector3 | VectorCoordinates + /** + * @description Set the gravity of the`RigidBody`. + * @default { x: 0, y: 0, z: 0 } + */ + angvel?: TresVector3 | VectorCoordinates + /** + * @description Set the linear damping of the`RigidBody`. + * @default 1 + */ + linearDamping?: number + /** + * @description Set the angular damping of the`RigidBody`. + * @default 1 + */ + angularDamping?: number + /** + * @description Set the dominance group of the`RigidBody`. + * @default 1 + */ + dominanceGroup?: number + /** + * @description Set the dominance group of the`RigidBody`. + * @default { x: true, y: true, z: true } + */ + enabledRotations?: enableBolean + /** + * @description Set the dominance group of the`RigidBody`. + * @default { x: true, y: true, z: true } + */ + enableTranslations?: enableBolean + /** + * @description Locks the translations of the `RigidBody`. + * @default false + */ + lockTranslations?: boolean + /** + * @description Locks the rotations of the `RigidBody`. + * @default false + */ + lockRotations?: boolean } export interface ExposedRigidBody { diff --git a/src/utils/props.ts b/src/utils/props.ts new file mode 100644 index 0000000..5907be5 --- /dev/null +++ b/src/utils/props.ts @@ -0,0 +1,30 @@ +import { watch } from 'vue' +import type { RigidBody } from '@dimforge/rapier3d-compat' +import type { ShallowRef } from 'vue' +import type { CallableProps, Methods, RigidBodyContext, RigidBodyProps } from '../types' + +export const makePropWatcherRB = < + K extends keyof RigidBodyProps, +>( + props: RigidBodyProps, + toWatch: K, + instance: ShallowRef, + onSet: `set${Capitalize}`, +) => watch([() => props[toWatch], instance], ([newValue, _]) => { + if (!instance.value) { return } + ((instance.value[onSet as keyof Methods]) as CallableProps[keyof CallableProps])?.(newValue, true) +}) + +export const makePropsWatcherRB = < + K extends keyof RigidBodyProps, +>( + props: RigidBodyProps, + watchers: K[], + instance: ShallowRef, +) => watchers.forEach((_) => { + const watcher = _ as string + // Uppercase only for the first letter in the watcher + const watcherName = watcher.charAt(0).toUpperCase() + + watcher.slice(1) as Capitalize + makePropWatcherRB(props, watcher as keyof RigidBodyProps, instance, `set${watcherName}`) +})