emitter
This commit is contained in:
@@ -1,135 +0,0 @@
|
||||
var color = use('color')
|
||||
var graphics = use('graphics')
|
||||
var transform = use('transform')
|
||||
|
||||
var ex = {}
|
||||
|
||||
ex.emitters = new Set()
|
||||
|
||||
ex.garbage = function()
|
||||
{
|
||||
ex.emitters.delete(this)
|
||||
}
|
||||
|
||||
ex.update = function(dt)
|
||||
{
|
||||
for (var e of ex.emitters)
|
||||
try { e.step(dt) } catch(e) { log.error(e) }
|
||||
}
|
||||
|
||||
ex.step_hook = function(p)
|
||||
{
|
||||
if (p.time < this.grow_for) {
|
||||
var s = Math.lerp(0, this.scale, p.time / this.grow_for);
|
||||
p.transform.scale = s;
|
||||
} else if (p.time > p.life - this.shrink_for) {
|
||||
var s = Math.lerp(0, this.scale, (p.life - p.time) / this.shrink_for);
|
||||
p.transform.scale = s;
|
||||
} else p.transform.scale = [this.scale, this.scale, this.scale];
|
||||
}
|
||||
|
||||
ex.step = function(dt)
|
||||
{
|
||||
// update spawning particles
|
||||
if (this.on && this.pps > 0) {
|
||||
this.spawn_timer += dt;
|
||||
var pp = 1 / this.pps;
|
||||
while (this.spawn_timer > pp) {
|
||||
this.spawn_timer -= pp;
|
||||
this.spawn();
|
||||
}
|
||||
}
|
||||
|
||||
// update all particles
|
||||
for (var p of this.particles) {
|
||||
p.time += dt;
|
||||
p.transform.move(p.body.velocity?.scale(dt));
|
||||
this.step_hook?.(p);
|
||||
|
||||
if (this.kill_hook?.(p) || p.time >= p.life) {
|
||||
this.die_hook?.(p);
|
||||
this.dead.push(p);
|
||||
this.particles.delete(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ex.burst = function(count,t)
|
||||
{
|
||||
for (var i = 0; i < count; i++) this.spawn(t)
|
||||
}
|
||||
|
||||
ex.spawn = function(t)
|
||||
{
|
||||
t ??= this.transform
|
||||
|
||||
var par = this.dead.shift()
|
||||
if (par) {
|
||||
par.transform.unit()
|
||||
par.transform.pos = t.pos;
|
||||
par.transform.scale = this.scale;
|
||||
this.particles.push(par);
|
||||
par.time = 0;
|
||||
this.spawn_hook?.(par);
|
||||
par.life = this.life;
|
||||
return;
|
||||
}
|
||||
|
||||
par = {
|
||||
transform: new transform,
|
||||
life: this.life,
|
||||
time: 0,
|
||||
color: this.color,
|
||||
body:{},
|
||||
}
|
||||
|
||||
par.transform.scale = this.scale
|
||||
this.particles.push(par)
|
||||
this.spawn_hook(par)
|
||||
}
|
||||
|
||||
ex.stat = function()
|
||||
{
|
||||
var stat = {};
|
||||
stat.emitters = emitters.length;
|
||||
var particles = 0;
|
||||
for (var e of emitters) particles += e.particles.length;
|
||||
stat.particles = particles;
|
||||
return stat;
|
||||
}
|
||||
|
||||
ex.life = 10
|
||||
ex.scale = 1
|
||||
ex.grow_for = 0
|
||||
ex.spawn_timer = 0
|
||||
ex.pps = 0
|
||||
ex.color = color.white
|
||||
|
||||
ex.draw = function()
|
||||
{
|
||||
/* var diff = graphics.texture(this.diffuse)
|
||||
if (!diff) throw new Error("emitter does not have a proper diffuse texture")
|
||||
|
||||
var mesh = graphics.make_sprite_mesh(this.particles)
|
||||
if (mesh.num_indices == 0) return
|
||||
render.queue({
|
||||
type:'geometry',
|
||||
mesh,
|
||||
image:diff,
|
||||
pipeline,
|
||||
first_index:0,
|
||||
num_indices:mesh.num_indices
|
||||
})
|
||||
*/
|
||||
}
|
||||
|
||||
return ex
|
||||
|
||||
---
|
||||
|
||||
this.particles = []
|
||||
this.dead = []
|
||||
|
||||
this.transform = this.overling.transform
|
||||
|
||||
$.emitters.add(this)
|
||||
@@ -78,8 +78,6 @@ function worldToScreenRect({x,y,width,height}, camera) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
var gameactor
|
||||
|
||||
var images = {}
|
||||
@@ -248,62 +246,74 @@ function translate_draw_commands(commands) {
|
||||
break
|
||||
|
||||
case "tilemap":
|
||||
// Group tiles by texture to batch draw calls
|
||||
var textureGroups = {}
|
||||
var tilePositions = []
|
||||
// Get cached geometry commands from tilemap
|
||||
var geometryCommands = cmd.tilemap.draw()
|
||||
|
||||
// Collect all tiles and their positions
|
||||
tilemap.for(cmd.tilemap, (tile, {x,y}) => {
|
||||
if (tile) {
|
||||
tilePositions.push({tile, x, y})
|
||||
}
|
||||
})
|
||||
|
||||
// Group tiles by texture
|
||||
tilePositions.forEach(({tile, x, y}) => {
|
||||
var img = graphics.texture(tile)
|
||||
if (img && img.gpu) {
|
||||
var texId = img.gpu.id
|
||||
if (!textureGroups[texId]) {
|
||||
textureGroups[texId] = {
|
||||
texture: img,
|
||||
tiles: []
|
||||
}
|
||||
}
|
||||
textureGroups[texId].tiles.push({x, y, img})
|
||||
}
|
||||
})
|
||||
|
||||
// Generate draw commands for each texture group
|
||||
Object.keys(textureGroups).forEach(texId => {
|
||||
var group = textureGroups[texId]
|
||||
var tiles = group.tiles
|
||||
// Process each geometry command (one per texture)
|
||||
for (var geomCmd of geometryCommands) {
|
||||
var img = graphics.texture(geomCmd.image)
|
||||
var gpu = img.gpu
|
||||
if (!gpu) continue
|
||||
|
||||
// Create a temporary tilemap with only tiles from this texture
|
||||
// Apply tilemap position to the offset to shift the world coordinates
|
||||
var tempMap = {
|
||||
tiles: [],
|
||||
offset_x: cmd.tilemap.offset_x + (cmd.tilemap.pos.x / cmd.tilemap.size_x),
|
||||
offset_y: cmd.tilemap.offset_y + (cmd.tilemap.pos.y / cmd.tilemap.size_y),
|
||||
size_x: cmd.tilemap.size_x,
|
||||
size_y: cmd.tilemap.size_y
|
||||
// Transform geometry through camera and send to renderer
|
||||
var geom = geomCmd.geometry
|
||||
|
||||
// Transform XY coordinates using camera matrix
|
||||
var camera_params = [camera.a, camera.c, camera.e, camera.f]
|
||||
var transformed_xy = geometry.transform_xy_blob(geom.xy, camera_params)
|
||||
|
||||
// Create new geometry object with transformed coordinates
|
||||
var transformed_geom = {
|
||||
xy: transformed_xy,
|
||||
xy_stride: geom.xy_stride,
|
||||
uv: geom.uv,
|
||||
uv_stride: geom.uv_stride,
|
||||
color: geom.color,
|
||||
color_stride: geom.color_stride,
|
||||
indices: geom.indices,
|
||||
num_vertices: geom.num_vertices,
|
||||
num_indices: geom.num_indices,
|
||||
size_indices: geom.size_indices,
|
||||
texture_id: gpu.id
|
||||
}
|
||||
|
||||
// Build sparse array for this texture's tiles
|
||||
tiles.forEach(({x, y, img}) => {
|
||||
var arrayX = x - cmd.tilemap.offset_x
|
||||
var arrayY = y - cmd.tilemap.offset_y
|
||||
if (!tempMap.tiles[arrayX]) tempMap.tiles[arrayX] = []
|
||||
tempMap.tiles[arrayX][arrayY] = img
|
||||
})
|
||||
// Generate geometry for this texture group
|
||||
var geom = geometry.tilemap_to_data(cmd.tilemap)
|
||||
geom.texture_id = parseInt(texId)
|
||||
|
||||
renderer_commands.push({
|
||||
op: "geometry_raw",
|
||||
data: geom
|
||||
data: transformed_geom
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
case "geometry":
|
||||
var img = graphics.texture(cmd.image)
|
||||
var gpu = img.gpu
|
||||
if (!gpu) break
|
||||
|
||||
// Transform geometry through camera and send to renderer
|
||||
var geom = cmd.geometry
|
||||
|
||||
// Transform XY coordinates using camera matrix
|
||||
var camera_params = [camera.a, camera.c, camera.e, camera.f]
|
||||
var transformed_xy = geometry.transform_xy_blob(geom.xy, camera_params)
|
||||
|
||||
// Create new geometry object with transformed coordinates
|
||||
var transformed_geom = {
|
||||
xy: transformed_xy,
|
||||
xy_stride: geom.xy_stride,
|
||||
uv: geom.uv,
|
||||
uv_stride: geom.uv_stride,
|
||||
color: geom.color,
|
||||
color_stride: geom.color_stride,
|
||||
indices: geom.indices,
|
||||
num_vertices: geom.num_vertices,
|
||||
num_indices: geom.num_indices,
|
||||
size_indices: geom.size_indices,
|
||||
texture_id: gpu.id
|
||||
}
|
||||
|
||||
renderer_commands.push({
|
||||
op: "geometry_raw",
|
||||
data: transformed_geom
|
||||
})
|
||||
break
|
||||
}
|
||||
@@ -412,7 +422,9 @@ prosperon.set_window = function(config)
|
||||
}
|
||||
|
||||
var renderer_cmds = {
|
||||
resolution(size) {
|
||||
resolution(e) {
|
||||
logical.width = e.width
|
||||
logical.height = e.height
|
||||
send(video, {kind:"renderer", op:'set', prop:'logicalPresentation', value: {...e}})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ function tilemap()
|
||||
this.offset_y = 0;
|
||||
this.size_x = 32;
|
||||
this.size_y = 32;
|
||||
this._geometry_cache = null;
|
||||
this._dirty = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -65,13 +67,91 @@ tilemap.prototype =
|
||||
|
||||
// Set the value
|
||||
this.tiles[x][y] = image;
|
||||
|
||||
// Mark cache as dirty when tiles change
|
||||
this._dirty = true;
|
||||
},
|
||||
|
||||
draw(pos = {x: 0, y: 0}) {
|
||||
return {
|
||||
cmd:'tilemap',
|
||||
tilemap:this,
|
||||
// Build cached geometry grouped by texture
|
||||
_build_geometry_cache(pos = {x: 0, y: 0}) {
|
||||
var geometry = use('geometry');
|
||||
|
||||
// Group tiles by texture
|
||||
var textureGroups = {};
|
||||
|
||||
// Collect all tiles and their positions
|
||||
for (var x = 0; x < this.tiles.length; x++) {
|
||||
if (!this.tiles[x]) continue;
|
||||
for (var y = 0; y < this.tiles[x].length; y++) {
|
||||
var tile = this.tiles[x][y];
|
||||
if (tile) {
|
||||
var textureKey = tile;
|
||||
if (!textureGroups[textureKey]) {
|
||||
textureGroups[textureKey] = {
|
||||
tiles: [],
|
||||
offset_x: this.offset_x,
|
||||
offset_y: this.offset_y,
|
||||
size_x: this.size_x,
|
||||
size_y: this.size_y
|
||||
};
|
||||
}
|
||||
textureGroups[textureKey].tiles.push({
|
||||
x: x + this.offset_x,
|
||||
y: y + this.offset_y,
|
||||
image: tile
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate geometry for each texture group
|
||||
var geometryCommands = [];
|
||||
for (var textureKey in textureGroups) {
|
||||
var group = textureGroups[textureKey];
|
||||
if (group.tiles.length == 0) continue;
|
||||
|
||||
// Create a temporary tilemap for this texture group
|
||||
var tempMap = {
|
||||
tiles: [],
|
||||
offset_x: group.offset_x,
|
||||
offset_y: group.offset_y,
|
||||
size_x: group.size_x,
|
||||
size_y: group.size_y,
|
||||
pos_x: pos.x,
|
||||
pos_y: pos.y
|
||||
};
|
||||
|
||||
// Build sparse array for this texture's tiles
|
||||
group.tiles.forEach(({x, y, image}) => {
|
||||
var arrayX = x - group.offset_x;
|
||||
var arrayY = y - group.offset_y;
|
||||
if (!tempMap.tiles[arrayX]) tempMap.tiles[arrayX] = [];
|
||||
tempMap.tiles[arrayX][arrayY] = image;
|
||||
});
|
||||
|
||||
// Generate geometry for this group
|
||||
var geom = geometry.tilemap_to_data(tempMap);
|
||||
|
||||
geometryCommands.push({
|
||||
cmd: "geometry",
|
||||
geometry: geom,
|
||||
image: textureKey
|
||||
});
|
||||
}
|
||||
|
||||
this._geometry_cache = geometryCommands;
|
||||
this._dirty = false;
|
||||
},
|
||||
|
||||
draw(pos = {x: 0, y: 0}) {
|
||||
// Rebuild cache if dirty or position changed
|
||||
if (this._dirty || !this._geometry_cache || this._last_pos?.x != pos.x || this._last_pos?.y != pos.y) {
|
||||
this._build_geometry_cache(pos);
|
||||
this._last_pos = {x: pos.x, y: pos.y};
|
||||
}
|
||||
|
||||
// Return cached geometry commands
|
||||
return this._geometry_cache;
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,10 @@ function makeAnim(frames, loop=true){
|
||||
function decode_image(bytes, ext)
|
||||
{
|
||||
switch(ext) {
|
||||
case 'gif': return graphics.make_gif(bytes)
|
||||
case 'gif':
|
||||
var g = graphics.make_gif(bytes)
|
||||
if (g.frames) return g.frames[0]
|
||||
return g
|
||||
case 'ase':
|
||||
case 'aseprite': return graphics.make_aseprite(bytes)
|
||||
default: return {surface:graphics.make_texture(bytes)}
|
||||
|
||||
@@ -812,11 +812,25 @@ JSC_CCALL(geometry_tilemap_to_data,
|
||||
|
||||
// Get tilemap properties
|
||||
double offset_x, offset_y, size_x, size_y;
|
||||
double pos_x = 0, pos_y = 0;
|
||||
JS_GETPROP(js, offset_x, tilemap_obj, offset_x, number)
|
||||
JS_GETPROP(js, offset_y, tilemap_obj, offset_y, number)
|
||||
JS_GETPROP(js, size_x, tilemap_obj, size_x, number)
|
||||
JS_GETPROP(js, size_y, tilemap_obj, size_y, number)
|
||||
|
||||
// Get position properties (optional, default to 0)
|
||||
JSValue pos_x_val = JS_GetPropertyStr(js, tilemap_obj, "pos_x");
|
||||
if (!JS_IsNull(pos_x_val)) {
|
||||
JS_ToFloat64(js, &pos_x, pos_x_val);
|
||||
}
|
||||
JS_FreeValue(js, pos_x_val);
|
||||
|
||||
JSValue pos_y_val = JS_GetPropertyStr(js, tilemap_obj, "pos_y");
|
||||
if (!JS_IsNull(pos_y_val)) {
|
||||
JS_ToFloat64(js, &pos_y, pos_y_val);
|
||||
}
|
||||
JS_FreeValue(js, pos_y_val);
|
||||
|
||||
JSValue tiles_array = JS_GetPropertyStr(js, tilemap_obj, "tiles");
|
||||
if (!JS_IsArray(js, tiles_array)) {
|
||||
JS_FreeValue(js, tiles_array);
|
||||
@@ -870,8 +884,8 @@ JSC_CCALL(geometry_tilemap_to_data,
|
||||
// x and y are array indices, need to convert to logical coordinates
|
||||
float logical_x = x + offset_x;
|
||||
float logical_y = y + offset_y;
|
||||
float world_x = logical_x * size_x;
|
||||
float world_y = logical_y * size_y;
|
||||
float world_x = logical_x * size_x + pos_x;
|
||||
float world_y = logical_y * size_y + pos_y;
|
||||
|
||||
// Set vertex positions (4 corners of the tile)
|
||||
int base = vertex_idx * 2;
|
||||
@@ -957,6 +971,277 @@ JSC_CCALL(geometry_tilemap_to_data,
|
||||
free(index_data);
|
||||
)
|
||||
|
||||
JSC_CCALL(geometry_sprites_to_data,
|
||||
JSValue sprites_array = argv[0];
|
||||
if (!JS_IsArray(js, sprites_array)) {
|
||||
return JS_ThrowTypeError(js, "sprites must be an array");
|
||||
}
|
||||
|
||||
int sprite_count = JS_ArrayLength(js, sprites_array);
|
||||
if (sprite_count == 0) {
|
||||
return JS_NewObject(js);
|
||||
}
|
||||
|
||||
// Allocate buffers - 4 vertices per sprite
|
||||
int vertex_count = sprite_count * 4;
|
||||
int index_count = sprite_count * 6;
|
||||
|
||||
float *xy_data = malloc(vertex_count * 2 * sizeof(float));
|
||||
float *uv_data = malloc(vertex_count * 2 * sizeof(float));
|
||||
SDL_FColor *color_data = malloc(vertex_count * sizeof(SDL_FColor));
|
||||
uint16_t *index_data = malloc(index_count * sizeof(uint16_t));
|
||||
|
||||
// Generate vertices
|
||||
int vertex_idx = 0;
|
||||
int index_idx = 0;
|
||||
|
||||
for (int i = 0; i < sprite_count; i++) {
|
||||
JSValue sprite = JS_GetPropertyUint32(js, sprites_array, i);
|
||||
|
||||
// Get sprite properties
|
||||
JSValue pos_val = JS_GetPropertyStr(js, sprite, "pos");
|
||||
JSValue texture_val = JS_GetPropertyStr(js, sprite, "texture");
|
||||
JSValue color_val = JS_GetPropertyStr(js, sprite, "color");
|
||||
|
||||
double width = 32, height = 32, anchor_x = 0.5, anchor_y = 0.5;
|
||||
|
||||
// Try to get width/height from sprite first, otherwise use texture dimensions
|
||||
JSValue width_val = JS_GetPropertyStr(js, sprite, "width");
|
||||
if (!JS_IsNull(width_val)) {
|
||||
JS_ToFloat64(js, &width, width_val);
|
||||
} else if (!JS_IsNull(texture_val)) {
|
||||
// Get width from texture
|
||||
JSValue texture_width = JS_GetPropertyStr(js, texture_val, "width");
|
||||
if (!JS_IsNull(texture_width)) {
|
||||
JS_ToFloat64(js, &width, texture_width);
|
||||
}
|
||||
JS_FreeValue(js, texture_width);
|
||||
}
|
||||
JS_FreeValue(js, width_val);
|
||||
|
||||
JSValue height_val = JS_GetPropertyStr(js, sprite, "height");
|
||||
if (!JS_IsNull(height_val)) {
|
||||
JS_ToFloat64(js, &height, height_val);
|
||||
} else if (!JS_IsNull(texture_val)) {
|
||||
// Get height from texture
|
||||
JSValue texture_height = JS_GetPropertyStr(js, texture_val, "height");
|
||||
if (!JS_IsNull(texture_height)) {
|
||||
JS_ToFloat64(js, &height, texture_height);
|
||||
}
|
||||
JS_FreeValue(js, texture_height);
|
||||
}
|
||||
JS_FreeValue(js, height_val);
|
||||
|
||||
JSValue anchor_x_val = JS_GetPropertyStr(js, sprite, "anchor_x");
|
||||
if (!JS_IsNull(anchor_x_val)) {
|
||||
JS_ToFloat64(js, &anchor_x, anchor_x_val);
|
||||
}
|
||||
JS_FreeValue(js, anchor_x_val);
|
||||
|
||||
JSValue anchor_y_val = JS_GetPropertyStr(js, sprite, "anchor_y");
|
||||
if (!JS_IsNull(anchor_y_val)) {
|
||||
JS_ToFloat64(js, &anchor_y, anchor_y_val);
|
||||
}
|
||||
JS_FreeValue(js, anchor_y_val);
|
||||
|
||||
HMM_Vec2 pos = js2vec2(js, pos_val);
|
||||
|
||||
// Calculate sprite corners with anchor
|
||||
float half_w = width * 0.5f;
|
||||
float half_h = height * 0.5f;
|
||||
float anchor_offset_x = width * anchor_x - half_w;
|
||||
float anchor_offset_y = height * anchor_y - half_h;
|
||||
|
||||
float left = pos.x - half_w - anchor_offset_x;
|
||||
float right = pos.x + half_w - anchor_offset_x;
|
||||
float bottom = pos.y - half_h - anchor_offset_y;
|
||||
float top = pos.y + half_h - anchor_offset_y;
|
||||
|
||||
// Set vertex positions (4 corners of the sprite)
|
||||
int base = vertex_idx * 2;
|
||||
xy_data[base + 0] = left; // bottom-left
|
||||
xy_data[base + 1] = bottom;
|
||||
xy_data[base + 2] = right; // bottom-right
|
||||
xy_data[base + 3] = bottom;
|
||||
xy_data[base + 4] = left; // top-left
|
||||
xy_data[base + 5] = top;
|
||||
xy_data[base + 6] = right; // top-right
|
||||
xy_data[base + 7] = top;
|
||||
|
||||
// Get UV coordinates from texture (if available)
|
||||
if (!JS_IsNull(texture_val)) {
|
||||
JSValue rect_val = JS_GetPropertyStr(js, texture_val, "rect");
|
||||
if (!JS_IsNull(rect_val)) {
|
||||
rect uv_rect = js2rect(js, rect_val);
|
||||
|
||||
// Get texture dimensions to normalize pixel coordinates
|
||||
double tex_width = 1.0, tex_height = 1.0;
|
||||
JSValue texture_width = JS_GetPropertyStr(js, texture_val, "width");
|
||||
JSValue texture_height = JS_GetPropertyStr(js, texture_val, "height");
|
||||
if (!JS_IsNull(texture_width)) {
|
||||
JS_ToFloat64(js, &tex_width, texture_width);
|
||||
}
|
||||
if (!JS_IsNull(texture_height)) {
|
||||
JS_ToFloat64(js, &tex_height, texture_height);
|
||||
}
|
||||
JS_FreeValue(js, texture_width);
|
||||
JS_FreeValue(js, texture_height);
|
||||
|
||||
// The rect contains pixel coordinates, normalize them
|
||||
float u0 = uv_rect.x / tex_width;
|
||||
float v0 = uv_rect.y / tex_height;
|
||||
float u1 = (uv_rect.x + uv_rect.w) / tex_width;
|
||||
float v1 = (uv_rect.y + uv_rect.h) / tex_height;
|
||||
|
||||
// Set UVs based on normalized texture rect
|
||||
uv_data[base + 0] = u0; uv_data[base + 1] = v1; // bottom-left
|
||||
uv_data[base + 2] = u1; uv_data[base + 3] = v1; // bottom-right
|
||||
uv_data[base + 4] = u0; uv_data[base + 5] = v0; // top-left
|
||||
uv_data[base + 6] = u1; uv_data[base + 7] = v0; // top-right
|
||||
JS_FreeValue(js, rect_val);
|
||||
} else {
|
||||
// Default UVs (0-1)
|
||||
uv_data[base + 0] = 0.0f; uv_data[base + 1] = 1.0f;
|
||||
uv_data[base + 2] = 1.0f; uv_data[base + 3] = 1.0f;
|
||||
uv_data[base + 4] = 0.0f; uv_data[base + 5] = 0.0f;
|
||||
uv_data[base + 6] = 1.0f; uv_data[base + 7] = 0.0f;
|
||||
}
|
||||
} else {
|
||||
// Default UVs (0-1)
|
||||
uv_data[base + 0] = 0.0f; uv_data[base + 1] = 1.0f;
|
||||
uv_data[base + 2] = 1.0f; uv_data[base + 3] = 1.0f;
|
||||
uv_data[base + 4] = 0.0f; uv_data[base + 5] = 0.0f;
|
||||
uv_data[base + 6] = 1.0f; uv_data[base + 7] = 0.0f;
|
||||
}
|
||||
|
||||
// Set colors
|
||||
SDL_FColor default_color = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
if (!JS_IsNull(color_val)) {
|
||||
HMM_Vec4 color = js2color(js, color_val);
|
||||
default_color.r = color.r;
|
||||
default_color.g = color.g;
|
||||
default_color.b = color.b;
|
||||
default_color.a = color.a;
|
||||
}
|
||||
|
||||
for (int j = 0; j < 4; j++) {
|
||||
color_data[vertex_idx + j] = default_color;
|
||||
}
|
||||
|
||||
// Set indices (two triangles per sprite)
|
||||
uint16_t base_idx = vertex_idx;
|
||||
index_data[index_idx++] = base_idx + 0; // triangle 1: 0,1,2
|
||||
index_data[index_idx++] = base_idx + 1;
|
||||
index_data[index_idx++] = base_idx + 2;
|
||||
index_data[index_idx++] = base_idx + 1; // triangle 2: 1,3,2
|
||||
index_data[index_idx++] = base_idx + 3;
|
||||
index_data[index_idx++] = base_idx + 2;
|
||||
|
||||
vertex_idx += 4;
|
||||
|
||||
JS_FreeValue(js, pos_val);
|
||||
JS_FreeValue(js, texture_val);
|
||||
JS_FreeValue(js, color_val);
|
||||
JS_FreeValue(js, sprite);
|
||||
}
|
||||
|
||||
// Create result object with blob data
|
||||
ret = JS_NewObject(js);
|
||||
|
||||
// Create blobs for each data type
|
||||
JSValue xy_blob = js_new_blob_stoned_copy(js, xy_data, vertex_count * 2 * sizeof(float));
|
||||
JSValue uv_blob = js_new_blob_stoned_copy(js, uv_data, vertex_count * 2 * sizeof(float));
|
||||
JSValue color_blob = js_new_blob_stoned_copy(js, color_data, vertex_count * sizeof(SDL_FColor));
|
||||
JSValue index_blob = js_new_blob_stoned_copy(js, index_data, index_count * sizeof(uint16_t));
|
||||
|
||||
JS_SetPropertyStr(js, ret, "xy", xy_blob);
|
||||
JS_SetPropertyStr(js, ret, "xy_stride", JS_NewInt32(js, 2 * sizeof(float)));
|
||||
JS_SetPropertyStr(js, ret, "uv", uv_blob);
|
||||
JS_SetPropertyStr(js, ret, "uv_stride", JS_NewInt32(js, 2 * sizeof(float)));
|
||||
JS_SetPropertyStr(js, ret, "color", color_blob);
|
||||
JS_SetPropertyStr(js, ret, "color_stride", JS_NewInt32(js, sizeof(SDL_FColor)));
|
||||
JS_SetPropertyStr(js, ret, "indices", index_blob);
|
||||
JS_SetPropertyStr(js, ret, "num_vertices", JS_NewInt32(js, vertex_count));
|
||||
JS_SetPropertyStr(js, ret, "num_indices", JS_NewInt32(js, index_count));
|
||||
JS_SetPropertyStr(js, ret, "size_indices", JS_NewInt32(js, 2)); // using uint16_t
|
||||
|
||||
free(xy_data);
|
||||
free(uv_data);
|
||||
free(color_data);
|
||||
free(index_data);
|
||||
)
|
||||
|
||||
JSC_CCALL(geometry_transform_xy_blob,
|
||||
// argv[0] = xy blob (contains vertex positions as float pairs)
|
||||
// argv[1] = camera transform parameters [a, c, e, f]
|
||||
|
||||
JSValue xy_blob = argv[0];
|
||||
if (!js_is_blob(js, xy_blob)) {
|
||||
return JS_ThrowTypeError(js, "First argument must be an XY blob");
|
||||
}
|
||||
|
||||
JSValue camera_params = argv[1];
|
||||
if (!JS_IsArray(js, camera_params) || JS_ArrayLength(js, camera_params) != 4) {
|
||||
return JS_ThrowTypeError(js, "Second argument must be an array of 4 camera transform parameters [a, c, e, f]");
|
||||
}
|
||||
|
||||
// Get camera transform parameters
|
||||
double a, c, e, f;
|
||||
JSValue a_val = JS_GetPropertyUint32(js, camera_params, 0);
|
||||
JSValue c_val = JS_GetPropertyUint32(js, camera_params, 1);
|
||||
JSValue e_val = JS_GetPropertyUint32(js, camera_params, 2);
|
||||
JSValue f_val = JS_GetPropertyUint32(js, camera_params, 3);
|
||||
|
||||
JS_ToFloat64(js, &a, a_val);
|
||||
JS_ToFloat64(js, &c, c_val);
|
||||
JS_ToFloat64(js, &e, e_val);
|
||||
JS_ToFloat64(js, &f, f_val);
|
||||
|
||||
JS_FreeValue(js, a_val);
|
||||
JS_FreeValue(js, c_val);
|
||||
JS_FreeValue(js, e_val);
|
||||
JS_FreeValue(js, f_val);
|
||||
|
||||
// Get blob data
|
||||
size_t xy_size;
|
||||
float *xy_data = (float*)js_get_blob_data(js, &xy_size, xy_blob);
|
||||
if (!xy_data) {
|
||||
return JS_ThrowTypeError(js, "Failed to get XY blob data");
|
||||
}
|
||||
|
||||
// Calculate number of vertices (each vertex has 2 floats: x, y)
|
||||
int vertex_count = xy_size / (2 * sizeof(float));
|
||||
if (vertex_count * 2 * sizeof(float) != xy_size) {
|
||||
return JS_ThrowTypeError(js, "XY blob size is not a multiple of vertex size");
|
||||
}
|
||||
|
||||
// Allocate new buffer for transformed coordinates
|
||||
float *transformed_xy = malloc(xy_size);
|
||||
if (!transformed_xy) {
|
||||
return JS_ThrowTypeError(js, "Failed to allocate memory for transformed coordinates");
|
||||
}
|
||||
|
||||
// Apply camera transformation to each vertex
|
||||
for (int i = 0; i < vertex_count; i++) {
|
||||
float world_x = xy_data[i * 2 + 0];
|
||||
float world_y = xy_data[i * 2 + 1];
|
||||
|
||||
// Apply 2D affine transformation: screen = world * camera_matrix
|
||||
float screen_x = a * world_x + c;
|
||||
float screen_y = e * world_y + f;
|
||||
|
||||
transformed_xy[i * 2 + 0] = screen_x;
|
||||
transformed_xy[i * 2 + 1] = screen_y;
|
||||
}
|
||||
|
||||
// Create new blob with transformed data
|
||||
JSValue transformed_blob = js_new_blob_stoned_copy(js, transformed_xy, xy_size);
|
||||
|
||||
free(transformed_xy);
|
||||
|
||||
ret = transformed_blob;
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_geometry_funcs[] = {
|
||||
MIST_FUNC_DEF(geometry, rect_intersection, 2),
|
||||
MIST_FUNC_DEF(geometry, rect_intersects, 2),
|
||||
@@ -969,6 +1254,8 @@ static const JSCFunctionListEntry js_geometry_funcs[] = {
|
||||
MIST_FUNC_DEF(geometry, rect_move, 2),
|
||||
MIST_FUNC_DEF(geometry, rect_transform, 2),
|
||||
MIST_FUNC_DEF(geometry, tilemap_to_data, 1),
|
||||
MIST_FUNC_DEF(geometry, sprites_to_data, 1),
|
||||
MIST_FUNC_DEF(geometry, transform_xy_blob, 2),
|
||||
MIST_FUNC_DEF(gpu, tile, 4),
|
||||
MIST_FUNC_DEF(gpu, slice9, 3),
|
||||
MIST_FUNC_DEF(gpu, make_sprite_mesh, 2),
|
||||
|
||||
Reference in New Issue
Block a user