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:
- Common Patterns - Basic geometry usage examples
- Advanced Patterns - Dynamic updates and performance tips
- MeshLine Class - Main API reference
Constructor
new MeshLineGeometry(options?: MeshLineGeometryOptions)MeshLineGeometryOptions (partial)
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:
- 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 sittingsmoothSharpBendsAlphaof the way back along each adjacent segment. smoothSharpBendsAlpha(default0.001once 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 thedot(dir_in, dir_out)cutoff below which a vertex is considered "too sharp". Lower (more negative) values subdivide only the very sharpest corners.
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 polylinesTuning 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.05–0.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
gpuPositionNodeis 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()
setLines(
lines: LinePoints | LinePoints[]
): voidReplace 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:Float32Arrayof flattened XYZ coordinates- Flat numeric XYZ array
- Nested number array of
[x,y,z]coordinates Vector2,Vector3, or plain{ x, y, z? }point arraysTHREE.BufferGeometrywith a 'position' attribute
dispose()
dispose(): voidReleases geometry resources. Call when the geometry is no longer needed.
setPositions()
setPositions(
positions: LinePoints | LinePoints[],
updateBounding?: boolean
): voidEfficiently 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:
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:
- Basic Line Creation in Common Patterns
- Multi-Line Segments for multiple disconnected lines
- Dynamic Updates for efficient position updates
- From BufferGeometry for converting existing geometries
Internal Structure
The geometry generates these vertex attributes:
position- Vertex positionsprevious- Previous point for direction calculationnext- Next point for direction calculationside- Side indicator (-1 or 1) for line thicknesswidth- Width multiplier per vertexuv- Texture coordinatesprogress- Position along line (0-1) for effects
These attributes work together with the MeshLineNodeMaterial to create smooth, thick lines.