├── .gitignore ├── .github └── screenshot.png ├── package.json ├── README.md ├── dam.proto └── parse.js /.gitignore: -------------------------------------------------------------------------------- 1 | assets/ 2 | node_modules/ 3 | .DS_Store 4 | 5 | -------------------------------------------------------------------------------- /.github/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codespacehelp/matterport-decoder/HEAD/.github/screenshot.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "matterport-protobuf", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "parse.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "protobufjs": "7.2.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Matterport DAM decoder 2 | 3 | This is a decoder for the Matterport viewer. 4 | 5 | ![Screenshot](/.github/screenshot.png) 6 | 7 | More explanation here: 8 | 9 | https://codespace.help/posts/2023-03-24-matterport-protocol/ 10 | 11 | ## Usage 12 | 13 | 1. Install the dependencies 14 | 15 | ```bash 16 | npm install 17 | ``` 18 | 19 | 2. Run the parser 20 | 21 | ```bash 22 | node parse.js 23 | ``` -------------------------------------------------------------------------------- /dam.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // type i 4 | message DAMFile { 5 | repeated Chunk chunk = 1; 6 | repeated QuantizedChunk quantized_chunk = 2; 7 | } 8 | 9 | // type "o" 10 | message Chunk { 11 | required Vertices vertices = 1; 12 | required Faces faces = 2; 13 | required string chunk_name = 3; 14 | required string material_name = 4; 15 | } 16 | 17 | // type "h" 18 | message QuantizedChunk { 19 | required string chunk_name = 1; 20 | required string material_name = 2; 21 | required QuantizedVertices vertices = 3; 22 | repeated QuantizedUVs uvs = 4; 23 | required Faces faces = 5; 24 | } 25 | 26 | // type r 27 | message QuantizedVertices { 28 | required float quantization = 1; 29 | repeated float translation = 2 [packed=true]; 30 | repeated sint32 x = 3 [packed=true]; 31 | repeated sint32 y = 4 [packed=true]; 32 | repeated sint32 z = 5 [packed=true]; 33 | } 34 | 35 | 36 | // type n 37 | message Faces { 38 | repeated int32 faces = 1 [packed=true]; 39 | } 40 | 41 | // type a 42 | message QuantizedUVs { 43 | required string name = 1; 44 | required float quantization = 2; 45 | repeated sint32 u = 3 [packed=true]; 46 | repeated sint32 v = 4 [packed=true]; 47 | } 48 | 49 | // type s 50 | message Vertices { 51 | repeated float xyz = 1 [packed=true]; 52 | repeated float uv = 2 [packed=true]; 53 | } -------------------------------------------------------------------------------- /parse.js: -------------------------------------------------------------------------------- 1 | // parse.js 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | const protobuf = require("protobufjs"); 5 | 6 | const result = protobuf.parse(fs.readFileSync("dam.proto", "utf8")); 7 | // console.log(result); 8 | 9 | const root = result.root; 10 | const DAMFile = root.lookupType("DAMFile"); 11 | 12 | if (process.argv.length < 3) { 13 | console.log("Usage: node parse.js "); 14 | process.exit(1); 15 | } 16 | const filePath = process.argv[2]; 17 | 18 | // Read the protobuf file content 19 | const buffer = fs.readFileSync(filePath); 20 | 21 | // Decode the protobuf data 22 | const damFile = DAMFile.decode(buffer); 23 | 24 | // Generate the OBJ and MTL files 25 | generateObjAndMtlFiles(damFile, filePath); 26 | 27 | function writeObjVertices(damFile) { 28 | let objContent = ""; 29 | let chunkVertSizes = []; 30 | 31 | damFile.chunk.forEach((chunk) => { 32 | const xyz = chunk.vertices.xyz; 33 | 34 | for (let i = 0; i < xyz.length; i += 3) { 35 | const x = xyz[i]; 36 | const y = xyz[i + 1]; 37 | const z = xyz[i + 2]; 38 | 39 | // Rotate -90 degrees around the X axis 40 | const rotatedY = z; 41 | const rotatedZ = -y; 42 | 43 | objContent += `v ${x} ${rotatedY} ${rotatedZ}\n`; 44 | } 45 | 46 | chunkVertSizes.push(xyz.length / 3); 47 | }); 48 | 49 | objContent += "\n"; 50 | 51 | return { objContent, chunkVertSizes }; 52 | } 53 | 54 | function writeObjUV(damFile) { 55 | let objContent = ""; 56 | 57 | damFile.chunk.forEach((chunk) => { 58 | const uv = chunk.vertices.uv; 59 | 60 | for (let i = 0; i < uv.length; i += 2) { 61 | objContent += `vt ${uv[i]} ${uv[i + 1]}\n`; 62 | } 63 | }); 64 | 65 | objContent += "\n"; 66 | 67 | return objContent; 68 | } 69 | 70 | function writeObjFaces(damFile, chunkVertSizes) { 71 | let objContent = ""; 72 | 73 | damFile.chunk.forEach((chunk, chunkId) => { 74 | const chunkName = chunk.chunkName; 75 | const materialName = chunk.materialName; 76 | const faces = chunk.faces.faces; 77 | 78 | objContent += `usemtl ${materialName}\n`; 79 | objContent += `o ${chunkName}\n`; 80 | 81 | const offset = chunkVertSizes.slice(0, chunkId).reduce((a, b) => a + b, 0); 82 | 83 | for (let i = 0; i < faces.length; i += 3) { 84 | const f1 = faces[i] + 1 + offset; 85 | const f2 = faces[i + 1] + 1 + offset; 86 | const f3 = faces[i + 2] + 1 + offset; 87 | 88 | objContent += `f ${f1}/${f1} ${f2}/${f2} ${f3}/${f3}\n`; 89 | } 90 | 91 | objContent += "\n"; 92 | }); 93 | 94 | return objContent; 95 | } 96 | 97 | function generateObjAndMtlFiles(damFile, filePath) { 98 | // Prepare the file names (because the obj file refers to the mtl file) 99 | 100 | const f = path.parse(filePath); 101 | const baseName = path.join(f.dir, f.name); 102 | const mtlFilename = `${baseName}.mtl`; 103 | const objFilename = `${baseName}.obj`; 104 | 105 | const { objContent: objVertices, chunkVertSizes } = writeObjVertices(damFile); 106 | const objUV = writeObjUV(damFile); 107 | const objFaces = writeObjFaces(damFile, chunkVertSizes); 108 | 109 | const objContent = `mtllib ${mtlFilename}\n\n${objVertices}${objUV}${objFaces}`; 110 | 111 | // Generate MTL content 112 | let mtlContent = ""; 113 | damFile.chunk.forEach((chunk) => { 114 | const materialName = chunk.materialName; 115 | 116 | mtlContent += `newmtl ${materialName}\n`; 117 | mtlContent += `map_Ka ${materialName}\n`; 118 | mtlContent += `map_Kd ${materialName}\n`; 119 | mtlContent += "\n"; 120 | }); 121 | 122 | // Save the generated OBJ and MTL files 123 | fs.writeFileSync(objFilename, objContent); 124 | fs.writeFileSync(mtlFilename, mtlContent); 125 | } 126 | --------------------------------------------------------------------------------