feat(assets): #81 namespaced spritesheet/atlas API (Sprite.* / Assets.*)
Sprite loading was a bare png_load — one file per 16x16 sprite, no way to load one sheet and address a cell by grid coords or name. Adds a Sprite.*/Assets.* runtime (atlas.ludic) over the variable-size image loader, so a cell is a sub-rect of the kept image and is NOT restricted to the 16x16 sprite table: Sprite.sheet(path,cw,ch), Sprite.cell(sheet,col,row), Sprite.cell_span(sheet,col,row,cols,rows) (a sprite may span >1 cell), Sprite.define/named (name + lookup), Sprite.draw/draw_scaled (through camera/zoom/clip like Screen.sprite), Sprite.width/height, and Assets.image/load/get. Spliced on demand (Sprite.sheet/… or Assets.*), so a program using neither is byte-identical. Example examples/library/atlas.ludic (verified against a real 12x11 Kenney sheet, incl. a 2x3 multi-cell span and named lookup). 14 docs pages. Full suite 114/0, goldens byte-identical, fixpoint holds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
ab4546e365
commit
f3f1336882
21 changed files with 34542 additions and 32915 deletions
3
changes/spritesheet-atlas.md
Normal file
3
changes/spritesheet-atlas.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
bump: minor
|
||||
type: feat
|
||||
Namespaced spritesheet / atlas API (#81). Sprite loading was a bare `png_load("floor0.png")` — one file per 16x16 sprite, with no way to load one sheet and address a cell by grid coords or name. Adds a `Sprite.*` / `Assets.*` runtime (over the variable-size image loader, so a cell is a sub-rect of the kept image and is **not** restricted to the 16x16 sprite table): `Sprite.sheet(path, cellw, cellh)` -> handle, `Sprite.cell(sheet, col, row)` and `Sprite.cell_span(sheet, col, row, cols, rows)` -> id (a sprite may span more than one cell — a tall character, a wide object), `Sprite.define(name, …)` / `Sprite.named(name)` to name and look up a cell, `Sprite.draw` / `Sprite.draw_scaled` (through the camera / zoom / clip, like `Screen.sprite`), `Sprite.width` / `height`, and `Assets.image(path)` / `Assets.load` / `Assets.get(name)`. Spliced on demand; a program that uses neither compiles byte-identically. Example: `examples/library/atlas.ludic`.
|
||||
7
docs/language/assets/_section.md
Normal file
7
docs/language/assets/_section.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
id: assets
|
||||
title: Assets
|
||||
order: 34
|
||||
---
|
||||
|
||||
Loading and lookup for whole-file assets, paired with the <a href="sprite-sheet"><code>Sprite.*</code></a> atlas API. <a href="assets-image"><code>Assets.image</code></a> (and its alias <a href="assets-load"><code>Assets.load</code></a>) loads a whole image file as one sprite; <a href="assets-get"><code>Assets.get</code></a> looks a named sprite up.
|
||||
14
docs/language/assets/assets-get.md
Normal file
14
docs/language/assets/assets-get.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: assets-get
|
||||
name: Assets.get
|
||||
category: assets
|
||||
kind: namespace-method
|
||||
tokens: Assets.get
|
||||
sig: Assets.get(name) -> int
|
||||
tip: Look up a named sprite's id (alias of Sprite.named).
|
||||
order: 3
|
||||
ns: Assets
|
||||
member: get
|
||||
---
|
||||
|
||||
Alias of <a href="sprite-named"><code>Sprite.named</code></a>: returns the sprite id registered under <code>name</code>, or <code>-1</code>.
|
||||
14
docs/language/assets/assets-image.md
Normal file
14
docs/language/assets/assets-image.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: assets-image
|
||||
name: Assets.image
|
||||
category: assets
|
||||
kind: namespace-method
|
||||
tokens: Assets.image
|
||||
sig: Assets.image(path) -> int
|
||||
tip: Load a whole image file as one sprite; returns its id.
|
||||
order: 1
|
||||
ns: Assets
|
||||
member: image
|
||||
---
|
||||
|
||||
Loads the image at <code>path</code> as a single atlas sprite the size of the image, returning its id — the namespaced replacement for a bare <code>png_load</code>. Draw it with <a href="sprite-draw"><code>Sprite.draw</code></a>.
|
||||
14
docs/language/assets/assets-load.md
Normal file
14
docs/language/assets/assets-load.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: assets-load
|
||||
name: Assets.load
|
||||
category: assets
|
||||
kind: namespace-method
|
||||
tokens: Assets.load
|
||||
sig: Assets.load(path) -> int
|
||||
tip: Alias of Assets.image.
|
||||
order: 2
|
||||
ns: Assets
|
||||
member: load
|
||||
---
|
||||
|
||||
Alias of <a href="assets-image"><code>Assets.image</code></a>: loads a whole image file as one sprite and returns its id.
|
||||
7
docs/language/sprite/_section.md
Normal file
7
docs/language/sprite/_section.md
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
id: sprite
|
||||
title: Sprite
|
||||
order: 33
|
||||
---
|
||||
|
||||
A namespaced spritesheet / atlas API. <a href="sprite-sheet"><code>Sprite.sheet</code></a> loads one image and remembers its cell grid; <a href="sprite-cell"><code>Sprite.cell</code></a> addresses a cell by grid coords and <a href="sprite-cell_span"><code>Sprite.cell_span</code></a> a sprite that spans more than one cell; <a href="sprite-define"><code>Sprite.define</code></a> / <a href="sprite-named"><code>Sprite.named</code></a> name and look up a cell; <a href="sprite-draw"><code>Sprite.draw</code></a> / <a href="sprite-draw_scaled"><code>Sprite.draw_scaled</code></a> blit it (through the camera / zoom / clip). It stands on the variable-size image loader, so a cell can be any size — not just 16x16. (Distinct from the <code>Sprite</code> engine component of the sprite-render system.)
|
||||
14
docs/language/sprite/sprite-cell.md
Normal file
14
docs/language/sprite/sprite-cell.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: sprite-cell
|
||||
name: Sprite.cell
|
||||
category: sprite
|
||||
kind: namespace-method
|
||||
tokens: Sprite.cell
|
||||
sig: Sprite.cell(sheet, col, row) -> int
|
||||
tip: One cell of a sheet, by grid coords; returns a sprite id.
|
||||
order: 2
|
||||
ns: Sprite
|
||||
member: cell
|
||||
---
|
||||
|
||||
Returns a sprite id for the cell at column <code>col</code>, row <code>row</code> of <code>sheet</code> (the sub-rect <code>(col*cellw, row*cellh, cellw, cellh)</code>). Draw it with <a href="sprite-draw"><code>Sprite.draw</code></a>.
|
||||
14
docs/language/sprite/sprite-cell_span.md
Normal file
14
docs/language/sprite/sprite-cell_span.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: sprite-cell_span
|
||||
name: Sprite.cell_span
|
||||
category: sprite
|
||||
kind: namespace-method
|
||||
tokens: Sprite.cell_span
|
||||
sig: Sprite.cell_span(sheet, col, row, cols, rows) -> int
|
||||
tip: A sprite spanning cols x rows cells (tall/wide art).
|
||||
order: 3
|
||||
ns: Sprite
|
||||
member: cell_span
|
||||
---
|
||||
|
||||
Returns a sprite id for a sub-rect spanning <code>cols x rows</code> cells from <code>(col, row)</code> — for a sprite that covers more than one cell (a tall character, a wide object). Same as <a href="sprite-cell"><code>Sprite.cell</code></a> but sized to the span.
|
||||
14
docs/language/sprite/sprite-define.md
Normal file
14
docs/language/sprite/sprite-define.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: sprite-define
|
||||
name: Sprite.define
|
||||
category: sprite
|
||||
kind: namespace-method
|
||||
tokens: Sprite.define
|
||||
sig: Sprite.define(name, sheet, col, row) -> int
|
||||
tip: Name a cell for later lookup; returns its sprite id.
|
||||
order: 4
|
||||
ns: Sprite
|
||||
member: define
|
||||
---
|
||||
|
||||
Creates the cell at <code>(col, row)</code> of <code>sheet</code> and registers it under <code>name</code>, returning its sprite id. Look it up later with <a href="sprite-named"><code>Sprite.named</code></a> — content data (item/tile names) can drive rendering without hard-coded ids.
|
||||
14
docs/language/sprite/sprite-draw.md
Normal file
14
docs/language/sprite/sprite-draw.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: sprite-draw
|
||||
name: Sprite.draw
|
||||
category: sprite
|
||||
kind: namespace-method
|
||||
tokens: Sprite.draw
|
||||
sig: Sprite.draw(id, x, y)
|
||||
tip: Blit an atlas sprite at (x, y) through the camera/zoom/clip.
|
||||
order: 6
|
||||
ns: Sprite
|
||||
member: draw
|
||||
---
|
||||
|
||||
Draws atlas sprite <code>id</code> with its top-left at <code>(x, y)</code>. Sufficiently-opaque pixels are drawn through <code>rt_put_px</code>, so the camera, zoom and clip apply exactly like <a href="screen-sprite"><code>Screen.sprite</code></a>.
|
||||
14
docs/language/sprite/sprite-draw_scaled.md
Normal file
14
docs/language/sprite/sprite-draw_scaled.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: sprite-draw_scaled
|
||||
name: Sprite.draw_scaled
|
||||
category: sprite
|
||||
kind: namespace-method
|
||||
tokens: Sprite.draw_scaled
|
||||
sig: Sprite.draw_scaled(id, x, y, scale)
|
||||
tip: Blit an atlas sprite scaled by an integer factor.
|
||||
order: 7
|
||||
ns: Sprite
|
||||
member: draw_scaled
|
||||
---
|
||||
|
||||
Draws atlas sprite <code>id</code> at <code>(x, y)</code> scaled by the integer <code>scale</code> (each source pixel becomes a <code>scale x scale</code> block). For sub-integer / camera zoom, use <a href="camera-zoom"><code>Camera.zoom</code></a> around a plain <a href="sprite-draw"><code>Sprite.draw</code></a>.
|
||||
14
docs/language/sprite/sprite-height.md
Normal file
14
docs/language/sprite/sprite-height.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: sprite-height
|
||||
name: Sprite.height
|
||||
category: sprite
|
||||
kind: namespace-method
|
||||
tokens: Sprite.height
|
||||
sig: Sprite.height(id) -> int
|
||||
tip: The atlas sprite's height in pixels.
|
||||
order: 9
|
||||
ns: Sprite
|
||||
member: height
|
||||
---
|
||||
|
||||
Returns the pixel height of atlas sprite <code>id</code> (its sub-rect height).
|
||||
14
docs/language/sprite/sprite-named.md
Normal file
14
docs/language/sprite/sprite-named.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: sprite-named
|
||||
name: Sprite.named
|
||||
category: sprite
|
||||
kind: namespace-method
|
||||
tokens: Sprite.named
|
||||
sig: Sprite.named(name) -> int
|
||||
tip: Look up a named sprite's id, or -1.
|
||||
order: 5
|
||||
ns: Sprite
|
||||
member: named
|
||||
---
|
||||
|
||||
Returns the sprite id registered under <code>name</code> by <a href="sprite-define"><code>Sprite.define</code></a>, or <code>-1</code> if none. Names compare by content.
|
||||
14
docs/language/sprite/sprite-sheet.md
Normal file
14
docs/language/sprite/sprite-sheet.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: sprite-sheet
|
||||
name: Sprite.sheet
|
||||
category: sprite
|
||||
kind: namespace-method
|
||||
tokens: Sprite.sheet
|
||||
sig: Sprite.sheet(path, cellw, cellh) -> int
|
||||
tip: Load a spritesheet and remember its cell grid; returns a sheet handle.
|
||||
order: 1
|
||||
ns: Sprite
|
||||
member: sheet
|
||||
---
|
||||
|
||||
Loads the PNG at <code>path</code> and records that its cells are <code>cellw x cellh</code> px, returning a sheet <em>handle</em>. Address its cells with <a href="sprite-cell"><code>Sprite.cell</code></a> / <a href="sprite-cell_span"><code>Sprite.cell_span</code></a>. The whole image is kept (variable size), so cells are not restricted to the 16x16 sprite table.
|
||||
14
docs/language/sprite/sprite-width.md
Normal file
14
docs/language/sprite/sprite-width.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
id: sprite-width
|
||||
name: Sprite.width
|
||||
category: sprite
|
||||
kind: namespace-method
|
||||
tokens: Sprite.width
|
||||
sig: Sprite.width(id) -> int
|
||||
tip: The atlas sprite's width in pixels.
|
||||
order: 8
|
||||
ns: Sprite
|
||||
member: width
|
||||
---
|
||||
|
||||
Returns the pixel width of atlas sprite <code>id</code> (its sub-rect width) — handy for centering and layout.
|
||||
40
examples/library/atlas.ludic
Normal file
40
examples/library/atlas.ludic
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# atlas.ludic — the namespaced spritesheet / atlas API (#81): load one sheet and
|
||||
# address a cell by grid coords or by name, including sprites that span more than
|
||||
# one cell, instead of one png_load per 16x16 file. Cells are sub-rects of the kept
|
||||
# image, so they are not restricted to the 16x16 sprite table. Draws go through the
|
||||
# camera / zoom / clip.
|
||||
#
|
||||
# Deterministic; a full run prints: 16 16 32 48 1 1 1 16 1
|
||||
program AtlasDemo {
|
||||
# a model so the program runs the ECS and links the core runtime / framebuffer.
|
||||
property Cam { z: fixed = 1.0 }
|
||||
model V { Cam }
|
||||
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
||||
entry {
|
||||
let sheet = Sprite.sheet("assets/kenney/tiny-dungeon/Tilemap/tilemap_packed.png", 16, 16)
|
||||
let cell = Sprite.cell(sheet, 0, 0)
|
||||
print(Sprite.width(cell)) # 16
|
||||
print(Sprite.height(cell)) # 16
|
||||
|
||||
let span = Sprite.cell_span(sheet, 0, 0, 2, 3) # a sprite spanning 2x3 cells
|
||||
print(Sprite.width(span)) # 32
|
||||
print(Sprite.height(span)) # 48
|
||||
|
||||
let d = Sprite.define("hero", sheet, 2, 3) # name a cell
|
||||
print(bi(Sprite.named("hero") == d)) # 1 — looked up by name
|
||||
print(bi(Assets.get("hero") == d)) # 1 — Assets.get is the same lookup
|
||||
print(bi(Sprite.named("nope") == (0 - 1))) # 1 — an unknown name is -1
|
||||
|
||||
let whole = Assets.image("assets/kenney/tiny-dungeon/Tiles/tile_0096.png")
|
||||
print(Sprite.width(whole)) # 16 — a whole tile as one sprite
|
||||
|
||||
# draw verification: clear, draw the tile, count non-transparent pixels drawn.
|
||||
Screen.clear(0)
|
||||
Sprite.draw(whole, 10, 10)
|
||||
var n = 0
|
||||
for yy in 10 .. 26 { for xx in 10 .. 26 { if Screen.pixel(xx, yy) != 0 { n = n + 1 } } }
|
||||
print(bi(n > 0)) # 1 — the sprite drew content
|
||||
}
|
||||
}
|
||||
212
runtime/native/atlas.ludic
Normal file
212
runtime/native/atlas.ludic
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
# ============================================================================
|
||||
# atlas.ludic — a namespaced spritesheet / atlas API (#81).
|
||||
#
|
||||
# Sprite loading used to be a bare png_load("assets/sprites/floor0.png") — one
|
||||
# file per 16x16 sprite, and no way to load one sheet and address a cell by grid
|
||||
# coords or by name. This adds:
|
||||
#
|
||||
# Sprite.sheet(path, cellw, cellh) -> handle load a sheet, remember its grid
|
||||
# Sprite.cell(sheet, col, row) -> id one cell, addressed by grid coords
|
||||
# Sprite.cell_span(sheet, col, row, cols, rows) a sprite spanning >1 cell (tall/wide)
|
||||
# Sprite.define(name, sheet, col, row) -> id name a cell for later lookup
|
||||
# Sprite.named(name) -> id look a named sprite up
|
||||
# Sprite.draw(id, x, y) / draw_scaled(id,x,y,sc) blit an atlas sprite
|
||||
# Sprite.width(id) / height(id) its pixel size
|
||||
# Assets.image(path) -> id a whole file as one sprite
|
||||
# Assets.load(path) -> id alias of Assets.image
|
||||
# Assets.get(name) -> id alias of Sprite.named
|
||||
#
|
||||
# It stands on the variable-size image loader in image.ludic (rt_image_load keeps
|
||||
# the whole decoded PNG; a cell is a sub-rect of it), so a sprite is NOT restricted
|
||||
# to the 16x16 sprite table — a cell (and a multi-cell span) can be any size. Draws
|
||||
# go through rt_put_px, so the camera, zoom (#78) and clip all apply, exactly like
|
||||
# Screen.sprite. Spliced on demand when a program uses Sprite.sheet / Assets.*.
|
||||
# ============================================================================
|
||||
|
||||
const ATLAS_MAX_SHEET: int = 32
|
||||
const ATLAS_MAX_SPR: int = 1024
|
||||
const ATLAS_MAX_NAME: int = 512
|
||||
|
||||
var at_sheet_img: words = null # image id per sheet
|
||||
var at_sheet_cw: words = null # cell width per sheet
|
||||
var at_sheet_ch: words = null # cell height per sheet
|
||||
var at_nsheet: int = 0
|
||||
|
||||
var at_spr_sheet: words = null # owning sheet per atlas sprite
|
||||
var at_spr_sx: words = null # source x/y (px) of the sub-rect
|
||||
var at_spr_sy: words = null
|
||||
var at_spr_w: words = null # sub-rect width/height (px)
|
||||
var at_spr_h: words = null
|
||||
var at_nspr: int = 0
|
||||
|
||||
var at_name_str: pointers = null # name per named sprite
|
||||
var at_name_id: words = null # atlas id per name
|
||||
var at_nname: int = 0
|
||||
|
||||
function atlas_init() -> void {
|
||||
if at_sheet_img != null { return }
|
||||
at_sheet_img = words(ATLAS_MAX_SHEET)
|
||||
at_sheet_cw = words(ATLAS_MAX_SHEET)
|
||||
at_sheet_ch = words(ATLAS_MAX_SHEET)
|
||||
at_spr_sheet = words(ATLAS_MAX_SPR)
|
||||
at_spr_sx = words(ATLAS_MAX_SPR)
|
||||
at_spr_sy = words(ATLAS_MAX_SPR)
|
||||
at_spr_w = words(ATLAS_MAX_SPR)
|
||||
at_spr_h = words(ATLAS_MAX_SPR)
|
||||
at_name_str = bytes(ATLAS_MAX_NAME * 8) # a pointer (8 bytes) per name slot
|
||||
at_name_id = words(ATLAS_MAX_NAME)
|
||||
}
|
||||
|
||||
# load a spritesheet PNG whose cells are cw x ch px; returns a sheet handle (>= 0).
|
||||
function atlas_sheet(path: pointer, cw: int, ch: int) -> int {
|
||||
atlas_init()
|
||||
if at_nsheet >= ATLAS_MAX_SHEET { return 0 - 1 }
|
||||
let img = rt_image_load(path)
|
||||
if img < 0 { return 0 - 1 }
|
||||
let h = at_nsheet
|
||||
at_sheet_img[h] = img
|
||||
at_sheet_cw[h] = cw
|
||||
at_sheet_ch[h] = ch
|
||||
at_nsheet = at_nsheet + 1
|
||||
return h
|
||||
}
|
||||
|
||||
# register an atlas sprite for a sub-rect (px) of a sheet's image.
|
||||
function atlas_make(sheet: int, sx: int, sy: int, w: int, h: int) -> int {
|
||||
atlas_init()
|
||||
if at_nspr >= ATLAS_MAX_SPR { return 0 - 1 }
|
||||
let id = at_nspr
|
||||
at_spr_sheet[id] = sheet
|
||||
at_spr_sx[id] = sx
|
||||
at_spr_sy[id] = sy
|
||||
at_spr_w[id] = w
|
||||
at_spr_h[id] = h
|
||||
at_nspr = at_nspr + 1
|
||||
return id
|
||||
}
|
||||
|
||||
# one cell (col, row) of a sheet, addressed by grid coords.
|
||||
function atlas_cell(sheet: int, col: int, row: int) -> int {
|
||||
if (sheet < 0) or (sheet >= at_nsheet) { return 0 - 1 }
|
||||
let cw = at_sheet_cw[sheet]
|
||||
let ch = at_sheet_ch[sheet]
|
||||
return atlas_make(sheet, col * cw, row * ch, cw, ch)
|
||||
}
|
||||
|
||||
# a sprite spanning cols x rows cells from (col, row) — some sprites cover more
|
||||
# than one cell (a tall character, a wide object).
|
||||
function atlas_cell_span(sheet: int, col: int, row: int, cols: int, rows: int) -> int {
|
||||
if (sheet < 0) or (sheet >= at_nsheet) { return 0 - 1 }
|
||||
let cw = at_sheet_cw[sheet]
|
||||
let ch = at_sheet_ch[sheet]
|
||||
return atlas_make(sheet, col * cw, row * ch, cols * cw, rows * ch)
|
||||
}
|
||||
|
||||
# name a cell so a game can look it up by name; returns the atlas id.
|
||||
function atlas_define(name: pointer, sheet: int, col: int, row: int) -> int {
|
||||
atlas_init()
|
||||
let id = atlas_cell(sheet, col, row)
|
||||
if id < 0 { return 0 - 1 }
|
||||
if at_nname < ATLAS_MAX_NAME {
|
||||
at_name_str[at_nname] = name
|
||||
at_name_id[at_nname] = id
|
||||
at_nname = at_nname + 1
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
# look up a named sprite's atlas id (names compare by content), or -1.
|
||||
function atlas_named(name: pointer) -> int {
|
||||
atlas_init()
|
||||
var i = 0
|
||||
while i < at_nname {
|
||||
if at_name_str[i] == name { return at_name_id[i] }
|
||||
i = i + 1
|
||||
}
|
||||
return 0 - 1
|
||||
}
|
||||
|
||||
# load a whole image file as one atlas sprite (a 1x1 sheet the size of the image).
|
||||
function atlas_image(path: pointer) -> int {
|
||||
atlas_init()
|
||||
if at_nsheet >= ATLAS_MAX_SHEET { return 0 - 1 }
|
||||
let img = rt_image_load(path)
|
||||
if img < 0 { return 0 - 1 }
|
||||
let w = img_w[img]
|
||||
let h = img_h[img]
|
||||
let sh = at_nsheet
|
||||
at_sheet_img[sh] = img
|
||||
at_sheet_cw[sh] = w
|
||||
at_sheet_ch[sh] = h
|
||||
at_nsheet = at_nsheet + 1
|
||||
return atlas_make(sh, 0, 0, w, h)
|
||||
}
|
||||
|
||||
# blit an atlas sprite at (dx, dy) through rt_put_px (camera / zoom / clip apply).
|
||||
# Only sufficiently-opaque pixels are drawn (the sprite's transparent border is
|
||||
# skipped), matching Screen.sprite.
|
||||
function atlas_draw(id: int, dx: int, dy: int) -> void {
|
||||
atlas_init()
|
||||
if (id < 0) or (id >= at_nspr) { return }
|
||||
let sheet = at_spr_sheet[id]
|
||||
let img = at_sheet_img[sheet]
|
||||
let iw = img_w[img]
|
||||
let ih = img_h[img]
|
||||
let s: words = img_px[img]
|
||||
let sx = at_spr_sx[id]
|
||||
let sy = at_spr_sy[id]
|
||||
let w = at_spr_w[id]
|
||||
let h = at_spr_h[id]
|
||||
var y = 0
|
||||
while y < h {
|
||||
let srcy = sy + y
|
||||
if (srcy >= 0) and (srcy < ih) {
|
||||
var x = 0
|
||||
while x < w {
|
||||
let srcx = sx + x
|
||||
if (srcx >= 0) and (srcx < iw) {
|
||||
let px = s[srcy * iw + srcx]
|
||||
if ((px >> 24) & 255) >= 128 { rt_put_px(dx + x, dy + y, px & 16777215) }
|
||||
}
|
||||
x = x + 1
|
||||
}
|
||||
}
|
||||
y = y + 1
|
||||
}
|
||||
}
|
||||
|
||||
# blit an atlas sprite scaled by an integer factor (through rt_fill_rect blocks).
|
||||
function atlas_draw_scaled(id: int, dx: int, dy: int, sc: int) -> void {
|
||||
atlas_init()
|
||||
if (id < 0) or (id >= at_nspr) { return }
|
||||
if sc < 1 { return }
|
||||
let sheet = at_spr_sheet[id]
|
||||
let img = at_sheet_img[sheet]
|
||||
let iw = img_w[img]
|
||||
let ih = img_h[img]
|
||||
let s: words = img_px[img]
|
||||
let sx = at_spr_sx[id]
|
||||
let sy = at_spr_sy[id]
|
||||
let w = at_spr_w[id]
|
||||
let h = at_spr_h[id]
|
||||
var y = 0
|
||||
while y < h {
|
||||
let srcy = sy + y
|
||||
if (srcy >= 0) and (srcy < ih) {
|
||||
var x = 0
|
||||
while x < w {
|
||||
let srcx = sx + x
|
||||
if (srcx >= 0) and (srcx < iw) {
|
||||
let px = s[srcy * iw + srcx]
|
||||
if ((px >> 24) & 255) >= 128 { rt_fill_rect(dx + x * sc, dy + y * sc, sc, sc, px & 16777215) }
|
||||
}
|
||||
x = x + 1
|
||||
}
|
||||
}
|
||||
y = y + 1
|
||||
}
|
||||
}
|
||||
|
||||
# an atlas sprite's pixel size — handy for centering / layout.
|
||||
function atlas_width(id: int) -> int { atlas_init(); if (id < 0) or (id >= at_nspr) { return 0 }; return at_spr_w[id] }
|
||||
function atlas_height(id: int) -> int { atlas_init(); if (id < 0) or (id >= at_nspr) { return 0 }; return at_spr_h[id] }
|
||||
|
|
@ -176,6 +176,27 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
if (meth == "measure_text") { bare = "measure_text"; push(labels, "text") }
|
||||
if (meth == "pixel") { bare = "get_px"; push(labels, "x"); push(labels, "y") }
|
||||
}
|
||||
# Sprite.* / Assets.* — the namespaced spritesheet / atlas API (#81), a runtime
|
||||
# in atlas.ludic over the variable-size image loader. A cell (or multi-cell span)
|
||||
# is a sub-rect of a loaded sheet; draws go through rt_put_px so camera/zoom/clip
|
||||
# apply. Distinct from the `Sprite` engine component (#85) — same name, different
|
||||
# namespace (a namespaced builtin, never an entity).
|
||||
if (ns == "Sprite") {
|
||||
if (meth == "sheet") { bare = "atlas_sheet"; push(labels, "path"); push(labels, "cellw"); push(labels, "cellh") }
|
||||
if (meth == "cell") { bare = "atlas_cell"; push(labels, "sheet"); push(labels, "col"); push(labels, "row") }
|
||||
if (meth == "cell_span") { bare = "atlas_cell_span"; push(labels, "sheet"); push(labels, "col"); push(labels, "row"); push(labels, "cols"); push(labels, "rows") }
|
||||
if (meth == "define") { bare = "atlas_define"; push(labels, "name"); push(labels, "sheet"); push(labels, "col"); push(labels, "row") }
|
||||
if (meth == "named") { bare = "atlas_named"; push(labels, "name") }
|
||||
if (meth == "draw") { bare = "atlas_draw"; push(labels, "id"); push(labels, "x"); push(labels, "y") }
|
||||
if (meth == "draw_scaled") { bare = "atlas_draw_scaled"; push(labels, "id"); push(labels, "x"); push(labels, "y"); push(labels, "scale") }
|
||||
if (meth == "width") { bare = "atlas_width"; push(labels, "id") }
|
||||
if (meth == "height") { bare = "atlas_height"; push(labels, "id") }
|
||||
}
|
||||
if (ns == "Assets") {
|
||||
if (meth == "image") { bare = "atlas_image"; push(labels, "path") }
|
||||
if (meth == "load") { bare = "atlas_image"; push(labels, "path") } # alias
|
||||
if (meth == "get") { bare = "atlas_named"; push(labels, "name") } # alias of Sprite.named
|
||||
}
|
||||
# Camera.* — the world-space camera: a draw offset threaded through the render
|
||||
# path (runtime/native/core.ludic). set/follow move it; shake jitters it from
|
||||
# the seeded RNG, so a replay shakes identically.
|
||||
|
|
|
|||
|
|
@ -186,6 +186,10 @@ function p_postfix() -> Node {
|
|||
if e.a.kind == E_ID and e.a.s == "Xml" { g_uses_xml = true } # splice the XML reader on demand (Tiled #67)
|
||||
if e.a.kind == E_ID and e.a.s == "Base64" { g_uses_base64 = true } # splice base64 codec + inflate on demand (Tiled #67)
|
||||
if e.a.kind == E_ID and e.a.s == "Tiled" { g_uses_tiled = true } # splice the Tiled map runtime on demand (#69)
|
||||
# #81 — Sprite.sheet/cell/… or Assets.* splice the spritesheet/atlas runtime
|
||||
# (it reads the framebuffer + the image loader, so it links against core).
|
||||
if e.a.kind == E_ID and e.a.s == "Sprite" and (e.s == "sheet" or e.s == "cell" or e.s == "cell_span" or e.s == "define" or e.s == "named" or e.s == "draw" or e.s == "draw_scaled" or e.s == "width" or e.s == "height") { g_uses_atlas = true }
|
||||
if e.a.kind == E_ID and e.a.s == "Assets" { g_uses_atlas = true }
|
||||
if e.a.kind == E_ID and e.a.s == "Reflect" and (e.s == "serialize" or e.s == "apply") { g_uses_value = true; g_uses_reflect_io = true } # Reflect.serialize/apply -> value tree + world table
|
||||
# Input.* action-map / record-replay methods (#7) -> splice input.ludic.
|
||||
# Input.key stays bare (no runtime), so gate on the new methods only.
|
||||
|
|
@ -442,6 +446,7 @@ var g_uses_value: bool = false # Value.*/Json.*/Reflect.serialize -> splice t
|
|||
var g_uses_xml: bool = false # Xml.* -> splice the XML reader (Tiled #67)
|
||||
var g_uses_base64: bool = false # Base64.* -> splice the base64 codec + inflate (Tiled #67)
|
||||
var g_uses_tiled: bool = false # Tiled.* -> splice the Tiled map runtime (#69)
|
||||
var g_uses_atlas: bool = false # Sprite.sheet/cell/… or Assets.* -> splice the spritesheet/atlas runtime (#81)
|
||||
var g_uses_reflect_io: bool = false # Reflect.serialize/apply -> splice the reflection serializer
|
||||
var g_uses_esys: bool = false # an engine-owned system component (SpriteAnim/Motion/Light2D) is declared -> splice systems.ludic + force the reflection ABI
|
||||
var g_uses_input: bool = false # a program used Input.bind/down/poll/… (action maps + record/replay) -> splice input.ludic
|
||||
|
|
@ -784,6 +789,15 @@ function maybe_splice_runtime() -> void {
|
|||
do_import("runtime/native/xml.ludic")
|
||||
cur_dir = saved
|
||||
}
|
||||
# Sprite.sheet/cell/… or Assets.* (#81) — the spritesheet / atlas runtime. It
|
||||
# reads the framebuffer and the image loader (rt_image_load / img_px in
|
||||
# image.ludic), so it links against core.ludic (do_import dedupes).
|
||||
if g_uses_atlas {
|
||||
cur_dir = ""
|
||||
do_import("runtime/native/core.ludic")
|
||||
do_import("runtime/native/atlas.ludic")
|
||||
cur_dir = saved
|
||||
}
|
||||
# Base64.* (#67) — base64 codec; pull in the DEFLATE inflater alongside it, so
|
||||
# a plain tool can run the full base64 -> zlib/gzip decode chain (inflate.ludic
|
||||
# is self-contained; do_import dedupes when a game already linked it via core).
|
||||
|
|
@ -942,6 +956,7 @@ function parse_program() -> void {
|
|||
g_uses_xml = false
|
||||
g_uses_base64 = false
|
||||
g_uses_tiled = false
|
||||
g_uses_atlas = false
|
||||
g_uses_reflect_io = false
|
||||
g_tests = new []Node
|
||||
g_mod_sys_fn = new []pointer
|
||||
|
|
|
|||
66983
selfhost/ludicc.seed.ll
66983
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
|
|
@ -241,6 +241,7 @@ function cmd_test() -> int {
|
|||
feat_case("library/grid", "", "1 2 3 4 5 6 7 8 9 10 11 12 13", "grid.ludic (Grid line/flood/line_of_sight + A* pathfinding over the tilemap)")
|
||||
feat_case("library/anim", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34", "anim.ludic (Anim frame/once/pingpong/cell + Tween progress/loop/yoyo/ease/number/round/point/tint)")
|
||||
feat_case("library/anim_sugar", "", "4 8 2 1 0 100 100 0 0 1 20 20 30 0 1", "anim_sugar.ludic (Anim.clip/play/on_frame/fired + Motion.to + fluent Tween.to/chain/delay/parallel handles; issue #48)")
|
||||
feat_case("library/atlas", "", "16 16 32 48 1 1 1 16 1", "atlas.ludic (#81 Sprite.sheet/cell/cell_span/define/named + Assets.image/get — namespaced spritesheet/atlas with multi-cell sprites)")
|
||||
feat_case("library/camera_zoom", "", "1 0 0 1 1", "camera_zoom.ludic (Camera.zoom deterministic Q16.16 render-time zoom about the screen centre, verified by pixel readback; issue #78)")
|
||||
feat_case("library/clear_color", "q", "1 1", "clear_color.ludic (@ClearColor: the Render phase auto-clears to the declared colour + auto-presents, no Screen.clear/show in the handler; issue #86)")
|
||||
feat_case("library/query", "", "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18", "query.ludic (Query count/first/nearest/within — ECS spatial queries over the reflection ABI)")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue