poppygl 0.0.14 → 0.0.16

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
@@ -35,6 +35,13 @@ interface GridOptions {
35
35
  y: number;
36
36
  z: number;
37
37
  }>;
38
+ infiniteGrid?: boolean;
39
+ cellSize?: number;
40
+ sectionSize?: number;
41
+ fadeDistance?: number;
42
+ fadeStrength?: number;
43
+ gridColor?: readonly [number, number, number];
44
+ sectionColor?: readonly [number, number, number];
38
45
  }
39
46
  interface GLTFResources {
40
47
  buffers: Uint8Array[];
@@ -80,6 +87,20 @@ declare function resolveRenderOptions(options?: RenderOptionsInput): RenderOptio
80
87
 
81
88
  declare function createGrid(options?: GridOptions): DrawCall;
82
89
 
90
+ /**
91
+ * Creates an infinite grid that matches the appearance of the 3d-viewer reference.
92
+ * This grid uses a special rendering approach with procedural generation and fading.
93
+ */
94
+ declare function createInfiniteGrid(options?: GridOptions): DrawCall & {
95
+ isInfiniteGrid: boolean;
96
+ cellSize: number;
97
+ sectionSize: number;
98
+ fadeDistance: number;
99
+ fadeStrength: number;
100
+ gridColor: readonly [number, number, number];
101
+ sectionColor: readonly [number, number, number];
102
+ };
103
+
83
104
  type GLTF = any;
84
105
  declare function createSceneFromGLTF(gltf: GLTF, resources: GLTFResources): GLTFScene;
85
106
 
@@ -130,6 +151,21 @@ declare class SoftwareRenderer {
130
151
  drawMesh(mesh: DrawCall, camera: Camera, light: LightSettings, material: Material, cullBackFaces?: boolean, gammaOut?: boolean): void;
131
152
  }
132
153
 
154
+ /**
155
+ * Draws an infinite grid with fade-out effect matching 3d-viewer appearance
156
+ */
157
+ declare function drawInfiniteGrid(software_renderer: SoftwareRenderer, params: {
158
+ camera: Camera;
159
+ grid_y?: number;
160
+ cell_size?: number;
161
+ section_size?: number;
162
+ fade_distance?: number;
163
+ fade_strength?: number;
164
+ grid_color?: readonly [number, number, number];
165
+ section_color?: readonly [number, number, number];
166
+ gamma_out?: boolean;
167
+ }): void;
168
+
133
169
  declare const pureImageFactory: ImageFactory;
134
170
 
135
171
  declare function encodePNGToBuffer(image: BitmapLike): Promise<Buffer>;
@@ -145,4 +181,4 @@ declare function renderGLTFToPNGBuffer(gltfPathOrString: string, options?: Rende
145
181
 
146
182
  declare function renderSceneFromGLTF(scene: GLTFScene, options?: RenderOptionsInput, imageFactory?: ImageFactory): RenderResult;
147
183
 
148
- 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 };
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 };
package/dist/index.js CHANGED
@@ -604,6 +604,151 @@ function resolveRenderOptions(options = {}) {
604
604
  };
605
605
  }
606
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
+
607
752
  // lib/render/renderDrawCalls.ts
