wowserhq/wowser

View on GitHub
src/lib/pipeline/wmo/group/index.js

Summary

Maintainability
A
2 hrs
Test Coverage
import THREE from 'three';

import WMOMaterial from '../material';

class WMOGroup extends THREE.Mesh {

  static cache = {};

  constructor(wmo, id, data, path) {
    super();

    this.dispose = ::this.dispose;

    this.matrixAutoUpdate = false;

    this.wmo = wmo;
    this.groupID = id;
    this.data = data;
    this.path = path;

    this.indoor = data.indoor;
    this.animated = false;

    const vertexCount = data.MOVT.vertices.length;
    const textureCoords = data.MOTV.textureCoords;

    const positions = new Float32Array(vertexCount * 3);
    const normals = new Float32Array(vertexCount * 3);
    const uvs = new Float32Array(vertexCount * 2);
    const colors = new Float32Array(vertexCount * 3);
    const alphas = new Float32Array(vertexCount);

    data.MOVT.vertices.forEach(function(vertex, index) {
      // Provided as (X, Z, -Y)
      positions[index * 3] = vertex[0];
      positions[index * 3 + 1] = vertex[2];
      positions[index * 3 + 2] = -vertex[1];

      uvs[index * 2] = textureCoords[index][0];
      uvs[index * 2 + 1] = textureCoords[index][1];
    });

    data.MONR.normals.forEach(function(normal, index) {
      normals[index * 3] = normal[0];
      normals[index * 3 + 1] = normal[2];
      normals[index * 3 + 2] = -normal[1];
    });

    if ('MOCV' in data) {
      data.MOCV.colors.forEach(function(color, index) {
        colors[index * 3] = color.r / 255.0;
        colors[index * 3 + 1] = color.g / 255.0;
        colors[index * 3 + 2] = color.b / 255.0;
        alphas[index] = color.a / 255.0;
      });
    } else if (this.indoor) {
      // Default indoor vertex color: rgba(0.5, 0.5, 0.5, 1.0)
      data.MOVT.vertices.forEach(function(_vertex, index) {
        colors[index * 3] = 127.0 / 255.0;
        colors[index * 3 + 1] = 127.0 / 255.0;
        colors[index * 3 + 2] = 127.0 / 255.0;
        alphas[index] = 1.0;
      });
    }

    const indices = new Uint32Array(data.MOVI.triangles);

    const geometry = this.geometry = new THREE.BufferGeometry();
    geometry.setIndex(new THREE.BufferAttribute(indices, 1));
    geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3));
    geometry.addAttribute('normal', new THREE.BufferAttribute(normals, 3));
    geometry.addAttribute('uv', new THREE.BufferAttribute(uvs, 2));

    // TODO: Perhaps it is possible to directly use a vec4 here? Currently, color + alpha is
    // combined into a vec4 in the material's vertex shader. For some reason, attempting to
    // directly use a BufferAttribute with a length of 4 resulted in incorrect ordering for the
    // values in the shader.
    geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3));
    geometry.addAttribute('alpha', new THREE.BufferAttribute(alphas, 1));

    // Mirror geometry over X and Y axes and rotate
    const matrix = new THREE.Matrix4();
    matrix.makeScale(-1, -1, 1);
    geometry.applyMatrix(matrix);
    geometry.rotateX(-Math.PI / 2);

    const materialIDs = [];

    data.MOBA.batches.forEach(function(batch) {
      materialIDs.push(batch.materialID);
      geometry.addGroup(batch.firstIndex, batch.indexCount, batch.materialID);
    });

    const materialDefs = this.wmo.data.MOMT.materials;
    const texturePaths = this.wmo.data.MOTX.filenames;

    this.material = this.createMultiMaterial(materialIDs, materialDefs, texturePaths);
  }

  createMultiMaterial(materialIDs, materialDefs, texturePaths) {
    const multiMaterial = new THREE.MultiMaterial();

    materialIDs.forEach((materialID) => {
      const materialDef = materialDefs[materialID];

      if (this.indoor) {
        materialDef.indoor = true;
      } else {
        materialDef.indoor = false;
      }

      if (!this.wmo.data.MOHD.skipBaseColor) {
        materialDef.useBaseColor = true;
        materialDef.baseColor = this.wmo.data.MOHD.baseColor;
      } else {
        materialDef.useBaseColor = false;
      }

      const material = this.createMaterial(materialDefs[materialID], texturePaths);

      multiMaterial.materials[materialID] = material;
    });

    return multiMaterial;
  }

  createMaterial(materialDef, texturePaths) {
    const textureDefs = [];

    materialDef.textures.forEach((textureDef) => {
      const texturePath = texturePaths[textureDef.offset];

      if (texturePath !== undefined) {
        textureDef.path = texturePath;
        textureDefs.push(textureDef);
      } else {
        textureDefs.push(null);
      }
    });

    const material = new WMOMaterial(materialDef, textureDefs);

    return material;
  }

  clone() {
    return new this.constructor(this.wmo, this.groupID, this.data, this.path);
  }

  dispose() {
    this.geometry.dispose();

    this.material.materials.forEach((material) => {
      material.dispose();
    });
  }

}

export default WMOGroup;