packages/miew/src/gfx/meshutils.js

Summary

Maintainability
B
5 hrs
Test Coverage
/**
 * Utils functions which is worked with meshes
 *
 * functions for doing something with all/specified meshes
 * functions for traversihg tree and create auxiliary meshes for transparency/shadowmaps...
 * functions for calculating data connected with meshes
 */

import UberMaterial from './shaders/UberMaterial'
import gfxutils from './gfxutils'
import {
  BufferGeometry,
  InstancedBufferAttribute,
  InstancedBufferGeometry,
  Line,
  LineSegments,
  Mesh
} from 'three'

function _gatherObjects(root, meshTypes) {
  const meshes = []
  root.traverse((object) => {
    for (let i = 0; i < meshTypes.length; i++) {
      if (object instanceof meshTypes[i]) {
        meshes[meshes.length] = object
        break
      }
    }
  })
  return meshes
}

// new mesh with the same geometry and specified material values and layer
function createDerivativeMesh(mesh, values, layer) {
  const material = mesh.material.createInstance()
  material.setValues(values)

  const newMesh = new mesh.constructor(mesh.geometry, material)
  newMesh.material.needsUpdate = true
  newMesh.applyMatrix4(mesh.matrix)
  newMesh.layers.set(layer)
  return newMesh
}

function traverseMeshes(root, meshTypes, func) {
  const meshes = _gatherObjects(root, meshTypes)

  for (let i = 0, n = meshes.length; i < n; ++i) {
    const mesh = meshes[i]
    if (!mesh.parent) {
      continue
    }
    func(mesh)
  }
}

function applyTransformsToMeshes(root, mtc) {
  const mtcCount = mtc.length
  if (mtcCount < 1) {
    return
  }

  const meshTypes = [Mesh, LineSegments, Line]
  traverseMeshes(root, meshTypes, (mesh) => {
    mesh.applyMatrix4(mtc[0])
    for (let j = 1; j < mtcCount; ++j) {
      const newMesh = new mesh.constructor(mesh.geometry, mesh.material)
      mesh.parent.add(newMesh)
      newMesh.applyMatrix4(mtc[j])
    }
  })
}

const processTransparentMaterial = (function () {
  const matValues = {
    prepassTransparancy: true,
    fakeOpacity: false,
    transparent: false,
    colorFromDepth: false,
    lights: false,
    shadowmap: false,
    fog: false
  }

  return function (root, material) {
    if (!(material instanceof UberMaterial)) {
      return
    }

    traverseMeshes(root, [Mesh, LineSegments], (mesh) => {
      mesh.material.setValues({
        prepassTransparancy: false,
        fakeOpacity: false
      })
      mesh.material.needsUpdate = true
      mesh.layers.set(gfxutils.LAYERS.TRANSPARENT)

      const prepassTranspMesh = createDerivativeMesh(
        mesh,
        matValues,
        gfxutils.LAYERS.PREPASS_TRANSPARENT
      )
      mesh.parent.add(prepassTranspMesh)
    })
  }
})()

const processColFromPosMaterial = (function () {
  const matValues = {
    colorFromPos: true,
    transparent: false,
    colorFromDepth: false,
    lights: false,
    shadowmap: false,
    fog: false,
    overrideColor: false,
    fogTransparent: false,
    attrColor: false,
    attrColor2: false,
    attrAlphaColor: false,
    fakeOpacity: false
  }

  return function (root, material) {
    if (!(material instanceof UberMaterial)) {
      return
    }

    traverseMeshes(root, [Mesh, LineSegments], (mesh) => {
      const colFromPosMesh = createDerivativeMesh(
        mesh,
        matValues,
        gfxutils.LAYERS.COLOR_FROM_POSITION
      )
      mesh.parent.add(colFromPosMesh)
    })
  }
})()

const createShadowmapMaterial = (function () {
  const matValues = {
    colorFromDepth: true,
    orthoCam: true,
    lights: false,
    shadowmap: false,
    fog: false
  }

  return function (root, material) {
    if (!(material instanceof UberMaterial)) {
      return
    }
    traverseMeshes(root, [Mesh, LineSegments], (mesh) => {
      if (!mesh.receiveShadow && mesh.material.shadowmap) {
        // remove shadow from non-receivers
        mesh.material.setValues({ shadowmap: false })
      }
      if (!mesh.material.lights) {
        // skip creating shadowmap meshes for materials without lighting
        return
      }
      if (!mesh.castShadow) {
        // skip creating shadowmap meshes for non-casters
        return
      }
      if (!gfxutils.belongToSelectLayers(mesh)) {
        // skip creating shadowmap meshes for selection layer
        return
      }

      const shadowmapMesh = createDerivativeMesh(
        mesh,
        matValues,
        gfxutils.LAYERS.SHADOWMAP
      )
      shadowmapMesh.isShadowmapMesh = true
      mesh.parent.add(shadowmapMesh)
    })
  }
})()

function removeShadowmapMaterial(root, material) {
  if (!(material instanceof UberMaterial)) {
    return
  }

  traverseMeshes(root, [Mesh, LineSegments], (mesh) => {
    if (mesh.isShadowmapMesh) {
      mesh.parent.remove(mesh)
    }
  })
}

function forEachMeshInGroup(group, process) {
  function processObj(object) {
    if (object instanceof Mesh) {
      process(object)
    }
    for (let i = 0, l = object.children.length; i < l; i++) {
      processObj(object.children[i])
    }
  }
  processObj(group)
}

function _countMeshTriangles(mesh) {
  const geom = mesh.geometry
  if (geom instanceof InstancedBufferGeometry) {
    const attribs = geom.attributes
    for (const property in attribs) {
      if (
        Object.hasOwn(attribs, property) &&
        attribs[property] instanceof InstancedBufferAttribute
      ) {
        const currAttr = attribs[property]
        const indexSize = geom.index ? geom.index.array.length / 3 : 0
        return (indexSize * currAttr.array.length) / currAttr.itemSize
      }
    }
    return 0
  }
  if (geom instanceof BufferGeometry) {
    return geom.index ? geom.index.array.length / 3 : 0
  }
  return geom.faces ? geom.faces.length : 0
}

function countTriangles(group) {
  let totalCount = 0
  forEachMeshInGroup(group, (mesh) => {
    totalCount += _countMeshTriangles(mesh)
  })
  return totalCount
}

export default {
  applyTransformsToMeshes,
  processTransparentMaterial,
  processColFromPosMaterial,
  createShadowmapMaterial,
  removeShadowmapMaterial,
  forEachMeshInGroup,
  countTriangles
}