From 53ad8ccc6d0978e73686532a9839d401edf7b412 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sun, 28 Dec 2025 14:19:17 -0600 Subject: [PATCH 01/22] offload weapon/ammo counter update commands to the generic damagemodel --- .../game/data/DamageModel/DamageModel.tscript | 2 + .../DamageModel/scripts/client/client.tscript | 67 +++++++++++++++++++ .../scripts/server/commands.tscript | 12 ++++ 3 files changed, 81 insertions(+) create mode 100644 Templates/BaseGame/game/data/DamageModel/scripts/client/client.tscript create mode 100644 Templates/BaseGame/game/data/DamageModel/scripts/server/commands.tscript diff --git a/Templates/BaseGame/game/data/DamageModel/DamageModel.tscript b/Templates/BaseGame/game/data/DamageModel/DamageModel.tscript index 3268a5b9e..f3932781d 100644 --- a/Templates/BaseGame/game/data/DamageModel/DamageModel.tscript +++ b/Templates/BaseGame/game/data/DamageModel/DamageModel.tscript @@ -23,6 +23,7 @@ function DamageModel::onCreateGameServer(%this) %this.queueExec("./scripts/server/shapeBase"); %this.queueExec("./scripts/server/vehicle"); %this.queueExec("./scripts/server/player"); + %this.queueExec("./scripts/server/commands"); } //This is called when the server is shut down due to the game/map being exited @@ -35,6 +36,7 @@ function DamageModel::initClient(%this) { %this.queueExec("./guis/damageGuiOverlay.gui"); %this.queueExec("./scripts/client/playGui"); + %this.queueExec("./scripts/client/client"); } //This is called when a client connects to a server diff --git a/Templates/BaseGame/game/data/DamageModel/scripts/client/client.tscript b/Templates/BaseGame/game/data/DamageModel/scripts/client/client.tscript new file mode 100644 index 000000000..4f015a954 --- /dev/null +++ b/Templates/BaseGame/game/data/DamageModel/scripts/client/client.tscript @@ -0,0 +1,67 @@ + +// ---------------------------------------------------------------------------- +// WeaponHUD +// ---------------------------------------------------------------------------- + +// Update the Ammo Counter with current ammo, if not any then hide the counter. +function clientCmdSetAmmoAmountHud(%amount, %amountInClips) +{ + if (!%amount) + AmmoAmount.setVisible(false); + else + { + AmmoAmount.setVisible(true); + AmmoAmount.setText("Ammo: " @ %amount @ "/" @ %amountInClips); + } +} + +// Here we update the Weapon Preview image & reticle for each weapon. We also +// update the Ammo Counter (just so we don't have to call it separately). +// Passing an empty parameter ("") hides the HUD component. + +function clientCmdRefreshWeaponHUD(%amount, %preview, %ret, %zoomRet, %amountInClips) +{ + if (!%amount) + AmmoAmount.setVisible(false); + else + { + AmmoAmount.setVisible(true); + AmmoAmount.setText("Ammo: " @ %amount @ "/" @ %amountInClips); + } + + if (%preview $= "" || detag(%preview) $= "blank") + { + WeaponHUD.setVisible(false); + PreviewImage.setBitmap(""); + } + else + { + WeaponHUD.setVisible(true); + PreviewImage.setbitmap(detag(%preview)); + } + + if (%ret $= "" || detag(%ret) $= "blank") + { + Reticle.setVisible(false); + Reticle.setBitmap(""); + } + else + { + Reticle.setVisible(true); + Reticle.setbitmap(detag(%ret)); + } + + if (isObject(ZoomReticle) || detag(%zoomRet) $= "blank") + { + if (%zoomRet $= "" || detag(%zoomRet) $= "blank") + { + ZoomReticle.setVisible(false); + ZoomReticle.setBitmap(""); + } + else + { + ZoomReticle.setVisible(true); + ZoomReticle.setBitmap(detag(%zoomRet)); + } + } +} \ No newline at end of file diff --git a/Templates/BaseGame/game/data/DamageModel/scripts/server/commands.tscript b/Templates/BaseGame/game/data/DamageModel/scripts/server/commands.tscript new file mode 100644 index 000000000..1a03625ce --- /dev/null +++ b/Templates/BaseGame/game/data/DamageModel/scripts/server/commands.tscript @@ -0,0 +1,12 @@ +// ---------------------------------------------------------------------------- +// weapon HUD +// ---------------------------------------------------------------------------- +function GameConnection::setAmmoAmountHud(%client, %amount, %amountInClips ) +{ + commandToClient(%client, 'SetAmmoAmountHud', %amount, %amountInClips); +} + +function GameConnection::RefreshWeaponHud(%client, %amount, %preview, %ret, %zoomRet, %amountInClips) +{ + commandToClient(%client, 'RefreshWeaponHud', %amount, %preview, %ret, %zoomRet, %amountInClips); +} \ No newline at end of file From 235be5eccd6f342f23f170c9931521d1389ca27f Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 29 Dec 2025 17:36:08 -0600 Subject: [PATCH 02/22] remove damagemodel from the game subgroup --- Templates/BaseGame/game/data/DamageModel/DamageModel.module | 1 - 1 file changed, 1 deletion(-) diff --git a/Templates/BaseGame/game/data/DamageModel/DamageModel.module b/Templates/BaseGame/game/data/DamageModel/DamageModel.module index 26de8f0dc..3fb5ab454 100644 --- a/Templates/BaseGame/game/data/DamageModel/DamageModel.module +++ b/Templates/BaseGame/game/data/DamageModel/DamageModel.module @@ -5,7 +5,6 @@ scriptFile="DamageModel.tscript" CreateFunction="onCreate" DestroyFunction="onDestroy" - Group="Game" Dependencies="UI=1"> Date: Thu, 1 Jan 2026 15:07:33 +0000 Subject: [PATCH 03/22] Update bitmapUtils.cpp add conversions for packed texture formats --- Engine/source/gfx/bitmap/bitmapUtils.cpp | 178 ++++++++++++++++++++++- 1 file changed, 171 insertions(+), 7 deletions(-) diff --git a/Engine/source/gfx/bitmap/bitmapUtils.cpp b/Engine/source/gfx/bitmap/bitmapUtils.cpp index 903fde81e..6d545b3c0 100644 --- a/Engine/source/gfx/bitmap/bitmapUtils.cpp +++ b/Engine/source/gfx/bitmap/bitmapUtils.cpp @@ -288,6 +288,13 @@ enum ChannelSemantic : U8 CH_B }; +struct PackedChannelDesc +{ + ChannelSemantic semantic; + U8 bits; + U8 shift; +}; + //-------------------------------------------------------------------------------- // Bitmap format descriptor struct GBitmapFormatDesc @@ -299,7 +306,9 @@ struct GBitmapFormatDesc bool premultiplied; bool isFloat; U8 bytesPerChannel; - + bool isPacked = false; + U8 bytesPerPixel = 0; + PackedChannelDesc packed[4] = {}; bool is8() const { return !isFloat && bytesPerChannel == 1; } bool is16() const { return !isFloat && bytesPerChannel == 2; } }; @@ -316,17 +325,47 @@ GBitmapFormatDesc getFormatDesc(GFXFormat fmt) case GFXFormatL8: return { 1, {CH_L, CH_NONE, CH_NONE, CH_NONE}, STBIR_TYPE_UINT8, false, false, false, 1 }; case GFXFormatA4L4: - return { 2, {CH_L, CH_A, CH_NONE, CH_NONE}, STBIR_TYPE_UINT8, false, false, false, 1 }; + return { 2, {CH_L, CH_A, CH_NONE, CH_NONE}, STBIR_TYPE_UINT8, false, false, false, 0, true, 1, + { + { CH_L, 4, 0 }, // CH_L must be declared before CH_A + { CH_A, 4, 4 } + } + }; // 16-bit formats case GFXFormatR5G6B5: - return { 3, {CH_R, CH_G, CH_B, CH_NONE}, STBIR_TYPE_UINT8, false, false, false, 1 }; + return { 3, {CH_R, CH_G, CH_B, CH_NONE}, STBIR_TYPE_UINT8, false, false, false, 0, true, 2, + { + { CH_R, 5, 11 }, + { CH_G, 6, 5 }, + { CH_B, 5, 0 } + } + }; case GFXFormatR5G5B5A1: - return { 4, {CH_R, CH_G, CH_B, CH_A}, STBIR_TYPE_UINT8, false, false, false, 1 }; + return { 4, {CH_R, CH_G, CH_B, CH_A}, STBIR_TYPE_UINT8, false, false, false, 0, true, 2, + { + { CH_R, 5, 11 }, + { CH_G, 5, 6 }, + { CH_B, 5, 1 }, + { CH_A, 1, 0 } + } + }; case GFXFormatR5G5B5X1: - return { 4, {CH_R, CH_G, CH_B, CH_NONE}, STBIR_TYPE_UINT8, false, false, false, 1 }; + return { 4, {CH_R, CH_G, CH_B, CH_NONE}, STBIR_TYPE_UINT8, false, false, false, 0, true, 2, + { + { CH_R, 5, 11 }, + { CH_G, 5, 6 }, + { CH_B, 5, 1 }, + { CH_A, 1, 0 } + } + }; case GFXFormatA8L8: - return { 2, {CH_L, CH_A, CH_NONE, CH_NONE}, STBIR_TYPE_UINT8, false, false, false, 1 }; + return { 2, {CH_L, CH_A, CH_NONE, CH_NONE}, STBIR_TYPE_UINT8, false, false, false, 0, true, 2, + { + { CH_L, 8, 0 }, // CH_L must be declared before CH_A + { CH_A, 8, 8 } + } + }; case GFXFormatL16: return { 1, {CH_L, CH_NONE, CH_NONE, CH_NONE}, STBIR_TYPE_UINT16, false, false, false, 2 }; case GFXFormatR16F: @@ -403,10 +442,73 @@ inline float linearToSrgb(float c) return (c <= 0.0031308f) ? c * 12.92f : 1.055f * powf(c, 1.f / 2.4f) - 0.055f; } +static inline LinearPixel loadPackedPixel( + const void* src, + const GBitmapFormatDesc& fmt, + U32 index) +{ + LinearPixel p; + p.r = p.g = p.b = 0.f; + p.a = 1.f; + + const U8* base = (const U8*)src + index * fmt.bytesPerPixel; + + U32 raw = 0; + dMemcpy(&raw, base, fmt.bytesPerPixel); + +#ifdef TORQUE_BIG_ENDIAN + if (fmt.bytesPerPixel == 2) + raw = convertLEndianToHost((U16)raw); + else if (fmt.bytesPerPixel == 4) + raw = convertLEndianToHost((U32)raw); +#endif + + for (U32 i = 0; i < fmt.channels; ++i) + { + const PackedChannelDesc& ch = fmt.packed[i]; + + U32 mask = (1u << ch.bits) - 1u; + U32 v = (raw >> ch.shift) & mask; + + // Expand to 8-bit exactly like GBitmap getColor code + U8 expanded8; + if (ch.bits == 5) + expanded8 = (v << 3) | (v >> 2); + else if (ch.bits == 6) + expanded8 = (v << 2) | (v >> 4); + else if (ch.bits == 4) + expanded8 = v * 17; + else if (ch.bits == 1) + expanded8 = v ? 255 : 0; + else // 8-bit + expanded8 = (U8)v; + + float f = expanded8 / 255.f; + + switch (ch.semantic) + { + case CH_R: p.r = f; break; + case CH_G: p.g = f; break; + case CH_B: p.b = f; break; + case CH_A: p.a = f; break; + case CH_L: + p.r = p.g = p.b = f; + break; + default: + break; + } + } + + return p; +} + //-------------------------------------------------------------------------------- // Load a pixel from src format into LinearPixel static inline LinearPixel loadPixel(const void* src, const GBitmapFormatDesc& fmt, U32 index) { + if (fmt.isPacked) + return loadPackedPixel(src, fmt, index); + LinearPixel p; const U8* base = (const U8*)src + index * fmt.channels * fmt.bytesPerChannel; @@ -441,10 +543,72 @@ static inline LinearPixel loadPixel(const void* src, const GBitmapFormatDesc& fm return p; } +static inline void storePackedPixel( + void* dst, + const GBitmapFormatDesc& fmt, + U32 index, + const LinearPixel& p) +{ + U32 raw = 0; + + for (U32 i = 0; i < fmt.channels; ++i) + { + const PackedChannelDesc& ch = fmt.packed[i]; + + float v = 0.f; + switch (ch.semantic) + { + case CH_R: v = p.r; break; + case CH_G: v = p.g; break; + case CH_B: v = p.b; break; + case CH_A: v = p.a; break; + case CH_L: v = p.r; break; // legacy behavior + default: continue; + } + + // Clamp + v = mClamp(v, 0.f, 1.f); + + // Canonical float → U8 + U32 expanded8 = U32(v * 255.f + 0.5f); + + // Contract to bit depth EXACTLY like Gbitmap setColor code + U32 maxv = (1u << ch.bits) - 1u; + U32 iv; + + if (ch.bits == 8) + iv = expanded8; + else + iv = expanded8 * maxv / 255; + + raw |= (iv & maxv) << ch.shift; + } + +#ifdef TORQUE_BIG_ENDIAN + if (fmt.bytesPerPixel == 2) + { + U16 v = convertLEndianToHost((U16)raw); + dMemcpy((U8*)dst + index * fmt.bytesPerPixel, &v, 2); + } + else if (fmt.bytesPerPixel == 4) + { + U32 v = convertLEndianToHost(raw); + dMemcpy((U8*)dst + index * fmt.bytesPerPixel, &v, 4); + } +#else + dMemcpy((U8*)dst + index * fmt.bytesPerPixel, &raw, fmt.bytesPerPixel); +#endif +} //-------------------------------------------------------------------------------- // Store a LinearPixel into dst format static inline void storePixel(void* dst, const GBitmapFormatDesc& fmt, U32 index, const LinearPixel& p) { + if (fmt.isPacked) + { + storePackedPixel(dst, fmt, index, p); + return; + } + U8* base = (U8*)dst + index * fmt.channels * fmt.bytesPerChannel; for (U32 c = 0; c < fmt.channels; ++c) { @@ -455,7 +619,7 @@ static inline void storePixel(void* dst, const GBitmapFormatDesc& fmt, U32 index case CH_G: v = p.g; break; case CH_B: v = p.b; break; case CH_A: v = p.a; break; - case CH_L: v = (p.r + p.g + p.b) / 3.f; break; + case CH_L: v = p.r; break; default: break; } From 1b528b818e081b24b93d18c4edf0c037041e4f71 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Fri, 2 Jan 2026 11:37:36 +0000 Subject: [PATCH 04/22] Update bitmapUtils.h the conversion functions were making assumptions that were wrong based on mant being 0 and the out being 0 completely on small numbers also if the sign was flipped it rounded to 0. Also simplified --- Engine/source/gfx/bitmap/bitmapUtils.h | 90 ++++++++++++++------------ 1 file changed, 49 insertions(+), 41 deletions(-) diff --git a/Engine/source/gfx/bitmap/bitmapUtils.h b/Engine/source/gfx/bitmap/bitmapUtils.h index 10053fdf7..54607bfea 100644 --- a/Engine/source/gfx/bitmap/bitmapUtils.h +++ b/Engine/source/gfx/bitmap/bitmapUtils.h @@ -61,45 +61,39 @@ inline F32 convertHalfToFloat(U16 h) U32 exp = (h >> 10) & 0x0000001F; U32 mant = h & 0x000003FF; - U32 outSign = sign << 31; - U32 outExp, outMant; + U32 out; if (exp == 0) { if (mant == 0) { // Zero - outExp = 0; - outMant = 0; + out = sign; } else { // Subnormal number -> normalize - exp = 1; - while ((mant & 0x00000400) == 0) + exp = 127 - 14; + while ((mant & 0x0400) == 0) { mant <<= 1; - exp -= 1; + exp--; } - mant &= 0x000003FF; - outExp = (exp + (127 - 15)) << 23; - outMant = mant << 13; + mant &= 0x03FF; + out = sign | (exp << 23) | (mant << 13); } } else if (exp == 31) { // Inf or NaN - outExp = 0xFF << 23; - outMant = mant ? (mant << 13) : 0; + out = sign | 0x7F800000 | (mant << 13); } else { // Normalized - outExp = (exp + (127 - 15)) << 23; - outMant = mant << 13; + out = sign | ((exp + (127 - 15)) << 23) | (mant << 13); } - U32 out = outSign | outExp | outMant; F32 result; dMemcpy(&result, &out, sizeof(F32)); return result; @@ -110,40 +104,54 @@ inline U16 convertFloatToHalf(F32 f) U32 bits; dMemcpy(&bits, &f, sizeof(U32)); - U32 sign = (bits >> 16) & 0x00008000; - U32 exp = ((bits >> 23) & 0x000000FF) - (127 - 15); + U32 sign = (bits >> 16) & 0x8000; + U32 exp = (bits >> 23) & 0xFF; U32 mant = bits & 0x007FFFFF; - if (exp <= 0) - { - if (exp < -10) - return (U16)sign; // Too small => 0 - mant = (mant | 0x00800000) >> (1 - exp); - return (U16)(sign | (mant >> 13)); - } - else if (exp == 0xFF - (127 - 15)) + if (exp == 255) { + // Inf or NaN if (mant == 0) - { - // Inf return (U16)(sign | 0x7C00); - } - else - { - // NaN - mant >>= 13; - return (U16)(sign | 0x7C00 | mant | (mant == 0)); - } + mant >>= 13; + return (U16)(sign | 0x7C00 | mant | (mant == 0)); } - else + + S32 newExp = (S32)exp - 127 + 15; + + if (newExp >= 31) { - if (exp > 30) - { - // Overflow => Inf - return (U16)(sign | 0x7C00); - } - return (U16)(sign | (exp << 10) | (mant >> 13)); + // Overflow → Inf + return (U16)(sign | 0x7C00); } + else if (newExp <= 0) + { + // Subnormal or underflow + if (newExp < -10) + return (U16)sign; + + mant |= 0x800000; + U32 shifted = mant >> (1 - newExp); + + // Round to nearest-even + if (shifted & 0x00001000) + shifted += 0x00002000; + + return (U16)(sign | (shifted >> 13)); + } + + // Normalized with rounding + mant += 0x00001000; + if (mant & 0x00800000) + { + mant = 0; + newExp++; + } + + if (newExp >= 31) + return (U16)(sign | 0x7C00); + + return (U16)(sign | (newExp << 10) | (mant >> 13)); } // Convert a single 16-bit value (0..65535) to 8-bit (0..255) From 56db8cadd004c6a49d1004f2a01d2e9c2f83a676 Mon Sep 17 00:00:00 2001 From: JeffR Date: Sat, 3 Jan 2026 02:45:14 -0600 Subject: [PATCH 05/22] Adjusts the setup of the AB preview buttons so we initialize the fields in the new block rather than risking the button being asleep while trying to set bitmaps. --- .../assetBrowser/scripts/assetBrowser.tscript | 95 ++++++++----------- 1 file changed, 42 insertions(+), 53 deletions(-) diff --git a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetBrowser.tscript b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetBrowser.tscript index 2406e4263..81c2deb0d 100644 --- a/Templates/BaseGame/game/tools/assetBrowser/scripts/assetBrowser.tscript +++ b/Templates/BaseGame/game/tools/assetBrowser/scripts/assetBrowser.tscript @@ -686,22 +686,7 @@ function AssetBrowser::buildAssetPreview( %this, %asset, %moduleName ) %tooltip = %assetName; %textBottomPad = 20; - - %previewButton = new GuiIconButtonCtrl() - { - class = "AssetBrowserPreviewButton"; - useMouseEvents = true; - iconLocation = "Center"; - sizeIconToButton = true; - makeIconSquare = true; - textLocation = "Bottom"; - extent = %previewSize.x SPC %previewSize.y + %textBottomPad; - buttonType = "RadioButton"; - buttonMargin = "0 -10"; - profile = ToolsGuiDefaultIconBtnProfile; - assetBrowser = %this; - }; - + %previewScaleSize = %this-->previewSlider.getValue(); if(%previewScaleSize $= "") @@ -710,29 +695,6 @@ function AssetBrowser::buildAssetPreview( %this, %asset, %moduleName ) %this-->previewSlider.setValue(1); } - if(%previewScaleSize == 0 || startsWith(%this.dirHandler.currentAddress, "Creator")) - { - %previewButton.iconLocation = "Left"; - %previewButton.textLocation = "Right"; - %previewButton.setextent(160,34); - %previewButton.buttonMargin = "8 8"; - %previewButton.textMargin = "6"; - - %this.previewListMode = true; - } - else - { - %size = %previewSize.x * %previewScaleSize; - %previewButton.setextent(%size,%size + %textBottomPad); - - %this.previewListMode = false; - } - - //%previewButton.extent = %previewSize.x + %previewBounds SPC %previewSize.y + %previewBounds + 24; - %previewButton.assetName = %assetName; - %previewButton.moduleName = %moduleName; - %previewButton.assetType = %assetType; - if(%this.selectMode) { %doubleClickCommand = %this @ ".selectAsset( "@ %this @ ".selectedAsset );"; @@ -754,22 +716,49 @@ function AssetBrowser::buildAssetPreview( %this, %asset, %moduleName ) if(%this.previewData.doubleClickCommand !$= "") %doubleClickCommand = %this.previewData.doubleClickCommand; + + %previewButton = new GuiIconButtonCtrl() + { + class = "AssetBrowserPreviewButton"; + useMouseEvents = true; + iconLocation = "Center"; + sizeIconToButton = true; + makeIconSquare = true; + textLocation = "Bottom"; + extent = %previewSize.x SPC %previewSize.y + %textBottomPad; + buttonType = "RadioButton"; + buttonMargin = "0 -10"; + profile = ToolsGuiDefaultIconBtnProfile; + tooltip = %this.previewData.tooltip; + assetBrowser = %this; + assetName = %assetName; + moduleName = %moduleName; + assetType = %assetType; + bitmapAsset = %this.previewData.previewImage; + Command = %this @ ".updateSelection( $ThisControl.assetName, $ThisControl.moduleName );"; + altCommand = %doubleClickCommand; + text = %this.previewData.assetName; + originalAssetName = %this.previewData.assetName; + }; - %previewButton.assetName = %assetName; - %previewButton.moduleName = %moduleName; - %previewButton.assetType = %assetType; - %previewButton.assetBrowser = %this; + if(%previewScaleSize == 0 || startsWith(%this.dirHandler.currentAddress, "Creator")) + { + %previewButton.iconLocation = "Left"; + %previewButton.textLocation = "Right"; + %previewButton.setextent(160,34); + %previewButton.buttonMargin = "8 8"; + %previewButton.textMargin = "6"; + + %this.previewListMode = true; + } + else + { + %size = %previewSize.x * %previewScaleSize; + %previewButton.setextent(%size,%size + %textBottomPad); + + %this.previewListMode = false; + } - %previewButton.setBitmap(%this.previewData.previewImage); - - %previewButton.profile = "AssetBrowserPreview" @ %previewButton.assetType; - %previewButton.tooltip = %this.previewData.tooltip; - %previewButton.Command = %this @ ".updateSelection( $ThisControl.assetName, $ThisControl.moduleName );"; - %previewButton.altCommand = %doubleClickCommand; - - %previewButton.text = %this.previewData.assetName; - %previewButton.text.originalAssetName = %this.previewData.assetName; - // add to the gui control array %this-->assetList.add(%previewButton); From 322d3bb0934f4fb8ef30d620a2a23cd56d23dc7f Mon Sep 17 00:00:00 2001 From: JeffR Date: Wed, 7 Jan 2026 17:57:53 -0600 Subject: [PATCH 06/22] Allows setBitmap calls on IconButtons to set the bitmap data even if the control isn't awake to avoid issues of a set call being 'skipped' --- Engine/source/gui/buttons/guiIconButtonCtrl.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Engine/source/gui/buttons/guiIconButtonCtrl.cpp b/Engine/source/gui/buttons/guiIconButtonCtrl.cpp index 9c0cd7517..10f84ba28 100644 --- a/Engine/source/gui/buttons/guiIconButtonCtrl.cpp +++ b/Engine/source/gui/buttons/guiIconButtonCtrl.cpp @@ -202,11 +202,10 @@ bool GuiIconButtonCtrl::resize(const Point2I &newPosition, const Point2I &newExt void GuiIconButtonCtrl::setBitmap(const char *name) { + _setBitmap(name); if(!isAwake()) return; - _setBitmap(name); - // So that extent is recalculated if autoSize is set. resize( getPosition(), getExtent() ); From 4481bdc726f453eb965332e5f9d75f284f863574 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Tue, 13 Jan 2026 20:02:37 -0600 Subject: [PATCH 07/22] don't use MarkerObjectTypes for navmesh generation do use a minium radius for clicking on a link to select it in the tooling --- Engine/source/navigation/navMesh.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Engine/source/navigation/navMesh.cpp b/Engine/source/navigation/navMesh.cpp index 2c3d1b8c1..420c7dbdd 100644 --- a/Engine/source/navigation/navMesh.cpp +++ b/Engine/source/navigation/navMesh.cpp @@ -454,8 +454,8 @@ S32 NavMesh::getLink(const Point3F &pos) { if(mDeleteLinks[i]) continue; - SphereF start(getLinkStart(i), mLinkRads[i]); - SphereF end(getLinkEnd(i), mLinkRads[i]); + SphereF start(getLinkStart(i), mMax(mLinkRads[i],0.25f)); + SphereF end(getLinkEnd(i), mMax(mLinkRads[i], 0.25f)); if(start.isContained(pos) || end.isContained(pos)) return i; } @@ -653,7 +653,7 @@ DefineEngineMethod(NavMesh, deleteLinks, void, (),, static void buildCallback(SceneObject* object, void* key) { SceneContainer::CallbackInfo* info = reinterpret_cast(key); - if (!object->mPathfindingIgnore) + if (!object->mPathfindingIgnore && (object->getTypeMask() & MarkerObjectType) == 0) object->buildPolyList(info->context, info->polyList, info->boundingBox, info->boundingSphere); } From b8f7d186d30daabb39b1edafa11dee24d200b5e1 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Wed, 14 Jan 2026 09:21:57 -0600 Subject: [PATCH 08/22] default controlables and items to mPathfindingIgnore = true; --- Engine/source/T3D/item.cpp | 1 + Engine/source/T3D/player.cpp | 2 +- Engine/source/T3D/vehicles/vehicle.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Engine/source/T3D/item.cpp b/Engine/source/T3D/item.cpp index 18f52ff5c..e8ca24611 100644 --- a/Engine/source/T3D/item.cpp +++ b/Engine/source/T3D/item.cpp @@ -312,6 +312,7 @@ IMPLEMENT_CALLBACK( Item, onLeaveLiquid, void, ( const char* objID, const char* Item::Item() { mTypeMask |= ItemObjectType | DynamicShapeObjectType; + mPathfindingIgnore = true; mDataBlock = 0; mStatic = false; mRotate = false; diff --git a/Engine/source/T3D/player.cpp b/Engine/source/T3D/player.cpp index d9f334820..c72dd94fa 100644 --- a/Engine/source/T3D/player.cpp +++ b/Engine/source/T3D/player.cpp @@ -1552,7 +1552,7 @@ ConsoleDocClass( Player, Player::Player() { mTypeMask |= PlayerObjectType | DynamicShapeObjectType; - + mPathfindingIgnore = true; mDelta.pos = mAnchorPoint = Point3F(0,0,100); mDelta.rot = mDelta.head = Point3F(0,0,0); mDelta.rotOffset.set(0.0f,0.0f,0.0f); diff --git a/Engine/source/T3D/vehicles/vehicle.cpp b/Engine/source/T3D/vehicles/vehicle.cpp index c1750b61d..fa7b5b160 100644 --- a/Engine/source/T3D/vehicles/vehicle.cpp +++ b/Engine/source/T3D/vehicles/vehicle.cpp @@ -376,7 +376,7 @@ Vehicle::Vehicle() { mDataBlock = 0; mTypeMask |= VehicleObjectType | DynamicShapeObjectType; - + mPathfindingIgnore = true; mDelta.pos = Point3F(0,0,0); mDelta.posVec = Point3F(0,0,0); mDelta.warpTicks = mDelta.warpCount = 0; From 7d020b93798f9fe2b0b57c8b5b1772f174bbbc3d Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sat, 17 Jan 2026 12:44:41 -0600 Subject: [PATCH 09/22] typofix --- Engine/source/T3D/fx/particle.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/T3D/fx/particle.cpp b/Engine/source/T3D/fx/particle.cpp index 932a1a19d..8186853b8 100644 --- a/Engine/source/T3D/fx/particle.cpp +++ b/Engine/source/T3D/fx/particle.cpp @@ -535,7 +535,7 @@ bool ParticleData::onAdd() { Con::warnf(ConsoleLogEntry::General, "ParticleData(%s) bad value(s) for animTexTiling [%d or %d <= 0], invalid datablock", - animTexTiling.x, animTexTiling.y, getName()); + getName(), animTexTiling.x, animTexTiling.y); return false; } From ae9ae1db4495aa5003add70039f44338cd399a60 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sat, 17 Jan 2026 13:17:02 -0600 Subject: [PATCH 10/22] provide a global scriped variable for TSShape::smMaxSkinBones allows per project to either expand or educe the cap depending on needs --- Engine/source/ts/tsShapeInstance.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Engine/source/ts/tsShapeInstance.cpp b/Engine/source/ts/tsShapeInstance.cpp index deb96ee67..2a7cb9c35 100644 --- a/Engine/source/ts/tsShapeInstance.cpp +++ b/Engine/source/ts/tsShapeInstance.cpp @@ -75,6 +75,10 @@ MODULE_BEGIN( TSShapeInstance ) "@brief Enables mesh instancing on non-skin meshes that have less that this count of verts.\n" "The default value is 2000. Higher values can degrade performance.\n" "@ingroup Rendering\n" ); + + Con::addVariable("$MaxSkinBones", TypeS32, &TSShape::smMaxSkinBones, + "@brief Max number of bones allowed by a given shape for hardwar skinning. Default 70\n" + "@ingroup Rendering\n"); } MODULE_END; From 311c3f224d879df440a31d4663571fd777932595 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sun, 18 Jan 2026 17:43:25 -0600 Subject: [PATCH 11/22] expanded movement state data for aitonrollers status tracking and scipt tests for stopped, inair (above a minum) and inwater --- Engine/source/T3D/AI/AIController.cpp | 30 +++++++++++++++++++++++++++ Engine/source/T3D/AI/AIController.h | 6 +++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/Engine/source/T3D/AI/AIController.cpp b/Engine/source/T3D/AI/AIController.cpp index d798585c4..a2ba15cdc 100644 --- a/Engine/source/T3D/AI/AIController.cpp +++ b/Engine/source/T3D/AI/AIController.cpp @@ -180,6 +180,11 @@ bool AIController::getAIMove(Move* movePtr) if (obj->getContainer()->castRay(start, end, StaticShapeObjectType, &info)) { getNav()->repath(); + mMovement.mInAir = false; + } + else + { + mMovement.mInAir = true; } obj->enableCollision(); getGoal()->mInRange = false; @@ -307,6 +312,14 @@ void AIController::Movement::onStuck() #endif } +bool AIController::Movement::isInWater() +{ + ShapeBase* sbo = dynamic_cast(getCtrl()->getAIInfo()->mObj.getPointer()); + if (!sbo) return false; + F32 waterDepth = 0.0f; + return sbo->getWaterCoverage() > 0.0f; +} + DefineEngineMethod(AIController, setMoveSpeed, void, (F32 speed), , "@brief Sets the move speed for an AI object.\n\n" @@ -335,6 +348,23 @@ DefineEngineMethod(AIController, stop, void, (), , object->mMovement.stopMove(); } +DefineEngineMethod(AIController, isStopped, bool, (), , + "@brief is the player moving?.\n\n") +{ + return object->mMovement.isStopped(); +} + +DefineEngineMethod(AIController, isInAir, bool, (), , + "@brief is the player moving?.\n\n") +{ + return object->mMovement.isInAir(); +} + +DefineEngineMethod(AIController, isInWater, bool, (), , + "@brief is the player in water?.\n\n") +{ + return object->mMovement.isInWater(); +} /** * Set the state of a movement trigger. diff --git a/Engine/source/T3D/AI/AIController.h b/Engine/source/T3D/AI/AIController.h index 340ee5d04..dcc46f661 100644 --- a/Engine/source/T3D/AI/AIController.h +++ b/Engine/source/T3D/AI/AIController.h @@ -85,10 +85,11 @@ public: AIController* mControllerRef; AIController* getCtrl() { return mControllerRef; }; MoveState mMoveState; + bool mInAir = false; F32 mMoveSpeed = 1.0; void setMoveSpeed(F32 speed) { mMoveSpeed = speed; }; F32 getMoveSpeed() { return mMoveSpeed; }; - bool mMoveSlowdown; // Slowdown as we near the destination + bool mMoveSlowdown = false; // Slowdown as we near the destination Point3F mLastLocation; // For stuck check S32 mMoveStuckTestCountdown; // The current countdown until at AI starts to check if it is stuck Point3F mAimLocation; @@ -96,6 +97,9 @@ public: bool mMoveTriggers[MaxTriggerKeys]; void stopMove(); void onStuck(); + bool isStopped() { return mMoveState == ModeStop; }; + bool isInAir() { return mInAir; }; + bool isInWater(); } mMovement; struct TriggerState From 8411ae3c4bc4c663f35df5e1e29012e891cf18be Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Sun, 18 Jan 2026 17:51:33 -0600 Subject: [PATCH 12/22] kill pointless line --- Engine/source/T3D/AI/AIController.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Engine/source/T3D/AI/AIController.cpp b/Engine/source/T3D/AI/AIController.cpp index a2ba15cdc..8ace880f1 100644 --- a/Engine/source/T3D/AI/AIController.cpp +++ b/Engine/source/T3D/AI/AIController.cpp @@ -316,7 +316,6 @@ bool AIController::Movement::isInWater() { ShapeBase* sbo = dynamic_cast(getCtrl()->getAIInfo()->mObj.getPointer()); if (!sbo) return false; - F32 waterDepth = 0.0f; return sbo->getWaterCoverage() > 0.0f; } From 0f827ac89ea72bd12df2765178213c0e52673678 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 19 Jan 2026 11:15:42 -0600 Subject: [PATCH 13/22] dont try and recreate singletons --- .../scripts/materialEditor.ed.tscript | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Templates/BaseGame/game/tools/materialEditor/scripts/materialEditor.ed.tscript b/Templates/BaseGame/game/tools/materialEditor/scripts/materialEditor.ed.tscript index 0ff1c9bf7..3640a276c 100644 --- a/Templates/BaseGame/game/tools/materialEditor/scripts/materialEditor.ed.tscript +++ b/Templates/BaseGame/game/tools/materialEditor/scripts/materialEditor.ed.tscript @@ -641,16 +641,14 @@ function MaterialEditorGui::setActiveMaterial( %this, %material ) MaterialEditorGui.currentMaterial = %material; MaterialEditorGui.lastMaterial = %material; - // we create or recreate a material to hold in a pristine state - // or, this crashes the ap. fix properly - BJR - //if(isObject(notDirtyMaterial)) - // notDirtyMaterial.delete(); - - singleton Material(notDirtyMaterial) + if(!isObject(notDirtyMaterial)) { - mapTo = "matEd_mappedMat"; - diffuseMapAsset[0] = "ToolsModule:matEd_mappedMat_image"; - }; + singleton Material(notDirtyMaterial) + { + mapTo = "matEd_mappedMat"; + diffuseMapAsset[0] = "ToolsModule:matEd_mappedMat_image"; + }; + } // Converts the texture files into absolute paths. MaterialEditorGui.convertTextureFields(); From 15f619c1aa0bcf9b757bf102537bb1037f1fd478 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 19 Jan 2026 12:00:10 -0600 Subject: [PATCH 14/22] GameBaseData::onNewDataBlock reload state if live editing a datablock for an object you're piloting, reapply the control post onadd --- .../game/core/utility/scripts/gameObjectManagement.tscript | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Templates/BaseGame/game/core/utility/scripts/gameObjectManagement.tscript b/Templates/BaseGame/game/core/utility/scripts/gameObjectManagement.tscript index 51a4566fa..7fde01a71 100644 --- a/Templates/BaseGame/game/core/utility/scripts/gameObjectManagement.tscript +++ b/Templates/BaseGame/game/core/utility/scripts/gameObjectManagement.tscript @@ -82,10 +82,17 @@ function GameBaseData::onNewDataBlock(%this, %obj, %reload) { if (%reload) { + %client = ""; + if (isObject(%obj.client)) + %client = %obj.client; + if(%this.isMethod("onRemove")) %this.onRemove(%obj); if(%this.isMethod("onAdd")) %this.onAdd(%obj); + + if (%client) + %client.setControlObject(%obj); } } From d8f585c552c311a04ad0ff11d6fa248bdf3ffdb8 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 19 Jan 2026 12:28:10 -0600 Subject: [PATCH 15/22] fix decal instance preview update previews for instance selection just like we do db selection --- .../tools/decalEditor/decalEditorGui.tscript | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Templates/BaseGame/game/tools/decalEditor/decalEditorGui.tscript b/Templates/BaseGame/game/tools/decalEditor/decalEditorGui.tscript index 09f44b702..02a7a55c3 100644 --- a/Templates/BaseGame/game/tools/decalEditor/decalEditorGui.tscript +++ b/Templates/BaseGame/game/tools/decalEditor/decalEditorGui.tscript @@ -629,14 +629,30 @@ function DecalEditorGui::updateInstancePreview( %this, %material ) if( isObject( %material ) ) { - %previewImage = %material.getDiffuseMap(0); + %previewImage = %material.getDiffuseMapAsset(0); } - else + else if(AssetDatabase.isDeclaredAsset(%material)) { - if(AssetDatabase.isDeclaredAsset(%material)) + if(AssetDatabase.getAssetType(%material) $= "MaterialAsset") + { + %matAsset = AssetDatabase.acquireAsset(%material); + + %previewImage = %matAsset.materialDefinitionName.getDiffuseMap(0); + } + else if(AssetDatabase.getAssetType(%material) $= "ImageAsset") { %previewImage = %material; } + else + { + error("DecalEditorGui::updateDecalPreview() - Tried to set an invalid asset type for the editor preview!"); + return; + } + } + else + { + error("DecalEditorGui::updateDecalPreview() - Tried to set a non material, non asset value for the editor preview!"); + return; } DecalPreviewWindow-->instancePreview.setBitmap( getAssetPreviewImage(%previewImage) ); From 0b19bd9aca4bab1e3803c2c2264681f21674bac3 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 19 Jan 2026 21:28:48 -0600 Subject: [PATCH 16/22] fix apcrash when loading a new mission after painting decals but before properly selecting a new one to paint --- .../BaseGame/game/tools/decalEditor/decalEditorGui.tscript | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Templates/BaseGame/game/tools/decalEditor/decalEditorGui.tscript b/Templates/BaseGame/game/tools/decalEditor/decalEditorGui.tscript index 02a7a55c3..472ffdfc6 100644 --- a/Templates/BaseGame/game/tools/decalEditor/decalEditorGui.tscript +++ b/Templates/BaseGame/game/tools/decalEditor/decalEditorGui.tscript @@ -350,6 +350,13 @@ function DecalEditorGui::completeGizmoTransform( %this, %decalId, %nodeDetails ) function DecalEditorGui::onSleep( %this ) { + DecalEditorTreeView.clearSelection(); + DecalDataList.setSelected( DecalDataList.getSelectedItems(), false ); + + DecalEditorGui.currentDecalData = ""; + DecalPreviewWindow-->decalPreview.setBitmap(""); + DecalEditorGui.selDecalInstanceId = ""; + DecalPreviewWindow-->instancePreview.setBitmap(""); } function DecalEditorGui::syncNodeDetails( %this ) From 3a4e2f8a7ee8c847150e302a107dba285ca01261 Mon Sep 17 00:00:00 2001 From: marauder2k7 Date: Tue, 20 Jan 2026 08:26:51 +0000 Subject: [PATCH 17/22] Update astNodes.cpp strings that are arguments for a function need to be put into the global string table. PrecompileIdent does this for the function name etc so it pulls from that during compiled eval --- Engine/source/console/torquescript/astNodes.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Engine/source/console/torquescript/astNodes.cpp b/Engine/source/console/torquescript/astNodes.cpp index bea3dca4f..6fd28bc58 100644 --- a/Engine/source/console/torquescript/astNodes.cpp +++ b/Engine/source/console/torquescript/astNodes.cpp @@ -1529,6 +1529,7 @@ U32 FunctionDeclStmtNode::compileStmt(CodeStream& codeStream, U32 ip) CodeBlock::smInFunction = false; + setCurrentStringTable(&getGlobalStringTable()); // check for argument setup for (VarNode* walk = args; walk; walk = (VarNode*)((StmtNode*)walk)->getNext()) { @@ -1541,6 +1542,7 @@ U32 FunctionDeclStmtNode::compileStmt(CodeStream& codeStream, U32 ip) ip = walk->defaultValue->compile(codeStream, ip, walkType); } } + setCurrentStringTable(&getFunctionStringTable()); codeStream.emit(OP_FUNC_DECL); codeStream.emitSTE(fnName); From 89fb2990df54dab7d13808563657e63cb6e7d10b Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Tue, 20 Jan 2026 11:56:44 -0600 Subject: [PATCH 18/22] fix clicking a treeview element tripping expand/collapse set the wasselected check earlier in the test chain, and test against it so just selecting it doesn't automatically also trip expand/collapse --- Engine/source/gui/controls/guiTreeViewCtrl.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Engine/source/gui/controls/guiTreeViewCtrl.cpp b/Engine/source/gui/controls/guiTreeViewCtrl.cpp index daf014fb3..e0a2d90e0 100644 --- a/Engine/source/gui/controls/guiTreeViewCtrl.cpp +++ b/Engine/source/gui/controls/guiTreeViewCtrl.cpp @@ -3389,7 +3389,7 @@ void GuiTreeViewCtrl::onMouseDown(const GuiEvent & event) mPossibleRenameItem = NULL; mRenamingItem = NULL; mTempItem = NULL; - + bool wasSelected = isSelected(item); // if( event.modifier & SI_MULTISELECT ) { @@ -3473,8 +3473,7 @@ void GuiTreeViewCtrl::onMouseDown(const GuiEvent & event) else if ( !hitFlags.test(OnImage) ) { mTempItem = item; - - bool wasSelected = isSelected( item ); + bool multiSelect = getSelectedItemsCount() > 1; if( !wasSelected || !multiSelect ) @@ -3520,7 +3519,7 @@ void GuiTreeViewCtrl::onMouseDown(const GuiEvent & event) return; // - if ( mFullRowSelect || hitFlags.test( OnImage ) ) + if ((mFullRowSelect || hitFlags.test(OnImage)) && wasSelected) { item->setExpanded(!item->isExpanded()); if( !item->isInspectorData() && item->mState.test(Item::VirtualParent) ) From aacb41ce0b60179506f1b84bde1dc57705690065 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Wed, 21 Jan 2026 12:16:12 -0600 Subject: [PATCH 19/22] adress https://github.com/TorqueGameEngines/Torque3D/issues/1663 by ensureing if the players is a camrea, then %conection.camera knows it's the same thing --- .../clientServer/scripts/server/connectionToClient.tscript | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript b/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript index 73a325dbe..3d817d4fb 100644 --- a/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript +++ b/Templates/BaseGame/game/core/clientServer/scripts/server/connectionToClient.tscript @@ -199,8 +199,13 @@ function GameConnectionListener::onSetSpawnPointComplete( %this, %client ) %client.player = spawnObject(%client.spawnClass, %client.spawnDataBlock, "", %client.spawnProperties, %client.spawnScript); if(!isObject(%client.player)) + { error("Failed to spawn player object!"); - + } + else if (%client.player.isMemberOfClass("Camera")) + { + %client.camera = %client.player; + } if (!%client.player.isMemberOfClass(%client.spawnClass)) warn("Trying to spawn a class that does not derive from "@ %client.spawnClass); From b56867be55d0f677bcc652b166a15c064ed7808f Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 26 Jan 2026 12:12:23 -0600 Subject: [PATCH 20/22] clean up straight downward facing convexshape normals --- Engine/source/T3D/convexShape.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Engine/source/T3D/convexShape.cpp b/Engine/source/T3D/convexShape.cpp index 7e3759ce7..de992b9a1 100644 --- a/Engine/source/T3D/convexShape.cpp +++ b/Engine/source/T3D/convexShape.cpp @@ -1436,15 +1436,13 @@ void ConvexShape::_updateGeometry( bool updateCollision ) const Vector< U32 > &facePntMap = face.points; const Vector< ConvexShape::Triangle > &triangles = face.triangles; - const Point3F binormal = mCross(face.normal, face.tangent); - for (S32 j = 0; j < triangles.size(); j++) { for (S32 k = 0; k < 3; k++) { pVert->normal = face.normal; pVert->T = face.tangent; - pVert->B = mCross(face.normal,face.tangent); + pVert->B = mCross(face.normal, face.tangent); pVert->point = pointList[facePntMap[triangles[j][k]]]; pVert->texCoord = face.texcoords[triangles[j][k]]; pVert->texCoord2 = pVert->texCoord; @@ -1966,10 +1964,12 @@ void ConvexShape::Geometry::generate(const Vector< PlaneF > &planes, const Vecto U32 *vertMap = new U32[pntCount]; + Point3F binormal = mCross(newFace.normal, newFace.tangent); + MatrixF quadMat( true ); quadMat.setPosition( averagePnt ); quadMat.setColumn( 0, newFace.tangent ); - quadMat.setColumn( 1, mCross( newFace.normal, newFace.tangent ) ); + quadMat.setColumn( 1, binormal); quadMat.setColumn( 2, newFace.normal ); quadMat.inverse(); @@ -2055,8 +2055,8 @@ void ConvexShape::Geometry::generate(const Vector< PlaneF > &planes, const Vecto // Calculate texture coordinates for each point in this face. + if (newFace.normal.z < -0.9f) binormal = -binormal; - const Point3F binormal = mCross( newFace.normal, newFace.tangent ); PlaneF planey( newFace.centroid - 0.5f * binormal, binormal ); PlaneF planex( newFace.centroid - 0.5f * newFace.tangent, newFace.tangent ); From afb3ed0f27fd9630265bdd6f248fd25ad27ee5a4 Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Mon, 26 Jan 2026 12:45:57 -0600 Subject: [PATCH 21/22] more straightforward (and corrected) texcoord assignment --- Engine/source/T3D/convexShape.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Engine/source/T3D/convexShape.cpp b/Engine/source/T3D/convexShape.cpp index de992b9a1..1034c474f 100644 --- a/Engine/source/T3D/convexShape.cpp +++ b/Engine/source/T3D/convexShape.cpp @@ -2064,7 +2064,7 @@ void ConvexShape::Geometry::generate(const Vector< PlaneF > &planes, const Vecto for ( S32 j = 0; j < newFace.points.size(); j++ ) { - F32 x = planex.distToPlane( points[ newFace.points[ j ] ] ); + F32 x = -planex.distToPlane( points[ newFace.points[ j ] ] ); F32 y = planey.distToPlane( points[ newFace.points[ j ] ] ); if (!texOffset.empty()) @@ -2086,7 +2086,7 @@ void ConvexShape::Geometry::generate(const Vector< PlaneF > &planes, const Vecto if (vertFlip.size() > 0 && vertFlip[i]) y *= -1; - newFace.texcoords[j].set(-x, -y); + newFace.texcoords[j].set(x, y); } // Data verification tests. From 8874c250dae168e680d6274a82613705a540d05f Mon Sep 17 00:00:00 2001 From: AzaezelX Date: Fri, 30 Jan 2026 10:34:19 -0600 Subject: [PATCH 22/22] folllowup to https://github.com/TorqueGameEngines/Torque3D/pull/1661 if onImage (the up/down arrow, not to be confused with the class icon) go ahead and open/close the tree view wether we're selected or not --- Engine/source/gui/controls/guiTreeViewCtrl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Engine/source/gui/controls/guiTreeViewCtrl.cpp b/Engine/source/gui/controls/guiTreeViewCtrl.cpp index e0a2d90e0..e8f13d38b 100644 --- a/Engine/source/gui/controls/guiTreeViewCtrl.cpp +++ b/Engine/source/gui/controls/guiTreeViewCtrl.cpp @@ -3519,7 +3519,7 @@ void GuiTreeViewCtrl::onMouseDown(const GuiEvent & event) return; // - if ((mFullRowSelect || hitFlags.test(OnImage)) && wasSelected) + if (hitFlags.test(OnImage) || ((mFullRowSelect || hitFlags.test(OnIcon)) && wasSelected)) { item->setExpanded(!item->isExpanded()); if( !item->isInspectorData() && item->mState.test(Item::VirtualParent) )