poppygl 0.0.12 → 0.0.13

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
@@ -10,23 +10,6 @@ interface BitmapLike {
10
10
  type ImageFactory = (width: number, height: number) => BitmapLike;
11
11
  declare const createUint8Bitmap: ImageFactory;
12
12
 
13
- declare const DEFAULT_LIGHT_DIR: readonly [-0.4, -0.9, -0.2];
14
- interface RenderOptions {
15
- width: number;
16
- height: number;
17
- fov: number;
18
- cull: boolean;
19
- gamma: boolean;
20
- ambient: number;
21
- lightDir: readonly [number, number, number];
22
- camPos?: readonly [number, number, number] | null;
23
- lookAt?: readonly [number, number, number] | null;
24
- backgroundColor?: readonly [number, number, number] | null;
25
- }
26
- type RenderOptionsInput = Partial<RenderOptions>;
27
- declare const DEFAULT_RENDER_OPTIONS: RenderOptions;
28
- declare function getDefaultRenderOptions(): RenderOptions;
29
-
30
13
  interface Material {
31
14
  baseColorFactor: [number, number, number, number];
32
15
  baseColorTexture: BitmapLike | null;
@@ -39,6 +22,17 @@ interface DrawCall {
39
22
  model: mat4;
40
23
  material: Material;
41
24
  colors?: Float32Array | null;
25
+ mode?: number;
26
+ }
27
+ interface GridOptions {
28
+ size?: number | readonly [number, number, number];
29
+ divisions?: number;
30
+ color?: readonly [number, number, number];
31
+ offset?: Partial<{
32
+ x: number;
33
+ y: number;
34
+ z: number;
35
+ }>;
42
36
  }
43
37
  interface GLTFResources {
44
38
  buffers: Uint8Array[];
@@ -49,6 +43,24 @@ interface GLTFScene {
49
43
  gltf: any;
50
44
  }
51
45
 
46
+ declare const DEFAULT_LIGHT_DIR: readonly [-0.4, -0.9, -0.2];
47
+ interface RenderOptions {
48
+ width: number;
49
+ height: number;
50
+ fov: number;
51
+ cull: boolean;
52
+ gamma: boolean;
53
+ ambient: number;
54
+ lightDir: readonly [number, number, number];
55
+ camPos?: readonly [number, number, number] | null;
56
+ lookAt?: readonly [number, number, number] | null;
57
+ backgroundColor?: readonly [number, number, number] | null;
58
+ grid?: boolean | GridOptions;
59
+ }
60
+ type RenderOptionsInput = Partial<RenderOptions>;
61
+ declare const DEFAULT_RENDER_OPTIONS: RenderOptions;
62
+ declare function getDefaultRenderOptions(): RenderOptions;
63
+
52
64
  interface Camera {
53
65
  view: mat4;
54
66
  proj: mat4;
@@ -64,6 +76,8 @@ declare function renderDrawCalls(drawCalls: DrawCall[], optionsInput?: RenderOpt
64
76
 
65
77
  declare function resolveRenderOptions(options?: RenderOptionsInput): RenderOptions;
66
78
 
79
+ declare function createGrid(options?: GridOptions): DrawCall;
80
+
67
81
  type GLTF = any;
68
82
  declare function createSceneFromGLTF(gltf: GLTF, resources: GLTFResources): GLTFScene;
69
83
 
@@ -108,6 +122,7 @@ declare class SoftwareRenderer {
108
122
  get buffer(): Uint8Array<ArrayBufferLike> | Uint8ClampedArray<ArrayBufferLike>;
109
123
  clear(colorRGBA?: [number, number, number, number]): void;
110
124
  setPixel(x: number, y: number, r: number, g: number, b: number, a: number): void;
125
+ drawLines(mesh: DrawCall, camera: Camera, gammaOut?: boolean): void;
111
126
  sampleTextureNearest(img: BitmapLike | null, u: number, v: number): MutableRGBA;
112
127
  perspInterp(attrs: number[][], invWs: number[], lambdas: number[]): number[];
113
128
  drawMesh(mesh: DrawCall, camera: Camera, light: LightSettings, material: Material, cullBackFaces?: boolean, gammaOut?: boolean): void;
@@ -128,4 +143,4 @@ declare function renderGLTFToPNGBuffer(gltfPathOrString: string, options?: Rende
128
143
 
129
144
  declare function renderSceneFromGLTF(scene: GLTFScene, options?: RenderOptionsInput, imageFactory?: ImageFactory): RenderResult;
130
145
 
131
- export { type BitmapLike, type Camera, DEFAULT_LIGHT_DIR, DEFAULT_RENDER_OPTIONS, type DrawCall, type FetchLike, type GLTFResources, type GLTFScene, 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, createSceneFromGLTF, createUint8Bitmap, decodeImageFromBuffer, encodePNGToBuffer, getDefaultRenderOptions, isJPG, isPNG, loadGLTFWithResourcesFromURL, pureImageFactory, renderDrawCalls, renderGLTFToPNGBuffer, renderGLTFToPNGBufferFromGLBBuffer, renderGLTFToPNGBufferFromURL, renderSceneFromGLTF, resolveRenderOptions };
146
+ 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, createSceneFromGLTF, createUint8Bitmap, decodeImageFromBuffer, encodePNGToBuffer, getDefaultRenderOptions, isJPG, isPNG, loadGLTFWithResourcesFromURL, pureImageFactory, renderDrawCalls, renderGLTFToPNGBuffer, renderGLTFToPNGBufferFromGLBBuffer, renderGLTFToPNGBufferFromURL, renderSceneFromGLTF, resolveRenderOptions };
package/dist/index.js CHANGED
@@ -23,6 +23,12 @@ function computeWorldAABB(drawCalls) {
23
23
  max[2] = Math.max(max[2], z);
24
24
  }
25
25
  }
26
+ if (!isFinite(min[0])) {
27
+ return {
28
+ min: [-1, -1, -1],
29
+ max: [1, 1, 1]
30
+ };
31
+ }
26
32
  return { min, max };
27
33
  }
28
34
 
@@ -76,6 +82,49 @@ function buildCamera(drawCalls, width, height, fovDeg, camPos, lookAt) {
76
82
  return { view, proj };
77
83
  }
78
84
 
85
+ // lib/gltf/createGrid.ts
86
+ import { mat4 as mat42 } from "gl-matrix";
87
+ function createGrid(options = {}) {
88
+ const size = options.size ?? 10;
89
+ const sizeX = typeof size === "number" ? size : size[0];
90
+ const sizeZ = typeof size === "number" ? size : size[2];
91
+ const divisions = options.divisions ?? 10;
92
+ const color = options.color ?? [0.5, 0.5, 0.5];
93
+ const offset = { x: 0, y: 0, z: 0, ...options.offset };
94
+ const positions = [];
95
+ const indices = [];
96
+ let vertexIndex = 0;
97
+ const halfSizeX = sizeX / 2;
98
+ const halfSizeZ = sizeZ / 2;
99
+ const stepX = sizeX / divisions;
100
+ const stepZ = sizeZ / divisions;
101
+ for (let i = 0; i <= divisions; i++) {
102
+ const pX = -halfSizeX + i * stepX;
103
+ const pZ = -halfSizeZ + i * stepZ;
104
+ positions.push(pX, 0, -halfSizeZ);
105
+ positions.push(pX, 0, halfSizeZ);
106
+ indices.push(vertexIndex++, vertexIndex++);
107
+ positions.push(-halfSizeX, 0, pZ);
108
+ positions.push(halfSizeX, 0, pZ);
109
+ indices.push(vertexIndex++, vertexIndex++);
110
+ }
111
+ const model = mat42.create();
112
+ mat42.fromTranslation(model, [offset.x, offset.y, offset.z]);
113
+ return {
114
+ positions: new Float32Array(positions),
115
+ indices: new Uint32Array(indices),
116
+ normals: null,
117
+ uvs: null,
118
+ model,
119
+ material: {
120
+ baseColorFactor: [...color, 1],
121
+ baseColorTexture: null
122
+ },
123
+ mode: 1
124
+ // 1 = LINES
125
+ };
126
+ }
127
+
79
128
  // lib/image/createUint8Bitmap.ts
80
129
  var createUint8Bitmap = (width, height) => ({
81
130
  width,
@@ -95,14 +144,15 @@ var DEFAULT_RENDER_OPTIONS = {
95
144
  lightDir: DEFAULT_LIGHT_DIR,
96
145
  camPos: null,
97
146
  lookAt: null,
98
- backgroundColor: null
147
+ backgroundColor: null,
148
+ grid: false
99
149
  };
100
150
  function getDefaultRenderOptions() {
101
151
  return { ...DEFAULT_RENDER_OPTIONS };
102
152
  }
103
153
 
104
154
  // lib/render/SoftwareRenderer.ts
105
- import { mat3, mat4 as mat42, vec3 as vec32, vec4 as vec42 } from "gl-matrix";
155
+ import { mat3, mat4 as mat43, vec3 as vec32, vec4 as vec42 } from "gl-matrix";
106
156
 
107
157
  // lib/gltf/computeSmoothNormals.ts
108
158
  function computeSmoothNormals(positions, indices) {
@@ -218,6 +268,96 @@ var SoftwareRenderer = class {
218
268
  this.buffer[idx + 2] = b;
219
269
  this.buffer[idx + 3] = a;
220
270
  }
271
+ drawLines(mesh, camera, gammaOut = true) {
272
+ const { positions, indices, model, material } = mesh;
273
+ if (!indices) return;
274
+ const view = camera.view;
275
+ const proj = camera.proj;
276
+ const mvp = mat43.create();
277
+ mat43.multiply(mvp, proj, mat43.multiply(mat43.create(), view, model));
278
+ const vertexCount = positions.length / 3 | 0;
279
+ const vScreen = new Array(vertexCount);
280
+ const vInvW = new Float32Array(vertexCount);
281
+ const vNDCz = new Float32Array(vertexCount);
282
+ for (let i = 0; i < vertexCount; i++) {
283
+ const p = vec42.fromValues(
284
+ positions[i * 3 + 0],
285
+ positions[i * 3 + 1],
286
+ positions[i * 3 + 2],
287
+ 1
288
+ );
289
+ const c = vec42.create();
290
+ vec42.transformMat4(c, p, mvp);
291
+ const invW = 1 / c[3];
292
+ vInvW[i] = invW;
293
+ if (!isFinite(invW)) {
294
+ vScreen[i] = [NaN, NaN];
295
+ vNDCz[i] = NaN;
296
+ continue;
297
+ }
298
+ const ndcX = c[0] * invW;
299
+ const ndcY = c[1] * invW;
300
+ const ndcZ = c[2] * invW;
301
+ const sx = (ndcX * 0.5 + 0.5) * (this.width - 1);
302
+ const sy = (1 - (ndcY * 0.5 + 0.5)) * (this.height - 1);
303
+ vScreen[i] = [sx, sy];
304
+ vNDCz[i] = ndcZ;
305
+ }
306
+ let [r, g, b, a] = material.baseColorFactor;
307
+ if (gammaOut) {
308
+ r = srgbEncodeLinear01(clamp(r, 0, 1));
309
+ g = srgbEncodeLinear01(clamp(g, 0, 1));
310
+ b = srgbEncodeLinear01(clamp(b, 0, 1));
311
+ } else {
312
+ r = clamp(r, 0, 1);
313
+ g = clamp(g, 0, 1);
314
+ b = clamp(b, 0, 1);
315
+ }
316
+ const r255 = r * 255 | 0;
317
+ const g255 = g * 255 | 0;
318
+ const b255 = b * 255 | 0;
319
+ const a255 = clamp(a, 0, 1) * 255 | 0;
320
+ for (let i = 0; i < indices.length; i += 2) {
321
+ const i0 = indices[i + 0];
322
+ const i1 = indices[i + 1];
323
+ const v0s = vScreen[i0];
324
+ const v1s = vScreen[i1];
325
+ const z0_ndc = vNDCz[i0];
326
+ const z1_ndc = vNDCz[i1];
327
+ if (isNaN(v0s[0]) || isNaN(v1s[0])) continue;
328
+ const z0_01 = z0_ndc * 0.5 + 0.5;
329
+ const z1_01 = z1_ndc * 0.5 + 0.5;
330
+ if (z0_01 < 0 && z1_01 < 0 || z0_01 > 1 && z1_01 > 1) continue;
331
+ const x0 = Math.round(v0s[0]);
332
+ const y0 = Math.round(v0s[1]);
333
+ const x1 = Math.round(v1s[0]);
334
+ const y1 = Math.round(v1s[1]);
335
+ const dx = x1 - x0;
336
+ const dy = y1 - y0;
337
+ const steps = Math.max(Math.abs(dx), Math.abs(dy));
338
+ if (steps === 0) continue;
339
+ const xinc = dx / steps;
340
+ const yinc = dy / steps;
341
+ const zinc = (z1_01 - z0_01) / steps;
342
+ let x = x0;
343
+ let y = y0;
344
+ let z = z0_01;
345
+ for (let k = 0; k <= steps; k++) {
346
+ const xi = Math.round(x);
347
+ const yi = Math.round(y);
348
+ if (xi >= 0 && xi < this.width && yi >= 0 && yi < this.height && z >= 0 && z <= 1) {
349
+ const di = yi * this.width + xi;
350
+ if (z < this.depth[di]) {
351
+ this.depth[di] = z;
352
+ this.setPixel(xi, yi, r255, g255, b255, a255);
353
+ }
354
+ }
355
+ x += xinc;
356
+ y += yinc;
357
+ z += zinc;
358
+ }
359
+ }
360
+ }
221
361
  sampleTextureNearest(img, u, v) {
222
362
  if (!img) return [1, 1, 1, 1];
223
363
  const x = clamp(Math.floor(u * (img.width - 1)), 0, img.width - 1);
@@ -247,8 +387,8 @@ var SoftwareRenderer = class {
247
387
  const { positions, normals, uvs, indices, model, colors } = mesh;
248
388
  const view = camera.view;
249
389
  const proj = camera.proj;
250
- const mvp = mat42.create();
251
- mat42.multiply(mvp, proj, mat42.multiply(mat42.create(), view, model));
390
+ const mvp = mat43.create();
391
+ mat43.multiply(mvp, proj, mat43.multiply(mat43.create(), view, model));
252
392
  const normalMat = mat3.create();
253
393
  mat3.normalFromMat4(normalMat, model);
254
394
  const vertexCount = positions.length / 3 | 0;
@@ -468,21 +608,52 @@ function renderDrawCalls(drawCalls, optionsInput = {}, imageFactory = createUint
468
608
  } else {
469
609
  renderer.clear([0, 0, 0, 0]);
470
610
  }
471
- for (const dc of drawCalls) {
472
- renderer.drawMesh(
473
- dc,
474
- camera,
475
- { dir: options.lightDir, ambient: options.ambient },
476
- dc.material,
477
- options.cull,
478
- options.gamma
479
- );
611
+ const allDrawCalls = [...drawCalls];
612
+ if (options.grid) {
613
+ const userGridOptions = typeof options.grid === "boolean" ? {} : options.grid;
614
+ const aabb = computeWorldAABB(drawCalls);
615
+ const sizeX = aabb.max[0] - aabb.min[0];
616
+ const sizeZ = aabb.max[2] - aabb.min[2];
617
+ const maxSize = Math.max(sizeX, sizeZ);
618
+ const defaultSize = Math.ceil(maxSize * 1.2 / 2) * 2;
619
+ const defaultOffset = {
620
+ x: (aabb.min[0] + aabb.max[0]) / 2,
621
+ y: aabb.min[1],
622
+ z: (aabb.min[2] + aabb.max[2]) / 2
623
+ };
624
+ const defaultGridOptions = {
625
+ size: defaultSize > 0 ? defaultSize : 10,
626
+ offset: defaultOffset
627
+ };
628
+ const finalGridOptions = {
629
+ ...defaultGridOptions,
630
+ ...userGridOptions,
631
+ offset: {
632
+ ...defaultGridOptions.offset,
633
+ ...userGridOptions.offset
634
+ }
635
+ };
636
+ allDrawCalls.push(createGrid(finalGridOptions));
637
+ }
638
+ for (const dc of allDrawCalls) {
639
+ if (dc.mode === 1) {
640
+ renderer.drawLines(dc, camera, options.gamma);
641
+ } else {
642
+ renderer.drawMesh(
643
+ dc,
644
+ camera,
645
+ { dir: options.lightDir, ambient: options.ambient },
646
+ dc.material,
647
+ options.cull,
648
+ options.gamma
649
+ );
650
+ }
480
651
  }
481
652
  return { bitmap: renderer.bitmap, camera, options };
482
653
  }
483
654
 
484
655
  // lib/gltf/createSceneFromGLTF.ts
485
- import { mat4 as mat43 } from "gl-matrix";
656
+ import { mat4 as mat44 } from "gl-matrix";
486
657
  var COMPONENT_INFO = {
487
658
  5120: {
488
659
  name: "BYTE",
@@ -680,17 +851,17 @@ function readIndices(ctx, accessorIndex) {
680
851
  }
681
852
  function nodeLocalMatrix(node) {
682
853
  if (node.matrix) {
683
- const m2 = mat43.create();
854
+ const m2 = mat44.create();
684
855
  for (let i = 0; i < 16; i++) m2[i] = node.matrix[i];
685
856
  return m2;
686
857
  }
687
858
  const translation = node.translation || [0, 0, 0];
688
859
  const rotation = node.rotation || [0, 0, 0, 1];
689
860
  const scale = node.scale || [1, 1, 1];
690
- const m = mat43.create();
691
- const tmp = mat43.create();
692
- mat43.fromRotationTranslationScale(tmp, rotation, translation, scale);
693
- mat43.copy(m, tmp);
861
+ const m = mat44.create();
862
+ const tmp = mat44.create();
863
+ mat44.fromRotationTranslationScale(tmp, rotation, translation, scale);
864
+ mat44.copy(m, tmp);
694
865
  return m;
695
866
  }
696
867
  function getMaterial(gltf, textures, images, materialIndex) {
@@ -721,8 +892,8 @@ function createSceneFromGLTF(gltf, resources) {
721
892
  const node = nodes[nodeIndex];
722
893
  if (!node) return;
723
894
  const local = nodeLocalMatrix(node);
724
- const world = mat43.create();
725
- mat43.multiply(world, parentMatrix, local);
895
+ const world = mat44.create();
896
+ mat44.multiply(world, parentMatrix, local);
726
897
  if (Number.isInteger(node.mesh)) {
727
898
  const mesh = meshes[node.mesh];
728
899
  if (mesh) {
@@ -760,7 +931,7 @@ function createSceneFromGLTF(gltf, resources) {
760
931
  traverse(childIndex, world);
761
932
  }
762
933
  }
763
- const identity = mat43.create();
934
+ const identity = mat44.create();
764
935
  for (const rootIndex of scene.nodes || []) {
765
936
  traverse(rootIndex, identity);
766
937
  }
@@ -1163,6 +1334,7 @@ export {
1163
1334
  buildCamera,
1164
1335
  computeSmoothNormals,
1165
1336
  computeWorldAABB,
1337
+ createGrid,
1166
1338
  createSceneFromGLTF,
1167
1339
  createUint8Bitmap,
1168
1340
  decodeImageFromBuffer,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "poppygl",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.12",
4
+ "version": "0.0.13",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/tscircuit/poppygl.git"