Cogs.Core
DracoMeshDecompressor.cpp
1#include "DracoMeshDecompressor.h"
2#include "GltfLoader.h"
3
4#include "Foundation/Logging/Logger.h"
5#include "Resources/MeshManager.h"
6
7#if defined ( _WIN32 )
8#pragma warning(push)
9#pragma warning(disable:4127 4804)
10#elif defined ( __clang__ )
11#pragma clang diagnostic push
12#pragma clang diagnostic ignored "-Wignored-qualifiers"
13#pragma clang diagnostic ignored "-Wdeprecated-this-capture"
14#pragma clang diagnostic ignored "-Wignored-qualifiers"
15#endif
16#include <draco/compression/decode.h>
17#include <draco/core/decoder_buffer.h>
18#if defined ( _WIN32 )
19#pragma warning(pop)
20#elif defined ( __clang__ )
21#pragma clang diagnostic pop
22#endif
23
24#include <string>
25
26using namespace Cogs::Core;
27
28namespace {
29 Cogs::Logging::Log logger = Cogs::Logging::getLogger("GltfLoader::DracoMeshDecompress");
30}
31
32DracoMeshDecompressor::DracoMeshDecompressor() = default;
33
34DracoMeshDecompressor::~DracoMeshDecompressor() = default;
35
36void
37DracoMeshDecompressor::initAttributes(const GltfLoader::Object& properties)
38{
39 if (properties.HasMember("bufferView")) {
40 this->bufferViewIdx = properties["bufferView"].GetInt();
41 }
42
43 if (properties.HasMember("attributes")) {
44 auto attrSection = properties["attributes"].GetObject();
45 this->attrPositionIdx = attrSection["POSITION"].GetInt();
46
47 if (attrSection.HasMember("NORMAL")) {
48 this->attrNormalIdx = attrSection["NORMAL"].GetInt();
49 }
50
51 if (attrSection.HasMember("TANGENT")) {
52 this->attrTangentIdx = attrSection["TANGENT"].GetInt();
53 }
54
55 // Search for TEXCOORD_n attributes
56 int n = 0;
57 this->attrTexCoordIndices.resize(16);
58 for (n = 0; n < 16; ++n) {
59 std::string key = "TEXCOORD_" + std::to_string(n);
60 if (attrSection.HasMember(key.c_str())) {
61 this->attrTexCoordIndices[n] = attrSection[key.c_str()].GetInt();
62 }
63 else {
64 break;
65 }
66 }
67 this->attrTexCoordIndices.resize(n);
68
69 // Search for COLOR_n attributes
70 this->attrColorIndices.resize(16);
71 for (n=0; n<16; ++n) {
72 std::string key = "COLOR_" + std::to_string(n);
73 if (attrSection.HasMember(key.c_str())) {
74 this->attrColorIndices[n] = attrSection[key.c_str()].GetInt();
75 }
76 else {
77 break;
78 }
79 }
80 this->attrColorIndices.resize(n);
81
82 // Add support for JOINTS_n and WEIGHTS_n? Are those even compressed?
83 }
84 else {
85 assert(false && "The KHR_draco_mesh_compression section does not have an 'attributes' property.");
86 }
87
88#if 0 // Debug helper
89 LOG_DEBUG(logger, "BufferViewIdx=%d, PositionIdx=%d, NormalIdx=%d, TangentIdx=%d",
90 this->bufferViewIdx, this->attrPositionIdx, this->attrNormalIdx, this->attrTangentIdx);
91 if (this->attrTexCoordIndices.size() > 0) {
92 for (int i = 0; i < this->attrTexCoordIndices.size();++i) {
93 printf(" TEXCOORD_%d=%d\n", i, this->attrTexCoordIndices[i]);
94 }
95 }
96 if (this->attrColorIndices.size() > 0) {
97 for (int i = 0; i < this->attrColorIndices.size(); ++i) {
98 printf(" COLOR_%d=%d\n", i, this->attrColorIndices[i]);
99 }
100 }
101#endif
102}
103
104
105bool
106DracoMeshDecompressor::decompress(GltfLoader::GltfModelDefinition& loadData, GltfLoader::VertexStreamsState& streamsState, std::vector<GltfLoader::VertexStream>& vertexStreams,
107 const GltfLoader::Object& attributeObject, bool skipNormals)
108{
109 GltfLoader::GltfBufferView bufferView = loadData.buffer_views[this->bufferViewIdx];
110 const char* buffer = reinterpret_cast<const char*>(&(loadData.buffer_data[bufferView.buffer].data())[bufferView.byteOffset]);
111
112 const size_t bufferSize = bufferView.byteLength;
113
114 draco::DecoderBuffer decoderBuffer;
115 decoderBuffer.Init(buffer, bufferSize);
116
117 // Decode the input data into a geometry.
118 std::unique_ptr<draco::PointCloud> pointCloud;
119 draco::Mesh* dmesh = nullptr;
120 draco::Decoder decoder;
121
122 auto type_statusor = draco::Decoder::GetEncodedGeometryType(&decoderBuffer);
123 if (!type_statusor.ok()) {
124 LOG_ERROR(logger, "Draco: %s", type_statusor.status().error_msg_string().c_str());
125 return false;
126 }
127 const draco::EncodedGeometryType geom_type = type_statusor.value();
128
129 if (geom_type == draco::TRIANGULAR_MESH) {
130 auto statusor = decoder.DecodeMeshFromBuffer(&decoderBuffer);
131 if (!statusor.ok()) {
132 LOG_ERROR(logger, "Draco: %s", statusor.status().error_msg_string().c_str());
133 return false;
134 }
135 std::unique_ptr<draco::Mesh> in_mesh = std::move(statusor).value();
136 if (in_mesh) {
137 dmesh = in_mesh.get();
138 pointCloud = std::move(in_mesh);
139 }
140 }
141 else if (geom_type == draco::POINT_CLOUD) {
142 auto statusor = decoder.DecodePointCloudFromBuffer(&decoderBuffer);
143 if (!statusor.ok()) {
144 LOG_ERROR(logger, "Draco: %s", statusor.status().error_msg_string().c_str());
145 return false;
146 }
147 pointCloud = std::move(statusor).value();
148 }
149
150 assert(dmesh && "Internal error");
151
152 if (pointCloud->num_points() > 0 && dmesh->num_points() == 0) {
153 // FIXME: A decompressed pointcloud is not handled yet. To be implemented.
154 LOG_WARNING(logger, "Point-cloud decompression not implemented yet.");
155 return false;
156 }
157
158
159#if 0 // Debug helper
160 int numattrs = dmesh->num_attributes();
161 int numPosAttr = dmesh->NumNamedAttributes(draco::GeometryAttribute::POSITION);
162 printf("Num draco mesh attributes: %d\n", numPosAttr);
163 for (int i = 0; i < numattrs; ++i) {
164 auto attr = dmesh->attribute(i);
165 int idx = attr->unique_id();
166 printf(" %s: idx=%d, datasize=%d items=%d, stride=%d\n",
167 draco::GeometryAttribute::TypeToString(attr->attribute_type()).c_str(),
168 idx,
169 attr->buffer()->data_size(),
170 attr->size(),
171 attr->byte_stride());
172 }
173#endif
174
175
176 //
177 // Build index buffer
178 // NOTE: Cogs/GltfLoader will only handle index lists of type uint32_t, so we'll convert to this format.
179 //
180 assert(this->decompressedIndices.size() == 0 && "decompressedIndices buffer was reused");
181 this->decompressedIndices.resize(static_cast<unsigned long long>(dmesh->num_faces()) * 3 * sizeof(uint32_t));
182 uint32_t* buf = reinterpret_cast<uint32_t *>(this->decompressedIndices.data());
183
184 for (size_t n=0; n<dmesh->num_faces(); ++n) {
185 const draco::Mesh::Face face = dmesh->face(draco::FaceIndex((uint32_t) n));
186 buf[n * 3] = face[0].value();
187 buf[n * 3 + 1] = face[1].value();
188 buf[n * 3 + 2] = face[2].value();
189 }
190
191 streamsState.indexData = GltfLoader::BufferDataView{
192 .data = reinterpret_cast<uint8_t*>(buf),
193 .stride = sizeof(uint32_t),
194 .count = dmesh->num_faces() * 3
195 };
196 streamsState.indexType = GltfLoader::AccessorComponentType::UnsignedInt;
197 streamsState.indexSize = sizeof(uint32_t);
198
199 //
200 // Move decompressed vertices to the "streamsState" object
201 //
202 int32_t attrId = dmesh->GetAttributeIdByUniqueId(this->attrPositionIdx);
203 const draco::PointAttribute* positionAttribute = dmesh->attribute(attrId);
204 assert(positionAttribute->IsValid() && "Internal error");
205 assert(positionAttribute->data_type() == draco::DT_FLOAT32 && "Only vertices using float32 is supported for now.");
206 assert(positionAttribute->num_components() == 3 && "Only 3-component float arrays are supported for vertices.");
207
208 GltfLoader::VertexStream vertexStream;
209 this->getAttributeData<float>(dmesh, positionAttribute, vertexStream);
210 vertexStream.format = Cogs::DataFormat::R32G32B32_FLOAT;
211 vertexStream.semantic = Cogs::ElementSemantic::Position;
212 vertexStream.semanticIndex = 0;
213 vertexStreams.push_back(vertexStream);
214
215#if 0 // Debug helper
216 printf("INDICES (%d, min=%d, max=%d):\n", numIndices, minIndexValue, maxIndexValue);
217 for (int i = 0; i < dmesh->num_faces() * 3; ++i) {
218 printf("%d, ", indices[i]);
219 }
220 printf("\n");
221 if (vertexStream.dataView.count < maxIndexValue) {
222 LOG_ERROR(logger, "Number of decompressed verices (%d) is less than highest index value (%d)", vertexStream.dataView.count, maxIndexValue);
223 }
224#endif
225
226#if 0 // Debug helper
227 printf("POSITION (%d, num items=%d): \n", vertexStream.dataView.count, vertexStream.dataView.count / vertexStream.dataView.stride);
228 int numcomps = positionAttribute->num_components();
229 float* buf = (float*)vertexStream.dataView.data;
230 for (int i = 0; i < vertexStream.dataView.count; ++i) {
231 float x = buf[i * numcomps];
232 float y = buf[i * numcomps + 1];
233 float z = buf[i * numcomps + 2];
234 printf("<%f, %f, %f>, ", x, y, z);
235 }
236 printf("\n");
237#endif
238
239 streamsState.positionPresent = true;
240 streamsState.positionCount = vertexStream.dataView.count;
241 streamsState.positionData = vertexStream.dataView;
242
243 // Some materials does not need normals at all (eg. 'KHR_materials_unlit' materials). We'll therefore skip
244 // decompressing the normals as they won't be used anyways.
245 if (!skipNormals && this->attrNormalIdx != -1) {
246 processNormals(dmesh, vertexStreams, streamsState);
247 }
248
249 if (this->attrTangentIdx != -1) {
250 processTangents(dmesh, vertexStreams, streamsState);
251 }
252
253 processTextureCoords(dmesh, vertexStreams, streamsState);
254 processVertexColors(dmesh, vertexStreams, streamsState);
255
256 //
257 // Setup correct BBOX from accessor (or recalc from scratch)
258 //
259 uint32_t modelPositionAttrIdx = attributeObject["POSITION"].GetInt();
260 const GltfLoader::GltfAccessor& positionAccessor = loadData.accessors[modelPositionAttrIdx];
261 if (positionAccessor.minCount == 3 && positionAccessor.maxCount == 3) {
262 streamsState.bbox.min = glm::make_vec3(positionAccessor.min.s_float);
263 streamsState.bbox.max = glm::make_vec3(positionAccessor.max.s_float);
264 }
265 else {
266 LOG_WARNING(logger, "%.*s: POSITION attribute has missing or malformed min and max values", StringViewFormat(loadData.path));
267 streamsState.bbox.min = glm::vec3(std::numeric_limits<float>::max());
268 streamsState.bbox.max = -streamsState.bbox.min;
269 for (size_t i=0; i<streamsState.positionCount; i++) {
270 const glm::vec3 &p = *(reinterpret_cast<const glm::vec3 *>((streamsState.positionData.data + streamsState.positionData.stride * i)));
271 if (std::isfinite(p.x) && std::isfinite(p.y) && std::isfinite(p.z)) {
272 streamsState.bbox.min = glm::min(streamsState.bbox.min, p);
273 streamsState.bbox.max = glm::max(streamsState.bbox.max, p);
274 }
275 }
276 }
277
278 return true;
279}
280
281void
282DracoMeshDecompressor::processVertexColors(draco::Mesh* dmesh, std::vector<GltfLoader::VertexStream>& vertexStreams, GltfLoader::VertexStreamsState& streamsState)
283{
284 for (size_t colorNum = 0; colorNum<this->attrColorIndices.size(); ++colorNum) {
285 uint32_t cAttrId = dmesh->GetAttributeIdByUniqueId(this->attrColorIndices[colorNum]);
286 const draco::PointAttribute* colorAttr = dmesh->attribute(cAttrId);
287 assert(colorAttr->IsValid() && "Internal error");
288 assert(colorAttr->data_type() == draco::DT_FLOAT32 && "Only texture coords using float32 is supported for now.");
289
290 GltfLoader::VertexStream colorStream;
291 this->getAttributeData<float>(dmesh, colorAttr, colorStream);
292
293 uint32_t numComponents = colorAttr->num_components();
294 assert(numComponents == 3 || numComponents == 4 && "Only Vec3 or Vec4 supported for color attributes.");
295
296 colorStream.format = numComponents == 3 ? Cogs::DataFormat::R32G32B32_FLOAT : Cogs::DataFormat::R32G32B32A32_FLOAT;
297 colorStream.semantic = Cogs::ElementSemantic::Color;
298 colorStream.semanticIndex = (uint32_t)colorNum;
299
300#if 0 // Debug helper
301 printf("COLOR_%d (%d, num items=%d): \n", colorNum, colorStream.dataView.count, colorStream.dataView.count / colorStream.dataView.stride);
302 float* buf = (float*)colorStream.dataView.data;
303 for (int i = 0; i < colorStream.dataView.count; ++i) {
304 float r = buf[i * numComponents];
305 float g = buf[i * numComponents + 1];
306 float b = buf[i * numComponents + 2];
307 if (numComponents == 4) {
308 float a = buf[i * numComponents + 3];
309 printf("<%f, %f, %f, %f>, ", r, g, b, a);
310 }
311 else {
312 printf("<%f, %f, %f>, ", r, g, b);
313 }
314 }
315 printf("\n");
316#endif
317
318 vertexStreams.push_back(colorStream);
319 }
320
321 streamsState.colorStreamCount = static_cast<uint32_t>(this->attrColorIndices.size());
322}
323
324void
325DracoMeshDecompressor::processTextureCoords(draco::Mesh* dmesh, std::vector<GltfLoader::VertexStream>& vertexStreams, GltfLoader::VertexStreamsState& streamsState)
326{
327 for (size_t texNum = 0; texNum<this->attrTexCoordIndices.size(); ++texNum) {
328 uint32_t tcAttrId = dmesh->GetAttributeIdByUniqueId(this->attrTexCoordIndices[texNum]);
329 const draco::PointAttribute* texAttr = dmesh->attribute(tcAttrId);
330 assert(texAttr->IsValid() && "Internal error");
331 assert(texAttr->data_type() == draco::DT_FLOAT32 && "Only texture coords using float32 is supported for now.");
332
333 uint32_t numComponents = texAttr->num_components();
334 assert(numComponents == 2 && "Only Vec2 is supported for texcoord attributes.");
335
336 GltfLoader::VertexStream texStream;
337 this->getAttributeData<float>(dmesh, texAttr, texStream);
338 texStream.format = Cogs::DataFormat::R32G32_FLOAT;
339 texStream.semantic = Cogs::ElementSemantic::TextureCoordinate;
340 texStream.semanticIndex = (uint32_t)texNum;
341
342#if 0 // Debug helper
343 printf("TEX_%d (%d, num items=%d): \n", texNum, texStream.dataView.count, texStream.dataView.count / texStream.dataView.stride);
344 float* buf = (float*)texStream.dataView.data;
345 for (int i = 0; i < texStream.dataView.count; ++i) {
346 float x = buf[i * numComponents];
347 float y = buf[i * numComponents + 1];
348 printf("<%f, %f>, ", x, y);
349 }
350 printf("\n");
351#endif
352
353 vertexStreams.push_back(texStream);
354 }
355
356 // Is this the correct way to add multiple arrays/datasets of texture coordinates? The VertexStreamsState
357 // object only has ONE 'texcoordData' member.
358 streamsState.texcoordStreamCount = static_cast<uint32_t>(this->attrTexCoordIndices.size());
359}
360
361void
362DracoMeshDecompressor::processNormals(draco::Mesh* dmesh, std::vector<GltfLoader::VertexStream>& vertexStreams, GltfLoader::VertexStreamsState& streamsState)
363{
364 uint32_t nAttrId = dmesh->GetAttributeIdByUniqueId(this->attrNormalIdx);
365 const draco::PointAttribute* normalAttribute = dmesh->attribute(nAttrId);
366 assert(normalAttribute->IsValid() && "Internal error");
367 assert(normalAttribute->data_type() == draco::DT_FLOAT32 && "Only normals using float32 is supported for now.");
368 assert(normalAttribute->num_components() == 3 && "Only 3-component float arrays are supported for normals.");
369
370
371 GltfLoader::VertexStream normalStream;
372 this->getAttributeData<float>(dmesh, normalAttribute, normalStream);
373 normalStream.format = Cogs::DataFormat::R32G32B32_FLOAT;
374 normalStream.semantic = Cogs::ElementSemantic::Normal;
375 normalStream.semanticIndex = 0;
376
377#if 0 // Debug helper
378 printf("NORMALS (%d, num items=%d): \n", normalStream.dataView.count, normalStream.dataView.count / normalStream.dataView.stride);
379 int numcomps = normalAttribute->num_components();
380 float* buf = (float*)normalStream.dataView.data;
381 for (int i = 0; i < normalStream.dataView.count; ++i) {
382 float x = buf[i * numcomps];
383 float y = buf[i * numcomps + 1];
384 float z = buf[i * numcomps + 2];
385 printf("<%f, %f, %f>, ", x, y, z);
386 }
387 printf("\n");
388#endif
389
390 vertexStreams.push_back(normalStream);
391 streamsState.normalPresent = true;
392 streamsState.normalData = normalStream.dataView;
393}
394
395void
396DracoMeshDecompressor::processTangents(draco::Mesh* dmesh, std::vector<GltfLoader::VertexStream>& vertexStreams, GltfLoader::VertexStreamsState& streamsState)
397{
398 uint32_t tAttrId = dmesh->GetAttributeIdByUniqueId(this->attrTangentIdx);
399 const draco::PointAttribute* tangentAttribute = dmesh->attribute(tAttrId);
400 assert(tangentAttribute->IsValid() && "Internal error");
401 assert(tangentAttribute->data_type() == draco::DT_FLOAT32 && "Only tangents using float32 is supported for now.");
402 assert(tangentAttribute->num_components() == 4 && "Only 4-component generic float arrays are supported for tangents for now.");
403
404
405 GltfLoader::VertexStream tangentStream;
406 this->getAttributeData<float>(dmesh, tangentAttribute, tangentStream);
407 tangentStream.format = Cogs::DataFormat::R32G32B32A32_FLOAT;
408 tangentStream.semantic = Cogs::ElementSemantic::Tangent;
409 tangentStream.semanticIndex = 0;
410
411#if 0 // Debug helper
412 printf("TANGENTS (%d, num items=%d): \n", tangentStream.dataView.count, tangentStream.dataView.count / tangentStream.dataView.stride);
413 int numcomps = tangentAttribute->num_components();
414 float* buf = (float*)tangentStream.dataView.data;
415 for (int i = 0; i < tangentStream.dataView.count; ++i) {
416 float x = buf[i * numcomps];
417 float y = buf[i * numcomps + 1];
418 float z = buf[i * numcomps + 2];
419 if (accessor.type == GltfLoader::AccessorType::Vec4) {
420 float a = buf[i * numcomps + 3];
421 printf("<%f, %f, %f, %f>, ", x, y, z, a);
422 }
423 else {
424 printf("<%f, %f, %f>, ", x, y, z);
425 }
426 }
427 printf("\n");
428#endif
429
430 vertexStreams.push_back(tangentStream);
431 streamsState.tangentPresent = true;
432}
433
Log implementation class.
Definition: LogManager.h:139
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:180
@ Position
Position semantic.
@ Tangent
Tangent semantic.
@ Normal
Normal semantic.
@ Color
Color semantic.
@ TextureCoordinate
Texture coordinate semantic.