fix examples
This commit is contained in:
86
camera.cm
86
camera.cm
@@ -1,7 +1,9 @@
|
|||||||
|
var backend = use('sdl_gpu')
|
||||||
|
|
||||||
var cam = {}
|
var cam = {}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
presentation can be one of
|
presentation can be one of
|
||||||
letterbox
|
letterbox
|
||||||
overscan
|
overscan
|
||||||
stretch
|
stretch
|
||||||
@@ -13,6 +15,27 @@ function rect_contains_pt(rect, x, y) {
|
|||||||
y >= rect.y && y <= rect.y + rect.height
|
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 = {
|
var basecam = {
|
||||||
pos: {x:0,y:0},
|
pos: {x:0,y:0},
|
||||||
ortho:true,
|
ortho:true,
|
||||||
@@ -30,36 +53,32 @@ var basecam = {
|
|||||||
return this.y/this.x;
|
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() {
|
rect() {
|
||||||
return {x:0, y:0, width: this.width, height: this.height}
|
return {x:0, y:0, width: this.width, height: this.height}
|
||||||
},
|
},
|
||||||
|
|
||||||
sensor() {
|
sensor() {
|
||||||
|
var ws = backend.get_window_size()
|
||||||
return {
|
return {
|
||||||
width: gameres.width,
|
width: ws.width,
|
||||||
height: gameres.height,
|
height: ws.height,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// The rectangle (in window pixels) where this camera’s target will be drawn
|
// The rectangle (in window pixels) where this camera’s target will be drawn
|
||||||
screen_rect() {
|
screen_rect() {
|
||||||
var src = { width: this.width, height: this.height }
|
var src = { width: this.width, height: this.height }
|
||||||
var dst = { x: 0, y: 0, width: gameres.width, height: gameres.height }
|
var ws = backend.get_window_size()
|
||||||
return place_rect(src, dst, this.presentation, this.align_x, this.align_y, false)
|
var dst = { x: 0, y: 0, width: ws.width, height: ws.height }
|
||||||
|
return place_rect(src, dst, this.presentation)
|
||||||
},
|
},
|
||||||
|
|
||||||
// --- Space converters ---------------------------------------------------
|
// --- Space converters ---------------------------------------------------
|
||||||
// World -> View UV [0..1] independent of pixels/letterbox
|
// World -> View UV [0..1] independent of pixels/letterbox
|
||||||
world_to_view_uv(wx, wy) {
|
world_to_view_uv(wx, wy) {
|
||||||
var ax = this.anchor[0], ay = this.anchor[1]
|
var ax = this.anchor.x, ay = this.anchor.y
|
||||||
var u = (wx - this.pos[0]) / this.width + ax
|
var u = (wx - this.pos.x) / this.width + ax
|
||||||
var v = (wy - this.pos[1]) / this.height + ay
|
var v = (wy - this.pos.y) / this.height + ay
|
||||||
|
|
||||||
// apply viewport (maps camera-local UV into sub-rect)
|
// apply viewport (maps camera-local UV into sub-rect)
|
||||||
var vp = this.viewport
|
var vp = this.viewport
|
||||||
@@ -73,9 +92,9 @@ var basecam = {
|
|||||||
var vp = this.viewport
|
var vp = this.viewport
|
||||||
var uu = (u - vp.x) / vp.width
|
var uu = (u - vp.x) / vp.width
|
||||||
var vv = (v - vp.y) / vp.height
|
var vv = (v - vp.y) / vp.height
|
||||||
var ax = this.anchor[0], ay = this.anchor[1]
|
var ax = this.anchor.x, ay = this.anchor.y
|
||||||
var wx = this.pos[0] + (uu - ax) * this.width
|
var wx = this.pos.x + (uu - ax) * this.width
|
||||||
var wy = this.pos[1] + (vv - ay) * this.height
|
var wy = this.pos.y + (vv - ay) * this.height
|
||||||
return { x: wx, y: wy }
|
return { x: wx, y: wy }
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -91,39 +110,38 @@ var basecam = {
|
|||||||
// Window pixels -> World (mouse picking)
|
// Window pixels -> World (mouse picking)
|
||||||
window_to_world(sx, sy) {
|
window_to_world(sx, sy) {
|
||||||
var sr = this.screen_rect()
|
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 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)
|
return this.view_uv_to_world(u, v)
|
||||||
},
|
},
|
||||||
|
|
||||||
// World -> normalized window [0..1]
|
// World -> normalized window [0..1]
|
||||||
world_to_screen_norm(wx, wy) {
|
world_to_screen_norm(wx, wy) {
|
||||||
var p = this.world_to_window(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
|
// Normalized window [0..1] -> World
|
||||||
screen_norm_to_world(nx, ny) {
|
screen_norm_to_world(nx, ny) {
|
||||||
var sx = nx * gameres.width
|
var ws = backend.get_window_size()
|
||||||
var sy = ny * gameres.height
|
var sx = nx * ws.width
|
||||||
|
var sy = ny * ws.height
|
||||||
return this.window_to_world(sx, sy)
|
return this.window_to_world(sx, sy)
|
||||||
},
|
},
|
||||||
|
|
||||||
screen_to_world: function(sx, sy) {
|
screen_to_world: function(sx, sy) {
|
||||||
// sx, sy are normalized screen coordinates (0-1), bottom left [0,0], top right [1,1]
|
// sx, sy are normalized screen coordinates (0-1), bottom left [0,0], top right [1,1]
|
||||||
var screen_rect = this.screen_rect()
|
var screen_rect = this.screen_rect()
|
||||||
var pixel_x = sx * gameres.width
|
var ws = backend.get_window_size()
|
||||||
var pixel_y = sy * gameres.height
|
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_x = (pixel_x - screen_rect.x) / screen_rect.width
|
||||||
var rel_y = (pixel_y - screen_rect.y) / screen_rect.height
|
var rel_y = (pixel_y - screen_rect.y) / screen_rect.height
|
||||||
var ax = this.anchor[0]
|
var ax = this.anchor.x
|
||||||
var ay = this.anchor[1]
|
var ay = this.anchor.y
|
||||||
var world_x = this.pos[0] + (rel_x - ax) * this.width
|
var world_x = this.pos.x + (rel_x - ax) * this.width
|
||||||
var world_y = this.pos[1] + (rel_y - ay) * this.height
|
var world_y = this.pos.y + (rel_y - ay) * this.height
|
||||||
return {x: world_x, y: world_y}
|
return {x: world_x, y: world_y}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,9 +123,8 @@ function compile_plane(plane_config, ctx, group_effects) {
|
|||||||
// Allocate plane target
|
// Allocate plane target
|
||||||
var plane_target = ctx.alloc(res.width, res.height, plane_config.name)
|
var plane_target = ctx.alloc(res.width, res.height, plane_config.name)
|
||||||
|
|
||||||
// Clear plane
|
// Always clear plane target to prevent stale data between frames
|
||||||
if (plane_config.clear)
|
push(ctx.passes, {type: 'clear', target: plane_target, color: plane_config.clear || {r: 0, g: 0, b: 0, a: 0}})
|
||||||
push(ctx.passes, {type: 'clear', target: plane_target, color: plane_config.clear})
|
|
||||||
|
|
||||||
// Render each effect group to temp target, apply effects, composite back
|
// Render each effect group to temp target, apply effects, composite back
|
||||||
arrfor(array(effect_groups), gname => {
|
arrfor(array(effect_groups), gname => {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ for (i = 0; i < 100; i++) {
|
|||||||
random.random() * GH
|
random.random() * GH
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
count_label.text = "Bunnies: " + length(bunnies)
|
count_label.text = "Bunnies: " + text(length(bunnies))
|
||||||
|
|
||||||
// Mouse position tracking
|
// Mouse position tracking
|
||||||
var mouse_x = GW / 2, mouse_y = GH / 2
|
var mouse_x = GW / 2, mouse_y = GH / 2
|
||||||
@@ -85,12 +85,12 @@ core.start({
|
|||||||
update: function(dt) {
|
update: function(dt) {
|
||||||
// Spawn bunnies while clicking
|
// Spawn bunnies while clicking
|
||||||
var down = input.player1().down()
|
var down = input.player1().down()
|
||||||
|
var si = 0
|
||||||
if (down.spawn) {
|
if (down.spawn) {
|
||||||
var si = 0
|
|
||||||
for (si = 0; si < 50; si++) {
|
for (si = 0; si < 50; si++) {
|
||||||
add_bunny(mouse_x, mouse_y)
|
add_bunny(mouse_x, mouse_y)
|
||||||
}
|
}
|
||||||
count_label.text = "Bunnies: " + length(bunnies)
|
count_label.text = "Bunnies: " + text(length(bunnies))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update all bunnies
|
// Update all bunnies
|
||||||
@@ -112,7 +112,7 @@ core.start({
|
|||||||
frame_count++
|
frame_count++
|
||||||
fps_timer += dt
|
fps_timer += dt
|
||||||
if (fps_timer >= 1) {
|
if (fps_timer >= 1) {
|
||||||
fps_label.text = "FPS: " + frame_count
|
fps_label.text = "FPS: " + text(frame_count)
|
||||||
frame_count = 0
|
frame_count = 0
|
||||||
fps_timer -= 1
|
fps_timer -= 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ var camera = use('camera')
|
|||||||
var compositor = use('compositor')
|
var compositor = use('compositor')
|
||||||
var input = use('input')
|
var input = use('input')
|
||||||
var shape = use('shape2d')
|
var shape = use('shape2d')
|
||||||
var sprite_factory = use('sprite')
|
|
||||||
var text2d = use('text2d')
|
var text2d = use('text2d')
|
||||||
|
|
||||||
var Grid = use('grid')
|
var Grid = use('examples/chess/grid')
|
||||||
var MovementSystem = use('movement').MovementSystem
|
var MovementSystem = use('examples/chess/movement')
|
||||||
var startingPos = use('pieces').startingPosition
|
var startingPos = use('examples/chess/pieces').startingPosition
|
||||||
var rules = use('rules')
|
var rules = use('examples/chess/rules')
|
||||||
|
|
||||||
var S = 60
|
var S = 60
|
||||||
var GW = S * 8, GH = S * 8
|
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 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}})
|
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 valid_color = {r: 0.6, g: 0.8, b: 0.4, a: 1}
|
||||||
|
|
||||||
var board_shapes = []
|
var board_shapes = []
|
||||||
var bx = 0, by = 0
|
var bx = 0, by = 0, row = null, col = null
|
||||||
for (by = 0; by < 8; by++) {
|
for (by = 0; by < 8; by++) {
|
||||||
var row = []
|
row = []
|
||||||
for (bx = 0; bx < 8; bx++) {
|
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({
|
push(row, shape.rect({
|
||||||
pos: {x: bx * S + S / 2, y: by * S + S / 2},
|
pos: {x: bx * S + S / 2, y: by * S + S / 2},
|
||||||
width: S, height: S,
|
width: S, height: S,
|
||||||
@@ -53,17 +53,25 @@ for (by = 0; by < 8; by++) {
|
|||||||
push(board_shapes, row)
|
push(board_shapes, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Piece sprites — one per piece, keyed by piece object
|
// Piece letter abbreviations
|
||||||
var piece_sprites = {}
|
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
|
var piece_id = 0
|
||||||
grid.each(function(p) {
|
grid.each(function(p) {
|
||||||
piece_id++
|
piece_id++
|
||||||
p._id = piece_id
|
p._id = piece_id
|
||||||
piece_sprites[piece_id] = sprite_factory({
|
piece_labels[text(piece_id)] = text2d({
|
||||||
image: p.sprite,
|
text: piece_letter[p.kind],
|
||||||
pos: {x: p.coord[0] * S + S / 2, y: p.coord[1] * S + S / 2},
|
pos: {x: p.coord[0] * S + S / 2, y: p.coord[1] * S + S / 2},
|
||||||
width: S, height: S,
|
size: 36,
|
||||||
anchor_x: 0.5, anchor_y: 0.5,
|
color: (p.colour == 'white') ? white_color : black_color,
|
||||||
plane: 'game', layer: 1
|
plane: 'game', layer: 1
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -86,17 +94,27 @@ function update_status() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function reset_board_colors() {
|
function reset_board_colors() {
|
||||||
var x = 0, y = 0
|
var x = 0, y = 0, col = null
|
||||||
for (y = 0; y < 8; y++) {
|
for (y = 0; y < 8; y++) {
|
||||||
for (x = 0; x < 8; x++) {
|
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}
|
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() {
|
function highlight_selection() {
|
||||||
reset_board_colors()
|
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
|
if (!selectPos) return
|
||||||
|
|
||||||
// Highlight selected square
|
// Highlight selected square
|
||||||
@@ -105,9 +123,9 @@ function highlight_selection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Highlight valid moves
|
// Highlight valid moves
|
||||||
var i = 0
|
var i = 0, m = null
|
||||||
for (i = 0; i < length(validMoves); i++) {
|
for (i = 0; i < length(validMoves); i++) {
|
||||||
var m = validMoves[i]
|
m = validMoves[i]
|
||||||
board_shapes[m[1]][m[0]].fill = {
|
board_shapes[m[1]][m[0]].fill = {
|
||||||
r: valid_color.r, g: valid_color.g, b: valid_color.b, a: valid_color.a
|
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() {
|
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) {
|
grid.each(function(p) {
|
||||||
var spr = piece_sprites[p._id]
|
var lbl = piece_labels[text(p._id)]
|
||||||
if (!spr) return
|
if (!lbl) return
|
||||||
if (p.captured) {
|
lbl.pos.x = p.coord[0] * S + S / 2
|
||||||
spr.visible = false
|
lbl.pos.y = p.coord[1] * S + S / 2
|
||||||
} else {
|
lbl.visible = true
|
||||||
spr.pos.x = p.coord[0] * S + S / 2
|
|
||||||
spr.pos.y = p.coord[1] * S + S / 2
|
|
||||||
spr.visible = true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input handler
|
// Input handler
|
||||||
var game_input = {
|
var game_input = {
|
||||||
on_input: function(action, data) {
|
on_input: function(action, data) {
|
||||||
|
var clicked = null, cell = null, is_valid = false, i = 0, src_piece = null
|
||||||
if (!data.pressed) return
|
if (!data.pressed) return
|
||||||
|
|
||||||
if (action == 'cancel') {
|
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) {
|
if (action == 'select' && hover_gx >= 0 && hover_gx < 8 && hover_gy >= 0 && hover_gy < 8) {
|
||||||
var clicked = [hover_gx, hover_gy]
|
clicked = [hover_gx, hover_gy]
|
||||||
var cell = grid.at(clicked)
|
cell = grid.at(clicked)
|
||||||
|
|
||||||
if (selectPos) {
|
if (selectPos) {
|
||||||
// Try to move
|
// Try to move
|
||||||
var is_valid = false
|
is_valid = false
|
||||||
var i = 0
|
i = 0
|
||||||
for (i = 0; i < length(validMoves); i++) {
|
for (i = 0; i < length(validMoves); i++) {
|
||||||
if (validMoves[i][0] == clicked[0] && validMoves[i][1] == clicked[1]) {
|
if (validMoves[i][0] == clicked[0] && validMoves[i][1] == clicked[1]) {
|
||||||
is_valid = true
|
is_valid = true
|
||||||
@@ -173,8 +193,10 @@ var game_input = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_valid) {
|
if (is_valid) {
|
||||||
var src_piece = grid.at(selectPos)[0]
|
src_piece = grid.at(selectPos)[0]
|
||||||
if (src_piece && mover.tryMove(src_piece, clicked)) {
|
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()
|
sync_piece_sprites()
|
||||||
update_status()
|
update_status()
|
||||||
}
|
}
|
||||||
@@ -201,6 +223,66 @@ var game_input = {
|
|||||||
}
|
}
|
||||||
input.player1().possess(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 = {
|
var comp_config = {
|
||||||
clear: {r: 0.15, g: 0.15, b: 0.2, a: 1},
|
clear: {r: 0.15, g: 0.15, b: 0.2, a: 1},
|
||||||
planes: [
|
planes: [
|
||||||
@@ -210,12 +292,13 @@ var comp_config = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
core.start({
|
core.start({
|
||||||
width: 640, height: 640, title: "Chess",
|
width: 640, height: 640, title: "Chess", probe: true,
|
||||||
|
|
||||||
input: function(ev) {
|
input: function(ev) {
|
||||||
|
var wp = null
|
||||||
if (ev.type == 'mouse_motion') {
|
if (ev.type == 'mouse_motion') {
|
||||||
// Convert pixel coords to grid coords via camera
|
// 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) {
|
if (wp) {
|
||||||
hover_gx = floor(wp.x / S)
|
hover_gx = floor(wp.x / S)
|
||||||
hover_gy = floor(wp.y / S)
|
hover_gy = floor(wp.y / S)
|
||||||
@@ -224,6 +307,7 @@ core.start({
|
|||||||
},
|
},
|
||||||
|
|
||||||
update: function(dt) {
|
update: function(dt) {
|
||||||
|
highlight_selection()
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
|||||||
@@ -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) {
|
function grid(w, h) {
|
||||||
var newgrid = meme(grid_prototype)
|
var newgrid = meme(grid_prototype)
|
||||||
newgrid.width = w;
|
newgrid.width = w;
|
||||||
@@ -23,25 +26,28 @@ var grid_prototype = {
|
|||||||
|
|
||||||
// alias for cell
|
// alias for cell
|
||||||
at(pos) {
|
at(pos) {
|
||||||
return this.cell(pos.x, pos.y);
|
return this.cell(px(pos), py(pos));
|
||||||
},
|
},
|
||||||
|
|
||||||
// add an entity into a cell
|
// add an entity into a cell
|
||||||
add(entity, pos) {
|
add(entity, pos) {
|
||||||
push(this.cell(pos.x, pos.y), entity);
|
var cx = px(pos), cy = py(pos);
|
||||||
entity.coord = array(pos);
|
push(this.cell(cx, cy), entity);
|
||||||
|
entity.coord = [cx, cy];
|
||||||
},
|
},
|
||||||
|
|
||||||
// remove an entity from a cell
|
// remove an entity from a cell
|
||||||
remove(entity, pos) {
|
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
|
// bounds check
|
||||||
inBounds(pos) {
|
inBounds(pos) {
|
||||||
|
var cx = px(pos), cy = py(pos);
|
||||||
return (
|
return (
|
||||||
pos.x >= 0 && pos.x < this.width &&
|
cx >= 0 && cx < this.width &&
|
||||||
pos.y >= 0 && pos.y < this.height
|
cy >= 0 && cy < this.height
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -54,7 +60,7 @@ var grid_prototype = {
|
|||||||
for (x = 0; x < this.width; x++) {
|
for (x = 0; x < this.width; x++) {
|
||||||
list = this.cells[y][x]
|
list = this.cells[y][x]
|
||||||
arrfor(list, function(entity) {
|
arrfor(list, function(entity) {
|
||||||
fn(entity, entity.coord);
|
fn(entity);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +73,7 @@ var grid_prototype = {
|
|||||||
var x = 0;
|
var x = 0;
|
||||||
for (y = 0; y < this.height; y++) {
|
for (y = 0; y < this.height; y++) {
|
||||||
for (x = 0; x < this.width; x++) {
|
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";
|
if (y != this.height - 1) out += "\n";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,15 +19,14 @@ var MovementSystem_prototype = {
|
|||||||
|
|
||||||
var victims = this.grid.at(dest);
|
var victims = this.grid.at(dest);
|
||||||
if (length(victims) && victims[0].colour == piece.colour) return false;
|
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.remove(piece, piece.coord);
|
||||||
this.grid.add (piece, dest);
|
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';
|
this.turn = (this.turn == 'white') ? 'black' : 'white';
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ function startingPosition(grid) {
|
|||||||
|
|
||||||
// pawns
|
// pawns
|
||||||
for (x = 0; x < 8; x++) {
|
for (x = 0; x < 8; x++) {
|
||||||
grid.add(Piece('pawn', W), [x, 6]);
|
grid.add(Piece('pawn', W), [x, 1]);
|
||||||
grid.add(Piece('pawn', B), [x, 1]);
|
grid.add(Piece('pawn', B), [x, 6]);
|
||||||
}
|
}
|
||||||
// major pieces
|
// major pieces
|
||||||
var back = ['rook','knight','bishop','queen','king','bishop','knight','rook'];
|
var back = ['rook','knight','bishop','queen','king','bishop','knight','rook'];
|
||||||
for (x = 0; x < 8; x++) {
|
for (x = 0; x < 8; x++) {
|
||||||
grid.add(Piece(back[x], W), [x, 7]);
|
grid.add(Piece(back[x], W), [x, 0]);
|
||||||
grid.add(Piece(back[x], B), [x, 0]);
|
grid.add(Piece(back[x], B), [x, 7]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ function cy(c) { return !is_null(c.y) ? c.y : c[1] }
|
|||||||
/* simple move-shape checks */
|
/* simple move-shape checks */
|
||||||
var deltas = {
|
var deltas = {
|
||||||
pawn: function (pc, dx, dy, ctx) {
|
pawn: function (pc, dx, dy, ctx) {
|
||||||
var dir = (pc.colour == 'white') ? -1 : 1;
|
var dir = (pc.colour == 'white') ? 1 : -1;
|
||||||
var base = (pc.colour == 'white') ? 6 : 1;
|
var base = (pc.colour == 'white') ? 1 : 6;
|
||||||
var one = (dy == dir && dx == 0 && length(ctx.grid.at(ctx.to)) == 0);
|
var one = (dy == dir && dx == 0 && length(ctx.grid.at(ctx.to)) == 0);
|
||||||
var two = (dy == 2 * dir && dx == 0 && cy(pc.coord) == base &&
|
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 &&
|
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 clearLine(from, to, grid);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { canMove };
|
return { canMove: canMove };
|
||||||
|
|||||||
@@ -111,12 +111,12 @@ core.start({
|
|||||||
// Scoring
|
// Scoring
|
||||||
if (bx < 0) {
|
if (bx < 0) {
|
||||||
score2++
|
score2++
|
||||||
score_label.text = score1 + " " + score2
|
score_label.text = text(score1) + " " + text(score2)
|
||||||
reset_ball()
|
reset_ball()
|
||||||
}
|
}
|
||||||
if (bx > GW) {
|
if (bx > GW) {
|
||||||
score1++
|
score1++
|
||||||
score_label.text = score1 + " " + score2
|
score_label.text = text(score1) + " " + text(score2)
|
||||||
reset_ball()
|
reset_ball()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ core.start({
|
|||||||
// New head position
|
// New head position
|
||||||
var hx = snake_pos[0].x + dir.x
|
var hx = snake_pos[0].x + dir.x
|
||||||
var hy = snake_pos[0].y + dir.y
|
var hy = snake_pos[0].y + dir.y
|
||||||
|
var tail = null
|
||||||
|
|
||||||
// Wrap
|
// Wrap
|
||||||
if (hx < 0) hx = gridW - 1
|
if (hx < 0) hx = gridW - 1
|
||||||
@@ -164,23 +165,27 @@ core.start({
|
|||||||
// Add head with tween from old head position
|
// Add head with tween from old head position
|
||||||
var old_wp = grid_to_world(snake_pos[0].x, snake_pos[0].y)
|
var old_wp = grid_to_world(snake_pos[0].x, snake_pos[0].y)
|
||||||
var new_wp = grid_to_world(hx, hy)
|
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({
|
var head = shape.rect({
|
||||||
pos: {x: old_wp.x, y: old_wp.y}, width: cellSize - 2, height: cellSize - 2,
|
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'
|
fill: {r: 0, g: 1, b: 0.3, a: 1}, plane: 'game'
|
||||||
})
|
})
|
||||||
// Smooth tween from old position to new position
|
// 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)
|
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?
|
// Eat apple?
|
||||||
if (hx == apple_gx && hy == apple_gy) {
|
if (hx == apple_gx && hy == apple_gy) {
|
||||||
score++
|
score++
|
||||||
score_label.text = "Score: " + score
|
score_label.text = "Score: " + text(score)
|
||||||
spawn_apple()
|
spawn_apple()
|
||||||
} else {
|
} else {
|
||||||
// Remove tail
|
// Remove tail
|
||||||
var tail = pop(snake_shapes)
|
tail = pop(snake_shapes)
|
||||||
tail.destroy()
|
tail.destroy()
|
||||||
pop(snake_pos)
|
pop(snake_pos)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,10 +45,10 @@ var board_shapes = []
|
|||||||
function init_board() {
|
function init_board() {
|
||||||
board = []
|
board = []
|
||||||
board_shapes = []
|
board_shapes = []
|
||||||
var r = 0, c = 0
|
var r = 0, c = 0, row = null, srow = null
|
||||||
for (r = 0; r < ROWS; r++) {
|
for (r = 0; r < ROWS; r++) {
|
||||||
var row = []
|
row = []
|
||||||
var srow = []
|
srow = []
|
||||||
for (c = 0; c < COLS; c++) {
|
for (c = 0; c < COLS; c++) {
|
||||||
push(row, null)
|
push(row, null)
|
||||||
push(srow, shape.rect({
|
push(srow, shape.rect({
|
||||||
@@ -155,7 +155,7 @@ function rotate_blocks(blocks) {
|
|||||||
|
|
||||||
function clear_lines() {
|
function clear_lines() {
|
||||||
var lines = 0
|
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) {
|
while (r >= 0) {
|
||||||
full = true
|
full = true
|
||||||
for (c = 0; c < COLS; c++) {
|
for (c = 0; c < COLS; c++) {
|
||||||
@@ -164,7 +164,7 @@ function clear_lines() {
|
|||||||
if (full) {
|
if (full) {
|
||||||
lines++
|
lines++
|
||||||
// Shift rows down
|
// Shift rows down
|
||||||
var sr = r
|
sr = r
|
||||||
while (sr > 0) {
|
while (sr > 0) {
|
||||||
for (c = 0; c < COLS; c++) {
|
for (c = 0; c < COLS; c++) {
|
||||||
board[sr][c] = board[sr - 1][c]
|
board[sr][c] = board[sr - 1][c]
|
||||||
@@ -191,15 +191,15 @@ function clear_lines() {
|
|||||||
else if (lines == 4) score += 800
|
else if (lines == 4) score += 800
|
||||||
linesCleared += lines
|
linesCleared += lines
|
||||||
level = floor(linesCleared / 10)
|
level = floor(linesCleared / 10)
|
||||||
score_label.text = "Score: " + score
|
score_label.text = "Score: " + text(score)
|
||||||
level_label.text = "Level: " + level
|
level_label.text = "Level: " + text(level)
|
||||||
}
|
}
|
||||||
|
|
||||||
function update_piece_shapes() {
|
function update_piece_shapes() {
|
||||||
var i = 0
|
var i = 0, bx = 0, by = 0
|
||||||
for (i = 0; i < 4; i++) {
|
for (i = 0; i < 4; i++) {
|
||||||
var bx = pieceX + piece.blocks[i][0]
|
bx = pieceX + piece.blocks[i][0]
|
||||||
var by = pieceY + piece.blocks[i][1]
|
by = pieceY + piece.blocks[i][1]
|
||||||
piece_shapes[i].pos.x = bx * TILE + TILE / 2
|
piece_shapes[i].pos.x = bx * TILE + TILE / 2
|
||||||
piece_shapes[i].pos.y = by * TILE + TILE / 2
|
piece_shapes[i].pos.y = by * TILE + TILE / 2
|
||||||
piece_shapes[i].fill = piece.color
|
piece_shapes[i].fill = piece.color
|
||||||
@@ -208,10 +208,10 @@ function update_piece_shapes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function update_next_display() {
|
function update_next_display() {
|
||||||
var i = 0
|
var i = 0, bx = 0, by = 0
|
||||||
for (i = 0; i < 4; i++) {
|
for (i = 0; i < 4; i++) {
|
||||||
var bx = nextPiece.blocks[i][0]
|
bx = nextPiece.blocks[i][0]
|
||||||
var by = nextPiece.blocks[i][1]
|
by = nextPiece.blocks[i][1]
|
||||||
next_shapes[i].pos.x = (COLS + 1) * TILE + bx * TILE + TILE / 2
|
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].pos.y = 20 + by * TILE + TILE / 2
|
||||||
next_shapes[i].fill = nextPiece.color
|
next_shapes[i].fill = nextPiece.color
|
||||||
@@ -220,13 +220,14 @@ function update_next_display() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function spawn_piece() {
|
function spawn_piece() {
|
||||||
|
var i = 0
|
||||||
piece = nextPiece || random_shape()
|
piece = nextPiece || random_shape()
|
||||||
nextPiece = random_shape()
|
nextPiece = random_shape()
|
||||||
pieceX = 3
|
pieceX = 3
|
||||||
pieceY = 0
|
pieceY = 0
|
||||||
if (collides(pieceX, pieceY, piece.blocks)) {
|
if (collides(pieceX, pieceY, piece.blocks)) {
|
||||||
gameOver = true
|
gameOver = true
|
||||||
var i = 0
|
i = 0
|
||||||
for (i = 0; i < 4; i++) piece_shapes[i].visible = false
|
for (i = 0; i < 4; i++) piece_shapes[i].visible = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -880,7 +880,7 @@ const JSCFunctionListEntry js_imgui_funcs[] = {
|
|||||||
MIST_FUNC_DEF(imgui, menu, 2),
|
MIST_FUNC_DEF(imgui, menu, 2),
|
||||||
MIST_FUNC_DEF(imgui, menubar, 1),
|
MIST_FUNC_DEF(imgui, menubar, 1),
|
||||||
MIST_FUNC_DEF(imgui, mainmenubar, 1),
|
MIST_FUNC_DEF(imgui, mainmenubar, 1),
|
||||||
MIST_FUNC_DEF(imgui, menuitem, 3),
|
MIST_FUNC_DEF(imgui, menuitem, 4),
|
||||||
|
|
||||||
// Input functions
|
// Input functions
|
||||||
MIST_FUNC_DEF(imgui, textinput, 2),
|
MIST_FUNC_DEF(imgui, textinput, 2),
|
||||||
|
|||||||
2
input.cm
2
input.cm
@@ -83,7 +83,7 @@ function create_user(index, config) {
|
|||||||
|
|
||||||
// Get action down state
|
// Get action down state
|
||||||
down() {
|
down() {
|
||||||
return this.router ? this.router.down : {}
|
return this.router ? this.router.down() : {}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Possess an entity (clears stack, sets as sole target)
|
// Possess an entity (clears stack, sets as sole target)
|
||||||
|
|||||||
@@ -1285,7 +1285,7 @@ function _execute_commands(commands, window_size) {
|
|||||||
current_pass = null
|
current_pass = null
|
||||||
}
|
}
|
||||||
|
|
||||||
_do_composite(cmd_buffer, cmd, window_size)
|
_do_composite(cmd_buffer, cmd)
|
||||||
|
|
||||||
} else if (cmd.cmd == 'end_render') {
|
} else if (cmd.cmd == 'end_render') {
|
||||||
// Flush pending draws
|
// 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)
|
font =staef.sdf_font(data, size, 12.0, 14, 1.0)
|
||||||
} else {
|
} else {
|
||||||
// Bitmap
|
// Bitmap
|
||||||
font =staef.font(data, size, false)
|
font =staef.font(data, size)
|
||||||
}
|
}
|
||||||
if (!font) {
|
if (!font) {
|
||||||
log.console(`sdl_gpu: Failed to load font ${path}:${size}:${local_mode}`)
|
log.console(`sdl_gpu: Failed to load font ${path}:${size}:${local_mode}`)
|
||||||
|
|||||||
64
shaders/msl/text_msdf.frag.msl
Normal file
64
shaders/msl/text_msdf.frag.msl
Normal 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);
|
||||||
|
}
|
||||||
59
shaders/msl/text_sdf.frag.msl
Normal file
59
shaders/msl/text_sdf.frag.msl
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user