@adatechnology/game-shapes 0.0.0-stage → 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anderson Fernandes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,148 @@
1
- # Temporary Holding Version
1
+ # @adatechnology/game-shapes
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The kit's single 3D generator. A shape is a JSON document (primitives, extrusions, lathes, a cheap boolean, transforms, groups, named parts,
4
+ materials by id, LOD rules, instancing hints); `generate` turns it into typed-array buffers per material, ready for one draw call each. No
5
+ renderer, no dependency: it runs the same in a page, a Web Worker or Node, and the same input always gives the same bytes.
6
+
7
+ Every generator is a line-by-line port of three.js r160's (`BoxGeometry`, `CylinderGeometry`, `ConeGeometry`, `SphereGeometry`,
8
+ `PlaneGeometry`, `CircleGeometry`, `RingGeometry`, `LatheGeometry`, `TorusGeometry`, `ExtrudeGeometry` for straight outlines), and baking uses
9
+ three's matrix arithmetic in three's order, so the arrays are **bit-identical** to what three builds. That is what lets the kit's packages move
10
+ their builders here without any scene golden changing. `@adatechnology/game-shapes-three` turns the buffers into meshes.
11
+
12
+ Design and rules: `specs/001-migracao-entregas-dos-times/design/T9-game-shapes-e-desempenho.md`.
13
+
14
+ ## Install
15
+
16
+ ```sh
17
+ pnpm add @adatechnology/game-shapes
18
+ ```
19
+
20
+ Browser without a bundler (the global is `GameShapes`):
21
+
22
+ ```html
23
+ <script src="https://unpkg.com/@adatechnology/game-shapes/dist/index.global.js"></script>
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ```ts
29
+ import { generate, type ShapeSchema } from '@adatechnology/game-shapes'
30
+
31
+ const stool: ShapeSchema = {
32
+ version: 1,
33
+ name: 'stool',
34
+ materials: { wood: { color: 0x8a643c, roughness: 0.8 }, metal: { roughness: 0.35, metalness: 0.7 } },
35
+ root: {
36
+ kind: 'group',
37
+ children: [
38
+ { kind: 'cylinder', name: 'seat', material: 'wood', color: 0x8a643c, radiusTop: 0.18, radiusBottom: 0.18, height: 0.04, radialSegments: 16, position: [0, 0.46, 0] },
39
+ // the same leg three times: an instancing hint
40
+ { kind: 'cylinder', material: 'metal', color: 0x9a9ea2, radiusTop: 0.012, radiusBottom: 0.012, height: 0.44, radialSegments: 6, position: [0.12, 0.22, 0],
41
+ instances: [{ heading: 0 }, { heading: 2.094 }, { heading: 4.189 }] },
42
+ { kind: 'group', part: 'lid', position: [0, 0.48, 0], children: [{ kind: 'box', size: [0.1, 0.01, 0.1], material: 'wood' }] },
43
+ { kind: 'point', name: 'sit', position: [0, 0.5, 0], data: { seat: true } },
44
+ ],
45
+ },
46
+ lod: [{ distance: 12, detail: 0.5, minSize: 0.02 }],
47
+ cullDistance: 40,
48
+ }
49
+
50
+ const buffers = generate(stool, { seed: 1 })
51
+ buffers.levels[0].batches // [{ material: 'wood', part: null, positions, normals, uvs, colors, indices, ranges, bounds }, { material: 'wood', part: 'lid', ... }]
52
+ buffers.levels[0].instanced // [{ material: 'metal', geometry, matrices (16 per copy), count: 3 }]
53
+ buffers.parts // [{ id: 'lid', position: [0, 0.48, 0], matrix }]
54
+ buffers.points // [{ name: 'sit', part: null, position: [0, 0.5, 0], data: { seat: true } }]
55
+ buffers.stats // { vertices, triangles, batches, instances, primitives }
56
+ ```
57
+
58
+ One primitive on its own, not baked (exactly three's arrays, from a shared template cache):
59
+
60
+ ```ts
61
+ import { buildPrimitive } from '@adatechnology/game-shapes'
62
+ const cylinder = buildPrimitive({ kind: 'cylinder', radiusTop: 0.5, radiusBottom: 0.35, height: 1, radialSegments: 14 })
63
+ cylinder.type // 'CylinderGeometry'
64
+ ```
65
+
66
+ ## Nodes
67
+
68
+ Common fields: `name` (named range in `ranges`), `part` (a movable part: its transform is the pivot, relative to the root; its children are baked
69
+ in the part's space), `material` (id, inherited), `color` (`0xRRGGBB` or `'#rrggbb'`, inherited), `uv` (atlas cell `{ u0, v0, u1, v1 }`),
70
+ `position` / `rotation` / `rotationOrder` / `scale` (three's `Object3D` composition, order `XYZ` by default), or `heading` + `position` (a rotation
71
+ about +y built as a rotation matrix), or `matrix` (16 numbers, column-major), `instances` (draw the node once per entry), `minDetail` (LOD), `tags`.
72
+
73
+ | Kind | Parameters (defaults are three's) |
74
+ |---|---|
75
+ | `group` | `children` |
76
+ | `box` | `size: [w, h, d]`, `segments` |
77
+ | `cylinder` | `radiusTop`, `radiusBottom`, `height`, `radialSegments`, `heightSegments`, `openEnded`, `thetaStart`, `thetaLength` |
78
+ | `cone` | `radius`, `height`, `radialSegments`, `heightSegments`, `openEnded`, `thetaStart`, `thetaLength` |
79
+ | `sphere` | `radius`, `widthSegments`, `heightSegments`, `phiStart`, `phiLength`, `thetaStart`, `thetaLength` |
80
+ | `plane` | `width`, `height`, `widthSegments`, `heightSegments` |
81
+ | `circle`, `ring` | as three's `CircleGeometry`, `RingGeometry` |
82
+ | `lathe` | `points: [[x, y], ...]`, `segments`, `phiStart`, `phiLength` |
83
+ | `torus` | `radius`, `tube`, `radialSegments`, `tubularSegments`, `arc` |
84
+ | `extrude` | `shape: { outer, holes? }` (straight edges), `depth`, `steps`, `bevel: false \| { thickness, size, offset, segments }` |
85
+ | `difference` | `size`, `subtract: [{ min: [x, y], max: [x, y] }]`: a box minus prisms through it along z (walls with windows and doors) |
86
+ | `mesh` | `positions`, `normals?` (computed when missing), `uvs?`, `indices?` |
87
+ | `point` | `data`: an anchor reported in `points` |
88
+ | `custom` | `generator`, `params`: a function registered with `registerGenerator(name, (params, { random, seed, detail, path }) => node)` |
89
+
90
+ The JSON Schema ships as `@adatechnology/game-shapes/schema.json` (`SHAPE_SCHEMA`); `validateShape(doc)` checks the same rules without a schema
91
+ engine and returns one message per problem with its path; `generate` throws on an invalid shape (pass `validate: false` to skip the check).
92
+
93
+ ## Options
94
+
95
+ | Option | Meaning |
96
+ |---|---|
97
+ | `seed` | Seed of custom generators (each node gets its own stream from seed, generator and node path) |
98
+ | `colors` | `'linear'` (three's working space, default), `'srgb'` or `'none'` |
99
+ | `indexed` | `false` for non-indexed batches |
100
+ | `materials`, `fallbackMaterial` | Material ids the host draws; any other id becomes the fallback |
101
+ | `materialAlias` | `{ metal: 'plain' }`: ids that share a draw (as when two ids share one material instance) |
102
+ | `instancing: { minCount }` | Instance identical leaf primitives repeated at least `minCount` times (off by default) |
103
+ | `levels` | Only these level indices |
104
+ | `generators` | Custom generators for this call only |
105
+
106
+ ## Buffers
107
+
108
+ `ShapeBuffers = { key, levels: [{ distance, detail, batches, instanced }], parts, points, materials, cullDistance, stats }`. A batch is one
109
+ (part, material): `positions`, `normals`, `uvs` (`Float32Array`), `colors`, `indices` (`Uint16Array`, `Uint32Array` above 65 535 vertices),
110
+ `ranges` and `bounds`. Batches come in the order of first use, the static body first. Every array owns its buffer: `transferablesOf(buffers)`
111
+ lists them for `postMessage`; cached results are shared and read-only (`cloneBuffers` before deforming). `shapeKey(schema, options)` is the
112
+ 64-bit cache key; `mergeTransformed(pieces)` is three's `toNonIndexed` + `applyMatrix4` + join, for merging existing geometry.
113
+
114
+ ## Cache and workers
115
+
116
+ ```ts
117
+ import { createShapeCache, createShapePool } from '@adatechnology/game-shapes'
118
+
119
+ const cache = createShapeCache({ maxEntries: 512, maxBytes: 128 * 1024 * 1024 }) // LRU by shapeKey, counted in bytes of the arrays
120
+ const pool = createShapePool({ size: 4, spawn: () => new Worker(new URL('./shape-worker.js', import.meta.url), { type: 'module' }), cache })
121
+ const house = await pool.generateMany(rooms.map((schema) => ({ schema, options: { seed } })))
122
+
123
+ // shape-worker.js
124
+ import { installShapeWorker } from '@adatechnology/game-shapes/worker'
125
+ installShapeWorker(self, { generators: myGenerators })
126
+ ```
127
+
128
+ - A cache hit resolves at once; a key already in flight shares its promise; otherwise the job goes to the least busy worker and every array
129
+ comes back transferred (zero copy). The result is shared: read-only.
130
+ - `spawn` returns anything with `postMessage`, `onmessage` and `terminate` (a browser `Worker`, a `worker_threads` wrapper, a fake). Without it,
131
+ or with `size: 0`, the pool generates in the calling thread with the same cache and sharing.
132
+ - Custom generators are functions: register them in the worker script too. A job whose options carry `generators` runs in the calling
133
+ thread. A worker that crashes rejects its jobs and is replaced.
134
+ - A worker result is byte-equal to `generate` in the calling thread for the same key (tested with `worker_threads`).
135
+
136
+ ## LOD and instancing
137
+
138
+ A `lod` rule `{ distance, detail, minSize?, minSegments? }` regenerates the tree with round segment counts times `detail` (never below
139
+ `minSegments`, default 3), leaves out nodes with `minDetail > detail` and leaves smaller than `minSize`, and bakes the parts at rest into the
140
+ static body. Instance a node when the same geometry repeats tens of times or is placed at run time; merge when copies are few or coloured per
141
+ vertex.
142
+
143
+ ## Português
144
+
145
+ Gerador único de 3D do kit: forma em JSON vira buffers por material, puro, por semente, pronto para worker e cache. Os geradores são portes linha a
146
+ linha do three r160 e o resultado é igual bit a bit, por isso os pacotes migram sem mudar nenhum golden de cena. O desenho em three.js fica no
147
+ `@adatechnology/game-shapes-three`. O `createShapePool` gera em workers (`installShapeWorker` no script do worker) e o `createShapeCache` guarda
148
+ os resultados por chave.