UNPKG

@geodanresearch/mapbox-3dtiles

Version:
282 lines (233 loc) 8.75 kB
import * as THREE from 'three'; export function applyStyle(scene, styleParams, renderOptions) { const type = styleParams.type; const settings = styleParams.settings; if (type) { switch (type) { case 'shade': styleShade(scene, settings, renderOptions); break; case 'random': styleRandom(scene, settings, renderOptions); break; case 'property': styleProperty(scene, settings, renderOptions); break; case 'basic': styleBasic(scene, settings, renderOptions); break; case 'shader': styleShader(scene, settings, renderOptions); break; } } return scene; } export function applyRenderOptions(scene, renderOptions) { const stylableMeshes = getStylableMeshes(scene); for (let i = 0; i < stylableMeshes.length; i++) { const child = stylableMeshes[i]; applyMeshRenderOptions(child, renderOptions); } } function getStylableMeshes(scene) { const meshes = []; scene.traverse(child => { if (child instanceof THREE.Mesh && child.stylable && child.stylable == true && child.geometry.attributes && child.geometry.attributes.position) { meshes.push(child); } }); return meshes; } function applyMeshRenderOptions(mesh, renderOptions) { if (mesh instanceof THREE.Mesh) { mesh.material.castShadow = renderOptions.castShadow; mesh.material.receiveShadow = renderOptions.receiveShadow; mesh.material.side = renderOptions.doubleSided ? THREE.DoubleSide : THREE.FrontSide; if (renderOptions.opacity) { const opacity = renderOptions.opacity >= 1.0 ? 1.0 : renderOptions.opacity <= 0.0 ? 0.0 : renderOptions.opacity; mesh.material.transparent = opacity === 1.0 ? false : true; mesh.material.opacity = opacity; } } } export function styleBasic(scene, styleParams, renderOptions) { const stylableMeshes = getStylableMeshes(scene); for (let i = 0; i < stylableMeshes.length; i++) { const child = stylableMeshes[i]; child.material = new THREE.MeshStandardMaterial({ color: styleParams.color ? styleParams.color : "#EFEFEF" }); applyMeshRenderOptions(child, renderOptions); } return scene; } export function styleRandom(scene, styleParams, renderOptions) { const stylableMeshes = getStylableMeshes(scene); for (let i = 0; i < stylableMeshes.length; i++) { const child = stylableMeshes[i]; const geom = child.geometry; const count = geom.attributes.position.count; geom.setAttribute('color', new THREE.BufferAttribute(new Float32Array(count * 3), 3)); const colors = geom.attributes.color; let batchColors = {}; for (let i = 0; i < count; i++) { const batchID = geom.attributes._batchid.getX(i); if (!batchColors[batchID]) { batchColors[batchID] = { r: Math.random(), g: Math.random(), b: Math.random() }; } colors.setXYZ(i, batchColors[batchID].r, batchColors[batchID].g, batchColors[batchID].b); } child.material = new THREE.MeshStandardMaterial(); child.material.vertexColors = true; child.material.depthWrite = true; applyMeshRenderOptions(child, renderOptions); } return scene; } export function styleShader(scene, styleParams, renderOptions) { const stylableMeshes = getStylableMeshes(scene); const setAttributes = styleParams.setAttributes ? styleParams.setAttributes : undefined; for (let i = 0; i < stylableMeshes.length; i++) { const child = stylableMeshes[i]; if(setAttributes !== undefined) { setAttributes(child.geometry); } child.material = styleParams.material; applyMeshRenderOptions(child, renderOptions); } return scene; } export function styleProperty(scene, styleParams, renderOptions) { const stylableMeshes = getStylableMeshes(scene); const property = styleParams.property; const colorParams = styleParams.colors; for (let i = 0; i < stylableMeshes.length; i++) { const child = stylableMeshes[i]; const geom = child.geometry; const count = geom.attributes.position.count; geom.setAttribute('color', new THREE.BufferAttribute(new Float32Array(count * 3), 3)); const colors = geom.attributes.color; let batchColors = {}; for (let i = 0; i < count; i++) { const batchID = geom.attributes._batchid.getX(i); if (!batchColors[batchID]) { const propertyValue = child.userData[property][batchID]; if (propertyValue) { for (let j = 0; j < colorParams.length; j++) { const colorParam = colorParams[j]; const color = byteColorToRGBFloat(colorParam.color); switch (colorParam.operator) { case "smaller-than": if (propertyValue < colorParam.value) { batchColors[batchID] = color; } break; case "greater-than": if (propertyValue > colorParam.value) { batchColors[batchID] = color; } break; case "range": if (propertyValue >= colorParam.value[0] && propertyValue <= colorParam.value[1]) { batchColors[batchID] = color; } break; case "equals": if (propertyValue.toString().toLowerCase() == colorParam.value.toString().toLowerCase()) { batchColors[batchID] = color; } break; } } } if (!batchColors[batchID] && styleParams.fallback) { batchColors[batchID] = byteColorToRGBFloat(styleParams.fallback); } } colors.setXYZ(i, batchColors[batchID].r, batchColors[batchID].g, batchColors[batchID].b); } child.material = new THREE.MeshStandardMaterial(); child.material.vertexColors = true; child.material.depthWrite = true; applyMeshRenderOptions(child, renderOptions); } return scene; } export function styleShade(scene, styleParams, renderOptions) { let maincolor = null; if (styleParams.color != null) { maincolor = createColor(styleParams.color); } const stylableMeshes = getStylableMeshes(scene); for (let i = 0; i < stylableMeshes.length; i++) { const child = stylableMeshes[i]; if (styleParams.color != null) { child.material.color = maincolor; } if (styleParams.opacity != null) { child.material.opacity = styleParams.opacity; child.material.transparent = styleParams.opacity < 1.0 ? true : false; } // some gltf has wrong bounding data, recompute here child.geometry.computeBoundingBox(); child.geometry.computeBoundingSphere(); //For changing individual colors later, we have to introduce vertexcolors //const color = new THREE.Color(); const positions = child.geometry.attributes.position; const count = positions.count; child.geometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(count * 3), 3)); const colors = child.geometry.attributes.color; const color = new THREE.Color(); const grey = new THREE.Color("rgb(20,20,20)"); const ymin = child.geometry.boundingBox.min.y; const ymax = child.geometry.boundingBox.max.y; const ydiff = ymax - ymin; //Currently attributes are kind of hardcoded in the tiles and have to be unpacked //let magnitude = scaleSequential(interpolateYlGnBu).domain([1600, 2020]) //const colormap = child.parent.userData.attr.map(d=>magnitude(d[0])); for (let i = 0; i < count; i++) { //Assign every vertex it's own color //let batchid = child.geometry.attributes._batchid.getX(i); //let colorval = colormap[batchid]; let colorval = child.material.color; color.set(colorval); //Create a little gradient from black to white //adding 0.3 not to start at black, dividing by 10 limits effect to bottom let greyval = Math.min(0.8 + (positions.getY(i) + Math.abs(ymin)) / 1, 1); color.lerp(grey, 1 - greyval); //lerp to grey const linear = color.convertSRGBToLinear(); colors.setXYZ(i, linear.r, linear.g, linear.b); } child.material = new THREE.MeshStandardMaterial(); child.material.vertexColors = true; child.material.depthWrite = !child.material.transparent; // necessary for Velsen dataset? applyMeshRenderOptions(child, renderOptions); } if (styleParams.color != null || styleParams.opacity != null) { let color = createColor(styleParams.color); for (let i = 0; i < stylableMeshes.length; i++) { const child = stylableMeshes[i]; if (styleParams.color != null) child.material.color = color; if (styleParams.opacity != null) { child.material.opacity = styleParams.opacity; child.material.transparent = styleParams.opacity < 1.0 ? true : false; } } } if (styleParams.debugColor) { for (let i = 0; i < stylableMeshes.length; i++) { const child = stylableMeshes[i]; child.material.color = styleParams.debugColor; } } return scene; } function byteColorToRGBFloat(color) { const threeColor = new THREE.Color(`rgb(${color[0]}, ${color[1]}, ${color[2]})`); return threeColor.convertSRGBToLinear(); } function createColor(color) { return new THREE.Color(color).convertSRGBToLinear(); }