threlte logo
@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

  let paneExpanded = false

  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"
  bind:expanded={paneExpanded}
>
  <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 either x, y or z to the axis property.
  • circular: grid formed of concentric circles. It includes a maxRadius property that sets the maximum growth extent for the grid. A value of 0 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 a maxRadius property. Additionally, it has two properties for specifying dividers: cellDivider and sectionDivider. 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.
GridLinesCircularPolar
Grid previewGrid previewGrid previewGrid preview

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.

Component Signature

<Grid> extends <T . Mesh> and supports all its props, slot props, bindings and events.

Props

name
type
required
default
description

axis
'x' | 'y' | 'z'
no
'x'
'line' only. Designates the world axis along which the line will be oriented.

cellColor
ColorRepresentation
no
'#000000'

cellDividers
number
no
6
'polar' only. How many lines will divide the polar grid. Specifies the number of lines that will subdivide the polar grid. For instance, 2 dividers will quarter the grid into 4 sections of 90° each, while 6 dividers will divide the grid into 12 segments, each measuring 30°.

cellSize
number
no
1

cellThickness
number
no
1

fadeDistance
number
no
100

fadeStrength
number
no
1

followCamera
boolean
no
false

gridSize
number | [number, number]
no
[20,20]

infiniteGrid
boolean
no
false

maxRadius
number
no
0
'circular' and 'polar' only. 0 removes the constraint.

plane
'xz' | 'xy' | 'zy'
no
'xz'

sectionColor
ColorRepresentation
no
'#0000ee'

sectionDividers
number
no
2
'polar' only. Specifies the number of lines that will subdivide the polar grid. For instance, 2 dividers will quarter the grid into 4 sections of 90° each, while 6 dividers will divide the grid into 12 segments, each measuring 30°.

sectionSize
number
no
10

sectionThickness
number
no
2

type
'grid' | 'lines' | 'circular' | 'polar'
no
'grid'