diff --git a/demo/distance.html b/demo/distance.html index e518edd..1e33c09 100644 --- a/demo/distance.html +++ b/demo/distance.html @@ -24,13 +24,9 @@ border: 2px solid rgba(0, 0, 0, 0.5); } - distance-field canvas { + distance-field { position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: -1; + inset: 0; } diff --git a/src/distance-field.ts b/src/distance-field.ts index 1c5887c..ade800e 100644 --- a/src/distance-field.ts +++ b/src/distance-field.ts @@ -9,15 +9,16 @@ import { WebGLUtils } from './utils/webgl.ts'; export class DistanceField extends HTMLElement { static tagName = 'distance-field'; - private geometries: NodeListOf; private textures: WebGLTexture[] = []; private pingPongIndex: number = 0; + private shapes!: NodeListOf; private canvas!: HTMLCanvasElement; private glContext!: WebGL2RenderingContext; private framebuffer!: WebGLFramebuffer; private fullscreenQuadVAO!: WebGLVertexArrayObject; private shapeVAO!: WebGLVertexArrayObject; + private offsetCache: Map = new Map(); private jfaProgram!: WebGLProgram; // Shader program for the Jump Flooding Algorithm private renderProgram!: WebGLProgram; // Shader program for final rendering @@ -25,11 +26,13 @@ export class DistanceField extends HTMLElement { private static readonly MAX_DISTANCE = 99999.0; - constructor() { - super(); + static define() { + customElements.define(this.tagName, this); + } + connectedCallback() { // Collect all geometry elements to process - this.geometries = document.querySelectorAll('fc-geometry'); + this.shapes = document.querySelectorAll('fc-geometry'); // Initialize WebGL context and canvas const { gl, canvas } = WebGLUtils.createWebGLCanvas(this.clientWidth, this.clientHeight, this); @@ -53,15 +56,9 @@ export class DistanceField extends HTMLElement { // Start the Jump Flooding Algorithm this.runJFA(); - } - static define() { - customElements.define(this.tagName, this); - } - - connectedCallback() { window.addEventListener('resize', this.handleResize); - this.geometries.forEach((geometry) => { + this.shapes.forEach((geometry) => { geometry.addEventListener('move', this.handleGeometryUpdate); geometry.addEventListener('resize', this.handleGeometryUpdate); }); @@ -69,7 +66,7 @@ export class DistanceField extends HTMLElement { disconnectedCallback() { window.removeEventListener('resize', this.handleResize); - this.geometries.forEach((geometry) => { + this.shapes.forEach((geometry) => { geometry.removeEventListener('move', this.handleGeometryUpdate); geometry.removeEventListener('resize', this.handleGeometryUpdate); }); @@ -146,6 +143,13 @@ export class DistanceField extends HTMLElement { if (!this.framebuffer) { this.framebuffer = gl.createFramebuffer()!; } + + // Check if framebuffer is complete + const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + if (status !== gl.FRAMEBUFFER_COMPLETE) { + console.error('Framebuffer is not complete:', status); + return; + } } /** @@ -163,7 +167,7 @@ export class DistanceField extends HTMLElement { // Collect positions and assign unique IDs to all shapes const positions: number[] = []; - this.geometries.forEach((geometry, index) => { + this.shapes.forEach((geometry, index) => { const rect = geometry.getBoundingClientRect(); // Convert DOM coordinates to Normalized Device Coordinates (NDC) @@ -239,7 +243,7 @@ export class DistanceField extends HTMLElement { // Bind VAO and draw shapes gl.bindVertexArray(this.shapeVAO); - gl.drawArrays(gl.TRIANGLES, 0, this.geometries.length * 6); + gl.drawArrays(gl.TRIANGLES, 0, this.shapes.length * 6); // Unbind VAO and framebuffer gl.bindVertexArray(null); @@ -251,15 +255,11 @@ export class DistanceField extends HTMLElement { * It progressively reduces step sizes to refine the distance calculations. */ private runJFA() { - const maxDimension = Math.max(this.canvas.width, this.canvas.height); - let stepSize = Math.pow(2, Math.floor(Math.log2(maxDimension))); - - const minStepSize = 1; + let stepSize = 1 << Math.floor(Math.log2(Math.max(this.canvas.width, this.canvas.height))); // Perform passes with decreasing step sizes - while (stepSize >= minStepSize) { + for (; stepSize >= 1; stepSize >>= 1) { this.renderPass(stepSize); - stepSize = Math.floor(stepSize / 2); } // Render the final result to the screen @@ -403,13 +403,19 @@ export class DistanceField extends HTMLElement { * @returns A Float32Array of offsets. */ private computeOffsets(stepSize: number): Float32Array { - const offsets: number[] = []; + if (this.offsetCache.has(stepSize)) { + return this.offsetCache.get(stepSize)!; + } + + const offsets = []; for (let y = -1; y <= 1; y++) { for (let x = -1; x <= 1; x++) { offsets.push((x * stepSize) / this.canvas.width, (y * stepSize) / this.canvas.height); } } - return new Float32Array(offsets); + const offsetArray = new Float32Array(offsets); + this.offsetCache.set(stepSize, offsetArray); + return offsetArray; } /** @@ -448,7 +454,7 @@ export class DistanceField extends HTMLElement { } // Clear other references - this.geometries = null!; + this.shapes = null!; } } @@ -526,6 +532,12 @@ out vec4 outColor; uniform sampler2D u_texture; +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + void main() { vec4 texel = texture(u_texture, v_texCoord); @@ -533,12 +545,9 @@ void main() { float shapeID = texel.z; float distance = texel.a; - // Generate a hash-based color for each shape - vec3 shapeColor = vec3( - fract(sin(shapeID * 12.9898) * 43758.5453), - fract(sin(shapeID * 78.233) * 43758.5453), - fract(sin(shapeID * 93.433) * 43758.5453) - ); + float hue = fract(shapeID * 0.61803398875); // Golden ratio conjugate + vec3 shapeColor = hsv2rgb(vec3(hue, 0.5, 0.95)); + // Visualize distance as intensity float intensity = exp(-distance * 10.0); diff --git a/src/utils/webgl.ts b/src/utils/webgl.ts index 33d6722..e0c47e9 100644 --- a/src/utils/webgl.ts +++ b/src/utils/webgl.ts @@ -41,10 +41,7 @@ export class WebGLUtils { // Set canvas styles canvas.style.position = 'absolute'; - canvas.style.top = '0'; - canvas.style.left = '0'; - canvas.style.width = '100%'; - canvas.style.height = '100%'; + canvas.style.inset = '0'; canvas.style.zIndex = '-1'; canvas.width = width;