diff --git a/.vscode/settings.json b/.vscode/settings.json
index 62190d4..8b168d0 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -50,5 +50,8 @@
},
"[vue]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
+ },
+ "[typescript]": {
+ "editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
}
diff --git a/docs/components/collider.md b/docs/components/collider.md
index ebed629..d841bb4 100644
--- a/docs/components/collider.md
+++ b/docs/components/collider.md
@@ -16,28 +16,82 @@ 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 intersection events.
+
+> ::: info
+> **@intersection-enter**: When another collider starts to traverse the *sensor*
+> **@intersection-exit**: When another collider leave the *sensor*
+>
+> ℹ️ Note that you can directly pass the events to the **`RigidBody`** for **auto-colliders**.
+> :::
+
+```vue
+
+
+
+
+
+
+
+
+
```
+
+
+
+> ::: 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,
diff --git a/playground/src/pages/basics/SensorDemo.vue b/playground/src/pages/basics/SensorDemo.vue
new file mode 100644
index 0000000..1a94fa9
--- /dev/null
+++ b/playground/src/pages/basics/SensorDemo.vue
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/playground/src/router/routes/basics.ts b/playground/src/router/routes/basics.ts
index fdd1696..ddbdaf3 100644
--- a/playground/src/router/routes/basics.ts
+++ b/playground/src/router/routes/basics.ts
@@ -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'),
+ },
]
diff --git a/src/components/Debug.vue b/src/components/Debug.vue
index c63829b..061a76a 100644
--- a/src/components/Debug.vue
+++ b/src/components/Debug.vue
@@ -11,7 +11,7 @@ const { onBeforeRender } = useLoop()
const lineSegmentsRef = ref(null)
onBeforeRender(() => {
- if (!world || !lineSegmentsRef.value) { return }
+ if (!world || !lineSegmentsRef.value?.geometry?.boundingSphere) { return }
const buffers = world.debugRender()
diff --git a/src/components/Physics.vue b/src/components/Physics.vue
index 386e783..6f8d836 100644
--- a/src/components/Physics.vue
+++ b/src/components/Physics.vue
@@ -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'
@@ -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),
+ )
})
})
diff --git a/src/components/RigidBody.vue b/src/components/RigidBody.vue
index fe7fc94..2963510 100644
--- a/src/components/RigidBody.vue
+++ b/src/components/RigidBody.vue
@@ -2,14 +2,26 @@
import { ActiveCollisionTypes } from '@dimforge/rapier3d-compat'
import { useLoop } from '@tresjs/core'
import { Object3D } from 'three'
-import { nextTick, onUnmounted, onUpdated, provide, shallowRef, watch } from 'vue'
-
+import {
+ nextTick,
+ onUnmounted,
+ onUpdated,
+ provide,
+ shallowRef,
+ watch,
+} 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'
+import type {
+ ColliderProps,
+ ExposedRigidBody,
+ RigidBodyContext,
+ RigidBodyProps,
+} from '../types'
const props = withDefaults(defineProps>(), {
type: 'dynamic',
@@ -19,22 +31,25 @@ const props = withDefaults(defineProps>(), {
linearDamping: 0,
angularDamping: 0,
dominanceGroup: 0,
+ lockTranslations: false,
+ lockRotations: false,
+ enableCcd: false,
linvel: () => ({ x: 0, y: 0, z: 0 }),
angvel: () => ({ x: 0, y: 0, z: 0 }),
enabledRotations: () => [true, true, true],
enabledTranslations: () => [true, true, true],
- lockTranslations: false,
- lockRotations: false,
- enableCcd: false,
- // Automatic collider props
+
+ // AUTOMATIC COLLIDERS PROPS
friction: 0.5,
mass: 1,
restitution: 0,
density: 1,
activeCollision: false,
activeCollisionTypes: ActiveCollisionTypes.DEFAULT,
- collisionGroups: undefined, // this is not working
+ collisionGroups: undefined, // TODO: Make the `collisionGroups` (Not working yet).
+ sensor: false,
})
+
const { onBeforeRender } = useLoop()
const { world } = useRapierContext()
@@ -70,13 +85,23 @@ watch(bodyGroup, async (group) => {
}
if (props.collider !== false) {
- const colliders = []
+ const collidersProps: ColliderProps[] = []
for (const child of group.children) {
- colliders.push(createColliderPropsFromObject(child, props.collider))
+ const _props = createColliderPropsFromObject(child, props.collider)
+ _props.friction = props.friction
+ _props.mass = props.mass
+ _props.restitution = props.restitution
+ _props.density = props.density
+ _props.activeCollision = props.activeCollision
+ _props.activeCollisionTypes = props.activeCollisionTypes
+ _props.collisionGroups = props.collisionGroups
+ _props.sensor = props.sensor
+
+ collidersProps.push(_props)
}
- autoColliderProps.value = colliders
+ autoColliderProps.value = collidersProps
}
instance.value = newPhysicsState.rigidBody
@@ -84,7 +109,6 @@ watch(bodyGroup, async (group) => {
bodyContext.value = newPhysicsState
}, { once: true })
-// PROPS
makePropsWatcherRB(props, [
'gravityScale',
'additionalMass',
@@ -146,13 +170,14 @@ onUnmounted(() => {
:shape="_props.shape"
:args="_props.args"
:object="_props.object"
- :friction="props.friction"
- :mass="props.mass"
- :restitution="props.restitution"
- :density="props.density"
- :activeCollision="props.activeCollision"
- :activeCollisionTypes="activeCollisionTypes"
- :collisionGroups="collisionGroups"
+ :friction="_props.friction"
+ :mass="_props.mass"
+ :restitution="_props.restitution"
+ :density="_props.density"
+ :activeCollision="_props.activeCollision"
+ :activeCollisionTypes="_props.activeCollisionTypes"
+ :collisionGroups="_props.collisionGroups"
+ :sensor="_props.sensor"
/>
diff --git a/src/components/colliders/BaseCollider.vue b/src/components/colliders/BaseCollider.vue
index dedcbd2..aa9d639 100644
--- a/src/components/colliders/BaseCollider.vue
+++ b/src/components/colliders/BaseCollider.vue
@@ -1,10 +1,10 @@
-
+
+
+
diff --git a/src/types/collider.ts b/src/types/collider.ts
index 35c60f1..1233360 100644
--- a/src/types/collider.ts
+++ b/src/types/collider.ts
@@ -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}.
@@ -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 {
diff --git a/src/types/collision.ts b/src/types/collision.ts
index ee8ab5a..e940c3b 100644
--- a/src/types/collision.ts
+++ b/src/types/collision.ts
@@ -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'
diff --git a/src/types/object.ts b/src/types/object.ts
index cf7a9e4..1f22b32 100644
--- a/src/types/object.ts
+++ b/src/types/object.ts
@@ -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: VNode & {
+ children: Array
+ ctx: SetupContext
+ }
+ children: Array
+ 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 = {
[K in keyof T as T[K] extends never ? never : K]: T[K];
@@ -8,4 +24,5 @@ export type Methods = NonNever<{
[K in keyof T]: T[K] extends (...args: any[]) => any ? T[K] : never;
}>
+/** @description Utility picking methods/functions withing object. */
export type CallableProps = Record, (...args: any[]) => unknown>
diff --git a/src/types/rigid-body.ts b/src/types/rigid-body.ts
index 3b12c55..5e45aed 100644
--- a/src/types/rigid-body.ts
+++ b/src/types/rigid-body.ts
@@ -1,7 +1,13 @@
-import type { ActiveCollisionTypes, Collider, ColliderDesc, RigidBody, RigidBodyDesc, World } from '@dimforge/rapier3d-compat'
+import type {
+ Collider,
+ ColliderDesc,
+ RigidBody,
+ RigidBodyDesc,
+ World,
+} from '@dimforge/rapier3d-compat'
import type { TresObject3D, TresVector3, VectorCoordinates } from '@tresjs/core'
-import type { ColliderShape } from './collider'
+import type { ColliderProps, ColliderShape } from './collider'
/** @description Tres Rapier supported `RigidBody` types. */
export type RigidBodyType =
@@ -10,7 +16,17 @@ export type RigidBodyType =
| 'kinematicVelocity'
| 'fixed'
-export interface RigidBodyProps {
+export interface RigidBodyProps
+ extends Pick {
/** @description Set the `RigidBody` type. */
type: RigidBodyType
@@ -81,44 +97,6 @@ export interface RigidBodyProps {
*/
enableCcd?: boolean
- // AUTOMATIC COLLIDERS
-
- /**
- * @description The friction coefficient of this collider.
- * @default 0.5
- */
- friction?: number
- /**
- * @description mass.
- * @default 1
- */
- mass?: number
- /**
- * @description Restitution controls how elastic (aka. bouncy) a contact is.
- * @default 0
- */
- restitution?: number
- /**
- * @description The collider density. If non-zero the collider's mass and angular inertia will be added.
- * @default 1.0
- */
- density?: number
- /**
- * @description Enables collisions event.
- * @default false
- */
- activeCollision?: boolean
- /**
- * @description To set the collision type.
- * @default ActiveCollisionTypes.DEFAULT
- */
- activeCollisionTypes?: ActiveCollisionTypes.DEFAULT
- /**
- * @description To set the collision group.
- * @default undefined
- */
- collisionGroups?: undefined | number
-
}
export interface ExposedRigidBody {
diff --git a/src/utils/collisions.ts b/src/utils/collisions.ts
index 62a777e..e269879 100644
--- a/src/utils/collisions.ts
+++ b/src/utils/collisions.ts
@@ -1,7 +1,7 @@
import type { ColliderHandle, World } from '@dimforge/rapier3d-compat'
import type { Scene } from 'three'
import type { Ref } from 'vue'
-import type { CollisionSource, collisionType, sourceTarget } from '../types/collision'
+import type { CollisionSource, CollisionType, SourceTarget, TresVNodeObject } from '../types'
export const getSourceFromColliderHandle = (world: World, handle: ColliderHandle) => {
const collider = world.getCollider(handle)
@@ -20,16 +20,16 @@ export const getSourceFromColliderHandle = (world: World, handle: ColliderHandle
export const get3DGroupFromSource = (source: CollisionSource, scene: Ref) => {
const uuid = (source.rigidBody?.userData as { uuid?: string })?.uuid
- const currentRigidBodyNode = scene.value.getObjectByProperty('uuid', uuid)
+ const currentRigidBodyNode = scene.value.getObjectByProperty('uuid', uuid) as TresVNodeObject
return currentRigidBodyNode
}
export const collisionEmisor = (
- source: sourceTarget,
- target: sourceTarget,
+ source: SourceTarget,
+ target: SourceTarget,
started: boolean,
) => {
- const collisionType: collisionType = started ? 'enter' : 'exit';
- (source.object as any)?.__vnode?.ctx?.emit?.(`collision-${collisionType}`, { source, target })
+ const CollisionType: CollisionType = started ? 'enter' : 'exit'
+ source.object?.__vnode?.ctx?.emit?.(`collision-${CollisionType}`, { source, target })
}
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 9dca4ae..07b1d3a 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -1,2 +1,4 @@
export * from './collider'
export * from './collisions'
+export * from './intersections'
+export * from './props'
diff --git a/src/utils/intersections.ts b/src/utils/intersections.ts
new file mode 100644
index 0000000..2d05147
--- /dev/null
+++ b/src/utils/intersections.ts
@@ -0,0 +1,31 @@
+import type { ComponentInternalInstance } from 'vue'
+import type { CollisionType, SourceTarget } from '../types'
+
+/**
+ * @description Make traversed {@link SourceTarget Source} emit an intersection event.
+ *
+ * Either `@intersection-start` or `intersection-exist` based on the {@link CollisionType}.
+ *
+ * @param source - The traversed source.
+ * @param target - The traversing target.
+ * @param started - Whether the intersection started or ended
+ */
+export const emitIntersection = (
+ source: SourceTarget,
+ target: SourceTarget,
+ started: boolean,
+) => {
+ const CollisionType: CollisionType = started ? 'enter' : 'exit'
+ const colliderNode = source.object?.__vnode?.children?.[1]?.children?.find(child => child?.component?.exposed?.instance?.value === source.context.collider)
+
+ if (
+ (source.object?.__vnode?.ctx as unknown as ComponentInternalInstance)?.props?.sensor
+ && colliderNode?.component === undefined
+ ) {
+ source.object?.__vnode?.ctx?.emit?.(
+ `intersection-${CollisionType}`,
+ { source, target },
+ )
+ }
+ colliderNode?.component?.emit?.(`intersection-${CollisionType}`, { source, target })
+}