fast sprite rendering with quadtrees

This commit is contained in:
2025-01-14 13:53:49 -06:00
parent d8cad40c50
commit 28f0b5478b
8 changed files with 497 additions and 109 deletions

View File

@@ -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 nodes 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;

View File

@@ -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();

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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); \
}\

View File

@@ -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*

View File

@@ -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)

View File

@@ -3,6 +3,7 @@
#include "HandmadeMath.h"
#include <quickjs.h>
#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);