UNPKG

ogl

Version:
184 lines (152 loc) 6.01 kB
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; } `;