diff --git a/src/components/intersection.js b/src/components/intersection.js new file mode 100644 index 000000000..3decd5764 --- /dev/null +++ b/src/components/intersection.js @@ -0,0 +1,389 @@ +/* global AFRAME */ +AFRAME.registerComponent('intersection', { + schema: { + dimensions: { type: 'string', default: '20 20' }, + // cardinal direction order for sidewalk, stopsign, crosswalk: west, east, north, south + sidewalk: { type: 'string', default: '0 0 0 0' }, + northeastcurb: { type: 'string', default: '4 4' }, + southwestcurb: { type: 'string', default: '4 4' }, + southeastcurb: { type: 'string', default: '4 4' }, + northwestcurb: { type: 'string', default: '4 4' }, + stopsign: { type: 'string', default: '0 0 0 0' }, + trafficsignal: { type: 'string', default: '1 1 1 1' }, + crosswalk: { type: 'string', default: '2 2 2 2' } + }, + update: function () { + var data = this.data; + var el = this.el; + const directionOrder = ['west', 'east', 'north', 'south']; + + // remove all child nodes if exists + while (el.firstChild) { + el.removeChild(el.lastChild); + } + const dimensionsArray = data.dimensions.split(' ').map((i) => Number(i)); + const sidewalkArray = data.sidewalk.split(' ').map((i) => Number(i)); + const northeastcurbArray = data.northeastcurb + .split(' ') + .map((i) => Number(i)); + const southwestcurbArray = data.southwestcurb + .split(' ') + .map((i) => Number(i)); + const southeastcurbArray = data.southeastcurb + .split(' ') + .map((i) => Number(i)); + const northwestcurbArray = data.northwestcurb + .split(' ') + .map((i) => Number(i)); + const stopsignArray = data.stopsign.split(' ').map((i) => Number(i)); + const trafficsignalArray = data.trafficsignal + .split(' ') + .map((i) => Number(i)); + const crosswalklArray = data.crosswalk.split(' ').map((i) => Number(i)); + + const intersectWidth = dimensionsArray[0]; + const intersectDepth = dimensionsArray[1]; + + this.el.setAttribute( + 'geometry', + `primitive:box; width: ${intersectWidth}; height: ${intersectDepth}; depth:0.2` + ); + this.el.setAttribute( + 'material', + 'src: #asphalt-texture; repeat:5 5; roughness:1' + ); + + function createSidewalkElem({ + length, + width, + positionVec, + scaleVec = { x: 1, y: 1, z: 1 }, + rotationVec, + displayName + }) { + const sd = document.createElement('a-entity'); + // every 2 meters repeat sidewalk texture + const repeatCountInter = [width / 2, parseInt(length / 2)]; + + sd.setAttribute( + 'geometry', + `primitive:box; depth: ${length}; width: ${width}; height: 0.4` + ); + sd.setAttribute('position', positionVec); + sd.setAttribute('scale', scaleVec); + sd.setAttribute('rotation', rotationVec); + sd.setAttribute('mixin', 'sidewalk'); + sd.classList.add('autocreated'); + sd.setAttribute( + 'material', + `repeat: ${repeatCountInter[0]} ${repeatCountInter[1]}` + ); + sd.setAttribute('data-layer-name', 'Sidewalk • ' + displayName); + el.appendChild(sd); + } + + // describe sidewalk parameters + const sidewalkParams = { + west: { + positionVec: { x: intersectWidth / 2 - sidewalkArray[0] / 2, z: 0.1 }, + rotationVec: { x: 90, y: 0, z: 0 }, + length: intersectDepth, + width: sidewalkArray[0], + displayName: 'West' + }, + east: { + positionVec: { x: -intersectWidth / 2 + sidewalkArray[1] / 2, z: 0.1 }, + rotationVec: { x: 90, y: 0, z: 0 }, + length: intersectDepth, + width: sidewalkArray[1], + displayName: 'East' + }, + north: { + positionVec: { + // add x offset to avoid sidewalk's element overlap + x: sidewalkArray[1] / 2 - sidewalkArray[0] / 2, + y: -intersectDepth / 2 + sidewalkArray[2] / 2, + z: 0.1 + }, + rotationVec: { x: 0, y: 90, z: -90 }, + // minus the width of the crossing sidewalk + length: intersectWidth - sidewalkArray[1] - sidewalkArray[0], + width: sidewalkArray[2], + displayName: 'North' + }, + south: { + positionVec: { + // add x offset to avoid sidewalk's element overlap + x: sidewalkArray[1] / 2 - sidewalkArray[0] / 2, + y: intersectDepth / 2 - sidewalkArray[3] / 2, + z: 0.1 + }, + rotationVec: { x: 0, y: 90, z: -90 }, + // minus the width of the crossing sidewalk + length: intersectWidth - sidewalkArray[1] - sidewalkArray[0], + width: sidewalkArray[3], + displayName: 'South' + } + }; + + // create sidewalks if they are given in sidewalkArray + const selectedSidewalks = Object.keys(sidewalkParams).filter( + (el, ind) => sidewalkArray[ind] + ); + + selectedSidewalks.forEach((sidewalkName, ind) => { + const params = sidewalkParams[sidewalkName]; + createSidewalkElem(params); + }); + + // describe curb parameters + const curbParams = { + northeast: { + positionVec: { + x: intersectWidth / 2 - northeastcurbArray[0] / 2, + y: intersectDepth / 2 - northeastcurbArray[1] / 2, + z: 0.1 + }, + rotationVec: { x: 0, y: 90, z: -90 }, + length: northeastcurbArray[0], + width: northeastcurbArray[1], + displayName: 'Northeast' + }, + southwest: { + positionVec: { + x: -intersectWidth / 2 + southwestcurbArray[0] / 2, + y: -intersectDepth / 2 + southwestcurbArray[1] / 2, + z: 0.1 + }, + rotationVec: { x: 0, y: 90, z: -90 }, + length: southwestcurbArray[0], + width: southwestcurbArray[1], + displayName: 'Southwest' + }, + southeast: { + positionVec: { + x: intersectWidth / 2 - southeastcurbArray[0] / 2, + y: -intersectDepth / 2 + southeastcurbArray[1] / 2, + z: 0.1 + }, + rotationVec: { x: 0, y: 90, z: -90 }, + length: southeastcurbArray[0], + width: southeastcurbArray[1], + displayName: 'Southeast' + }, + northwest: { + positionVec: { + x: -intersectWidth / 2 + northwestcurbArray[0] / 2, + y: intersectDepth / 2 - northwestcurbArray[1] / 2, + z: 0.1 + }, + rotationVec: { x: 0, y: 90, z: -90 }, + length: northwestcurbArray[0], + width: northwestcurbArray[1], + displayName: 'Northwest' + } + }; + + // create curbs if they are given + for (const [curbName, params] of Object.entries(curbParams)) { + if (data[`${curbName}curb`] !== '0 0') { + createSidewalkElem(params); + } + } + + // describe stop signals parameters + const stopsignals = { + west: { + position: { + x: intersectWidth / 2, + y: intersectDepth / 3, + z: 0.1 + }, + rotation: { x: 0, y: 90, z: 90 } + }, + east: { + position: { + x: -intersectWidth / 2, + y: -intersectDepth / 3, + z: 0.1 + }, + rotation: { x: 0, y: -90, z: -90 } + }, + north: { + position: { + x: -intersectWidth / 3, + y: intersectDepth / 2, + z: 0.1 + }, + rotation: { x: -90, y: 90, z: 90 } + }, + south: { + position: { + x: intersectWidth / 3, + y: -intersectDepth / 2, + z: 0.1 + }, + rotation: { x: 90, y: -90, z: -90 } + } + }; + + function createStopSignal(direction) { + const stopSignEl = document.createElement('a-entity'); + const params = stopsignals[direction]; + stopSignEl.setAttribute('position', params['position']); + stopSignEl.setAttribute('rotation', params['rotation']); + stopSignEl.setAttribute('mixin', 'stop_sign'); + stopSignEl.classList.add('autocreated'); + stopSignEl.setAttribute('data-layer-name', 'Traffic Control • Stop Sign'); + return stopSignEl; + } + + // create stop signals + directionOrder.forEach((direction, index) => { + if (stopsignArray[index]) { + const stopSignEl = createStopSignal(direction); + el.appendChild(stopSignEl); + } + }); + + // describe traffic signals parameters + const trafficSignals = { + west: { + left: { + position: { + x: intersectWidth / 2, + y: intersectDepth / 3, + z: 0.3 + }, + rotation: { x: 210, y: 90, z: 90 } + }, + right: { + position: { + x: intersectWidth / 2, + y: -intersectDepth / 3, + z: 0.3 + }, + rotation: { x: 180, y: 90, z: 90 } + } + }, + east: { + left: { + position: { + x: -intersectWidth / 2, + y: -intersectDepth / 3, + z: 0.3 + }, + rotation: { x: 210, y: 90, z: 90 } + }, + right: { + position: { + x: -intersectWidth / 2, + y: intersectDepth / 3, + z: 0.3 + }, + rotation: { x: 0, y: 90, z: 90 } + } + }, + north: { + left: { + position: { + x: -intersectWidth / 3, + y: intersectDepth / 2, + z: 0.1 + }, + rotation: { x: 120, y: 90, z: 90 } + }, + right: { + position: { + x: intersectWidth / 3, + y: intersectDepth / 2, + z: 0.1 + }, + rotation: { x: 90, y: 90, z: 90 } + } + }, + south: { + left: { + position: { + x: intersectWidth / 3, + y: -intersectDepth / 2, + z: 0.1 + }, + rotation: { x: -60, y: 90, z: 90 } + }, + right: { + position: { + x: -intersectWidth / 3, + y: -intersectDepth / 2, + z: 0.1 + }, + rotation: { x: -90, y: 90, z: 90 } + } + } + }; + + function createTrafficSignals(direction) { + const params = trafficSignals[direction]; + ['left', 'right'].forEach((side) => { + const trafficSignalEl = document.createElement('a-entity'); + trafficSignalEl.setAttribute('position', params[side].position); + trafficSignalEl.setAttribute('rotation', params[side].rotation); + trafficSignalEl.setAttribute('mixin', `signal_${side}`); + trafficSignalEl.classList.add('autocreated'); + trafficSignalEl.setAttribute( + 'data-layer-name', + 'Traffic Signal • ' + direction + ' ' + side + ); + el.appendChild(trafficSignalEl); + }); + } + + // create traffic signals + directionOrder.forEach((direction, index) => { + if (trafficsignalArray[index]) { + createTrafficSignals(direction); + } + }); + + if (crosswalklArray[0]) { + const cw1 = document.createElement('a-entity'); + cw1.setAttribute('position', { x: intersectWidth / 2 - 2, z: 0.11 }); + cw1.setAttribute('rotation', { x: 0, y: 0, z: 180 }); + cw1.setAttribute('scale', { y: intersectDepth / 12 }); + cw1.setAttribute('mixin', 'markings crosswalk-zebra'); + cw1.setAttribute('data-layer-name', 'Crosswalk • East'); + cw1.classList.add('autocreated'); + el.appendChild(cw1); + } + if (crosswalklArray[1]) { + const cw2 = document.createElement('a-entity'); + cw2.setAttribute('position', { x: -intersectWidth / 2 + 2, z: 0.11 }); + cw2.setAttribute('rotation', { x: 0, y: 0, z: 180 }); + cw2.setAttribute('scale', { y: intersectDepth / 12 }); + cw2.setAttribute('mixin', 'markings crosswalk-zebra'); + cw2.setAttribute('data-layer-name', 'Crosswalk • West'); + cw2.classList.add('autocreated'); + el.appendChild(cw2); + } + if (crosswalklArray[2]) { + const cw3 = document.createElement('a-entity'); + cw3.setAttribute('position', { y: -intersectDepth / 2 + 2, z: 0.11 }); + cw3.setAttribute('rotation', { x: 0, y: 0, z: 90 }); + cw3.setAttribute('scale', { y: intersectWidth / 12 }); + cw3.setAttribute('mixin', 'markings crosswalk-zebra'); + cw3.setAttribute('data-layer-name', 'Crosswalk • Zebra (Continental)'); + cw3.classList.add('autocreated'); + el.appendChild(cw3); + } + if (crosswalklArray[3]) { + const cw4 = document.createElement('a-entity'); + cw4.setAttribute('position', { y: intersectDepth / 2 - 2, z: 0.11 }); + cw4.setAttribute('data-layer-name', 'Crosswalk • Zebra (Continental)'); + cw4.setAttribute('rotation', { x: 0, y: 0, z: 90 }); + cw4.setAttribute('scale', { y: intersectWidth / 12 }); + cw4.setAttribute('mixin', 'markings crosswalk-zebra'); + cw4.classList.add('autocreated'); + el.appendChild(cw4); + } + } +}); diff --git a/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx b/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx index cda26c421..315a784ce 100644 --- a/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx +++ b/src/editor/components/components/AddLayerPanel/AddLayerPanel.component.jsx @@ -9,18 +9,10 @@ import { Dropdown } from '../Dropdown'; import CardPlaceholder from '../../../../../ui_assets/card-placeholder.svg'; import LockedCard from '../../../../../ui_assets/locked-card.svg'; +import { layersData } from './layersData.js'; import { LayersOptions } from './LayersOptions.js'; import mixinCatalog from '../../../../catalog.json'; import posthog from 'posthog-js'; - -import { - createSvgExtrudedEntity, - createMapbox, - createStreetmixStreet, - create3DTiles, - createCustomModel, - createPrimitiveGeometry -} from './createLayerFunctions'; import Events from '../../../lib/Events'; const AddLayerPanel = ({ onClose, isAddLayerPanelOpen }) => { @@ -91,70 +83,6 @@ const AddLayerPanel = ({ onClose, isAddLayerPanelOpen }) => { return groupedArray; }; - // data for layers cards - const layersData = [ - { - name: 'Mapbox 2D Aerial', - img: 'ui_assets/cards/mapbox2d.jpg', - icon: 'ui_assets/cards/icons/mapbox24.png', - requiresPro: true, - description: - 'Create entity with mapbox component, that accepts a long / lat and renders a plane with dimensions that (should be) at a correct scale.', - id: 1, - handlerFunction: createMapbox - }, - { - name: 'Street from Streetmix URL', - img: 'ui_assets/cards/streetmix.jpg', - icon: 'ui_assets/cards/icons/streetmix24.png', - requiresPro: true, - description: - 'Create an additional Streetmix street in your 3DStreet scene without replacing any existing streets.', - id: 2, - handlerFunction: createStreetmixStreet - }, - { - name: 'Entity from extruded SVG', - img: '', - icon: '', - requiresPro: true, - description: - 'Create entity with svg-extruder component, that accepts a svgString and creates a new entity with geometry extruded from the svg and applies the default mixin material grass.', - id: 3, - handlerFunction: createSvgExtrudedEntity - }, - { - name: 'Google Maps 3D Tiles', - img: 'ui_assets/cards/google3d.jpg', - icon: 'ui_assets/cards/icons/google24.png', - requiresPro: true, - description: - 'Adds an entity to load and display 3d tiles from Google Maps Tiles API 3D Tiles endpoint. This will break your scene and you cannot save it yet, so beware before testing.', - id: 4, - handlerFunction: create3DTiles - }, - { - name: 'glTF model from URL', - img: '', - requiresPro: true, - icon: '', - description: - 'Create entity with model from path for a glTF (or Glb) file hosted on any publicly accessible HTTP server.', - id: 5, - handlerFunction: createCustomModel - }, - { - name: 'Create primitive geometry', - img: '', - requiresPro: true, - icon: '', - description: - 'Create entity with A-Frame primitive geometry. Geometry type could be changed in properties panel.', - id: 6, - handlerFunction: createPrimitiveGeometry - } - ]; - // get array with objects data (cardsData) from mixinGroups of selectedOption const getSelectedMixinCards = (selectedOption) => { if (!selectedOption) return []; diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 56884464a..043abb800 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -137,7 +137,7 @@ function createCustomModel() { function createPrimitiveGeometry() { const newEl = document.createElement('a-entity'); newEl.setAttribute('geometry', 'primitive: circle; radius: 50;'); - newEl.setAttribute('rotation', '-90 0 0'); + newEl.setAttribute('rotation', '-90 -90 0'); newEl.setAttribute( 'data-layer-name', 'Plane Geometry • Traffic Circle Asphalt' @@ -149,11 +149,46 @@ function createPrimitiveGeometry() { Events.emit('entitycreated', newEl); } +function createIntersection() { + const newEl = document.createElement('a-entity'); + newEl.setAttribute('intersection', ''); + newEl.setAttribute('data-layer-name', 'Street • Intersection 90º'); + newEl.setAttribute('rotation', '-90 -90 0'); + const parentEl = document.querySelector('#street-container'); + parentEl.appendChild(newEl); + // update sceneGraph + Events.emit('entitycreated', newEl); +} + +function createSplatObject() { + // accepts a path for a .splat file hosted on any publicly accessible HTTP server. + // Then create entity with model from that path by using gaussian_splatting component + const modelUrl = prompt( + 'Please enter a URL to custom Splat model', + 'https://cdn.glitch.me/f80a77a3-62a6-4024-9bef-a6b523d1abc0/gs_Bioswale3_treat.splat' + ); + + if (modelUrl && modelUrl !== '') { + const newEl = document.createElement('a-entity'); + newEl.classList.add('splat-model'); + newEl.setAttribute('data-no-pause', ''); + newEl.setAttribute('gaussian_splatting', `src: ${modelUrl}`); + newEl.setAttribute('data-layer-name', 'Splat Model • My Custom Object'); + newEl.play(); + const parentEl = document.querySelector('#street-container'); + parentEl.appendChild(newEl); + // update sceneGraph + Events.emit('entitycreated', newEl); + } +} + export { createSvgExtrudedEntity, createMapbox, createStreetmixStreet, create3DTiles, createCustomModel, - createPrimitiveGeometry + createPrimitiveGeometry, + createIntersection, + createSplatObject }; diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js new file mode 100644 index 000000000..6605c8953 --- /dev/null +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -0,0 +1,85 @@ +import { + createSvgExtrudedEntity, + createMapbox, + createStreetmixStreet, + create3DTiles, + createCustomModel, + createPrimitiveGeometry, + createIntersection +} from './createLayerFunctions'; + +// data for PRO layers cards +const layersData = [ + { + name: 'Mapbox 2D Aerial', + img: 'ui_assets/cards/mapbox2d.jpg', + icon: 'ui_assets/cards/icons/mapbox24.png', + requiresPro: true, + description: + 'Create entity with mapbox component, that accepts a long / lat and renders a plane with dimensions that (should be) at a correct scale.', + id: 1, + handlerFunction: createMapbox + }, + { + name: 'Street from Streetmix URL', + img: 'ui_assets/cards/streetmix.jpg', + icon: 'ui_assets/cards/icons/streetmix24.png', + requiresPro: true, + description: + 'Create an additional Streetmix street in your 3DStreet scene without replacing any existing streets.', + id: 2, + handlerFunction: createStreetmixStreet + }, + { + name: 'Entity from extruded SVG', + img: '', + icon: '', + requiresPro: true, + description: + 'Create entity with svg-extruder component, that accepts a svgString and creates a new entity with geometry extruded from the svg and applies the default mixin material grass.', + id: 3, + handlerFunction: createSvgExtrudedEntity + }, + { + name: 'Google Maps 3D Tiles', + img: 'ui_assets/cards/google3d.jpg', + icon: 'ui_assets/cards/icons/google24.png', + requiresPro: true, + description: + 'Adds an entity to load and display 3d tiles from Google Maps Tiles API 3D Tiles endpoint. This will break your scene and you cannot save it yet, so beware before testing.', + id: 4, + handlerFunction: create3DTiles + }, + { + name: 'glTF model from URL', + img: '', + requiresPro: true, + icon: '', + description: + 'Create entity with model from path for a glTF (or Glb) file hosted on any publicly accessible HTTP server.', + id: 5, + handlerFunction: createCustomModel + }, + { + name: 'Create primitive geometry', + img: '', + requiresPro: true, + icon: '', + description: + 'Create entity with A-Frame primitive geometry. Geometry type could be changed in properties panel.', + id: 6, + handlerFunction: createPrimitiveGeometry + }, + { + name: 'Create intersection', + img: '', + requiresPro: true, + icon: '', + description: + 'Create intersection entity. Parameters of intersection component could be changed in properties panel.', + id: 7, + handlerFunction: createIntersection + } +]; + +export { layersData }; diff --git a/src/index.js b/src/index.js index afdd3d581..097870577 100644 --- a/src/index.js +++ b/src/index.js @@ -13,6 +13,7 @@ require('./components/ocean'); require('./components/svg-extruder.js'); require('./lib/aframe-cursor-teleport-component.min.js'); require('./lib/animation-mixer.js'); +require('./lib/aframe-gaussian-splatting-component.min.js'); require('./assets.js'); require('./components/notify.js'); require('./components/create-from-json'); @@ -21,6 +22,7 @@ require('aframe-atlas-uvs-component'); require('./components/streetplan-loader'); require('./components/street-geo.js'); require('./components/street-environment.js'); +require('./components/intersection.js'); AFRAME.registerComponent('street', { schema: { @@ -246,359 +248,6 @@ AFRAME.registerComponent('streetmix-loader', { } }); -AFRAME.registerComponent('intersection', { - schema: { - dimensions: { type: 'string', default: '20 20' }, - sidewalk: { type: 'string', default: '0 0 0 0' }, - northeastcurb: { type: 'string', default: '0 0' }, - southwestcurb: { type: 'string', default: '0 0' }, - southeastcurb: { type: 'string', default: '0 0' }, - northwestcurb: { type: 'string', default: '0 0' }, - stopsign: { type: 'string', default: '0 0 0 0' }, - trafficsignal: { type: 'string', default: '0 0 0 0' }, - crosswalk: { type: 'string', default: '0 0 0 0' } - }, - init: function () { - var data = this.data; - var el = this.el; - - // remove all child nodes if exists - while (el.firstChild) { - el.removeChild(el.lastChild); - } - const dimensionsArray = data.dimensions.split(' ').map((i) => Number(i)); - const positionArray = [ - this.el.getAttribute('position').x, - this.el.getAttribute('position').y, - this.el.getAttribute('position').z - ]; - const sidewalkArray = data.sidewalk.split(' ').map((i) => Number(i)); - const northeastcurbArray = data.northeastcurb - .split(' ') - .map((i) => Number(i)); - const southwestcurbArray = data.southwestcurb - .split(' ') - .map((i) => Number(i)); - const southeastcurbArray = data.southeastcurb - .split(' ') - .map((i) => Number(i)); - const northwestcurbArray = data.northwestcurb - .split(' ') - .map((i) => Number(i)); - const stopsignArray = data.stopsign.split(' ').map((i) => Number(i)); - const trafficsignalArray = data.trafficsignal - .split(' ') - .map((i) => Number(i)); - const crosswalklArray = data.crosswalk.split(' ').map((i) => Number(i)); - - const intersectWidth = dimensionsArray[0]; - const intersectDepth = dimensionsArray[1]; - - this.el.setAttribute( - 'geometry', - `primitive:box; width: ${intersectWidth}; height: ${intersectDepth}; depth:0.2` - ); - this.el.setAttribute('position', { - x: positionArray[0], - y: -0.1, - z: positionArray[2] - }); - this.el.setAttribute('rotation', '-90 0 0'); - this.el.setAttribute( - 'material', - 'src: #asphalt-texture; repeat:5 5; roughness:1' - ); - - function createSidewalkElem({ - length, - width, - positionVec, - scaleVec = { x: 1, y: 1, z: 1 }, - rotationVec - }) { - const sd = document.createElement('a-entity'); - const repeatCountInter = []; - repeatCountInter[0] = width / 2; - // every 2 meters repeat sidewalk texture - repeatCountInter[1] = parseInt(length / 2); - - sd.setAttribute('geometry', 'primitive', 'box'); - sd.setAttribute('geometry', 'height: 0.4'); - sd.setAttribute('position', positionVec); - sd.setAttribute('scale', scaleVec); - sd.setAttribute('geometry', 'depth', length); - sd.setAttribute('geometry', 'width', width); - sd.setAttribute('rotation', rotationVec); - sd.setAttribute('mixin', 'sidewalk'); - sd.setAttribute( - 'material', - `repeat: ${repeatCountInter[0]} ${repeatCountInter[1]}` - ); - el.appendChild(sd); - } - - // describe sidewalk parameters - const sidewalkParams = { - west: { - positionVec: { x: intersectWidth / 2 - sidewalkArray[0] / 2, z: 0.1 }, - rotationVec: { x: 90, y: 0, z: 0 }, - length: intersectDepth, - width: sidewalkArray[0] - }, - east: { - positionVec: { x: -intersectWidth / 2 + sidewalkArray[1] / 2, z: 0.1 }, - rotationVec: { x: 90, y: 0, z: 0 }, - length: intersectDepth, - width: sidewalkArray[1] - }, - north: { - positionVec: { - y: -intersectDepth / 2 + sidewalkArray[2] / 2, - // add x offset to avoid sidewalk's element overlap - x: sidewalkArray[1] / 2 - sidewalkArray[0] / 2, - z: 0.1 - }, - rotationVec: { x: 0, y: 90, z: -90 }, - // minus the width of the crossing sidewalk - length: intersectWidth - sidewalkArray[1] - sidewalkArray[0], - width: sidewalkArray[2] - }, - south: { - positionVec: { - y: intersectDepth / 2 - sidewalkArray[3] / 2, - // add x offset to avoid sidewalk's element overlap - x: sidewalkArray[1] / 2 - sidewalkArray[0] / 2, - z: 0.1 - }, - rotationVec: { x: 0, y: 90, z: -90 }, - // minus the width of the crossing sidewalk - length: intersectWidth - sidewalkArray[1] - sidewalkArray[0], - width: sidewalkArray[3] - } - }; - - // create sidewalks if they are given in sidewalkArray - const selectedSidewalks = Object.keys(sidewalkParams).filter( - (el, ind) => sidewalkArray[ind] - ); - - selectedSidewalks.forEach((sidewalkName, ind) => { - const params = sidewalkParams[sidewalkName]; - createSidewalkElem(params); - }); - - // describe curb parameters - const curbParams = { - northeast: { - positionVec: { - x: intersectWidth / 2 - northeastcurbArray[0] / 2, - y: intersectDepth / 2 - northeastcurbArray[1] / 2, - z: 0.1 - }, - rotationVec: { x: 0, y: 90, z: -90 }, - length: northeastcurbArray[0], - width: northeastcurbArray[1] - }, - southwest: { - positionVec: { - x: -intersectWidth / 2 + southwestcurbArray[0] / 2, - y: -intersectDepth / 2 + southwestcurbArray[1] / 2, - z: 0.1 - }, - rotationVec: { x: 0, y: 90, z: -90 }, - length: southwestcurbArray[0], - width: southwestcurbArray[1] - }, - southeast: { - positionVec: { - x: intersectWidth / 2 - southeastcurbArray[0] / 2, - y: -intersectDepth / 2 + southeastcurbArray[1] / 2, - z: 0.1 - }, - rotationVec: { x: 0, y: 90, z: -90 }, - length: southeastcurbArray[0], - width: southeastcurbArray[1] - }, - northwest: { - positionVec: { - x: -intersectWidth / 2 + northwestcurbArray[0] / 2, - y: intersectDepth / 2 - northwestcurbArray[1] / 2, - z: 0.1 - }, - rotationVec: { x: 0, y: 90, z: -90 }, - length: northwestcurbArray[0], - width: northwestcurbArray[1] - } - }; - - // create curbs if they are given - for (const [curbName, params] of Object.entries(curbParams)) { - if (data[`${curbName}curb`] !== '0 0') { - createSidewalkElem(params); - } - } - - if (stopsignArray[0]) { - const ss1 = document.createElement('a-entity'); - ss1.setAttribute('position', { - x: intersectWidth / 2, - y: intersectDepth / 3, - z: 0.1 - }); - ss1.setAttribute('rotation', { x: 0, y: 90, z: 90 }); - ss1.setAttribute('mixin', 'stop_sign'); - el.appendChild(ss1); - } - if (stopsignArray[1]) { - const ss2 = document.createElement('a-entity'); - ss2.setAttribute('position', { - x: -intersectWidth / 2, - y: -intersectDepth / 3, - z: 0.1 - }); - ss2.setAttribute('rotation', { x: 0, y: -90, z: -90 }); - ss2.setAttribute('mixin', 'stop_sign'); - el.appendChild(ss2); - } - if (stopsignArray[2]) { - const ss3 = document.createElement('a-entity'); - ss3.setAttribute('position', { - x: -intersectWidth / 3, - y: intersectDepth / 2, - z: 0.1 - }); - ss3.setAttribute('rotation', { x: -90, y: 90, z: 90 }); - ss3.setAttribute('mixin', 'stop_sign'); - el.appendChild(ss3); - } - if (stopsignArray[3]) { - const ss4 = document.createElement('a-entity'); - ss4.setAttribute('position', { - x: intersectWidth / 3, - y: -intersectDepth / 2, - z: 0.1 - }); - ss4.setAttribute('rotation', { x: 90, y: -90, z: -90 }); - ss4.setAttribute('mixin', 'stop_sign'); - el.appendChild(ss4); - } - - if (trafficsignalArray[0]) { - const ts1 = document.createElement('a-entity'); - ts1.setAttribute('position', { - x: intersectWidth / 2, - y: intersectDepth / 3, - z: 0.3 - }); - ts1.setAttribute('rotation', { x: 210, y: 90, z: 90 }); - ts1.setAttribute('mixin', 'signal_left'); - el.appendChild(ts1); - const ts2 = document.createElement('a-entity'); - ts2.setAttribute('position', { - x: intersectWidth / 2, - y: -intersectDepth / 3, - z: 0.3 - }); - ts2.setAttribute('rotation', { x: 180, y: 90, z: 90 }); - ts2.setAttribute('mixin', 'signal_right'); - el.appendChild(ts2); - } - if (trafficsignalArray[1]) { - const ts3 = document.createElement('a-entity'); - ts3.setAttribute('position', { - x: -intersectWidth / 2, - y: -intersectDepth / 3, - z: 0.3 - }); - ts3.setAttribute('rotation', { x: 30, y: 90, z: 90 }); - ts3.setAttribute('mixin', 'signal_left'); - el.appendChild(ts3); - const ts4 = document.createElement('a-entity'); - ts4.setAttribute('position', { - x: -intersectWidth / 2, - y: intersectDepth / 3, - z: 0.3 - }); - ts4.setAttribute('rotation', { x: 0, y: 90, z: 90 }); - ts4.setAttribute('mixin', 'signal_right'); - el.appendChild(ts4); - } - if (trafficsignalArray[2]) { - const ts5 = document.createElement('a-entity'); - ts5.setAttribute('position', { - x: -intersectWidth / 3, - y: intersectDepth / 2, - z: 0.1 - }); - ts5.setAttribute('rotation', { x: 120, y: 90, z: 90 }); - ts5.setAttribute('mixin', 'signal_left'); - el.appendChild(ts5); - const ts6 = document.createElement('a-entity'); - ts6.setAttribute('position', { - x: intersectWidth / 3, - y: intersectDepth / 2, - z: 0.1 - }); - ts6.setAttribute('rotation', { x: 90, y: 90, z: 90 }); - ts6.setAttribute('mixin', 'signal_right'); - el.appendChild(ts6); - } - if (trafficsignalArray[3]) { - const ts7 = document.createElement('a-entity'); - ts7.setAttribute('position', { - x: intersectWidth / 3, - y: -intersectDepth / 2, - z: 0.1 - }); - ts7.setAttribute('rotation', { x: -60, y: 90, z: 90 }); - ts7.setAttribute('mixin', 'signal_left'); - el.appendChild(ts7); - const ts8 = document.createElement('a-entity'); - ts8.setAttribute('position', { - x: -intersectWidth / 3, - y: -intersectDepth / 2, - z: 0.1 - }); - ts8.setAttribute('rotation', { x: -90, y: 90, z: 90 }); - ts8.setAttribute('mixin', 'signal_right'); - el.appendChild(ts8); - } - - if (crosswalklArray[0]) { - const cw1 = document.createElement('a-entity'); - cw1.setAttribute('position', { x: intersectWidth / 2 - 2, z: 0.11 }); - cw1.setAttribute('rotation', { x: 0, y: 0, z: 180 }); - cw1.setAttribute('scale', { y: intersectDepth / 12 }); - cw1.setAttribute('mixin', 'markings crosswalk-zebra'); - el.appendChild(cw1); - } - if (crosswalklArray[1]) { - const cw2 = document.createElement('a-entity'); - cw2.setAttribute('position', { x: -intersectWidth / 2 + 2, z: 0.11 }); - cw2.setAttribute('rotation', { x: 0, y: 0, z: 180 }); - cw2.setAttribute('scale', { y: intersectDepth / 12 }); - cw2.setAttribute('mixin', 'markings crosswalk-zebra'); - el.appendChild(cw2); - } - if (crosswalklArray[2]) { - const cw3 = document.createElement('a-entity'); - cw3.setAttribute('position', { y: -intersectDepth / 2 + 2, z: 0.11 }); - cw3.setAttribute('rotation', { x: 0, y: 0, z: 90 }); - cw3.setAttribute('scale', { y: intersectWidth / 12 }); - cw3.setAttribute('mixin', 'markings crosswalk-zebra'); - el.appendChild(cw3); - } - if (crosswalklArray[3]) { - const cw4 = document.createElement('a-entity'); - cw4.setAttribute('position', { y: intersectDepth / 2 - 2, z: 0.11 }); - cw4.setAttribute('rotation', { x: 0, y: 0, z: 90 }); - cw4.setAttribute('scale', { y: intersectWidth / 12 }); - cw4.setAttribute('mixin', 'markings crosswalk-zebra'); - el.appendChild(cw4); - } - } -}); - // Vehicle wheel Animation AFRAME.registerComponent('wheel', { schema: { diff --git a/src/lib/aframe-gaussian-splatting-component.min.js b/src/lib/aframe-gaussian-splatting-component.min.js new file mode 100644 index 000000000..2cb3a16b9 --- /dev/null +++ b/src/lib/aframe-gaussian-splatting-component.min.js @@ -0,0 +1,104 @@ +AFRAME.registerComponent("gaussian_splatting",{schema:{src:{type:"string",default:"train.splat"},cutoutEntity:{type:"selector"},pixelRatio:{type:"number",default:1},xrPixelRatio:{type:"number",default:.5},depthWrite:{type:"boolean",default:!1},discardFilter:{type:"number",default:0}},init:function(){0{this.initLoadProcess()})},initLoadProcess(){this.loadData(this.el.sceneEl.camera,this.el.object3D,this.el.sceneEl.renderer,this.data.src),this.data.cutoutEntity&&(this.cutout=this.data.cutoutEntity.object3D),this.el.isPlaying||this.el.play()},initGL:async function(numVertexes){console.log("initGL",numVertexes),this.object.frustumCulled=!1;var gl=this.renderer.getContext(),gl=gl.getParameter(gl.MAX_TEXTURE_SIZE),gl=(this.maxVertexes=gl*gl,numVertexes>this.maxVertexes&&(console.log("numVertexes limited to ",this.maxVertexes,numVertexes),numVertexes=this.maxVertexes),this.bufferTextureWidth=gl,this.bufferTextureHeight=Math.floor((numVertexes-1)/gl)+1,this.centerAndScaleData=new Float32Array(this.bufferTextureWidth*this.bufferTextureHeight*4),this.covAndColorData=new Uint32Array(this.bufferTextureWidth*this.bufferTextureHeight*4),this.centerAndScaleTexture=new THREE.DataTexture(this.centerAndScaleData,this.bufferTextureWidth,this.bufferTextureHeight,THREE.RGBA,THREE.FloatType),this.centerAndScaleTexture.needsUpdate=!0,this.covAndColorTexture=new THREE.DataTexture(this.covAndColorData,this.bufferTextureWidth,this.bufferTextureHeight,THREE.RGBAIntegerFormat,THREE.UnsignedIntType),this.covAndColorTexture.internalFormat="RGBA32UI",this.covAndColorTexture.needsUpdate=!0,new Uint32Array(this.bufferTextureWidth*this.bufferTextureHeight)),gl=new THREE.InstancedBufferAttribute(gl,1,!1),baseGeometry=(gl.setUsage(THREE.DynamicDrawUsage),new THREE.BufferGeometry),positionsArray=new Float32Array(18),positionsArray=new THREE.BufferAttribute(positionsArray,3),positionsArray=(baseGeometry.setAttribute("position",positionsArray),positionsArray.setXYZ(2,-2,2,0),positionsArray.setXYZ(1,2,2,0),positionsArray.setXYZ(0,-2,-2,0),positionsArray.setXYZ(5,-2,-2,0),positionsArray.setXYZ(4,2,2,0),positionsArray.setXYZ(3,2,-2,0),positionsArray.needsUpdate=!0,(new THREE.InstancedBufferGeometry).copy(baseGeometry));positionsArray.setAttribute("splatIndex",gl),positionsArray.instanceCount=1;let material=new THREE.ShaderMaterial({uniforms:{viewport:{value:new Float32Array([1980,1080])},focal:{value:1e3},centerAndScaleTexture:{value:this.centerAndScaleTexture},covAndColorTexture:{value:this.covAndColorTexture},gsProjectionMatrix:{value:this.getProjectionMatrix()},gsModelViewMatrix:{value:this.getModelViewMatrix()},discardFilter:{value:this.data.discardFilter}},vertexShader:` + precision highp sampler2D; + precision highp usampler2D; + + out vec4 vColor; + out vec2 vPosition; + out float fDF; + uniform vec2 viewport; + uniform float focal; + uniform mat4 gsProjectionMatrix; + uniform mat4 gsModelViewMatrix; + uniform float discardFilter; + + attribute uint splatIndex; + uniform sampler2D centerAndScaleTexture; + uniform usampler2D covAndColorTexture; + + vec2 unpackInt16(in uint value) { + int v = int(value); + int v0 = v >> 16; + int v1 = (v & 0xFFFF); + if((v & 0x8000) != 0) + v1 |= 0xFFFF0000; + return vec2(float(v1), float(v0)); + } + + void main () { + ivec2 texSize = textureSize(centerAndScaleTexture, 0); + ivec2 texPos = ivec2(splatIndex%uint(texSize.x), splatIndex/uint(texSize.x)); + vec4 centerAndScaleData = texelFetch(centerAndScaleTexture, texPos, 0); + + vec4 center = vec4(centerAndScaleData.xyz, 1); + vec4 camspace = gsModelViewMatrix * center; + vec4 pos2d = gsProjectionMatrix * camspace; + + float bounds = 1.2 * pos2d.w; + if (pos2d.z < -pos2d.w || pos2d.x < -bounds || pos2d.x > bounds + || pos2d.y < -bounds || pos2d.y > bounds) { + gl_Position = vec4(0.0, 0.0, 2.0, 1.0); + return; + } + + uvec4 covAndColorData = texelFetch(covAndColorTexture, texPos, 0); + vec2 cov3D_M11_M12 = unpackInt16(covAndColorData.x) * centerAndScaleData.w; + vec2 cov3D_M13_M22 = unpackInt16(covAndColorData.y) * centerAndScaleData.w; + vec2 cov3D_M23_M33 = unpackInt16(covAndColorData.z) * centerAndScaleData.w; + mat3 Vrk = mat3( + cov3D_M11_M12.x, cov3D_M11_M12.y, cov3D_M13_M22.x, + cov3D_M11_M12.y, cov3D_M13_M22.y, cov3D_M23_M33.x, + cov3D_M13_M22.x, cov3D_M23_M33.x, cov3D_M23_M33.y + ); + + mat3 J = mat3( + focal / camspace.z, 0., -(focal * camspace.x) / (camspace.z * camspace.z), + 0., -focal / camspace.z, (focal * camspace.y) / (camspace.z * camspace.z), + 0., 0., 0. + ); + + mat3 W = transpose(mat3(gsModelViewMatrix)); + mat3 T = W * J; + mat3 cov = transpose(T) * Vrk * T; + + vec2 vCenter = vec2(pos2d) / pos2d.w; + + float diagonal1 = cov[0][0] + 0.3; + float offDiagonal = cov[0][1]; + float diagonal2 = cov[1][1] + 0.3; + + float mid = 0.5 * (diagonal1 + diagonal2); + float radius = length(vec2((diagonal1 - diagonal2) / 2.0, offDiagonal)); + float lambda1 = mid + radius; + float lambda2 = max(mid - radius, 0.1); + vec2 diagonalVector = normalize(vec2(offDiagonal, lambda1 - diagonal1)); + vec2 v1 = min(sqrt(2.0 * lambda1), 1024.0) * diagonalVector; + vec2 v2 = min(sqrt(2.0 * lambda2), 1024.0) * vec2(diagonalVector.y, -diagonalVector.x); + + uint colorUint = covAndColorData.w; + vColor = vec4( + float(colorUint & uint(0xFF)) / 255.0, + float((colorUint >> uint(8)) & uint(0xFF)) / 255.0, + float((colorUint >> uint(16)) & uint(0xFF)) / 255.0, + float(colorUint >> uint(24)) / 255.0 + ); + vPosition = position.xy; + fDF = discardFilter; + + gl_Position = vec4( + vCenter + + position.x * v2 / viewport * 2.0 + + position.y * v1 / viewport * 2.0, pos2d.z / pos2d.w, 1.0); + } + `,fragmentShader:` + in vec4 vColor; + in vec2 vPosition; + in float fDF; + + void main () { + float A = -dot(vPosition, vPosition); + if (A < -4.0) discard; + float B = exp(A) * vColor.a; + if(B < fDF) discard; + gl_FragColor = vec4(vColor.rgb, B); + } + `,blending:THREE.CustomBlending,blendSrcAlpha:THREE.OneFactor,depthTest:!0,depthWrite:this.data.depthWrite,transparent:!0}),mesh=(material.onBeforeRender=(renderer,scene,camera,geometry,object,group)=>{var projectionMatrix=this.getProjectionMatrix(camera),viewport=(mesh.material.uniforms.gsProjectionMatrix.value=projectionMatrix,mesh.material.uniforms.gsModelViewMatrix.value=this.getModelViewMatrix(camera),new THREE.Vector4),projectionMatrix=(renderer.getCurrentViewport(viewport),viewport.w/2*Math.abs(projectionMatrix.elements[5]));material.uniforms.viewport.value[0]=viewport.z,material.uniforms.viewport.value[1]=viewport.w,material.uniforms.focal.value=projectionMatrix},new THREE.Mesh(positionsArray,material));for(mesh.frustumCulled=!1,this.object.add(mesh),this.worker.onmessage=e=>{var indexes=new Uint32Array(e.data.sortedIndexes);mesh.geometry.attributes.splatIndex.set(indexes),mesh.geometry.attributes.splatIndex.needsUpdate=!0,mesh.geometry.instanceCount=indexes.length,this.sortReady=!0};;){var centerAndScaleTextureProperties=this.renderer.properties.get(this.centerAndScaleTexture),covAndColorTextureProperties=this.renderer.properties.get(this.covAndColorTexture);if(centerAndScaleTextureProperties&¢erAndScaleTextureProperties.__webglTexture&&covAndColorTextureProperties&¢erAndScaleTextureProperties.__webglTexture)break;await new Promise(resolve=>setTimeout(resolve,10))}this.sortReady=!0},loadData:async function(camera,object,renderer,src){this.camera=camera,this.object=object,this.renderer=renderer,this.loadedVertexCount=0,this.rowLength=32,this.worker=new Worker(URL.createObjectURL(new Blob(["(",this.createWorker.toString(),")(self)"],{type:"application/javascript"}))),this.worker.postMessage({method:"clear"});try{for(;;){if((data=await fetch(src)).ok)break;console.log("retry",src),await new Promise(resolve=>setTimeout(resolve,1e3))}this.parseData(data,src)}catch(error){console.error(error)}},parseData:async function(data,src){var reader=data.body.getReader();let glInitialized=!1,bytesDownloaded=0,bytesProcesses=0;var _totalDownloadBytes=data.headers.get("Content-Length"),totalDownloadBytes=_totalDownloadBytes?parseInt(_totalDownloadBytes):void 0,chunks=(null!=totalDownloadBytes&&(_totalDownloadBytes=Math.floor(totalDownloadBytes/this.rowLength),await this.initGL(_totalDownloadBytes),glInitialized=!0),[]),start=Date.now();let lastReportedProgress=0;for(var isPly=src.endsWith(".ply");;)try{var mbps,percent,{value,done}=await reader.read();if(done){console.log("Completed download.");break}bytesDownloaded+=value.length,null!=totalDownloadBytes?(mbps=bytesDownloaded/1024/1024/((Date.now()-start)/1e3),1<(percent=bytesDownloaded/totalDownloadBytes*100)-lastReportedProgress&&(console.log("download progress:",percent.toFixed(2)+"%",mbps.toFixed(2)+" Mbps"),lastReportedProgress=percent)):console.log("download progress:",bytesDownloaded,", unknown total"),chunks.push(value);var bytesRemains=bytesDownloaded-bytesProcesses;if(!isPly&&null!=totalDownloadBytes&&bytesRemains>this.rowLength){var chunk,extra_data,vertexCount=Math.floor(bytesRemains/this.rowLength),concatenatedChunksbuffer=new Uint8Array(bytesRemains);let offset=0;for(chunk of chunks)concatenatedChunksbuffer.set(chunk,offset),offset+=chunk.length;chunks.length=0,bytesRemains>vertexCount*this.rowLength&&((extra_data=new Uint8Array(bytesRemains-vertexCount*this.rowLength)).set(concatenatedChunksbuffer.subarray(bytesRemains-extra_data.length,bytesRemains),0),chunks.push(extra_data));var buffer=new Uint8Array(vertexCount*this.rowLength);buffer.set(concatenatedChunksbuffer.subarray(0,buffer.byteLength),0),this.pushDataBuffer(buffer.buffer,vertexCount),bytesProcesses+=vertexCount*this.rowLength}}catch(error){console.error(error);break}if(0acc+chunk.length,0)),offset=0;for(let chunk of chunks)concatenatedChunks.set(chunk,offset),offset+=chunk.length;isPly&&(concatenatedChunks=new Uint8Array(this.processPlyBuffer(concatenatedChunks.buffer)));let numVertexes=Math.floor(concatenatedChunks.byteLength/this.rowLength);glInitialized||(await this.initGL(numVertexes),glInitialized=!0),this.pushDataBuffer(concatenatedChunks.buffer,numVertexes)}},pushDataBuffer:function(buffer,vertexCount){if(this.loadedVertexCount+vertexCount>this.maxVertexes&&(console.log("vertexCount limited to ",this.maxVertexes,vertexCount),vertexCount=this.maxVertexes-this.loadedVertexCount),!(vertexCount<=0)){var u_buffer=new Uint8Array(buffer),f_buffer=new Float32Array(buffer),matrices=new Float32Array(16*vertexCount),covAndColorData_uint8=new Uint8Array(this.covAndColorData.buffer),covAndColorData_int16=new Int16Array(this.covAndColorData.buffer);for(let i=0;imax_value&&(max_value=Math.abs(mtx.elements[cov_indexes[j]]));let destOffset=4*this.loadedVertexCount+4*i;this.centerAndScaleData[destOffset+0]=center.x,this.centerAndScaleData[destOffset+1]=center.y,this.centerAndScaleData[destOffset+2]=center.z,this.centerAndScaleData[destOffset+3]=max_value/32767,destOffset=8*this.loadedVertexCount+4*i*2;for(let j=0;j-1e-4*depth&&cutoutArea&&(depthList[validCount]=depth,validIndexList[validCount]=i,validCount++,depth>maxDepth&&(maxDepth=depth),depth{if("clear"==e.data.method&&(matrices=void 0),"push"==e.data.method&&(new_matrices=new Float32Array(e.data.matrices),matrices=void 0===matrices?new_matrices:((resized=new Float32Array(matrices.length+new_matrices.length)).set(matrices),resized.set(new_matrices,matrices.length),resized)),"sort"==e.data.method)if(void 0===matrices){var new_matrices=new Uint32Array(1);self.postMessage({sortedIndexes:new_matrices},[new_matrices.buffer])}else{var new_matrices=new Float32Array(e.data.view),cutout=void 0!==e.data.cutout?new Float32Array(e.data.cutout):void 0;let sortedIndexes=sortSplats(matrices,new_matrices,cutout);self.postMessage({sortedIndexes:sortedIndexes},[sortedIndexes.buffer])}}},processPlyBuffer:function(inputBuffer){var ubuf=new Uint8Array(inputBuffer),ubuf=(new TextDecoder).decode(ubuf.slice(0,10240)),header_end_index=ubuf.indexOf("end_header\n");if(header_end_index<0)throw new Error("Unable to read .ply file header");var vertexCount=parseInt(/element vertex (\d+)\n/.exec(ubuf)[1]);console.log("Vertex Count",vertexCount);let row_offset=0,offsets={},types={};var prop,TYPE_MAP={double:"getFloat64",int:"getInt32",uint:"getUint32",float:"getFloat32",short:"getInt16",ushort:"getUint16",uchar:"getUint8"};for(prop of ubuf.slice(0,header_end_index).split("\n").filter(k=>k.startsWith("property "))){var[,type,name]=prop.split(" "),type=TYPE_MAP[type]||"getInt8";types[name]=type,offsets[name]=row_offset,row_offset+=parseInt(type.replace(/[^\d]/g,""))/8}console.log("Bytes per row",row_offset,types,offsets);let dataView=new DataView(inputBuffer,header_end_index+"end_header\n".length),row=0;var attrs=new Proxy({},{get(target,prop){if(types[prop])return dataView[types[prop]](row*row_offset+offsets[prop],!0);throw new Error(prop+" not found")}});console.time("calculate importance");let sizeList=new Float32Array(vertexCount);var size,opacity,sizeIndex=new Uint32Array(vertexCount);for(row=0;rowsizeList[a]-sizeList[b]),console.timeEnd("sort");var buffer=new ArrayBuffer(32*vertexCount);console.time("build buffer");for(let j=0;j