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 == "measure_text") { bare = "measure_text"; push(labels, "text") }
|
||||||
if (meth == "pixel") { bare = "get_px"; push(labels, "x"); push(labels, "y") }
|
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
|
# 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
|
# path (runtime/native/core.ludic). set/follow move it; shake jitters it from
|
||||||
# the seeded RNG, so a replay shakes identically.
|
# 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 == "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 == "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)
|
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
|
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.* action-map / record-replay methods (#7) -> splice input.ludic.
|
||||||
# Input.key stays bare (no runtime), so gate on the new methods only.
|
# 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_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_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_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_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_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
|
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")
|
do_import("runtime/native/xml.ludic")
|
||||||
cur_dir = saved
|
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
|
# 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
|
# 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).
|
# 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_xml = false
|
||||||
g_uses_base64 = false
|
g_uses_base64 = false
|
||||||
g_uses_tiled = false
|
g_uses_tiled = false
|
||||||
|
g_uses_atlas = false
|
||||||
g_uses_reflect_io = false
|
g_uses_reflect_io = false
|
||||||
g_tests = new []Node
|
g_tests = new []Node
|
||||||
g_mod_sys_fn = new []pointer
|
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/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", "", "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/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/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/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)")
|
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