fix examples

This commit is contained in:
2026-02-25 16:58:06 -06:00
parent 250f535abe
commit 29818b1b0b
16 changed files with 354 additions and 119 deletions

View File

@@ -1,7 +1,9 @@
var backend = use('sdl_gpu')
var cam = {}
/*
presentation can be one of
/*
presentation can be one of
letterbox
overscan
stretch
@@ -13,6 +15,27 @@ function rect_contains_pt(rect, x, y) {
y >= rect.y && y <= rect.y + rect.height
}
function place_rect(src, dst, mode) {
if (mode == 'stretch')
return {x: 0, y: 0, width: dst.width, height: dst.height}
var sx = 0, sy = 0, s = 0, w = 0, h = 0, scale = 0
if (mode == 'integer_scale') {
sx = floor(dst.width / src.width)
sy = floor(dst.height / src.height)
s = max(1, min(sx, sy))
w = src.width * s
h = src.height * s
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
}
// letterbox (default)
scale = min(dst.width / src.width, dst.height / src.height)
w = src.width * scale
h = src.height * scale
return {x: (dst.width - w) / 2, y: (dst.height - h) / 2, width: w, height: h}
}
var basecam = {
pos: {x:0,y:0},
ortho:true,
@@ -30,36 +53,32 @@ var basecam = {
return this.y/this.x;
},
screen_rect() {
var src = { width: this.width, height: this.height }
var dst = { x: 0, y: 0, width: gameres.width, height: gameres.height }
return place_rect(src, dst, this.presentation, this.align_x, this.align_y, false)
},
rect() {
return {x:0, y:0, width: this.width, height: this.height}
},
sensor() {
var ws = backend.get_window_size()
return {
width: gameres.width,
height: gameres.height,
width: ws.width,
height: ws.height,
}
},
// The rectangle (in window pixels) where this cameras target will be drawn
// The rectangle (in window pixels) where this cameras target will be drawn
screen_rect() {
var src = { width: this.width, height: this.height }
var dst = { x: 0, y: 0, width: gameres.width, height: gameres.height }
return place_rect(src, dst, this.presentation, this.align_x, this.align_y, false)
var ws = backend.get_window_size()
var dst = { x: 0, y: 0, width: ws.width, height: ws.height }
return place_rect(src, dst, this.presentation)
},
// --- Space converters ---------------------------------------------------
// World -> View UV [0..1] independent of pixels/letterbox
world_to_view_uv(wx, wy) {
var ax = this.anchor[0], ay = this.anchor[1]
var u = (wx - this.pos[0]) / this.width + ax
var v = (wy - this.pos[1]) / this.height + ay
var ax = this.anchor.x, ay = this.anchor.y
var u = (wx - this.pos.x) / this.width + ax
var v = (wy - this.pos.y) / this.height + ay
// apply viewport (maps camera-local UV into sub-rect)
var vp = this.viewport
@@ -73,9 +92,9 @@ var basecam = {
var vp = this.viewport
var uu = (u - vp.x) / vp.width
var vv = (v - vp.y) / vp.height
var ax = this.anchor[0], ay = this.anchor[1]
var wx = this.pos[0] + (uu - ax) * this.width
var wy = this.pos[1] + (vv - ay) * this.height
var ax = this.anchor.x, ay = this.anchor.y
var wx = this.pos.x + (uu - ax) * this.width
var wy = this.pos.y + (vv - ay) * this.height
return { x: wx, y: wy }
},
@@ -91,39 +110,38 @@ var basecam = {
// Window pixels -> World (mouse picking)
window_to_world(sx, sy) {
var sr = this.screen_rect()
if (!rect_contains_pt(sr, sx, sy)) {
// outside letterbox bars; clamp or return null
// return null
}
var u = (sx - sr.x) / sr.width
var v = (sy - sr.y) / sr.height
var v = 1 - (sy - sr.y) / sr.height
return this.view_uv_to_world(u, v)
},
// World -> normalized window [0..1]
world_to_screen_norm(wx, wy) {
var p = this.world_to_window(wx, wy)
return { x: p.x / gameres.width, y: p.y / gameres.height }
var ws = backend.get_window_size()
return { x: p.x / ws.width, y: p.y / ws.height }
},
// Normalized window [0..1] -> World
screen_norm_to_world(nx, ny) {
var sx = nx * gameres.width
var sy = ny * gameres.height
var ws = backend.get_window_size()
var sx = nx * ws.width
var sy = ny * ws.height
return this.window_to_world(sx, sy)
},
screen_to_world: function(sx, sy) {
// sx, sy are normalized screen coordinates (0-1), bottom left [0,0], top right [1,1]
var screen_rect = this.screen_rect()
var pixel_x = sx * gameres.width
var pixel_y = sy * gameres.height
var ws = backend.get_window_size()
var pixel_x = sx * ws.width
var pixel_y = sy * ws.height
var rel_x = (pixel_x - screen_rect.x) / screen_rect.width
var rel_y = (pixel_y - screen_rect.y) / screen_rect.height
var ax = this.anchor[0]
var ay = this.anchor[1]
var world_x = this.pos[0] + (rel_x - ax) * this.width
var world_y = this.pos[1] + (rel_y - ay) * this.height
var ax = this.anchor.x
var ay = this.anchor.y
var world_x = this.pos.x + (rel_x - ax) * this.width
var world_y = this.pos.y + (rel_y - ay) * this.height
return {x: world_x, y: world_y}
},
}

View File

@@ -123,9 +123,8 @@ function compile_plane(plane_config, ctx, group_effects) {
// Allocate plane target
var plane_target = ctx.alloc(res.width, res.height, plane_config.name)
// Clear plane
if (plane_config.clear)
push(ctx.passes, {type: 'clear', target: plane_target, color: plane_config.clear})
// Always clear plane target to prevent stale data between frames
push(ctx.passes, {type: 'clear', target: plane_target, color: plane_config.clear || {r: 0, g: 0, b: 0, a: 0}})
// Render each effect group to temp target, apply effects, composite back
arrfor(array(effect_groups), gname => {

View File

@@ -59,7 +59,7 @@ for (i = 0; i < 100; i++) {
random.random() * GH
)
}
count_label.text = "Bunnies: " + length(bunnies)
count_label.text = "Bunnies: " + text(length(bunnies))
// Mouse position tracking
var mouse_x = GW / 2, mouse_y = GH / 2
@@ -85,12 +85,12 @@ core.start({
update: function(dt) {
// Spawn bunnies while clicking
var down = input.player1().down()
var si = 0
if (down.spawn) {
var si = 0
for (si = 0; si < 50; si++) {
add_bunny(mouse_x, mouse_y)
}
count_label.text = "Bunnies: " + length(bunnies)
count_label.text = "Bunnies: " + text(length(bunnies))
}
// Update all bunnies
@@ -112,7 +112,7 @@ core.start({
frame_count++
fps_timer += dt
if (fps_timer >= 1) {
fps_label.text = "FPS: " + frame_count
fps_label.text = "FPS: " + text(frame_count)
frame_count = 0
fps_timer -= 1
}

View File

@@ -3,16 +3,16 @@ var camera = use('camera')
var compositor = use('compositor')
var input = use('input')
var shape = use('shape2d')
var sprite_factory = use('sprite')
var text2d = use('text2d')
var Grid = use('grid')
var MovementSystem = use('movement').MovementSystem
var startingPos = use('pieces').startingPosition
var rules = use('rules')
var Grid = use('examples/chess/grid')
var MovementSystem = use('examples/chess/movement')
var startingPos = use('examples/chess/pieces').startingPosition
var rules = use('examples/chess/rules')
var S = 60
var GW = S * 8, GH = S * 8
var move_history = []
var game_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
var hud_cam = camera.make({width: GW, height: GH, pos: {x: GW / 2, y: GH / 2}})
@@ -38,11 +38,11 @@ var select_color = {r: 1, g: 0.84, b: 0, a: 1}
var valid_color = {r: 0.6, g: 0.8, b: 0.4, a: 1}
var board_shapes = []
var bx = 0, by = 0
var bx = 0, by = 0, row = null, col = null
for (by = 0; by < 8; by++) {
var row = []
row = []
for (bx = 0; bx < 8; bx++) {
var col = ((bx + by) & 1) ? dark_color : light_color
col = ((bx + by) & 1) ? dark_color : light_color
push(row, shape.rect({
pos: {x: bx * S + S / 2, y: by * S + S / 2},
width: S, height: S,
@@ -53,17 +53,25 @@ for (by = 0; by < 8; by++) {
push(board_shapes, row)
}
// Piece sprites — one per piece, keyed by piece object
var piece_sprites = {}
// Piece letter abbreviations
var piece_letter = {
king: "K", queen: "Q", rook: "R",
bishop: "B", knight: "N", pawn: "P"
}
var white_color = {r: 1, g: 1, b: 1, a: 1}
var black_color = {r: 0.1, g: 0.1, b: 0.1, a: 1}
// Piece labels — one per piece, keyed by piece id
var piece_labels = {}
var piece_id = 0
grid.each(function(p) {
piece_id++
p._id = piece_id
piece_sprites[piece_id] = sprite_factory({
image: p.sprite,
piece_labels[text(piece_id)] = text2d({
text: piece_letter[p.kind],
pos: {x: p.coord[0] * S + S / 2, y: p.coord[1] * S + S / 2},
width: S, height: S,
anchor_x: 0.5, anchor_y: 0.5,
size: 36,
color: (p.colour == 'white') ? white_color : black_color,
plane: 'game', layer: 1
})
})
@@ -86,17 +94,27 @@ function update_status() {
}
function reset_board_colors() {
var x = 0, y = 0
var x = 0, y = 0, col = null
for (y = 0; y < 8; y++) {
for (x = 0; x < 8; x++) {
var col = ((x + y) & 1) ? dark_color : light_color
col = ((x + y) & 1) ? dark_color : light_color
board_shapes[y][x].fill = {r: col.r, g: col.g, b: col.b, a: col.a}
}
}
}
var hover_color = {r: 0.8, g: 0.85, b: 0.6, a: 1}
function highlight_selection() {
reset_board_colors()
// Hover highlight
if (hover_gx >= 0 && hover_gx < 8 && hover_gy >= 0 && hover_gy < 8) {
board_shapes[hover_gy][hover_gx].fill = {
r: hover_color.r, g: hover_color.g, b: hover_color.b, a: hover_color.a
}
}
if (!selectPos) return
// Highlight selected square
@@ -105,9 +123,9 @@ function highlight_selection() {
}
// Highlight valid moves
var i = 0
var i = 0, m = null
for (i = 0; i < length(validMoves); i++) {
var m = validMoves[i]
m = validMoves[i]
board_shapes[m[1]][m[0]].fill = {
r: valid_color.r, g: valid_color.g, b: valid_color.b, a: valid_color.a
}
@@ -132,22 +150,24 @@ function compute_valid_moves(from) {
}
function sync_piece_sprites() {
var keys = array(piece_labels)
var i = 0
for (i = 0; i < length(keys); i++) {
piece_labels[keys[i]].visible = false
}
grid.each(function(p) {
var spr = piece_sprites[p._id]
if (!spr) return
if (p.captured) {
spr.visible = false
} else {
spr.pos.x = p.coord[0] * S + S / 2
spr.pos.y = p.coord[1] * S + S / 2
spr.visible = true
}
var lbl = piece_labels[text(p._id)]
if (!lbl) return
lbl.pos.x = p.coord[0] * S + S / 2
lbl.pos.y = p.coord[1] * S + S / 2
lbl.visible = true
})
}
// Input handler
var game_input = {
on_input: function(action, data) {
var clicked = null, cell = null, is_valid = false, i = 0, src_piece = null
if (!data.pressed) return
if (action == 'cancel') {
@@ -158,13 +178,13 @@ var game_input = {
}
if (action == 'select' && hover_gx >= 0 && hover_gx < 8 && hover_gy >= 0 && hover_gy < 8) {
var clicked = [hover_gx, hover_gy]
var cell = grid.at(clicked)
clicked = [hover_gx, hover_gy]
cell = grid.at(clicked)
if (selectPos) {
// Try to move
var is_valid = false
var i = 0
is_valid = false
i = 0
for (i = 0; i < length(validMoves); i++) {
if (validMoves[i][0] == clicked[0] && validMoves[i][1] == clicked[1]) {
is_valid = true
@@ -173,8 +193,10 @@ var game_input = {
}
if (is_valid) {
var src_piece = grid.at(selectPos)[0]
src_piece = grid.at(selectPos)[0]
if (src_piece && mover.tryMove(src_piece, clicked)) {
move_history[] = sq_name(selectPos[0], selectPos[1]) + "-" + sq_name(clicked[0], clicked[1])
log.chess("move " + sq_name(selectPos[0], selectPos[1]) + "-" + sq_name(clicked[0], clicked[1]) + " turn=" + mover.turn)
sync_piece_sprites()
update_status()
}
@@ -201,6 +223,66 @@ var game_input = {
}
input.player1().possess(game_input)
// --- Probe endpoints for AI play ---
var probe = use('probe')
var file_letters = "abcdefgh"
function sq_name(x, y) {
return text(file_letters, x, x + 1) + text(y + 1)
}
function piece_char(p) {
var chars = {king: "K", queen: "Q", rook: "R", bishop: "B", knight: "N", pawn: "P"}
var c = chars[p.kind]
if (p.colour == 'black') c = lower(c)
return c
}
probe.register("chess", {
board: function(a) {
var rows = []
var y = 0, x = 0, cell = null, row = null
for (y = 7; y >= 0; y--) {
row = ""
for (x = 0; x < 8; x++) {
cell = grid.at([x, y])
if (length(cell)) {
row = row + piece_char(cell[0])
} else {
row = row + "."
}
if (x < 7) row = row + " "
}
rows[] = row
}
return {turn: mover.turn, moves: move_history, board: rows}
},
move: function(a) {
var fx = a.fx, fy = a.fy, tx = a.tx, ty = a.ty
if (is_null(fx) || is_null(fy) || is_null(tx) || is_null(ty))
return {ok: false, error: "need fx fy tx ty"}
var from = [fx, fy]
var to = [tx, ty]
var cell = grid.at(from)
if (!length(cell))
return {ok: false, error: "no piece at " + sq_name(fx, fy)}
var piece = cell[0]
if (piece.colour != mover.turn)
return {ok: false, error: "not " + piece.colour + "'s turn"}
if (!rules.canMove(piece, from, to, grid))
return {ok: false, error: "illegal move"}
if (mover.tryMove(piece, to)) {
move_history[] = sq_name(fx, fy) + "-" + sq_name(tx, ty)
log.chess("move " + sq_name(fx, fy) + "-" + sq_name(tx, ty) + " turn=" + mover.turn)
sync_piece_sprites()
update_status()
return {ok: true, move: sq_name(fx, fy) + "-" + sq_name(tx, ty)}
}
return {ok: false, error: "move rejected"}
}
})
var comp_config = {
clear: {r: 0.15, g: 0.15, b: 0.2, a: 1},
planes: [
@@ -210,12 +292,13 @@ var comp_config = {
}
core.start({
width: 640, height: 640, title: "Chess",
width: 640, height: 640, title: "Chess", probe: true,
input: function(ev) {
var wp = null
if (ev.type == 'mouse_motion') {
// Convert pixel coords to grid coords via camera
var wp = game_cam.window_to_world(ev.pos.x, ev.pos.y)
wp = game_cam.window_to_world(ev.pos[0], ev.pos[1])
if (wp) {
hover_gx = floor(wp.x / S)
hover_gy = floor(wp.y / S)
@@ -224,6 +307,7 @@ core.start({
},
update: function(dt) {
highlight_selection()
},
render: function() {

View File

@@ -1,3 +1,6 @@
function px(p) { return !is_null(p.x) ? p.x : p[0] }
function py(p) { return !is_null(p.y) ? p.y : p[1] }
function grid(w, h) {
var newgrid = meme(grid_prototype)
newgrid.width = w;
@@ -23,25 +26,28 @@ var grid_prototype = {
// alias for cell
at(pos) {
return this.cell(pos.x, pos.y);
return this.cell(px(pos), py(pos));
},
// add an entity into a cell
add(entity, pos) {
push(this.cell(pos.x, pos.y), entity);
entity.coord = array(pos);
var cx = px(pos), cy = py(pos);
push(this.cell(cx, cy), entity);
entity.coord = [cx, cy];
},
// remove an entity from a cell
remove(entity, pos) {
this.cells[pos.y][pos.x] = filter(this.cells[pos.y][pos.x], x => x != entity)
var cx = px(pos), cy = py(pos);
this.cells[cy][cx] = filter(this.cells[cy][cx], x => x != entity)
},
// bounds check
inBounds(pos) {
var cx = px(pos), cy = py(pos);
return (
pos.x >= 0 && pos.x < this.width &&
pos.y >= 0 && pos.y < this.height
cx >= 0 && cx < this.width &&
cy >= 0 && cy < this.height
);
},
@@ -54,7 +60,7 @@ var grid_prototype = {
for (x = 0; x < this.width; x++) {
list = this.cells[y][x]
arrfor(list, function(entity) {
fn(entity, entity.coord);
fn(entity);
})
}
}
@@ -67,7 +73,7 @@ var grid_prototype = {
var x = 0;
for (y = 0; y < this.height; y++) {
for (x = 0; x < this.width; x++) {
out += length(this.cells[y][x]);
out += text(length(this.cells[y][x]));
}
if (y != this.height - 1) out += "\n";
}

View File

@@ -19,15 +19,14 @@ var MovementSystem_prototype = {
var victims = this.grid.at(dest);
if (length(victims) && victims[0].colour == piece.colour) return false;
if (length(victims)) victims[0].captured = true;
if (length(victims)) {
victims[0].captured = true;
this.grid.remove(victims[0], dest);
}
this.grid.remove(piece, piece.coord);
this.grid.add (piece, dest);
// grid.add() re-creates coord; re-add .x/.y fields:
piece.coord.x = dest.x;
piece.coord.y = dest.y;
this.turn = (this.turn == 'white') ? 'black' : 'white';
return true;
}

View File

@@ -14,14 +14,14 @@ function startingPosition(grid) {
// pawns
for (x = 0; x < 8; x++) {
grid.add(Piece('pawn', W), [x, 6]);
grid.add(Piece('pawn', B), [x, 1]);
grid.add(Piece('pawn', W), [x, 1]);
grid.add(Piece('pawn', B), [x, 6]);
}
// major pieces
var back = ['rook','knight','bishop','queen','king','bishop','knight','rook'];
for (x = 0; x < 8; x++) {
grid.add(Piece(back[x], W), [x, 7]);
grid.add(Piece(back[x], B), [x, 0]);
grid.add(Piece(back[x], W), [x, 0]);
grid.add(Piece(back[x], B), [x, 7]);
}
}

View File

@@ -5,8 +5,8 @@ function cy(c) { return !is_null(c.y) ? c.y : c[1] }
/* simple move-shape checks */
var deltas = {
pawn: function (pc, dx, dy, ctx) {
var dir = (pc.colour == 'white') ? -1 : 1;
var base = (pc.colour == 'white') ? 6 : 1;
var dir = (pc.colour == 'white') ? 1 : -1;
var base = (pc.colour == 'white') ? 1 : 6;
var one = (dy == dir && dx == 0 && length(ctx.grid.at(ctx.to)) == 0);
var two = (dy == 2 * dir && dx == 0 && cy(pc.coord) == base &&
length(ctx.grid.at({ x: cx(pc.coord), y: cy(pc.coord)+dir })) == 0 &&
@@ -42,4 +42,4 @@ function canMove(piece, from, to, grid) {
return clearLine(from, to, grid);
}
return { canMove };
return { canMove: canMove };

View File

@@ -111,12 +111,12 @@ core.start({
// Scoring
if (bx < 0) {
score2++
score_label.text = score1 + " " + score2
score_label.text = text(score1) + " " + text(score2)
reset_ball()
}
if (bx > GW) {
score1++
score_label.text = score1 + " " + score2
score_label.text = text(score1) + " " + text(score2)
reset_ball()
}
},

View File

@@ -140,6 +140,7 @@ core.start({
// New head position
var hx = snake_pos[0].x + dir.x
var hy = snake_pos[0].y + dir.y
var tail = null
// Wrap
if (hx < 0) hx = gridW - 1
@@ -164,23 +165,27 @@ core.start({
// Add head with tween from old head position
var old_wp = grid_to_world(snake_pos[0].x, snake_pos[0].y)
var new_wp = grid_to_world(hx, hy)
snake_pos.unshift({x: hx, y: hy})
var new_pos = [{x: hx, y: hy}]
for (i = 0; i < length(snake_pos); i++) push(new_pos, snake_pos[i])
snake_pos = new_pos
var head = shape.rect({
pos: {x: old_wp.x, y: old_wp.y}, width: cellSize - 2, height: cellSize - 2,
fill: {r: 0, g: 1, b: 0.3, a: 1}, plane: 'game'
})
// Smooth tween from old position to new position
tw.tween(head.pos).to({x: new_wp.x, y: new_wp.y}, move_interval).ease(ease.linear)
snake_shapes.unshift(head)
var new_shapes = [head]
for (i = 0; i < length(snake_shapes); i++) push(new_shapes, snake_shapes[i])
snake_shapes = new_shapes
// Eat apple?
if (hx == apple_gx && hy == apple_gy) {
score++
score_label.text = "Score: " + score
score_label.text = "Score: " + text(score)
spawn_apple()
} else {
// Remove tail
var tail = pop(snake_shapes)
tail = pop(snake_shapes)
tail.destroy()
pop(snake_pos)
}

View File

@@ -45,10 +45,10 @@ var board_shapes = []
function init_board() {
board = []
board_shapes = []
var r = 0, c = 0
var r = 0, c = 0, row = null, srow = null
for (r = 0; r < ROWS; r++) {
var row = []
var srow = []
row = []
srow = []
for (c = 0; c < COLS; c++) {
push(row, null)
push(srow, shape.rect({
@@ -155,7 +155,7 @@ function rotate_blocks(blocks) {
function clear_lines() {
var lines = 0
var r = ROWS - 1, c = 0, full = false, newRow = null
var r = ROWS - 1, c = 0, full = false, newRow = null, sr = 0
while (r >= 0) {
full = true
for (c = 0; c < COLS; c++) {
@@ -164,7 +164,7 @@ function clear_lines() {
if (full) {
lines++
// Shift rows down
var sr = r
sr = r
while (sr > 0) {
for (c = 0; c < COLS; c++) {
board[sr][c] = board[sr - 1][c]
@@ -191,15 +191,15 @@ function clear_lines() {
else if (lines == 4) score += 800
linesCleared += lines
level = floor(linesCleared / 10)
score_label.text = "Score: " + score
level_label.text = "Level: " + level
score_label.text = "Score: " + text(score)
level_label.text = "Level: " + text(level)
}
function update_piece_shapes() {
var i = 0
var i = 0, bx = 0, by = 0
for (i = 0; i < 4; i++) {
var bx = pieceX + piece.blocks[i][0]
var by = pieceY + piece.blocks[i][1]
bx = pieceX + piece.blocks[i][0]
by = pieceY + piece.blocks[i][1]
piece_shapes[i].pos.x = bx * TILE + TILE / 2
piece_shapes[i].pos.y = by * TILE + TILE / 2
piece_shapes[i].fill = piece.color
@@ -208,10 +208,10 @@ function update_piece_shapes() {
}
function update_next_display() {
var i = 0
var i = 0, bx = 0, by = 0
for (i = 0; i < 4; i++) {
var bx = nextPiece.blocks[i][0]
var by = nextPiece.blocks[i][1]
bx = nextPiece.blocks[i][0]
by = nextPiece.blocks[i][1]
next_shapes[i].pos.x = (COLS + 1) * TILE + bx * TILE + TILE / 2
next_shapes[i].pos.y = 20 + by * TILE + TILE / 2
next_shapes[i].fill = nextPiece.color
@@ -220,13 +220,14 @@ function update_next_display() {
}
function spawn_piece() {
var i = 0
piece = nextPiece || random_shape()
nextPiece = random_shape()
pieceX = 3
pieceY = 0
if (collides(pieceX, pieceY, piece.blocks)) {
gameOver = true
var i = 0
i = 0
for (i = 0; i < 4; i++) piece_shapes[i].visible = false
return
}

View File

@@ -880,7 +880,7 @@ const JSCFunctionListEntry js_imgui_funcs[] = {
MIST_FUNC_DEF(imgui, menu, 2),
MIST_FUNC_DEF(imgui, menubar, 1),
MIST_FUNC_DEF(imgui, mainmenubar, 1),
MIST_FUNC_DEF(imgui, menuitem, 3),
MIST_FUNC_DEF(imgui, menuitem, 4),
// Input functions
MIST_FUNC_DEF(imgui, textinput, 2),

View File

@@ -83,7 +83,7 @@ function create_user(index, config) {
// Get action down state
down() {
return this.router ? this.router.down : {}
return this.router ? this.router.down() : {}
},
// Possess an entity (clears stack, sets as sole target)

View File

@@ -1285,7 +1285,7 @@ function _execute_commands(commands, window_size) {
current_pass = null
}
_do_composite(cmd_buffer, cmd, window_size)
_do_composite(cmd_buffer, cmd)
} else if (cmd.cmd == 'end_render') {
// Flush pending draws
@@ -1960,7 +1960,7 @@ function _get_font_cache(path, size, mode) {
font =staef.sdf_font(data, size, 12.0, 14, 1.0)
} else {
// Bitmap
font =staef.font(data, size, false)
font =staef.font(data, size)
}
if (!font) {
log.console(`sdl_gpu: Failed to load font ${path}:${size}:${local_mode}`)

View File

@@ -0,0 +1,64 @@
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
float2 uv;
float4 color;
};
struct Uniforms {
float outline_width; // Outline width in normalized SDF units (0.0 = no outline, 0.1-0.3 typical)
float sharpness; // Sharpness multiplier (1.0 = normal, higher = sharper)
float2 _pad; // Padding for alignment
float4 outline_color; // Outline color RGBA
};
// Median of three values - used to combine MSDF channels
float median(float r, float g, float b) {
return max(min(r, g), min(max(r, g), b));
}
fragment float4 fragment_main(VertexOut in [[stage_in]],
texture2d<float> tex [[texture(0)]],
sampler smp [[sampler(0)]],
constant Uniforms &u [[buffer(0)]]) {
// Sample RGB channels from MSDF texture
float3 msdf = tex.sample(smp, in.uv).rgb;
// Compute signed distance from the median of the three channels
float dist = median(msdf.r, msdf.g, msdf.b);
// Edge is at 0.5 (where distance = 0 in the original SDF)
float edge = 0.5;
// Calculate anti-aliasing width based on screen-space derivatives
// MSDF typically needs tighter AA than single-channel SDF
float aa = fwidth(dist);
float sharpness = max(u.sharpness, 0.1);
aa = aa / sharpness;
// Fill coverage: inside the glyph
float fill = smoothstep(edge - aa, edge + aa, dist);
// Outline coverage: extends outward from the edge
float outline_coverage = 0.0;
if (u.outline_width > 0.0) {
float outline_edge = edge - u.outline_width;
outline_coverage = smoothstep(outline_edge - aa, outline_edge + aa, dist);
}
// Total coverage is the union of fill and outline
float total_coverage = max(fill, outline_coverage);
// Blend colors: outline where not filled, fill color where filled
float3 rgb = in.color.rgb;
if (u.outline_width > 0.0) {
rgb = mix(u.outline_color.rgb, in.color.rgb, fill);
}
// Final alpha combines coverage with vertex alpha
float a = total_coverage * in.color.a;
return float4(rgb, a);
}

View File

@@ -0,0 +1,59 @@
#include <metal_stdlib>
using namespace metal;
struct VertexOut {
float4 position [[position]];
float2 uv;
float4 color;
};
struct Uniforms {
float outline_width; // Outline width in normalized SDF units (0.0 = no outline, 0.1-0.3 typical)
float sharpness; // Sharpness multiplier (1.0 = normal, higher = sharper)
float2 _pad; // Padding for alignment
float4 outline_color; // Outline color RGBA
};
fragment float4 fragment_main(VertexOut in [[stage_in]],
texture2d<float> tex [[texture(0)]],
sampler smp [[sampler(0)]],
constant Uniforms &u [[buffer(0)]]) {
// Sample distance from alpha channel (SDF fonts store distance in alpha)
float dist = tex.sample(smp, in.uv).a;
// Edge is at 0.5 (where distance = 0 in the original SDF)
float edge = 0.5;
// Calculate anti-aliasing width based on screen-space derivatives
// Sharpness multiplier controls how tight the AA band is
float aa = fwidth(dist);
float sharpness = max(u.sharpness, 0.1); // Prevent division issues
aa = aa / sharpness;
// Fill coverage: inside the glyph
float fill = smoothstep(edge - aa, edge + aa, dist);
// Outline coverage: extends outward from the edge
float outline_coverage = 0.0;
if (u.outline_width > 0.0) {
// Outline edge is further out (lower distance value = further from glyph center)
float outline_edge = edge - u.outline_width;
outline_coverage = smoothstep(outline_edge - aa, outline_edge + aa, dist);
}
// Total coverage is the union of fill and outline
float total_coverage = max(fill, outline_coverage);
// Blend colors: outline where not filled, fill color where filled
// fill goes from 0 (outside glyph) to 1 (inside glyph)
// outline_coverage goes from 0 (outside outline) to 1 (inside outline+glyph)
float3 rgb = in.color.rgb;
if (u.outline_width > 0.0) {
rgb = mix(u.outline_color.rgb, in.color.rgb, fill);
}
// Final alpha combines coverage with vertex alpha
float a = total_coverage * in.color.a;
return float4(rgb, a);
}