@threlte/extras
<Grid>
A robust grid implementation with multiple tweakable parameters.
<script lang="ts">
import { Canvas, T } from '@threlte/core'
import { Grid, Gizmo } from '@threlte/extras'
import { Pane, Slider, Checkbox, Folder, List, Color } from 'svelte-tweakpane-ui'
import Scene from './Scene.svelte'
import { PlaneGeometry } from 'three'
import { createNoise2D } from 'simplex-noise'
let cellSize = 1
let cellColor = '#cccccc'
let cellThickness = 1.4
let sectionSize = 5
let sectionColor = '#ff3e00'
let sectionThickness = 2
let gridSize1 = 20
let gridSize2 = 20
let plane: 'xz' | 'xy' | 'zy' = 'xz'
let planeOptions = {
xz: 'xz',
xy: 'xy',
zy: 'zy'
}
let followCamera = false
let infiniteGrid = false
let fadeDistance = 100
let backGroundColor = '#003eff'
let backgroundOpacity = 0
let fadeStrength = 1
let gridGeometry = 'default'
let gridGeometryOptions = {
plane: 'default',
terrain: 'Terrain'
}
let gridType: 'polar' | 'grid' | 'lines' | 'circular' = 'polar'
let gridTypeOptions = {
polar: 'polar',
grid: 'grid',
lines: 'lines',
circular: 'circular'
}
let linesAxis = 'x'
let linesAxisOptions = {
x: 'x',
y: 'y',
z: 'z'
}
let maxRadius = 10
let cellDividers = 6
let sectionDividers = 2
const terrainSize = 30
const geometry = new PlaneGeometry(terrainSize, terrainSize, 100, 100)
const noise = createNoise2D()
const vertices = geometry.getAttribute('position').array
for (let i = 0; i < vertices.length; i += 3) {
const x = vertices[i]
const y = vertices[i + 1]
// @ts-ignore
vertices[i + 2] = noise(x / 5, y / 5) * 1 + noise(x / 40, y / 40) * 2
}
geometry.computeVertexNormals()
</script>
<Pane
title="Grid"
position="fixed"
>
<Folder title="Cells">
<Slider
bind:value={cellSize}
label="size"
step={1}
min={1}
max={5}
/>
<Color
bind:value={cellColor}
label="color"
/>
<Slider
bind:value={cellThickness}
label="thickness"
step={0.1}
min={1}
max={10}
/>
</Folder>
<Folder title="Sections">
<Slider
bind:value={sectionSize}
label="size"
step={1}
min={1}
max={50}
/>
<Color
bind:value={sectionColor}
label="color"
/>
<Slider
bind:value={sectionThickness}
label="thickness"
step={0.1}
min={1}
max={10}
/>
</Folder>
<Folder title="General">
<Slider
bind:value={gridSize1}
label="size 1"
step={1}
min={1}
max={100}
/>
<Slider
bind:value={gridSize2}
label="size 2"
step={1}
min={1}
max={100}
/>
<List
bind:value={plane}
label="plane"
options={planeOptions}
/>
<Checkbox
bind:value={followCamera}
label="follow camera"
/>
<Checkbox
bind:value={infiniteGrid}
label="infinite Grid"
/>
<Slider
bind:value={fadeDistance}
label="fade distance"
step={10}
min={10}
max={400}
/>
<Color
bind:value={backGroundColor}
label="background color"
/>
<Slider
bind:value={backgroundOpacity}
label="background opacity"
step={0.01}
min={0}
max={1}
/>
<Slider
bind:value={fadeStrength}
label="fade strength"
step={0.1}
min={0}
max={20}
/>
<List
bind:value={gridGeometry}
options={gridGeometryOptions}
label="grid geometry"
/>
</Folder>
<Folder title="Types of Grid">
<List
bind:value={gridType}
options={gridTypeOptions}
label="type"
/>
{#if gridType == 'lines'}
<List
bind:value={linesAxis}
options={linesAxisOptions}
label="axis"
/>
{/if}
{#if gridType == 'polar' || gridType == 'circular'}
<Slider
bind:value={maxRadius}
label="max radius"
step={1}
min={0}
max={15}
/>
{/if}
{#if gridType == 'polar'}
<Slider
bind:value={cellDividers}
label="cell dividers"
step={1}
min={0}
max={18}
/>
<Slider
bind:value={sectionDividers}
label="section dividers"
step={1}
min={0}
max={18}
/>
{/if}
</Folder>
</Pane>
<div>
<Canvas>
<Gizmo
horizontalPlacement="left"
size={70}
paddingX={10}
paddingY={10}
/>
{#if gridGeometry == 'Terrain'}
<Grid
position.y={-2}
{plane}
{cellColor}
{cellSize}
{cellThickness}
{sectionColor}
{sectionSize}
{sectionThickness}
{followCamera}
{infiniteGrid}
{fadeDistance}
{fadeStrength}
gridSize={[gridSize1, gridSize2]}
backgroundColor={backGroundColor}
{backgroundOpacity}
type={gridType}
axis={linesAxis}
{maxRadius}
{cellDividers}
{sectionDividers}
>
<T is={geometry} />
</Grid>
{:else}
<Grid
{plane}
{cellColor}
{cellSize}
{cellThickness}
{sectionColor}
{sectionSize}
{sectionThickness}
{followCamera}
{infiniteGrid}
{fadeDistance}
{fadeStrength}
gridSize={[gridSize1, gridSize2]}
backgroundColor={backGroundColor}
{backgroundOpacity}
type={gridType}
axis={linesAxis}
{maxRadius}
{cellDividers}
{sectionDividers}
/>
{/if}
<Scene />
</Canvas>
</div>
<style>
div {
height: 100%;
}
</style>
<script lang="ts">
import { T } from '@threlte/core'
import { OrbitControls } from '@threlte/extras'
import { BoxGeometry } from 'three'
</script>
<T.PerspectiveCamera
makeDefault
position={[15, 15, 15]}
fov={36}
target={[0, 0, 0]}
>
<OrbitControls />
</T.PerspectiveCamera>
<!-- Make a box in every second cell to show aligment -->
{#each { length: 10 } as _h, x}
{#each { length: 10 } as _v, y}
{#if x % 3 == 0 && y % 3 == 0}
<T.Group position={[x - 4.5, 0.5, y - 4.5]}>
<T.Mesh>
<T.BoxGeometry />
<T.MeshBasicMaterial
args={[
{
color: '#ffffff',
opacity: 0.9,
transparent: true
}
]}
/>
</T.Mesh>
<T.LineSegments>
<T.EdgesGeometry args={[new BoxGeometry()]} />
<T.MeshBasicMaterial
args={[
{
color: 0x000000
}
]}
/>
</T.LineSegments>
</T.Group>
{/if}
{/each}
{/each}
Usage
This component provides sensible defaults. You can initialize the default grid with just <Grid>
. ref
passes a reference from the <T.Mesh/>
the grid is constructed on.
Grid types
The grid type can be selected by setting the type
parameter. The available grid types are:
grid
: represents a standard box grid. It does not require any additional properties. (default)lines
: grid consisting of lines that align along a single world axis. You specify this axis by providing eitherx
,y
orz
to theaxis
property.circular
: grid formed of concentric circles. It includes amaxRadius
property that sets the maximum growth extent for the grid. A value of0
removes this limit, allowing the grid to occupy the entire geometry, even if it results in incomplete circles.polar
: similar to the circular type, but it also features lines that subdivide the concentric circles. It too has amaxRadius
property. Additionally, it has two properties for specifying dividers:cellDivider
andsectionDivider
. These determine how many lines will segment the circle into various sectors. For example, 2 lines result in 4 segments at 90° each, while 6 lines create 12 sectors at 30° apiece.
Grid | Lines | Circular | Polar |
---|---|---|---|
Cells and Sections
Grid is split into cells and sections. Cell is meant to represent the smallest units on your grid, whereas
section is a group of cells. You can adjust the size of the grid by changing the cellSize
and sectionSize
parameters. Size is in Three world units, so for example a mesh with BoxGeometry(1,1,1)
will fit perfectly into
a size 1 cell. By default a cell is 1 unit and a section 10, which means that a grid of 10x10 cells will be
outlined with a section line.
Lines
You can adjust the color and thickness of cell and section lines with cellColor
, cellThickness
, sectionColor
, sectionThickness
.
Grid size and fading
The <Grid>
component is a THREE.Mesh
with a PlaneGeometry
attached to it. The gridSize
parameter defines the size of the PlaneGeometry
.
You can extend the grid into infinity if you set the infiniteGrid
parameter to true
.
Changing fadeDistance
sets how far from the camera position the grid begins to fade by having its alpha reduced. fadeStrength
determines how fast it happens (exponent). fadeStrength = 0
means that there is no fading (not recommended for large grids).
Custom geometry
You have the option to insert your own custom geometry into the <Grid/>
slot. The preceding example demonstrates this by showcasing a preview of a terrain-like geometry generated using Perlin noise.
<Grid>
<T.BoxGeometry />
</Grid>
Follow camera
Setting followCamera
to true applies a transform that moves the grid to the camera’s position on the chosen plane
.