update method

void update(
  1. Object3D object,
  2. BufferGeometry geometry,
  3. Material material,
  4. WebGLProgram program,
)

Implementation

void update(Object3D object, BufferGeometry geometry, Material material, WebGLProgram program) {
  List<num>? objectInfluences = object.morphTargetInfluences;
  if (capabilities.isWebGL2) {
    final morphAttribute = geometry.morphAttributes["position"] ?? geometry.morphAttributes["normal"] ?? geometry.morphAttributes["color"];
    final morphTargetsCount = (morphAttribute != null) ? morphAttribute.length : 0;

    Map? entry = morphTextures.get(geometry);
    if (entry == null || (entry["count"] != morphTargetsCount)) {
      if (entry != null) entry["texture"].dispose();

      final hasMorphPosition = geometry.morphAttributes["position"] != null;
      final hasMorphNormals = geometry.morphAttributes["normal"] != null;
      final hasMorphColors = geometry.morphAttributes["color"] != null;

      final morphTargets = geometry.morphAttributes["position"] ?? [];
      final morphNormals = geometry.morphAttributes["normal"] ?? [];
      final morphColors = geometry.morphAttributes["color"] ?? [];

      int vertexDataCount = 0;
      if (hasMorphPosition) vertexDataCount = 1;
      if (hasMorphNormals) vertexDataCount = 2;
      if (hasMorphColors) vertexDataCount = 3;

      int width = (geometry.attributes["position"].count * vertexDataCount).toInt();
      int height = 1;

      if (width > capabilities.maxTextureSize) {
        height = (width / capabilities.maxTextureSize).ceil();
        width = capabilities.maxTextureSize.toInt();
      }

      final buffer = Float32Array((width * height * 4 * morphTargetsCount).toInt());

      final texture = DataArrayTexture(buffer, width, height, morphTargetsCount);
      texture.type = FloatType;
      texture.needsUpdate = true;

      int vertexDataStride = vertexDataCount * 4;

      for (int i = 0; i < morphTargetsCount; i++) {
        final morphTarget = morphTargets[i];

        int offset = (width * height * 4 * i).toInt();

        for (int j = 0; j < morphTarget.count; j++) {
          final stride = j * vertexDataStride;

          if (hasMorphPosition) {
            morph.fromBuffer(morphTarget, j);

            if (morphTarget.normalized) {
              denormalize(morph, morphTarget);
            }

            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) {
            final morphNormal = morphNormals[i];
            morph.fromBuffer(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;
          }

          if (hasMorphColors) {
            final morphColor = morphColors[i];
            morph.fromBuffer(morphColor, j);

            if (morphColor.normalized) {
              denormalize(morph, morphColor);
            }

            buffer[offset + stride + 8] = morph.x;
            buffer[offset + stride + 9] = morph.y;
            buffer[offset + stride + 10] = morph.z;
            buffer[offset + stride + 11] = ((morphColor.itemSize == 4) ? morph.w : 1);
          }
        }
      }

      entry = {"count": morphTargetsCount, "texture": texture, "size": Vector2(width.toDouble(), height.toDouble())};

      morphTextures.set(geometry, entry);

      void disposeTexture() {
        texture.dispose();
        morphTextures.delete(geometry);
        geometry.removeEventListener('dispose', disposeTexture);
      }

      geometry.addEventListener('dispose', disposeTexture);
    }

    double morphInfluencesSum = 0;

    for (int i = 0; i < objectInfluences.length; i++) {
      morphInfluencesSum += objectInfluences[i];
    }

    final 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

    final length = objectInfluences.length;

    List<List<num>>? influences = influencesList[geometry.id];

    if (influences == null || influences.length != length) {
      influences = [];

      for (int i = 0; i < length; i++) {
        influences.add([i, 0.0]);
      }

      influencesList[geometry.id] = influences;
    }

    // Collect influences

    for (int i = 0; i < length; i++) {
      final influence = influences[i];
      influence[0] = i;
      influence[1] = objectInfluences[i];
    }

    influences.sort(absNumericalSort);

    for (int i = 0; i < 8; i++) {
      if (i < length && influences[i][1] != 0) {
        workInfluences[i][0] = influences[i][0];
        workInfluences[i][1] = influences[i][1];
      } else {
        workInfluences[i][0] = MathUtils.maxSafeInteger;
        workInfluences[i][1] = 0;
      }
    }

    workInfluences.sort(numericalSort);

    final morphTargets = geometry.morphAttributes["position"];
    final morphNormals = geometry.morphAttributes["normal"];

    double morphInfluencesSum = 0;

    for (int i = 0; i < 8; i++) {
      final influence = workInfluences[i];
      final index = influence[0].toInt();
      final value = influence[1];

      if (index != MathUtils.maxSafeInteger && value != 0) {
        if (morphTargets != null && geometry.getAttributeFromString('morphTarget$i') != morphTargets[index]) {
          geometry.setAttributeFromString('morphTarget$i', morphTargets[index]);
        }

        if (morphNormals != null && geometry.getAttributeFromString('morphNormal$i') != morphNormals[index]) {
          geometry.setAttributeFromString('morphNormal$i', morphNormals[index]);
        }

        morphInfluences[i] = value.toDouble();
        morphInfluencesSum += value;
      }
      else {
        if (morphTargets != null && geometry.hasAttributeFromString('morphTarget$i') == true) {
          geometry.deleteAttributeFromString('morphTarget$i');
        }

        if (morphNormals != null && geometry.hasAttributeFromString('morphNormal$i') == true) {
          geometry.deleteAttributeFromString('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)
    final morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;
    program.getUniforms().setValue(gl, 'morphTargetBaseInfluence', morphBaseInfluence);
    program.getUniforms().setValue(gl, 'morphTargetInfluences', morphInfluences);
  }
}