Reference Source

src/viewer/scene/model/SceneModelMesh.js

import {math} from "../math/math.js";
import {meshSurfaceArea, meshVolume} from "../math/index.js";

const tempOBB3 = math.OBB3();
const tempOBB3b = math.OBB3();
const tempOBB3c = math.OBB3();


/**
 * A mesh within a {@link SceneModel}.
 *
 * * Created with {@link SceneModel#createMesh}
 * * Belongs to exactly one {@link SceneModelEntity}
 * * Stored by ID in {@link SceneModel#meshes}
 * * Referenced by {@link SceneModelEntity#meshes}
 * * Can have a {@link SceneModelTransform} to dynamically scale, rotate and translate it.
 */
export class SceneModelMesh {

    /**
     * @private
     */
    constructor(model, id, color, opacity, transform, textureSet, layer = null, portionId = 0) {

        /**
         * The {@link SceneModel} that owns this SceneModelMesh.
         *
         * @type {SceneModel}
         */
        this.model = model;

        /**
         * The {@link SceneModelEntity} that owns this SceneModelMesh.
         *
         * @type {SceneModelEntity}
         */
        this.object = null;

        /**
         * @private
         */
        this.parent = null;

        /**
         * The {@link SceneModelTransform} that transforms this SceneModelMesh.
         *
         * * This only exists when the SceneModelMesh is instancing its geometry.
         * * These are created with {@link SceneModel#createTransform}
         * * Each of these is also registered in {@link SceneModel#transforms}.
         *
         * @type {SceneModelTransform}
         */
        this.transform = transform;

        /**
         * The {@link SceneModelTextureSet} that optionally textures this SceneModelMesh.
         *
         * * This only exists when the SceneModelMesh has texture.
         * * These are created with {@link SceneModel#createTextureSet}
         * * Each of these is also registered in {@link SceneModel#textureSets}.
         *
         * @type {SceneModelTextureSet}
         */
        this.textureSet = textureSet;

        this._matrixDirty = false;
        this._matrixUpdateScheduled = false;

        /**
         * Unique ID of this SceneModelMesh.
         *
         * The SceneModelMesh is registered against this ID in {@link SceneModel#meshes}.
         */
        this.id = id;

        /**
         * @private
         */
        this.obb = null;

        this._aabbLocal = null;
        this._aabbWorld = math.AABB3();
        this._aabbWorldDirty = false;

        /**
         * @private
         */
        this.layer = layer;

        /**
         * @private
         */
        this.portionId = portionId;

        this._color = new Uint8Array([color[0], color[1], color[2], opacity]); // [0..255]
        this._colorize = new Uint8Array([color[0], color[1], color[2], opacity]); // [0..255]
        this._colorizing = false;
        this._transparent = (opacity < 255);

        /**
         * @private
         */
        this.numTriangles = 0;

        /**
         * @private
         * @type {null}
         */
        this.origin = null; // Set By SceneModel

        /**
         * The {@link SceneModelEntity} that owns this SceneModelMesh.
         *
         * @type {SceneModelEntity}
         */
        this.entity = null;

        if (transform) {
            transform._addMesh(this);
        }

        this._volume = null;
        this._surfaceArea = null;
    }

    _sceneModelDirty() {
        this._aabbWorldDirty = true;
        this.layer.aabbDirty = true;
    }

    _transformDirty() {
        if (!this._matrixDirty && !this._matrixUpdateScheduled) {
            this.model._meshMatrixDirty(this);
            this._matrixDirty = true;
            this._matrixUpdateScheduled = true;
        }
        this._aabbWorldDirty = true;
        this.layer.aabbDirty = true;
        if (this.entity) {
            this.entity._transformDirty();
        }
    }

    _updateMatrix() {
        if (this.transform && this._matrixDirty) {
            this.layer.setMatrix(this.portionId, this.transform.worldMatrix);
        }
        this._matrixDirty = false;
        this._matrixUpdateScheduled = false;
    }

    _finalize(entityFlags) {
        this.layer.initFlags(this.portionId, entityFlags, this._transparent);
    }

    _finalize2() {
        if (this.layer.flushInitFlags) {
            this.layer.flushInitFlags();
        }
    }

    _setVisible(entityFlags) {
        this.layer.setVisible(this.portionId, entityFlags, this._transparent);
    }

    _setColor(color) {
        this._color[0] = color[0];
        this._color[1] = color[1];
        this._color[2] = color[2];
        if (!this._colorizing) {
            this.layer.setColor(this.portionId, this._color, false);
        }
    }

    _setColorize(colorize) {
        const setOpacity = false;
        if (colorize) {
            this._colorize[0] = colorize[0];
            this._colorize[1] = colorize[1];
            this._colorize[2] = colorize[2];
            this.layer.setColor(this.portionId, this._colorize, setOpacity);
            this._colorizing = true;
        } else {
            this.layer.setColor(this.portionId, this._color, setOpacity);
            this._colorizing = false;
        }
    }

    _setOpacity(opacity, entityFlags) {
        const newTransparent = (opacity < 255);
        const lastTransparent = this._transparent;
        const changingTransparency = (lastTransparent !== newTransparent);
        this._color[3] = opacity;
        this._colorize[3] = opacity;
        this._transparent = newTransparent;
        if (this._colorizing) {
            this.layer.setColor(this.portionId, this._colorize);
        } else {
            this.layer.setColor(this.portionId, this._color);
        }
        if (changingTransparency) {
            this.layer.setTransparent(this.portionId, entityFlags, newTransparent);
        }
    }

