@threlte/extras
<Wireframe>
<Wireframe>
patches the material of its parent to render an antialiased, shader based wireframe on a geometry.
<script lang="ts">
import { Color } from 'three'
import { Pane, Checkbox, Slider, Color as ColorInput, Separator } from 'svelte-tweakpane-ui'
import { Canvas } from '@threlte/core'
import Scene from './Scene.svelte'
let wireframeProps = $state({
thickness: 0.7,
squeeze: true,
squeezeMin: 0.2,
squeezeMax: -0.13,
dash: false,
dashInvert: true,
dashRepeats: 4,
dashLength: 0.1,
fill: new Color('lightgreen'),
fillOpacity: 1,
fillMix: 0,
stroke: new Color('red'),
strokeOpacity: 1,
colorBackfaces: false,
backfaceStroke: new Color('lightred')
})
</script>
<Pane
title="Wireframe"
position="fixed"
>
<Slider
label="thickness"
bind:value={wireframeProps.thickness}
/>
<Separator />
<Checkbox
label="squeeze"
bind:value={wireframeProps.squeeze}
/>
<Slider
label="squeezeMin"
bind:value={wireframeProps.squeezeMin}
/>
<Slider
label="squeezeMax"
bind:value={wireframeProps.squeezeMax}
/>
<Separator />
<Checkbox
label="dash"
bind:value={wireframeProps.dash}
/>
<Checkbox
label="dashInvert"
bind:value={wireframeProps.dashInvert}
/>
<Slider
label="dashLength"
bind:value={wireframeProps.dashLength}
/>
<Slider
label="dashRepeats"
step={1}
bind:value={wireframeProps.dashRepeats}
/>
<Separator />
<ColorInput
label="fill"
type="float"
bind:value={wireframeProps.fill}
/>
<Slider
label="fillOpacity"
bind:value={wireframeProps.fillOpacity}
/>
<Slider
label="fillMix"
step={0.01}
bind:value={wireframeProps.fillMix}
/>
<Separator />
<ColorInput
label="stroke"
type="float"
bind:value={wireframeProps.stroke}
/>
<Slider
label="fillOpacity"
bind:value={wireframeProps.strokeOpacity}
/>
<ColorInput
label="backfaceStroke"
type="float"
bind:value={wireframeProps.backfaceStroke}
/>
</Pane>
<div>
<Canvas>
<Scene {wireframeProps} />
</Canvas>
</div>
<style>
div {
position: relative;
height: 100%;
width: 100%;
}
</style>
<script lang="ts">
import { T } from '@threlte/core'
import { useGltf, useGltfAnimations, Wireframe } from '@threlte/extras'
let { wireframeProps } = $props()
const gltf = useGltf('https://threejs.org/examples/models/gltf/Xbot.glb')
let { actions } = useGltfAnimations(gltf)
$effect(() => {
// This effect acts like an init default pose
$actions?.['idle']?.play()
})
</script>
<T.Group dispose={false}>
{#await gltf then { nodes, materials }}
<T is={nodes.Scene}>
<T
is={nodes.Armature}
name="Armature"
scale={0.01}
>
<T is={nodes.mixamorigHips} />
<T.SkinnedMesh
name="Beta_Joints"
geometry={nodes.Beta_Joints.geometry}
material={materials.Beta_Joints_MAT}
skeleton={nodes.Beta_Joints.skeleton}
castShadow
/>
<T.SkinnedMesh
name="Beta_Surface"
geometry={nodes.Beta_Surface.geometry}
material={materials['asdf1:Beta_HighLimbsGeoSG2']}
skeleton={nodes.Beta_Surface.skeleton}
castShadow
>
<Wireframe {...wireframeProps} />
</T.SkinnedMesh>
</T>
</T>
{/await}
</T.Group>
<script lang="ts">
import type { InstancedMesh as ThreeInstancedMesh } from 'three'
import { T, useTask } from '@threlte/core'
import Character from './Character.svelte'
import { InstancedMesh, Instance, Wireframe, Outlines, Float } from '@threlte/extras'
import { Vector3, Quaternion, type QuaternionTuple, type Vector3Tuple } from 'three'
let { wireframeProps } = $props()
let boxes = $state.raw<ThreeInstancedMesh>()
let poses: {
position: Vector3Tuple
quaternion: QuaternionTuple
}[] = []
const numCubes = 70
for (let i = 0; i < numCubes; i += 1) {
const position = new Vector3().randomDirection().multiplyScalar(1).toArray()
position[1] += 1.2
poses.push({
position,
quaternion: new Quaternion().random().toArray()
})
}
useTask((delta) => {
if (boxes) boxes.rotation.y += delta / 60
})
</script>
<T.PerspectiveCamera
makeDefault
position={[-0.8, 1.2, 1.7]}
oncreate={(ref) => {
ref.lookAt(0, 1, 0)
}}
/>
<T.AmbientLight />
<T.DirectionalLight
position={[10, 5, 5]}
castShadow
/>
<Character {wireframeProps} />
<T.Mesh
rotation.x={-90 * (Math.PI / 180)}
receiveShadow
>
<T.CircleGeometry args={[3, 72]} />
<T.MeshStandardMaterial color={'white'} />
<Outlines
color="red"
thickness={10}
/>
</T.Mesh>
<InstancedMesh
bind:ref={boxes}
castShadow
>
<T.BoxGeometry args={[0.07, 0.07, 0.07]} />
<T.MeshStandardMaterial />
<Wireframe {...wireframeProps} />
{#each poses as { position, quaternion }, index}
<Float seed={Math.random() * index}>
<Instance
{position}
{quaternion}
/>
</Float>
{/each}
</InstancedMesh>
<Wireframe>
uses barycentric coordinates to generate the wireframe, which requires a non-indexed geometry to create.
This means that any geometry with a <Wireframe>
that has indices will be converted to a non-indexed version.
Basic Example
Wireframe.svelte
<script>
import { T } from '@threlte/core'
import { Wireframe } from '@threlte/extras'
</script>
<T.Mesh>
<T.BoxGeometry />
<T.MeshStandardMaterial />
<Wireframe />
</T.Mesh>