From a231b309dcf2839a0fdd0651fe84af8694f38c4d Mon Sep 17 00:00:00 2001 From: metamuffin Date: Tue, 1 Apr 2025 16:03:53 +0200 Subject: websocket loader --- src/main.rs | 30 +++++++++++++----- viewer/loader.ts | 71 ++++++++++++++++++++++++++++++++++++++++++ viewer/main.ts | 90 +++++++++++++++++++++++++++++++---------------------- viewer/resources.ts | 25 +++++++-------- 4 files changed, 157 insertions(+), 59 deletions(-) create mode 100644 viewer/loader.ts diff --git a/src/main.rs b/src/main.rs index 1ce78ed..662fc05 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ pub mod spatial; use anyhow::Result; -use glam::{Affine3A, DAffine3, DVec3, Vec3}; +use glam::{Affine3A, DAffine3, DVec3}; use indicatif::ProgressIterator; use log::{debug, info}; use osm_pbf_reader::{Blobs, data::primitives::Primitive}; @@ -68,7 +68,7 @@ fn main() -> Result<()> { let store = ResourceStore::new_memory(); info!("Exporting resources..."); - let (_, root) = export_level(&tree, &store)?; + let (_, root) = export_level(&tree, &store, &mut Vec::new())?; let entry = Some(store.set(&RespackEntry { c_spatial_index: vec![root], ..Default::default() @@ -81,26 +81,40 @@ fn main() -> Result<()> { Ok(()) } -fn export_level(node: &Octtree, store: &ResourceStore) -> Result<(AABB, Resource)> { +fn export_level( + node: &Octtree, + store: &ResourceStore, + important_features: &mut Vec, +) -> Result<(AABB, Resource)> { + let mut lod_features = Vec::new(); let child = node .children .iter() .flat_map(|e| e.as_slice()) .flatten() .flatten() - .map(|o| export_level(o, store)) + .map(|o| export_level(o, store, &mut lod_features)) .collect::>>()?; - let prefab = if node.elems.is_empty() { - None - } else { + let prefab = { + let points = if node.elems.is_empty() { + lod_features + } else { + node.elems.iter().map(|(e, _id)| *e).collect() + }; + let local_origin = node.center; let mut g = GraphicsPart::default(); use weareshared::graphics::GraphicsCommand::*; g.push(StrokeWidth(0.001)); g.push(Stroke(5)); - g.push(Point(Vec3::ZERO)); + for (i, p) in points.into_iter().enumerate() { + g.push(Point((p - local_origin).as_vec3())); + if i % 8 == 0 { + important_features.push(p); + } + } Some(store.set(&Prefab { transform: Some(DAffine3::from_translation(local_origin)), diff --git a/viewer/loader.ts b/viewer/loader.ts new file mode 100644 index 0000000..487102f --- /dev/null +++ b/viewer/loader.ts @@ -0,0 +1,71 @@ +import { Resource, RespackEntry } from "./resources.ts"; + +export abstract class Loader { + num_loading: number = 0 + abstract download(res: Resource): Promise + abstract get_entry(): Promise>; + abstract wait_ready(): Promise +} + +export class HTTPLoader extends Loader { + constructor() { + super(); + } + // deno-lint-ignore require-await + async wait_ready(): Promise { + return + } + async download(res: Resource): Promise { + this.num_loading += 1 + const resp = await fetch(`http://127.0.0.1:28556/${res.toString()}`) + if (!resp.ok) throw new Error("aaaa"); + const buf = await resp.bytes() + this.num_loading -= 1 + return buf + } + async get_entry(): Promise> { + const resp = await fetch("http://127.0.0.1:28556/entry") + if (!resp.ok) throw new Error("aaaa"); + return new Resource(await resp.bytes()) + } +} +export class WSLoader extends Loader { + ws: WebSocket + queue: ((r: Uint8Array) => void)[] = [] + constructor() { + super() + this.ws = new WebSocket(`http://127.0.0.1:28557/`) + this.ws.onopen = () => console.log("open"); + this.ws.onclose = () => console.log("close"); + this.ws.onmessage = m => { + if (typeof m.data == "string") throw new Error(m.data); + if (m.data instanceof Blob) { + const h = this.queue.shift()! + m.data.arrayBuffer() + .then(b => new Uint8Array(b)) + .then(h) + .then(() => this.num_loading -= 1) + } + } + } + wait_ready(): Promise { + return new Promise(resolve => { + if (this.ws.readyState == this.ws.OPEN) return resolve() + else this.ws.addEventListener("open", () => resolve()) + }) + } + download(res: Resource): Promise { + return new Promise(resolve => { + this.num_loading += 1 + this.queue.push(resolve) + this.ws.send(res.hash) + }) + } + get_entry(): Promise> { + return new Promise(resolve => { + this.num_loading += 1 + this.queue.push(buf => resolve(new Resource(buf))) + this.ws.send("entry") + }) + } +} diff --git a/viewer/main.ts b/viewer/main.ts index f59bb1c..a85443a 100644 --- a/viewer/main.ts +++ b/viewer/main.ts @@ -1,6 +1,7 @@ /// import { aabb_overlap } from "./helper.ts"; +import { WSLoader } from "./loader.ts"; import { AABB, get_graphics_part, get_prefab, get_respackentry, get_spatialindex, GraphicsPart, Prefab, Resource, SpatialIndex } from "./resources.ts"; const canvas = document.createElement("canvas") @@ -12,13 +13,18 @@ canvas.style.height = "100vh" document.body.append(canvas) const ctx = canvas.getContext("2d")! -let loader: Loader | undefined; +let thing: Thing | undefined; +const loader = new WSLoader() function draw() { ctx.fillStyle = "black" ctx.fillRect(0, 0, canvas.width, canvas.height) - loader?.root.draw() + ctx.fillStyle = "white" + ctx.font = "64px sans-serif" + ctx.fillText(`${loader.num_loading}`, 20, 80) + + thing?.root.draw() requestAnimationFrame(draw) } @@ -26,29 +32,28 @@ function resize() { canvas.width = globalThis.innerWidth canvas.height = globalThis.innerHeight } -canvas.addEventListener("resize", () => resize()) +globalThis.addEventListener("resize", () => resize()) resize() draw() - async function init() { - const resp = await fetch("http://127.0.0.1:28556/entry") - if (!resp.ok) throw new Error("aaaa"); - const entry_res = new Resource(await resp.bytes()) - - const entry = await get_respackentry(entry_res) - const root = await get_spatialindex(entry.c_spatial_index!) - loader = new Loader(root) - console.log("begin load"); - await loader.update() - console.log("end load"); - + await loader.wait_ready() + const entry_res = await loader.get_entry() + const entry = await get_respackentry(loader, entry_res) + const root = await get_spatialindex(loader, entry.c_spatial_index!) + thing = new Thing(root) + loader_loop() +} +async function loader_loop() { + while (1) { + await thing!.update() + await new Promise(r => setTimeout(r, 100)) + } } -init() -let limit = 10000; +init() -class Loader { +class Thing { view: AABB root: SNode constructor(root: SpatialIndex) { @@ -59,34 +64,45 @@ class Loader { await this.root.load(this.view) } } + +let num_loaded = 0 + class SNode { - children: (SNode | undefined)[] + children?: SNode[] prefab?: Prefab graphics?: GraphicsPart aabb?: AABB - constructor(public data: SpatialIndex) { - this.children = new Array(data.child.length).fill(undefined) - } - async load(view: AABB) { + constructor(public data: SpatialIndex) { } + + async load_graphics() { if (this.data.prefab && !this.prefab) - this.prefab = await get_prefab(this.data.prefab) + this.prefab = await get_prefab(loader, this.data.prefab) if (this.prefab?.graphics[0] && !this.graphics) - this.graphics = await get_graphics_part(this.prefab.graphics[0][1]) - for (let i = 0; i < this.data.child.length; i++) { - const [aabb, child_data_hash] = this.data.child[i]; - if (!aabb_overlap(aabb, view)) continue - if (this.children[i] == undefined) { - this.children[i] = new SNode(await get_spatialindex(child_data_hash)) - limit -= 1 - if (limit <= 0) return + this.graphics = await get_graphics_part(loader, this.prefab.graphics[0][1]) + } + async load(view: AABB) { + if (!this.aabb || aabb_overlap(this.aabb, view)) { + if (this.children) { + await Promise.all(this.children.map(c => c.load(view))) + } else if (this.data.child.length) { + if (num_loaded > 300) return + num_loaded += 1 + this.children = await Promise.all(this.data.child.map(async c => { + const n = new SNode(await get_spatialindex(loader, c[1])) + n.aabb = c[0] + await n.load_graphics() + return n + })) + } + } else { + if (this.children) { + num_loaded -= 1 + this.children = undefined } - await this.children[i]!.load(view) } } draw() { - for (const c of this.children) { - if (c) c.draw() - } + if (this.children) return this.children.forEach(c => c.draw()) if (!this.graphics) return ctx.fillStyle = "red" ctx.save() @@ -94,7 +110,7 @@ class SNode { ctx.translate(-0.155, -0.63) ctx.translate(this.prefab!.transform![10], this.prefab!.transform![11]) for (const c of this.graphics.read()) { - if (c.point) ctx.fillRect(c.point.x, c.point.y, 0.00003, 0.00003) + if (c.point) ctx.fillRect(c.point.z, c.point.y, 0.00001, 0.00001) } ctx.restore() } diff --git a/viewer/resources.ts b/viewer/resources.ts index 0e9162e..c0eaba3 100644 --- a/viewer/resources.ts +++ b/viewer/resources.ts @@ -1,3 +1,4 @@ +import { Loader } from "./loader.ts"; export class Resource { constructor(public hash: Uint8Array) { } @@ -6,11 +7,7 @@ export class Resource { .map(e => e.toString(16).padStart(2, "0")) .join("") } - async download_raw() { - const res = await fetch(`http://127.0.0.1:28556/${this.toString()}`) - if (!res.ok) throw new Error("aaaa"); - return await res.bytes() - } + } export function read_res_table(buffer: Uint8Array, cb: (key: string, value: Uint8Array) => void) { @@ -54,9 +51,9 @@ async function ob_cached(r: Resource, make: () => Promise): Promise return m } -export async function get_respackentry(r: Resource): Promise { +export async function get_respackentry(l: Loader, r: Resource): Promise { return await ob_cached(r, async () => { - const buf = await r.download_raw(); + const buf = await l.download(r); const o: RespackEntry = {} read_res_table(buf, (key, value) => { switch (key) { @@ -69,9 +66,9 @@ export async function get_respackentry(r: Resource): Promise): Promise { +export async function get_spatialindex(l: Loader, r: Resource): Promise { return await ob_cached(r, async () => { - const buf = await r.download_raw(); + const buf = await l.download(r); const o: SpatialIndex = { child: [] } read_res_table(buf, (key, value) => { switch (key) { @@ -90,9 +87,9 @@ export async function get_spatialindex(r: Resource): Promise): Promise { +export async function get_prefab(l: Loader, r: Resource): Promise { return await ob_cached(r, async () => { - const buf = await r.download_raw(); + const buf = await l.download(r); const o: Prefab = { graphics: [] } read_res_table(buf, (key, value) => { let v, x = 0 @@ -123,9 +120,9 @@ export async function get_prefab(r: Resource): Promise { return o }) } -export async function get_graphics_part(r: Resource): Promise { +export async function get_graphics_part(l: Loader, r: Resource): Promise { return await ob_cached(r, async () => { - return new GraphicsPart(await r.download_raw()) + return new GraphicsPart(await l.download(r)) }) } @@ -192,4 +189,4 @@ export interface GraphicsCommand { stroke?: number stroke_width?: number point?: Vec3 -} \ No newline at end of file +} -- cgit v1.2.3-70-g09d2