@felbrasil/dreamy 0.1.1 → 0.2.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/README.md +21 -0
- package/dist/bake.d.ts +1 -0
- package/dist/bake.js +25 -0
- package/dist/bake.js.map +7 -0
- package/dist/chunks/{chunk-VZMBE5MV.js → chunk-5KZP6X65.js} +412 -119
- package/dist/chunks/chunk-5KZP6X65.js.map +7 -0
- package/dist/chunks/chunk-6TE546ZS.js +107 -0
- package/dist/chunks/chunk-6TE546ZS.js.map +7 -0
- package/dist/index.js +7 -37
- package/dist/index.js.map +3 -3
- package/dist/types/api/src/index.d.ts +1 -1
- package/dist/types/api/src/ports/viewport.d.ts +48 -0
- package/dist/types/dreamy/src/bake.d.ts +5 -0
- package/dist/types/studio/src/index.d.ts +2 -0
- package/dist/types/studio/src/sprite-baker.d.ts +26 -0
- package/dist/types/viewport/src/index.d.ts +1 -0
- package/dist/types/viewport/src/math/sprite-roles.d.ts +32 -0
- package/dist/types/viewport/src/scene/frame-camera.d.ts +6 -1
- package/dist/types/viewport/src/scene/render-pipeline.d.ts +11 -0
- package/dist/types/viewport/src/scene/resolve-frag.d.ts +3 -1
- package/dist/viewport.js +17 -3
- package/package.json +5 -1
- package/dist/chunks/chunk-VZMBE5MV.js.map +0 -7
|
@@ -28,4 +28,4 @@ export type { CreationApi, DispatchResult, PartVariantInfo, PremadeInfo } from "
|
|
|
28
28
|
export type { Clock, IdGenerator } from "./ports/basic-ports.js";
|
|
29
29
|
export type { GitProvider, GitSettings, LibraryEntry, LibrarySync, Persistence, PreferencesStore, SyncStatus, SyncSummary, ViewPreferences, } from "./ports/persistence.js";
|
|
30
30
|
export type { Evaluator, MeshRequest, MeshResult, SkinRequest, SkinResult } from "./ports/evaluator.js";
|
|
31
|
-
export type { BoneTool, DecalBrush, DecalCatalogEntry, FrameOptions, Selection, StampBrush, Tool, ViewSession, Viewport, ViewportCallbacks, } from "./ports/viewport.js";
|
|
31
|
+
export type { BoneTool, DecalBrush, DecalCatalogEntry, CameraView, FrameOptions, Selection, SpriteBake, SpriteBakeOptions, StampBrush, Tool, ViewSession, Viewport, ViewportCallbacks, } from "./ports/viewport.js";
|
|
@@ -98,6 +98,49 @@ export type FrameOptions = {
|
|
|
98
98
|
readonly padding?: number;
|
|
99
99
|
/** Where the camera looks from, as a direction from the objects (default: the current view direction). */
|
|
100
100
|
readonly direction?: Vec3;
|
|
101
|
+
/** An exact angle around the first object's front; replaces `direction` when given. */
|
|
102
|
+
readonly view?: CameraView;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* A camera angle around an object, in degrees, from its front (+z in its own space, turned by its transform):
|
|
106
|
+
* 0 / 0 looks straight at the front middle. Positive yaw swings the camera toward the object's left (+x), positive
|
|
107
|
+
* pitch raises it (90: from above). Yaw 180 looks at the back.
|
|
108
|
+
*/
|
|
109
|
+
export type CameraView = {
|
|
110
|
+
readonly yaw: number;
|
|
111
|
+
readonly pitch: number;
|
|
112
|
+
};
|
|
113
|
+
/** What `Viewport.bakeSprite` renders: the objects alone, in pixel art, in a square framed from `view`. */
|
|
114
|
+
export type SpriteBakeOptions = {
|
|
115
|
+
readonly objectIds: readonly Id[];
|
|
116
|
+
/** Width and height in pixels (16–512). */
|
|
117
|
+
readonly size: number;
|
|
118
|
+
readonly view: CameraView;
|
|
119
|
+
/** The share of the square left empty around the silhouette, 0–0.5 (default 0.03). */
|
|
120
|
+
readonly padding?: number;
|
|
121
|
+
/** The camera's vertical field of view in degrees, 5–60 (default 25: flatter than the studio's 50, as sprites are). */
|
|
122
|
+
readonly fov?: number;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* A baked sprite. Pixels hold palette roles, not colors, so the sprite can be recolored: 0 transparent, 1 ink
|
|
126
|
+
* (outlines and black), and `2 + slot × 3 + tone` for the slots primary, secondary, detail, highlight, eye, pupil,
|
|
127
|
+
* custom, tongue and teeth (in that order) with tone 0 base, 1 light, 2 shade (roles 2–28).
|
|
128
|
+
*/
|
|
129
|
+
export type SpriteBake = {
|
|
130
|
+
readonly width: number;
|
|
131
|
+
readonly height: number;
|
|
132
|
+
/** One role per pixel, rows from the top. */
|
|
133
|
+
readonly roles: Uint8Array;
|
|
134
|
+
/** The color of each role (index = role) for the creation's palette, as 0xRRGGBB; role 0 is unused. */
|
|
135
|
+
readonly colors: readonly number[];
|
|
136
|
+
/** Where the sprite stands: the lowest opaque row and the centre of the opaque pixels' box, in pixels. */
|
|
137
|
+
readonly anchor: {
|
|
138
|
+
readonly footY: number;
|
|
139
|
+
readonly centerX: number;
|
|
140
|
+
readonly centerY: number;
|
|
141
|
+
};
|
|
142
|
+
/** Opaque pixels whose color was only matched to the nearest role (decal art in colors outside the palette). */
|
|
143
|
+
readonly approximated: number;
|
|
101
144
|
};
|
|
102
145
|
/** The 3D view. Imperative so the port stays framework-agnostic. */
|
|
103
146
|
export type Viewport = {
|
|
@@ -121,5 +164,10 @@ export type Viewport = {
|
|
|
121
164
|
frame(objectIds: readonly Id[], options?: FrameOptions): void;
|
|
122
165
|
/** Small preview for the library (data URL). */
|
|
123
166
|
thumbnail(): string | undefined;
|
|
167
|
+
/**
|
|
168
|
+
* Bakes a sprite of objects (see `SpriteBake`): waits until they have meshed at the finest level, then renders them
|
|
169
|
+
* alone. The camera the view had is put back. Rejects when the viewport is not mounted or meshing does not settle.
|
|
170
|
+
*/
|
|
171
|
+
bakeSprite(options: SpriteBakeOptions): Promise<SpriteBake>;
|
|
124
172
|
dispose(): void;
|
|
125
173
|
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Headless sprite baking: `createSpriteBaker()` renders creations as palette-role sprites; the role helpers recolor them. */
|
|
2
|
+
export { createSpriteBaker } from "../../studio/src/sprite-baker.js";
|
|
3
|
+
export type { SpriteBaker, SpriteBakerOptions, SpriteShot } from "../../studio/src/sprite-baker.js";
|
|
4
|
+
export { ROLE_INK, ROLE_TRANSPARENT, roleSlot, SPRITE_ROLE_COUNT, SPRITE_SLOTS, spriteColors, spriteRole, type SpriteSlot, type SpriteTone, } from "../../viewport/src/index.js";
|
|
5
|
+
export type { CameraView, SpriteBake, SpriteBakeOptions } from "../../api/src/index.js";
|
|
@@ -13,3 +13,5 @@ export type { GitRemote } from "./git-remote.js";
|
|
|
13
13
|
export { EMPTY_LOG, parseCreationLog, serializeCreationLog } from "./creation-file.js";
|
|
14
14
|
export { createIds } from "./ids.js";
|
|
15
15
|
export { clock } from "./clock.js";
|
|
16
|
+
export { createSpriteBaker } from "./sprite-baker.js";
|
|
17
|
+
export type { SpriteBaker, SpriteBakerOptions, SpriteShot } from "./sprite-baker.js";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CameraView, Creation, Evaluator, Id, SpriteBake } from "../../api/src/index.js";
|
|
2
|
+
/** One picture of a bake: its angle around the creature's front and its size (see `SpriteBakeOptions`). */
|
|
3
|
+
export type SpriteShot = {
|
|
4
|
+
readonly view: CameraView;
|
|
5
|
+
readonly size: number;
|
|
6
|
+
readonly padding?: number;
|
|
7
|
+
readonly fov?: number;
|
|
8
|
+
};
|
|
9
|
+
/** Bakes sprites of creations, one at a time, with no studio UI. */
|
|
10
|
+
export type SpriteBaker = {
|
|
11
|
+
/** The shots of objects in a creation (default: every object), in order. */
|
|
12
|
+
bake(creation: Creation, shots: readonly SpriteShot[], objectIds?: readonly Id[]): Promise<SpriteBake[]>;
|
|
13
|
+
/** Releases the canvas, the GPU resources and (when the baker made it) the mesher. */
|
|
14
|
+
dispose(): void;
|
|
15
|
+
};
|
|
16
|
+
export type SpriteBakerOptions = {
|
|
17
|
+
/** The element the canvas goes into (default: one made off-screen, removed on dispose). */
|
|
18
|
+
readonly host?: HTMLElement;
|
|
19
|
+
/** The mesher (default: a new one, disposed with the baker). */
|
|
20
|
+
readonly evaluator?: Evaluator;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* A headless sprite baker: the WASM mesher and the viewport on a canvas nobody sees. A page (or a headless browser
|
|
24
|
+
* on a server) builds creations with `@dreamy/core` and bakes their pictures here. Bakes run one after another.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createSpriteBaker(options?: SpriteBakerOptions): SpriteBaker;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Palette, SpriteBake } from "../../../api/src/index.js";
|
|
2
|
+
/** The slots a sprite role can name, in role order (the pixel-art palette list without black, which is ink). */
|
|
3
|
+
export declare const SPRITE_SLOTS: readonly ["primary", "secondary", "detail", "highlight", "eye", "pupil", "custom", "tongue", "teeth"];
|
|
4
|
+
export type SpriteSlot = (typeof SPRITE_SLOTS)[number];
|
|
5
|
+
/** 0 base, 1 light, 2 shade (the pixel-art tones). */
|
|
6
|
+
export type SpriteTone = 0 | 1 | 2;
|
|
7
|
+
export declare const ROLE_TRANSPARENT = 0;
|
|
8
|
+
export declare const ROLE_INK = 1;
|
|
9
|
+
/** Roles in use: transparent, ink, and three tones per slot. */
|
|
10
|
+
export declare const SPRITE_ROLE_COUNT: number;
|
|
11
|
+
/** The role of a slot's tone. */
|
|
12
|
+
export declare const spriteRole: (slot: SpriteSlot, tone: SpriteTone) => number;
|
|
13
|
+
/** The slot and tone a role names (undefined for transparent and ink). */
|
|
14
|
+
export declare function roleSlot(role: number): {
|
|
15
|
+
readonly slot: SpriteSlot;
|
|
16
|
+
readonly tone: SpriteTone;
|
|
17
|
+
} | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* The color of every role for a palette (index = role), as the pixel-art render draws them: a slot's base is its
|
|
20
|
+
* swatch, light and shade its pixel-art variations. Recoloring a sprite is drawing its roles with another table.
|
|
21
|
+
*/
|
|
22
|
+
export declare function spriteColors(palette: Palette): number[];
|
|
23
|
+
/**
|
|
24
|
+
* Reads the index pass back: `rgba` is the render target's bytes (rows from the bottom, as WebGL reads them), the role
|
|
25
|
+
* in red and an "approximated" flag in green. Returns rows from the top.
|
|
26
|
+
*/
|
|
27
|
+
export declare function decodeRoles(rgba: Uint8Array, width: number, height: number): {
|
|
28
|
+
roles: Uint8Array;
|
|
29
|
+
approximated: number;
|
|
30
|
+
};
|
|
31
|
+
/** Where a sprite stands: its lowest opaque row and the centre of its opaque pixels' box (0s when empty). */
|
|
32
|
+
export declare function spriteAnchor(roles: Uint8Array, width: number, height: number): SpriteBake["anchor"];
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import type { FrameOptions } from "../../../api/src/index.js";
|
|
1
|
+
import type { CameraView, FrameOptions, Quaternion } from "../../../api/src/index.js";
|
|
2
2
|
import { Vector3, type Box3 } from "three";
|
|
3
3
|
import type { Stage } from "./stage.js";
|
|
4
|
+
/**
|
|
5
|
+
* The direction from an object toward the camera for a `CameraView`: around the object's front (+z in its space,
|
|
6
|
+
* turned by `rotation`, kept level), yaw toward its left (+x), pitch up from the ground plane.
|
|
7
|
+
*/
|
|
8
|
+
export declare function viewDirection(view: CameraView, rotation: Quaternion): Vector3;
|
|
4
9
|
/**
|
|
5
10
|
* Fits objects in the view like CSS `object-fit: contain`, in 3D. The camera keeps its angle (or takes `direction`)
|
|
6
11
|
* and moves so the silhouette of `points` exactly fills, on its limiting side, a centred area of `aspect` (as large as
|
|
@@ -17,10 +17,21 @@ export declare class RenderPipeline {
|
|
|
17
17
|
private readonly quadCam;
|
|
18
18
|
private paletteKey;
|
|
19
19
|
private readonly size;
|
|
20
|
+
/** Sprite bakes: the world pass and the role target, kept apart from the view's target. */
|
|
21
|
+
private bake;
|
|
20
22
|
constructor(stage: Stage, globals: GlobalUniforms);
|
|
21
23
|
render(prefs: ViewPreferences, palette: Palette, keep: readonly Object3D[] | null, isolated: boolean): void;
|
|
22
24
|
private hideUnselected;
|
|
23
25
|
private ensureTarget;
|
|
26
|
+
/** The offscreen world pass: linear, with "kind" in alpha, and a depth texture for pixel-art outlines. */
|
|
27
|
+
private worldTarget;
|
|
28
|
+
/**
|
|
29
|
+
* A sprite bake: `keep` alone (lights stay, ground and helpers go) in pixel art into a `size`-square target, resolved
|
|
30
|
+
* to sprite roles instead of colors, read back as RGBA bytes (rows from the bottom). The camera must already be
|
|
31
|
+
* square. Nothing reaches the canvas.
|
|
32
|
+
*/
|
|
33
|
+
renderSprite(size: number, palette: Palette, keep: readonly Object3D[]): Uint8Array;
|
|
34
|
+
private disposeBake;
|
|
24
35
|
private setPalette;
|
|
25
36
|
dispose(): void;
|
|
26
37
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** The teeth color's place in the pixel-art palette list (the palette slots, then tongue, teeth, black). */
|
|
2
|
+
export declare const TEETH_INDEX = 8;
|
|
1
3
|
/** Full-screen resolve of the offscreen world pass (the POC's rendering.js): tone mapping + sRGB, or pixel-art
|
|
2
4
|
palette cel shading, dithering and depth outlines; background or a checkerboard behind transparent pixels. */
|
|
3
|
-
export declare const RESOLVE_FRAG = "\nuniform sampler2D tColor;\nuniform sampler2D tDepth;\nuniform vec2 uSize;\nuniform float uNear;\nuniform float uFar;\nuniform float uExposure;\nuniform float uDpr;\nuniform float uPixel;\nuniform float uChecker;\nuniform vec3 uBg;\nuniform int uPalCount;\nuniform vec3 uPalRaw[12];\nuniform vec3 uPalBase[12];\nuniform vec3 uPalLight[12];\nuniform vec3 uPalShade[12];\nuniform vec3 uTeeth;\nuniform vec3 uInk;\nuniform float uEdge;\nuniform float uTone1;\nuniform float uTone2;\nuniform float uDither;\nuniform float uGlossWhite;\nvarying vec2 vUv;\n\n// same curves three.js applies to a material's output on screen\nvec3 RRTAndODTFit(vec3 v) {\n vec3 a = v * (v + 0.0245786) - 0.000090537;\n vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;\n return a / b;\n}\nvec3 acesFilmic(vec3 color) {\n const mat3 inMat = mat3(vec3(0.59719, 0.07600, 0.02840), vec3(0.35458, 0.90834, 0.13383), vec3(0.04823, 0.01566, 0.83777));\n const mat3 outMat = mat3(vec3(1.60475, -0.10208, -0.00327), vec3(-0.53108, 1.10813, -0.07276), vec3(-0.07367, -0.00605, 1.07602));\n color *= uExposure / 0.6;\n color = inMat * color;\n color = RRTAndODTFit(color);\n color = outMat * color;\n return clamp(color, 0.0, 1.0);\n}\nvec3 toSRGB(vec3 c) {\n return mix(pow(c, vec3(0.41666)) * 1.055 - vec3(0.055), c * 12.92, vec3(lessThanEqual(c, vec3(0.0031308))));\n}\n\nfloat bayer2(vec2 a) { a = floor(a); return fract(a.x / 2.0 + a.y * a.y * 0.75); }\nfloat bayer4(vec2 a) { return bayer2(0.5 * a) * 0.25 + bayer2(a); }\n\nvec2 pixUv(vec2 p) { return (clamp(p, vec2(0.0), uSize - 1.0) + 0.5) / uSize; }\n\n// 1 / view-space depth: planes are linear in it across the screen, so a flat floor seen at a glancing\n// angle gives no edge while a real depth step does\nfloat invZ(vec2 p) {\n float d = texture2D(tDepth, pixUv(p)).r;\n float z = 2.0 * uNear * uFar / (uFar + uNear - (2.0 * d - 1.0) * (uFar - uNear));\n return 1.0 / z;\n}\nfloat kindAt(vec2 p) { return texture2D(tColor, pixUv(p)).a; }\n\n// A pixel is outline when a neighbour (4-connected) is clearly nearer than the surface around it and\n// that nearer neighbour is an object (not the ground): the line sits on the far side, outside the shape.\nbool outlineAt(vec2 p) {\n float w0 = invZ(p);\n for (int i = 0; i < 4; i++) {\n vec2 dir = (i == 0) ? vec2(1.0, 0.0) : ((i == 1) ? vec2(0.0, 1.0) : ((i == 2) ? vec2(-1.0, 0.0) : vec2(0.0, -1.0)));\n float wq = invZ(p + dir);\n float wq2 = invZ(p - dir);\n if (wq + wq2 - 2.0 * w0 > uEdge * max(wq, wq2)) {\n float nearer = (wq >= wq2) ? kindAt(p + dir) : kindAt(p - dir);\n if (nearer > 0.4) return true;\n }\n }\n return false;\n}\n\
|
|
5
|
+
export declare const RESOLVE_FRAG = "\nuniform sampler2D tColor;\nuniform sampler2D tDepth;\nuniform vec2 uSize;\nuniform float uNear;\nuniform float uFar;\nuniform float uExposure;\nuniform float uDpr;\nuniform float uPixel;\nuniform float uChecker;\nuniform vec3 uBg;\nuniform int uPalCount;\nuniform vec3 uPalRaw[12];\nuniform vec3 uPalBase[12];\nuniform vec3 uPalLight[12];\nuniform vec3 uPalShade[12];\nuniform vec3 uTeeth;\nuniform vec3 uInk;\nuniform float uEdge;\nuniform float uTone1;\nuniform float uTone2;\nuniform float uDither;\nuniform float uGlossWhite;\nuniform float uIndexOut;\nvarying vec2 vUv;\n\n// same curves three.js applies to a material's output on screen\nvec3 RRTAndODTFit(vec3 v) {\n vec3 a = v * (v + 0.0245786) - 0.000090537;\n vec3 b = v * (0.983729 * v + 0.4329510) + 0.238081;\n return a / b;\n}\nvec3 acesFilmic(vec3 color) {\n const mat3 inMat = mat3(vec3(0.59719, 0.07600, 0.02840), vec3(0.35458, 0.90834, 0.13383), vec3(0.04823, 0.01566, 0.83777));\n const mat3 outMat = mat3(vec3(1.60475, -0.10208, -0.00327), vec3(-0.53108, 1.10813, -0.07276), vec3(-0.07367, -0.00605, 1.07602));\n color *= uExposure / 0.6;\n color = inMat * color;\n color = RRTAndODTFit(color);\n color = outMat * color;\n return clamp(color, 0.0, 1.0);\n}\nvec3 toSRGB(vec3 c) {\n return mix(pow(c, vec3(0.41666)) * 1.055 - vec3(0.055), c * 12.92, vec3(lessThanEqual(c, vec3(0.0031308))));\n}\n\nfloat bayer2(vec2 a) { a = floor(a); return fract(a.x / 2.0 + a.y * a.y * 0.75); }\nfloat bayer4(vec2 a) { return bayer2(0.5 * a) * 0.25 + bayer2(a); }\n\nvec2 pixUv(vec2 p) { return (clamp(p, vec2(0.0), uSize - 1.0) + 0.5) / uSize; }\n\n// 1 / view-space depth: planes are linear in it across the screen, so a flat floor seen at a glancing\n// angle gives no edge while a real depth step does\nfloat invZ(vec2 p) {\n float d = texture2D(tDepth, pixUv(p)).r;\n float z = 2.0 * uNear * uFar / (uFar + uNear - (2.0 * d - 1.0) * (uFar - uNear));\n return 1.0 / z;\n}\nfloat kindAt(vec2 p) { return texture2D(tColor, pixUv(p)).a; }\n\n// A pixel is outline when a neighbour (4-connected) is clearly nearer than the surface around it and\n// that nearer neighbour is an object (not the ground): the line sits on the far side, outside the shape.\nbool outlineAt(vec2 p) {\n float w0 = invZ(p);\n for (int i = 0; i < 4; i++) {\n vec2 dir = (i == 0) ? vec2(1.0, 0.0) : ((i == 1) ? vec2(0.0, 1.0) : ((i == 2) ? vec2(-1.0, 0.0) : vec2(0.0, -1.0)));\n float wq = invZ(p + dir);\n float wq2 = invZ(p - dir);\n if (wq + wq2 - 2.0 * w0 > uEdge * max(wq, wq2)) {\n float nearer = (wq >= wq2) ? kindAt(p + dir) : kindAt(p - dir);\n if (nearer > 0.4) return true;\n }\n }\n return false;\n}\n\n// The pixel-art choice for a lit pixel, as a sprite role: 1 ink, or 2 + palette index \u00D7 3 + tone (0 base, 1 light,\n// 2 shade); a shiny surface's light tone is teeth-white (the teeth base role).\nfloat celRole(vec3 c, float gloss, vec2 p) {\n // Pixels from black decals or unlit black surfaces render directly as solid ink\n if (max(c.r, max(c.g, c.b)) < 0.05) return 1.0;\n\n vec3 base = vec3(0.0);\n float bestScore = 1e9;\n float bestS = 1.0;\n float bestI = 0.0;\n float cl = length(c) + 0.02;\n for (int i = 0; i < 12; i++) {\n if (i >= uPalCount) break;\n vec3 P = uPalRaw[i];\n float s = clamp(dot(c, P) / max(dot(P, P), 0.0001), 0.2, 4.0);\n float score = length(c - s * P) / cl + 0.12 * abs(log2(s / 1.25));\n if (score < bestScore) {\n bestScore = score; bestS = s; bestI = float(i);\n base = uPalBase[i];\n }\n }\n\n // Black from the palette stays flat: no teeth-white specular, no dithering, no stepping\n if (length(base - uInk) < 0.05 || length(base) < 0.10) return 1.0;\n\n float tau = log2(bestS) + (bayer4(p) - 0.47) * uDither;\n if (tau > uTone2) return (gloss > uGlossWhite) ? 26.0 : 2.0 + bestI * 3.0 + 1.0; // only shiny materials catch teeth-white light\n if (tau > uTone1) return 2.0 + bestI * 3.0;\n return 2.0 + bestI * 3.0 + 2.0;\n}\n\nvec3 roleColor(float role) {\n if (role < 1.5) return uInk;\n float k = role - 2.0;\n float slot = floor(k / 3.0 + 0.001);\n float tone = k - slot * 3.0;\n vec3 col = uInk;\n for (int i = 0; i < 12; i++) {\n if (float(i) == slot) col = (tone < 0.5) ? uPalBase[i] : ((tone < 1.5) ? uPalLight[i] : uPalShade[i]);\n }\n return col;\n}\n\nvec3 celShade(vec3 c, float gloss, vec2 p) { return roleColor(celRole(c, gloss, p)); }\n\n// A flat (decal) color as the nearest role: x the role, y 1 when the match is not close (art outside the palette)\nvec2 nearestRole(vec3 c) {\n if (max(c.r, max(c.g, c.b)) < 0.05) return vec2(1.0, 0.0);\n float best = length(c - uInk), role = 1.0;\n for (int i = 0; i < 12; i++) {\n if (i >= uPalCount) break;\n if (length(uPalBase[i] - uInk) < 0.05) continue;\n float d0 = length(c - uPalBase[i]), d1 = length(c - uPalLight[i]), d2 = length(c - uPalShade[i]);\n if (d0 < best) { best = d0; role = 2.0 + float(i) * 3.0; }\n if (d1 < best) { best = d1; role = 2.0 + float(i) * 3.0 + 1.0; }\n if (d2 < best) { best = d2; role = 2.0 + float(i) * 3.0 + 2.0; }\n }\n return vec2(role, best > 0.08 ? 1.0 : 0.0);\n}\n\nvoid main() {\n vec2 p = floor(vUv * uSize);\n vec4 s = texture2D(tColor, (uPixel > 0.5) ? (p + 0.5) / uSize : vUv);\n // Sprite bake: the role of each pixel in red (/ 255), \"approximated\" in green\n if (uIndexOut > 0.5) {\n vec2 r = vec2(0.0);\n if (s.a > 0.05) {\n if (s.a >= 0.45 && s.a <= 0.50) r = nearestRole(s.rgb);\n else r = vec2(celRole(s.rgb, (s.a < 0.4) ? 0.0 : clamp((1.0 - s.a) * 2.0, 0.0, 1.0), p), 0.0);\n }\n if (outlineAt(p)) r = vec2(1.0, 0.0);\n gl_FragColor = vec4(r.x / 255.0, r.y, 0.0, 1.0);\n return;\n }\n vec3 bg = uBg;\n if (uChecker > 0.5) {\n vec2 g = floor(gl_FragCoord.xy / (9.0 * uDpr));\n bg = (mod(g.x + g.y, 2.0) < 0.5) ? vec3(0.96) : vec3(0.78);\n }\n vec3 col = bg;\n if (uPixel < 0.5) {\n if (s.a > 0.05) col = toSRGB(acesFilmic(s.rgb));\n } else {\n if (s.a > 0.05) {\n // Decal pixel: render flat and clean (no scene light, no shadows, no glossiness)\n // We use a broader threshold to catch blended light/shadow modifier pixels\n if (s.a >= 0.45 && s.a <= 0.50) {\n // Ensure pure black decals stay flat black, others render their flat color\n if (max(s.r, max(s.g, s.b)) < 0.05) col = uInk;\n else col = s.rgb;\n } else {\n float gloss = (s.a < 0.4) ? 0.0 : clamp((1.0 - s.a) * 2.0, 0.0, 1.0);\n col = celShade(s.rgb, gloss, p);\n }\n }\n if (outlineAt(p)) col = uInk;\n }\n gl_FragColor = vec4(col, 1.0);\n}\n";
|
package/dist/viewport.js
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
ROLE_INK,
|
|
4
|
+
ROLE_TRANSPARENT,
|
|
5
|
+
SPRITE_ROLE_COUNT,
|
|
6
|
+
SPRITE_SLOTS,
|
|
7
|
+
createViewport,
|
|
8
|
+
roleSlot,
|
|
9
|
+
spriteColors,
|
|
10
|
+
spriteRole
|
|
11
|
+
} from "./chunks/chunk-5KZP6X65.js";
|
|
5
12
|
export {
|
|
6
|
-
|
|
13
|
+
ROLE_INK,
|
|
14
|
+
ROLE_TRANSPARENT,
|
|
15
|
+
SPRITE_ROLE_COUNT,
|
|
16
|
+
SPRITE_SLOTS,
|
|
17
|
+
createViewport,
|
|
18
|
+
roleSlot,
|
|
19
|
+
spriteColors,
|
|
20
|
+
spriteRole
|
|
7
21
|
};
|
|
8
22
|
//# sourceMappingURL=viewport.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@felbrasil/dreamy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Dreamy: a voxel creature sculpting studio for React (WASM mesher, Three.js viewport, git-backed saves).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Felipe Bravo",
|
|
@@ -48,6 +48,10 @@
|
|
|
48
48
|
"types": "./dist/api.d.ts",
|
|
49
49
|
"default": "./dist/api.js"
|
|
50
50
|
},
|
|
51
|
+
"./bake": {
|
|
52
|
+
"types": "./dist/bake.d.ts",
|
|
53
|
+
"default": "./dist/bake.js"
|
|
54
|
+
},
|
|
51
55
|
"./styles.css": "./dist/styles.css",
|
|
52
56
|
"./dreamy_evaluator.wasm": "./dist/dreamy_evaluator.wasm",
|
|
53
57
|
"./package.json": "./package.json"
|