-
-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* (fix) BREAKING - remove `event.intersects` – use `event.intersections` * (fix) BREAKING - remove `event.stopPropagating` – use `event.stopPropagation` * (fix) BREAKING – from TresCanvas, don't emit pointer/click events bubbled from Tres objects * (fix) BREAKING – `event.delta` is now reset to `0` following a `click`. * (fix) BREAKING – `pointerleave` handlers were sent previous intersections at `event.intersections`, now current intersections. * (fix) BREAKING – `@dblclick` now fires whenever the canvas `dblclick` is emitted. Any TresObject with an `@dblclick` handler will receive the event if it is under the pointer. (Previously objects not under the initial click did not receive the `@dblclick`.) * (fix) `event.pointer` is now defined – previously typed but was `undefined` in implementation. * (fix) `event.unprojectedPoint` is now properly calculated. Was previously left as `TODO`. * (fix) `event.eventObject` holds the object that registered the event handler. Was previously typed but `undefined` in implementation. * (fix) use Vue-style ("flatcase") event names, e.g. `@pointerdown`. Keep support for old-style ("kebab-case") event names, e.g. `@pointer-down`, and warn. Mixing both on the same object is not supported and may lead to handlers being overwritten. * (fix) `@pointer{leave,out}` was fired multiple times on a single "leave" – Issue #801 * (feat) BREAKING – `pointermissed` fires when the object that registered the handler is missed – previously only fired when ALL objects were missed * (feat) add filtering/sorting for intersections, prior to handling events. `:events={filter: (intersections) => ...}` * (feat) support Vue event modifiers – `stop, prevent, self, once`. (Tres cannot support `passive` and does not currently support `capture`.) * (feat) via `:events-target` prop, allow users to specify an HTML `addEventListener` target – allows Tres to respond to events, even if there's an overlay on the canvas. * (feat) via `:events-enabled` prop, allow `eventManager` to be enabled/disabled while Tres is running. * (feat) via `:events` prop, allow `eventManager` functions to be set/overwritten (non-reactive). * (feat) via `:events` prop, allow events system to be disabled completely: `<TresCanvas :events="null" />` (non-reactive) * (feat) `pointer{leave,out}` is triggered (if necessary) just prior to object removal. * (feat) `:blocking="true"` makes a subtree "solid"; objects behind objects in the subtree will not be "hit".
- Loading branch information
1 parent
04b001b
commit 46545d5
Showing
39 changed files
with
4,594 additions
and
888 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
<!-- eslint-disable no-console --> | ||
<script setup lang="ts"> | ||
import { OrbitControls } from '@tresjs/cientos' | ||
import { TresCanvas } from '@tresjs/core' | ||
import { Vector3 } from 'three' | ||
import type { ThreeEvent } from '@tresjs/core' | ||
|
||
function onClick(ev: ThreeEvent<MouseEvent>) { | ||
ev.eventObject.material.color.set('red') | ||
} | ||
|
||
function onPointerOver(ev: ThreeEvent<MouseEvent>) { | ||
ev.eventObject.material.color.set('#FFCC00') | ||
} | ||
|
||
function onPointerOut(ev: ThreeEvent<MouseEvent>) { | ||
ev.eventObject.material.color.set(ev.eventObject.color) | ||
} | ||
|
||
const blockingIdRef = ref<string>('scene') | ||
function onChangeBlockingId(id: string) { | ||
blockingIdRef.value = id | ||
} | ||
|
||
const COUNT = 12 | ||
const positions0: Vector3[] = [] | ||
const positions1: Vector3[] = [] | ||
for (let i = 0; i < COUNT; i++) { | ||
const ang = i / COUNT * 2 * Math.PI | ||
positions0.push(new Vector3(Math.cos(ang) * 20, 0, Math.sin(ang) * 20)) | ||
} | ||
for (let i = 0; i < COUNT; i++) { | ||
const ang = i / COUNT * 2 * Math.PI | ||
positions1.push(new Vector3(Math.cos(ang) * 30, 0, Math.sin(ang) * 30)) | ||
} | ||
console.log(positions0) | ||
</script> | ||
|
||
<template> | ||
<OverlayInfo> | ||
<h1><code>:blocking</code></h1> | ||
<p>By default, from the perspective of the pointer/mouse in Tres, 3D objects are <strong>not</strong> "solid".</p> | ||
<p>Clicks and other pointer events "hit" all objects under the pointer, event if those objects are behind other objects.</p> | ||
<p>But you can make a subtree "solid" by marking it with <code>:blocking="true"</code></p> | ||
<h2>Setup</h2> | ||
<p><strong>Select "blocking" objects in the scene.</strong> Then interact with the scene to see the effect.</p> | ||
<div v-for="id of ['scene', 'cubes', 'spheres', 'none (default Tres behavior)']" :key="id"> | ||
<input :id="id" :checked="id === blockingIdRef" type="radio" name="target" @change="() => onChangeBlockingId(id)" /> | ||
<label :for="id">{{ id }}</label> | ||
</div> | ||
</OverlayInfo> | ||
<TresCanvas window-size clear-color="gray" :blocking="blockingIdRef === 'scene'"> | ||
<OrbitControls /> | ||
<TresPerspectiveCamera | ||
:position="[0, 10, 80]" | ||
:look-at="[0, 0, 0]" | ||
/> | ||
<TresGroup :blocking="blockingIdRef === 'scene'"> | ||
<TresMesh | ||
v-for="pos, i of positions0" | ||
:key="i" | ||
:position="pos" | ||
:blocking="blockingIdRef === 'cubes'" | ||
:scale="5" | ||
color="green" | ||
@pointer-over="onPointerOver" | ||
@pointer-out="onPointerOut" | ||
@click="onClick" | ||
> | ||
<TresBoxGeometry :args="[]" /> | ||
<TresMeshBasicMaterial color="green" /> | ||
</TresMesh> | ||
|
||
<TresMesh | ||
v-for="pos, i of positions1" | ||
:key="i" | ||
:position="pos" | ||
:blocking="blockingIdRef === 'spheres'" | ||
color="purple" | ||
@pointer-over="onPointerOver" | ||
@pointer-out="onPointerOut" | ||
@click="onClick" | ||
> | ||
<TresSphereGeometry :args="[]" /> | ||
<TresMeshBasicMaterial color="purple" /> | ||
</TresMesh> | ||
<TresMesh | ||
:scale="10" | ||
color="blue" | ||
@pointer-over="onPointerOver" | ||
@pointer-out="onPointerOut" | ||
@click="onClick" | ||
> | ||
<TresTorusGeometry /> | ||
<TresMeshBasicMaterial color="blue" /> | ||
</TresMesh> | ||
|
||
<TresMesh | ||
:scale="40" | ||
color="gray" | ||
@pointer-over="onPointerOver" | ||
@pointer-out="onPointerOut" | ||
@click="onClick" | ||
> | ||
<TresCylinderGeometry :scale="[-1, 1, 1]" /> | ||
<TresMeshBasicMaterial color="gray" /> | ||
</TresMesh> | ||
</TresGroup> | ||
<TresDirectionalLight :intensity="1" /> | ||
</TresCanvas> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
<!-- eslint-disable no-console --> | ||
<script setup lang="ts"> | ||
import { OrbitControls } from '@tresjs/cientos' | ||
import { TresCanvas } from '@tresjs/core' | ||
import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three' | ||
import type { ThreeEvent, TresContext } from '@tresjs/core' | ||
|
||
const gl = { | ||
clearColor: '#202020', | ||
shadows: true, | ||
alpha: false, | ||
shadowMapType: BasicShadowMap, | ||
outputColorSpace: SRGBColorSpace, | ||
toneMapping: NoToneMapping, | ||
} | ||
|
||
function onClick(ev: ThreeEvent<MouseEvent>) { | ||
ev.eventObject.material?.color.set('#008080') | ||
} | ||
|
||
function onDoubleClick(ev: ThreeEvent<MouseEvent>) { | ||
ev.eventObject.material.color.set('#FFAA00') | ||
} | ||
|
||
function onPointerEnter(ev: ThreeEvent<MouseEvent>) { | ||
ev.eventObject.material.color.set('#CCFF03') | ||
} | ||
|
||
function onPointerLeave(ev: ThreeEvent<MouseEvent>) { | ||
ev.eventObject.material.color.set('#efefef') | ||
} | ||
|
||
function onPointerMove(ev: ThreeEvent<MouseEvent>) { | ||
ev.eventObject.material.color.set('#F00') | ||
} | ||
|
||
function onContextMenu(ev: ThreeEvent<MouseEvent>) { | ||
ev.eventObject.material.color.set('#FF4500') | ||
} | ||
|
||
function onPointerMissed(ev: ThreeEvent<MouseEvent>) { | ||
ev.eventObject.material.color.set('#000000') | ||
} | ||
|
||
function onWheel(ev: ThreeEvent<MouseEvent>) { | ||
ev.eventObject.material.color.set('#FFFF00') | ||
} | ||
|
||
const defaultConnecter = (target: EventTarget, eventManagerHandler: EventListener) => { | ||
const POINTER_EVENT_NAMES = ['wheel', 'click', 'pointermove', 'pointerup', 'pointerdown', 'contextmenu', 'dblclick'] | ||
for (const domEventName of POINTER_EVENT_NAMES) { | ||
target.addEventListener(domEventName, eventManagerHandler) | ||
} | ||
|
||
return { | ||
disconnect: () => { | ||
for (const domEventName of POINTER_EVENT_NAMES) { | ||
target.removeEventListener(domEventName, eventManagerHandler) | ||
} | ||
}, | ||
} | ||
} | ||
|
||
const CONNECTERS: Record<string, (eventTarget: HTMLElement, handler: EventListener) => ({ disconnect: () => void })> = { | ||
'default': defaultConnecter, | ||
'click': (eventTarget, handler) => { | ||
eventTarget.addEventListener('click', handler) | ||
return { disconnect: () => { eventTarget.removeEventListener('click', handler) } } | ||
}, | ||
'dblclick, wheel': (eventTarget, handler) => { | ||
eventTarget.addEventListener('dblclick', handler) | ||
eventTarget.addEventListener('wheel', handler) | ||
return { disconnect: () => { | ||
eventTarget.removeEventListener('dblclick', handler) | ||
eventTarget.removeEventListener('wheel', handler) | ||
} } | ||
}, | ||
} | ||
|
||
const CONNECTER_IDS = Object.keys(CONNECTERS) | ||
const connecterIdRef = ref<keyof typeof CONNECTERS>('default') | ||
|
||
const eventsFns = { | ||
connect: CONNECTERS[connecterIdRef.value], | ||
} | ||
|
||
type DisconnectFn = () => void | ||
type ConnectFn = (target: HTMLElement) => void | ||
|
||
let eventManager: { | ||
connect: ConnectFn | ||
disconnect: DisconnectFn | ||
target: HTMLElement | ||
} | null = null | ||
|
||
function onChangeConnecterId(id: keyof typeof CONNECTERS) { | ||
if (!eventManager) { return } | ||
const target = eventManager.target | ||
eventManager.disconnect() | ||
connecterIdRef.value = id | ||
eventsFns.connect = CONNECTERS[connecterIdRef.value] | ||
eventManager.connect(target) | ||
} | ||
|
||
function ready(ctx: TresContext) { | ||
eventManager = ctx.eventManager | ||
} | ||
</script> | ||
|
||
<template> | ||
<OverlayInfo> | ||
<h1><code>connect</code></h1> | ||
<p>By default, <code>eventManager</code> will listen for DOM events emitted from the canvas. But if you don't need some of those events – particularly <code>pointermove</code>, you can get extra performance by not listening for them.</p> | ||
<p>:events has a settable <code>connect</code> function.</p> | ||
<code><TresCanvas :events="{connect: ...}" /></code> | ||
<hr /> | ||
<h2>Example <code>connect</code></h2> | ||
<p><strong>Select a connector function below (see function implementation in the Vue playground file) and then interact with the on-screen objects to see its effect.</strong></p> | ||
<div v-for="id of CONNECTER_IDS" :key="id"> | ||
<input :id="id" :checked="id === connecterIdRef" type="radio" name="target" @change="() => onChangeConnecterId(id)" /> | ||
<label :for="id">{{ id }}</label> | ||
</div> | ||
<hr /> | ||
<h2>NOTE</h2> | ||
<p>The <code>:events</code> prop is not reactive. But if you have a reference to it, you can redefine functions, which will then be called at some later point during the lifecycle.</p> | ||
<p><code>connect</code> is not designed to be changed on the fly. To do so, as we have done here, you will have to get a handle on <code>context.eventManager</code> and call <code>disconnect</code>, then <code>connect</code>.</p> | ||
</OverlayInfo> | ||
<TresCanvas | ||
window-size | ||
v-bind="gl" | ||
:events="eventsFns" | ||
@ready="ready" | ||
> | ||
<TresPerspectiveCamera | ||
:position="[15, 15, 15]" | ||
:look-at="[0, 0, 0]" | ||
/> | ||
<OrbitControls /> | ||
|
||
<template v-for="x in [-2.5, 0, 2.5]"> | ||
<template v-for="y in [-2.5, 0, 2.5]"> | ||
<TresMesh | ||
v-for="z in [-2.5, 0, 2.5]" | ||
:key="`${[x, y, z]}`" | ||
:position="[x, y, z]" | ||
@click="onClick" | ||
@dblclick="onDoubleClick" | ||
@pointerenter="onPointerEnter" | ||
@pointerleave="onPointerLeave" | ||
@pointermove="onPointerMove" | ||
@contextmenu="onContextMenu" | ||
@pointermissed="onPointerMissed" | ||
@wheel="onWheel" | ||
> | ||
<TresBoxGeometry :args="[1, 1, 1]" /> | ||
<TresMeshToonMaterial color="#efefef" /> | ||
</TresMesh> | ||
</template> | ||
</template> | ||
<TresDirectionalLight :intensity="1" /> | ||
<TresAmbientLight :intensity="1" /> | ||
</TresCanvas> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
<!-- eslint-disable no-console --> | ||
<script setup lang="ts"> | ||
import { OrbitControls } from '@tresjs/cientos' | ||
import { TresCanvas } from '@tresjs/core' | ||
import type { ThreeEvent } from '@tresjs/core' | ||
import '@tresjs/leches/styles' | ||
|
||
function onClick(ev: ThreeEvent<MouseEvent>) { | ||
console.log('click', ev) | ||
ev.eventObject.material.color.set('#008080') | ||
} | ||
|
||
function onDoubleClick(ev: ThreeEvent<MouseEvent>) { | ||
console.log('double-click', ev) | ||
ev.eventObject.material.color.set('#FFD700') | ||
} | ||
|
||
function onPointerEnter(ev: ThreeEvent<MouseEvent>) { | ||
ev.eventObject.material.color.set('#CCFF03') | ||
} | ||
|
||
function onPointerLeave(ev: ThreeEvent<MouseEvent>) { | ||
console.log('pointer-leave', ev) | ||
} | ||
|
||
function onPointerMove(ev: ThreeEvent<MouseEvent>) { | ||
console.log('pointer-move', ev) | ||
} | ||
|
||
function onContextMenu(ev: ThreeEvent<MouseEvent>) { | ||
console.log('context-menu', ev) | ||
ev.eventObject.material.color.set('#FF4500') | ||
} | ||
|
||
function onPointerMissed(ev: ThreeEvent<MouseEvent>) { | ||
console.log('pointer-missed', ev) | ||
ev.eventObject.material.color.set('#000') | ||
} | ||
</script> | ||
|
||
<template> | ||
<OverlayInfo> | ||
<h1>Deprecated event names</h1> | ||
<h2>Setup</h2> | ||
<p>This scene contains TresObjects with deprecated event names.</p> | ||
<p>Regardless, interacting with the objects should trigger their handlers. (They will change colors and log to the console.)</p> | ||
<h2>Background</h2> | ||
<p>Vue event names are written like:</p> | ||
<p><strong><code>@</code> + DOM event name</strong>, e.g. <code>@pointerdown</code></p> | ||
<p>DOM events names are typically, but not always, written in <a href="https://en.wikipedia.org/wiki/Naming_convention_(programming)#Examples_of_multiple-word_identifier_formats">flatcase</a>.</p> | ||
<p>Through Tres v4.x, Tres' multiple-word event names were written in <a href="https://en.wikipedia.org/wiki/Naming_convention_(programming)#Examples_of_multiple-word_identifier_formats">kebab-case</a>, e.g. <code>@pointer-down</code>.</p> | ||
<p>Now Tres has adopted the Vue naming convention for supported events.</p> | ||
<p>Old-style hyphenated events will still respond, though they will generate a console warning in dev mode the first time they're encountered in a scene.</p> | ||
<p>The developper should take care not to mix hyphenated and non-hyphenated event names.</p> | ||
</OverlayInfo> | ||
<TresCanvas | ||
window-size | ||
clear-color="gray" | ||
> | ||
<TresPerspectiveCamera | ||
:position="[11, 11, 11]" | ||
:look-at="[0, 0, 0]" | ||
/> | ||
<OrbitControls /> | ||
<template v-for="x in [-2.5, 0, 2.5]"> | ||
<template v-for="y in [-2.5, 0, 2.5]"> | ||
<TresMesh | ||
v-for="z in [-2.5, 0, 2.5]" | ||
:key="`${[x, y, z]}`" | ||
:position="[x, y, z]" | ||
@click="onClick" | ||
@double-click="onDoubleClick" | ||
@pointer-enter="onPointerEnter" | ||
@pointer-leave="onPointerLeave" | ||
@pointer-move="onPointerMove" | ||
@context-menu="onContextMenu" | ||
@pointer-missed="onPointerMissed" | ||
> | ||
<TresBoxGeometry :args="[1, 1, 1]" /> | ||
<TresMeshToonMaterial color="#efefef" /> | ||
</TresMesh> | ||
</template> | ||
</template> | ||
<TresDirectionalLight :intensity="1" /> | ||
<TresAmbientLight :intensity="1" /> | ||
</TresCanvas> | ||
</template> |
Oops, something went wrong.