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

refactor: events (WIP) #831

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions playground/vue/src/pages/events/Blocking.vue
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>
163 changes: 163 additions & 0 deletions playground/vue/src/pages/events/Connect.vue
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>&lt;TresCanvas :events="{connect: ...}" /&gt;</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>
87 changes: 87 additions & 0 deletions playground/vue/src/pages/events/DeprecatedEventNames.vue
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>
Loading
Loading