@three-flatland/normals 0.1.0-alpha.1 → 0.1.0-alpha.3

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.d.ts CHANGED
@@ -1,320 +1,5 @@
1
- import { Loader, Texture, LoadingManager } from 'three';
2
- import { BakedAssetLoaderOptions } from '@three-flatland/bake';
3
-
4
- /**
5
- * Normal source descriptor the shared shape used across three
6
- * surfaces:
7
- *
8
- * 1. Library API `bakeNormalMap(pixels, w, h, descriptor)`
9
- * 2. CLI `flatland-bake normal --descriptor <file>`
10
- * 3. Loader API `LDtkLoader.load(url, { normals: descriptor })`
11
- *
12
- * The descriptor carries enough information for the baker to emit a
13
- * 1:1 co-registered normal map: where the regions live, how each
14
- * region tilts, and what per-texel bump source to use.
15
- *
16
- * All types are pure and browser-safe — consumed identically by node
17
- * bakers and browser runtime loaders.
18
- */
19
- /**
20
- * Screen-relative direction the surface normal's XY component points.
21
- *
22
- * Mental model: imagine the baked normal as an arrow. `NormalDirection`
23
- * is the screen-space direction that arrow tilts toward — equivalently,
24
- * the direction the tile's visible face is "pointing." For a wall at
25
- * the top of the map (visible face toward the camera below), that's
26
- * `'south'` / `'down'`.
27
- *
28
- * Cardinal and compass aliases are equivalent; the canonical form in
29
- * the codebase is NSEW. Numbers are interpreted as radians in standard
30
- * math convention (0 = +X / right, CCW positive).
31
- */
32
- type NormalDirection = 'flat' | 'up' | 'north' | 'down' | 'south' | 'left' | 'west' | 'right' | 'east' | 'up-left' | 'north-west' | 'up-right' | 'north-east' | 'down-left' | 'south-west' | 'down-right' | 'south-east' | number;
33
- /**
34
- * Resolve a `NormalDirection` to an angle in radians, or `null` when
35
- * the direction is `'flat'` (no tilt).
36
- *
37
- * Convention: 0 = +X / right, π/2 = +Y / up, π = -X / left,
38
- * -π/2 = -Y / down. Matches `Math.atan2` output.
39
- */
40
- declare function directionToAngle(direction: NormalDirection | undefined): number | null;
41
- /**
42
- * Per-texel bump source inside a region. Each non-'none' mode runs a
43
- * central-difference gradient on the named channel; the result is
44
- * treated as a height field where high values are raised and low
45
- * values are sunken. Negative `strength` inverts (dark = raised).
46
- *
47
- * - `'alpha'` — gradient on the source alpha channel (default).
48
- * Preserves sprite silhouette edges — the classic path for
49
- * transparent sprites. Solid opaque regions produce no bump.
50
- * - `'luminance'` — gradient on Rec. 709 luminance `(0.2126·R +
51
- * 0.7152·G + 0.0722·B)`. Brick faces read as raised; dark mortar
52
- * lines read as sunken grooves. Right mode for solid opaque
53
- * tilesets.
54
- * - `'red'` / `'green'` / `'blue'` — gradient on a single color
55
- * channel. Use when your art treats one channel as a height map
56
- * (e.g., packing height into the red channel of a data texture).
57
- * - `'none'` — flat fill at the region's tilt direction. No per-texel
58
- * variation; cheapest and useful for uniform surfaces.
59
- */
60
- type NormalBump = 'alpha' | 'luminance' | 'red' | 'green' | 'blue' | 'none';
61
- interface NormalRegion {
62
- x: number;
63
- y: number;
64
- w: number;
65
- h: number;
66
- /** Per-texel bump source. Default inherits from descriptor (`'alpha'`). */
67
- bump?: NormalBump;
68
- /** Base tilt direction. Default inherits from descriptor (`'flat'`). */
69
- direction?: NormalDirection;
70
- /**
71
- * Tilt angle in radians from flat. Ignored when `direction === 'flat'`.
72
- * Default inherits from descriptor, which defaults to `Math.PI / 4`.
73
- */
74
- pitch?: number;
75
- /** Gradient strength multiplier for this region. Default 1. */
76
- strength?: number;
77
- /**
78
- * World-space elevation of the region in [0, 1], where 0 = ground
79
- * plane (floor) and 1 = top-of-wall (cap). Baked into the normal
80
- * atlas's B channel and consumed by the light pass to compute
81
- * per-fragment light direction (`L.z = lightHeight − elevation`).
82
- *
83
- * Default inherits from the descriptor, which defaults to 0. Cap
84
- * regions typically set 1; tilted face regions typically set 0.5.
85
- */
86
- elevation?: number;
87
- }
88
- interface NormalSourceDescriptor {
89
- /** Reserved for future schema evolution. Currently always `1`. */
90
- version?: 1;
91
- /** Default bump source for regions that don't specify one. Default `'alpha'`. */
92
- bump?: NormalBump;
93
- /** Default tilt for regions that don't specify one. Default `'flat'`. */
94
- direction?: NormalDirection;
95
- /** Default tilt pitch in radians. Default `Math.PI / 4`. */
96
- pitch?: number;
97
- /** Default gradient strength. Default `1`. */
98
- strength?: number;
99
- /** Default elevation for regions that don't specify one. Default 0 (ground). */
100
- elevation?: number;
101
- /**
102
- * Regions of the source texture. When omitted, the whole texture is
103
- * treated as a single region inheriting all descriptor defaults.
104
- */
105
- regions?: NormalRegion[];
106
- }
107
- declare const DEFAULT_PITCH: number;
108
- declare const DEFAULT_STRENGTH = 1;
109
- declare const DEFAULT_BUMP: NormalBump;
110
- declare const DEFAULT_ELEVATION = 0;
111
- /**
112
- * Merge a region against its parent descriptor defaults. Returns a
113
- * fully-populated region suitable for direct consumption by the baker.
114
- */
115
- interface ResolvedNormalRegion {
116
- x: number;
117
- y: number;
118
- w: number;
119
- h: number;
120
- bump: NormalBump;
121
- /** Tilt angle in radians, or `null` when the region is flat. */
122
- angle: number | null;
123
- pitch: number;
124
- strength: number;
125
- /** World-space elevation in [0, 1]. Written to the output B channel. */
126
- elevation: number;
127
- }
128
- declare function resolveRegion(region: NormalRegion, descriptor?: NormalSourceDescriptor): ResolvedNormalRegion;
129
-
130
- /**
131
- * @deprecated — legacy flat-texture bake options. Use
132
- * `bakeNormalMap(pixels, w, h, descriptor)` for region-aware control.
133
- */
134
- interface BakeOptions {
135
- /** Scales the alpha gradient before normalization. Default 1. */
136
- strength?: number;
137
- }
138
- /**
139
- * Produce a tangent-space normal map from source pixels + a descriptor.
140
- *
141
- * Cross-platform: browser + node. No filesystem, no pngjs. Used by
142
- * the CLI baker (wrapped with file I/O) and the runtime loader
143
- * (called directly on decoded image pixels).
144
- *
145
- * Per texel in each region:
146
- * 1. `bump: 'alpha'` — central-difference gradient on the source
147
- * alpha channel, clamped to the region bounds so adjacent regions
148
- * (e.g. atlas cells) can't bleed into each other.
149
- * 2. Compose with a tilt rotation that takes `+Z` to the region's
150
- * `direction` at `pitch` radians.
151
- * 3. Normalize and encode to `rgb = normal * 0.5 + 0.5`. Alpha is
152
- * copied from the source so the baked map carries its own
153
- * silhouette.
154
- *
155
- * Texels outside every region receive the flat normal `(0, 0, 1)`
156
- * with the source's alpha — safe default for sparse descriptors.
157
- *
158
- * @param pixels RGBA pixel buffer, row-major, 4 bytes per pixel.
159
- * @param width Pixel width.
160
- * @param height Pixel height.
161
- * @param descriptor Region / tilt / bump control. Defaults to a single
162
- * whole-texture flat region with alpha-derived bump.
163
- * @returns RGBA pixel buffer containing the encoded normal map.
164
- */
165
- declare function bakeNormalMap(pixels: Uint8Array, width: number, height: number, descriptor?: NormalSourceDescriptor): Uint8Array;
166
- /**
167
- * Legacy back-compat wrapper. Use `bakeNormalMap(pixels, w, h, {
168
- * strength })` for equivalent behavior in new code.
169
- *
170
- * @deprecated
171
- */
172
- declare function bakeNormalMapFromPixels(pixels: Uint8Array, width: number, height: number, options?: BakeOptions): Uint8Array;
173
- /**
174
- * Derive the conventional `.normal.png` sibling URL for a sprite PNG.
175
- *
176
- * Runtime loaders call this to try the baked output before falling
177
- * back to the runtime TSL path.
178
- *
179
- * Pure string rewrite — browser-safe, no filesystem.
180
- */
181
- declare function bakedNormalURL(spriteURL: string): string;
182
-
183
- /**
184
- * Options accepted by `NormalMapLoader.load()`. Inherits `forceRuntime`
185
- * from the shared {@link BakedAssetLoaderOptions} so every baked-asset
186
- * loader in the codebase advertises the same option shape.
187
- */
188
- interface NormalMapLoaderStaticOptions extends BakedAssetLoaderOptions {
189
- /**
190
- * When provided, missing sidecars trigger an in-memory bake via
191
- * `resolveNormalMap`. Without a descriptor, `NormalMapLoader.load()` can
192
- * only probe the baked sibling and returns `null` on miss (legacy
193
- * behavior, preserved for backward compat).
194
- */
195
- descriptor?: NormalSourceDescriptor;
196
- }
197
- /**
198
- * Result of loading a sprite's normal data.
199
- *
200
- * Either a baked normal texture (fast path) or `null`, signalling the caller
201
- * to use the runtime TSL `normalFromSprite` helper against the sprite's own
202
- * alpha channel.
203
- */
204
- type NormalMapResult = Texture | null;
205
- /**
206
- * Loader for per-sprite normal maps following the canonical three-flatland
207
- * "try baked → fall back to runtime" pattern documented in
208
- * `planning/bake/loader-pattern.md`.
209
- *
210
- * Given a sprite PNG URL, this loader fetches the sibling `.normal.png` that
211
- * `flatland-bake normal` produces. If it is absent, the loader resolves to
212
- * `null` so the lit material can switch to its TSL runtime fallback.
213
- *
214
- * Only the baked path uses the network — the runtime branch is a shader
215
- * concern and lives in `@three-flatland/nodes/lighting/normalFromSprite`.
216
- *
217
- * @example
218
- * ```ts
219
- * // Vanilla static API
220
- * const normalTex = await NormalMapLoader.load('/sprites/knight.png')
221
- * if (normalTex) material.normalMap = normalTex
222
- * else material.useRuntimeNormals = true
223
- *
224
- * // Skip the baked probe — go straight to runtime (or null without descriptor)
225
- * const tex = await NormalMapLoader.load(url, { forceRuntime: true })
226
- *
227
- * // R3F useLoader
228
- * const tex = useLoader(NormalMapLoader, '/sprites/knight.png')
229
- * ```
230
- */
231
- declare class NormalMapLoader extends Loader<NormalMapResult> {
232
- /**
233
- * Generate this asset's normal map in the browser on every load
234
- * instead of loading a pre-baked `.normal.png` sidecar. With a
235
- * `descriptor`, the in-memory bake runs on every load. Without a
236
- * descriptor, the loader resolves directly to `null` and the caller
237
- * uses the TSL runtime fallback. Suppresses the "no baked sibling"
238
- * warn either way.
239
- *
240
- * Use when runtime is the right home for the bake. Not a dev-iteration
241
- * knob — the default path (probe → bake on miss + warn) already
242
- * handles iteration. See {@link BakedAssetLoaderOptions.forceRuntime}.
243
- */
244
- forceRuntime: boolean;
245
- /**
246
- * When set, missing sidecars trigger an in-memory bake via
247
- * `resolveNormalMap`. Without a descriptor, the loader can only probe
248
- * the baked sibling and returns `null` on miss (legacy behavior).
249
- *
250
- * Set via the `useLoader` callback:
251
- * ```ts
252
- * useLoader(NormalMapLoader, url, undefined, (l) => { l.descriptor = desc })
253
- * ```
254
- */
255
- descriptor: NormalSourceDescriptor | undefined;
256
- constructor(manager?: LoadingManager);
257
- load(url: string, onLoad?: (data: NormalMapResult) => void, onProgress?: (event: ProgressEvent) => void, onError?: (err: unknown) => void): NormalMapResult;
258
- loadAsync(url: string): Promise<NormalMapResult>;
259
- /**
260
- * Vanilla cache: keyed by `(url, forceRuntime, descriptor hash)` so
261
- * two callers passing *different* descriptors for the same URL get
262
- * distinct results instead of colliding on the first one's bake.
263
- *
264
- * Instance API (R3F `useLoader`) bypasses this map — R3F has its own
265
- * suspense cache and we don't want double-caching to fight the lifecycle.
266
- */
267
- private static _cache;
268
- static load(url: string, options?: NormalMapLoaderStaticOptions): Promise<NormalMapResult>;
269
- static clearCache(): void;
270
- private static _loadImpl;
271
- private static _tryLoadBaked;
272
- }
273
-
274
- interface ResolveNormalMapOptions {
275
- /**
276
- * Generate this asset's normal map in the browser on every load
277
- * instead of loading a pre-baked `.normal.png` sidecar. Fetches the
278
- * source image, decodes its pixels, and runs `bakeNormalMap` in
279
- * memory each time — no HEAD probe, no devtime "no baked sibling"
280
- * warning.
281
- *
282
- * The returned data is always a valid normal map: this flag chooses
283
- * *where* the bake happens (browser vs CI), not whether you get one.
284
- *
285
- * Use when runtime is the right home for the bake — procedurally
286
- * varied content, throwaway prototypes, asset bundles where shipping
287
- * the sidecar isn't worth the bytes. Not a dev-iteration knob: the
288
- * default path (probe → bake on miss + warn) already handles that.
289
- *
290
- * Mirrors `SlugFontLoader.forceRuntime` and every other baked-asset
291
- * loader in the codebase.
292
- */
293
- forceRuntime?: boolean;
294
- /**
295
- * Force the returned texture's `flipY` to this value. Pass the
296
- * diffuse texture's `flipY` so the normal map samples 1:1 with the
297
- * diffuse on the GPU regardless of whether the normal came from the
298
- * image-loader path (default true) or the in-memory `DataTexture`
299
- * path (default false).
300
- */
301
- flipY?: boolean;
302
- }
303
- /**
304
- * Resolve the normal map for an asset URL + descriptor.
305
- *
306
- * 1. Hash the descriptor.
307
- * 2. Probe the baked sibling `<source>.normal.png` (unless
308
- * `forceRuntime`). If present and its stamped hash matches, load
309
- * and return that texture directly.
310
- * 3. On miss (or stale hash), fetch the source, decode its pixels,
311
- * lazy-import + run `bakeNormalMap` in memory, and wrap the result
312
- * in a `DataTexture`. Runtime bake is the always-on fallback when
313
- * normals were requested.
314
- *
315
- * Browser-only — uses `fetch`, `createImageBitmap`, `OffscreenCanvas`.
316
- * Safe to call from any Three.js loader runtime.
317
- */
318
- declare function resolveNormalMap(sourceURL: string, descriptor: NormalSourceDescriptor, options?: ResolveNormalMapOptions): Promise<Texture>;
319
-
320
- export { type BakeOptions, DEFAULT_BUMP, DEFAULT_ELEVATION, DEFAULT_PITCH, DEFAULT_STRENGTH, type NormalBump, type NormalDirection, NormalMapLoader, type NormalMapResult, type NormalRegion, type NormalSourceDescriptor, type ResolveNormalMapOptions, type ResolvedNormalRegion, bakeNormalMap, bakeNormalMapFromPixels, bakedNormalURL, directionToAngle, resolveNormalMap, resolveRegion };
1
+ import { DEFAULT_BUMP, DEFAULT_ELEVATION, DEFAULT_PITCH, DEFAULT_STRENGTH, NormalBump, NormalDirection, NormalRegion, NormalSourceDescriptor, ResolvedNormalRegion, directionToAngle, resolveRegion } from "./descriptor.js";
2
+ import { NormalMapLoader, NormalMapResult } from "./NormalMapLoader.js";
3
+ import { BakeOptions, bakeNormalMap, bakeNormalMapFromPixels, bakedNormalURL } from "./bake.js";
4
+ import { ResolveNormalMapOptions, resolveNormalMap } from "./resolveNormalMap.js";
5
+ export { type BakeOptions, DEFAULT_BUMP, DEFAULT_ELEVATION, DEFAULT_PITCH, DEFAULT_STRENGTH, type NormalBump, type NormalDirection, NormalMapLoader, type NormalMapResult, type NormalRegion, type NormalSourceDescriptor, type ResolveNormalMapOptions, type ResolvedNormalRegion, bakeNormalMap, bakeNormalMapFromPixels, bakedNormalURL, directionToAngle, resolveNormalMap, resolveRegion };
package/dist/index.js CHANGED
@@ -1,33 +1,5 @@
1
- import {
2
- bakeNormalMap,
3
- bakeNormalMapFromPixels,
4
- bakedNormalURL
5
- } from "./bake.js";
6
- import {
7
- NormalMapLoader
8
- } from "./NormalMapLoader.js";
9
- import {
10
- resolveNormalMap
11
- } from "./resolveNormalMap.js";
12
- import {
13
- directionToAngle,
14
- resolveRegion,
15
- DEFAULT_PITCH,
16
- DEFAULT_STRENGTH,
17
- DEFAULT_BUMP,
18
- DEFAULT_ELEVATION
19
- } from "./descriptor.js";
20
- export {
21
- DEFAULT_BUMP,
22
- DEFAULT_ELEVATION,
23
- DEFAULT_PITCH,
24
- DEFAULT_STRENGTH,
25
- NormalMapLoader,
26
- bakeNormalMap,
27
- bakeNormalMapFromPixels,
28
- bakedNormalURL,
29
- directionToAngle,
30
- resolveNormalMap,
31
- resolveRegion
32
- };
33
- //# sourceMappingURL=index.js.map
1
+ import { DEFAULT_BUMP, DEFAULT_ELEVATION, DEFAULT_PITCH, DEFAULT_STRENGTH, directionToAngle, resolveRegion } from "./descriptor.js";
2
+ import { bakeNormalMap, bakeNormalMapFromPixels, bakedNormalURL } from "./bake.js";
3
+ import { NormalMapLoader } from "./NormalMapLoader.js";
4
+ import { resolveNormalMap } from "./resolveNormalMap.js";
5
+ export { DEFAULT_BUMP, DEFAULT_ELEVATION, DEFAULT_PITCH, DEFAULT_STRENGTH, NormalMapLoader, bakeNormalMap, bakeNormalMapFromPixels, bakedNormalURL, directionToAngle, resolveNormalMap, resolveRegion };
package/dist/node.d.ts CHANGED
@@ -1,21 +1,7 @@
1
- import { NormalSourceDescriptor, BakeOptions } from './index.js';
2
- export { DEFAULT_BUMP, DEFAULT_ELEVATION, DEFAULT_PITCH, DEFAULT_STRENGTH, NormalBump, NormalDirection, NormalMapLoader, NormalMapResult, NormalRegion, ResolveNormalMapOptions, ResolvedNormalRegion, bakeNormalMap, bakeNormalMapFromPixels, bakedNormalURL, directionToAngle, resolveNormalMap, resolveRegion } from './index.js';
3
- import 'three';
4
- import '@three-flatland/bake';
5
-
6
- /**
7
- * Bake a normal map from a PNG file on disk.
8
- *
9
- * Node-only wrapper around `bakeNormalMap` — handles file I/O and
10
- * stamps the output PNG with a `tEXt` chunk containing the descriptor
11
- * hash, so `probeBakedSibling` can invalidate stale outputs.
12
- *
13
- * Second argument accepts either:
14
- * - A full `NormalSourceDescriptor` (region-aware).
15
- * - A legacy `BakeOptions` (`{ strength }`) — for back-compat with
16
- * existing callers; promoted to a zero-region descriptor.
17
- * - A path to a descriptor JSON file.
18
- */
19
- declare function bakeNormalMapFile(inputPath: string, descriptorOrOptions?: NormalSourceDescriptor | BakeOptions | string, outputPath?: string): string;
20
-
21
- export { BakeOptions, NormalSourceDescriptor, bakeNormalMapFile };
1
+ import { DEFAULT_BUMP, DEFAULT_ELEVATION, DEFAULT_PITCH, DEFAULT_STRENGTH, NormalBump, NormalDirection, NormalRegion, NormalSourceDescriptor, ResolvedNormalRegion, directionToAngle, resolveRegion } from "./descriptor.js";
2
+ import { NormalMapLoader, NormalMapResult } from "./NormalMapLoader.js";
3
+ import { BakeOptions, bakeNormalMap, bakeNormalMapFromPixels, bakedNormalURL } from "./bake.js";
4
+ import { bakeNormalMapFile } from "./bake.node.js";
5
+ import { ResolveNormalMapOptions, resolveNormalMap } from "./resolveNormalMap.js";
6
+ import "./index.js";
7
+ export { type BakeOptions, DEFAULT_BUMP, DEFAULT_ELEVATION, DEFAULT_PITCH, DEFAULT_STRENGTH, type NormalBump, type NormalDirection, NormalMapLoader, type NormalMapResult, type NormalRegion, type NormalSourceDescriptor, type ResolveNormalMapOptions, type ResolvedNormalRegion, bakeNormalMap, bakeNormalMapFile, bakeNormalMapFromPixels, bakedNormalURL, directionToAngle, resolveNormalMap, resolveRegion };
package/dist/node.js CHANGED
@@ -1,6 +1,6 @@
1
- export * from "./index.js";
1
+ import { DEFAULT_BUMP, DEFAULT_ELEVATION, DEFAULT_PITCH, DEFAULT_STRENGTH, directionToAngle, resolveRegion } from "./descriptor.js";
2
+ import { bakeNormalMap, bakeNormalMapFromPixels, bakedNormalURL } from "./bake.js";
3
+ import { NormalMapLoader } from "./NormalMapLoader.js";
2
4
  import { bakeNormalMapFile } from "./bake.node.js";
