@solidrt/2d 0.0.67 → 0.0.69
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +246 -69
- package/README.md +33 -18
- package/examples/README.md +29 -0
- package/examples/anim.tsx +1 -1
- package/examples/atlases.tsx +151 -0
- package/examples/camera-probe.tsx +7 -7
- package/examples/camera.tsx +2 -3
- package/examples/markers.tsx +185 -0
- package/examples/parity.tsx +2 -2
- package/examples/pick.tsx +1 -1
- package/examples/split-screen.tsx +1 -1
- package/examples/springs.tsx +1 -1
- package/examples/sprites.tsx +1 -1
- package/examples/text.tsx +143 -0
- package/examples/tiles.tsx +1 -1
- package/examples/views.tsx +3 -3
- package/package.json +3 -3
- package/src/atlas.ts +75 -12
- package/src/camera2d.ts +47 -34
- package/src/collision.ts +20 -0
- package/src/components/camera2d.tsx +6 -13
- package/src/components/group.tsx +5 -10
- package/src/components/index.ts +3 -1
- package/src/components/sprite-layer.tsx +47 -14
- package/src/components/sprite.tsx +15 -7
- package/src/components/text2d.tsx +77 -0
- package/src/components/tile-layer.tsx +11 -5
- package/src/dispatch.ts +3 -2
- package/src/extrude.ts +156 -0
- package/src/font.ts +207 -0
- package/src/frames.ts +65 -55
- package/src/index.ts +37 -18
- package/src/layer.ts +267 -45
- package/src/pack.ts +70 -0
- package/src/pick.ts +25 -1
- package/src/records.ts +286 -121
- package/src/shaders.ts +224 -77
- package/src/shots.ts +23 -9
- package/src/text-layout.ts +163 -0
- package/src/text.ts +240 -0
- package/src/tiles.ts +111 -71
- package/src/views.ts +20 -13
package/src/frames.ts
CHANGED
|
@@ -1,19 +1,44 @@
|
|
|
1
|
-
// Atlas frame math, pure: names and grid coordinates to normalized UV rects
|
|
2
|
-
// No GPU or GUI imports
|
|
3
|
-
//
|
|
1
|
+
// Atlas frame math, pure: names and grid coordinates to normalized UV rects,
|
|
2
|
+
// each stamped with the texture it is cut from. No GPU or GUI imports (the
|
|
3
|
+
// Atlas and TextureId imports are types), so its test
|
|
4
|
+
// (tests/frames.test.ts) exercises this module headless on the flux binary.
|
|
5
|
+
import type { TextureId } from "@solidrt/core/gpu"
|
|
6
|
+
import type { Atlas } from "./atlas.ts"
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
|
-
* One atlas frame
|
|
7
|
-
* top-left origin (the texture pixel
|
|
9
|
+
* One atlas frame: the texture it is cut from plus the rect [u0, v0] to
|
|
10
|
+
* [u1, v1] in normalized UVs, top-left origin (the texture pixel
|
|
11
|
+
* contract), u right, v down. A frame is self-describing - PixiJS's
|
|
12
|
+
* Texture, Phaser's Frame, Unity's Sprite and Godot's AtlasTexture all
|
|
13
|
+
* pair the texture with the rect - so the same frame draws in every layer
|
|
14
|
+
* that declares its atlas, and a layer that does not rejects it at the
|
|
15
|
+
* write (the wrong-sheet bug, caught instead of drawn).
|
|
8
16
|
*/
|
|
9
|
-
export type Frame = { u0: number; v0: number; u1: number; v1: number }
|
|
17
|
+
export type Frame = { texture: TextureId; u0: number; v0: number; u1: number; v1: number }
|
|
18
|
+
|
|
19
|
+
/** Whether a value is a frame: a non-negative integer texture id and four
|
|
20
|
+
* numeric UVs (what the layers check before touching a record). */
|
|
21
|
+
export function isFrame(f: unknown): f is Frame {
|
|
22
|
+
let o = f as Frame | null
|
|
23
|
+
return (
|
|
24
|
+
typeof o === "object" &&
|
|
25
|
+
o !== null &&
|
|
26
|
+
Number.isInteger(o.texture) &&
|
|
27
|
+
o.texture >= 0 &&
|
|
28
|
+
typeof o.u0 === "number" &&
|
|
29
|
+
typeof o.v0 === "number" &&
|
|
30
|
+
typeof o.u1 === "number" &&
|
|
31
|
+
typeof o.v1 === "number"
|
|
32
|
+
)
|
|
33
|
+
}
|
|
10
34
|
|
|
11
35
|
/**
|
|
12
36
|
* Write a frame's four UVs at `at`, mirrored on the UV side when flipped:
|
|
13
37
|
* `flipX` swaps u0/u1, `flipY` swaps v0/v1. A flip is an involution, so the
|
|
14
38
|
* same call with the stored floats and only the CHANGED axes set toggles a
|
|
15
39
|
* flip in place, and reading back through it recovers the frame. Positional
|
|
16
|
-
* floats so the write allocates nothing
|
|
40
|
+
* floats so the write allocates nothing; the texture's sampler index is the
|
|
41
|
+
* layer's own field beside these four.
|
|
17
42
|
*/
|
|
18
43
|
export function writeFrame(
|
|
19
44
|
data: Float32Array,
|
|
@@ -32,12 +57,9 @@ export function writeFrame(
|
|
|
32
57
|
}
|
|
33
58
|
|
|
34
59
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* anywhere else (a render target, a camera) passes `{ width, height }`.
|
|
60
|
+
* The geometry of a uniform sheet: what `grid` reads to place cells, and
|
|
61
|
+
* what `extrudeGrid` returns for the sheet it repacks.
|
|
38
62
|
*/
|
|
39
|
-
export type AtlasSize = { width: number; height: number }
|
|
40
|
-
|
|
41
63
|
export type GridOptions = {
|
|
42
64
|
/** Cell size in pixels; defaults to width/cols x height/rows. */
|
|
43
65
|
cellW?: number
|
|
@@ -47,29 +69,23 @@ export type GridOptions = {
|
|
|
47
69
|
/** Pixel offset of the first cell from the top-left corner; default 0. */
|
|
48
70
|
marginX?: number
|
|
49
71
|
marginY?: number
|
|
50
|
-
/**
|
|
51
|
-
* Texels shaved off every side of each frame; default 0. Only a
|
|
52
|
-
* mipmapped atlas needs it: 2^k texels keep mip level k inside the cell
|
|
53
|
-
* (the layer shaders already clamp level-0 samples into the frame), at
|
|
54
|
-
* the cost of that border of every sprite. The stopgap until extrusion
|
|
55
|
-
* (okf/backlog/2d-atlas-extrude.md).
|
|
56
|
-
*/
|
|
57
|
-
inset?: number
|
|
58
72
|
}
|
|
59
73
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
/** The slicers' atlas check: a positive pixel size and a texture id to
|
|
75
|
+
* stamp (throws - the dev validation policy). */
|
|
76
|
+
function checkAtlas(what: string, atlas: Atlas): void {
|
|
77
|
+
if (!(atlas.width > 0 && atlas.height > 0)) {
|
|
78
|
+
throw new Error(`${what}: atlas size must be positive, got ${atlas.width} x ${atlas.height}`)
|
|
79
|
+
}
|
|
80
|
+
if (!(Number.isInteger(atlas.texture) && atlas.texture >= 0)) {
|
|
81
|
+
throw new Error(`${what}: atlas has no texture id, got ${JSON.stringify(atlas.texture)}`)
|
|
82
|
+
}
|
|
63
83
|
}
|
|
64
84
|
|
|
65
|
-
/** A pixel rect
|
|
66
|
-
function frameOf(atlas:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
let ih = h - inset * 2
|
|
70
|
-
if (!(iw > 0 && ih > 0)) throw new Error(`${what}: inset ${inset} leaves no frame of a ${w} x ${h} rect`)
|
|
71
|
-
let { width, height } = atlas
|
|
72
|
-
return { u0: (x + inset) / width, v0: (y + inset) / height, u1: (x + inset + iw) / width, v1: (y + inset + ih) / height }
|
|
85
|
+
/** A pixel rect of `atlas` to a frame over its texture. */
|
|
86
|
+
function frameOf(atlas: Atlas, x: number, y: number, w: number, h: number): Frame {
|
|
87
|
+
let { width, height, texture } = atlas
|
|
88
|
+
return { texture, u0: x / width, v0: y / height, u1: (x + w) / width, v1: (y + h) / height }
|
|
73
89
|
}
|
|
74
90
|
|
|
75
91
|
/**
|
|
@@ -80,18 +96,17 @@ function frameOf(atlas: AtlasSize, x: number, y: number, w: number, h: number, i
|
|
|
80
96
|
*
|
|
81
97
|
* Cells that touch do not bleed at mip level 0: the layer shaders clamp
|
|
82
98
|
* every sample into its frame (shaders.ts). A mip chain is different - its
|
|
83
|
-
* texels straddle cell edges before sampling - so a
|
|
84
|
-
*
|
|
99
|
+
* texels straddle cell edges before sampling - so a sheet going through
|
|
100
|
+
* `createAtlas` with `mipmap` is extruded first (`extrudeGrid`, whose
|
|
101
|
+
* returned options slice the repacked sheet).
|
|
85
102
|
*/
|
|
86
|
-
export function grid(atlas:
|
|
103
|
+
export function grid(atlas: Atlas, cols: number, rows: number, opts?: GridOptions): Frame[] {
|
|
87
104
|
if (!(cols > 0 && rows > 0 && Number.isInteger(cols) && Number.isInteger(rows))) {
|
|
88
105
|
throw new Error(`grid: cols and rows must be positive integers, got ${cols} x ${rows}`)
|
|
89
106
|
}
|
|
107
|
+
checkAtlas("grid", atlas)
|
|
90
108
|
let { width, height } = atlas
|
|
91
|
-
|
|
92
|
-
throw new Error(`grid: atlas size must be positive, got ${width} x ${height}`)
|
|
93
|
-
}
|
|
94
|
-
let { spacing = 0, marginX = 0, marginY = 0, inset = 0 } = opts ?? {}
|
|
109
|
+
let { spacing = 0, marginX = 0, marginY = 0 } = opts ?? {}
|
|
95
110
|
let cellW = opts?.cellW ?? (width - marginX * 2 - spacing * (cols - 1)) / cols
|
|
96
111
|
let cellH = opts?.cellH ?? (height - marginY * 2 - spacing * (rows - 1)) / rows
|
|
97
112
|
if (!(cellW > 0 && cellH > 0)) {
|
|
@@ -102,7 +117,7 @@ export function grid(atlas: AtlasSize, cols: number, rows: number, opts?: GridOp
|
|
|
102
117
|
for (let col = 0; col < cols; col++) {
|
|
103
118
|
let x = marginX + col * (cellW + spacing)
|
|
104
119
|
let y = marginY + row * (cellH + spacing)
|
|
105
|
-
frames.push(frameOf(atlas, x, y, cellW, cellH
|
|
120
|
+
frames.push(frameOf(atlas, x, y, cellW, cellH))
|
|
106
121
|
}
|
|
107
122
|
}
|
|
108
123
|
return frames
|
|
@@ -123,28 +138,23 @@ export function grid(atlas: AtlasSize, cols: number, rows: number, opts?: GridOp
|
|
|
123
138
|
* flashing a one-texel line of the cell next door - is handled in the
|
|
124
139
|
* layer shaders, which clamp every sample into its frame. What they cannot
|
|
125
140
|
* handle is a mip chain, whose texels average across cell edges before
|
|
126
|
-
* sampling: a
|
|
127
|
-
*
|
|
128
|
-
* (okf/backlog/2d-atlas-extrude.md).
|
|
141
|
+
* sampling: a sheet uploaded with `mipmap` is extruded first
|
|
142
|
+
* (`extrudeRects`, whose returned table names the repacked rects).
|
|
129
143
|
*/
|
|
130
|
-
export function namedFrames<K extends string>(
|
|
131
|
-
atlas
|
|
132
|
-
rects: Record<K, [number, number, number, number]>,
|
|
133
|
-
opts?: NamedFramesOptions,
|
|
134
|
-
): Record<K, Frame> {
|
|
135
|
-
let { width, height } = atlas
|
|
136
|
-
if (!(width > 0 && height > 0)) {
|
|
137
|
-
throw new Error(`namedFrames: atlas size must be positive, got ${width} x ${height}`)
|
|
138
|
-
}
|
|
139
|
-
let inset = opts?.inset ?? 0
|
|
144
|
+
export function namedFrames<K extends string>(atlas: Atlas, rects: Record<K, [number, number, number, number]>): Record<K, Frame> {
|
|
145
|
+
checkAtlas("namedFrames", atlas)
|
|
140
146
|
let out = {} as Record<K, Frame>
|
|
141
147
|
for (let name in rects) {
|
|
142
148
|
let [x, y, w, h] = rects[name]
|
|
143
149
|
if (!(w > 0 && h > 0)) throw new Error(`namedFrames: frame '${name}' has non-positive size ${w} x ${h}`)
|
|
144
|
-
out[name] = frameOf(atlas, x, y, w, h
|
|
150
|
+
out[name] = frameOf(atlas, x, y, w, h)
|
|
145
151
|
}
|
|
146
152
|
return out
|
|
147
153
|
}
|
|
148
154
|
|
|
149
|
-
/** The whole texture as one frame (a plain image used as a sprite
|
|
150
|
-
|
|
155
|
+
/** The whole texture as one frame (a plain image used as a sprite, a
|
|
156
|
+
* render target shown by a sprite). */
|
|
157
|
+
export function fullFrame(atlas: Atlas): Frame {
|
|
158
|
+
checkAtlas("fullFrame", atlas)
|
|
159
|
+
return { texture: atlas.texture, u0: 0, v0: 0, u1: 1, v1: 1 }
|
|
160
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
// @solidrt/2d - an instanced sprite layer above @solidrt/core/gpu.
|
|
2
|
-
//
|
|
3
|
-
// shows only through its views (layer.createView /
|
|
4
|
-
// with a camera of its own - a Unity scene renders
|
|
5
|
-
// a Godot World2D only through Viewports)
|
|
2
|
+
// A layer's atlases bound together, N quads in one draw per VIEW: a layer
|
|
3
|
+
// holds the sprites and shows only through its views (layer.createView /
|
|
4
|
+
// <View2d>, each a target with a camera of its own - a Unity scene renders
|
|
5
|
+
// only through Cameras, a Godot World2D only through Viewports); every
|
|
6
|
+
// frame names its texture, so sprites from several sheets interleave in
|
|
7
|
+
// the one draw (the PixiJS/Phaser multi-texture batch). The live layer
|
|
6
8
|
// (createSpriteLayer/addSprite) backs every sprite with a SPATIAL ARENA
|
|
7
9
|
// node whose Pose2D record sink writes the pose instance buffer at the
|
|
8
10
|
// core flush, so core producers reach sprites and picking walks the core
|
|
9
11
|
// BVH; style stays a JS-written second instance buffer. The records layer (createRecordLayer)
|
|
10
|
-
// is the raw escape hatch for motion only JS can compute:
|
|
11
|
-
// floats per sprite
|
|
12
|
+
// is the raw escape hatch for motion only JS can compute: 16 JS-owned
|
|
13
|
+
// floats per sprite in a mirror, published by dirty range through the
|
|
14
|
+
// 3d record mesh's verbs (records/updateRecords/setRecordCount). The baked
|
|
12
15
|
// tile layer (createTileLayer/TileLayer) is the static sibling: a tile
|
|
13
16
|
// world rendered once into textures and composited as a few quads,
|
|
14
17
|
// re-baked on change. Two faces throughout: the imperative core (usable
|
|
15
18
|
// without Solid components) and the components (SpriteLayer/Sprite/Group/
|
|
16
|
-
// View2d/TileLayer) on top.
|
|
19
|
+
// View2d/TileLayer) on top. Sheets reach the GPU through createAtlas,
|
|
20
|
+
// sliced by grid/namedFrames/fullFrame, and a mipmapped sheet is extruded
|
|
21
|
+
// first (extrudeGrid/extrudeRects). See AGENTS.md for the model and the
|
|
22
|
+
// traps.
|
|
17
23
|
|
|
18
24
|
export { addGroup, addSprite, createSpriteLayer, getSprite, destroyGroup, destroySprite, setGroup, setGroupTransition, setSprite, setSpriteParent, setSpriteTransition, worldPosition, POSE_FLOATS, STYLE_FLOATS } from "./layer.ts"
|
|
19
|
-
export { createRecordLayer,
|
|
20
|
-
export type { RecordLayer as RecordLayerHandle, RecordLayerOptions } from "./records.ts"
|
|
21
|
-
export { pointInSprite } from "./pick.ts"
|
|
25
|
+
export { createRecordLayer, records, setRecordCount, updateRecords, INSTANCE_FLOATS } from "./records.ts"
|
|
26
|
+
export type { RecordLayer as RecordLayerHandle, RecordLayerOptions, UpdateRecordsOptions } from "./records.ts"
|
|
27
|
+
export { floorReach, pointInSprite } from "./pick.ts"
|
|
22
28
|
export { projectCamera, unprojectCamera } from "./camera.ts"
|
|
23
29
|
export type { CameraState, CameraUpdate } from "./camera.ts"
|
|
24
30
|
export { createCamera2d } from "./camera2d.ts"
|
|
25
|
-
export type { Camera2d as Camera2dHandle, Camera2dAxes, Camera2dOptions, Camera2dPose, Camera2dTarget, Rect2d } from "./camera2d.ts"
|
|
26
|
-
export { createShots,
|
|
31
|
+
export type { Camera2d as Camera2dHandle, Camera2dAxes, Camera2dOptions, Camera2dPose, Camera2dPoseState, Camera2dTarget, Rect2d } from "./camera2d.ts"
|
|
32
|
+
export { createShots, mixCamera } from "./shots.ts"
|
|
27
33
|
export type { ShotTarget, ShotsHandle } from "./shots.ts"
|
|
28
34
|
export { camera2dActions, camera2dBindings } from "./input.ts"
|
|
29
35
|
export type { CameraDevices } from "./input.ts"
|
|
@@ -39,6 +45,7 @@ export type {
|
|
|
39
45
|
QueryOptions,
|
|
40
46
|
Hit,
|
|
41
47
|
Rect,
|
|
48
|
+
LayerEventBase,
|
|
42
49
|
LayerPointerEvent,
|
|
43
50
|
LayerPointerListener,
|
|
44
51
|
LayerTapEvent,
|
|
@@ -59,17 +66,29 @@ export type {
|
|
|
59
66
|
Volume,
|
|
60
67
|
} from "./layer.ts"
|
|
61
68
|
export { feedPointer } from "./views.ts"
|
|
69
|
+
export { moveAndSlide } from "./collision.ts"
|
|
70
|
+
export type { MoveLayer } from "./collision.ts"
|
|
62
71
|
export type { ViewHandle, ViewOptions } from "./views.ts"
|
|
63
72
|
export { createTileLayer } from "./tiles.ts"
|
|
64
73
|
export type { TileChunk, TileLayer as TileLayerHandle, TileLayerOptions } from "./tiles.ts"
|
|
65
74
|
|
|
66
|
-
export type {
|
|
67
|
-
export { grid,
|
|
68
|
-
export type {
|
|
75
|
+
export type { NodeMotionSpec, NodeTransitionSpec } from "flux:spatial"
|
|
76
|
+
export { fullFrame, grid, isFrame, namedFrames } from "./frames.ts"
|
|
77
|
+
export type { Frame, GridOptions } from "./frames.ts"
|
|
78
|
+
export { extrudeGrid, extrudeRects } from "./extrude.ts"
|
|
79
|
+
export type { ExtrudedGrid, ExtrudedRects, Rects } from "./extrude.ts"
|
|
80
|
+
export { packRects, packWidth } from "./pack.ts"
|
|
81
|
+
export type { Packing, Placement, Size } from "./pack.ts"
|
|
69
82
|
export { createAnimation } from "./animation.ts"
|
|
70
83
|
export type { AnimationOptions, SpriteAnimation } from "./animation.ts"
|
|
71
84
|
export { fitOversample } from "./oversample.ts"
|
|
72
|
-
export { createAtlas } from "./atlas.ts"
|
|
85
|
+
export { createAtlas, isAtlas } from "./atlas.ts"
|
|
73
86
|
export type { Atlas, AtlasOptions } from "./atlas.ts"
|
|
74
|
-
export {
|
|
75
|
-
export type {
|
|
87
|
+
export { createSpriteFont } from "./font.ts"
|
|
88
|
+
export type { FontListener, Glyph, SpriteFont, SpriteFontOptions } from "./font.ts"
|
|
89
|
+
export { layoutText } from "./text-layout.ts"
|
|
90
|
+
export type { GlyphPlacement, TextAlign, TextAnchor, TextAnchorY, TextLayout, TextLayoutOptions, TextLine } from "./text-layout.ts"
|
|
91
|
+
export { addText, destroyText, setText } from "./text.ts"
|
|
92
|
+
export type { AddTextOptions, TextPose, TextRun, TextStyle } from "./text.ts"
|
|
93
|
+
export { Camera2d, Group, Sprite, SpriteLayer, Text2d, TileLayer, View2d, useSpriteLayer, Shot, Shots } from "./components/index.ts"
|
|
94
|
+
export type { BubblingSpritePointerProps, Camera2dProps, GroupProps, LayerPointerProps, SpriteLayerProps, SpritePointerProps, SpriteProps, TileCamera, TileLayerProps, View2dProps, ShotProps, ShotsProps } from "./components/index.ts"
|