diff --git a/scripts/components.js b/scripts/components.js index b7ea1cb7..1ab22e19 100644 --- a/scripts/components.js +++ b/scripts/components.js @@ -1,321 +1,5 @@ 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, @@ -337,17 +21,8 @@ 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); -//globalThis.sprite_qt = os.make_qtree(world); globalThis.sprite_qt = os.make_rtree(); -var spritetree = os.make_quadtree([0,0], [10000,10000]); -globalThis.spritetree = spritetree; - var sprite = { image: undefined, get diffuse() { return this.image; }, @@ -564,6 +239,11 @@ component.sprite = function (obj) { sp.transform.parent = obj.transform; sp.guid = prosperon.guid(); allsprites.push(sp); + sp.transform.change_hook = function() { + sprite_qt.remove(sp); + sp.rect = sp.transform.torect(); + sprite_qt.insert(sp); + } return sp; }; diff --git a/scripts/particle.js b/scripts/particle.js index 3542e840..81f16dd7 100644 --- a/scripts/particle.js +++ b/scripts/particle.js @@ -75,8 +75,8 @@ emitter.step = function step(dt) { } } - for (var p of this.particles) - p.transform.clean(); +// for (var p of this.particles) +// p.transform.clean(); }; emitter.burst = function (count, t) { diff --git a/scripts/render.js b/scripts/render.js index effa77f4..70700eaa 100644 --- a/scripts/render.js +++ b/scripts/render.js @@ -756,19 +756,8 @@ function insertion_sort(arr, cmp) return arr } -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 = { diff --git a/source/jsffi.c b/source/jsffi.c index 3349a7f6..e7608253 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -371,8 +371,6 @@ struct lrtb { float b; }; -static JSContext *global_js; - static SDL_GPUDevice *global_gpu; SDL_GPUGraphicsPipelineTargetInfo js2SDL_GPUGraphicsPipelineTargetInfo(JSContext *js, JSValue v) @@ -3312,7 +3310,7 @@ JSC_CCALL(renderer_make_sprite_mesh, size_t base = i * 4; - HMM_Mat3 trmat = tr->gcache3; +// HMM_Mat3 trmat = transform2mat3_global(tr); HMM_Vec3 base_quad[4] = { {0.0,0.0,1.0}, @@ -3321,8 +3319,8 @@ JSC_CCALL(renderer_make_sprite_mesh, {1.0,1.0,1.0} }; - for (int j = 0; j < 4; j++) - posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]).xy; +// for (int j = 0; j < 4; j++) +// posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]).xy; // Define the UV coordinates based on the source rectangle uvdata[base + 0] = (HMM_Vec2){ src.x, src.y + src.h }; @@ -3989,12 +3987,10 @@ JSC_CCALL(gpu_sort_sprite, JS_GETATOM(js,blayer,b,layer_atom,number) if (alayer != blayer) return number2js(js,alayer - blayer); - transform *atr, *btr; - JS_GETATOM(js,atr,a,transform_atom,transform) - JS_GETATOM(js,btr,b,transform_atom,transform); - HMM_Mat3 am3 = transform2mat3_global(atr); - HMM_Mat3 bm3 = transform2mat3_global(btr); - if (am3.Columns[2].y != bm3.Columns[2].y) return number2js(js,bm3.Columns[2].y - am3.Columns[2].y); + rect ar, br; + JS_GETATOM(js,ar,a,rect_atom,rect) + JS_GETATOM(js,br,b,rect_atom,rect) + if (ar.y != br.y) return number2js(js,br.y-ar.y); JSValue aimg,bimg; aimg = JS_GetProperty(js,a,image_atom); @@ -4148,8 +4144,7 @@ JSC_CCALL(gpu_make_sprite_mesh, JS_FreeValue(js,sub); size_t base = i*4; - HMM_Mat3 trmat = tr->gcache3; -// HMM_Mat3 trmat = transform2mat3(tr); + HMM_Mat3 trmat = transform2mat3(tr); for (int j = 0; j < 4; j++) posdata[base+j] = HMM_MulM3V3(trmat, base_quad[j]).xy; @@ -5769,7 +5764,7 @@ JSC_CCALL(transform_lookat, transform *go = js2transform(js,self); HMM_Mat4 m = HMM_LookAt_RH(go->pos, point, vUP); go->rotation = HMM_M4ToQ_RH(m); - go->dirty = true; + transform_apply(go); ) JSC_CCALL(transform_rotate, @@ -5811,7 +5806,6 @@ JSC_CCALL(transform_unit, t->rotation = QUAT1; t->scale = v3one; transform_apply(t); - t->parent = NULL; ) JSC_CCALL(transform_trs, @@ -5839,6 +5833,20 @@ JSC_CCALL(transform_array, JS_SetPropertyUint32(js,ret,i, number2js(js,m.em[i])); ) +static JSValue js_transform_get_change_hook(JSContext *js, JSValueConst self) +{ + transform *t = js2transform(js,self); + return JS_DupValue(js,t->change_hook); +} + +static JSValue js_transform_set_change_hook(JSContext *js, JSValueConst self, JSValue v) +{ + transform *t = js2transform(js,self); + if (!JS_IsUndefined(v) && !JS_IsFunction(js,v)) return JS_ThrowReferenceError(js, "Hook must be a function."); + JS_FreeValue(js,t->change_hook); + t->change_hook = JS_DupValue(js,v); +} + static JSValue js_transform_get_parent(JSContext *js, JSValueConst self) { transform *t = js2transform(js,self); @@ -5848,26 +5856,42 @@ static JSValue js_transform_get_parent(JSContext *js, JSValueConst self) static JSValue js_transform_set_parent(JSContext *js, JSValueConst self, JSValue v) { + transform *p = js2transform(js,v); + if (!p) + return JS_ThrowReferenceError(js,"Parent must be another transform."); + transform *t = js2transform(js,self); - if (t->parent) + if (t->parent) { JS_FreeValue(js,t->jsparent); - transform *p = js2transform(js,v); + for (int i = 0; i < arrlen(p->children); i++) { + if (p->children[i] == t) { + arrdelswap(p->children,i); + break; + } + } + + for (int i = 0; i < arrlen(p->jschildren); i++) { + if (JS_SameValue(js,p->jschildren[i],self)) { + JS_FreeValue(js,p->jschildren[i]); + arrdelswap(p->jschildren,i); + break; + } + } + } + t->parent = p; - if (p) - t->jsparent = JS_DupValue(js,v); + t->jsparent = JS_DupValue(js,v); + + arrput(p->children, t); + JSValue child = JS_DupValue(js,self); + arrput(p->jschildren,child); + + transform_apply(t); return JS_UNDEFINED; } -JSC_CCALL(transform_clean, - transform2mat3_global(js2transform(js,self)); -) - -JSC_CCALL(transform_dirty, - return JS_NewBool(js,transform_dirty_chain(js2transform(js,self))); -) - JSC_CCALL(transform_torect, transform *t = js2transform(js,self); return rect2js(js,transform2rect(t)); @@ -5877,7 +5901,8 @@ static const JSCFunctionListEntry js_transform_funcs[] = { CGETSET_ADD(transform, pos), CGETSET_ADD(transform, scale), CGETSET_ADD(transform, rotation), - CGETSET_ADD(transform, parent), + CGETSET_ADD(transform, parent), + CGETSET_ADD(transform, change_hook), MIST_FUNC_DEF(transform, trs, 3), MIST_FUNC_DEF(transform, phys2d, 3), MIST_FUNC_DEF(transform, move, 1), @@ -5888,8 +5913,6 @@ static const JSCFunctionListEntry js_transform_funcs[] = { MIST_FUNC_DEF(transform, unit, 0), MIST_FUNC_DEF(transform, rect, 1), MIST_FUNC_DEF(transform, array, 0), - MIST_FUNC_DEF(transform, clean, 0), - MIST_FUNC_DEF(transform, dirty, 0), MIST_FUNC_DEF(transform, torect, 0), }; diff --git a/source/script.c b/source/script.c index ccd8298f..4d2f9e40 100644 --- a/source/script.c +++ b/source/script.c @@ -13,6 +13,8 @@ static JSContext *js = NULL; static JSRuntime *rt = NULL; +JSContext *global_js = NULL; + #ifndef NDEBUG #define JS_EVAL_FLAGS JS_EVAL_FLAG_STRICT #else diff --git a/source/script.h b/source/script.h index a07c6ca0..008e03ee 100644 --- a/source/script.h +++ b/source/script.h @@ -10,4 +10,6 @@ void script_evalf(const char *format, ...); JSValue script_eval(const char *file, const char *script); void script_call_sym(JSValue sym, int argc, JSValue *argv); +extern JSContext *global_js; + #endif diff --git a/source/transform.c b/source/transform.c index 9444022b..7cbb3cc1 100644 --- a/source/transform.c +++ b/source/transform.c @@ -1,6 +1,9 @@ #include "transform.h" #include #include +#include "script.h" + +#include "stb_ds.h" transform *make_transform() { @@ -9,10 +12,50 @@ transform *make_transform() t->scale = (HMM_Vec3){1,1,1}; t->rotation = (HMM_Quat){0,0,0,1}; transform_apply(t); + t->change_hook = JS_UNDEFINED; return t; } -void transform_free(JSRuntime *rt, transform *t) { free(t); } +void transform_clean(transform *t) +{ + if (!t->dirty) return; + t->dirty = 0; + t->cache = HMM_M4TRS(t->pos,t->rotation,t->scale); + if (t->parent) + t->gcache = HMM_MulM4(t->parent->gcache, t->cache); + else + t->gcache = t->cache; + + HMM_Mat4 m = t->gcache; + rect r = {0}; + r.x = m.Columns[3].x; + r.y = m.Columns[3].y; + r.w = m.Columns[0].x; + r.h = m.Columns[1].y; + t->rcache = r; + + for (int i = 0; i < arrlen(t->children); i++) { + t->children[i]->dirty = 1; + transform_clean(t->children[i]); + } + + if (!JS_IsUndefined(t->change_hook)) { + JSValue ret = JS_Call(global_js, t->change_hook, JS_UNDEFINED, 0, NULL); + JS_FreeValue(global_js,ret); + } +} + +void transform_free(JSRuntime *rt, transform *t) { + JS_FreeValueRT(rt,t->change_hook); + JS_FreeValueRT(rt,t->jsparent); + for (int i = 0; i < arrlen(t->jschildren); i++) + JS_FreeValueRT(rt,t->jschildren[i]); + + arrfree(t->jschildren); + arrfree(t->children); + free(t); + printf("FREED TRANSFORM!\n"); +} void transform_move(transform *t, HMM_Vec3 v) { t->pos = HMM_AddV3(t->pos, v); @@ -52,76 +95,30 @@ HMM_Vec3 mat3_t_dir(HMM_Mat4 m, HMM_Vec3 dir) return mat3_t_pos(m, dir); } -static inline void transform_clean(transform *t) -{ - if (!t->dirty) return; - t->dirty = 0; - t->cache = HMM_M4TRS(t->pos,t->rotation,t->scale); - t->cache3 = HMM_M3TRS(t->pos.xy,t->rotation.x,t->scale.xy); -} - HMM_Mat4 transform2mat(transform *t) { - transform_clean(t); return t->cache; } HMM_Mat3 transform2mat3(transform *t) { - transform_clean(t); - return t->cache3; + return HMM_M3TRS(t->pos.xy, t->rotation.x, t->scale.xy); } HMM_Mat4 transform2mat4_global(transform *t) { - if (!transform_dirty_chain(t)) return t->gcache; - - HMM_Mat4 tm = transform2mat(t); - if (t->parent) - t->gcache = HMM_MulM4(transform2mat(t->parent), tm); - else - t->gcache = tm; - 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) -{ - if (!transform_dirty_chain(t)) return t->gcache3; - - HMM_Mat3 tm = transform2mat3(t); - if (t->parent) - t->gcache3 = HMM_MulM3(transform2mat3(t->parent), tm); - else - t->gcache3 = tm; - - return t->gcache3; -} - -int transform_dirty_chain(transform *t) -{ - if (t->dirty) return 1; - if (t->parent) - return transform_dirty_chain(t->parent); - return 0; -} - void transform_apply(transform *t) { t->dirty = 1; + transform_clean(t); } HMM_Quat angle2rotation(float angle) diff --git a/source/transform.h b/source/transform.h index 1fd20fff..f2f8bf86 100644 --- a/source/transform.h +++ b/source/transform.h @@ -10,15 +10,14 @@ typedef struct transform { HMM_Vec3 scale; HMM_Quat rotation; HMM_Mat4 cache; - HMM_Mat3 cache3; - HMM_Mat3 gcache3; HMM_Mat4 gcache; rect rcache; int dirty; struct transform *parent; JSValue jsparent; - struct transform *children; + struct transform **children; JSValue *jschildren; + JSValue change_hook; } transform; transform *make_transform(); @@ -52,9 +51,7 @@ HMM_Mat4 transform2mat(transform *t); 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);