reze-engine 0.18.1 → 0.20.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 +291 -252
- package/dist/engine.d.ts +45 -41
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +401 -421
- package/dist/graph/compile.d.ts +6 -0
- package/dist/graph/compile.d.ts.map +1 -1
- package/dist/graph/compile.js +1 -1
- package/dist/graph/presets/body.js +1 -1
- package/dist/graph/presets/cloth_rough.js +1 -1
- package/dist/graph/presets/cloth_smooth.js +1 -1
- package/dist/graph/presets/default.d.ts.map +1 -1
- package/dist/graph/presets/default.js +14 -7
- package/dist/graph/presets/eye.js +1 -1
- package/dist/graph/presets/face.js +1 -1
- package/dist/graph/presets/hair.js +1 -1
- package/dist/graph/presets/metal.js +1 -1
- package/dist/graph/presets/stockings.js +1 -1
- package/dist/graph/registry.d.ts.map +1 -1
- package/dist/graph/registry.js +8 -0
- package/dist/graph/render-class.d.ts +16 -0
- package/dist/graph/render-class.d.ts.map +1 -0
- package/dist/graph/render-class.js +15 -0
- package/dist/graph/schema.d.ts +4 -5
- package/dist/graph/schema.d.ts.map +1 -1
- package/dist/graph/slots.d.ts +7 -12
- package/dist/graph/slots.d.ts.map +1 -1
- package/dist/graph/slots.js +68 -100
- package/dist/graph/style-group.d.ts +38 -0
- package/dist/graph/style-group.d.ts.map +1 -0
- package/dist/graph/style-group.js +6 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/tga-loader.d.ts +7 -0
- package/dist/tga-loader.d.ts.map +1 -0
- package/dist/tga-loader.js +154 -0
- package/package.json +2 -2
- package/src/engine.ts +4488 -4479
- package/src/graph/compile.ts +7 -1
- package/src/graph/presets/body.ts +1 -1
- package/src/graph/presets/cloth_rough.ts +1 -1
- package/src/graph/presets/cloth_smooth.ts +1 -1
- package/src/graph/presets/default.ts +14 -7
- package/src/graph/presets/eye.ts +1 -1
- package/src/graph/presets/face.ts +1 -1
- package/src/graph/presets/hair.ts +1 -1
- package/src/graph/presets/metal.ts +1 -1
- package/src/graph/presets/stockings.ts +1 -1
- package/src/graph/registry.ts +8 -0
- package/src/graph/render-class.ts +33 -0
- package/src/graph/schema.ts +4 -6
- package/src/graph/slots.ts +76 -113
- package/src/graph/style-group.ts +39 -0
- package/src/index.ts +7 -1
- package/src/tga-loader.ts +154 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Minimal TGA decoder. TGA is not a web-native format (createImageBitmap can't decode
|
|
2
|
+
// it), yet it's common in PMX assets — especially sphere maps (.spa/.sph) and eye/detail
|
|
3
|
+
// textures — so without this those materials render untextured. Produces top-left-origin
|
|
4
|
+
// RGBA8, ready for queue.writeTexture into an rgba8unorm(-srgb) texture.
|
|
5
|
+
//
|
|
6
|
+
// Handles the variants that actually appear in the wild: true-color (16/24/32), grayscale
|
|
7
|
+
// (8), and color-mapped (8-bit index), each raw or RLE-compressed, with the image
|
|
8
|
+
// descriptor's origin bits honored. Throws on malformed/unsupported input — the caller
|
|
9
|
+
// catches, logs, and falls back to the white texture (never panics the render loop).
|
|
10
|
+
|
|
11
|
+
export type DecodedImage = { width: number; height: number; rgba: Uint8Array }
|
|
12
|
+
|
|
13
|
+
// Image type byte (header offset 2).
|
|
14
|
+
const enum TgaType {
|
|
15
|
+
ColorMapped = 1,
|
|
16
|
+
TrueColor = 2,
|
|
17
|
+
Grayscale = 3,
|
|
18
|
+
RleColorMapped = 9,
|
|
19
|
+
RleTrueColor = 10,
|
|
20
|
+
RleGrayscale = 11,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function decodeTga(buffer: ArrayBuffer): DecodedImage {
|
|
24
|
+
const bytes = new Uint8Array(buffer)
|
|
25
|
+
const view = new DataView(buffer)
|
|
26
|
+
if (bytes.length < 18) throw new Error("TGA too small for header")
|
|
27
|
+
|
|
28
|
+
const idLength = view.getUint8(0)
|
|
29
|
+
const colorMapType = view.getUint8(1)
|
|
30
|
+
const imageType = view.getUint8(2) as TgaType
|
|
31
|
+
const cmapLength = view.getUint16(5, true)
|
|
32
|
+
const cmapEntryBits = view.getUint8(7)
|
|
33
|
+
const width = view.getUint16(12, true)
|
|
34
|
+
const height = view.getUint16(14, true)
|
|
35
|
+
const pixelDepth = view.getUint8(16)
|
|
36
|
+
const descriptor = view.getUint8(17)
|
|
37
|
+
|
|
38
|
+
if (width <= 0 || height <= 0) throw new Error(`TGA bad dimensions ${width}x${height}`)
|
|
39
|
+
|
|
40
|
+
const rle =
|
|
41
|
+
imageType === TgaType.RleColorMapped ||
|
|
42
|
+
imageType === TgaType.RleTrueColor ||
|
|
43
|
+
imageType === TgaType.RleGrayscale
|
|
44
|
+
const colorMapped = imageType === TgaType.ColorMapped || imageType === TgaType.RleColorMapped
|
|
45
|
+
const grayscale = imageType === TgaType.Grayscale || imageType === TgaType.RleGrayscale
|
|
46
|
+
const trueColor = imageType === TgaType.TrueColor || imageType === TgaType.RleTrueColor
|
|
47
|
+
if (!colorMapped && !grayscale && !trueColor) throw new Error(`TGA unsupported image type ${imageType}`)
|
|
48
|
+
|
|
49
|
+
let offset = 18 + idLength
|
|
50
|
+
|
|
51
|
+
// Color map (BGR/BGRA entries) → precomputed RGBA lookup.
|
|
52
|
+
let colorMap: Uint8Array | null = null
|
|
53
|
+
if (colorMapType === 1) {
|
|
54
|
+
const entryBytes = Math.ceil(cmapEntryBits / 8)
|
|
55
|
+
colorMap = new Uint8Array(cmapLength * 4)
|
|
56
|
+
for (let i = 0; i < cmapLength; i++) {
|
|
57
|
+
const [r, g, b, a] = unpackColor(bytes, offset + i * entryBytes, cmapEntryBits)
|
|
58
|
+
colorMap.set([r, g, b, a], i * 4)
|
|
59
|
+
}
|
|
60
|
+
offset += cmapLength * entryBytes
|
|
61
|
+
} else if (colorMapped) {
|
|
62
|
+
throw new Error("TGA color-mapped image without a color map")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Bytes per stored element (index byte for color-mapped, else pixel bytes).
|
|
66
|
+
const elemBits = colorMapped ? pixelDepth : pixelDepth
|
|
67
|
+
const elemBytes = Math.ceil(elemBits / 8)
|
|
68
|
+
const pixelCount = width * height
|
|
69
|
+
|
|
70
|
+
// Decode the (possibly RLE) element stream into one element per pixel, file order.
|
|
71
|
+
const elements = new Uint8Array(pixelCount * elemBytes)
|
|
72
|
+
if (rle) {
|
|
73
|
+
let out = 0
|
|
74
|
+
let src = offset
|
|
75
|
+
while (out < elements.length) {
|
|
76
|
+
if (src >= bytes.length) throw new Error("TGA RLE stream truncated")
|
|
77
|
+
const packet = bytes[src++]
|
|
78
|
+
const count = (packet & 0x7f) + 1
|
|
79
|
+
if (packet & 0x80) {
|
|
80
|
+
// RLE packet: one element repeated `count` times.
|
|
81
|
+
if (src + elemBytes > bytes.length) throw new Error("TGA RLE packet truncated")
|
|
82
|
+
for (let i = 0; i < count && out < elements.length; i++) {
|
|
83
|
+
elements.set(bytes.subarray(src, src + elemBytes), out)
|
|
84
|
+
out += elemBytes
|
|
85
|
+
}
|
|
86
|
+
src += elemBytes
|
|
87
|
+
} else {
|
|
88
|
+
// Raw packet: `count` elements follow.
|
|
89
|
+
const span = count * elemBytes
|
|
90
|
+
if (src + span > bytes.length) throw new Error("TGA raw packet truncated")
|
|
91
|
+
elements.set(bytes.subarray(src, src + span), out)
|
|
92
|
+
out += span
|
|
93
|
+
src += span
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
const span = pixelCount * elemBytes
|
|
98
|
+
if (offset + span > bytes.length) throw new Error("TGA pixel data truncated")
|
|
99
|
+
elements.set(bytes.subarray(offset, offset + span), 0)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Convert elements → RGBA in file order.
|
|
103
|
+
const linear = new Uint8Array(pixelCount * 4)
|
|
104
|
+
for (let p = 0; p < pixelCount; p++) {
|
|
105
|
+
let rgba: [number, number, number, number]
|
|
106
|
+
if (colorMapped) {
|
|
107
|
+
const index = elements[p] // 8-bit index (pixelDepth 8)
|
|
108
|
+
const c = index * 4
|
|
109
|
+
rgba = colorMap ? [colorMap[c], colorMap[c + 1], colorMap[c + 2], colorMap[c + 3]] : [0, 0, 0, 255]
|
|
110
|
+
} else if (grayscale) {
|
|
111
|
+
const g = elements[p * elemBytes]
|
|
112
|
+
rgba = [g, g, g, 255]
|
|
113
|
+
} else {
|
|
114
|
+
rgba = unpackColor(elements, p * elemBytes, pixelDepth)
|
|
115
|
+
}
|
|
116
|
+
linear.set(rgba, p * 4)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Honor origin bits: bit5 set → top-to-bottom (no flip); clear → bottom-to-top.
|
|
120
|
+
// bit4 set → right-to-left. Output is always top-left origin for GPU upload.
|
|
121
|
+
const flipY = (descriptor & 0x20) === 0
|
|
122
|
+
const flipX = (descriptor & 0x10) !== 0
|
|
123
|
+
if (!flipX && !flipY) return { width, height, rgba: linear }
|
|
124
|
+
|
|
125
|
+
const rgba = new Uint8Array(pixelCount * 4)
|
|
126
|
+
for (let y = 0; y < height; y++) {
|
|
127
|
+
const sy = flipY ? height - 1 - y : y
|
|
128
|
+
for (let x = 0; x < width; x++) {
|
|
129
|
+
const sx = flipX ? width - 1 - x : x
|
|
130
|
+
const src = (sy * width + sx) * 4
|
|
131
|
+
const dst = (y * width + x) * 4
|
|
132
|
+
rgba[dst] = linear[src]
|
|
133
|
+
rgba[dst + 1] = linear[src + 1]
|
|
134
|
+
rgba[dst + 2] = linear[src + 2]
|
|
135
|
+
rgba[dst + 3] = linear[src + 3]
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { width, height, rgba }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Unpack one true-color element (16/24/32-bit, stored BGR[A]) to RGBA8.
|
|
142
|
+
function unpackColor(src: Uint8Array, o: number, bits: number): [number, number, number, number] {
|
|
143
|
+
if (bits === 32) return [src[o + 2], src[o + 1], src[o], src[o + 3]]
|
|
144
|
+
if (bits === 24) return [src[o + 2], src[o + 1], src[o], 255]
|
|
145
|
+
if (bits === 16 || bits === 15) {
|
|
146
|
+
// 1-5-5-5: bit15 = attribute, then 5 red, 5 green, 5 blue (little-endian).
|
|
147
|
+
const val = src[o] | (src[o + 1] << 8)
|
|
148
|
+
const r = ((val >> 10) & 0x1f) * 255
|
|
149
|
+
const g = ((val >> 5) & 0x1f) * 255
|
|
150
|
+
const b = (val & 0x1f) * 255
|
|
151
|
+
return [Math.round(r / 31), Math.round(g / 31), Math.round(b / 31), 255]
|
|
152
|
+
}
|
|
153
|
+
throw new Error(`TGA unsupported pixel depth ${bits}`)
|
|
154
|
+
}
|