Cogs.Core
PackMeshCommand.cpp
1#include "Foundation/Logging/Logger.h"
2
3#include "Components/Core/MeshComponent.h"
4#include "Components/Core/SceneComponent.h"
5#include "Components/Core/TransformComponent.h"
6
7#include "Systems/Core/TransformSystem.h"
8
9#include "Resources/Mesh.h"
10#include "Resources/MeshManager.h"
11#include "Resources/VertexFormats.h"
12
13#include "EntityStore.h"
14#include "Context.h"
15
16#include "PackMeshCommand.h"
17
18#include <meshoptimizer/meshoptimizer.h>
19
20
21namespace {
22 using namespace Cogs::Core;
23 const Cogs::Logging::Log logger = Cogs::Logging::getLogger("PackMeshCommand");
24
25 void handle(Context* context, PackMeshCommand* cmd, EntityPtr entity)
26 {
27 // Recurse into children
28 if (SceneComponent* sceneComp = entity->getComponent<SceneComponent>(); sceneComp && !sceneComp->children.empty()) {
29 for (EntityPtr& child : sceneComp->children) {
30 handle(context, cmd, child);
31 }
32 }
33
34 // Handle mesh
35 if (MeshComponent* meshComp = entity->getComponent<MeshComponent>(); meshComp && meshComp->meshHandle) {
36 cmd->undoItems.emplace_back(PackMeshCommand::UndoItem{ entity, meshComp->meshHandle });
37
38 glm::mat4 transform(1.f);
39
40 std::string status;
41 if (!PackMeshCommand::pack(context, status, transform, meshComp->meshHandle, cmd->packOptions)) {
42 LOG_ERROR(logger, "Faild to pack mesh: %s", status.c_str());
43 }
44 else {
45 if (!status.empty()) {
46 LOG_DEBUG(logger, "%s", status.c_str());
47 }
48
49 if (TransformComponent* trComp = entity->getComponent<TransformComponent>(); trComp) {
50 context->transformSystem->setLocalTransform(trComp, context->transformSystem->getLocalTransform(trComp) * transform);
51 }
52
53 }
54 meshComp->setChanged();
55 }
56 }
57
58}
59
60
61Cogs::Core::PackMeshCommand::PackMeshCommand(EditorState* state) :
62 EditorCommand(state, state->context)
63{
64}
65
67{
68 for (EntityId entityId : state->selected) {
69 if(EntityPtr entity = context->store->getEntity(entityId); entity) {
70 handle(context, this, entity);
71 }
72 }
73
74}
75
76void Cogs::Core::PackMeshCommand::undo()
77{
78 for (UndoItem& undoItem : undoItems) {
79 if (EntityPtr entity = undoItem.entity.lock(); entity) {
80 if (MeshComponent* meshComp = entity->getComponent<MeshComponent>(); meshComp) {
81 meshComp->meshHandle = undoItem.meshHandle;
82 meshComp->setChanged();
83 }
84 else {
85 LOG_ERROR(logger, "Failed to undo entity, entity lost its mesh component");
86 }
87 }
88 else {
89 LOG_ERROR(logger, "Failed to undo entity, entity does not exist");
90 }
91 }
92}
93
94
95namespace {
96
97 glm::vec2 octEncode(const glm::vec3& n)
98 {
99 const float t = std::abs(n.x) + std::abs(n.y) + std::abs(n.z);
100 glm::vec2 p = (1.f / t)*glm::vec2(n);
101 if (n.z <= 0.f) {
102 p = glm::vec2((0.f <= p.x ? 1.f : -1.f) * (1.f - std::abs(p.y)),
103 (0.f <= p.y ? 1.f : -1.f) * (1.f - std::abs(p.x)));
104
105 }
106 return p;
107 }
108
109 glm::vec3 octDecode(const glm::vec2& f)
110 {
111 // From https://twitter.com/Stubbesaurus/status/937994790553227264
112 glm::vec3 n = glm::vec3(f.x, f.y, 1.f - std::abs(f.x) - std::abs(f.y));
113 float t = std::max(-n.z, 0.f);
114 n.x += 0.f <= n.x ? -t : t;
115 n.y += 0.f <= n.y ? -t : t;
116 return glm::normalize(n);
117 }
118
119 glm::i16vec2 octEncodei16(const glm::vec3& n)
120 {
121#if 1
122 glm::vec2 enc = glm::clamp(32767.f * octEncode(n), glm::vec2(-32767.f), glm::vec2(32767.f));
123 glm::i16vec2 q[4] = {
124 glm::i16vec2(std::floor(enc.x), floor(enc.y)),
125 glm::i16vec2(std::floor(enc.x), ceil(enc.y)),
126 glm::i16vec2(std::ceil(enc.x), floor(enc.y)),
127 glm::i16vec2(std::ceil(enc.x), ceil(enc.y)),
128 };
129 size_t best = 0;
130 float error = 1.f - std::abs(dot(n, octDecode((1.f / 32767.f) * glm::vec2(q[0]))));
131 for (size_t i = 1; i < 4; i++) {
132 float e = 1.f - std::abs(dot(n, octDecode((1.f / 32767.f) * glm::vec2(q[i]))));
133 if (e < error) {
134 best = i;
135 error = e;
136 }
137 }
138 return q[best];
139#else
140 return glm::i16vec2(glm::clamp(glm::round(32767.f * octEncode(n)),
141 glm::vec2(-32767.f),
142 glm::vec2(32767.f)));
143#endif
144 }
145
146 glm::i8vec2 octEncodei8(const glm::vec3& n)
147 {
148#if 1
149 glm::vec2 enc = glm::clamp(127.f * octEncode(n), glm::vec2(-127.f), glm::vec2(127.f));
150 glm::i8vec2 q[4] = {
151 glm::i8vec2(std::floor(enc.x), floor(enc.y)),
152 glm::i8vec2(std::floor(enc.x), ceil(enc.y)),
153 glm::i8vec2(std::ceil(enc.x), floor(enc.y)),
154 glm::i8vec2(std::ceil(enc.x), ceil(enc.y)),
155 };
156
157 size_t best = 0;
158 float error = 1.f - std::abs(dot(n, octDecode((1.f / 127.f) * glm::vec2(q[0]))));
159 for (size_t i = 1; i < 4; i++) {
160 float e = 1.f - std::abs(dot(n, octDecode((1.f / 127.f) * glm::vec2(q[i]))));
161 if (e < error) {
162 best = i;
163 error = e;
164 }
165 }
166 return q[best];
167#else
168 return glm::i8vec2(glm::clamp(glm::round(127.f * octEncode(n)),
169 glm::vec2(-127.f),
170 glm::vec2(127.f)));
171#endif
172 }
173
174 enum struct PackFormat
175 {
176 Pos10un_Nrm8sn_Id16ui,
177 Pos16un_Nrm16sn_Id16ui,
178 Pos16un_Nrm8sn_Id32ui,
179 Pos16un_Nrm8sn_Id32f,
180 Pos16un_Nrm8sn,
181 Pos10un,
182 Count
183 };
184
185 namespace Pos10un_Nrm8sn_Id16ui {
186 struct Vtx
187 {
188 union {
189 struct {
190 unsigned pos_x : 10;
191 unsigned pos_y : 10;
192 unsigned pos_z : 10;
193 unsigned pos_w : 2;
194 };
195 uint32_t pos;
196 };
197 uint16_t tex;
198 glm::u8vec2 nrm;
199 };
200 static_assert(sizeof(Vtx) == 8);
201
202 const Cogs::VertexElement Fmt[] = {
203 {
204 .offset = uint16_t(offsetof(Vtx, pos)),
205 .format = Cogs::Format::R10G10B10A2_UNORM,
207 .semanticIndex = 0,
208 .inputType = Cogs::InputType::VertexData,
209 .instanceStep = 0
210 },
211 {
212 .offset = uint16_t(offsetof(Vtx, nrm)),
213 .format = Cogs::Format::R8G8_SNORM,
215 .semanticIndex = 0,
216 .inputType = Cogs::InputType::VertexData,
217 .instanceStep = 0
218 },
219 {
220 .offset = uint16_t(offsetof(Vtx, tex)),
221 .format = Cogs::Format::R16_UINT,
223 .semanticIndex = 0,
224 .inputType = Cogs::InputType::VertexData,
225 .instanceStep = 0
226 }
227 };
228 }
229
230 namespace Pos16un_Nrm16sn_Id16ui {
231 struct Vtx
232 {
233 glm::u16vec3 pos;
234 uint16_t tex;
235 glm::u16vec2 nrm;
236 };
237 static_assert(sizeof(Vtx) == 12);
238
239 const Cogs::VertexElement Fmt[] = {
240 {
241 .offset = uint16_t(offsetof(Vtx, pos)),
242 .format = Cogs::Format::R16G16B16_UNORM,
244 .semanticIndex = 0,
245 .inputType = Cogs::InputType::VertexData,
246 .instanceStep = 0
247 },
248 {
249 .offset = uint16_t(offsetof(Vtx, nrm)),
250 .format = Cogs::Format::R16G16_SNORM,
252 .semanticIndex = 0,
253 .inputType = Cogs::InputType::VertexData,
254 .instanceStep = 0
255 },
256 {
257 .offset = uint16_t(offsetof(Vtx, tex)),
258 .format = Cogs::Format::R16_UINT,
260 .semanticIndex = 0,
261 .inputType = Cogs::InputType::VertexData,
262 .instanceStep = 0
263 }
264 };
265 }
266
267 namespace Pos16un_Nrm8sn_Id32ui {
268 struct Vtx
269 {
270 glm::u16vec3 pos;
271 glm::u8vec2 nrm;
272 uint32_t tex;
273 };
274 static_assert(sizeof(Vtx) == 12);
275
276 const Cogs::VertexElement Fmt[] = {
277 {
278 .offset = uint16_t(offsetof(Vtx, pos)),
279 .format = Cogs::Format::R16G16B16_UNORM,
281 .semanticIndex = 0,
282 .inputType = Cogs::InputType::VertexData,
283 .instanceStep = 0
284 },
285 {
286 .offset = uint16_t(offsetof(Vtx, nrm)),
287 .format = Cogs::Format::R8G8_SNORM,
289 .semanticIndex = 0,
290 .inputType = Cogs::InputType::VertexData,
291 .instanceStep = 0
292 },
293 {
294 .offset = uint16_t(offsetof(Vtx, tex)),
295 .format = Cogs::Format::R32_UINT,
297 .semanticIndex = 0,
298 .inputType = Cogs::InputType::VertexData,
299 .instanceStep = 0
300 }
301 };
302 }
303
304 namespace Pos16un_Nrm8sn_Id32f {
305 struct Vtx
306 {
307 glm::u16vec3 pos;
308 glm::u8vec2 nrm;
309 float tex;
310 };
311 static_assert(sizeof(Vtx) == 12);
312
313 const Cogs::VertexElement Fmt[] = {
314 {
315 .offset = uint16_t(offsetof(Vtx, pos)),
316 .format = Cogs::Format::R16G16B16_UNORM,
318 .semanticIndex = 0,
319 .inputType = Cogs::InputType::VertexData,
320 .instanceStep = 0
321 },
322 {
323 .offset = uint16_t(offsetof(Vtx, nrm)),
324 .format = Cogs::Format::R8G8_SNORM,
326 .semanticIndex = 0,
327 .inputType = Cogs::InputType::VertexData,
328 .instanceStep = 0
329 },
330 {
331 .offset = uint16_t(offsetof(Vtx, tex)),
332 .format = Cogs::Format::R32_FLOAT,
334 .semanticIndex = 0,
335 .inputType = Cogs::InputType::VertexData,
336 .instanceStep = 0
337 }
338 };
339 }
340
341 namespace Pos16un_Nrm8sn {
342 struct Vtx
343 {
344 glm::u16vec3 pos;
345 glm::u8vec2 nrm;
346 };
347 static_assert(sizeof(Vtx) == 8);
348
349 const Cogs::VertexElement Fmt[2] = {
350 {
351 .offset = uint16_t(offsetof(Vtx, pos)),
352 .format = Cogs::Format::R16G16B16_UNORM,
354 .semanticIndex = 0,
355 .inputType = Cogs::InputType::VertexData,
356 .instanceStep = 0
357 },
358 {
359 .offset = uint16_t(offsetof(Vtx, nrm)),
360 .format = Cogs::Format::R8G8_SNORM,
362 .semanticIndex = 0,
363 .inputType = Cogs::InputType::VertexData,
364 .instanceStep = 0
365 }
366 };
367 }
368
369 namespace Pos10un {
370 struct Vtx
371 {
372 union {
373 struct {
374 unsigned pos_x : 10;
375 unsigned pos_y : 10;
376 unsigned pos_z : 10;
377 unsigned pos_w : 2;
378 };
379 uint32_t pos;
380 };
381 };
382 static_assert(sizeof(Vtx) == 4);
383
384 const Cogs::VertexElement Fmt[] = {
385 {
386 .offset = uint16_t(offsetof(Vtx, pos)),
387 .format = Cogs::Format::R10G10B10A2_UNORM,
389 .semanticIndex = 0,
390 .inputType = Cogs::InputType::VertexData,
391 .instanceStep = 0
392 }
393 };
394 }
395
396 namespace Id16ui {
397 const Cogs::VertexElement Fmt[] = {
398 {
399 .offset = 0,
400 .format = Cogs::Format::R16_UINT,
402 .semanticIndex = 0,
403 .inputType = Cogs::InputType::VertexData,
404 .instanceStep = 0
405 }
406 };
407 }
408
409 namespace Id32ui {
410 const Cogs::VertexElement Fmt[] = {
411 {
412 .offset = 0,
413 .format = Cogs::Format::R32_UINT,
415 .semanticIndex = 0,
416 .inputType = Cogs::InputType::VertexData,
417 .instanceStep = 0
418 }
419 };
420 }
421
422 namespace Id32f {
423 const Cogs::VertexElement Fmt[] = {
424 {
425 .offset = 0,
426 .format = Cogs::Format::R32_FLOAT,
428 .semanticIndex = 0,
429 .inputType = Cogs::InputType::VertexData,
430 .instanceStep = 0
431 }
432 };
433 }
434
435 namespace Id32ui_Inst {
436 const Cogs::VertexElement Fmt[] = {
437 {
438 .offset = 0,
439 .format = Cogs::Format::R32_UINT,
441 .semanticIndex = 1,
443 .instanceStep = 1
444 },
445 };
446 }
447
448 void findUniqueVertices(Cogs::Memory::MemoryBuffer& uniqueVertexData, std::vector<uint32_t>& uniqueVertexMap, const Cogs::Memory::MemoryBuffer& inVertexData, const size_t stride)
449 {
450 if (inVertexData.empty()) return;
451
452 const char* inVertices = (const char*)inVertexData.data();
453 const size_t inVertexCount = inVertexData.size() / stride;
454
455 std::vector<uint32_t> permutation(inVertexCount);
456 for (uint32_t i = 0; i < inVertexCount; i++) {
457 permutation[i] = i;
458 }
459 std::sort(permutation.begin(), permutation.end(),
460 [inVertices, stride](const uint32_t& a, const uint32_t& b) -> bool
461 {
462 return std::memcmp(&inVertices[stride * a], &inVertices[stride * b], stride) < 0;
463 });
464
465 uniqueVertexData.resize(inVertexData.size());
466 char* uniqueVertices = (char*)uniqueVertexData.data();
467 size_t currentUniqueVertex = 0;
468
469 uniqueVertexMap.resize(inVertexCount);
470 uniqueVertexMap[permutation[0]] = uint32_t(currentUniqueVertex);
471 std::memcpy(&uniqueVertices[stride * currentUniqueVertex], &inVertices[stride * permutation[0]], stride);
472
473 for (size_t i = 1; i < inVertexCount; i++) {
474 if (std::memcmp(&uniqueVertices[stride * currentUniqueVertex], &inVertices[stride * permutation[i]], stride) != 0) {
475 std::memcpy(&uniqueVertices[stride * (++currentUniqueVertex)], &inVertices[stride * permutation[i]], stride);
476 }
477 uniqueVertexMap[permutation[i]] = uint32_t(currentUniqueVertex);
478 }
479
480 const size_t uniqueVertexCount = currentUniqueVertex + 1;
481
482 assert(uniqueVertexCount <= inVertexCount);
483 uniqueVertexData.resize(stride * uniqueVertexCount);
484 }
485
486 bool buildPackedVertices(Cogs::VertexFormatHandle& fmtHandle,
487 Cogs::Memory::MemoryBuffer& uniqueVertexData,
488 std::vector<uint32_t>& uniqueVertexMap,
489 const PackFormat packFmt,
493 const size_t vertexCount,
494 const glm::vec3& posMin,
495 const float invScale,
496 const uint32_t idOffset)
497 {
499
500 size_t fmtSize = 0;
501
502 float posFormatScale = 0.f;
503 switch (packFmt) {
504
505 case PackFormat::Pos10un_Nrm8sn_Id16ui: {
506 fmtHandle = Cogs::VertexFormats::createVertexFormat(Pos10un_Nrm8sn_Id16ui::Fmt, std::size(Pos10un_Nrm8sn_Id16ui::Fmt));
507 fmtSize = Cogs::getSize(fmtHandle);
508 assert(sizeof(Pos10un_Nrm8sn_Id16ui::Vtx) == fmtSize);
509 posFormatScale = 1023.f;
510
511 vertexData.resize(fmtSize * vertexCount, false);
512 Pos10un_Nrm8sn_Id16ui::Vtx* vtx = (Pos10un_Nrm8sn_Id16ui::Vtx*)vertexData.data();
513 for (size_t i = 0; i < vertexCount; i++) {
514 glm::vec3 p = glm::clamp(glm::round(posFormatScale * invScale * (srcPos[i] - posMin)), glm::vec3(0.f), glm::vec3(posFormatScale));
515 vtx[i].pos_x = uint32_t(p.x);
516 vtx[i].pos_y = uint32_t(p.y);
517 vtx[i].pos_z = uint32_t(p.z);
518 vtx[i].pos_w = 3u;
519 vtx[i].nrm = octEncodei8(glm::normalize(srcNrm[i]));
520 vtx[i].tex = uint16_t(uint32_t(std::max(0.f, srcTex[i].x)) - idOffset);
521 }
522
523 findUniqueVertices(uniqueVertexData, uniqueVertexMap, vertexData, fmtSize);
524 break;
525 }
526
527 case PackFormat::Pos16un_Nrm16sn_Id16ui: {
528 fmtHandle = Cogs::VertexFormats::createVertexFormat(Pos16un_Nrm16sn_Id16ui::Fmt, std::size(Pos16un_Nrm16sn_Id16ui::Fmt));
529 fmtSize = Cogs::getSize(fmtHandle);
530 assert(sizeof(Pos16un_Nrm16sn_Id16ui::Vtx) == fmtSize);
531 posFormatScale = std::numeric_limits<uint16_t>::max();
532
533 vertexData.resize(fmtSize * vertexCount, false);
534 Pos16un_Nrm16sn_Id16ui::Vtx* vtx = (Pos16un_Nrm16sn_Id16ui::Vtx*)vertexData.data();
535 for (size_t i = 0; i < vertexCount; i++) {
536 glm::vec3 p = glm::clamp(glm::round(posFormatScale * invScale * (srcPos[i] - posMin)), glm::vec3(0.f), glm::vec3(posFormatScale));
537 vtx[i].pos = glm::u16vec3(p);
538 vtx[i].nrm = octEncodei16(glm::normalize(srcNrm[i]));
539 vtx[i].tex = uint16_t(uint32_t(std::max(0.f, srcTex[i].x)) - idOffset);
540 }
541
542 findUniqueVertices(uniqueVertexData, uniqueVertexMap, vertexData, fmtSize);
543 break;
544 }
545
546 case PackFormat::Pos16un_Nrm8sn_Id32ui: {
547 fmtHandle = Cogs::VertexFormats::createVertexFormat(Pos16un_Nrm8sn_Id32ui::Fmt, std::size(Pos16un_Nrm8sn_Id32ui::Fmt));
548 fmtSize = Cogs::getSize(fmtHandle);
549 assert(sizeof(Pos16un_Nrm8sn_Id32ui::Vtx) == fmtSize);
550 posFormatScale = std::numeric_limits<uint16_t>::max();
551
552 vertexData.resize(fmtSize * vertexCount, false);
553 Pos16un_Nrm8sn_Id32ui::Vtx* vtx = (Pos16un_Nrm8sn_Id32ui::Vtx*)vertexData.data();
554 for (size_t i = 0; i < vertexCount; i++) {
555 glm::vec3 p = glm::clamp(glm::round(posFormatScale * invScale * (srcPos[i] - posMin)), glm::vec3(0.f), glm::vec3(posFormatScale));
556 vtx[i].pos = glm::u16vec3(p);
557 vtx[i].nrm = octEncodei8(glm::normalize(srcNrm[i]));
558 vtx[i].tex = uint32_t(std::max(0.f, srcTex[i].x)) - idOffset;
559 }
560
561 findUniqueVertices(uniqueVertexData, uniqueVertexMap, vertexData, fmtSize);
562 break;
563 }
564
565 case PackFormat::Pos16un_Nrm8sn_Id32f: {
566 fmtHandle = Cogs::VertexFormats::createVertexFormat(Pos16un_Nrm8sn_Id32f::Fmt, std::size(Pos16un_Nrm8sn_Id32f::Fmt));
567 fmtSize = Cogs::getSize(fmtHandle);
568 assert(sizeof(Pos16un_Nrm8sn_Id32f::Vtx) == fmtSize);
569 posFormatScale = std::numeric_limits<uint16_t>::max();
570
571 vertexData.resize(fmtSize * vertexCount, false);
572 Pos16un_Nrm8sn_Id32f::Vtx* vtx = (Pos16un_Nrm8sn_Id32f::Vtx*)vertexData.data();
573 for (size_t i = 0; i < vertexCount; i++) {
574 glm::vec3 p = glm::clamp(glm::round(posFormatScale * invScale * (srcPos[i] - posMin)), glm::vec3(0.f), glm::vec3(posFormatScale));
575 vtx[i].pos = glm::u16vec3(p);
576 vtx[i].nrm = octEncodei8(glm::normalize(srcNrm[i]));
577 vtx[i].tex = float(uint32_t(std::max(0.f, srcTex[i].x)) - idOffset);
578 }
579
580 findUniqueVertices(uniqueVertexData, uniqueVertexMap, vertexData, fmtSize);
581 break;
582 }
583
584 case PackFormat::Pos16un_Nrm8sn: {
585 fmtHandle = Cogs::VertexFormats::createVertexFormat(Pos16un_Nrm8sn::Fmt, std::size(Pos16un_Nrm8sn::Fmt));
586 fmtSize = Cogs::getSize(fmtHandle);
587 assert(sizeof(Pos16un_Nrm8sn::Vtx) == fmtSize);
588 posFormatScale = std::numeric_limits<uint16_t>::max();
589
590 vertexData.resize(fmtSize * vertexCount, false);
591 Pos16un_Nrm8sn::Vtx* vtx = (Pos16un_Nrm8sn::Vtx*)vertexData.data();
592 for (size_t i = 0; i < vertexCount; i++) {
593 glm::vec3 p = glm::clamp(glm::round(posFormatScale * invScale * (srcPos[i] - posMin)), glm::vec3(0.f), glm::vec3(posFormatScale));
594 vtx[i].pos = glm::u16vec3(p);
595 vtx[i].nrm = octEncodei8(glm::normalize(srcNrm[i]));
596 }
597 findUniqueVertices(uniqueVertexData, uniqueVertexMap, vertexData, fmtSize);
598 break;
599 }
600
601 case PackFormat::Pos10un: {
602 fmtHandle = Cogs::VertexFormats::createVertexFormat(Pos10un::Fmt, std::size(Pos10un::Fmt));
603 fmtSize = Cogs::getSize(fmtHandle);
604 assert(sizeof(Pos10un::Vtx) == fmtSize);
605 posFormatScale = 1023.f;
606 vertexData.resize(fmtSize * vertexCount, false);
607 Pos10un::Vtx* vtx = (Pos10un::Vtx*)vertexData.data();
608 for (size_t i = 0; i < vertexCount; i++) {
609 glm::vec3 p = glm::clamp(glm::round(posFormatScale * invScale * (srcPos[i] - posMin)), glm::vec3(0.f), glm::vec3(posFormatScale));
610 vtx[i].pos_x = uint32_t(p.x);
611 vtx[i].pos_y = uint32_t(p.y);
612 vtx[i].pos_z = uint32_t(p.z);
613 vtx[i].pos_w = 3u;
614 }
615 findUniqueVertices(uniqueVertexData, uniqueVertexMap, vertexData, fmtSize);
616 break;
617 }
618
619
620 default:
621 assert(false && "Invalid enum");
622 return false;
623 }
624
625 return true;
626 }
627
628 bool buildIndices(std::vector<uint32_t>& indices, const std::vector<uint32_t>& uniqueVertexMap, const Mesh* srcMesh)
629 {
630 // Build indexed representation
631 if (srcMesh->isIndexed()) {
632 const DataStream& indexStream = srcMesh->getStream(VertexDataType::Indexes);
633
634 indices.reserve(indexStream.numElements);
635 switch (indexStream.stride)
636 {
637 case 2: {
638 const uint16_t* srcIndices = (const uint16_t*)indexStream.data();
639 for (size_t i = 0; i + 2 < indexStream.numElements; i += 3) {
640 uint32_t a = uniqueVertexMap[srcIndices[i + 0]];
641 uint32_t b = uniqueVertexMap[srcIndices[i + 1]];
642 uint32_t c = uniqueVertexMap[srcIndices[i + 2]];
643 if (a != b && b != c && a != c) {
644 indices.emplace_back(a);
645 indices.emplace_back(b);
646 indices.emplace_back(c);
647 }
648 }
649 break;
650 }
651 case 4: {
652 const uint32_t* srcIndices = (const uint32_t*)indexStream.data();
653 for (size_t i = 0; i + 2 < indexStream.numElements; i += 3) {
654 uint32_t a = uniqueVertexMap[srcIndices[i + 0]];
655 uint32_t b = uniqueVertexMap[srcIndices[i + 1]];
656 uint32_t c = uniqueVertexMap[srcIndices[i + 2]];
657 if (a != b && b != c && a != c) {
658 indices.emplace_back(a);
659 indices.emplace_back(b);
660 indices.emplace_back(c);
661 }
662 }
663 break;
664 }
665 default:
666 assert(false && "Illegal index size");
667 return false;
668 }
669 }
670 else {
671 size_t inVertexCount = uniqueVertexMap.size();
672 indices.reserve(inVertexCount);
673 for (size_t i = 0; i + 2 < inVertexCount; i += 3) {
674 uint32_t a = uniqueVertexMap[i + 0];
675 uint32_t b = uniqueVertexMap[i + 1];
676 uint32_t c = uniqueVertexMap[i + 2];
677 if (a != b && b != c && a != c) {
678 indices.emplace_back(a);
679 indices.emplace_back(b);
680 indices.emplace_back(c);
681 }
682 }
683 }
684
685 return true;
686 }
687
688
689 MeshHandle buildIndexedMesh(Context* context,
690 const Mesh* srcMesh,
691 const Cogs::Geometry::BoundingBox& bbox,
692 const Cogs::VertexFormatHandle& posFmtHandle,
693 const Cogs::Memory::MemoryBuffer& posVertexData,
694 const Cogs::VertexFormatHandle& idsFmtHandle,
695 const Cogs::Memory::MemoryBuffer& idsVertexData,
696 const size_t vertexCount,
697 const std::vector<uint32_t>& indexData,
698 const uint32_t dstIndexTypeSize,
699 const uint32_t idOffset)
700 {
701 MeshManager::ResourceProxy dstMesh = context->meshManager->createLocked();
702 dstMesh->primitiveType = srcMesh->primitiveType;
703
704 // Position, normal and maybe id goes into Interleaved0 stream.
705 const size_t posFmtSize = Cogs::getSize(posFmtHandle);
706 assert(posVertexData.size() == posFmtSize * vertexCount);
707 if (uint8_t* dst = dstMesh->mapStream(VertexDataType::Interleaved0, posFmtHandle, 0, vertexCount, posFmtSize, true); dst != nullptr) {
708 std::memcpy(dst, posVertexData.data(), posVertexData.size());
709 dstMesh->unmap(VertexDataType::Interleaved0);
710 }
711 else {
713 }
714
715 // We have a separate ids tream, store it in TexCoord0 stream
716 if (idsFmtHandle) {
717 const size_t idsFmtSize = Cogs::getSize(idsFmtHandle);
718 assert(idsVertexData.size() == idsFmtSize * vertexCount);
719 if (uint8_t* dst = dstMesh->mapStream(VertexDataType::TexCoords0, idsFmtHandle, 0, vertexCount, idsFmtSize, true); dst != nullptr) {
720 std::memcpy(dst, idsVertexData.data(), idsVertexData.size());
721 dstMesh->unmap(VertexDataType::Interleaved0);
722 }
723 else {
725 }
726 }
727
728 // If we have an offset to the ids, put it texcoord1
729 if (idOffset) {
730 Cogs::VertexFormatHandle instanceFmtHandle = Cogs::VertexFormats::createVertexFormat(Id32ui_Inst::Fmt, std::size(Id32ui_Inst::Fmt));
731 if (uint8_t* dst = dstMesh->mapStream(VertexDataType::TexCoords1, instanceFmtHandle, 0, 1, Cogs::getSize(instanceFmtHandle), true); dst != nullptr) {
732 std::memcpy(dst, &idOffset, sizeof(idOffset));
733 dstMesh->unmap(VertexDataType::TexCoords1);
734 }
735 else {
737 }
738 }
739
740 // Store indices
741 const size_t indexCount = indexData.size();
742
743 if (uint8_t* ptr = dstMesh->mapStream(VertexDataType::Indexes, 0, indexCount, dstIndexTypeSize, true); ptr != nullptr) {
744 switch (dstIndexTypeSize) {
745 case 2: {
746 uint16_t* dstIndices = (uint16_t*)ptr;
747 for (size_t i = 0; i < indexCount; i++) {
748 dstIndices[i] = uint16_t(indexData[i]);
749 }
750 break;
751 }
752 case 4:
753 std::memcpy(ptr, indexData.data(), sizeof(uint32_t) * indexCount);
754 break;
755 default:
756 assert(false);
758 }
759 dstMesh->unmap(VertexDataType::Indexes);
760 dstMesh->setMeshFlag(MeshFlags::Indexed);
761 dstMesh->setMeshFlag(MeshFlags::IndexesChanged);
762 dstMesh->setCount(indexCount);
763 }
764 else {
766 }
767
768 dstMesh->setBounds(bbox);
769 return dstMesh.getHandle();
770 }
771
772 MeshHandle buildUnindexedMesh(Context* context,
773 const Mesh* srcMesh,
774 const Cogs::Geometry::BoundingBox& bbox,
775 Cogs::VertexFormatHandle& posFmtHandle,
776 const Cogs::Memory::MemoryBuffer& posVertexData,
777 const Cogs::VertexFormatHandle& idsFmtHandle,
778 const Cogs::Memory::MemoryBuffer& idsVertexData,
779 const size_t vertexCount,
780 const std::vector<uint32_t>& indexData,
781 const uint32_t idOffset)
782 {
783 const size_t indexCount = indexData.size();
784
785 MeshManager::ResourceProxy dstMesh = context->meshManager->createLocked();
786 dstMesh->primitiveType = srcMesh->primitiveType;
787
788 // Position, normal and maybe id goes into Interleaved0 stream.
789 const size_t posFmtSize = Cogs::getSize(posFmtHandle);
790 assert(posVertexData.size() == posFmtSize * vertexCount);
791 if (uint8_t* dst = dstMesh->mapStream(VertexDataType::Interleaved0, posFmtHandle, 0, indexCount, posFmtSize, true); dst != nullptr)
792 {
793 const uint8_t* src = static_cast<const uint8_t*>(posVertexData.data());
794 for (size_t i = 0; i < indexCount; i++) {
795 const size_t ix = indexData[i];
796 assert(ix < vertexCount);
797 std::memcpy(dst + posFmtSize * i, src + posFmtSize * ix, posFmtSize);
798 }
799 dstMesh->unmap(VertexDataType::Interleaved0);
800 }
801 else {
803 }
804
805 // We have a separate ids tream, store it in TexCoord0 stream
806 if (idsFmtHandle) {
807 const size_t idsFmtSize = Cogs::getSize(idsFmtHandle);
808 assert(idsVertexData.size() == idsFmtSize * vertexCount);
809 if (uint8_t* dst = dstMesh->mapStream(VertexDataType::TexCoords0, idsFmtHandle, 0, indexCount, idsFmtSize, true); dst != nullptr)
810 {
811 const uint8_t* src = static_cast<const uint8_t*>(idsVertexData.data());
812 for (size_t i = 0; i < indexCount; i++) {
813 const size_t ix = indexData[i];
814 assert(ix < vertexCount);
815 std::memcpy(dst + idsFmtSize * i, src + idsFmtSize * ix, idsFmtSize);
816 }
817 dstMesh->unmap(VertexDataType::TexCoords0);
818 }
819 else {
821 }
822 }
823
824 // If we have an offset to the ids, put it texcoord1
825 if (idOffset) {
826 Cogs::VertexFormatHandle instanceFmtHandle = Cogs::VertexFormats::createVertexFormat(Id32ui_Inst::Fmt, std::size(Id32ui_Inst::Fmt));
827 if (uint8_t* dst = dstMesh->mapStream(VertexDataType::TexCoords1, instanceFmtHandle, 0, 1, Cogs::getSize(instanceFmtHandle), true); dst != nullptr) {
828 std::memcpy(dst, &idOffset, sizeof(idOffset));
829 dstMesh->unmap(VertexDataType::TexCoords1);
830 }
831 else {
833 }
834 }
835
836 dstMesh->setBounds(bbox);
837 return dstMesh.getHandle();
838 }
839
840}
841
842namespace {
843 using namespace Cogs::Core;
844
845 bool getMeshStreams(Context* /*context*/, std::string& status,
846 size_t& srcVertexCount,
850 Mesh* srcMesh,
851 const PackMeshCommand::Options& options)
852 {
853 if (srcMesh == nullptr) {
854 status = "Empty mesh";
855 return false;
856 }
857
858 // -- Verify and map positions
859 srcVertexCount = 0;
860 if (srcMesh->hasStream(VertexDataType::Positions)) {
861 const DataStream& dataStream = srcMesh->getStream(VertexDataType::Positions);
862 srcVertexCount = dataStream.numElements;
863 srcPos = srcMesh->mapPositionsReadOnly(0, srcVertexCount);
864 if (!srcPos.isValid()) {
865 status = "Failed to map mesh positions as float3.";
866 return false;
867 }
868 }
869 else {
870 status = "Mesh does not have positions stream.";
871 return false;
872 }
873
874 // -- Verify and map normals
875 if (!options.discardNormals && srcMesh->hasStream(VertexDataType::Normals)) {
876 const DataStream& srcNrmStream = srcMesh->getStream(VertexDataType::Normals);
877 if (srcNrmStream.numElements < srcVertexCount) {
878 status = "Mesh has less normals than positions.";
879 return false;
880 }
881 srcNrm = srcMesh->mapNormalsReadOnly(0, srcVertexCount);
882 if (!srcNrm.isValid()) {
883 status = "Failed to map mesh normals as float3.";
884 return false;
885 }
886 }
887
888 // -- Verify and map texcoords
889 if (!options.discardTexCoords && srcMesh->hasStream(VertexDataType::TexCoords0)) {
890 const DataStream& dataStream = srcMesh->getStream(VertexDataType::TexCoords0);
891 if (dataStream.numElements < srcVertexCount) {
892 status = "Mesh has less texcoords than positions.";
893 return false;
894 }
895 srcTex = srcMesh->mapTexCoordsReadOnly(0, srcVertexCount);
896 if (!srcTex.isValid()) {
897 status = "Failed to map mesh normals as float3, ignoring.";
898 return false;
899 }
900 }
901
902 return true;
903 }
904
905
906}
907
908
909bool Cogs::Core::PackMeshCommand::pack(Context* context, std::string& status, glm::mat4& transform, MeshHandle& h, const Options& options)
910{
911 Mesh* srcMesh = h.resolve();
912 size_t srcVertexCount = 0;
916 if (!getMeshStreams(context, status, srcVertexCount, srcPos, srcNrm, srcTex, srcMesh, options)) {
917 return false;
918 }
919
920 const bool hasNormals = srcNrm.isValid();
921 const bool hasTexCoords = srcTex.isValid();
922
923 // Calculate precise bounds
924 glm::vec3 posMin(std::numeric_limits<float>::max());
925 glm::vec3 posMax(-std::numeric_limits<float>::max());
926 for (size_t i = 0; i < srcVertexCount; i++) {
927 posMin = glm::min(posMin, srcPos[i]);
928 posMax = glm::max(posMax, srcPos[i]);
929 }
930 glm::vec3 extent = posMax - posMin;
931 float scale = std::max(std::max(extent.x, extent.y), extent.z);
932 if (srcVertexCount == 0) {
933 LOG_WARNING(logger, "No vertices - skipping scaling");
934 scale = 1.0f;
935 }
936 else if (scale <= 0.0f) {
937 LOG_WARNING(logger, "Zero extent(%f) - skipping scaling", scale);
938 scale = 1.0f;
939 }
940
941 const float invScale = 1.f / scale;
942
943 // Figure out how many bits needed for id. Negative ids is encoded as zero.
944 uint32_t idMax = 0;
945 uint32_t idMin = 0;
946 if (hasTexCoords && 0 < srcVertexCount) {
947 idMin = std::numeric_limits<uint32_t>::max();
948 for (size_t i = 0; i < srcVertexCount; i++) {
949 uint32_t id = uint32_t(std::max(0.f, srcTex[i].x));
950 idMin = std::min(idMin, id);
951 idMax = std::max(idMax, id);
952 }
953 }
954
955 // Choose packing format
956 uint32_t idOffset = 0;
957 PackFormat packFormat;
958 if (hasTexCoords) {
959
960 if (!hasNormals) {
961 status = "Mesh with texcoords but no normals are currently not supported, ignoring";
962 return false;
963 }
964
965 switch (options.target)
966 {
967 case Target::WebGL1_Low:
968 packFormat = PackFormat::Pos16un_Nrm8sn_Id32f;
969 break;
970
971 case Target::WebGL2_Low:
972 // Use id offset if allowed and it maks sense
973 if (options.allowIdOffset && (0xFFFFu <= idMax) && ((idMax - idMin) < 0xFFFFu)) {
974 idOffset = idMin;
975 }
976
977 // If we are not splitting out ids later, and it is possible, use 16 bits for ids.
978 if (((idMax - idOffset) < 0xFFFFu)) {
979 // pos + normal is 6 bytes, but webgl cannot represent this format, it has to be 4 or 8 bytes, so we can just use padding for id.
980 packFormat = PackFormat::Pos10un_Nrm8sn_Id16ui;
981 }
982 else {
983 // If Id32 gets stripped out, so we are left with Pos16Un_Nrm8sn, which is 8 bytes.
984 packFormat = PackFormat::Pos16un_Nrm8sn_Id32ui;
985 }
986 break;
987
988 case Target::WebGL2_Med:
989
990 // Use id offset if allowed and it makes sense
991 if (options.allowIdOffset && (0xFFFFu <= idMax) && ((idMax - idMin) < 0xFFFFu)) {
992 idOffset = idMin;
993 }
994
995 // If we are not splitting out ids later, and it is possible, use 16 bits for ids.
996 if (!options.allowSeparateIdStream && ((idMax - idOffset) < 0xFFFFu)) {
997 packFormat = PackFormat::Pos16un_Nrm16sn_Id16ui;
998 }
999 else {
1000 packFormat = PackFormat::Pos16un_Nrm8sn_Id32ui;
1001 }
1002 break;
1003
1004 default:
1005 assert(false && "Invalid enum");
1006 return false;
1007 }
1008 }
1009 else {
1010
1011 packFormat = hasNormals ? PackFormat::Pos16un_Nrm8sn : PackFormat::Pos10un;
1012
1013 }
1014
1016
1017 std::vector<uint32_t> remapping;
1019
1020 VertexFormatHandle posFmtHandle;
1021 if (!buildPackedVertices(posFmtHandle,
1022 posData,
1023 remapping,
1024 packFormat,
1025 srcPos, srcNrm, srcTex, srcVertexCount, posMin, invScale, idOffset)) return false;
1026
1027 size_t posFmtSize = Cogs::getSize(posFmtHandle);
1028 const size_t vertexCount = posData.size() / posFmtSize;
1029
1030 std::vector<uint32_t> indices;
1031 if (!buildIndices(indices, remapping, srcMesh)) return false;
1032 const size_t indexCount = indices.size();
1033
1034 // Reorder indices to reduce the number of GPU vertex shader invocations
1035 if (options.optimizeTriangleOrder) {
1036 std::vector<uint32_t> indices2(indexCount);
1037 meshopt_optimizeVertexCache(indices2.data(), indices.data(), indexCount, vertexCount);
1038 indices.swap(indices2);
1039 }
1040
1041 // Reorder vertices to optimize for vertex fetch cache
1042 if (options.optimizeVertexOrder) {
1043 tmp.resize(posData.size(), false);
1044 meshopt_optimizeVertexFetch(tmp.data(), indices.data(), indexCount, posData.data(), vertexCount, posFmtSize);
1045 posData.swap(tmp);
1046 }
1047
1048 // Move id/texcoord into its own stream where reasonable.
1049 size_t interleavedIdsOffset = 0; // Offset of id into the interleaved vertex format.
1050 VertexFormatHandle newPosFmtHandle;
1051 VertexFormatHandle idsFmtHandle;
1052 if (hasTexCoords && options.allowSeparateIdStream) {
1053 switch (packFormat) {
1054 case PackFormat::Pos10un_Nrm8sn_Id16ui:
1055 case PackFormat::Pos16un_Nrm16sn_Id16ui:
1056 break;
1057
1058 case PackFormat::Pos16un_Nrm8sn_Id32ui:
1059 interleavedIdsOffset = offsetof(Pos16un_Nrm8sn_Id32ui::Vtx, tex);
1060 newPosFmtHandle = Cogs::VertexFormats::createVertexFormat(Pos16un_Nrm8sn::Fmt, std::size(Pos16un_Nrm8sn::Fmt));
1061 if (idMax - idOffset < 0xFFFFu) {
1062 idsFmtHandle = Cogs::VertexFormats::createVertexFormat(Id16ui::Fmt, std::size(Id16ui::Fmt));
1063 }
1064 else {
1065 idsFmtHandle = Cogs::VertexFormats::createVertexFormat(Id32ui::Fmt, std::size(Id32ui::Fmt));
1066 }
1067 break;
1068
1069 case PackFormat::Pos16un_Nrm8sn_Id32f:
1070 interleavedIdsOffset = offsetof(Pos16un_Nrm8sn_Id32f::Vtx, tex);
1071 newPosFmtHandle = Cogs::VertexFormats::createVertexFormat(Pos16un_Nrm8sn::Fmt, std::size(Pos16un_Nrm8sn::Fmt));
1072 idsFmtHandle = Cogs::VertexFormats::createVertexFormat(Id32f::Fmt, std::size(Id32f::Fmt));
1073 break;
1074
1075 default:
1076 assert(false && "Invalid enum value");
1077 break;
1078 }
1079 }
1080
1081 // If we are to split out ids into its own stream, we have a new position format and a id format
1082 size_t idsFmtSize = 0;
1084 if (newPosFmtHandle && idsFmtHandle) {
1085 const size_t newPosFmtSize = Cogs::getSize(newPosFmtHandle);
1086 idsFmtSize = Cogs::getSize(idsFmtHandle);
1087
1088 tmp.resize(newPosFmtSize * vertexCount, false);
1089 idsData.resize(idsFmtSize * vertexCount, false);
1090
1091 char* newPosDataPtr = static_cast<char*>(tmp.data());
1092 char* idsDataPtr = static_cast<char*>(idsData.data());
1093 const char* posDataPtr = static_cast<char*>(posData.data());
1094 for (size_t i = 0; i < vertexCount; i++) {
1095 std::memcpy(newPosDataPtr + newPosFmtSize * i, posDataPtr + posFmtSize * i, newPosFmtSize);
1096 std::memcpy(idsDataPtr + idsFmtSize * i, posDataPtr + posFmtSize * i + interleavedIdsOffset, idsFmtSize);
1097 }
1098
1099 // Swap in new pos/normal data without id
1100 posData.swap(tmp);
1101 posFmtHandle = newPosFmtHandle;
1102 posFmtSize = newPosFmtSize;
1103 }
1104
1105 const uint32_t indexTypeSize = vertexCount < 0xffffu ? 2 : 4;
1106 const size_t indexedSize = (posFmtSize + idsFmtSize) * vertexCount + indexTypeSize * indexCount;
1107 const size_t unindexedSize = (posFmtSize + idsFmtSize) * indexCount;
1108
1109 bool indexed = indexedSize < unindexedSize;
1110 //LOG_DEBUG(logger, "fmt=%s indexed size: %zu, unindexed size: %zu", PackFormatName[size_t(packFormat)], indexedSize, unindexedSize);
1111
1112 const Cogs::Geometry::BoundingBox bbox{ glm::vec3(0.f), invScale * extent };
1113 if (indexed) {
1114 h = buildIndexedMesh(context, srcMesh, bbox, posFmtHandle, posData, idsFmtHandle, idsData, vertexCount, indices, indexTypeSize, idOffset);
1115 }
1116 else {
1117 h = buildUnindexedMesh(context, srcMesh, bbox, posFmtHandle, posData, idsFmtHandle, idsData, vertexCount, indices, idOffset);
1118 }
1119
1120 glm::vec3 shift = posMin;
1121 transform = glm::mat4(scale, 0.f, 0.f, 0.f,
1122 0.f, scale, 0.f, 0.f,
1123 0.f, 0.f, scale, 0.f,
1124 shift.x, shift.y, shift.z, 1.f);
1125
1126 return true;
1127}
ComponentType * getComponent() const
Definition: Component.h:159
A Context instance contains all the services, systems and runtime components needed to use Cogs.
Definition: Context.h:83
class EntityStore * store
Entity store.
Definition: Context.h:231
EntityPtr getEntity(const StringView &name, bool logIfNotFound=true) const
Retrieve a reference to the shared entity pointer to the Entity with the given name.
Contains a handle to a Mesh resource to use when rendering using the MeshRenderComponent.
Definition: MeshComponent.h:15
MeshHandle meshHandle
Handle to a Mesh resource to use when rendering.
Definition: MeshComponent.h:29
ResourceProxy< Mesh, ResourceManager > ResourceProxy
Type of resource proxy objects, specialized on the type of resource.
Contains information on how the entity behaves in the scene.
std::vector< EntityPtr > children
Contains all child entities owned by this component.
Defines a 4x4 transformation matrix for the entity and a global offset for root entities.
Log implementation class.
Definition: LogManager.h:140
Contains the Engine, Renderer, resource managers and other systems needed to run Cogs....
std::shared_ptr< ComponentModel::Entity > EntityPtr
Smart pointer for Entity access.
Definition: EntityPtr.h:12
constexpr Log getLogger(const char(&name)[LEN]) noexcept
Definition: LogManager.h:181
@ InstanceData
Per instance data.
@ VertexData
Per vertex data.
@ Position
Position semantic.
@ Normal
Normal semantic.
@ TextureCoordinate
Texture coordinate semantic.
Contains a stream of data used by Mesh resources.
Definition: Mesh.h:80
uint32_t numElements
Number of elements of the type given by format contained in data.
Definition: Mesh.h:108
uint32_t stride
Element stride.
Definition: Mesh.h:105
Base class for Cogs Editor commands.
Definition: EditorCommand.h:19
Wrapper for read-only access to mapped stream data.
Definition: Mesh.h:197
bool isValid() const
Returns true if stream mapping successful.
Definition: Mesh.h:212
@ IndexesChanged
The index data of the mesh changed.
Definition: Mesh.h:61
@ Indexed
The mesh should be drawn indexed, using index data to order the triangle vertexes.
Definition: Mesh.h:65
Meshes contain streams of vertex data in addition to index data and options defining geometry used fo...
Definition: Mesh.h:265
bool isIndexed() const
If the mesh uses indexed geometry.
Definition: Mesh.h:953
bool hasStream(VertexDataType::EVertexDataType type) const
Check if the Mesh has a DataStream for the given type.
Definition: Mesh.h:933
MappedStreamReadOnly< glm::vec3 > mapNormalsReadOnly(const size_t start, const size_t end)
Map the normal stream for read access, range between start and end.
Definition: Mesh.h:438
DataStream & getStream(const VertexDataType::EVertexDataType dataType)
Get the stream corresponding to the given dataType.
Definition: Mesh.cpp:85
MappedStreamReadOnly< glm::vec3 > mapPositionsReadOnly(const size_t start, const size_t end)
Map the position stream for read access, range between start and end.
Definition: Mesh.h:382
MappedStreamReadOnly< glm::vec2 > mapTexCoordsReadOnly(const size_t start, const size_t end)
Map the texture coordinate stream for read access, range between start and end.
Definition: Mesh.h:550
void apply() override
Run the command.
static const ResourceHandle_t NoHandle
Handle representing a default (or none if default not present) resource.
ResourceType * resolve() const
Resolve the handle, returning a pointer to the actual resource.
Vertex element structure used to describe a single data element in a vertex for the input assembler.
Definition: VertexFormat.h:38
uint16_t offset
Offset in bytes from the vertex position in memory.
Definition: VertexFormat.h:39