3
- export {
4
- bakeNormalMapFile
5
- };
6
- //# sourceMappingURL=node.js.map
5
+ import { resolveNormalMap } from "./resolveNormalMap.js";
6
+ export { DEFAULT_BUMP, DEFAULT_ELEVATION, DEFAULT_PITCH, DEFAULT_STRENGTH, NormalMapLoader, bakeNormalMap, bakeNormalMapFile, bakeNormalMapFromPixels, bakedNormalURL, directionToAngle, resolveNormalMap, resolveRegion };
@@ -0,0 +1,51 @@
1
+ import { NormalSourceDescriptor } from "./descriptor.js";
2
+ import { Texture } from "three";
3
+ //#region src/resolveNormalMap.d.ts
4
+ interface ResolveNormalMapOptions {
5
+ /**
6
+ * Generate this asset's normal map in the browser on every load
7
+ * instead of loading a pre-baked `.normal.png` sidecar. Fetches the
8
+ * source image, decodes its pixels, and runs `bakeNormalMap` in
9
+ * memory each time — no HEAD probe, no devtime "no baked sibling"
10
+ * warning.
11
+ *
12
+ * The returned data is always a valid normal map: this flag chooses
13
+ * *where* the bake happens (browser vs CI), not whether you get one.
14
+ *
15
+ * Use when runtime is the right home for the bake — procedurally
16
+ * varied content, throwaway prototypes, asset bundles where shipping
17
+ * the sidecar isn't worth the bytes. Not a dev-iteration knob: the
18
+ * default path (probe → bake on miss + warn) already handles that.
19
+ *
20
+ * Mirrors `SlugFontLoader.forceRuntime` and every other baked-asset
21
+ * loader in the codebase.
22
+ */
23
+ forceRuntime?: boolean;
24
+ /**
25
+ * Force the returned texture's `flipY` to this value. Pass the
26
+ * diffuse texture's `flipY` so the normal map samples 1:1 with the
27
+ * diffuse on the GPU regardless of whether the normal came from the
28
+ * image-loader path (default true) or the in-memory `DataTexture`
29
+ * path (default false).
30
+ */
31
+ flipY?: boolean;
32
+ }
33
+ /**
34
+ * Resolve the normal map for an asset URL + descriptor.
35
+ *
36
+ * 1. Hash the descriptor.
37
+ * 2. Probe the baked sibling `<source>.normal.png` (unless
38
+ * `forceRuntime`). If present and its stamped hash matches, load
39
+ * and return that texture directly.
40
+ * 3. On miss (or stale hash), fetch the source, decode its pixels,
41
+ * lazy-import + run `bakeNormalMap` in memory, and wrap the result
42
+ * in a `DataTexture`. Runtime bake is the always-on fallback when
43
+ * normals were requested.
44
+ *
45
+ * Browser-only — uses `fetch`, `createImageBitmap`, `OffscreenCanvas`.
46
+ * Safe to call from any Three.js loader runtime.
47
+ */
48
+ declare function resolveNormalMap(sourceURL: string, descriptor: NormalSourceDescriptor, options?: ResolveNormalMapOptions): Promise<Texture>;
49
+ //#endregion
50
+ export { ResolveNormalMapOptions, resolveNormalMap };
51
+ //# sourceMappingURL=resolveNormalMap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveNormalMap.d.ts","names":[],"sources":["../src/resolveNormalMap.ts"],"mappings":";;;UAIiB;;;;;;;;;;;;;;;;;;;EAmBf;;;;;;;;EAQA;;;;;;;;;;;;;;;;;iBAkBoB,iBACpB,mBACA,YAAY,wBACZ,UAAS,0BACR,QAAQ"}
@@ -1,97 +1,76 @@
1
- import {
2
- DataTexture,
3
- RGBAFormat,
4
- UnsignedByteType,
5
- TextureLoader as ThreeTextureLoader
6
- } from "three";
7
- import {
8
- bakedSiblingURL,
9
- devtimeWarn,
10
- hashDescriptor,
11
- probeBakedSibling
12
- } from "@three-flatland/bake";
1
+ import { DataTexture, RGBAFormat, TextureLoader, UnsignedByteType } from "three";
2
+ import { bakedSiblingURL, devtimeWarn, hashDescriptor, probeBakedSibling } from "@three-flatland/bake";
3
+ //#region src/resolveNormalMap.ts
4
+ /**
5
+ * Resolve the normal map for an asset URL + descriptor.
6
+ *
7
+ * 1. Hash the descriptor.
8
+ * 2. Probe the baked sibling `<source>.normal.png` (unless
9
+ * `forceRuntime`). If present and its stamped hash matches, load
10
+ * and return that texture directly.
11
+ * 3. On miss (or stale hash), fetch the source, decode its pixels,
12
+ * lazy-import + run `bakeNormalMap` in memory, and wrap the result
13
+ * in a `DataTexture`. Runtime bake is the always-on fallback when
14
+ * normals were requested.
15
+ *
16
+ * Browser-only — uses `fetch`, `createImageBitmap`, `OffscreenCanvas`.
17
+ * Safe to call from any Three.js loader runtime.
18
+ */
13
19
  async function resolveNormalMap(sourceURL, descriptor, options = {}) {
14
- const hash = hashDescriptor(descriptor);
15
- if (!options.forceRuntime) {
16
- const bakedURL = bakedSiblingURL(sourceURL, ".normal.png");
17
- const probe = await probeBakedSibling(bakedURL, { expectedHash: hash });
18
- if (probe.ok && probe.hashMatches) {
19
- const tex2 = await loadTextureURL(bakedURL);
20
- if (options.flipY !== void 0) tex2.flipY = options.flipY;
21
- tex2.needsUpdate = true;
22
- return tex2;
23
- }
24
- if (probe.ok && !probe.hashMatches) {
25
- devtimeWarn(
26
- "normal",
27
- sourceURL,
28
- `${bakedURL} exists but its descriptor hash is stale \u2014 re-baking in memory. Run \`npx flatland-bake normal ${sourceURL} --descriptor <descriptor>.json\` to refresh.`
29
- );
30
- } else {
31
- devtimeWarn(
32
- "normal",
33
- sourceURL,
34
- `No baked sibling at ${bakedURL} \u2014 baking in memory. Run \`npx flatland-bake normal\` for production.`
35
- );
36
- }
37
- }
38
- const tex = await bakeInMemory(sourceURL, descriptor);
39
- if (options.flipY !== void 0) tex.flipY = options.flipY;
40
- tex.needsUpdate = true;
41
- return tex;
20
+ const hash = hashDescriptor(descriptor);
21
+ if (!options.forceRuntime) {
22
+ const bakedURL = bakedSiblingURL(sourceURL, ".normal.png");
23
+ const probe = await probeBakedSibling(bakedURL, { expectedHash: hash });
24
+ if (probe.ok && probe.hashMatches) {
25
+ const tex = await loadTextureURL(bakedURL);
26
+ if (options.flipY !== void 0) tex.flipY = options.flipY;
27
+ tex.needsUpdate = true;
28
+ return tex;
29
+ }
30
+ if (probe.ok && !probe.hashMatches) devtimeWarn("normal", sourceURL, `${bakedURL} exists but its descriptor hash is stale — re-baking in memory. Run \`npx flatland-bake normal ${sourceURL} --descriptor <descriptor>.json\` to refresh.`);
31
+ else devtimeWarn("normal", sourceURL, `No baked sibling at ${bakedURL} — baking in memory. Run \`npx flatland-bake normal\` for production.`);
32
+ }
33
+ const tex = await bakeInMemory(sourceURL, descriptor);
34
+ if (options.flipY !== void 0) tex.flipY = options.flipY;
35
+ tex.needsUpdate = true;
36
+ return tex;
42
37
  }
