Skip to content

MeshLineGeometry

The MeshLineGeometry class builds the line mesh geometry from raw point data, handling the complex vertex calculations needed for thick, smooth lines.

Quick Links:

Constructor

ts
new MeshLineGeometry(options?: MeshLineGeometryOptions)

MeshLineGeometryOptions (partial)

ts
type LinePoint = [x: number, y: number, z?: number] | THREE.Vector2 | THREE.Vector3 | { x: number, y: number, z?: number }
type LinePoints = Float32Array | number[] | THREE.BufferGeometry | LinePoint[]

interface MeshLineGeometryOptions {
  lines?: LinePoints | LinePoints[]          // One line or multiple lines
  closed?: boolean | boolean[]               // Close the loop(s)
  widthCallback?: (t: number) => number      // variable width 
  usage?: THREE.Usage                        // Optional buffer usage hint : StaticDrawUsage / DynamicDrawUsage / StreamDrawUsage
  verbose?: boolean                          // Console logging

  // Optional CPU-side corner smoothing (see "Smooth sharp bends" below)
  smoothSharpBends?: boolean                 // default false; skipped under gpuPositionNode
  smoothSharpBendsAlpha?: number             // default 0.001
  smoothSharpBendsThreshold?: number         // default -0.5 (dot(dir_in, dir_out) cutoff)

  // Flags to include / exclude generated attributes (advanced)
  needsPositions?: boolean
  needsPrevious?: boolean
  needsNext?: boolean
  needsUV?: boolean
  needsSide?: boolean
  needsProgress?: boolean
  needsWidth?: boolean
}

MeshLineGeometry mirrors most of MeshLine's geometry-related options and can be used directly when you need fine-grained control.

Smooth sharp bends

Screen-space meshlines fundamentally can't render a ribbon cleanly through a single vertex whose two adjacent segments diverge at a near-hairpin angle — the bisector collapses and the ribbon picks up spikes or bowtie artifacts at oblique camera views. Industrial wide-line libraries (Mapbox GL, Cesium, Spite's original MeshLine) all sidestep this by subdividing sharp corners on the CPU before handing vertices to the shader.

MeshLineGeometry can do the same when smoothSharpBends is enabled:

Without smoothingsingle hairpin vertex → ribbon spike · width collapsespike / bowtie artifactWith smoothSharpBends · α (illustrative)hairpin split into two cutoff vertices → clean beveloriginal vertexα · segment
  • Default off. GPU buffers match your input points unless you enable smoothSharpBends. When enabled, every vertex whose interior bend is sharper than ~60° (i.e. dot(dir_in, dir_out) < -0.5) is replaced by two cutoff points sitting smoothSharpBendsAlpha of the way back along each adjacent segment.
  • smoothSharpBendsAlpha (default 0.001 once smoothing is enabled) controls how much of the peak you sacrifice. The default is small enough that the cutoff is visually imperceptible while keeping the shader miter math stable; larger values flatten the tip into a bevel-like cap. The bend angles at the new vertices are fixed by the original corner angle — α only controls how visible the cutoff is.
  • smoothSharpBendsThreshold (default -0.5) is the dot(dir_in, dir_out) cutoff below which a vertex is considered "too sharp". Lower (more negative) values subdivide only the very sharpest corners.
js
new MeshLine({
  lines: myZigzag,
  smoothSharpBends: true,          // opt in: changes topology at sharp corners
  smoothSharpBendsAlpha: 0.001,    // default once enabled — near-imperceptible cutoff
})
  .join({ limit: 2 })              // pair with a tighter miter clamp for zigzag-style polylines

Tuning for very sharp polylines: the default α is usually fine once smoothing is enabled. If you want a visibly flatter bevel at the tip, raise α to 0.050.1. Pair a small α with a lower miterLimit (around 2): the geometry pass handles sharp corners below the threshold and the tighter miter clamp flattens any residual spikes above it into clean bevels.

Leave smoothSharpBends off when you need the GPU vertex count to match your input polyline exactly — e.g. if you're animating per-vertex data, using custom per-vertex attributes, or relying on a stable index mapping.

ℹ️ GPU-positioned lines: when gpuPositionNode is set, the CPU polyline is a straight-line template whose point count drives the progress grid the GPU samples against. CPU smoothing is skipped in that case — subdividing the template would shift progress values and break GPU position lookups. If you need corner smoothing for a GPU-positioned line, do it inside your position node.

Methods

setLines()

ts
setLines(
  lines: LinePoints | LinePoints[]
): void

Replace or initialize the geometry with one or multiple line segments. A single line can be passed directly; pass an array of line inputs for multiple disconnected lines.

When a THREE.BufferGeometry is provided, the positions are extracted from its 'position' attribute. This allows direct conversion of existing Three.js geometries into MeshLine format.

Parameters

  • lines – A line input, or an array of line inputs for multiple disconnected lines. Each line can be:
    • Float32Array of flattened XYZ coordinates
    • Flat numeric XYZ array
    • Nested number array of [x,y,z] coordinates
    • Vector2, Vector3, or plain { x, y, z? } point arrays
    • THREE.BufferGeometry with a 'position' attribute

dispose()

ts
dispose(): void

Releases geometry resources. Call when the geometry is no longer needed.

setPositions()

ts
setPositions(
  positions: LinePoints | LinePoints[],
  updateBounding?: boolean
): void

Efficiently updates vertex positions without rebuilding GPU buffers. The function supports:

Float32Array – update a single line.
Float32Array[] – update multiple lines (each array must keep its original length).
• Arrays of tuples, vectors, or point objects are converted under the hood (slower, avoid in hot loops).

If the line count or point count changes, the geometry falls back to a full rebuild automatically using setLines(). This ensures proper buffer allocation but is less efficient than in-place updates. For best performance, maintain consistent line counts and point counts when using setPositions().

positions – Must match the original line(s) vertex count exactly. Re-use the same typed arrays each frame for best performance.
updateBounding – Recomputes bounding volumes when true (default false). Skip when the line stays roughly inside view.

Example with multiple dynamic lines:

js
const lines = [ new Float32Array( NUM * 3 ), new Float32Array( NUM * 3 ) ]
const geometry = new MeshLineGeometry({ lines });

function animate() {
  updateFirstLine(lines[0])
  updateSecondLine(lines[1])
  geometry.setPositions( lines ); // uploads changes for both lines
  requestAnimationFrame( animate );
}

Pass updateBounding: true when dynamic edits can move the line outside its previous bounds and frustum culling should stay exact.

Usage Examples

For practical examples, see:

Internal Structure

The geometry generates these vertex attributes:

Input polyline · N pointsCPU pointsexpandGPU triangle strip · 2N verticesside = +1side = -1each CPU point → pair of GPU vertices, offset perpendicular by `lineWidth`
  • position - Vertex positions
  • previous - Previous point for direction calculation
  • next - Next point for direction calculation
  • side - Side indicator (-1 or 1) for line thickness
  • width - Width multiplier per vertex
  • uv - Texture coordinates
  • progress - Position along line (0-1) for effects

These attributes work together with the MeshLineNodeMaterial to create smooth, thick lines.