starry / backend /libs /three /renderers /webgl /WebGLMorphtargets.js
k-l-lambda's picture
feat: add Python ML services (CPU mode) with model download
2b7aae2
import { FloatType, RGBAFormat } from '../../constants.js';
import { DataTexture2DArray } from '../../textures/DataTexture2DArray.js';
import { Vector3 } from '../../math/Vector3.js';
import { Vector2 } from '../../math/Vector2.js';
function numericalSort(a, b) {
return a[0] - b[0];
}
function absNumericalSort(a, b) {
return Math.abs(b[1]) - Math.abs(a[1]);
}
function denormalize(morph, attribute) {
let denominator = 1;
const array = attribute.isInterleavedBufferAttribute ? attribute.data.array : attribute.array;
if (array instanceof Int8Array) denominator = 127;
else if (array instanceof Int16Array) denominator = 32767;
else if (array instanceof Int32Array) denominator = 2147483647;
else console.error('THREE.WebGLMorphtargets: Unsupported morph attribute data type: ', array);
morph.divideScalar(denominator);
}
function WebGLMorphtargets(gl, capabilities, textures) {
const influencesList = {};
const morphInfluences = new Float32Array(8);
const morphTextures = new WeakMap();
const morph = new Vector3();
const workInfluences = [];
for (let i = 0; i < 8; i++) {
workInfluences[i] = [i, 0];
}
function update(object, geometry, material, program) {
const objectInfluences = object.morphTargetInfluences;
if (capabilities.isWebGL2 === true) {
// instead of using attributes, the WebGL 2 code path encodes morph targets
// into an array of data textures. Each layer represents a single morph target.
const numberOfMorphTargets = geometry.morphAttributes.position.length;
let entry = morphTextures.get(geometry);
if (entry === undefined || entry.count !== numberOfMorphTargets) {
if (entry !== undefined) entry.texture.dispose();
const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
const morphTargets = geometry.morphAttributes.position;
const morphNormals = geometry.morphAttributes.normal || [];
const numberOfVertices = geometry.attributes.position.count;
const numberOfVertexData = hasMorphNormals === true ? 2 : 1; // (v,n) vs. (v)
let width = numberOfVertices * numberOfVertexData;
let height = 1;
if (width > capabilities.maxTextureSize) {
height = Math.ceil(width / capabilities.maxTextureSize);
width = capabilities.maxTextureSize;
}
const buffer = new Float32Array(width * height * 4 * numberOfMorphTargets);
const texture = new DataTexture2DArray(buffer, width, height, numberOfMorphTargets);
texture.format = RGBAFormat; // using RGBA since RGB might be emulated (and is thus slower)
texture.type = FloatType;
texture.needsUpdate = true;
// fill buffer
const vertexDataStride = numberOfVertexData * 4;
for (let i = 0; i < numberOfMorphTargets; i++) {
const morphTarget = morphTargets[i];
const morphNormal = morphNormals[i];
const offset = width * height * 4 * i;
for (let j = 0; j < morphTarget.count; j++) {
morph.fromBufferAttribute(morphTarget, j);
if (morphTarget.normalized === true) denormalize(morph, morphTarget);
const stride = j * vertexDataStride;
buffer[offset + stride + 0] = morph.x;
buffer[offset + stride + 1] = morph.y;
buffer[offset + stride + 2] = morph.z;
buffer[offset + stride + 3] = 0;
if (hasMorphNormals === true) {
morph.fromBufferAttribute(morphNormal, j);
if (morphNormal.normalized === true) denormalize(morph, morphNormal);
buffer[offset + stride + 4] = morph.x;
buffer[offset + stride + 5] = morph.y;
buffer[offset + stride + 6] = morph.z;
buffer[offset + stride + 7] = 0;
}
}
}
entry = {
count: numberOfMorphTargets,
texture: texture,
size: new Vector2(width, height),
};
morphTextures.set(geometry, entry);
function disposeTexture() {
texture.dispose();
morphTextures.delete(geometry);
geometry.removeEventListener('dispose', disposeTexture);
}
geometry.addEventListener('dispose', disposeTexture);
}
//
let morphInfluencesSum = 0;
for (let i = 0; i < objectInfluences.length; i++) {
morphInfluencesSum += objectInfluences[i];
}
const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;
program.getUniforms().setValue(gl, 'morphTargetBaseInfluence', morphBaseInfluence);
program.getUniforms().setValue(gl, 'morphTargetInfluences', objectInfluences);
program.getUniforms().setValue(gl, 'morphTargetsTexture', entry.texture, textures);
program.getUniforms().setValue(gl, 'morphTargetsTextureSize', entry.size);
} else {
// When object doesn't have morph target influences defined, we treat it as a 0-length array
// This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences
const length = objectInfluences === undefined ? 0 : objectInfluences.length;
let influences = influencesList[geometry.id];
if (influences === undefined || influences.length !== length) {
// initialise list
influences = [];
for (let i = 0; i < length; i++) {
influences[i] = [i, 0];
}
influencesList[geometry.id] = influences;
}
// Collect influences
for (let i = 0; i < length; i++) {
const influence = influences[i];
influence[0] = i;
influence[1] = objectInfluences[i];
}
influences.sort(absNumericalSort);
for (let i = 0; i < 8; i++) {
if (i < length && influences[i][1]) {
workInfluences[i][0] = influences[i][0];
workInfluences[i][1] = influences[i][1];
} else {
workInfluences[i][0] = Number.MAX_SAFE_INTEGER;
workInfluences[i][1] = 0;
}
}
workInfluences.sort(numericalSort);
const morphTargets = geometry.morphAttributes.position;
const morphNormals = geometry.morphAttributes.normal;
let morphInfluencesSum = 0;
for (let i = 0; i < 8; i++) {
const influence = workInfluences[i];
const index = influence[0];
const value = influence[1];
if (index !== Number.MAX_SAFE_INTEGER && value) {
if (morphTargets && geometry.getAttribute('morphTarget' + i) !== morphTargets[index]) {
geometry.setAttribute('morphTarget' + i, morphTargets[index]);
}
if (morphNormals && geometry.getAttribute('morphNormal' + i) !== morphNormals[index]) {
geometry.setAttribute('morphNormal' + i, morphNormals[index]);
}
morphInfluences[i] = value;
morphInfluencesSum += value;
} else {
if (morphTargets && geometry.hasAttribute('morphTarget' + i) === true) {
geometry.deleteAttribute('morphTarget' + i);
}
if (morphNormals && geometry.hasAttribute('morphNormal' + i) === true) {
geometry.deleteAttribute('morphNormal' + i);
}
morphInfluences[i] = 0;
}
}
// GLSL shader uses formula baseinfluence * base + sum(target * influence)
// This allows us to switch between absolute morphs and relative morphs without changing shader code
// When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence)
const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;
program.getUniforms().setValue(gl, 'morphTargetBaseInfluence', morphBaseInfluence);
program.getUniforms().setValue(gl, 'morphTargetInfluences', morphInfluences);
}
}
return {
update: update,
};
}
export { WebGLMorphtargets };