43
38
  async function loadTextureURL(url) {
44
- return new Promise((resolve, reject) => {
45
- const loader = new ThreeTextureLoader();
46
- loader.load(
47
- url,
48
- (tex) => resolve(tex),
49
- void 0,
50
- (err) => reject(err)
51
- );
52
- });
39
+ return new Promise((resolve, reject) => {
40
+ new TextureLoader().load(url, (tex) => resolve(tex), void 0, (err) => reject(err));
41
+ });
53
42
  }
54
43
  async function bakeInMemory(sourceURL, descriptor) {
55
- const res = await fetch(sourceURL);
56
- if (!res.ok) {
57
- throw new Error(`resolveNormalMap: failed to fetch ${sourceURL} (${res.status})`);
58
- }
59
- const blob = await res.blob();
60
- const bitmap = await createImageBitmap(blob);
61
- const { width, height } = bitmap;
62
- const pixels = imageBitmapToRGBA(bitmap, width, height);
63
- bitmap.close();
64
- const { bakeNormalMap } = await import("./bake.js");
65
- const normalPixels = bakeNormalMap(pixels, width, height, descriptor);
66
- const texture = new DataTexture(
67
- normalPixels,
68
- width,
69
- height,
70
- RGBAFormat,
71
- UnsignedByteType
72
- );
73
- texture.needsUpdate = true;
74
- return texture;
44
+ const res = await fetch(sourceURL);
45
+ if (!res.ok) throw new Error(`resolveNormalMap: failed to fetch ${sourceURL} (${res.status})`);
46
+ const blob = await res.blob();
47
+ const bitmap = await createImageBitmap(blob);
48
+ const { width, height } = bitmap;
49
+ const pixels = imageBitmapToRGBA(bitmap, width, height);
50
+ bitmap.close();
51
+ const { bakeNormalMap } = await import("./bake.js");
52
+ const texture = new DataTexture(bakeNormalMap(pixels, width, height, descriptor), width, height, RGBAFormat, UnsignedByteType);
53
+ texture.needsUpdate = true;
54
+ return texture;
75
55
  }
76
56
  function imageBitmapToRGBA(bitmap, width, height) {
77
- if (typeof OffscreenCanvas !== "undefined") {
78
- const canvas2 = new OffscreenCanvas(width, height);
79
- const ctx2 = canvas2.getContext("2d");
80
- if (!ctx2) throw new Error("resolveNormalMap: OffscreenCanvas 2D context unavailable");
81
- ctx2.drawImage(bitmap, 0, 0);
82
- const data2 = ctx2.getImageData(0, 0, width, height);
83
- return new Uint8Array(data2.data.buffer, data2.data.byteOffset, data2.data.byteLength);
84
- }
85
- const canvas = document.createElement("canvas");
86
- canvas.width = width;
87
- canvas.height = height;
88
- const ctx = canvas.getContext("2d");
89
- if (!ctx) throw new Error("resolveNormalMap: Canvas 2D context unavailable");
90
- ctx.drawImage(bitmap, 0, 0);
91
- const data = ctx.getImageData(0, 0, width, height);
92
- return new Uint8Array(data.data.buffer, data.data.byteOffset, data.data.byteLength);
57
+ if (typeof OffscreenCanvas !== "undefined") {
58
+ const ctx = new OffscreenCanvas(width, height).getContext("2d");
59
+ if (!ctx) throw new Error("resolveNormalMap: OffscreenCanvas 2D context unavailable");
60
+ ctx.drawImage(bitmap, 0, 0);
61
+ const data = ctx.getImageData(0, 0, width, height);
62
+ return new Uint8Array(data.data.buffer, data.data.byteOffset, data.data.byteLength);
63
+ }
64
+ const canvas = document.createElement("canvas");
65
+ canvas.width = width;
66
+ canvas.height = height;
67
+ const ctx = canvas.getContext("2d");
68
+ if (!ctx) throw new Error("resolveNormalMap: Canvas 2D context unavailable");
69
+ ctx.drawImage(bitmap, 0, 0);
70
+ const data = ctx.getImageData(0, 0, width, height);
71
+ return new Uint8Array(data.data.buffer, data.data.byteOffset, data.data.byteLength);
93
72
  }
94
- export {
95
- resolveNormalMap
96
- };
73
+ //#endregion
74
+ export { resolveNormalMap };
75
+
97
76
  //# sourceMappingURL=resolveNormalMap.js.map