-
-
Save unitycoder/fd87e9d3ba9a9771286dc0a533ba7912 to your computer and use it in GitHub Desktop.
convert 3D-Tiles v1 to v1.1 (3D-Tiles-Next) for point clouds
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// PURPOSE: convert draco-pnts to glb (ie. 3dTiles v1.0 to v1.1) | |
// INSTALL: npm install draco3d gltfpack | |
// RUN: | |
// > node infolderPNTS outfolderGLB | |
import fs from 'fs'; | |
import draco3d from 'draco3d'; | |
import gltfpack from 'gltfpack'; | |
const inFolder = process.argv.slice(2)[0] || 'infolder'; | |
const outFolder = process.argv.slice(2)[1] || 'outfolder'; | |
const uris = []; | |
function recurveNode(node) { | |
uris.push(node.content.uri); | |
for (var n in node.children) { | |
recurveNode(node.children[n]); | |
}; | |
return node; | |
} | |
// https://github.com/Nicktho/batch-promises/blob/3b73b218b165b974d116b2cc5a7720895af489db/index.js#L4 | |
async function batchPromises(batchSize, collection, callback) { | |
const arr = await Promise.resolve(collection); | |
return arr | |
.map((_, i) => (i % batchSize ? [] : arr.slice(i, i + batchSize))) | |
.map((group) => (res) => Promise.all(group.map(callback)).then((r) => res.concat(r))) | |
.reduce((chain, work) => chain.then(work), Promise.resolve([])); | |
} | |
let decoderModule = null; | |
draco3d.createDecoderModule({}).then(async function (module) { | |
decoderModule = module; | |
console.log('Draco decoder Module Initialized!'); | |
//main | |
const szTileset = fs.readFileSync(`${inFolder}/tileset.json`, 'utf8'); | |
recurveNode(JSON.parse(szTileset).root); | |
fs.promises.mkdir(outFolder, { recursive: true }); // make destination folder, if not exist | |
updateTilesetFile(szTileset,outFolder); | |
await convertPNTS(uris, inFolder, outFolder); | |
console.log('done'); | |
}); | |
function decodeDracoData(rawBuffer, decoder) { | |
const buffer = new decoderModule.DecoderBuffer(); | |
buffer.Init(new Int8Array(rawBuffer), rawBuffer.byteLength); | |
const geometryType = decoder.GetEncodedGeometryType(buffer); | |
const dracoGeometry = new decoderModule.PointCloud(); | |
const status = decoder.DecodeBufferToPointCloud(buffer, dracoGeometry); | |
return dracoGeometry; | |
} | |
function indexOfDraco(data) { | |
const sz = new TextDecoder('ascii').decode(data.slice(0, 500)); | |
return sz.indexOf("DRACO"); | |
} | |
async function convertPNTS(filenames, infolder, outfolder) { | |
const decoder = new decoderModule.Decoder(); | |
await batchPromises(4, filenames, convertFile); | |
await batchPromises(4, filenames, compressToGLB); | |
async function convertFile(filename) { | |
const data = fs.readFileSync(`${infolder}/${filename}`); | |
const decodedGeometry = decodeDracoData(data.slice(indexOfDraco(data)), decoder); | |
var numPoints = decodedGeometry.num_points(); | |
console.log(`Decoding ${filename}, size ${data.byteLength}, points:${numPoints}`); | |
const numValues = encodeGLTF(decodedGeometry); | |
function encodeGLTF(buffer) { | |
const attrs = { POSITION: 3, COLOR: 3 }; | |
let numValues = 0; | |
Object.keys(attrs).forEach((attr) => { | |
numValues = numPoints * 3; | |
const decoderAttr = decoderModule[attr]; | |
const attrId = decoder.GetAttributeId(buffer, decoderAttr); | |
if (attrId < 0) return; | |
const attribute = decoder.GetAttribute(buffer, attrId); | |
const attributeData = new decoderModule.DracoFloat32Array(); | |
decoder.GetAttributeFloatForAllPoints(buffer, attribute, attributeData); | |
const attributeDataArray = new Float32Array(numValues); | |
for (let i = 0; i < numValues; ++i) { | |
const div = (attr == "COLOR") ? 256 : 1; | |
attributeDataArray[i] = attributeData.GetValue(i) / div; | |
} | |
decoderModule.destroy(attributeData); | |
// write gltf/bin files | |
fs.writeFileSync(`${outfolder}/${filename}-${attr}.bin`, attributeDataArray); | |
}); | |
return numValues; | |
} | |
decoderModule.destroy(decodedGeometry); | |
const szgltf = getglTFString(`${filename}-POSITION`, `${filename}-COLOR`, numValues / 3); | |
fs.writeFileSync(`${outfolder}/${filename}.gltf`, szgltf); | |
}; | |
async function compressToGLB(filename) { | |
console.log(`Compressing ${filename}.gltf to GLB`); | |
await gltfpack.pack(['-cc', '-i', `${outfolder}/${filename}.gltf`, '-o', `${outfolder}/${filename.slice(0,-5)}.glb`], { read: fs.readFileSync, write: fs.writeFileSync}); | |
fs.rmSync(`${outfolder}/${filename}.gltf`); | |
fs.rmSync(`${outfolder}/${filename}-POSITION.bin`); | |
fs.rmSync(`${outfolder}/${filename}-COLOR.bin`); | |
} | |
decoderModule.destroy(decoder); | |
} | |
function updateTilesetFile(szjson, outfolder) { | |
const szresult = szjson.replaceAll('pnts','glb'); | |
fs.writeFileSync(`${outfolder}/tileset.json`, szresult); | |
console.log("updated tileset.json"); | |
} | |
function getglTFString(filenamePOS, filenameRGB, count) { | |
return ` | |
{ | |
"asset": { | |
"version": "2.0" | |
}, | |
"scene": 0, | |
"scenes": [ | |
{ | |
"nodes": [ | |
0 | |
] | |
} | |
], | |
"accessors": [ | |
{ | |
"bufferView": 0, | |
"componentType": 5126, | |
"count": ${count}, | |
"max": [ | |
1, | |
1, | |
1 | |
], | |
"min": [ | |
0, | |
0, | |
0 | |
], | |
"type": "VEC3" | |
}, | |
{ | |
"bufferView": 1, | |
"componentType": 5126, | |
"count": ${count}, | |
"max": [ | |
1, | |
1, | |
1 | |
], | |
"min": [ | |
0, | |
0, | |
0 | |
], | |
"type": "VEC3" | |
} | |
], | |
"bufferViews": [ | |
{ | |
"buffer": 0, | |
"byteLength": ${count * 3 * 4}, | |
"byteOffset": 0, | |
"byteStride": 12, | |
"target": 34962 | |
}, | |
{ | |
"buffer": 1, | |
"byteLength": ${count * 3 * 4}, | |
"byteOffset": 0, | |
"byteStride": 12, | |
"target": 34962 | |
} | |
], | |
"buffers": [ | |
{ | |
"byteLength": ${count * 3 * 4}, | |
"uri": "${filenamePOS}.bin" | |
}, | |
{ | |
"byteLength": ${count * 3 * 4}, | |
"uri": "${filenameRGB}.bin" | |
} | |
], | |
"extensionsUsed": [ | |
"KHR_materials_unlit" | |
], | |
"materials": [ | |
{ | |
"extensions": { | |
"KHR_materials_unlit": {} | |
} | |
} | |
], | |
"meshes": [ | |
{ | |
"primitives": [ | |
{ | |
"attributes": { | |
"COLOR_0": 1, | |
"POSITION": 0 | |
}, | |
"material": 0, | |
"mode": 0 | |
} | |
] | |
} | |
], | |
"nodes": [ | |
{ | |
"children": [ | |
1 | |
], | |
"name": "RootNode (gltf orientation matrix)", | |
"rotation": [ | |
-0.70710678118654746, | |
-0, | |
-0, | |
0.70710678118654757 | |
] | |
}, | |
{ | |
"mesh": 0, | |
"name": "" | |
} | |
] | |
} | |
` | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment