-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
.mtl files support for .obj files to render color in 3D custom geometry #6710
Merged
Qianqianye
merged 39 commits into
processing:main
from
diyaayay:Mtl-support-to-obj-files
Mar 19, 2024
Merged
Changes from all commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
a47fbbc
fixed unit test
diyaayay 2143dba
removed console logs
diyaayay 3498ad0
changed calculation of diffuse to vertex colors
diyaayay 5c7c07e
Merge branch 'processing:main' into Mtl-support-to-obj-files
diyaayay b46af55
removed console logs, changed default vertexColors to 1
diyaayay 0b7a1fe
Merge branch 'Mtl-support-to-obj-files' of https://github.com/diyaaya…
diyaayay 77637ba
Fixes vertexColor and mtlpath assignment
diyaayay 1169993
remove console logs
diyaayay 06c5e28
added unit test for loading obj mtl and assigned vertex colors
diyaayay 2b46a55
unit test for missing mtl file
diyaayay 0790146
unit tests for later when vertexColors are properly assigned
diyaayay 1fa08a2
plant.obj to smaller file for testing
diyaayay be5cf6c
added multiple mtl file handling, still need to fix vertexColors
diyaayay cd6039d
handles other failing unit tests
diyaayay 32f94f3
Merge branch 'processing:main' into Mtl-support-to-obj-files
diyaayay 16fad39
vertex color assign and duplicate vertices
diyaayay c3538b7
Merge branch 'Mtl-support-to-obj-files' of https://github.com/diyaaya…
diyaayay 3cb1a13
Merge branch 'main' into Mtl-support-to-obj-files
diyaayay 90a3a53
commit before merging
diyaayay 2fffa53
linting erros , conflicts
diyaayay 48b07a3
had merge conflicts
diyaayay 341b131
Merge remote-tracking branch 'upstream/main' into Mtl-support-to-obj-…
diyaayay 60a3b06
fixes vertexColors to flat array
diyaayay 9538edc
changes duplication logic
diyaayay 56bc7f2
checks for existence of current material
diyaayay 97950c7
fixes unit test for vertexColors being a flat array
diyaayay 4e46332
added visual test for diffuse colors of obj files
diyaayay 104463a
Fixed visual test for diffuse colors
diyaayay a4047af
screenshot for visual test
diyaayay 7236340
removes console logs
diyaayay b32c935
normalize model for visual test
diyaayay 19e4655
Merge remote-tracking branch 'upstream/main' into Mtl-support-to-obj-…
diyaayay 14a2420
visual tests work for fill in .obj model and mtl color png fix
diyaayay 687e010
adds check for either a fully colored model or colorless model
diyaayay 2aa937f
Merge remote-tracking branch 'upstream/main' into Mtl-support-to-obj-…
diyaayay 31bdeb9
colored model and unit test for color-no color
diyaayay ac68110
mtl file not found, obj displayed without materials
diyaayay d7aa2f7
unit test for missing mtl displayes obj file without color
diyaayay 25bbdc9
Sequential to Parallel mtl file handling
diyaayay File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -158,6 +158,56 @@ p5.prototype.loadModel = function(path,options) { | |
model.gid = `${path}|${normalize}`; | ||
const self = this; | ||
|
||
async function getMaterials(lines){ | ||
const parsedMaterialPromises=[]; | ||
|
||
for (let i = 0; i < lines.length; i++) { | ||
const mtllibMatch = lines[i].match(/^mtllib (.+)/); | ||
if (mtllibMatch) { | ||
let mtlPath=''; | ||
const mtlFilename = mtllibMatch[1]; | ||
const objPathParts = path.split('/'); | ||
if(objPathParts.length > 1){ | ||
objPathParts.pop(); | ||
const objFolderPath = objPathParts.join('/'); | ||
mtlPath = objFolderPath + '/' + mtlFilename; | ||
}else{ | ||
mtlPath = mtlFilename; | ||
} | ||
parsedMaterialPromises.push( | ||
fileExists(mtlPath).then(exists => { | ||
if (exists) { | ||
return parseMtl(self, mtlPath); | ||
} else { | ||
console.warn(`MTL file not found or error in parsing; proceeding without materials: ${mtlPath}`); | ||
return {}; | ||
|
||
} | ||
}).catch(error => { | ||
console.warn(`Error loading MTL file: ${mtlPath}`, error); | ||
return {}; | ||
}) | ||
); | ||
} | ||
} | ||
try { | ||
const parsedMaterials = await Promise.all(parsedMaterialPromises); | ||
const materials= Object.assign({}, ...parsedMaterials); | ||
return materials ; | ||
} catch (error) { | ||
return {}; | ||
} | ||
} | ||
|
||
|
||
async function fileExists(url) { | ||
try { | ||
const response = await fetch(url, { method: 'HEAD' }); | ||
return response.ok; | ||
} catch (error) { | ||
return false; | ||
} | ||
} | ||
if (fileType.match(/\.stl$/i)) { | ||
this.httpDo( | ||
path, | ||
|
@@ -188,31 +238,41 @@ p5.prototype.loadModel = function(path,options) { | |
} else if (fileType.match(/\.obj$/i)) { | ||
this.loadStrings( | ||
path, | ||
strings => { | ||
parseObj(model, strings); | ||
async lines => { | ||
try{ | ||
const parsedMaterials=await getMaterials(lines); | ||
|
||
if (normalize) { | ||
model.normalize(); | ||
} | ||
|
||
if (flipU) { | ||
model.flipU(); | ||
} | ||
parseObj(model, lines, parsedMaterials); | ||
|
||
if (flipV) { | ||
model.flipV(); | ||
}catch (error) { | ||
if (failureCallback) { | ||
failureCallback(error); | ||
} else { | ||
p5._friendlyError('Error during parsing: ' + error.message); | ||
} | ||
return; | ||
} | ||
finally{ | ||
if (normalize) { | ||
model.normalize(); | ||
} | ||
if (flipU) { | ||
model.flipU(); | ||
} | ||
if (flipV) { | ||
model.flipV(); | ||
} | ||
|
||
self._decrementPreload(); | ||
if (typeof successCallback === 'function') { | ||
successCallback(model); | ||
self._decrementPreload(); | ||
if (typeof successCallback === 'function') { | ||
successCallback(model); | ||
} | ||
} | ||
}, | ||
failureCallback | ||
); | ||
} else { | ||
p5._friendlyFileLoadError(3, path); | ||
|
||
if (failureCallback) { | ||
failureCallback(); | ||
} else { | ||
|
@@ -224,6 +284,52 @@ p5.prototype.loadModel = function(path,options) { | |
return model; | ||
}; | ||
|
||
function parseMtl(p5,mtlPath){ | ||
return new Promise((resolve, reject)=>{ | ||
let currentMaterial = null; | ||
let materials= {}; | ||
p5.loadStrings( | ||
mtlPath, | ||
lines => { | ||
for (let line = 0; line < lines.length; ++line){ | ||
const tokens = lines[line].trim().split(/\s+/); | ||
if(tokens[0] === 'newmtl') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice work on this parser, looks great! |
||
const materialName = tokens[1]; | ||
currentMaterial = materialName; | ||
materials[currentMaterial] = {}; | ||
}else if (tokens[0] === 'Kd'){ | ||
//Diffuse color | ||
materials[currentMaterial].diffuseColor = [ | ||
parseFloat(tokens[1]), | ||
parseFloat(tokens[2]), | ||
parseFloat(tokens[3]) | ||
]; | ||
} else if (tokens[0] === 'Ka'){ | ||
//Ambient Color | ||
materials[currentMaterial].ambientColor = [ | ||
parseFloat(tokens[1]), | ||
parseFloat(tokens[2]), | ||
parseFloat(tokens[3]) | ||
]; | ||
}else if (tokens[0] === 'Ks'){ | ||
//Specular color | ||
materials[currentMaterial].specularColor = [ | ||
parseFloat(tokens[1]), | ||
parseFloat(tokens[2]), | ||
parseFloat(tokens[3]) | ||
]; | ||
|
||
}else if (tokens[0] === 'map_Kd') { | ||
//Texture path | ||
materials[currentMaterial].texturePath = tokens[1]; | ||
} | ||
} | ||
resolve(materials); | ||
},reject | ||
); | ||
}); | ||
} | ||
|
||
/** | ||
* Parse OBJ lines into model. For reference, this is what a simple model of a | ||
* square might look like: | ||
|
@@ -235,7 +341,7 @@ p5.prototype.loadModel = function(path,options) { | |
* | ||
* f 4 3 2 1 | ||
*/ | ||
function parseObj(model, lines) { | ||
function parseObj(model, lines, materials= {}) { | ||
// OBJ allows a face to specify an index for a vertex (in the above example), | ||
// but it also allows you to specify a custom combination of vertex, UV | ||
// coordinate, and vertex normal. So, "3/4/3" would mean, "use vertex 3 with | ||
|
@@ -250,16 +356,25 @@ function parseObj(model, lines) { | |
vt: [], | ||
vn: [] | ||
}; | ||
const indexedVerts = {}; | ||
|
||
|
||
// Map from source index → Map of material → destination index | ||
const usedVerts = {}; // Track colored vertices | ||
let currentMaterial = null; | ||
const coloredVerts = new Set(); //unique vertices with color | ||
let hasColoredVertices = false; | ||
let hasColorlessVertices = false; | ||
for (let line = 0; line < lines.length; ++line) { | ||
// Each line is a separate object (vertex, face, vertex normal, etc) | ||
// For each line, split it into tokens on whitespace. The first token | ||
// describes the type. | ||
const tokens = lines[line].trim().split(/\b\s+/); | ||
|
||
if (tokens.length > 0) { | ||
if (tokens[0] === 'v' || tokens[0] === 'vn') { | ||
if (tokens[0] === 'usemtl') { | ||
// Switch to a new material | ||
currentMaterial = tokens[1]; | ||
}else if (tokens[0] === 'v' || tokens[0] === 'vn') { | ||
// Check if this line describes a vertex or vertex normal. | ||
// It will have three numeric parameters. | ||
const vertex = new p5.Vector( | ||
|
@@ -280,40 +395,44 @@ function parseObj(model, lines) { | |
// OBJ faces can have more than three points. Triangulate points. | ||
for (let tri = 3; tri < tokens.length; ++tri) { | ||
const face = []; | ||
|
||
const vertexTokens = [1, tri - 1, tri]; | ||
|
||
for (let tokenInd = 0; tokenInd < vertexTokens.length; ++tokenInd) { | ||
// Now, convert the given token into an index | ||
const vertString = tokens[vertexTokens[tokenInd]]; | ||
let vertIndex = 0; | ||
let vertParts=vertString.split('/'); | ||
|
||
// TODO: Faces can technically use negative numbers to refer to the | ||
// previous nth vertex. I haven't seen this used in practice, but | ||
// it might be good to implement this in the future. | ||
|
||
if (indexedVerts[vertString] !== undefined) { | ||
vertIndex = indexedVerts[vertString]; | ||
} else { | ||
const vertParts = vertString.split('/'); | ||
for (let i = 0; i < vertParts.length; i++) { | ||
vertParts[i] = parseInt(vertParts[i]) - 1; | ||
} | ||
for (let i = 0; i < vertParts.length; i++) { | ||
vertParts[i] = parseInt(vertParts[i]) - 1; | ||
} | ||
|
||
vertIndex = indexedVerts[vertString] = model.vertices.length; | ||
model.vertices.push(loadedVerts.v[vertParts[0]].copy()); | ||
if (loadedVerts.vt[vertParts[1]]) { | ||
model.uvs.push(loadedVerts.vt[vertParts[1]].slice()); | ||
} else { | ||
model.uvs.push([0, 0]); | ||
} | ||
if (!usedVerts[vertParts[0]]) { | ||
usedVerts[vertParts[0]] = {}; | ||
} | ||
|
||
if (loadedVerts.vn[vertParts[2]]) { | ||
model.vertexNormals.push(loadedVerts.vn[vertParts[2]].copy()); | ||
if (usedVerts[vertParts[0]][currentMaterial] === undefined) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! |
||
const vertIndex = model.vertices.length; | ||
model.vertices.push(loadedVerts.v[vertParts[0]].copy()); | ||
model.uvs.push(loadedVerts.vt[vertParts[1]] ? | ||
loadedVerts.vt[vertParts[1]].slice() : [0, 0]); | ||
model.vertexNormals.push(loadedVerts.vn[vertParts[2]] ? | ||
loadedVerts.vn[vertParts[2]].copy() : new p5.Vector()); | ||
|
||
usedVerts[vertParts[0]][currentMaterial] = vertIndex; | ||
face.push(vertIndex); | ||
if (currentMaterial | ||
&& materials[currentMaterial] | ||
&& materials[currentMaterial].diffuseColor) { | ||
// Mark this vertex as colored | ||
coloredVerts.add(loadedVerts.v[vertParts[0]]); //since a set would only push unique values | ||
} | ||
} else { | ||
face.push(usedVerts[vertParts[0]][currentMaterial]); | ||
} | ||
|
||
face.push(vertIndex); | ||
} | ||
|
||
if ( | ||
|
@@ -322,6 +441,23 @@ function parseObj(model, lines) { | |
face[1] !== face[2] | ||
) { | ||
model.faces.push(face); | ||
//same material for all vertices in a particular face | ||
if (currentMaterial | ||
&& materials[currentMaterial] | ||
&& materials[currentMaterial].diffuseColor) { | ||
hasColoredVertices=true; | ||
//flag to track color or no color model | ||
hasColoredVertices = true; | ||
davepagurek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const materialDiffuseColor = | ||
materials[currentMaterial].diffuseColor; | ||
for (let i = 0; i < face.length; i++) { | ||
model.vertexColors.push(materialDiffuseColor[0]); | ||
model.vertexColors.push(materialDiffuseColor[1]); | ||
model.vertexColors.push(materialDiffuseColor[2]); | ||
} | ||
}else{ | ||
hasColorlessVertices=true; | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -331,7 +467,10 @@ function parseObj(model, lines) { | |
if (model.vertexNormals.length === 0) { | ||
model.computeNormals(); | ||
} | ||
|
||
if (hasColoredVertices === hasColorlessVertices) { | ||
// If both are true or both are false, throw an error because the model is inconsistent | ||
throw new Error('Model coloring is inconsistent. Either all vertices should have colors or none should.'); | ||
} | ||
return model; | ||
} | ||
|
||
|
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Simple Cube OBJ File | ||
|
||
# Vertices | ||
v 0.0 0.0 0.0 | ||
v 1.0 0.0 0.0 | ||
v 1.0 1.0 0.0 | ||
v 0.0 1.0 0.0 | ||
v 0.0 0.0 1.0 | ||
v 1.0 0.0 1.0 | ||
v 1.0 1.0 1.0 | ||
v 0.0 1.0 1.0 | ||
|
||
# Faces | ||
f 1 2 3 4 | ||
f 5 6 7 8 | ||
f 1 5 8 4 | ||
f 2 6 7 3 | ||
f 4 3 7 8 | ||
f 1 2 6 5 |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
newmtl coloredMaterial | ||
Ns 96.078431 | ||
Ka 1.000000 1.000000 1.000000 | ||
Kd 1.000000 0.000000 0.000000 # Only this material has a diffuse color | ||
Ks 0.500000 0.500000 0.500000 | ||
Ke 0.0 0.0 0.0 | ||
Ni 1.000000 | ||
d 1.000000 | ||
illum 2 |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
mtllib eg1.mtl | ||
|
||
v 0 0 0 | ||
v 1 0 0 | ||
v 1 1 0 | ||
v 0 1 0 | ||
v 0.5 0.5 1 | ||
|
||
# Define faces without material | ||
f 1 2 5 | ||
f 2 3 5 | ||
|
||
# Apply material to subsequent faces | ||
usemtl coloredMaterial | ||
f 1 4 5 | ||
f 4 1 5 |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
mtllib octa.mtl | ||
v 1 0 0 | ||
v 0 1 0 | ||
v 0 0 1 | ||
v -1 0 0 | ||
v 0 -1 0 | ||
v 0 0 -1 | ||
usemtl m000001 | ||
f 1 5 6 | ||
usemtl m003627 | ||
f 2 1 6 | ||
usemtl m010778 | ||
f 1 2 3 | ||
usemtl m012003 | ||
f 4 2 6 | ||
usemtl m019240 | ||
f 4 5 3 | ||
usemtl m021392 | ||
f 5 4 6 | ||
usemtl m022299 | ||
f 2 4 3 | ||
usemtl m032767 | ||
f 5 1 3 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!