@solidrt/2d 0.0.66 → 0.0.68

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/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, so its test (tests/frames.test.ts) exercises
3
- // this module headless on the flux binary.
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 as normalized UVs: the rect [u0, v0] to [u1, v1] with
7
- * top-left origin (the texture pixel contract), u right, v down.
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
- * What the slicers need of an atlas: its pixel size, the space every rect
36
- * and gap below is measured in. An `Atlas` fits as it is; a texture from
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
- export type NamedFramesOptions = {
61
- /** As GridOptions.inset: texels shaved off every side of each rect. */
62
- inset?: number
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 to UVs, shrunk by `inset` on every side; `what` names the throw. */
66
- function frameOf(atlas: AtlasSize, x: number, y: number, w: number, h: number, inset: number, what: string): Frame {
67
- if (!(inset >= 0)) throw new Error(`${what}: inset must be non-negative, got ${inset}`)
68
- let iw = w - inset * 2
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 mipmapped sheet packs
84
- * a gutter (`spacing`) or shaves its frames (`inset`); see namedFrames.
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: AtlasSize, cols: number, rows: number, opts?: GridOptions): Frame[] {
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
- if (!(width > 0 && height > 0)) {
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, inset, "grid"))
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 mipmapped sheet passes `inset` (2^k texels keeps level k
127
- * clean, losing that border) or packs a gutter, until extrusion lands
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: AtlasSize,
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, inset, `namedFrames: frame '${name}'`)
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
- export const FULL_FRAME: Frame = { u0: 0, v0: 0, u1: 1, v1: 1 }
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
- // One atlas, N quads in one draw per VIEW: a layer holds the sprites and
3
- // shows only through its views (layer.createView / <View2d>, each a target
4
- // with a camera of its own - a Unity scene renders only through Cameras,
5
- // a Godot World2D only through Viewports). The live layer
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: 13 JS-owned
11
- // floats per sprite published through the zero-copy write lease. The baked
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. See AGENTS.md for the model and the traps.
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, FLOATS_PER_SPRITE } from "./records.ts"
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, mixCamera2d } from "./shots.ts"
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 { NodeTransition, NodeTransitionSpec } from "flux:spatial"
67
- export { grid, namedFrames, FULL_FRAME } from "./frames.ts"
68
- export type { AtlasSize, Frame, GridOptions, NamedFramesOptions } from "./frames.ts"
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 { Camera2d, Group, Sprite, SpriteLayer, TileLayer, View2d, useSpriteLayer, Shot, Shots } from "./components/index.ts"
75
- export type { Camera2dProps, GroupProps, LayerPointerProps, SpriteLayerProps, SpritePointerProps, SpriteProps, TileCamera, TileLayerProps, View2dProps, ShotProps, ShotsProps } from "./components/index.ts"
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"