NgtsSampler / surfaceSampler
import { ChangeDetectionStrategy, Component } from '@angular/core';import { SobaWrapper } from '@soba/wrapper.ts';import { NgtCanvas, provideNgtRenderer } from 'angular-three/dom';import { SceneGraph } from './scene-graph';
@Component({ selector: 'app-sampler', template: ` <ngt-canvas [camera]="{ position: [0, 0, 5], fov: 50 }"> <app-soba-wrapper *canvasContent [grid]="false"> <app-scene-graph /> </app-soba-wrapper> </ngt-canvas> `, changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'sampler-demo relative block h-full' }, imports: [NgtCanvas, SobaWrapper, SceneGraph],})export default class Sampler { static clientProviders = [provideNgtRenderer()];}import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';import { NgtArgs } from 'angular-three';import type { TransformFn } from 'angular-three-soba/misc';import { NgtsSampler } from 'angular-three-soba/misc';
@Component({ selector: 'app-scene-graph', template: ` <ngts-sampler [options]="{ count: 500, transform: transformFn }"> <!-- Source mesh to sample from --> <ngt-mesh> <ngt-torus-knot-geometry *args="[1, 0.3, 128, 32]" /> <ngt-mesh-standard-material color="#444" [wireframe]="true" [opacity]="0.3" transparent /> </ngt-mesh>
<!-- Instanced mesh for samples --> <ngt-instanced-mesh *args="[undefined, undefined, 500]"> <ngt-sphere-geometry *args="[0.02, 8, 8]" /> <ngt-mesh-standard-material color="#ff6b6b" /> </ngt-instanced-mesh> </ngts-sampler> `, schemas: [CUSTOM_ELEMENTS_SCHEMA], changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgtArgs, NgtsSampler],})export class SceneGraph { transformFn: TransformFn = ({ dummy, position, normal }) => { dummy.position.copy(position); // Orient along surface normal dummy.lookAt(position.clone().add(normal)); // Random scale variation dummy.scale.setScalar(0.5 + Math.random() * 1.5); };}NgtsSampler distributes instances across a mesh surface using THREE.js MeshSurfaceSampler. It samples points from a mesh and automatically updates an InstancedMesh with the sampled transforms.
Usage
With Content Children
<ngts-sampler [options]="{ weight: 'normal', transform: transformPoint, count: 500 }"> <ngt-mesh> <ngt-sphere-geometry *args="[2]" /> </ngt-mesh>
<ngt-instanced-mesh *args="[undefined, undefined, 500]"> <ngt-sphere-geometry *args="[0.1]" /> </ngt-instanced-mesh></ngts-sampler>Transform Function
The transform function receives sample data and should mutate payload.dummy to set position, rotation, and scale:
const transformPoint = ({ dummy, position, normal }: TransformPayload) => { dummy.position.copy(position); dummy.lookAt(position.clone().add(normal)); dummy.scale.setScalar(Math.random() * 0.5 + 0.5);};surfaceSampler Function
A reactive function that creates a computed signal sampling points on a mesh surface:
import { surfaceSampler } from 'angular-three-soba/misc';
const samples = surfaceSampler(() => meshRef()?.nativeElement, { count: () => 1000, instancedMesh: () => instancesRef()?.nativeElement, transform: () => ({ dummy, position, normal }) => { dummy.position.copy(position); dummy.lookAt(position.clone().add(normal)); }});Options
Properties
| name | type | description |
|---|---|---|
| count | number | Number of samples to distribute across the mesh surface. Default: 16 |
| weight | string | Name of a vertex attribute for weighted sampling. Higher values = more likely to be sampled |
| transform | TransformFn | Custom transform function applied to each sampled instance |
Utilities for distributing instances across a mesh surface using THREE.js MeshSurfaceSampler.
NgtsSampler
Section titled “NgtsSampler”A component that distributes instances across a mesh surface. It samples points from a mesh and automatically updates an InstancedMesh with the sampled transforms. Both the source mesh and target instances can be provided as inputs or as children.
Usage with Content Children
Section titled “Usage with Content Children”<ngts-sampler [options]="{ weight: 'normal', transform: transformPoint, count: 500 }"> <ngt-mesh> <ngt-sphere-geometry *args="[2]" /> </ngt-mesh>
<ngt-instanced-mesh *args="[undefined, undefined, 500]"> <ngt-sphere-geometry *args="[0.1]" /> </ngt-instanced-mesh></ngts-sampler>Usage with References
Section titled “Usage with References”@Component({ template: ` <ngts-sampler [instances]="instancedRef()" [mesh]="mesh()" [options]="{ count: 500 }" />
<ngt-instanced-mesh #instanced *args="[undefined, undefined, 500]"> <!-- content --> </ngt-instanced-mesh> `,})class MyComponent { instancedRef = viewChild<ElementRef<InstancedMesh>>('instanced');
gltf = gltfResource(() => 'my/mesh/url'); mesh = computed(() => this.gltf.value()?.scene || null);}Inputs
Section titled “Inputs”| Property | Description | Default |
|---|---|---|
mesh | The mesh to sample points from. If not provided, uses the first Mesh child | null |
instances | The InstancedMesh to update with sampled transforms. If not provided, uses first child | null |
options | Sampler configuration object | - |
Options (NgtsSamplerOptions)
Section titled “Options (NgtsSamplerOptions)”| Property | Description | Default |
|---|---|---|
weight | Name of a vertex attribute for weighted sampling. Higher values = more likely to be sampled | undefined |
transform | Custom transform function applied to each sampled instance | undefined |
count | Number of samples to distribute across the mesh surface | 16 |
Transform Function
Section titled “Transform Function”The transform function receives sample data and should mutate payload.dummy to set position, rotation, and scale:
const transformPoint = ({ dummy, position, normal }: TransformPayload) => { dummy.position.copy(position); dummy.lookAt(position.clone().add(normal)); dummy.scale.setScalar(Math.random() * 0.5 + 0.5);};surfaceSampler
Section titled “surfaceSampler”A function that creates a computed signal sampling points on a mesh surface. Returns an InstancedBufferAttribute containing transform matrices for each sample, suitable for use with InstancedMesh or custom instancing solutions.
Signature
Section titled “Signature”function surfaceSampler( mesh: () => ElementRef<THREE.Mesh> | THREE.Mesh | null | undefined, options?: { count?: () => number; transform?: () => TransformFn | undefined; weight?: () => string | undefined; instancedMesh?: () => ElementRef<THREE.InstancedMesh> | THREE.InstancedMesh | null | undefined; },): Signal<THREE.InstancedBufferAttribute>;@Component({ template: ` <ngt-mesh #mesh> <ngt-sphere-geometry /> </ngt-mesh> <ngt-instanced-mesh #instances *args="[undefined, undefined, 1000]"> <ngt-box-geometry *args="[0.1, 0.1, 0.1]" /> </ngt-instanced-mesh> `,})class MyComponent { meshRef = viewChild<ElementRef<THREE.Mesh>>('mesh'); instancesRef = viewChild<ElementRef<THREE.InstancedMesh>>('instances');
samples = surfaceSampler(() => this.meshRef()?.nativeElement, { count: () => 1000, instancedMesh: () => this.instancesRef()?.nativeElement, transform: () => ({ dummy, position, normal }) => { dummy.position.copy(position); dummy.lookAt(position.clone().add(normal)); dummy.scale.setScalar(Math.random() * 0.5 + 0.5); }, });}Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
mesh | () => ElementRef<Mesh> | Mesh | null | undefined | Signal of the mesh to sample from |
options | Object (see below) | Configuration options |
Options
Section titled “Options”| Property | Type | Description |
|---|---|---|
count | () => number | Signal of sample count |
transform | () => TransformFn | undefined | Signal of transform function |
weight | () => string | undefined | Signal of vertex attribute name |
instancedMesh | () => ElementRef<InstancedMesh> | InstancedMesh | null | Signal of target instanced mesh |
Return Value
Section titled “Return Value”Returns a Signal<THREE.InstancedBufferAttribute> containing the transform matrices.