disable hdr

This commit is contained in:
Milovann Yanatchkov 2025-11-22 13:00:42 +01:00
parent 85562d5406
commit d610fd0f08
2 changed files with 531 additions and 467 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
viewer/package-lock.json
viewer/web/viewer/render.mjs

View file

@ -1,20 +1,19 @@
// (C) buildingSMART International
// published under MIT license
import { ComposedObject } from './composed-object';
import { IfcxFile } from '../ifcx-core/schema/schema-helper';
import { compose3 } from './compose-flattened';
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
import { PCDLoader } from 'three/addons/loaders/PCDLoader.js';
import { ComposedObject } from "./composed-object";
import { IfcxFile } from "../ifcx-core/schema/schema-helper";
import { compose3 } from "./compose-flattened";
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { RGBELoader } from "three/addons/loaders/RGBELoader.js";
import { PCDLoader } from "three/addons/loaders/PCDLoader.js";
let controls, renderer, scene, camera;
type datastype = [string, IfcxFile][];
let datas: datastype = [];
let autoCamera = true;
let objectMap: { [path: string]: any } = {};
let domMap: { [path: string]: HTMLElement } = {};
let primMap: { [path: string]: ComposedObject } = {};
@ -24,7 +23,6 @@ let rootPrim: ComposedObject | null = null;
let selectedObject: any = null;
let selectedDom: HTMLElement | null = null;
let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
@ -46,21 +44,27 @@ async function init() {
rimLight.position.set(0, 8, -10);
scene.add(rimLight);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
100,
);
camera.up.set(0, 0, 1);
camera.position.set(50, 50, 50);
camera.lookAt(0, 0, 0);
const nd = document.querySelector('.viewport');
const nd = document.querySelector(".viewport");
renderer = new THREE.WebGLRenderer({
alpha: true,
logarithmicDepthBuffer: true
logarithmicDepthBuffer: true,
});
// for GLTF PBR rendering, create environment map using PMREMGenerator:
// see https://threejs.org/docs/#api/en/extras/PMREMGenerator
/*
const pmremGenerator = new THREE.PMREMGenerator(renderer);
pmremGenerator.compileEquirectangularShader();
new RGBELoader()
@ -75,6 +79,7 @@ async function init() {
texture.dispose();
pmremGenerator.dispose();
});
*/
//@ts-ignore
renderer.setSize(nd.offsetWidth, nd.offsetHeight);
@ -85,24 +90,20 @@ async function init() {
controls.dampingFactor = 0.25;
nd!.appendChild(renderer.domElement);
renderer.domElement.addEventListener('click', onCanvasClick);
renderer.domElement.addEventListener("click", onCanvasClick);
return scene;
}
function HasAttr(node: ComposedObject | undefined, attrName: string)
{
function HasAttr(node: ComposedObject | undefined, attrName: string) {
if (!node || !node.attributes) return false;
return !!node.attributes[attrName];
}
function FindChildWithAttr(node: ComposedObject | undefined, attrName: string)
{
function FindChildWithAttr(node: ComposedObject | undefined, attrName: string) {
if (!node || !node.children) return undefined;
for (let i = 0; i < node.children.length; i++)
{
if (HasAttr(node.children[i], attrName))
{
for (let i = 0; i < node.children.length; i++) {
if (HasAttr(node.children[i], attrName)) {
return node.children[i];
}
}
@ -132,7 +133,7 @@ function setHighlight(obj: any, highlight: boolean) {
function selectPath(path: string | null) {
if (!path) {
if (selectedObject) setHighlight(selectedObject, false);
if (selectedDom) selectedDom.classList.remove('selected');
if (selectedDom) selectedDom.classList.remove("selected");
selectedObject = null;
selectedDom = null;
return;
@ -142,12 +143,12 @@ function selectPath(path: string | null) {
setHighlight(selectedObject, false);
}
if (selectedDom) {
selectedDom.classList.remove('selected');
selectedDom.classList.remove("selected");
}
selectedObject = objectMap[path] || null;
selectedDom = domMap[path] || null;
if (selectedObject) setHighlight(selectedObject, true);
if (selectedDom) selectedDom.classList.add('selected');
if (selectedDom) selectedDom.classList.add("selected");
}
function onCanvasClick(event) {
@ -167,20 +168,19 @@ function onCanvasClick(event) {
}
selectPath(path);
}
}
else {
} else {
selectPath(null);
}
}
function tryCreateMeshGltfMaterial(path: ComposedObject[]) {
// check for PBR defined by the gltf::material schema
for (let p of path) {
if (!p.attributes) {
continue;
}
const pbrMetallicRoughness = p.attributes["gltf::material::pbrMetallicRoughness"];
const pbrMetallicRoughness =
p.attributes["gltf::material::pbrMetallicRoughness"];
const normalTexture = p.attributes["gltf::material::normalTexture"];
const occlusionTexture = p.attributes["gltf::material::occlusionTexture"];
const emissiveTexture = p.attributes["gltf::material::emissiveTexture"];
@ -188,7 +188,16 @@ function tryCreateMeshGltfMaterial(path: ComposedObject[]) {
const alphaMode = p.attributes["gltf::material::alphaMode"];
const alphaCutoff = p.attributes["gltf::material::alphaCutoff"];
const doubleSided = p.attributes["gltf::material::doubleSided"];
if (!pbrMetallicRoughness && !normalTexture && !occlusionTexture && !emissiveTexture && !emissiveFactor && !alphaMode && !alphaCutoff && !doubleSided) {
if (
!pbrMetallicRoughness &&
!normalTexture &&
!occlusionTexture &&
!emissiveTexture &&
!emissiveFactor &&
!alphaMode &&
!alphaCutoff &&
!doubleSided
) {
// if none of the gltf::material properties are defined, we don't use pbr rendering, but default to the bsi::ifc::presentation definitions
continue;
}
@ -209,7 +218,11 @@ function tryCreateMeshGltfMaterial(path: ComposedObject[]) {
if (pbrMetallicRoughness) {
let baseColorFactor = pbrMetallicRoughness["baseColorFactor"];
if (baseColorFactor) {
material.color = new THREE.Color(baseColorFactor[0], baseColorFactor[1], baseColorFactor[2]);
material.color = new THREE.Color(
baseColorFactor[0],
baseColorFactor[1],
baseColorFactor[2],
);
}
let metallicFactor = pbrMetallicRoughness["metallicFactor"];
@ -222,24 +235,26 @@ function tryCreateMeshGltfMaterial(path: ComposedObject[]) {
material.roughness = roughnessFactor;
}
}
material.envMap = envMap
material.needsUpdate = true
material.envMap = envMap;
material.needsUpdate = true;
material.envMapRotation = new THREE.Euler(0.5 * Math.PI, 0, 0);
// console.log(material)
return material;
}
return undefined
return undefined;
}
function createMaterialFromParent(path: ComposedObject[]) {
let material = {
color: new THREE.Color(0.6, 0.6, 0.6),
transparent: false,
opacity: 1
opacity: 1,
};
for (let p of path) {
const color = p.attributes ? p.attributes["bsi::ifc::presentation::diffuseColor"] : null;
const color = p.attributes
? p.attributes["bsi::ifc::presentation::diffuseColor"]
: null;
if (color) {
material.color = new THREE.Color(...color);
const opacity = p.attributes["bsi::ifc::presentation::opacity"];
@ -254,7 +269,9 @@ function createMaterialFromParent(path: ComposedObject[]) {
}
function createCurveFromJson(path: ComposedObject[]) {
let points = new Float32Array(path[0].attributes["usd::usdgeom::basiscurves::points"].flat());
let points = new Float32Array(
path[0].attributes["usd::usdgeom::basiscurves::points"].flat(),
);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.BufferAttribute(points, 3));
@ -266,20 +283,23 @@ function createCurveFromJson(path: ComposedObject[]) {
}
function createMeshFromJson(path: ComposedObject[]) {
let points = new Float32Array(path[0].attributes["usd::usdgeom::mesh::points"].flat());
let indices = new Uint16Array(path[0].attributes["usd::usdgeom::mesh::faceVertexIndices"]);
let points = new Float32Array(
path[0].attributes["usd::usdgeom::mesh::points"].flat(),
);
let indices = new Uint16Array(
path[0].attributes["usd::usdgeom::mesh::faceVertexIndices"],
);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.BufferAttribute(points, 3));
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
geometry.computeVertexNormals();
var meshMaterial;
let gltfPbrMaterial = tryCreateMeshGltfMaterial(path);
if (gltfPbrMaterial) {
meshMaterial = gltfPbrMaterial
meshMaterial = gltfPbrMaterial;
// console.log(meshMaterial)
} else {
const m = createMaterialFromParent(path);
@ -305,7 +325,10 @@ function createPointsFromJsonPcdBase64(path: ComposedObject[]) {
return points;
}
function createPoints(geometry: THREE.BufferGeometry, withColors: boolean): THREE.Points {
function createPoints(
geometry: THREE.BufferGeometry,
withColors: boolean,
): THREE.Points {
const material = new THREE.PointsMaterial();
material.sizeAttenuation = false;
material.fog = true;
@ -321,13 +344,21 @@ function createPoints(geometry: THREE.BufferGeometry, withColors: boolean): THRE
function createPointsFromJsonArray(path: ComposedObject[]) {
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(path[0].attributes["points::array::positions"].flat());
geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
const positions = new Float32Array(
path[0].attributes["points::array::positions"].flat(),
);
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(positions, 3),
);
const colors = path[0].attributes["points::array::colors"];
if (colors) {
const colors_ = new Float32Array(colors.flat());
geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors_, 3));
geometry.setAttribute(
"color",
new THREE.Float32BufferAttribute(colors_, 3),
);
}
return createPoints(geometry, colors);
}
@ -336,8 +367,7 @@ function base64ToArrayBuffer(str): ArrayBuffer | undefined {
let binary;
try {
binary = atob(str);
}
catch(e) {
} catch (e) {
throw new Error("base64 encoded string is invalid");
}
const bytes = new Uint8Array(binary.length);
@ -356,14 +386,20 @@ function createPointsFromJsonPositionBase64(path: ComposedObject[]) {
return null;
}
const positions = new Float32Array(positions_bytes!);
geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
geometry.setAttribute(
"position",
new THREE.Float32BufferAttribute(positions, 3),
);
const colors_base64 = path[0].attributes["points::base64::colors"];
if (colors_base64) {
const colors_bytes = base64ToArrayBuffer(colors_base64);
if (colors_bytes) {
const colors = new Float32Array(colors_bytes!);
geometry.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
geometry.setAttribute(
"color",
new THREE.Float32BufferAttribute(colors, 3),
);
}
}
return createPoints(geometry, colors_base64);
@ -372,31 +408,23 @@ function createPointsFromJsonPositionBase64(path: ComposedObject[]) {
function traverseTree(path: ComposedObject[], parent, pathMapping) {
const node = path[0];
let elem: any = new THREE.Group();
if (HasAttr(node, "usd::usdgeom::visibility::visibility"))
{
if (node.attributes["usd::usdgeom::visibility::visibility"] === 'invisible') {
if (HasAttr(node, "usd::usdgeom::visibility::visibility")) {
if (
node.attributes["usd::usdgeom::visibility::visibility"] === "invisible"
) {
return;
}
}
else if (HasAttr(node, "usd::usdgeom::mesh::points"))
{
} else if (HasAttr(node, "usd::usdgeom::mesh::points")) {
elem = createMeshFromJson(path);
}
else if (HasAttr(node, "usd::usdgeom::basiscurves::points"))
{
} else if (HasAttr(node, "usd::usdgeom::basiscurves::points")) {
elem = createCurveFromJson(path);
}
// point cloud data types:
else if (HasAttr(node, "pcd::base64"))
{
else if (HasAttr(node, "pcd::base64")) {
elem = createPointsFromJsonPcdBase64(path);
}
else if (HasAttr(node, "points::array::positions"))
{
} else if (HasAttr(node, "points::array::positions")) {
elem = createPointsFromJsonArray(path);
}
else if (HasAttr(node, "points::base64::positions"))
{
} else if (HasAttr(node, "points::base64::positions")) {
elem = createPointsFromJsonPositionBase64(path);
}
@ -404,15 +432,22 @@ function traverseTree(path: ComposedObject[], parent, pathMapping) {
primMap[node.name] = node;
elem.userData.path = node.name;
for (let path of Object.entries(node.attributes || {}).filter(([k, _]) => k.startsWith('__internal_')).map(([_, v]) => v)) {
(pathMapping[String(path)] = pathMapping[String(path)] || []).push(node.name);
for (let path of Object.entries(node.attributes || {})
.filter(([k, _]) => k.startsWith("__internal_"))
.map(([_, v]) => v)) {
(pathMapping[String(path)] = pathMapping[String(path)] || []).push(
node.name,
);
}
parent.add(elem);
if (path.length > 1) {
elem.matrixAutoUpdate = false;
let matrixNode = node.attributes && node.attributes['usd::xformop::transform'] ? node.attributes['usd::xformop::transform'].flat() : null;
let matrixNode =
node.attributes && node.attributes["usd::xformop::transform"]
? node.attributes["usd::xformop::transform"].flat()
: null;
if (matrixNode) {
let matrix = new THREE.Matrix4();
//@ts-ignore
@ -422,22 +457,24 @@ function traverseTree(path: ComposedObject[], parent, pathMapping) {
}
}
(node.children || []).forEach(child => traverseTree([child, ...path], elem || parent, pathMapping));
(node.children || []).forEach((child) =>
traverseTree([child, ...path], elem || parent, pathMapping),
);
}
function encodeHtmlEntities(str) {
const div = document.createElement('div');
const div = document.createElement("div");
div.textContent = str;
return div.innerHTML;
};
}
const icons = {
'usd::usdgeom::mesh::points': 'deployed_code',
'usd::usdgeom::basiscurves::points': 'line_curve',
'usd::usdshade::material::outputs::surface.connect': 'line_style',
'pcd::base64': 'grain',
'points::array::positions': 'grain',
'points::base64::positions': 'grain',
"usd::usdgeom::mesh::points": "deployed_code",
"usd::usdgeom::basiscurves::points": "line_curve",
"usd::usdshade::material::outputs::surface.connect": "line_style",
"pcd::base64": "grain",
"points::array::positions": "grain",
"points::base64::positions": "grain",
};
function handleClick(prim, pathMapping, root) {
@ -446,27 +483,37 @@ function handleClick(prim, pathMapping, root) {
container.innerHTML = "";
const table = document.createElement("table");
table.setAttribute("border", "0");
const entries = [["name", prim.name], ...Object.entries(prim.attributes).filter(([k, _]) => !k.startsWith('__internal_'))];
const entries = [
["name", prim.name],
...Object.entries(prim.attributes).filter(
([k, _]) => !k.startsWith("__internal_"),
),
];
const format = (value) => {
if (Array.isArray(value)) {
let N = document.createElement('span');
N.appendChild(document.createTextNode('('));
let N = document.createElement("span");
N.appendChild(document.createTextNode("("));
let first = true;
for (let n of value.map(format)) {
if (!first) {
N.appendChild(document.createTextNode(','));
N.appendChild(document.createTextNode(","));
}
N.appendChild(n);
first = false;
}
N.appendChild(document.createTextNode(')'));
N.appendChild(document.createTextNode(")"));
return N;
} else if (typeof value === "object") {
const ks = Object.keys(value);
if (ks.length == 1 && ks[0] === 'ref' && pathMapping[value.ref] && pathMapping[value.ref].length == 1) {
let a = document.createElement('a');
if (
ks.length == 1 &&
ks[0] === "ref" &&
pathMapping[value.ref] &&
pathMapping[value.ref].length == 1
) {
let a = document.createElement("a");
let resolvedRefAsPath = pathMapping[value.ref][0];
a.setAttribute('href', '#');
a.setAttribute("href", "#");
a.textContent = resolvedRefAsPath;
a.onclick = () => {
let prim = null;
@ -476,12 +523,12 @@ function handleClick(prim, pathMapping, root) {
} else {
(n.children || []).forEach(recurse);
}
}
};
recurse(root);
if (prim) {
handleClick(prim, pathMapping, root);
}
}
};
return a;
} else {
return document.createTextNode(JSON.stringify(value));
@ -504,12 +551,18 @@ function handleClick(prim, pathMapping, root) {
}
}
function buildDomTree(prim, node, pathMapping, root=null) {
const elem = document.createElement('div');
function buildDomTree(prim, node, pathMapping, root = null) {
const elem = document.createElement("div");
let span;
elem.appendChild(document.createTextNode(prim.name ? prim.name.split('/').reverse()[0] : 'root'));
elem.appendChild(span = document.createElement('span'));
Object.entries(icons).forEach(([k, v]) => span.innerText += (prim.attributes || {})[k] ? v : ' ');
elem.appendChild(
document.createTextNode(
prim.name ? prim.name.split("/").reverse()[0] : "root",
),
);
elem.appendChild((span = document.createElement("span")));
Object.entries(icons).forEach(
([k, v]) => (span.innerText += (prim.attributes || {})[k] ? v : " "),
);
span.className = "material-symbols-outlined";
domMap[prim.name] = elem as HTMLElement;
elem.dataset.path = prim.name;
@ -519,14 +572,16 @@ function buildDomTree(prim, node, pathMapping, root=null) {
evt.stopPropagation();
};
node.appendChild(elem);
(prim.children || []).forEach(p => buildDomTree(p, elem, pathMapping, root || prim));
(prim.children || []).forEach((p) =>
buildDomTree(p, elem, pathMapping, root || prim),
);
}
export async function composeAndRender() {
if (scene) {
// @todo does this actually free up resources?
// retain only the lights
scene.children = scene.children.filter(n => n instanceof THREE.Light);
scene.children = scene.children.filter((n) => n instanceof THREE.Light);
}
objectMap = {};
@ -535,14 +590,14 @@ export async function composeAndRender() {
currentPathMapping = null;
rootPrim = null;
document.querySelector('.tree')!.innerHTML = '';
document.querySelector(".tree")!.innerHTML = "";
if (datas.length === 0) {
return;
}
let tree: null | ComposedObject = null;
let dataArray = datas.map(arr => arr[1]);
let dataArray = datas.map((arr) => arr[1]);
tree = await compose3(dataArray as IfcxFile[]);
if (!tree) {
@ -551,7 +606,7 @@ export async function composeAndRender() {
}
if (!scene) {
await init()
await init();
}
let pathMapping = {};
@ -563,9 +618,16 @@ export async function composeAndRender() {
const boundingBox = new THREE.Box3();
boundingBox.setFromObject(scene);
if (!boundingBox.isEmpty()) {
let avg = boundingBox.min.clone().add(boundingBox.max).multiplyScalar(0.5);
let avg = boundingBox.min
.clone()
.add(boundingBox.max)
.multiplyScalar(0.5);
let ext = boundingBox.max.clone().sub(boundingBox.min).length();
camera.position.copy(avg.clone().add(new THREE.Vector3(1,1,1).normalize().multiplyScalar(ext)));
camera.position.copy(
avg
.clone()
.add(new THREE.Vector3(1, 1, 1).normalize().multiplyScalar(ext)),
);
camera.far = ext * 3;
camera.updateProjectionMatrix();
controls.target.copy(avg);
@ -576,17 +638,17 @@ export async function composeAndRender() {
}
}
buildDomTree(tree, document.querySelector('.tree'), pathMapping);
buildDomTree(tree, document.querySelector(".tree"), pathMapping);
animate();
}
function createLayerDom() {
document.querySelector('.layers div')!.innerHTML = '';
document.querySelector(".layers div")!.innerHTML = "";
datas.forEach(([name, _], index) => {
const elem = document.createElement('div');
const elem = document.createElement("div");
elem.appendChild(document.createTextNode(name));
['\u25B3', '\u25BD', '\u00D7'].reverse().forEach((lbl, cmd) => {
const btn = document.createElement('span');
["\u25B3", "\u25BD", "\u00D7"].reverse().forEach((lbl, cmd) => {
const btn = document.createElement("span");
btn.onclick = (evt) => {
evt.stopPropagation();
if (cmd === 2) {
@ -603,11 +665,11 @@ function createLayerDom() {
// TODO: await this
composeAndRender();
createLayerDom();
}
};
btn.appendChild(document.createTextNode(lbl));
elem.appendChild(btn);
});
document.querySelector('.layers div')!.appendChild(elem);
document.querySelector(".layers div")!.appendChild(elem);
});
}