@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.cjs +492 -18
- package/dist/3dgs-lib.cjs.map +1 -1
- package/dist/3dgs-lib.js +492 -18
- package/dist/3dgs-lib.js.map +1 -1
- package/dist/App.d.ts +13 -1
- package/dist/core/SceneAidsRenderer.d.ts +32 -0
- package/dist/gs/PLYLoader.d.ts +7 -1
- package/dist/gs/PLYLoaderMobile.d.ts +8 -0
- package/dist/index.d.ts +3 -2
- package/package.json +1 -1
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
|
-
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
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
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
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,
|