16 Oct 2018

Load XML of Navisworks Saved Viewpoints to Forge Viewer Cameras

Default blog image

navis xml

Follow @Xiaodong Liang

Forge Model Derivative API can extract Navisworks saved viewpoints and build camera objects (NOP_VIEWER.model.getData().cameras) in Forge Viewer. The other blog tells more. However, in some cases, the workflows need to load the XML of Navisworks saved viewpoints and convert the data to Forge Viewer objects, because XML contains more info of viewpoints, and also contains clip plans/box data. The below is one example:

<exchange xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://download.autodesk.com/us/navisworks/schemas/nw-exchange-12.0.xsd" units="m" filename="ClashTest.nwd" filepath="C:\Program Files\Autodesk\Navisworks Manage 2019\api\COM\examples">
  <viewpoints>
    <view name="View1" guid="0ab1e9b4-cb96-41ce-aa71-7c4b83a80978">
      <viewpoint tool="navigator_zoom" render="shaded" lighting="user" focal="1550.2882399154" linear="49.2732722032" angular="0.7853981634">
        <camera projection="persp" near="1.0000000000" far="10.0000000000" aspect="0.8460000000" height="0.7853981634">
          <position>
            <pos3f x="854.0685942394" y="243.8875607304" z="967.6407589236"/>
          </position>
          <rotation>
            <quaternion a="0.4594456548" b="-0.1425955159" c="-0.2598635311" d="0.8372855871"/>
          </rotation>
        </camera>
        <viewer radius="0.2500000000" height="1.7000000000" actual_height="1.7000000000" eye_height="0.1500000000" avatar="dummy" camera_mode="first" first_to_third_angle="0.2617993878" first_to_third_distance="4.0000000000" first_to_third_param="1.0000000000" first_to_third_correction="1" collision_detection="0" auto_crouch="0" gravity="0" gravity_value="9.8000000000" terminal_velocity="50.0000000000"/>
        <up>
          <vec3f x="0.0000000000" y="0.0000000000" z="1.0000000000"/>
        </up>
      </viewpoint>
      <clipplaneset linked="0" current="0" mode="planes" enabled="0">
        <range>
          <box3f>
            <min>
              <pos3f x="1.0000000000" y="1.0000000000" z="1.0000000000"/>
            </min>
            <max>
              <pos3f x="0.0000000000" y="0.0000000000" z="0.0000000000"/>
            </max>
          </box3f>
        </range>
        <clipplanes>
          <clipplane state="default" distance="0.0000000000" alignment="top">
            <plane distance="0.0000000000">
              <vec3f x="0.0000000000" y="1.0000000000" z="0.0000000000"/>
            </plane>
          </clipplane>
          <clipplane state="default" distance="0.0000000000" alignment="bottom">
            <plane distance="0.0000000000">
              <vec3f x="0.0000000000" y="1.0000000000" z="0.0000000000"/>
            </plane>
          </clipplane>
          <clipplane state="default" distance="0.0000000000" alignment="front">
            <plane distance="0.0000000000">
              <vec3f x="0.0000000000" y="1.0000000000" z="0.0000000000"/>
            </plane>
          </clipplane>
          <clipplane state="default" distance="0.0000000000" alignment="back">
            <plane distance="0.0000000000">
              <vec3f x="0.0000000000" y="1.0000000000" z="0.0000000000"/>
            </plane>
          </clipplane>
          <clipplane state="default" distance="0.0000000000" alignment="left">
            <plane distance="0.0000000000">
              <vec3f x="0.0000000000" y="1.0000000000" z="0.0000000000"/>
            </plane>
          </clipplane>
          <clipplane state="default" distance="0.0000000000" alignment="right">
            <plane distance="0.0000000000">
              <vec3f x="0.0000000000" y="1.0000000000" z="0.0000000000"/>
            </plane>
          </clipplane>
        </clipplanes>
        <box>
          <box3f>
            <min>
              <pos3f x="1.0000000000" y="1.0000000000" z="1.0000000000"/>
            </min>
            <max>
              <pos3f x="0.0000000000" y="0.0000000000" z="0.0000000000"/>
            </max>
          </box3f>
        </box>
        <box-rotation>
          <rotation>
            <quaternion a="0.0000000000" b="0.0000000000" c="0.0000000000" d="1.0000000000"/>
          </rotation>
        </box-rotation>
      </clipplaneset>
    </view>

