From 299d358d0b2e561cf99c1e418b6cb3e2641198ec Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Thu, 19 Feb 2026 10:06:54 +0000 Subject: [PATCH 1/6] Event manager priority chaining listeners can register with a priority to get an event into an ordered stream of listeners --- Engine/source/util/messaging/eventManager.cpp | 91 +++++++++++-------- Engine/source/util/messaging/eventManager.h | 14 +-- 2 files changed, 63 insertions(+), 42 deletions(-) diff --git a/Engine/source/util/messaging/eventManager.cpp b/Engine/source/util/messaging/eventManager.cpp index aee4d3476..340a73fb7 100644 --- a/Engine/source/util/messaging/eventManager.cpp +++ b/Engine/source/util/messaging/eventManager.cpp @@ -79,26 +79,30 @@ bool EventManagerListener::onMessageReceived( StringTableEntry queue, const char if( subscribers == NULL ) return true; - for (U32 i = 0; i < subscribers->size(); i++) + for (U32 i = 0; i < subscribers->size();) { - Subscriber* itter = &((*subscribers)[i]); - if (itter->listener == NULL) + Subscriber& itter = (*subscribers)[i]; + + if (itter.listener == NULL) { - (*subscribers).erase(i); + subscribers->erase(i); + continue; } - else if (!itter->removeFlag) + + if (!itter.removeFlag) { - itter->callDepth++; - Con::executef(itter->listener, itter->callback, data); - itter->callDepth--; - if (itter->removeFlag && itter->callDepth == 0) + itter.callDepth++; + Con::executef(itter.listener, itter.callback, data); + itter.callDepth--; + + if (itter.removeFlag && itter.callback == 0) { - (*subscribers).erase(i); + subscribers->erase(i); + continue; } } - if (subscribers->size() == 0) - return true; + i++; } return true; @@ -270,7 +274,7 @@ bool EventManager::postEvent( const char* event, const char* data ) // CodeReview [tom, 2/20/2007] The "listener" argument was an IMessageListener, // but it was actually used as a SimObject and never a listener. Thus, it is now a SimObject. -bool EventManager::subscribe(SimObject *callbackObj, const char* event, const char* callback /*= NULL */) +bool EventManager::subscribe(SimObject *callbackObj, const char* event, const char* callback /*= NULL */, S32 priority /*=0*/) { // Make sure the event is valid. if( !isRegisteredEvent( event ) ) @@ -300,6 +304,7 @@ bool EventManager::subscribe(SimObject *callbackObj, const char* event, const ch subscriber.listener = callbackObj; subscriber.event = StringTable->insert( event ); subscriber.callback = StringTable->insert( cb ); + subscriber.priority = priority; subscriber.callDepth = 0; subscriber.removeFlag = false; @@ -311,8 +316,22 @@ bool EventManager::subscribe(SimObject *callbackObj, const char* event, const ch // If the event exists, there should always be a valid subscriber list. AssertFatal( subscribers, "Invalid event subscriber list." ); - // Add the subscriber. - subscribers->push_back( subscriber ); + // Add the subscriber at the appropriate index based on priority. + S32 insertIndex = subscribers->size(); + + for (S32 i = 0; i < subscribers->size(); i++) + { + EventManagerListener::Subscriber& cur = (*subscribers)[i]; + + if (subscriber.priority > cur.priority) + { + insertIndex = i; + break; + } + } + + subscribers->insert(insertIndex); + (*subscribers)[insertIndex] = subscriber; return true; } @@ -342,10 +361,10 @@ void EventManager::remove(SimObject *cbObj, const char* event) // Erase the event. if( iter->listener == cbObj ) { - if (iter->callDepth > 0) - iter->removeFlag = true; - else - subscribers->erase_fast( iter ); + if (iter->callDepth > 0) + iter->removeFlag = true; + else + subscribers->erase_fast( iter ); break; } } @@ -356,21 +375,21 @@ void EventManager::removeAll(SimObject *cbObj) // Iterate over all events. for( Vector::const_iterator iter1 = mEvents.begin(); iter1 != mEvents.end(); iter1++ ) { - Vector* subscribers = mListener.mSubscribers.retreive( *iter1 ); - if( !subscribers ) - continue; - for( Vector::iterator iter2 = subscribers->begin(); iter2 != subscribers->end(); iter2++ ) - { - // Erase the event. - if( iter2->listener == cbObj ) - { - if (iter2->callDepth > 0) - iter2->removeFlag = true; - else - subscribers->erase_fast( iter2 ); - break; - } - } + Vector* subscribers = mListener.mSubscribers.retreive( *iter1 ); + if( !subscribers ) + continue; + for( Vector::iterator iter2 = subscribers->begin(); iter2 != subscribers->end(); iter2++ ) + { + // Erase the event. + if( iter2->listener == cbObj ) + { + if (iter2->callDepth > 0) + iter2->removeFlag = true; + else + subscribers->erase_fast( iter2 ); + break; + } + } } } @@ -460,7 +479,7 @@ DefineEngineMethod( EventManager, postEvent, bool, ( const char * evt, const cha return object->postEvent( evt, data ); } -DefineEngineMethod( EventManager, subscribe, bool, ( const char * listenerName, const char * evt, const char * callback ), (""), "( SimObject listener, String event, String callback )\n\n" +DefineEngineMethod( EventManager, subscribe, bool, ( const char * listenerName, const char * evt, const char * callback, S32 priority ), ("", 0), "( SimObject listener, String event, String callback, int priority = 0 )\n\n" "Subscribe a listener to an event.\n" "@param listener The listener to subscribe.\n" "@param event The event to subscribe to.\n" @@ -475,7 +494,7 @@ DefineEngineMethod( EventManager, subscribe, bool, ( const char * listenerName, return false; } - return object->subscribe( cbObj, evt, callback ); + return object->subscribe( cbObj, evt, callback, priority); } DefineEngineMethod( EventManager, remove, void, ( const char * listenerName, const char * evt), , "( SimObject listener, String event )\n\n" diff --git a/Engine/source/util/messaging/eventManager.h b/Engine/source/util/messaging/eventManager.h index af2cfd46a..1e84af84a 100644 --- a/Engine/source/util/messaging/eventManager.h +++ b/Engine/source/util/messaging/eventManager.h @@ -47,11 +47,13 @@ class EventManagerListener : public Dispatcher::IMessageListener /// Stores subscription information for a subscriber. struct Subscriber { - SimObjectPtr< SimObject > listener; ///< The listener object. - StringTableEntry callback; ///< The callback to execute when the event is triggered. - StringTableEntry event; ///< The event being listened for. - U32 callDepth; - bool removeFlag; + SimObjectPtr< SimObject > listener; ///< The listener object. + StringTableEntry callback; ///< The callback to execute when the event is triggered. + StringTableEntry event; ///< The event being listened for. + + S32 priority; + U32 callDepth; + bool removeFlag; }; /// Subscriber table hashed by event name. @@ -154,7 +156,7 @@ public: /// Triggers an event. bool postEvent( const char* eventName, const char* data ); /// Adds a subscription to an event. - bool subscribe( SimObject *callbackObj, const char* event, const char* callback = NULL ); + bool subscribe( SimObject *callbackObj, const char* event, const char* callback = NULL, S32 priority = 0 ); /// Remove a subscriber from an event. void remove( SimObject *cbObj, const char* event ); void removeAll( SimObject *cbObj ); From 21a0afe81efd5b404562478610319f54a8c7454b Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 1 Mar 2026 12:18:30 +0000 Subject: [PATCH 2/6] opengl debug committing to test on windows with proper debug output --- Engine/source/gfx/gl/gfxGLDevice.cpp | 122 ++++++++++++++----- Engine/source/gfx/gl/gfxGLTextureManager.cpp | 99 +++++++++++---- Engine/source/gfx/gl/gfxGLTextureObject.cpp | 93 ++++++++++---- Engine/source/gfx/gl/gfxGLUtils.h | 26 ++++ Engine/source/gfx/gl/sdl/gfxGLDevice.sdl.cpp | 10 +- Engine/source/platformSDL/sdlPlatformGL.cpp | 4 +- 6 files changed, 270 insertions(+), 84 deletions(-) diff --git a/Engine/source/gfx/gl/gfxGLDevice.cpp b/Engine/source/gfx/gl/gfxGLDevice.cpp index 8019f21bf..8ec644b8a 100644 --- a/Engine/source/gfx/gl/gfxGLDevice.cpp +++ b/Engine/source/gfx/gl/gfxGLDevice.cpp @@ -91,19 +91,74 @@ void loadGLExtensions(void *context) GL::gglPerformExtensionBinds(context); } -void STDCALL glDebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, - const GLchar *message, const void *userParam) +void APIENTRY glDebugCallback( + GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam) { - // JTH [11/24/2016]: This is a temporary fix so that we do not get spammed for redundant fbo changes. - // This only happens on Intel cards. This should be looked into sometime in the near future. - if (dStrStartsWith(message, "API_ID_REDUNDANT_FBO")) + // Ignore non-significant notifications (optional) + if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) return; + + const char* srcStr = "UNKNOWN"; + const char* typeStr = "UNKNOWN"; + const char* sevStr = "UNKNOWN"; + + switch (source) + { + case GL_DEBUG_SOURCE_API: srcStr = "API"; break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: srcStr = "WINDOW"; break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: srcStr = "SHADER"; break; + case GL_DEBUG_SOURCE_THIRD_PARTY: srcStr = "THIRD_PARTY"; break; + case GL_DEBUG_SOURCE_APPLICATION: srcStr = "APP"; break; + case GL_DEBUG_SOURCE_OTHER: srcStr = "OTHER"; break; + } + + switch (type) + { + case GL_DEBUG_TYPE_ERROR: typeStr = "ERROR"; break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: typeStr = "DEPRECATED"; break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: typeStr = "UNDEFINED"; break; + case GL_DEBUG_TYPE_PORTABILITY: typeStr = "PORTABILITY"; break; + case GL_DEBUG_TYPE_PERFORMANCE: typeStr = "PERFORMANCE"; break; + case GL_DEBUG_TYPE_MARKER: typeStr = "MARKER"; break; + case GL_DEBUG_TYPE_PUSH_GROUP: typeStr = "PUSH"; break; + case GL_DEBUG_TYPE_POP_GROUP: typeStr = "POP"; break; + case GL_DEBUG_TYPE_OTHER: typeStr = "OTHER"; break; + } + + switch (severity) + { + case GL_DEBUG_SEVERITY_HIGH: sevStr = "HIGH"; break; + case GL_DEBUG_SEVERITY_MEDIUM: sevStr = "MEDIUM"; break; + case GL_DEBUG_SEVERITY_LOW: sevStr = "LOW"; break; + case GL_DEBUG_SEVERITY_NOTIFICATION: sevStr = "NOTIFY"; break; + } + + // Filter known noisy IDs here if needed + // Example: + // if (id == 131185) return; + if (severity == GL_DEBUG_SEVERITY_HIGH) - Con::errorf("OPENGL: %s", message); + { + Con::errorf("OPENGL [%s][%s][%s][%u]: %s", + sevStr, srcStr, typeStr, id, message); + AssertFatal(false, "OpenGL HIGH severity error."); + } else if (severity == GL_DEBUG_SEVERITY_MEDIUM) - Con::warnf("OPENGL: %s", message); - else if (severity == GL_DEBUG_SEVERITY_LOW) - Con::printf("OPENGL: %s", message); + { + Con::warnf("OPENGL [%s][%s][%s][%u]: %s", + sevStr, srcStr, typeStr, id, message); + } + else + { + Con::printf("OPENGL [%s][%s][%s][%u]: %s", + sevStr, srcStr, typeStr, id, message); + } } void STDCALL glAmdDebugCallback(GLuint id, GLenum category, GLenum severity, GLsizei length, @@ -157,27 +212,34 @@ void GFXGLDevice::initGLState() #endif #if TORQUE_DEBUG - if( gglHasExtension(ARB_debug_output) ) - { - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallbackARB(glDebugCallback, NULL); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); - GLuint unusedIds = 0; - glDebugMessageControlARB(GL_DONT_CARE, - GL_DONT_CARE, - GL_DONT_CARE, - 0, - &unusedIds, - GL_TRUE); - } - else if(gglHasExtension(AMD_debug_output)) - { - glEnable(GL_DEBUG_OUTPUT); - glDebugMessageCallbackAMD(glAmdDebugCallback, NULL); - //glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB); - GLuint unusedIds = 0; - glDebugMessageEnableAMD(GL_DONT_CARE, GL_DONT_CARE, 0,&unusedIds, GL_TRUE); - } + +bool debugInitialized = false; +int flags; +glGetIntegerv(GL_CONTEXT_FLAGS, &flags); +if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) +{ + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + + glDebugMessageCallback(glDebugCallback, nullptr); + + glDebugMessageControl( + GL_DONT_CARE, + GL_DONT_CARE, + GL_DONT_CARE, + 0, + nullptr, + GL_TRUE); + + Con::printf("OpenGL debug output enabled."); + debugInitialized = true; +} + +if (!debugInitialized) +{ + Con::warnf("OpenGL debug output NOT available."); +} + #endif PlatformGL::setVSync(smEnableVSync); diff --git a/Engine/source/gfx/gl/gfxGLTextureManager.cpp b/Engine/source/gfx/gl/gfxGLTextureManager.cpp index ca0a958f3..c6c73fbee 100644 --- a/Engine/source/gfx/gl/gfxGLTextureManager.cpp +++ b/Engine/source/gfx/gl/gfxGLTextureManager.cpp @@ -175,7 +175,7 @@ void GFXGLTextureManager::innerCreateTexture( GFXGLTextureObject *retTex, //calculate num mipmaps if(retTex->mMipLevels == 0) retTex->mMipLevels = getMaxMipmaps(width, height, 1); - + glTexParameteri(binding, GL_TEXTURE_MAX_LEVEL, retTex->mMipLevels-1 ); bool hasTexStorage = false; @@ -364,34 +364,83 @@ void GFXGLTextureManager::innerCreateTexture( GFXGLTextureObject *retTex, // loadTexture - GBitmap //----------------------------------------------------------------------------- -static void _textureUpload(const S32 width, const S32 height,const S32 bytesPerPixel,const GFXGLTextureObject* texture, const GFXFormat fmt, const U8* data,const S32 mip=0, const U32 face = 0, Swizzle *pSwizzle = NULL) +static void _textureUpload( + const S32 width, + const S32 height, + const S32 bytesPerPixel, + const GFXGLTextureObject* texture, + const GFXFormat fmt, + const U8* data, + const S32 mip = 0, + const U32 face = 0, + Swizzle* pSwizzle = NULL) { - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, texture->getBuffer()); - U32 bufSize = width * height * bytesPerPixel; - glBufferData(GL_PIXEL_UNPACK_BUFFER, bufSize, NULL, GL_STREAM_DRAW); + const GLenum target = texture->getBinding(); - if(pSwizzle) - { - PROFILE_SCOPE(Swizzle32_Upload); - U8* pboMemory = (U8*)dMalloc(bufSize); - pSwizzle->ToBuffer(pboMemory, data, bufSize); - glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, bufSize, pboMemory); - dFree(pboMemory); - } - else - { - PROFILE_SCOPE(SwizzleNull_Upload); - glBufferSubData(GL_PIXEL_UNPACK_BUFFER, 0, bufSize, data); - } + // Save pixel store state + GLint prevUnpackAlign; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlign); - if(texture->getBinding() == GL_TEXTURE_CUBE_MAP) - glTexSubImage2D(GFXGLFaceType[face], mip, 0, 0, width, height, GFXGLTextureFormat[fmt], GFXGLTextureType[fmt], NULL); - else if (texture->getBinding() == GL_TEXTURE_2D) - glTexSubImage2D(texture->getBinding(), mip, 0, 0, width, height, GFXGLTextureFormat[fmt], GFXGLTextureType[fmt], NULL); - else - glTexSubImage1D(texture->getBinding(), mip, 0, (width > 1 ? width : height), GFXGLTextureFormat[fmt], GFXGLTextureType[fmt], NULL); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + const U32 bufSize = width * height * bytesPerPixel; + + const U8* uploadPtr = data; + U8* tempBuffer = nullptr; + + if (pSwizzle) + { + tempBuffer = (U8*)dMalloc(bufSize); + pSwizzle->ToBuffer(tempBuffer, data, bufSize); + uploadPtr = tempBuffer; + } + + if (target == GL_TEXTURE_CUBE_MAP) + { + glTexSubImage2D( + GFXGLFaceType[face], + mip, + 0, 0, + width, height, + GFXGLTextureFormat[fmt], + GFXGLTextureType[fmt], + uploadPtr + ); + } + else if (target == GL_TEXTURE_2D) + { + glTexSubImage2D( + GL_TEXTURE_2D, + mip, + 0, 0, + width, height, + GFXGLTextureFormat[fmt], + GFXGLTextureType[fmt], + uploadPtr + ); + } + else if (target == GL_TEXTURE_1D) + { + glTexSubImage1D( + GL_TEXTURE_1D, + mip, + 0, + width, + GFXGLTextureFormat[fmt], + GFXGLTextureType[fmt], + uploadPtr + ); + } + + if (tempBuffer) + dFree(tempBuffer); + + // Restore state + glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlign); + +#ifdef TORQUE_DEBUG + glCheckErrors(); +#endif } bool GFXGLTextureManager::_loadTexture(GFXTextureObject *aTexture, GBitmap *pDL) diff --git a/Engine/source/gfx/gl/gfxGLTextureObject.cpp b/Engine/source/gfx/gl/gfxGLTextureObject.cpp index 651059d34..81ca6fa9d 100644 --- a/Engine/source/gfx/gl/gfxGLTextureObject.cpp +++ b/Engine/source/gfx/gl/gfxGLTextureObject.cpp @@ -102,36 +102,79 @@ GFXLockedRect* GFXGLTextureObject::lock(U32 mipLevel /*= 0*/, RectI* inRect /*= void GFXGLTextureObject::unlock(U32 mipLevel /*= 0*/, U32 faceIndex /*= 0*/) { - if(!mLockedRect.bits) - return; + if (!mLockedRect.bits) + return; - // I know this is in unlock, but in GL we actually do our submission in unlock. - PROFILE_SCOPE(GFXGLTextureObject_lockRT); + PROFILE_SCOPE(GFXGLTextureObject_unlock); - PRESERVE_TEXTURE(mBinding); - glBindTexture(mBinding, mHandle); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mBuffer); - glBufferData(GL_PIXEL_UNPACK_BUFFER, (mLockedRectRect.extent.x + 1) * (mLockedRectRect.extent.y + 1) * mBytesPerTexel, mFrameAllocatorPtr, GL_STREAM_DRAW); - S32 z = getDepth(); - if (mBinding == GL_TEXTURE_3D) - glTexSubImage3D(mBinding, mipLevel, mLockedRectRect.point.x, mLockedRectRect.point.y, z, - mLockedRectRect.extent.x, mLockedRectRect.extent.y, z, GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], NULL); - else if(mBinding == GL_TEXTURE_2D) - glTexSubImage2D(mBinding, mipLevel, mLockedRectRect.point.x, mLockedRectRect.point.y, - mLockedRectRect.extent.x, mLockedRectRect.extent.y, GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], NULL); - else if(mBinding == GL_TEXTURE_1D) - glTexSubImage1D(mBinding, mipLevel, (mLockedRectRect.point.x > 1 ? mLockedRectRect.point.x : mLockedRectRect.point.y), - (mLockedRectRect.extent.x > 1 ? mLockedRectRect.extent.x : mLockedRectRect.extent.y), GFXGLTextureFormat[mFormat], GFXGLTextureType[mFormat], NULL); + PRESERVE_TEXTURE(mBinding); + glBindTexture(mBinding, mHandle); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + // --- Save pixel store state --- + GLint prevUnpackAlign; + glGetIntegerv(GL_UNPACK_ALIGNMENT, &prevUnpackAlign); - mLockedRect.bits = NULL; -#if TORQUE_DEBUG - AssertFatal(mFrameAllocatorMarkGuard == FrameAllocator::getWaterMark(), ""); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + const U32 width = mLockedRectRect.extent.x; + const U32 height = mLockedRectRect.extent.y; + const U32 depth = getDepth(); + + if (mBinding == GL_TEXTURE_3D) + { + glTexSubImage3D( + mBinding, + mipLevel, + mLockedRectRect.point.x, + mLockedRectRect.point.y, + 0, + width, + height, + depth, + GFXGLTextureFormat[mFormat], + GFXGLTextureType[mFormat], + mLockedRect.bits + ); + } + else if (mBinding == GL_TEXTURE_2D) + { + glTexSubImage2D( + mBinding, + mipLevel, + mLockedRectRect.point.x, + mLockedRectRect.point.y, + width, + height, + GFXGLTextureFormat[mFormat], + GFXGLTextureType[mFormat], + mLockedRect.bits + ); + } + else if (mBinding == GL_TEXTURE_1D) + { + glTexSubImage1D( + mBinding, + mipLevel, + mLockedRectRect.point.x, + width, + GFXGLTextureFormat[mFormat], + GFXGLTextureType[mFormat], + mLockedRect.bits + ); + } + + // --- Restore state --- + glPixelStorei(GL_UNPACK_ALIGNMENT, prevUnpackAlign); + + mLockedRect.bits = NULL; + + FrameAllocator::setWaterMark(mFrameAllocatorMark); + mFrameAllocatorMark = 0; + mFrameAllocatorPtr = NULL; + +#ifdef TORQUE_DEBUG + glCheckErrors(); #endif - FrameAllocator::setWaterMark(mFrameAllocatorMark); - mFrameAllocatorMark = 0; - mFrameAllocatorPtr = NULL; } void GFXGLTextureObject::release() diff --git a/Engine/source/gfx/gl/gfxGLUtils.h b/Engine/source/gfx/gl/gfxGLUtils.h index 2218836b0..928cf98e3 100644 --- a/Engine/source/gfx/gl/gfxGLUtils.h +++ b/Engine/source/gfx/gl/gfxGLUtils.h @@ -28,6 +28,32 @@ #include "gfx/gl/gfxGLStateCache.h" #include "gfx/bitmap/imageUtils.h" +inline const char* glGetErrorString(GLenum error) +{ + switch (error) + { + case GL_NO_ERROR: return "No Error"; + case GL_INVALID_ENUM: return "Invalid Enum"; + case GL_INVALID_VALUE: return "Invalid Value"; + case GL_INVALID_OPERATION: return "Invalid Operation"; + case GL_INVALID_FRAMEBUFFER_OPERATION: return "Invalid Framebuffer Operation"; + case GL_OUT_OF_MEMORY: return "Out of Memory"; + case GL_STACK_UNDERFLOW: return "Stack Underflow"; + case GL_STACK_OVERFLOW: return "Stack Overflow"; + case GL_CONTEXT_LOST: return "Context Lost"; + default: return "Unknown Error"; + } +} + +inline void _glCheckErrors(const char *filename, int line) +{ + GLenum err; + while ((err = glGetError()) != GL_NO_ERROR) + Con::printf("OpenGL Error: %s (%d) [%u] %s\n", filename, line, err, glGetErrorString(err)); +} + +#define glCheckErrors() _glCheckErrors(__FILE__, __LINE__) + inline U32 getMaxMipmaps(U32 width, U32 height, U32 depth) { return getMax( getBinLog2(depth), getMax(getBinLog2(width), getBinLog2(height))) + 1; diff --git a/Engine/source/gfx/gl/sdl/gfxGLDevice.sdl.cpp b/Engine/source/gfx/gl/sdl/gfxGLDevice.sdl.cpp index 1bb34d4f2..a81c0334c 100644 --- a/Engine/source/gfx/gl/sdl/gfxGLDevice.sdl.cpp +++ b/Engine/source/gfx/gl/sdl/gfxGLDevice.sdl.cpp @@ -103,9 +103,17 @@ void GFXGLDevice::enumerateAdapters( Vector &adapterList ) } SDL_ClearError(); + U32 debugFlag = 0; +#ifdef TORQUE_DEBUG + debugFlag |= SDL_GL_CONTEXT_DEBUG_FLAG; +#endif SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, debugFlag); SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 1); - + SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); +#ifdef TORQUE_GL_SOFTWARE + SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 0); +#endif SDL_GLContext tempContext = SDL_GL_CreateContext( tempWindow ); if( !tempContext ) { diff --git a/Engine/source/platformSDL/sdlPlatformGL.cpp b/Engine/source/platformSDL/sdlPlatformGL.cpp index fd55727b8..22e719f58 100644 --- a/Engine/source/platformSDL/sdlPlatformGL.cpp +++ b/Engine/source/platformSDL/sdlPlatformGL.cpp @@ -19,12 +19,10 @@ namespace PlatformGL #ifdef TORQUE_DEBUG debugFlag |= SDL_GL_CONTEXT_DEBUG_FLAG; #endif - - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, majorOGL); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, minorOGL); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, debugFlag); SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 1); + SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); #ifdef TORQUE_GL_SOFTWARE SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 0); #endif From 2895e98cbbdbb4530d9070b86c070d8baa6e27cf Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 1 May 2026 16:43:58 +0100 Subject: [PATCH 3/6] TSShape loader refactor final setup for assimp Added assimp importers and exporters, removed defaulting to all Added compression to tshape added dts version to tshape and cmake Update assimpShapeLoader.cpp quick fix fix previewing dsq ground work automatically export dsq files for animations Groundwork Adds the same sort of model for registering loaders and exporters as is set out on gbitmap Added a bit more safety around the assimp matrix fix to convert incoming models to torques coordinate system. --- Engine/lib/CMakeLists.txt | 11 +- Engine/source/T3D/assets/assetImporter.cpp | 2 +- .../source/gui/editor/guiShapeEdPreview.cpp | 103 +++++- Engine/source/gui/editor/guiShapeEdPreview.h | 1 + Engine/source/ts/assimp/assimpAppMesh.cpp | 4 +- Engine/source/ts/assimp/assimpAppMesh.h | 2 + Engine/source/ts/assimp/assimpShapeLoader.cpp | 315 +++++++----------- Engine/source/ts/assimp/assimpShapeLoader.h | 3 - .../source/ts/collada/colladaShapeLoader.cpp | 171 +++------- Engine/source/ts/collada/colladaShapeLoader.h | 3 - Engine/source/ts/loader/tsShapeLoader.cpp | 30 +- Engine/source/ts/tsShape.cpp | 164 ++++++--- Engine/source/ts/tsShape.h | 53 ++- Engine/source/ts/tsShapeEdit.cpp | 11 +- Tools/CMake/torqueConfig.h.in | 1 + Tools/CMake/torque_configs.cmake | 6 + 16 files changed, 466 insertions(+), 414 deletions(-) diff --git a/Engine/lib/CMakeLists.txt b/Engine/lib/CMakeLists.txt index d48ff6642..13c0cc25e 100644 --- a/Engine/lib/CMakeLists.txt +++ b/Engine/lib/CMakeLists.txt @@ -143,8 +143,15 @@ IF(MSVC) advanced_option( ASSIMP_INSTALL_PDB "Install MSVC debug files." ON ) endif() advanced_option(ASSIMP_RAPIDJSON_NO_MEMBER_ITERATOR "Suppress rapidjson warning on MSVC (NOTE: breaks android build)" ON ) -advanced_option(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_IMPORTER values" ON) -advanced_option(ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_EXPORTER values" ON) +advanced_option(ASSIMP_BUILD_ALL_IMPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_IMPORTER values" OFF) +advanced_option(ASSIMP_BUILD_FBX_IMPORTER "FBX Importer" ON) +advanced_option(ASSIMP_BUILD_GLTF_IMPORTER "GLTF Importer" ON) +advanced_option(ASSIMP_BUILD_OBJ_IMPORTER "OBJ Importer" ON) +advanced_option(ASSIMP_BUILD_BLEND_IMPORTER "BLENDER Importer" ON) +advanced_option(ASSIMP_BUILD_ALL_EXPORTERS_BY_DEFAULT "default value of all ASSIMP_BUILD_XXX_EXPORTER values" OFF) +advanced_option(ASSIMP_BUILD_FBX_EXPORTER "FBX Importer" ON) +advanced_option(ASSIMP_BUILD_GLTF_EXPORTER "GLTF Importer" ON) +advanced_option(ASSIMP_BUILD_OBJ_EXPORTER "OBJ Importer" ON) mark_as_advanced(ASSIMP_ARCHIVE_OUTPUT_DIRECTORY) mark_as_advanced(ASSIMP_BIN_INSTALL_DIR) mark_as_advanced(ASSIMP_LIB_INSTALL_DIR) diff --git a/Engine/source/T3D/assets/assetImporter.cpp b/Engine/source/T3D/assets/assetImporter.cpp index 6c22ce7d2..3851c945b 100644 --- a/Engine/source/T3D/assets/assetImporter.cpp +++ b/Engine/source/T3D/assets/assetImporter.cpp @@ -2012,7 +2012,7 @@ void AssetImporter::processShapeAsset(AssetImportObject* assetItem) { enumColladaForImport(filePath, shapeInfo, false); } - else if ((fileExt.compare("dts") == 0) || (fileExt.compare("dsq") == 0)) + else if ((fileExt.compare("dts") == 0)) { enumDTSForImport(filePath, shapeInfo); } diff --git a/Engine/source/gui/editor/guiShapeEdPreview.cpp b/Engine/source/gui/editor/guiShapeEdPreview.cpp index e47880233..d5c0e2b6c 100644 --- a/Engine/source/gui/editor/guiShapeEdPreview.cpp +++ b/Engine/source/gui/editor/guiShapeEdPreview.cpp @@ -405,6 +405,53 @@ bool GuiShapeEdPreview::setObjectModel(const char* modelName) return true; } +bool GuiShapeEdPreview::findCompanionShape(const Torque::Path& dsqPath, Torque::Path& outShapePath) +{ + // AssimpLoader and ColladaLoader exports as "modelname_sequencename.dsq" alongside "modelname.cached.dts" + // so strip everything from the last underscore to find the base name + String fileName = dsqPath.getFileName(); + String::SizeType sep = fileName.find('_'); + + if (sep != String::NPos) + { + Torque::Path candidate(dsqPath); + candidate.setFileName(fileName.substr(0, sep)); + + candidate.setExtension("cached.dts"); + if (Torque::FS::IsFile(candidate.getFullPath())) + { + outShapePath = candidate; + return true; + } + + candidate.setExtension("dts"); + if (Torque::FS::IsFile(candidate.getFullPath())) + { + outShapePath = candidate; + return true; + } + } + + // fallback: same filename, just swap extension + Torque::Path direct(dsqPath); + + direct.setExtension("cached.dts"); + if (Torque::FS::IsFile(direct.getFullPath())) + { + outShapePath = direct; + return true; + } + + direct.setExtension("dts"); + if (Torque::FS::IsFile(direct.getFullPath())) + { + outShapePath = direct; + return true; + } + + return false; +} + bool GuiShapeEdPreview::setObjectShapeAsset(const char* assetId) { SAFE_DELETE(mModel); @@ -422,16 +469,68 @@ bool GuiShapeEdPreview::setObjectShapeAsset(const char* assetId) ShapeAsset* asset = AssetDatabase.acquireAsset(id); modelName = asset->getShapeFile(); AssetDatabase.releaseAsset(id); + return setObjectModel(modelName); } else if (assetType == StringTable->insert("ShapeAnimationAsset")) { ShapeAnimationAsset* asset = AssetDatabase.acquireAsset(id); - modelName = asset->getAnimationPath(); + StringTableEntry animPath = asset->getAnimationPath(); AssetDatabase.releaseAsset(id); + + Torque::Path dsqPath(animPath); + Torque::Path shapePath; + if (!findCompanionShape(dsqPath, shapePath)) + { + Con::warnf("GuiShapeEdPreview::setObjectShapeAsset - " + "No companion shape found for '%s'", animPath); + return false; + } + + if (!setObjectModel(shapePath.getFullPath())) + { + Con::warnf("GuiShapeEdPreview::setObjectShapeAsset - " + "Could not load companion shape for '%s'", animPath); + return false; + } + + FileStream dsqStream; + if (!dsqStream.open(animPath, Torque::FS::File::Read)) + { + Con::warnf("GuiShapeEdPreview::setObjectShapeAsset - " + "Could not open '%s'", animPath); + SAFE_DELETE(mModel); + return false; + } + + TSShape* shape = mModel->getShape(); + + bool ok = shape->importSequences(&dsqStream, String(animPath)); + dsqStream.close(); + + if (!ok) + { + Con::warnf("GuiShapeEdPreview::setObjectShapeAsset - " + "importSequences failed for '%s'", animPath); + SAFE_DELETE(mModel); + return false; + } + + setAllMeshesHidden(true); + mRenderNodes = true; + if (shape->sequences.size() > 0) + { + // importSequences appends, so the new one is always last + const String& seqName = shape->getSequenceName(shape->sequences.size() - 1); + addThread(); + mActiveThread = 0; + setActiveThreadSequence(seqName.c_str(), 0.0f, 0.0f, true); + } + + return true; } } - return setObjectModel(modelName); + return false; } void GuiShapeEdPreview::_onResourceChanged(const Torque::Path& path) diff --git a/Engine/source/gui/editor/guiShapeEdPreview.h b/Engine/source/gui/editor/guiShapeEdPreview.h index 36cad41aa..b9757b8c5 100644 --- a/Engine/source/gui/editor/guiShapeEdPreview.h +++ b/Engine/source/gui/editor/guiShapeEdPreview.h @@ -199,6 +199,7 @@ public: void setCurrentDetail(S32 dl); bool setObjectModel(const char * modelName); + bool findCompanionShape(const Torque::Path& dsqPath, Torque::Path& outShapePath); bool setObjectShapeAsset(const char* assetId); void _onResourceChanged(const Torque::Path& path); diff --git a/Engine/source/ts/assimp/assimpAppMesh.cpp b/Engine/source/ts/assimp/assimpAppMesh.cpp index d78898c32..60cee3a2e 100644 --- a/Engine/source/ts/assimp/assimpAppMesh.cpp +++ b/Engine/source/ts/assimp/assimpAppMesh.cpp @@ -44,6 +44,7 @@ bool AssimpAppMesh::fixedSizeEnabled = false; S32 AssimpAppMesh::fixedSize = 2; +Vector AssimpAppMesh::sMaterialRemap; //------------------------------------------------------------------------------ @@ -164,7 +165,8 @@ void AssimpAppMesh::lockMesh(F32 t, const MatrixF& objOffset) primitives.increment(); TSDrawPrimitive& primitive = primitives.last(); primitive.start = 0; - primitive.matIndex = (TSDrawPrimitive::Triangles | TSDrawPrimitive::Indexed) | (S32)mMeshData->mMaterialIndex; + S32 mappedMat = (mMeshData->mMaterialIndex < (U32)AssimpAppMesh::sMaterialRemap.size()) ? AssimpAppMesh::sMaterialRemap[mMeshData->mMaterialIndex] : TSDrawPrimitive::NoMaterial; + primitive.matIndex = (TSDrawPrimitive::Triangles | TSDrawPrimitive::Indexed) | mappedMat; primitive.numElements = indicesCount; for ( U32 n = 0; n < mMeshData->mNumFaces; ++n) diff --git a/Engine/source/ts/assimp/assimpAppMesh.h b/Engine/source/ts/assimp/assimpAppMesh.h index 81f0b251d..48c1cfc34 100644 --- a/Engine/source/ts/assimp/assimpAppMesh.h +++ b/Engine/source/ts/assimp/assimpAppMesh.h @@ -119,6 +119,8 @@ public: /// @return The mesh transform at the specified time MatrixF getMeshTransform(F32 time) override; F32 getVisValue(F32 t) override; + + static Vector sMaterialRemap; }; #endif // _COLLADA_APPMESH_H_ diff --git a/Engine/source/ts/assimp/assimpShapeLoader.cpp b/Engine/source/ts/assimp/assimpShapeLoader.cpp index 4bcc1df91..5a52d4e44 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.cpp +++ b/Engine/source/ts/assimp/assimpShapeLoader.cpp @@ -56,12 +56,13 @@ #undef new #endif #endif -// assimp include files. +// assimp include files. #include #include #include #include #include +#include #include #if !defined(TORQUE_DISABLE_MEMORY_MANAGER) @@ -69,56 +70,68 @@ # define new _new #endif -extern bool gTryUseDSQs; +static bool sReadAssimp(const Torque::Path& path, TSShape*& shape); -MODULE_BEGIN( AssimpShapeLoader ) - MODULE_INIT_AFTER( ShapeLoader ) - MODULE_INIT +static struct _privateRegisterAssimp +{ + _privateRegisterAssimp() { - TSShapeLoader::addFormat("DirectX X", "x"); - TSShapeLoader::addFormat("Autodesk FBX", "fbx"); - TSShapeLoader::addFormat("Blender 3D", "blend" ); - TSShapeLoader::addFormat("3ds Max 3DS", "3ds"); - TSShapeLoader::addFormat("3ds Max ASE", "ase"); - TSShapeLoader::addFormat("Wavefront Object", "obj"); - TSShapeLoader::addFormat("Industry Foundation Classes (IFC/Step)", "ifc"); - TSShapeLoader::addFormat("Stanford Polygon Library", "ply"); - TSShapeLoader::addFormat("AutoCAD DXF", "dxf"); - TSShapeLoader::addFormat("LightWave", "lwo"); - TSShapeLoader::addFormat("LightWave Scene", "lws"); - TSShapeLoader::addFormat("Modo", "lxo"); - TSShapeLoader::addFormat("Stereolithography", "stl"); - TSShapeLoader::addFormat("AC3D", "ac"); - TSShapeLoader::addFormat("Milkshape 3D", "ms3d"); - TSShapeLoader::addFormat("TrueSpace COB", "cob"); - TSShapeLoader::addFormat("TrueSpace SCN", "scn"); - TSShapeLoader::addFormat("Ogre XML", "xml"); - TSShapeLoader::addFormat("Irrlicht Mesh", "irrmesh"); - TSShapeLoader::addFormat("Irrlicht Scene", "irr"); - TSShapeLoader::addFormat("Quake I", "mdl" ); - TSShapeLoader::addFormat("Quake II", "md2" ); - TSShapeLoader::addFormat("Quake III Mesh", "md3"); - TSShapeLoader::addFormat("Quake III Map/BSP", "pk3"); - TSShapeLoader::addFormat("Return to Castle Wolfenstein", "mdc"); - TSShapeLoader::addFormat("Doom 3", "md5" ); - TSShapeLoader::addFormat("Valve SMD", "smd"); - TSShapeLoader::addFormat("Valve VTA", "vta"); - TSShapeLoader::addFormat("Starcraft II M3", "m3"); - TSShapeLoader::addFormat("Unreal", "3d"); - TSShapeLoader::addFormat("BlitzBasic 3D", "b3d" ); - TSShapeLoader::addFormat("Quick3D Q3D", "q3d"); - TSShapeLoader::addFormat("Quick3D Q3S", "q3s"); - TSShapeLoader::addFormat("Neutral File Format", "nff"); - TSShapeLoader::addFormat("Object File Format", "off"); - TSShapeLoader::addFormat("PovRAY Raw", "raw"); - TSShapeLoader::addFormat("Terragen Terrain", "ter"); - TSShapeLoader::addFormat("3D GameStudio (3DGS)", "mdl"); - TSShapeLoader::addFormat("3D GameStudio (3DGS) Terrain", "hmp"); - TSShapeLoader::addFormat("Izware Nendo", "ndo"); - TSShapeLoader::addFormat("gltf", "gltf"); - TSShapeLoader::addFormat("gltf binary", "glb"); + TSShape::ShapeRegistration reg; + + Assimp::Importer importer; + for (U32 i = 0; i < importer.GetImporterCount(); i++) + { + const aiImporterDesc* desc = importer.GetImporterInfo(i); + + String extensions(desc->mFileExtensions); + + Vector tokens; + extensions.split(" ", tokens); + for (U32 t = 0; t < tokens.size(); ++t) + { + const String& ext = tokens[t]; + if (ext.isEmpty() || + ext.equal("dae", String::NoCase) || // filter out collada importer formats (for now). + ext.equal("zae", String::NoCase) || + ext.equal("xml", String::NoCase) + ) + continue; + + reg.extensions.push_back({ + String(desc->mName), // convert from const char* + ext + }); + } + + } + + Assimp::Exporter exporter; + + for (U32 i = 0; i < exporter.GetExportFormatCount(); ++i) + { + const aiExportFormatDesc* desc = exporter.GetExportFormatDescription(i); + String ext(desc->fileExtension); + + if (ext.isEmpty() || + ext.equal("dae", String::NoCase) || // filter out collada importer formats (for now). + ext.equal("zae", String::NoCase) || + ext.equal("xml", String::NoCase) + ) + continue; + + reg.export_extensions.push_back({ + String(desc->description), + ext + }); + } + + reg.readFunc = sReadAssimp; + reg.writeFunc = NULL; + + TSShape::sRegisterFormat(reg); } -MODULE_END; +} sStaticRegisterAssimp; + //----------------------------------------------------------------------------- @@ -272,7 +285,16 @@ void AssimpShapeLoader::enumerateScene() // Load all materials AssimpAppMaterial::sDefaultMatNumber = 0; - for (U32 i = 0; i < mScene->mNumMaterials; ++i) { + AssimpAppMesh::sMaterialRemap.setSize(mScene->mNumMaterials); + for (U32 i = 0; i < mScene->mNumMaterials; ++i) + { + if (FindMatch::isMatchMultipleExprs(ColladaUtils::getOptions().neverImportMat, + mScene->mMaterials[i]->GetName().C_Str(), false)) + { + AssimpAppMesh::sMaterialRemap[i] = TSDrawPrimitive::NoMaterial; // TSDrawPrimitive::NoMaterial + continue; + } + AssimpAppMesh::sMaterialRemap[i] = AppMesh::appMaterials.size(); AppMesh::appMaterials.push_back(new AssimpAppMaterial(mScene->mMaterials[i])); } @@ -391,54 +413,34 @@ void AssimpShapeLoader::getRootAxisTransform() MatrixF rot(true); - // ===== Y-UP SOURCE ===== - if (upAxis == 1) + // Build source basis + auto axisToVector = [](int axis, int sign) -> Point3F { - if (frontAxis == 2) - { - // Y-up, Z-forward → Z-up, Y-forward - // Rotate 180° Y, then 90° X - rot(0, 0) = -1.0f; - rot(1, 1) = 0.0f; rot(2, 1) = 1.0f; - rot(1, 2) = 1.0f; rot(2, 2) = 0.0f; - } - else if (frontAxis == 0) - { - // Y-up, X-forward → Z-up, Y-forward - // Rotate -90° around Z then 90° around X - rot(0, 0) = 0.0f; rot(0, 1) = -1.0f; - rot(1, 0) = 1.0f; rot(1, 1) = 0.0f; - rot(2, 2) = 1.0f; - } - } + Point3F v(0, 0, 0); + v[axis] = (F32)sign; + return v; + }; - // ===== Z-UP SOURCE ===== - if (upAxis == 2) - { - if (frontAxis == 1) - { - // Already Z-up, Y-forward → no change - } - else if (frontAxis == 0) - { - // Z-up, X-forward → rotate -90° around Z - rot(0, 0) = 0.0f; rot(0, 1) = -1.0f; - rot(1, 0) = 1.0f; rot(1, 1) = 0.0f; - } - } + Point3F forward = axisToVector(frontAxis, -frontSign); + Point3F up = axisToVector(upAxis, upSign); + Point3F right = mCross(forward, up); - // ===== X-UP SOURCE ===== - if (upAxis == 0) - { - if (frontAxis == 2) - { - // X-up, Z-forward → Z-up, Y-forward - // Rotate -90° around Y then -90° around Z - rot(0, 0) = 0.0f; rot(0, 1) = 0.0f; rot(0, 2) = -1.0f; - rot(1, 0) = 1.0f; rot(1, 1) = 0.0f; rot(1, 2) = 0.0f; - rot(2, 0) = 0.0f; rot(2, 1) = -1.0f; rot(2, 2) = 0.0f; - } - } + // Recompute forward + forward = mCross(up, right); + + // Normalize (defensive, though they should already be unit) + right.normalize(); + forward.normalize(); + up.normalize(); + + MatrixF srcBasis(true); + srcBasis.setColumn(0, right); + srcBasis.setColumn(1, forward); + srcBasis.setColumn(2, up); + + // Convert to Torque space + rot = srcBasis; + rot.inverse(); ColladaUtils::getOptions().axisCorrectionMat = rot; } @@ -689,30 +691,6 @@ bool AssimpShapeLoader::canLoadCachedDTS(const Torque::Path& path) return false; } -/// Check if an up-to-date cached DSQ is available for this file -bool AssimpShapeLoader::canLoadCachedDSQ(const Torque::Path& path) -{ - // Generate the cached filename - Torque::Path cachedPath(path); - cachedPath.setExtension("dsq"); - - // Check if a cached DTS newer than this file is available - FileTime cachedModifyTime; - if (Platform::getFileTimes(cachedPath.getFullPath(), NULL, &cachedModifyTime)) - { - bool forceLoad = Con::getBoolVariable("$assimp::forceLoad", false); - - FileTime daeModifyTime; - if (!Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime) || - (!forceLoad && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0))) - { - // Original file not found, or cached DTS is newer - return true; - } - } - return false; -} - void AssimpShapeLoader::assimpLogCallback(const char* message, char* user) { Con::printf("[Assimp log message] %s", StringUnit::getUnit(message, 0, "\n")); @@ -992,65 +970,12 @@ bool AssimpShapeLoader::getMetaString(const char* key, String& stringVal) } //----------------------------------------------------------------------------- /// This function is invoked by the resource manager based on file extension. -TSShape* assimpLoadShape(const Torque::Path &path) +static bool sReadAssimp(const Torque::Path &path, TSShape*& res_shape) { - // TODO: add .cached.dts generation. - // Generate the cached filename - Torque::Path cachedPath(path); - bool canLoadCached = false; - bool canLoadDSQ = false; - - // Check if an up-to-date cached DTS version of this file exists, and - // if so, use that instead. - if (AssimpShapeLoader::canLoadCachedDTS(path)) - { - cachedPath.setExtension("cached.dts"); - canLoadCached = true; - } - else if (gTryUseDSQs && AssimpShapeLoader::canLoadCachedDSQ(path)) - { - cachedPath.setExtension("dsq"); - canLoadDSQ = true; - } - if (canLoadCached || canLoadDSQ) - { - FileStream cachedStream; - cachedStream.open(cachedPath.getFullPath(), Torque::FS::File::Read); - if (cachedStream.getStatus() == Stream::Ok) - { - TSShape *shape = new TSShape; - bool readSuccess = false; - if (canLoadCached) - { - readSuccess = shape->read(&cachedStream); - } - else - { - readSuccess = shape->importSequences(&cachedStream, cachedPath); - } - cachedStream.close(); - - if (readSuccess) - { - #ifdef TORQUE_DEBUG - Con::printf("Loaded cached shape from %s", cachedPath.getFullPath().c_str()); - #endif - return shape; - } - else - { - #ifdef TORQUE_DEBUG - Con::errorf("assimpLoadShape: Load sequence file '%s' failed", cachedPath.getFullPath().c_str()); - #endif - delete shape; - } - } - } - if (!Torque::FS::IsFile(path)) { // File does not exist, bail. - return NULL; + return false; } // Allow TSShapeConstructor object to override properties @@ -1068,40 +993,40 @@ TSShape* assimpLoadShape(const Torque::Path &path) TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import complete"); Con::printf("[ASSIMP] Shape created successfully."); - bool realMesh = false; - for (U32 i = 0; i < tss->meshes.size(); ++i) + Torque::Path cachedPath(path); + // Cache the model to a DTS file for faster loading next time. + cachedPath.setExtension("cached.dts"); + // Cache the model to a DTS file for faster loading next time. + FileStream dtsStream; + if (dtsStream.open(cachedPath.getFullPath(), Torque::FS::File::Write)) { - if (tss->meshes[i] && tss->meshes[i]->getMeshType() != TSMesh::NullMeshType) - realMesh = true; + Con::printf("Writing cached shape to %s", cachedPath.getFullPath().c_str()); + tss->write(&dtsStream); } - if (!realMesh && gTryUseDSQs) + if (tss->sequences.size() > 0) { Torque::Path dsqPath(cachedPath); dsqPath.setExtension("dsq"); FileStream animOutStream; - dsqPath.setFileName(cachedPath.getFileName()); - if (animOutStream.open(dsqPath.getFullPath(), Torque::FS::File::Write)) + + for (S32 i = 0; i < tss->sequences.size(); i++) { - Con::printf("Writing DSQ Animation File for '%s'", dsqPath.getFileName().c_str()); - tss->exportSequences(&animOutStream); - } - } - else - { - // Cache the model to a DTS file for faster loading next time. - cachedPath.setExtension("cached.dts"); - // Cache the model to a DTS file for faster loading next time. - FileStream dtsStream; - if (dtsStream.open(cachedPath.getFullPath(), Torque::FS::File::Write)) - { - Con::printf("Writing cached shape to %s", cachedPath.getFullPath().c_str()); - tss->write(&dtsStream); + const String& seqName = tss->getName(tss->sequences[i].nameIndex); + Con::printf("Writing DSQ Animation File for sequence '%s'", seqName.c_str()); + + dsqPath.setFileName(cachedPath.getFileName() + "_" + seqName); + if (animOutStream.open(dsqPath.getFullPath(), Torque::FS::File::Write)) + { + tss->exportSequence(&animOutStream, tss->sequences[i], false); + animOutStream.close(); + } } } } loader.releaseImport(); - return tss; + res_shape = tss; + return true; } DefineEngineFunction(GetShapeInfo, bool, (const char* shapePath, const char* ctrl, bool loadCachedDts), ("", "", true), diff --git a/Engine/source/ts/assimp/assimpShapeLoader.h b/Engine/source/ts/assimp/assimpShapeLoader.h index ac81c5d85..37c4aa308 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.h +++ b/Engine/source/ts/assimp/assimpShapeLoader.h @@ -41,8 +41,6 @@ struct aiMetadata; //----------------------------------------------------------------------------- class AssimpShapeLoader : public TSShapeLoader { - friend TSShape* assimpLoadShape(const Torque::Path &path); - protected: Assimp::Importer mImporter; const aiScene* mScene; @@ -79,7 +77,6 @@ public: bool fillGuiTreeView(const char* shapePath, GuiTreeViewCtrl* tree); static bool canLoadCachedDTS(const Torque::Path& path); - static bool canLoadCachedDSQ(const Torque::Path& path); static void assimpLogCallback(const char* message, char* user); }; diff --git a/Engine/source/ts/collada/colladaShapeLoader.cpp b/Engine/source/ts/collada/colladaShapeLoader.cpp index 66a859934..65ea667aa 100644 --- a/Engine/source/ts/collada/colladaShapeLoader.cpp +++ b/Engine/source/ts/collada/colladaShapeLoader.cpp @@ -50,15 +50,23 @@ #include "core/util/zip/zipVolume.h" #include "gfx/bitmap/gBitmap.h" -extern bool gTryUseDSQs; -MODULE_BEGIN( ColladaShapeLoader ) - MODULE_INIT_AFTER( ShapeLoader ) - MODULE_INIT +static bool sReadCollada(const Torque::Path& path, TSShape*& shape); + +static struct _privateRegisterCollada +{ + _privateRegisterCollada() { - TSShapeLoader::addFormat("Collada", "dae"); - TSShapeLoader::addFormat("Google Earth", "kmz"); + TSShape::ShapeRegistration reg; + reg.extensions.push_back({ "Collada", "dae" }); + reg.export_extensions.push_back({ "Collada", "dae" }); + reg.extensions.push_back({ "Google Earth", "kmz" }); + + reg.readFunc = sReadCollada; + reg.writeFunc = NULL; + + TSShape::sRegisterFormat(reg); } -MODULE_END; +} sStaticRegisterCollada; // static DAE sDAE; // Collada model database (holds the last loaded file) @@ -549,39 +557,6 @@ bool ColladaShapeLoader::canLoadCachedDTS(const Torque::Path& path) return false; } -bool ColladaShapeLoader::canLoadCachedDSQ(const Torque::Path& path) -{ - // Generate the cached filename - Torque::Path cachedPath(path); - cachedPath.setExtension("dsq"); - - // Check if a cached DSQ newer than this file is available - FileTime cachedModifyTime; - if (Platform::getFileTimes(cachedPath.getFullPath(), NULL, &cachedModifyTime)) - { - bool forceLoadDAE = Con::getBoolVariable("$collada::forceLoadDAE", false); - - FileTime daeModifyTime; - if (!Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime) || - (!forceLoadDAE && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0))) - { - // DAE not found, or cached DTS is newer - return true; - } - } - - //assume the dts is good since it was zipped on purpose - Torque::FS::FileSystemRef ref = Torque::FS::GetFileSystem(cachedPath); - if (ref && !String::compare("Zip", ref->getTypeStr().c_str())) - { - bool forceLoadDAE = Con::getBoolVariable("$collada::forceLoadDAE", false); - - if (!forceLoadDAE && Torque::FS::IsFile(cachedPath)) - return true; - } - - return false; -} bool ColladaShapeLoader::checkAndMountSketchup(const Torque::Path& path, String& mountPoint, Torque::Path& daePath) { bool isSketchup = path.getExtension().equal("kmz", String::NoCase); @@ -683,61 +658,8 @@ domCOLLADA* ColladaShapeLoader::readColladaFile(const String& path) //----------------------------------------------------------------------------- /// This function is invoked by the resource manager based on file extension. -TSShape* loadColladaShape(const Torque::Path &path) +static bool sReadCollada(const Torque::Path& path, TSShape*& res_shape) { -#ifndef DAE2DTS_TOOL - // Generate the cached filename - Torque::Path cachedPath(path); - bool canLoadCached = false; - bool canLoadDSQ = false; - // Check if an up-to-date cached DTS version of this file exists, and - // if so, use that instead. - if (ColladaShapeLoader::canLoadCachedDTS(path)) - { - cachedPath.setExtension("cached.dts"); - canLoadCached = true; - } - else if (gTryUseDSQs && ColladaShapeLoader::canLoadCachedDSQ(path)) - { - cachedPath.setExtension("dsq"); - canLoadDSQ = true; - } - if (canLoadCached || canLoadDSQ) - { - FileStream cachedStream; - cachedStream.open(cachedPath.getFullPath(), Torque::FS::File::Read); - if (cachedStream.getStatus() == Stream::Ok) - { - TSShape *shape = new TSShape; - bool readSuccess = false; - if (canLoadCached) - { - readSuccess = shape->read(&cachedStream); - } - else - { - readSuccess = shape->importSequences(&cachedStream, cachedPath); - } - cachedStream.close(); - - if (readSuccess) - { - #ifdef TORQUE_DEBUG - Con::printf("Loaded cached Collada shape from %s", cachedPath.getFullPath().c_str()); - #endif - return shape; - } - else - { - #ifdef TORQUE_DEBUG - Con::errorf("loadColladaShape: Load sequence file '%s' failed", cachedPath.getFullPath().c_str()); - #endif - delete shape; - } - } - } -#endif // DAE2DTS_TOOL - if (!Torque::FS::IsFile(path)) { // DAE file does not exist, bail. @@ -777,58 +699,51 @@ TSShape* loadColladaShape(const Torque::Path &path) tss = loader.generateShape(daePath); if (tss) { -#ifndef DAE2DTS_TOOL + TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import complete"); + Con::printf("[COLLADA] Shape created successfully."); - bool realMesh = false; - for (U32 i = 0; i < tss->meshes.size(); ++i) + Torque::Path cachedPath(path); + // Cache the model to a DTS file for faster loading next time. + cachedPath.setExtension("cached.dts"); + // Cache the model to a DTS file for faster loading next time. + FileStream dtsStream; + if (dtsStream.open(cachedPath.getFullPath(), Torque::FS::File::Write)) { - if (tss->meshes[i] && tss->meshes[i]->getMeshType() != TSMesh::NullMeshType) - realMesh = true; + Con::printf("Writing cached shape to %s", cachedPath.getFullPath().c_str()); + tss->write(&dtsStream); } - if (!realMesh && gTryUseDSQs) + // Add collada materials to materials.tscript + updateMaterialsScript(path, isSketchup); + + if (tss->sequences.size() > 0) { Torque::Path dsqPath(cachedPath); dsqPath.setExtension("dsq"); FileStream animOutStream; - dsqPath.setFileName(cachedPath.getFileName()); - if (animOutStream.open(dsqPath.getFullPath(), Torque::FS::File::Write)) + + for (S32 i = 0; i < tss->sequences.size(); i++) { - Con::printf("Writing DSQ Animation File for '%s'", dsqPath.getFileName().c_str()); - tss->exportSequences(&animOutStream); - animOutStream.close(); + const String& seqName = tss->getName(tss->sequences[i].nameIndex); + Con::printf("Writing DSQ Animation File for sequence '%s'", seqName.c_str()); + + dsqPath.setFileName(cachedPath.getFileName() + "_" + seqName); + if (animOutStream.open(dsqPath.getFullPath(), Torque::FS::File::Write)) + { + tss->exportSequence(&animOutStream, tss->sequences[i], false); + animOutStream.close(); + } } } - else - { - // Cache the Collada model to a DTS file for faster loading next time. - cachedPath.setExtension("cached.dts"); - FileStream dtsStream; - if (dtsStream.open(cachedPath.getFullPath(), Torque::FS::File::Write)) - { - Torque::FS::FileSystemRef ref = Torque::FS::GetFileSystem(daePath); - if (ref && !String::compare("Zip", ref->getTypeStr().c_str())) - Con::errorf("No cached dts file found in archive for %s. Forcing cache to disk.", daePath.getFullFileName().c_str()); - - Con::printf("Writing cached COLLADA shape to %s", cachedPath.getFullPath().c_str()); - tss->write(&dtsStream); - } - } -#endif // DAE2DTS_TOOL - - // Add collada materials to materials.tscript - updateMaterialsScript(path, isSketchup); } } - // Close progress dialog - TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import complete"); - if (isSketchup) { // Unmount the zip if we mounted it Torque::FS::Unmount(mountPoint); } - return tss; + res_shape = tss; + return true; } diff --git a/Engine/source/ts/collada/colladaShapeLoader.h b/Engine/source/ts/collada/colladaShapeLoader.h index 54cb2e70f..780b403db 100644 --- a/Engine/source/ts/collada/colladaShapeLoader.h +++ b/Engine/source/ts/collada/colladaShapeLoader.h @@ -34,8 +34,6 @@ struct AnimChannels; //----------------------------------------------------------------------------- class ColladaShapeLoader : public TSShapeLoader { - friend TSShape* loadColladaShape(const Torque::Path &path); - domCOLLADA* root; Vector animations; ///< Holds all animation channels for deletion after loading @@ -53,7 +51,6 @@ public: void computeBounds(Box3F& bounds) override; static bool canLoadCachedDTS(const Torque::Path& path); - static bool canLoadCachedDSQ(const Torque::Path& path); static bool checkAndMountSketchup(const Torque::Path& path, String& mountPoint, Torque::Path& daePath); static domCOLLADA* getDomCOLLADA(const Torque::Path& path); static domCOLLADA* readColladaFile(const String& path); diff --git a/Engine/source/ts/loader/tsShapeLoader.cpp b/Engine/source/ts/loader/tsShapeLoader.cpp index 7baceec71..10d54bfec 100644 --- a/Engine/source/ts/loader/tsShapeLoader.cpp +++ b/Engine/source/ts/loader/tsShapeLoader.cpp @@ -36,11 +36,9 @@ MODULE_BEGIN( ShapeLoader ) MODULE_INIT { TSShapeLoader::addFormat("Torque DTS", "dts"); - TSShapeLoader::addFormat("Torque DSQ", "dsq"); } MODULE_END; -bool gTryUseDSQs = false; const F32 TSShapeLoader::DefaultTime = -1.0f; const F64 TSShapeLoader::MinFrameRate = 15.0f; const F64 TSShapeLoader::MaxFrameRate = 60.0f; @@ -1304,11 +1302,16 @@ String TSShapeLoader::getFormatExtensions() { // "*.dsq TAB *.dae TAB StringBuilder output; - for(U32 n = 0; n < smFormats.size(); ++n) + for(U32 n = 0; n < TSShape::sRegistrations.size(); ++n) { - output.append("*."); - output.append(smFormats[n].mExtension); - output.append("\t"); + TSShape::ShapeRegistration reg = TSShape::sRegistrations[n]; + for (U32 i = 0; i < reg.extensions.size(); i++) + { + TSShape::ShapeFormat format = reg.extensions[i]; + output.append("*."); + output.append(format.mExtension); + output.append("\t"); + } } return output.end(); } @@ -1317,12 +1320,17 @@ String TSShapeLoader::getFormatFilters() { // "DSQ Files|*.dsq|COLLADA Files|*.dae|" StringBuilder output; - for(U32 n = 0; n < smFormats.size(); ++n) + for (U32 n = 0; n < TSShape::sRegistrations.size(); ++n) { - output.append(smFormats[n].mName); - output.append("|*."); - output.append(smFormats[n].mExtension); - output.append("|"); + TSShape::ShapeRegistration reg = TSShape::sRegistrations[n]; + for (U32 i = 0; i < reg.extensions.size(); i++) + { + TSShape::ShapeFormat format = reg.extensions[i]; + output.append(format.mName); + output.append("|*."); + output.append(format.mExtension); + output.append("|"); + } } return output.end(); } diff --git a/Engine/source/ts/tsShape.cpp b/Engine/source/ts/tsShape.cpp index 19b873a33..72cc8af52 100644 --- a/Engine/source/ts/tsShape.cpp +++ b/Engine/source/ts/tsShape.cpp @@ -38,16 +38,33 @@ #include "core/stream/fileStream.h" #include "core/fileObject.h" -#ifdef TORQUE_COLLADA -extern TSShape* loadColladaShape(const Torque::Path &path); -#endif +Vector TSShape::sRegistrations(__FILE__, __LINE__); -#ifdef TORQUE_ASSIMP -extern TSShape* assimpLoadShape(const Torque::Path &path); -#endif +void TSShape::sRegisterFormat(const ShapeRegistration& reg) +{ + U32 insert = sRegistrations.size(); + sRegistrations.insert(insert, reg); +} + +const TSShape::ShapeRegistration* TSShape::sFindRegInfo(const String& extension, bool exporting) +{ + for (U32 i = 0; i < TSShape::sRegistrations.size(); i++) + { + const TSShape::ShapeRegistration& reg = TSShape::sRegistrations[i]; + const Vector& extensions = exporting ? reg.export_extensions : reg.extensions; + + for (U32 j = 0; j < extensions.size(); j++) + { + if (extensions[j].mExtension.equal(extension, String::NoCase)) + return ® + } + } + + return NULL; +} /// most recent version -- this is the version we write -S32 TSShape::smVersion = 28; +S32 TSShape::smVersion = TORQUE_DTS_VERSION; /// the version currently being read...valid only during a read S32 TSShape::smReadVersion = -1; const U32 TSShape::smMostRecentExporterVersion = DTS_EXPORTER_CURRENT_VERSION; @@ -168,6 +185,34 @@ TSShape::~TSShape() delete[] mShapeData; } +void TSShape::compressionKey(U8* keyOut, U32 keyLen) +{ + // Concatenate app name and zip password at compile time + static const char kSeed[] = TORQUE_APP_NAME DEFAULT_ZIP_PASSWORD; + + // djb2 hash to collapse the seed string into a 32-bit value + U32 state = 5381u; + for (const char* c = kSeed; *c; ++c) + state = ((state << 5) + state) ^ (U8)*c; + + // Expand into a key stream via a linear-congruential generator + // (Numerical Recipes coefficients) + for (U32 i = 0; i < keyLen; ++i) + { + state = state * 1664525u + 1013904223u; + keyOut[i] = (U8)(state >> 16); + } +} + +void TSShape::xorBufferAtOffset(void* data, U32 byteCount, + U32 startOffset, + const U8* key, U32 keyLen) +{ + U8* p = (U8*)data; + for (U32 i = 0; i < byteCount; ++i) + p[i] ^= key[(startOffset + i) % keyLen]; +} + const String& TSShape::getName( S32 nameIndex ) const { AssertFatal(nameIndex>=0 && nameIndex>= 2; + if (smVersion >= 29) + { + const U32 KEY_LEN = 256; + U8 key[KEY_LEN]; + compressionKey(key, KEY_LEN); + + U32 offset = 0; + xorBufferAtOffset(buffer32, size32 * 4, offset, key, KEY_LEN); + offset += size32 * 4; + xorBufferAtOffset(buffer16, size16 * 4, offset, key, KEY_LEN); + offset += size16 * 4; + xorBufferAtOffset(buffer8, size8 * 4, offset, key, KEY_LEN); + } + S32 sizeMemBuffer, start16, start8; sizeMemBuffer = size32 + size16 + size8; start16 = size32; @@ -1971,7 +2030,18 @@ bool TSShape::read(Stream * s) } S32 * tmp = new S32[sizeMemBuffer]; - s->read(sizeof(S32)*sizeMemBuffer,(U8*)tmp); + s->read(sizeof(S32) * sizeMemBuffer, (U8*)tmp); + + if (smReadVersion >= 29) + { + const U32 KEY_LEN = 256; + U8 key[KEY_LEN]; + compressionKey(key, KEY_LEN); + + // The whole tmp block is one contiguous encrypted region + xorBufferAtOffset(tmp, sizeMemBuffer * 4, 0, key, KEY_LEN); + } + memBuffer32 = tmp; memBuffer16 = (S16*)(tmp+startU16); memBuffer8 = (S8*)(tmp+startU8); @@ -2166,11 +2236,41 @@ template<> void *Resource::create(const Torque::Path &path) TSShape * ret = 0; bool readSuccess = false; const String extension = path.getExtension(); + bool canLoadCached = false; - if ( extension.equal( "dts", String::NoCase ) ) + // Generate the cached filename + Torque::Path cachedPath(path); + cachedPath.setExtension("cached.dts"); + + // Check if a cached DTS newer than this file is available + FileTime cachedModifyTime; + if (Platform::getFileTimes(cachedPath.getFullPath(), NULL, &cachedModifyTime)) + { + bool forceLoadDAE = Con::getBoolVariable("$collada::forceLoadDAE", false); + + FileTime daeModifyTime; + if (!Platform::getFileTimes(path.getFullPath(), NULL, &daeModifyTime) || + (!forceLoadDAE && (Platform::compareFileTimes(cachedModifyTime, daeModifyTime) >= 0))) + { + // Non DTS not found, or cached DTS is newer + canLoadCached = true; + } + } + + //assume the dts is good since it was zipped on purpose + Torque::FS::FileSystemRef ref = Torque::FS::GetFileSystem(cachedPath); + if (ref && !String::compare("Zip", ref->getTypeStr().c_str())) + { + bool forceLoadDAE = Con::getBoolVariable("$collada::forceLoadDAE", false); + + if (!forceLoadDAE && Torque::FS::IsFile(cachedPath)) + canLoadCached = true; + } + + if (extension.equal("dts", String::NoCase) || canLoadCached) { FileStream stream; - stream.open( path.getFullPath(), Torque::FS::File::Read ); + stream.open(canLoadCached ? cachedPath.getFullPath() : path.getFullPath(), Torque::FS::File::Read); if ( stream.getStatus() != Stream::Ok ) { Con::errorf( "Resource::create - Could not open '%s'", path.getFullPath().c_str() ); @@ -2180,46 +2280,16 @@ template<> void *Resource::create(const Torque::Path &path) ret = new TSShape; readSuccess = ret->read(&stream); } - else if ( extension.equal( "dae", String::NoCase ) || extension.equal( "kmz", String::NoCase ) ) - { -#ifdef TORQUE_COLLADA - // Attempt to load the DAE file - ret = loadColladaShape(path); - readSuccess = (ret != NULL); -#else - // No COLLADA support => attempt to load the cached DTS file instead - Torque::Path cachedPath = path; - cachedPath.setExtension("cached.dts"); - - FileStream stream; - stream.open( cachedPath.getFullPath(), Torque::FS::File::Read ); - if ( stream.getStatus() != Stream::Ok ) - { - Con::errorf( "Resource::create - Could not open '%s'", cachedPath.getFullPath().c_str() ); - return NULL; - } - ret = new TSShape; - readSuccess = ret->read(&stream); -#endif - } else { - //Con::errorf( "Resource::create - '%s' has an unknown file format", path.getFullPath().c_str() ); - //delete ret; - //return NULL; - - // andrewmac: Open Asset Import Library -#ifdef TORQUE_ASSIMP - ret = assimpLoadShape(path); - readSuccess = (ret != NULL); -#endif - - // andrewmac : I could have used another conditional macro but I think this is suffice: - if (!readSuccess) + const TSShape::ShapeRegistration* regInfo = TSShape::sFindRegInfo(extension); + if (regInfo == NULL) { - Con::errorf("Resource::create - '%s' has an unknown file format", path.getFullPath().c_str()); - delete ret; - return NULL; + readSuccess = false; + } + else + { + readSuccess = regInfo->readFunc(path, ret); } } diff --git a/Engine/source/ts/tsShape.h b/Engine/source/ts/tsShape.h index 8e2071fb2..733a36008 100644 --- a/Engine/source/ts/tsShape.h +++ b/Engine/source/ts/tsShape.h @@ -83,18 +83,47 @@ struct TSShapeVertexArray /// @see TSShapeInstance for a further discussion of the 3space system. class TSShape { - public: - enum +public: + enum + { + UniformScale = BIT(0), + AlignedScale = BIT(1), + ArbitraryScale = BIT(2), + Blend = BIT(3), + Cyclic = BIT(4), + MakePath = BIT(5), + HasTranslucency= BIT(6), + AnyScale = UniformScale | AlignedScale | ArbitraryScale + }; + + struct ShapeFormat + { + String mName; + String mExtension; + }; + + struct ShapeRegistration + { + typedef bool(*ReadShape)(const Torque::Path& path, TSShape*& shape); + typedef bool(*WriteShape)(const Torque::Path& path, TSShape* shape); + Vector extensions; ///< the list of file extensions for this Loader [these should be lower case] + Vector export_extensions; ///< the list of file extensions for this Loader [these should be lower case] + + ReadShape readFunc; ///< the read function to read from a file. + WriteShape writeFunc; ///< the write function to write to a file. + + ShapeRegistration() { - UniformScale = BIT(0), - AlignedScale = BIT(1), - ArbitraryScale = BIT(2), - Blend = BIT(3), - Cyclic = BIT(4), - MakePath = BIT(5), - HasTranslucency= BIT(6), - AnyScale = UniformScale | AlignedScale | ArbitraryScale - }; + readFunc = NULL; + writeFunc = NULL; + VECTOR_SET_ASSOCIATION(extensions); + VECTOR_SET_ASSOCIATION(export_extensions); + } + }; + + static void sRegisterFormat(const ShapeRegistration& reg); + static const ShapeRegistration* sFindRegInfo(const String& extension, bool exporting = false); + static Vector sRegistrations; /// Nodes hold the transforms in the shape's tree. They are the bones of the skeleton. struct Node @@ -422,6 +451,8 @@ class TSShape // constructor/destructor TSShape(); ~TSShape(); + void compressionKey(U8* keyOut, U32 keyLen); + void xorBufferAtOffset(void* data, U32 byteCount, U32 startOffset, const U8* key, U32 keyLen); void init(); void initMaterialList(); ///< you can swap in a new material list, but call this if you do void finalizeEditable(); diff --git a/Engine/source/ts/tsShapeEdit.cpp b/Engine/source/ts/tsShapeEdit.cpp index db4c61549..042fb130f 100644 --- a/Engine/source/ts/tsShapeEdit.cpp +++ b/Engine/source/ts/tsShapeEdit.cpp @@ -1361,16 +1361,7 @@ bool TSShape::isShapeFileType(Torque::Path filePath) { String fileExt = filePath.getExtension(); - if ( - fileExt.equal("dts", String::NoCase) || - fileExt.equal("dsq", String::NoCase) || - fileExt.equal("dae", String::NoCase) || - fileExt.equal("fbx", String::NoCase) || - fileExt.equal("blend", String::NoCase) || - fileExt.equal("obj", String::NoCase) || - fileExt.equal("gltf", String::NoCase) || - fileExt.equal("glb", String::NoCase) - ) + if (TSShape::sFindRegInfo(fileExt)) return true; return false; diff --git a/Tools/CMake/torqueConfig.h.in b/Tools/CMake/torqueConfig.h.in index 11eba5fbe..8e50b16ef 100644 --- a/Tools/CMake/torqueConfig.h.in +++ b/Tools/CMake/torqueConfig.h.in @@ -231,3 +231,4 @@ /// Password to use when opening encrypted zip files. Change this to whatever the password is for your zips. #define DEFAULT_ZIP_PASSWORD "@TORQUE_APP_PASSWORD@" +#define TORQUE_DTS_VERSION @TORQUE_DTS_VERSION@ \ No newline at end of file diff --git a/Tools/CMake/torque_configs.cmake b/Tools/CMake/torque_configs.cmake index 241fb1e9e..c71fd3a13 100644 --- a/Tools/CMake/torque_configs.cmake +++ b/Tools/CMake/torque_configs.cmake @@ -114,6 +114,12 @@ advanced_option(TORQUE_DISABLE_MEMORY_MANAGER "Disable memory manager" ON) set(TORQUE_ENTRY_FUNCTION "" CACHE STRING "Specify a console function to execute instead of looking for a main.tscript file") mark_as_advanced(TORQUE_ENTRY_FUNCTION) +set(TORQUE_DTS_VERSION "29" CACHE STRING "Device Tree Source version") + +set_property(CACHE TORQUE_DTS_VERSION PROPERTY STRINGS + 28 29 +) + #fileIO set(TORQUE_APP_PASSWORD "changeme" CACHE STRING "zip file password") advanced_option(TORQUE_DISABLE_VIRTUAL_MOUNT_SYSTEM "Disable virtual mount system" OFF) From ec2a55c535559d1a468925e9f95869ea66bcb9a8 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 24 May 2026 13:23:46 +0100 Subject: [PATCH 4/6] few fixes Only export DSQ if we have a realmesh to pair with it. When looking for the companion shape use the last '_' to strip instead of the first --- .../source/gui/editor/guiShapeEdPreview.cpp | 2 +- Engine/source/ts/assimp/assimpShapeLoader.cpp | 20 ++++- .../source/ts/collada/colladaShapeLoader.cpp | 18 ++++- Engine/source/ts/tsShape.cpp | 80 ++++++++++++++++++- 4 files changed, 113 insertions(+), 7 deletions(-) diff --git a/Engine/source/gui/editor/guiShapeEdPreview.cpp b/Engine/source/gui/editor/guiShapeEdPreview.cpp index d5c0e2b6c..41a17d786 100644 --- a/Engine/source/gui/editor/guiShapeEdPreview.cpp +++ b/Engine/source/gui/editor/guiShapeEdPreview.cpp @@ -410,7 +410,7 @@ bool GuiShapeEdPreview::findCompanionShape(const Torque::Path& dsqPath, Torque:: // AssimpLoader and ColladaLoader exports as "modelname_sequencename.dsq" alongside "modelname.cached.dts" // so strip everything from the last underscore to find the base name String fileName = dsqPath.getFileName(); - String::SizeType sep = fileName.find('_'); + String::SizeType sep = fileName.find('_',0, String::Right); if (sep != String::NPos) { diff --git a/Engine/source/ts/assimp/assimpShapeLoader.cpp b/Engine/source/ts/assimp/assimpShapeLoader.cpp index 5a52d4e44..d0560345a 100644 --- a/Engine/source/ts/assimp/assimpShapeLoader.cpp +++ b/Engine/source/ts/assimp/assimpShapeLoader.cpp @@ -991,7 +991,22 @@ static bool sReadAssimp(const Torque::Path &path, TSShape*& res_shape) if (tss) { TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import complete"); - Con::printf("[ASSIMP] Shape created successfully."); + + bool realMesh = false; + for (U32 i = 0; i < tss->meshes.size(); ++i) + { + if (tss->meshes[i] && tss->meshes[i]->getMeshType() != TSMesh::NullMeshType) + { + realMesh = true; + break; + } + } + + + if (realMesh) + Con::printf("[ASSIMP] Shape created successfully."); + else + Con::printf("[ASSIMP] Animation created successfully."); Torque::Path cachedPath(path); // Cache the model to a DTS file for faster loading next time. @@ -1004,7 +1019,8 @@ static bool sReadAssimp(const Torque::Path &path, TSShape*& res_shape) tss->write(&dtsStream); } - if (tss->sequences.size() > 0) + // only save dsq if we have a real mesh to pair with it. + if (tss->sequences.size() > 0 && realMesh) { Torque::Path dsqPath(cachedPath); dsqPath.setExtension("dsq"); diff --git a/Engine/source/ts/collada/colladaShapeLoader.cpp b/Engine/source/ts/collada/colladaShapeLoader.cpp index 65ea667aa..b94e5aa11 100644 --- a/Engine/source/ts/collada/colladaShapeLoader.cpp +++ b/Engine/source/ts/collada/colladaShapeLoader.cpp @@ -700,7 +700,21 @@ static bool sReadCollada(const Torque::Path& path, TSShape*& res_shape) if (tss) { TSShapeLoader::updateProgress(TSShapeLoader::Load_Complete, "Import complete"); - Con::printf("[COLLADA] Shape created successfully."); + + bool realMesh = false; + for (U32 i = 0; i < tss->meshes.size(); ++i) + { + if (tss->meshes[i] && tss->meshes[i]->getMeshType() != TSMesh::NullMeshType) + { + realMesh = true; + break; + } + } + + if(realMesh) + Con::printf("[COLLADA] Shape created successfully."); + else + Con::printf("[COLLADA] Animation created successfully."); Torque::Path cachedPath(path); // Cache the model to a DTS file for faster loading next time. @@ -716,7 +730,7 @@ static bool sReadCollada(const Torque::Path& path, TSShape*& res_shape) // Add collada materials to materials.tscript updateMaterialsScript(path, isSketchup); - if (tss->sequences.size() > 0) + if (tss->sequences.size() > 0 && realMesh) { Torque::Path dsqPath(cachedPath); dsqPath.setExtension("dsq"); diff --git a/Engine/source/ts/tsShape.cpp b/Engine/source/ts/tsShape.cpp index 72cc8af52..dc218ad72 100644 --- a/Engine/source/ts/tsShape.cpp +++ b/Engine/source/ts/tsShape.cpp @@ -204,11 +204,11 @@ void TSShape::compressionKey(U8* keyOut, U32 keyLen) } } -void TSShape::xorBufferAtOffset(void* data, U32 byteCount, +void TSShape::xorBufferAtOffset(void* buffer, U32 byteCount, U32 startOffset, const U8* key, U32 keyLen) { - U8* p = (U8*)data; + U8* p = (U8*)buffer; for (U32 i = 0; i < byteCount; ++i) p[i] ^= key[(startOffset + i) % keyLen]; } @@ -2280,6 +2280,82 @@ template<> void *Resource::create(const Torque::Path &path) ret = new TSShape; readSuccess = ret->read(&stream); } + else if (extension.equal("dsq", String::NoCase)) + { + // DSQ is an animation-only file. Try to find a companion shape to load + // sequences into; if none exists, create a minimal empty shape instead. + Torque::Path companionPath(path); + bool foundCompanion = false; + + // Look for basename.cached.dts or basename.dts alongside the DSQ + String fileName = path.getFileName(); + String::SizeType sep = fileName.find('_', 0, String::Right); + if (sep != String::NPos) + { + companionPath.setFileName(fileName.substr(0, sep)); + companionPath.setExtension("cached.dts"); + if (Torque::FS::IsFile(companionPath.getFullPath())) + foundCompanion = true; + + if (!foundCompanion) + { + companionPath.setExtension("dts"); + if (Torque::FS::IsFile(companionPath.getFullPath())) + foundCompanion = true; + } + } + + if (!foundCompanion) + { + companionPath.setFileName(fileName); + companionPath.setExtension("cached.dts"); + if (Torque::FS::IsFile(companionPath.getFullPath())) + foundCompanion = true; + } + + if (!foundCompanion) + { + companionPath.setExtension("dts"); + if (Torque::FS::IsFile(companionPath.getFullPath())) + foundCompanion = true; + } + + if (foundCompanion) + { + FileStream shapeStream; + shapeStream.open(companionPath.getFullPath(), Torque::FS::File::Read); + if (shapeStream.getStatus() == Stream::Ok) + { + ret = new TSShape; + if (!ret->read(&shapeStream)) + { + delete ret; + ret = NULL; + } + } + } + + if (!ret || !foundCompanion) + { + // No companion shape found; build a minimal empty shape so sequences + // have somewhere to live (matches ShapeAnimationAsset's expectations). + ret = new TSShape; + ret->createEmptyShape(); + } + + FileStream dsqStream; + dsqStream.open(path.getFullPath(), Torque::FS::File::Read); + if (dsqStream.getStatus() != Stream::Ok) + { + Con::errorf("Resource::create - Could not open DSQ '%s'", path.getFullPath().c_str()); + delete ret; + return NULL; + } + + readSuccess = ret->importSequences(&dsqStream, path); + if (!readSuccess) + Con::errorf("Resource::create - Failed to import sequences from '%s'", path.getFullPath().c_str()); + } else { const TSShape::ShapeRegistration* regInfo = TSShape::sFindRegInfo(extension); From 4f32fec3207da7eafa3dc50ac6487212a941d820 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Sun, 24 May 2026 13:56:02 +0100 Subject: [PATCH 5/6] Update tsShapeOldRead.cpp --- Engine/source/ts/tsShapeOldRead.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Engine/source/ts/tsShapeOldRead.cpp b/Engine/source/ts/tsShapeOldRead.cpp index 104e40840..3f2dd368b 100644 --- a/Engine/source/ts/tsShapeOldRead.cpp +++ b/Engine/source/ts/tsShapeOldRead.cpp @@ -435,7 +435,8 @@ bool TSShape::importSequences(Stream * s, const String& sequencePath) { // error -- don't support future version yet :> Con::errorf(ConsoleLogEntry::General, - "Sequence import failed: shape exporter newer than running executable."); + "Sequence import failed: attempt to load a version %i ddsq-animation, can currently only load version %i and before.", + smReadVersion, smVersion); return false; } if (smReadVersion<19) From d6c32f3e351d0cb17aecbc75b168b0cd50b0aa7f Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 26 May 2026 11:33:06 +0100 Subject: [PATCH 6/6] only dsq should call import sequence --- Engine/source/gui/editor/guiShapeEdPreview.cpp | 8 +++++++- Engine/source/ts/tsShapeOldRead.cpp | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Engine/source/gui/editor/guiShapeEdPreview.cpp b/Engine/source/gui/editor/guiShapeEdPreview.cpp index 41a17d786..2fc62c51a 100644 --- a/Engine/source/gui/editor/guiShapeEdPreview.cpp +++ b/Engine/source/gui/editor/guiShapeEdPreview.cpp @@ -476,8 +476,14 @@ bool GuiShapeEdPreview::setObjectShapeAsset(const char* assetId) ShapeAnimationAsset* asset = AssetDatabase.acquireAsset(id); StringTableEntry animPath = asset->getAnimationPath(); AssetDatabase.releaseAsset(id); - Torque::Path dsqPath(animPath); + String fileExt = String::ToLower(dsqPath.getExtension()); + + if (fileExt != String("dsq")) + { + return setObjectModel(animPath); + } + Torque::Path shapePath; if (!findCompanionShape(dsqPath, shapePath)) { diff --git a/Engine/source/ts/tsShapeOldRead.cpp b/Engine/source/ts/tsShapeOldRead.cpp index 3f2dd368b..bac4c85b7 100644 --- a/Engine/source/ts/tsShapeOldRead.cpp +++ b/Engine/source/ts/tsShapeOldRead.cpp @@ -435,7 +435,7 @@ bool TSShape::importSequences(Stream * s, const String& sequencePath) { // error -- don't support future version yet :> Con::errorf(ConsoleLogEntry::General, - "Sequence import failed: attempt to load a version %i ddsq-animation, can currently only load version %i and before.", + "Sequence import failed: attempt to load a version %i dsq-animation, can currently only load version %i and before.", smReadVersion, smVersion); return false; }