Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: sensor support integration #141

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
78 changes: 59 additions & 19 deletions docs/components/collider.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,68 @@ Be aware that the event will be emitted by the `RigidBody` parent

## 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
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).
:::
| 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` |

## Expose object
## 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 following intersection events:

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

I.e:

```vue
<RigidBody type="fixed">
<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="onIntersection2Enter"
@intersection-exit="onIntersectionExit"
/>
</RigidBody>
```
<!-- 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
102 changes: 102 additions & 0 deletions playground/src/pages/basics/SensorDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<script setup lang="ts">
import { OrbitControls } from '@tresjs/cientos'
import { TresCanvas } from '@tresjs/core'
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 = () => {
if (ballRef.value?.material instanceof MeshNormalMaterial) {
ballRef.value.material.visible = false
}
}

const onIntersection2Enter = () => {
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.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="[-16, 8, 0]" :look-at="[0, 0, 0]" />
<OrbitControls />

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

<RigidBody
type="fixed"
>
<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="onIntersection2Enter"
@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
20 changes: 17 additions & 3 deletions src/components/colliders/BaseCollider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ActiveCollisionTypes, ActiveEvents } from '@dimforge/rapier3d-compat'
import { inject, nextTick, onUnmounted, type ShallowRef, shallowRef, watch } from 'vue'
import { useRapierContext } from '../../composables'
import { createCollider } from '../../core/collider'
import { makePropsWatcherCL } from '../../utils/props'
import { makePropsWatcherCL } from '../../utils'
import type { ColliderProps, CreateColliderReturnType, ExposedCollider, RigidBodyContext } from '../../types'

const props = withDefaults(defineProps<Partial<ColliderProps>>(), {
Expand All @@ -17,6 +17,7 @@ const props = withDefaults(defineProps<Partial<ColliderProps>>(), {
activeCollision: false,
activeCollisionTypes: ActiveCollisionTypes.DEFAULT,
collisionGroups: undefined,
sensor: false,
})

const { world } = useRapierContext()
Expand Down Expand Up @@ -59,7 +60,18 @@ watch(bodyContext, async (state) => {
}, { immediate: true })

// TODO: collisionGroups
makePropsWatcherCL(props, ['friction', 'restitution', 'density', 'mass', 'activeCollisionTypes'], colliderInfos)
makePropsWatcherCL(
props,
[
'friction',
'restitution',
'density',
'mass',
'activeCollisionTypes',
'sensor',
],
colliderInfos,
)

watch([() => props.collisionGroups, colliderInfos], ([_collisionGroups, _]) => {
if (!colliderInfos.value?.collider || !_collisionGroups) { return }
Expand All @@ -86,5 +98,7 @@ onUnmounted(() => {
</script>

<template>
<slot></slot>
<TresObject3D>
<slot></slot>
</TresObject3D>
</template>
7 changes: 6 additions & 1 deletion src/types/collider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export type ColliderShape =

export interface ColliderProps {
/** @description Set the {@link Collider} shape. */
shape: ColliderShape
shape?: ColliderShape

/**
* @description Shape based {@link TresObject3D}.
Expand Down Expand Up @@ -83,6 +83,11 @@ export interface ColliderProps {
* @default undefined
*/
collisionGroups?: undefined | number
/**
* @description Whether this collider is a sensor.
* @default undefined
*/
sensor?: boolean
}

export interface ExposedCollider {
Expand Down
8 changes: 4 additions & 4 deletions src/types/collision.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { Collider, RigidBody } from '@dimforge/rapier3d-compat'
import type { Object3D } from 'three'
import type { TresVNodeObject } from './object'

export interface CollisionSource {
collider: Collider
rigidBody: RigidBody | undefined
};

export interface sourceTarget {
object: Object3D
export interface SourceTarget {
object: TresVNodeObject
context: CollisionSource
}

export type collisionType = 'enter' | 'exit'
export type CollisionType = 'enter' | 'exit'
17 changes: 17 additions & 0 deletions src/types/object.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
import type { TresObject } from '@tresjs/core'
import type { SetupContext, VNode } from 'vue'

/** @description approximate {@link VNode} representation. */
export type TresVNode = Omit<VNode, 'children'> & {
__vnode: VNode & {
children: Array<TresVNode>
ctx: SetupContext
}
children: Array<TresVNode>
ctx: SetupContext
}

/** @description approximate {@link TresObject} representation. */
export type TresVNodeObject = TresObject & { __vnode: TresVNode }

/** @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];
Expand All @@ -8,4 +24,5 @@ export type Methods<T extends object> = NonNever<{
[K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : never;
}>

/** @description Utility picking methods/functions withing object. */
export type CallableProps<T extends object = object> = Record<keyof Methods<T>, (...args: any[]) => unknown>
Loading