use private fields
This commit is contained in:
parent
09b0fed6ec
commit
e036e9859f
|
|
@ -13,21 +13,20 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
|
|
||||||
static readonly MAX_DISTANCE = 99999.0;
|
static readonly MAX_DISTANCE = 99999.0;
|
||||||
|
|
||||||
private canvas!: HTMLCanvasElement;
|
#canvas!: HTMLCanvasElement;
|
||||||
private glContext!: WebGL2RenderingContext;
|
#glContext!: WebGL2RenderingContext;
|
||||||
private framebuffer!: WebGLFramebuffer;
|
#framebuffer!: WebGLFramebuffer;
|
||||||
private fullscreenQuadVAO!: WebGLVertexArrayObject;
|
#fullscreenQuadVAO!: WebGLVertexArrayObject;
|
||||||
|
#jfaProgram!: WebGLProgram; // Shader program for the Jump Flooding Algorithm
|
||||||
private jfaProgram!: WebGLProgram; // Shader program for the Jump Flooding Algorithm
|
#renderProgram!: WebGLProgram; // Shader program for final rendering
|
||||||
private renderProgram!: WebGLProgram; // Shader program for final rendering
|
#seedProgram!: WebGLProgram; // Shader program for rendering seed points
|
||||||
private seedProgram!: WebGLProgram; // Shader program for rendering seed points
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Groups data for handling different sets of shapes.
|
* Groups data for handling different sets of shapes.
|
||||||
* 'mergeA' and 'mergeB' shapes will have their distance fields merged in rendering,
|
* 'mergeA' and 'mergeB' shapes will have their distance fields merged in rendering,
|
||||||
* while 'others' will be processed separately.
|
* while 'others' will be processed separately.
|
||||||
*/
|
*/
|
||||||
private groups: {
|
#groups: {
|
||||||
[groupName: string]: {
|
[groupName: string]: {
|
||||||
textures: WebGLTexture[];
|
textures: WebGLTexture[];
|
||||||
isPingTexture: boolean;
|
isPingTexture: boolean;
|
||||||
|
|
@ -37,7 +36,7 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
// Add class property to store Float32Arrays
|
// Add class property to store Float32Arrays
|
||||||
private groupBuffers: {
|
#groupBuffers: {
|
||||||
[groupName: string]: Float32Array;
|
[groupName: string]: Float32Array;
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
|
|
@ -45,7 +44,7 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
// Initialize groups for 'mergeA', 'mergeB', and 'others'
|
// Initialize groups for 'mergeA', 'mergeB', and 'others'
|
||||||
this.groups = {
|
this.#groups = {
|
||||||
mergeA: {
|
mergeA: {
|
||||||
textures: [],
|
textures: [],
|
||||||
isPingTexture: true,
|
isPingTexture: true,
|
||||||
|
|
@ -66,35 +65,35 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.initWebGL();
|
this.#initWebGL();
|
||||||
this.initShaders();
|
this.#initShaders();
|
||||||
this.initPingPongTextures();
|
this.#initPingPongTextures();
|
||||||
|
|
||||||
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.#handleResize);
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
|
|
||||||
window.removeEventListener('resize', this.handleResize);
|
window.removeEventListener('resize', this.#handleResize);
|
||||||
|
|
||||||
this.cleanupWebGLResources();
|
this.#cleanupWebGLResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
private initWebGL() {
|
#initWebGL() {
|
||||||
const { gl, canvas } = WebGLUtils.createWebGLCanvas(this.clientWidth, this.clientHeight);
|
const { gl, canvas } = WebGLUtils.createWebGLCanvas(this.clientWidth, this.clientHeight);
|
||||||
|
|
||||||
if (!gl || !canvas) {
|
if (!gl || !canvas) {
|
||||||
throw new Error('Failed to initialize WebGL context.');
|
throw new Error('Failed to initialize WebGL context.');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canvas = canvas;
|
this.#canvas = canvas;
|
||||||
this.renderRoot.prepend(canvas);
|
this.renderRoot.prepend(canvas);
|
||||||
this.glContext = gl;
|
this.#glContext = gl;
|
||||||
|
|
||||||
// Create framebuffer object
|
// Create framebuffer object
|
||||||
this.framebuffer = gl.createFramebuffer();
|
this.#framebuffer = gl.createFramebuffer()!;
|
||||||
if (!this.framebuffer) {
|
if (!this.#framebuffer) {
|
||||||
throw new Error('Failed to create framebuffer.');
|
throw new Error('Failed to create framebuffer.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -107,38 +106,38 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
|
|
||||||
if (this.sourcesMap.size !== this.sourceElements.size) return;
|
if (this.sourcesMap.size !== this.sourceElements.size) return;
|
||||||
|
|
||||||
this.populateSeedPoints();
|
this.#populateSeedPoints();
|
||||||
this.runJumpFloodingAlgorithm();
|
this.#runJumpFloodingAlgorithm();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes all shader programs used in rendering.
|
* Initializes all shader programs used in rendering.
|
||||||
*/
|
*/
|
||||||
private initShaders() {
|
#initShaders() {
|
||||||
this.jfaProgram = WebGLUtils.createShaderProgram(this.glContext, commonVertShader, jfaFragShader);
|
this.#jfaProgram = WebGLUtils.createShaderProgram(this.#glContext, commonVertShader, jfaFragShader);
|
||||||
this.renderProgram = WebGLUtils.createShaderProgram(this.glContext, commonVertShader, renderFragShader);
|
this.#renderProgram = WebGLUtils.createShaderProgram(this.#glContext, commonVertShader, renderFragShader);
|
||||||
this.seedProgram = WebGLUtils.createShaderProgram(this.glContext, seedVertShader, seedFragShader);
|
this.#seedProgram = WebGLUtils.createShaderProgram(this.#glContext, seedVertShader, seedFragShader);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes textures and framebuffer for ping-pong rendering.
|
* Initializes textures and framebuffer for ping-pong rendering.
|
||||||
* Supports separate textures for 'mergeA', 'mergeB', and 'others' groups.
|
* Supports separate textures for 'mergeA', 'mergeB', and 'others' groups.
|
||||||
*/
|
*/
|
||||||
private initPingPongTextures() {
|
#initPingPongTextures() {
|
||||||
// Initialize textures for each group
|
// Initialize textures for each group
|
||||||
for (const groupName in this.groups) {
|
for (const groupName in this.#groups) {
|
||||||
this.groups[groupName].textures = this.createPingPongTextures();
|
this.#groups[groupName].textures = this.#createPingPongTextures();
|
||||||
this.groups[groupName].isPingTexture = true;
|
this.#groups[groupName].isPingTexture = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method to create ping-pong textures.
|
* Utility method to create ping-pong textures.
|
||||||
*/
|
*/
|
||||||
private createPingPongTextures(): WebGLTexture[] {
|
#createPingPongTextures(): WebGLTexture[] {
|
||||||
const gl = this.glContext;
|
const gl = this.#glContext;
|
||||||
const width = this.canvas.width;
|
const width = this.#canvas.width;
|
||||||
const height = this.canvas.height;
|
const height = this.#canvas.height;
|
||||||
const textures: WebGLTexture[] = [];
|
const textures: WebGLTexture[] = [];
|
||||||
|
|
||||||
// Enable the EXT_color_buffer_half_float extension for high-precision floating-point textures
|
// Enable the EXT_color_buffer_half_float extension for high-precision floating-point textures
|
||||||
|
|
@ -171,8 +170,8 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
* Populates seed points and assigns shapes to 'mergeA', 'mergeB', or 'others' groups.
|
* Populates seed points and assigns shapes to 'mergeA', 'mergeB', or 'others' groups.
|
||||||
* Shapes with index 0 and 1 are assigned to 'mergeA' and 'mergeB' respectively.
|
* Shapes with index 0 and 1 are assigned to 'mergeA' and 'mergeB' respectively.
|
||||||
*/
|
*/
|
||||||
private populateSeedPoints() {
|
#populateSeedPoints() {
|
||||||
const gl = this.glContext;
|
const gl = this.#glContext;
|
||||||
const groupPositions: { [groupName: string]: number[] } = {
|
const groupPositions: { [groupName: string]: number[] } = {
|
||||||
mergeA: [],
|
mergeA: [],
|
||||||
mergeB: [],
|
mergeB: [],
|
||||||
|
|
@ -252,7 +251,7 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
// Initialize buffers and VAOs for each group
|
// Initialize buffers and VAOs for each group
|
||||||
for (const groupName in groupPositions) {
|
for (const groupName in groupPositions) {
|
||||||
const positions = groupPositions[groupName];
|
const positions = groupPositions[groupName];
|
||||||
const group = this.groups[groupName];
|
const group = this.#groups[groupName];
|
||||||
|
|
||||||
if (!group.shapeVAO) {
|
if (!group.shapeVAO) {
|
||||||
// First time initialization
|
// First time initialization
|
||||||
|
|
@ -261,23 +260,23 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
group.positionBuffer = gl.createBuffer()!;
|
group.positionBuffer = gl.createBuffer()!;
|
||||||
|
|
||||||
// Create and store the Float32Array
|
// Create and store the Float32Array
|
||||||
this.groupBuffers[groupName] = new Float32Array(positions);
|
this.#groupBuffers[groupName] = new Float32Array(positions);
|
||||||
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, group.positionBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, group.positionBuffer);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, this.groupBuffers[groupName], gl.DYNAMIC_DRAW);
|
gl.bufferData(gl.ARRAY_BUFFER, this.#groupBuffers[groupName], gl.DYNAMIC_DRAW);
|
||||||
|
|
||||||
const positionLocation = gl.getAttribLocation(this.seedProgram, 'a_position');
|
const positionLocation = gl.getAttribLocation(this.#seedProgram, 'a_position');
|
||||||
gl.enableVertexAttribArray(positionLocation);
|
gl.enableVertexAttribArray(positionLocation);
|
||||||
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
|
gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);
|
||||||
gl.bindVertexArray(null);
|
gl.bindVertexArray(null);
|
||||||
} else {
|
} else {
|
||||||
// Reuse existing Float32Array if size hasn't changed
|
// Reuse existing Float32Array if size hasn't changed
|
||||||
const existingArray = this.groupBuffers[groupName];
|
const existingArray = this.#groupBuffers[groupName];
|
||||||
if (positions.length !== existingArray.length) {
|
if (positions.length !== existingArray.length) {
|
||||||
// Only create new array if size changed
|
// Only create new array if size changed
|
||||||
this.groupBuffers[groupName] = new Float32Array(positions);
|
this.#groupBuffers[groupName] = new Float32Array(positions);
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, group.positionBuffer!);
|
gl.bindBuffer(gl.ARRAY_BUFFER, group.positionBuffer!);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, this.groupBuffers[groupName], gl.DYNAMIC_DRAW);
|
gl.bufferData(gl.ARRAY_BUFFER, this.#groupBuffers[groupName], gl.DYNAMIC_DRAW);
|
||||||
} else {
|
} else {
|
||||||
// Reuse existing array
|
// Reuse existing array
|
||||||
existingArray.set(positions);
|
existingArray.set(positions);
|
||||||
|
|
@ -291,9 +290,9 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
for (const groupName in groupPositions) {
|
for (const groupName in groupPositions) {
|
||||||
const positions = groupPositions[groupName];
|
const positions = groupPositions[groupName];
|
||||||
const vertexCount = positions.length / 3;
|
const vertexCount = positions.length / 3;
|
||||||
this.renderSeedPointsForGroup(
|
this.#renderSeedPointsForGroup(
|
||||||
this.groups[groupName].shapeVAO,
|
this.#groups[groupName].shapeVAO,
|
||||||
this.groups[groupName].textures[this.groups[groupName].isPingTexture ? 0 : 1],
|
this.#groups[groupName].textures[this.#groups[groupName].isPingTexture ? 0 : 1],
|
||||||
vertexCount,
|
vertexCount,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -302,24 +301,24 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
/**
|
/**
|
||||||
* Utility method to render seed points for a given group.
|
* Utility method to render seed points for a given group.
|
||||||
*/
|
*/
|
||||||
private renderSeedPointsForGroup(vao: WebGLVertexArrayObject, seedTexture: WebGLTexture, vertexCount: number) {
|
#renderSeedPointsForGroup(vao: WebGLVertexArrayObject, seedTexture: WebGLTexture, vertexCount: number) {
|
||||||
const gl = this.glContext;
|
const gl = this.#glContext;
|
||||||
|
|
||||||
// Bind framebuffer to render to the seed texture
|
// Bind framebuffer to render to the seed texture
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.#framebuffer);
|
||||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, seedTexture, 0);
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, seedTexture, 0);
|
||||||
|
|
||||||
// Clear the texture with a large initial distance
|
// Clear the texture with a large initial distance
|
||||||
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
gl.viewport(0, 0, this.#canvas.width, this.#canvas.height);
|
||||||
gl.clearColor(0.0, 0.0, 0.0, FolkDistanceField.MAX_DISTANCE);
|
gl.clearColor(0.0, 0.0, 0.0, FolkDistanceField.MAX_DISTANCE);
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
// Use the seed shader program
|
// Use the seed shader program
|
||||||
gl.useProgram(this.seedProgram);
|
gl.useProgram(this.#seedProgram);
|
||||||
|
|
||||||
// Set the canvas size uniform
|
// Set the canvas size uniform
|
||||||
const canvasSizeLocation = gl.getUniformLocation(this.seedProgram, 'u_canvasSize');
|
const canvasSizeLocation = gl.getUniformLocation(this.#seedProgram, 'u_canvasSize');
|
||||||
gl.uniform2f(canvasSizeLocation, this.canvas.width, this.canvas.height);
|
gl.uniform2f(canvasSizeLocation, this.#canvas.width, this.#canvas.height);
|
||||||
|
|
||||||
// Bind VAO and draw shapes
|
// Bind VAO and draw shapes
|
||||||
gl.bindVertexArray(vao);
|
gl.bindVertexArray(vao);
|
||||||
|
|
@ -334,18 +333,18 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
* Executes the Jump Flooding Algorithm (JFA) for each group separately.
|
* Executes the Jump Flooding Algorithm (JFA) for each group separately.
|
||||||
* 'mergeA' and 'mergeB' groups will have their distance fields merged in rendering.
|
* 'mergeA' and 'mergeB' groups will have their distance fields merged in rendering.
|
||||||
*/
|
*/
|
||||||
private runJumpFloodingAlgorithm() {
|
#runJumpFloodingAlgorithm() {
|
||||||
// Compute initial step size
|
// Compute initial step size
|
||||||
let stepSize = 1 << Math.floor(Math.log2(Math.max(this.canvas.width, this.canvas.height)));
|
let stepSize = 1 << Math.floor(Math.log2(Math.max(this.#canvas.width, this.#canvas.height)));
|
||||||
|
|
||||||
// Perform passes with decreasing step sizes for each group
|
// Perform passes with decreasing step sizes for each group
|
||||||
for (const groupName in this.groups) {
|
for (const groupName in this.#groups) {
|
||||||
const group = this.groups[groupName];
|
const group = this.#groups[groupName];
|
||||||
const textures = group.textures;
|
const textures = group.textures;
|
||||||
let isPingTexture = group.isPingTexture;
|
let isPingTexture = group.isPingTexture;
|
||||||
|
|
||||||
for (let size = stepSize; size >= 1; size >>= 1) {
|
for (let size = stepSize; size >= 1; size >>= 1) {
|
||||||
this.renderPass(size, textures, isPingTexture);
|
this.#renderPass(size, textures, isPingTexture);
|
||||||
isPingTexture = !isPingTexture;
|
isPingTexture = !isPingTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -353,38 +352,38 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the final result to the screen
|
// Render the final result to the screen
|
||||||
this.renderToScreen();
|
this.#renderToScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a single pass of the Jump Flooding Algorithm with a given step size for a specific distance field.
|
* Performs a single pass of the Jump Flooding Algorithm with a given step size for a specific distance field.
|
||||||
*/
|
*/
|
||||||
private renderPass(stepSize: number, textures: WebGLTexture[], isPingTexture: boolean) {
|
#renderPass(stepSize: number, textures: WebGLTexture[], isPingTexture: boolean) {
|
||||||
const gl = this.glContext;
|
const gl = this.#glContext;
|
||||||
|
|
||||||
// Swap textures for ping-pong rendering
|
// Swap textures for ping-pong rendering
|
||||||
const inputTexture = isPingTexture ? textures[0] : textures[1];
|
const inputTexture = isPingTexture ? textures[0] : textures[1];
|
||||||
const outputTexture = isPingTexture ? textures[1] : textures[0];
|
const outputTexture = isPingTexture ? textures[1] : textures[0];
|
||||||
|
|
||||||
// Bind framebuffer to output texture
|
// Bind framebuffer to output texture
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.#framebuffer);
|
||||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, outputTexture, 0);
|
||||||
|
|
||||||
// Use the JFA shader program
|
// Use the JFA shader program
|
||||||
gl.useProgram(this.jfaProgram);
|
gl.useProgram(this.#jfaProgram);
|
||||||
|
|
||||||
// Compute and set the offsets uniform for neighboring pixels
|
// Compute and set the offsets uniform for neighboring pixels
|
||||||
const offsets = this.computeOffsets(stepSize);
|
const offsets = this.#computeOffsets(stepSize);
|
||||||
const offsetsLocation = gl.getUniformLocation(this.jfaProgram, 'u_offsets');
|
const offsetsLocation = gl.getUniformLocation(this.#jfaProgram, 'u_offsets');
|
||||||
gl.uniform2fv(offsetsLocation, offsets);
|
gl.uniform2fv(offsetsLocation, offsets);
|
||||||
|
|
||||||
// Bind input texture containing the previous step's results
|
// Bind input texture containing the previous step's results
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, inputTexture);
|
gl.bindTexture(gl.TEXTURE_2D, inputTexture);
|
||||||
gl.uniform1i(gl.getUniformLocation(this.jfaProgram, 'u_previousTexture'), 0);
|
gl.uniform1i(gl.getUniformLocation(this.#jfaProgram, 'u_previousTexture'), 0);
|
||||||
|
|
||||||
// Draw a fullscreen quad to process all pixels
|
// Draw a fullscreen quad to process all pixels
|
||||||
this.drawFullscreenQuad();
|
this.#drawFullscreenQuad();
|
||||||
|
|
||||||
// Unbind framebuffer
|
// Unbind framebuffer
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
|
|
@ -394,44 +393,44 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
* Renders the final distance field to the screen using the render shader program.
|
* Renders the final distance field to the screen using the render shader program.
|
||||||
* Merges 'mergeA' and 'mergeB' distance fields during rendering, while 'others' are not merged.
|
* Merges 'mergeA' and 'mergeB' distance fields during rendering, while 'others' are not merged.
|
||||||
*/
|
*/
|
||||||
private renderToScreen() {
|
#renderToScreen() {
|
||||||
const gl = this.glContext;
|
const gl = this.#glContext;
|
||||||
|
|
||||||
// Unbind framebuffer to render directly to the canvas
|
// Unbind framebuffer to render directly to the canvas
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
gl.viewport(0, 0, this.#canvas.width, this.#canvas.height);
|
||||||
|
|
||||||
// Use the render shader program
|
// Use the render shader program
|
||||||
gl.useProgram(this.renderProgram);
|
gl.useProgram(this.#renderProgram);
|
||||||
|
|
||||||
// Bind the final textures from each group
|
// Bind the final textures from each group
|
||||||
let textureUnit = 0;
|
let textureUnit = 0;
|
||||||
for (const groupName in this.groups) {
|
for (const groupName in this.#groups) {
|
||||||
const group = this.groups[groupName];
|
const group = this.#groups[groupName];
|
||||||
const finalTexture = group.textures[group.isPingTexture ? 0 : 1];
|
const finalTexture = group.textures[group.isPingTexture ? 0 : 1];
|
||||||
gl.activeTexture(gl.TEXTURE0 + textureUnit);
|
gl.activeTexture(gl.TEXTURE0 + textureUnit);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, finalTexture);
|
gl.bindTexture(gl.TEXTURE_2D, finalTexture);
|
||||||
gl.uniform1i(gl.getUniformLocation(this.renderProgram, `u_texture_${groupName}`), textureUnit);
|
gl.uniform1i(gl.getUniformLocation(this.#renderProgram, `u_texture_${groupName}`), textureUnit);
|
||||||
textureUnit++;
|
textureUnit++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw a fullscreen quad to display the result
|
// Draw a fullscreen quad to display the result
|
||||||
this.drawFullscreenQuad();
|
this.#drawFullscreenQuad();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws a fullscreen quad to cover the entire canvas.
|
* Draws a fullscreen quad to cover the entire canvas.
|
||||||
* This is used in shader passes where every pixel needs to be processed.
|
* This is used in shader passes where every pixel needs to be processed.
|
||||||
*/
|
*/
|
||||||
private drawFullscreenQuad() {
|
#drawFullscreenQuad() {
|
||||||
const gl = this.glContext;
|
const gl = this.#glContext;
|
||||||
|
|
||||||
// Initialize the quad geometry if it hasn't been done yet
|
// Initialize the quad geometry if it hasn't been done yet
|
||||||
if (!this.fullscreenQuadVAO) {
|
if (!this.#fullscreenQuadVAO) {
|
||||||
this.initFullscreenQuad();
|
this.#initFullscreenQuad();
|
||||||
}
|
}
|
||||||
|
|
||||||
gl.bindVertexArray(this.fullscreenQuadVAO);
|
gl.bindVertexArray(this.#fullscreenQuadVAO);
|
||||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||||
gl.bindVertexArray(null);
|
gl.bindVertexArray(null);
|
||||||
}
|
}
|
||||||
|
|
@ -439,20 +438,20 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
/**
|
/**
|
||||||
* Initializes the geometry and buffers for the fullscreen quad.
|
* Initializes the geometry and buffers for the fullscreen quad.
|
||||||
*/
|
*/
|
||||||
private initFullscreenQuad() {
|
#initFullscreenQuad() {
|
||||||
const gl = this.glContext;
|
const gl = this.#glContext;
|
||||||
|
|
||||||
// Define positions for a quad covering the entire screen
|
// Define positions for a quad covering the entire screen
|
||||||
const positions = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
|
const positions = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
|
||||||
|
|
||||||
this.fullscreenQuadVAO = gl.createVertexArray()!;
|
this.#fullscreenQuadVAO = gl.createVertexArray()!;
|
||||||
gl.bindVertexArray(this.fullscreenQuadVAO);
|
gl.bindVertexArray(this.#fullscreenQuadVAO);
|
||||||
|
|
||||||
const positionBuffer = gl.createBuffer()!;
|
const positionBuffer = gl.createBuffer()!;
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
|
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
|
||||||
|
|
||||||
const positionAttributeLocation = gl.getAttribLocation(this.jfaProgram, 'a_position');
|
const positionAttributeLocation = gl.getAttribLocation(this.#jfaProgram, 'a_position');
|
||||||
gl.enableVertexAttribArray(positionAttributeLocation);
|
gl.enableVertexAttribArray(positionAttributeLocation);
|
||||||
gl.vertexAttribPointer(
|
gl.vertexAttribPointer(
|
||||||
positionAttributeLocation,
|
positionAttributeLocation,
|
||||||
|
|
@ -470,24 +469,24 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
* Handles window resize events by updating canvas size, re-initializing textures and seed points,
|
* Handles window resize events by updating canvas size, re-initializing textures and seed points,
|
||||||
* and rerunning the Jump Flooding Algorithm.
|
* and rerunning the Jump Flooding Algorithm.
|
||||||
*/
|
*/
|
||||||
private handleResize = () => {
|
#handleResize = () => {
|
||||||
const gl = this.glContext;
|
const gl = this.#glContext;
|
||||||
|
|
||||||
// Update canvas size to match the container instead of window
|
// Update canvas size to match the container instead of window
|
||||||
this.canvas.width = this.clientWidth;
|
this.#canvas.width = this.clientWidth;
|
||||||
this.canvas.height = this.clientHeight;
|
this.#canvas.height = this.clientHeight;
|
||||||
|
|
||||||
// Update the viewport
|
// Update the viewport
|
||||||
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
gl.viewport(0, 0, this.#canvas.width, this.#canvas.height);
|
||||||
|
|
||||||
// Re-initialize textures with the new dimensions
|
// Re-initialize textures with the new dimensions
|
||||||
this.initPingPongTextures();
|
this.#initPingPongTextures();
|
||||||
|
|
||||||
// Re-initialize seed point rendering to update positions
|
// Re-initialize seed point rendering to update positions
|
||||||
this.populateSeedPoints();
|
this.#populateSeedPoints();
|
||||||
|
|
||||||
// Rerun the Jump Flooding Algorithm with the new sizes
|
// Rerun the Jump Flooding Algorithm with the new sizes
|
||||||
this.runJumpFloodingAlgorithm();
|
this.#runJumpFloodingAlgorithm();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -496,13 +495,13 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
* @param stepSize The current step size for neighbor sampling.
|
* @param stepSize The current step size for neighbor sampling.
|
||||||
* @returns A Float32Array of offsets.
|
* @returns A Float32Array of offsets.
|
||||||
*/
|
*/
|
||||||
private computeOffsets(stepSize: number): Float32Array {
|
#computeOffsets(stepSize: number): Float32Array {
|
||||||
const aspectRatio = this.canvas.width / this.canvas.height;
|
const aspectRatio = this.#canvas.width / this.#canvas.height;
|
||||||
const offsets: number[] = [];
|
const offsets: number[] = [];
|
||||||
for (let y = -1; y <= 1; y++) {
|
for (let y = -1; y <= 1; y++) {
|
||||||
for (let x = -1; x <= 1; x++) {
|
for (let x = -1; x <= 1; x++) {
|
||||||
// Adjust x offset by aspect ratio to maintain uniform distances
|
// Adjust x offset by aspect ratio to maintain uniform distances
|
||||||
offsets.push((x * stepSize * aspectRatio) / this.canvas.width, (y * stepSize) / this.canvas.height);
|
offsets.push((x * stepSize * aspectRatio) / this.#canvas.width, (y * stepSize) / this.#canvas.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Float32Array(offsets);
|
return new Float32Array(offsets);
|
||||||
|
|
@ -512,12 +511,12 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
* Cleans up WebGL resources to prevent memory leaks.
|
* Cleans up WebGL resources to prevent memory leaks.
|
||||||
* This is called when the element is disconnected from the DOM.
|
* This is called when the element is disconnected from the DOM.
|
||||||
*/
|
*/
|
||||||
private cleanupWebGLResources() {
|
#cleanupWebGLResources() {
|
||||||
const gl = this.glContext;
|
const gl = this.#glContext;
|
||||||
|
|
||||||
// Delete resources for each group
|
// Delete resources for each group
|
||||||
for (const groupName in this.groups) {
|
for (const groupName in this.#groups) {
|
||||||
const group = this.groups[groupName];
|
const group = this.#groups[groupName];
|
||||||
|
|
||||||
// Delete textures
|
// Delete textures
|
||||||
group.textures.forEach((texture) => gl.deleteTexture(texture));
|
group.textures.forEach((texture) => gl.deleteTexture(texture));
|
||||||
|
|
@ -535,27 +534,27 @@ export class FolkDistanceField extends FolkBaseSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete framebuffer
|
// Delete framebuffer
|
||||||
if (this.framebuffer) {
|
if (this.#framebuffer) {
|
||||||
gl.deleteFramebuffer(this.framebuffer);
|
gl.deleteFramebuffer(this.#framebuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete fullscreen quad VAO
|
// Delete fullscreen quad VAO
|
||||||
if (this.fullscreenQuadVAO) {
|
if (this.#fullscreenQuadVAO) {
|
||||||
gl.deleteVertexArray(this.fullscreenQuadVAO);
|
gl.deleteVertexArray(this.#fullscreenQuadVAO);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete shader programs
|
// Delete shader programs
|
||||||
if (this.jfaProgram) {
|
if (this.#jfaProgram) {
|
||||||
gl.deleteProgram(this.jfaProgram);
|
gl.deleteProgram(this.#jfaProgram);
|
||||||
}
|
}
|
||||||
if (this.renderProgram) {
|
if (this.#renderProgram) {
|
||||||
gl.deleteProgram(this.renderProgram);
|
gl.deleteProgram(this.#renderProgram);
|
||||||
}
|
}
|
||||||
if (this.seedProgram) {
|
if (this.#seedProgram) {
|
||||||
gl.deleteProgram(this.seedProgram);
|
gl.deleteProgram(this.#seedProgram);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.groupBuffers = {};
|
this.#groupBuffers = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,29 +25,29 @@ export class FolkSand extends FolkBaseSet {
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
private canvas = document.createElement('canvas');
|
#canvas = document.createElement('canvas');
|
||||||
private gl!: WebGL2RenderingContext;
|
#gl!: WebGL2RenderingContext;
|
||||||
|
|
||||||
private program!: WebGLProgram;
|
#program!: WebGLProgram;
|
||||||
private blitProgram!: WebGLProgram;
|
#blitProgram!: WebGLProgram;
|
||||||
private jfaShadowProgram!: WebGLProgram;
|
#jfaShadowProgram!: WebGLProgram;
|
||||||
private jfaInitProgram!: WebGLProgram;
|
#jfaInitProgram!: WebGLProgram;
|
||||||
|
|
||||||
private vao!: WebGLVertexArrayObject;
|
#vao!: WebGLVertexArrayObject;
|
||||||
private posBuffer!: WebGLBuffer;
|
#posBuffer!: WebGLBuffer;
|
||||||
|
|
||||||
private bufferWidth!: number;
|
#bufferWidth!: number;
|
||||||
private bufferHeight!: number;
|
#bufferHeight!: number;
|
||||||
|
|
||||||
private fbo: WebGLFramebuffer[] = [];
|
#fbo: WebGLFramebuffer[] = [];
|
||||||
private tex: WebGLTexture[] = [];
|
#tex: WebGLTexture[] = [];
|
||||||
|
|
||||||
private shadowFbo: WebGLFramebuffer[] = [];
|
#shadowFbo: WebGLFramebuffer[] = [];
|
||||||
private shadowTexR: WebGLTexture[] = [];
|
#shadowTexR: WebGLTexture[] = [];
|
||||||
private shadowTexG: WebGLTexture[] = [];
|
#shadowTexG: WebGLTexture[] = [];
|
||||||
private shadowTexB: WebGLTexture[] = [];
|
#shadowTexB: WebGLTexture[] = [];
|
||||||
|
|
||||||
private pointer = {
|
#pointer = {
|
||||||
x: -1,
|
x: -1,
|
||||||
y: -1,
|
y: -1,
|
||||||
prevX: -1,
|
prevX: -1,
|
||||||
|
|
@ -55,88 +55,88 @@ export class FolkSand extends FolkBaseSet {
|
||||||
down: false,
|
down: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
private materialType = 4;
|
#materialType = 4;
|
||||||
private brushRadius = 5;
|
#brushRadius = 5;
|
||||||
|
|
||||||
private frames = 0;
|
#frames = 0;
|
||||||
private swap = 0;
|
#swap = 0;
|
||||||
private shadowSwap = 0;
|
#shadowSwap = 0;
|
||||||
|
|
||||||
private PIXELS_PER_PARTICLE = 4;
|
#PIXELS_PER_PARTICLE = 4;
|
||||||
private PIXEL_RATIO = window.devicePixelRatio || 1;
|
#PIXEL_RATIO = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
private collisionProgram!: WebGLProgram;
|
#collisionProgram!: WebGLProgram;
|
||||||
private collisionFbo!: WebGLFramebuffer;
|
#collisionFbo!: WebGLFramebuffer;
|
||||||
private collisionTex!: WebGLTexture;
|
#collisionTex!: WebGLTexture;
|
||||||
private shapeVao!: WebGLVertexArrayObject;
|
#shapeVao!: WebGLVertexArrayObject;
|
||||||
private shapePositionBuffer!: WebGLBuffer;
|
#shapePositionBuffer!: WebGLBuffer;
|
||||||
private shapeIndexBuffer!: WebGLBuffer;
|
#shapeIndexBuffer!: WebGLBuffer;
|
||||||
private shapeIndexCount = 0;
|
#shapeIndexCount = 0;
|
||||||
|
|
||||||
onMaterialChange?: (type: number) => void;
|
onMaterialChange?: (type: number) => void;
|
||||||
|
|
||||||
connectedCallback(): void {
|
connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
|
|
||||||
this.renderRoot.appendChild(this.canvas);
|
this.renderRoot.appendChild(this.#canvas);
|
||||||
this.initializeWebGL();
|
this.#initializeWebGL();
|
||||||
this.initializeSimulation();
|
this.#initializeSimulation();
|
||||||
this.initializeCollisionDetection();
|
this.#initializeCollisionDetection();
|
||||||
this.attachEventListeners();
|
this.#attachEventListeners();
|
||||||
this.handleShapeTransform();
|
this.#handleShapeTransform();
|
||||||
this.render();
|
this.#render();
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
this.detachEventListeners();
|
this.#detachEventListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeWebGL() {
|
#initializeWebGL() {
|
||||||
this.gl = this.canvas.getContext('webgl2')!;
|
this.#gl = this.#canvas.getContext('webgl2')!;
|
||||||
if (!this.gl) {
|
if (!this.#gl) {
|
||||||
console.error('WebGL2 context not available!');
|
console.error('WebGL2 context not available!');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.gl.getExtension('EXT_color_buffer_float')) {
|
if (!this.#gl.getExtension('EXT_color_buffer_float')) {
|
||||||
console.error('need EXT_color_buffer_float');
|
console.error('need EXT_color_buffer_float');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.gl.getExtension('OES_texture_float_linear')) {
|
if (!this.#gl.getExtension('OES_texture_float_linear')) {
|
||||||
console.error('need OES_texture_float_linear');
|
console.error('need OES_texture_float_linear');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeSimulation() {
|
#initializeSimulation() {
|
||||||
// Create shaders and programs
|
// Create shaders and programs
|
||||||
this.program = this.createProgramFromStrings({
|
this.#program = this.#createProgramFromStrings({
|
||||||
vertex: vertexShader,
|
vertex: vertexShader,
|
||||||
fragment: simulationShader,
|
fragment: simulationShader,
|
||||||
})!;
|
})!;
|
||||||
this.blitProgram = this.createProgramFromStrings({
|
this.#blitProgram = this.#createProgramFromStrings({
|
||||||
vertex: vertexShader,
|
vertex: vertexShader,
|
||||||
fragment: visualizationShader,
|
fragment: visualizationShader,
|
||||||
})!;
|
})!;
|
||||||
this.jfaShadowProgram = this.createProgramFromStrings({
|
this.#jfaShadowProgram = this.#createProgramFromStrings({
|
||||||
vertex: vertexShader,
|
vertex: vertexShader,
|
||||||
fragment: distanceFieldPropagationShader,
|
fragment: distanceFieldPropagationShader,
|
||||||
})!;
|
})!;
|
||||||
this.jfaInitProgram = this.createProgramFromStrings({
|
this.#jfaInitProgram = this.#createProgramFromStrings({
|
||||||
vertex: vertexShader,
|
vertex: vertexShader,
|
||||||
fragment: distanceFieldInitShader,
|
fragment: distanceFieldInitShader,
|
||||||
})!;
|
})!;
|
||||||
|
|
||||||
// Setup buffers and vertex arrays
|
// Setup buffers and vertex arrays
|
||||||
this.setupBuffers();
|
this.#setupBuffers();
|
||||||
|
|
||||||
// Initialize framebuffers and textures
|
// Initialize framebuffers and textures
|
||||||
this.initializeFramebuffers();
|
this.#initializeFramebuffers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeCollisionDetection() {
|
#initializeCollisionDetection() {
|
||||||
const gl = this.gl;
|
const gl = this.#gl;
|
||||||
|
|
||||||
const collisionProgram = this.createProgramFromStrings({
|
const collisionProgram = this.#createProgramFromStrings({
|
||||||
vertex: collisionVertexShader,
|
vertex: collisionVertexShader,
|
||||||
fragment: collisionFragmentShader,
|
fragment: collisionFragmentShader,
|
||||||
});
|
});
|
||||||
|
|
@ -147,59 +147,59 @@ export class FolkSand extends FolkBaseSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create collision shader program
|
// Create collision shader program
|
||||||
this.collisionProgram = collisionProgram!;
|
this.#collisionProgram = collisionProgram!;
|
||||||
|
|
||||||
// Create collision texture
|
// Create collision texture
|
||||||
this.collisionTex = gl.createTexture()!;
|
this.#collisionTex = gl.createTexture()!;
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.collisionTex);
|
gl.bindTexture(gl.TEXTURE_2D, this.#collisionTex);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, this.bufferWidth, this.bufferHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, this.#bufferWidth, this.#bufferHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
|
||||||
// Create collision framebuffer
|
// Create collision framebuffer
|
||||||
this.collisionFbo = gl.createFramebuffer()!;
|
this.#collisionFbo = gl.createFramebuffer()!;
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.collisionFbo);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.#collisionFbo);
|
||||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.collisionTex, 0);
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.#collisionTex, 0);
|
||||||
|
|
||||||
// Initialize shape buffers with larger initial sizes
|
// Initialize shape buffers with larger initial sizes
|
||||||
this.shapeVao = gl.createVertexArray()!;
|
this.#shapeVao = gl.createVertexArray()!;
|
||||||
gl.bindVertexArray(this.shapeVao);
|
gl.bindVertexArray(this.#shapeVao);
|
||||||
|
|
||||||
this.shapePositionBuffer = gl.createBuffer()!;
|
this.#shapePositionBuffer = gl.createBuffer()!;
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.shapePositionBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.#shapePositionBuffer);
|
||||||
// Allocate space for up to 100 shapes (400 vertices)
|
// Allocate space for up to 100 shapes (400 vertices)
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, 4 * 2 * 100 * Float32Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW);
|
gl.bufferData(gl.ARRAY_BUFFER, 4 * 2 * 100 * Float32Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW);
|
||||||
|
|
||||||
this.shapeIndexBuffer = gl.createBuffer()!;
|
this.#shapeIndexBuffer = gl.createBuffer()!;
|
||||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.shapeIndexBuffer);
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.#shapeIndexBuffer);
|
||||||
// Allocate space for up to 100 shapes (600 indices)
|
// Allocate space for up to 100 shapes (600 indices)
|
||||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 6 * 100 * Uint16Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW);
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 6 * 100 * Uint16Array.BYTES_PER_ELEMENT, gl.DYNAMIC_DRAW);
|
||||||
|
|
||||||
// Set up vertex attributes
|
// Set up vertex attributes
|
||||||
const posAttribLoc = gl.getAttribLocation(this.collisionProgram, 'aPosition');
|
const posAttribLoc = gl.getAttribLocation(this.#collisionProgram, 'aPosition');
|
||||||
gl.enableVertexAttribArray(posAttribLoc);
|
gl.enableVertexAttribArray(posAttribLoc);
|
||||||
gl.vertexAttribPointer(posAttribLoc, 2, gl.FLOAT, false, 0, 0);
|
gl.vertexAttribPointer(posAttribLoc, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
|
||||||
gl.bindVertexArray(null);
|
gl.bindVertexArray(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupBuffers() {
|
#setupBuffers() {
|
||||||
const gl = this.gl;
|
const gl = this.#gl;
|
||||||
const quad = [-1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 0.0];
|
const quad = [-1.0, -1.0, 0.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 0.0];
|
||||||
|
|
||||||
this.posBuffer = gl.createBuffer()!;
|
this.#posBuffer = gl.createBuffer()!;
|
||||||
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.posBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.#posBuffer);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(quad), gl.STATIC_DRAW);
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(quad), gl.STATIC_DRAW);
|
||||||
|
|
||||||
this.vao = gl.createVertexArray()!;
|
this.#vao = gl.createVertexArray()!;
|
||||||
|
|
||||||
gl.bindVertexArray(this.vao);
|
gl.bindVertexArray(this.#vao);
|
||||||
|
|
||||||
const posAttribLoc = gl.getAttribLocation(this.program, 'aPosition');
|
const posAttribLoc = gl.getAttribLocation(this.#program, 'aPosition');
|
||||||
const uvAttribLoc = gl.getAttribLocation(this.program, 'aUv');
|
const uvAttribLoc = gl.getAttribLocation(this.#program, 'aUv');
|
||||||
|
|
||||||
gl.vertexAttribPointer(posAttribLoc, 2, gl.FLOAT, false, 16, 0);
|
gl.vertexAttribPointer(posAttribLoc, 2, gl.FLOAT, false, 16, 0);
|
||||||
gl.enableVertexAttribArray(posAttribLoc);
|
gl.enableVertexAttribArray(posAttribLoc);
|
||||||
|
|
@ -208,21 +208,21 @@ export class FolkSand extends FolkBaseSet {
|
||||||
gl.enableVertexAttribArray(uvAttribLoc);
|
gl.enableVertexAttribArray(uvAttribLoc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeFramebuffers() {
|
#initializeFramebuffers() {
|
||||||
const gl = this.gl;
|
const gl = this.#gl;
|
||||||
|
|
||||||
this.resizeCanvas();
|
this.#resizeCanvas();
|
||||||
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
||||||
|
|
||||||
this.bufferWidth = Math.ceil(gl.canvas.width / this.PIXELS_PER_PARTICLE);
|
this.#bufferWidth = Math.ceil(gl.canvas.width / this.#PIXELS_PER_PARTICLE);
|
||||||
this.bufferHeight = Math.ceil(gl.canvas.height / this.PIXELS_PER_PARTICLE);
|
this.#bufferHeight = Math.ceil(gl.canvas.height / this.#PIXELS_PER_PARTICLE);
|
||||||
|
|
||||||
// Initialize framebuffers and textures for simulation
|
// Initialize framebuffers and textures for simulation
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
// Create textures
|
// Create textures
|
||||||
this.tex[i] = gl.createTexture()!;
|
this.#tex[i] = gl.createTexture()!;
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.tex[i]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#tex[i]);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.bufferWidth, this.bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.#bufferWidth, this.#bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
||||||
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
|
|
@ -230,133 +230,133 @@ export class FolkSand extends FolkBaseSet {
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
|
||||||
// Create framebuffers
|
// Create framebuffers
|
||||||
this.fbo[i] = gl.createFramebuffer()!;
|
this.#fbo[i] = gl.createFramebuffer()!;
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo[i]);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.#fbo[i]);
|
||||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.tex[i], 0);
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.#tex[i], 0);
|
||||||
|
|
||||||
// Setup shadow textures
|
// Setup shadow textures
|
||||||
this.shadowTexR[i] = gl.createTexture()!;
|
this.#shadowTexR[i] = gl.createTexture()!;
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.shadowTexR[i]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#shadowTexR[i]);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.bufferWidth, this.bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.#bufferWidth, this.#bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
|
||||||
this.shadowTexG[i] = gl.createTexture()!;
|
this.#shadowTexG[i] = gl.createTexture()!;
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.shadowTexG[i]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#shadowTexG[i]);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.bufferWidth, this.bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.#bufferWidth, this.#bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
|
||||||
this.shadowTexB[i] = gl.createTexture()!;
|
this.#shadowTexB[i] = gl.createTexture()!;
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.shadowTexB[i]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#shadowTexB[i]);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.bufferWidth, this.bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.#bufferWidth, this.#bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
|
||||||
// Setup shadow framebuffers
|
// Setup shadow framebuffers
|
||||||
this.shadowFbo[i] = gl.createFramebuffer()!;
|
this.#shadowFbo[i] = gl.createFramebuffer()!;
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.shadowFbo[i]);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.#shadowFbo[i]);
|
||||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.shadowTexR[i], 0);
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.#shadowTexR[i], 0);
|
||||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, this.shadowTexG[i], 0);
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, this.#shadowTexG[i], 0);
|
||||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, this.shadowTexB[i], 0);
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, this.#shadowTexB[i], 0);
|
||||||
|
|
||||||
// Set up draw buffers for the shadow FBO
|
// Set up draw buffers for the shadow FBO
|
||||||
gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2]);
|
gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private attachEventListeners() {
|
#attachEventListeners() {
|
||||||
this.canvas.addEventListener('pointerdown', this.handlePointerDown);
|
this.#canvas.addEventListener('pointerdown', this.#handlePointerDown);
|
||||||
this.canvas.addEventListener('pointermove', this.handlePointerMove);
|
this.#canvas.addEventListener('pointermove', this.#handlePointerMove);
|
||||||
this.canvas.addEventListener('pointerup', this.handlePointerUp);
|
this.#canvas.addEventListener('pointerup', this.#handlePointerUp);
|
||||||
document.addEventListener('keydown', this.handleKeyDown);
|
document.addEventListener('keydown', this.#handleKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
private detachEventListeners() {
|
#detachEventListeners() {
|
||||||
this.canvas.removeEventListener('pointerdown', this.handlePointerDown);
|
this.#canvas.removeEventListener('pointerdown', this.#handlePointerDown);
|
||||||
this.canvas.removeEventListener('pointermove', this.handlePointerMove);
|
this.#canvas.removeEventListener('pointermove', this.#handlePointerMove);
|
||||||
this.canvas.removeEventListener('pointerup', this.handlePointerUp);
|
this.#canvas.removeEventListener('pointerup', this.#handlePointerUp);
|
||||||
document.removeEventListener('keydown', this.handleKeyDown);
|
document.removeEventListener('keydown', this.#handleKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
private handlePointerMove = (event: PointerEvent) => {
|
#handlePointerMove = (event: PointerEvent) => {
|
||||||
const rect = this.canvas.getBoundingClientRect();
|
const rect = this.#canvas.getBoundingClientRect();
|
||||||
const x = event.clientX - rect.left;
|
const x = event.clientX - rect.left;
|
||||||
const y = event.clientY - rect.top;
|
const y = event.clientY - rect.top;
|
||||||
|
|
||||||
// Update previous position before setting new position
|
// Update previous position before setting new position
|
||||||
this.pointer.prevX = this.pointer.x;
|
this.#pointer.prevX = this.#pointer.x;
|
||||||
this.pointer.prevY = this.pointer.y;
|
this.#pointer.prevY = this.#pointer.y;
|
||||||
|
|
||||||
// Scale coordinates relative to canvas size
|
// Scale coordinates relative to canvas size
|
||||||
this.pointer.x = (x / rect.width) * this.canvas.width;
|
this.#pointer.x = (x / rect.width) * this.#canvas.width;
|
||||||
this.pointer.y = (y / rect.height) * this.canvas.height;
|
this.#pointer.y = (y / rect.height) * this.#canvas.height;
|
||||||
};
|
};
|
||||||
|
|
||||||
private handlePointerDown = (event: PointerEvent) => {
|
#handlePointerDown = (event: PointerEvent) => {
|
||||||
const rect = this.canvas.getBoundingClientRect();
|
const rect = this.#canvas.getBoundingClientRect();
|
||||||
const x = event.clientX - rect.left;
|
const x = event.clientX - rect.left;
|
||||||
const y = event.clientY - rect.top;
|
const y = event.clientY - rect.top;
|
||||||
|
|
||||||
// Scale coordinates relative to canvas size
|
// Scale coordinates relative to canvas size
|
||||||
this.pointer.x = (x / rect.width) * this.canvas.width;
|
this.#pointer.x = (x / rect.width) * this.#canvas.width;
|
||||||
this.pointer.y = (y / rect.height) * this.canvas.height;
|
this.#pointer.y = (y / rect.height) * this.#canvas.height;
|
||||||
this.pointer.prevX = this.pointer.x;
|
this.#pointer.prevX = this.#pointer.x;
|
||||||
this.pointer.prevY = this.pointer.y;
|
this.#pointer.prevY = this.#pointer.y;
|
||||||
this.pointer.down = true;
|
this.#pointer.down = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
private handlePointerUp = () => {
|
#handlePointerUp = () => {
|
||||||
this.pointer.down = false;
|
this.#pointer.down = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleKeyDown = (event: KeyboardEvent) => {
|
#handleKeyDown = (event: KeyboardEvent) => {
|
||||||
const key = parseInt(event.key);
|
const key = parseInt(event.key);
|
||||||
if (!isNaN(key)) {
|
if (!isNaN(key)) {
|
||||||
this.setMaterialType(key);
|
this.#setMaterialType(key);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private setMaterialType(type: number) {
|
#setMaterialType(type: number) {
|
||||||
this.materialType = Math.min(Math.max(type, 0), 9);
|
this.#materialType = Math.min(Math.max(type, 0), 9);
|
||||||
this.onMaterialChange?.(this.materialType);
|
this.onMaterialChange?.(this.#materialType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private resizeCanvas() {
|
#resizeCanvas() {
|
||||||
const width = (this.canvas.clientWidth * this.PIXEL_RATIO) | 0;
|
const width = (this.#canvas.clientWidth * this.#PIXEL_RATIO) | 0;
|
||||||
const height = (this.canvas.clientHeight * this.PIXEL_RATIO) | 0;
|
const height = (this.#canvas.clientHeight * this.#PIXEL_RATIO) | 0;
|
||||||
if (this.canvas.width !== width || this.canvas.height !== height) {
|
if (this.#canvas.width !== width || this.#canvas.height !== height) {
|
||||||
this.canvas.width = width;
|
this.#canvas.width = width;
|
||||||
this.canvas.height = height;
|
this.#canvas.height = height;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private render = (time: number = performance.now()) => {
|
#render = (time: number = performance.now()) => {
|
||||||
if (this.resizeCanvas()) {
|
if (this.#resizeCanvas()) {
|
||||||
this.processResize();
|
this.#processResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.simulationPass(time);
|
this.#simulationPass(time);
|
||||||
this.shadowPass();
|
this.#shadowPass();
|
||||||
this.jfaPass();
|
this.#jfaPass();
|
||||||
this.renderPass(time);
|
this.#renderPass(time);
|
||||||
|
|
||||||
this.pointer.prevX = this.pointer.x;
|
this.#pointer.prevX = this.#pointer.x;
|
||||||
this.pointer.prevY = this.pointer.y;
|
this.#pointer.prevY = this.#pointer.y;
|
||||||
|
|
||||||
requestAnimationFrame(this.render);
|
requestAnimationFrame(this.#render);
|
||||||
};
|
};
|
||||||
|
|
||||||
private renderPass(time: number) {
|
#renderPass(time: number) {
|
||||||
const gl = this.gl;
|
const gl = this.#gl;
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
gl.drawBuffers([gl.BACK]);
|
gl.drawBuffers([gl.BACK]);
|
||||||
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
||||||
|
|
@ -364,127 +364,127 @@ export class FolkSand extends FolkBaseSet {
|
||||||
gl.clearColor(0, 0, 0, 1);
|
gl.clearColor(0, 0, 0, 1);
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
gl.useProgram(this.blitProgram);
|
gl.useProgram(this.#blitProgram);
|
||||||
gl.bindVertexArray(this.vao);
|
gl.bindVertexArray(this.#vao);
|
||||||
|
|
||||||
const timeLoc = gl.getUniformLocation(this.blitProgram, 'time');
|
const timeLoc = gl.getUniformLocation(this.#blitProgram, 'time');
|
||||||
const resLoc = gl.getUniformLocation(this.blitProgram, 'resolution');
|
const resLoc = gl.getUniformLocation(this.#blitProgram, 'resolution');
|
||||||
const texLoc = gl.getUniformLocation(this.blitProgram, 'tex');
|
const texLoc = gl.getUniformLocation(this.#blitProgram, 'tex');
|
||||||
const shadowTexLoc = gl.getUniformLocation(this.blitProgram, 'shadowTexR');
|
const shadowTexLoc = gl.getUniformLocation(this.#blitProgram, 'shadowTexR');
|
||||||
const shadowTexGLoc = gl.getUniformLocation(this.blitProgram, 'shadowTexG');
|
const shadowTexGLoc = gl.getUniformLocation(this.#blitProgram, 'shadowTexG');
|
||||||
const shadowTexBLoc = gl.getUniformLocation(this.blitProgram, 'shadowTexB');
|
const shadowTexBLoc = gl.getUniformLocation(this.#blitProgram, 'shadowTexB');
|
||||||
const scaleLoc = gl.getUniformLocation(this.blitProgram, 'scale');
|
const scaleLoc = gl.getUniformLocation(this.#blitProgram, 'scale');
|
||||||
const texResLoc = gl.getUniformLocation(this.blitProgram, 'texResolution');
|
const texResLoc = gl.getUniformLocation(this.#blitProgram, 'texResolution');
|
||||||
const texScaleLoc = gl.getUniformLocation(this.blitProgram, 'texScale');
|
const texScaleLoc = gl.getUniformLocation(this.#blitProgram, 'texScale');
|
||||||
|
|
||||||
gl.uniform1f(timeLoc, time * 0.001);
|
gl.uniform1f(timeLoc, time * 0.001);
|
||||||
gl.uniform2f(resLoc, gl.canvas.width, gl.canvas.height);
|
gl.uniform2f(resLoc, gl.canvas.width, gl.canvas.height);
|
||||||
gl.uniform2f(texResLoc, this.bufferWidth, this.bufferHeight);
|
gl.uniform2f(texResLoc, this.#bufferWidth, this.#bufferHeight);
|
||||||
gl.uniform1f(texScaleLoc, this.PIXELS_PER_PARTICLE);
|
gl.uniform1f(texScaleLoc, this.#PIXELS_PER_PARTICLE);
|
||||||
gl.uniform1f(scaleLoc, 1.0);
|
gl.uniform1f(scaleLoc, 1.0);
|
||||||
|
|
||||||
gl.uniform1i(texLoc, 0);
|
gl.uniform1i(texLoc, 0);
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.tex[this.swap]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#tex[this.#swap]);
|
||||||
|
|
||||||
gl.uniform1i(shadowTexLoc, 1);
|
gl.uniform1i(shadowTexLoc, 1);
|
||||||
gl.uniform1i(shadowTexGLoc, 2);
|
gl.uniform1i(shadowTexGLoc, 2);
|
||||||
gl.uniform1i(shadowTexBLoc, 3);
|
gl.uniform1i(shadowTexBLoc, 3);
|
||||||
gl.activeTexture(gl.TEXTURE1);
|
gl.activeTexture(gl.TEXTURE1);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.shadowTexR[1 - this.shadowSwap]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#shadowTexR[1 - this.#shadowSwap]);
|
||||||
gl.activeTexture(gl.TEXTURE2);
|
gl.activeTexture(gl.TEXTURE2);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.shadowTexG[1 - this.shadowSwap]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#shadowTexG[1 - this.#shadowSwap]);
|
||||||
gl.activeTexture(gl.TEXTURE3);
|
gl.activeTexture(gl.TEXTURE3);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.shadowTexB[1 - this.shadowSwap]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#shadowTexB[1 - this.#shadowSwap]);
|
||||||
|
|
||||||
gl.activeTexture(gl.TEXTURE4);
|
gl.activeTexture(gl.TEXTURE4);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.collisionTex);
|
gl.bindTexture(gl.TEXTURE_2D, this.#collisionTex);
|
||||||
|
|
||||||
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
private simulationPass(time: number) {
|
#simulationPass(time: number) {
|
||||||
const gl = this.gl;
|
const gl = this.#gl;
|
||||||
gl.useProgram(this.program);
|
gl.useProgram(this.#program);
|
||||||
gl.bindVertexArray(this.vao);
|
gl.bindVertexArray(this.#vao);
|
||||||
|
|
||||||
const timeLoc = gl.getUniformLocation(this.program, 'time');
|
const timeLoc = gl.getUniformLocation(this.#program, 'time');
|
||||||
const frameLoc = gl.getUniformLocation(this.program, 'frame');
|
const frameLoc = gl.getUniformLocation(this.#program, 'frame');
|
||||||
const resLoc = gl.getUniformLocation(this.program, 'resolution');
|
const resLoc = gl.getUniformLocation(this.#program, 'resolution');
|
||||||
const texLoc = gl.getUniformLocation(this.program, 'tex');
|
const texLoc = gl.getUniformLocation(this.#program, 'tex');
|
||||||
const mouseLoc = gl.getUniformLocation(this.program, 'mouse');
|
const mouseLoc = gl.getUniformLocation(this.#program, 'mouse');
|
||||||
const materialTypeLoc = gl.getUniformLocation(this.program, 'materialType');
|
const materialTypeLoc = gl.getUniformLocation(this.#program, 'materialType');
|
||||||
const brushRadiusLoc = gl.getUniformLocation(this.program, 'brushRadius');
|
const brushRadiusLoc = gl.getUniformLocation(this.#program, 'brushRadius');
|
||||||
const collisionTexLoc = gl.getUniformLocation(this.program, 'u_collisionTex');
|
const collisionTexLoc = gl.getUniformLocation(this.#program, 'u_collisionTex');
|
||||||
if (!collisionTexLoc) {
|
if (!collisionTexLoc) {
|
||||||
console.error('Could not find u_collisionTex uniform 1');
|
console.error('Could not find u_collisionTex uniform 1');
|
||||||
}
|
}
|
||||||
if (collisionTexLoc !== null) {
|
if (collisionTexLoc !== null) {
|
||||||
gl.uniform1i(collisionTexLoc, 5); // Use texture unit 5
|
gl.uniform1i(collisionTexLoc, 5); // Use texture unit 5
|
||||||
gl.activeTexture(gl.TEXTURE5);
|
gl.activeTexture(gl.TEXTURE5);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.collisionTex);
|
gl.bindTexture(gl.TEXTURE_2D, this.#collisionTex);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mx = (this.pointer.x / gl.canvas.width) * this.bufferWidth;
|
let mx = (this.#pointer.x / gl.canvas.width) * this.#bufferWidth;
|
||||||
let my = (1.0 - this.pointer.y / gl.canvas.height) * this.bufferHeight;
|
let my = (1.0 - this.#pointer.y / gl.canvas.height) * this.#bufferHeight;
|
||||||
let mpx = (this.pointer.prevX / gl.canvas.width) * this.bufferWidth;
|
let mpx = (this.#pointer.prevX / gl.canvas.width) * this.#bufferWidth;
|
||||||
let mpy = (1.0 - this.pointer.prevY / gl.canvas.height) * this.bufferHeight;
|
let mpy = (1.0 - this.#pointer.prevY / gl.canvas.height) * this.#bufferHeight;
|
||||||
|
|
||||||
let pressed = false;
|
let pressed = false;
|
||||||
|
|
||||||
gl.uniform1f(timeLoc, time * 0.001);
|
gl.uniform1f(timeLoc, time * 0.001);
|
||||||
gl.uniform2f(resLoc, this.bufferWidth, this.bufferHeight);
|
gl.uniform2f(resLoc, this.#bufferWidth, this.#bufferHeight);
|
||||||
gl.uniform1i(materialTypeLoc, this.materialType);
|
gl.uniform1i(materialTypeLoc, this.#materialType);
|
||||||
gl.uniform1f(brushRadiusLoc, this.brushRadius);
|
gl.uniform1f(brushRadiusLoc, this.#brushRadius);
|
||||||
|
|
||||||
if (this.pointer.down || pressed) gl.uniform4f(mouseLoc, mx, my, mpx, mpy);
|
if (this.#pointer.down || pressed) gl.uniform4f(mouseLoc, mx, my, mpx, mpy);
|
||||||
else gl.uniform4f(mouseLoc, -mx, -my, -mpx, -mpy);
|
else gl.uniform4f(mouseLoc, -mx, -my, -mpx, -mpy);
|
||||||
|
|
||||||
const PASSES = 3;
|
const PASSES = 3;
|
||||||
for (let i = 0; i < PASSES; i++) {
|
for (let i = 0; i < PASSES; i++) {
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo[this.swap]);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.#fbo[this.#swap]);
|
||||||
gl.viewport(0, 0, this.bufferWidth, this.bufferHeight);
|
gl.viewport(0, 0, this.#bufferWidth, this.#bufferHeight);
|
||||||
|
|
||||||
gl.clearColor(0, 0, 0, 0);
|
gl.clearColor(0, 0, 0, 0);
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
gl.uniform1i(frameLoc, this.frames * PASSES + i);
|
gl.uniform1i(frameLoc, this.#frames * PASSES + i);
|
||||||
|
|
||||||
gl.uniform1i(texLoc, 0);
|
gl.uniform1i(texLoc, 0);
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.tex[1 - this.swap]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#tex[1 - this.#swap]);
|
||||||
|
|
||||||
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
||||||
|
|
||||||
this.swap = 1 - this.swap;
|
this.#swap = 1 - this.#swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.frames++;
|
this.#frames++;
|
||||||
}
|
}
|
||||||
|
|
||||||
private jfaPass() {
|
#jfaPass() {
|
||||||
const gl = this.gl;
|
const gl = this.#gl;
|
||||||
const JFA_PASSES = 5;
|
const JFA_PASSES = 5;
|
||||||
|
|
||||||
gl.useProgram(this.jfaShadowProgram);
|
gl.useProgram(this.#jfaShadowProgram);
|
||||||
|
|
||||||
const resLoc = gl.getUniformLocation(this.jfaShadowProgram, 'resolution');
|
const resLoc = gl.getUniformLocation(this.#jfaShadowProgram, 'resolution');
|
||||||
const texLoc = gl.getUniformLocation(this.jfaShadowProgram, 'texR');
|
const texLoc = gl.getUniformLocation(this.#jfaShadowProgram, 'texR');
|
||||||
const texGLoc = gl.getUniformLocation(this.jfaShadowProgram, 'texG');
|
const texGLoc = gl.getUniformLocation(this.#jfaShadowProgram, 'texG');
|
||||||
const texBLoc = gl.getUniformLocation(this.jfaShadowProgram, 'texB');
|
const texBLoc = gl.getUniformLocation(this.#jfaShadowProgram, 'texB');
|
||||||
const stepSizeLoc = gl.getUniformLocation(this.jfaShadowProgram, 'stepSize');
|
const stepSizeLoc = gl.getUniformLocation(this.#jfaShadowProgram, 'stepSize');
|
||||||
const passCountLoc = gl.getUniformLocation(this.jfaShadowProgram, 'passCount');
|
const passCountLoc = gl.getUniformLocation(this.#jfaShadowProgram, 'passCount');
|
||||||
const passIdxLoc = gl.getUniformLocation(this.jfaShadowProgram, 'passIndex');
|
const passIdxLoc = gl.getUniformLocation(this.#jfaShadowProgram, 'passIndex');
|
||||||
|
|
||||||
gl.uniform2f(resLoc, this.bufferWidth, this.bufferHeight);
|
gl.uniform2f(resLoc, this.#bufferWidth, this.#bufferHeight);
|
||||||
gl.uniform1i(texLoc, 0);
|
gl.uniform1i(texLoc, 0);
|
||||||
gl.uniform1i(texGLoc, 1);
|
gl.uniform1i(texGLoc, 1);
|
||||||
gl.uniform1i(texBLoc, 2);
|
gl.uniform1i(texBLoc, 2);
|
||||||
gl.uniform1i(passCountLoc, JFA_PASSES);
|
gl.uniform1i(passCountLoc, JFA_PASSES);
|
||||||
|
|
||||||
for (let i = 0; i < JFA_PASSES; i++) {
|
for (let i = 0; i < JFA_PASSES; i++) {
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.shadowFbo[this.shadowSwap]);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.#shadowFbo[this.#shadowSwap]);
|
||||||
gl.viewport(0, 0, this.bufferWidth, this.bufferHeight);
|
gl.viewport(0, 0, this.#bufferWidth, this.#bufferHeight);
|
||||||
|
|
||||||
gl.clearColor(0, 0, 0, 0);
|
gl.clearColor(0, 0, 0, 0);
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
@ -495,65 +495,65 @@ export class FolkSand extends FolkBaseSet {
|
||||||
gl.uniform1i(passIdxLoc, i);
|
gl.uniform1i(passIdxLoc, i);
|
||||||
|
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.shadowTexR[1 - this.shadowSwap]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#shadowTexR[1 - this.#shadowSwap]);
|
||||||
gl.activeTexture(gl.TEXTURE1);
|
gl.activeTexture(gl.TEXTURE1);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.shadowTexG[1 - this.shadowSwap]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#shadowTexG[1 - this.#shadowSwap]);
|
||||||
gl.activeTexture(gl.TEXTURE2);
|
gl.activeTexture(gl.TEXTURE2);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.shadowTexB[1 - this.shadowSwap]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#shadowTexB[1 - this.#shadowSwap]);
|
||||||
|
|
||||||
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
||||||
|
|
||||||
this.shadowSwap = 1 - this.shadowSwap;
|
this.#shadowSwap = 1 - this.#shadowSwap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private shadowPass() {
|
#shadowPass() {
|
||||||
const gl = this.gl;
|
const gl = this.#gl;
|
||||||
this.shadowSwap = 0;
|
this.#shadowSwap = 0;
|
||||||
|
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.shadowFbo[this.shadowSwap]);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.#shadowFbo[this.#shadowSwap]);
|
||||||
gl.viewport(0, 0, this.bufferWidth, this.bufferHeight);
|
gl.viewport(0, 0, this.#bufferWidth, this.#bufferHeight);
|
||||||
|
|
||||||
gl.clearColor(0, 0, 0, 0);
|
gl.clearColor(0, 0, 0, 0);
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
gl.useProgram(this.jfaInitProgram);
|
gl.useProgram(this.#jfaInitProgram);
|
||||||
gl.bindVertexArray(this.vao);
|
gl.bindVertexArray(this.#vao);
|
||||||
|
|
||||||
const resLoc = gl.getUniformLocation(this.jfaInitProgram, 'resolution');
|
const resLoc = gl.getUniformLocation(this.#jfaInitProgram, 'resolution');
|
||||||
const texLoc = gl.getUniformLocation(this.jfaInitProgram, 'dataTex');
|
const texLoc = gl.getUniformLocation(this.#jfaInitProgram, 'dataTex');
|
||||||
|
|
||||||
gl.uniform2f(resLoc, this.bufferWidth, this.bufferHeight);
|
gl.uniform2f(resLoc, this.#bufferWidth, this.#bufferHeight);
|
||||||
|
|
||||||
gl.uniform1i(texLoc, 0);
|
gl.uniform1i(texLoc, 0);
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.tex[this.swap]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#tex[this.#swap]);
|
||||||
|
|
||||||
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
|
||||||
|
|
||||||
this.shadowSwap = 1 - this.shadowSwap;
|
this.#shadowSwap = 1 - this.#shadowSwap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private processResize() {
|
#processResize() {
|
||||||
const gl = this.gl;
|
const gl = this.#gl;
|
||||||
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
|
||||||
|
|
||||||
this.bufferWidth = Math.ceil(gl.canvas.width / this.PIXELS_PER_PARTICLE);
|
this.#bufferWidth = Math.ceil(gl.canvas.width / this.#PIXELS_PER_PARTICLE);
|
||||||
this.bufferHeight = Math.ceil(gl.canvas.height / this.PIXELS_PER_PARTICLE);
|
this.#bufferHeight = Math.ceil(gl.canvas.height / this.#PIXELS_PER_PARTICLE);
|
||||||
|
|
||||||
// Update collision texture size
|
// Update collision texture size
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.collisionTex);
|
gl.bindTexture(gl.TEXTURE_2D, this.#collisionTex);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, this.bufferWidth, this.bufferHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, this.#bufferWidth, this.#bufferHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
||||||
|
|
||||||
// Re-render collision data after resize
|
// Re-render collision data after resize
|
||||||
this.handleShapeTransform();
|
this.#handleShapeTransform();
|
||||||
|
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo[i]);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.#fbo[i]);
|
||||||
|
|
||||||
const newTex = gl.createTexture();
|
const newTex = gl.createTexture();
|
||||||
gl.bindTexture(gl.TEXTURE_2D, newTex);
|
gl.bindTexture(gl.TEXTURE_2D, newTex);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.bufferWidth, this.bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.#bufferWidth, this.#bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
||||||
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||||
|
|
@ -561,51 +561,51 @@ export class FolkSand extends FolkBaseSet {
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||||
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, newTex);
|
gl.bindTexture(gl.TEXTURE_2D, newTex);
|
||||||
gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, this.bufferWidth, this.bufferHeight);
|
gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, this.#bufferWidth, this.#bufferHeight);
|
||||||
|
|
||||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, newTex, 0);
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, newTex, 0);
|
||||||
|
|
||||||
gl.deleteTexture(this.tex[i]);
|
gl.deleteTexture(this.#tex[i]);
|
||||||
if (!this.tex[i]) {
|
if (!this.#tex[i]) {
|
||||||
throw new Error('Failed to create texture1');
|
throw new Error('Failed to create texture1');
|
||||||
}
|
}
|
||||||
if (!newTex) {
|
if (!newTex) {
|
||||||
throw new Error('Failed to create texture2');
|
throw new Error('Failed to create texture2');
|
||||||
}
|
}
|
||||||
this.tex[i] = newTex;
|
this.#tex[i] = newTex;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < 2; i++) {
|
for (let i = 0; i < 2; i++) {
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.shadowTexR[i]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#shadowTexR[i]);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.bufferWidth, this.bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.#bufferWidth, this.#bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
||||||
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.shadowTexG[i]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#shadowTexG[i]);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.bufferWidth, this.bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.#bufferWidth, this.#bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
||||||
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.shadowTexB[i]);
|
gl.bindTexture(gl.TEXTURE_2D, this.#shadowTexB[i]);
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.bufferWidth, this.bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, this.#bufferWidth, this.#bufferHeight, 0, gl.RGBA, gl.FLOAT, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private createProgramFromStrings({
|
#createProgramFromStrings({
|
||||||
vertex,
|
vertex,
|
||||||
fragment,
|
fragment,
|
||||||
}: {
|
}: {
|
||||||
vertex: string;
|
vertex: string;
|
||||||
fragment: string;
|
fragment: string;
|
||||||
}): WebGLProgram | undefined {
|
}): WebGLProgram | undefined {
|
||||||
const vertexShader = WebGLUtils.createShader(this.gl, this.gl.VERTEX_SHADER, vertex);
|
const vertexShader = WebGLUtils.createShader(this.#gl, this.#gl.VERTEX_SHADER, vertex);
|
||||||
const fragmentShader = WebGLUtils.createShader(this.gl, this.gl.FRAGMENT_SHADER, fragment);
|
const fragmentShader = WebGLUtils.createShader(this.#gl, this.#gl.FRAGMENT_SHADER, fragment);
|
||||||
|
|
||||||
if (!vertexShader || !fragmentShader) {
|
if (!vertexShader || !fragmentShader) {
|
||||||
console.error('Failed to create shaders');
|
console.error('Failed to create shaders');
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return WebGLUtils.createProgram(this.gl, vertexShader, fragmentShader);
|
return WebGLUtils.createProgram(this.#gl, vertexShader, fragmentShader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private collectShapeData() {
|
#collectShapeData() {
|
||||||
const positions: number[] = [];
|
const positions: number[] = [];
|
||||||
const indices: number[] = [];
|
const indices: number[] = [];
|
||||||
let vertexOffset = 0;
|
let vertexOffset = 0;
|
||||||
|
|
@ -626,7 +626,7 @@ export class FolkSand extends FolkBaseSet {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the transformed points to buffer coordinates
|
// Convert the transformed points to buffer coordinates
|
||||||
const bufferPoints = transformedPoints.map((point) => this.convertToBufferCoordinates(point.x, point.y));
|
const bufferPoints = transformedPoints.map((point) => this.#convertToBufferCoordinates(point.x, point.y));
|
||||||
|
|
||||||
// Add vertices
|
// Add vertices
|
||||||
bufferPoints.forEach((point) => {
|
bufferPoints.forEach((point) => {
|
||||||
|
|
@ -639,30 +639,30 @@ export class FolkSand extends FolkBaseSet {
|
||||||
vertexOffset += 4;
|
vertexOffset += 4;
|
||||||
});
|
});
|
||||||
|
|
||||||
const gl = this.gl;
|
const gl = this.#gl;
|
||||||
|
|
||||||
// Update buffers with new data
|
// Update buffers with new data
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.shapePositionBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.#shapePositionBuffer);
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.DYNAMIC_DRAW);
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.DYNAMIC_DRAW);
|
||||||
|
|
||||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.shapeIndexBuffer);
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.#shapeIndexBuffer);
|
||||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.DYNAMIC_DRAW);
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.DYNAMIC_DRAW);
|
||||||
|
|
||||||
this.shapeIndexCount = indices.length;
|
this.#shapeIndexCount = indices.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
private convertToBufferCoordinates(x: number, y: number) {
|
#convertToBufferCoordinates(x: number, y: number) {
|
||||||
return {
|
return {
|
||||||
x: (x / this.clientWidth) * 2 - 1,
|
x: (x / this.clientWidth) * 2 - 1,
|
||||||
y: -((y / this.clientHeight) * 2 - 1), // Flip Y coordinate
|
y: -((y / this.clientHeight) * 2 - 1), // Flip Y coordinate
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateCollisionTexture() {
|
#updateCollisionTexture() {
|
||||||
const gl = this.gl;
|
const gl = this.#gl;
|
||||||
|
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.collisionFbo);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, this.#collisionFbo);
|
||||||
gl.viewport(0, 0, this.bufferWidth, this.bufferHeight);
|
gl.viewport(0, 0, this.#bufferWidth, this.#bufferHeight);
|
||||||
|
|
||||||
// Clear with transparent black (no collision)
|
// Clear with transparent black (no collision)
|
||||||
gl.clearColor(0, 0, 0, 0);
|
gl.clearColor(0, 0, 0, 0);
|
||||||
|
|
@ -673,12 +673,12 @@ export class FolkSand extends FolkBaseSet {
|
||||||
gl.disable(gl.BLEND);
|
gl.disable(gl.BLEND);
|
||||||
|
|
||||||
// Use collision shader program
|
// Use collision shader program
|
||||||
gl.useProgram(this.collisionProgram);
|
gl.useProgram(this.#collisionProgram);
|
||||||
gl.bindVertexArray(this.shapeVao);
|
gl.bindVertexArray(this.#shapeVao);
|
||||||
|
|
||||||
// Draw all shapes
|
// Draw all shapes
|
||||||
if (this.shapeIndexCount > 0) {
|
if (this.#shapeIndexCount > 0) {
|
||||||
gl.drawElements(gl.TRIANGLES, this.shapeIndexCount, gl.UNSIGNED_SHORT, 0);
|
gl.drawElements(gl.TRIANGLES, this.#shapeIndexCount, gl.UNSIGNED_SHORT, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup
|
||||||
|
|
@ -691,13 +691,13 @@ export class FolkSand extends FolkBaseSet {
|
||||||
|
|
||||||
if (this.sourcesMap.size !== this.sourceElements.size) return;
|
if (this.sourcesMap.size !== this.sourceElements.size) return;
|
||||||
|
|
||||||
this.handleShapeTransform();
|
this.#handleShapeTransform();
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleShapeTransform() {
|
#handleShapeTransform() {
|
||||||
// Recollect and update all shape data when any shape changes
|
// Recollect and update all shape data when any shape changes
|
||||||
// TODO: do this more piecemeal
|
// TODO: do this more piecemeal
|
||||||
this.collectShapeData();
|
this.#collectShapeData();
|
||||||
this.updateCollisionTexture();
|
this.#updateCollisionTexture();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue