lecodes-cli 0.18.2 → 0.19.2
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/dist/index.js +1866 -301
- package/package.json +14 -12
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk-types.json +1 -1
- package/src/api.ts +302 -0
- package/src/browserAuth.ts +87 -0
- package/src/cmgenTool.ts +104 -0
- package/src/commands/app.ts +891 -0
- package/src/commands/appAndroid.ts +603 -0
- package/src/commands/appDesktop.ts +326 -0
- package/src/commands/appDesktopMac.ts +470 -0
- package/src/commands/appIcon.ts +187 -0
- package/src/commands/appShared.ts +448 -0
- package/src/commands/appTemplates.ts +883 -0
- package/src/commands/appTemplatesAndroid.ts +599 -0
- package/src/commands/appTemplatesGradlew.ts +9 -0
- package/src/commands/assets.ts +28 -0
- package/src/commands/clone.ts +60 -0
- package/src/commands/compile.ts +253 -0
- package/src/commands/create.ts +59 -0
- package/src/commands/design.ts +863 -0
- package/src/commands/designTemplates.ts +11 -0
- package/src/commands/desktop.ts +122 -0
- package/src/commands/dev.ts +214 -0
- package/src/commands/diff.ts +59 -0
- package/src/commands/init.ts +191 -0
- package/src/commands/install.ts +198 -0
- package/src/commands/lightmap.ts +290 -0
- package/src/commands/link.ts +147 -0
- package/src/commands/login.ts +24 -0
- package/src/commands/navmesh.ts +225 -0
- package/src/commands/pn.ts +305 -0
- package/src/commands/projectTemplates.ts +129 -0
- package/src/commands/pull.ts +109 -0
- package/src/commands/push.ts +173 -0
- package/src/commands/render.ts +414 -0
- package/src/commands/scene.ts +148 -0
- package/src/commands/shaders.ts +157 -0
- package/src/commands/status.ts +29 -0
- package/src/commands/test.ts +370 -0
- package/src/commands/thumbs.ts +178 -0
- package/src/commands/types.ts +87 -0
- package/src/commands/update.ts +190 -0
- package/src/compile/assetIcons.ts +214 -0
- package/src/compile/collect.ts +46 -0
- package/src/compile/collectLocal.ts +37 -0
- package/src/compile/designCompile.ts +104 -0
- package/src/compile/fonts.ts +156 -0
- package/src/compile/headlessBundle.ts +129 -0
- package/src/compile/nativeStack.ts +21 -0
- package/src/compile/projectCompile.ts +109 -0
- package/src/compile/sceneCompile.ts +113 -0
- package/src/compile/screenEntry.ts +127 -0
- package/src/compile/shaders.ts +245 -0
- package/src/config.ts +42 -0
- package/src/designMeta.ts +35 -0
- package/src/desktopRenderer.ts +532 -0
- package/src/desktopScript.ts +276 -0
- package/src/dev/clientTemplates.ts +160 -0
- package/src/dev/devServer.ts +292 -0
- package/src/dev/wsServer.ts +144 -0
- package/src/distRoot.ts +20 -0
- package/src/ignore.ts +163 -0
- package/src/index.ts +491 -0
- package/src/lecodes-3d-editor.d.ts +41 -0
- package/src/lecodes-assets.d.ts +7 -0
- package/src/lecodes-design.d.ts +191 -0
- package/src/lecodes-renderer.d.ts +131 -0
- package/src/localFiles.ts +144 -0
- package/src/manifest.ts +46 -0
- package/src/matcTool.ts +137 -0
- package/src/peers.ts +40 -0
- package/src/project.ts +61 -0
- package/src/projectEnv.ts +94 -0
- package/src/qrcode-terminal.d.ts +9 -0
- package/src/releases.ts +125 -0
- package/src/serverDiff.ts +78 -0
- package/src/textDiff.ts +103 -0
- package/src/types.ts +0 -0
- package/src/util.ts +146 -0
- package/runtime/sdk/animate/animate.ts +0 -238
- package/runtime/sdk/animate/bezier.ts +0 -138
- package/runtime/sdk/animate/easings.ts +0 -126
- package/runtime/sdk/canvas/Canvas.ts +0 -305
- package/runtime/sdk/core/Aspect.ts +0 -512
- package/runtime/sdk/core/InspectorUI.ts +0 -212
- package/runtime/sdk/core/color.ts +0 -66
- package/runtime/sdk/core/compWrite.ts +0 -42
- package/runtime/sdk/core/events.ts +0 -38
- package/runtime/sdk/core/fields.ts +0 -120
- package/runtime/sdk/core/registry.ts +0 -23
- package/runtime/sdk/core/signals.ts +0 -277
- package/runtime/sdk/core/time.ts +0 -81
- package/runtime/sdk/g2/Camera2D.ts +0 -40
- package/runtime/sdk/g2/CharacterController2D.ts +0 -276
- package/runtime/sdk/g2/Node2D.ts +0 -267
- package/runtime/sdk/g2/OneWay2D.ts +0 -66
- package/runtime/sdk/g2/Physics2D.ts +0 -346
- package/runtime/sdk/g2/Scene2D.ts +0 -209
- package/runtime/sdk/g2/Shape2D.ts +0 -259
- package/runtime/sdk/g2/Sprite.ts +0 -89
- package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
- package/runtime/sdk/g2/SpriteSheet.ts +0 -166
- package/runtime/sdk/g2/Texture2D.ts +0 -47
- package/runtime/sdk/g2/Tilemap.ts +0 -41
- package/runtime/sdk/g2/Tileset.ts +0 -71
- package/runtime/sdk/g2/Trigger2D.ts +0 -77
- package/runtime/sdk/g2/autotile.ts +0 -433
- package/runtime/sdk/g2/cells.ts +0 -91
- package/runtime/sdk/g2/defineScene2d.ts +0 -381
- package/runtime/sdk/g2/groups2d.ts +0 -106
- package/runtime/sdk/g2/loop.ts +0 -50
- package/runtime/sdk/g2/scenarios2d.ts +0 -69
- package/runtime/sdk/g2/touch.ts +0 -83
- package/runtime/sdk/gl/Camera.ts +0 -160
- package/runtime/sdk/gl/CameraPlace.ts +0 -52
- package/runtime/sdk/gl/CharacterController.ts +0 -238
- package/runtime/sdk/gl/Gearbox.ts +0 -212
- package/runtime/sdk/gl/Geometry.ts +0 -279
- package/runtime/sdk/gl/IK.ts +0 -193
- package/runtime/sdk/gl/InstancedMesh.ts +0 -132
- package/runtime/sdk/gl/Light.ts +0 -99
- package/runtime/sdk/gl/Lightmap.ts +0 -179
- package/runtime/sdk/gl/Material.ts +0 -245
- package/runtime/sdk/gl/Mesh.ts +0 -83
- package/runtime/sdk/gl/Model.ts +0 -64
- package/runtime/sdk/gl/Node.ts +0 -350
- package/runtime/sdk/gl/Noise.ts +0 -30
- package/runtime/sdk/gl/Particles.ts +0 -676
- package/runtime/sdk/gl/Physics.ts +0 -222
- package/runtime/sdk/gl/Plane.ts +0 -53
- package/runtime/sdk/gl/Ray.ts +0 -16
- package/runtime/sdk/gl/Scene.ts +0 -479
- package/runtime/sdk/gl/Shape.ts +0 -377
- package/runtime/sdk/gl/Texture.ts +0 -46
- package/runtime/sdk/gl/Trigger.ts +0 -45
- package/runtime/sdk/gl/Vehicle.ts +0 -473
- package/runtime/sdk/gl/Wheel.ts +0 -240
- package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
- package/runtime/sdk/gl/animation/Animator.ts +0 -87
- package/runtime/sdk/gl/animation/Layer.ts +0 -29
- package/runtime/sdk/gl/animation/Loop.ts +0 -25
- package/runtime/sdk/gl/animation/Playback.ts +0 -43
- package/runtime/sdk/gl/animation/core.ts +0 -294
- package/runtime/sdk/gl/controls.ts +0 -95
- package/runtime/sdk/gl/physicsEvents.ts +0 -20
- package/runtime/sdk/gl/scenarios.ts +0 -291
- package/runtime/sdk/gl/state.ts +0 -6
- package/runtime/sdk/gl/touch.ts +0 -68
- package/runtime/sdk/inject.ts +0 -186
- package/runtime/sdk/math/Mathf.ts +0 -118
- package/runtime/sdk/math/mat4.ts +0 -278
- package/runtime/sdk/math/quat.ts +0 -232
- package/runtime/sdk/math/vec.ts +0 -255
- package/runtime/sdk/plugins/camera.ts +0 -81
- package/runtime/sdk/plugins/geolocation.ts +0 -123
- package/runtime/sdk/plugins/oauth.ts +0 -61
- package/runtime/sdk/plugins/permission.ts +0 -7
- package/runtime/sdk/plugins/push.ts +0 -132
- package/runtime/sdk/plugins/qr.ts +0 -73
- package/runtime/sdk/plugins/service.ts +0 -47
- package/runtime/sdk/runtime/app.ts +0 -101
- package/runtime/sdk/runtime/appEvents.ts +0 -54
- package/runtime/sdk/runtime/channel.ts +0 -61
- package/runtime/sdk/runtime/clipboard.ts +0 -20
- package/runtime/sdk/runtime/datetime.ts +0 -329
- package/runtime/sdk/runtime/device.ts +0 -293
- package/runtime/sdk/runtime/fetch.ts +0 -77
- package/runtime/sdk/runtime/files.ts +0 -23
- package/runtime/sdk/runtime/input.ts +0 -175
- package/runtime/sdk/runtime/media.ts +0 -111
- package/runtime/sdk/runtime/misc.ts +0 -16
- package/runtime/sdk/runtime/net.ts +0 -36
- package/runtime/sdk/runtime/rpc.ts +0 -218
- package/runtime/sdk/runtime/service.ts +0 -83
- package/runtime/sdk/runtime/share.ts +0 -9
- package/runtime/sdk/runtime/storage.ts +0 -13
- package/runtime/sdk/runtime/touch.ts +0 -76
- package/runtime/sdk/scene/defineScene.ts +0 -1227
- package/runtime/sdk/scene/editorPlugins.ts +0 -92
- package/runtime/sdk/scene/gizmos.ts +0 -148
- package/runtime/sdk/scene/grammar.ts +0 -120
- package/runtime/sdk/scene/material.ts +0 -188
- package/runtime/sdk/server/auth/appConfig.ts +0 -12
- package/runtime/sdk/server/auth/global.ts +0 -80
- package/runtime/sdk/server/auth/host.ts +0 -318
- package/runtime/sdk/server/auth/models.ts +0 -83
- package/runtime/sdk/server/auth/types.ts +0 -50
- package/runtime/sdk/server/channel.ts +0 -56
- package/runtime/sdk/server/context.ts +0 -36
- package/runtime/sdk/server/db/defineDb.ts +0 -237
- package/runtime/sdk/server/db/fields.ts +0 -132
- package/runtime/sdk/server/db/httpTransport.ts +0 -93
- package/runtime/sdk/server/db/index.ts +0 -7
- package/runtime/sdk/server/db/marci/query.ts +0 -412
- package/runtime/sdk/server/db/types.ts +0 -202
- package/runtime/sdk/server/errors.ts +0 -12
- package/runtime/sdk/server/host.ts +0 -74
- package/runtime/sdk/server/inject.ts +0 -13
- package/runtime/sdk/server/runtime.ts +0 -133
- package/runtime/sdk/server/validate.ts +0 -87
- package/runtime/sdk/ui/NativeView.ts +0 -142
- package/runtime/sdk/ui/UI.ts +0 -39
- package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
- package/runtime/sdk/ui/UIButton.ts +0 -101
- package/runtime/sdk/ui/UIContainer.ts +0 -60
- package/runtime/sdk/ui/UIImage.ts +0 -83
- package/runtime/sdk/ui/UIInput.ts +0 -185
- package/runtime/sdk/ui/UIModal.ts +0 -139
- package/runtime/sdk/ui/UINode.ts +0 -826
- package/runtime/sdk/ui/UIPager.ts +0 -362
- package/runtime/sdk/ui/UIPopover.ts +0 -100
- package/runtime/sdk/ui/UIScreen.ts +0 -123
- package/runtime/sdk/ui/UIScrollable.ts +0 -87
- package/runtime/sdk/ui/UISpacer.ts +0 -14
- package/runtime/sdk/ui/UITabs.ts +0 -236
- package/runtime/sdk/ui/UIText.ts +0 -51
- package/runtime/sdk/ui/UIVideo.ts +0 -88
- package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
- package/runtime/sdk/ui/UIWidget.ts +0 -127
- package/runtime/sdk/ui/fonts.ts +0 -13
- package/runtime/sdk/ui/presentable.ts +0 -117
- package/runtime/sdk/ui/router.ts +0 -132
- package/runtime/sdk/ui/theme.ts +0 -84
|
@@ -1,433 +0,0 @@
|
|
|
1
|
-
// Autotile materials: the pure model + deriver behind `.tiles.ts` files (docs/scene2d-editor-plan.md
|
|
2
|
-
// §7). A map painted with MATERIAL ids (intent) is turned into atlas tile indices (display) by
|
|
3
|
-
// neighbor-mask rules — never the other way around, and the derived indices are never persisted.
|
|
4
|
-
//
|
|
5
|
-
// Two material kinds, differing only in their neighbor classifier:
|
|
6
|
-
// - 'patch' — area materials (grass, dirt, water, platformer ground): the 8-neighbor blob mask.
|
|
7
|
-
// Minimal slot set = fill (+ weighted scatter variants) + 4 edges + 4 outer corners + 4 inner
|
|
8
|
-
// corners; missing slots fall back to simpler ones, so a partial set always renders.
|
|
9
|
-
// `size: 2 | 3` makes the material live on a size×size BLOCK lattice (anchored at map 0,0):
|
|
10
|
-
// classification runs block-to-block, fill holds size² tiles and each edge `size` tiles, while
|
|
11
|
-
// corners stay one cell. That is what art with a multi-cell pattern phase needs (herringbone,
|
|
12
|
-
// lane dashes), and it makes a one-block-wide strip carry BOTH edges by construction — a
|
|
13
|
-
// 2-lane road with its curbs and centre line is just `size: 2` painted one block wide.
|
|
14
|
-
// - 'path' — line materials (roads, fences, walls, rails): the 4-neighbor mask, 16 classes
|
|
15
|
-
// (isolated / ends / straights / corners / tees / cross), same fallback principle.
|
|
16
|
-
//
|
|
17
|
-
// Direction conventions (data is row-major, row 0 = the TOP row): N = row-1, S = row+1, E = col+1,
|
|
18
|
-
// W = col-1. Edge arrays are [n, e, s, w]; corner arrays are [ne, se, sw, nw] (clockwise from
|
|
19
|
-
// north-east). Cells outside the map count as SAME material, so patches continue off-map instead
|
|
20
|
-
// of growing a border along the map edge.
|
|
21
|
-
//
|
|
22
|
-
// A cell holds a material id, a part id, -1 for empty, or — below -1 — a RAW atlas tile
|
|
23
|
-
// (`rawCell(index)`), so hand-placed art can sit in a material map without colliding with ids.
|
|
24
|
-
//
|
|
25
|
-
// Any atlas index (a raw cell, a slot value) may carry an ORIENTATION packed into the same int:
|
|
26
|
-
// `rot90(i)` / `flipX(i)` — see the oriented-tiles section. The deriver treats oriented values as
|
|
27
|
-
// opaque positive ints and copies them through; renderers decode the bits into a UV permutation.
|
|
28
|
-
//
|
|
29
|
-
// Pure and engine-free on purpose — the 2D editor keeps a mirrored copy
|
|
30
|
-
// (packages/editor-2d/src/autotile.ts, like cells.ts / history.ts): keep them in sync.
|
|
31
|
-
|
|
32
|
-
/** A fill entry: an atlas index, or [index, weight] for seeded scatter variants. */
|
|
33
|
-
export type FillEntry = number | [number, number]
|
|
34
|
-
|
|
35
|
-
export type Dir4 = [number, number, number, number]
|
|
36
|
-
|
|
37
|
-
/** One side's tiles: a single index (used for every cell along that side), or — with `size` > 1 —
|
|
38
|
-
* the `size` indices ALONG it (n/s run left→right, w/e run top→bottom). */
|
|
39
|
-
export type EdgeSlot = number | number[]
|
|
40
|
-
|
|
41
|
-
/** A whole `size`×`size` piece: one index (drawn in every cell of the block), or size² indices
|
|
42
|
-
* row-major. Path pieces and parts are blocks — at size 1 they are just a tile. */
|
|
43
|
-
export type TileBlock = number | number[]
|
|
44
|
-
|
|
45
|
-
/** `size`² indices, row-major within the block. */
|
|
46
|
-
export type FillBlock = number[]
|
|
47
|
-
|
|
48
|
-
/** A named element placed BY HAND (a pedestrian crossing in a road, a gate in a fence). It carries
|
|
49
|
-
* its own `id`, so the map stores it like any material — but it CONNECTS as its parent, so the
|
|
50
|
-
* run continues straight through it. Never picked at random: you paint it from the palette, ONE
|
|
51
|
-
* CELL at a time, and the tile is worked out from where that cell sits. */
|
|
52
|
-
export type TilePart = {
|
|
53
|
-
/** Stable small int, unique across every material and part of the tileset. */
|
|
54
|
-
id: number
|
|
55
|
-
/** Which run the art is drawn for — 'v' vertical, 'h' horizontal. It also picks the art: the
|
|
56
|
-
* element varies ACROSS the line (a crossing has one tile per lane) and repeats along it, so
|
|
57
|
-
* `tiles` is `size` long, indexed by row for 'h' and by column for 'v'. */
|
|
58
|
-
axis?: "v" | "h"
|
|
59
|
-
/** The tiles — one index, `size` across the run when `axis` is set, else the size² block. */
|
|
60
|
-
tiles?: TileBlock
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** A block variant: the block, or [block, weight] for seeded scatter. */
|
|
64
|
-
export type BlockEntry = FillBlock | [FillBlock, number]
|
|
65
|
-
|
|
66
|
-
/** One transition set of a patch material — the tiles its border shows against a neighbor. */
|
|
67
|
-
export type PatchSlots = {
|
|
68
|
-
/** Edge line tiles [n, e, s, w] — e.g. `edges[0]` draws where the patch stops toward the top. */
|
|
69
|
-
edges?: [EdgeSlot, EdgeSlot, EdgeSlot, EdgeSlot]
|
|
70
|
-
/** Convex corner tiles [ne, se, sw, nw] — the patch's outside corners. Always one cell. */
|
|
71
|
-
outer?: Dir4
|
|
72
|
-
/** Concave corner tiles [ne, se, sw, nw] — the notch where two patch arms meet. One cell. */
|
|
73
|
-
inner?: Dir4
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export type PatchMaterial = {
|
|
77
|
-
/** Stable small int — what `cells('…')` stores when a map carries Autotile2D. */
|
|
78
|
-
id: number
|
|
79
|
-
kind: "patch"
|
|
80
|
-
/** Editor swatch color ('#4a7c3a'). */
|
|
81
|
-
color?: string
|
|
82
|
-
/** Block size in cells (default 1). With 2 or 3 the material is classified block-to-block on a
|
|
83
|
-
* lattice anchored at the map origin, `fill` holds size² tiles and each edge `size` tiles. */
|
|
84
|
-
size?: number
|
|
85
|
-
/** Interior tile — one index, or an ARRAY of scatter variants (each `index` or
|
|
86
|
-
* `[index, weight]`; bare indices weigh 1): `fill: [261, [262, 0.1], [263, 0.05]]`.
|
|
87
|
-
* With `size` > 1 it is instead the row-major BLOCK of size² indices
|
|
88
|
-
* (`fill: [130, 131, 146, 147]`), or a list of such blocks for scatter variants. */
|
|
89
|
-
fill: number | FillEntry[] | BlockEntry[]
|
|
90
|
-
/** Transition sets keyed by the neighboring material's name; '*' = any other neighbor
|
|
91
|
-
* (including empty). Baked packs draw a material fading into a SPECIFIC background. */
|
|
92
|
-
over?: Record<string, PatchSlots>
|
|
93
|
-
/** Hand-placed elements that connect as this material (see TilePart). */
|
|
94
|
-
parts?: Record<string, TilePart>
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export type PathMaterial = {
|
|
98
|
-
id: number
|
|
99
|
-
kind: "path"
|
|
100
|
-
color?: string
|
|
101
|
-
/** Cells ACROSS the line (default 1). A `size: 2` road is two lanes wide: connectivity runs
|
|
102
|
-
* block to block and every piece below is a 2×2 block instead of one tile. */
|
|
103
|
-
size?: number
|
|
104
|
-
/** No connections at all. */
|
|
105
|
-
isolated?: TileBlock
|
|
106
|
-
/** End caps [n, e, s, w] — `ends[0]` connects toward the top only. */
|
|
107
|
-
ends?: [TileBlock, TileBlock, TileBlock, TileBlock]
|
|
108
|
-
/** [vertical (n+s), horizontal (e+w)]. */
|
|
109
|
-
straights?: [TileBlock, TileBlock]
|
|
110
|
-
/** Elbows [ne, se, sw, nw] — `corners[0]` connects top + right. */
|
|
111
|
-
corners?: [TileBlock, TileBlock, TileBlock, TileBlock]
|
|
112
|
-
/** T pieces [n, e, s, w] — `tees[0]` is the tee MISSING the top connection. */
|
|
113
|
-
tees?: [TileBlock, TileBlock, TileBlock, TileBlock]
|
|
114
|
-
/** All four connections. */
|
|
115
|
-
cross?: TileBlock
|
|
116
|
-
/** Hand-placed elements that connect as this material (see TilePart). */
|
|
117
|
-
parts?: Record<string, TilePart>
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export type TileMaterial = PatchMaterial | PathMaterial
|
|
121
|
-
|
|
122
|
-
export type TilesetDef = {
|
|
123
|
-
/** The atlas image — write `asset('./TileSet.png')`. */
|
|
124
|
-
image: string
|
|
125
|
-
/** Atlas cell size in image pixels — a number (square) or [w, h]. */
|
|
126
|
-
tile: number | [number, number]
|
|
127
|
-
/** Named materials; names are the editor/`over:` vocabulary, `id`s are what maps store. */
|
|
128
|
-
materials?: Record<string, TileMaterial>
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// ---- helpers -------------------------------------------------------------------
|
|
132
|
-
|
|
133
|
-
export const normalizeFill = (fill: number | FillEntry[]): [number, number][] => {
|
|
134
|
-
if (typeof fill === "number") return [ [ fill, 1 ] ]
|
|
135
|
-
return fill.map((e) => (typeof e === "number" ? [ e, 1 ] : [ e[0], e[1] ]))
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** A material's block size — 1 unless it declares `size` (clamped to a positive int). */
|
|
139
|
-
export const blockSize = (mat: TileMaterial): number => Math.max(1, Math.round(mat.size ?? 1))
|
|
140
|
-
|
|
141
|
-
/** One cell of a block: a scalar covers the whole block, an array reads row-major. Undefined =
|
|
142
|
-
* unassigned (missing, or the negative sentinel the editor writes for a blank slot). */
|
|
143
|
-
export const blockTile = (
|
|
144
|
-
block: TileBlock | undefined, n: number, lx: number, ly: number,
|
|
145
|
-
): number | undefined => {
|
|
146
|
-
if (typeof block === "number") return block >= 0 ? block : undefined
|
|
147
|
-
if (!Array.isArray(block)) return undefined
|
|
148
|
-
const t = block[ly * n + lx]
|
|
149
|
-
return typeof t === "number" && t >= 0 ? t : undefined
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** How many tiles a part holds: `size` across the run when it declares an axis, else the block. */
|
|
153
|
-
export const partSlots = (part: TilePart, n: number): number => (part.axis ? n : n * n)
|
|
154
|
-
|
|
155
|
-
// ---- raw tiles inside a material map ------------------------------------------------------------
|
|
156
|
-
// A material map stores INTENT, so a cell holding a plain atlas index would be ambiguous (is `3`
|
|
157
|
-
// material 3 or tile 3?). Raw tiles therefore live below -1, out of the material id space: -2 is
|
|
158
|
-
// tile 0, -3 is tile 1, and so on. -1 stays empty, and a raw cell simply draws itself.
|
|
159
|
-
|
|
160
|
-
/** The cell value that draws atlas tile `index` verbatim in a material map. */
|
|
161
|
-
export const rawCell = (index: number): number => -index - 2
|
|
162
|
-
|
|
163
|
-
/** True for a cell holding a raw atlas tile rather than a material id. */
|
|
164
|
-
export const isRawCell = (v: number): boolean => v <= -2
|
|
165
|
-
|
|
166
|
-
/** The atlas index behind a raw cell (garbage in, garbage out — guard with isRawCell). */
|
|
167
|
-
export const rawIndex = (v: number): number => -v - 2
|
|
168
|
-
|
|
169
|
-
// ---- oriented tiles -----------------------------------------------------------------------------
|
|
170
|
-
// Any tile value — a slot in a `.tiles.ts` material, a raw cell, a plain index in a raw map — can
|
|
171
|
-
// carry an orientation in the SAME int: bits 0–27 hold the atlas index, bit 28 a horizontal mirror,
|
|
172
|
-
// bits 29–30 the clockwise quarter-turns; the mirror applies FIRST, then the rotation. The sign bit
|
|
173
|
-
// is never touched, so every oriented value stays positive: -1 (empty), raw cells (≤ -2) and the
|
|
174
|
-
// negative "unassigned slot" sentinel all keep working, and the deriver passes oriented values
|
|
175
|
-
// through untouched. Renderers permute the atlas cell's UV corners — same quads, same batches.
|
|
176
|
-
// `rot90`/`flipX` COMPOSE (they act on the value's current orientation), so `rot90(rot90(t))` is a
|
|
177
|
-
// half turn and all 8 orientations of a tile are reachable; write them in `.tiles.ts` files
|
|
178
|
-
// directly: `ends: [121, rot90(121), rot180(121), rot270(121)]`.
|
|
179
|
-
|
|
180
|
-
export const TILE_INDEX_MASK = 0x0fffffff
|
|
181
|
-
const TILE_FLIP = 1 << 28
|
|
182
|
-
|
|
183
|
-
/** The atlas index behind a (possibly oriented) tile value. */
|
|
184
|
-
export const tileIndex = (v: number): number => v & TILE_INDEX_MASK
|
|
185
|
-
/** Clockwise quarter-turns (0–3) of a tile value. */
|
|
186
|
-
export const tileTurns = (v: number): number => (v >> 29) & 3
|
|
187
|
-
/** Whether the tile is mirrored horizontally (the mirror applies before the rotation). */
|
|
188
|
-
export const tileFlip = (v: number): boolean => (v & TILE_FLIP) !== 0
|
|
189
|
-
/** True when the value carries any orientation (renderers keep the fast path otherwise). */
|
|
190
|
-
export const tileOriented = (v: number): boolean => v > TILE_INDEX_MASK
|
|
191
|
-
/** Build an oriented tile value: `index`, mirrored when `flip`, then `turns` quarter-turns CW. */
|
|
192
|
-
export const packTile = (index: number, turns = 0, flip = false): number =>
|
|
193
|
-
(index & TILE_INDEX_MASK) | (flip ? TILE_FLIP : 0) | ((turns & 3) << 29)
|
|
194
|
-
|
|
195
|
-
/** The value rotated a further 90° clockwise. */
|
|
196
|
-
export const rot90 = (v: number): number => packTile(tileIndex(v), tileTurns(v) + 1, tileFlip(v))
|
|
197
|
-
/** The value rotated a further 180°. */
|
|
198
|
-
export const rot180 = (v: number): number => packTile(tileIndex(v), tileTurns(v) + 2, tileFlip(v))
|
|
199
|
-
/** The value rotated a further 270° clockwise (90° counter-clockwise). */
|
|
200
|
-
export const rot270 = (v: number): number => packTile(tileIndex(v), tileTurns(v) + 3, tileFlip(v))
|
|
201
|
-
/** The value mirrored horizontally (on screen — existing turns are re-based, group math). */
|
|
202
|
-
export const flipX = (v: number): number => packTile(tileIndex(v), 4 - tileTurns(v), !tileFlip(v))
|
|
203
|
-
|
|
204
|
-
/** The tile a part draws in a cell — indexed ACROSS the run ('h' by row, 'v' by column) so one
|
|
205
|
-
* element covers a lane each and repeats along the run; without an axis it is a plain block. */
|
|
206
|
-
export const partTile = (part: TilePart, n: number, lx: number, ly: number): number | undefined => {
|
|
207
|
-
const tiles = part.tiles
|
|
208
|
-
if (typeof tiles === "number") return tiles >= 0 ? tiles : undefined
|
|
209
|
-
if (!Array.isArray(tiles)) return undefined
|
|
210
|
-
const t = tiles[part.axis === "h" ? ly : part.axis === "v" ? lx : ly * n + lx]
|
|
211
|
-
return typeof t === "number" && t >= 0 ? t : undefined
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/** `fill` as weighted BLOCKS of `size`² indices (row-major). Size 1 keeps the scalar/scatter
|
|
215
|
-
* reading; with size > 1 a flat number array is one positional block, an array of arrays is the
|
|
216
|
-
* variant list, and a bare number fills the whole block. Missing entries read as -1. */
|
|
217
|
-
export const fillBlocks = (fill: PatchMaterial["fill"], size = 1): [number[], number][] => {
|
|
218
|
-
const n = Math.max(1, Math.round(size))
|
|
219
|
-
const area = n * n
|
|
220
|
-
const pad = (b: readonly number[]): number[] =>
|
|
221
|
-
Array.from({ length: area }, (_, i) => (typeof b[i] === "number" ? b[i]! : -1))
|
|
222
|
-
if (typeof fill === "number") return [ [ new Array<number>(area).fill(fill), 1 ] ]
|
|
223
|
-
if (!Array.isArray(fill) || fill.length === 0) return []
|
|
224
|
-
if (n === 1) return normalizeFill(fill as FillEntry[]).map(([ i, w ]) => [ [ i ], w ])
|
|
225
|
-
if (!Array.isArray(fill[0])) return [ [ pad(fill as number[]), 1 ] ]
|
|
226
|
-
return (fill as BlockEntry[]).map((e): [number[], number] =>
|
|
227
|
-
Array.isArray(e[0]) ? [ pad(e[0] as number[]), (e as [FillBlock, number])[1] ?? 1 ] : [ pad(e as number[]), 1 ])
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** Deterministic per-cell hash in [0, 1) — scatter must not reshuffle when nearby cells change. */
|
|
231
|
-
const hash01 = (x: number, y: number, seed: number): number => {
|
|
232
|
-
let h = (Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(seed, 2246822519)) | 0
|
|
233
|
-
h = Math.imul(h ^ (h >>> 13), 1274126177)
|
|
234
|
-
h ^= h >>> 16
|
|
235
|
-
return (h >>> 0) / 4294967296
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/** The fill tile for block (bc, br) at block-local (lx, ly). Scatter rolls once per BLOCK, so a
|
|
239
|
-
* multi-cell pattern never tears; at size 1 the block IS the cell (identical to before). */
|
|
240
|
-
const pickFill = (
|
|
241
|
-
mat: PatchMaterial, bc: number, br: number, seed: number, n: number, lx: number, ly: number,
|
|
242
|
-
): number => {
|
|
243
|
-
const variants = fillBlocks(mat.fill, n)
|
|
244
|
-
if (variants.length === 0) return -1
|
|
245
|
-
const at = ly * n + lx
|
|
246
|
-
if (variants.length === 1) return variants[0]![0][at] ?? -1
|
|
247
|
-
const total = variants.reduce((s, v) => s + v[1], 0)
|
|
248
|
-
let roll = hash01(bc, br, seed + mat.id * 7919) * total
|
|
249
|
-
for (const [ block, w ] of variants) {
|
|
250
|
-
roll -= w
|
|
251
|
-
if (roll < 0) return block[at] ?? -1
|
|
252
|
-
}
|
|
253
|
-
return variants[variants.length - 1]![0][at] ?? -1
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// corner order [ne, se, sw, nw] as (dx, dy) pairs; edge order [n, e, s, w]
|
|
257
|
-
const EDGE_D: [number, number][] = [ [ 0, -1 ], [ 1, 0 ], [ 0, 1 ], [ -1, 0 ] ]
|
|
258
|
-
const CORNER_D: [number, number][] = [ [ 1, -1 ], [ 1, 1 ], [ -1, 1 ], [ -1, -1 ] ]
|
|
259
|
-
|
|
260
|
-
// ---- the deriver ---------------------------------------------------------------
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Derive display atlas indices from a material-id grid (row-major, row 0 = top, -1 = empty).
|
|
264
|
-
* Values that match no material id pass through unchanged (so converting maps degrades
|
|
265
|
-
* gracefully); -1 stays -1. Deterministic: same inputs + seed → same output on every platform.
|
|
266
|
-
*/
|
|
267
|
-
export const deriveCells = (
|
|
268
|
-
def: TilesetDef, cols: number, rows: number, cells: ArrayLike<number>, seed = 0,
|
|
269
|
-
): Int32Array => {
|
|
270
|
-
const byId = new Map<number, { name: string, mat: TileMaterial }>()
|
|
271
|
-
const parts = new Map<number, { part: TilePart, mat: TileMaterial, owner: number }>()
|
|
272
|
-
for (const [ name, mat ] of Object.entries(def.materials ?? {})) {
|
|
273
|
-
if (!byId.has(mat.id)) byId.set(mat.id, { name, mat })
|
|
274
|
-
for (const part of Object.values(mat.parts ?? {})) {
|
|
275
|
-
if (!parts.has(part.id)) parts.set(part.id, { part, mat, owner: mat.id })
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
// a part's cells read as their PARENT everywhere connectivity is asked — the run continues
|
|
279
|
-
// straight through a crossing or a gate; only the drawing differs
|
|
280
|
-
const ownerOf = (v: number): number => parts.get(v)?.owner ?? v
|
|
281
|
-
|
|
282
|
-
const out = new Int32Array(cols * rows)
|
|
283
|
-
const at = (c: number, r: number, self: number): number =>
|
|
284
|
-
(c < 0 || r < 0 || c >= cols || r >= rows) ? self : ownerOf(cells[r * cols + c] as number)
|
|
285
|
-
/** A block belongs to `self` when ANY of its cells does — so a block you painted one cell at a
|
|
286
|
-
* time still reads as part of the run (and off-map keeps counting as same). */
|
|
287
|
-
const blockAt = (ax: number, ay: number, n: number, self: number): number => {
|
|
288
|
-
if (n === 1) return at(ax, ay, self)
|
|
289
|
-
for (let y = ay; y < ay + n; y++) {
|
|
290
|
-
for (let x = ax; x < ax + n; x++) if (at(x, y, self) === self) return self
|
|
291
|
-
}
|
|
292
|
-
return at(ax, ay, self)
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
for (let r = 0; r < rows; r++) {
|
|
296
|
-
for (let c = 0; c < cols; c++) {
|
|
297
|
-
const raw = cells[r * cols + c] as number
|
|
298
|
-
// a raw cell draws its atlas tile verbatim — hand-placed art inside a material map
|
|
299
|
-
if (isRawCell(raw)) { out[r * cols + c] = rawIndex(raw); continue }
|
|
300
|
-
const v = raw >= 0 ? ownerOf(raw) : raw
|
|
301
|
-
const entry = v >= 0 ? byId.get(v) : undefined
|
|
302
|
-
if (!entry) { out[r * cols + c] = raw; continue }
|
|
303
|
-
const hit = parts.get(raw)
|
|
304
|
-
if (hit) {
|
|
305
|
-
const n = blockSize(entry.mat)
|
|
306
|
-
const t = partTile(hit.part, n, c - Math.floor(c / n) * n, r - Math.floor(r / n) * n)
|
|
307
|
-
// an unassigned part still draws its material, so a half-authored element never blanks out
|
|
308
|
-
if (t !== undefined) { out[r * cols + c] = t; continue }
|
|
309
|
-
}
|
|
310
|
-
out[r * cols + c] = entry.mat.kind === "patch"
|
|
311
|
-
? derivePatch(entry.mat, byId, blockAt, c, r, v, seed)
|
|
312
|
-
: derivePath(entry.mat, blockAt, c, r, v)
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return out
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/** Convex corners as [cardA, cardB, cornerIndex]: n+e → ne(0), e+s → se(1), s+w → sw(2), w+n → nw(3). */
|
|
319
|
-
const CORNER_PAIRS: [number, number, number][] = [ [ 0, 1, 0 ], [ 1, 2, 1 ], [ 2, 3, 2 ], [ 3, 0, 3 ] ]
|
|
320
|
-
|
|
321
|
-
/** Where each concave corner shows inside a block: [localX, localY, cardA, cardB], in [ne, se, sw, nw]. */
|
|
322
|
-
const INNER_AT = (n: number): [number, number, number, number][] => [
|
|
323
|
-
[ n - 1, 0, 0, 1 ], [ n - 1, n - 1, 1, 2 ], [ 0, n - 1, 2, 3 ], [ 0, 0, 3, 0 ],
|
|
324
|
-
]
|
|
325
|
-
|
|
326
|
-
/** Samples a whole BLOCK: (anchorX, anchorY, size, self) → the material that block belongs to. */
|
|
327
|
-
type BlockAt = (ax: number, ay: number, n: number, self: number) => number
|
|
328
|
-
|
|
329
|
-
const derivePatch = (
|
|
330
|
-
mat: PatchMaterial, byId: Map<number, { name: string, mat: TileMaterial }>,
|
|
331
|
-
at: BlockAt, c: number, r: number, v: number, seed: number,
|
|
332
|
-
): number => {
|
|
333
|
-
// With `size` > 1 the material lives on a block lattice anchored at the map origin: neighbors are
|
|
334
|
-
// sampled one BLOCK away (at that block's anchor cell) and the tile is placed by the cell's
|
|
335
|
-
// position INSIDE the block. n = 1 makes every block one cell — the original per-cell rules.
|
|
336
|
-
const n = blockSize(mat)
|
|
337
|
-
const bc = Math.floor(c / n), br = Math.floor(r / n)
|
|
338
|
-
const lx = c - bc * n, ly = r - br * n
|
|
339
|
-
const ax = bc * n, ay = br * n
|
|
340
|
-
const same = (dx: number, dy: number): boolean => at(ax + dx * n, ay + dy * n, n, v) === v
|
|
341
|
-
// cardinal + diagonal same-bits (diagonals only matter where both adjacent cardinals are same)
|
|
342
|
-
const card = EDGE_D.map(([ dx, dy ]) => same(dx, dy)) // [n, e, s, w]
|
|
343
|
-
const diag = CORNER_D.map(([ dx, dy ]) => same(dx, dy)) // [ne, se, sw, nw]
|
|
344
|
-
const fill = (): number => pickFill(mat, bc, br, seed, n, lx, ly)
|
|
345
|
-
|
|
346
|
-
const slots = pickSlots(mat, byId, at, ax, ay, v, n)
|
|
347
|
-
if (!slots) return fill()
|
|
348
|
-
// a negative value inside a slot array means "unassigned" (the editor writes partial sets)
|
|
349
|
-
const pick = (t: number | undefined): number | undefined => (t !== undefined && t >= 0 ? t : undefined)
|
|
350
|
-
const outer = (i: number): number | undefined => pick(slots.outer?.[i])
|
|
351
|
-
const edge = (d: number): number | undefined => {
|
|
352
|
-
const s = slots.edges?.[d]
|
|
353
|
-
if (typeof s === "number") return pick(s)
|
|
354
|
-
return Array.isArray(s) ? pick(s[d === 0 || d === 2 ? lx : ly]) : undefined
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// sides of THIS cell that sit on a block border facing another material
|
|
358
|
-
const on = [
|
|
359
|
-
!card[0]! && ly === 0, !card[1]! && lx === n - 1, !card[2]! && ly === n - 1, !card[3]! && lx === 0,
|
|
360
|
-
]
|
|
361
|
-
for (const [ a, b, i ] of CORNER_PAIRS) {
|
|
362
|
-
if (on[a]! && on[b]!) { const t = outer(i); if (t !== undefined) return t }
|
|
363
|
-
}
|
|
364
|
-
for (let d = 0; d < 4; d++) if (on[d]!) { const t = edge(d); if (t !== undefined) return t }
|
|
365
|
-
if (on.some(Boolean)) return fill()
|
|
366
|
-
|
|
367
|
-
// block interior — a differing relevant diagonal is a concave (inner) corner, drawn in the one
|
|
368
|
-
// cell of the block that touches it
|
|
369
|
-
const inner = INNER_AT(n)
|
|
370
|
-
for (let i = 0; i < 4; i++) {
|
|
371
|
-
const [ px, py, a, b ] = inner[i]!
|
|
372
|
-
if (lx === px && ly === py && card[a]! && card[b]! && !diag[i]!) return pick(slots.inner?.[i]) ?? fill()
|
|
373
|
-
}
|
|
374
|
-
return fill()
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/** The transition set for this block: keyed by the dominant differing neighbor's material name,
|
|
378
|
-
* '*' as the fallback (empty and unknown neighbors always land on '*'). */
|
|
379
|
-
const pickSlots = (
|
|
380
|
-
mat: PatchMaterial, byId: Map<number, { name: string, mat: TileMaterial }>,
|
|
381
|
-
at: BlockAt, ax: number, ay: number, v: number, n: number,
|
|
382
|
-
): PatchSlots | undefined => {
|
|
383
|
-
const over = mat.over
|
|
384
|
-
if (!over) return undefined
|
|
385
|
-
const counts = new Map<number, number>()
|
|
386
|
-
for (const [ dx, dy ] of [ ...EDGE_D, ...CORNER_D ]) {
|
|
387
|
-
const nv = at(ax + dx * n, ay + dy * n, n, v)
|
|
388
|
-
if (nv !== v) counts.set(nv, (counts.get(nv) ?? 0) + 1)
|
|
389
|
-
}
|
|
390
|
-
let bestId: number | null = null, best = 0
|
|
391
|
-
for (const [ id, n ] of counts) {
|
|
392
|
-
if (n > best || (n === best && bestId !== null && id < bestId)) { best = n; bestId = id }
|
|
393
|
-
}
|
|
394
|
-
const name = bestId !== null && bestId >= 0 ? byId.get(bestId)?.name : undefined
|
|
395
|
-
return (name !== undefined ? over[name] : undefined) ?? over["*"]
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
const derivePath = (
|
|
399
|
-
mat: PathMaterial, at: BlockAt, c: number, r: number, v: number,
|
|
400
|
-
): number => {
|
|
401
|
-
// `size` makes the line that many cells ACROSS: connectivity is block to block and every piece
|
|
402
|
-
// is a size² block placed by the cell's position inside it (size 1 = one tile per piece).
|
|
403
|
-
const n = blockSize(mat)
|
|
404
|
-
const bc = Math.floor(c / n), br = Math.floor(r / n)
|
|
405
|
-
const lx = c - bc * n, ly = r - br * n
|
|
406
|
-
const ax = bc * n, ay = br * n
|
|
407
|
-
const conn = EDGE_D.map(([ dx, dy ]) => at(ax + dx * n, ay + dy * n, n, v) === v) // [n, e, s, w]
|
|
408
|
-
const links = conn.filter(Boolean).length
|
|
409
|
-
// a negative value inside a piece means "unassigned" (the editor writes partial sets)
|
|
410
|
-
const pick = (b: TileBlock | undefined): number | undefined => blockTile(b, n, lx, ly)
|
|
411
|
-
const any = (): number =>
|
|
412
|
-
pick(mat.isolated) ?? pick(mat.cross) ?? pick(mat.straights?.[1]) ?? pick(mat.straights?.[0])
|
|
413
|
-
?? mat.ends?.map((b) => pick(b)).find((t) => t !== undefined)
|
|
414
|
-
?? pick(mat.corners?.[0]) ?? pick(mat.tees?.[0]) ?? v
|
|
415
|
-
|
|
416
|
-
if (links === 0) return pick(mat.isolated) ?? any()
|
|
417
|
-
if (links === 1) {
|
|
418
|
-
const d = conn.findIndex(Boolean)
|
|
419
|
-
return pick(mat.ends?.[d]) ?? pick(mat.straights?.[d % 2 === 0 ? 0 : 1]) ?? any()
|
|
420
|
-
}
|
|
421
|
-
if (links === 2) {
|
|
422
|
-
if (conn[0] && conn[2]) return pick(mat.straights?.[0]) ?? any()
|
|
423
|
-
if (conn[1] && conn[3]) return pick(mat.straights?.[1]) ?? any()
|
|
424
|
-
// elbows: n+e → ne(0), e+s → se(1), s+w → sw(2), w+n → nw(3)
|
|
425
|
-
const corner = conn[0] && conn[1] ? 0 : conn[1] && conn[2] ? 1 : conn[2] && conn[3] ? 2 : 3
|
|
426
|
-
return pick(mat.corners?.[corner]) ?? pick(mat.cross) ?? any()
|
|
427
|
-
}
|
|
428
|
-
if (links === 3) {
|
|
429
|
-
const missing = conn.findIndex((s) => !s)
|
|
430
|
-
return pick(mat.tees?.[missing]) ?? pick(mat.cross) ?? pick(mat.straights?.[missing % 2 === 0 ? 1 : 0]) ?? any()
|
|
431
|
-
}
|
|
432
|
-
return pick(mat.cross) ?? any()
|
|
433
|
-
}
|
package/runtime/sdk/g2/cells.ts
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
// The `cells('…')` payload literal used by `.scene2d.ts` tilemap blocks (and terrain material maps
|
|
2
|
-
// later): one opaque base64 string holding the grid dimensions + row-major Int32 cell values, so a
|
|
3
|
-
// whole map prints on one line of the scene file. Layout (little-endian Int32): [cols, rows,
|
|
4
|
-
// runLen₁, value₁, runLen₂, value₂, …] — run-length pairs, because tilemaps are dominated by long
|
|
5
|
-
// runs of the same tile (a 100×100 grass field is a handful of pairs, not 10k ints). The payload is
|
|
6
|
-
// versionless by design: cols is always > 0, so a future format can claim a negative sentinel.
|
|
7
|
-
//
|
|
8
|
-
// Self-contained on purpose — QuickJS has no atob/Buffer, and the editor keeps its own mirrored
|
|
9
|
-
// copy of this codec (the scene-doc/editor side must not depend on the SDK).
|
|
10
|
-
|
|
11
|
-
export type CellsData = {
|
|
12
|
-
cols: number
|
|
13
|
-
rows: number
|
|
14
|
-
/** Row-major tile values (row 0 = the top row), length cols*rows. */
|
|
15
|
-
data: Int32Array
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
|
19
|
-
let B64_REV: Int8Array | null = null
|
|
20
|
-
|
|
21
|
-
const b64encode = (bytes: Uint8Array): string => {
|
|
22
|
-
let out = ""
|
|
23
|
-
for (let i = 0; i < bytes.length; i += 3) {
|
|
24
|
-
const b0 = bytes[i]!, b1 = bytes[i + 1], b2 = bytes[i + 2]
|
|
25
|
-
out += B64[b0 >> 2]! + B64[((b0 & 3) << 4) | ((b1 ?? 0) >> 4)]!
|
|
26
|
-
out += b1 === undefined ? "=" : B64[((b1 & 15) << 2) | ((b2 ?? 0) >> 6)]!
|
|
27
|
-
out += b2 === undefined ? "=" : B64[b2 & 63]!
|
|
28
|
-
}
|
|
29
|
-
return out
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const b64decode = (s: string): Uint8Array => {
|
|
33
|
-
if (!B64_REV) {
|
|
34
|
-
B64_REV = new Int8Array(128).fill(-1)
|
|
35
|
-
for (let i = 0; i < 64; i++) B64_REV[B64.charCodeAt(i)] = i
|
|
36
|
-
}
|
|
37
|
-
const clean = s.replace(/=+$/, "")
|
|
38
|
-
const out = new Uint8Array(Math.floor(clean.length * 3 / 4))
|
|
39
|
-
let acc = 0, bits = 0, w = 0
|
|
40
|
-
for (let i = 0; i < clean.length; i++) {
|
|
41
|
-
const v = B64_REV[clean.charCodeAt(i)] ?? -1
|
|
42
|
-
if (v < 0) throw new Error("cells(): payload is not base64")
|
|
43
|
-
acc = (acc << 6) | v
|
|
44
|
-
bits += 6
|
|
45
|
-
if (bits >= 8) { bits -= 8; out[w++] = (acc >> bits) & 0xff }
|
|
46
|
-
}
|
|
47
|
-
return out
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Encode a grid into a `cells()` payload (the editor / tools side of the codec). */
|
|
51
|
-
export const encodeCells = (cols: number, rows: number, data: ArrayLike<number>): string => {
|
|
52
|
-
if (data.length !== cols * rows) throw new Error(`encodeCells: expected ${cols * rows} values, got ${data.length}`)
|
|
53
|
-
const ints: number[] = [ cols, rows ]
|
|
54
|
-
for (let i = 0; i < data.length;) {
|
|
55
|
-
const v = data[i]!
|
|
56
|
-
let run = 1
|
|
57
|
-
while (i + run < data.length && data[i + run] === v) run++
|
|
58
|
-
ints.push(run, v)
|
|
59
|
-
i += run
|
|
60
|
-
}
|
|
61
|
-
const bytes = new Uint8Array(ints.length * 4)
|
|
62
|
-
const dv = new DataView(bytes.buffer)
|
|
63
|
-
ints.forEach((n, i) => dv.setInt32(i * 4, n, true))
|
|
64
|
-
return b64encode(bytes)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Decode a `cells('…')` payload into the grid a tilemap block consumes. Scene files call this via
|
|
69
|
-
* the injected global; the value is plain data, so hand-written code may pass any
|
|
70
|
-
* `{ cols, rows, data }` of its own instead (procedural maps).
|
|
71
|
-
*/
|
|
72
|
-
export const cells = (payload: string): CellsData => {
|
|
73
|
-
const bytes = b64decode(payload)
|
|
74
|
-
if (bytes.length < 16 || bytes.length % 4 !== 0) throw new Error("cells(): malformed payload")
|
|
75
|
-
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
|
|
76
|
-
const cols = dv.getInt32(0, true)
|
|
77
|
-
const rows = dv.getInt32(4, true)
|
|
78
|
-
const count = bytes.length / 4 - 2
|
|
79
|
-
if (cols <= 0 || rows <= 0 || count % 2 !== 0) throw new Error("cells(): malformed payload")
|
|
80
|
-
const data = new Int32Array(cols * rows)
|
|
81
|
-
let w = 0
|
|
82
|
-
for (let i = 0; i < count; i += 2) {
|
|
83
|
-
const run = dv.getInt32(8 + i * 4, true)
|
|
84
|
-
const value = dv.getInt32(12 + i * 4, true)
|
|
85
|
-
if (run <= 0 || w + run > data.length) throw new Error("cells(): payload does not match its dimensions")
|
|
86
|
-
data.fill(value, w, w + run)
|
|
87
|
-
w += run
|
|
88
|
-
}
|
|
89
|
-
if (w !== data.length) throw new Error("cells(): payload does not match its dimensions")
|
|
90
|
-
return { cols, rows, data }
|
|
91
|
-
}
|