diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..401d608 --- /dev/null +++ b/LICENCE @@ -0,0 +1,7 @@ +Copyright (c) 2022 Vojtěch Tomas - Metacity Tools + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/example/App.tsx b/example/App.tsx index 6838b2f..4a5b93d 100644 --- a/example/App.tsx +++ b/example/App.tsx @@ -1,22 +1,44 @@ import React from 'react' import './App.css' import { MetacityGL, Utils } from '../metacitygl/metacitygl'; -import { MetacityLayer } from '../metacitygl/extensions'; +import { MetacityTreeLayer } from '../metacitygl/extensions'; function App() { return ( - + + - + + + ) +} + +export default App +/* + + + + - - ) -} -export default App \ No newline at end of file + */ \ No newline at end of file diff --git a/example/main.tsx b/example/main.tsx index 7246cc9..fd64a3b 100644 --- a/example/main.tsx +++ b/example/main.tsx @@ -4,8 +4,8 @@ import App from './App' ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - + // - + // ) diff --git a/metacitygl/components/label.tsx b/metacitygl/components/label.tsx index d8479f9..5c01797 100644 --- a/metacitygl/components/label.tsx +++ b/metacitygl/components/label.tsx @@ -8,11 +8,6 @@ interface MetacityLabelProps { export function MetacityLabel(props: MetacityLabelProps) { return (
-
Visualization powered by MetacityGL
); diff --git a/metacitygl/components/metacitygl.tsx b/metacitygl/components/metacitygl.tsx index ca04fce..6d7481a 100644 --- a/metacitygl/components/metacitygl.tsx +++ b/metacitygl/components/metacitygl.tsx @@ -10,6 +10,7 @@ interface MetacityGLProps { background?: number; children?: React.ReactNode | React.ReactNode[]; target?: [number, number, number]; + position?: [number, number, number]; } export function MetacityGL(props: MetacityGLProps) { @@ -34,6 +35,7 @@ export function MetacityGL(props: MetacityGLProps) { container: container, background: props.background ?? 0x000000, target: props.target ?? [0, 0, 0], + position: props.position, }); setContext(context); context.updateSize(); @@ -41,6 +43,15 @@ export function MetacityGL(props: MetacityGLProps) { canvas.onpointerup = () => { context?.navigation.update(); } + + let updateCall: NodeJS.Timeout; + canvas.addEventListener('wheel', (e) => { + clearTimeout(updateCall); + updateCall = setTimeout(() => { + context?.navigation.update(); + //TODO ideally calculate near and far to fit + }, 20); + }); } }, [canvasRef, containerRef]); diff --git a/metacitygl/components/timeline.tsx b/metacitygl/components/timeline.tsx index 2582033..cb71313 100644 --- a/metacitygl/components/timeline.tsx +++ b/metacitygl/components/timeline.tsx @@ -34,16 +34,17 @@ export function Timeline(props: TimelineProps) { if (props.context) { props.context.onBeforeFrame = (time) => { setTime(time); - const timeframe = props.context?.timeframe; - if (timeframe) { - setTimeStart(timeframe[0]); - setTimeEnd(timeframe[1]); - } - const timeRunning = props.context?.timeRunning; - if (timeRunning !== undefined && !initialized) { + if (timeRunning && !initialized) { setInitialized(timeRunning); } + + if (initialized) { + const timeMin = props.context?.timeMin ?? 0; + const timeMax = props.context?.timeMax ?? 0; + setTimeStart(timeMin); + setTimeEnd(timeMax); + } } } }, [props.context]); diff --git a/metacitygl/extensions.ts b/metacitygl/extensions.ts index 159a9b7..381ce70 100644 --- a/metacitygl/extensions.ts +++ b/metacitygl/extensions.ts @@ -1,6 +1,8 @@ -import { MetacityLayer } from "./extensions/metacity/layer"; +import { MetacityLayer } from "./extensions/metacity/gridLayer"; +import { MetacityTreeLayer } from "./extensions/metacity/treeLayer"; export { - MetacityLayer + MetacityLayer, + MetacityTreeLayer } \ No newline at end of file diff --git a/metacitygl/extensions/gtlf/loader.ts b/metacitygl/extensions/gtlf/loader.ts new file mode 100644 index 0000000..010d47f --- /dev/null +++ b/metacitygl/extensions/gtlf/loader.ts @@ -0,0 +1,18 @@ +import { WorkerPool } from "../../utils"; +import { GLTFLoaderInput, GLTFLoaderOutput, GLTFWorkerInput } from "./types"; +import GLTFWorker from "./worker?worker&inline"; + + +export class GLTFLoader { + private workerPool: WorkerPool; + + constructor() { + this.workerPool = new WorkerPool(GLTFWorker, 4); + } + + load(data: GLTFLoaderInput, callback: (output: GLTFLoaderOutput) => void) { + this.workerPool.process({ + pointInstanceModel: data.pointInstanceModel + }, callback); + } +} \ No newline at end of file diff --git a/metacitygl/extensions/gtlf/types.ts b/metacitygl/extensions/gtlf/types.ts new file mode 100644 index 0000000..028a253 --- /dev/null +++ b/metacitygl/extensions/gtlf/types.ts @@ -0,0 +1,10 @@ +export interface GLTFLoaderInput { + pointInstanceModel: string; +} + +export interface GLTFWorkerInput extends GLTFLoaderInput {} + +export interface GLTFLoaderOutput { + positions: Float32Array; + dots: Float32Array; +} \ No newline at end of file diff --git a/metacitygl/extensions/gtlf/worker.ts b/metacitygl/extensions/gtlf/worker.ts new file mode 100644 index 0000000..0be6d50 --- /dev/null +++ b/metacitygl/extensions/gtlf/worker.ts @@ -0,0 +1,13 @@ +import * as Utils from "../../utils"; + +declare var self: any; + +//eslint-disable-next-line no-restricted-globals +self.onmessage = async (message: MessageEvent) => { + const { pointInstanceModel } = message.data; + const data = await Utils.loadGLTF(pointInstanceModel); + + //eslint-disable-next-line no-restricted-globals + const transferable = [data.positions.buffer, data.dots.buffer]; + self.postMessage(data, transferable); +}; \ No newline at end of file diff --git a/metacitygl/extensions/metacity/gridLayer.tsx b/metacitygl/extensions/metacity/gridLayer.tsx new file mode 100644 index 0000000..f5c9916 --- /dev/null +++ b/metacitygl/extensions/metacity/gridLayer.tsx @@ -0,0 +1,51 @@ +import * as MetacityGL from "../../metacitygl"; +import axios from "axios"; +import React from "react"; +import { GridLayer } from "./layers/grid"; +import { LayerProps } from "./props"; + + +export function MetacityLayer(props: LayerProps) { + const { context, children } = props; + const [layoutInit, setLayoutInit] = React.useState(false); + const [instanceInit, setInstanceInit] = React.useState(false); + const [layer] = React.useState(new GridLayer(props)); + + React.useEffect(() => { + if (context) { + layer.context = context; + const url = layer.api + "/layout.json"; + axios.get(url).then((response) => { + const layout = response.data; + layer.layout = layout; + setLayoutInit(true); + }); + } + }, [context]); + + + React.useEffect(() => { + if (context && layer.instance) { + context.services.gltf.loader.load({ + pointInstanceModel: layer.instance!, + }, (instance) => { + layer.instanceModel = instance; + setInstanceInit(true); + }) + } else { + setInstanceInit(true); + } + }, [context]); + + React.useEffect(() => { + if (instanceInit && context && layoutInit) { + layer.setup(); + } + }, [instanceInit, context, layoutInit]); + + return ( + <> + {children} + + ); +} \ No newline at end of file diff --git a/metacitygl/extensions/metacity/layer.tsx b/metacitygl/extensions/metacity/layer.tsx deleted file mode 100644 index 159876e..0000000 --- a/metacitygl/extensions/metacity/layer.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import * as MetacityGL from "../../metacitygl"; -import { MetacityLoader } from "./loader/loader"; -import axios from "axios"; -import React from "react"; -import { MetacityLoaderOutput } from "./loader/types"; - -type vec3 = MetacityGL.Utils.Types.vec3; - -interface LayerProps extends MetacityGL.MetacityLayerProps { - api: string; - pickable?: boolean; - color?: number; - styles?: MetacityGL.Utils.Styles.Style[]; - radius?: number; - pointInstanceModel?: string; - size?: number; - swapDistance?: number; - - children?: React.ReactNode; -} - -interface MetacityTile { - file: string; - size: number; - x: number; - y: number; - loaded?: boolean; -} - -interface MetacityLayout { - tileWidth: number; - tileHeight: number; - tiles: MetacityTile[]; -} - - -export function MetacityLayer(props: LayerProps) { - const { api, context, children, pointInstanceModel } = props; - const [layout, setLayout] = React.useState(); - const [loader] = React.useState(new MetacityLoader()); - const pickable = props.pickable ?? false; - const color = props.color ?? 0xffffff; - const radius = props.radius ?? 2500; - const swapDistance = props.swapDistance ?? 1000; - const size = props.size ?? 1; - let styles: string[] = []; - - if (props.styles) { - for (let style of props.styles) { - styles.push(style.serialize()); - } - } - - React.useEffect(() => { - const url = api + "/layout.json"; - axios.get(url).then((response) => { - const layout = response.data; - setLayout(layout); - } - ); - }, [api]); - - const dist = (xa: number, ya: number, xb: number, yb: number) => { - return Math.sqrt(Math.pow(xa - xb, 2) + Math.pow(ya - yb, 2)); - } - - React.useEffect(() => { - if (layout && context) { - context.onNavChange = loadTiles; - loadTiles(context.navigation.target, context.navigation.position); - } - }, [layout, context]); - - return ( - <> - {children} - - ); - - function loadTiles(target: vec3, _: vec3) { - if (layout && context) { - const dx = layout.tileWidth * 0.5; - const dy = layout.tileHeight * 0.5; - layout.tiles.forEach((tile) => { - const d = dist(tile.x * layout.tileWidth + dx, tile.y * layout.tileHeight + dy, target.x, target.y); - if (d < radius && !tile.loaded) { - loadTile(tile); - } - }); - } - } - - function loadTile(tile: MetacityTile) { - tile.loaded = true; - loader.load({ - url: api + "/" + tile.file, - tileSize: tile.size, - color: color, - styles: styles, - }, (data: MetacityLoaderOutput) => { - addMesh(data); - addPoints(data); - }); - } - - async function addPoints(data: MetacityLoaderOutput) { - if (data.points) { - const unfs = { - size, - modelColor: MetacityGL.Utils.Color.colorHexToArr(color), - swapDistance, - }; - - if (pointInstanceModel) { - const instance = await MetacityGL.Utils.loadGLTF(pointInstanceModel); - const points = MetacityGL.Graphics.Models.PointsInstancedModel.create({ - ...data.points, - instancePositions: instance.positions, - instanceNormals: instance.normals, - centroid: data.points.centroid, - }, unfs); - context!.add(points); - } else { - const points = MetacityGL.Graphics.Models.PointModel.create(data.points, unfs); - context!.add(points); - } - } - } - - function addMesh(data: MetacityLoaderOutput) { - if (data.mesh) { - const mesh = MetacityGL.Graphics.Models.MeshModel.create(data.mesh); - if (pickable) - context!.add(mesh, data.mesh.metadata); - else - context!.add(mesh); - } - } -} \ No newline at end of file diff --git a/metacitygl/extensions/metacity/layers/base.ts b/metacitygl/extensions/metacity/layers/base.ts new file mode 100644 index 0000000..7ac52d3 --- /dev/null +++ b/metacitygl/extensions/metacity/layers/base.ts @@ -0,0 +1,120 @@ +import { MetacityLoaderOutput } from "../loader/types"; +import * as MetacityGL from "../../../metacitygl"; +import { LayerProps } from "../props"; + + +export interface MetacityTile { + file: string; + size: number; + loaded?: boolean; + placeholder?: MetacityGL.Graphics.Models.Model; +} + +export interface LayoutMetacityTile extends MetacityTile { + x: number; + y: number; +} + +interface InstancedPointsUniforms { + size: number; + modelColor: [number, number, number]; + swapDistance: number; +}; + + +export class Layer { + context?: MetacityGL.Graphics.GraphicsContext; + api: string; + pickable?: boolean; + color: number; + colorPlaceholder: number; + styles: string[] = []; + radius: number; + size: number; + swapDistance: number; + instance?: string; + instanceModel?: MetacityGL.Utils.Types.InstanceData; + + constructor(props: LayerProps) { + this.api = props.api; + this.pickable = props.pickable; + this.color = props.color || 0x000000; + this.colorPlaceholder = props.colorPlaceholder || 0x000000; + + if (props.styles) { + for (let style of props.styles) { + this.styles.push(style.serialize()); + } + } + + this.radius = props.radius ?? 2500; + this.size = props.size ?? 1; + this.swapDistance = props.swapDistance ?? 1000; + this.instance = props.instance; + } + + loadTile(tile: MetacityTile) { + if (!this.context) + return; + this.context.services.metacity.loader.load({ + url: this.api + "/" + tile.file, + tileSize: tile.size, + color: this.color, + styles: this.styles, + }, (data: MetacityLoaderOutput) => { + if (tile.placeholder) + this.context?.remove(tile.placeholder); + this.addMesh(data); + this.addPoints(data); + }); + } + + private addPoints(data: MetacityLoaderOutput) { + if (!this.context) + return; + + if (data.points) { + const unfs = { + size: this.size, + modelColor: MetacityGL.Utils.Color.colorHexToArr(this.color), + swapDistance: this.swapDistance, + }; + + if (this.instanceModel) { + this.addInstancedPoints(data, unfs); + } else { + const points = MetacityGL.Graphics.Models.PointModel.create(data.points, unfs); + this.context.add(points); + } + } + } + + private addInstancedPoints(data: MetacityLoaderOutput, unfs: InstancedPointsUniforms) { + if (!this.instanceModel) + return; + + if (!this.context) + return; + + const points = MetacityGL.Graphics.Models.PointsInstancedModel.create({ + ...data.points!, + instancePositions: this.instanceModel.positions, + instanceDots: this.instanceModel.dots, + centroid: data.points!.centroid, + }, unfs); + this.context.add(points); + } + + private addMesh(data: MetacityLoaderOutput) { + if (!this.context) + return; + + if (data.mesh) { + const mesh = MetacityGL.Graphics.Models.MeshModel.create(data.mesh); + if (this.pickable) + this.context.add(mesh, data.mesh.metadata); + else + this.context.add(mesh); + } + } +} \ No newline at end of file diff --git a/metacitygl/extensions/metacity/layers/grid.ts b/metacitygl/extensions/metacity/layers/grid.ts new file mode 100644 index 0000000..2790002 --- /dev/null +++ b/metacitygl/extensions/metacity/layers/grid.ts @@ -0,0 +1,66 @@ +import * as MetacityGL from "../../../metacitygl"; +import { LayerProps } from "../props"; +import { Layer, LayoutMetacityTile } from "./base"; + + + +interface MetacityLayout { + tileWidth: number; + tileHeight: number; + tiles: LayoutMetacityTile[]; +} + +type vec3 = MetacityGL.Utils.Types.vec3; + +export class GridLayer extends Layer { + layout?: MetacityLayout; + + constructor(props: LayerProps) { + super(props); + } + + setup() { + if (!this.context) + return; + + this.initPlaceholders(); + this.context.onNavChange = (t, p) => this.loadTiles(t, p); + this.loadTiles(this.context.navigation.target, this.context.navigation.position); + } + + initPlaceholders() { + if (!this.layout) return; + if (!this.context) + return; + + const c = MetacityGL.Utils.Color.colorHexToArr(this.colorPlaceholder); + for (let tile of this.layout.tiles) { + const cOff = (Math.random() - 1.0) * 15; + const placeholder = MetacityGL.Graphics.Models.TileModel.create({ + center: [(tile.x + 0.5) * this.layout.tileWidth, (tile.y + 0.5) * this.layout.tileHeight, 0], + width: this.layout.tileWidth * 0.95, + height: this.layout.tileHeight * 0.95, + color: [c[0] + cOff, c[1] + cOff, c[2] + cOff], + }); + tile.placeholder = placeholder; + this.context.add(placeholder); + } + } + + loadTiles(target: vec3, position: vec3) { + if (!this.layout) return; + + const dx = this.layout.tileWidth * 0.5; + const dy = this.layout.tileHeight * 0.5; + this.layout.tiles.forEach((tile) => { + const d = GridLayer.dist(tile.x * this.layout!.tileWidth + dx, tile.y * this.layout!.tileHeight + dy, target.x, target.y); + if (d < this.radius && !tile.loaded) { + this.loadTile(tile); + } + }); + } + + static dist(xa: number, ya: number, xb: number, yb: number) { + return Math.sqrt(Math.pow(xa - xb, 2) + Math.pow(ya - yb, 2)); + } +} \ No newline at end of file diff --git a/metacitygl/extensions/metacity/layers/tree.ts b/metacitygl/extensions/metacity/layers/tree.ts new file mode 100644 index 0000000..deef9ab --- /dev/null +++ b/metacitygl/extensions/metacity/layers/tree.ts @@ -0,0 +1,80 @@ +import * as MetacityGL from "../../../metacitygl"; +import TreeWorker from "../tree/worker?worker&inline"; +import { TreeConfig, TreeWorkerOutput } from "../tree/types"; +import { Layer } from "./base"; +import { TreeLayerProps } from "../props"; + + +export class TreeLayer extends Layer { + treeWorker!: Worker; + treeModel?: MetacityGL.Graphics.Models.TreeModel; + + treeConfig: TreeConfig; + + constructor(props: TreeLayerProps) { + super(props); + this.treeConfig = props.tree; + } + + setup() { + if (!this.context) + return; + + this.treeWorker = new TreeWorker(); + this.treeWorker.onmessage = (event) => { + const data = event.data as TreeWorkerOutput; + this.updateView(data); + }; + + this.treeWorker.postMessage({ + api: this.api, + color: this.color, + styles: this.styles, + position: { + x: (this.context.navigation.target.x), + y: (this.context.navigation.target.y), + z: this.context.navigation.position.distanceTo(this.context.navigation.target), + }, + treeConfig: this.treeConfig, + }); + + this.context.onNavChange = (tar, pos) => { + this.treeWorker.postMessage({ + position: { + x: tar.x, + y: tar.y, + z: tar.distanceTo(pos), + } + }); + } + } + + updateView(data: TreeWorkerOutput) { + if (!this.context) + return; + + data.tilesToLoad.forEach((tile) => { + let pch; + if (tile.model.array) { + pch = MetacityGL.Graphics.Models.TreeModel.create(tile.model as MetacityGL.Utils.Types.TreeData); + this.context!.add(pch); + } + this.loadTile({ + size: tile.size, + file: tile.name, + placeholder: pch, + }); + }); + + if (this.treeModel) { + this.treeModel.updateBuffer(data as MetacityGL.Utils.Types.TreeData); + } else { + if (data.array) + { + const model = MetacityGL.Graphics.Models.TreeModel.create(data as MetacityGL.Utils.Types.TreeData); + this.treeModel = model; + this.context.add(model); + } + } + } +} \ No newline at end of file diff --git a/metacitygl/extensions/metacity/loader/group.ts b/metacitygl/extensions/metacity/loader/group.ts index 071baf9..c7088c8 100644 --- a/metacitygl/extensions/metacity/loader/group.ts +++ b/metacitygl/extensions/metacity/loader/group.ts @@ -16,6 +16,9 @@ export function groupBuffersByType(gltf: any) : ModelGroups { meshes: [] }; + if (!gltf.meshes) + return groups; + for(let i = 0; i < gltf.meshes.length; i++) { const model = gltf.meshes[i]; const positions = model.primitives[0].attributes.POSITION; diff --git a/metacitygl/extensions/metacity/loader/style.ts b/metacitygl/extensions/metacity/loader/style.ts index 2917db9..3442e1d 100644 --- a/metacitygl/extensions/metacity/loader/style.ts +++ b/metacitygl/extensions/metacity/loader/style.ts @@ -12,15 +12,15 @@ function computeColorTable(styles: Utils.Styles.Style[], baseColor: number, meta } -function computeColorBuffer(ids: Float32Array, colorBuffer: Float32Array, colorTable: Map) { +function computeColorBuffer(ids: Uint8Array, colorBuffer: Uint8Array, colorTable: Map) { const idBuffer = new Uint8Array(4); const view = new DataView(idBuffer.buffer); idBuffer[0] = 0; const idToNumber = (offset: number) => { - idBuffer[1] = ids[offset] * 255; - idBuffer[2] = ids[offset + 1] * 255; - idBuffer[3] = ids[offset + 2] * 255; + idBuffer[1] = ids[offset]; + idBuffer[2] = ids[offset + 1]; + idBuffer[3] = ids[offset + 2]; return view.getInt32(0); }; @@ -38,7 +38,7 @@ function computeColorBuffer(ids: Float32Array, colorBuffer: Float32Array, colorT } -export function applyStyle(styles: string[], baseColor: number, ids: Float32Array, colorBuffer: Float32Array, metadata: Utils.Types.Metadata) { +export function applyStyle(styles: string[], baseColor: number, ids: Uint8Array, colorBuffer: Uint8Array, metadata: Utils.Types.Metadata) { const stylesCls = []; for (let i = 0; i < styles.length; i++) stylesCls.push(Utils.Styles.Style.deserialize(styles[i])); diff --git a/metacitygl/extensions/metacity/loader/types.ts b/metacitygl/extensions/metacity/loader/types.ts index 259a078..8648c35 100644 --- a/metacitygl/extensions/metacity/loader/types.ts +++ b/metacitygl/extensions/metacity/loader/types.ts @@ -15,21 +15,21 @@ export interface MetacityWorkerInput { export interface MetacityLoaderOutput { mesh?: { positions: Float32Array; - normals: Float32Array; - colors: Float32Array; - ids: Float32Array; - metadata: { + dots: Float32Array; + colors: Uint8Array; + ids?: Uint8Array; + metadata?: { [id: number]: any; }; type: string; }, points?: { positions: Float32Array; - ids: Float32Array; - centroid: [number, number, number]; - metadata: { + ids?: Uint8Array; + metadata?: { [id: number]: any; }; + centroid: [number, number, number]; type: string; } } \ No newline at end of file diff --git a/metacitygl/extensions/metacity/loader/worker.ts b/metacitygl/extensions/metacity/loader/worker.ts index ad3d261..6588682 100644 --- a/metacitygl/extensions/metacity/loader/worker.ts +++ b/metacitygl/extensions/metacity/loader/worker.ts @@ -19,7 +19,8 @@ async function loadModel(message: any) { const colorArr = Utils.Color.colorHexToArr(color); const gltf = await load(url, GLTFLoader); const groups = groupBuffersByType(gltf); - const meshASM = new Utils.Assemblers.MeshAssembler(idOffset); + const useMetadata = styles !== undefined && styles.length > 0; + const meshASM = new Utils.Assemblers.MeshAssembler(idOffset, useMetadata); for(let i = 0; i < groups.meshes.length; i++) { const mesh = groups.meshes[i]; meshASM.addMesh(mesh.positions, colorArr, mesh.meta); @@ -35,7 +36,7 @@ async function loadModel(message: any) { const meshBuffers = meshASM.toBuffers(); if (styles.length > 0 && meshBuffers) { - applyStyle(styles, color, meshBuffers.ids, meshBuffers.colors, meshBuffers.metadata); + applyStyle(styles, color, meshBuffers.ids!, meshBuffers.colors, meshBuffers.metadata!); } const transferables = meshASM.pickTransferables(meshBuffers); diff --git a/metacitygl/extensions/metacity/props.ts b/metacitygl/extensions/metacity/props.ts new file mode 100644 index 0000000..35a8d86 --- /dev/null +++ b/metacitygl/extensions/metacity/props.ts @@ -0,0 +1,19 @@ +import * as MetacityGL from "../../metacitygl"; +import { TreeConfig } from "./tree/types"; + +export interface LayerProps extends MetacityGL.MetacityLayerProps { + api: string; + pickable?: boolean; + color?: number; + colorPlaceholder?: number; + styles?: MetacityGL.Utils.Styles.Style[]; + radius?: number; + instance?: string; + size?: number; + swapDistance?: number; + children?: React.ReactNode; +} + +export interface TreeLayerProps extends LayerProps { + tree: TreeConfig; +} diff --git a/metacitygl/extensions/metacity/tree/tree.ts b/metacitygl/extensions/metacity/tree/tree.ts new file mode 100644 index 0000000..c083142 --- /dev/null +++ b/metacitygl/extensions/metacity/tree/tree.ts @@ -0,0 +1,267 @@ +import { TreeQuery, SubTreeQuery, QuadrantData, TreeConfig } from "./types"; +import * as Utils from "../../../utils" + +interface QuadTreeProps { + data: QuadrantData; + bmin: [number, number]; + bmax: [number, number]; + color: [number, number, number]; + styles: Utils.Styles.Style[]; + aboveLoadingLevel?: boolean; + config: TreeConfig; +} + +export class QuadTree { + min: { x: number, y: number, z: number }; + max: { x: number, y: number, z: number }; + center: { x: number, y: number, z: number }; + dimensions: { x: number, y: number, z: number }; + metadata: { [key: string]: number | string }; + color: number[]; + depth: number; + aboveLoadingLevel: boolean; + url?: string; + size: number; + + tileLoaded = false; + + ne?: QuadTree; + nw?: QuadTree; + sw?: QuadTree; + se?: QuadTree; + + LOADING_RADIUS: number = 300; + REQEST_TILE_RADIUS: number = 280; + DIST_FACTOR = 2.25; + DIST_Z_FACTOR = 2; + RAD_FACTOR = 2; + + cachcedSpaceRequired = 0; + cachedDistSqr = 0; + cachedRadiusSqr = 0; + visualizeTree = true; + zOffset = 0; + + + constructor(props: QuadTreeProps, depth: number = 0) { + const { data, bmin, bmax, color, styles } = props; + this.aboveLoadingLevel = props.aboveLoadingLevel ?? true; + + this.min = { x: bmin[0], y: bmin[1], z: data.z[0] }; + this.max = { x: bmax[0], y: bmax[1], z: data.z[1] }; + + this.center = { + x: (bmin[0] + bmax[0]) * 0.5, + y: (bmin[1] + bmax[1]) * 0.5, + z: (data.z[0] + data.z[1]) * 0.5 + }; + + this.dimensions = { + x: bmax[0] - bmin[0], + y: bmax[1] - bmin[1], + z: data.z[1] - data.z[0] + }; + + this.color = color; + this.size = data.size; + this.url = data.file; + this.depth = depth; + this.metadata = data.metadata ?? {}; + this.metadata["height"] = this.dimensions.z; + + this.LOADING_RADIUS = (props.config.loadingRadius ?? this.LOADING_RADIUS) ** this.RAD_FACTOR; + this.REQEST_TILE_RADIUS = (props.config.requestTileRadius ?? this.REQEST_TILE_RADIUS) ** this.RAD_FACTOR; + this.DIST_FACTOR = props.config.distFactor ?? this.DIST_FACTOR; + this.DIST_Z_FACTOR = props.config.distZFactor ?? this.DIST_Z_FACTOR; + this.RAD_FACTOR = props.config.radFactor ?? this.RAD_FACTOR; + this.visualizeTree = props.config.visualizeTree ?? this.visualizeTree; + this.zOffset = props.config.zOffset ?? this.zOffset; + + this.applyStyles(styles); + this.initChildren(props, data, depth); + this.cachcedSpaceRequired = this.spaceRequired(); + } + + + private initChildren(props: QuadTreeProps, data: QuadrantData, depth: number) { + const childLoading = props.aboveLoadingLevel && this.url !== undefined; + if (data.ne) { + this.ne = new QuadTree({ + data: data.ne, + bmin: [this.center.x, this.center.y], + bmax: [this.max.x, this.max.y], + color: props.color, + styles: props.styles, + aboveLoadingLevel: childLoading, + config: props.config + }, depth + 1); + } + + if (data.se) { + this.se = new QuadTree({ + data: data.se, + bmin: [this.center.x, this.min.y], + bmax: [this.max.x, this.center.y], + color: props.color, + styles: props.styles, + aboveLoadingLevel: childLoading, + config: props.config + }, depth + 1); + } + + if (data.sw) { + this.sw = new QuadTree({ + data: data.sw, + bmin: [this.min.x, this.min.y], + bmax: [this.center.x, this.center.y], + color: props.color, + styles: props.styles, + aboveLoadingLevel: childLoading, + config: props.config + }, depth + 1); + } + + if (data.nw) { + this.nw = new QuadTree({ + data: data.nw, + bmin: [this.min.x, this.center.y], + bmax: [this.center.x, this.max.y], + color: props.color, + styles: props.styles, + aboveLoadingLevel: childLoading, + config: props.config + }, depth + 1); + } + } + + private applyStyles(styles: Utils.Styles.Style[]) { + let c; + for (let i = 0; i < styles.length; i++) + c = styles[i].apply(this.metadata) ?? c; + + if (c) + this.color = Utils.Color.colorHexToArr(c); + } + + query(position: { x: number, y: number, z: number }, q: TreeQuery) { + this.allChildrenModelLoaded(position, q); + this.buildTreeModel(position, q); + } + + private allChildrenModelLoaded(position: { x: number; y: number; z: number; }, q: TreeQuery) { + if (this.tileLoaded) { + return true; + } + + const { distSqr, radiusSqr } = this.computeDistances(position); + this.cachedRadiusSqr = radiusSqr; + this.cachedDistSqr = distSqr; + + if (this.isLeaf && distSqr < radiusSqr * this.REQEST_TILE_RADIUS) { + return true; + } + + if (!this.isLeaf && distSqr < radiusSqr * this.REQEST_TILE_RADIUS) { + const nw = this.nw ? this.nw?.allChildrenModelLoaded(position, q) : true; + const ne = this.ne ? this.ne?.allChildrenModelLoaded(position, q) : true; + const sw = this.sw ? this.sw?.allChildrenModelLoaded(position, q) : true; + const se = this.se ? this.se?.allChildrenModelLoaded(position, q) : true; + const countYes = (nw ? 1 : 0) + (ne ? 1 : 0) + (sw ? 1 : 0) + (se ? 1 : 0); + + if (countYes > 2) + { + if (this.url !== undefined) this.tileToQuery(position, q); + return true; + } + + } + + return false; + } + + private buildTreeModel(position: { x: number; y: number; z: number; }, q: TreeQuery) { + if (this.tileLoaded) + return; + + const { distSqr, radiusSqr } = this.computeDistances(position); + + if (!this.isLeaf && distSqr < radiusSqr * this.LOADING_RADIUS) { + this.ne?.buildTreeModel(position, q); + this.se?.buildTreeModel(position, q); + this.sw?.buildTreeModel(position, q); + this.nw?.buildTreeModel(position, q); + } else { + //append geometry of current node + this.nodeToModel(q); + } + + } + + private tileToQuery(position: { x: number; y: number; z: number; }, q: TreeQuery) { + this.tileLoaded = true; + const pch = { + name: this.url!, + size: this.size, + model: { + array: new Float32Array(this.cachcedSpaceRequired), + filled: 0, + } + }; + this.buildLeafModel(position, pch.model); + q.tilesToLoad.push(pch); + } + + private buildLeafModel(position: { x: number; y: number; z: number; }, q: SubTreeQuery) { + if (!this.isLeaf) { + this.ne?.buildLeafModel(position, q); + this.se?.buildLeafModel(position, q); + this.sw?.buildLeafModel(position, q); + this.nw?.buildLeafModel(position, q); + } else { + this.nodeToModel(q); + } + } + + private nodeToModel(q: SubTreeQuery) { + if (!this.visualizeTree || !q.array) + return; + + q.array[q.filled++] = this.center.x; + q.array[q.filled++] = this.center.y; + q.array[q.filled++] = this.center.z + this.zOffset; + q.array[q.filled++] = this.dimensions.x; + q.array[q.filled++] = this.dimensions.y; + q.array[q.filled++] = this.dimensions.z; + q.array[q.filled++] = this.color[0]; + q.array[q.filled++] = this.color[1]; + q.array[q.filled++] = this.color[2]; + } + + private computeDistances(position: { x: number; y: number; z: number; }) { + const distSqr = Math.abs(position.x - this.center.x) ** (this.DIST_FACTOR) + Math.abs(position.y - this.center.y) ** (this.DIST_FACTOR) + Math.abs(position.z - this.center.z) ** (this.DIST_Z_FACTOR); + const radiusSqr = Math.max(this.max.x - this.min.x, this.max.y - this.min.y) ** (this.RAD_FACTOR); + return { distSqr, radiusSqr }; + } + + private get isLeaf() { + return !this.ne && !this.nw && !this.se && !this.sw; + } + + spaceRequired() { + let s = 0; + + if (this.isLeaf) + s += 9; + + if (this.ne) + s += this.ne.spaceRequired(); + if (this.se) + s += this.se.spaceRequired(); + if (this.sw) + s += this.sw.spaceRequired(); + if (this.nw) + s += this.nw.spaceRequired(); + + return s; + } +} \ No newline at end of file diff --git a/metacitygl/extensions/metacity/tree/types.ts b/metacitygl/extensions/metacity/tree/types.ts new file mode 100644 index 0000000..da964d3 --- /dev/null +++ b/metacitygl/extensions/metacity/tree/types.ts @@ -0,0 +1,53 @@ + + +export interface SubTreeQuery { + array?: Float32Array; + filled: number; +} + +export interface TreeQuery extends SubTreeQuery { + tilesToLoad: { + name: string; + size: number; + model: SubTreeQuery; + }[]; +} + +export interface TreeConfig { + loadingRadius?: number; + requestTileRadius?: number; + distFactor?: number; + distZFactor?: number; + radFactor?: number; + visualizeTree?: boolean; + zOffset?: number; +} + +export interface TreeWorkerInitInput { + api: string; + styles: string[]; + color: number; + config: TreeConfig +} + +export interface TreeWorkerOutput extends TreeQuery {} + +export interface QuadrantData { + z: [number, number]; + metadata?: { + [key: string]: number|string; + } + sw?: QuadrantData; + se?: QuadrantData; + nw?: QuadrantData; + ne?: QuadrantData; + file?: string; + size: number; +} + +export interface QuadTreeData extends QuadrantData { + border: { + min: [number, number]; + max: [number, number]; + } +} \ No newline at end of file diff --git a/metacitygl/extensions/metacity/tree/worker.ts b/metacitygl/extensions/metacity/tree/worker.ts new file mode 100644 index 0000000..25f11b1 --- /dev/null +++ b/metacitygl/extensions/metacity/tree/worker.ts @@ -0,0 +1,67 @@ + + + +import { TreeConfig, TreeWorkerInitInput } from './types'; +import axios from 'axios'; +import { QuadTree } from './tree'; +import * as Utils from '../../../utils'; + + +declare var self: any; + + +let tree: QuadTree; + +//eslint-disable-next-line no-restricted-globals +self.onmessage = (message: MessageEvent) => { + loadModel(message); +}; + +async function loadModel(message: any) { + const data = message.data; + + if (data.api) { + await initWorker(data.api, data.color, data.styles, data.treeConfig); + } + + if (data.position) { + const geometry = constructModel(data.position); + const transferable = geometry.array ? [geometry.array.buffer] : []; + self.postMessage(geometry, transferable); + } +} + +async function initWorker(api: string, color: number, styles: string[], config: TreeConfig) { + const meta = api + '/meta.json'; + const treedata = await axios.get(meta); + const { data } = treedata; + const arrColor = Utils.Color.colorHexToArr(color); + const stylesCls = []; + for (let i = 0; i < styles.length; i++) + stylesCls.push(Utils.Styles.Style.deserialize(styles[i])); + + tree = new QuadTree({ + data, + bmin: data.border.min, + bmax: data.border.max, + color: arrColor, + styles: stylesCls, + config + }); + console.log(`Tree requires MAX ${tree.spaceRequired() / 1024 / 1024} MB`); +} + +function constructModel(position: {x: number, y: number, z: number}) { + const geometry = { + array: tree.visualizeTree ? new Float32Array(tree.spaceRequired()) : undefined, + filled: 0, + tilesToLoad: [] + }; + + tree.query(position, geometry); + return geometry; +} + + + + diff --git a/metacitygl/extensions/metacity/treeLayer.tsx b/metacitygl/extensions/metacity/treeLayer.tsx new file mode 100644 index 0000000..80ffb47 --- /dev/null +++ b/metacitygl/extensions/metacity/treeLayer.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import * as MetacityGL from "../../metacitygl"; +import { TreeLayer } from "./layers/tree"; +import { TreeLayerProps } from "./props"; + + +export function MetacityTreeLayer(props: TreeLayerProps) {; + const { context, children } = props; + const [layerInit, setLayerInit] = React.useState(false); + const [layer] = React.useState(new TreeLayer(props)); + + React.useEffect(() => { + if (context) { + layer.context = context; + } + }, [context]); + + React.useEffect(() => { + if (context && layer.instance) { + context.services.gltf.loader.load({ + pointInstanceModel: layer.instance!, + }, (instance) => { + layer.instanceModel = instance; + setLayerInit(true); + }) + } else { + setLayerInit(true); + } + }, [context]); + + + React.useEffect(() => { + if (context && layerInit) { + layer.setup(); + } + }, [context, layerInit]); + + + return (<>{children}); +} + diff --git a/metacitygl/extensions/services.ts b/metacitygl/extensions/services.ts new file mode 100644 index 0000000..c303ee6 --- /dev/null +++ b/metacitygl/extensions/services.ts @@ -0,0 +1,15 @@ +import { GLTFLoader } from "./gtlf/loader" +import { MetacityLoader } from "./metacity/loader/loader" + + + +const services = { + metacity: { + loader: new MetacityLoader(), + }, + gltf: { + loader: new GLTFLoader(), + } +} + +export default services \ No newline at end of file diff --git a/metacitygl/graphics/context.ts b/metacitygl/graphics/context.ts index 65d1106..f65457f 100644 --- a/metacitygl/graphics/context.ts +++ b/metacitygl/graphics/context.ts @@ -2,13 +2,12 @@ import * as THREE from 'three'; import { Navigation, NavigationProps } from './core/navigation'; import { GPUPicker } from './core/gpuPicker' import { Renderer, RendererProps } from './core/renderer'; -import { Model } from './models/model'; -import { Metadata, vec3 } from '../utils/types'; +import { Metadata } from '../utils/types'; import Stats from 'three/examples/jsm/libs/stats.module.js'; +import services from '../extensions/services'; export interface GraphicsContextProps extends NavigationProps, RendererProps { - onFrame?: (time: number, timeMax: number) => void; container: HTMLDivElement; } @@ -20,13 +19,14 @@ export class GraphicsContext { readonly picker: GPUPicker; readonly container: HTMLDivElement; readonly stats: Stats; + readonly services = services; private metadata: Metadata; - private speed_: number = 0; - private time_: number = 0; - private timeframe_: [number, number] = [Infinity, -Infinity]; + private _speed: number = 0; + private _time: number = 0; + private _timeMin = Infinity; + private _timeMax = -Infinity; - private onFrameFn: ((time: number, timeMax: number) => void) | undefined; private beforeFrameUpdateFns: ((time: number) => void)[] = []; constructor(props: GraphicsContextProps) { @@ -40,7 +40,6 @@ export class GraphicsContext { this.stats.showPanel( 0 ); // 0: fps, 1: ms, 2: mb, 3+: custom document.body.appendChild( this.stats.dom ); this.setupStats(); - this.onFrameFn = props.onFrame; let time = Date.now(); const frame = async () => { @@ -49,7 +48,7 @@ export class GraphicsContext { time = this.updateTime(time); //update - this.beforeFrameUpdateFns.forEach(fn => fn(this.time_)); + this.beforeFrameUpdateFns.forEach(fn => fn(this._time)); //rendering this.navigation.controls.update(); @@ -63,19 +62,15 @@ export class GraphicsContext { } private updateTime(time: number) { - if (this.speed_ !== 0) { - const delta = (Date.now() - time) / 1000 * this.speed_; - this.time_ = (this.time_ + delta) % this.timeframe_[1]; + if (this._speed !== 0) { + const delta = (Date.now() - time) / 1000 * this._speed; + this._time = (this._time + delta) % this._timeMax; - if (this.time_ < this.timeframe_[0]) - this.time_ = this.timeframe_[0]; + if (this._time < this._timeMin) + this._time = this._timeMin; } - this.scene.userData.time = this.time_; - - if (this.onFrameFn) - this.onFrameFn(this.time_, this.timeframe_[1]); - + this.scene.userData.time = this._time; time = Date.now(); return time; } @@ -94,35 +89,44 @@ export class GraphicsContext { this.stats.dom.style.display = this.stats.dom.style.display === 'none' ? 'block' : 'none'; } - get timeframe() { - return this.timeframe_; + set time(t: number) { + this._time = t; + this.scene.userData.time = t; } - get timeRunning() { - return this.timeframe_[1] !== -Infinity; + get time() { + return this._time; } - set time(t: number) { - this.time_ = t; - this.scene.userData.time = t; + get timeMin() { + return this._timeMin; } - set timeframe(timeframe: [number, number]) { - this.timeframe_[0] = Math.min(this.timeframe_[0], timeframe[0]); - this.timeframe_[1] = Math.max(this.timeframe_[1], timeframe[1]); + set timeMin(value: number) { + this._timeMin = Math.min(value, this._timeMax); + } - if (this.speed_ === 0){ - this.time_ = this.timeframe_[0]; - this.speed_ = 1; - } + get timeMax() { + return this._timeMax; } + set timeMax(value: number) { + this._timeMax = Math.max(value, this._timeMax); + } + set speed(value: number) { - if(this.timeRunning) - this.speed_ = value; + this._speed = value; + } + + get speed() { + return this._speed; + } + + get timeRunning() { + return this._speed !== 0 && this._timeMax > this._timeMin; } - set onNavChange(fn: (target: vec3, position: vec3) => void) { + set onNavChange(fn: (target: THREE.Vector3, position: THREE.Vector3) => void) { this.navigation.onChange = fn; } @@ -144,6 +148,10 @@ export class GraphicsContext { } } + remove(model: any) { + this.scene.remove(model); + } + getMetadata(key: number) { return this.metadata[key]; } diff --git a/metacitygl/graphics/core/controls.ts b/metacitygl/graphics/core/controls.ts index bc58708..457f41d 100644 --- a/metacitygl/graphics/core/controls.ts +++ b/metacitygl/graphics/core/controls.ts @@ -16,7 +16,7 @@ export class MapControls extends OrbitControls { constructor(props: MapControlsProps, domElement: HTMLCanvasElement) { const camera = new THREE.PerspectiveCamera( - 20, + 30, domElement.clientWidth / domElement.clientHeight, props.near ?? 200, props.far ?? 100000 @@ -34,7 +34,7 @@ export class MapControls extends OrbitControls { this.minDistance = props.minDistance ?? 1000; this.maxDistance = props.maxDistance ?? 96000; this.minPolarAngle = props.minPolarAngle ?? 0.001; - this.maxPolarAngle = props.maxPolarAngle ?? Math.PI * 0.3; + this.maxPolarAngle = props.maxPolarAngle ?? Math.PI * 0.4; this.update(); } diff --git a/metacitygl/graphics/core/navigation.ts b/metacitygl/graphics/core/navigation.ts index 7aeb30b..2511e02 100644 --- a/metacitygl/graphics/core/navigation.ts +++ b/metacitygl/graphics/core/navigation.ts @@ -18,7 +18,7 @@ export class Navigation { constructor(props: NavigationProps) { let { position, target } = parseUrl(); this.controls = new MapControls(props, props.canvas); - this.offset = props.offset ?? 8000; + this.offset = props.offset ?? 5000; target = target || props.target || [0, 0, 0]; position = position || props.position || this.isoPosition(target); this.set(new THREE.Vector3(...target), new THREE.Vector3(...position)); diff --git a/metacitygl/graphics/materials/instancedMeshMaterial.ts b/metacitygl/graphics/materials/instancedMeshMaterial.ts index 76634bd..3fc675e 100644 --- a/metacitygl/graphics/materials/instancedMeshMaterial.ts +++ b/metacitygl/graphics/materials/instancedMeshMaterial.ts @@ -1,33 +1,31 @@ import * as THREE from 'three'; const vs3D = ` -varying vec3 fscolor; -varying vec3 fsnormal; - attribute vec3 instanceShift; +attribute float dot; uniform vec3 modelColor; +varying vec3 fscolor; +varying float fsdot; + void main(){ - fscolor = modelColor; - fsnormal = normal; + fscolor = modelColor / 255.0; + fsdot = dot; vec3 transformed = position + instanceShift; gl_Position = projectionMatrix * (modelViewMatrix * vec4(transformed, 1.0)); }`; const fs3D = ` -varying vec3 fscolor; -varying vec3 fsnormal; -uniform float grayscale; +uniform float grayscale; -//light always shines from the top -vec3 light = vec3(0.0, 0.0, -1.0); +varying vec3 fscolor; +varying float fsdot; void main() { //pseudo-phong shading - float diffuse = abs(dot(fsnormal, light)); - vec3 phcolor = fscolor * diffuse * 0.2 + fscolor * 0.8; + vec3 phcolor = fscolor * fsdot * 0.2 + fscolor * 0.8; float grs = phcolor.r * 0.2126 + phcolor.g * 0.7152 + phcolor.b * 0.0722; vec3 gcolor = vec3(grs, grs, grs); @@ -41,7 +39,7 @@ export class InstancedMeshMaterial extends THREE.ShaderMaterial { super({ uniforms: { grayscale: { value: 0 }, - modelColor: { value: [1, 1, 1] }, + modelColor: { value: [255, 255, 255] }, }, vertexShader: vs3D, fragmentShader: fs3D, diff --git a/metacitygl/graphics/materials/meshMaterial.ts b/metacitygl/graphics/materials/meshMaterial.ts index b0ef44d..e4de40a 100644 --- a/metacitygl/graphics/materials/meshMaterial.ts +++ b/metacitygl/graphics/materials/meshMaterial.ts @@ -1,12 +1,14 @@ import * as THREE from 'three'; const vs3D = ` +attribute float dot; + varying vec3 fscolor; -varying vec3 fsnormal; +varying float fsdot; void main(){ fscolor = color; - fsnormal = normal; + fsdot = dot; vec3 transformed = position; gl_Position = projectionMatrix * (modelViewMatrix * vec4(transformed, 1.0)); @@ -14,17 +16,13 @@ void main(){ const fs3D = ` varying vec3 fscolor; -varying vec3 fsnormal; -uniform float grayscale; +varying float fsdot; - -//light always shines from the top -vec3 light = vec3(0.0, 0.0, -1.0); +uniform float grayscale; void main() { //pseudo-phong shading - float diffuse = abs(dot(fsnormal, light)); - vec3 phcolor = fscolor * diffuse * 0.2 + fscolor * 0.8; + vec3 phcolor = fscolor * fsdot * 0.2 + fscolor * 0.8; float grs = phcolor.r * 0.2126 + phcolor.g * 0.7152 + phcolor.b * 0.0722; vec3 gcolor = vec3(grs, grs, grs); diff --git a/metacitygl/graphics/materials/meshPickMaterial.ts b/metacitygl/graphics/materials/meshPickMaterial.ts index ad287aa..9073ee0 100644 --- a/metacitygl/graphics/materials/meshPickMaterial.ts +++ b/metacitygl/graphics/materials/meshPickMaterial.ts @@ -2,7 +2,6 @@ import * as THREE from 'three'; const vs3D = ` attribute vec3 idcolor; - varying vec3 fscolor; void main(){ diff --git a/metacitygl/graphics/materials/meshUniformMaterial.ts b/metacitygl/graphics/materials/meshUniformMaterial.ts index 16c0c63..4a4f94e 100644 --- a/metacitygl/graphics/materials/meshUniformMaterial.ts +++ b/metacitygl/graphics/materials/meshUniformMaterial.ts @@ -2,13 +2,11 @@ import * as THREE from 'three'; const vs3D = ` varying vec3 fscolor; - uniform vec3 modelColor; void main(){ - fscolor = modelColor; + fscolor = modelColor / 255.0; vec3 transformed = position; - gl_Position = projectionMatrix * (modelViewMatrix * vec4(transformed, 1.0)); }`; @@ -16,10 +14,6 @@ const fs3D = ` varying vec3 fscolor; uniform float grayscale; - -//light always shines from the top -vec3 light = vec3(0.0, 0.0, 1.0); - void main() { float grs = fscolor.r * 0.2126 + fscolor.g * 0.7152 + fscolor.b * 0.0722; vec3 gcolor = vec3(grs, grs, grs); @@ -32,13 +26,12 @@ export class MeshUniformMaterial extends THREE.ShaderMaterial { constructor() { super({ uniforms: { - grayscale: { value: 1 }, - modelColor: { value: [0, 0, 0] }, + grayscale: { value: 0 }, + modelColor: { value: [255, 255, 255] }, }, vertexShader: vs3D, fragmentShader: fs3D, side: THREE.DoubleSide, - vertexColors: true, }); } } diff --git a/metacitygl/graphics/materials/pointsMaterial.ts b/metacitygl/graphics/materials/pointsMaterial.ts index cab9273..cb7ab68 100644 --- a/metacitygl/graphics/materials/pointsMaterial.ts +++ b/metacitygl/graphics/materials/pointsMaterial.ts @@ -8,7 +8,7 @@ uniform float size; uniform float scale; void main(){ - fscolor = modelColor; + fscolor = modelColor / 255.0; vec3 transformed = position; vec4 mvPosition = (modelViewMatrix * vec4(transformed, 1.0)); gl_PointSize = size * scale / -mvPosition.z; @@ -34,12 +34,11 @@ export class PointsMaterial extends THREE.ShaderMaterial { size: { value: 10 }, scale: { value: 1000 }, grayscale: { value: 0 }, - modelColor: { value: [1, 1, 1] }, + modelColor: { value: [255, 255, 255] }, }, vertexShader: vs3D, fragmentShader: fs3D, side: THREE.DoubleSide, - //vertexColors: false, }); } } diff --git a/metacitygl/graphics/materials/treeMaterial.ts b/metacitygl/graphics/materials/treeMaterial.ts new file mode 100644 index 0000000..02edf1e --- /dev/null +++ b/metacitygl/graphics/materials/treeMaterial.ts @@ -0,0 +1,49 @@ +import * as THREE from 'three'; + +const vs3D = ` +attribute float dot; + +attribute vec3 center; +attribute vec3 dimensions; + +varying vec3 fscolor; +varying float fsdot; + +void main(){ + fscolor = color / 255.0; + fsdot = dot; + vec3 transformed = position; + transformed = transformed * dimensions + center; + gl_Position = projectionMatrix * (modelViewMatrix * vec4(transformed, 1.0)); +}`; + +const fs3D = ` +varying vec3 fscolor; +varying float fsdot; + +uniform float grayscale; + +void main() { + //pseudo-phong shading + vec3 phcolor = fscolor * fsdot * 0.2 + fscolor * 0.8; + + float grs = phcolor.r * 0.2126 + phcolor.g * 0.7152 + phcolor.b * 0.0722; + vec3 gcolor = vec3(grs, grs, grs); + vec3 color = mix(phcolor, gcolor, grayscale); + gl_FragColor = vec4(color, 1.0); +}`; + + +export class TreeMaterial extends THREE.ShaderMaterial { + constructor() { + super({ + uniforms: { + grayscale: { value: 0 }, + }, + vertexShader: vs3D, + fragmentShader: fs3D, + side: THREE.DoubleSide, + vertexColors: true, + }); + } +} diff --git a/metacitygl/graphics/models.ts b/metacitygl/graphics/models.ts index 9ac7f15..aef21a9 100644 --- a/metacitygl/graphics/models.ts +++ b/metacitygl/graphics/models.ts @@ -3,8 +3,11 @@ import { DoubleLineModel } from "./models/doubleLine"; import { GridModel } from "./models/grid"; import { LineModel } from "./models/line"; import { MeshModel } from "./models/mesh"; +import { Model } from "./models/model"; import { PointModel } from "./models/points"; import { PointsInstancedModel } from "./models/pointsInstanced"; +import { TileModel } from "./models/tile"; +import { TreeModel } from "./models/tree"; export { DoubleLineModel, @@ -14,4 +17,10 @@ export { GridModel, PointModel, PointsInstancedModel, + TileModel, + TreeModel, +} + +export type { + Model } \ No newline at end of file diff --git a/metacitygl/graphics/models/agents.ts b/metacitygl/graphics/models/agents.ts index 451dc0b..7ccc587 100644 --- a/metacitygl/graphics/models/agents.ts +++ b/metacitygl/graphics/models/agents.ts @@ -58,7 +58,8 @@ type uniforms = { } export class AgentModel extends BaseGroupModel { - timeframe: [number, number] | undefined; + timeMin: number | undefined; + timeMax: number | undefined; lastVisible: Movement | undefined; movementArrayTimeSorted: Movement[] = []; @@ -73,7 +74,8 @@ export class AgentModel extends BaseGroupModel { const colors = new THREE.InstancedBufferAttribute(data.colors, 3, false, 1); const dimensions = new THREE.InstancedBufferAttribute(data.dimensions, 3, false, 1); const group = new AgentModel(); - group.timeframe = [data.timestamps[0], data.timestamps[data.timestamps.length - 1]]; + group.timeMin = data.timestamps[0]; + group.timeMax = data.timestamps[data.timestamps.length - 1]; for (let i = 0; i < data.positions.length - 1; i++) { const movement = Movement.create({ @@ -120,8 +122,10 @@ export class AgentModel extends BaseGroupModel { } } - if (this.timeframe !== undefined) - context.timeframe = this.timeframe; + if (this.timeMin !== undefined && this.timeMax !== undefined) { + context.timeMin = this.timeMin; + context.timeMax = this.timeMax; + } } binarySearchMovement(time: number): Movement | undefined { diff --git a/metacitygl/graphics/models/doubleLine.ts b/metacitygl/graphics/models/doubleLine.ts index 17f59ea..a118f45 100644 --- a/metacitygl/graphics/models/doubleLine.ts +++ b/metacitygl/graphics/models/doubleLine.ts @@ -22,12 +22,10 @@ export class DoubleLineModel extends BaseInstancedModel { static create(data: LineData, uniforms: uniforms) { const geometry = new THREE.InstancedBufferGeometry(); geometry.setAttribute('position', new THREE.BufferAttribute(SEGMENT_INSTANCE, 3)); - geometry.setAttribute('lineStart', new THREE.InterleavedBufferAttribute( - new THREE.InstancedInterleavedBuffer(data.positions, 6, 1), 3, 0)); - - geometry.setAttribute('lineEnd', new THREE.InterleavedBufferAttribute( - new THREE.InstancedInterleavedBuffer(data.positions, 6, 1), 3, 3)); + const buffer = new THREE.InstancedInterleavedBuffer(data.positions, 6, 1); + geometry.setAttribute('lineStart', new THREE.InterleavedBufferAttribute(buffer, 3, 0)); + geometry.setAttribute('lineEnd', new THREE.InterleavedBufferAttribute(buffer, 3, 3)); geometry.setAttribute('color', new THREE.InstancedBufferAttribute(data.colors, 3, true, 1)); if (data.ids) diff --git a/metacitygl/graphics/models/geometry/cube.ts b/metacitygl/graphics/models/geometry/cube.ts new file mode 100644 index 0000000..cd33dcd --- /dev/null +++ b/metacitygl/graphics/models/geometry/cube.ts @@ -0,0 +1,16 @@ +import * as THREE from "three"; +import { computeDots } from "../../../utils/utils/normals"; + +export function unitCube() { + const positions = new THREE.BoxGeometry().toNonIndexed(); + const posArr = positions.attributes.position.array as Float32Array; + const dots = computeDots(posArr); + + return { + positions: posArr, + dots, + } +} + + + diff --git a/metacitygl/graphics/models/geometry/tile.ts b/metacitygl/graphics/models/geometry/tile.ts new file mode 100644 index 0000000..c2e3847 --- /dev/null +++ b/metacitygl/graphics/models/geometry/tile.ts @@ -0,0 +1,23 @@ + + + +export function tileGeometry(center: number[], width: number, height: number) { + const hh = height * 0.5; + const hw = width * 0.5; + + //as triangles + const positions = new Float32Array([ + center[0] - hw, center[1] - hh, center[2], + center[0] + hw, center[1] - hh, center[2], + center[0] + hw, center[1] + hh, center[2], + center[0] - hw, center[1] - hh, center[2], + center[0] + hw, center[1] + hh, center[2], + center[0] - hw, center[1] + hh, center[2] + ]); + + const dots = new Float32Array([ + 0, 0, 0, 0, 0, 0 + ]); + + return { positions, dots }; +} \ No newline at end of file diff --git a/metacitygl/graphics/models/line.ts b/metacitygl/graphics/models/line.ts index 709b36f..291b60b 100644 --- a/metacitygl/graphics/models/line.ts +++ b/metacitygl/graphics/models/line.ts @@ -21,11 +21,10 @@ export class LineModel extends BaseInstancedModel { static create(data: LineData, uniforms: uniforms) { const geometry = new THREE.InstancedBufferGeometry(); geometry.setAttribute('position', new THREE.BufferAttribute(SEGMENT_INSTANCE, 3)); - geometry.setAttribute('lineStart', new THREE.InterleavedBufferAttribute( - new THREE.InstancedInterleavedBuffer(data.positions, 6, 1), 3, 0)); + const buffer = new THREE.InstancedInterleavedBuffer(data.positions, 6, 1); + geometry.setAttribute('lineStart', new THREE.InterleavedBufferAttribute(buffer, 3, 0)); - geometry.setAttribute('lineEnd', new THREE.InterleavedBufferAttribute( - new THREE.InstancedInterleavedBuffer(data.positions, 6, 1), 3, 3)); + geometry.setAttribute('lineEnd', new THREE.InterleavedBufferAttribute(buffer, 3, 3)); geometry.setAttribute('color', new THREE.InstancedBufferAttribute(data.colors, 3, true, 1)); diff --git a/metacitygl/graphics/models/mesh.ts b/metacitygl/graphics/models/mesh.ts index 1e5d723..3a884b9 100644 --- a/metacitygl/graphics/models/mesh.ts +++ b/metacitygl/graphics/models/mesh.ts @@ -11,15 +11,15 @@ export class MeshModel extends BaseMeshModel { static create(data: MeshData) { const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(data.positions, 3)); - geometry.setAttribute('normal', new THREE.BufferAttribute(data.normals, 3)); + geometry.setAttribute('dot', new THREE.BufferAttribute(data.dots, 1)); + geometry.setAttribute('color', new THREE.BufferAttribute(data.colors, 3, true)); if (data.ids) - geometry.setAttribute('idcolor', new THREE.BufferAttribute(data.ids, 3)); - - if (data.colors) - geometry.setAttribute('color', new THREE.BufferAttribute(data.colors, 3)); + geometry.setAttribute('idcolor', new THREE.BufferAttribute(data.ids, 3, true)); + const mesh = new MeshModel(geometry, this.defaultMaterial); //just to test (mesh.material as THREE.ShaderMaterial).wireframe = true; return mesh; diff --git a/metacitygl/graphics/models/meshInstanced.ts b/metacitygl/graphics/models/meshInstanced.ts index 18ae01b..984dfa4 100644 --- a/metacitygl/graphics/models/meshInstanced.ts +++ b/metacitygl/graphics/models/meshInstanced.ts @@ -12,10 +12,10 @@ type uniforms = { export class InstancedMeshModel extends BaseInstancedModel { static readonly defaultMaterial = new InstancedMeshMaterial(); - static create(data: InstancedMeshData, uniforms: uniforms): THREE.Object3D { + static create(data: InstancedMeshData, uniforms: uniforms) { const geometry = new THREE.InstancedBufferGeometry(); geometry.setAttribute('position', new THREE.BufferAttribute(data.instancePositions, 3)); - geometry.setAttribute('normal', new THREE.BufferAttribute(data.instanceNormals, 3)); + geometry.setAttribute('dot', new THREE.BufferAttribute(data.instanceDots, 1)); geometry.setAttribute('instanceShift', new THREE.InstancedBufferAttribute(data.positions, 3, false, 1)); diff --git a/metacitygl/graphics/models/pointsInstanced.ts b/metacitygl/graphics/models/pointsInstanced.ts index 8317cba..bebca65 100644 --- a/metacitygl/graphics/models/pointsInstanced.ts +++ b/metacitygl/graphics/models/pointsInstanced.ts @@ -20,7 +20,6 @@ export class PointsInstancedModel extends BaseGroupModel { const group = new PointsInstancedModel(); group.add(points); group.add(mesh); - const swp = uniforms.swapDistance || 1000; group.userData.swapDistance = swp * swp; group.userData.centroid = new THREE.Vector3(...data.centroid); @@ -29,9 +28,8 @@ export class PointsInstancedModel extends BaseGroupModel { } onAdd(context: GraphicsContext): void { - context.onBeforeFrame = () => { - const distSqr = context.navigation.position.distanceToSquared(this.userData.centroid); - //TODO eliminate swaps every frame + context.onNavChange = (_, position: THREE.Vector3) => { + const distSqr = position.distanceToSquared(this.userData.centroid); if (distSqr < this.userData.swapDistance) { this.children[0].visible = false; this.children[1].visible = true; diff --git a/metacitygl/graphics/models/tile.ts b/metacitygl/graphics/models/tile.ts new file mode 100644 index 0000000..03aefa4 --- /dev/null +++ b/metacitygl/graphics/models/tile.ts @@ -0,0 +1,36 @@ +import * as THREE from 'three'; +import { TileData } from '../../utils/types'; +import { MeshUniformMaterial } from '../materials/meshUniformMaterial'; +import { tileGeometry } from './geometry/tile'; +import { BaseMeshModel } from './model'; + + +export class TileModel extends BaseMeshModel { + static readonly defaultMaterial = new MeshUniformMaterial(); + + static create(data: TileData) { + const tile = tileGeometry(data.center, data.width, data.height); + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(tile.positions, 3)); + //geometry.setAttribute('dot', new THREE.BufferAttribute(tile.dots, 1)); + + const mesh = new TileModel(geometry, this.defaultMaterial); + mesh.matrixAutoUpdate = false; + mesh.uniforms = { + modelColor: data.color + }; + return mesh; + } + + toPickable(): void { + this.visible = false; + } + + lighten() { + this.uniforms = { + modelColor: this._uniforms.modelColor.map((c: number) => c * 1.5) + }; + } + + +} diff --git a/metacitygl/graphics/models/tree.ts b/metacitygl/graphics/models/tree.ts new file mode 100644 index 0000000..da63b25 --- /dev/null +++ b/metacitygl/graphics/models/tree.ts @@ -0,0 +1,46 @@ +import { TreeData } from "../../utils/types"; +import { BaseInstancedModel } from "./model"; +import * as THREE from "three"; +import { unitCube } from "./geometry/cube"; +import { TreeMaterial } from "../materials/treeMaterial"; + + +export class TreeModel extends BaseInstancedModel { + static readonly defaultMaterial = new TreeMaterial(); + + static create(data: TreeData) { + const cube = unitCube(); + const geometry = new THREE.InstancedBufferGeometry(); + + geometry.setAttribute('position', new THREE.BufferAttribute(cube.positions, 3)); + geometry.setAttribute('dot', new THREE.BufferAttribute(cube.dots, 1)); + + const buffer = new THREE.InstancedInterleavedBuffer(data.array, 9, 1); + geometry.setAttribute('center', new THREE.InterleavedBufferAttribute(buffer, 3, 0)); + geometry.setAttribute('dimensions', new THREE.InterleavedBufferAttribute(buffer, 3, 3)); + geometry.setAttribute('color', new THREE.InterleavedBufferAttribute(buffer, 3, 6)); + + const count = data.array.length / 9; + const mesh = new TreeModel(geometry, this.defaultMaterial, 0); + mesh.count = count; + mesh.frustumCulled = false; + mesh.matrixAutoUpdate = false; + mesh.instanceMatrix = new THREE.InstancedBufferAttribute(new Float32Array(0), 0); + return mesh; + } + + updateBuffer(data: TreeData) { + const buffer = new THREE.InstancedInterleavedBuffer(data.array, 9, 1); + this.geometry.setAttribute('center', new THREE.InterleavedBufferAttribute(buffer, 3, 0)); + this.geometry.setAttribute('dimensions', new THREE.InterleavedBufferAttribute(buffer, 3, 3)); + this.geometry.setAttribute('color', new THREE.InterleavedBufferAttribute(buffer, 3, 6)); + this.count = data.array.length / 9; + this.geometry.attributes.center.needsUpdate = true; + this.geometry.attributes.dimensions.needsUpdate = true; + this.geometry.attributes.color.needsUpdate = true; + } + + toPickable(): void { + this.visible = false; + } +} \ No newline at end of file diff --git a/metacitygl/utils/assemblers/agents.ts b/metacitygl/utils/assemblers/agents.ts index fdf0aa8..d0f16ac 100644 --- a/metacitygl/utils/assemblers/agents.ts +++ b/metacitygl/utils/assemblers/agents.ts @@ -13,6 +13,12 @@ interface Timestamp { visible: boolean; } + + +// FIXME: needs to be refactored: +// - not necesary to store dimensions for each agent +// - on `toBuffers`, partition into several agent groups to save space + export class AgentAssembler { colors: number[] = []; //ids: number[] = []; @@ -138,7 +144,7 @@ export class AgentAssembler { visible: visibleVis, timestamps: new Float32Array(sortedTimestamps), dimensions: new Float32Array(this.dimensions), - colors: new Float32Array(this.colors), + colors: new Uint8Array(this.colors), //ids: new Float32Array(this.ids), metadata: this.metadata, } diff --git a/metacitygl/utils/assemblers/line.ts b/metacitygl/utils/assemblers/line.ts index 3025b7f..b24ddc0 100644 --- a/metacitygl/utils/assemblers/line.ts +++ b/metacitygl/utils/assemblers/line.ts @@ -11,22 +11,30 @@ export class LineAssembler { static readonly type = "line"; - constructor(private id = 1) {} + constructor(private id = 1, private useMetadata = false) {} - addEdge(from: vec3, to: vec3, rgb: number[], metadata: any) { + addEdge(from: vec3, to: vec3, rgb: number[], metadata: any = {}) { this.positions.push(from.x, from.y, from.z, to.x, to.y, to.z); this.colors.push(rgb[0], rgb[1], rgb[2]); - const idcolor = colorHexToArr(this.id); - this.ids.push(idcolor[0], idcolor[1], idcolor[2]); - this.metadata[this.id] = metadata; - this.id++; + if (this.useMetadata) { + const idcolor = colorHexToArr(this.id); + this.ids.push(idcolor[0], idcolor[1], idcolor[2]); + this.metadata[this.id++] = metadata; + } } pickTransferables(buffers: any) { + let transferables: any[] = []; + if (buffers === undefined) - return []; + return transferables; + + transferables.push(buffers.positions.buffer); + transferables.push(buffers.colors.buffer); + if (buffers.ids !== undefined) + transferables.push(buffers.ids.buffer); - return [buffers.positions.buffer, buffers.colors.buffer, buffers.ids.buffer]; + return transferables; } toBuffers() { @@ -35,9 +43,9 @@ export class LineAssembler { return { positions: new Float32Array(this.positions), - colors: new Float32Array(this.colors), - ids: new Float32Array(this.ids), - metadata: this.metadata, + colors: new Uint8Array(this.colors), + ids: this.useMetadata ? new Uint8Array(this.ids) : undefined, + metadata: this.useMetadata ? this.metadata : undefined, type: LineAssembler.type }; } diff --git a/metacitygl/utils/assemblers/mesh.ts b/metacitygl/utils/assemblers/mesh.ts index 20173e2..22489b8 100644 --- a/metacitygl/utils/assemblers/mesh.ts +++ b/metacitygl/utils/assemblers/mesh.ts @@ -1,6 +1,6 @@ import { computeBBox } from "../utils/bbox"; import { colorHexToArr } from "../utils/color"; -import { computeNormals } from "../utils/normals"; +import { computeDots } from "../utils/normals"; @@ -12,30 +12,42 @@ export class MeshAssembler { static readonly type = "mesh"; - constructor(private id = 1) {} + constructor(private id = 1, private useMetadata = false) {} - addMesh(vertices: Float32Array|number[], rgb: number[], metadata: any) { + addMesh(vertices: Float32Array|number[], rgb: number[], metadata: any = {}) { for(let i = 0; i < vertices.length; i++) this.positions.push(vertices[i]); const vertexCount = vertices.length / 3; - const idcolor = colorHexToArr(this.id); for (let i = 0; i < vertexCount; i++) { this.colors.push(rgb[0], rgb[1], rgb[2]); - this.ids.push(idcolor[0], idcolor[1], idcolor[2]); } - metadata["bbox"] = computeBBox(vertices); - metadata["height"] = metadata["bbox"][1][2] - metadata["bbox"][0][2]; - this.metadata[this.id] = metadata; - this.id++; + if (this.useMetadata) { + const idcolor = colorHexToArr(this.id); + for (let i = 0; i < vertexCount; i++) { + this.ids.push(idcolor[0], idcolor[1], idcolor[2]); + } + const bbox = computeBBox(vertices); + metadata["height"] = bbox[1][2] - bbox[0][2]; + this.metadata[this.id++] = metadata; + } } pickTransferables(buffers: any) { + let transferables: any[] = []; + if (buffers === undefined) - return []; - return [buffers.positions.buffer, buffers.normals.buffer, buffers.colors.buffer, buffers.ids.buffer]; + return transferables; + + transferables.push(buffers.positions.buffer); + transferables.push(buffers.dots.buffer); + transferables.push(buffers.colors.buffer); + if (buffers.ids !== undefined) + transferables.push(buffers.ids.buffer); + + return transferables; } get idCounter() { @@ -48,10 +60,10 @@ export class MeshAssembler { return { positions: new Float32Array(this.positions), - normals: computeNormals(this.positions), - colors: new Float32Array(this.colors), - ids: new Float32Array(this.ids), - metadata: this.metadata, + dots: computeDots(this.positions), + colors: new Uint8Array(this.colors), + ids: this.useMetadata ? new Uint8Array(this.ids) : undefined, + metadata: this.useMetadata ? this.metadata : undefined, type: MeshAssembler.type }; } diff --git a/metacitygl/utils/assemblers/points.ts b/metacitygl/utils/assemblers/points.ts index 5d82f29..31ae227 100644 --- a/metacitygl/utils/assemblers/points.ts +++ b/metacitygl/utils/assemblers/points.ts @@ -9,10 +9,10 @@ export class PointsAssembler { private centroidAcc = [0, 0, 0]; static readonly type = "points"; + constructor(private id = 1, private useMetadata: boolean = false) { + } - constructor(private id = 1) {} - - addPoints(vertices: Float32Array|number[], metadata: any) { + addPoints(vertices: Float32Array|number[], metadata?: any) { for(let i = 0; i < vertices.length; i += 3) { this.positions.push(vertices[i], vertices[i + 1], vertices[i + 2]); this.centroidAcc[0] += vertices[i]; @@ -20,21 +20,29 @@ export class PointsAssembler { this.centroidAcc[2] += vertices[i + 2]; } - const vertexCount = vertices.length / 3; - const idcolor = colorHexToArr(this.id); - for (let i = 0; i < vertexCount; i++) { - this.ids.push(idcolor[0], idcolor[1], idcolor[2]); + if (this.useMetadata) { + const vertexCount = vertices.length / 3; + const idcolor = colorHexToArr(this.id); + for (let i = 0; i < vertexCount; i++) { + this.ids.push(idcolor[0], idcolor[1], idcolor[2]); + } + const bbox = computeBBox(vertices); + metadata["height"] = bbox[1][2] - bbox[0][2]; + this.metadata[this.id++] = metadata; } - - metadata["bbox"] = computeBBox(vertices); - metadata["height"] = metadata["bbox"][1][2] - metadata["bbox"][0][2]; - this.metadata[this.id] = metadata; } pickTransferables(buffers: any) { + let transferables: any[] = []; + if (buffers === undefined) - return []; - return [buffers.positions.buffer, buffers.ids.buffer]; + return transferables; + + transferables.push(buffers.positions.buffer); + if (buffers.ids !== undefined) + transferables.push(buffers.ids.buffer); + + return transferables; } toBuffers() { @@ -45,9 +53,9 @@ export class PointsAssembler { return { positions: new Float32Array(this.positions), - ids: new Float32Array(this.ids), - metadata: this.metadata, centroid: [this.centroidAcc[0] / l, this.centroidAcc[1] / l, this.centroidAcc[2] / l], + ids: this.useMetadata ? new Uint8Array(this.ids) : undefined, + metadata: this.useMetadata ? this.metadata : undefined, type: PointsAssembler.type }; } diff --git a/metacitygl/utils/styles/style.ts b/metacitygl/utils/styles/style.ts index 40735f1..240566d 100644 --- a/metacitygl/utils/styles/style.ts +++ b/metacitygl/utils/styles/style.ts @@ -1,4 +1,4 @@ -import { Metadata } from '../types'; +import { Metadata, MetadataRecord } from '../types'; import { sampleColor } from '../utils/color'; import { StyleRule } from './rule'; import { deserializeRule } from './deserialize'; @@ -18,7 +18,7 @@ export class Style { return this; } - apply(metadata: Metadata) { + apply(metadata: MetadataRecord) { let applyColorIndicator = Math.random(); for (const rule of this.rules) { applyColorIndicator = rule.apply(metadata); diff --git a/metacitygl/utils/types.ts b/metacitygl/utils/types.ts index 0e54113..46941a9 100644 --- a/metacitygl/utils/types.ts +++ b/metacitygl/utils/types.ts @@ -1,23 +1,34 @@ export interface LineData { positions: Float32Array; - colors: Float32Array; - ids?: Float32Array; + colors: Uint8Array; + ids?: Uint8Array; +} + +export interface TileData { + center: number[]; + width: number; + height: number; + color: [number, number, number]; } export interface MeshData { positions: Float32Array; - normals: Float32Array; - ids?: Float32Array; - colors?: Float32Array; + dots: Float32Array; + colors: Uint8Array; + ids?: Uint8Array; } export interface AgentData { positions: Float32Array[]; visible: Float32Array[]; timestamps: Float32Array; - colors: Float32Array; - dimensions: Float32Array; + colors: Uint8Array; + dimensions: Float32Array; //questionalbe, would be nice to remove +} + +export interface TreeData { + array: Float32Array; //center/dim/color } export interface MovementData { @@ -33,9 +44,14 @@ export interface PointData { positions: Float32Array; } +export interface InstanceData { + positions: Float32Array; + dots: Float32Array; +} + export interface InstancedMeshData { instancePositions: Float32Array; - instanceNormals: Float32Array; + instanceDots: Float32Array; positions: Float32Array; } diff --git a/metacitygl/utils/utils/color.ts b/metacitygl/utils/utils/color.ts index ed65acb..fb17514 100644 --- a/metacitygl/utils/utils/color.ts +++ b/metacitygl/utils/utils/color.ts @@ -34,9 +34,9 @@ export function sampleColor(color: number | number[], indicator: number) { } export function colorHexToArr(hex: number): [number, number, number] { - const r = ( hex >> 16 & 255 ) / 255; - const g = ( hex >> 8 & 255 ) / 255; - const b = ( hex & 255 ) / 255; + const r = ( hex >> 16 & 255 ); + const g = ( hex >> 8 & 255 ); + const b = ( hex & 255 ); return [r, g, b]; } diff --git a/metacitygl/utils/utils/gltf.ts b/metacitygl/utils/utils/gltf.ts index d2ee7f7..6632a87 100644 --- a/metacitygl/utils/utils/gltf.ts +++ b/metacitygl/utils/utils/gltf.ts @@ -1,6 +1,6 @@ import { load } from '@loaders.gl/core'; import { GLTFLoader } from '@loaders.gl/gltf'; -import { computeNormals } from './normals'; +import { computeDots } from './normals'; export async function loadGLTF(model: string) { @@ -30,11 +30,11 @@ export async function loadGLTF(model: string) { } } - const normals = computeNormals(positions); const posArr = new Float32Array(positions); + const dots = computeDots(positions); return { positions: posArr, - normals: normals + dots: dots } } \ No newline at end of file diff --git a/metacitygl/utils/utils/normals.ts b/metacitygl/utils/utils/normals.ts index 6dcfd1b..413fb3c 100644 --- a/metacitygl/utils/utils/normals.ts +++ b/metacitygl/utils/utils/normals.ts @@ -1,9 +1,16 @@ + +const TOP_NORMAL = [0, 0, -1]; + function cross(a: number[], b: number[], out: number[]) { out[0] = a[1] * b[2] - a[2] * b[1]; out[1] = a[2] * b[0] - a[0] * b[2]; out[2] = a[0] * b[1] - a[1] * b[0]; } +function dot(a: number[], b: number[]) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + function normalize(v: number[]) { const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); v[0] /= length; @@ -11,13 +18,14 @@ function normalize(v: number[]) { v[2] /= length; } -export function computeNormals(positions: number[]) { - const normals = new Float32Array(positions.length); +export function computeDots(positions: number[]|Float32Array) { + const dots = new Float32Array(positions.length / 3); const v2 = [0, 0, 0]; const v3 = [0, 0, 0]; const n = [0, 0, 0]; + let d, c, j = 0; - for (let i = 0; i < positions.length;) { + for (let i = 0; i < positions.length; i += 9) { v2[0] = positions[i + 3] - positions[i]; v2[1] = positions[i + 4] - positions[i + 1]; v2[2] = positions[i + 5] - positions[i + 2]; @@ -28,16 +36,12 @@ export function computeNormals(positions: number[]) { cross(v2, v3, n); normalize(n); - normals[i++] = n[0]; - normals[i++] = n[1]; - normals[i++] = n[2]; - normals[i++] = n[0]; - normals[i++] = n[1]; - normals[i++] = n[2]; - normals[i++] = n[0]; - normals[i++] = n[1]; - normals[i++] = n[2]; + c = dot(n, TOP_NORMAL); + d = Math.abs(c); + dots[j++] = d; + dots[j++] = d; + dots[j++] = d; } - return normals; + return dots; } \ No newline at end of file diff --git a/package.json b/package.json index 251ae10..01f1c47 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metacitygl", - "version": "0.0.4", + "version": "0.1.0", "type": "module", "repository": { "type": "git", @@ -13,6 +13,11 @@ "threejs", "react" ], + "author": { + "name": "Vojtěch Tomas", + "email": "hello@vojtatom.cz", + "url": "https://vojtatom.cz" + }, "files": [ "dist/**/*" ],