ogl
Version:
WebGL Library
184 lines (152 loc) • 6.01 kB
JavaScript
import { Geometry } from '../core/Geometry.js';
import { Program } from '../core/Program.js';
import { Mesh } from '../core/Mesh.js';
import { Vec2 } from '../math/Vec2.js';
import { Vec3 } from '../math/Vec3.js';
import { Color } from '../math/Color.js';
const tmp = /* @__PURE__ */ new Vec3();
export class Polyline {
constructor(
gl,
{
points, // Array of Vec3s
vertex = defaultVertex,
fragment = defaultFragment,
uniforms = {},
attributes = {}, // For passing in custom attribs
}
) {
this.gl = gl;
this.points = points;
this.count = points.length;
// Create buffers
this.position = new Float32Array(this.count * 3 * 2);
this.prev = new Float32Array(this.count * 3 * 2);
this.next = new Float32Array(this.count * 3 * 2);
const side = new Float32Array(this.count * 1 * 2);
const uv = new Float32Array(this.count * 2 * 2);
const index = new Uint16Array((this.count - 1) * 3 * 2);
// Set static buffers
for (let i = 0; i < this.count; i++) {
side.set([-1, 1], i * 2);
const v = i / (this.count - 1);
uv.set([0, v, 1, v], i * 4);
if (i === this.count - 1) continue;
const ind = i * 2;
index.set([ind + 0, ind + 1, ind + 2], (ind + 0) * 3);
index.set([ind + 2, ind + 1, ind + 3], (ind + 1) * 3);
}
const geometry = (this.geometry = new Geometry(
gl,
Object.assign(attributes, {
position: { size: 3, data: this.position },
prev: { size: 3, data: this.prev },
next: { size: 3, data: this.next },
side: { size: 1, data: side },
uv: { size: 2, data: uv },
index: { size: 1, data: index },
})
));
// Populate dynamic buffers
this.updateGeometry();
if (!uniforms.uResolution) this.resolution = uniforms.uResolution = { value: new Vec2() };
if (!uniforms.uDPR) this.dpr = uniforms.uDPR = { value: 1 };
if (!uniforms.uThickness) this.thickness = uniforms.uThickness = { value: 1 };
if (!uniforms.uColor) this.color = uniforms.uColor = { value: new Color('#000') };
if (!uniforms.uMiter) this.miter = uniforms.uMiter = { value: 1 };
// Set size uniforms' values
this.resize();
const program = (this.program = new Program(gl, {
vertex,
fragment,
uniforms,
}));
this.mesh = new Mesh(gl, { geometry, program });
}
updateGeometry() {
this.points.forEach((p, i) => {
p.toArray(this.position, i * 3 * 2);
p.toArray(this.position, i * 3 * 2 + 3);
if (!i) {
// If first point, calculate prev using the distance to 2nd point
tmp.copy(p)
.sub(this.points[i + 1])
.add(p);
tmp.toArray(this.prev, i * 3 * 2);
tmp.toArray(this.prev, i * 3 * 2 + 3);
} else {
p.toArray(this.next, (i - 1) * 3 * 2);
p.toArray(this.next, (i - 1) * 3 * 2 + 3);
}
if (i === this.points.length - 1) {
// If last point, calculate next using distance to 2nd last point
tmp.copy(p)
.sub(this.points[i - 1])
.add(p);
tmp.toArray(this.next, i * 3 * 2);
tmp.toArray(this.next, i * 3 * 2 + 3);
} else {
p.toArray(this.prev, (i + 1) * 3 * 2);
p.toArray(this.prev, (i + 1) * 3 * 2 + 3);
}
});
this.geometry.attributes.position.needsUpdate = true;
this.geometry.attributes.prev.needsUpdate = true;
this.geometry.attributes.next.needsUpdate = true;
}
// Only need to call if not handling resolution uniforms manually
resize() {
// Update automatic uniforms if not overridden
if (this.resolution) this.resolution.value.set(this.gl.canvas.width, this.gl.canvas.height);
if (this.dpr) this.dpr.value = this.gl.renderer.dpr;
}
}
const defaultVertex = /* glsl */ `
precision highp float;
attribute vec3 position;
attribute vec3 next;
attribute vec3 prev;
attribute vec2 uv;
attribute float side;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
uniform vec2 uResolution;
uniform float uDPR;
uniform float uThickness;
uniform float uMiter;
varying vec2 vUv;
vec4 getPosition() {
mat4 mvp = projectionMatrix * modelViewMatrix;
vec4 current = mvp * vec4(position, 1);
vec4 nextPos = mvp * vec4(next, 1);
vec4 prevPos = mvp * vec4(prev, 1);
vec2 aspect = vec2(uResolution.x / uResolution.y, 1);
vec2 currentScreen = current.xy / current.w * aspect;
vec2 nextScreen = nextPos.xy / nextPos.w * aspect;
vec2 prevScreen = prevPos.xy / prevPos.w * aspect;
vec2 dir1 = normalize(currentScreen - prevScreen);
vec2 dir2 = normalize(nextScreen - currentScreen);
vec2 dir = normalize(dir1 + dir2);
vec2 normal = vec2(-dir.y, dir.x);
normal /= mix(1.0, max(0.3, dot(normal, vec2(-dir1.y, dir1.x))), uMiter);
normal /= aspect;
float pixelWidthRatio = 1.0 / (uResolution.y / uDPR);
float pixelWidth = current.w * pixelWidthRatio;
normal *= pixelWidth * uThickness;
current.xy -= normal * side;
return current;
}
void main() {
vUv = uv;
gl_Position = getPosition();
}
`;
const defaultFragment = /* glsl */ `
precision highp float;
uniform vec3 uColor;
varying vec2 vUv;
void main() {
gl_FragColor.rgb = uColor;
gl_FragColor.a = 1.0;
}
`;