poppygl 0.0.31 → 0.0.33

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
@@ -15,6 +15,9 @@ interface Material {
15
15
  baseColorTexture: BitmapLike | null;
16
16
  metallicFactor?: number;
17
17
  roughnessFactor?: number;
18
+ emissiveFactor?: [number, number, number];
19
+ emissiveTexture?: BitmapLike | null;
20
+ emissiveStrength?: number;
18
21
  alphaMode?: "OPAQUE" | "MASK" | "BLEND";
19
22
  alphaCutoff?: number;
20
23
  }
@@ -177,6 +180,7 @@ declare class StudioLighting {
177
180
  private occlusion;
178
181
  private eye;
179
182
  private shadowDirections;
183
+ private diffuseCache;
180
184
  constructor(drawCalls: DrawCall[], camera: Camera);
181
185
  private bounceRadiance;
182
186
  shade(position: V3, n: V3, base: V3, material: Material, sampleIndex?: number): V3;
package/dist/index.js CHANGED
@@ -1038,6 +1038,23 @@ var SoftwareRenderer = class {
1038
1038
  di
1039
1039
  );
1040
1040
  }
1041
+ const emission = material.emissiveFactor ?? [0, 0, 0];
1042
+ const strength = material.emissiveStrength ?? 1;
1043
+ let er = emission[0], eg = emission[1], eb = emission[2];
1044
+ if (uv && material.emissiveTexture) {
1045
+ const uvp = this.perspInterp(uv, invW, [l0, l1, l2]);
1046
+ const texel = this.sampleTextureNearest(
1047
+ material.emissiveTexture,
1048
+ uvp[0],
1049
+ uvp[1]
1050
+ );
1051
+ er *= srgbDecodeToLinear01(texel[0]);
1052
+ eg *= srgbDecodeToLinear01(texel[1]);
1053
+ eb *= srgbDecodeToLinear01(texel[2]);
1054
+ }
1055
+ r += er * strength;
1056
+ g += eg * strength;
1057
+ b += eb * strength;
1041
1058
  let a = baseColor[3];
1042
1059
  const linearR = r, linearG = g, linearB = b;
1043
1060
  const alphaMode = material.alphaMode ?? "OPAQUE";
