Skip to content

Commit

Permalink
feat(app): 119 Add props to rigidbody (#129)
Browse files Browse the repository at this point in the history
* 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 <neodarksoulink@gmail.com>
  • Loading branch information
JaimeTorrealba and Neosoulink authored Sep 24, 2024
1 parent 7b509d4 commit e3ec76f
Show file tree
Hide file tree
Showing 12 changed files with 314 additions and 34 deletions.
88 changes: 63 additions & 25 deletions docs/components/rigid-body.md
Original file line number Diff line number Diff line change
@@ -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
<RigidBody>
Expand All @@ -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
<RigidBody type="fixed">
<TresMesh :position="[0, 0, 0]">
Expand All @@ -37,38 +45,46 @@ 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}
<RigidBody collider="ball">
<TresMesh :position="[0,7, 0]">
<TresSphereGeometry />
<TresMeshNormalMaterial />
</TresMesh>
</RigidBody>
```

### 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:

Expand All @@ -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)
}
}
Expand All @@ -106,7 +120,8 @@ const jumpCube = () => {
</template>
```

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

Expand All @@ -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,
}
```
Expand Down
5 changes: 5 additions & 0 deletions docs/guide/how-does-it-work.md
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 0 additions & 4 deletions playground/src/pages/basics/ApplyingForcesDemo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,13 @@ const rigidSphereRef = shallowRef<ExposedRigidBody>(null)
const jumpCube = () => {
if (!rigidCubeRef.value) { return }
rigidCubeRef.value.rigidBodyDesc.mass = 5
rigidCubeRef.value.instance.applyImpulse({ x: 0, y: 15, z: 0 }, true)
}
const windSphere = () => {
if (!rigidSphereRef.value) {
return
}
rigidSphereRef.value.rigidBodyDesc.mass = 5
rigidSphereRef.value.instance.applyImpulse({ x: 5, y: 0, z: 0 }, true)
}
</script>
Expand Down
103 changes: 103 additions & 0 deletions playground/src/pages/basics/RigidBodyProps.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<script setup lang="ts">
import { OrbitControls } from '@tresjs/cientos'
import { TresCanvas } from '@tresjs/core'
import { TresLeches, useControls } from '@tresjs/leches'
import { type ExposedRigidBody, Physics, RigidBody } from '@tresjs/rapier'
import { ACESFilmicToneMapping, SRGBColorSpace } from 'three'
import { shallowRef } from 'vue'
import '@tresjs/leches/styles'
const gl = {
clearColor: '#82DBC5',
shadows: true,
alpha: false,
outputColorSpace: SRGBColorSpace,
toneMapping: ACESFilmicToneMapping,
}
const rigidTorusRef = shallowRef<ExposedRigidBody>(null)
const rigidBoxRef = shallowRef<ExposedRigidBody>(null)
const jump = () => {
if (!rigidTorusRef.value) { return }
rigidTorusRef.value.instance.applyImpulse({ x: 0, y: 35, z: 0 }, true)
}
const rotate = () => {
if (!rigidBoxRef.value) { return }
rigidBoxRef.value.instance.applyTorqueImpulse({ x: 3, y: 5, z: 0 }, true)
}
const { gravityScale, linearDamping, angularDamping, lockT, linvelX } = useControls({
gravityScale: { value: 2.5, min: -10, max: 10, step: 1 },
linearDamping: { value: 0, min: -10, max: 10, step: 1 },
angularDamping: { value: 0, min: -10, max: 10, step: 1 },
linvelX: { value: 0, min: -10, max: 10, step: 1 },
lockT: true,
})
// TODO test locks and enabledTranslations, check docs
</script>

<template>
<TresLeches />
<TresCanvas v-bind="gl" window-size>
<TresPerspectiveCamera :position="[11, 11, 11]" :look-at="[0, 0, 0]" />
<OrbitControls />

<Suspense>
<Physics debug>
<!-- TORUS -->
<RigidBody
ref="rigidTorusRef"
:gravityScale="gravityScale.value"
:linearDamping="linearDamping.value"
:angularDamping="angularDamping.value"
:enabledTranslations="{ x: true, y: true, z: true }"
:lockTranslations="lockT.value"
>
<TresMesh :position="[4, 8, 0]" @click="jump">
<TresTorusGeometry />
<TresMeshNormalMaterial />
</TresMesh>
</RigidBody>

<RigidBody>
<TresMesh :position="[0, 8, 0]">
<TresTorusGeometry />
<TresMeshNormalMaterial />
</TresMesh>
</RigidBody>

<!-- BOX -->

<RigidBody
ref="rigidBoxRef"
:gravityScale="-0.01"
:additionalMass="50"
:enabledRotations="{ x: true, y: false, z: true }"
:linvel="{ x: linvelX.value, y: 0, z: 0 }"
>
<TresMesh :position="[4, 2, 5]" @click="rotate">
<TresBoxGeometry />
<TresMeshBasicMaterial :color="0xFF0000" />
</TresMesh>
</RigidBody>

<RigidBody>
<TresMesh :position="[0, 8, 5]">
<TresBoxGeometry />
<TresMeshBasicMaterial :color="0xFF0000" />
</TresMesh>
</RigidBody>

<RigidBody type="fixed">
<TresMesh :position="[0, 0, 0]">
<TresPlaneGeometry :args="[20, 20, 20]" :rotate-x="-Math.PI / 2" />
<TresMeshBasicMaterial color="#f4f4f4" />
</TresMesh>
</RigidBody>
</Physics>
</Suspense>
</TresCanvas>
</template>
5 changes: 5 additions & 0 deletions playground/src/router/routes/basics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
},
]
37 changes: 35 additions & 2 deletions src/components/RigidBody.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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<Partial<RigidBodyProps>>(), {
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()
Expand Down Expand Up @@ -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 }
Expand Down
1 change: 1 addition & 0 deletions src/types/boolean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export interface enableBolean { x: boolean, y: boolean, z: boolean }
2 changes: 2 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from './boolean'
export * from './collider'
export * from './object'
export * from './physics'
export * from './rapier'
export * from './rigid-body'
11 changes: 11 additions & 0 deletions src/types/object.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/** @description Utility type to exclude properties with the type `never` */
export type NonNever<T extends object> = {
[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<T extends object> = NonNever<{
[K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : never;
}>

export type CallableProps<T extends object = object> = Record<keyof Methods<T>, (...args: any[]) => unknown>
Loading

0 comments on commit e3ec76f

Please sign in to comment.