diff options
-rw-r--r-- | viewer/helper.ts | 5 | ||||
-rw-r--r-- | viewer/main.ts | 98 | ||||
-rw-r--r-- | viewer/resources.ts | 154 |
3 files changed, 222 insertions, 35 deletions
diff --git a/viewer/helper.ts b/viewer/helper.ts new file mode 100644 index 0000000..e8b675f --- /dev/null +++ b/viewer/helper.ts @@ -0,0 +1,5 @@ +import { AABB } from "./resources.ts"; + +export function aabb_overlap(a: AABB, b: AABB): boolean { + return true +}
\ No newline at end of file diff --git a/viewer/main.ts b/viewer/main.ts index 5aa1df6..e0c2fde 100644 --- a/viewer/main.ts +++ b/viewer/main.ts @@ -1,5 +1,8 @@ /// <reference lib="dom" /> +import { aabb_overlap } from "./helper.ts"; +import { AABB, get_graphics_part, get_prefab, get_respackentry, get_spatialindex, GraphicsPart, Prefab, Resource, SpatialIndex } from "./resources.ts"; + const canvas = document.createElement("canvas") canvas.style.position = "absolute" canvas.style.top = "0px" @@ -9,53 +12,78 @@ canvas.style.height = "100vh" document.body.append(canvas) const ctx = canvas.getContext("2d")! +let loader: Loader | undefined; + function draw() { ctx.fillStyle = "black" ctx.fillRect(0, 0, canvas.width, canvas.height) + + loader?.root.children + + requestAnimationFrame(draw) } canvas.addEventListener("resize", () => draw()) draw() -class Resource<T> { - constructor(public hash: Uint8Array) { } - toString() { - return Array.from(this.hash) - .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() - } -} - -function read_res_table(buffer: Uint8Array): { [key: string]: Uint8Array[] } { - const view = new DataView(buffer.buffer) - let p = 0 - const out: { [key: string]: Uint8Array[] } = {} - while (p < view.byteLength) { - const key_len = view.getInt16(p, true) - p += 2 - const value_len = view.getInt16(p, true) - p += 2 - const key = String.fromCharCode(...buffer.slice(p, p + key_len)) - p += key_len - const value = buffer.slice(p, p + value_len) - p += value_len - out[key] ??= [] - out[key].push(value) - } - return out -} 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 entry_res.download_raw() - console.log(read_res_table(entry)) + 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"); + +} +init() + +let limit = 100; + +class Loader { + view: AABB + root: SNode + constructor(root: SpatialIndex) { + this.view = { min: { x: 0, y: 0, z: 0 }, max: { x: 1, y: 1, z: 1 } } + this.root = new SNode(root) + } + async update() { + await this.root.load(this.view) + } +} +class SNode { + children: (SNode | undefined)[] + prefab?: Prefab + graphics?: GraphicsPart + aabb?: AABB + constructor(public data: SpatialIndex) { + this.children = new Array(data.child.length).fill(undefined) + } + async load(view: AABB) { + if (this.data.prefab && !this.prefab) this.prefab = await get_prefab(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 + } + await this.children[i]!.load(view) + } + } + draw() { + for (const c of this.children) { + if (c) c.draw() + } + if (!this.graphics) return + for (const c of this.graphics.read()) { + console.log(c); + } + } } -init()
\ No newline at end of file diff --git a/viewer/resources.ts b/viewer/resources.ts new file mode 100644 index 0000000..0340e31 --- /dev/null +++ b/viewer/resources.ts @@ -0,0 +1,154 @@ + +export class Resource<T> { + constructor(public hash: Uint8Array) { } + toString() { + return Array.from(this.hash) + .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) { + const view = new DataView(buffer.buffer) + let p = 0 + while (p < view.byteLength) { + const key_len = view.getInt16(p, true) + p += 2 + const value_len = view.getInt16(p, true) + p += 2 + const key = String.fromCharCode(...buffer.slice(p, p + key_len)) + p += key_len + const value = buffer.slice(p, p + value_len) + p += value_len + cb(key, value) + } +} + +function get_vec3(b: Uint8Array): Vec3 { + const k = new DataView(b.buffer) + return { + x: k.getFloat32(0, true), + y: k.getFloat32(4, true), + z: k.getFloat32(8, true), + } +} +function get_aabb(b: Uint8Array): AABB { + return { + min: get_vec3(b.slice(0, 12)), + max: get_vec3(b.slice(12, 24)) + } +} + +export async function get_respackentry(r: Resource<RespackEntry>): Promise<RespackEntry> { + console.log(`load respackentry ${r}`); + const buf = await r.download_raw(); + const o: RespackEntry = {} + read_res_table(buf, (key, value) => { + switch (key) { + case "c_spatial_index": + o.c_spatial_index = new Resource(value) + break + default: + } + }); + return o +} +export async function get_spatialindex(r: Resource<SpatialIndex>): Promise<SpatialIndex> { + // console.log(`load spatialindex ${r}`); + const buf = await r.download_raw(); + const o: SpatialIndex = { child: [] } + read_res_table(buf, (key, value) => { + switch (key) { + case "prefab": + o.prefab = new Resource(value) + break + case "child": + o.child.push([ + get_aabb(value.slice(0, 24)), + new Resource(value.slice(24, 56)) + ]) + break + default: + } + }); + return o +} +export async function get_prefab(r: Resource<Prefab>): Promise<Prefab> { + console.log(`load prefab ${r}`); + const buf = await r.download_raw(); + const o: Prefab = { graphics: [] } + read_res_table(buf, (key, value) => { + switch (key) { + case "graphics": + o.graphics.push([undefined as unknown as Affine3, new Resource(value.slice(48, 80))]) + break + default: + } + }); + return o +} +export async function get_graphics_part(r: Resource<GraphicsPart>): Promise<GraphicsPart> { + console.log(`load graphics ${r}`); + return new GraphicsPart(await r.download_raw()) +} + +export interface Vec3 { x: number, y: number, z: number } +export interface AABB { min: Vec3, max: Vec3 } +export interface Mat3 { x: Vec3, y: Vec3, z: Vec3 } +export interface Affine3 { basis: Mat3, origin: Vec3 } + +export interface SpatialIndex { + level?: number + prefab?: Resource<undefined> + child: [AABB, Resource<undefined>][] +} +export interface RespackEntry { + c_spatial_index?: Resource<SpatialIndex> +} +export interface Prefab { + graphics: [Affine3, Resource<GraphicsPart>][] +} +export class GraphicsPart { + commands: GraphicsCommand[] | undefined + constructor(public buffer: Uint8Array) { } + + read(): GraphicsCommand[] { + if (this.commands) return this.commands + + const out: GraphicsCommand[] = [] + let p = 0 + while (p < this.buffer.length) { + const step = this.buffer[p + 0] + const kind = this.buffer[p + 1] + p += 2 + if (kind == 0) { + if (step == 0) out.push({ no_fill: true }) + else if (step == 1) out.push({ fill: this.buffer[p + 0] }) + else throw new Error("bbb"); + } else if (kind == 1) { + if (step == 0) out.push({ no_stroke: true }) + else if (step == 1) out.push({ stroke: this.buffer[p + 0] }) + else throw new Error("ccc"); + } + else throw new Error("aaa"); + + p += step + } + + this.commands = out + return out + } +} + +export interface GraphicsCommand { + no_fill?: true, + fill?: number, + no_stroke?: true + stroke?: number + point?: Vec3 +}
\ No newline at end of file |