Partial Explode Class

While HOOPS Communicator has a built-in explode feature it is unfortunately not very customizable. The code below is a straightforward example of a more customizable explode with the following features:

• Limit explode to X/Y/Z Axis
• Only explode along major axis
• Exclude nodes from explode based on distance threshold
• Only explode a subset of the model

It is important to note that the built-in explode operator is tied into the markup views which means its state will be saved with a view which will not be the case when using this class. Also the built-in explode will most likely perform better with larger models especially in server-side rendering.

Usage:

var myPartialExplode = new PartialExplode(hwv);

Instantiate new partial explode object. It can make sense to have more than one objects if you want to explode different parts of the model with different settings at the same time.

myPartialExplode.initializeFromSelection()

This is the simplest way to initialize the explode operator. It will use the currently selected nodes as the entities to explode. If only one node is selected it will gather all leaf nodes of the model under this node.

myPartialExplode.initialize(nodeids);

This function takes an array of nodeids. Similar to the function above if only one node is in the array it will gather all leaf nodes under that node instead.

myPartialExplode.explode(2);

This will explode the selected nodes by a factor of two based on the distance from the center of all explode nodes. You would typically call this function dynamically based on the value of some slider (see html/js sample below).

myPartialExplode.explode(2,true,true,false);

This will explode the selected nodes by a factor of two but only the X and Y axis will be taken into account.

myPartialExplode.explode(2,true,true,true,true);

This will explode the selected nodes by a factor of two along all axis but all exploded entities will only move along the major axis.

myPartialExplode.reset();

Resets all explode nodes to their original matrix.


Below is some simple HTML/JS that you can use to quickly test the class in your own application:

 <div id="explodetools" style="width:100%;height:100%;position:relative">     
        <input type="range" min="0" max="800" value="0" id="explodeslider" style="width:60%">
    Th:<input type="text" id="explodethreshold" value="0" style="width:20%">
    <br />
    <input type="checkbox" id="explodeX" checked="checked">X
    <input type="checkbox" id="explodeY" checked="checked">Y
    <input type="checkbox" id="explodeZ" checked="checked">Z
    <input type="checkbox" id="onlymajoraxis">Major Axis
    <br>
    <div id="buttonrow" style="font-size:10px; margin-top:5px">
        <input type="button" onclick='myPartialExplode.initializeFromSelection()' value="Init">
        <input type="button" onclick='myPartialExplode.reset()' value="Reset">
    </div>
   var explodeslider = document.getElementById("explodeslider");
    explodeslider.oninput = function () {
        myPartialExplode.explode(this.value / 100.0, document.getElementById("explodeX").checked, document.getElementById("explodeY").checked, document.getElementById("explodeZ").checked,
            document.getElementById("onlymajoraxis").checked, document.getElementById("explodethreshold").value);
    }


This is the main code for the explode class. It has no external dependencies except for the relevant HOOPS Web Viewer libraries. Simply save it into a separate file and include it with your other js files.

class PartialExplode {

    constructor(viewer) {
        this._viewer = viewer;
        this._explodeNodes = [];
        this._explodeCenter = null;
    }

 

    initialize(nodeids) {

        this._explodeNodes = [];

        var explodenodeids;
        var pnodes = [];

        if (nodeids.length == 1) {
            var leafnodes = [];
            this._findLeafNodesRecursive(nodeids[0], leafnodes);
            explodenodeids = leafnodes;
        }
        else
            explodenodeids = nodeids;

        for (var i = 0; i < explodenodeids.length; i++) {
            var en = { nodeid: null, bbox: null, matrix: null };
            en.nodeid = explodenodeids[i];
            en.matrix = this._viewer.model.getNodeMatrix(en.nodeid).copy();
            this._explodeNodes.push(en);
            pnodes.push(this._viewer.model.getNodesBounding([en.nodeid]));

        }

        var _this = this;
        Promise.allSettled(pnodes).then(function (data) {

            for (var i = 0; i < data.length; i++) {
                if (data[i].status == "fulfilled")
                    _this._explodeNodes[i].bbox = data[i].value.copy();
                else
                    _this._explodeNodes[i].bbox = null;

            }
            _this._calculateCenter();
        });

    }