608
753
  function renderDrawCalls(drawCalls, optionsInput = {}, imageFactory = createUint8Bitmap) {
609
754
  const options = resolveRenderOptions(optionsInput);
@@ -632,31 +777,36 @@ function renderDrawCalls(drawCalls, optionsInput = {}, imageFactory = createUint
632
777
  renderer.clear([0, 0, 0, 0]);
633
778
  }
634
779
  const allDrawCalls = [...drawCalls];
780
+ let infiniteGridOptions = null;
635
781
  if (options.grid) {
636
782
  const userGridOptions = typeof options.grid === "boolean" ? {} : options.grid;
637
- const aabb = computeWorldAABB(drawCalls);
638
- const sizeX = aabb.max[0] - aabb.min[0];
639
- const sizeZ = aabb.max[2] - aabb.min[2];
640
- const maxSize = Math.max(sizeX, sizeZ);
641
- const defaultSize = Math.ceil(maxSize * 1.2 / 2) * 2;
642
- const defaultOffset = {
643
- x: (aabb.min[0] + aabb.max[0]) / 2,
644
- y: aabb.min[1],
645
- z: (aabb.min[2] + aabb.max[2]) / 2
646
- };
647
- const defaultGridOptions = {
648
- size: defaultSize > 0 ? defaultSize : 10,
649
- offset: defaultOffset
650
- };
651
- const finalGridOptions = {
652
- ...defaultGridOptions,
653
- ...userGridOptions,
654
- offset: {
655
- ...defaultGridOptions.offset,
656
- ...userGridOptions.offset
657
- }
658
- };
659
- allDrawCalls.push(createGrid(finalGridOptions));
783
+ if (userGridOptions.infiniteGrid) {
784
+ infiniteGridOptions = userGridOptions;
785
+ } else {
786
+ const aabb = computeWorldAABB(drawCalls);
787
+ const sizeX = aabb.max[0] - aabb.min[0];
788
+ const sizeZ = aabb.max[2] - aabb.min[2];
789
+ const maxSize = Math.max(sizeX, sizeZ);
790
+ const defaultSize = Math.ceil(maxSize * 1.2 / 2) * 2;
791
+ const defaultOffset = {
792
+ x: (aabb.min[0] + aabb.max[0]) / 2,
793
+ y: aabb.min[1],
794
+ z: (aabb.min[2] + aabb.max[2]) / 2
795
+ };
796
+ const defaultGridOptions = {
797
+ size: defaultSize > 0 ? defaultSize : 10,
798
+ offset: defaultOffset
799
+ };
800
+ const finalGridOptions = {
801
+ ...defaultGridOptions,
802
+ ...userGridOptions,
803
+ offset: {
804
+ ...defaultGridOptions.offset,
805
+ ...userGridOptions.offset
806
+ }
807
+ };
808
+ allDrawCalls.push(createGrid(finalGridOptions));
809
+ }
660
810
  }
661
811
  const opaqueDraws = allDrawCalls.filter(
662
812
  (dc) => (dc.material.alphaMode ?? "OPAQUE") === "OPAQUE"
@@ -685,12 +835,79 @@ function renderDrawCalls(drawCalls, optionsInput = {}, imageFactory = createUint
685
835
  };
686
836
  renderGroup(opaqueDraws);
687
837
  renderGroup(maskDraws);
838
+ if (infiniteGridOptions) {
839
+ const aabb = computeWorldAABB(drawCalls);
840
+ const modelCenterY = (aabb.min[1] + aabb.max[1]) / 2;
841
+ const gridY = infiniteGridOptions.offset?.y ?? modelCenterY;
842
+ drawInfiniteGrid(renderer, {
843
+ camera,
844
+ grid_y: gridY,
845
+ cell_size: infiniteGridOptions.cellSize,
846
+ section_size: infiniteGridOptions.sectionSize,
847
+ fade_distance: infiniteGridOptions.fadeDistance,
848
+ fade_strength: infiniteGridOptions.fadeStrength,
849
+ grid_color: infiniteGridOptions.gridColor,
850
+ section_color: infiniteGridOptions.sectionColor,
851
+ gamma_out: options.gamma
852
+ });
853
+ }
688
854
  renderGroup(blendDraws);
689
855
  return { bitmap: renderer.bitmap, camera, options };
690
856
  }
691
857
 
858
+ // lib/gltf/createInfiniteGrid.ts
859
+ import { mat4 as mat45 } from "gl-matrix";
860
+ function createInfiniteGrid(options = {}) {
861
+ const cellSize = options.cellSize ?? 1;
862
+ const sectionSize = options.sectionSize ?? 10;
863
+ const fadeDistance = options.fadeDistance ?? 100;
864
+ const fadeStrength = options.fadeStrength ?? 1.5;
865
+ const gridColor = options.gridColor ?? [0.93, 0.93, 0.93];
866
+ const sectionColor = options.sectionColor ?? [0.8, 0.8, 1];
867
+ const size = 1e3;
868
+ const positions = new Float32Array([
869
+ -size,
870
+ 0,
871
+ -size,
872
+ size,
873
+ 0,
874
+ -size,
875
+ size,
876
+ 0,
877
+ size,
878
+ -size,
879
+ 0,
880
+ size
881
+ ]);
882
+ const indices = new Uint32Array([0, 1, 2, 0, 2, 3]);
883
+ const model = mat45.create();
884
+ const offset = options.offset ?? {};
885
+ mat45.fromTranslation(model, [offset.x ?? 0, offset.y ?? 0, offset.z ?? 0]);
886
+ return {
887
+ positions,
888
+ indices,
889
+ normals: null,
890
+ uvs: null,
891
+ model,
892
+ material: {
893
+ baseColorFactor: [1, 1, 1, 1],
894
+ baseColorTexture: null,
895
+ alphaMode: "BLEND"
896
+ },
897
+ mode: 4,
898
+ // triangles
899
+ isInfiniteGrid: true,
900
+ cellSize,
901
+ sectionSize,
902
+ fadeDistance,
903
+ fadeStrength,
904
+ gridColor,
905
+ sectionColor
906
+ };
907
+ }
908
+
692
909
  // lib/gltf/createSceneFromGLTF.ts
693
- import { mat4 as mat44 } from "gl-matrix";
910
+ import { mat4 as mat46 } from "gl-matrix";
694
911
  var COMPONENT_INFO = {
695
912
  5120: {
696
913
  name: "BYTE",
@@ -888,17 +1105,17 @@ function readIndices(ctx, accessorIndex) {
888
1105
  }
889
1106
  function nodeLocalMatrix(node) {
890
1107
  if (node.matrix) {
891
- const m2 = mat44.create();
1108
+ const m2 = mat46.create();
892
1109
  for (let i = 0; i < 16; i++) m2[i] = node.matrix[i];
893
1110
  return m2;
894
1111
  }
895
1112
  const translation = node.translation || [0, 0, 0];
896
1113
  const rotation = node.rotation || [0, 0, 0, 1];
897
1114
  const scale = node.scale || [1, 1, 1];
898
- const m = mat44.create();
899
- const tmp = mat44.create();
900
- mat44.fromRotationTranslationScale(tmp, rotation, translation, scale);
901
- mat44.copy(m, tmp);
1115
+ const m = mat46.create();
1116
+ const tmp = mat46.create();
1117
+ mat46.fromRotationTranslationScale(tmp, rotation, translation, scale);
1118
+ mat46.copy(m, tmp);
902
1119
  return m;
903
1120
  }
904
1121
  function getMaterial(gltf, textures, images, materialIndex) {
@@ -936,8 +1153,8 @@ function createSceneFromGLTF(gltf, resources) {
936
1153
  const node = nodes[nodeIndex];
937
1154
  if (!node) return;
938
1155
  const local = nodeLocalMatrix(node);
939
- const world = mat44.create();
940
- mat44.multiply(world, parentMatrix, local);
1156
+ const world = mat46.create();
1157
+ mat46.multiply(world, parentMatrix, local);
941
1158
  if (Number.isInteger(node.mesh)) {
942
1159
  const mesh = meshes[node.mesh];
943
1160
  if (mesh) {
@@ -975,7 +1192,7 @@ function createSceneFromGLTF(gltf, resources) {
975
1192
  traverse(childIndex, world);
976
1193
  }
977
1194
  }
978
- const identity = mat44.create();
1195
+ const identity = mat46.create();
979
1196
  for (const rootIndex of scene.nodes || []) {
980
1197
  traverse(rootIndex, identity);
981
1198
  }
@@ -1379,9 +1596,11 @@ export {
1379
1596
  computeSmoothNormals,
1380
1597
  computeWorldAABB,
1381
1598
  createGrid,
1599
+ createInfiniteGrid,
1382
1600
  createSceneFromGLTF,
1383
1601
  createUint8Bitmap,
1384
1602
  decodeImageFromBuffer,
1603
+ drawInfiniteGrid,
1385
1604
  encodePNGToBuffer,
1386
1605
  getDefaultRenderOptions,
1387
1606
  isJPG,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "poppygl",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.14",
4
+ "version": "0.0.16",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/tscircuit/poppygl.git"