b3d-trigger
Invisible proximity zone that fires callbacks and dispatches events when a target (the active camera or a named mesh) enters or exits a spherical region. Useful for mission waypoints, area-of-effect zones, and cutscene triggers.
Set onEnter and onExit callback properties from JavaScript, or listen
for 'enter' / 'exit' CustomEvents on the element.
Demo
import { b3d, b3dTrigger, b3dSphere, b3dLight, b3dSkybox, b3dBiped, b3dGround, emptyInput } from 'tosijs-3d'
import { tosi, elements } from 'tosijs'
const { div, span, p } = elements
const { demo } = tosi({ demo: { status: 'walking…' } })
// A wandering goal (a glowing marker) with a proximity trigger around it.
let goal = { x: 6, z: 5 }
const marker = b3dSphere({ meshName: 'goal', diameter: 0.7, y: 0.35, x: goal.x, z: goal.z, color: '#ffcc00' })
const trigger = b3dTrigger({ x: goal.x, y: 0.5, z: goal.z, radius: 1.4, debug: true })
// The NPC: a NON-player biped driven by a tiny "walk to the goal" AI. A biped
// polls whatever is on `.inputProvider` every frame, so an AI is just an
// InputProvider emitting the same ControlInput (forward / turn) a player would.
const walker = b3dBiped({ url: '/omnidude.glb', x: -6, z: -6, initialState: 'idle' })
const STOP = 1.2 // how close counts as "arrived"
walker.inputProvider = {
poll() {
const m = walker.mesh
if (!m) return emptyInput
const p = m.getAbsolutePosition()
const dx = goal.x - p.x
const dz = goal.z - p.z
const dist = Math.hypot(dx, dz)
// Turn toward the goal (biped forward is +Z) and walk until we're there.
const f = m.forward
let turn = Math.atan2(dx, dz) - Math.atan2(f.x, f.z)
while (turn > Math.PI) turn -= 2 * Math.PI
while (turn < -Math.PI) turn += 2 * Math.PI
return {
...emptyInput,
forward: dist > STOP ? 1 : 0,
turn: Math.max(-1, Math.min(1, turn * 2)),
}
},
}
// Watch the biped (not the camera): point the trigger at its mesh once loaded.
const wire = setInterval(() => {
if (walker.mesh) { trigger.target = walker.mesh.name; clearInterval(wire) }
}, 100)
// On arrival: pause, then teleport the goal (marker + trigger) to a random spot
// on the ground. The NPC notices the trigger it's now outside of and walks to the
// new position. Repeat forever — trigger + simple AI in a loop.
trigger.onEnter = () => {
demo.status.value = 'reached it — relocating…'
setTimeout(() => {
goal = { x: (Math.random() - 0.5) * 16, z: (Math.random() - 0.5) * 16 }
marker.x = goal.x; marker.z = goal.z
trigger.x = goal.x; trigger.z = goal.z
demo.status.value = 'walking…'
}, 1000)
}
preview.append(
b3d(
{
sceneCreated(el, BABYLON) {
const camera = new BABYLON.ArcRotateCamera(
'cam', -Math.PI / 2, Math.PI / 3.2, 22,
new BABYLON.Vector3(0, 0, 0), el.scene
)
camera.attachControl(el.querySelector('canvas'), true)
el.setActiveCamera(camera)
},
},
b3dLight({ y: 1, intensity: 0.8 }),
b3dSkybox({ timeOfDay: 12 }),
b3dGround({ size: 20, color: '#556644' }),
marker,
trigger,
walker,
),
div(
{ style: 'position:absolute; top:8px; left:8px; background:rgba(0,0,0,0.6); color:white; padding:8px 12px; border-radius:6px; font:14px monospace' },
p('An NPC walks to the marker → it relocates → repeat'),
span({ bindText: demo.status }),
)
)
Attributes
| Attribute | Default | Description |
|---|---|---|
x |
0 |
Center X |
y |
0 |
Center Y |
z |
0 |
Center Z |
radius |
5 |
Trigger sphere radius |
active |
true |
Enable/disable the trigger |
target |
'camera' |
'camera' or a mesh name to watch |
debug |
false |
Show wireframe sphere |
once |
false |
Fire onEnter once then deactivate |