diff --git a/package-lock.json b/package-lock.json index bfb8fa7..2eb8839 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "vite-plugin-static-copy": "^3.0.0" }, "peerDependencies": { - "three": ">=0.159.0" + "three": ">=0.177.0" } }, "node_modules/@dimforge/rapier3d-compat": { diff --git a/package.json b/package.json index ce541fa..f980c2f 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "vite-plugin-static-copy": "^3.0.0" }, "peerDependencies": { - "three": ">=0.159.0" + "three": ">=0.177.0" }, "dependencies": { "bvh.js": "^0.0.13" diff --git a/src/core/InstancedEntity.ts b/src/core/InstancedEntity.ts index b7f92cd..a29059e 100644 --- a/src/core/InstancedEntity.ts +++ b/src/core/InstancedEntity.ts @@ -1,5 +1,5 @@ import { Color, ColorRepresentation, Euler, Matrix4, Mesh, Object3D, Quaternion, Vector3 } from 'three'; -import { InstancedMesh2 } from './InstancedMesh2.js'; +import { InstancedMesh2 } from './InstancedMesh2.common.js'; import { UniformValue, UniformValueObj } from './utils/SquareDataTexture.js'; // TODO add other object3D methods diff --git a/src/core/InstancedMesh2.ts b/src/core/InstancedMesh2.common.ts similarity index 90% rename from src/core/InstancedMesh2.ts rename to src/core/InstancedMesh2.common.ts index cc080c0..24f5cd9 100644 --- a/src/core/InstancedMesh2.ts +++ b/src/core/InstancedMesh2.common.ts @@ -1,11 +1,12 @@ import { AttachedBindMode, BindMode, Box3, BufferAttribute, BufferGeometry, Camera, Color, ColorManagement, ColorRepresentation, DataTexture, DetachedBindMode, InstancedBufferAttribute, Material, Matrix4, Mesh, Object3D, Object3DEventMap, Scene, Skeleton, Sphere, TypedArray, Vector3, WebGLProgramParametersWithUniforms, WebGLRenderer } from 'three'; -import { CustomSortCallback, OnFrustumEnterCallback } from './feature/FrustumCulling.js'; + import { Entity } from './feature/Instances.js'; import { LODInfo } from './feature/LOD.js'; import { InstancedEntity } from './InstancedEntity.js'; import { BVHParams, InstancedMeshBVH } from './InstancedMeshBVH.js'; import { GLInstancedBufferAttribute } from './utils/GLInstancedBufferAttribute.js'; import { SquareDataTexture } from './utils/SquareDataTexture.js'; +import { CustomSortCallback, OnFrustumEnterCallback } from './feature/FrustumCulling.js'; // TODO: Add check to not update partial texture if needsuupdate already true // TODO: if bvh present, can override? @@ -63,7 +64,7 @@ export class InstancedMesh2< /** * @defaultValue `InstancedMesh2` */ - public override readonly type = 'InstancedMesh2'; + public override type = 'InstancedMesh2'; /** * Indicates if this is an `InstancedMesh2`. */ @@ -80,11 +81,11 @@ export class InstancedMesh2< /** * Texture storing matrices for instances. */ - public matricesTexture: SquareDataTexture; + public matricesTexture: SquareDataTexture; // initialized in renderer-specific prototype /** * Texture storing colors for instances. */ - public colorsTexture: SquareDataTexture = null; + public colorsTexture: SquareDataTexture = null; // initialized in renderer-specific prototype /** * Texture storing morph target influences for instances. */ @@ -92,11 +93,11 @@ export class InstancedMesh2< /** * Texture storing bones for instances. */ - public boneTexture: SquareDataTexture = null; + public boneTexture: SquareDataTexture = null; // initialized in renderer-specific prototype /** * Texture storing custom uniforms per instance. */ - public uniformsTexture: SquareDataTexture = null; + public uniformsTexture: SquareDataTexture = null; // initialized in renderer-specific prototype /** * This bounding box encloses all instances, which can be calculated with `computeBoundingBox` method. * Bounding box isn't computed by default. It needs to be explicitly computed, otherwise it's `null`. @@ -160,7 +161,7 @@ export class InstancedMesh2< * Callback function called if an instance is inside the frustum. */ public onFrustumEnter: OnFrustumEnterCallback = null; - /** @internal */ _renderer: WebGLRenderer = null; + /** @internal */ _renderer: WebGLRenderer = null; // initialized in renderer-specific prototype /** @internal */ _instancesCount = 0; /** @internal */ _instancesArrayCount = 0; /** @internal */ _perObjectFrustumCulled = true; @@ -182,9 +183,16 @@ export class InstancedMesh2< protected _createEntities: boolean; // HACK TO MAKE IT WORK WITHOUT UPDATE CORE - /** @internal */ isInstancedMesh = true; // must be set to use instancing rendering - /** @internal */ instanceMatrix = new InstancedBufferAttribute(new Float32Array(0), 16); // must be init to avoid exception - /** @internal */ instanceColor = null; // must be null to avoid exception + /** @internal */ isInstancedMesh = true; + /** @internal */ instanceMatrix = new InstancedBufferAttribute(new Float32Array(0), 16); // overridden in renderer-specific prototype if needed + /** @internal */ instanceColor = null; + + // Todo: WebGPU-specific methods + init: () => void; + onBeforeCompile: (shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer) => void; + initPositionsNode: () => void; + initColorsNode: () => void; + initBonesNode: () => void; /** * The capacity of the instance buffers. @@ -255,8 +263,10 @@ export class InstancedMesh2< this.availabilityArray = LOD?.availabilityArray ?? new Array(capacity * 2); this._createEntities = createEntities; + // Only initialize common attributes here. this.initIndexAttribute(); - this.initMatricesTexture(); + + // Renderer-specific initialization (matricesTexture, colorsTexture, etc.) is done in prototype extension. } public override onBeforeShadow(renderer: WebGLRenderer, scene: Scene, camera: Camera, shadowCamera: Camera, geometry: BufferGeometry, depthMaterial: Material, group: any): void { @@ -345,21 +355,6 @@ export class InstancedMesh2< this._geometry.setAttribute('instanceIndex', this.instanceIndex as unknown as BufferAttribute); } - protected initMatricesTexture(): void { - if (!this._parentLOD) { - this.matricesTexture = new SquareDataTexture(Float32Array, 4, 4, this._capacity); - } - } - - protected initColorsTexture(): void { - if (!this._parentLOD) { - this.colorsTexture = new SquareDataTexture(Float32Array, 4, 1, this._capacity); - this.colorsTexture.colorSpace = ColorManagement.workingColorSpace; - this.colorsTexture._data.fill(1); - this.materialsNeedsUpdate(); - } - } - protected materialsNeedsUpdate(): void { if ((this.material as Material).isMaterial) { (this.material as Material).needsUpdate = true; @@ -391,55 +386,12 @@ export class InstancedMesh2< return `ezInstancedMesh2_${this.id}_${!!this.colorsTexture}_${this._useOpacity}_${!!this.boneTexture}_${!!this.uniformsTexture}_${this._customProgramCacheKeyBase.call(this._currentMaterial)}`; }; - protected _onBeforeCompile = (shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void => { - if (this._onBeforeCompileBase) this._onBeforeCompileBase.call(this._currentMaterial, shader, renderer); - - shader.instancing = false; - - shader.defines ??= {}; - shader.defines['USE_INSTANCING_INDIRECT'] = ''; - - shader.uniforms.matricesTexture = { value: this.matricesTexture }; - - if (this.uniformsTexture) { - shader.uniforms.uniformsTexture = { value: this.uniformsTexture }; - const { vertex, fragment } = this.uniformsTexture.getUniformsGLSL('uniformsTexture', 'instanceIndex', 'uint'); - shader.vertexShader = shader.vertexShader.replace('void main() {', vertex); - shader.fragmentShader = shader.fragmentShader.replace('void main() {', fragment); - } - - if (this.colorsTexture && shader.fragmentShader.includes('#include ')) { - shader.defines['USE_INSTANCING_COLOR_INDIRECT'] = ''; - shader.uniforms.colorsTexture = { value: this.colorsTexture }; - shader.vertexShader = shader.vertexShader.replace('', ''); - - if (shader.vertexColors) { - shader.defines['USE_VERTEX_COLOR'] = ''; - } - - if (this._useOpacity) { - shader.defines['USE_COLOR_ALPHA'] = ''; - } else { - shader.defines['USE_COLOR'] = ''; - } - } - - if (this.boneTexture) { - shader.defines['USE_SKINNING'] = ''; - shader.defines['USE_INSTANCING_SKINNING'] = ''; - shader.uniforms.bindMatrix = { value: this.bindMatrix }; - shader.uniforms.bindMatrixInverse = { value: this.bindMatrixInverse }; - shader.uniforms.bonesPerInstance = { value: this.skeleton.bones.length }; - shader.uniforms.boneTexture = { value: this.boneTexture }; - } - }; - protected patchMaterial(renderer: WebGLRenderer, material: Material): void { this._currentMaterial = material; this._customProgramCacheKeyBase = material.customProgramCacheKey; // avoid .bind(material); to prevent memory leak this._onBeforeCompileBase = material.onBeforeCompile; material.customProgramCacheKey = this._customProgramCacheKey; - material.onBeforeCompile = this._onBeforeCompile; + // material.onBeforeCompile = this._onBeforeCompile; const propertiesBase = renderer.properties; @@ -677,6 +629,15 @@ export class InstancedMesh2< this.colorsTexture.enqueueUpdate(id); } + /** + * Initializes the colors texture for the instances. + * This method should be implemented in the renderer-specific prototype. + * @throws Error if not implemented. + */ + initColorsTexture() { + throw new Error('Method not implemented.'); + } + /** * Gets the color of a specific instance. * @param id The index of the instance. diff --git a/src/core/InstancedMesh2.webgl.ts b/src/core/InstancedMesh2.webgl.ts new file mode 100644 index 0000000..958d323 --- /dev/null +++ b/src/core/InstancedMesh2.webgl.ts @@ -0,0 +1,90 @@ +import { ColorManagement, WebGLProgramParametersWithUniforms, WebGLRenderer } from 'three'; + +import { InstancedMesh2 } from '../core/InstancedMesh2.common.js'; +import { SquareDataTexture } from './utils/SquareDataTexture.js'; + + +/** + * @internal + * Enhances the InstancedMesh2 prototype with WebGL methods. + */ +export function extendInstancedMesh2PrototypeWebGL(): void { + // WebGL-specific member initialization + InstancedMesh2.prototype.matricesTexture = null; + InstancedMesh2.prototype.colorsTexture = null; + InstancedMesh2.prototype.boneTexture = null; + InstancedMesh2.prototype.uniformsTexture = null; + InstancedMesh2.prototype._renderer = null; + // instanceMatrix is already initialized in .common, but can be overridden if needed + + InstancedMesh2.prototype.init = function(): void { + this.initMatricesTexture(); + this.initColorsTexture(); + // Ensure textures are updated before first render + this.matricesTexture.update(this._renderer); + this.colorsTexture?.update(this._renderer); + }; + + InstancedMesh2.prototype.initMatricesTexture = function(): void { + if (!this._parentLOD) { + // Only initialize if not already set + if (!this.matricesTexture) { + this.matricesTexture = new SquareDataTexture(Float32Array, 4, 4, this._capacity); + } + } + }; + + InstancedMesh2.prototype.initColorsTexture = function(): void { + if (!this._parentLOD) { + if (!this.colorsTexture) { + this.colorsTexture = new SquareDataTexture(Float32Array, 4, 1, this._capacity); + this.colorsTexture.colorSpace = ColorManagement.workingColorSpace; + this.colorsTexture._data.fill(1); + this.materialsNeedsUpdate(); + } + } + }; + + InstancedMesh2.prototype.onBeforeCompile = function(shader: WebGLProgramParametersWithUniforms, renderer: WebGLRenderer): void { + if (this._onBeforeCompileBase) this._onBeforeCompileBase.call(this._currentMaterial, shader, renderer); + + shader.instancing = false; + + shader.defines ??= {}; + shader.defines['USE_INSTANCING_INDIRECT'] = ''; + + shader.uniforms.matricesTexture = { value: this.matricesTexture }; + + if (this.uniformsTexture) { + shader.uniforms.uniformsTexture = { value: this.uniformsTexture }; + const { vertex, fragment } = this.uniformsTexture.getUniformsGLSL('uniformsTexture', 'instanceIndex', 'uint'); + shader.vertexShader = shader.vertexShader.replace('void main() {', vertex); + shader.fragmentShader = shader.fragmentShader.replace('void main() {', fragment); + } + + if (this.colorsTexture && shader.fragmentShader.includes('#include ')) { + shader.defines['USE_INSTANCING_COLOR_INDIRECT'] = ''; + shader.uniforms.colorsTexture = { value: this.colorsTexture }; + shader.vertexShader = shader.vertexShader.replace('', ''); + + if (shader.vertexColors) { + shader.defines['USE_VERTEX_COLOR'] = ''; + } + + if (this._useOpacity) { + shader.defines['USE_COLOR_ALPHA'] = ''; + } else { + shader.defines['USE_COLOR'] = ''; + } + } + + if (this.boneTexture) { + shader.defines['USE_SKINNING'] = ''; + shader.defines['USE_INSTANCING_SKINNING'] = ''; + shader.uniforms.bindMatrix = { value: this.bindMatrix }; + shader.uniforms.bindMatrixInverse = { value: this.bindMatrixInverse }; + shader.uniforms.bonesPerInstance = { value: this.skeleton.bones.length }; + shader.uniforms.boneTexture = { value: this.boneTexture }; + } + }; +} \ No newline at end of file diff --git a/src/core/InstancedMesh2.webgpu.ts b/src/core/InstancedMesh2.webgpu.ts new file mode 100644 index 0000000..ef7e659 --- /dev/null +++ b/src/core/InstancedMesh2.webgpu.ts @@ -0,0 +1,95 @@ +import { ColorManagement } from 'three'; +import { MeshBasicNodeMaterial, StorageInstancedBufferAttribute, WebGPURenderer } from 'three/webgpu'; + +import { getBoneMatrix, getColorTexture, getInstancedMatrix } from '../shaders/tsl/nodes.js'; +import { InstancedMesh2, InstancedMesh2Params } from './InstancedMesh2.common.js'; +import { uniform } from 'three/tsl'; + +import { SquareDataTextureGPU } from './utils/SquareDataTexture.js'; + + +/** + * Parameters for configuring an `InstancedMeshGPU` instance. + */ +export interface InstancedMeshGPUParams extends Omit { + capacity?: number; + + createEntities?: boolean; + + allowsEuler?: boolean; + + renderer?: WebGPURenderer; +} + + +/** + * @internal + * Enhances the InstancedMesh2 prototype with WebGPU methods. + */ +export function extendInstancedMesh2PrototypeWebGPU(): void { + + InstancedMesh2.prototype.type = 'InstancedMeshGPU'; + + // WebGPU-specific member initialization + InstancedMesh2.prototype.matricesTexture = null; // SquareDataTextureGPU + InstancedMesh2.prototype.colorsTexture = null; // SquareDataTextureGPU + InstancedMesh2.prototype.boneTexture = null; // SquareDataTextureGPU + InstancedMesh2.prototype.uniformsTexture = null; // SquareDataTextureGPU + InstancedMesh2.prototype._renderer = null; // WebGPURenderer | any + InstancedMesh2.prototype.instanceMatrix = new StorageInstancedBufferAttribute(new Float32Array(0), 16); + + InstancedMesh2.prototype.init = async function(): Promise { + this._currentMaterial = new MeshBasicNodeMaterial(); + this.initMatricesTexture(); + this.initColorsTexture(); + // Ensure textures are updated before first render + this.matricesTexture.update(this._renderer); + this.colorsTexture?.update(this._renderer); + }; + + InstancedMesh2.prototype.initPositionsNode = function(): void { + if (!this._parentLOD) { + this.matricesTexture = new SquareDataTextureGPU(Float32Array, 4, 4, this._capacity); + } + // Set the node for instance matrix in the material + if (this._currentMaterial) { + (this._currentMaterial as any).positionNode = getInstancedMatrix(uniform(this.matricesTexture)); + } + }; + + InstancedMesh2.prototype.initColorsNode = function(): void { + if (!this._parentLOD) { + this.colorsTexture = new SquareDataTextureGPU(Float32Array, 4, 1, this._capacity); + this.colorsTexture.colorSpace = ColorManagement.workingColorSpace; + this.colorsTexture._data.fill(1); + this.materialsNeedsUpdate(); + } + // Set the node for instance color in the material + if (this._currentMaterial) { + (this._currentMaterial as any).colorNode = getColorTexture(uniform(this.colorsTexture)); + } + }; + + InstancedMesh2.prototype.initBonesNode = function(): void { + if (!this._parentLOD) { + this.boneTexture = new SquareDataTextureGPU(Float32Array, 4, 4, this._capacity); + this.boneTexture.colorSpace = ColorManagement.workingColorSpace; + this.boneTexture._data.fill(1); + this.materialsNeedsUpdate(); + } + // Set the node for bone matrix in the material + if (this._currentMaterial) { + (this._currentMaterial as any).boneMatrixNode = getBoneMatrix(uniform(this.boneTexture)); + } + }; + + // Ensure textures are updated before each render + InstancedMesh2.prototype.onBeforeRender = function(renderer, scene, camera, geometry, material, group): void { + this.onBeforeRender(renderer, scene, camera, geometry, material, group); + this.matricesTexture.update(renderer); + this.colorsTexture?.update(renderer); + }; + +} + +const _defaultCapacity = 1000; diff --git a/src/core/InstancedMeshBVH.ts b/src/core/InstancedMeshBVH.ts index 4fe614f..3a7ca9e 100644 --- a/src/core/InstancedMeshBVH.ts +++ b/src/core/InstancedMeshBVH.ts @@ -1,7 +1,7 @@ import { box3ToArray, BVH, BVHNode, HybridBuilder, onFrustumIntersectionCallback, onFrustumIntersectionLODCallback, onIntersectionCallback, onIntersectionRayCallback, vec3ToArray, WebGLCoordinateSystem } from 'bvh.js'; import { Box3, Matrix4, Raycaster, Sphere, Vector3 } from 'three'; import { LODLevel } from './feature/LOD.js'; -import { InstancedMesh2 } from './InstancedMesh2.js'; +import { InstancedMesh2 } from './InstancedMesh2.common.js'; // TODO getBoxFromSphere updated if change geometry (and create accessor) // TODO accurateCulling in bvh.js? diff --git a/src/core/feature/Capacity.ts b/src/core/feature/Capacity.ts index 1700526..5316e7f 100644 --- a/src/core/feature/Capacity.ts +++ b/src/core/feature/Capacity.ts @@ -1,9 +1,9 @@ import { DataTexture, FloatType, RedFormat, TypedArray } from 'three'; -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; // TODO: add optimize method to reduce buffer size and remove instances objects -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Resizes internal buffers to accommodate the specified capacity. diff --git a/src/core/feature/FrustumCulling.ts b/src/core/feature/FrustumCulling.ts index ed992d8..bad628d 100644 --- a/src/core/feature/FrustumCulling.ts +++ b/src/core/feature/FrustumCulling.ts @@ -1,7 +1,7 @@ import { BVHNode } from 'bvh.js'; import { Camera, Frustum, Material, Matrix4, Sphere, Vector3 } from 'three'; import { sortOpaque, sortTransparent } from '../../utils/SortingUtils.js'; -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; import { InstancedRenderItem, InstancedRenderList } from '../utils/InstancedRenderList.js'; import { LODRenderList } from './LOD.js'; @@ -22,7 +22,7 @@ export type CustomSortCallback = (list: InstancedRenderItem[]) => void; */ export type OnFrustumEnterCallback = (index: number, camera: Camera, cameraLOD?: Camera, LODindex?: number) => boolean; -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Performs frustum culling and manages LOD visibility. diff --git a/src/core/feature/Instances.ts b/src/core/feature/Instances.ts index d0e998b..10f20e1 100644 --- a/src/core/feature/Instances.ts +++ b/src/core/feature/Instances.ts @@ -1,5 +1,5 @@ import { InstancedEntity } from '../InstancedEntity.js'; -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; // TODO: optimize method to fill 'holes'. @@ -12,7 +12,7 @@ export type Entity = InstancedEntity & T; */ export type UpdateEntityCallback = (obj: Entity, index: number) => void; -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Updates instances by applying a callback function to each instance. It calls `updateMatrix` for each instance. diff --git a/src/core/feature/LOD.ts b/src/core/feature/LOD.ts index eec1789..2fdf534 100644 --- a/src/core/feature/LOD.ts +++ b/src/core/feature/LOD.ts @@ -1,5 +1,5 @@ import { BufferGeometry, Material, ShaderMaterial } from 'three'; -import { InstancedMesh2, InstancedMesh2Params } from '../InstancedMesh2.js'; +import { InstancedMesh2, InstancedMesh2Params } from '../InstancedMesh2.common.js'; // TODO check squaured distance in comments and code @@ -56,7 +56,7 @@ export interface LODLevel { object: InstancedMesh2; } -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Retrieves the index of the LOD level for a given distance. diff --git a/src/core/feature/Morph.ts b/src/core/feature/Morph.ts index 5950d7a..63b36d5 100644 --- a/src/core/feature/Morph.ts +++ b/src/core/feature/Morph.ts @@ -1,7 +1,7 @@ import { DataTexture, FloatType, Mesh, RedFormat } from 'three'; -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Gets the morph target data for a specific instance. diff --git a/src/core/feature/Raycasting.ts b/src/core/feature/Raycasting.ts index bc4168f..25491e8 100644 --- a/src/core/feature/Raycasting.ts +++ b/src/core/feature/Raycasting.ts @@ -1,7 +1,7 @@ import { Intersection, Matrix4, Mesh, Ray, Raycaster, Sphere, Vector3 } from 'three'; -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** @internal */ raycastInstances(raycaster: Raycaster, result: Intersection[]): void; /** @internal */ checkObjectIntersection(raycaster: Raycaster, objectIndex: number, result: Intersection[]): void; diff --git a/src/core/feature/Skeleton.ts b/src/core/feature/Skeleton.ts index d357242..03e70f3 100644 --- a/src/core/feature/Skeleton.ts +++ b/src/core/feature/Skeleton.ts @@ -1,8 +1,8 @@ import { Matrix4, Skeleton } from 'three'; -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; import { SquareDataTexture } from '../utils/SquareDataTexture.js'; -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Initialize the skeleton of the instances. diff --git a/src/core/feature/Uniforms.ts b/src/core/feature/Uniforms.ts index d9da9aa..95cae4e 100644 --- a/src/core/feature/Uniforms.ts +++ b/src/core/feature/Uniforms.ts @@ -1,4 +1,4 @@ -import { InstancedMesh2 } from '../InstancedMesh2.js'; +import { InstancedMesh2 } from '../InstancedMesh2.common.js'; import { ChannelSize, SquareDataTexture, UniformMap, UniformMapType, UniformType, UniformValue, UniformValueObj } from '../utils/SquareDataTexture.js'; type UniformSchema = { [x: string]: UniformType }; @@ -11,7 +11,7 @@ type UniformSchemaResult = { fetchInFragmentShader: boolean; }; -declare module '../InstancedMesh2.js' { +declare module '../InstancedMesh2.common.js' { interface InstancedMesh2 { /** * Retrieves a uniform value for a specific instance. diff --git a/src/index.ts b/src/index.ts index 7327037..24d7c4a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ export * from './core/InstancedEntity.js'; -export * from './core/InstancedMesh2.js'; +export * from './core/InstancedMesh2.common.js'; export * from './core/InstancedMeshBVH.js'; export * from './core/feature/Capacity.js'; diff --git a/src/shaders/tsl/nodes.ts b/src/shaders/tsl/nodes.ts new file mode 100644 index 0000000..fba6ac5 --- /dev/null +++ b/src/shaders/tsl/nodes.ts @@ -0,0 +1,39 @@ +import { Fn, instanceIndex, int, ivec2, mat4, textureSize, uniform, vec4 } from 'three/tsl'; + + +export const getColorTexture = (colorsTexture) => { + const size = int(textureSize(colorsTexture, int(0)).x).toVar(); + const j = int(instanceIndex).toVar(); + const x = int(j.mod(size)).toVar(); + const y = int( j.div( size ) ).toVar(); + return colorsTexture.sample( ivec2( x, y ) ).setSampler( false ); +}; + + +export const getInstancedMatrix = (matricesTexture) => { + const size = int( textureSize( matricesTexture, int( 0 ) ).x ).toVar(); + const j = int( int( instanceIndex ).mul( int( 4 ) ) ).toVar(); + const x = int( j.mod( size ) ).toVar(); + const y = int( j.div( size ) ).toVar(); + const v1 = vec4( matricesTexture.sample( ivec2( x, y ) ).setSampler( false ) ).toVar(); + const v2 = vec4( matricesTexture.sample( ivec2( x.add( int( 1 ) ), y ) ).setSampler( false ) ).toVar(); + const v3 = vec4( matricesTexture.sample( ivec2( x.add( int( 2 ) ), y ) ).setSampler( false ) ).toVar(); + const v4 = vec4( matricesTexture.sample( ivec2( x.add( int( 3 ) ), y ) ).setSampler( false ) ).toVar(); + return mat4( v1, v2, v3, v4 ); +}; + + +export const getBoneMatrix = (boneTexture) => Fn((i) => { + const bonesPerInstance = uniform( 'int' ); + + const size = int( textureSize( boneTexture, int( 0 ) ).x ).toVar(); + const j = int( bonesPerInstance.mul( int( instanceIndex ) ).add( int( i ) ).mul( int( 4 ) ) ).toVar(); + const x = int( j.mod( size ) ).toVar(); + const y = int( j.div( size ) ).toVar(); + const v1 = vec4( boneTexture.sample( ivec2( x, y ) ).setSampler( false ) ).toVar(); + const v2 = vec4( boneTexture.sample( ivec2( x.add( int( 1 ) ), y ) ).setSampler( false ) ).toVar(); + const v3 = vec4( boneTexture.sample( ivec2( x.add( int( 2 ) ), y ) ).setSampler( false ) ).toVar(); + const v4 = vec4( boneTexture.sample( ivec2( x.add( int( 3 ) ), y ) ).setSampler( false ) ).toVar(); + + return mat4( v1, v2, v3, v4 ); +}); diff --git a/src/utils/CreateFrom.ts b/src/utils/CreateFrom.ts index cf7391f..5ea22f1 100644 --- a/src/utils/CreateFrom.ts +++ b/src/utils/CreateFrom.ts @@ -1,5 +1,5 @@ import { InstancedBufferAttribute, InstancedMesh, Mesh, SkinnedMesh } from 'three'; -import { InstancedMesh2, InstancedMesh2Params } from '../core/InstancedMesh2.js'; +import { InstancedMesh2, InstancedMesh2Params } from '../core/InstancedMesh2.common.js'; /** * Create an `InstancedMesh2` instance from an existing `Mesh` or `InstancedMesh`. diff --git a/src/utils/SortingUtils.ts b/src/utils/SortingUtils.ts index 07e5bfd..81c0030 100644 --- a/src/utils/SortingUtils.ts +++ b/src/utils/SortingUtils.ts @@ -1,6 +1,6 @@ import { Material } from 'three'; import { radixSort, RadixSortOptions } from 'three/addons/utils/SortUtils.js'; -import { InstancedMesh2 } from '../core/InstancedMesh2.js'; +import { InstancedMesh2 } from '../core/InstancedMesh2.common.js'; import { InstancedRenderItem } from '../core/utils/InstancedRenderList.js'; type radixSortCallback = (list: InstancedRenderItem[]) => void;