@d5techs/3dgs-lib 1.1.3 → 1.3.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/dist/3dgs-lib.js CHANGED
@@ -1698,6 +1698,429 @@ class BoundingBoxRenderer {
1698
1698
  this.bindGroup = null;
1699
1699
  }
1700
1700
  }
1701
+ class SceneAidsRenderer {
1702
+ constructor(renderer, camera) {
1703
+ __publicField(this, "renderer");
1704
+ __publicField(this, "camera");
1705
+ // 可见性
1706
+ __publicField(this, "_gridVisible", true);
1707
+ __publicField(this, "_axesVisible", true);
1708
+ // Grid GPU 资源
1709
+ __publicField(this, "gridPipeline", null);
1710
+ __publicField(this, "gridUniformBuffer", null);
1711
+ __publicField(this, "gridBindGroup", null);
1712
+ // Axes GPU 资源
1713
+ __publicField(this, "axesPipeline", null);
1714
+ __publicField(this, "axesUniformBuffer", null);
1715
+ __publicField(this, "axesBindGroup", null);
1716
+ __publicField(this, "axesVertexBuffer", null);
1717
+ this.renderer = renderer;
1718
+ this.camera = camera;
1719
+ this.createGridPipeline();
1720
+ this.createAxesPipeline();
1721
+ }
1722
+ // ─── Grid Pipeline ──────────────────────────────────────
1723
+ createGridPipeline() {
1724
+ const device = this.renderer.device;
1725
+ const shaderCode = (
1726
+ /* wgsl */
1727
+ `
1728
+ struct Uniforms {
1729
+ viewProjection: mat4x4<f32>,
1730
+ invViewProjection: mat4x4<f32>,
1731
+ cameraPos: vec3<f32>,
1732
+ _pad: f32,
1733
+ }
1734
+
1735
+ @group(0) @binding(0) var<uniform> u: Uniforms;
1736
+
1737
+ struct VSOut {
1738
+ @builtin(position) position: vec4<f32>,
1739
+ @location(0) nearPoint: vec3<f32>,
1740
+ @location(1) farPoint: vec3<f32>,
1741
+ }
1742
+
1743
+ // fullscreen triangle positions (NDC)
1744
+ var<private> triPos: array<vec2<f32>, 6> = array<vec2<f32>, 6>(
1745
+ vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(1.0, 1.0),
1746
+ vec2(-1.0, -1.0), vec2(1.0, 1.0), vec2(-1.0, 1.0),
1747
+ );
1748
+
1749
+ fn unprojectPoint(p: vec3<f32>) -> vec3<f32> {
1750
+ let clip = u.invViewProjection * vec4(p, 1.0);
1751
+ return clip.xyz / clip.w;
1752
+ }
1753
+
1754
+ @vertex
1755
+ fn vertexMain(@builtin(vertex_index) vid: u32) -> VSOut {
1756
+ var out: VSOut;
1757
+ let xy = triPos[vid];
1758
+ out.nearPoint = unprojectPoint(vec3(xy, 0.0));
1759
+ out.farPoint = unprojectPoint(vec3(xy, 1.0));
1760
+ out.position = vec4(xy, 0.0, 1.0);
1761
+ return out;
1762
+ }
1763
+
1764
+ struct FSOut {
1765
+ @location(0) color: vec4<f32>,
1766
+ @builtin(frag_depth) depth: f32,
1767
+ }
1768
+
1769
+ fn gridLine(coord: vec2<f32>, scale: f32) -> f32 {
1770
+ let d = abs(fract(coord * scale - 0.5) - 0.5) / fwidth(coord * scale);
1771
+ return 1.0 - min(min(d.x, d.y), 1.0);
1772
+ }
1773
+
1774
+ fn computeDepth(worldPos: vec3<f32>) -> f32 {
1775
+ let clip = u.viewProjection * vec4(worldPos, 1.0);
1776
+ return clip.z / clip.w;
1777
+ }
1778
+
1779
+ @fragment
1780
+ fn fragmentMain(input: VSOut) -> FSOut {
1781
+ // ray-plane intersection with Y=0
1782
+ let t = -input.nearPoint.y / (input.farPoint.y - input.nearPoint.y);
1783
+ let worldPos = input.nearPoint + t * (input.farPoint - input.nearPoint);
1784
+
1785
+ var out: FSOut;
1786
+ out.depth = computeDepth(worldPos);
1787
+
1788
+ // only draw if we actually hit the plane (t > 0) and depth is valid
1789
+ let visible = t > 0.0 && out.depth > 0.0 && out.depth < 1.0;
1790
+
1791
+ if (!visible) {
1792
+ discard;
1793
+ }
1794
+
1795
+ // minor grid (1 unit)
1796
+ let minorGrid = gridLine(worldPos.xz, 1.0);
1797
+ // major grid (10 units)
1798
+ let majorGrid = gridLine(worldPos.xz, 0.1);
1799
+
1800
+ // fade by distance from camera
1801
+ let dist = length(worldPos.xz - u.cameraPos.xz);
1802
+ let fadeFar = 1.0 - smoothstep(30.0, 150.0, dist);
1803
+
1804
+ // base color
1805
+ var color = vec3(0.35);
1806
+ var alpha = minorGrid * 0.15 * fadeFar;
1807
+
1808
+ // major lines brighter
1809
+ alpha = max(alpha, majorGrid * 0.3 * fadeFar);
1810
+
1811
+ // X axis (red line at z=0)
1812
+ let xAxisDist = abs(worldPos.z);
1813
+ let xAxisLine = 1.0 - smoothstep(0.0, fwidth(worldPos.z) * 1.5, xAxisDist);
1814
+ if (xAxisLine > 0.01) {
1815
+ color = mix(color, vec3(0.8, 0.2, 0.2), xAxisLine);
1816
+ alpha = max(alpha, xAxisLine * 0.8 * fadeFar);
1817
+ }
1818
+
1819
+ // Z axis (blue line at x=0)
1820
+ let zAxisDist = abs(worldPos.x);
1821
+ let zAxisLine = 1.0 - smoothstep(0.0, fwidth(worldPos.x) * 1.5, zAxisDist);
1822
+ if (zAxisLine > 0.01) {
1823
+ color = mix(color, vec3(0.2, 0.4, 0.9), zAxisLine);
1824
+ alpha = max(alpha, zAxisLine * 0.8 * fadeFar);
1825
+ }
1826
+
1827
+ out.color = vec4(color, alpha);
1828
+ return out;
1829
+ }
1830
+ `
1831
+ );
1832
+ const shaderModule = device.createShaderModule({ code: shaderCode });
1833
+ this.gridUniformBuffer = device.createBuffer({
1834
+ size: 144,
1835
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
1836
+ });
1837
+ const bindGroupLayout = device.createBindGroupLayout({
1838
+ entries: [{
1839
+ binding: 0,
1840
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT,
1841
+ buffer: { type: "uniform" }
1842
+ }]
1843
+ });
1844
+ this.gridBindGroup = device.createBindGroup({
1845
+ layout: bindGroupLayout,
1846
+ entries: [{ binding: 0, resource: { buffer: this.gridUniformBuffer } }]
1847
+ });
1848
+ const pipelineLayout = device.createPipelineLayout({
1849
+ bindGroupLayouts: [bindGroupLayout]
1850
+ });
1851
+ this.gridPipeline = device.createRenderPipeline({
1852
+ layout: pipelineLayout,
1853
+ vertex: {
1854
+ module: shaderModule,
1855
+ entryPoint: "vertexMain"
1856
+ },
1857
+ fragment: {
1858
+ module: shaderModule,
1859
+ entryPoint: "fragmentMain",
1860
+ targets: [{
1861
+ format: this.renderer.format,
1862
+ blend: {
1863
+ color: {
1864
+ srcFactor: "src-alpha",
1865
+ dstFactor: "one-minus-src-alpha",
1866
+ operation: "add"
1867
+ },
1868
+ alpha: {
1869
+ srcFactor: "one",
1870
+ dstFactor: "one-minus-src-alpha",
1871
+ operation: "add"
1872
+ }
1873
+ }
1874
+ }]
1875
+ },
1876
+ primitive: {
1877
+ topology: "triangle-list",
1878
+ cullMode: "none"
1879
+ },
1880
+ depthStencil: {
1881
+ format: this.renderer.depthFormat,
1882
+ depthWriteEnabled: true,
1883
+ depthCompare: "less-equal"
1884
+ }
1885
+ });
1886
+ }
1887
+ // ─── Axes Pipeline ──────────────────────────────────────
1888
+ createAxesPipeline() {
1889
+ const device = this.renderer.device;
1890
+ const shaderCode = (
1891
+ /* wgsl */
1892
+ `
1893
+ struct Uniforms {
1894
+ viewProjection: mat4x4<f32>,
1895
+ }
1896
+
1897
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
1898
+
1899
+ struct VertexInput {
1900
+ @location(0) position: vec3<f32>,
1901
+ @location(1) color: vec3<f32>,
1902
+ }
1903
+
1904
+ struct VertexOutput {
1905
+ @builtin(position) position: vec4<f32>,
1906
+ @location(0) color: vec3<f32>,
1907
+ }
1908
+
1909
+ @vertex
1910
+ fn vertexMain(input: VertexInput) -> VertexOutput {
1911
+ var output: VertexOutput;
1912
+ output.position = uniforms.viewProjection * vec4(input.position, 1.0);
1913
+ output.color = input.color;
1914
+ return output;
1915
+ }
1916
+
1917
+ @fragment
1918
+ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
1919
+ return vec4(input.color, 1.0);
1920
+ }
1921
+ `
1922
+ );
1923
+ const shaderModule = device.createShaderModule({ code: shaderCode });
1924
+ this.axesUniformBuffer = device.createBuffer({
1925
+ size: 64,
1926
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
1927
+ });
1928
+ const bindGroupLayout = device.createBindGroupLayout({
1929
+ entries: [{
1930
+ binding: 0,
1931
+ visibility: GPUShaderStage.VERTEX,
1932
+ buffer: { type: "uniform" }
1933
+ }]
1934
+ });
1935
+ this.axesBindGroup = device.createBindGroup({
1936
+ layout: bindGroupLayout,
1937
+ entries: [{ binding: 0, resource: { buffer: this.axesUniformBuffer } }]
1938
+ });
1939
+ const pipelineLayout = device.createPipelineLayout({
1940
+ bindGroupLayouts: [bindGroupLayout]
1941
+ });
1942
+ this.axesPipeline = device.createRenderPipeline({
1943
+ layout: pipelineLayout,
1944
+ vertex: {
1945
+ module: shaderModule,
1946
+ entryPoint: "vertexMain",
1947
+ buffers: [{
1948
+ arrayStride: 24,
1949
+ attributes: [
1950
+ { shaderLocation: 0, offset: 0, format: "float32x3" },
1951
+ { shaderLocation: 1, offset: 12, format: "float32x3" }
1952
+ ]
1953
+ }]
1954
+ },
1955
+ fragment: {
1956
+ module: shaderModule,
1957
+ entryPoint: "fragmentMain",
1958
+ targets: [{ format: this.renderer.format }]
1959
+ },
1960
+ primitive: {
1961
+ topology: "line-list",
1962
+ cullMode: "none"
1963
+ },
1964
+ depthStencil: {
1965
+ format: this.renderer.depthFormat,
1966
+ depthWriteEnabled: true,
1967
+ depthCompare: "less-equal"
1968
+ }
1969
+ });
1970
+ this.createAxesVertexBuffer();
1971
+ }
1972
+ createAxesVertexBuffer() {
1973
+ const device = this.renderer.device;
1974
+ const L = 100;
1975
+ const verts = new Float32Array([
1976
+ // X axis: red
1977
+ 0,
1978
+ 0,
1979
+ 0,
1980
+ 0.9,
1981
+ 0.2,
1982
+ 0.2,
1983
+ L,
1984
+ 0,
1985
+ 0,
1986
+ 0.9,
1987
+ 0.2,
1988
+ 0.2,
1989
+ // Y axis: green
1990
+ 0,
1991
+ 0,
1992
+ 0,
1993
+ 0.2,
1994
+ 0.8,
1995
+ 0.2,
1996
+ 0,
1997
+ L,
1998
+ 0,
1999
+ 0.2,
2000
+ 0.8,
2001
+ 0.2,
2002
+ // Z axis: blue
2003
+ 0,
2004
+ 0,
2005
+ 0,
2006
+ 0.3,
2007
+ 0.4,
2008
+ 0.9,
2009
+ 0,
2010
+ 0,
2011
+ L,
2012
+ 0.3,
2013
+ 0.4,
2014
+ 0.9
2015
+ ]);
2016
+ this.axesVertexBuffer = device.createBuffer({
2017
+ size: verts.byteLength,
2018
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
2019
+ });
2020
+ device.queue.writeBuffer(this.axesVertexBuffer, 0, verts);
2021
+ }
2022
+ // ─── Public API ─────────────────────────────────────────
2023
+ get gridVisible() {
2024
+ return this._gridVisible;
2025
+ }
2026
+ set gridVisible(v) {
2027
+ this._gridVisible = v;
2028
+ }
2029
+ get axesVisible() {
2030
+ return this._axesVisible;
2031
+ }
2032
+ set axesVisible(v) {
2033
+ this._axesVisible = v;
2034
+ }
2035
+ // ─── Render ─────────────────────────────────────────────
2036
+ render(pass) {
2037
+ if (this._gridVisible) {
2038
+ this.renderGrid(pass);
2039
+ }
2040
+ if (this._axesVisible) {
2041
+ this.renderAxes(pass);
2042
+ }
2043
+ }
2044
+ renderGrid(pass) {
2045
+ if (!this.gridPipeline || !this.gridBindGroup || !this.gridUniformBuffer) return;
2046
+ const device = this.renderer.device;
2047
+ const vp = new Float32Array(this.camera.viewProjectionMatrix);
2048
+ const invVP = this.invertMatrix4(vp);
2049
+ const camPos = new Float32Array(this.camera.position);
2050
+ const buf = new Float32Array(36);
2051
+ buf.set(vp, 0);
2052
+ buf.set(invVP, 16);
2053
+ buf.set(camPos, 32);
2054
+ device.queue.writeBuffer(this.gridUniformBuffer, 0, buf);
2055
+ pass.setPipeline(this.gridPipeline);
2056
+ pass.setBindGroup(0, this.gridBindGroup);
2057
+ pass.draw(6);
2058
+ }
2059
+ renderAxes(pass) {
2060
+ if (!this.axesPipeline || !this.axesBindGroup || !this.axesUniformBuffer || !this.axesVertexBuffer) return;
2061
+ const device = this.renderer.device;
2062
+ const vpMatrix = new Float32Array(this.camera.viewProjectionMatrix);
2063
+ device.queue.writeBuffer(this.axesUniformBuffer, 0, vpMatrix);
2064
+ pass.setPipeline(this.axesPipeline);
2065
+ pass.setBindGroup(0, this.axesBindGroup);
2066
+ pass.setVertexBuffer(0, this.axesVertexBuffer);
2067
+ pass.draw(6);
2068
+ }
2069
+ // ─── Math helpers ───────────────────────────────────────
2070
+ invertMatrix4(m) {
2071
+ const out = new Float32Array(16);
2072
+ const a00 = m[0], a01 = m[1], a02 = m[2], a03 = m[3];
2073
+ const a10 = m[4], a11 = m[5], a12 = m[6], a13 = m[7];
2074
+ const a20 = m[8], a21 = m[9], a22 = m[10], a23 = m[11];
2075
+ const a30 = m[12], a31 = m[13], a32 = m[14], a33 = m[15];
2076
+ const b00 = a00 * a11 - a01 * a10;
2077
+ const b01 = a00 * a12 - a02 * a10;
2078
+ const b02 = a00 * a13 - a03 * a10;
2079
+ const b03 = a01 * a12 - a02 * a11;
2080
+ const b04 = a01 * a13 - a03 * a11;
2081
+ const b05 = a02 * a13 - a03 * a12;
2082
+ const b06 = a20 * a31 - a21 * a30;
2083
+ const b07 = a20 * a32 - a22 * a30;
2084
+ const b08 = a20 * a33 - a23 * a30;
2085
+ const b09 = a21 * a32 - a22 * a31;
2086
+ const b10 = a21 * a33 - a23 * a31;
2087
+ const b11 = a22 * a33 - a23 * a32;
2088
+ let det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
2089
+ if (Math.abs(det) < 1e-10) return out;
2090
+ det = 1 / det;
2091
+ out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det;
2092
+ out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det;
2093
+ out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det;
2094
+ out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det;
2095
+ out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det;
2096
+ out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det;
2097
+ out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det;
2098
+ out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det;
2099
+ out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det;
2100
+ out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det;
2101
+ out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det;
2102
+ out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det;
2103
+ out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det;
2104
+ out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det;
2105
+ out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det;
2106
+ out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det;
2107
+ return out;
2108
+ }
2109
+ // ─── Cleanup ────────────────────────────────────────────
2110
+ destroy() {
2111
+ var _a2, _b2, _c;
2112
+ (_a2 = this.gridUniformBuffer) == null ? void 0 : _a2.destroy();
2113
+ (_b2 = this.axesUniformBuffer) == null ? void 0 : _b2.destroy();
2114
+ (_c = this.axesVertexBuffer) == null ? void 0 : _c.destroy();
2115
+ this.gridUniformBuffer = null;
2116
+ this.axesUniformBuffer = null;
2117
+ this.axesVertexBuffer = null;
2118
+ this.gridPipeline = null;
2119
+ this.axesPipeline = null;
2120
+ this.gridBindGroup = null;
2121
+ this.axesBindGroup = null;
2122
+ }
2123
+ }
1701
2124
  class Mesh {
1702
2125
  constructor(vertexBuffer, vertexCount, indexBuffer = null, indexCount = 0, boundingBox) {
1703
2126
  __publicField(this, "vertexBuffer");
@@ -3988,7 +4411,8 @@ function readProperty$1(dataView, baseOffset, prop, littleEndian) {
3988
4411
  function sigmoid$1(x) {
3989
4412
  return 1 / (1 + Math.exp(-x));
3990
4413
  }
3991
- async function loadPLY(url) {
4414
+ async function loadPLY(url, options = {}) {
4415
+ const { coordinateSystem = "blender" } = options;
3992
4416
  const response = await fetch(url);
3993
4417
  if (!response.ok) {
3994
4418
  throw new Error(`无法加载 PLY 文件: ${url}`);
@@ -4000,6 +4424,7 @@ async function loadPLY(url) {
4000
4424
  throw new Error("不支持 ASCII 格式的 PLY 文件,请使用 binary_little_endian 或 binary_big_endian 格式");
4001
4425
  }
4002
4426
  const littleEndian = format === "binary_little_endian";
4427
+ const swapYZ = coordinateSystem === "blender";
4003
4428
  const propMap = buildPropertyMap(properties);
4004
4429
  const props = {
4005
4430
  x: propMap.get("x"),
@@ -4065,9 +4490,14 @@ async function loadPLY(url) {
4065
4490
  shRest[dstBase + 2] = srcB < shRestProps.length ? readProperty$1(dataView, base, shRestProps[srcB], littleEndian) : 0;
4066
4491
  }
4067
4492
  splats[i] = {
4068
- mean: [x, y, z],
4069
- scale: [scale_0, scale_1, scale_2],
4070
- rotation: [
4493
+ mean: swapYZ ? [x, z, y] : [x, y, z],
4494
+ scale: swapYZ ? [scale_0, scale_2, scale_1] : [scale_0, scale_1, scale_2],
4495
+ rotation: swapYZ ? [
4496
+ -rot_0 * qnorm,
4497
+ rot_1 * qnorm,
4498
+ rot_3 * qnorm,
4499
+ rot_2 * qnorm
4500
+ ] : [
4071
4501
  rot_0 * qnorm,
4072
4502
  rot_1 * qnorm,
4073
4503
  rot_2 * qnorm,
@@ -4283,7 +4713,8 @@ async function parsePLYBuffer(buffer, options = {}) {
4283
4713
  const {
4284
4714
  maxSplats = 2e5,
4285
4715
  loadSH = false,
4286
- onProgress
4716
+ onProgress,
4717
+ coordinateSystem = "blender"
4287
4718
  } = options;
4288
4719
  const seed = options.seed ?? buffer.byteLength;
4289
4720
  const { headerText, dataOffset } = extractHeader(buffer);
@@ -4373,27 +4804,41 @@ async function parsePLYBuffer(buffer, options = {}) {
4373
4804
  const opacities = new Float32Array(actualCount);
4374
4805
  const shCoeffs = loadSH ? new Float32Array(actualCount * 45) : void 0;
4375
4806
  const dataView = new DataView(buffer, dataOffset);
4807
+ const swapYZ = coordinateSystem === "blender";
4376
4808
  let outputIdx = 0;
4377
4809
  let lastProgress = 0;
4378
4810
  for (let i = 0; i < actualCount; i++) {
4379
4811
  const srcIdx = sampleIndices ? sampleIndices[i] : i;
4380
4812
  const base = srcIdx * stride;
4381
- positions[outputIdx * 3 + 0] = offsets.x >= 0 ? readProperty(dataView, base + offsets.x, types.x, littleEndian) : 0;
4382
- positions[outputIdx * 3 + 1] = offsets.y >= 0 ? readProperty(dataView, base + offsets.y, types.y, littleEndian) : 0;
4383
- positions[outputIdx * 3 + 2] = offsets.z >= 0 ? readProperty(dataView, base + offsets.z, types.z, littleEndian) : 0;
4384
- scales[outputIdx * 3 + 0] = offsets.scale_0 >= 0 ? Math.exp(readProperty(dataView, base + offsets.scale_0, types.scale_0, littleEndian)) : 1;
4385
- scales[outputIdx * 3 + 1] = offsets.scale_1 >= 0 ? Math.exp(readProperty(dataView, base + offsets.scale_1, types.scale_1, littleEndian)) : 1;
4386
- scales[outputIdx * 3 + 2] = offsets.scale_2 >= 0 ? Math.exp(readProperty(dataView, base + offsets.scale_2, types.scale_2, littleEndian)) : 1;
4813
+ const px = offsets.x >= 0 ? readProperty(dataView, base + offsets.x, types.x, littleEndian) : 0;
4814
+ const py = offsets.y >= 0 ? readProperty(dataView, base + offsets.y, types.y, littleEndian) : 0;
4815
+ const pz = offsets.z >= 0 ? readProperty(dataView, base + offsets.z, types.z, littleEndian) : 0;
4816
+ positions[outputIdx * 3 + 0] = px;
4817
+ positions[outputIdx * 3 + 1] = swapYZ ? pz : py;
4818
+ positions[outputIdx * 3 + 2] = swapYZ ? py : pz;
4819
+ const sx = offsets.scale_0 >= 0 ? Math.exp(readProperty(dataView, base + offsets.scale_0, types.scale_0, littleEndian)) : 1;
4820
+ const sy = offsets.scale_1 >= 0 ? Math.exp(readProperty(dataView, base + offsets.scale_1, types.scale_1, littleEndian)) : 1;
4821
+ const sz = offsets.scale_2 >= 0 ? Math.exp(readProperty(dataView, base + offsets.scale_2, types.scale_2, littleEndian)) : 1;
4822
+ scales[outputIdx * 3 + 0] = sx;
4823
+ scales[outputIdx * 3 + 1] = swapYZ ? sz : sy;
4824
+ scales[outputIdx * 3 + 2] = swapYZ ? sy : sz;
4387
4825
  const rot_0 = offsets.rot_0 >= 0 ? readProperty(dataView, base + offsets.rot_0, types.rot_0, littleEndian) : 1;
4388
4826
  const rot_1 = offsets.rot_1 >= 0 ? readProperty(dataView, base + offsets.rot_1, types.rot_1, littleEndian) : 0;
4389
4827
  const rot_2 = offsets.rot_2 >= 0 ? readProperty(dataView, base + offsets.rot_2, types.rot_2, littleEndian) : 0;
4390
4828
  const rot_3 = offsets.rot_3 >= 0 ? readProperty(dataView, base + offsets.rot_3, types.rot_3, littleEndian) : 0;
4391
4829
  const qlen = Math.sqrt(rot_0 * rot_0 + rot_1 * rot_1 + rot_2 * rot_2 + rot_3 * rot_3);
4392
4830
  const qnorm = qlen > 0 ? 1 / qlen : 1;
4393
- rotations[outputIdx * 4 + 0] = rot_0 * qnorm;
4394
- rotations[outputIdx * 4 + 1] = rot_1 * qnorm;
4395
- rotations[outputIdx * 4 + 2] = rot_2 * qnorm;
4396
- rotations[outputIdx * 4 + 3] = rot_3 * qnorm;
4831
+ if (swapYZ) {
4832
+ rotations[outputIdx * 4 + 0] = -rot_0 * qnorm;
4833
+ rotations[outputIdx * 4 + 1] = rot_1 * qnorm;
4834
+ rotations[outputIdx * 4 + 2] = rot_3 * qnorm;
4835
+ rotations[outputIdx * 4 + 3] = rot_2 * qnorm;
4836
+ } else {
4837
+ rotations[outputIdx * 4 + 0] = rot_0 * qnorm;
4838
+ rotations[outputIdx * 4 + 1] = rot_1 * qnorm;
4839
+ rotations[outputIdx * 4 + 2] = rot_2 * qnorm;
4840
+ rotations[outputIdx * 4 + 3] = rot_3 * qnorm;
4841
+ }
4397
4842
  const f_dc_0 = offsets.f_dc_0 >= 0 ? readProperty(dataView, base + offsets.f_dc_0, types.f_dc_0, littleEndian) : 0;
4398
4843
  const f_dc_1 = offsets.f_dc_1 >= 0 ? readProperty(dataView, base + offsets.f_dc_1, types.f_dc_1, littleEndian) : 0;
4399
4844
  const f_dc_2 = offsets.f_dc_2 >= 0 ? readProperty(dataView, base + offsets.f_dc_2, types.f_dc_2, littleEndian) : 0;
@@ -13471,6 +13916,7 @@ class App {
13471
13916
  __publicField(this, "sceneManager");
13472
13917
  __publicField(this, "gizmoManager");
13473
13918
  __publicField(this, "hotspotManager");
13919
+ __publicField(this, "sceneAids");
13474
13920
  __publicField(this, "isRunning", false);
13475
13921
  __publicField(this, "animationId", 0);
13476
13922
  // 是否使用移动端渲染器
@@ -13506,6 +13952,7 @@ class App {
13506
13952
  this.controls,
13507
13953
  this.meshRenderer
13508
13954
  );
13955
+ this.sceneAids = new SceneAidsRenderer(this.renderer, this.camera);
13509
13956
  window.addEventListener("resize", this.boundOnResize);
13510
13957
  }
13511
13958
  // ============================================
@@ -13543,8 +13990,12 @@ class App {
13543
13990
  }
13544
13991
  /**
13545
13992
  * 加载 PLY 文件 (3D Gaussian Splatting)
13993
+ * @param urlOrBuffer PLY 文件 URL 或 ArrayBuffer
13994
+ * @param onProgress 进度回调
13995
+ * @param isLocalFile 是否为本地文件
13996
+ * @param coordinateSystem 源数据坐标系,默认 'blender'(Z-up → Y-up 自动转换)
13546
13997
  */
13547
- async addPLY(urlOrBuffer, onProgress, isLocalFile = false) {
13998
+ async addPLY(urlOrBuffer, onProgress, isLocalFile = false, coordinateSystem = "blender") {
13548
13999
  try {
13549
14000
  const isMobile = isMobileDevice();
13550
14001
  let buffer;
@@ -13573,7 +14024,8 @@ class App {
13573
14024
  const compactData = await this.parsePLYBuffer(buffer, {
13574
14025
  maxSplats: Infinity,
13575
14026
  loadSH: false,
13576
- onProgress: parseProgressCallback
14027
+ onProgress: parseProgressCallback,
14028
+ coordinateSystem
13577
14029
  });
13578
14030
  if (onProgress) onProgress(90, "upload");
13579
14031
  gsRenderer.setCompactData(compactData);
@@ -13587,7 +14039,8 @@ class App {
13587
14039
  const compactData = await this.parsePLYBuffer(buffer, {
13588
14040
  maxSplats: Infinity,
13589
14041
  loadSH: true,
13590
- onProgress: parseProgressCallback
14042
+ onProgress: parseProgressCallback,
14043
+ coordinateSystem
13591
14044
  });
13592
14045
  if (onProgress) onProgress(90, "upload");
13593
14046
  gsRenderer.setCompactData(compactData);
@@ -13726,6 +14179,7 @@ class App {
13726
14179
  gsRenderer.render(pass);
13727
14180
  }
13728
14181
  this.meshRenderer.render(pass);
14182
+ this.sceneAids.render(pass);
13729
14183
  this.gizmoManager.render(pass);
13730
14184
  this.renderer.endFrame();
13731
14185
  this.hotspotManager.consumeGPUResult();
@@ -14002,6 +14456,24 @@ class App {
14002
14456
  const { parsePLYBuffer: parsePLYBuffer2 } = await Promise.resolve().then(() => PLYLoaderMobile);
14003
14457
  return parsePLYBuffer2(buffer, options);
14004
14458
  }
14459
+ // ============================================
14460
+ // 场景辅助(网格 + 坐标轴)
14461
+ // ============================================
14462
+ setGridVisible(visible) {
14463
+ this.sceneAids.gridVisible = visible;
14464
+ }
14465
+ getGridVisible() {
14466
+ return this.sceneAids.gridVisible;
14467
+ }
14468
+ setAxesVisible(visible) {
14469
+ this.sceneAids.axesVisible = visible;
14470
+ }
14471
+ getAxesVisible() {
14472
+ return this.sceneAids.axesVisible;
14473
+ }
14474
+ getSceneAidsRenderer() {
14475
+ return this.sceneAids;
14476
+ }
14005
14477
  /**
14006
14478
  * 销毁应用及所有资源
14007
14479
  */
@@ -14011,6 +14483,7 @@ class App {
14011
14483
  this.sceneManager.destroy();
14012
14484
  this.gizmoManager.destroy();
14013
14485
  this.hotspotManager.destroy();
14486
+ this.sceneAids.destroy();
14014
14487
  if (this.meshRenderer) {
14015
14488
  this.meshRenderer.destroy();
14016
14489
  }
@@ -14045,6 +14518,7 @@ export {
14045
14518
  OrbitControls,
14046
14519
  Renderer,
14047
14520
  SHMode,
14521
+ SceneAidsRenderer,
14048
14522
  SceneManager,
14049
14523
  SplatBoundingBoxProvider,
14050
14524
  SplatTransformProxy,