|
| 1 | +import { InstancedBufferAttribute, InstancedMesh, MeshBasicMaterial } from 'three' |
| 2 | +import { getShadersForMaterial } from './getShadersForMaterial.js' |
| 3 | +import { createInstancedUniformsDerivedMaterial } from './InstancedUniformsDerivedMaterial.js' |
| 4 | + |
| 5 | +const defaultMaterial = new MeshBasicMaterial() |
| 6 | + |
| 7 | +export class InstancedUniformsMesh extends InstancedMesh { |
| 8 | + constructor (geometry, material, count) { |
| 9 | + super(geometry, material, count) |
| 10 | + this._instancedUniformNames = [] //treated as immutable |
| 11 | + } |
| 12 | + |
| 13 | + /* |
| 14 | + * Getter/setter for automatically wrapping the user-supplied geometry with one that will |
| 15 | + * carry our extra InstancedBufferAttribute(s) |
| 16 | + */ |
| 17 | + get geometry () { |
| 18 | + return this._derivedGeometry |
| 19 | + } |
| 20 | + |
| 21 | + set geometry (geometry) { |
| 22 | + // Extend the geometry so we can add our instancing attributes but inherit everything else |
| 23 | + if (geometry) { |
| 24 | + geometry = Object.create(geometry) |
| 25 | + geometry.attributes = Object.create(geometry.attributes) |
| 26 | + } |
| 27 | + this._derivedGeometry = geometry |
| 28 | + } |
| 29 | + |
| 30 | + /* |
| 31 | + * Getter/setter for automatically wrapping the user-supplied material with our upgrades. We do the |
| 32 | + * wrapping lazily on _read_ rather than write to avoid unnecessary wrapping on transient values. |
| 33 | + */ |
| 34 | + get material () { |
| 35 | + let derivedMaterial = this._derivedMaterial |
| 36 | + const baseMaterial = this._baseMaterial || this._defaultMaterial || (this._defaultMaterial = defaultMaterial.clone()) |
| 37 | + const uniformNames = this._instancedUniformNames |
| 38 | + if (!derivedMaterial || derivedMaterial.baseMaterial !== baseMaterial || derivedMaterial._instancedUniformNames !== uniformNames) { |
| 39 | + derivedMaterial = this._derivedMaterial = createInstancedUniformsDerivedMaterial(baseMaterial, uniformNames) |
| 40 | + derivedMaterial._instancedUniformNames = uniformNames |
| 41 | + // dispose the derived material when its base material is disposed: |
| 42 | + baseMaterial.addEventListener('dispose', function onDispose () { |
| 43 | + baseMaterial.removeEventListener('dispose', onDispose) |
| 44 | + derivedMaterial.dispose() |
| 45 | + }) |
| 46 | + } |
| 47 | + return derivedMaterial |
| 48 | + } |
| 49 | + |
| 50 | + set material (baseMaterial) { |
| 51 | + if (Array.isArray(baseMaterial)) { |
| 52 | + throw new Error('InstancedUniformsMesh does not support multiple materials') |
| 53 | + } |
| 54 | + // Unwrap already-derived materials |
| 55 | + while (baseMaterial && baseMaterial.isInstancedUniformsMaterial) { |
| 56 | + baseMaterial = baseMaterial.baseMaterial |
| 57 | + } |
| 58 | + this._baseMaterial = baseMaterial |
| 59 | + } |
| 60 | + |
| 61 | + get customDepthMaterial () { |
| 62 | + return this.material.getDepthMaterial() |
| 63 | + } |
| 64 | + |
| 65 | + get customDistanceMaterial () { |
| 66 | + return this.material.getDistanceMaterial() |
| 67 | + } |
| 68 | + |
| 69 | + /** |
| 70 | + * Set the value of a shader uniform for a single instance. |
| 71 | + * @param {string} name - the name of the shader uniform |
| 72 | + * @param {number} index - the index of the instance to set the value for |
| 73 | + * @param {number|Vector2|Vector3|Vector4|Color|Array} value - the uniform value for this instance |
| 74 | + */ |
| 75 | + setUniformAt (name, index, value) { |
| 76 | + const attrs = this.geometry.attributes |
| 77 | + const attrName = `troika_attr_${name}` |
| 78 | + let attr = attrs[attrName] |
| 79 | + if (!attr) { |
| 80 | + const defaultValue = getDefaultUniformValue(this._baseMaterial, name) |
| 81 | + const itemSize = getItemSizeForValue(defaultValue) |
| 82 | + attr = attrs[attrName] = new InstancedBufferAttribute(new Float32Array(itemSize * this.count), itemSize) |
| 83 | + // Fill with default value: |
| 84 | + if (defaultValue !== null) { |
| 85 | + for (let i = 0; i < this.count; i++) { |
| 86 | + setAttributeValue(attr, i, defaultValue) |
| 87 | + } |
| 88 | + } |
| 89 | + this._instancedUniformNames = [...this._instancedUniformNames, name] |
| 90 | + } |
| 91 | + setAttributeValue(attr, index, value) |
| 92 | + attr.needsUpdate = true |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +function setAttributeValue (attr, index, value) { |
| 97 | + let size = attr.itemSize |
| 98 | + if (size === 1) { |
| 99 | + attr.setX(index, value) |
| 100 | + } else if (size === 2) { |
| 101 | + attr.setXY(index, value.x, value.y) |
| 102 | + } else if (size === 3) { |
| 103 | + if (value.isColor) { |
| 104 | + attr.setXYZ(index, value.r, value.g, value.b) |
| 105 | + } else { |
| 106 | + attr.setXYZ(index, value.x, value.y, value.z) |
| 107 | + } |
| 108 | + } else if (size === 4) { |
| 109 | + attr.setXYZW(index, value.x, value.y, value.z, value.w) |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +function getDefaultUniformValue (material, name) { |
| 114 | + // Try uniforms on the material itself, then try the builtin material shaders |
| 115 | + let uniforms = material.uniforms |
| 116 | + if (uniforms && uniforms[name]) { |
| 117 | + return uniforms[name].value |
| 118 | + } |
| 119 | + uniforms = getShadersForMaterial(material).uniforms |
| 120 | + if (uniforms && uniforms[name]) { |
| 121 | + return uniforms[name].value |
| 122 | + } |
| 123 | + return null |
| 124 | +} |
| 125 | + |
| 126 | +function getItemSizeForValue (value) { |
| 127 | + return value == null ? 0 |
| 128 | + : typeof value === 'number' ? 1 |
| 129 | + : value.isVector2 ? 2 |
| 130 | + : value.isVector3 || value.isColor ? 3 |
| 131 | + : value.isVector4 ? 4 |
| 132 | + : Array.isArray(value) ? value.length |
| 133 | + : 0 |
| 134 | +} |
| 135 | + |
0 commit comments