fast sprite rendering with quadtrees
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
190
source/jsffi.c
190
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);
|
||||
}
|
||||
|
||||
@@ -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); \
|
||||
}\
|
||||
|
||||
@@ -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*
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user