@@ -1466,8 +1483,19 @@ var studio_environment_default = {
1466
1483
  // lib/render/RayOcclusion.ts
1467
1484
  import { vec3 as vec33 } from "gl-matrix";
1468
1485
  var RayOcclusion = class {
1469
- root;
1486
+ bounds = new Float64Array(0);
1487
+ escapes = new Uint32Array(0);
1488
+ starts = new Uint32Array(0);
1489
+ counts = new Uint8Array(0);
1490
+ triangleData = new Float64Array(0);
1491
+ triangles = [];
1470
1492
  bias;
1493
+ inverse = [0, 0, 0];
1494
+ parallelAxes = 0;
1495
+ closest = {
1496
+ hit: null,
1497
+ limit: Infinity
1498
+ };
1471
1499
  constructor(drawCalls) {
1472
1500
  const triangles = [];
1473
1501
  for (const dc of drawCalls) {
@@ -1491,10 +1519,19 @@ var RayOcclusion = class {
1491
1519
  const max = [0, 1, 2].map(
1492
1520
  (a) => Math.max(p[a], q[a], r[a])
1493
1521
  );
1522
+ const e1 = q.map((v, a) => v - p[a]), e2 = r.map((v, a) => v - p[a]);
1523
+ const normal = [
1524
+ e1[1] * e2[2] - e1[2] * e2[1],
1525
+ e1[2] * e2[0] - e1[0] * e2[2],
1526
+ e1[0] * e2[1] - e1[1] * e2[0]
1527
+ ];
1528
+ const length = Math.hypot(...normal) || 1;
1529
+ for (let a = 0; a < 3; a++) normal[a] /= length;
1494
1530
  triangles.push({
1495
1531
  p,
1496
- e1: q.map((v, a) => v - p[a]),
1497
- e2: r.map((v, a) => v - p[a]),
1532
+ e1,
1533
+ e2,
1534
+ normal,
1498
1535
  min,
1499
1536
  max,
1500
1537
  center: min.map((v, a) => (v + max[a]) / 2),
@@ -1502,10 +1539,52 @@ var RayOcclusion = class {
1502
1539
  });
1503
1540
  }
1504
1541
  }
1505
- this.root = triangles.length ? this.build(triangles) : null;
1506
- const extent = this.root ? Math.hypot(...this.root.max.map((v, a) => v - this.root.min[a])) : 1;
1542
+ const root = triangles.length ? this.build(triangles) : null;
1543
+ if (root) this.flatten(root, triangles.length);
1544
+ const extent = root ? Math.hypot(...root.max.map((v, a) => v - root.min[a])) : 1;
1507
1545
  this.bias = Math.max(1e-7, extent * 1e-5);
1508
1546
  }
1547
+ /** Preorder bounds with subtree escape offsets retain the existing SAH
1548
+ * primitive order, while avoiding recursive calls and pointer-heavy nodes
1549
+ * during millions of ray queries. Float64 retains the original precision.
1550
+ */
1551
+ flatten(root, triangleCount) {
1552
+ const nodes = [], escapes = [];
1553
+ const visit = (node) => {
1554
+ const index = nodes.length;
1555
+ nodes.push(node);
1556
+ escapes.push(0);
1557
+ if (!node.triangles) {
1558
+ visit(node.left);
1559
+ visit(node.right);
1560
+ }
1561
+ escapes[index] = nodes.length;
1562
+ };
1563
+ visit(root);
1564
+ this.bounds = new Float64Array(nodes.length * 6);
1565
+ this.escapes = Uint32Array.from(escapes);
1566
+ this.starts = new Uint32Array(nodes.length);
1567
+ this.counts = new Uint8Array(nodes.length);
1568
+ this.triangleData = new Float64Array(triangleCount * 9);
1569
+ for (let i = 0; i < nodes.length; i++) {
1570
+ const node = nodes[i];
1571
+ this.bounds.set(node.min, i * 6);
1572
+ this.bounds.set(node.max, i * 6 + 3);
1573
+ if (!node.triangles) continue;
1574
+ this.starts[i] = this.triangles.length;
1575
+ this.counts[i] = node.triangles.length;
1576
+ for (const triangle of node.triangles) {
1577
+ const offset = this.triangles.length * 9;
1578
+ this.triangleData.set(triangle.p, offset);
1579
+ this.triangleData.set(triangle.e1, offset + 3);
1580
+ this.triangleData.set(triangle.e2, offset + 6);
1581
+ this.triangles.push({
1582
+ normal: triangle.normal,
1583
+ material: triangle.material
1584
+ });
1585
+ }
1586
+ }
1587
+ }
1509
1588
  build(triangles) {
1510
1589
  const min = [Infinity, Infinity, Infinity], max = [-Infinity, -Infinity, -Infinity];
1511
1590
  for (const t of triangles)
@@ -1554,78 +1633,128 @@ var RayOcclusion = class {
1554
1633
  right: this.build(best.slice(middle))
1555
1634
  };
1556
1635
  }
1636
+ prepareRay(direction) {
1637
+ this.parallelAxes = 0;
1638
+ for (let a = 0; a < 3; a++) {
1639
+ this.inverse[a] = 1 / direction[a];
1640
+ if (Math.abs(direction[a]) < 1e-12) this.parallelAxes |= 1 << a;
1641
+ }
1642
+ }
1557
1643
  occluded(origin, direction, maxDistance = Infinity) {
1558
- return this.root ? this.intersect(this.root, origin, direction, maxDistance) : false;
1644
+ this.prepareRay(direction);
1645
+ return this.intersect(origin, direction, maxDistance);
1559
1646
  }
1560
1647
  /** Closest opaque triangle for one-bounce reflections and diffuse fill. */
1561
1648
  trace(origin, direction, maxDistance = Infinity) {
1562
- const state = {
1563
- hit: null,
1564
- limit: maxDistance
1649
+ this.prepareRay(direction);
1650
+ const state = this.closest;
1651
+ state.hit = null;
1652
+ state.limit = maxDistance;
1653
+ this.intersect(origin, direction, maxDistance, state);
1654
+ if (!state.hit) return null;
1655
+ const { normal, material } = state.hit;
1656
+ const sign = dotNormal(normal, direction) > 0 ? -1 : 1;
1657
+ return {
1658
+ position: [
1659
+ origin[0] + direction[0] * state.limit,
1660
+ origin[1] + direction[1] * state.limit,
1661
+ origin[2] + direction[2] * state.limit
1662
+ ],
1663
+ normal: [normal[0] * sign, normal[1] * sign, normal[2] * sign],
1664
+ material,
1665
+ distance: state.limit
1565
1666
  };
1566
- if (this.root)
1567
- this.intersect(this.root, origin, direction, maxDistance, state);
1568
- return state.hit;
1569
1667
  }
1570
- intersect(node, o, d, limit, state) {
1571
- if (state) limit = state.limit;
1572
- let near = 0, far = limit;
1573
- for (let a = 0; a < 3; a++) {
1574
- if (Math.abs(d[a]) < 1e-12) {
1575
- if (o[a] < node.min[a] || o[a] > node.max[a]) return false;
1576
- continue;
1668
+ intersect(o, d, maxDistance, state) {
1669
+ const ox = o[0], oy = o[1], oz = o[2], dx = d[0], dy = d[1], dz = d[2], ix = this.inverse[0], iy = this.inverse[1], iz = this.inverse[2], parallel = this.parallelAxes, bounds = this.bounds, data = this.triangleData;
1670
+ let node = 0;
1671
+ while (node < this.counts.length) {
1672
+ const offset = node * 6;
1673
+ let limit = state ? state.limit : maxDistance, near = 0, far = limit;
1674
+ if (parallel & 2) {
1675
+ if (oy < bounds[offset + 1] || oy > bounds[offset + 4]) {
1676
+ node = this.escapes[node];
1677
+ continue;
1678
+ }
1679
+ } else {
1680
+ let t0 = (bounds[offset + 1] - oy) * iy, t1 = (bounds[offset + 4] - oy) * iy;
1681
+ if (t0 > t1) {
1682
+ const swap = t0;
1683
+ t0 = t1;
1684
+ t1 = swap;
1685
+ }
1686
+ if (t0 > near) near = t0;
1687
+ if (t1 < far) far = t1;
1688
+ if (far < near) {
1689
+ node = this.escapes[node];
1690
+ continue;
1691
+ }
1577
1692
  }
1578
- const t0 = (node.min[a] - o[a]) / d[a], t1 = (node.max[a] - o[a]) / d[a];
1579
- near = Math.max(near, Math.min(t0, t1));
1580
- far = Math.min(far, Math.max(t0, t1));
1581
- if (far < near) return false;
1582
- }
1583
- if (node.triangles) {
1584
- for (const { p, e1, e2, material } of node.triangles) {
1585
- const px = d[1] * e2[2] - d[2] * e2[1], py = d[2] * e2[0] - d[0] * e2[2], pz = d[0] * e2[1] - d[1] * e2[0];
1586
- const det = e1[0] * px + e1[1] * py + e1[2] * pz;
1693
+ if (parallel & 1) {
1694
+ if (ox < bounds[offset + 0] || ox > bounds[offset + 3]) {
1695
+ node = this.escapes[node];
1696
+ continue;
1697
+ }
1698
+ } else {
1699
+ let t0 = (bounds[offset + 0] - ox) * ix, t1 = (bounds[offset + 3] - ox) * ix;
1700
+ if (t0 > t1) {
1701
+ const swap = t0;
1702
+ t0 = t1;
1703
+ t1 = swap;
1704
+ }
1705
+ if (t0 > near) near = t0;
1706
+ if (t1 < far) far = t1;
1707
+ if (far < near) {
1708
+ node = this.escapes[node];
1709
+ continue;
1710
+ }
1711
+ }
1712
+ if (parallel & 4) {
1713
+ if (oz < bounds[offset + 2] || oz > bounds[offset + 5]) {
1714
+ node = this.escapes[node];
1715
+ continue;
1716
+ }
1717
+ } else {
1718
+ let t0 = (bounds[offset + 2] - oz) * iz, t1 = (bounds[offset + 5] - oz) * iz;
1719
+ if (t0 > t1) {
1720
+ const swap = t0;
1721
+ t0 = t1;
1722
+ t1 = swap;
1723
+ }
1724
+ if (t0 > near) near = t0;
1725
+ if (t1 < far) far = t1;
1726
+ if (far < near) {
1727
+ node = this.escapes[node];
1728
+ continue;
1729
+ }
1730
+ }
1731
+ const start = this.starts[node], end = start + this.counts[node];
1732
+ for (let i = start; i < end; i++) {
1733
+ const offset2 = i * 9, p0 = data[offset2], p1 = data[offset2 + 1], p2 = data[offset2 + 2], e10 = data[offset2 + 3], e11 = data[offset2 + 4], e12 = data[offset2 + 5], e20 = data[offset2 + 6], e21 = data[offset2 + 7], e22 = data[offset2 + 8];
1734
+ const px = dy * e22 - dz * e21, py = dz * e20 - dx * e22, pz = dx * e21 - dy * e20;
1735
+ const det = e10 * px + e11 * py + e12 * pz;
1587
1736
  if (Math.abs(det) < 1e-12) continue;
1588
- const inv = 1 / det, tx = o[0] - p[0], ty = o[1] - p[1], tz = o[2] - p[2];
1737
+ const inv = 1 / det, tx = ox - p0, ty = oy - p1, tz = oz - p2;
1589
1738
  const u = (tx * px + ty * py + tz * pz) * inv;
1590
1739
  if (u < 0 || u > 1) continue;
1591
- const qx = ty * e1[2] - tz * e1[1], qy = tz * e1[0] - tx * e1[2], qz = tx * e1[1] - ty * e1[0];
1592
- const v = (d[0] * qx + d[1] * qy + d[2] * qz) * inv;
1740
+ const qx = ty * e12 - tz * e11, qy = tz * e10 - tx * e12, qz = tx * e11 - ty * e10;
1741
+ const v = (dx * qx + dy * qy + dz * qz) * inv;
1593
1742
  if (v < 0 || u + v > 1) continue;
1594
- const distance = (e2[0] * qx + e2[1] * qy + e2[2] * qz) * inv;
1743
+ const distance = (e20 * qx + e21 * qy + e22 * qz) * inv;
1595
1744
  if (distance > this.bias * 0.25 && distance < limit) {
1596
1745
  if (!state) return true;
1597
- const normal = [
1598
- e1[1] * e2[2] - e1[2] * e2[1],
1599
- e1[2] * e2[0] - e1[0] * e2[2],
1600
- e1[0] * e2[1] - e1[1] * e2[0]
1601
- ];
1602
- const length = Math.hypot(...normal) || 1;
1603
- for (let a = 0; a < 3; a++) normal[a] /= length;
1604
- if (normal[0] * d[0] + normal[1] * d[1] + normal[2] * d[2] > 0)
1605
- for (let a = 0; a < 3; a++) normal[a] *= -1;
1606
- state.hit = {
1607
- position: [
1608
- o[0] + d[0] * distance,
1609
- o[1] + d[1] * distance,
1610
- o[2] + d[2] * distance
1611
- ],
1612
- normal,
1613
- material,
1614
- distance
1615
- };
1746
+ state.hit = this.triangles[i];
1616
1747
  state.limit = limit = distance;
1617
1748
  }
1618
1749
  }
1619
- return false;
1620
- }
1621
- if (state) {
1622
- this.intersect(node.left, o, d, limit, state);
1623
- this.intersect(node.right, o, d, state.limit, state);
1624
- return state.hit !== null;
1750
+ node++;
1625
1751
  }
1626
- return this.intersect(node.left, o, d, limit) || this.intersect(node.right, o, d, limit);
1752
+ return state?.hit != null;
1627
1753
  }
1628
1754
  };
1755
+ function dotNormal(normal, direction) {
1756
+ return normal[0] * direction[0] + normal[1] * direction[1] + normal[2] * direction[2];
1757
+ }
1629
1758
 
1630
1759
  // lib/render/StudioLighting.ts
1631
1760
  var dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
@@ -1669,7 +1798,7 @@ function orient(x, y, z, n, t, b) {
1669
1798
  t[2] * x + b[2] * y + n[2] * z
1670
1799
  ];
1671
1800
  }
1672
- function sampleTable(table, n) {
1801
+ function tableLookup(n) {
1673
1802
  const x = (Math.atan2(n[2], n[0]) / (2 * Math.PI) + 0.5) * W - 0.5;
1674
1803
  const y = Math.max(
1675
1804
  0,
@@ -1680,7 +1809,11 @@ function sampleTable(table, n) {
1680
1809
  );
1681
1810
  const ix = Math.floor(x), iy = Math.floor(y), fx = x - ix, fy = y - iy;
1682
1811
  const a = (ix % W + W) % W, b = (a + 1) % W, c = Math.min(H - 1, iy + 1);
1683
- return (table[iy * W + a] * (1 - fx) + table[iy * W + b] * fx) * (1 - fy) + (table[c * W + a] * (1 - fx) + table[c * W + b] * fx) * fy;
1812
+ return [iy * W + a, iy * W + b, c * W + a, c * W + b, fx, fy];
1813
+ }
1814
+ function sampleTable(table, lookup) {
1815
+ const [i00, i10, i01, i11, fx, fy] = lookup;
1816
+ return (table[i00] * (1 - fx) + table[i10] * fx) * (1 - fy) + (table[i01] * (1 - fx) + table[i11] * fx) * fy;
1684
1817
  }
1685
1818
  var cachedTables;
1686
1819
  function getTables() {
@@ -1764,6 +1897,7 @@ var StudioLighting = class {
1764
1897
  occlusion;
1765
1898
  eye;
1766
1899
  shadowDirections;
1900
+ diffuseCache = /* @__PURE__ */ new WeakMap();
1767
1901
  constructor(drawCalls, camera) {
1768
1902
  this.occlusion = new RayOcclusion(drawCalls);
1769
1903
  const world = mat46.invert(mat46.create(), camera.view);
@@ -1774,7 +1908,7 @@ var StudioLighting = class {
1774
1908
  const [t, b] = basis(l.direction);
1775
1909
  return Array.from({ length: 8 }, (_, i) => {
1776
1910
  const u = (i + 0.5) / 8, cos = 1 + Math.log(1 - u) / l.sharpness, sin = Math.sqrt(Math.max(0, 1 - cos * cos)), phi = 2 * Math.PI * radicalInverse(i);
1777
- return orient(
1911
+ const direction = orient(
1778
1912
  sin * Math.cos(phi),
1779
1913
  sin * Math.sin(phi),
1780
1914
  cos,
@@ -1782,16 +1916,36 @@ var StudioLighting = class {
1782
1916
  t,
1783
1917
  b
1784
1918
  );
1919
+ const parallel = dot(direction, l.direction);
1920
+ return {
1921
+ parallel: l.direction.map((v) => v * parallel),
1922
+ perpendicular: direction.map(
1923
+ (v, k) => v - l.direction[k] * parallel
1924
+ ),
1925
+ cross: [
1926
+ l.direction[1] * direction[2] - l.direction[2] * direction[1],
1927
+ l.direction[2] * direction[0] - l.direction[0] * direction[2],
1928
+ l.direction[0] * direction[1] - l.direction[1] * direction[0]
1929
+ ]
1930
+ };
1785
1931
  });
1786
1932
  });
1787
1933
  }
1788
1934
  bounceRadiance(hit, incoming) {
1789
1935
  const metal = Math.max(0, Math.min(1, hit.material.metallicFactor ?? 1));
1790
- const irradiance = [...studio_environment_default.ambient];
1791
- for (let j = 0; j < lobes.length; j++) {
1792
- const value = sampleTable(this.tables.diffuse[j], hit.normal);
1793
- for (let k = 0; k < 3; k++) irradiance[k] += value * lobes[j].color[k];
1936
+ let cached = this.diffuseCache.get(hit.material);
1937
+ if (!cached || cached.normal.some((v, k) => !Object.is(v, hit.normal[k]))) {
1938
+ const irradiance2 = [...studio_environment_default.ambient];
1939
+ const lookup = tableLookup(hit.normal);
1940
+ for (let j = 0; j < lobes.length; j++) {
1941
+ const value = sampleTable(this.tables.diffuse[j], lookup);
1942
+ for (let k = 0; k < 3; k++)
1943
+ irradiance2[k] += value * lobes[j].color[k];
1944
+ }
1945
+ cached = { normal: [...hit.normal], irradiance: irradiance2 };
1946
+ this.diffuseCache.set(hit.material, cached);
1794
1947
  }
1948
+ const irradiance = cached.irradiance;
1795
1949
  const base = hit.material.baseColorFactor;
1796
1950
  const rough = Math.max(0.04, Math.min(1, hit.material.roughnessFactor ?? 1));
1797
1951
  const nv = Math.max(1e-3, -dot(hit.normal, incoming));
@@ -1810,8 +1964,9 @@ var StudioLighting = class {
1810
1964
  (c, k) => c * base[k] * (1 - metal) * 0.96
1811
1965
  );
1812
1966
  const pref = [...studio_environment_default.ambient];
1967
+ const reflectedLookup = tableLookup(reflected);
1813
1968
  for (let j = 0; j < lobes.length; j++) {
1814
- const value = sampleTable(this.tables.specular[level][j], reflected) * (1 - blend) + sampleTable(this.tables.specular[level + 1][j], reflected) * blend;
1969
+ const value = sampleTable(this.tables.specular[level][j], reflectedLookup) * (1 - blend) + sampleTable(this.tables.specular[level + 1][j], reflectedLookup) * blend;
1815
1970
  for (let k = 0; k < 3; k++) pref[k] += value * lobes[j].color[k];
1816
1971
  }
1817
1972
  for (let k = 0; k < 3; k++)
@@ -1855,27 +2010,23 @@ var StudioLighting = class {
1855
2010
  bits = Math.imul(bits ^ bits >>> 15, 2221713035);
1856
2011
  const hash = ((bits ^ bits >>> 16) >>> 0) / 4294967296;
1857
2012
  const phase = hash * 2 * Math.PI, cosPhase = Math.cos(phase), sinPhase = Math.sin(phase);
2013
+ const normalLookup = tableLookup(n), reflectedLookup = tableLookup(reflected);
1858
2014
  for (let j = 0; j < lobes.length; j++) {
1859
2015
  const dirs = this.shadowDirections[j];
1860
2016
  let visible = 0, total = 0;
1861
- const axis = lobes[j].direction;
1862
- for (const direction of dirs) {
1863
- const parallel = dot(direction, axis);
1864
- const cross = [
1865
- axis[1] * direction[2] - axis[2] * direction[1],
1866
- axis[2] * direction[0] - axis[0] * direction[2],
1867
- axis[0] * direction[1] - axis[1] * direction[0]
2017
+ for (const { parallel, perpendicular, cross } of dirs) {
2018
+ const l = [
2019
+ parallel[0] + perpendicular[0] * cosPhase + cross[0] * sinPhase,
2020
+ parallel[1] + perpendicular[1] * cosPhase + cross[1] * sinPhase,
2021
+ parallel[2] + perpendicular[2] * cosPhase + cross[2] * sinPhase
1868
2022
  ];
1869
- const l = direction.map(
1870
- (value, k) => axis[k] * parallel + (value - axis[k] * parallel) * cosPhase + cross[k] * sinPhase
1871
- );
1872
2023
  const weight = Math.max(0, dot(n, l));
1873
2024
  total += weight;
1874
2025
  if (weight > 0 && !this.occlusion.occluded(origin, l)) visible += weight;
1875
2026
  }
1876
2027
  const visibility = total > 0 ? visible / total : 1;
1877
- const diffuse = sampleTable(this.tables.diffuse[j], n) * visibility;
1878
- const specular = (sampleTable(this.tables.specular[level][j], reflected) * (1 - blend) + sampleTable(this.tables.specular[level + 1][j], reflected) * blend) * visibility;
2028
+ const diffuse = sampleTable(this.tables.diffuse[j], normalLookup) * visibility;
2029
+ const specular = (sampleTable(this.tables.specular[level][j], reflectedLookup) * (1 - blend) + sampleTable(this.tables.specular[level + 1][j], reflectedLookup) * blend) * visibility;
1879
2030
  for (let k = 0; k < 3; k++) {
1880
2031
  color[k] += lobes[j].color[k] * base[k] * (1 - metal) * 0.96 * diffuse;
1881
2032
  specColor[k] += lobes[j].color[k] * (f0[k] * A + B) * specular;
@@ -1883,7 +2034,7 @@ var StudioLighting = class {
1883
2034
  }
1884
2035
  const [t, b] = basis(n);
1885
2036
  let blocked = 0;
1886
- for (let i = 0; i < 4; i++) {
2037
+ for (let i = 0; metal < 1 && i < 4; i++) {
1887
2038
  const phi = i * Math.PI / 2 + phase, l = orient(
1888
2039
  0.7 * Math.cos(phi),
1889
2040
  0.7 * Math.sin(phi),
@@ -2385,6 +2536,9 @@ function getMaterial(gltf, textures, images, materialIndex) {
2385
2536
  baseColorTexture: texImg,
2386
2537
  metallicFactor: pbr.metallicFactor ?? 1,
2387
2538
  roughnessFactor: pbr.roughnessFactor ?? 1,
2539
+ emissiveFactor: material.emissiveFactor ?? [0, 0, 0],
2540
+ emissiveTexture: images[textures[material.emissiveTexture?.index]?.source] ?? null,
2541
+ emissiveStrength: material.extensions?.KHR_materials_emissive_strength?.emissiveStrength ?? 1,
2388
2542
  alphaMode,
2389
2543
  alphaCutoff
2390
2544
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "poppygl",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.31",
4
+ "version": "0.0.33",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/tscircuit/poppygl.git"