poppygl 0.0.16 → 0.0.18
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 +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.js +255 -185
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,7 @@ const png = await renderGLTFToPNGBufferFromGLBBuffer(glb, {
|
|
|
50
50
|
`renderGLTFToPNGBufferFromURL` accepts the same render options as the lower-level APIs:
|
|
51
51
|
|
|
52
52
|
- `width`/`height` (default `512`): output resolution in pixels.
|
|
53
|
+
- `supersampling`: render at `width * supersampling` / `height * supersampling`, then downsample (default `1`).
|
|
53
54
|
- `fov`: vertical field of view in degrees (defaults to `35`).
|
|
54
55
|
- `camPos` and `lookAt`: override the auto-framed camera position and target.
|
|
55
56
|
- `lightDir`: normalized directional light vector (defaults to a top-right key light).
|
package/dist/index.d.ts
CHANGED
|
@@ -53,9 +53,16 @@ interface GLTFScene {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
declare const DEFAULT_LIGHT_DIR: readonly [-0.4, -0.9, -0.2];
|
|
56
|
+
/**
|
|
57
|
+
* Convert a hex color string to RGB array with values 0-1
|
|
58
|
+
* @param hex - Hex color string like "#ffffff" or "ffffff"
|
|
59
|
+
* @returns RGB array [r, g, b] with values 0-1, or null if invalid
|
|
60
|
+
*/
|
|
61
|
+
declare function hexToRgb(hex: string): [number, number, number] | null;
|
|
56
62
|
interface RenderOptions {
|
|
57
63
|
width: number;
|
|
58
64
|
height: number;
|
|
65
|
+
supersampling: number;
|
|
59
66
|
fov: number;
|
|
60
67
|
cull: boolean;
|
|
61
68
|
gamma: boolean;
|
|
@@ -63,7 +70,7 @@ interface RenderOptions {
|
|
|
63
70
|
lightDir: readonly [number, number, number];
|
|
64
71
|
camPos?: readonly [number, number, number] | null;
|
|
65
72
|
lookAt?: readonly [number, number, number] | null;
|
|
66
|
-
backgroundColor?: readonly [number, number, number] | null;
|
|
73
|
+
backgroundColor?: readonly [number, number, number] | string | null;
|
|
67
74
|
grid?: boolean | GridOptions;
|
|
68
75
|
}
|
|
69
76
|
type RenderOptionsInput = Partial<RenderOptions>;
|
|
@@ -181,4 +188,4 @@ declare function renderGLTFToPNGBuffer(gltfPathOrString: string, options?: Rende
|
|
|
181
188
|
|
|
182
189
|
declare function renderSceneFromGLTF(scene: GLTFScene, options?: RenderOptionsInput, imageFactory?: ImageFactory): RenderResult;
|
|
183
190
|
|
|
184
|
-
export { type BitmapLike, type Camera, DEFAULT_LIGHT_DIR, DEFAULT_RENDER_OPTIONS, type DrawCall, type FetchLike, type GLTFResources, type GLTFScene, type GridOptions, type ImageFactory, type LightSettings, type LoadGLTFWithResourcesFromURLOptions, type Material, type MutableRGBA, type RGBA, type RenderGLTFToPNGBufferFromGLBBufferOptions, type RenderGLTFToPNGBufferFromURLOptions, type RenderOptions, type RenderOptionsInput, type RenderResult, SoftwareRenderer, bufferFromDataURI, buildCamera, computeSmoothNormals, computeWorldAABB, createGrid, createInfiniteGrid, createSceneFromGLTF, createUint8Bitmap, decodeImageFromBuffer, drawInfiniteGrid, encodePNGToBuffer, getDefaultRenderOptions, isJPG, isPNG, loadGLTFWithResourcesFromURL, pureImageFactory, renderDrawCalls, renderGLTFToPNGBuffer, renderGLTFToPNGBufferFromGLBBuffer, renderGLTFToPNGBufferFromURL, renderSceneFromGLTF, resolveRenderOptions };
|
|
191
|
+
export { type BitmapLike, type Camera, DEFAULT_LIGHT_DIR, DEFAULT_RENDER_OPTIONS, type DrawCall, type FetchLike, type GLTFResources, type GLTFScene, type GridOptions, type ImageFactory, type LightSettings, type LoadGLTFWithResourcesFromURLOptions, type Material, type MutableRGBA, type RGBA, type RenderGLTFToPNGBufferFromGLBBufferOptions, type RenderGLTFToPNGBufferFromURLOptions, type RenderOptions, type RenderOptionsInput, type RenderResult, SoftwareRenderer, bufferFromDataURI, buildCamera, computeSmoothNormals, computeWorldAABB, createGrid, createInfiniteGrid, createSceneFromGLTF, createUint8Bitmap, decodeImageFromBuffer, drawInfiniteGrid, encodePNGToBuffer, getDefaultRenderOptions, hexToRgb, isJPG, isPNG, loadGLTFWithResourcesFromURL, pureImageFactory, renderDrawCalls, renderGLTFToPNGBuffer, renderGLTFToPNGBufferFromGLBBuffer, renderGLTFToPNGBufferFromURL, renderSceneFromGLTF, resolveRenderOptions };
|
package/dist/index.js
CHANGED
|
@@ -132,11 +132,220 @@ var createUint8Bitmap = (width, height) => ({
|
|
|
132
132
|
data: new Uint8ClampedArray(width * height * 4)
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
+
// lib/render/downsampleBitmap.ts
|
|
136
|
+
function downsampleBitmap(source, targetWidth, targetHeight, imageFactory) {
|
|
137
|
+
const target = imageFactory(targetWidth, targetHeight);
|
|
138
|
+
const scaleX = source.width / targetWidth;
|
|
139
|
+
const scaleY = source.height / targetHeight;
|
|
140
|
+
for (let y = 0; y < targetHeight; y++) {
|
|
141
|
+
const srcY0 = Math.floor(y * scaleY);
|
|
142
|
+
const srcY1 = Math.max(
|
|
143
|
+
srcY0 + 1,
|
|
144
|
+
Math.min(source.height, Math.floor((y + 1) * scaleY))
|
|
145
|
+
);
|
|
146
|
+
for (let x = 0; x < targetWidth; x++) {
|
|
147
|
+
const srcX0 = Math.floor(x * scaleX);
|
|
148
|
+
const srcX1 = Math.max(
|
|
149
|
+
srcX0 + 1,
|
|
150
|
+
Math.min(source.width, Math.floor((x + 1) * scaleX))
|
|
151
|
+
);
|
|
152
|
+
let r = 0;
|
|
153
|
+
let g = 0;
|
|
154
|
+
let b = 0;
|
|
155
|
+
let a = 0;
|
|
156
|
+
let count = 0;
|
|
157
|
+
for (let sy = srcY0; sy < srcY1; sy++) {
|
|
158
|
+
for (let sx = srcX0; sx < srcX1; sx++) {
|
|
159
|
+
const srcIdx = (sy * source.width + sx) * 4;
|
|
160
|
+
r += source.data[srcIdx + 0];
|
|
161
|
+
g += source.data[srcIdx + 1];
|
|
162
|
+
b += source.data[srcIdx + 2];
|
|
163
|
+
a += source.data[srcIdx + 3];
|
|
164
|
+
count++;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const dstIdx = (y * targetWidth + x) * 4;
|
|
168
|
+
target.data[dstIdx + 0] = Math.round(r / count);
|
|
169
|
+
target.data[dstIdx + 1] = Math.round(g / count);
|
|
170
|
+
target.data[dstIdx + 2] = Math.round(b / count);
|
|
171
|
+
target.data[dstIdx + 3] = Math.round(a / count);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return target;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// lib/render/drawInfiniteGrid.ts
|
|
178
|
+
import { mat4 as mat43, vec4 as vec42 } from "gl-matrix";
|
|
179
|
+
|
|
180
|
+
// lib/utils/srgbEncodeLinear01.ts
|
|
181
|
+
function srgbEncodeLinear01(linear) {
|
|
182
|
+
if (linear <= 31308e-7) return 12.92 * linear;
|
|
183
|
+
return 1.055 * Math.pow(linear, 1 / 2.4) - 0.055;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// lib/utils/clamp.ts
|
|
187
|
+
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
188
|
+
|
|
189
|
+
// lib/render/drawInfiniteGrid.ts
|
|
190
|
+
function computeGridLineIntensity(params) {
|
|
191
|
+
const rx = params.world_x / params.cell_size;
|
|
192
|
+
const rz = params.world_z / params.cell_size;
|
|
193
|
+
const fract_x = rx - Math.floor(rx + 0.5);
|
|
194
|
+
const fract_z = rz - Math.floor(rz + 0.5);
|
|
195
|
+
const grid_x = Math.abs(fract_x);
|
|
196
|
+
const grid_z = Math.abs(fract_z);
|
|
197
|
+
const pixel_size_in_world = params.dist / Math.min(params.screen_width, params.screen_height);
|
|
198
|
+
const fwidth_x = pixel_size_in_world / params.cell_size;
|
|
199
|
+
const fwidth_z = pixel_size_in_world / params.cell_size;
|
|
200
|
+
const line_x = grid_x / (fwidth_x + 1e-4);
|
|
201
|
+
const line_z = grid_z / (fwidth_z + 1e-4);
|
|
202
|
+
const line = Math.min(line_x, line_z);
|
|
203
|
+
return 1 - Math.min(line * params.line_thickness_multiplier, 1);
|
|
204
|
+
}
|
|
205
|
+
function computeGridFadeAlpha(params) {
|
|
206
|
+
const fade_start = params.fade_distance;
|
|
207
|
+
const fade_end = params.fade_distance * params.fade_strength;
|
|
208
|
+
return 1 - Math.min(
|
|
209
|
+
1,
|
|
210
|
+
Math.max(0, (params.dist - fade_start) / (fade_end - fade_start))
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
function drawInfiniteGrid(software_renderer, params) {
|
|
214
|
+
const grid_y = params.grid_y ?? 0;
|
|
215
|
+
const cell_size = params.cell_size ?? 1;
|
|
216
|
+
const section_size = params.section_size ?? 10;
|
|
217
|
+
const fade_distance = params.fade_distance ?? 100;
|
|
218
|
+
const fade_strength = params.fade_strength ?? 1.5;
|
|
219
|
+
const grid_color = params.grid_color ?? [0.93, 0.93, 0.93];
|
|
220
|
+
const section_color = params.section_color ?? [0.8, 0.8, 1];
|
|
221
|
+
const gamma_out = params.gamma_out ?? true;
|
|
222
|
+
const camera = params.camera;
|
|
223
|
+
const view = camera.view;
|
|
224
|
+
const proj = camera.proj;
|
|
225
|
+
const inv_view = mat43.create();
|
|
226
|
+
mat43.invert(inv_view, view);
|
|
227
|
+
const cam_pos_x = inv_view[12];
|
|
228
|
+
const cam_pos_y = inv_view[13];
|
|
229
|
+
const cam_pos_z = inv_view[14];
|
|
230
|
+
const vp = mat43.create();
|
|
231
|
+
mat43.multiply(vp, proj, view);
|
|
232
|
+
const inv_vp = mat43.create();
|
|
233
|
+
mat43.invert(inv_vp, vp);
|
|
234
|
+
for (let y = 0; y < software_renderer.height; y++) {
|
|
235
|
+
for (let x = 0; x < software_renderer.width; x++) {
|
|
236
|
+
const ndc_x = x / (software_renderer.width - 1) * 2 - 1;
|
|
237
|
+
const ndc_y = 1 - y / (software_renderer.height - 1) * 2;
|
|
238
|
+
const near_point = vec42.fromValues(ndc_x, ndc_y, -1, 1);
|
|
239
|
+
const far_point = vec42.fromValues(ndc_x, ndc_y, 1, 1);
|
|
240
|
+
vec42.transformMat4(near_point, near_point, inv_vp);
|
|
241
|
+
vec42.transformMat4(far_point, far_point, inv_vp);
|
|
242
|
+
near_point[0] /= near_point[3];
|
|
243
|
+
near_point[1] /= near_point[3];
|
|
244
|
+
near_point[2] /= near_point[3];
|
|
245
|
+
far_point[0] /= far_point[3];
|
|
246
|
+
far_point[1] /= far_point[3];
|
|
247
|
+
far_point[2] /= far_point[3];
|
|
248
|
+
const ray_dir_x = far_point[0] - near_point[0];
|
|
249
|
+
const ray_dir_y = far_point[1] - near_point[1];
|
|
250
|
+
const ray_dir_z = far_point[2] - near_point[2];
|
|
251
|
+
if (Math.abs(ray_dir_y) < 1e-6) continue;
|
|
252
|
+
const t = (grid_y - near_point[1]) / ray_dir_y;
|
|
253
|
+
if (t < 0 || t > 1) continue;
|
|
254
|
+
const world_x = near_point[0] + t * ray_dir_x;
|
|
255
|
+
const world_z = near_point[2] + t * ray_dir_z;
|
|
256
|
+
const world_pt = vec42.fromValues(world_x, grid_y, world_z, 1);
|
|
257
|
+
const clip_pt = vec42.create();
|
|
258
|
+
vec42.transformMat4(clip_pt, world_pt, vp);
|
|
259
|
+
const ndc_z = clip_pt[2] / clip_pt[3];
|
|
260
|
+
const depth_01 = ndc_z * 0.5 + 0.5;
|
|
261
|
+
const di = y * software_renderer.width + x;
|
|
262
|
+
if (depth_01 >= software_renderer.depth[di]) continue;
|
|
263
|
+
const dx = world_x - cam_pos_x;
|
|
264
|
+
const dy = grid_y - cam_pos_y;
|
|
265
|
+
const dz = world_z - cam_pos_z;
|
|
266
|
+
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
267
|
+
const alpha = computeGridFadeAlpha({
|
|
268
|
+
dist,
|
|
269
|
+
fade_distance,
|
|
270
|
+
fade_strength
|
|
271
|
+
});
|
|
272
|
+
if (alpha <= 0) continue;
|
|
273
|
+
const g1 = computeGridLineIntensity({
|
|
274
|
+
world_x,
|
|
275
|
+
world_z,
|
|
276
|
+
cell_size,
|
|
277
|
+
dist,
|
|
278
|
+
screen_width: software_renderer.width,
|
|
279
|
+
screen_height: software_renderer.height,
|
|
280
|
+
line_thickness_multiplier: 0.8
|
|
281
|
+
});
|
|
282
|
+
const g2 = computeGridLineIntensity({
|
|
283
|
+
world_x,
|
|
284
|
+
world_z,
|
|
285
|
+
cell_size: section_size,
|
|
286
|
+
dist,
|
|
287
|
+
screen_width: software_renderer.width,
|
|
288
|
+
screen_height: software_renderer.height,
|
|
289
|
+
line_thickness_multiplier: 0.5
|
|
290
|
+
// Thicker section lines
|
|
291
|
+
});
|
|
292
|
+
const grid_strength = Math.max(g1, g2);
|
|
293
|
+
if (grid_strength <= 0) continue;
|
|
294
|
+
const final_r = grid_color[0] * (1 - g2) + section_color[0] * g2;
|
|
295
|
+
const final_g = grid_color[1] * (1 - g2) + section_color[1] * g2;
|
|
296
|
+
const final_b = grid_color[2] * (1 - g2) + section_color[2] * g2;
|
|
297
|
+
const final_alpha = grid_strength * alpha;
|
|
298
|
+
if (final_alpha <= 0.01) continue;
|
|
299
|
+
let out_r = final_r;
|
|
300
|
+
let out_g = final_g;
|
|
301
|
+
let out_b = final_b;
|
|
302
|
+
if (gamma_out) {
|
|
303
|
+
out_r = srgbEncodeLinear01(clamp(out_r, 0, 1));
|
|
304
|
+
out_g = srgbEncodeLinear01(clamp(out_g, 0, 1));
|
|
305
|
+
out_b = srgbEncodeLinear01(clamp(out_b, 0, 1));
|
|
306
|
+
} else {
|
|
307
|
+
out_r = clamp(out_r, 0, 1);
|
|
308
|
+
out_g = clamp(out_g, 0, 1);
|
|
309
|
+
out_b = clamp(out_b, 0, 1);
|
|
310
|
+
}
|
|
311
|
+
const dst_idx = (y * software_renderer.width + x) * 4;
|
|
312
|
+
const dst_r = (software_renderer.buffer[dst_idx + 0] ?? 0) / 255;
|
|
313
|
+
const dst_g = (software_renderer.buffer[dst_idx + 1] ?? 0) / 255;
|
|
314
|
+
const dst_b = (software_renderer.buffer[dst_idx + 2] ?? 0) / 255;
|
|
315
|
+
const dst_a = (software_renderer.buffer[dst_idx + 3] ?? 0) / 255;
|
|
316
|
+
const one_minus_a = 1 - final_alpha;
|
|
317
|
+
const blend_r = out_r * final_alpha + dst_r * one_minus_a;
|
|
318
|
+
const blend_g = out_g * final_alpha + dst_g * one_minus_a;
|
|
319
|
+
const blend_b = out_b * final_alpha + dst_b * one_minus_a;
|
|
320
|
+
const blend_a = final_alpha + dst_a * one_minus_a;
|
|
321
|
+
software_renderer.setPixel(
|
|
322
|
+
x,
|
|
323
|
+
y,
|
|
324
|
+
clamp(blend_r, 0, 1) * 255 | 0,
|
|
325
|
+
clamp(blend_g, 0, 1) * 255 | 0,
|
|
326
|
+
clamp(blend_b, 0, 1) * 255 | 0,
|
|
327
|
+
clamp(blend_a, 0, 1) * 255 | 0
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
135
333
|
// lib/render/getDefaultRenderOptions.ts
|
|
136
334
|
var DEFAULT_LIGHT_DIR = [-0.4, -0.9, -0.2];
|
|
335
|
+
function hexToRgb(hex) {
|
|
336
|
+
const cleanHex = hex.replace(/^#/, "");
|
|
337
|
+
if (!/^[0-9A-Fa-f]{6}$/.test(cleanHex)) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
const r = Number.parseInt(cleanHex.substring(0, 2), 16) / 255;
|
|
341
|
+
const g = Number.parseInt(cleanHex.substring(2, 4), 16) / 255;
|
|
342
|
+
const b = Number.parseInt(cleanHex.substring(4, 6), 16) / 255;
|
|
343
|
+
return [r, g, b];
|
|
344
|
+
}
|
|
137
345
|
var DEFAULT_RENDER_OPTIONS = {
|
|
138
346
|
width: 800,
|
|
139
347
|
height: 600,
|
|
348
|
+
supersampling: 1,
|
|
140
349
|
fov: 60,
|
|
141
350
|
cull: true,
|
|
142
351
|
gamma: true,
|
|
@@ -151,8 +360,21 @@ function getDefaultRenderOptions() {
|
|
|
151
360
|
return { ...DEFAULT_RENDER_OPTIONS };
|
|
152
361
|
}
|
|
153
362
|
|
|
363
|
+
// lib/render/resolveRenderOptions.ts
|
|
364
|
+
function resolveRenderOptions(options = {}) {
|
|
365
|
+
const supersampling = typeof options.supersampling === "number" && Number.isFinite(options.supersampling) ? Math.max(1, Math.floor(options.supersampling)) : DEFAULT_RENDER_OPTIONS.supersampling;
|
|
366
|
+
return {
|
|
367
|
+
...DEFAULT_RENDER_OPTIONS,
|
|
368
|
+
...options,
|
|
369
|
+
supersampling,
|
|
370
|
+
lightDir: options.lightDir != null ? options.lightDir : DEFAULT_RENDER_OPTIONS.lightDir,
|
|
371
|
+
camPos: options.camPos !== void 0 ? options.camPos : DEFAULT_RENDER_OPTIONS.camPos,
|
|
372
|
+
lookAt: options.lookAt !== void 0 ? options.lookAt : DEFAULT_RENDER_OPTIONS.lookAt
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
154
376
|
// lib/render/SoftwareRenderer.ts
|
|
155
|
-
import { mat3, mat4 as
|
|
377
|
+
import { mat3, mat4 as mat44, vec3 as vec32, vec4 as vec43 } from "gl-matrix";
|
|
156
378
|
|
|
157
379
|
// lib/gltf/computeSmoothNormals.ts
|
|
158
380
|
function computeSmoothNormals(positions, indices) {
|
|
@@ -225,15 +447,6 @@ function mulColor(a, b) {
|
|
|
225
447
|
return [a[0] * b[0], a[1] * b[1], a[2] * b[2], a[3] * b[3]];
|
|
226
448
|
}
|
|
227
449
|
|
|
228
|
-
// lib/utils/srgbEncodeLinear01.ts
|
|
229
|
-
function srgbEncodeLinear01(linear) {
|
|
230
|
-
if (linear <= 31308e-7) return 12.92 * linear;
|
|
231
|
-
return 1.055 * Math.pow(linear, 1 / 2.4) - 0.055;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// lib/utils/clamp.ts
|
|
235
|
-
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
236
|
-
|
|
237
450
|
// lib/render/SoftwareRenderer.ts
|
|
238
451
|
var SoftwareRenderer = class {
|
|
239
452
|
width;
|
|
@@ -273,21 +486,21 @@ var SoftwareRenderer = class {
|
|
|
273
486
|
if (!indices) return;
|
|
274
487
|
const view = camera.view;
|
|
275
488
|
const proj = camera.proj;
|
|
276
|
-
const mvp =
|
|
277
|
-
|
|
489
|
+
const mvp = mat44.create();
|
|
490
|
+
mat44.multiply(mvp, proj, mat44.multiply(mat44.create(), view, model));
|
|
278
491
|
const vertexCount = positions.length / 3 | 0;
|
|
279
492
|
const vScreen = new Array(vertexCount);
|
|
280
493
|
const vInvW = new Float32Array(vertexCount);
|
|
281
494
|
const vNDCz = new Float32Array(vertexCount);
|
|
282
495
|
for (let i = 0; i < vertexCount; i++) {
|
|
283
|
-
const p =
|
|
496
|
+
const p = vec43.fromValues(
|
|
284
497
|
positions[i * 3 + 0],
|
|
285
498
|
positions[i * 3 + 1],
|
|
286
499
|
positions[i * 3 + 2],
|
|
287
500
|
1
|
|
288
501
|
);
|
|
289
|
-
const c =
|
|
290
|
-
|
|
502
|
+
const c = vec43.create();
|
|
503
|
+
vec43.transformMat4(c, p, mvp);
|
|
291
504
|
const invW = 1 / c[3];
|
|
292
505
|
vInvW[i] = invW;
|
|
293
506
|
if (!isFinite(invW)) {
|
|
@@ -387,8 +600,8 @@ var SoftwareRenderer = class {
|
|
|
387
600
|
const { positions, normals, uvs, indices, model, colors } = mesh;
|
|
388
601
|
const view = camera.view;
|
|
389
602
|
const proj = camera.proj;
|
|
390
|
-
const mvp =
|
|
391
|
-
|
|
603
|
+
const mvp = mat44.create();
|
|
604
|
+
mat44.multiply(mvp, proj, mat44.multiply(mat44.create(), view, model));
|
|
392
605
|
const normalMat = mat3.create();
|
|
393
606
|
mat3.normalFromMat4(normalMat, model);
|
|
394
607
|
const vertexCount = positions.length / 3 | 0;
|
|
@@ -407,14 +620,14 @@ var SoftwareRenderer = class {
|
|
|
407
620
|
const vWorldN = new Array(vertexCount);
|
|
408
621
|
const vColor = new Array(vertexCount);
|
|
409
622
|
for (let i = 0; i < vertexCount; i++) {
|
|
410
|
-
const p =
|
|
623
|
+
const p = vec43.fromValues(
|
|
411
624
|
positions[i * 3 + 0],
|
|
412
625
|
positions[i * 3 + 1],
|
|
413
626
|
positions[i * 3 + 2],
|
|
414
627
|
1
|
|
415
628
|
);
|
|
416
|
-
const c =
|
|
417
|
-
|
|
629
|
+
const c = vec43.create();
|
|
630
|
+
vec43.transformMat4(c, p, mvp);
|
|
418
631
|
const invW = 1 / c[3];
|
|
419
632
|
const ndcX = c[0] * invW;
|
|
420
633
|
const ndcY = c[1] * invW;
|
|
@@ -593,180 +806,30 @@ function edge(a, b, p) {
|
|
|
593
806
|
return (p[0] - a[0]) * (b[1] - a[1]) - (p[1] - a[1]) * (b[0] - a[0]);
|
|
594
807
|
}
|
|
595
808
|
|
|
596
|
-
// lib/render/resolveRenderOptions.ts
|
|
597
|
-
function resolveRenderOptions(options = {}) {
|
|
598
|
-
return {
|
|
599
|
-
...DEFAULT_RENDER_OPTIONS,
|
|
600
|
-
...options,
|
|
601
|
-
lightDir: options.lightDir != null ? options.lightDir : DEFAULT_RENDER_OPTIONS.lightDir,
|
|
602
|
-
camPos: options.camPos !== void 0 ? options.camPos : DEFAULT_RENDER_OPTIONS.camPos,
|
|
603
|
-
lookAt: options.lookAt !== void 0 ? options.lookAt : DEFAULT_RENDER_OPTIONS.lookAt
|
|
604
|
-
};
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// lib/render/drawInfiniteGrid.ts
|
|
608
|
-
import { mat4 as mat44, vec4 as vec43 } from "gl-matrix";
|
|
609
|
-
function computeGridLineIntensity(params) {
|
|
610
|
-
const rx = params.world_x / params.cell_size;
|
|
611
|
-
const rz = params.world_z / params.cell_size;
|
|
612
|
-
const fract_x = rx - Math.floor(rx + 0.5);
|
|
613
|
-
const fract_z = rz - Math.floor(rz + 0.5);
|
|
614
|
-
const grid_x = Math.abs(fract_x);
|
|
615
|
-
const grid_z = Math.abs(fract_z);
|
|
616
|
-
const pixel_size_in_world = params.dist / Math.min(params.screen_width, params.screen_height);
|
|
617
|
-
const fwidth_x = pixel_size_in_world / params.cell_size;
|
|
618
|
-
const fwidth_z = pixel_size_in_world / params.cell_size;
|
|
619
|
-
const line_x = grid_x / (fwidth_x + 1e-4);
|
|
620
|
-
const line_z = grid_z / (fwidth_z + 1e-4);
|
|
621
|
-
const line = Math.min(line_x, line_z);
|
|
622
|
-
return 1 - Math.min(line * params.line_thickness_multiplier, 1);
|
|
623
|
-
}
|
|
624
|
-
function computeGridFadeAlpha(params) {
|
|
625
|
-
const fade_start = params.fade_distance;
|
|
626
|
-
const fade_end = params.fade_distance * params.fade_strength;
|
|
627
|
-
return 1 - Math.min(
|
|
628
|
-
1,
|
|
629
|
-
Math.max(0, (params.dist - fade_start) / (fade_end - fade_start))
|
|
630
|
-
);
|
|
631
|
-
}
|
|
632
|
-
function drawInfiniteGrid(software_renderer, params) {
|
|
633
|
-
const grid_y = params.grid_y ?? 0;
|
|
634
|
-
const cell_size = params.cell_size ?? 1;
|
|
635
|
-
const section_size = params.section_size ?? 10;
|
|
636
|
-
const fade_distance = params.fade_distance ?? 100;
|
|
637
|
-
const fade_strength = params.fade_strength ?? 1.5;
|
|
638
|
-
const grid_color = params.grid_color ?? [0.93, 0.93, 0.93];
|
|
639
|
-
const section_color = params.section_color ?? [0.8, 0.8, 1];
|
|
640
|
-
const gamma_out = params.gamma_out ?? true;
|
|
641
|
-
const camera = params.camera;
|
|
642
|
-
const view = camera.view;
|
|
643
|
-
const proj = camera.proj;
|
|
644
|
-
const inv_view = mat44.create();
|
|
645
|
-
mat44.invert(inv_view, view);
|
|
646
|
-
const cam_pos_x = inv_view[12];
|
|
647
|
-
const cam_pos_y = inv_view[13];
|
|
648
|
-
const cam_pos_z = inv_view[14];
|
|
649
|
-
const vp = mat44.create();
|
|
650
|
-
mat44.multiply(vp, proj, view);
|
|
651
|
-
const inv_vp = mat44.create();
|
|
652
|
-
mat44.invert(inv_vp, vp);
|
|
653
|
-
for (let y = 0; y < software_renderer.height; y++) {
|
|
654
|
-
for (let x = 0; x < software_renderer.width; x++) {
|
|
655
|
-
const ndc_x = x / (software_renderer.width - 1) * 2 - 1;
|
|
656
|
-
const ndc_y = 1 - y / (software_renderer.height - 1) * 2;
|
|
657
|
-
const near_point = vec43.fromValues(ndc_x, ndc_y, -1, 1);
|
|
658
|
-
const far_point = vec43.fromValues(ndc_x, ndc_y, 1, 1);
|
|
659
|
-
vec43.transformMat4(near_point, near_point, inv_vp);
|
|
660
|
-
vec43.transformMat4(far_point, far_point, inv_vp);
|
|
661
|
-
near_point[0] /= near_point[3];
|
|
662
|
-
near_point[1] /= near_point[3];
|
|
663
|
-
near_point[2] /= near_point[3];
|
|
664
|
-
far_point[0] /= far_point[3];
|
|
665
|
-
far_point[1] /= far_point[3];
|
|
666
|
-
far_point[2] /= far_point[3];
|
|
667
|
-
const ray_dir_x = far_point[0] - near_point[0];
|
|
668
|
-
const ray_dir_y = far_point[1] - near_point[1];
|
|
669
|
-
const ray_dir_z = far_point[2] - near_point[2];
|
|
670
|
-
if (Math.abs(ray_dir_y) < 1e-6) continue;
|
|
671
|
-
const t = (grid_y - near_point[1]) / ray_dir_y;
|
|
672
|
-
if (t < 0 || t > 1) continue;
|
|
673
|
-
const world_x = near_point[0] + t * ray_dir_x;
|
|
674
|
-
const world_z = near_point[2] + t * ray_dir_z;
|
|
675
|
-
const world_pt = vec43.fromValues(world_x, grid_y, world_z, 1);
|
|
676
|
-
const clip_pt = vec43.create();
|
|
677
|
-
vec43.transformMat4(clip_pt, world_pt, vp);
|
|
678
|
-
const ndc_z = clip_pt[2] / clip_pt[3];
|
|
679
|
-
const depth_01 = ndc_z * 0.5 + 0.5;
|
|
680
|
-
const di = y * software_renderer.width + x;
|
|
681
|
-
if (depth_01 >= software_renderer.depth[di]) continue;
|
|
682
|
-
const dx = world_x - cam_pos_x;
|
|
683
|
-
const dy = grid_y - cam_pos_y;
|
|
684
|
-
const dz = world_z - cam_pos_z;
|
|
685
|
-
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
686
|
-
const alpha = computeGridFadeAlpha({
|
|
687
|
-
dist,
|
|
688
|
-
fade_distance,
|
|
689
|
-
fade_strength
|
|
690
|
-
});
|
|
691
|
-
if (alpha <= 0) continue;
|
|
692
|
-
const g1 = computeGridLineIntensity({
|
|
693
|
-
world_x,
|
|
694
|
-
world_z,
|
|
695
|
-
cell_size,
|
|
696
|
-
dist,
|
|
697
|
-
screen_width: software_renderer.width,
|
|
698
|
-
screen_height: software_renderer.height,
|
|
699
|
-
line_thickness_multiplier: 0.8
|
|
700
|
-
});
|
|
701
|
-
const g2 = computeGridLineIntensity({
|
|
702
|
-
world_x,
|
|
703
|
-
world_z,
|
|
704
|
-
cell_size: section_size,
|
|
705
|
-
dist,
|
|
706
|
-
screen_width: software_renderer.width,
|
|
707
|
-
screen_height: software_renderer.height,
|
|
708
|
-
line_thickness_multiplier: 0.5
|
|
709
|
-
// Thicker section lines
|
|
710
|
-
});
|
|
711
|
-
const grid_strength = Math.max(g1, g2);
|
|
712
|
-
if (grid_strength <= 0) continue;
|
|
713
|
-
const final_r = grid_color[0] * (1 - g2) + section_color[0] * g2;
|
|
714
|
-
const final_g = grid_color[1] * (1 - g2) + section_color[1] * g2;
|
|
715
|
-
const final_b = grid_color[2] * (1 - g2) + section_color[2] * g2;
|
|
716
|
-
const final_alpha = grid_strength * alpha;
|
|
717
|
-
if (final_alpha <= 0.01) continue;
|
|
718
|
-
let out_r = final_r;
|
|
719
|
-
let out_g = final_g;
|
|
720
|
-
let out_b = final_b;
|
|
721
|
-
if (gamma_out) {
|
|
722
|
-
out_r = srgbEncodeLinear01(clamp(out_r, 0, 1));
|
|
723
|
-
out_g = srgbEncodeLinear01(clamp(out_g, 0, 1));
|
|
724
|
-
out_b = srgbEncodeLinear01(clamp(out_b, 0, 1));
|
|
725
|
-
} else {
|
|
726
|
-
out_r = clamp(out_r, 0, 1);
|
|
727
|
-
out_g = clamp(out_g, 0, 1);
|
|
728
|
-
out_b = clamp(out_b, 0, 1);
|
|
729
|
-
}
|
|
730
|
-
const dst_idx = (y * software_renderer.width + x) * 4;
|
|
731
|
-
const dst_r = (software_renderer.buffer[dst_idx + 0] ?? 0) / 255;
|
|
732
|
-
const dst_g = (software_renderer.buffer[dst_idx + 1] ?? 0) / 255;
|
|
733
|
-
const dst_b = (software_renderer.buffer[dst_idx + 2] ?? 0) / 255;
|
|
734
|
-
const dst_a = (software_renderer.buffer[dst_idx + 3] ?? 0) / 255;
|
|
735
|
-
const one_minus_a = 1 - final_alpha;
|
|
736
|
-
const blend_r = out_r * final_alpha + dst_r * one_minus_a;
|
|
737
|
-
const blend_g = out_g * final_alpha + dst_g * one_minus_a;
|
|
738
|
-
const blend_b = out_b * final_alpha + dst_b * one_minus_a;
|
|
739
|
-
const blend_a = final_alpha + dst_a * one_minus_a;
|
|
740
|
-
software_renderer.setPixel(
|
|
741
|
-
x,
|
|
742
|
-
y,
|
|
743
|
-
clamp(blend_r, 0, 1) * 255 | 0,
|
|
744
|
-
clamp(blend_g, 0, 1) * 255 | 0,
|
|
745
|
-
clamp(blend_b, 0, 1) * 255 | 0,
|
|
746
|
-
clamp(blend_a, 0, 1) * 255 | 0
|
|
747
|
-
);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
809
|
// lib/render/renderDrawCalls.ts
|
|
753
810
|
function renderDrawCalls(drawCalls, optionsInput = {}, imageFactory = createUint8Bitmap) {
|
|
754
811
|
const options = resolveRenderOptions(optionsInput);
|
|
812
|
+
const renderWidth = options.width * options.supersampling;
|
|
813
|
+
const renderHeight = options.height * options.supersampling;
|
|
755
814
|
const camera = buildCamera(
|
|
756
815
|
drawCalls,
|
|
757
|
-
|
|
758
|
-
|
|
816
|
+
renderWidth,
|
|
817
|
+
renderHeight,
|
|
759
818
|
options.fov,
|
|
760
819
|
options.camPos ?? null,
|
|
761
820
|
options.lookAt ?? null
|
|
762
821
|
);
|
|
763
|
-
const renderer = new SoftwareRenderer(
|
|
764
|
-
|
|
765
|
-
options.height,
|
|
766
|
-
imageFactory
|
|
767
|
-
);
|
|
822
|
+
const renderer = new SoftwareRenderer(renderWidth, renderHeight, imageFactory);
|
|
823
|
+
let rgb = null;
|
|
768
824
|
if (options.backgroundColor) {
|
|
769
|
-
|
|
825
|
+
if (typeof options.backgroundColor === "string") {
|
|
826
|
+
rgb = hexToRgb(options.backgroundColor);
|
|
827
|
+
} else {
|
|
828
|
+
rgb = options.backgroundColor;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
if (rgb) {
|
|
832
|
+
const [r, g, b] = rgb;
|
|
770
833
|
renderer.clear([
|
|
771
834
|
Math.round(r * 255),
|
|
772
835
|
Math.round(g * 255),
|
|
@@ -852,7 +915,13 @@ function renderDrawCalls(drawCalls, optionsInput = {}, imageFactory = createUint
|
|
|
852
915
|
});
|
|
853
916
|
}
|
|
854
917
|
renderGroup(blendDraws);
|
|
855
|
-
|
|
918
|
+
const bitmap = options.supersampling > 1 ? downsampleBitmap(
|
|
919
|
+
renderer.bitmap,
|
|
920
|
+
options.width,
|
|
921
|
+
options.height,
|
|
922
|
+
imageFactory
|
|
923
|
+
) : renderer.bitmap;
|
|
924
|
+
return { bitmap, camera, options };
|
|
856
925
|
}
|
|
857
926
|
|
|
858
927
|
// lib/gltf/createInfiniteGrid.ts
|
|
@@ -1603,6 +1672,7 @@ export {
|
|
|
1603
1672
|
drawInfiniteGrid,
|
|
1604
1673
|
encodePNGToBuffer,
|
|
1605
1674
|
getDefaultRenderOptions,
|
|
1675
|
+
hexToRgb,
|
|
1606
1676
|
isJPG,
|
|
1607
1677
|
isPNG,
|
|
1608
1678
|
loadGLTFWithResourcesFromURL,
|