From 38a52fcb73fadaab999757db2d5c0e9b06f30f78 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Fri, 13 Jun 2025 20:48:33 -0500 Subject: [PATCH] add tilemap --- prosperon/draw2d.cm | 7 -- prosperon/prosperon.ce | 64 ++++++++++++------ prosperon/sdl_video.ce | 5 ++ prosperon/tilemap.cm | 78 +++++++++++++++++++++ scripts/engine.cm | 2 +- source/qjs_geometry.c | 149 ++++++++++++++++++++++++++++++++++++++++- source/qjs_sdl_video.c | 34 ++++++++++ 7 files changed, 310 insertions(+), 29 deletions(-) create mode 100644 prosperon/tilemap.cm diff --git a/prosperon/draw2d.cm b/prosperon/draw2d.cm index c9532b79..88028acb 100644 --- a/prosperon/draw2d.cm +++ b/prosperon/draw2d.cm @@ -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 diff --git a/prosperon/prosperon.ce b/prosperon/prosperon.ce index e4d1a8b5..a991608a 100644 --- a/prosperon/prosperon.ce +++ b/prosperon/prosperon.ce @@ -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)) diff --git a/prosperon/sdl_video.ce b/prosperon/sdl_video.ce index 3ecc8956..e0f93885 100644 --- a/prosperon/sdl_video.ce +++ b/prosperon/sdl_video.ce @@ -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); diff --git a/prosperon/tilemap.cm b/prosperon/tilemap.cm new file mode 100644 index 00000000..4d3a3d17 --- /dev/null +++ b/prosperon/tilemap.cm @@ -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 \ No newline at end of file diff --git a/scripts/engine.cm b/scripts/engine.cm index 23e61b54..27345849 100644 --- a/scripts/engine.cm +++ b/scripts/engine.cm @@ -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)) } diff --git a/source/qjs_geometry.c b/source/qjs_geometry.c index 6342d599..3b8fdb00 100644 --- a/source/qjs_geometry.c +++ b/source/qjs_geometry.c @@ -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), diff --git a/source/qjs_sdl_video.c b/source/qjs_sdl_video.c index 9a22c9c8..252404d1 100644 --- a/source/qjs_sdl_video.c +++ b/source/qjs_sdl_video.c @@ -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),