Skip to content

Creating your own XML3D Components

Christian Schlinkmann edited this page Apr 4, 2016 · 2 revisions

WIP

XML3D 5.2 adds a new feature based on concept of web components which we're calling XML3D components. Before we get into the details lets look at a quick use case.

Use case: Bounding Box

Sometimes when you're designing 3D scenes or collision detection it's useful to be able to see an object's bounding box. Before XML3D components this would have taken a fair bit of Javascript. We need to query an object's bounding box, create a bunch of DOM elements, link them together and insert them into the scene:

function showBoundingBox(element) {
  var bbox = element.getWorldSpaceBoundingBox();

  var data = document.createElement("data");
  data.setAttribute("id", "bboxData");
  data.setAttribute("compute", "position, vertexCount = xflow.genLinesFromBBox(bbox)");
  
  var floatData = document.createElement("float3");
  floatData.setAttribute("name", "bbox");
  floatData.textContent = bbox.toDOMString();
  data.appendChild(floatData);

  var mesh = document.createElement("mesh");
  mesh.src = "#bboxData";
  mesh.setAttribute("type", "linestrips");
  mesh.setAttribute("material", "#bboxm"); // Still need to create the material too!

  element.parentElement.appendChild(data);
  element.parentElement.appendChild(mesh);
}

On top of all that we'd need to import the xflow.genLinesFromBBox script into every scene and create a material for the bounding box (bboxm). And we'd need to add more application level Javascript code to deal with changes in the object's bounding box. We could save ourselves some trouble by creating templates in Javascript for this structure, or loading some things (such as the material) from an external source, but thankfully there's an easier way now.

Bounding Box Component

Our goal for this component is to reduce all the boilerplate code down to a single Javascript include and a single element to inject into the DOM tree. To do this we'll use the web component paradigm applied to XML3D, so we'll be creating a custom element template called x-bounding-box. This will contain all of our HTML markup:

<template id='bbox-inspector' name='x-bounding-box'>
  <material id='bboxm' model='urn:xml3d:material:flat'>
    <content select="float3[name='diffuseColor']">
      <float3 name='diffuseColor'>0 1 0</float3>
    </content>
  </material>
	
  <data id='bboxdata' compute='position, vertexCount = xflow.genLinesFromBBox(bbox)'>
    <content select="float3[name='bbox']">
      <float3 name='bbox'>0 0 0 0 0 0</float3>
    </content>
  </data>
	
  <mesh material="#bboxm" src='#bboxdata' type='linestrips'></mesh>
</template>

The first thing to note is that the web component standard says all custom elements must contain a hyphen '-' in their name, and XML3D will check for this. Similar to the Javascript approach above we define the elements that we need to render the bounding box data. The <content> elements are another piece of the web component standard, think of them as slots waiting to be filled from the outside. They can hold default data (as they do here) that will be overridden as soon as something fits into the slot. We'll come back to this later when we create an instance of our <x-bounding-box> element.

For now lets move on to the Javascript portion of our component. This file is that we'll include into our scene to let us use the component, like a plugin.

XML3D.BoundingBoxInspector = {};
XML3D.BoundingBoxInspector.showBoundingBox = function(element, optColor) {
    var bbox = document.createElement('float3');
    bbox.setAttribute('name', 'bbox');
    bbox.textContent = element.getWorldBoundingBox().toDOMString();
        
    var xbbox = document.createElement('x-bounding-box');
    xbbox.appendChild(bbox);
        
    if (optColor) {
        var color = document.createElement("float3");
        color.setAttribute("name", "diffuseColor");
        color.textContent = optColor.toDOMString();
        xbbox.appendChild(color);
    }
        
    element.parentElement.appendChild(xbbox);
}

XML3D.BoundingBoxInspector.hideBoundingBox = function(element) {
    var sibling = element;
    while (sibling = sibling.nextElementSibling) {
        if (sibling.nodeName == 'X-BOUNDING-BOX') {
            sibling.parentElement.removeChild(sibling);
            break;
        }
    }
}

Xflow.registerOperator("xflow.genLinesFromBBox", {
   ... Here we would register the custom Xflow operator that creates a 
   box made of linestrips out of the bounding box min and max, see 
   below for a link to the operator that we use ...
});

// We can either host the template somewhere (as below) or 
// include it as part of our Javascript (eg. as a DOM string)
XML3D.registerComponent("path/to/component/template.html", { name : "x-bounding-box" });

xflow-genLinesFromBBox.js

The only required call in this script is XML3D.registerComponent, which automatically loads the component template from a given URL (or template element) and registers it with the browser as per the web component standard. The show and hide functions are specific to our component and are designed to make it easier to use. After including this script in our scene we only need to call the show function on an XML3D element to see its bounding box displayed in the scene:

  <!-- DOM before -->
  <group>
    <mesh id="myMesh" src="aMesh.xml"></mesh>
  </group>
XML3D.BoundingBoxInspector.showBoundingBox( document.getElementById("myMesh"), new XML3D.Vec3(0, 1, 0) );
<!-- DOM after -->
  <group>
    <mesh id="myMesh" src="aMesh.xml"></mesh>
    <x-bounding-box>
       <float3 name="diffuseColor">0 1 0</float3>
       <float3 name="bbox">0 0 0 10 10 10</float3>
    </x-bounding-box>
  </group>

The elements inside the component template are automatically created by the browser and added to the DOM, but they're hidden inside the <x-bounding-box> element's shadow DOM. We also fill both <content> slots, once with a color for the bounding box and once with the bounding box sizes given to us by the mesh element. When we want to stop displaying the bounding box we simply remove the component element from the DOM, which is done for us in the hide function:

XML3D.BoundingBoxInspector.hideBoundingBox( document.getElementById("myMesh") );
Clone this wiki locally