diff options
Diffstat (limited to 'src/importlib')
-rw-r--r-- | src/importlib/Qt3DSImportContainers.h | 88 | ||||
-rw-r--r-- | src/importlib/Qt3DSImportLibPrecompile.h | 56 | ||||
-rw-r--r-- | src/importlib/Qt3DSImportMesh.cpp | 829 | ||||
-rw-r--r-- | src/importlib/Qt3DSImportMesh.h | 530 | ||||
-rw-r--r-- | src/importlib/Qt3DSImportMeshBuilder.cpp | 598 | ||||
-rw-r--r-- | src/importlib/Qt3DSImportPath.cpp | 152 | ||||
-rw-r--r-- | src/importlib/Qt3DSImportPath.h | 87 |
7 files changed, 2340 insertions, 0 deletions
diff --git a/src/importlib/Qt3DSImportContainers.h b/src/importlib/Qt3DSImportContainers.h new file mode 100644 index 0000000..2f2a72e --- /dev/null +++ b/src/importlib/Qt3DSImportContainers.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** +** Copyright (C) 1993-2009 NVIDIA Corporation. +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_IMPORT_CONTAINERS_H +#define QT3DS_IMPORT_CONTAINERS_H + +#include "EASTL/hash_map.h" +#include "EASTL/hash_set.h" +#include "EASTL/vector.h" +#include "foundation/Qt3DSDataRef.h" +#include "Qt3DSDMWStrOps.h" + +namespace qt3dsimp { + +#define QT3DSIMP_FOREACH(idxnm, val) \ + for (QT3DSU32 idxnm = 0, __numItems = (QT3DSU32)val; idxnm < __numItems; ++idxnm) + +template <typename TKey, typename TValue, typename THash = eastl::hash<TKey>, + typename TPredicate = eastl::equal_to<TKey>> +struct ImportHashMap : public eastl::hash_map<TKey, TValue, THash, TPredicate> +{ + using base_type = eastl::hash_map<TKey, TValue, THash, TPredicate>; + + ImportHashMap() {} + + + + bool contains(const TKey &key) const { return find(key) != base_type::end(); } +}; +template <typename TKey> +struct ImportHashSet : public eastl::hash_set<TKey, eastl::hash<TKey>, eastl::equal_to<TKey>> +{ + using base_type = eastl::hash_set<TKey, eastl::hash<TKey>, eastl::equal_to<TKey>>; + ImportHashSet() {} + bool contains(const TKey &key) const { return base_type::find(key) != base_type::end(); } +}; +template <typename TValue> +struct ImportArray : public eastl::vector<TValue> +{ + using base_type = eastl::vector<TValue>; + + ImportArray() {} + operator NVConstDataRef<TValue>() const + { + return NVConstDataRef<TValue>(base_type::data(), (QT3DSU32) base_type::size()); + } + + operator NVDataRef<TValue>() { + return NVDataRef<TValue>(base_type::data(), (QT3DSU32) base_type::size()); + } +}; + +inline NVConstDataRef<wchar_t> toRef(const wchar_t *data, QT3DSU32 len = 0) +{ + if (IsTrivial(data)) + return NVConstDataRef<wchar_t>(L"", 0); + len = len ? len : (QT3DSU32)wcslen(data); + return NVConstDataRef<wchar_t>(data, len); +} +} + +#endif diff --git a/src/importlib/Qt3DSImportLibPrecompile.h b/src/importlib/Qt3DSImportLibPrecompile.h new file mode 100644 index 0000000..34da79d --- /dev/null +++ b/src/importlib/Qt3DSImportLibPrecompile.h @@ -0,0 +1,56 @@ +/**************************************************************************** +** +** Copyright (C) 2016 NVIDIA Corporation. +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#pragma once + +#include "foundation/Qt3DS.h" +#include "foundation/Qt3DSDataRef.h" +#include "foundation/Qt3DSOption.h" +#include "render/Qt3DSRenderBaseTypes.h" +#include <map> +#include <vector> +#include <string> +#include <exception> +#include <set> +#include <iostream> +#include <sstream> +#include <fstream> +#include "Qt3DSDMDataTypes.h" +#include "foundation/Qt3DSIntrinsics.h" +#include "foundation/Qt3DSBasicTemplates.h" + +namespace qt3dsimp { +using namespace qt3ds; +using namespace qt3ds::foundation; +using namespace qt3ds::render; +using namespace qt3dsdm; +using namespace std; +} + +// TODO: reference additional headers your program requires here diff --git a/src/importlib/Qt3DSImportMesh.cpp b/src/importlib/Qt3DSImportMesh.cpp new file mode 100644 index 0000000..1535b91 --- /dev/null +++ b/src/importlib/Qt3DSImportMesh.cpp @@ -0,0 +1,829 @@ +/**************************************************************************** +** +** Copyright (C) 1993-2009 NVIDIA Corporation. +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifdef WIN32 +#pragma warning(disable : 4100) +#endif +#include "Qt3DSImportLibPrecompile.h" +#include "Qt3DSImportMesh.h" + +using namespace qt3dsimp; + +#ifdef QT3DS_X86 + +// Ensure our objects are of expected sizes. This keeps us honest +// And ensures that we can load the datastructures we expect to by simply +// mapping the memory. +QT3DS_COMPILE_TIME_ASSERT(sizeof(NVRenderComponentTypes::Enum) == 4); +QT3DS_COMPILE_TIME_ASSERT(sizeof(NVRenderDrawMode::Enum) == 4); +QT3DS_COMPILE_TIME_ASSERT(sizeof(NVRenderVertexBufferEntry) == 20); +QT3DS_COMPILE_TIME_ASSERT(sizeof(VertexBuffer) == 20); +QT3DS_COMPILE_TIME_ASSERT(sizeof(IndexBuffer) == 12); +QT3DS_COMPILE_TIME_ASSERT(sizeof(MeshSubset) == 40); +QT3DS_COMPILE_TIME_ASSERT(sizeof(MeshDataHeader) == 12); +QT3DS_COMPILE_TIME_ASSERT(sizeof(Mesh) == 56); + +#endif + +#define QT3DSIMP_FOREACH(idxnm, val) \ + for (QT3DSU32 idxnm = 0, __numItems = (QT3DSU32)val; idxnm < __numItems; ++idxnm) + +namespace { + +struct MeshSubsetV1 +{ + // See description of a logical vertex buffer below + QT3DSU32 m_LogicalVbufIndex; + // QT3DS_MAX_U32 means use all available items + QT3DSU32 m_Count; + // Offset is in item size, not bytes. + QT3DSU32 m_Offset; + // Bounds of this subset. This is filled in by the builder + // see AddMeshSubset + NVBounds3 m_Bounds; +}; + +struct LogicalVertexBuffer +{ + QT3DSU32 m_ByteOffset; + QT3DSU32 m_ByteSize; + LogicalVertexBuffer(QT3DSU32 byteOff, QT3DSU32 byteSize) + : m_ByteOffset(byteOff) + , m_ByteSize(byteSize) + { + } + LogicalVertexBuffer() + : m_ByteOffset(0) + , m_ByteSize(0) + { + } +}; + +struct MeshV1 +{ + VertexBuffer m_VertexBuffer; + IndexBuffer m_IndexBuffer; + SOffsetDataRef<LogicalVertexBuffer> m_LogicalVertexBuffers; // may be empty + SOffsetDataRef<MeshSubsetV1> m_Subsets; + NVRenderDrawMode::Enum m_DrawMode; + NVRenderWinding::Enum m_Winding; + typedef MeshSubsetV1 TSubsetType; +}; + +template <typename TSerializer> +void Serialize(TSerializer &serializer, MeshV1 &mesh) +{ + QT3DSU8 *baseAddr = reinterpret_cast<QT3DSU8 *>(&mesh); + serializer.streamify(mesh.m_VertexBuffer.m_Entries); + serializer.align(); + QT3DSIMP_FOREACH(entry, mesh.m_VertexBuffer.m_Entries.size()) + { + MeshVertexBufferEntry &entryData = const_cast<MeshVertexBufferEntry &>( + mesh.m_VertexBuffer.m_Entries.index(baseAddr, entry)); + serializer.streamifyCharPointerOffset(entryData.m_NameOffset); + serializer.align(); + } + serializer.streamify(mesh.m_VertexBuffer.m_Data); + serializer.align(); + serializer.streamify(mesh.m_IndexBuffer.m_Data); + serializer.align(); + serializer.streamify(mesh.m_LogicalVertexBuffers); + serializer.align(); + serializer.streamify(mesh.m_Subsets); + serializer.align(); +} + +struct MeshSubsetV2 +{ + QT3DSU32 m_LogicalVbufIndex; + QT3DSU32 m_Count; + QT3DSU32 m_Offset; + NVBounds3 m_Bounds; + SOffsetDataRef<char16_t> m_Name; +}; + +struct MeshV2 +{ + static const char16_t *s_DefaultName; + + VertexBuffer m_VertexBuffer; + IndexBuffer m_IndexBuffer; + SOffsetDataRef<LogicalVertexBuffer> m_LogicalVertexBuffers; // may be empty + SOffsetDataRef<MeshSubsetV2> m_Subsets; + NVRenderDrawMode::Enum m_DrawMode; + NVRenderWinding::Enum m_Winding; + typedef MeshSubsetV2 TSubsetType; +}; + +template <typename TSerializer> +void Serialize(TSerializer &serializer, MeshV2 &mesh) +{ + QT3DSU8 *baseAddr = reinterpret_cast<QT3DSU8 *>(&mesh); + serializer.streamify(mesh.m_VertexBuffer.m_Entries); + serializer.align(); + QT3DSIMP_FOREACH(entry, mesh.m_VertexBuffer.m_Entries.size()) + { + MeshVertexBufferEntry &entryData = const_cast<MeshVertexBufferEntry &>( + mesh.m_VertexBuffer.m_Entries.index(baseAddr, entry)); + serializer.streamifyCharPointerOffset(entryData.m_NameOffset); + serializer.align(); + } + serializer.streamify(mesh.m_VertexBuffer.m_Data); + serializer.align(); + serializer.streamify(mesh.m_IndexBuffer.m_Data); + serializer.align(); + serializer.streamify(mesh.m_LogicalVertexBuffers); + serializer.align(); + serializer.streamify(mesh.m_Subsets); + serializer.align(); + QT3DSIMP_FOREACH(entry, mesh.m_Subsets.size()) + { + MeshSubsetV2 &theSubset = const_cast<MeshSubsetV2 &>(mesh.m_Subsets.index(baseAddr, entry)); + serializer.streamify(theSubset.m_Name); + serializer.align(); + } +} + +// Localize the knowledge required to read/write a mesh into one function +// written in such a way that you can both read and write by passing +// in one serializer type or another. +// This function needs to be careful to request alignment after every write of a +// buffer that may leave us unaligned. The easiest way to be correct is to request +// alignment a lot. The hardest way is to use knowledge of the datatypes and +// only request alignment when necessary. +template <typename TSerializer> +void Serialize(TSerializer &serializer, Mesh &mesh) +{ + QT3DSU8 *baseAddr = reinterpret_cast<QT3DSU8 *>(&mesh); + serializer.streamify(mesh.m_VertexBuffer.m_Entries); + serializer.align(); + QT3DSIMP_FOREACH(entry, mesh.m_VertexBuffer.m_Entries.size()) + { + MeshVertexBufferEntry &entryData = mesh.m_VertexBuffer.m_Entries.index(baseAddr, entry); + serializer.streamifyCharPointerOffset(entryData.m_NameOffset); + serializer.align(); + } + serializer.streamify(mesh.m_VertexBuffer.m_Data); + serializer.align(); + serializer.streamify(mesh.m_IndexBuffer.m_Data); + serializer.align(); + serializer.streamify(mesh.m_Subsets); + serializer.align(); + QT3DSIMP_FOREACH(entry, mesh.m_Subsets.size()) + { + MeshSubset &theSubset = const_cast<MeshSubset &>(mesh.m_Subsets.index(baseAddr, entry)); + serializer.streamify(theSubset.m_Name); + serializer.align(); + } + serializer.streamify(mesh.m_Joints); + serializer.align(); +} + +struct TotallingSerializer +{ + QT3DSU32 m_NumBytes; + QT3DSU8 *m_BaseAddress; + TotallingSerializer(QT3DSU8 *inBaseAddr) + : m_NumBytes(0) + , m_BaseAddress(inBaseAddr) + { + } + template <typename TDataType> + void streamify(const SOffsetDataRef<TDataType> &data) + { + m_NumBytes += data.size() * sizeof(TDataType); + } + void streamify(const char *data) + { + if (data == NULL) + data = ""; + QT3DSU32 len = (QT3DSU32)strlen(data) + 1; + m_NumBytes += 4; + m_NumBytes += len; + } + void streamifyCharPointerOffset(QT3DSU32 inOffset) + { + if (inOffset) { + const char *dataPtr = (const char *)(inOffset + m_BaseAddress); + streamify(dataPtr); + } else + streamify(""); + } + bool needsAlignment() const { return getAlignmentAmount() > 0; } + QT3DSU32 getAlignmentAmount() const { return 4 - (m_NumBytes % 4); } + void align() + { + if (needsAlignment()) + m_NumBytes += getAlignmentAmount(); + } +}; + +struct ByteWritingSerializer +{ + IOutStream &m_Stream; + TotallingSerializer m_ByteCounter; + QT3DSU8 *m_BaseAddress; + ByteWritingSerializer(IOutStream &str, QT3DSU8 *inBaseAddress) + : m_Stream(str) + , m_ByteCounter(inBaseAddress) + , m_BaseAddress(inBaseAddress) + { + } + + template <typename TDataType> + void streamify(const SOffsetDataRef<TDataType> &data) + { + m_ByteCounter.streamify(data); + m_Stream.Write(data.begin(m_BaseAddress), data.size()); + } + void streamify(const char *data) + { + m_ByteCounter.streamify(data); + if (data == NULL) + data = ""; + QT3DSU32 len = (QT3DSU32)strlen(data) + 1; + m_Stream.Write(len); + m_Stream.Write(data, len); + } + void streamifyCharPointerOffset(QT3DSU32 inOffset) + { + const char *dataPtr = (const char *)(inOffset + m_BaseAddress); + streamify(dataPtr); + } + + void align() + { + if (m_ByteCounter.needsAlignment()) { + QT3DSU8 buffer[] = { 0, 0, 0, 0 }; + m_Stream.Write(buffer, m_ByteCounter.getAlignmentAmount()); + m_ByteCounter.align(); + } + } +}; + +struct MemoryAssigningSerializer +{ + QT3DSU8 *m_Memory; + QT3DSU8 *m_BaseAddress; + QT3DSU32 m_Size; + TotallingSerializer m_ByteCounter; + bool m_Failure; + MemoryAssigningSerializer(QT3DSU8 *data, QT3DSU32 size, QT3DSU32 startOffset) + : m_Memory(data + startOffset) + , m_BaseAddress(data) + , m_Size(size) + , m_ByteCounter(data) + , m_Failure(false) + { + // We expect 4 byte aligned memory to begin with + QT3DS_ASSERT((((size_t)m_Memory) % 4) == 0); + } + + template <typename TDataType> + void streamify(const SOffsetDataRef<TDataType> &_data) + { + SOffsetDataRef<TDataType> &data = const_cast<SOffsetDataRef<TDataType> &>(_data); + if (m_Failure) { + data.m_Size = 0; + data.m_Offset = 0; + return; + } + QT3DSU32 current = m_ByteCounter.m_NumBytes; + m_ByteCounter.streamify(_data); + if (m_ByteCounter.m_NumBytes > m_Size) { + data.m_Size = 0; + data.m_Offset = 0; + m_Failure = true; + return; + } + QT3DSU32 numBytes = m_ByteCounter.m_NumBytes - current; + if (numBytes) { + data.m_Offset = (QT3DSU32)(m_Memory - m_BaseAddress); + updateMemoryBuffer(numBytes); + } else { + data.m_Offset = 0; + data.m_Size = 0; + } + } + void streamify(const char *&_data) + { + QT3DSU32 len; + m_ByteCounter.m_NumBytes += 4; + if (m_ByteCounter.m_NumBytes > m_Size) { + _data = ""; + m_Failure = true; + return; + } + qt3ds::intrinsics::memCopy(&len, m_Memory, 4); + updateMemoryBuffer(4); + m_ByteCounter.m_NumBytes += len; + if (m_ByteCounter.m_NumBytes > m_Size) { + _data = ""; + m_Failure = true; + return; + } + _data = (const char *)m_Memory; + updateMemoryBuffer(len); + } + void streamifyCharPointerOffset(QT3DSU32 &inOffset) + { + const char *dataPtr; + streamify(dataPtr); + inOffset = (QT3DSU32)(dataPtr - (const char *)m_BaseAddress); + } + void align() + { + if (m_ByteCounter.needsAlignment()) { + QT3DSU32 numBytes = m_ByteCounter.getAlignmentAmount(); + m_ByteCounter.align(); + updateMemoryBuffer(numBytes); + } + } + void updateMemoryBuffer(QT3DSU32 numBytes) { m_Memory += numBytes; } +}; + +inline QT3DSU32 GetMeshDataSize(Mesh &mesh) +{ + TotallingSerializer s(reinterpret_cast<QT3DSU8 *>(&mesh)); + Serialize(s, mesh); + return s.m_NumBytes; +} + +template <typename TDataType> +QT3DSU32 NextIndex(const QT3DSU8 *inBaseAddress, const SOffsetDataRef<QT3DSU8> data, QT3DSU32 idx) +{ + QT3DSU32 numItems = data.size() / sizeof(TDataType); + if (idx < numItems) { + const TDataType *dataPtr(reinterpret_cast<const TDataType *>(data.begin(inBaseAddress))); + return dataPtr[idx]; + } else { + QT3DS_ASSERT(false); + return 0; + } +} + +template <typename TDataType> +QT3DSU32 NextIndex(NVConstDataRef<QT3DSU8> data, QT3DSU32 idx) +{ + QT3DSU32 numItems = data.size() / sizeof(TDataType); + if (idx < numItems) { + const TDataType *dataPtr(reinterpret_cast<const TDataType *>(data.begin())); + return dataPtr[idx]; + } else { + QT3DS_ASSERT(false); + return 0; + } +} + +inline QT3DSU32 NextIndex(NVConstDataRef<QT3DSU8> inData, + qt3ds::render::NVRenderComponentTypes::Enum inCompType, QT3DSU32 idx) +{ + if (inData.size() == 0) + return idx; + switch (inCompType) { + case NVRenderComponentTypes::QT3DSU8: + return NextIndex<QT3DSU8>(inData, idx); + case NVRenderComponentTypes::QT3DSI8: + return NextIndex<QT3DSI8>(inData, idx); + case NVRenderComponentTypes::QT3DSU16: + return NextIndex<QT3DSU16>(inData, idx); + case NVRenderComponentTypes::QT3DSI16: + return NextIndex<QT3DSI16>(inData, idx); + case NVRenderComponentTypes::QT3DSU32: + return NextIndex<QT3DSU32>(inData, idx); + case NVRenderComponentTypes::QT3DSI32: + return NextIndex<QT3DSI32>(inData, idx); + default: + break; + } + + // Invalid index buffer index type. + QT3DS_ASSERT(false); + return 0; +} + +template <typename TMeshType> +// Not exposed to the outside world +TMeshType *DoInitialize(MeshBufHeaderFlags /*meshFlags*/, NVDataRef<QT3DSU8> data) +{ + QT3DSU8 *newMem = data.begin(); + QT3DSU32 amountLeft = data.size() - sizeof(TMeshType); + MemoryAssigningSerializer s(newMem, amountLeft, sizeof(TMeshType)); + TMeshType *retval = (TMeshType *)newMem; + Serialize(s, *retval); + if (s.m_Failure) + return NULL; + return retval; +} +} + +NVBounds3 Mesh::CalculateSubsetBounds(const NVRenderVertexBufferEntry &inEntry, + NVConstDataRef<QT3DSU8> inVertxData, QT3DSU32 inStride, + NVConstDataRef<QT3DSU8> inIndexData, + qt3ds::render::NVRenderComponentTypes::Enum inIndexCompType, + QT3DSU32 inSubsetCount, QT3DSU32 inSubsetOffset) +{ + NVBounds3 retval(NVBounds3::empty()); + const NVRenderVertexBufferEntry &entry(inEntry); + if (entry.m_ComponentType != NVRenderComponentTypes::QT3DSF32 || entry.m_NumComponents != 3) { + QT3DS_ASSERT(false); + return retval; + } + + const QT3DSU8 *beginPtr = inVertxData.begin(); + QT3DSU32 numBytes = inVertxData.size(); + QT3DSU32 dataStride = inStride; + QT3DSU32 posOffset = entry.m_FirstItemOffset; + // The loop below could be template specialized *if* we wanted to do this. + // and the perf of the existing loop was determined to be a problem. + // Else I would rather stay way from the template specialization. + QT3DSIMP_FOREACH(idx, inSubsetCount) + { + QT3DSU32 dataIdx = NextIndex(inIndexData, inIndexCompType, idx + inSubsetOffset); + QT3DSU32 finalOffset = (dataIdx * dataStride) + posOffset; + if (finalOffset + sizeof(QT3DSVec3) <= numBytes) { + const QT3DSU8 *dataPtr = beginPtr + finalOffset; + retval.include(*reinterpret_cast<const QT3DSVec3 *>(dataPtr)); + } else { + QT3DS_ASSERT(false); + } + } + + return retval; +} + +void Mesh::Save(IOutStream &outStream) const +{ + Mesh &mesh(const_cast<Mesh &>(*this)); + QT3DSU8 *baseAddress = reinterpret_cast<QT3DSU8 *>(&mesh); + QT3DSU32 numBytes = sizeof(Mesh) + GetMeshDataSize(mesh); + MeshDataHeader header(numBytes); + outStream.Write(header); + outStream.Write(*this); + ByteWritingSerializer writer(outStream, baseAddress); + Serialize(writer, mesh); +} + +wchar_t g_DefaultName[] = { 0 }; + +const wchar_t *Mesh::s_DefaultName = g_DefaultName; + +template <typename TMeshType> +struct SubsetNameHandler +{ +}; + +template <> +struct SubsetNameHandler<MeshV1> +{ + void AssignName(const QT3DSU8 * /*v1BaseAddress*/, const MeshSubsetV1 & /*mesh*/, + QT3DSU8 * /*baseAddress*/, QT3DSU8 *& /*nameBuffer*/, MeshSubset &outDest) + { + outDest.m_Name = SOffsetDataRef<char16_t>(); + } + QT3DSU32 NameLength(const MeshSubsetV1 &) { return 0; } +}; + +using qt3ds::intrinsics::memCopy; + +template <> +struct SubsetNameHandler<MeshV2> +{ + void AssignName(const QT3DSU8 *v2BaseAddress, const MeshSubsetV2 &mesh, QT3DSU8 *baseAddress, + QT3DSU8 *&nameBuffer, MeshSubset &outDest) + { + outDest.m_Name.m_Size = mesh.m_Name.m_Size; + outDest.m_Name.m_Offset = (QT3DSU32)(nameBuffer - baseAddress); + QT3DSU32 dtypeSize = mesh.m_Name.m_Size * 2; + memCopy(nameBuffer, mesh.m_Name.begin(v2BaseAddress), dtypeSize); + nameBuffer += dtypeSize; + } + QT3DSU32 NameLength(const MeshSubsetV2 &mesh) { return (mesh.m_Name.size() + 1) * 2; } +}; + +QT3DSU32 GetAlignedOffset(QT3DSU32 offset, QT3DSU32 align) +{ + QT3DSU32 leftover = offset % align; + if (leftover) + return offset + (align - leftover); + return offset; +} + +template <typename TPreviousMeshType> +Mesh *CreateMeshFromPreviousMesh(TPreviousMeshType *temp, NVAllocatorCallback &alloc) +{ + QT3DSU32 newMeshSize = sizeof(Mesh); + QT3DSU8 *tempBaseAddress = reinterpret_cast<QT3DSU8 *>(temp); + QT3DSU32 alignment = sizeof(void *); + + QT3DSU32 vertBufferSize = GetAlignedOffset(temp->m_VertexBuffer.m_Data.size(), alignment); + newMeshSize += vertBufferSize; + QT3DSU32 entryDataSize = temp->m_VertexBuffer.m_Entries.size() * sizeof(MeshVertexBufferEntry); + newMeshSize += entryDataSize; + QT3DSU32 indexBufferSize = GetAlignedOffset(temp->m_IndexBuffer.m_Data.size(), alignment); + newMeshSize += indexBufferSize; + QT3DSU32 entryNameSize = 0; + for (QT3DSU32 entryIdx = 0, entryEnd = temp->m_VertexBuffer.m_Entries.size(); entryIdx < entryEnd; + ++entryIdx) { + const qt3ds::render::NVRenderVertexBufferEntry theEntry = + temp->m_VertexBuffer.m_Entries.index(tempBaseAddress, entryIdx) + .ToVertexBufferEntry(tempBaseAddress); + const char *namePtr = theEntry.m_Name; + if (namePtr == NULL) + namePtr = ""; + + entryNameSize += (QT3DSU32)strlen(theEntry.m_Name) + 1; + } + entryNameSize = GetAlignedOffset(entryNameSize, alignment); + + newMeshSize += entryNameSize; + QT3DSU32 subsetBufferSize = temp->m_Subsets.size() * sizeof(MeshSubset); + newMeshSize += subsetBufferSize; + QT3DSU32 nameLength = 0; + for (QT3DSU32 subsetIdx = 0, subsetEnd = temp->m_Subsets.size(); subsetIdx < subsetEnd; + ++subsetIdx) { + nameLength += SubsetNameHandler<TPreviousMeshType>().NameLength( + temp->m_Subsets.index(tempBaseAddress, subsetIdx)); + } + nameLength = GetAlignedOffset(nameLength, alignment); + + newMeshSize += nameLength; + + Mesh *retval = (Mesh *)alloc.allocate(newMeshSize, "TempData", __FILE__, __LINE__); + new (retval) Mesh(); + QT3DSU8 *baseOffset = reinterpret_cast<QT3DSU8 *>(retval); + QT3DSU8 *vertBufferData = baseOffset + sizeof(Mesh); + QT3DSU8 *entryBufferData = vertBufferData + vertBufferSize; + QT3DSU8 *entryNameBuffer = entryBufferData + entryDataSize; + QT3DSU8 *indexBufferData = entryNameBuffer + entryNameSize; + QT3DSU8 *subsetBufferData = indexBufferData + indexBufferSize; + QT3DSU8 *nameData = subsetBufferData + subsetBufferSize; + + retval->m_DrawMode = temp->m_DrawMode; + retval->m_Winding = temp->m_Winding; + retval->m_VertexBuffer = temp->m_VertexBuffer; + retval->m_VertexBuffer.m_Data.m_Offset = (QT3DSU32)(vertBufferData - baseOffset); + retval->m_VertexBuffer.m_Entries.m_Offset = (QT3DSU32)(entryBufferData - baseOffset); + memCopy(vertBufferData, temp->m_VertexBuffer.m_Data.begin(tempBaseAddress), + temp->m_VertexBuffer.m_Data.size()); + memCopy(entryBufferData, temp->m_VertexBuffer.m_Entries.begin(tempBaseAddress), entryDataSize); + QT3DSIMP_FOREACH(idx, temp->m_VertexBuffer.m_Entries.size()) + { + const MeshVertexBufferEntry &src = + temp->m_VertexBuffer.m_Entries.index(tempBaseAddress, idx); + MeshVertexBufferEntry &dest = retval->m_VertexBuffer.m_Entries.index(baseOffset, idx); + + const char *targetName = reinterpret_cast<const char *>(src.m_NameOffset + tempBaseAddress); + if (src.m_NameOffset == 0) + targetName = ""; + QT3DSU32 nameLen = (QT3DSU32)strlen(targetName) + 1; + dest.m_NameOffset = (QT3DSU32)(entryNameBuffer - baseOffset); + memCopy(entryNameBuffer, targetName, nameLen); + entryNameBuffer += nameLen; + } + + retval->m_IndexBuffer = temp->m_IndexBuffer; + retval->m_IndexBuffer.m_Data.m_Offset = (QT3DSU32)(indexBufferData - baseOffset); + memCopy(indexBufferData, temp->m_IndexBuffer.m_Data.begin(tempBaseAddress), + temp->m_IndexBuffer.m_Data.size()); + + retval->m_Subsets.m_Size = temp->m_Subsets.m_Size; + retval->m_Subsets.m_Offset = (QT3DSU32)(subsetBufferData - baseOffset); + QT3DSIMP_FOREACH(idx, temp->m_Subsets.size()) + { + MeshSubset &dest = const_cast<MeshSubset &>(retval->m_Subsets.index(baseOffset, idx)); + const typename TPreviousMeshType::TSubsetType &src = + temp->m_Subsets.index(tempBaseAddress, idx); + dest.m_Count = src.m_Count; + dest.m_Offset = src.m_Offset; + dest.m_Bounds = src.m_Bounds; + SubsetNameHandler<TPreviousMeshType>().AssignName(tempBaseAddress, src, baseOffset, + nameData, dest); + } + alloc.deallocate(temp); + return retval; +} + +Mesh *Mesh::Load(NVAllocatorCallback &alloc, IInStream &inStream) +{ + MeshDataHeader header; + inStream.Read(header); + QT3DS_ASSERT(header.m_FileId == MeshDataHeader::GetFileId()); + if (header.m_FileId != MeshDataHeader::GetFileId()) + return NULL; + if (header.m_FileVersion < 1 || header.m_FileVersion > MeshDataHeader::GetCurrentFileVersion()) + return NULL; + if (header.m_SizeInBytes < sizeof(Mesh)) + return NULL; + QT3DSU8 *newMem = (QT3DSU8 *)alloc.allocate(header.m_SizeInBytes, "Mesh", __FILE__, __LINE__); + QT3DSU32 amountRead = inStream.Read(NVDataRef<QT3DSU8>(newMem, header.m_SizeInBytes)); + if (amountRead != header.m_SizeInBytes) + goto failure; + + if (header.m_FileVersion == 1) { + MeshV1 *temp = DoInitialize<MeshV1>(header.m_HeaderFlags, + NVDataRef<QT3DSU8>(newMem, header.m_SizeInBytes)); + if (temp == NULL) + goto failure; + return CreateMeshFromPreviousMesh(temp, alloc); + + } else if (header.m_FileVersion == 2) { + MeshV2 *temp = DoInitialize<MeshV2>(header.m_HeaderFlags, + NVDataRef<QT3DSU8>(newMem, header.m_SizeInBytes)); + if (temp == NULL) + goto failure; + return CreateMeshFromPreviousMesh(temp, alloc); + } else { + Mesh *retval = Initialize(header.m_FileVersion, header.m_HeaderFlags, + NVDataRef<QT3DSU8>(newMem, header.m_SizeInBytes)); + if (retval == NULL) + goto failure; + return retval; + } + +failure: + QT3DS_ASSERT(false); + alloc.deallocate(newMem); + return NULL; +} + +Mesh *Mesh::Initialize(QT3DSU16 meshVersion, MeshBufHeaderFlags meshFlags, NVDataRef<QT3DSU8> data) +{ + if (meshVersion != MeshDataHeader::GetCurrentFileVersion()) + return NULL; + return DoInitialize<Mesh>(meshFlags, data); +} + +// Multimesh support where you have multiple meshes in a single file. +// Save multi where you have overridden the allocator. +QT3DSU32 Mesh::SaveMulti(NVAllocatorCallback &alloc, ISeekableIOStream &inStream, QT3DSU32 inId) const +{ + QT3DSU32 nextId = 1; + MeshMultiHeader tempHeader; + MeshMultiHeader *theHeader = NULL; + MeshMultiHeader *theWriteHeader = NULL; + + QT3DSI64 newMeshStartPos = 0; + if (inStream.GetLength() != 0) { + theHeader = LoadMultiHeader(alloc, inStream); + if (theHeader == NULL) { + QT3DS_ASSERT(false); + return 0; + } + QT3DSU8 *headerBaseAddr = reinterpret_cast<QT3DSU8 *>(theHeader); + for (QT3DSU32 idx = 0, end = theHeader->m_Entries.size(); idx < end; ++idx) { + if (inId != 0) { + QT3DS_ASSERT(inId != theHeader->m_Entries.index(headerBaseAddr, idx).m_MeshId); + } + nextId = qMax(nextId, theHeader->m_Entries.index(headerBaseAddr, idx).m_MeshId + 1); + } + newMeshStartPos = + sizeof(MeshMultiHeader) + theHeader->m_Entries.size() * sizeof(MeshMultiEntry); + theWriteHeader = theHeader; + } else + theWriteHeader = &tempHeader; + + inStream.SetPosition(-newMeshStartPos, SeekPosition::End); + QT3DSI64 meshOffset = inStream.GetPosition(); + + Save(inStream); + + if (inId != 0) + nextId = inId; + QT3DSU8 *theWriteBaseAddr = reinterpret_cast<QT3DSU8 *>(theWriteHeader); + // Now write a new header out. + inStream.Write(theWriteHeader->m_Entries.begin(theWriteBaseAddr), + theWriteHeader->m_Entries.size()); + MeshMultiEntry newEntry(static_cast<QT3DSI64>(meshOffset), nextId); + inStream.Write(newEntry); + theWriteHeader->m_Entries.m_Size++; + inStream.Write(*theWriteHeader); + + if (theHeader != NULL) { + alloc.deallocate(theHeader); + } + return static_cast<QT3DSU32>(nextId); +} + +// Load a single mesh directly from a multi file with the provided overridden items +SMultiLoadResult Mesh::LoadMulti(NVAllocatorCallback &alloc, ISeekableIOStream &inStream, + QT3DSU32 inId) +{ + MeshMultiHeader *theHeader(LoadMultiHeader(alloc, inStream)); + if (theHeader == NULL) { + return SMultiLoadResult(); + } + QT3DSU64 fileOffset = (QT3DSU64)-1; + QT3DSU32 theId = inId; + QT3DSU8 *theHeaderBaseAddr = reinterpret_cast<QT3DSU8 *>(theHeader); + bool foundMesh = false; + for (QT3DSU32 idx = 0, end = theHeader->m_Entries.size(); idx < end && !foundMesh; ++idx) { + const MeshMultiEntry &theEntry(theHeader->m_Entries.index(theHeaderBaseAddr, idx)); + if (theEntry.m_MeshId == inId || (inId == 0 && theEntry.m_MeshId > theId)) { + if (theEntry.m_MeshId == inId) + foundMesh = true; + theId = qMax(theId, (QT3DSU32)theEntry.m_MeshId); + fileOffset = theEntry.m_MeshOffset; + } + } + Mesh *retval = NULL; + if (fileOffset == (QT3DSU64)-1) { + goto endFunction; + } + + inStream.SetPosition(static_cast<QT3DSI64>(fileOffset), SeekPosition::Begin); + retval = Load(alloc, inStream); +endFunction: + if (theHeader != NULL) + alloc.deallocate(theHeader); + return SMultiLoadResult(retval, theId); +} + +// Returns true if this is a multimesh (several meshes in one file). +bool Mesh::IsMulti(ISeekableIOStream &inStream) +{ + MeshMultiHeader theHeader; + inStream.SetPosition(-((QT3DSI64)(sizeof(MeshMultiHeader))), SeekPosition::End); + QT3DSU32 numBytes = inStream.Read(theHeader); + if (numBytes != sizeof(MeshMultiHeader)) + return false; + return theHeader.m_Version == MeshMultiHeader::GetMultiStaticFileId(); +} +// Load a multi header from a stream. +MeshMultiHeader *Mesh::LoadMultiHeader(NVAllocatorCallback &alloc, ISeekableIOStream &inStream) +{ + MeshMultiHeader theHeader; + inStream.SetPosition(-((QT3DSI64)sizeof(MeshMultiHeader)), SeekPosition::End); + QT3DSU32 numBytes = inStream.Read(theHeader); + if (numBytes != sizeof(MeshMultiHeader) + || theHeader.m_FileId != MeshMultiHeader::GetMultiStaticFileId() + || theHeader.m_Version > MeshMultiHeader::GetMultiStaticVersion()) { + return NULL; + } + size_t allocSize = + sizeof(MeshMultiHeader) + theHeader.m_Entries.m_Size * sizeof(MeshMultiEntry); + MeshMultiHeader *retval = + (MeshMultiHeader *)alloc.allocate(allocSize, "MeshMultiHeader", __FILE__, __LINE__); + if (retval == NULL) { + QT3DS_ASSERT(false); + return NULL; + } + QT3DSU8 *baseAddr = reinterpret_cast<QT3DSU8 *>(retval); + QT3DSU8 *entryData = baseAddr + sizeof(MeshMultiHeader); + *retval = theHeader; + retval->m_Entries.m_Offset = (QT3DSU32)(entryData - baseAddr); + inStream.SetPosition(-((QT3DSI64)allocSize), SeekPosition::End); + + numBytes = + inStream.Read(reinterpret_cast<MeshMultiEntry *>(entryData), retval->m_Entries.m_Size); + if (numBytes != retval->m_Entries.m_Size * sizeof(MeshMultiEntry)) { + QT3DS_ASSERT(false); + alloc.deallocate(retval); + retval = NULL; + } + return retval; +} + +QT3DSU32 GetHighestId(NVAllocatorCallback &inAlloc, MeshMultiHeader *inHeader) +{ + if (inHeader == NULL) { + QT3DS_ASSERT(false); + return 0; + } + QT3DSU8 *baseHeaderAddr = reinterpret_cast<QT3DSU8 *>(inHeader); + QT3DSU32 highestId = 0; + for (QT3DSU32 idx = 0, end = inHeader->m_Entries.size(); idx < end; ++idx) + highestId = qMax(highestId, inHeader->m_Entries.index(baseHeaderAddr, idx).m_MeshId); + inAlloc.deallocate(inHeader); + return highestId; +} + +QT3DSU32 Mesh::GetHighestMultiVersion(NVAllocatorCallback &alloc, ISeekableIOStream &inStream) +{ + return GetHighestId(alloc, LoadMultiHeader(alloc, inStream)); +} diff --git a/src/importlib/Qt3DSImportMesh.h b/src/importlib/Qt3DSImportMesh.h new file mode 100644 index 0000000..36b7a84 --- /dev/null +++ b/src/importlib/Qt3DSImportMesh.h @@ -0,0 +1,530 @@ +/**************************************************************************** +** +** Copyright (C) 1993-2009 NVIDIA Corporation. +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_IMPORT_MESH_H +#define QT3DS_IMPORT_MESH_H +#include "foundation/Qt3DS.h" +#include "foundation/Qt3DSDataRef.h" +#include "render/Qt3DSRenderBaseTypes.h" +#include "foundation/Qt3DSBounds3.h" +#include "foundation/Qt3DSAllocatorCallback.h" +#include "foundation/IOStreams.h" + +namespace qt3dsimp { +using namespace qt3ds; +using namespace qt3ds::foundation; +using namespace qt3ds::render; +template <typename TDataType> +struct SOffsetDataRef +{ + QT3DSU32 m_Offset; + QT3DSU32 m_Size; + SOffsetDataRef() + : m_Offset(0) + , m_Size(0) + { + } + TDataType *begin(QT3DSU8 *inBase) { return reinterpret_cast<TDataType *>(inBase + m_Offset); } + TDataType *end(QT3DSU8 *inBase) { return begin(inBase) + m_Size; } + const TDataType *begin(const QT3DSU8 *inBase) const + { + return reinterpret_cast<const TDataType *>(inBase + m_Offset); + } + const TDataType *end(const QT3DSU8 *inBase) const { return begin(inBase) + m_Size; } + QT3DSU32 size() const { return m_Size; } + bool empty() const { return m_Size == 0; } + TDataType &index(QT3DSU8 *inBase, QT3DSU32 idx) + { + QT3DS_ASSERT(idx < m_Size); + return begin(inBase)[idx]; + } + const TDataType &index(const QT3DSU8 *inBase, QT3DSU32 idx) const + { + QT3DS_ASSERT(idx < m_Size); + return begin(inBase)[idx]; + } +}; + +struct MeshVertexBufferEntry +{ + QT3DSU32 m_NameOffset; + /** Datatype of the this entry points to in the buffer */ + NVRenderComponentTypes::Enum m_ComponentType; + /** Number of components of each data member. 1,2,3, or 4. Don't be stupid.*/ + QT3DSU32 m_NumComponents; + /** Offset from the beginning of the buffer of the first item */ + QT3DSU32 m_FirstItemOffset; + MeshVertexBufferEntry() + : m_NameOffset(0) + , m_ComponentType(NVRenderComponentTypes::QT3DSF32) + , m_NumComponents(3) + , m_FirstItemOffset(0) + { + } + NVRenderVertexBufferEntry ToVertexBufferEntry(QT3DSU8 *inBaseAddress) + { + const char *nameBuffer = ""; + if (m_NameOffset) + nameBuffer = reinterpret_cast<const char *>(inBaseAddress + m_NameOffset); + return NVRenderVertexBufferEntry(nameBuffer, m_ComponentType, m_NumComponents, + m_FirstItemOffset); + } +}; + +struct VertexBuffer +{ + SOffsetDataRef<MeshVertexBufferEntry> m_Entries; + QT3DSU32 m_Stride; + SOffsetDataRef<QT3DSU8> m_Data; + VertexBuffer(SOffsetDataRef<MeshVertexBufferEntry> entries, QT3DSU32 stride, + SOffsetDataRef<QT3DSU8> data) + : m_Entries(entries) + , m_Stride(stride) + , m_Data(data) + { + } + VertexBuffer() + : m_Stride(0) + { + } +}; + +struct IndexBuffer +{ + // Component types must be either QT3DSU16 or QT3DSU8 in order for the + // graphics hardware to deal with the buffer correctly. + NVRenderComponentTypes::Enum m_ComponentType; + SOffsetDataRef<QT3DSU8> m_Data; + // Either QT3DSU8 or QT3DSU16 component types are allowed by the underlying rendering + // system, so you would be wise to stick with those. + IndexBuffer(NVRenderComponentTypes::Enum compType, SOffsetDataRef<QT3DSU8> data) + : m_ComponentType(compType) + , m_Data(data) + { + } + IndexBuffer() + : m_ComponentType(NVRenderComponentTypes::Unknown) + { + } +}; + +template <QT3DSU32 TNumBytes> +struct MeshPadding +{ + QT3DSU8 m_Padding[TNumBytes]; + MeshPadding() { memZero(m_Padding, TNumBytes); } +}; + +struct MeshSubset +{ + // QT3DS_MAX_U32 means use all available items + QT3DSU32 m_Count; + // Offset is in item size, not bytes. + QT3DSU32 m_Offset; + // Bounds of this subset. This is filled in by the builder + // see AddMeshSubset + NVBounds3 m_Bounds; + + // Subsets have to be named else artists will be unable to use + // a mesh with multiple subsets as they won't have any idea + // while part of the model a given mesh actually maps to. + SOffsetDataRef<char16_t> m_Name; + + MeshSubset(QT3DSU32 count, QT3DSU32 off, const NVBounds3 &bounds, SOffsetDataRef<char16_t> inName) + : m_Count(count) + , m_Offset(off) + , m_Bounds(bounds) + , m_Name(inName) + { + } + MeshSubset() + : m_Count((QT3DSU32)-1) + , m_Offset(0) + , m_Bounds(NVBounds3::empty()) + { + } + bool HasCount() const { return m_Count != QT3DS_MAX_U32; } +}; + +// Placeholder for bitflags in the mesh header. +struct MeshBufHeaderFlagValues +{ + enum Enum {}; +}; + +struct MeshBufHeaderFlags : NVFlags<MeshBufHeaderFlagValues::Enum, QT3DSU16> +{ + MeshBufHeaderFlags() {} + MeshBufHeaderFlags(QT3DSU16 value) + : NVFlags<MeshBufHeaderFlagValues::Enum, QT3DSU16>(value) + { + } + // Binary or creates an integer. + MeshBufHeaderFlags(int value) + : NVFlags<MeshBufHeaderFlagValues::Enum, QT3DSU16>((QT3DSU16)value) + { + } +}; + +struct MeshDataHeader +{ + static QT3DSU32 GetFileId() { return (QT3DSU32)-929005747; } + static QT3DSU16 GetCurrentFileVersion() { return 3; } + QT3DSU32 m_FileId; + QT3DSU16 m_FileVersion; + MeshBufHeaderFlags m_HeaderFlags; + QT3DSU32 m_SizeInBytes; + MeshDataHeader(QT3DSU32 size = 0) + : m_FileId(GetFileId()) + , m_FileVersion(GetCurrentFileVersion()) + , m_SizeInBytes(size) + { + } +}; + +struct Joint +{ + QT3DSI32 m_JointID; + QT3DSI32 m_ParentID; + QT3DSF32 m_invBindPose[16]; + QT3DSF32 m_localToGlobalBoneSpace[16]; + + Joint(QT3DSI32 jointID, QT3DSI32 parentID, const QT3DSF32 *invBindPose, + const QT3DSF32 *localToGlobalBoneSpace) + : m_JointID(jointID) + , m_ParentID(parentID) + { + ::memcpy(m_invBindPose, invBindPose, sizeof(float) * 16); + ::memcpy(m_localToGlobalBoneSpace, localToGlobalBoneSpace, sizeof(float) * 16); + } + Joint() + : m_JointID(-1) + , m_ParentID(-1) + { + ::memset(m_invBindPose, 0, sizeof(float) * 16); + ::memset(m_localToGlobalBoneSpace, 0, sizeof(float) * 16); + } +}; + +// Tells us what offset a mesh with this ID starts. +struct MeshMultiEntry +{ + QT3DSU64 m_MeshOffset; + QT3DSU32 m_MeshId; + QT3DSU32 m_Padding; + MeshMultiEntry() + : m_MeshOffset(0) + , m_MeshId(0) + , m_Padding(0) + { + } + MeshMultiEntry(QT3DSU64 mo, QT3DSU32 meshId) + : m_MeshOffset(mo) + , m_MeshId(meshId) + , m_Padding(0) + { + } +}; + +// The multi headers are actually saved at the end of the file. +// Thus when you append to the file we overwrite the last header +// then write out a new header structure. +// The last 8 bytes of the file contain the multi header. +// The previous N*8 bytes contain the mesh entries. +struct MeshMultiHeader +{ + QT3DSU32 m_FileId; + QT3DSU32 m_Version; + SOffsetDataRef<MeshMultiEntry> m_Entries; + static QT3DSU32 GetMultiStaticFileId() { return (QT3DSU32)555777497; } + static QT3DSU32 GetMultiStaticVersion() { return 1; } + + MeshMultiHeader() + : m_FileId(GetMultiStaticFileId()) + , m_Version(GetMultiStaticVersion()) + { + } +}; + +struct Mesh; + +// Result of a multi-load operation. This returns both the mesh +// and the id of the mesh that was loaded. +struct SMultiLoadResult +{ + Mesh *m_Mesh; + QT3DSU32 m_Id; + SMultiLoadResult(Mesh *inMesh, QT3DSU32 inId) + : m_Mesh(inMesh) + , m_Id(inId) + { + } + SMultiLoadResult() + : m_Mesh(NULL) + , m_Id(0) + { + } + operator Mesh *() { return m_Mesh; } +}; + +/** +* A Mesh defines one vertex buffer layout, one or more logical vertex buffers +* one index buffer, and a set of defined draw calls (subsets). +* +* The vertex buffer data is held together continguously and the layout cannot +* change. There can be several actual vertex buffers on the card, however, in order +* to facilitate using index buffer components of smaller component sizes than there +* are actual vertex buffer data entries. For instance, you may have a very large vertex +* buffer, larger than 64 K but openglES has a restriction on the size of the index buffer +*component +* that it cannot be larger than two bytes per entry. So you would need to split the vertex +*buffer +* into multiple logical vertex buffers and adjust your indexes such that you indexed into +* only one logical vertex buffer per draw call. +* +* No logical vertex buffers means that the vertex buffer index on a mesh subset will be +*ignored. +*/ +struct Mesh +{ + static const wchar_t *s_DefaultName; + + VertexBuffer m_VertexBuffer; + IndexBuffer m_IndexBuffer; + SOffsetDataRef<MeshSubset> m_Subsets; + SOffsetDataRef<Joint> m_Joints; + NVRenderDrawMode::Enum m_DrawMode; + NVRenderWinding::Enum m_Winding; + + Mesh() + : m_DrawMode(NVRenderDrawMode::Triangles) + , m_Winding(NVRenderWinding::CounterClockwise) + { + } + Mesh(VertexBuffer vbuf, IndexBuffer ibuf, const SOffsetDataRef<MeshSubset> &insts, + const SOffsetDataRef<Joint> &joints, + NVRenderDrawMode::Enum drawMode = NVRenderDrawMode::Triangles, + NVRenderWinding::Enum winding = NVRenderWinding::CounterClockwise) + : m_VertexBuffer(vbuf) + , m_IndexBuffer(ibuf) + , m_Subsets(insts) + , m_Joints(joints) + , m_DrawMode(drawMode) + , m_Winding(winding) + { + } + + QT3DSU8 *GetBaseAddress() { return reinterpret_cast<QT3DSU8 *>(this); } + const QT3DSU8 *GetBaseAddress() const { return reinterpret_cast<const QT3DSU8 *>(this); } + + static const char *GetPositionAttrName() { return "attr_pos"; } + static const char *GetNormalAttrName() { return "attr_norm"; } + static const char *GetUVAttrName() { return "attr_uv0"; } + static const char *GetUV2AttrName() { return "attr_uv1"; } + static const char *GetTexTanAttrName() { return "attr_textan"; } + static const char *GetTexBinormalAttrName() { return "attr_binormal"; } + static const char *GetWeightAttrName() { return "attr_weight"; } + static const char *GetBoneIndexAttrName() { return "attr_boneid"; } + static const char *GetColorAttrName() { return "attr_color"; } + + // Run through the vertex buffer items indicated by subset + // Assume vbuf entry[posEntryIndex] is the position entry + // This entry has to be QT3DSF32 and 3 components. + // Using this entry and the (possibly empty) index buffer + // along with the (possibly emtpy) logical vbuf data + // return a bounds of the given vertex buffer. + static NVBounds3 CalculateSubsetBounds(const NVRenderVertexBufferEntry &inEntry, + NVConstDataRef<QT3DSU8> inVertxData, QT3DSU32 inStride, + NVConstDataRef<QT3DSU8> inIndexData, + qt3ds::render::NVRenderComponentTypes::Enum inIndexCompType, + QT3DSU32 inSubsetCount, QT3DSU32 inSubsetOffset); + + // Format is: + // MeshDataHeader + // mesh data. + void Save(IOutStream &outStream) const; + + // Save a mesh using fopen and fwrite + bool Save(const char *inFilePath) const; + + // read the header, then read the object. + // Object data is written in LE format for now. + // Free the new mesh by calling: + // alloc.deallocate( mesh ); + // All the memory is allocated once and then pointers are back + // filled, so this will work. + static Mesh *Load(NVAllocatorCallback &alloc, IInStream &inStream); + + // Load a mesh using fopen and fread + // Mesh needs to be freed by the caller using free + static Mesh *Load(const char *inFilePath); + + // Create a mesh given this header, and that data. data.size() must match + // header.SizeInBytes. The mesh returned starts a data[0], so however data + // was allocated is how the mesh should be deallocated. + static Mesh *Initialize(QT3DSU16 meshVersion, MeshBufHeaderFlags meshFlags, NVDataRef<QT3DSU8> data); + + // Multimesh support where you have multiple meshes in a single file. + // Save multi where you have overridden the allocator. + QT3DSU32 SaveMulti(NVAllocatorCallback &alloc, ISeekableIOStream &inStream, QT3DSU32 inId = 0) const; + // You can save multiple meshes in a file. Each mesh returns an incrementing + // integer for the multi file. The original meshes aren't changed, and the file + // is appended to. + QT3DSU32 SaveMulti(const char *inFilePath) const; + + // Load a single mesh directly from a multi file with the provided overridden items + // Loading a multimesh with id == 0 indicates to just load the mesh with the highest id. + static SMultiLoadResult LoadMulti(NVAllocatorCallback &alloc, ISeekableIOStream &inStream, + QT3DSU32 inId = 0); + // Load a single mesh using c file API and malloc/free. + static SMultiLoadResult LoadMulti(const char *inFilePath, QT3DSU32 inId); + // Returns true if this is a multimesh (several meshes in one file). + static bool IsMulti(ISeekableIOStream &inStream); + // Load a multi header from a stream. + static MeshMultiHeader *LoadMultiHeader(NVAllocatorCallback &alloc, + ISeekableIOStream &inStream); + // Load a multi header from a file using malloc. Header needs to be freed using free. + static MeshMultiHeader *LoadMultiHeader(const char *inFilePath); + + // Get the highest mesh version from a stream. + static QT3DSU32 GetHighestMultiVersion(NVAllocatorCallback &alloc, ISeekableIOStream &inStream); + // Get the highest mesh version from a file. + static QT3DSU32 GetHighestMultiVersion(const char *inFilePath); +}; + +struct ScopedMesh +{ + Mesh *m_Mesh; + NVAllocatorCallback *m_Callback; + ScopedMesh(Mesh *m, NVAllocatorCallback *cback = NULL) + : m_Mesh(m) + , m_Callback(cback) + { + } + ~ScopedMesh() + { + if (m_Mesh) { + if (m_Callback) + m_Callback->deallocate(m_Mesh); + else + free(m_Mesh); + } + } + Mesh *operator->() { return m_Mesh; } + operator Mesh *() { return m_Mesh; } +}; + +struct MeshBuilderVBufEntry +{ + const char *m_Name; + NVConstDataRef<QT3DSU8> m_Data; + NVRenderComponentTypes::Enum m_ComponentType; + QT3DSU32 m_NumComponents; + MeshBuilderVBufEntry() + : m_Name(NULL) + , m_ComponentType(NVRenderComponentTypes::Unknown) + , m_NumComponents(0) + { + } + MeshBuilderVBufEntry(const char *name, NVConstDataRef<QT3DSU8> data, + NVRenderComponentTypes::Enum componentType, QT3DSU32 numComponents) + : m_Name(name) + , m_Data(data) + , m_ComponentType(componentType) + , m_NumComponents(numComponents) + { + } +}; + +// Useful class to build up a mesh. Necessary since meshes don't include that +// sort of utility. +class MeshBuilder +{ +protected: + virtual ~MeshBuilder() {} +public: + virtual void Release() = 0; + virtual void Reset() = 0; + // Set the draw parameters for any subsets. Defaults to triangles and counter clockwise + virtual void SetDrawParameters(NVRenderDrawMode::Enum drawMode, + NVRenderWinding::Enum winding) = 0; + // Set the vertex buffer and have the mesh builder interleave the data for you + virtual bool SetVertexBuffer(NVConstDataRef<MeshBuilderVBufEntry> entries) = 0; + // Set the vertex buffer from interleaved data. + virtual void SetVertexBuffer(NVConstDataRef<NVRenderVertexBufferEntry> entries, QT3DSU32 stride, + NVConstDataRef<QT3DSU8> data) = 0; + // The builder (and the majority of the rest of the product) only supports unsigned 16 bit + // indexes + virtual void SetIndexBuffer(NVConstDataRef<QT3DSU8> data, NVRenderComponentTypes::Enum comp) = 0; + // Assets if the supplied parameters are out of range. + virtual void AddJoint(QT3DSI32 jointID, QT3DSI32 parentID, const QT3DSF32 *invBindPose, + const QT3DSF32 *localToGlobalBoneSpace) = 0; + /** + * Add a subset, which equates roughly to a draw call. + * A logical vertex buffer allows you to have more that 64K vertexes but still + * use u16 index buffers. In any case, if the mesh has an index buffer then this subset + * refers to that index buffer, else it is assumed to index into the vertex buffer. + * count and offset do exactly what they seem to do, while boundsPositionEntryIndex, if set to + * something other than QT3DS_MAX_U32, drives the calculation of the aa-bounds of the subset + * using mesh::CalculateSubsetBounds + */ + virtual void AddMeshSubset(const wchar_t *inSubsetName = Mesh::s_DefaultName, + QT3DSU32 count = QT3DS_MAX_U32, QT3DSU32 offset = 0, + QT3DSU32 boundsPositionEntryIndex = QT3DS_MAX_U32) = 0; + + virtual void AddMeshSubset(const wchar_t *inSubsetName, QT3DSU32 count, QT3DSU32 offset, + const NVBounds3 &inBounds) = 0; + + // Call to optimize the index and vertex buffers. This doesn't change the subset information, + // each triangle is rendered precisely the same. + // It just orders the vertex data so we iterate through it as linearly as possible. + // This *only* works if the *entire* builder is using triangles as the draw mode. This will be + // a disaster if that + // condition is not met. + virtual void OptimizeMesh() = 0; + + /** + * @brief This functions stitches together sub-meshes with the same material. + * This re-writes the index buffer + * + * @return no return. + */ + virtual void ConnectSubMeshes() = 0; + + // Return the current mesh. This is only good for this function call, item may change or be + // released + // due to any further function calls. + virtual Mesh &GetMesh() = 0; + + // Uses new/delete. + static MeshBuilder &CreateMeshBuilder(); +}; +} + +#endif diff --git a/src/importlib/Qt3DSImportMeshBuilder.cpp b/src/importlib/Qt3DSImportMeshBuilder.cpp new file mode 100644 index 0000000..e8c6d92 --- /dev/null +++ b/src/importlib/Qt3DSImportMeshBuilder.cpp @@ -0,0 +1,598 @@ +/**************************************************************************** +** +** Copyright (C) 1993-2009 NVIDIA Corporation. +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "Qt3DSImportLibPrecompile.h" +#include "Qt3DSImportMesh.h" +#include "foundation/Qt3DSMemoryBuffer.h" +#include "Qt3DSImportContainers.h" + +using namespace qt3dsimp; + +// Disable mesh optimization. TODO: After removing NvTriStrip need +// to implement mesh optimization. +//#define DISABLE_MESH_OPTIMIZATION 1 + +// It turns out we can't enable vertex remapping because it would break +// mesh morphing. +#define DISABLE_VERTEX_REMAP 1 + +namespace { +template <typename TDataType> +NVConstDataRef<TDataType> toRefBuf(QT3DSU8 *bufData, QT3DSU32 off, QT3DSU32 size) +{ + QT3DS_ASSERT(size % sizeof(TDataType) == 0); + if (size) + return NVConstDataRef<TDataType>((TDataType *)(bufData + off), size / sizeof(TDataType)); + return NVConstDataRef<TDataType>(); +} +struct DynamicVBuf +{ + QT3DSU32 m_Stride; + ImportArray<NVRenderVertexBufferEntry> m_VertexBufferEntries; + MemoryBuffer<RawAllocator> m_VertexData; + + void clear() + { + m_Stride = 0; + m_VertexBufferEntries.clear(); + m_VertexData.clear(); + } +}; +struct DynamicIndexBuf +{ + NVRenderComponentTypes::Enum m_CompType; + MemoryBuffer<RawAllocator> m_IndexData; + DynamicIndexBuf() {} + + void clear() { m_IndexData.clear(); } +}; + +struct SubsetDesc +{ + QT3DSU32 m_Count; + QT3DSU32 m_Offset; + + NVBounds3 m_Bounds; + QString m_Name; + SubsetDesc(QT3DSU32 c, QT3DSU32 off) + : m_Count(c) + , m_Offset(off) + { + } + SubsetDesc() + : m_Count(0) + , m_Offset(0) + { + } +}; + +QT3DSU32 GetAlignedOffset(QT3DSU32 offset, QT3DSU32 align) +{ + QT3DSU32 leftover = offset % align; + if (leftover) + return offset + (align - leftover); + return offset; +} + +class MeshBuilderImpl : public MeshBuilder +{ + DynamicVBuf m_VertexBuffer; + DynamicIndexBuf m_IndexBuffer; + ImportArray<Joint> m_Joints; + ImportArray<SubsetDesc> m_MeshSubsetDescs; + NVRenderDrawMode::Enum m_DrawMode; + NVRenderWinding::Enum m_Winding; + MemoryBuffer<RawAllocator> m_RemappedVertexData; + MemoryBuffer<RawAllocator> m_NewIndexBuffer; + ImportArray<QT3DSU8> m_MeshBuffer; + +public: + MeshBuilderImpl() { Reset(); } + virtual ~MeshBuilderImpl() { Reset(); } + void Release() override { delete this; } + void Reset() override + { + m_VertexBuffer.clear(); + m_IndexBuffer.clear(); + m_Joints.clear(); + m_MeshSubsetDescs.clear(); + m_DrawMode = NVRenderDrawMode::Triangles; + m_Winding = NVRenderWinding::CounterClockwise; + m_MeshBuffer.clear(); + } + + void SetDrawParameters(NVRenderDrawMode::Enum drawMode, NVRenderWinding::Enum winding) override + { + m_DrawMode = drawMode; + m_Winding = winding; + } + + // Somewhat burly method to interleave the data as tightly as possible + // while taking alignment into account. + bool SetVertexBuffer(NVConstDataRef<MeshBuilderVBufEntry> entries) override + { + QT3DSU32 currentOffset = 0; + QT3DSU32 bufferAlignment = 0; + QT3DSU32 numItems = 0; + bool retval = true; + QT3DSIMP_FOREACH(idx, entries.size()) + { + const MeshBuilderVBufEntry &entry(entries[idx]); + // Ignore entries with no data. + if (entry.m_Data.begin() == NULL || entry.m_Data.size() == 0) + continue; + + QT3DSU32 alignment = NVRenderComponentTypes::getSizeofType(entry.m_ComponentType); + bufferAlignment = qMax(bufferAlignment, alignment); + QT3DSU32 byteSize = alignment * entry.m_NumComponents; + + if (entry.m_Data.size() % alignment != 0) { + QT3DS_ASSERT(false); + retval = false; + } + + QT3DSU32 localNumItems = entry.m_Data.size() / byteSize; + if (numItems == 0) { + numItems = localNumItems; + } else if (numItems != localNumItems) { + QT3DS_ASSERT(false); + retval = false; + numItems = qMin(numItems, localNumItems); + } + // Lots of platforms can't handle non-aligned data. + // so ensure we are aligned. + currentOffset = GetAlignedOffset(currentOffset, alignment); + NVRenderVertexBufferEntry vbufEntry(entry.m_Name, entry.m_ComponentType, + entry.m_NumComponents, currentOffset); + m_VertexBuffer.m_VertexBufferEntries.push_back(vbufEntry); + currentOffset += byteSize; + } + m_VertexBuffer.m_Stride = GetAlignedOffset(currentOffset, bufferAlignment); + + // Packed interleave the data + QT3DSIMP_FOREACH(idx, numItems) + { + QT3DSU32 dataOffset = 0; + QT3DSIMP_FOREACH(entryIdx, entries.size()) + { + const MeshBuilderVBufEntry &entry(entries[entryIdx]); + // Ignore entries with no data. + if (entry.m_Data.begin() == NULL || entry.m_Data.size() == 0) + continue; + + QT3DSU32 alignment = NVRenderComponentTypes::getSizeofType(entry.m_ComponentType); + QT3DSU32 byteSize = alignment * entry.m_NumComponents; + QT3DSU32 offset = byteSize * idx; + QT3DSU32 newOffset = GetAlignedOffset(dataOffset, alignment); + if (newOffset != dataOffset) + m_VertexBuffer.m_VertexData.writeZeros(newOffset - dataOffset); + m_VertexBuffer.m_VertexData.write(entry.m_Data.begin() + offset, byteSize); + dataOffset = newOffset + byteSize; + } + QT3DS_ASSERT(dataOffset == m_VertexBuffer.m_Stride); + } + return retval; + } + + void SetVertexBuffer(NVConstDataRef<NVRenderVertexBufferEntry> entries, QT3DSU32 stride, + NVConstDataRef<QT3DSU8> data) override + { + QT3DSIMP_FOREACH(idx, (QT3DSU32)entries.size()) + { + m_VertexBuffer.m_VertexBufferEntries.push_back(entries[idx]); + } + m_VertexBuffer.m_VertexData.write(data.begin(), data.size()); + if (stride == 0) { + // Calculate the stride of the buffer using the vbuf entries + QT3DSIMP_FOREACH(idx, entries.size()) + { + const NVRenderVertexBufferEntry &entry(entries[idx]); + stride = qMax(stride, entry.m_FirstItemOffset + + (entry.m_NumComponents * NVRenderComponentTypes::getSizeofType( + entry.m_ComponentType))); + } + } + m_VertexBuffer.m_Stride = stride; + } + + void SetIndexBuffer(NVConstDataRef<QT3DSU8> data, NVRenderComponentTypes::Enum comp) override + { + m_IndexBuffer.m_CompType = comp; + m_IndexBuffer.m_IndexData.write(data.begin(), data.size()); + } + + void AddJoint(QT3DSI32 jointID, QT3DSI32 parentID, const QT3DSF32 *invBindPose, + const QT3DSF32 *localToGlobalBoneSpace) override + { + m_Joints.push_back(Joint(jointID, parentID, invBindPose, localToGlobalBoneSpace)); + } + + SubsetDesc CreateSubset(const wchar_t *inName, QT3DSU32 count, QT3DSU32 offset) + { + if (inName == NULL) + inName = L""; + SubsetDesc retval(count, offset); + retval.m_Name = QString::fromWCharArray(inName); + return retval; + } + + // indexBuffer QT3DS_MAX_U32 means no index buffer. + // count of QT3DS_MAX_U32 means use all available items + // offset means exactly what you would think. Offset is in item size, not bytes. + void AddMeshSubset(const wchar_t *inName, QT3DSU32 count, QT3DSU32 offset, + QT3DSU32 boundsPositionEntryIndex) override + { + SubsetDesc retval = CreateSubset(inName, count, offset); + if (boundsPositionEntryIndex != QT3DS_MAX_U32) { + retval.m_Bounds = Mesh::CalculateSubsetBounds( + m_VertexBuffer.m_VertexBufferEntries[boundsPositionEntryIndex], + m_VertexBuffer.m_VertexData, m_VertexBuffer.m_Stride, m_IndexBuffer.m_IndexData, + m_IndexBuffer.m_CompType, count, offset); + } + m_MeshSubsetDescs.push_back(retval); + } + + void AddMeshSubset(const wchar_t *inName, QT3DSU32 count, QT3DSU32 offset, + const NVBounds3 &inBounds) override + { + SubsetDesc retval = CreateSubset(inName, count, offset); + retval.m_Bounds = inBounds; + m_MeshSubsetDescs.push_back(retval); + } +#ifndef DISABLE_MESH_OPTIMIZATION + void DeletePrimitiveGroup(PrimitiveGroup *&inGroup) + { + if (inGroup) + delete[] inGroup; + inGroup = NULL; + } +#endif + // We connect sub meshes which habe the same material + void ConnectSubMeshes() override + { + if (m_MeshSubsetDescs.size() < 2) { + // nothing to do + return; + } + + QT3DSU32 matDuplicates = 0; + + // as a pre-step we check if we have duplicate material at all + for (QT3DSU32 i = 0, subsetEnd = m_MeshSubsetDescs.size(); i < subsetEnd && !matDuplicates; + ++i) { + SubsetDesc ¤tSubset = m_MeshSubsetDescs[i]; + + for (QT3DSU32 j = 0, subsetEnd = m_MeshSubsetDescs.size(); j < subsetEnd; ++j) { + SubsetDesc &theSubset = m_MeshSubsetDescs[j]; + + if (i == j) + continue; + + if (currentSubset.m_Name == theSubset.m_Name) { + matDuplicates++; + break; // found a duplicate bail out + } + } + } + + // did we find some duplicates? + if (matDuplicates) { + ImportArray<SubsetDesc> newMeshSubsetDescs; + ImportArray<SubsetDesc>::iterator theIter; + QString curMatName; + m_NewIndexBuffer.clear(); + + for (theIter = m_MeshSubsetDescs.begin(); theIter != m_MeshSubsetDescs.end(); + ++theIter) { + bool bProcessed = false; + + for (ImportArray<SubsetDesc>::iterator iter = newMeshSubsetDescs.begin(); + iter != newMeshSubsetDescs.end(); ++iter) { + if (theIter->m_Name == iter->m_Name) { + bProcessed = true; + break; + } + } + + if (bProcessed) + continue; + + curMatName = theIter->m_Name; + + QT3DSU32 theIndexCompSize = + NVRenderComponentTypes::getSizeofType(m_IndexBuffer.m_CompType); + // get pointer to indices + QT3DSU8 *theIndices = + (m_IndexBuffer.m_IndexData.begin()) + (theIter->m_Offset * theIndexCompSize); + // write new offset + theIter->m_Offset = m_NewIndexBuffer.size() / theIndexCompSize; + // store indices + m_NewIndexBuffer.write(theIndices, theIter->m_Count * theIndexCompSize); + + for (QT3DSU32 j = 0, subsetEnd = m_MeshSubsetDescs.size(); j < subsetEnd; ++j) { + if (theIter == &m_MeshSubsetDescs[j]) + continue; + + SubsetDesc &theSubset = m_MeshSubsetDescs[j]; + + if (curMatName == theSubset.m_Name) { + // get pointer to indices + QT3DSU8 *theIndices = (m_IndexBuffer.m_IndexData.begin()) + + (theSubset.m_Offset * theIndexCompSize); + // store indices + m_NewIndexBuffer.write(theIndices, theSubset.m_Count * theIndexCompSize); + // increment indices count + theIter->m_Count += theSubset.m_Count; + } + } + + newMeshSubsetDescs.push_back(*theIter); + } + + m_MeshSubsetDescs.clear(); + m_MeshSubsetDescs = newMeshSubsetDescs; + m_IndexBuffer.m_IndexData.clear(); + m_IndexBuffer.m_IndexData.write(m_NewIndexBuffer.begin(), m_NewIndexBuffer.size()); + + // compute new bounding box + for (theIter = m_MeshSubsetDescs.begin(); theIter != m_MeshSubsetDescs.end(); + ++theIter) { + theIter->m_Bounds = Mesh::CalculateSubsetBounds( + m_VertexBuffer.m_VertexBufferEntries[0], m_VertexBuffer.m_VertexData, + m_VertexBuffer.m_Stride, m_IndexBuffer.m_IndexData, m_IndexBuffer.m_CompType, + theIter->m_Count, theIter->m_Offset); + } + } + } + + // Here is the NVTriStrip magic. + void OptimizeMesh() override + { + if (NVRenderComponentTypes::getSizeofType(m_IndexBuffer.m_CompType) != 2) { + // we currently re-arrange unsigned int indices. + // this is because NvTriStrip only supports short indices + QT3DS_ASSERT(NVRenderComponentTypes::getSizeofType(m_IndexBuffer.m_CompType) == 4); + return; + } +#ifndef DISABLE_MESH_OPTIMIZATION + SetCacheSize(CACHESIZE_GEFORCE3); + SetStitchStrips(true); + SetMinStripSize(0); + // Create the optimized list indices + SetListsOnly(true); + // Optimize the indices using NvTriStrip + + // First, nv-tri-strip all of the indexes. They shouldn't be interleaved, meaning + // there is an assumption here that mesh subset1 doesn't uses indexes from mesh subset 2. + // They may share vertexes, however, which means that we need to be careful when remapping + // them. + // Have to go subset by subset in order for the tri-strip to avoid stepping on subsets. + m_NewIndexBuffer.clear(); + for (QT3DSU32 subsetIdx = 0, subsetEnd = m_MeshSubsetDescs.size(); subsetIdx < subsetEnd; + ++subsetIdx) { + SubsetDesc &theSubset = m_MeshSubsetDescs[subsetIdx]; + QT3DSU16 *theIndices = + reinterpret_cast<QT3DSU16 *>(m_IndexBuffer.m_IndexData.begin()) + theSubset.m_Offset; + QT3DSU32 theIndexCount = theSubset.m_Count; + QT3DSU16 theNumGroups = 0; + PrimitiveGroup *thePrimitiveGroupsList = NULL; + theSubset.m_Offset = m_NewIndexBuffer.size() / sizeof(QT3DSU16); + theSubset.m_Count = 0; + + // We don't support larger vertex buffers. That requires splitting the buffer, + // an operation we haven't implemented (yet). + if (GenerateStrips(theIndices, theIndexCount, &thePrimitiveGroupsList, &theNumGroups)) { + if (theNumGroups) { + QT3DS_ASSERT(theNumGroups == 1); + PrimitiveGroup &srcGroup(thePrimitiveGroupsList[0]); + QT3DS_ASSERT(srcGroup.type == PT_LIST); + m_NewIndexBuffer.write(srcGroup.indices, srcGroup.numIndices); + theSubset.m_Count = srcGroup.numIndices; + } + } + + DeletePrimitiveGroup(thePrimitiveGroupsList); + } + m_IndexBuffer.m_IndexData.clear(); + m_IndexBuffer.m_IndexData.write(m_NewIndexBuffer.begin(), m_NewIndexBuffer.size()); + +#ifndef DISABLE_VERTEX_REMAP + // This operation does not go subset by subset + // by rather remaps the entire vertex buffer in one shot + // once all of the index buffers are setup. + QT3DSU16 *theIndices = reinterpret_cast<QT3DSU16 *>(m_IndexBuffer.m_IndexData.begin()); + QT3DSU32 theIndexCount = m_IndexBuffer.m_IndexData.size() / sizeof(QT3DSU16); + + // Setup input to the remap system + QT3DSU16 theNumGroups = 1; + PrimitiveGroup thePrimitiveGroup; + thePrimitiveGroup.type = PT_LIST; + thePrimitiveGroup.numIndices = theIndexCount; + thePrimitiveGroup.indices = new QT3DSU16[theIndexCount]; + memCopy(thePrimitiveGroup.indices, theIndices, theIndexCount * sizeof(QT3DSU16)); + + PrimitiveGroup *theRemappedGroup = NULL; + QT3DSU32 vertBufByteSize = m_VertexBuffer.m_VertexData.size(); + QT3DSU32 numVertexIndices = vertBufByteSize / m_VertexBuffer.m_Stride; + QT3DS_ASSERT(numVertexIndices < QT3DS_MAX_U16); + QT3DSU32 vertBufStride = m_VertexBuffer.m_Stride; + // This remaps the vertexes + RemapIndices(&thePrimitiveGroup, theNumGroups, static_cast<QT3DSU16>(numVertexIndices), + &theRemappedGroup); + m_RemappedVertexData.reserve(vertBufByteSize); + const QT3DSU8 *srcVertexPtr(m_VertexBuffer.m_VertexData.begin()); + QT3DSU8 *dstVertexPtr(m_RemappedVertexData.begin()); + QT3DS_ASSERT(theNumGroups == 1); + for (QT3DSU32 theGroupIdx = 0; theGroupIdx < 1; ++theGroupIdx) { + PrimitiveGroup &srcGroup(thePrimitiveGroup); + PrimitiveGroup &dstGroup(theRemappedGroup[theGroupIdx]); + QT3DS_ASSERT(srcGroup.type == PT_LIST); + + for (QT3DSU32 theIndexIdx = 0; theIndexIdx < srcGroup.numIndices; ++theIndexIdx) { + QT3DSU16 srcIndex = srcGroup.indices[theIndexIdx]; + QT3DSU16 dstIndex = dstGroup.indices[theIndexIdx]; + QT3DS_ASSERT(dstIndex * m_VertexBuffer.m_Stride < vertBufByteSize); + QT3DS_ASSERT(srcIndex * m_VertexBuffer.m_Stride < vertBufByteSize); + // Maybe add in a check to see if we possibly already copied this information + // That would of course be only an optimization. + memCopy(dstVertexPtr + dstIndex * vertBufStride, + srcVertexPtr + srcIndex * vertBufStride, vertBufStride); + theIndices[theIndexIdx] = dstIndex; + } + memCopy(m_VertexBuffer.m_VertexData.begin(), m_RemappedVertexData.begin(), + vertBufByteSize); + } + + DeletePrimitiveGroup(theRemappedGroup); +#endif // DISABLE_VERTEX_REMAP +#endif // DISABLE_MESH_OPTIMIZATION + } + + template <typename TDataType> + static void Assign(QT3DSU8 *inBaseAddress, QT3DSU8 *inDataAddress, + SOffsetDataRef<TDataType> &inBuffer, const TDataType *inDestData, + QT3DSU32 inDestSize) + { + inBuffer.m_Offset = (QT3DSU32)(inDataAddress - inBaseAddress); + inBuffer.m_Size = inDestSize; + memCopy(inDataAddress, inDestData, inDestSize * sizeof(TDataType)); + } + template <typename TDataType> + static void Assign(QT3DSU8 *inBaseAddress, QT3DSU8 *inDataAddress, + SOffsetDataRef<TDataType> &inBuffer, QT3DSU32 inDestSize) + { + inBuffer.m_Offset = (QT3DSU32)(inDataAddress - inBaseAddress); + inBuffer.m_Size = inDestSize; + } + // Return the current mesh. This is only good for this function call, item may change or be + // released + // due to any further function calls. + Mesh &GetMesh() override + { + QT3DSU32 meshSize = sizeof(Mesh); + QT3DSU32 alignment = sizeof(void *); + QT3DSU32 vertDataSize = GetAlignedOffset(m_VertexBuffer.m_VertexData.size(), alignment); + meshSize += vertDataSize; + QT3DSU32 entrySize = m_VertexBuffer.m_VertexBufferEntries.size() + * sizeof(qt3ds::render::NVRenderVertexBufferEntry); + meshSize += entrySize; + QT3DSU32 entryNameSize = 0; + for (QT3DSU32 idx = 0, end = m_VertexBuffer.m_VertexBufferEntries.size(); idx < end; ++idx) { + const qt3ds::render::NVRenderVertexBufferEntry &theEntry( + m_VertexBuffer.m_VertexBufferEntries[idx]); + const char *entryName = theEntry.m_Name; + if (entryName == NULL) + entryName = ""; + entryNameSize += (QT3DSU32)(strlen(theEntry.m_Name)) + 1; + } + entryNameSize = GetAlignedOffset(entryNameSize, alignment); + meshSize += entryNameSize; + QT3DSU32 indexBufferSize = GetAlignedOffset(m_IndexBuffer.m_IndexData.size(), alignment); + meshSize += indexBufferSize; + QT3DSU32 subsetSize = m_MeshSubsetDescs.size() * sizeof(MeshSubset); + QT3DSU32 nameSize = 0; + for (QT3DSU32 idx = 0, end = m_MeshSubsetDescs.size(); idx < end; ++idx) { + if (!m_MeshSubsetDescs[idx].m_Name.isEmpty()) + nameSize += m_MeshSubsetDescs[idx].m_Name.size() + 1; + } + nameSize *= sizeof(char16_t); + nameSize = GetAlignedOffset(nameSize, alignment); + + meshSize += subsetSize + nameSize; + QT3DSU32 jointsSize = m_Joints.size() * sizeof(qt3dsimp::Joint); + meshSize += jointsSize; + m_MeshBuffer.resize(meshSize); + QT3DSU8 *baseAddress = m_MeshBuffer.data(); + Mesh *retval = reinterpret_cast<Mesh *>(baseAddress); + retval->m_DrawMode = m_DrawMode; + retval->m_Winding = m_Winding; + QT3DSU8 *vertBufferData = baseAddress + sizeof(Mesh); + QT3DSU8 *vertEntryData = vertBufferData + vertDataSize; + QT3DSU8 *vertEntryNameData = vertEntryData + entrySize; + QT3DSU8 *indexBufferData = vertEntryNameData + entryNameSize; + QT3DSU8 *subsetBufferData = indexBufferData + indexBufferSize; + QT3DSU8 *nameBufferData = subsetBufferData + subsetSize; + QT3DSU8 *jointBufferData = nameBufferData + nameSize; + + retval->m_VertexBuffer.m_Stride = m_VertexBuffer.m_Stride; + Assign(baseAddress, vertBufferData, retval->m_VertexBuffer.m_Data, + m_VertexBuffer.m_VertexData.begin(), m_VertexBuffer.m_VertexData.size()); + retval->m_VertexBuffer.m_Entries.m_Size = m_VertexBuffer.m_VertexBufferEntries.size(); + retval->m_VertexBuffer.m_Entries.m_Offset = (QT3DSU32)(vertEntryData - baseAddress); + for (QT3DSU32 idx = 0, end = m_VertexBuffer.m_VertexBufferEntries.size(); idx < end; ++idx) { + const qt3ds::render::NVRenderVertexBufferEntry &theEntry( + m_VertexBuffer.m_VertexBufferEntries[idx]); + MeshVertexBufferEntry &theDestEntry( + retval->m_VertexBuffer.m_Entries.index(baseAddress, idx)); + theDestEntry.m_ComponentType = theEntry.m_ComponentType; + theDestEntry.m_FirstItemOffset = theEntry.m_FirstItemOffset; + theDestEntry.m_NumComponents = theEntry.m_NumComponents; + const char *targetName = theEntry.m_Name; + if (targetName == NULL) + targetName = ""; + + QT3DSU32 entryNameLen = (QT3DSU32)(strlen(targetName)) + 1; + theDestEntry.m_NameOffset = (QT3DSU32)(vertEntryNameData - baseAddress); + memCopy(vertEntryNameData, theEntry.m_Name, entryNameLen); + vertEntryNameData += entryNameLen; + } + + retval->m_IndexBuffer.m_ComponentType = m_IndexBuffer.m_CompType; + Assign(baseAddress, indexBufferData, retval->m_IndexBuffer.m_Data, + m_IndexBuffer.m_IndexData.begin(), m_IndexBuffer.m_IndexData.size()); + Assign(baseAddress, subsetBufferData, retval->m_Subsets, m_MeshSubsetDescs.size()); + for (QT3DSU32 idx = 0, end = m_MeshSubsetDescs.size(); idx < end; ++idx) { + SubsetDesc &theDesc = m_MeshSubsetDescs[idx]; + MeshSubset &theSubset = reinterpret_cast<MeshSubset *>(subsetBufferData)[idx]; + theSubset.m_Bounds = theDesc.m_Bounds; + theSubset.m_Count = theDesc.m_Count; + theSubset.m_Offset = theDesc.m_Offset; + if (!theDesc.m_Name.isEmpty()) { + theSubset.m_Name.m_Size = theDesc.m_Name.size() + 1; + theSubset.m_Name.m_Offset = (QT3DSU32)(nameBufferData - baseAddress); + std::transform(theDesc.m_Name.begin(), theDesc.m_Name.end(), + reinterpret_cast<char16_t *>(nameBufferData), + [](QChar c) { return static_cast<char16_t>(c.unicode()); }); + reinterpret_cast<char16_t *>(nameBufferData)[theDesc.m_Name.size()] = 0; + nameBufferData += (theDesc.m_Name.size() + 1) * sizeof(char16_t); + } else { + theSubset.m_Name.m_Size = 0; + theSubset.m_Name.m_Offset = 0; + } + } + Assign(baseAddress, jointBufferData, retval->m_Joints, m_Joints.data(), m_Joints.size()); + return *retval; + } +}; +} + +// Uses new/delete. +MeshBuilder &MeshBuilder::CreateMeshBuilder() +{ + return *(new MeshBuilderImpl()); +} diff --git a/src/importlib/Qt3DSImportPath.cpp b/src/importlib/Qt3DSImportPath.cpp new file mode 100644 index 0000000..3523ef5 --- /dev/null +++ b/src/importlib/Qt3DSImportPath.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 1993-2009 NVIDIA Corporation. +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "Qt3DSImportLibPrecompile.h" +#include "Qt3DSImportPath.h" +#include "foundation/Qt3DSBroadcastingAllocator.h" +#include "foundation/Qt3DSAtomic.h" + +using namespace qt3dsimp; + +void SPathBuffer::Save(IOutStream &outStream) const +{ + outStream.Write(GetFileTag()); + outStream.Write(GetFileVersion()); + outStream.Write((QT3DSU32)m_Commands.size()); + outStream.Write((QT3DSU32)m_Data.size()); + outStream.Write(toU8ConstDataRef((PathCommand::Enum *)m_Commands.begin(), m_Commands.size())); + outStream.Write(toU8ConstDataRef((QT3DSF32 *)m_Data.begin(), m_Data.size())); +} + +SPathBuffer *SPathBuffer::Load(IInStream &inStream, NVFoundationBase &inFoundation) +{ + QT3DSU64 fileTag; + QT3DSU32 version, numCommands, numData; + inStream.Read(fileTag); + inStream.Read(version); + inStream.Read(numCommands); + inStream.Read(numData); + if (fileTag != GetFileTag()) { + qCCritical(INVALID_OPERATION, "Invalid file, not a path file"); + return NULL; + } + if (version > GetFileVersion()) { + qCCritical(INVALID_OPERATION, "Version number out of range."); + return NULL; + } + QT3DSU32 commandSize = numCommands * sizeof(QT3DSU32); + QT3DSU32 dataSize = numData * sizeof(QT3DSF32); + QT3DSU32 objectSize = sizeof(SPathBuffer); + QT3DSU32 allocSize = objectSize + commandSize + dataSize; + QT3DSU8 *rawData = + (QT3DSU8 *)inFoundation.getAllocator().allocate(allocSize, "SPathBuffer", __FILE__, __LINE__); + SPathBuffer *retval = new (rawData) SPathBuffer(); + QT3DSU8 *commandBuffer = rawData + sizeof(SPathBuffer); + QT3DSU8 *dataBuffer = commandBuffer + commandSize; + inStream.Read(commandBuffer, commandSize); + inStream.Read(dataBuffer, dataSize); + retval->m_Commands = toDataRef((PathCommand::Enum *)commandBuffer, numCommands); + retval->m_Data = toDataRef((QT3DSF32 *)dataBuffer, numData); + return retval; +} + +void SPathBuffer::Free(NVAllocatorCallback &inAllocator) +{ + inAllocator.deallocate(this); +} + +namespace { +struct SBuilder : public IPathBufferBuilder +{ + NVFoundationBase &m_Foundation; + nvvector<PathCommand::Enum> m_Commands; + nvvector<QT3DSF32> m_Data; + QT3DSI32 m_RefCount; + + SBuilder(NVFoundationBase &inFoundation) + : m_Foundation(inFoundation) + , m_Commands(inFoundation.getAllocator(), "m_Commands") + , m_Data(inFoundation.getAllocator(), "m_Data") + , m_RefCount(0) + { + } + + void addRef() override { atomicIncrement(&m_RefCount); } + + void release() override + { + atomicDecrement(&m_RefCount); + if (m_RefCount <= 0) { + NVAllocatorCallback &theAlloc(m_Foundation.getAllocator()); + NVDelete(theAlloc, this); + } + } + + void Clear() override + { + m_Commands.clear(); + m_Data.clear(); + } + + void Push(const QT3DSVec2 inPos) + { + m_Data.push_back(inPos.x); + m_Data.push_back(inPos.y); + } + + void MoveTo(const QT3DSVec2 &inPos) override + { + m_Commands.push_back(PathCommand::MoveTo); + Push(inPos); + } + + void CubicCurveTo(const QT3DSVec2 &inC1, const QT3DSVec2 &inC2, const QT3DSVec2 &inP2) override + { + m_Commands.push_back(PathCommand::CubicCurveTo); + Push(inC1); + Push(inC2); + Push(inP2); + } + + void Close() override { m_Commands.push_back(PathCommand::Close); } + + // Points back to internal data structures, must use or copy. + SPathBuffer GetPathBuffer() override + { + SPathBuffer retval; + retval.m_Data = m_Data; + retval.m_Commands = m_Commands; + return retval; + } +}; +} + +IPathBufferBuilder &IPathBufferBuilder::CreateBuilder(NVFoundationBase &inFoundation) +{ + return *QT3DS_NEW(inFoundation.getAllocator(), SBuilder)(inFoundation); +} diff --git a/src/importlib/Qt3DSImportPath.h b/src/importlib/Qt3DSImportPath.h new file mode 100644 index 0000000..3b52c8c --- /dev/null +++ b/src/importlib/Qt3DSImportPath.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 1993-2009 NVIDIA Corporation. +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt 3D Studio. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#pragma once +#ifndef QT3DS_IMPORT_PATH_H +#define QT3DS_IMPORT_PATH_H +#include "foundation/Qt3DS.h" +#include "foundation/Qt3DSDataRef.h" +#include "render/Qt3DSRenderBaseTypes.h" +#include "foundation/Qt3DSBounds3.h" +#include "foundation/Qt3DSAllocatorCallback.h" +#include "foundation/IOStreams.h" +#include "foundation/Qt3DSFoundation.h" +#include "foundation/Qt3DSRefCounted.h" +#include "foundation/Qt3DSVec2.h" + +namespace qt3dsimp { +using namespace qt3ds; +using namespace qt3ds::foundation; +using namespace qt3ds::render; +struct PathCommand +{ + enum Enum { + Noner = 0, + MoveTo, // 2 floats + CubicCurveTo, // 6 floats, c1, c2, p2. p1 is existing location + Close, // 0 floats + }; +}; + +struct SPathBuffer +{ + // 64 bit random number to uniquely identify this file type. + static QT3DSU64 GetFileTag() { return 0x7b1a41633c43a6afULL; } + static QT3DSU32 GetFileVersion() { return 1; } + NVConstDataRef<PathCommand::Enum> m_Commands; + NVConstDataRef<QT3DSF32> m_Data; + SPathBuffer() {} + void Save(IOutStream &outStream) const; + static SPathBuffer *Load(IInStream &inStream, NVFoundationBase &inFoundation); + + // Object is unused after this call. Anything created with Load must use this function. + void Free(NVAllocatorCallback &inAllocator); + SPathBuffer *Copy(NVAllocatorCallback &inAllocator) const; +}; + +class IPathBufferBuilder : public NVRefCounted +{ +public: + virtual void Clear() = 0; + + virtual void MoveTo(const QT3DSVec2 &inPos) = 0; + virtual void CubicCurveTo(const QT3DSVec2 &inC1, const QT3DSVec2 &inC2, const QT3DSVec2 &inP2) = 0; + virtual void Close() = 0; + // Points back to internal data structures, must use or copy. + virtual SPathBuffer GetPathBuffer() = 0; + + static IPathBufferBuilder &CreateBuilder(NVFoundationBase &inFoundation); +}; +} + +#endif
\ No newline at end of file |