diff --git a/demo/[shaders]falling-sand.html b/demo/[shaders]falling-sand.html index 2ec174d..9eb369a 100644 --- a/demo/[shaders]falling-sand.html +++ b/demo/[shaders]falling-sand.html @@ -23,11 +23,6 @@ border-radius: 2px; } - folk-sand { - position: absolute; - inset: 0; - } - .key-helper { position: fixed; top: 20px; @@ -50,6 +45,15 @@ border-radius: 3px; margin-right: 10px; } + + p { + box-sizing: border-box; + color: white; + position: absolute; + top: 150px; + left: 25px; + border: 1px solid white; + } @@ -64,6 +68,7 @@ +

Sanding

diff --git a/src/folk-sand.ts b/src/folk-sand.ts index ffe92e4..ed5f8f6 100644 --- a/src/folk-sand.ts +++ b/src/folk-sand.ts @@ -8,13 +8,26 @@ import { visualizationShader, vertexShader, } from './folk-sand.glsl.ts'; -import { FolkShape } from './folk-shape.ts'; import { requestAnimationFrame } from './common/rAF.ts'; +import { FolkBaseSet } from './folk-base-set.ts'; +import { css, PropertyValues } from '@lit/reactive-element'; +import { DOMRectTransformReadonly } from './common/DOMRectTransform.ts'; + +export class FolkSand extends FolkBaseSet { + static override tagName = 'folk-sand'; + + static styles = [ + FolkBaseSet.styles, + css` + canvas { + height: 100%; + width: 100%; + pointer-events: auto; + } + `, + ]; -export class FolkSand extends HTMLElement { - static tagName = 'folk-sand'; - - private canvas!: HTMLCanvasElement; + private canvas = document.createElement('canvas'); private gl!: WebGL2RenderingContext; private program!: WebGLProgram; @@ -47,8 +60,6 @@ export class FolkSand extends HTMLElement { private materialType = 4; private brushRadius = 5; - private shapes: NodeListOf = document.querySelectorAll('folk-shape'); - private frames = 0; private swap = 0; private shadowSwap = 0; @@ -64,53 +75,21 @@ export class FolkSand extends HTMLElement { private shapeIndexBuffer!: WebGLBuffer; private shapeIndexCount = 0; - static define() { - if (customElements.get(this.tagName)) return; - FolkShape.define(); - customElements.define(this.tagName, this); - } + connectedCallback(): void { + super.connectedCallback(); - connectedCallback() { - this.setupCanvas(); + this.renderRoot.appendChild(this.canvas); this.initializeWebGL(); this.initializeSimulation(); this.initializeCollisionDetection(); - - // Collect all FolkShape elements - this.shapes = document.querySelectorAll('folk-shape'); - - // Attach event listeners to shapes - this.shapes.forEach((shape) => { - shape.addEventListener('transform', this.handleShapeTransform); - }); - - // Initialize collision texture with current shapes - this.collectShapeData(); - this.updateCollisionTexture(); - this.attachEventListeners(); + this.handleShapeTransform(); this.render(); } disconnectedCallback() { + super.disconnectedCallback(); this.detachEventListeners(); - - // Remove event listeners from shapes - this.shapes.forEach((shape) => { - shape.removeEventListener('transform', this.handleShapeTransform); - }); - } - - private setupCanvas() { - this.canvas = document.createElement('canvas'); - this.canvas.id = 'main-canvas'; - this.canvas.style.width = '100%'; - this.canvas.style.height = '100%'; - this.canvas.style.display = 'block'; - this.style.display = 'block'; - this.style.width = '100%'; - this.style.height = '100%'; - this.appendChild(this.canvas); } private initializeWebGL() { @@ -206,10 +185,6 @@ export class FolkSand extends HTMLElement { gl.vertexAttribPointer(posAttribLoc, 2, gl.FLOAT, false, 0, 0); gl.bindVertexArray(null); - - // Initial collection and render of shape data - this.collectShapeData(); - this.updateCollisionTexture(); } private setupBuffers() { @@ -295,11 +270,6 @@ export class FolkSand extends HTMLElement { } private attachEventListeners() { - this.handlePointerDown = this.handlePointerDown.bind(this); - this.handlePointerMove = this.handlePointerMove.bind(this); - this.handlePointerUp = this.handlePointerUp.bind(this); - this.handleKeyDown = this.handleKeyDown.bind(this); - this.canvas.addEventListener('pointerdown', this.handlePointerDown); this.canvas.addEventListener('pointermove', this.handlePointerMove); this.canvas.addEventListener('pointerup', this.handlePointerUp); @@ -313,7 +283,7 @@ export class FolkSand extends HTMLElement { document.removeEventListener('keydown', this.handleKeyDown); } - private handlePointerMove(event: PointerEvent) { + private handlePointerMove = (event: PointerEvent) => { const rect = this.canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; @@ -325,9 +295,9 @@ export class FolkSand extends HTMLElement { // Scale coordinates relative to canvas size this.pointer.x = (x / rect.width) * this.canvas.width; this.pointer.y = (y / rect.height) * this.canvas.height; - } + }; - private handlePointerDown(event: PointerEvent) { + private handlePointerDown = (event: PointerEvent) => { const rect = this.canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; @@ -338,18 +308,18 @@ export class FolkSand extends HTMLElement { this.pointer.prevX = this.pointer.x; this.pointer.prevY = this.pointer.y; this.pointer.down = true; - } + }; - private handlePointerUp() { + private handlePointerUp = () => { this.pointer.down = false; - } + }; - private handleKeyDown(event: KeyboardEvent) { + private handleKeyDown = (event: KeyboardEvent) => { const key = parseInt(event.key); if (!isNaN(key)) { this.setMaterialType(key); } - } + }; private setMaterialType(type: number) { this.materialType = Math.min(Math.max(type, 0), 9); @@ -636,12 +606,20 @@ export class FolkSand extends HTMLElement { const indices: number[] = []; let vertexOffset = 0; - this.shapes.forEach((shape) => { - const rect = shape.getTransformDOMRect(); - if (!rect) return; - + this.sourceRects.forEach((rect) => { // Get the transformed vertices in parent space - const transformedPoints = rect.vertices().map((point) => rect.toParentSpace(point)); + let transformedPoints; + + if (rect instanceof DOMRectTransformReadonly) { + transformedPoints = rect.vertices().map((point) => rect.toParentSpace(point)); + } else { + transformedPoints = [ + { x: rect.left, y: rect.top }, + { x: rect.right, y: rect.top }, + { x: rect.left, y: rect.bottom }, + { x: rect.right, y: rect.bottom }, + ]; + } // Convert the transformed points to buffer coordinates const bufferPoints = transformedPoints.map((point) => this.convertToBufferCoordinates(point.x, point.y)); @@ -704,10 +682,18 @@ export class FolkSand extends HTMLElement { gl.bindFramebuffer(gl.FRAMEBUFFER, null); } - private handleShapeTransform = () => { + override update(changedProperties: PropertyValues) { + super.update(changedProperties); + + if (this.sourcesMap.size !== this.sourceElements.size) return; + + this.handleShapeTransform(); + } + + private handleShapeTransform() { // Recollect and update all shape data when any shape changes // TODO: do this more piecemeal this.collectShapeData(); this.updateCollisionTexture(); - }; + } }