    initializeFromSelection() {
        var nodeids = [];
        var sels = this._viewer.selectionManager.getResults();

        for (var i = 0; i < sels.length; i++) {
            nodeids.push(sels[i].getNodeId());
        }
        this.initialize(nodeids);
    }

  
    reset() {

        for (var i = 0; i < this._explodeNodes.length; i++) {
            this._viewer.model.resetNodeMatrixToInitial(this._explodeNodes[i].nodeid);

        }

    }

    explode(magnitude, explodeX, explodeY, explodeZ, onlymajoraxis, threshold) {

        if (!this._explodeCenter)
            return;
            
        explodeX = (typeof explodeX !== 'undefined') ? explodeX : true;
        explodeY = (typeof explodeY !== 'undefined') ? explodeY : true;
        explodeZ = (typeof explodeZ !== 'undefined') ? explodeZ : true;
        onlymajoraxis = (typeof onlymajoraxis !== 'undefined') ? onlymajoraxis : false;
        threshold = (typeof threshold !== 'undefined') ? threshold : 0;

        for (var i = 0; i < this._explodeNodes.length; i++) {
            if (magnitude == 0)
                this._viewer.model.setNodeMatrix(this._explodeNodes[i].nodeid, this._explodeNodes[i].matrix)
            else {
                if (this._explodeNodes[i].bbox != null) {
                    var delta = Communicator.Point3.subtract(this._explodeNodes[i].bbox.center(), this._explodeCenter);

                    var scaledDelta = Communicator.Point3.scale(delta, magnitude);

                    if (onlymajoraxis) {
                        var olddelta = delta.copy();
                        olddelta.normalize();
                        olddelta.x = Math.abs(olddelta.x);
                        olddelta.y = Math.abs(olddelta.y);
                        olddelta.z = Math.abs(olddelta.z);

                        if (explodeX == true && olddelta.x > olddelta.y && olddelta.x > olddelta.z) {
                            scaledDelta.y = 1.0;
                            scaledDelta.z = 1.0;
                        }
                        else if (explodeY == true && olddelta.y > olddelta.x && olddelta.y > olddelta.z) {
                            scaledDelta.x = 1.0;
                            scaledDelta.z = 1.0;
                        }
                        else if (explodeZ == true && olddelta.z > olddelta.x && olddelta.z > olddelta.y) {
                            scaledDelta.x = 1.0;
                            scaledDelta.y = 1.0;
                        }
                    }

                    if (!explodeX)
                        scaledDelta.x = 1.0;
                    if (!explodeY)
                        scaledDelta.y = 1.0;
                    if (!explodeZ)
                        scaledDelta.z = 1.0;

                    if (delta.length() < threshold) {
                        this._viewer.model.setNodeMatrix(this._explodeNodes[i].nodeid, this._explodeNodes[i].matrix);
                        continue;
                    }

                    var netmatrix = this._viewer.model.getNodeNetMatrix(this._viewer.model.getNodeParent(this._explodeNodes[i].nodeid));
                    var netmatrixinverse = Communicator.Matrix.inverse(netmatrix);

                    var m = new Communicator.Matrix();
                    m.setTranslationComponent(scaledDelta.x, scaledDelta.y, scaledDelta.z);

                    var tempmatrix = Communicator.Matrix.multiply(netmatrix, m);
                    var tempmatrix2= Communicator.Matrix.multiply(tempmatrix, netmatrixinverse);
                    var resultmatrix = Communicator.Matrix.multiply(this._explodeNodes[i].matrix, tempmatrix2);
                    this._viewer.model.setNodeMatrix(this._explodeNodes[i].nodeid, resultmatrix)
                }
            }
        }
    }

    _findLeafNodesRecursive(nodeid, leafnodes) {
        var children = this._viewer.model.getNodeChildren(nodeid);
        if (children.length == 0)
            leafnodes.push(nodeid);
        for (var i = 0; i < children.length; i++)
            this._findLeafNodesRecursive(children[i], leafnodes);
    }

    _calculateCenter() {
        this._explodeCenter = new Communicator.Point3(0, 0, 0);
        var startbox = this._explodeNodes[0].bbox.copy();
        for (var i = 1; i < this._explodeNodes.length; i++) {
            if (this._explodeNodes[i].bbox != null)
                startbox.addBox(this._explodeNodes[i].bbox);
        }
        this._explodeCenter = startbox.center().copy();

    }

}