/** * @author André Storhaug <[email protected]> */ import autoBind from 'auto-bind'; import { Color, BufferGeometry, MeshPhongMaterial, BoxGeometry, Vector3, Mesh, Geometry, VertexColors } from 'three'; import { LoaderFactory } from "./loaders/LoaderFactory"; import { levelOfDetail } from './mixins/levelOfDetail'; export class VoxelLoader { /** * Create a VoxelLoader. * @classdesc Class for loading voxel data stored in various formats. * @param {LoadingManager} manager * @mixes levelOfDetail */ constructor(manager) { autoBind(this); Object.assign(this, levelOfDetail); this.manager = manager; this.octree = null; this.material = null; this.voxelSize = null; this.setVoxelMaterial(); this.setVoxelSize(); } /** * Set the material used for all voxels. * Note that the {@link Material.vertexColors} will be set to {@link VertexColors}. * @param {Material} Material The wanted material. */ setVoxelMaterial(material) { let defaultMaterial = new MeshPhongMaterial({ color: 0xffffff }); material = typeof material !== 'undefined' ? material : defaultMaterial; material.vertexColors = VertexColors this.material = material; } /** * Set the size of the cubes representing voxels generated in {@link VoxelLoader#generateMesh}. * @param {float} [voxelSize=1] */ setVoxelSize(voxelSize = 1) { this.voxelSize = voxelSize; } /** * Update the internal data structures and settings. * @return {Promise<PointOctree>} Promise with an updated octree. */ update() { if (this.octree === null) { throw new Error('Octree is not built'); } return this.parseData(this.octree, 'octree'); } /** * Loads and parses a 3D model file from a URL. * * @param {String} url - URL to the VOX file. * @param {Function} [onLoad] - Callback invoked with the Mesh object. * @param {Function} [onProgress] - Callback for download progress. * @param {Function} [onError] - Callback for download errors. */ loadFile(url, onLoad, onProgress, onError) { let scope = this; let extension = url.split('.').pop().toLowerCase(); let loaderFactory = new LoaderFactory(this.manager); let loader = loaderFactory.getLoader(extension); loader.setLOD(this.LOD.maxPoints, this.LOD.maxDepth); loader.load(url, function (octree) { scope.octree = octree; onLoad(scope.generateMesh(octree)); }, onProgress, onError); } /** * Parses voxel data. * @param {PointOctree} octree Octree with voxel data stored as points in space. * @return {Promise<PointOctree>} Promise with an octree filled with voxel data. */ parseData(data, type) { let scope = this; let loaderFactory = new LoaderFactory(this.manager); let loader = loaderFactory.getLoader(type); loader.setLOD(this.LOD.maxPoints, this.LOD.maxDepth); return new Promise((resolve) => { loader.parse(data).then((octree) => { scope.octree = octree; resolve(octree); }); }); } /** * Generates a polygon mesh with cubes based on voxel data. * One cube for each voxel. * @param {PointOctree} octree Octree with voxel data stored as points in space. * @returns {Mesh} 3D mesh based on voxel data */ generateMesh(octree) { let mergedGeometry = new Geometry(); const material = this.material; for (const leaf of octree.leaves()) { if (leaf.points !== null) { const pos = new Vector3(); var i; let min = { x: leaf.points[0].x, y: leaf.points[0].y, z: leaf.points[0].z }; let max = { x: leaf.points[0].x, y: leaf.points[0].y, z: leaf.points[0].z }; for (i = 0; i < leaf.points.length; i++) { const point = leaf.points[i]; pos.add(point); min.x = Math.min(min.x, point.x); min.y = Math.min(min.y, point.y); min.z = Math.min(min.z, point.z); max.x = Math.max(max.x, point.x); max.y = Math.max(max.y, point.y); max.z = Math.max(max.z, point.z); } let width = Math.round((this.voxelSize + (max.x - min.x)) * 100) / 100;; let height = Math.round((this.voxelSize + (max.y - min.y)) * 100) / 100;; let depth = Math.round((this.voxelSize + (max.z - min.z)) * 100) / 100; let voxelGeometry = new BoxGeometry(width, height, depth); pos.divideScalar(i); const rgb = leaf.data[0].color; if (rgb != null) { const color = new Color().setRGB(rgb.r / 255, rgb.g / 255, rgb.b / 255); for (var i = 0; i < voxelGeometry.faces.length; i++) { let face = voxelGeometry.faces[i]; face.color.set(color); } } voxelGeometry.translate(pos.x, pos.y, pos.z); mergedGeometry.merge(voxelGeometry); voxelGeometry.translate(-pos.x, -pos.y, -pos.z); } } let bufGeometry = new BufferGeometry().fromGeometry(mergedGeometry); bufGeometry.computeFaceNormals(); bufGeometry.computeVertexNormals(); var voxels = new Mesh(bufGeometry, material); return voxels; } }