Open NumaNumaNuma opened 7 years ago
@NumaNumaNuma Thanks for the report. Is this happening with the latest version of the javascript decoder? Just checking, because last week we have fixed some issues with UV decoding that may be related to the problem that you are experiencing.
I'm 2 commits behind I think, is this the one you're talking about: "6763aa32 - Our javascript decoders are now built with flag for precise floating point operations. This should resolve some issues with texture coordinate compression."
Yes, that's the one that fixed the issue for other users
I'll pull and check
It looks different but still not right (assuming my code is correct?) http://puu.sh/v7C0M/454a8f779b.png
Can you post your code for generating globalToLocalMap
? Also if you send me the model with the texture + material I can try it on our end. From the last image, it looks like the texture coordinates are decoded correctly (no weird deformations), but the mapping between uvs and subobjects (or textures and subobjects) is somehow wrong.
I think 'k' is ok, it's incremented once while 'i' is incremented 3 times it's basically i/3. points/normals/faces all map correctly.
Here is the code:
// Count
for (var i = 0, k = 0; i < numVertexCoordinates; i += 3, k ++) {
subObjectId = subObjAttributeData.GetValue(k);
// If subObjectId isn't in the list yet, create it and give it a count of 1
if (!(subObjectId in subObjectInfo)) {
subObjectInfo[subObjectId] = 1;
}
// Otherwise increment count
else {
subObjectInfo[subObjectId]++;
}
// Map global index with local index, (count -1)
globalToLocalMap[k] = subObjectInfo[subObjectId] - 1;
}
// Create buffers for each subobject
for (var key in subObjectInfo){
geometryBuffer = {
indices: [],
vertices: new Float32Array(subObjectInfo[key] * 3),
normals: new Float32Array(subObjectInfo[key] * 3),
uvs: new Float32Array(subObjectInfo[key] * 2)
};
subObjectBuffers[key] = geometryBuffer;
}
for (var i = 0, k = 0, l = 0; i < numVertexCoordinates; i += 3, k++, l +=2) {
subObjectId = subObjAttributeData.GetValue(Math.floor(k));
// Positions
subObjectBuffers[subObjectId].vertices[globalToLocalMap[k]*3] = -posAttributeData.GetValue(i);
subObjectBuffers[subObjectId].vertices[globalToLocalMap[k]*3+1] = posAttributeData.GetValue(i + 1);
subObjectBuffers[subObjectId].vertices[globalToLocalMap[k]*3+2] = posAttributeData.GetValue(i + 2);
// Normals
if (normalAttId != -1) {
subObjectBuffers[subObjectId].normals[globalToLocalMap[k]*3] = norAttributeData.GetValue(i);
subObjectBuffers[subObjectId].normals[globalToLocalMap[k]*3+1] = norAttributeData.GetValue(i + 1);
subObjectBuffers[subObjectId].normals[globalToLocalMap[k]*3+2] = norAttributeData.GetValue(i + 2);
}
// Uv's
if (texCoordAttId != -1) {
subObjectBuffers[subObjectId].uvs[globalToLocalMap[k]*2] = textCoordAttributeData.GetValue(l);
subObjectBuffers[subObjectId].uvs[globalToLocalMap[k]*2+1] = textCoordAttributeData.GetValue(l + 1);
}
}
I have emailed you the files. thanks!
Ok I found the problem, your latest patch DID fix it. The engine I'm using has inverted Y coordinates, so I had to flip the second UV value and it's all good now. thanks very much!
@NumaNumaNuma Thanks for the update. Good to know!
@NumaNumaNuma I want to know how you get the subObjAttributeData and the subObjectId? Also the subObject model is mesh or point ? use wrapper.GetAttributeId(dracoGeometry, dracoDecoder.GENERIC)?
@openforus Sub object attribute are automatically created when encoding .obj files that define object groups using o
property. More details how to get the attribute can be found for example here: https://github.com/google/draco/issues/67#issuecomment-283124761
thanks I find the example in draco_encoder problem #67
At 2017-04-06 00:45:06, "Ondrej Stava" notifications@github.com wrote:
@openforus Sub object attribute are automatically created when encoding .obj files that define object groups using o property. More details how to get the attribute can be found for example here: #67 (comment)
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.
Hi ondys NumaNumaNuma, Is it only supported point cloud for sub-objects?So for TRIANGULAR_MESH I want to know how to get the MeshFace to set the sub-object's geometryBuffer.indices
@openforus I believe it's actually supported only for triangular meshes (not point clouds). To add indices for sub-objects above you can for example add one extra loop before the Create buffers for each subobjects
comment:
numSubObjectIndices = new Int32Array(subObjectInfo.length);
for (let i = 0; i < subObjectInfo.length; ++i) numSubObjectIndices[i] = 0;
for (let i = 0; i < 3 * numFaces; ++i) {
const pointId = indices.GetValue(i);
subObjectId = subObjAttributeData.GetValue(pointId);
numSubObjectIndices[subObjectId]++;
}
then you allocate indices for each subobject: replace indices: [],
in the Numa's code above with:
indices: new Uint32Array(numSubObjectIndices[key]),
lastly you add one more loop at the end where you fill the indices as:
for (let i = 0; i < subObjectInfo.length; ++i) numSubObjectIndices[i] = 0;
for (let i = 0; i < 3 * numFaces; ++i) {
const pointId = indices.GetValue(i);
subObjectId = subObjAttributeData.GetValue(pointId);
subObjectBuffers[subObjectId].indices[numSubObjectIndices[subObjectId]++] = globalToLocalMap[pointId];
}
@ondys thaks ondys ,I will try later ! Thanks again for your help!
@NumaNumaNuma @ondys I used the approach suggested here to get subobjects and apply material on them which are stored in a separate .mtl file. The approach works fine. But sometimes the subobject have multiple materials associated with them. For ex; the model at the link, https://drive.google.com/open?id=0B5ZnEve9BlcVWldWUFU5cS1IQ3M. For including such cases I separated subobjects with both different subobject id and material id. For ex
for (var i = 0, k = 0; i < numVertexCoordinates; i += 3, k ++) {
subObjectId = subObjAttributeData.GetValue(k);
var matId = materialAttributeData.GetValue(k);
var key = subObjectId.toString() + ":" + matId.toString();
// If key isn't in the dict yet, create it and give it a count of 1
if (!(key in subObjectInfo)) {
subObjectInfo[key] = 1;
}
// Otherwise increment count
else {
subObjectInfo[key]++;
}
// Map global index with local index, (count -1)
globalToLocalMap[k] = subObjectInfo[key] - 1;
}
Changing the subsequent code accordingly. But the issue occurs when the same sub-object have same material repeated twice in .obj file (For ex; the one in the link). So can you suggest what can be done to include that case (Though these may not occur very frequently)?
Hi, I’m little bit confused when I try to decode models materialAttrId and subObjAttId return always -1. For shubhamagarwal003’s ex (you add extra 'a' at the end of your link) if I encode it with command
$ ./draco_encoder -i car.obj -o car.drc --metadata
I have:
Encoder options: Compression level = 7 Positions: Quantization = 14 bits Texture coordinates: Quantization = 12 bits Normals: Quantization = 10 bits
Encoded mesh saved to car.drc (42 ms to encode)
Encoded size = 133520 bytes
And same size without --metadata tag
@shubhamagarwal003 can you share me your DracoLoader code please?
@shubhamagarwal003 I believe there is a bug in Draco obj loader with repeated materials in one obj files that should be fixed in our next release
@JackC09 Thanks for the report, I'll investigate
@JackC09 which commit are you using. I suppose this PR should fix your issue
@shubhamagarwal003 I use the latest commit. I think I'm doing something wrong in DracoLoader. Can you send me yours?
@JackC09 I've done changes in DracoLoader.js in the repo. Mostly in function convertDracoGeometryTo3JS. Here is my modified function (Sorry for the code quality).
convertDracoGeometryTo3JS: function(dracoDecoder, decoder, geometryType, buffer) {
if (this.getAttributeOptions('position').skipDequantization === true) {
decoder.SkipAttributeTransform(dracoDecoder.POSITION);
}
var dracoGeometry;
var decodingStatus;
const start_time = performance.now();
if (geometryType === dracoDecoder.TRIANGULAR_MESH) {
dracoGeometry = new dracoDecoder.Mesh();
decodingStatus = decoder.DecodeBufferToMesh(buffer, dracoGeometry);
} else {
dracoGeometry = new dracoDecoder.PointCloud();
decodingStatus =
decoder.DecodeBufferToPointCloud(buffer, dracoGeometry);
}
if (!decodingStatus.ok() || dracoGeometry.ptr == 0) {
var errorMsg = 'THREE.DRACOLoader: Decoding failed: ';
errorMsg += decodingStatus.error_msg();
console.error(errorMsg);
dracoDecoder.destroy(decoder);
dracoDecoder.destroy(dracoGeometry);
throw new Error(errorMsg);
}
var decode_end = performance.now();
dracoDecoder.destroy(buffer);
/*
* Example on how to retrieve mesh and attributes.
*/
var numFaces, numPoints;
var numVertexCoordinates, numTextureCoordinates, numColorCoordinates;
var numAttributes;
var numColorCoordinateComponents = 3;
// For output basic geometry information.
var geometryInfoStr;
if (geometryType == dracoDecoder.TRIANGULAR_MESH) {
numFaces = dracoGeometry.num_faces();
if (this.verbosity > 0) {
console.log('Number of faces loaded: ' + numFaces.toString());
}
} else {
numFaces = 0;
}
numPoints = dracoGeometry.num_points();
numVertexCoordinates = numPoints * 3;
numTextureCoordinates = numPoints * 2;
numColorCoordinates = numPoints * 3;
numAttributes = dracoGeometry.num_attributes();
console.log("dracoGeometry", dracoGeometry);
if (this.verbosity > 0) {
console.log('Number of points loaded: ' + numPoints.toString());
console.log('Number of attributes loaded: ' +
numAttributes.toString());
}
// Get position attribute. Must exists.
var posAttId = decoder.GetAttributeId(dracoGeometry,
dracoDecoder.POSITION);
if (posAttId == -1) {
var errorMsg = 'THREE.DRACOLoader: No position attribute found.';
console.error(errorMsg);
dracoDecoder.destroy(decoder);
dracoDecoder.destroy(dracoGeometry);
throw new Error(errorMsg);
}
var posAttribute = decoder.GetAttribute(dracoGeometry, posAttId);
var posAttributeData = new dracoDecoder.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(
dracoGeometry, posAttribute, posAttributeData);
console.log("posAttribute", posAttribute);
// Get color attributes if exists.
var colorAttId = decoder.GetAttributeId(dracoGeometry,
dracoDecoder.COLOR);
var colAttributeData;
if (colorAttId != -1) {
if (this.verbosity > 0) {
console.log('Loaded color attribute.');
}
var colAttribute = decoder.GetAttribute(dracoGeometry, colorAttId);
if (colAttribute.num_components() === 4) {
numColorCoordinates = numPoints * 4;
numColorCoordinateComponents = 4;
}
colAttributeData = new dracoDecoder.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(dracoGeometry, colAttribute,
colAttributeData);
}
// Get normal attributes if exists.
var normalAttId =
decoder.GetAttributeId(dracoGeometry, dracoDecoder.NORMAL);
var norAttributeData;
if (normalAttId != -1) {
if (this.verbosity > 0) {
console.log('Loaded normal attribute.');
}
var norAttribute = decoder.GetAttribute(dracoGeometry, normalAttId);
norAttributeData = new dracoDecoder.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(dracoGeometry, norAttribute,
norAttributeData);
}
// Get texture coord attributes if exists.
var texCoordAttId =
decoder.GetAttributeId(dracoGeometry, dracoDecoder.TEX_COORD);
var textCoordAttributeData;
if (texCoordAttId != -1) {
if (this.verbosity > 0) {
console.log('Loaded texture coordinate attribute.');
}
var texCoordAttribute = decoder.GetAttribute(dracoGeometry,
texCoordAttId);
textCoordAttributeData = new dracoDecoder.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(dracoGeometry,
texCoordAttribute,
textCoordAttributeData);
}
var materialAttrId = decoder.GetAttributeIdByName(dracoGeometry, "material");
var materialAttributeData;
if (materialAttrId != -1) {
if (this.verbosity > 0) {
console.log('Loaded material attribute.');
}
}
var materialAttribute = decoder.GetAttribute(dracoGeometry,
materialAttrId);
materialAttributeData = new dracoDecoder.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(dracoGeometry,
materialAttribute,
materialAttributeData);
var subObjAttId = decoder.GetAttributeIdByName(dracoGeometry, "sub_obj");
var subObjAttributeData;
if (subObjAttId != -1) {
if (this.verbosity > 0) {
console.log('Loaded Sub Obj attribute.');
}
}
var subObjAttribute = decoder.GetAttribute(dracoGeometry,
subObjAttId);
subObjAttributeData = new dracoDecoder.DracoFloat32Array();
decoder.GetAttributeFloatForAllPoints(dracoGeometry,
subObjAttribute,
subObjAttributeData);
var subObjectInfo = {};
var subObjectBuffers = [];
var globalToLocalMap = {};
for (var i = 0, k = 0; i < numVertexCoordinates; i += 3, k ++) {
var subObjectId = subObjAttributeData.GetValue(k);
var matId = materialAttributeData.GetValue(k);
var key = subObjectId.toString() + ":" + matId.toString();
// If subObjectId isn't in the list yet, create it and give it a count of 1
if (!(key in subObjectInfo)) {
subObjectInfo[key] = 1;
}
// Otherwise increment count
else {
subObjectInfo[key]++;
}
// Map global index with local index, (count -1)
globalToLocalMap[k] = subObjectInfo[key] - 1;
}
var indices;
if (geometryType == dracoDecoder.TRIANGULAR_MESH) {
if (this.drawMode === THREE.TriangleStripDrawMode) {
var stripsArray = new dracoDecoder.DracoInt32Array();
var numStrips = decoder.GetTriangleStripsFromMesh(
dracoGeometry, stripsArray);
indices = new Uint32Array(stripsArray.size());
for (var i = 0; i < stripsArray.size(); ++i) {
indices[i] = stripsArray.GetValue(i);
}
dracoDecoder.destroy(stripsArray);
} else {
var numIndices = numFaces * 3;
indices = new Uint32Array(numIndices);
var ia = new dracoDecoder.DracoInt32Array();
for (var i = 0; i < numFaces; ++i) {
decoder.GetFaceFromMesh(dracoGeometry, i, ia);
var index = i * 3;
indices[index] = ia.GetValue(0);
indices[index + 1] = ia.GetValue(1);
indices[index + 2] = ia.GetValue(2);
}
dracoDecoder.destroy(ia);
}
}
var subObjectInfoKeys = Object.keys(subObjectInfo);
var numSubObjectIndices = {}; //new Int32Array(subObjectInfoKeys.length);
for (var i in subObjectInfo) numSubObjectIndices[i] = 0;
for (var i = 0; i < 3 * numFaces; ++i) {
const pointId = indices[i];
var subObjectId = subObjAttributeData.GetValue(pointId);
var matId = materialAttributeData.GetValue(pointId);
var key = subObjectId.toString() + ":" + matId.toString();
numSubObjectIndices[key]++;
}
for (var key in subObjectInfo){
var geometryBuffer = {
indices: new Uint32Array(numSubObjectIndices[key]),
vertices: new Float32Array(subObjectInfo[key] * 3),
normals: new Float32Array(subObjectInfo[key] * 3),
uvs: new Float32Array(subObjectInfo[key] * 2),
colors: new Float32Array(subObjectInfo[key] * 3)
};
subObjectBuffers[key] = {"geometryBuffer": geometryBuffer, "materialId": -1};
}
for (var i = 0, k = 0, l = 0; i < numVertexCoordinates; i += 3, k++, l +=2) {
var subObjectId = subObjAttributeData.GetValue(k);
var matId = materialAttributeData.GetValue(k);
var key = subObjectId.toString() + ":" + matId.toString();
// Positions
subObjectBuffers[key].geometryBuffer.vertices[globalToLocalMap[k]*3] = posAttributeData.GetValue(i);
subObjectBuffers[key].geometryBuffer.vertices[globalToLocalMap[k]*3+1] = posAttributeData.GetValue(i + 1);
subObjectBuffers[key].geometryBuffer.vertices[globalToLocalMap[k]*3+2] = posAttributeData.GetValue(i + 2);
// Normals
if (normalAttId != -1) {
subObjectBuffers[key].geometryBuffer.normals[globalToLocalMap[k]*3] = norAttributeData.GetValue(i);
subObjectBuffers[key].geometryBuffer.normals[globalToLocalMap[k]*3+1] = norAttributeData.GetValue(i + 1);
subObjectBuffers[key].geometryBuffer.normals[globalToLocalMap[k]*3+2] = norAttributeData.GetValue(i + 2);
}
if (colorAttId != -1) {
// Draco colors are already normalized.
subObjectBuffers[key].geometryBuffer.colors[globalToLocalMap[k]*3] = colAttributeData.GetValue(i);
subObjectBuffers[key].geometryBuffer.colors[globalToLocalMap[k]*3+1] = colAttributeData.GetValue(i + 1);
subObjectBuffers[key].geometryBuffer.colors[globalToLocalMap[k]*3+2] = colAttributeData.GetValue(i + 2);
} else {
// Default is white. This is faster than TypedArray.fill().
subObjectBuffers[key].geometryBuffer.colors[globalToLocalMap[k]*3] = 1.0;
subObjectBuffers[key].geometryBuffer.colors[globalToLocalMap[k]*3+1] = 1.0;
subObjectBuffers[key].geometryBuffer.colors[globalToLocalMap[k]*3+2] = 1.0;
}
// Uv's
if (texCoordAttId != -1) {
subObjectBuffers[key].geometryBuffer.uvs[globalToLocalMap[k]*2] = textCoordAttributeData.GetValue(l);
subObjectBuffers[key].geometryBuffer.uvs[globalToLocalMap[k]*2+1] = textCoordAttributeData.GetValue(l + 1);
}
if(materialAttrId != -1){
subObjectBuffers[key].materialId = materialAttributeData.GetValue(k);
/*if(subObjectBuffers[key].materialId.indexOf(matId) == -1){
subObjectBuffers[key].materialId.push(matId);
}*/
}
}
for (var i in subObjectInfo) numSubObjectIndices[i] = 0;
for (var i = 0; i < 3 * numFaces; ++i) {
const pointId = indices[i];
var subObjectId = subObjAttributeData.GetValue(pointId);
var matId = materialAttributeData.GetValue(pointId);
var key = subObjectId.toString() + ":" + matId.toString();
subObjectBuffers[key].geometryBuffer.indices[numSubObjectIndices[key]++] = globalToLocalMap[pointId];
}
var container = new THREE.Group();
var scales = [];
var materialKeys = Object.keys(this.materials.materials);
for(var key in subObjectBuffers){
var geometryBuffer = subObjectBuffers[key].geometryBuffer;
var geometry = new THREE.BufferGeometry();
geometry.drawMode = this.drawMode;
if (geometryType == dracoDecoder.TRIANGULAR_MESH) {
geometry.setIndex(new(geometryBuffer.indices.length > 65535 ?
THREE.Uint32BufferAttribute : THREE.Uint16BufferAttribute)
(geometryBuffer.indices, 1));
}
geometry.addAttribute('position',
new THREE.Float32BufferAttribute(geometryBuffer.vertices, 3));
var posTransform = new dracoDecoder.AttributeQuantizationTransform();
if (posTransform.InitFromAttribute(posAttribute)) {
// Quantized attribute. Store the quantization parameters into the
// THREE.js attribute.
geometry.attributes['position'].isQuantized = false;
geometry.attributes['position'].maxRange = posTransform.range();
geometry.attributes['position'].numQuantizationBits =
posTransform.quantization_bits();
geometry.attributes['position'].minValues = new Float32Array(3);
for (var i = 0; i < 3; ++i) {
geometry.attributes['position'].minValues[i] =
posTransform.min_value(i);
}
}
dracoDecoder.destroy(posTransform);
geometry.addAttribute('color',
new THREE.Float32BufferAttribute(geometryBuffer.colors,
numColorCoordinateComponents));
if (normalAttId != -1) {
geometry.addAttribute('normal',
new THREE.Float32BufferAttribute(geometryBuffer.normals, 3));
}
if (texCoordAttId != -1) {
geometry.addAttribute('uv',
new THREE.Float32BufferAttribute(geometryBuffer.uvs, 2));
}
var material = this.materials.materials[materialKeys[subObjectBuffers[key].materialId]];
material.shading = THREE.SmoothShading;
var mesh = new THREE.Mesh( geometry, material );
mesh.drawMode = this.drawMode;
geometry.computeBoundingBox();
mesh.name = key;
container.add( mesh );
}
return container;
}
I've another function setMaterials in which I set the materials loaded from Mtl-Loader.
mtlLoader.setPath('<path-to-your-mtl-file>');
mtlLoader.load('<mtl-filename>', function(materials) {
materials.preload();
dracoLoader.setMaterials(materials);
dracoLoader.decodeDracoFile(reader.result, function(contain) {
scene.add(contain);
});
});
@shubhamagarwal003 thanks it's really useful for me.
@ondys by placing --metadata flag before like this
$ ./draco_encoder --metadata -i car.obj -o car.drc
result is better with shubhamagarwal003's model:
Encoded size = 133614 bytes
Now I have subObjAttId but materialAttrId still return -1
@JackC09 Yes there was a bug in Draco when --metadata was the last argument passed in (in which case it was ignored). It's fixed now in our private repo and we will push the fix to public in the coming days.
I'll look into the issue with materialAttrId == -1
@ondys Hi, I tried the latest commit 1.1.0 and for some models materialAttrId was ok but subObjAttId == -1 https://drive.google.com/drive/folders/0B7BhishE3I-cc1VkMS0xekd4Wjg?usp=sharing
In the description it is written 'Draco can preserve material or sub-object names' how can I get the subObjName?
Which of the shared model had the problem with subObjAttId ? Note that subobject ids are currently generated only when the obj uses "o " keyword. When the .obj is encoded with --metadata
flag, the metadata for sub object id attribute will then contain mapping between sub-object ids and the corresponding sub object names.
@ondys where map between sub-object ids and the corresponding sub object names? or how get sub object name?
@pumpkindev In c++ you can see an example in ObjEncoder::GetSubObjects()
method (in obj_encoder.cc
).
For javascript, it seems we miss some API that is needed to accomplish this. I'll make the necessary changes and try to push into into our next update.
Do we have any programmatic way of loading a .drc file that has several textures? I have a large model (.obj
) that was generated using a photogrammetry service. When I compress the file to .drc
, I cannot get the multiple textures that the photogrammetry services generates for the single .obj
to map properly. If you have any suggestions, it would be extremely helpful, as it's becoming a huge blocker for us.
I am thinking of writing a parser to separate the OBJ into several sub OBJs to reference each materials file individually, but perhaps we already have a working version of something similar (or better) than this approach?
Hi,
I'm bringing in an obj model with sub-objects. the suObjectAttribute works fine to assign points back to the correct sub-objects, but uv's don't come out properly. here's what I'm doing:
result: https://puu.sh/v58lD/bf2c2363d9.png
thanks