/**************************************************************************** ** ** 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 m_LogicalVertexBuffers; // may be empty SOffsetDataRef m_Subsets; NVRenderDrawMode::Enum m_DrawMode; NVRenderWinding::Enum m_Winding; typedef MeshSubsetV1 TSubsetType; }; template void Serialize(TSerializer &serializer, MeshV1 &mesh) { QT3DSU8 *baseAddr = reinterpret_cast(&mesh); serializer.streamify(mesh.m_VertexBuffer.m_Entries); serializer.align(); QT3DSIMP_FOREACH(entry, mesh.m_VertexBuffer.m_Entries.size()) { MeshVertexBufferEntry &entryData = const_cast( 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 m_Name; }; struct MeshV2 { static const char16_t *s_DefaultName; VertexBuffer m_VertexBuffer; IndexBuffer m_IndexBuffer; SOffsetDataRef m_LogicalVertexBuffers; // may be empty SOffsetDataRef m_Subsets; NVRenderDrawMode::Enum m_DrawMode; NVRenderWinding::Enum m_Winding; typedef MeshSubsetV2 TSubsetType; }; template void Serialize(TSerializer &serializer, MeshV2 &mesh) { QT3DSU8 *baseAddr = reinterpret_cast(&mesh); serializer.streamify(mesh.m_VertexBuffer.m_Entries); serializer.align(); QT3DSIMP_FOREACH(entry, mesh.m_VertexBuffer.m_Entries.size()) { MeshVertexBufferEntry &entryData = const_cast( 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(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 void Serialize(TSerializer &serializer, Mesh &mesh) { QT3DSU8 *baseAddr = reinterpret_cast(&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(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 void streamify(const SOffsetDataRef &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 void streamify(const SOffsetDataRef &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 void streamify(const SOffsetDataRef &_data) { SOffsetDataRef &data = const_cast &>(_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(&mesh)); Serialize(s, mesh); return s.m_NumBytes; } template QT3DSU32 NextIndex(const QT3DSU8 *inBaseAddress, const SOffsetDataRef data, QT3DSU32 idx) { QT3DSU32 numItems = data.size() / sizeof(TDataType); if (idx < numItems) { const TDataType *dataPtr(reinterpret_cast(data.begin(inBaseAddress))); return dataPtr[idx]; } else { QT3DS_ASSERT(false); return 0; } } template QT3DSU32 NextIndex(NVConstDataRef data, QT3DSU32 idx) { QT3DSU32 numItems = data.size() / sizeof(TDataType); if (idx < numItems) { const TDataType *dataPtr(reinterpret_cast(data.begin())); return dataPtr[idx]; } else { QT3DS_ASSERT(false); return 0; } } inline QT3DSU32 NextIndex(NVConstDataRef inData, qt3ds::render::NVRenderComponentTypes::Enum inCompType, QT3DSU32 idx) { if (inData.size() == 0) return idx; switch (inCompType) { case NVRenderComponentTypes::QT3DSU8: return NextIndex(inData, idx); case NVRenderComponentTypes::QT3DSI8: return NextIndex(inData, idx); case NVRenderComponentTypes::QT3DSU16: return NextIndex(inData, idx); case NVRenderComponentTypes::QT3DSI16: return NextIndex(inData, idx); case NVRenderComponentTypes::QT3DSU32: return NextIndex(inData, idx); case NVRenderComponentTypes::QT3DSI32: return NextIndex(inData, idx); default: break; } // Invalid index buffer index type. QT3DS_ASSERT(false); return 0; } template // Not exposed to the outside world TMeshType *DoInitialize(MeshBufHeaderFlags /*meshFlags*/, NVDataRef 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 inVertxData, QT3DSU32 inStride, NVConstDataRef 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(dataPtr)); } else { QT3DS_ASSERT(false); } } return retval; } void Mesh::Save(IOutStream &outStream) const { Mesh &mesh(const_cast(*this)); QT3DSU8 *baseAddress = reinterpret_cast(&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 struct SubsetNameHandler { }; template <> struct SubsetNameHandler { void AssignName(const QT3DSU8 * /*v1BaseAddress*/, const MeshSubsetV1 & /*mesh*/, QT3DSU8 * /*baseAddress*/, QT3DSU8 *& /*nameBuffer*/, MeshSubset &outDest) { outDest.m_Name = SOffsetDataRef(); } QT3DSU32 NameLength(const MeshSubsetV1 &) { return 0; } }; using qt3ds::intrinsics::memCopy; template <> struct SubsetNameHandler { 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 Mesh *CreateMeshFromPreviousMesh(TPreviousMeshType *temp, NVAllocatorCallback &alloc) { QT3DSU32 newMeshSize = sizeof(Mesh); QT3DSU8 *tempBaseAddress = reinterpret_cast(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().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(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(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(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().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(newMem, header.m_SizeInBytes)); if (amountRead != header.m_SizeInBytes) goto failure; if (header.m_FileVersion == 1) { MeshV1 *temp = DoInitialize(header.m_HeaderFlags, NVDataRef(newMem, header.m_SizeInBytes)); if (temp == NULL) goto failure; return CreateMeshFromPreviousMesh(temp, alloc); } else if (header.m_FileVersion == 2) { MeshV2 *temp = DoInitialize(header.m_HeaderFlags, NVDataRef(newMem, header.m_SizeInBytes)); if (temp == NULL) goto failure; return CreateMeshFromPreviousMesh(temp, alloc); } else { Mesh *retval = Initialize(header.m_FileVersion, header.m_HeaderFlags, NVDataRef(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 data) { if (meshVersion != MeshDataHeader::GetCurrentFileVersion()) return NULL; return DoInitialize(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(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(theWriteHeader); // Now write a new header out. inStream.Write(theWriteHeader->m_Entries.begin(theWriteBaseAddr), theWriteHeader->m_Entries.size()); MeshMultiEntry newEntry(static_cast(meshOffset), nextId); inStream.Write(newEntry); theWriteHeader->m_Entries.m_Size++; inStream.Write(*theWriteHeader); if (theHeader != NULL) { alloc.deallocate(theHeader); } return static_cast(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(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(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(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(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(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)); }