    _setOffset(offset) {
        this.layer.setOffset(this.portionId, offset);
    }

    _setHighlighted(entityFlags) {
        this.layer.setHighlighted(this.portionId, entityFlags, this._transparent);
    }

    _setXRayed(entityFlags) {
        this.layer.setXRayed(this.portionId, entityFlags, this._transparent);
    }

    _setSelected(entityFlags) {
        this.layer.setSelected(this.portionId, entityFlags, this._transparent);
    }

    _setEdges(entityFlags) {
        this.layer.setEdges(this.portionId, entityFlags, this._transparent);
    }

    _setClippable(entityFlags) {
        this.layer.setClippable(this.portionId, entityFlags, this._transparent);
    }

    _setCollidable(entityFlags) {
        this.layer.setCollidable(this.portionId, entityFlags);
    }

    _setPickable(flags) {
        this.layer.setPickable(this.portionId, flags, this._transparent);
    }

    _setCulled(flags) {
        this.layer.setCulled(this.portionId, flags, this._transparent);
    }

    /**
     * @private
     */
    canPickTriangle() {
        return false;
    }

    /**
     * @private
     */
    drawPickTriangles(renderFlags, frameCtx) {
        // NOP
    }

    /**
     * @private
     */
    pickTriangleSurface(pickResult) {
        // NOP
    }

    /**
     * @private
     */
    precisionRayPickSurface(worldRayOrigin, worldRayDir, worldSurfacePos, worldSurfaceNormal) {
        return this.layer.precisionRayPickSurface ? this.layer.precisionRayPickSurface(this.portionId, worldRayOrigin, worldRayDir, worldSurfacePos, worldSurfaceNormal) : false;
    }

    /**
     * @private
     */
    canPickWorldPos() {
        return true;
    }

    /**
     * @private
     */
    drawPickDepths(frameCtx) {
        this.model.drawPickDepths(frameCtx);
    }

    /**
     * @private
     */
    drawPickNormals(frameCtx) {
        this.model.drawPickNormals(frameCtx);
    }

    /**
     * @private
     */
    delegatePickedEntity() {
        return this.parent;
    }

    /**
     * @private
     */
    getEachVertex(callback) {
        if (this.layer.getEachVertex) {
            this.layer.getEachVertex(this.portionId, callback);
        }
    }

    /**
     * @private
     */
    getEachIndex(callback) {
        if (this.layer.getEachIndex ) {
            this.layer.getEachIndex(this.portionId, callback);
        }
    }

    /**
     * Returns the volume of this SceneModelMesh.
     * @returns {number}
     */
    get volume() {
        if (this._volume !== null) {
            return this._volume;
        }
        switch (this.layer.primitive) {
            case "solid":
            case "surface":
            case "triangles":
                meshVolume.reset();
                meshVolume.setPrimitive(this.layer.primitive);
                this.getEachVertex((vertex) =>{
                    meshVolume.addVertex(vertex);
                });
                this.getEachIndex((index)=>{
                   meshVolume.addIndex(index);
                });
                this._volume = meshVolume.volume;
                break;
            default:
                this._volume = 0;
                break;
        }
        return this._volume;
    }

    /**
     * Returns the surface area of this SceneModelMesh.
     * @returns {number}
     */
    get surfaceArea() {
        if (this._surfaceArea !== null) {
            return this._surfaceArea;
        }
        switch (this.layer.primitive) {
            case "solid":
            case "surface":
            case "triangles":
                meshSurfaceArea.reset();
                this.getEachVertex((vertex) =>{
                    meshSurfaceArea.addVertex(vertex);
                });
                this.getEachIndex((index)=>{
                    meshSurfaceArea.addIndex(index);
                });
                this._surfaceArea = meshSurfaceArea.surfaceArea;
                break;
            default:
                this._surfaceArea = 0;
                break;
        }
        return this._surfaceArea;
    }

    /**
     * @private
     */
    set aabb(aabb) { // Called by SceneModel
        this._aabbLocal = aabb;
        if (this.origin) {
            this._aabbLocal = aabb.slice(0);
            const origin = this.origin;
            this._aabbLocal[0] += origin[0];
            this._aabbLocal[1] += origin[1];
            this._aabbLocal[2] += origin[2];
            this._aabbLocal[3] += origin[0];
            this._aabbLocal[4] += origin[1];
            this._aabbLocal[5] += origin[2];
        }
    }

    /**
     * @private
     */
    get aabb() { // called by SceneModelEntity
        if (this._aabbWorldDirty) {
            math.AABB3ToOBB3(this._aabbLocal, tempOBB3);
            if (this.transform) {
                math.transformOBB3(this.transform.worldMatrix, tempOBB3, tempOBB3b);
                math.transformOBB3(this.model.worldMatrix, tempOBB3b, tempOBB3c);
                math.OBB3ToAABB3(tempOBB3c, this._aabbWorld);
            } else {
                math.transformOBB3(this.model.worldMatrix, tempOBB3, tempOBB3b);
                math.OBB3ToAABB3(tempOBB3b, this._aabbWorld);
            }
            this._aabbWorldDirty = false;
        }
        return this._aabbWorld;
    }

    /**
     * @private
     */
    _destroy() {
        this.model.scene._renderer.putPickID(this.pickId);
    }
}