add tilemap

This commit is contained in:
2025-06-13 20:48:33 -05:00
parent 95a95e55e3
commit 38a52fcb73
7 changed files with 310 additions and 29 deletions

View File

@@ -75,13 +75,6 @@ draw.point = function(pos, size, opt = {}, material) {
material: material
})
}
draw.point[cell.DOC] = `
:param pos: A 2D position ([x, y]) where the point should be drawn.
:param size: The size of the point.
:param opt: Optional geometry properties.
:param material: Material/styling information (color, shaders, etc.)
:return: None
`
draw.ellipse = function(pos, radii, def, material) {
var opt = def ? {...ellipse_def, ...def} : ellipse_def

View File

@@ -3,7 +3,7 @@ var io = use('io');
var transform = use('transform');
var rasterize = use('rasterize');
var time = use('time')
var num = use('num');
var tilemap = use('tilemap')
// Frame timing variables
var frame_times = []
@@ -32,9 +32,6 @@ $_.start(e => {
})
}, 'prosperon/sdl_video', cnf)
var input = use('input')
var geometry = use('geometry')
function updateCameraMatrix(camera, winW, winH) {
@@ -101,7 +98,6 @@ function worldToScreenRect(rect, camera) {
};
}
var camera = {
size: [640,480],//{width:500,height:500}, // pixel size the camera "sees", like its resolution
pos: [250,250],//{x:0,y:0}, // where it is
@@ -256,6 +252,22 @@ function translate_draw_commands(commands) {
}
})
break
case "tilemap":
var texid
tilemap.for(cmd.tilemap, (tile,{x,y}) => {
if (!texid) texid = graphics.texture(tile)
return graphics.texture(tile)
})
var geom = geometry.tilemap_to_data(cmd.tilemap)
if (texid.gpu)
geom.texture_id = texid.gpu.id
renderer_commands.push({
op: "geometry_raw",
data: geom
})
break
}
})
@@ -289,12 +301,36 @@ var last_time = time.number()
var frames = []
var frame_avg = 0
var input = use('input')
var input_state = {
poll: 1/60
}
// 1) input runs completely independently
function poll_input() {
send(video, {kind:'input', op:'get'}, evs => {
for (let ev of evs) if (ev.type === 'quit') $_.stop()
for (var ev of evs) {
if (ev.type === 'quit')
$_.stop()
if (ev.type.includes('mouse')) {
if (ev.pos)
ev.pos = screenToWorldPoint(ev.pos, camera, 500,500)
if (ev.d_pos)
ev.d_pos.y *= -1
}
if (ev.type.includes('key')) {
if (ev.key)
ev.key = input.keyname(ev.key)
}
}
send(gameactor, evs)
})
$_.delay(poll_input, 1/60)
$_.delay(poll_input, input_state.poll)
}
// 2) helper to build & send a batch, then call done()
@@ -361,20 +397,8 @@ function render_step() {
}
$_.receiver(e => {
if (e.type) {
if (e.type === 'quit')
$_.stop()
if (e.type.includes('mouse')) {
if (e.pos)
e.pos = screenToWorldPoint(e.pos, camera, 500, 500)
if (e.d_pos)
e.d_pos.y *= -1
}
}
switch(e.op) {
case 'resolution':
log.console(json.encode(e))

View File

@@ -360,6 +360,11 @@ var renderfuncs = {
return {success: true};
},
geometry_raw: function geometry_raw(msg) {
var geom = msg.data
ren.geometry_raw(resources.texture[geom.texture_id], geom.xy, geom.xy_stride, geom.color, geom.color_stride, geom.uv, geom.uv_stride, geom.num_vertices, geom.indices, geom.num_indices, geom.size_indices);
},
debugText: function(msg) {
if (!msg.data || !msg.data.text) return {error: "Missing text"};
ren.debugText([msg.data.pos.x, msg.data.pos.y], msg.data.text);

78
prosperon/tilemap.cm Normal file
View File

@@ -0,0 +1,78 @@
// tilemap
function tilemap()
{
this.tiles = [];
this.offset_x = 0;
this.offset_y = 0;
this.size_x = 32;
this.size_y = 32;
return this;
}
tilemap.for = function (map, fn) {
for (var x = 0; x < map.tiles.length; x++) {
if (!map.tiles[x]) continue;
for (var y = 0; y < map.tiles[x].length; y++) {
if (map.tiles[x][y] !== undefined) {
var result = fn(map.tiles[x][y], {
x: x + map.offset_x,
y: y + map.offset_y
});
if (result !== undefined) {
map.tiles[x][y] = result;
}
}
}
}
}
tilemap.prototype =
{
at(pos) {
var x = pos.x - this.offset_x;
var y = pos.y - this.offset_y;
if (!this.tiles[x]) return undefined;
return this.tiles[x][y];
},
set(pos, image) {
// Shift arrays if negative indices
if (pos.x < this.offset_x) {
var shift = this.offset_x - pos.x;
var new_tiles = [];
for (var i = 0; i < shift; i++) new_tiles[i] = [];
this.tiles = new_tiles.concat(this.tiles);
this.offset_x = pos.x;
}
if (pos.y < this.offset_y) {
var shift = this.offset_y - pos.y;
for (var i = 0; i < this.tiles.length; i++) {
if (!this.tiles[i]) this.tiles[i] = [];
var new_col = [];
for (var j = 0; j < shift; j++) new_col[j] = undefined;
this.tiles[i] = new_col.concat(this.tiles[i]);
}
this.offset_y = pos.y;
}
var x = pos.x - this.offset_x;
var y = pos.y - this.offset_y;
// Ensure array exists up to x
while (this.tiles.length <= x) this.tiles.push([]);
// Set the value
this.tiles[x][y] = image;
},
draw() {
return {
cmd:'tilemap',
tilemap:this
}
},
}
return tilemap

View File

@@ -43,7 +43,7 @@ var console_mod = cell.hidden.console
var logs = {}
logs.console = function(msg)
{
var caller = caller_data(4)
var caller = caller_data(2)
console_mod.print(console_rec(caller.line, caller.file, msg))
}

View File

@@ -243,7 +243,6 @@ static inline void tile_region(text_vert **verts, rect src_uv, rect dst, float t
}
}
JSC_CCALL(gpu_slice9,
JSValue jstex = argv[0];
rect dst = js2rect(js, argv[1]);
@@ -808,6 +807,153 @@ JSC_CCALL(geometry_rect_transform,
return rect2js(js, newrect);
)
JSC_CCALL(geometry_tilemap_to_data,
JSValue tilemap_obj = argv[0];
// Get tilemap properties
double offset_x, offset_y, size_x, size_y;
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)
JSValue tiles_array = JS_GetPropertyStr(js, tilemap_obj, "tiles");
if (!JS_IsArray(js, tiles_array)) {
JS_FreeValue(js, tiles_array);
return JS_ThrowTypeError(js, "tilemap.tiles must be an array");
}
// Count tiles
int tile_count = 0;
int tiles_len = JS_ArrayLength(js, tiles_array);
for (int x = 0; x < tiles_len; x++) {
JSValue col = JS_GetPropertyUint32(js, tiles_array, x);
if (JS_IsArray(js, col)) {
int col_len = JS_ArrayLength(js, col);
for (int y = 0; y < col_len; y++) {
JSValue tile = JS_GetPropertyUint32(js, col, y);
if (!JS_IsUndefined(tile) && !JS_IsNull(tile)) {
tile_count++;
}
JS_FreeValue(js, tile);
}
}
JS_FreeValue(js, col);
}
if (tile_count == 0) {
JS_FreeValue(js, tiles_array);
return JS_NewObject(js);
}
// Allocate buffers - 4 vertices per tile
int vertex_count = tile_count * 4;
int index_count = tile_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 x = 0; x < tiles_len; x++) {
JSValue col = JS_GetPropertyUint32(js, tiles_array, x);
if (JS_IsArray(js, col)) {
int col_len = JS_ArrayLength(js, col);
for (int y = 0; y < col_len; y++) {
JSValue tile = JS_GetPropertyUint32(js, col, y);
if (!JS_IsUndefined(tile) && !JS_IsNull(tile)) {
// Calculate world position
float world_x = (x + offset_x) * size_x;
float world_y = (y + offset_y) * size_y;
// Set vertex positions (4 corners of the tile)
int base = vertex_idx * 2;
xy_data[base + 0] = world_x;
xy_data[base + 1] = world_y;
xy_data[base + 2] = world_x + size_x;
xy_data[base + 3] = world_y;
xy_data[base + 4] = world_x;
xy_data[base + 5] = world_y + size_y;
xy_data[base + 6] = world_x + size_x;
xy_data[base + 7] = world_y + size_y;
// Set UVs (normalized 0-1 for now)
uv_data[base + 0] = 0.0f;
uv_data[base + 1] = 0.0f;
uv_data[base + 2] = 1.0f;
uv_data[base + 3] = 0.0f;
uv_data[base + 4] = 0.0f;
uv_data[base + 5] = 1.0f;
uv_data[base + 6] = 1.0f;
uv_data[base + 7] = 1.0f;
// Set colors (check if tile has color property)
SDL_FColor default_color = {1.0f, 1.0f, 1.0f, 1.0f};
if (JS_IsObject(tile)) {
JSValue color_val = JS_GetPropertyStr(js, tile, "color");
if (!JS_IsUndefined(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;
JS_FreeValue(js, color_val);
}
}
for (int i = 0; i < 4; i++) {
color_data[vertex_idx + i] = default_color;
}
// Set indices (two triangles per tile)
uint16_t base_idx = vertex_idx;
index_data[index_idx++] = base_idx + 0;
index_data[index_idx++] = base_idx + 1;
index_data[index_idx++] = base_idx + 2;
index_data[index_idx++] = base_idx + 1;
index_data[index_idx++] = base_idx + 3;
index_data[index_idx++] = base_idx + 2;
vertex_idx += 4;
}
JS_FreeValue(js, tile);
}
}
JS_FreeValue(js, col);
}
JS_FreeValue(js, tiles_array);
// 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);
)
static const JSCFunctionListEntry js_geometry_funcs[] = {
MIST_FUNC_DEF(geometry, rect_intersection, 2),
MIST_FUNC_DEF(geometry, rect_intersects, 2),
@@ -819,6 +965,7 @@ static const JSCFunctionListEntry js_geometry_funcs[] = {
MIST_FUNC_DEF(geometry, rect_pos, 1),
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(gpu, tile, 4),
MIST_FUNC_DEF(gpu, slice9, 3),
MIST_FUNC_DEF(gpu, make_sprite_mesh, 2),

View File

@@ -888,6 +888,39 @@ JSC_CCALL(renderer_geometry,
JS_FreeValue(js,indices);
)
JSC_CCALL(renderer_geometry_raw,
SDL_Renderer *r = js2SDL_Renderer(js,self);
// argv[0] is texture
SDL_Texture *tex = NULL;
if (argc > 0 && !JS_IsNull(argv[0]) && !JS_IsUndefined(argv[0]))
tex = js2SDL_Texture(js,argv[0]);
// Get blob data
size_t xy_size, color_size, uv_size, indices_size;
void *xy_data = js_get_blob_data(js, &xy_size, argv[1]);
int xy_stride = js2number(js, argv[2]);
void *color_data = js_get_blob_data(js, &color_size, argv[3]);
int color_stride = js2number(js, argv[4]);
void *uv_data = js_get_blob_data(js, &uv_size, argv[5]);
int uv_stride = js2number(js, argv[6]);
int num_vertices = js2number(js, argv[7]);
void *indices_data = js_get_blob_data(js, &indices_size, argv[8]);
int num_indices = js2number(js, argv[9]);
int size_indices = js2number(js, argv[10]);
if (!xy_data || !color_data || !uv_data)
return JS_ThrowTypeError(js, "Invalid geometry data");
if (!SDL_RenderGeometryRaw(r, tex,
(const float *)xy_data, xy_stride,
(const SDL_FColor *)color_data, color_stride,
(const float *)uv_data, uv_stride,
num_vertices,
indices_data, num_indices, size_indices))
return JS_ThrowReferenceError(js, "Error rendering geometry: %s", SDL_GetError());
)
JSC_CCALL(renderer_geometry2,
SDL_Renderer *r = js2SDL_Renderer(js, self);
@@ -1378,6 +1411,7 @@ static const JSCFunctionListEntry js_SDL_Renderer_funcs[] = {
MIST_FUNC_DEF(renderer, texture, 5),
MIST_FUNC_DEF(renderer, rects, 1),
MIST_FUNC_DEF(renderer, geometry, 2),
MIST_FUNC_DEF(renderer, geometry_raw, 11),
MIST_FUNC_DEF(renderer, geometry2, 2),
MIST_FUNC_DEF(renderer, sprite, 1),
MIST_FUNC_DEF(renderer, load_texture, 1),