UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

425 lines (376 loc) 13.7 kB
import { Vector2, Scene, WebGLRenderer, BufferGeometry, BufferAttribute, Mesh, WebGLRenderTarget, ClampToEdgeWrapping, NearestFilter, RGBAFormat, HalfFloatType, DataTexture, Vector3, Texture, Material, OrthographicCamera, Sphere, } from 'three'; import {ClothController} from '../ClothController'; import {Ref} from '@vue/reactivity'; import { adjacencyTexture, distancesTexture, positionTexture, viscositySpringTexture, createTexturesFromAllocation, } from './ClothAttributeToTexture'; export interface ClothMaterialUniformConfigRef { tSize: Ref<Vector2>; tPosition0: Ref<Texture>; tPosition1: Ref<Texture>; tNormal: Ref<Texture>; // tOriginalRT: Ref<Texture>; tViscositySpringT: Ref<Texture>; tPreviousRT0: Ref<Texture>; tPreviousRT1: Ref<Texture>; tTargetRT0: Ref<Texture>; tTargetRT1: Ref<Texture>; tNormalsRT: Ref<Texture>; tPositionRT0: Ref<Texture>; tPositionRT1: Ref<Texture>; tAdjacentsRT0: Ref<Texture>; tAdjacentsRT1: Ref<Texture>; tDistanceRT0: Ref<Texture>; tDistanceRT1: Ref<Texture>; // integrationMat: Ref<Material>; } export type ClothMaterialUniformConfigRefString = Record<keyof ClothMaterialUniformConfigRef, string>; export function clothMaterialCopyConfigRef( src: ClothMaterialUniformConfigRef, target: ClothMaterialUniformConfigRef ): void { target.tSize.value.copy(src.tSize.value); } export interface ClothMaterialUniformConfig { tSize: Vector2; tPosition0: Texture; tPosition1: Texture; tNormal: Texture; } export interface ClothMaterialUniformNameConfig { tSize: string; tPosition0: string; tPosition1: string; tNormal: string; } interface TextureContainer { texture: DataTexture | null; } function textureContainerToRef(src: TextureContainer, ref: Ref<Texture>) { if (src.texture) { ref.value = src.texture; } } export class ClothFBOController { public readonly tSize = new Vector2(); public readonly fboScene = new Scene(); public readonly fboCamera = new OrthographicCamera(); public readonly fboMesh: Mesh; public RESOLUTION = new Vector2(); public originalRT: TextureContainer = {texture: null}; public viscositySpringT: TextureContainer = {texture: null}; public readonly previousRT: [WebGLRenderTarget, WebGLRenderTarget] = new Array(2) as [ WebGLRenderTarget, WebGLRenderTarget ]; public readonly targetRT: [WebGLRenderTarget, WebGLRenderTarget] = new Array(2) as [ WebGLRenderTarget, WebGLRenderTarget ]; public normalsRT: WebGLRenderTarget; public readonly positionRT: [WebGLRenderTarget, WebGLRenderTarget] = new Array(2) as [ WebGLRenderTarget, WebGLRenderTarget ]; public readonly adjacentsRT: [TextureContainer, TextureContainer] = new Array(2) as [ TextureContainer, TextureContainer ]; public readonly distancesRT: [TextureContainer, TextureContainer] = new Array(2) as [ TextureContainer, TextureContainer ]; public renderer: WebGLRenderer | undefined; constructor(public readonly mainController: ClothController) { this.RESOLUTION.copy(this.mainController.geometryInit.resolution); this.tSize.set(this.RESOLUTION.x, this.RESOLUTION.y); // geometry const geometry = new BufferGeometry(); const positions = new Float32Array([-1.0, -1.0, 3.0, -1.0, -1.0, 3.0]); geometry.setAttribute('position', new BufferAttribute(positions, 2)); geometry.boundingSphere = new Sphere(new Vector3(0, 0, 0), 5); // mesh this.fboMesh = new Mesh(geometry, this.mainController.materials.copyShader); this.fboMesh.frustumCulled = false; this.fboScene.add(this.fboMesh); this.fboScene.updateMatrixWorld = function () {}; this.normalsRT = createRenderTarget(this.RESOLUTION); } private _initialized: boolean = false; init(renderer: WebGLRenderer) { if (this._initialized) { return; } this._initialized = true; this.renderer = renderer; // save renderer state const originalRenderTarget = renderer.getRenderTarget(); // prepare this.createPositionTexture(); this.createViscositySpringTexture(); this.createTexturesFromAllocation(); // setup relaxed vertices conditions for (let i = 0; i < 2; i++) { this.createAdjacentsTexture(i); this.createDistancesTexture(i); this.positionRT[i] = createRenderTarget(this.RESOLUTION); this.previousRT[i] = createRenderTarget(this.RESOLUTION); this.targetRT[i] = createRenderTarget(this.RESOLUTION); this.copyTexture(this.originalRT, this.positionRT[i], !i, renderer); this.copyTexture(this.originalRT, this.previousRT[i], !i, renderer); } // restore renderer renderer.setRenderTarget(originalRenderTarget); // update once, to have the geometry properly display the normals. // UPDATE: this doesn't work anymore since the material first needs to be rendered and compiled. // this.update(1 / 60); } private copyTexture(input: TextureContainer, output: WebGLRenderTarget, order: boolean, renderer: WebGLRenderer) { const copyShader = this.mainController.materials.copyShader; this.fboMesh.material = copyShader; copyShader.uniforms.order.value = order ? 1 : -1; copyShader.uniforms.tSize.value = this.tSize; copyShader.uniforms.texture.value = input.texture; renderer.setRenderTarget(output); renderer.render(this.fboScene, this.fboCamera); } // // // create textures // // private createPositionTexture() { this.originalRT = { texture: positionTexture( this.mainController.geometryInit.geometry, // this.mainController.geometryInit.vertices, this.RESOLUTION ), }; } private createViscositySpringTexture() { const texture = viscositySpringTexture(this.mainController.geometryInit.geometry, this.RESOLUTION); if (texture) { this.viscositySpringT = { texture, }; } } private createTexturesFromAllocation() { const allocation = this.mainController.textureAllocationsController(); if (!allocation) { return; } const texturesByName = createTexturesFromAllocation( this.mainController.geometryInit.geometry, this.RESOLUTION, allocation ); this.mainController.assignReadonlyTextures(this.mainController.materials.integrateShader, texturesByName); } private createAdjacentsTexture(k: number) { this.adjacentsRT[k] = { texture: adjacencyTexture( this.mainController.geometryInit.geometry, this.RESOLUTION, this.mainController.geometryInit.adjacency, k ), }; } private createDistancesTexture(k: number) { this.distancesRT[k] = { texture: distancesTexture( this.mainController.geometryInit.geometry, // this.mainController.geometryInit.vertices, this.RESOLUTION, this.mainController.geometryInit.adjacency, k ), }; } // // // update // // update(config?: ClothMaterialUniformConfigRef) { const renderer = this.renderer; if (!(renderer && this._initialized)) { return; } // save renderer state const originalRenderTarget = renderer.getRenderTarget(); // start update this.integrate(renderer); // const mouseUpdating = this.mainController.inputs.updating(camera); const selectedVertexIndex = this.mainController.selectedVertexIndex(); const steps = this.mainController.stepsCount; for (let i = 0; i < steps; i++) { if (selectedVertexIndex >= 0 && i < steps - 5) { this.mouseOffset(renderer); } this.solveConstraints(renderer, i == steps - 1 ? 1 : 0); } this.computeVertexNormals(renderer); // restore renderer renderer.setRenderTarget(originalRenderTarget); if (config) { this._updateTextureRefs(config); } } private _updateTextureRefs(config: ClothMaterialUniformConfigRef) { config.tSize.value = this.mainController.fbo.tSize; config.tPosition0.value = this.positionRT[0].texture; config.tPosition1.value = this.positionRT[1].texture; config.tNormal.value = this.normalsRT.texture; // textureContainerToRef(this.originalRT, config.tOriginalRT); textureContainerToRef(this.viscositySpringT, config.tViscositySpringT); config.tPreviousRT0.value = this.previousRT[0].texture; config.tPreviousRT1.value = this.previousRT[1].texture; config.tTargetRT0.value = this.targetRT[0].texture; config.tTargetRT1.value = this.targetRT[1].texture; config.tNormalsRT.value = this.normalsRT.texture; config.tPositionRT0.value = this.positionRT[0].texture; config.tPositionRT1.value = this.positionRT[1].texture; textureContainerToRef(this.adjacentsRT[0], config.tAdjacentsRT0); textureContainerToRef(this.adjacentsRT[1], config.tAdjacentsRT1); textureContainerToRef(this.distancesRT[0], config.tDistanceRT0); textureContainerToRef(this.distancesRT[1], config.tDistanceRT1); config.integrationMat.value = this.mainController.materials.integrateShader; } private integrate(renderer: WebGLRenderer) { const integrateShader = this.mainController.materials.integrateShader; this.fboMesh.material = integrateShader; // integrateShader.uniforms.timeDelta.value = delta; integrateShader.uniforms.viscosity.value = this.mainController.viscosity; integrateShader.uniforms.spring.value = this.mainController.spring; integrateShader.uniforms.tSize.value.copy(this.tSize); integrateShader.uniforms.tOriginal.value = this.originalRT.texture; integrateShader.uniforms.tPrevious0.value = this.previousRT[0].texture; integrateShader.uniforms.tPrevious1.value = this.previousRT[1].texture; integrateShader.uniforms.tPosition0.value = this.positionRT[0].texture; integrateShader.uniforms.tPosition1.value = this.positionRT[1].texture; integrateShader.uniforms.tViscositySpring.value = this.viscositySpringT.texture; // integer-part integrateShader.uniforms.order.value = 1; renderer.setRenderTarget(this.targetRT[0]); renderer.render(this.fboScene, this.fboCamera); // fraction-part integrateShader.uniforms.order.value = -1; renderer.setRenderTarget(this.targetRT[1]); renderer.render(this.fboScene, this.fboCamera); // swap framebuffers let tmp = this.previousRT[0]; this.previousRT[0] = this.positionRT[0]; this.positionRT[0] = this.targetRT[0]; this.targetRT[0] = tmp; tmp = this.previousRT[1]; this.previousRT[1] = this.positionRT[1]; this.positionRT[1] = this.targetRT[1]; this.targetRT[1] = tmp; } protected solveConstraints(renderer: WebGLRenderer, secondaryMotionMult: number) { const constraintsShader = this.mainController.materials.constraintsShader; this.fboMesh.material = constraintsShader; constraintsShader.uniforms.constraintInfluence.value = this.mainController.constraintInfluence; constraintsShader.uniforms.tSize.value.copy(this.tSize); constraintsShader.uniforms.tPosition0.value = this.positionRT[0].texture; constraintsShader.uniforms.tPosition1.value = this.positionRT[1].texture; constraintsShader.uniforms.tAdjacentsA.value = this.adjacentsRT[0].texture; constraintsShader.uniforms.tAdjacentsB.value = this.adjacentsRT[1].texture; constraintsShader.uniforms.tDistancesA.value = this.distancesRT[0].texture; constraintsShader.uniforms.tDistancesB.value = this.distancesRT[1].texture; constraintsShader.uniforms.secondaryMotionMult.value = secondaryMotionMult; // integer-part constraintsShader.uniforms.order.value = 1; renderer.setRenderTarget(this.targetRT[0]); renderer.render(this.fboScene, this.fboCamera); // fraction-part constraintsShader.uniforms.order.value = -1; renderer.setRenderTarget(this.targetRT[1]); renderer.render(this.fboScene, this.fboCamera); // swap framebuffers let tmp = this.positionRT[0]; this.positionRT[0] = this.targetRT[0]; this.targetRT[0] = tmp; tmp = this.positionRT[1]; this.positionRT[1] = this.targetRT[1]; this.targetRT[1] = tmp; } protected mouseOffset(renderer: WebGLRenderer) { const mouseShader = this.mainController.materials.mouseShader; this.fboMesh.material = mouseShader; mouseShader.uniforms.tSize.value.copy(this.tSize); mouseShader.uniforms.vertex.value = this.mainController.selectedVertexIndex(); this.mainController.constraintPosition(mouseShader.uniforms.coordinates.value); mouseShader.uniforms.tOriginal.value = this.originalRT.texture; mouseShader.uniforms.tPosition0.value = this.positionRT[0].texture; mouseShader.uniforms.tPosition1.value = this.positionRT[1].texture; // integer-part mouseShader.uniforms.order.value = 1; renderer.setRenderTarget(this.targetRT[0]); renderer.render(this.fboScene, this.fboCamera); // fraction-part mouseShader.uniforms.order.value = -1; renderer.setRenderTarget(this.targetRT[1]); renderer.render(this.fboScene, this.fboCamera); // swap framebuffers let tmp = this.positionRT[0]; this.positionRT[0] = this.targetRT[0]; this.targetRT[0] = tmp; tmp = this.positionRT[1]; this.positionRT[1] = this.targetRT[1]; this.targetRT[1] = tmp; } protected computeVertexNormals(renderer: WebGLRenderer) { const normalsShader = this.mainController.materials.normalsShader; this.fboMesh.material = normalsShader; normalsShader.uniforms.tSize.value.copy(this.tSize); normalsShader.uniforms.tPosition0.value = this.positionRT[0].texture; normalsShader.uniforms.tPosition1.value = this.positionRT[1].texture; normalsShader.uniforms.tAdjacentsA.value = this.adjacentsRT[0].texture; normalsShader.uniforms.tAdjacentsB.value = this.adjacentsRT[1].texture; renderer.setRenderTarget(this.normalsRT); renderer.render(this.fboScene, this.fboCamera); } } function createRenderTarget(resolution: Vector2) { return new WebGLRenderTarget(resolution.x, resolution.y, { wrapS: ClampToEdgeWrapping, wrapT: ClampToEdgeWrapping, minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat, type: HalfFloatType, // depthTest: false, // depthWrite: false, depthBuffer: false, stencilBuffer: false, }); }