diff --git a/sandbox/embed_edges.html b/sandbox/embed_edges.html index 8de44c7..9219791 100644 --- a/sandbox/embed_edges.html +++ b/sandbox/embed_edges.html @@ -15,6 +15,7 @@ + diff --git a/sandbox/embed_filehost.html b/sandbox/embed_filehost.html index e86b533..078d28f 100644 --- a/sandbox/embed_filehost.html +++ b/sandbox/embed_filehost.html @@ -15,6 +15,7 @@ + diff --git a/sandbox/embed_selfhost_code.html b/sandbox/embed_selfhost_code.html index b0a7a79..65c741a 100644 --- a/sandbox/embed_selfhost_code.html +++ b/sandbox/embed_selfhost_code.html @@ -15,6 +15,7 @@ + diff --git a/sandbox/embed_selfhost_errors.html b/sandbox/embed_selfhost_errors.html index fae2cc1..148a2ff 100644 --- a/sandbox/embed_selfhost_errors.html +++ b/sandbox/embed_selfhost_errors.html @@ -15,6 +15,7 @@ + diff --git a/sandbox/embed_selfhost_externallibs.html b/sandbox/embed_selfhost_externallibs.html index 8786cb4..0461ca5 100644 --- a/sandbox/embed_selfhost_externallibs.html +++ b/sandbox/embed_selfhost_externallibs.html @@ -15,6 +15,7 @@ + diff --git a/sandbox/embed_selfhost_fullscreen.html b/sandbox/embed_selfhost_fullscreen.html index d582929..ce3ff17 100644 --- a/sandbox/embed_selfhost_fullscreen.html +++ b/sandbox/embed_selfhost_fullscreen.html @@ -14,6 +14,7 @@ + diff --git a/sandbox/embed_selfhost_manual.html b/sandbox/embed_selfhost_manual.html index ef23b9f..e620a55 100644 --- a/sandbox/embed_selfhost_manual.html +++ b/sandbox/embed_selfhost_manual.html @@ -15,6 +15,7 @@ + diff --git a/sandbox/embed_selfhost_multiple.html b/sandbox/embed_selfhost_multiple.html index a80cbbc..d77b66a 100644 --- a/sandbox/embed_selfhost_multiple.html +++ b/sandbox/embed_selfhost_multiple.html @@ -15,6 +15,7 @@ + diff --git a/sandbox/embed_selfhost_single.html b/sandbox/embed_selfhost_single.html index 2c24360..0024f93 100644 --- a/sandbox/embed_selfhost_single.html +++ b/sandbox/embed_selfhost_single.html @@ -14,6 +14,7 @@ + diff --git a/sandbox/embed_selfhost_single_scroll.html b/sandbox/embed_selfhost_single_scroll.html index 64938f6..5691a6f 100644 --- a/sandbox/embed_selfhost_single_scroll.html +++ b/sandbox/embed_selfhost_single_scroll.html @@ -14,6 +14,7 @@ + diff --git a/source/geometry/coord4d.js b/source/geometry/coord4d.js new file mode 100644 index 0000000..5968dc7 --- /dev/null +++ b/source/geometry/coord4d.js @@ -0,0 +1,15 @@ +OV.Coord4D = class +{ + constructor (x, y, z, w) + { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + } + + Clone () + { + return new OV.Coord4D (this.x, this.y, this.z, this.w); + } +}; diff --git a/source/import/importergltf.js b/source/import/importergltf.js index 97335c3..3202c49 100644 --- a/source/import/importergltf.js +++ b/source/import/importergltf.js @@ -121,6 +121,13 @@ OV.GltfBufferReader = class let z = this.ReadComponent (); this.SkipBytesByStride (3); return new OV.Coord3D (x, y, z); + } else if (this.dataType === OV.GltfDataType.VEC4) { + let x = this.ReadComponent (); + let y = this.ReadComponent (); + let z = this.ReadComponent (); + let w = this.ReadComponent (); + this.SkipBytesByStride (4); + return new OV.Coord4D (x, y, z, w); } return null; } @@ -353,6 +360,15 @@ OV.GltfExtensions = class attributeArray[i + 2] )); } + } else if (numComponents === 4) { + for (let i = 0; i < attributeArray.length; i += 4) { + processor (new OV.Coord4D ( + attributeArray[i + 0], + attributeArray[i + 1], + attributeArray[i + 2], + attributeArray[i + 3] + )); + } } draco._free (attributePtr); } @@ -385,6 +401,7 @@ OV.GltfExtensions = class } let hasVertices = (extensionParams.attributes.POSITION !== undefined); + let hasVertexColors = (extensionParams.attributes.COLOR_0 !== undefined); let hasNormals = (extensionParams.attributes.NORMAL !== undefined); let hasUVs = (extensionParams.attributes.TEXCOORD_0 !== undefined); @@ -400,6 +417,13 @@ OV.GltfExtensions = class mesh.AddVertex (vertex); }); + if (hasVertexColors) { + EnumerateComponents (this.draco, decoder, dracoMesh, extensionParams.attributes.COLOR_0, (vertexColor) => { + let color = new OV.Color (vertexColor.x * 255.0, vertexColor.y * 255.0, vertexColor.z * 255.0); + mesh.AddVertexColor (color); + }); + } + if (hasNormals) { EnumerateComponents (this.draco, decoder, dracoMesh, extensionParams.attributes.NORMAL, (normal) => { mesh.AddNormal (normal); @@ -778,6 +802,7 @@ OV.ImporterGltf = class extends OV.ImporterBase } let hasVertices = (primitive.attributes.POSITION !== undefined); + let hasVertexColors = (primitive.attributes.COLOR_0 !== undefined); let hasNormals = (primitive.attributes.NORMAL !== undefined); let hasUVs = (primitive.attributes.TEXCOORD_0 !== undefined); let hasIndices = (primitive.indices !== undefined); @@ -807,6 +832,18 @@ OV.ImporterGltf = class extends OV.ImporterBase return; } + if (hasVertexColors) { + let accessor = gltf.accessors[primitive.attributes.COLOR_0]; + let reader = this.GetReaderFromAccessor (gltf, accessor); + if (reader === null) { + return; + } + reader.EnumerateData ((data) => { + let color = new OV.Color (data.x * 255.0, data.y * 255.0, data.z * 255.0); + mesh.AddVertexColor (color); + }); + } + if (hasNormals) { let accessor = gltf.accessors[primitive.attributes.NORMAL]; let reader = this.GetReaderFromAccessor (gltf, accessor); diff --git a/source/import/importerply.js b/source/import/importerply.js index df802ec..8da4444 100644 --- a/source/import/importerply.js +++ b/source/import/importerply.js @@ -97,38 +97,9 @@ OV.PlyMaterialHandler = class constructor (model) { this.model = model; - this.vertexColors = []; this.colorToMaterial = {}; } - AddVertexColor (color) - { - this.vertexColors.push (color); - } - - GetTriangleColor (v0, v1, v2) - { - let vertexCount = this.vertexColors.length; - if (v0 >= vertexCount || v1 >= vertexCount || v2 >= vertexCount) { - return null; - } - return this.vertexColors[v0]; - } - - GetTriangleFaceMaterialIndex (color) - { - return this.GetMaterialIndexByColor (color); - } - - GetTriangleVertexMaterialIndex (v0, v1, v2) - { - let color = this.GetTriangleColor (v0, v1, v2); - if (color === null) { - return null; - } - return this.GetMaterialIndexByColor (color); - } - GetMaterialIndexByColor (color) { let materialName = 'Color ' + @@ -397,7 +368,7 @@ OV.ImporterPly = class extends OV.ImporterBase let z = ReadByFormat (reader, element.format[2]); let color = SkipAndGetColor (reader, element.format, 3); if (color !== null) { - materialHandler.AddVertexColor (color); + this.mesh.AddVertexColor (new OV.Color (color[0], color[1], color[2])); } this.mesh.AddVertex (new OV.Coord3D (x, y, z)); } @@ -411,9 +382,7 @@ OV.ImporterPly = class extends OV.ImporterBase let v2 = vertices[i + 2]; let triangle = new OV.Triangle (v0, v1, v2); if (faceColor !== null) { - triangle.mat = materialHandler.GetTriangleFaceMaterialIndex (faceColor); - } else { - triangle.mat = materialHandler.GetTriangleVertexMaterialIndex (v0, v1, v2); + triangle.mat = materialHandler.GetMaterialIndexByColor (faceColor); } this.mesh.AddTriangle (triangle); } diff --git a/source/model/material.js b/source/model/material.js index 7a3a50c..305ffc2 100644 --- a/source/model/material.js +++ b/source/model/material.js @@ -100,6 +100,8 @@ OV.MaterialBase = class this.name = ''; this.color = new OV.Color (0, 0, 0); + + this.vertexColors = false; } IsEqual (rhs) @@ -116,6 +118,9 @@ OV.MaterialBase = class if (!OV.ColorIsEqual (this.color, rhs.color)) { return false; } + if (this.vertexColors !== rhs.vertexColors) { + return false; + } return true; } }; diff --git a/source/model/mesh.js b/source/model/mesh.js index 0d4ce3c..5b54f44 100644 --- a/source/model/mesh.js +++ b/source/model/mesh.js @@ -4,6 +4,7 @@ OV.Mesh = class extends OV.ModelObject3D { super (); this.vertices = []; + this.vertexColors = []; this.normals = []; this.uvs = []; this.triangles = []; @@ -14,6 +15,11 @@ OV.Mesh = class extends OV.ModelObject3D return this.vertices.length; } + VertexColorCount () + { + return this.vertexColors.length; + } + NormalCount () { return this.normals.length; @@ -45,6 +51,22 @@ OV.Mesh = class extends OV.ModelObject3D return this.vertices[index]; } + AddVertexColor (color) + { + this.vertexColors.push (color); + return this.vertexColors.length - 1; + } + + SetVertexColor (index, color) + { + this.vertexColors[index] = color; + } + + GetVertexColor (index) + { + return this.vertexColors[index]; + } + AddNormal (normal) { this.normals.push (normal); diff --git a/source/model/modelfinalization.js b/source/model/modelfinalization.js index 475911a..caecbfe 100644 --- a/source/model/modelfinalization.js +++ b/source/model/modelfinalization.js @@ -1,6 +1,6 @@ OV.FinalizeModel = function (model, getDefaultMaterial) { - function FinalizeMesh (mesh, getDefaultMaterialIndex) + function FinalizeMesh (model, mesh, getDefaultMaterialIndex) { function CalculateCurveNormals (mesh) { @@ -104,6 +104,17 @@ OV.FinalizeModel = function (model, getDefaultMaterial) if (status.calculateCurveNormals) { CalculateCurveNormals (mesh); } + + let hasVertexColors = (mesh.VertexCount () === mesh.VertexColorCount ()); + if (hasVertexColors) { + for (let i = 0; i < mesh.TriangleCount (); i++) { + let triangle = mesh.GetTriangle (i); + if (triangle.mat !== null) { + let material = model.GetMaterial (triangle.mat); + material.vertexColors = true; + } + } + } } let defaultMaterialIndex = null; @@ -123,7 +134,7 @@ OV.FinalizeModel = function (model, getDefaultMaterial) i = i - 1; continue; } - FinalizeMesh (mesh, getDefaultMaterialIndex); + FinalizeMesh (model, mesh, getDefaultMaterialIndex); } let rootNode = model.GetRootNode (); diff --git a/source/threejs/threeconverter.js b/source/threejs/threeconverter.js index 5c4919c..ec4ff47 100644 --- a/source/threejs/threeconverter.js +++ b/source/threejs/threeconverter.js @@ -122,9 +122,13 @@ OV.ConvertModelToThreeObject = function (model, params, output, callbacks) let material = model.GetMaterial (materialIndex); let baseColor = new THREE.Color (material.color.r / 255.0, material.color.g / 255.0, material.color.b / 255.0); + if (material.vertexColors) { + baseColor.setRGB (1.0, 1.0, 1.0); + } let materialParams = { color : baseColor, + vertexColors : material.vertexColors, opacity : material.opacity, transparent : material.transparent, alphaTest : material.alphaTest, @@ -210,9 +214,10 @@ OV.ConvertModelToThreeObject = function (model, params, output, callbacks) let threeGeometry = new THREE.BufferGeometry (); let meshThreeMaterials = []; let meshOriginalMaterials = []; - let modelToThreeMaterials = {}; + let modelToThreeMaterials = new Map (); let vertices = []; + let vertexColors = []; let normals = []; let uvs = []; @@ -222,7 +227,8 @@ OV.ConvertModelToThreeObject = function (model, params, output, callbacks) end : -1 }); - let meshHasUVs = mesh.TextureUVCount () > 0; + let meshHasVertexColors = (mesh.VertexCount () === mesh.VertexColorCount ()); + let meshHasUVs = (mesh.TextureUVCount () > 0); for (let i = 0; i < triangleIndices.length; i++) { let triangleIndex = triangleIndices[i]; let triangle = mesh.GetTriangle (triangleIndex); @@ -232,6 +238,17 @@ OV.ConvertModelToThreeObject = function (model, params, output, callbacks) let v2 = mesh.GetVertex (triangle.v2); vertices.push (v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z); + if (meshHasVertexColors) { + let vc0 = mesh.GetVertexColor (triangle.v0); + let vc1 = mesh.GetVertexColor (triangle.v1); + let vc2 = mesh.GetVertexColor (triangle.v2); + vertexColors.push ( + vc0.r / 255.0, vc0.g / 255.0, vc0.b / 255.0, + vc1.r / 255.0, vc1.g / 255.0, vc1.b / 255.0, + vc2.r / 255.0, vc2.g / 255.0, vc2.b / 255.0 + ); + } + let n0 = mesh.GetNormal (triangle.n0); let n1 = mesh.GetNormal (triangle.n1); let n2 = mesh.GetNormal (triangle.n2); @@ -247,10 +264,8 @@ OV.ConvertModelToThreeObject = function (model, params, output, callbacks) } let modelMaterialIndex = triangle.mat; - let materialIndex = modelToThreeMaterials[modelMaterialIndex]; - if (materialIndex === undefined) { - materialIndex = meshThreeMaterials.length; - modelToThreeMaterials[modelMaterialIndex] = materialIndex; + if (!modelToThreeMaterials.has (modelMaterialIndex)) { + modelToThreeMaterials.set (modelMaterialIndex, meshThreeMaterials.length); meshThreeMaterials.push (modelThreeMaterials[modelMaterialIndex]); meshOriginalMaterials.push (modelMaterialIndex); if (i > 0) { @@ -266,6 +281,9 @@ OV.ConvertModelToThreeObject = function (model, params, output, callbacks) groups[groups.length - 1].end = triangleCount - 1; threeGeometry.setAttribute ('position', new THREE.Float32BufferAttribute (vertices, 3)); + if (vertexColors.length !== 0) { + threeGeometry.setAttribute ('color', new THREE.Float32BufferAttribute (vertexColors, 3)); + } threeGeometry.setAttribute ('normal', new THREE.Float32BufferAttribute (normals, 3)); if (uvs.length !== 0) { threeGeometry.setAttribute ('uv', new THREE.Float32BufferAttribute (uvs, 2)); diff --git a/test/testfiles/gltf/BoxVertexColors/README.md b/test/testfiles/gltf/BoxVertexColors/README.md new file mode 100644 index 0000000..af9fccc --- /dev/null +++ b/test/testfiles/gltf/BoxVertexColors/README.md @@ -0,0 +1,8 @@ +# Box Vertex Colors +## Screenshot + +![screenshot](screenshot/screenshot.png) + +## License Information + +Donated by Michael Feldstein for glTF testing. \ No newline at end of file diff --git a/test/testfiles/gltf/BoxVertexColors/glTF-Binary/BoxVertexColors.glb b/test/testfiles/gltf/BoxVertexColors/glTF-Binary/BoxVertexColors.glb new file mode 100644 index 0000000..4409cfb Binary files /dev/null and b/test/testfiles/gltf/BoxVertexColors/glTF-Binary/BoxVertexColors.glb differ diff --git a/test/testfiles/gltf/BoxVertexColors/glTF-Embedded/BoxVertexColors.gltf b/test/testfiles/gltf/BoxVertexColors/glTF-Embedded/BoxVertexColors.gltf new file mode 100644 index 0000000..d7bebd9 --- /dev/null +++ b/test/testfiles/gltf/BoxVertexColors/glTF-Embedded/BoxVertexColors.gltf @@ -0,0 +1,220 @@ +{ + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 0.5, + 0.5, + 0.5 + ], + "min": [ + -0.5, + -0.5, + -0.5 + ], + "type": "VEC3" + }, + { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 3, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "type": "VEC4" + }, + { + "bufferView": 4, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "type": "VEC2" + } + ], + "asset": { + "generator": "FBX2glTF", + "version": "2.0" + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 72, + "byteOffset": 0, + "target": 34963 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 72, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 360, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 384, + "byteOffset": 648, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 192, + "byteOffset": 1032, + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 1224, + "uri": "data:application/octet-stream;base64,AAABAAIAAQAAAAMABAAFAAYABwAEAAYACAAJAAoACwAJAAgADAANAA4ADQAMAA8AEAARABIAEAASABMAFAAVABYAFAAWABcAAAAAvwAAAD8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAL8AAAA/AAAAPwAAAD8AAAA/AAAAvwAAAL8AAAA/AAAAPwAAAL8AAAA/AAAAvwAAAD8AAAA/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAL8AAAC/AAAAPwAAAD8AAAA/AAAAPwAAAL8AAAC/AAAAvwAAAL8AAAC/AAAAvwAAAD8AAAC/AAAAPwAAAD8AAAC/AAAAPwAAAL8AAAA/AAAAvwAAAL8AAAA/AAAAvwAAAL8AAAC/AAAAPwAAAL8AAAC/AAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAACAvwAAAAAAAAAAAAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AAAAAAAAAAAAAIA/AACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAAAAAAAAAgL8AAAAAHekGMHp6JTz+/38/UdInMBHtfz9SNZs6rTHKPJsTwTrvy00/XMpNP0HLTT8AAIA/3ssCP4z/fz+gF+43whYUON7LAj+M/38/oBfuN8IWFDgd6QYwenolPP7/fz9R0icw6vR/PwjDNTqNSsY80xtiOu/LTT9cyk0/QctNPwAAgD8R7X8/UjWbOq0xyjybE8E678tNP1zKTT9By00/AACAP6bmIzurlgY+BNh/P0ziSzveywI/jP9/P6AX7jfCFhQ478tNP1zKTT9By00/AACAP6bmIzurlgY+BNh/P0ziSzsSAI885Oh+PxdguT574rE8Ee1/P1I1mzqtMco8mxPBOhIAjzzk6H4/F2C5PnvisTzq9H8/CMM1Oo1KxjzTG2I6HekGMHp6JTz+/38/UdInMO/LTT9cyk0/QctNPwAAgD+m5iM7q5YGPgTYfz9M4ks778tNP1zKTT9By00/AACAP+r0fz8IwzU6jUrGPNMbYjoSAI885Oh+PxdguT574rE8AACAPgAAAAAAAAA/qqqqPgAAAD8AAAAAAACAPqqqqj4AAIA+qqqqPgAAAACqqqo+AAAAAKqqKj8AAIA+qqoqPwAAAD+qqqo+AACAPqqqKj8AAAA/qqoqPwAAgD6qqqo+AABAP6qqqj4AAAA/qqoqPwAAQD+qqio/AAAAP6qqqj4AAEA/qqoqPwAAgD+qqio/AACAP6qqqj4AAEA/qqqqPgAAAD+qqio/AACAPqqqKj8AAIA+AACAPwAAAD8AAIA/" + } + ], + "materials": [ + { + "name": "Default", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1.0 + ], + "metallicFactor": 0.100000001490116, + "roughnessFactor": 0.990000005960464 + } + } + ], + "meshes": [ + { + "name": "Cube", + "primitives": [ + { + "attributes": { + "COLOR_0": 3, + "NORMAL": 2, + "POSITION": 1, + "TEXCOORD_0": 4 + }, + "indices": 0, + "material": 0, + "mode": 4 + } + ] + } + ], + "nodes": [ + { + "children": [ + 1, + 2, + 3 + ], + "name": "RootNode", + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "translation": [ + 0.0, + 0.0, + 0.0 + ] + }, + { + "name": "Mesh", + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "translation": [ + 0.0, + 0.0, + 0.0 + ] + }, + { + "mesh": 0, + "name": "Cube", + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "translation": [ + 0.0, + 0.0, + 0.0 + ] + }, + { + "name": "Texture Group", + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "translation": [ + 0.0, + 0.0, + 0.0 + ] + } + ], + "samplers": [ + {} + ], + "scene": 0, + "scenes": [ + { + "name": "Root Scene", + "nodes": [ + 0 + ] + } + ] +} diff --git a/test/testfiles/gltf/BoxVertexColors/glTF/BoxVertexColors.gltf b/test/testfiles/gltf/BoxVertexColors/glTF/BoxVertexColors.gltf new file mode 100644 index 0000000..5ea966d --- /dev/null +++ b/test/testfiles/gltf/BoxVertexColors/glTF/BoxVertexColors.gltf @@ -0,0 +1,220 @@ +{ + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 36, + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 0.5, + 0.5, + 0.5 + ], + "min": [ + -0.5, + -0.5, + -0.5 + ], + "type": "VEC3" + }, + { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "type": "VEC3" + }, + { + "bufferView": 3, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "type": "VEC4" + }, + { + "bufferView": 4, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "type": "VEC2" + } + ], + "asset": { + "generator": "FBX2glTF", + "version": "2.0" + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 72, + "byteOffset": 0, + "target": 34963 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 72, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 288, + "byteOffset": 360, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 384, + "byteOffset": 648, + "target": 34962 + }, + { + "buffer": 0, + "byteLength": 192, + "byteOffset": 1032, + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 1224, + "uri": "buffer.bin" + } + ], + "materials": [ + { + "name": "Default", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1.0 + ], + "metallicFactor": 0.100000001490116, + "roughnessFactor": 0.990000005960464 + } + } + ], + "meshes": [ + { + "name": "Cube", + "primitives": [ + { + "attributes": { + "COLOR_0": 3, + "NORMAL": 2, + "POSITION": 1, + "TEXCOORD_0": 4 + }, + "indices": 0, + "material": 0, + "mode": 4 + } + ] + } + ], + "nodes": [ + { + "children": [ + 1, + 2, + 3 + ], + "name": "RootNode", + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "translation": [ + 0.0, + 0.0, + 0.0 + ] + }, + { + "name": "Mesh", + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "translation": [ + 0.0, + 0.0, + 0.0 + ] + }, + { + "mesh": 0, + "name": "Cube", + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "translation": [ + 0.0, + 0.0, + 0.0 + ] + }, + { + "name": "Texture Group", + "rotation": [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "scale": [ + 1.0, + 1.0, + 1.0 + ], + "translation": [ + 0.0, + 0.0, + 0.0 + ] + } + ], + "samplers": [ + {} + ], + "scene": 0, + "scenes": [ + { + "name": "Root Scene", + "nodes": [ + 0 + ] + } + ] +} diff --git a/test/testfiles/gltf/BoxVertexColors/glTF/buffer.bin b/test/testfiles/gltf/BoxVertexColors/glTF/buffer.bin new file mode 100644 index 0000000..a9d7d1b Binary files /dev/null and b/test/testfiles/gltf/BoxVertexColors/glTF/buffer.bin differ diff --git a/test/testfiles/gltf/BoxVertexColors/screenshot/screenshot.png b/test/testfiles/gltf/BoxVertexColors/screenshot/screenshot.png new file mode 100644 index 0000000..70523a2 Binary files /dev/null and b/test/testfiles/gltf/BoxVertexColors/screenshot/screenshot.png differ diff --git a/test/testfiles/gltf/VertexColorTest/README.md b/test/testfiles/gltf/VertexColorTest/README.md new file mode 100644 index 0000000..e69c166 --- /dev/null +++ b/test/testfiles/gltf/VertexColorTest/README.md @@ -0,0 +1,11 @@ +# Vertex Color Test + +## Screenshot + +![screenshot](screenshot/screenshot.png) + +## Description + +This model tests the attribute semantic `COLOR_0`, as defined in the glTF [Metallic Roughness Material](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material), to check if it has been multipled by `baseColor`. For engines that read the vertex colors and apply them, you should see two rows of checkmarks, as shown in the screenshot. The top row is the "Test" row, which has been multipled by red, green, and blue vertex colors to reveal checkmarks in the corresponding color channels. The bottom row is a "sample pass" row, where checkmarks of each color are multiplied by white and should appear the same as the screenshot regardless of the rendering engine's ability to process vertex colors. + +For engines that ignore vertex colors, the top row of checks will look noticably mangled. The red check has a cyan X, the green check has a magenta X, and the blue check has a yellow X, occupying the other two color channels of each test checkmark. If you see these "X" marks fighting with the checkmarks, then you are seeing color channels that are supposed to have been zeroed out by the applied vertex colors on the mesh, and it means your rendering engine has not applied the vertex colors. diff --git a/test/testfiles/gltf/VertexColorTest/glTF-Binary/VertexColorTest.glb b/test/testfiles/gltf/VertexColorTest/glTF-Binary/VertexColorTest.glb new file mode 100644 index 0000000..2bb5e14 Binary files /dev/null and b/test/testfiles/gltf/VertexColorTest/glTF-Binary/VertexColorTest.glb differ diff --git a/test/testfiles/gltf/VertexColorTest/glTF-Embedded/VertexColorTest.gltf b/test/testfiles/gltf/VertexColorTest/glTF-Embedded/VertexColorTest.gltf new file mode 100644 index 0000000..e08fbb5 --- /dev/null +++ b/test/testfiles/gltf/VertexColorTest/glTF-Embedded/VertexColorTest.gltf @@ -0,0 +1,366 @@ +{ + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5121, + "count" : 36, + "max" : [ + 23 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 24, + "max" : [ + 0.7554783821105957, + -0.8512416481971741, + 0.9750328660011292 + ], + "min" : [ + -0.7045658230781555, + -0.8512417078018188, + -0.9611154794692993 + ], + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 24, + "max" : [ + 0.0, + 1.0, + -0.0 + ], + "min" : [ + 0.0, + 1.0, + -0.0 + ], + "type" : "VEC3" + }, + { + "bufferView" : 3, + "componentType" : 5126, + "count" : 24, + "max" : [ + 1.0, + 7.121938949694595e-08, + 1.5105192119335697e-07, + 1.0 + ], + "min" : [ + 1.0, + -1.4348813692777185e-07, + -1.1257676391096538e-07, + 1.0 + ], + "type" : "VEC4" + }, + { + "bufferView" : 4, + "componentType" : 5126, + "count" : 24, + "max" : [ + 0.9999998807907104, + 0.8010409474372864 + ], + "min" : [ + 0.0, + 0.05539727210998535 + ], + "type" : "VEC2" + }, + { + "bufferView" : 5, + "componentType" : 5121, + "count" : 72, + "max" : [ + 47 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + }, + { + "bufferView" : 6, + "componentType" : 5126, + "count" : 48, + "max" : [ + 1.0000004768371582, + 1.0, + 0.48742246627807617 + ], + "min" : [ + -1.0000003576278687, + -1.0, + -0.1718665361404419 + ], + "type" : "VEC3" + }, + { + "bufferView" : 7, + "componentType" : 5126, + "count" : 48, + "max" : [ + 1.0, + 1.0, + 1.0 + ], + "min" : [ + -1.0, + -1.0, + -1.0 + ], + "type" : "VEC3" + }, + { + "bufferView" : 8, + "componentType" : 5126, + "count" : 48, + "max" : [ + 1.0, + 7.940933880509066e-23, + 4.470348358154297e-08, + 1.0 + ], + "min" : [ + 1.0, + -6.617444900424222e-24, + -0.0, + 1.0 + ], + "type" : "VEC4" + }, + { + "bufferView" : 9, + "componentType" : 5126, + "count" : 48, + "max" : [ + 0.8361296057701111, + 0.7666319012641907 + ], + "min" : [ + 0.22613045573234558, + 0.2321176528930664 + ], + "type" : "VEC2" + }, + { + "bufferView" : 10, + "componentType" : 5126, + "count" : 48, + "max" : [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "min" : [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "type" : "VEC4" + } + ], + "asset" : { + "copyright": "Copyright 2018 Analytical Graphics, Inc., CC-BY 4.0 https://creativecommons.org/licenses/by/4.0/ - Mesh and textures by Ed Mackey.", + "generator" : "Khronos Blender glTF 2.0 exporter", + "version" : "2.0" + }, + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 36, + "byteOffset" : 0, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 288, + "byteOffset" : 36, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 288, + "byteOffset" : 324, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 384, + "byteOffset" : 612, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 192, + "byteOffset" : 996, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 72, + "byteOffset" : 1188, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 576, + "byteOffset" : 1260, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 576, + "byteOffset" : 1836, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 768, + "byteOffset" : 2412, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 384, + "byteOffset" : 3180, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 768, + "byteOffset" : 3564, + "target" : 34962 + } + ], + "buffers" : [ + { + "byteLength" : 4332, + "uri" : "data:application/octet-stream;base64,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52tHv7phQr9Qj/k+ahKivrphQr9Qj/k+52tHvw7+l75wj/k+AAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAAAAAAAAgD8AAAAAAACAP7H/fzIRGt01AACAP7H/fzIRGt01AACAP7H/fzIRGt01AACAP7H/fzIRGt01AQAAswIAALMAAIA/AQAAswIAALMAAIA/AQAAswIAALMAAIA/AQAAswIAALMAAIA/AACAvwAAELTXja61AACAvwAAELTXja61AACAvwAAELTXja61AACAvwAAELTXja61AABAM/7/fzIAAIC/AABAM/7/fzIAAIC/AABAM/7/fzIAAIC/AABAM/7/fzIAAIC/AAAAADFlCrb//38/AAAAADFlCrb//38/AAAAADFlCrb//38/AAAAADFlCrb//38/AAAAADFlCrb//38/AAAAADFlCrb//38/AAAAADFlCrb//38/AAAAADFlCrb//38/AAAAADFlCrb//38/AAAAADFlCrb//38/AAAAADFlCrb//38/AAAAADFlCrb//38/AAAAADFlCrYAAIA/AAAAADFlCrYAAIA/AAAAADFlCrYAAIA/AAAAADFlCrYAAIA/AAAAADFlCrYAAIA/AAAAADFlCrYAAIA/AAAAADFlCrYAAIA/AAAAADFlCrYAAIA/AAAAADFlCrYAAIA/AAAAADFlCrYAAIA/AAAAADFlCrYAAIA/AAAAADFlCrYAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAwBoBAAAzAACAPwAAgD8AAMAaAQAAMwAAgD8AAIA/AADAGgEAADMAAIA/AACAPwAAwBoBAAAzAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAmQAAQDMAAIA/AACAPwAAAJkAAEAzAACAPwAAgD8AAACZAABAMwAAgD8AAIA/AAAAmQAAQDMAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/AACAPwAAAAAAAACAAACAPwAAgD8AAAAAAAAAgAAAgD8AAIA/AAAAAAAAAIAAAIA/CtA4PwK8OD8OwzU/Arw4Pw7DNT8CvDg/CtA4PwK8OD8OwzU/Bq81PwrQOD8GrzU/DsM1PwavNT8K0Dg/Bq81PwrQOD8GrzU/CtA4PwK8OD8K0Dg/Arw4PwrQOD8GrzU/CtA4PwavNT8OwzU/Arw4PwrQOD8CvDg/DsM1PwavNT8OwzU/Bq81Pw7DNT8CvDg/DsM1PwK8OD8OwzU/Bq81Pw7DNT8GrzU/CtA4PwK8OD8OwzU/Arw4PwrQOD8GrzU/OOAfP0CwbT6eNeA+9GLWPjjgHz/0YtY+njXgPkCwbT6XDFY/QLBzPi5HJj/0Ytk+lwxWP/Ri2T4uRyY/QLBzPr6U1D5AsHM+0hNqPvRi2T6+lNQ+9GLZPtITaj5AsHM+8z4fP5P8Ej8U894+/cFCP/M+Hz/9wUI/FPPePpP8Ej9Sa1U/k3wUP+mlJT/9QUQ/UmtVP/1BRD/ppSU/k3wUPzRS0z6TfBQ/vo5nPv1BRD80UtM+/UFEP76OZz6TfBQ/gYAAP4GAAD+BgAA/AACAP4GAAD+BgAA/gYAAPwAAgD+BgAA/gYAAP4GAAD8AAIA/gYAAP4GAAD+BgAA/AACAP4WEBD+FhAQ/hYQEPwAAgD+BgAA/gYAAP4GAAD8AAIA/gYAAP4GAAD+BgAA/AACAP4GAAD+BgAA/gYAAPwAAgD8AAIA/AACAPwAAgD8AAIA/gYAAP4GAAD+BgAA/AACAP4GAAD+BgAA/gYAAPwAAgD8AAIA/AACAPwAAgD8AAIA/gYAAP4GAAD+BgAA/AACAP4GAAD+BgAA/gYAAPwAAgD+BgAA/gYAAP4GAAD8AAIA/gYAAP4GAAD+BgAA/AACAP4GAAD+BgAA/gYAAPwAAgD+BgAA/gYAAP4GAAD8AAIA/gYAAP4GAAD+BgAA/AACAP4GAAD+BgAA/gYAAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AAAAAAAAgD8AAAAAAACAPwAAAAAAAIA/AAAAAAAAgD8AAAAAAACAPwAAAAAAAIA/AAAAAAAAgD8AAAAAAACAPwAAAAAAAAAAAACAPwAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAAAAAIA/AACAPwAAAAAAAAAAAACAPwAAgD8AAIA/AAAAAAAAAAAAAIA/AACAPwAAAAAAAAAAAACAPwAAgD8AAAAAAAAAAAAAgD8AAIA/AAAAAAAAAAAAAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAP//+fj///n4/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/" + } + ], + "images" : [ + { + "uri" : "data:image/png;base64,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" + }, + { + "uri" : "data:image/png;base64,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" + } + ], + "materials" : [ + { + "doubleSided" : true, + "name" : "Label_Mat", + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 0 + }, + "metallicFactor" : 0.0, + "roughnessFactor" : 0.8999999761581421 + } + }, + { + "name" : "VC_Checks_Mat", + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 1 + }, + "metallicFactor" : 0.0, + "roughnessFactor" : 0.8999999761581421 + } + } + ], + "meshes" : [ + { + "name" : "LabelMesh", + "primitives" : [ + { + "attributes" : { + "NORMAL" : 2, + "POSITION" : 1, + "TANGENT" : 3, + "TEXCOORD_0" : 4 + }, + "indices" : 0, + "material" : 0 + } + ] + }, + { + "name" : "VertexColorTestMesh", + "primitives" : [ + { + "attributes" : { + "COLOR_0" : 10, + "NORMAL" : 7, + "POSITION" : 6, + "TANGENT" : 8, + "TEXCOORD_0" : 9 + }, + "indices" : 5, + "material" : 1 + } + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "Labels", + "rotation" : [ + 0.7071068286895752, + 0.0, + -0.0, + 0.7071068286895752 + ], + "translation" : [ + 0.0, + 0.0, + 0.9126079678535461 + ] + }, + { + "mesh" : 1, + "name" : "VertexColorTest", + "scale" : [ + 1.0, + 1.0, + 0.07413393259048462 + ] + } + ], + "samplers" : [ + {} + ], + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 0, + 1 + ] + } + ], + "textures" : [ + { + "sampler" : 0, + "source" : 0 + }, + { + "sampler" : 0, + "source" : 1 + } + ] +} diff --git a/test/testfiles/gltf/VertexColorTest/glTF/VertexColorChecks.png b/test/testfiles/gltf/VertexColorTest/glTF/VertexColorChecks.png new file mode 100644 index 0000000..9408cf8 Binary files /dev/null and b/test/testfiles/gltf/VertexColorTest/glTF/VertexColorChecks.png differ diff --git a/test/testfiles/gltf/VertexColorTest/glTF/VertexColorTest.bin b/test/testfiles/gltf/VertexColorTest/glTF/VertexColorTest.bin new file mode 100644 index 0000000..3cebcb4 Binary files /dev/null and b/test/testfiles/gltf/VertexColorTest/glTF/VertexColorTest.bin differ diff --git a/test/testfiles/gltf/VertexColorTest/glTF/VertexColorTest.gltf b/test/testfiles/gltf/VertexColorTest/glTF/VertexColorTest.gltf new file mode 100644 index 0000000..dad26ac --- /dev/null +++ b/test/testfiles/gltf/VertexColorTest/glTF/VertexColorTest.gltf @@ -0,0 +1,366 @@ +{ + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5121, + "count" : 36, + "max" : [ + 23 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 24, + "max" : [ + 0.7554783821105957, + -0.8512416481971741, + 0.9750328660011292 + ], + "min" : [ + -0.7045658230781555, + -0.8512417078018188, + -0.9611154794692993 + ], + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 24, + "max" : [ + 0.0, + 1.0, + -0.0 + ], + "min" : [ + 0.0, + 1.0, + -0.0 + ], + "type" : "VEC3" + }, + { + "bufferView" : 3, + "componentType" : 5126, + "count" : 24, + "max" : [ + 1.0, + 7.121938949694595e-08, + 1.5105192119335697e-07, + 1.0 + ], + "min" : [ + 1.0, + -1.4348813692777185e-07, + -1.1257676391096538e-07, + 1.0 + ], + "type" : "VEC4" + }, + { + "bufferView" : 4, + "componentType" : 5126, + "count" : 24, + "max" : [ + 0.9999998807907104, + 0.8010409474372864 + ], + "min" : [ + 0.0, + 0.05539727210998535 + ], + "type" : "VEC2" + }, + { + "bufferView" : 5, + "componentType" : 5121, + "count" : 72, + "max" : [ + 47 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + }, + { + "bufferView" : 6, + "componentType" : 5126, + "count" : 48, + "max" : [ + 1.0000004768371582, + 1.0, + 0.48742246627807617 + ], + "min" : [ + -1.0000003576278687, + -1.0, + -0.1718665361404419 + ], + "type" : "VEC3" + }, + { + "bufferView" : 7, + "componentType" : 5126, + "count" : 48, + "max" : [ + 1.0, + 1.0, + 1.0 + ], + "min" : [ + -1.0, + -1.0, + -1.0 + ], + "type" : "VEC3" + }, + { + "bufferView" : 8, + "componentType" : 5126, + "count" : 48, + "max" : [ + 1.0, + 7.940933880509066e-23, + 4.470348358154297e-08, + 1.0 + ], + "min" : [ + 1.0, + -6.617444900424222e-24, + -0.0, + 1.0 + ], + "type" : "VEC4" + }, + { + "bufferView" : 9, + "componentType" : 5126, + "count" : 48, + "max" : [ + 0.8361296057701111, + 0.7666319012641907 + ], + "min" : [ + 0.22613045573234558, + 0.2321176528930664 + ], + "type" : "VEC2" + }, + { + "bufferView" : 10, + "componentType" : 5126, + "count" : 48, + "max" : [ + 1.0, + 1.0, + 1.0, + 1.0 + ], + "min" : [ + 0.0, + 0.0, + 0.0, + 1.0 + ], + "type" : "VEC4" + } + ], + "asset" : { + "copyright": "Copyright 2018 Analytical Graphics, Inc., CC-BY 4.0 https://creativecommons.org/licenses/by/4.0/ - Mesh and textures by Ed Mackey.", + "generator" : "Khronos Blender glTF 2.0 exporter", + "version" : "2.0" + }, + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 36, + "byteOffset" : 0, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 288, + "byteOffset" : 36, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 288, + "byteOffset" : 324, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 384, + "byteOffset" : 612, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 192, + "byteOffset" : 996, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 72, + "byteOffset" : 1188, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 576, + "byteOffset" : 1260, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 576, + "byteOffset" : 1836, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 768, + "byteOffset" : 2412, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 384, + "byteOffset" : 3180, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 768, + "byteOffset" : 3564, + "target" : 34962 + } + ], + "buffers" : [ + { + "byteLength" : 4332, + "uri" : "VertexColorTest.bin" + } + ], + "images" : [ + { + "uri" : "VertexColorTestLabels.png" + }, + { + "uri" : "VertexColorChecks.png" + } + ], + "materials" : [ + { + "doubleSided" : true, + "name" : "Label_Mat", + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 0 + }, + "metallicFactor" : 0.0, + "roughnessFactor" : 0.8999999761581421 + } + }, + { + "name" : "VC_Checks_Mat", + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 1 + }, + "metallicFactor" : 0.0, + "roughnessFactor" : 0.8999999761581421 + } + } + ], + "meshes" : [ + { + "name" : "LabelMesh", + "primitives" : [ + { + "attributes" : { + "NORMAL" : 2, + "POSITION" : 1, + "TANGENT" : 3, + "TEXCOORD_0" : 4 + }, + "indices" : 0, + "material" : 0 + } + ] + }, + { + "name" : "VertexColorTestMesh", + "primitives" : [ + { + "attributes" : { + "COLOR_0" : 10, + "NORMAL" : 7, + "POSITION" : 6, + "TANGENT" : 8, + "TEXCOORD_0" : 9 + }, + "indices" : 5, + "material" : 1 + } + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "Labels", + "rotation" : [ + 0.7071068286895752, + 0.0, + -0.0, + 0.7071068286895752 + ], + "translation" : [ + 0.0, + 0.0, + 0.9126079678535461 + ] + }, + { + "mesh" : 1, + "name" : "VertexColorTest", + "scale" : [ + 1.0, + 1.0, + 0.07413393259048462 + ] + } + ], + "samplers" : [ + {} + ], + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 0, + 1 + ] + } + ], + "textures" : [ + { + "sampler" : 0, + "source" : 0 + }, + { + "sampler" : 0, + "source" : 1 + } + ] +} diff --git a/test/testfiles/gltf/VertexColorTest/glTF/VertexColorTestLabels.png b/test/testfiles/gltf/VertexColorTest/glTF/VertexColorTestLabels.png new file mode 100644 index 0000000..792660e Binary files /dev/null and b/test/testfiles/gltf/VertexColorTest/glTF/VertexColorTestLabels.png differ diff --git a/test/testfiles/gltf/VertexColorTest/screenshot/screenshot.png b/test/testfiles/gltf/VertexColorTest/screenshot/screenshot.png new file mode 100644 index 0000000..7ea98d8 Binary files /dev/null and b/test/testfiles/gltf/VertexColorTest/screenshot/screenshot.png differ diff --git a/tools/config.json b/tools/config.json index 218840a..2ae6fa3 100644 --- a/tools/config.json +++ b/tools/config.json @@ -5,6 +5,7 @@ "source/geometry/geometry.js", "source/geometry/coord2d.js", "source/geometry/coord3d.js", + "source/geometry/coord4d.js", "source/geometry/quaternion.js", "source/geometry/box3d.js", "source/geometry/octree.js", diff --git a/website/embed.html b/website/embed.html index fa7a901..163a71b 100644 --- a/website/embed.html +++ b/website/embed.html @@ -21,6 +21,7 @@ + diff --git a/website/index.html b/website/index.html index 515dcb9..aa4c6ef 100644 --- a/website/index.html +++ b/website/index.html @@ -23,6 +23,7 @@ + diff --git a/website/o3dv/js/sidebardetailspanel.js b/website/o3dv/js/sidebardetailspanel.js index 09c887e..b02a252 100644 --- a/website/o3dv/js/sidebardetailspanel.js +++ b/website/o3dv/js/sidebardetailspanel.js @@ -80,9 +80,11 @@ OV.SidebarDetailsPanel = class extends OV.SidebarPanel this.AddProperty (table, new OV.Property (OV.PropertyType.Text, 'Type', typeString)); this.AddProperty (table, new OV.Property (OV.PropertyType.Color, 'Color', material.color)); if (material.type === OV.MaterialType.Phong) { - this.AddProperty (table, new OV.Property (OV.PropertyType.Color, 'Ambient', material.ambient)); + this.AddProperty (table, new OV.Property (OV.PropertyType.Color, 'Ambient', material.ambient)); this.AddProperty (table, new OV.Property (OV.PropertyType.Color, 'Specular', material.specular)); - } else if (material.type === OV.MaterialType.Physical) { + } + this.AddProperty (table, new OV.Property (OV.PropertyType.Boolean, 'Vertex Colors', material.vertexColors)); + if (material.type === OV.MaterialType.Physical) { this.AddProperty (table, new OV.Property (OV.PropertyType.Percent, 'Metalness', material.metalness)); this.AddProperty (table, new OV.Property (OV.PropertyType.Percent, 'Roughness', material.roughness)); }