From 28f0b5478b016707f34b0a270ae1d1f9b9b6115e Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 14 Jan 2025 13:53:49 -0600 Subject: [PATCH] fast sprite rendering with quadtrees --- scripts/components.js | 322 ++++++++++++++++++++++++++++++++++++++++++ scripts/render.js | 25 +++- source/HandmadeMath.c | 40 +++--- source/jsffi.c | 190 +++++++++++++++---------- source/qjs_macros.h | 1 - source/quadtree.c | 9 +- source/transform.c | 15 +- source/transform.h | 4 + 8 files changed, 497 insertions(+), 109 deletions(-) diff --git a/scripts/components.js b/scripts/components.js index a3edbdca..6f8255b7 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -1,5 +1,321 @@ var component = {}; +class LooseQuadtree { + constructor(boundary, capacity=4, looseness=1.25) { + this.boundary = boundary // {x, y, width, height} + this.capacity = capacity // max items before subdivision + this.looseness = looseness // factor by which nodes expand + this.sprites = [] // sprite objects stored here + this.divided = false + + // "loose boundary": expand our node’s bounding box + // so items that cross the boundary lines can be placed deeper + this.looseBounds = this.getLooseBounds(boundary) + } + + // Expand boundary by the looseness factor (looseness >= 1) + getLooseBounds(boundary) { + // For each dimension, we expand half on each side + let marginW = (this.looseness - 1) * boundary.width / 2 + let marginH = (this.looseness - 1) * boundary.height / 2 + + return { + x: boundary.x - marginW, + y: boundary.y - marginH, + width: boundary.width + marginW * 2, + height: boundary.height + marginH * 2 + } + } + + subdivide() { + let x = this.boundary.x + let y = this.boundary.y + let w = this.boundary.width / 2 + let h = this.boundary.height / 2 + + this.northwest = new LooseQuadtree({x, y, width: w, height: h}, this.capacity, this.looseness) + this.northeast = new LooseQuadtree({x: x + w, y, width: w, height: h}, this.capacity, this.looseness) + this.southwest = new LooseQuadtree({x, y: y + h, width: w, height: h}, this.capacity, this.looseness) + this.southeast = new LooseQuadtree({x: x + w, y: y + h, width: w, height: h}, this.capacity, this.looseness) + + this.divided = true + } + + insert(sprite) { + let rect = sprite.rect; + + // If outside *loose* bounds, ignore + if (!geometry.rect_intersects(this.looseBounds, rect)) return false + + // If we have room and no subdivision, store it here + if (this.sprites.length < this.capacity && !this.divided) { + this.sprites.push(sprite) + return true + } + + // Otherwise, subdivide if not already + if (!this.divided) this.subdivide() + + // Try placing into children + if (this.northwest.insert(sprite)) return true + if (this.northeast.insert(sprite)) return true + if (this.southwest.insert(sprite)) return true + if (this.southeast.insert(sprite)) return true + + // If it doesn't cleanly fit (overlaps multiple child boundaries), + // store at this level + this.sprites.push(sprite) + return true + } + + query(range, found=[]) { + // If query doesn't intersect our *loose* boundary, no need to check further + if (!geometry.rect_intersects(this.looseBounds, range)) return found + + // Check sprites in this node + for (let s of this.sprites) + if (geometry.rect_intersects(s.rect, range)) found.push(s) + + // If subdivided, recurse + if (this.divided) { + this.northwest.query(range, found) + this.northeast.query(range, found) + this.southwest.query(range, found) + this.southeast.query(range, found) + } + return found + } +} + +class Quadtree { + constructor(boundary, capacity=4) { + this.boundary = boundary // {x, y, width, height} for this node + this.capacity = capacity // max sprites before subdividing + this.sprites = [] // sprite objects stored in this node + this.divided = false // has this node subdivided? + } + + subdivide() { + let x = this.boundary.x + let y = this.boundary.y + let w = this.boundary.width / 2 + let h = this.boundary.height / 2 + + this.northwest = new Quadtree({x, y, width: w, height: h}, this.capacity) + this.northeast = new Quadtree({x: x + w, y, width: w, height: h}, this.capacity) + this.southwest = new Quadtree({x, y: y + h, width: w, height: h}, this.capacity) + this.southeast = new Quadtree({x: x + w, y: y + h, width: w, height: h}, this.capacity) + + this.divided = true + } + + insert(sprite) { + // Get the sprite's bounding rect + let rect = sprite.rect; + + // If it doesn't intersect this quadtree's boundary, ignore + if (!geometry.rect_intersects(this.boundary, rect)) return false + + // If there's room here and no subdivision yet, just store it + if (this.sprites.length < this.capacity && !this.divided) { + this.sprites.push(sprite) + return true + } + + // Otherwise, subdivide if not done yet + if (!this.divided) this.subdivide() + + // Try inserting into children + if (this.northwest.insert(sprite)) return true + if (this.northeast.insert(sprite)) return true + if (this.southwest.insert(sprite)) return true + if (this.southeast.insert(sprite)) return true + + // If it doesn't cleanly fit into a child (spans multiple quadrants), store here + this.sprites.push(sprite) + return true + } + + // Query all sprites that intersect the given range (e.g. your camera rect) + query(range, found=[]) { + // If there's no overlap, nothing to do + if (!geometry.rect_intersects(range, this.boundary)) return found + + // Check sprites in this node + for (let s of this.sprites) + if (geometry.rect_intersects(range, s.rect)) found.push(s) + + // Recursively query children if subdivided + if (this.divided) { + this.northwest.query(range, found) + this.northeast.query(range, found) + this.southwest.query(range, found) + this.southeast.query(range, found) + } + return found + } +} + +class RNode { + constructor() { + this.children = [] + this.bbox = null + this.leaf = true + this.height = 1 + } +} + +class RTree { + constructor(maxEntries = 4) { + this.maxEntries = maxEntries + this.minEntries = Math.max(2, Math.floor(maxEntries * 0.4)) + this.root = new RNode() + } + + _getBBox(child) { + return child instanceof RNode ? child.bbox : child.rect + } + + _unionBBox(a, b) { + if (!a) return {...b} + if (!b) return {...a} + const minX = Math.min(a.x, b.x) + const minY = Math.min(a.y, b.y) + const maxX = Math.max(a.x + a.width, b.x + b.width) + const maxY = Math.max(a.y + a.height, b.y + b.height) + return {x: minX, y: minY, width: maxX - minX, height: maxY - minY} + } + + insert(item) { + if (!item) return + + if (this.root.leaf && this.root.children.length === 0) { + this.root.children.push(item) + this.root.bbox = {...item.rect} + return + } + + let node = this._chooseSubtree(this.root, item) + node.children.push(item) + this._extend(node, item) + + while (node && node.children.length > this.maxEntries) { + this._split(node) + node = this._findParent(this.root, node) + } + } + + _chooseSubtree(node, item) { + while (!node.leaf) { + let minEnlargement = Infinity + let bestChild = null + + for (const child of node.children) { + const enlargement = this._enlargement(child.bbox, item) + if (enlargement < minEnlargement) { + minEnlargement = enlargement + bestChild = child + } + } + node = bestChild || node.children[0] + } + return node + } + + _enlargement(bbox, item) { + if (!bbox) return Number.MAX_VALUE + const itemBBox = this._getBBox(item) + const union = this._unionBBox(bbox, itemBBox) + return (union.width * union.height) - (bbox.width * bbox.height) + } + + _split(node) { + const items = node.children + + items.sort((a, b) => { + const aBBox = this._getBBox(a) + const bBBox = this._getBBox(b) + return aBBox.x - bBBox.x + }) + + const splitIndex = Math.ceil(items.length / 2) + const group1 = items.slice(0, splitIndex) + const group2 = items.slice(splitIndex) + + if (node === this.root) { + this.root = new RNode() + this.root.leaf = false + node.children = group1 + node.bbox = this._getBBoxes(group1) + + const sibling = new RNode() + sibling.leaf = node.leaf + sibling.children = group2 + sibling.bbox = this._getBBoxes(group2) + + this.root.children = [node, sibling] + this.root.bbox = this._unionBBox(node.bbox, sibling.bbox) + } else { + node.children = group1 + node.bbox = this._getBBoxes(group1) + + const sibling = new RNode() + sibling.leaf = node.leaf + sibling.children = group2 + sibling.bbox = this._getBBoxes(group2) + + const parent = this._findParent(this.root, node) + parent.children.push(sibling) + parent.bbox = this._unionBBox(parent.bbox, sibling.bbox) + } + } + + _getBBoxes(items) { + let bbox = null + for (const item of items) { + bbox = this._unionBBox(bbox, this._getBBox(item)) + } + return bbox + } + + _extend(node, item) { + const itemBBox = this._getBBox(item) + node.bbox = node.bbox ? this._unionBBox(node.bbox, itemBBox) : {...itemBBox} + } + + _findParent(node, target, parent = null) { + if (!node || node === target) return parent + if (!node.leaf) { + for (const child of node.children) { + if (child instanceof RNode) { + const result = this._findParent(child, target, node) + if (result) return result + } + } + } + return null + } + + query(range, results = []) { + const stack = [this.root] + while (stack.length > 0) { + const node = stack.pop() + if (!node.bbox || !geometry.rect_intersects(node.bbox, range)) continue + + if (node.leaf) { + for (const item of node.children) { + if (geometry.rect_intersects(item.rect, range)) { + results.push(item) + } + } + } else { + stack.push(...node.children) + } + } + return results + } +} + function make_point_obj(o, p) { return { pos: p, @@ -21,6 +337,12 @@ frog = { } */ +var world = {x:-1000,y:-1000, width:3000, height:3000}; +//globalThis.sprite_qt = new Quadtree({x:-1000,y:-1000, width:3000, height:3000}, 10); +//globalThis.sprite_qt = new LooseQuadtree(world, 10, 1.2); +//globalThis.sprite_qt = new RTree(10) +globalThis.sprite_qt = os.make_quadtree(world, 4); + var spritetree = os.make_quadtree([0,0], [10000,10000]); globalThis.spritetree = spritetree; diff --git a/scripts/render.js b/scripts/render.js index 709f88b9..effa77f4 100644 --- a/scripts/render.js +++ b/scripts/render.js @@ -758,12 +758,33 @@ function insertion_sort(arr, cmp) var culled; +var treed; function sprites_to_queue(sprites, ysort = false) { + if (!treed) { + for (var sp of allsprites) { + sp.rect = sp.transform.torect(); + sprite_qt.insert(sp) + } + treed = true; + } + + var pos = prosperon.camera.transform.pos; + var size = prosperon.camera.size; + var camrect = { + x:pos.x-size.x/2, + y:pos.y-size.y/2, + width:size.x, + height:size.y + }; + var culled = sprite_qt.query(camrect) + if (culled.length == 0) return []; + //var culled = spritetree.find(prosperon.camera.pos, prosperon.camera.size); //var culled = spritetree.find(prosperon.camera.transform.pos,prosperon.camera.size) - var culled = os.cull_sprites(allsprites,prosperon.camera); - return render._main.make_sprite_queue(culled, prosperon.camera, sprite_pipeline) +// var culled = os.cull_sprites(allsprites,prosperon.camera); + var cmd = render._main.make_sprite_queue(culled, prosperon.camera, sprite_pipeline); + return cmd; var sprites = allsprites; // for (var i = 0; i < sprites.length; i++) // sprites[i].transform.clean(); diff --git a/source/HandmadeMath.c b/source/HandmadeMath.c index c03b1a3e..c95c9351 100644 --- a/source/HandmadeMath.c +++ b/source/HandmadeMath.c @@ -583,31 +583,31 @@ HMM_Vec4 HMM_LerpV4(HMM_Vec4 A, float Time, HMM_Vec4 B) { Result.NEON = sum; #elif defined(HANDMADE_MATH__USE_SSE) - Result.SSE = _mm_mul_ps(_mm_shuffle_ps(Left.SSE, Left.SSE, 0x00), Right.Columns[0].SSE); - Result.SSE = _mm_add_ps(Result.SSE, _mm_mul_ps(_mm_shuffle_ps(Left.SSE, Left.SSE, 0x55), Right.Columns[1].SSE)); - Result.SSE = _mm_add_ps(Result.SSE, _mm_mul_ps(_mm_shuffle_ps(Left.SSE, Left.SSE, 0xaa), Right.Columns[2].SSE)); - Result.SSE = _mm_add_ps(Result.SSE, _mm_mul_ps(_mm_shuffle_ps(Left.SSE, Left.SSE, 0xff), Right.Columns[3].SSE)); + Result.SSE = _mm_mul_ps(_mm_shuffle_ps(Left->SSE, Left->SSE, 0x00), Right->Columns[0].SSE); + Result.SSE = _mm_add_ps(Result.SSE, _mm_mul_ps(_mm_shuffle_ps(Left->SSE, Left->SSE, 0x55), Right->Columns[1].SSE)); + Result.SSE = _mm_add_ps(Result.SSE, _mm_mul_ps(_mm_shuffle_ps(Left->SSE, Left->SSE, 0xaa), Right->Columns[2].SSE)); + Result.SSE = _mm_add_ps(Result.SSE, _mm_mul_ps(_mm_shuffle_ps(Left->SSE, Left->SSE, 0xff), Right->Columns[3].SSE)); #else - Result.X = Left.Elements[0] * Right.Columns[0].X; - Result.Y = Left.Elements[0] * Right.Columns[0].Y; - Result.Z = Left.Elements[0] * Right.Columns[0].Z; - Result.W = Left.Elements[0] * Right.Columns[0].W; + Result.X = Left->Elements[0] * Right->Columns[0].X; + Result.Y = Left->Elements[0] * Right->Columns[0].Y; + Result.Z = Left->Elements[0] * Right->Columns[0].Z; + Result.W = Left->Elements[0] * Right->Columns[0].W; - Result.X += Left.Elements[1] * Right.Columns[1].X; - Result.Y += Left.Elements[1] * Right.Columns[1].Y; - Result.Z += Left.Elements[1] * Right.Columns[1].Z; - Result.W += Left.Elements[1] * Right.Columns[1].W; + Result.X += Left->Elements[1] * Right->Columns[1].X; + Result.Y += Left->Elements[1] * Right->Columns[1].Y; + Result.Z += Left->Elements[1] * Right->Columns[1].Z; + Result.W += Left->Elements[1] * Right->Columns[1].W; - Result.X += Left.Elements[2] * Right.Columns[2].X; - Result.Y += Left.Elements[2] * Right.Columns[2].Y; - Result.Z += Left.Elements[2] * Right.Columns[2].Z; - Result.W += Left.Elements[2] * Right.Columns[2].W; + Result.X += Left->Elements[2] * Right->Columns[2].X; + Result.Y += Left->Elements[2] * Right->Columns[2].Y; + Result.Z += Left->Elements[2] * Right->Columns[2].Z; + Result.W += Left->Elements[2] * Right->Columns[2].W; - Result.X += Left.Elements[3] * Right.Columns[3].X; - Result.Y += Left.Elements[3] * Right.Columns[3].Y; - Result.Z += Left.Elements[3] * Right.Columns[3].Z; - Result.W += Left.Elements[3] * Right.Columns[3].W; + Result.X += Left->Elements[3] * Right->Columns[3].X; + Result.Y += Left->Elements[3] * Right->Columns[3].Y; + Result.Z += Left->Elements[3] * Right->Columns[3].Z; + Result.W += Left->Elements[3] * Right->Columns[3].W; #endif return Result; diff --git a/source/jsffi.c b/source/jsffi.c index 8d9bf79e..e27e0aaa 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -1766,7 +1766,6 @@ static const JSCFunctionListEntry js_spline_funcs[] = { MIST_FUNC_DEF(spline, bezier, 2) }; - shader_globals camera_globals(JSContext *js, JSValue camera) { shader_globals data = {0}; @@ -4121,27 +4120,26 @@ inline int sprite_in_view(HMM_Mat4 sprite, HMM_Mat4 camera) JSC_CCALL(gpu_make_sprite_queue, size_t quads = js_arrlen(js, argv[0]); - shader_globals info = camera_globals(js,argv[1]); - sprite *sprites = NULL; arrsetcap(sprites, quads); for (int i = 0; i < quads; i++) { JSValue sub = JS_GetPropertyUint32(js, argv[0], i); - transform *t; - JS_GETATOM(js,t,sub,transform_atom,transform) - HMM_Mat4 trmat = transform2mat4_global(t); - + rect src; HMM_Vec4 color; - + rect pr; + + JS_GETATOM(js,pr,sub,rect_atom,rect); JS_GETATOM(js, src, sub, src_atom, rect) JS_GETATOM(js, color, sub, color_atom, color) + // Profile: build quad quad sprite_quad; - - for (int j = 0; j < 4; j++) - sprite_quad.vert[j].pos = HMM_MulM4V4_P(&trmat, &(HMM_Vec4){base_quad[j].x,base_quad[j].y,1.0,1.0}).xy; + sprite_quad.vert[0].pos = (HMM_Vec2){pr.x, pr.y}; + sprite_quad.vert[1].pos = (HMM_Vec2){pr.x+pr.w, pr.y}; + sprite_quad.vert[2].pos = (HMM_Vec2){pr.x, pr.y+pr.h}; + sprite_quad.vert[3].pos = (HMM_Vec2){pr.x+pr.w, pr.y+pr.h}; sprite_quad.vert[0].uv = (HMM_Vec2){ src.x, src.y + src.h }; sprite_quad.vert[1].uv = (HMM_Vec2){ src.x+src.w, src.y + src.h }; @@ -4155,22 +4153,24 @@ JSC_CCALL(gpu_make_sprite_queue, sprite sp; sp.quad = sprite_quad; - sp.image = JS_GetPropertyStr(js,sub,"image"); + sp.image = JS_GetProperty(js, sub, image_atom); sp.js = js; - JS_GETPROP(js,sp.layer,sub,layer,number) - arrput(sprites,sp); + JS_GETATOM(js, sp.layer, sub, layer_atom, number) + arrput(sprites, sp); JS_FreeValue(js, sub); } - - qsort(sprites, arrlen(sprites),sizeof(sprite),sort_sprite); - + + qsort(sprites, arrlen(sprites), sizeof(sprite), sort_sprite); + text_vert *buffer = NULL; - arrsetcap(buffer,arrlen(sprites)*4); + arrsetcap(buffer, arrlen(sprites)*4); for (int i = 0; i < arrlen(sprites); i++) for (int j = 0; j < 4; j++) arrput(buffer, sprites[i].quad.vert[j]); + JSValue mesh = quads_to_mesh(js, buffer); + arrfree(buffer); ret = JS_NewArray(js); @@ -4178,41 +4178,43 @@ JSC_CCALL(gpu_make_sprite_queue, int count = 0; int n = 0; JSValue img = JS_UNDEFINED; + for (int i = 0; i < arrlen(sprites); i++) { - if (!JS_SameValue(js,sprites[i].image, img)) { + if (!JS_SameValue(js, sprites[i].image, img)) { if (count > 0) { JSValue q = JS_NewObject(js); - JS_SetPropertyStr(js,q,"type", JS_NewString(js,"geometry")); - JS_SetPropertyStr(js,q,"mesh", JS_DupValue(js,mesh)); - JS_SetPropertyStr(js,q,"pipeline", JS_DupValue(js,argv[2])); - JS_SetPropertyStr(js,q,"image", JS_DupValue(js,img)); - JS_SetPropertyStr(js,q,"first_index", number2js(js,first_index)); - JS_SetPropertyStr(js,q,"num_indices",number2js(js,count*6)); - JS_SetPropertyUint32(js,ret,n++,q); + JS_SetPropertyStr(js, q, "type", JS_NewString(js, "geometry")); + JS_SetPropertyStr(js, q, "mesh", JS_DupValue(js, mesh)); + JS_SetPropertyStr(js, q, "pipeline", JS_DupValue(js, argv[2])); + JS_SetPropertyStr(js, q, "image", JS_DupValue(js, img)); + JS_SetPropertyStr(js, q, "first_index", number2js(js, first_index)); + JS_SetPropertyStr(js, q, "num_indices", number2js(js, count * 6)); + JS_SetPropertyUint32(js, ret, n++, q); } first_index = i*6; count = 1; - JS_FreeValue(js,img); - img = JS_DupValue(js,sprites[i].image); + JS_FreeValue(js, img); + img = JS_DupValue(js, sprites[i].image); } else count++; - JS_FreeValue(js,sprites[i].image); + JS_FreeValue(js, sprites[i].image); } - + if (count > 0) { JSValue q = JS_NewObject(js); - JS_SetPropertyStr(js,q,"type", JS_NewString(js,"geometry")); - JS_SetPropertyStr(js,q,"mesh", JS_DupValue(js,mesh)); - JS_SetPropertyStr(js,q,"pipeline", JS_DupValue(js,argv[2])); - JS_SetPropertyStr(js,q,"image", JS_DupValue(js,img)); - JS_SetPropertyStr(js,q,"first_index", number2js(js,first_index)); - JS_SetPropertyStr(js,q,"num_indices",number2js(js,count*6)); - JS_SetPropertyUint32(js,ret,n++,q); + JS_SetPropertyStr(js, q, "type", JS_NewString(js, "geometry")); + JS_SetPropertyStr(js, q, "mesh", JS_DupValue(js, mesh)); + JS_SetPropertyStr(js, q, "pipeline", JS_DupValue(js, argv[2])); + JS_SetPropertyStr(js, q, "image", JS_DupValue(js, img)); + JS_SetPropertyStr(js, q, "first_index", number2js(js, first_index)); + JS_SetPropertyStr(js, q, "num_indices", number2js(js, count * 6)); + JS_SetPropertyUint32(js, ret, n++, q); } arrfree(sprites); - JS_FreeValue(js,mesh); + JS_FreeValue(js, mesh); ) + JSC_CCALL(gpu_make_sprite_mesh, size_t quads = js_arrlen(js, argv[0]); size_t verts = quads*4; @@ -5958,13 +5960,7 @@ JSC_CCALL(transform_dirty, JSC_CCALL(transform_torect, transform *t = js2transform(js,self); - HMM_Mat4 m3 = transform2mat4_global(t); - rect r = {0}; - r.x = m3.Columns[3].x; - r.y = m3.Columns[3].y; - r.w = m3.Columns[0].x; - r.h = m3.Columns[1].y; - return rect2js(js,r); + return rect2js(js,transform2rect(t)); ) static const JSCFunctionListEntry js_transform_funcs[] = { @@ -6057,6 +6053,12 @@ JSC_CCALL(geometry_rect_intersection, return rect2js(js,c); ) +JSC_CCALL(geometry_rect_intersects, + rect a = js2rect(js,argv[0]); + rect b = js2rect(js,argv[1]); + return JS_NewBool(js, SDL_HasRectIntersectionFloat(&a,&b)); +) + JSC_CCALL(geometry_rect_inside, rect inner = js2rect(js,argv[0]); rect outer = js2rect(js,argv[1]); @@ -6110,8 +6112,33 @@ JSC_CCALL(geometry_rect_move, return rect2js(js,r); ) +/*static inline float fmin(float a, float b) +{ + if (a < b) return a; + return b; +} + +static inline float fmax(float a, float b) +{ + if (a > b) return a; + return b; +} +*/ +JSC_CCALL(geometry_rect_expand, + rect a = js2rect(js,argv[0]); + rect b = js2rect(js,argv[1]); + rect c = {0}; + c.x = fmin(a.x,b.x); + c.y = fmin(a.y,b.y); + c.w = fmax(a.x+a.w,b.x+b.w); + c.h = fmax(a.y+a.h,b.y+b.h); + return rect2js(js,c); +) + static const JSCFunctionListEntry js_geometry_funcs[] = { MIST_FUNC_DEF(geometry, rect_intersection, 2), + MIST_FUNC_DEF(geometry, rect_intersects, 2), + MIST_FUNC_DEF(geometry, rect_expand, 2), MIST_FUNC_DEF(geometry, rect_inside, 2), MIST_FUNC_DEF(geometry, rect_random, 1), MIST_FUNC_DEF(geometry, cwh2rect, 2), @@ -7054,15 +7081,21 @@ JSC_CCALL(os_cull_sprites, JSValue sprites = argv[0]; shader_globals info = camera_globals(js,argv[1]); + rect camera_rect = {0}; + camera_rect.x = info.camera_pos_world.x - info.render_size.x/2.0; + camera_rect.y = info.camera_pos_world.y - info.render_size.y/2.0; + camera_rect.w = info.render_size.x; + camera_rect.h = info.render_size.y; int len = js_arrlen(js,sprites); for (int i = 0; i < len; i++) { - JSValue sub = JS_GetPropertyUint32(js,sprites, i); + JSValue sub = JS_GetPropertyUint32(js,sprites,i); transform *t; JS_GETATOM(js,t,sub,transform_atom,transform) - HMM_Mat4 trmat = transform2mat4_global(t); - if (sprite_in_view(trmat, info.world_to_projection)) { + + rect sprite = transform2rect(t); + if (SDL_HasRectIntersectionFloat(&sprite, &camera_rect)) { JS_SetPropertyUint32(js,ret,n,JS_DupValue(js,sub)); n++; } @@ -7070,23 +7103,28 @@ JSC_CCALL(os_cull_sprites, } ) -static JSContext *global_js; - -int js_qtree_cmp(JSValue *v, aabb *range) -{ +struct qtree_sprite { rect rect; - JSValue val = *v; - JS_GETATOM(global_js,rect,val,rect_atom,rect) - return (rect.x + rect.w < range->center.x+range->dims.w - && rect.x > range->center.x-range->dims.w - && rect.y > range->center.y-range->dims.h - && rect.y + rect.h < range->center.y + range->dims.h); + JSValue value; +}; + +int js_qtree_cmp(struct qtree_sprite *v, aabb *range) +{ + rect ab = { + .x = range->center.x-range->dims.w, + .y = range->center.y-range->dims.h, + .w = range->dims.w*2.0, + .h = range->dims.h*2.0 + }; +// printf("INTERSECT? %d\n", SDL_HasRectIntersectionFloat(&vrect,&ab)); +// printf("ab: %g,%g,%g,%g\n", ab.x,ab.y,ab.w,ab.h); +// printf("vrect:%g,%g,%g,%g\n", vrect.x,vrect.y,vrect.w,vrect.h); + return SDL_HasRectIntersectionFloat(&v->rect, &ab); } JSC_CCALL(os_make_quadtree, - HMM_Vec2 center = js2vec2(js,argv[0]); - HMM_Vec2 wh = js2vec2(js,argv[1]); - qtree tree = qtree_new(center.x,center.y,wh.x,wh.y, js_qtree_cmp); + rect area = js2rect(js,argv[0]); + qtree tree = qtree_new(area.x,area.y,area.w,area.h, js_qtree_cmp); return qtree2js(js,tree); ) @@ -7106,7 +7144,7 @@ static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, exit, 1), MIST_FUNC_DEF(os, gc, 0), MIST_FUNC_DEF(os, eval, 2), - MIST_FUNC_DEF(os, make_quadtree, 2), + MIST_FUNC_DEF(os, make_quadtree, 1), MIST_FUNC_DEF(os, make_texture, 1), MIST_FUNC_DEF(os, make_gif, 1), MIST_FUNC_DEF(os, make_aseprite, 1), @@ -7157,8 +7195,10 @@ static const JSCFunctionListEntry js_os_funcs[] = { JSC_CCALL(qtree_insert, qtree tree = js2qtree(js,self); - JSValue *item = malloc(sizeof(*item)); - *item = JS_DupValue(js,argv[0]); + JSValue v = argv[0]; + struct qtree_sprite *item = malloc(sizeof(*item)); + item->value = JS_DupValue(js,v); + JS_GETATOM(js,item->rect,v,rect_atom,rect) qtree_insert(tree, item); ) @@ -7167,28 +7207,23 @@ JSC_CCALL(qtree_remove, JSValue item = argv[0]; ) -JSC_CCALL(qtree_find, +JSC_CCALL(qtree_query, qtree tree = js2qtree(js,self); - HMM_Vec2 c = js2vec2(js,argv[0]); - HMM_Vec2 wh = js2vec2(js,argv[1]); - c.x -= wh.x/2.0; - c.y -= wh.y/2.0; + rect area = js2rect(js,argv[0]); uint32_t n; - JSValue **items = qtree_findInArea(tree, c.x, c.y, wh.x, wh.y, &n); + struct qtree_sprite **items = qtree_findInArea(tree, area.x, area.y, area.w, area.h, &n); ret = JS_NewArray(js); - if (n > 0) - JS_SetPropertyUint32(js,ret,0,JS_DupValue(js,*items[0])); - - /* for (int i = 0; i < n; i++) - JS_SetPropertyUint32(js,ret,i, JS_DupValue(js,*items[i]));*/ + JS_SetPropertyUint32(js,ret,i, JS_DupValue(js,items[i]->value)); + + free(items); ) static const JSCFunctionListEntry js_qtree_funcs[] = { MIST_FUNC_DEF(qtree, insert, 1), MIST_FUNC_DEF(qtree, remove, 1), - MIST_FUNC_DEF(qtree, find, 2), + MIST_FUNC_DEF(qtree, query, 2), }; static const JSCFunctionListEntry js_jssprite_funcs[] = { @@ -7406,7 +7441,6 @@ void ffi_load(JSContext *js) { rect_atom = JS_NewAtom(js,"rect"); fill_event_atoms(js); - global_js = js; - + JS_FreeValue(js,globalThis); } diff --git a/source/qjs_macros.h b/source/qjs_macros.h index b9ac9ac4..b4ee4fff 100644 --- a/source/qjs_macros.h +++ b/source/qjs_macros.h @@ -91,7 +91,6 @@ static JSClassDef js_##TYPE##_class = {\ .finalizer = js_##TYPE##_finalizer,\ };\ TYPE *js2##TYPE (JSContext *js, JSValue val) { \ - if (JS_IsUndefined(val)) return NULL; \ if (JS_GetClassID(val) != js_##TYPE##_id) return NULL; \ return JS_GetOpaque(val,js_##TYPE##_id); \ }\ diff --git a/source/quadtree.c b/source/quadtree.c index c0f1c5f7..93029dc7 100644 --- a/source/quadtree.c +++ b/source/quadtree.c @@ -131,13 +131,11 @@ static int qnode_insert(qtree q, qnode *qn, void *ptr) { int ret = 0; - if(! (q->cmpfnc)(ptr, &qn->bound)) - goto QN_INS_EXIT; + if(! (q->cmpfnc)(ptr, &qn->bound)) return 0; if(qn->cnt < qtree_getMaxNodeCnt(q)) { add(qn, ptr); - ret = 1; - goto QN_INS_EXIT; + return 1; } if(! qn->nw) @@ -151,9 +149,6 @@ qnode_insert(qtree q, qnode *qn, void *ptr) { return 1; else if(qnode_insert(q,qn->se,ptr)) return 1; - - QN_INS_EXIT: - return ret; } static void* diff --git a/source/transform.c b/source/transform.c index 6325b2cb..9444022b 100644 --- a/source/transform.c +++ b/source/transform.c @@ -82,7 +82,20 @@ HMM_Mat4 transform2mat4_global(transform *t) else t->gcache = tm; -return t->gcache; + return t->gcache; +} + +rect transform2rect(transform *t) +{ + if (!transform_dirty_chain(t)) return t->rcache; + HMM_Mat4 m3 = transform2mat4_global(t); + rect r = {0}; + r.x = m3.Columns[3].x; + r.y = m3.Columns[3].y; + r.w = m3.Columns[0].x; + r.h = m3.Columns[1].y; + t->rcache = r; + return t->rcache; } HMM_Mat3 transform2mat3_global(transform *t) diff --git a/source/transform.h b/source/transform.h index 4eadf008..1fd20fff 100644 --- a/source/transform.h +++ b/source/transform.h @@ -3,6 +3,7 @@ #include "HandmadeMath.h" #include +#include "render.h" typedef struct transform { HMM_Vec3 pos; @@ -12,10 +13,12 @@ typedef struct transform { HMM_Mat3 cache3; HMM_Mat3 gcache3; HMM_Mat4 gcache; + rect rcache; int dirty; struct transform *parent; JSValue jsparent; struct transform *children; + JSValue *jschildren; } transform; transform *make_transform(); @@ -50,6 +53,7 @@ HMM_Mat3 transform2mat3(transform *t); HMM_Mat4 transform2mat4_global(transform *t); HMM_Mat3 transform2mat3_global(transform *t); +rect transform2rect(transform *t); int transform_dirty_chain(transform *t); transform mat2transform(HMM_Mat4 m);