Skip to content

Commit 826833c

Browse files
authoredNov 29, 2023
feat: sampler and useSampler (#286)
* feat: add useSampler and sampler * chore: add git ignore for .d.ts files * docs: add sampler and use sampler docs * chore: change name useSampler for useSurfaceSampler
1 parent d69e971 commit 826833c

File tree

13 files changed

+387
-1
lines changed

13 files changed

+387
-1
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ docs/.vitepress/dist/
2727
docs/.vitepress/cache/
2828
docs/.vitepress/.temp/
2929
docs/components.d.ts
30+
playground/components.d.ts

‎docs/.vitepress/config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ export default defineConfig({
6363
{ text: 'GlobalAudio', link: '/guide/abstractions/global-audio' },
6464
{ text: 'Fbo', link: '/guide/abstractions/fbo' },
6565
{ text: 'useFBO', link: '/guide/abstractions/use-fbo' },
66+
{ text: 'useSurfaceSampler', link: '/guide/abstractions/use-surface-sampler' },
67+
{ text: 'Sampler', link: '/guide/abstractions/sampler' },
6668
],
6769
},
6870
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
import { TresCanvas } from '@tresjs/core'
4+
import { OrbitControls, Sampler } from '@tresjs/cientos'
5+
</script>
6+
7+
<template>
8+
<TresCanvas clear-color="#82DBC5">
9+
<TresPerspectiveCamera :position="[0, 0.5, 5]" />
10+
<OrbitControls />
11+
12+
<Sampler :count="50">
13+
<TresMesh>
14+
<TresTorusGeometry />
15+
</TresMesh>
16+
17+
<TresInstancedMesh :args="[null!, null!, 1000]">
18+
<TresBoxGeometry :args="[0.1, 0.1, 0.1]" />
19+
<TresMeshNormalMaterial />
20+
</TresInstancedMesh>
21+
</Sampler>
22+
<TresGridHelper :args="[10, 10]" />
23+
</TresCanvas>
24+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<script setup lang="ts">
2+
import { ref, watch } from 'vue'
3+
import { TresCanvas } from '@tresjs/core'
4+
import { OrbitControls, useSurfaceSampler } from '@tresjs/cientos'
5+
6+
const torusRef = ref()
7+
const instancesRef = ref()
8+
9+
watch(torusRef, (value) => {
10+
useSurfaceSampler(value, 50, instancesRef.value, 'color')
11+
})
12+
</script>
13+
14+
<template>
15+
<TresCanvas clear-color="#82DBC5">
16+
<TresPerspectiveCamera :position="[0, 0.5, 5]" />
17+
<OrbitControls />
18+
19+
<TresMesh ref="torusRef">
20+
<TresTorusGeometry />
21+
</TresMesh>
22+
23+
<TresInstancedMesh
24+
ref="instancesRef"
25+
:args="[null!, null!, 1_000]"
26+
>
27+
<TresSphereGeometry :args="[0.1, 32, 32]" />
28+
<TresMeshNormalMaterial />
29+
</TresInstancedMesh>
30+
31+
<TresGridHelper :args="[10, 10]" />
32+
</TresCanvas>
33+
</template>

‎docs/guide/abstractions/sampler.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Sampler <Badge type="warning" text="^3.7.0" />
2+
3+
Declarative abstraction around MeshSurfaceSampler & InstancedMesh. It samples points from the passed mesh and transforms an InstancedMesh's matrix to distribute instances on the points.
4+
5+
<DocsDemo>
6+
<SamplerDemo />
7+
</DocsDemo>
8+
9+
## Usage
10+
11+
`Experience.vue`
12+
13+
<<< @/.vitepress/theme/components/SamplerDemo.vue{4,12-21}
14+
15+
## Props
16+
17+
| Props | Description |
18+
|--------------|--------------------------------------------------------------------|
19+
| mesh | **Mesh** Surface mesh from which to sample |
20+
| count | **Number** Number of samples |
21+
| instanceMesh | **InstanceMesh** InstanceMesh to scatter |
22+
| weight | **String** A vertex attribute to be used as a weight when sampling |
23+
| transform | **Function** A function that can be used as a custom sampling |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# useSurfaceSampler <Badge type="warning" text="^3.7.0" />
2+
3+
A hook to obtain the result of the <Sampler /> as a buffer. Useful for driving anything other than InstancedMesh via the Sampler.
4+
5+
<DocsDemo>
6+
<UseSurfaceSamplerDemo />
7+
</DocsDemo>
8+
9+
## Usage
10+
11+
<<< @/.vitepress/theme/components/UseSurfaceSamplerDemo.vue{4,10,19-29}
12+
13+
## Props
14+
15+
| Props | Description |
16+
|--------------|--------------------------------------------------------------------|
17+
| mesh | **Mesh** Surface mesh from which to sample |
18+
| count | **Number** Number of samples |
19+
| instanceMesh | **InstanceMesh** InstanceMesh to scatter |
20+
| weight | **String** A vertex attribute to be used as a weight when sampling |
21+
| transform | **Function** A function that can be used as a custom sampling |

‎playground/components.d.ts

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export {}
88
declare module 'vue' {
99
export interface GlobalComponents {
1010
AkuAku: typeof import('./src/components/AkuAku.vue')['default']
11-
DirectivesDemo: typeof import('./src/components/DirectivesDemo.vue')['default']
1211
FboCube: typeof import('./src/components/FboCube.vue')['default']
1312
Gltf: typeof import('./src/components/gltf/index.vue')['default']
1413
ModelsDemo: typeof import('./src/components/ModelsDemo.vue')['default']
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<script setup lang="ts">
2+
import { ref, shallowReactive } from 'vue'
3+
import { TresCanvas } from '@tresjs/core'
4+
import { OrbitControls, Sampler, useTweakPane } from '@tresjs/cientos'
5+
import { SRGBColorSpace, ACESFilmicToneMapping } from 'three'
6+
import type { Mesh } from 'three'
7+
8+
const gl = {
9+
clearColor: '#82DBC5',
10+
shadows: true,
11+
alpha: false,
12+
outputColorSpace: SRGBColorSpace,
13+
toneMapping: ACESFilmicToneMapping,
14+
}
15+
16+
const torusRef = ref<Mesh>()
17+
const instancesRef = ref<Mesh>()
18+
19+
const state = shallowReactive({
20+
count: 1,
21+
})
22+
23+
const { pane } = useTweakPane()
24+
25+
pane.addInput(state, 'count', {
26+
label: 'samplers',
27+
min: 1,
28+
max: 50,
29+
step: 1,
30+
})
31+
</script>
32+
33+
<template>
34+
<TresLeches />
35+
<TresCanvas v-bind="gl">
36+
<TresPerspectiveCamera :position="[0, 0.5, 5]" />
37+
<OrbitControls />
38+
39+
<Sampler :count="state.count">
40+
<TresMesh ref="torusRef">
41+
<TresTorusGeometry />
42+
</TresMesh>
43+
44+
<TresInstancedMesh
45+
ref="instancesRef"
46+
:args="[null!, null!, 1000]"
47+
>
48+
<TresBoxGeometry
49+
:args="[0.1, 0.1, 0.1]"
50+
/>
51+
<TresMeshNormalMaterial />
52+
</TresInstancedMesh>
53+
</Sampler>
54+
<TresGridHelper
55+
:args="[10, 10]"
56+
/>
57+
</TresCanvas>
58+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<script setup lang="ts">
2+
import { TresCanvas } from '@tresjs/core'
3+
import { OrbitControls, useSurfaceSampler } from '@tresjs/cientos'
4+
5+
const torusRef = ref()
6+
const instancesRef = ref()
7+
8+
watch(torusRef, (value) => {
9+
useSurfaceSampler(value, 50, instancesRef.value, 'color')
10+
})
11+
</script>
12+
13+
<template>
14+
<TresCanvas clear-color="#82DBC5">
15+
<TresPerspectiveCamera :position="[0, 0.5, 5]" />
16+
<OrbitControls />
17+
18+
<TresMesh
19+
ref="torusRef"
20+
>
21+
<TresTorusGeometry />
22+
</TresMesh>
23+
24+
<TresInstancedMesh
25+
ref="instancesRef"
26+
:args="[null!, null!, 1_000]"
27+
>
28+
<TresSphereGeometry
29+
:args="[0.1, 32, 32]"
30+
/>
31+
<TresMeshNormalMaterial />
32+
</TresInstancedMesh>
33+
34+
<TresGridHelper
35+
:args="[10, 10]"
36+
/>
37+
</TresCanvas>
38+
</template>

‎playground/src/router/routes/abstractions.ts

+10
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,14 @@ export const abstractionsRoutes = [
3939
name: 'useFbo',
4040
component: () => import('../../pages/abstractions/useFBODemo.vue'),
4141
},
42+
{
43+
path: '/abstractions/use-surface-sampler',
44+
name: 'useSampler',
45+
component: () => import('../../pages/abstractions/useSurfaceSampler.vue'),
46+
},
47+
{
48+
path: '/abstractions/sampler',
49+
name: 'Sampler',
50+
component: () => import('../../pages/abstractions/Sampler.vue'),
51+
},
4252
]

‎src/core/abstractions/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import MouseParallax from './MouseParallax.vue'
66
import { GlobalAudio } from './GlobalAudio'
77
import Lensflare from './Lensflare/component.vue'
88
import Fbo from './useFBO/component.vue'
9+
import Sampler from './useSurfaceSampler/component.vue'
910

1011
export * from './useFBO/'
12+
export * from './useSurfaceSampler'
1113
export * from '../staging/useEnvironment'
1214
export {
1315
Text3D,
@@ -18,4 +20,5 @@ export {
1820
Lensflare,
1921
GlobalAudio,
2022
Fbo,
23+
Sampler,
2124
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!-- eslint-disable max-len -->
2+
<script setup lang="ts">
3+
import { ref, watchEffect } from 'vue'
4+
import type { InstancedMesh, Mesh } from 'three'
5+
import type { useSurfaceSamplerProps } from '.'
6+
import { useSurfaceSampler } from '.'
7+
8+
const props = defineProps<useSurfaceSamplerProps>()
9+
10+
const samplerRef = ref()
11+
const instancedRef = ref()
12+
const meshToSampleRef = ref()
13+
14+
watchEffect(() => {
15+
instancedRef.value = props.instanceMesh ?? samplerRef.value?.children.find((c: any ) => c.hasOwnProperty('instanceMatrix')) as InstancedMesh
16+
17+
meshToSampleRef.value = props.mesh ?? (samplerRef.value?.children.find((c: any) => c.type === 'Mesh') as Mesh)
18+
19+
useSurfaceSampler(meshToSampleRef.value, props.count, instancedRef.value, props.weight, props.transform)
20+
})
21+
22+
defineExpose({
23+
samplerRef,
24+
})
25+
</script>
26+
27+
<template>
28+
<TresGroup ref="samplerRef">
29+
<slot />
30+
</TresGroup>
31+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { ref } from 'vue'
2+
import {
3+
Vector3,
4+
Color,
5+
Object3D,
6+
InterleavedBuffer,
7+
} from 'three'
8+
import type { Mesh, InstancedMesh } from 'three'
9+
import { MeshSurfaceSampler } from 'three-stdlib'
10+
11+
export interface useSurfaceSamplerProps {
12+
/*
13+
* A function that can be .
14+
*
15+
* @type {function}
16+
* @memberof useSamplerProps
17+
*/
18+
transform?: TransformFn
19+
/*
20+
* Specifies a vertex attribute to be used as a weight when sampling from the surface.
21+
*
22+
* @type {string}
23+
* @memberof useSamplerProps
24+
*/
25+
weight?: string
26+
/*
27+
* Number of samples.
28+
*
29+
* @type {number}
30+
* @memberof useSamplerProps
31+
*/
32+
count?: number
33+
/*
34+
* Surface mesh from which to sample.
35+
*
36+
* @type {Mesh}
37+
* @memberof useSamplerProps
38+
*/
39+
mesh?: Mesh
40+
/*
41+
* Instanced mesh to scatter.
42+
*
43+
* @type {InstancedMesh}
44+
* @memberof useSamplerProps
45+
*/
46+
instanceMesh?: InstancedMesh | null
47+
}
48+
49+
interface SamplePayload {
50+
/**
51+
* The position of the sample.
52+
*/
53+
position: Vector3
54+
/**
55+
* The normal of the mesh at the sampled position.
56+
*/
57+
normal: Vector3
58+
/**
59+
* The vertex color of the mesh at the sampled position.
60+
*/
61+
color: Color
62+
}
63+
64+
type TransformPayload = SamplePayload & {
65+
/**
66+
* The dummy object used to transform each instance.
67+
* This object's matrix will be updated after transforming & it will be used
68+
* to set the instance's matrix.
69+
*/
70+
dummy: Object3D
71+
/**
72+
* The mesh that's initially passed to the sampler.
73+
* Use this if you need to apply transforms from your mesh to your instances
74+
* or if you need to grab attributes from the geometry.
75+
*/
76+
sampledMesh: Mesh
77+
}
78+
79+
export type TransformFn = (payload: TransformPayload, i: number) => void
80+
81+
export const useSurfaceSampler = (
82+
mesh: Mesh,
83+
count: number = 16,
84+
instanceMesh?: InstancedMesh | null,
85+
weight?: string,
86+
transform?: TransformFn,
87+
) => {
88+
const arr = new Float32Array(count * 16)
89+
const buffer = ref(new InterleavedBuffer(arr, 16))
90+
91+
const updateBuffer = () => {
92+
if (!mesh) return
93+
94+
const sampler = new MeshSurfaceSampler(mesh)
95+
96+
if (weight) {
97+
sampler.setWeightAttribute(weight)
98+
}
99+
sampler.build()
100+
101+
const position = new Vector3()
102+
const normal = new Vector3()
103+
const color = new Color()
104+
const dummy = new Object3D()
105+
106+
mesh.updateMatrixWorld(true)
107+
108+
for (let i = 0; i < count; i++) {
109+
sampler.sample(position, normal, color)
110+
111+
if (typeof transform === 'function') {
112+
transform(
113+
{
114+
dummy,
115+
sampledMesh: mesh,
116+
position,
117+
normal,
118+
color,
119+
},
120+
i,
121+
)
122+
}
123+
else {
124+
dummy.position.copy(position)
125+
}
126+
dummy.updateMatrix()
127+
128+
if (instanceMesh) {
129+
instanceMesh.setMatrixAt(i, dummy.matrix)
130+
}
131+
dummy.matrix.toArray(buffer.value.array, i * 16)
132+
}
133+
if (instanceMesh) {
134+
instanceMesh.instanceMatrix.needsUpdate = true
135+
}
136+
137+
buffer.value.needsUpdate = true
138+
}
139+
140+
updateBuffer()
141+
142+
return { buffer }
143+
}

0 commit comments

Comments
 (0)
Please sign in to comment.