Files
ComfyUI_frontend/src/scripts/metadata/ply.ts
Terry Jia 3c4b99ed84 3dgs & ply support (#7602)
## Summary

integrated sparkjs https://sparkjs.dev/, built by [world labs
](https://www.worldlabs.ai/) to support 3dgs.

- Add 3D Gaussian Splatting (3DGS) support using @sparkjsdev/spark
library
- Add PLY file format support with multiple rendering engines
- Support new file formats: `.ply`, `.spz`, `.splat`, `.ksplat`
- Add PLY Engine setting with three options: `threejs` (mesh), `fastply`
(optimized ASCII point clouds), `sparkjs` (3DGS)
- Add `FastPLYLoader` for 4-5x faster ASCII PLY parsing
- Add `original(Advanced)` material mode for point cloud rendering with
THREE.Points

3dgs generated by https://marble.worldlabs.ai/

test ply file from:
1. made by https://github.com/PozzettiAndrea/ComfyUI-DepthAnythingV3
2. threejs offically repo

## Screenshots


https://github.com/user-attachments/assets/44e64d3e-b58d-4341-9a70-a9aa64801220



https://github.com/user-attachments/assets/76b0dfba-0c12-4f64-91cb-bfc5d672294d



https://github.com/user-attachments/assets/2a8bfe81-1fb2-44c4-8787-dff325369c61



https://github.com/user-attachments/assets/e4beecee-d7a2-40c9-97f7-79b09c60312d

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7602-3dgs-ply-support-2cd6d73d3650814098fcea86cfaf747d)
by [Unito](https://www.unito.io)
2025-12-20 14:04:16 -07:00

154 lines
3.7 KiB
TypeScript

/**
* PLY (Polygon File Format) decoder
* Parses ASCII PLY files and extracts vertex positions and colors
*/
interface PLYHeader {
vertexCount: number
hasColor: boolean
propertyIndices: {
x: number
y: number
z: number
red: number
green: number
blue: number
}
headerEndLine: number
}
interface PLYData {
positions: Float32Array
colors: Float32Array | null
vertexCount: number
}
function parsePLYHeader(lines: string[]): PLYHeader | null {
let vertexCount = 0
let headerEndLine = 0
let hasColor = false
let xIndex = -1
let yIndex = -1
let zIndex = -1
let redIndex = -1
let greenIndex = -1
let blueIndex = -1
let propertyIndex = 0
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim()
if (line.startsWith('element vertex')) {
vertexCount = parseInt(line.split(/\s+/)[2])
} else if (line.startsWith('property')) {
const parts = line.split(/\s+/)
const propName = parts[parts.length - 1]
if (propName === 'x') xIndex = propertyIndex
else if (propName === 'y') yIndex = propertyIndex
else if (propName === 'z') zIndex = propertyIndex
else if (propName === 'red') {
hasColor = true
redIndex = propertyIndex
} else if (propName === 'green') greenIndex = propertyIndex
else if (propName === 'blue') blueIndex = propertyIndex
propertyIndex++
} else if (line === 'end_header') {
headerEndLine = i
break
}
}
if (vertexCount === 0 || xIndex < 0 || yIndex < 0 || zIndex < 0) {
return null
}
return {
vertexCount,
hasColor,
propertyIndices: {
x: xIndex,
y: yIndex,
z: zIndex,
red: redIndex,
green: greenIndex,
blue: blueIndex
},
headerEndLine
}
}
function parsePLYVertices(lines: string[], header: PLYHeader): PLYData {
const { vertexCount, hasColor, propertyIndices, headerEndLine } = header
const { x: xIndex, y: yIndex, z: zIndex } = propertyIndices
const { red: redIndex, green: greenIndex, blue: blueIndex } = propertyIndices
const positions = new Float32Array(vertexCount * 3)
const colors = hasColor ? new Float32Array(vertexCount * 3) : null
let vertexIndex = 0
for (
let i = headerEndLine + 1;
i < lines.length && vertexIndex < vertexCount;
i++
) {
const line = lines[i].trim()
if (!line) continue
const parts = line.split(/\s+/)
if (parts.length < 3) continue
const posIndex = vertexIndex * 3
positions[posIndex] = parseFloat(parts[xIndex])
positions[posIndex + 1] = parseFloat(parts[yIndex])
positions[posIndex + 2] = parseFloat(parts[zIndex])
if (
hasColor &&
colors &&
redIndex >= 0 &&
greenIndex >= 0 &&
blueIndex >= 0
) {
if (parts.length > Math.max(redIndex, greenIndex, blueIndex)) {
colors[posIndex] = parseInt(parts[redIndex]) / 255
colors[posIndex + 1] = parseInt(parts[greenIndex]) / 255
colors[posIndex + 2] = parseInt(parts[blueIndex]) / 255
}
}
vertexIndex++
}
return {
positions,
colors,
vertexCount: vertexIndex
}
}
/**
* Parse ASCII PLY data from an ArrayBuffer
* Returns positions and colors as typed arrays
*/
export function parseASCIIPLY(arrayBuffer: ArrayBuffer): PLYData | null {
const text = new TextDecoder().decode(arrayBuffer)
const lines = text.split('\n')
const header = parsePLYHeader(lines)
if (!header) return null
return parsePLYVertices(lines, header)
}
/**
* Check if PLY data is in ASCII format
*/
export function isPLYAsciiFormat(arrayBuffer: ArrayBuffer): boolean {
const header = new TextDecoder().decode(arrayBuffer.slice(0, 500))
return header.includes('format ascii')
}