In this blog, we will talk about how to convert the data to cameras of Forge Viewer. Forge Viewer's camera system is configured by the same graphics logic as Navisworks. The only tricky is Navisworks XML describes camera by position and quaternion, while Forge Viewer cameras mainly takes position and targets (and up vector). Fortunately, there are a lot of materials on internet. I love one which also demos the equations on extracting view direction, up vector from quaternion.  With view direction, we can easily calculate target.

However, the position and target are different to those by NOP_VIEWER.model.getData().cameras. After debugging Viewer3D.js, I found the source position and target from Navisworks will need to be applied with global offset matrix. 

The code below assumes the XML file is available on server side:

var NavisworksVPList = []; 
        function readNavisVPXML()
        {
            //assume the XML file is available on the server
            var offsetMatrix = viewer.model.myData.placementWithOffset;
            $.get("ClashTest", {}, function (xml){
                NavisworksVPList = [];

                //get XML content
                var NavisworksVPXML =xml;

                var rootNode = NavisworksVPXML.documentElement;
                var vpNodes = NavisworksVPXML.documentElement.children[0];

                for(var index=0;index<vpNodes.children.length;index++){
                    //each viewpoint node
                    var eachVPNode = vpNodes.children[index];
                    //each viewpoint
                    var vp= eachVPNode.getElementsByTagName('viewpoint')[0]
                    //attributes
                    var focal = Number(vp.attributes.focal.nodeValue);
                    
                    //FOV
                    var fov = Number(vp.attributes.angular.nodeValue);
                    
                   //camera node
                    var cameraNode =  eachVPNode.getElementsByTagName('camera')[0];
                    //aspect
                    var aspect =  Number(cameraNode.attributes.aspect.nodeValue);
                    //isperspective
                    var projection =  cameraNode.attributes.projection.nodeValue;
                    var isPerspective = projection=='persp'?true:false;
                    //position
                    var position = cameraNode.getElementsByTagName('pos3f')[0];
                    var threePos = new THREE.Vector3(Number(position.attributes.x.nodeValue), 
                                                    Number(position.attributes.y.nodeValue),
                                                    Number(position.attributes.z.nodeValue));


                    //quaternion
                    var quaternion = cameraNode.getElementsByTagName('quaternion')[0];
                    var threeQN = new THREE.Quaternion(Number(quaternion.attributes.a.nodeValue), 
                                                  Number(quaternion.attributes.b.nodeValue),
                                                  Number(quaternion.attributes.c.nodeValue),
                                                  Number(quaternion.attributes.d.nodeValue));
                     
                    //calculate view direction
                    var v1 = -2 * (threeQN.x * threeQN.z + threeQN.w * threeQN.y);
                    var v2 = -2 * (threeQN.y * threeQN.z - threeQN.w * threeQN.x);
                    var v3 = -1+ 2 * (threeQN.x * threeQN.x + threeQN.y * threeQN.y);

                    //calculate target
                    var threeTarget = new THREE.Vector3(
                                        threePos.x + focal*v1,
                                        threePos.y + focal*v2,
                                        threePos.z + focal*v3 
                                    );

                   //apply with global offset
                    var offsetPos = threePos.applyMatrix4(offsetMatrix); 
                    var offsetTarget = threeTarget.applyMatrix4(offsetMatrix);
                    fov = fov * 180 / Math.PI;

                    //calculate up vector
                    v1 = 2 * (threeQN.x * threeQN.y - threeQN.w * threeQN.z);
                    v2 = 1 - 2 * (threeQN.x*threeQN.x + threeQN.z*threeQN.z);
                    v3 = 2 * (threeQN.y*threeQN.z + threeQN.w*threeQN.x); 
                    var up = new THREE.Vector3(v1,v2,v3);

                    //build one camera
                    NavisworksVPList.push(
                        {
                            aspect:aspect,
                            isPerspective:isPerspective,
                            fov:fov,
                            position:offsetPos,
                            target:offsetTarget,
                            up:up,
                            orthoScale: 1
                        }
                    ); 
                }

            });
        }

After running the function readNavisVPXML, you can switch the viewpoint by 

NOP_VIEWER.impl.setViewFromCamera(NavisworksVPList[0]);

Related Article