Published
Edited
Sep 28, 2021
Insert cell
Insert cell
Insert cell
x3d0 = FileAttachment("GeometryPrimitiveNodes.x3d").xml()
Insert cell
x3d = addDefaultsToX3D(x3d0)
Insert cell
scene = x3d.querySelector('Scene')
Insert cell
scene.outerHTML
Insert cell
Insert cell
cameras = {
const cameras = [];
//only do examine mode for now
scene
.querySelectorAll("Viewpoint")
.forEach((vp) => cameras.push(vp2camera(vp)));
return cameras;
}
Insert cell
function vp2camera(vp) {
const cor = parseMFFloat(vp.getAttribute("centerOfRotation"));
const camera = new BABYLON.ArcRotateCamera(
"Viewpoint",
0,
0,
10,
new BABYLON.Vector3().fromArray(cor),
nullScene
);
camera.fov = +vp.getAttribute("fieldOfView");
// Positions the camera overwriting alpha, beta, radius
const position = parseMFFloat(vp.getAttribute("position"));
camera.setPosition(new BABYLON.Vector3().fromArray(position));
// use outer transform/rotation for orientation
const orientation = parseMFFloat(vp.getAttribute("orientation"));
const oAxis = orientation.slice(0, 3);
const oAngle = orientation.slice(3, 4);
// ignore for now
// camera.rotationQuaternion = new BABYLON.Quaternion.RotationAxis( new BABYLON.Vector3().fromArray(oAxis), oAngle );
return camera;
}
Insert cell
lights = {
const lights = [];
//make headlight
//will need camera as parent
const headlight = new BABYLON.DirectionalLight(
"HeadLight",
new BABYLON.Vector3(0, 0, -1),
nullScene
);
lights.push(headlight);
scene
.querySelectorAll("DirectionalLight")
.forEach((light) => lights.push(convertDirectionalLight(light)));
scene
.querySelectorAll("PointLight")
.forEach((light) => lights.push(convertPointLight(light)));
//...
return lights;
}
Insert cell
function convertDirectionalLight (light) {
return new BABYLON.DirectionalLight("dirLight");
}
Insert cell
function convertPointLight (light) {
return new BABYLON.PointLight("pointLight");
}
Insert cell
meshes = {
const meshes = [];
const materials = [];
scene.querySelectorAll("Shape").forEach((shape) => {
const mesh = shape2mesh(shape);
meshes.push(mesh);
materials.push(mesh.material);
});
return { meshes: meshes, materials: materials };
}
Insert cell
transforms = {
const trafos = new Map();
const createParentAndAssign = function (BNode) {
const x3dParent = BNode.metadata.x3dNode.parentNode;
let trafo = null;
if (trafos.has(x3dParent)) {
trafo = trafos.get(x3dParent);
} else {
switch (x3dParent.tagName) {
case "Scene":
return;
case "Transform":
trafo = convertTransform(x3dParent);
break;
default:
trafo = new BABYLON.TransformNode("identityTrafo", nullScene);
trafo.metadata = { x3dNode: x3dParent };
}
}
//debugger;
BNode.parent = trafo;
trafos.set(x3dParent, trafo);
createParentAndAssign(trafo);
};
meshes.meshes.forEach((mesh) => {
createParentAndAssign(mesh);
});
return trafos;
}
Insert cell
function convertTransform(transform) {
const DEFname = transform.getAttribute("DEF") || "trafo";
const trafo = new BABYLON.TransformNode(DEFname, nullScene);
trafo.id = trafo.name + trafo.uniqueId; // important for serialization to have unique id
//trafo.id = trafo.name;
trafo.position.fromArray(parseMFFloat(transform.getAttribute("translation")));
trafo.scaling.fromArray(parseMFFloat(transform.getAttribute("scale")));
const rotation = parseMFFloat(transform.getAttribute("rotation"));
const rAxis = rotation.slice(0, 3);
const rAngle = rotation.slice(3, 4);
trafo.rotationQuaternion = new BABYLON.Quaternion.RotationAxis(
new BABYLON.Vector3().fromArray(rAxis),
rAngle
);
trafo.metadata = { x3dNode: transform };
return trafo;
}
Insert cell
shape2mesh = function (shape) {
let mesh = null;
const clone = shape.cloneNode(true);
const app = clone.querySelector("Appearance");
app && app.remove();
const geometry = clone.children[0];
switch (geometry.tagName) {
case "Box":
mesh = box2mesh(geometry);
break;
case "Cone":
mesh = cone2mesh(geometry);
break;
case "Cylinder":
mesh = cylinder2mesh(geometry);
break;
case "Sphere":
mesh = sphere2mesh(geometry);
break;
case "Text":
mesh = text2mesh(geometry);
break;
default:
mesh = box2mesh(geometry);
}
mesh.name = geometry.getAttribute("DEF") || "shapeMesh";
mesh.id = mesh.name;
//DEFmap[mesh.name] = mesh;
mesh.material = app2material(app);
mesh.metadata = { x3dNode: shape };
return mesh;
}
Insert cell
function app2material(app) {
const mat = app.querySelector("Material");
const name = "appMaterial";
const myMaterial = new BABYLON.StandardMaterial(name, nullScene);
myMaterial.id = myMaterial.name + myMaterial.uniqueId; // important for serialization to have unique id
myMaterial.diffuseColor.fromArray(
parseMFFloat(mat.getAttribute("diffuseColor"))
);
myMaterial.emissiveColor.fromArray(
parseMFFloat(mat.getAttribute("emissiveColor"))
);
myMaterial.specularColor.fromArray(
parseMFFloat(mat.getAttribute("specularColor"))
);
myMaterial.specularPower = +mat.getAttribute("shininess");
myMaterial.alpha = 1.0 - mat.getAttribute("transparency");
//ambientIntensity needs to be applied differently.
const ambient = +mat.getAttribute("ambientIntensity");
myMaterial.ambientColor.fromArray([ambient, ambient, ambient]);
return myMaterial;
}
Insert cell
sphere2mesh = function (geometry) {
// radius
const solid = geometry.getAttribute("solid") == "true";
const radius = +geometry.getAttribute("radius");
const mesh = BABYLON.MeshBuilder.CreateSphere(
"Sphere",
{
diameter: radius * 2.0,
sideOrientation: solid ? BABYLON.Mesh.FRONTSIDE : BABYLON.Mesh.DOUBLESIDE
},
nullScene
);
return mesh;
}
Insert cell
cone2mesh = function (geometry) {
// /bottom="true" bottomRadius="1" height="2" side="true";
const bottom = geometry.getAttribute("bottom") == "true";
const side = geometry.getAttribute("side") == "true";
const solid = geometry.getAttribute("solid") == "true";
const bottomRadius = +geometry.getAttribute("bottomRadius");
const height = +geometry.getAttribute("height");
const mesh = BABYLON.MeshBuilder.CreateCylinder(
"Cone",
{
height: height,
diameterTop: 0,
diameterBottom: bottomRadius * 2.0,
cap: bottom ? BABYLON.Mesh.CAP_START : BABYLON.Mesh.NO_CAP,
sideOrientation: solid ? BABYLON.Mesh.FRONTSIDE : BABYLON.Mesh.DOUBLESIDE
},
nullScene
);
return mesh;
}
Insert cell
cylinder2mesh = function (geometry) {
// bottom="true" height="2" radius="1" side="true" solid="true" top="true"/>
const bottom = geometry.getAttribute("bottom") == "true";
const top = geometry.getAttribute("top") == "true";
const side = geometry.getAttribute("side") == "true";
const solid = geometry.getAttribute("solid") == "true";
const radius = +geometry.getAttribute("radius");
const height = +geometry.getAttribute("height");
let cap = BABYLON.Mesh.NO_CAP;
if (bottom && top) cap = BABYLON.Mesh.CAP_ALL;
if (bottom && !top) cap = BABYLON.Mesh.CAP_START;
if (!bottom && top) cap = BABYLON.Mesh.CAP_END;
const mesh = BABYLON.MeshBuilder.CreateCylinder(
"Cylinder",
{
height: height,
diameterTop: radius * 2.0,
diameterBottom: radius * 2.0,
cap: cap,
sideOrientation: solid ? BABYLON.Mesh.FRONTSIDE : BABYLON.Mesh.DOUBLESIDE
},
nullScene
);
return mesh;
}
Insert cell
box2mesh = function (geometry) {
const size = geometry.getAttribute("size") || "1 2 3";
const solid = geometry.getAttribute("solid") == "true";
const parsedSize = parseMFFloat(size);
const boxMesh = BABYLON.MeshBuilder.CreateBox(
"Box",
{
width: parsedSize[0],
height: parsedSize[1],
depth: parsedSize[2],
sideOrientation: solid ? BABYLON.Mesh.FRONTSIDE : BABYLON.Mesh.DOUBLESIDE
},
nullScene
);
return boxMesh;
}
Insert cell
text2mesh = function (geometry) {
//string=""hello" "X3D!"" maxExtent="0.0" solid="false"
const text = geometry.getAttribute("string");
const solid = geometry.getAttribute("solid") == "true";
const maxExtent = +geometry.getAttribute("maxExtent");
//FontStyle DEF="DefaultFontStyle" family=""SERIF"" horizontal="true" justify=""BEGIN"" leftToRight="true" size="1.0" spacing="1.0" style="PLAIN" topToBottom="true"
const lines = text.match(/"[^"]*"/g).map((l) => l.replaceAll('"', ""));
const textSPS = new TextWriter( //replace with opentype based one, wasm
lines.join(" "), // just join for now
{
"font-family": "Jura", //"Arial","Jura","Comic","HirukoPro-Book"
"letter-height": 1,
"letter-thickness": 0.1,
//color: "#1C3870",
anchor: "left",
// colors:
// {
// diffuse: "#F0F0F0",
// specular: "#000000",
// ambient: "#F0F0F0",
// emissive: "#ff00f0"
// },
position: {
x: 0,
y: 0,
z: 0
}
}
);
const mesh = textSPS.getMesh();
mesh.rotation.x = -Math.PI / 2;
return mesh;
}
Insert cell
parseMFFloat = function (floatString) {
let sep = floatString.includes(",") ? "," : " ";
let floats = floatString.split(sep);

return floats.map((s) => +s);
}
Insert cell
serialized = {
cameras, lights, meshes, transforms; // needed as dependency
return BABYLON.SceneSerializer.Serialize(nullScene);
}
Insert cell
JSON.stringify(serialized)
Insert cell
Insert cell
renderCanvas = html`<canvas style='width: 100%'></canvas>`
Insert cell
render = {
INSPECTOR;
const engine = new BABYLON.Engine(renderCanvas, true); // Generate the BABYLON 3D engine

const fle = new File([JSON.stringify(serialized)], "scene.babylon", {type: 'text/plain'});
const url = URL.createObjectURL(fle);

async function createScene () {
//const scene = new BABYLON.Scene(engine);

const scene = await BABYLON.SceneLoader.LoadAsync("", url, engine); //, undefined, undefined, undefined, ".babylon");
scene.useRightHandedSystem = true; // may work

//scene.executeWhenReady(function () { // still needed even after await

//attach headlight to camera
scene.getLightByName('HeadLight').parent = scene.activeCamera ;

// Attach imported camera to canvas inputs
scene.activeCamera.attachControl(renderCanvas);

scene.activeCamera.inertia /= 2;

scene.debugLayer.show();
//const light = new BABYLON.HemisphericLight("light", new BABYLON.Vector3(1, 1, 0));

// Once the scene is loaded, register a render loop
engine.runRenderLoop(function() {
scene.render();
});
//});

// const camera = scene.cameras[0]//.clone();
// scene.activeCamera = camera;

// camera.attachControl(renderCanvas, true);
return scene;
};

const scene = createScene(); //Call the createScene function
// Watch for browser/canvas resize events
window.addEventListener("resize", function () {
engine.resize();
});
return scene;
}
Insert cell
//render.debugLayer.show()
Insert cell
nullScene = {
const scene = new BABYLON.Scene(
//new BABYLON.Engine(renderCanvas)
new BABYLON.NullEngine(renderCanvas)
);
scene.useRightHandedSystem = true;
return scene
}
Insert cell
BABYLON = require('https://preview.babylonjs.com/babylon.js')
Insert cell
INSPECTOR = require.alias({
babylonjs: BABYLON
})('https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js');
Insert cell
MeshWriter = require.alias({
babylonjs: BABYLON
})('meshwriter');
Insert cell
TextWriter = MeshWriter (nullScene)
Insert cell
import { addDefaultsToX3D, serializeDefaultsToX3D } from '@andreasplesch/x3d-defaults'
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more