Skip to content

Commit

Permalink
feat: sensor support integration (#141)
Browse files Browse the repository at this point in the history
* feat: implement `intersection` feature

* feat(playground): add `sesnor` demo

* fix(playground): remove unsupported `name`

* fix(core): debug `line bounding-box` availability

* fix(core): unsupported props warns correction

### Description

- Add new `vNode` extended types
  - `TresVNodeObject`
  - `TresVNode`
- Add a `TresObject` to collider

* docs(component): minimal `sensor` documentation

* fix(docs): minor

* refactor(core): improve `emitIntersection` integration

* refactor(core): use `TresObject3D`

* feat(core): `RigdBody` inherit props from `Collider`

### Description

- Add `Sensor` to `RigidBody` component props
- `RigidBodyProps` inherits types from `ColliderProps`

* feat(core): `RigidBody` catch intersection events

* refactor(playground): add `RigidBody` intersection demo

* chore(conf): set default `TS` formater

* docs(collider): add note for sensor auto-colliders

* docs(collider): move intersection refs to info-block

* docs: improve docs guide for colliders

* chore: lint github folder

---------

Co-authored-by: alvarosabu <alvaro.saburido@gmail.com>
  • Loading branch information
Neosoulink and alvarosabu authored Dec 12, 2024
1 parent 2f906e2 commit 41eae54
Show file tree
Hide file tree
Showing 21 changed files with 388 additions and 116 deletions.
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/bug.report.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "Bug report \U0001F41B"
name: Bug report 🐛
description: Report an issue with Tres Rapier
labels: [pending triage]
body:
Expand Down Expand Up @@ -61,4 +61,4 @@ body:
- label: Check that there isn't [already an issue](https://github.com/Tresjs/rapier/issues) that reports the same bug to avoid creating a duplicate.
required: true
- label: The provided reproduction is a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) of the bug.
required: true
required: true
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
blank_issues_enabled: false
blank_issues_enabled: false
12 changes: 6 additions & 6 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: "New feature proposal \U0001FA90"
description: Propose a new feature to be added to Tres Rapier
labels: ["enhancement"]
name: New feature proposal 🪐
description: Propose a new feature to be added to Tres Rapier
labels: [enhancement]
body:
- type: markdown
attributes:
Expand All @@ -10,15 +10,15 @@ body:
id: feature-description
attributes:
label: Description
description: "Clear and concise description of the problem. Please make the reason and usecases as detailed as possible. If you intend to submit a PR for this issue, tell us in the description. Thanks!"
description: 'Clear and concise description of the problem. Please make the reason and usecases as detailed as possible. If you intend to submit a PR for this issue, tell us in the description. Thanks!'
placeholder: As a developer using TresJS I want [goal / wish] so that [benefit].
validations:
required: true
- type: textarea
id: suggested-solution
attributes:
label: Suggested solution
description: "In module [xy] we could provide following implementation..."
description: 'In module [xy] we could provide following implementation...'
validations:
required: true
- type: textarea
Expand All @@ -44,4 +44,4 @@ body:
- label: Read the [docs](https://rapier.tresjs.org/guide).
required: true
- label: Check that there isn't [already an issue](https://github.com/tresjs/rapier/issues) that reports the same bug to avoid creating a duplicate.
required: true
required: true
2 changes: 1 addition & 1 deletion .github/workflows/lint-pr.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: 'Lint PR'
name: Lint PR

on:
pull_request_target:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'pnpm'
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Run Lint
Expand Down
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,8 @@
},
"[vue]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
},
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
}
93 changes: 74 additions & 19 deletions docs/components/collider.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,93 @@ Similar to [how collisions work in rigid-bodies](./rigid-body.md#collisions) you

Be aware that the event will be emitted by the `RigidBody` parent

```html
...
<RigidBody @collision-enter="onCollisionEnter" @collision-exit="onCollisionExit">
```vue { 2,3}
<RigidBody
@collision-enter="onCollisionEnter"
@collision-exit="onCollisionExit"
>
<BallCollider activeCollision />
</RigidBody>
...
```

## Props

| Prop | Description | Default |
| :-------------- | :------------------------------------------------------------------------------------------------------------ | --------- |
| **shape** | shape of the collider | `cuboid` |
| **args** | The half-sizes of the collider shapes | `[1,1,1]` |
| **object** | Required for certain shapes like `trimesh`, `hull`, `heightfield`. | |
| **friction** | The friction coefficient of this collider. (automatic-collider) | `0.5` |
| **mass** | Mass of the collider. (automatic-collider) | `1` |
| **density** | Restitution controls how elastic (aka. bouncy) a contact is. (automatic-collider) | `0` |
| **restitution** | The collider density. If non-zero the collider's mass and angular inertia will be added. (automatic-collider) | `1` |
| **activeCollision** | To set the collider receiver/emitter collision events | `false` |
| **activeCollisionTypes** | Type of the collision event. | `ActiveCollisionTypes.DEFAULT` |
| **collisionGroups** | To specify collision groups. | `undefined` |

:::info
| Prop | Description | Default |
| :----------------------- | :--------------------------- | :------- |
| **shape** | shape of the collider | `cuboid` |
| **args** | The half-sizes of the collider shapes | `[1,1,1]` |
| **object** | Required for certain shapes like `trimesh`, `hull`, `heightfield`. | |
| **friction** | The friction coefficient of this collider. (automatic-collider) | `0.5` |
| **mass** | Mass of the collider. (automatic-collider) | `1` |
| **density** | Restitution controls how elastic (aka. bouncy) a contact is. (automatic-collider) | `0` |
| **restitution** | The collider density. If non-zero the collider's mass and angular inertia will be added. (automatic-collider). | `1` |
| **activeCollision** | To set the collider receiver/emitter collision events | `false` |
| **activeCollisionTypes** | Type of the collision event. | `ActiveCollisionTypes.DEFAULT` |
| **collisionGroups** | To specify collision groups. | `undefined` |
| **sensor** | Set the collider as senor. More details [here](#sensor). | `undefined` |

## Events

The `Collider` component comes with a set of useful events allowing actions based on collisions or intersections (aka sensor).

### Sensor

The **Sensor** feature allows events to be triggered when there's an intersection or in other words, when the collider is traversed by another collider.

The traversed `Collider` (or the collider that will trigger events), is the sensor and should set the `activeCollision` and `sensor` properties to `true`.
By passing the above properties, the collider will no longer be affected by the physics law and will now start triggering the intersection events:

- **@intersection-enter**: When another collider starts to traverse the *sensor*
- **@intersection-exit**: When another collider leave the *sensor*

::: info
Note that you can directly pass the events to the **`RigidBody`** for **auto-colliders**.
:::

```vue
<RigidBody
type="fixed"
activeCollision
sensor
@intersection-enter="onIntersection2Enter"
@intersection-exit="onIntersectionExit"
>
<TresMesh :position="[0, 5, 0]">
<TresBoxGeometry :args="[10, 10, 0.5]" />
<TresMeshBasicMaterial color="#f4f4f4" />
</TresMesh>
<CuboidCollider
:args="[10, 3, 0.5]"
:position="[0, 3, 3]"
activeCollision
sensor
@intersection-enter="onIntersection1Enter"
@intersection-exit="onIntersectionExit"
/>
<CuboidCollider
:args="[10, 3, 0.5]"
:position="[0, 3, -3]"
activeCollision
sensor
@intersection-enter="onIntersection3Enter"
@intersection-exit="onIntersectionExit"
/>
</RigidBody>
```

<!-- TODO: Add the demo link -->

::: info
You can access the [Collider](https://rapier.rs/docs/user_guides/javascript/colliders) instance
which offers full control over all the properties & methods available
by using [Template refs](https://vuejs.org/guide/essentials/template-refs.html#template-refs).
:::

## Expose object

```
```md
{
instance,
colliderDesc,
Expand Down
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { tresLintConfig } from '@tresjs/eslint-config'

export default tresLintConfig({
ignores: ['dist', 'node_modules', 'public', '.github', 'docs/blog'],
ignores: ['**/node_modules/**', 'public', 'README.md', 'docs/blog'],
}, {
rules: {
'style/max-statements-per-line': 'off',
Expand Down
122 changes: 122 additions & 0 deletions playground/src/pages/basics/SensorDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<script setup lang="ts">
import { OrbitControls } from '@tresjs/cientos'
import { TresCanvas } from '@tresjs/core'
// eslint-disable-next-line ts/ban-ts-comment
// @ts-ignore
import { CuboidCollider, Physics, RigidBody } from '@tresjs/rapier'
import { ACESFilmicToneMapping, MeshNormalMaterial, SRGBColorSpace } from 'three'
import { onMounted, shallowRef } from 'vue'
import type { Mesh } from 'three'
const gl = {
clearColor: '#82DBC5',
shadows: true,
alpha: false,
outputColorSpace: SRGBColorSpace,
toneMapping: ACESFilmicToneMapping,
}
const bodyContextRef = shallowRef()
const ballRef = shallowRef<Mesh>()
const onIntersection1Enter = (ev) => {
// eslint-disable-next-line no-console
console.log('Intersection 1 enter', ev)
if (ballRef.value?.material instanceof MeshNormalMaterial) {
ballRef.value.material.visible = false
}
}
const onIntersection2Enter = () => {
if (ballRef.value?.material instanceof MeshNormalMaterial) {
ballRef.value.material.colorWrite = false
}
}
const onIntersection3Enter = () => {
if (ballRef.value?.material instanceof MeshNormalMaterial) {
ballRef.value.material.wireframe = true
}
}
const onIntersectionExit = () => {
if (ballRef.value?.material instanceof MeshNormalMaterial) {
ballRef.value.material.visible = true
ballRef.value.material.colorWrite = true
ballRef.value.material.wireframe = false
}
}
const resetBall = () => {
bodyContextRef.value?.instance?.setAngvel({ x: -2, y: 0, z: 0 }, true)
bodyContextRef.value?.instance?.setLinvel({ x: 0, y: 0, z: -8 }, true)
bodyContextRef.value?.instance?.setRotation({ x: 0, y: 0, z: 0, w: 1 }, true)
bodyContextRef.value?.instance?.setTranslation({ x: 0, y: 0, z: 0 }, true)
}
onMounted(() => {
setTimeout(() => {
resetBall()
setInterval(() => {
resetBall()
}, 3000)
}, 100)
})
</script>

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

<Suspense>
<Physics debug>
<RigidBody ref="bodyContextRef" collider="ball">
<TresMesh ref="ballRef" :position="[0, 8, 8]" name="ball">
<TresSphereGeometry />
<TresMeshNormalMaterial />
</TresMesh>
</RigidBody>

<RigidBody
type="fixed"
activeCollision
sensor
@intersection-enter="onIntersection2Enter"
@intersection-exit="onIntersectionExit"
>
<TresMesh :position="[0, 5, 0]">
<TresBoxGeometry :args="[10, 10, 0.5]" />
<TresMeshBasicMaterial color="#f4f4f4" />
</TresMesh>

<CuboidCollider
:args="[10, 3, 0.5]"
:position="[0, 3, 3]"
activeCollision
sensor
@intersection-enter="onIntersection1Enter"
@intersection-exit="onIntersectionExit"
/>

<CuboidCollider
:args="[10, 3, 0.5]"
:position="[0, 3, -3]"
activeCollision
sensor
@intersection-enter="onIntersection3Enter"
@intersection-exit="onIntersectionExit"
/>
</RigidBody>

<RigidBody type="fixed" name="fixedFloor" :restitution="0.2">
<TresMesh :position="[0, 0, 0]">
<TresPlaneGeometry :args="[20, 20, 1]" :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 @@ -34,4 +34,9 @@ export const basicsRoutes = [
name: 'Collision',
component: () => import('../../pages/basics/CollisionDemo.vue'),
},
{
path: '/basics/sensor',
name: 'Sensor',
component: () => import('../../pages/basics/SensorDemo.vue'),
},
]
2 changes: 1 addition & 1 deletion src/components/Debug.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const { onBeforeRender } = useLoop()
const lineSegmentsRef = ref<LineSegments | null>(null)
onBeforeRender(() => {
if (!world || !lineSegmentsRef.value) { return }
if (!world || !lineSegmentsRef.value?.geometry?.boundingSphere) { return }
const buffers = world.debugRender()
Expand Down
32 changes: 23 additions & 9 deletions src/components/Physics.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ import { useLoop, useTresContext } from '@tresjs/core'
import { Vector3 } from 'three'
import { watch } from 'vue'
import type { VectorCoordinates } from '@tresjs/core'
import { useRapierContextProvider } from '../composables/useRapier'
import { GRAVITY } from '../constants/physics'
import { useRapierContextProvider } from '../composables'
import { GRAVITY } from '../constants'
import { collisionEmisor, get3DGroupFromSource, getSourceFromColliderHandle } from '../utils'
import {
collisionEmisor,
emitIntersection,
get3DGroupFromSource,
getSourceFromColliderHandle,
} from '../utils'
import Debug from './Debug.vue'
import type { PhysicsProps } from '../types'
Expand Down Expand Up @@ -57,13 +62,22 @@ onBeforeRender(() => {
const source2 = getSourceFromColliderHandle(world, handle2)
const group1 = get3DGroupFromSource(source1, scene)
const group2 = get3DGroupFromSource(source2, scene)
if (group1 && group2) {
collisionEmisor(
{ object: group1, context: source1 },
{ object: group2, context: source2 },
started,
)
if (!group1 || !group2) {
return
}
collisionEmisor(
{ object: group1, context: source1 },
{ object: group2, context: source2 },
started,
)
emitIntersection(
{ object: group2, context: source2 },
{ object: group1, context: source1 },
started && world.intersectionPair(source1.collider, source2.collider),
)
})
})
</script>
Expand Down
Loading

0 comments on commit 41eae54

Please sign in to comment.