[Scummvm-git-logs] scummvm master -> 4831f25b374be77d1574af9bd6bcae8ae1b8a6e3
bluegr
noreply at scummvm.org
Sun Nov 17 07:32:35 UTC 2024
This automated email contains information about 9 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
4d4dddb5f6 TINSEL: Build time option to skip intro
3ca5cb68ca TINSEL: Add more Noir movers code
b3f2d79cdf TINSEL: Prepare Noir background drawing code
dc24eeca59 TINSEL: Add loading of 3D model in Noir
d1f69ea958 TINSEL: Add basic 3D rendering for Noir
7d0320fe68 TINSEL: Fix typos
e4b4ca03fb TINSEL: Improve comments for Noir 3D renderer
f8e35460c9 TINSEL: Return correct error when TinyGL is disabled
4831f25b37 TINSEL: Make TinyGL memory requirements explicit
Commit: 4d4dddb5f6abf318cf32531bc5841b9475a259ce
https://github.com/scummvm/scummvm/commit/4d4dddb5f6abf318cf32531bc5841b9475a259ce
Author: Peter Kohaut (peter.kohaut at gmail.com)
Date: 2024-11-17T09:32:29+02:00
Commit Message:
TINSEL: Build time option to skip intro
Changed paths:
engines/tinsel/pcode.cpp
engines/tinsel/tinsel.h
diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp
index b7d85d2ebfb..acaf955683d 100644
--- a/engines/tinsel/pcode.cpp
+++ b/engines/tinsel/pcode.cpp
@@ -158,6 +158,19 @@ static const byte fragment14[] = {OP_LIBCALL | OPSIZE8, 58,
};
static const byte fragment15[] = { OP_JMPFALSE | OPSIZE16, FRAGMENT_WORD(154) };
+#if NOIR_SKIP_INTRO
+static const byte fragment_noir_skip_intro_1[] = {
+ OP_IMM + 1, FRAGMENT_DWORD(0x1e000000), // scene = 0x1e = LOFFINTE.SCN
+ (OP_IMM + 1) | OPSIZE16, FRAGMENT_WORD(0x95), // entrance = 0x95
+ (OP_ZERO + 1), // no transition
+ (OP_LIBCALL + 1) | OPSIZE8, 99, // call NEWSCENE
+ OP_HALT + 1 // done
+};
+static const byte fragment_noir_skip_intro_2[] = {
+ (OP_JUMP + 1) | OPSIZE16, FRAGMENT_WORD(0x50f) // jump to 0x50f, skip the discussion with Carlotta Von Uberwald
+};
+#endif
+
#undef FRAGMENT_WORD
const WorkaroundEntry workaroundList[] = {
@@ -230,6 +243,12 @@ const WorkaroundEntry workaroundList[] = {
// DW1-GRA: Fixes hang in Temple, when trying to use items on the big hammer
{TINSEL_V1, false, false, Common::kPlatformUnknown, 276915849, 0x98, sizeof(fragment15), fragment15},
+#if NOIR_SKIP_INTRO
+ // NOIR: Skip the menu and intro, and skip the first conversation.
+ {TINSEL_V3, false, false, Common::kPlatformUnknown, 0, 0x6a2, sizeof(fragment_noir_skip_intro_1), fragment_noir_skip_intro_1},
+ {TINSEL_V3, false, false, Common::kPlatformUnknown, 0x1f3dc654, 0x23c, sizeof(fragment_noir_skip_intro_2), fragment_noir_skip_intro_2},
+#endif
+
{TINSEL_V0, false, false, Common::kPlatformUnknown, 0, 0, 0, NULL}
};
diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h
index 90da5fa2be3..f1b5ac6c7dc 100644
--- a/engines/tinsel/tinsel.h
+++ b/engines/tinsel/tinsel.h
@@ -98,6 +98,9 @@ enum {
kTinselDebugMusic = 2 << 3
};
+// Just for development
+#define NOIR_SKIP_INTRO 0
+
#define DEBUG_BASIC 1
#define DEBUG_INTERMEDIATE 2
#define DEBUG_DETAILED 3
Commit: 3ca5cb68cae28bdd9ab7df3d267093e46cef6505
https://github.com/scummvm/scummvm/commit/3ca5cb68cae28bdd9ab7df3d267093e46cef6505
Author: Peter Kohaut (peter.kohaut at gmail.com)
Date: 2024-11-17T09:32:29+02:00
Commit Message:
TINSEL: Add more Noir movers code
Changed paths:
engines/tinsel/movers.cpp
engines/tinsel/movers.h
engines/tinsel/object.h
diff --git a/engines/tinsel/movers.cpp b/engines/tinsel/movers.cpp
index d44bedad29a..5b47bd10ec3 100644
--- a/engines/tinsel/movers.cpp
+++ b/engines/tinsel/movers.cpp
@@ -235,7 +235,7 @@ void HideMover(MOVER *pMover, int sf) {
}
}
- if (pMover->actorObj)
+ if (pMover->actorObj && pMover->type == MOVER_2D)
MultiSetZPosition(pMover->actorObj, -1);
}
@@ -253,6 +253,8 @@ bool MoverHidden(MOVER *pMover) {
* To be or not to be? If it be, then it is.
*/
bool MoverIs(MOVER *pMover) {
+ if (TinselVersion == 3 && pMover->type == MOVER_3D)
+ return pMover->bActive;
if (TinselVersion >= 2)
return pMover->actorObj ? true : false;
else
@@ -871,6 +873,33 @@ void T2MoverProcess(CORO_PARAM, const void *param) {
CORO_END_CODE;
}
+void T3SetMoverStanding(CORO_PARAM, MOVER *pMover, bool bImmediate) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ if (!MoverIs(pMover)) {
+ CORO_GIVE_WAY;
+ }
+ while (!MoverIs(pMover)) {
+ CORO_SLEEP(1);
+ }
+
+ pMover->targetX = pMover->targetY = -1;
+ pMover->ItargetX = pMover->ItargetY = -1;
+ pMover->UtargetX = pMover->UtargetY = -1;
+
+ if (pMover->type == MOVER_3D) {
+ AnimateObjectFlags(pMover->actorObj, pMover->actorObj->flags | DMA_CHANGED, pMover->actorObj->hImg);
+ // SpriterSetSequence(0, bImmediate ? 0 : 8);
+ pMover->animSpeed = 0x10000;
+ pMover->nextIdleAnim = DwGetCurrentTime() + 24 + _vm->getRandomNumber(216);
+ }
+
+ CORO_END_CODE;
+}
+
/**
* Tinsel 3 Moving actor process
* - 1 per moving actor in current scene.
@@ -882,14 +911,48 @@ void T3MoverProcess(CORO_PARAM, const void *param) {
// Get the co-ordinates - copied to process when it was created
const MAINIT *rpos = (const MAINIT *)param;
MOVER *pMover = rpos->pMover;
+ MULTI_INIT mi;
CORO_BEGIN_CODE(_ctx);
- warning("TODO: Finish implementation of T3MoverProcess() for Noir");
-
InitMover(pMover);
InitialPathChecks(pMover, rpos->X, rpos->Y);
+ if (pMover->type == MOVER_3D) {
+ assert(pMover->hModelName != 0);
+
+ pMover->bActive = true;
+
+ mi.hMulFrame = 0;
+ mi.mulID = 0;
+ mi.mulX = 0;
+ mi.mulY = 0;
+ mi.mulZ = 0;
+ mi.otherFlags = 0;
+ mi.mulFlags = DMA_3D;
+
+ pMover->actorObj = MultiInitObject(&mi);
+
+ MultiInsertObject(_vm->_bg->GetPlayfieldList(FIELD_WORLD), pMover->actorObj);
+ MultiSetAniXY(pMover->actorObj,pMover->objX,pMover->objY);
+
+ warning("TODO: Finish implementation of T3MoverProcess() for Noir");
+
+ AnimateObjectFlags(pMover->actorObj, pMover->actorObj->flags | DMA_CHANGED, pMover->actorObj->hImg);
+ // SpriterSetSequence(0, 4);
+
+ pMover->animSpeed = 0x10000;
+ pMover->nextIdleAnim = 0;
+ }
+
+ // If no path, just use first path in the scene
+ if (pMover->hCpath != NOPOLY)
+ SetMoverZ(pMover, pMover->objY, GetPolyZfactor(pMover->hCpath));
+ else
+ SetMoverZ(pMover, pMover->objY, GetPolyZfactor(FirstPathPoly()));
+
+ CORO_INVOKE_2(T3SetMoverStanding, pMover, true);
+
HideMover(pMover); // Allows a play to come in before this appears
pMover->bHidden = false; // ...but don't stay hidden
diff --git a/engines/tinsel/movers.h b/engines/tinsel/movers.h
index 78c311294c8..c2221bd8838 100644
--- a/engines/tinsel/movers.h
+++ b/engines/tinsel/movers.h
@@ -54,9 +54,9 @@ struct MOVER {
int ItargetX, ItargetY; /* Intermediate destination */
int UtargetX, UtargetY; /* Ultimate destination */
- HPOLYGON hIpath;
- HPOLYGON hUpath;
- HPOLYGON hCpath;
+ HPOLYGON hIpath; /* Intermediate path */
+ HPOLYGON hUpath; /* Ultimate path */
+ HPOLYGON hCpath; /* Current path */
bool over;
int walkNumber;
@@ -119,12 +119,14 @@ struct MOVER {
int paletteLength;
HPOLYGON hRpath; // Recent path
- // Noir specific, just for 3D actors
- MOVER_TYPE type;
- SCNHANDLE hModelName;
- SCNHANDLE hTextureName;
- bool bIsValid;
+ // Noir specific fields
+ MOVER_TYPE type;
+ SCNHANDLE hModelName;
+ SCNHANDLE hTextureName;
+ int posX, posY, posZ;
+ int animSpeed;
+ uint nextIdleAnim;
};
struct MAINIT {
diff --git a/engines/tinsel/object.h b/engines/tinsel/object.h
index 360e2134322..ca66d1217da 100644
--- a/engines/tinsel/object.h
+++ b/engines/tinsel/object.h
@@ -49,6 +49,7 @@ enum {
DMA_CHANGED = 0x0200, ///< object has changed in some way since the last frame
DMA_USERDEF = 0x0400, ///< user defined flags start here
DMA_GHOST = 0x0080,
+ DMA_3D = 0x0400, ///< 3D objects for TinselV3
/** flags that effect an objects appearance */
DMA_HARDFLAGS = (DMA_WNZ | DMA_CNZ | DMA_CONST | DMA_WA | DMA_FLIPH | DMA_FLIPV | DMA_TRANS)
Commit: b3f2d79cdf948a68eee4558a5016f720dc0eb908
https://github.com/scummvm/scummvm/commit/b3f2d79cdf948a68eee4558a5016f720dc0eb908
Author: Peter Kohaut (peter.kohaut at gmail.com)
Date: 2024-11-17T09:32:29+02:00
Commit Message:
TINSEL: Prepare Noir background drawing code
Noir 3D model's cliprect is updated during the rendering, and
the background drawing code is slightly different.
Split implementation of UpdateClipRect to support Noir path.
Changed paths:
engines/tinsel/background.cpp
engines/tinsel/cliprect.cpp
engines/tinsel/cliprect.h
diff --git a/engines/tinsel/background.cpp b/engines/tinsel/background.cpp
index d647af4b24b..910208b5555 100644
--- a/engines/tinsel/background.cpp
+++ b/engines/tinsel/background.cpp
@@ -218,12 +218,10 @@ void Background::DrawBackgnd() {
// redraw all playfields within the clipping rectangles
const RectList &clipRects = GetClipRects();
- for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) {
- // clear the clip rectangle on the virtual screen
- // for each background playfield
- for (unsigned int i = 0; i < _pCurBgnd->fieldArray.size(); i++) {
- Common::Rect rcPlayClip; // clip rect for this playfield
+ // Noir 3D model's cliprect is updated the rendering.
+ if (TinselVersion == 3) {
+ for (unsigned int i = 0; i < _pCurBgnd->fieldArray.size(); i++) {
// get pointer to correct playfield
pPlay = &_pCurBgnd->fieldArray[i];
@@ -231,9 +229,41 @@ void Background::DrawBackgnd() {
ptWin.x = fracToInt(pPlay->fieldX);
ptWin.y = fracToInt(pPlay->fieldY);
- if (IntersectRectangle(rcPlayClip, pPlay->rcClip, *r))
- // redraw all objects within this clipping rect
- UpdateClipRect(&pPlay->pDispList, &ptWin, &rcPlayClip);
+ for (OBJECT* pObj = pPlay->pDispList; pObj != NULL; pObj = pObj->pNext) {
+ if (pObj->flags & DMA_3D) {
+ //SpriterRenderModel();
+ //AddClipRect(SpriterGetModelRect());
+ //AddClipRect(SpriterGetShadowRect());
+ //MergeClipRect();
+ } else {
+ for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) {
+ Common::Rect rcPlayClip; // clip rect for this playfield
+ if (IntersectRectangle(rcPlayClip, pPlay->rcClip, *r)) {
+ // redraw all objects within this clipping rect
+ UpdateClipRectSingle(pObj, &ptWin, &rcPlayClip);
+ }
+ }
+ }
+ }
+ }
+ } else {
+ for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) {
+ // clear the clip rectangle on the virtual screen
+ // for each background playfield
+ for (unsigned int i = 0; i < _pCurBgnd->fieldArray.size(); i++) {
+ Common::Rect rcPlayClip; // clip rect for this playfield
+
+ // get pointer to correct playfield
+ pPlay = &_pCurBgnd->fieldArray[i];
+
+ // convert fixed point window pos to a int
+ ptWin.x = fracToInt(pPlay->fieldX);
+ ptWin.y = fracToInt(pPlay->fieldY);
+
+ if (IntersectRectangle(rcPlayClip, pPlay->rcClip, *r))
+ // redraw all objects within this clipping rect
+ UpdateClipRect(&pPlay->pDispList, &ptWin, &rcPlayClip);
+ }
}
}
diff --git a/engines/tinsel/cliprect.cpp b/engines/tinsel/cliprect.cpp
index 9d32cb57eaf..0258d82e46a 100644
--- a/engines/tinsel/cliprect.cpp
+++ b/engines/tinsel/cliprect.cpp
@@ -197,116 +197,127 @@ void MergeClipRect() {
}
/**
- * Redraws all objects within this clipping rectangle.
+ * Redraws single objects within this clipping rectangle.
* @param pObjList Object list to draw
* @param pWin Window top left position
* @param pClip Pointer to clip rectangle
*/
-void UpdateClipRect(OBJECT **pObjList, Common::Point *pWin, Common::Rect *pClip) {
+void UpdateClipRectSingle(OBJECT *pObj, Common::Point *pWin, Common::Rect *pClip) {
int x, y, right, bottom; // object corners
int hclip, vclip; // total size of object clipping
DRAWOBJECT currentObj; // filled in to draw the current object in list
- OBJECT *pObj; // object list iterator
// Initialize the fields of the drawing object to empty
memset(¤tObj, 0, sizeof(DRAWOBJECT));
- for (pObj = *pObjList; pObj != NULL; pObj = pObj->pNext) {
- if (pObj->flags & DMA_ABS) {
- // object position is absolute
- x = fracToInt(pObj->xPos);
- y = fracToInt(pObj->yPos);
- } else {
- // object position is relative to window
- x = fracToInt(pObj->xPos) - pWin->x;
- y = fracToInt(pObj->yPos) - pWin->y;
- }
+ if (pObj->flags & DMA_ABS) {
+ // object position is absolute
+ x = fracToInt(pObj->xPos);
+ y = fracToInt(pObj->yPos);
+ } else {
+ // object position is relative to window
+ x = fracToInt(pObj->xPos) - pWin->x;
+ y = fracToInt(pObj->yPos) - pWin->y;
+ }
- // calc object right
- right = x + pObj->width;
- if (right < 0)
- // totally clipped if negative
- continue;
-
- // calc object bottom
- bottom = y + pObj->height;
- if (bottom < 0)
- // totally clipped if negative
- continue;
-
- // bottom clip = low right y - clip low right y
- currentObj.botClip = bottom - pClip->bottom;
- if (currentObj.botClip < 0) {
- // negative - object is not clipped
- currentObj.botClip = 0;
- }
+ // calc object right
+ right = x + pObj->width;
+ if (right < 0)
+ // totally clipped if negative
+ return;
- // right clip = low right x - clip low right x
- currentObj.rightClip = right - pClip->right;
- if (currentObj.rightClip < 0) {
- // negative - object is not clipped
- currentObj.rightClip = 0;
- }
+ // calc object bottom
+ bottom = y + pObj->height;
+ if (bottom < 0)
+ // totally clipped if negative
+ return;
- // top clip = clip top left y - top left y
- currentObj.topClip = pClip->top - y;
- if (currentObj.topClip < 0) {
- // negative - object is not clipped
- currentObj.topClip = 0;
- } else { // clipped - adjust start position to top of clip rect
- y = pClip->top;
- }
+ // bottom clip = low right y - clip low right y
+ currentObj.botClip = bottom - pClip->bottom;
+ if (currentObj.botClip < 0) {
+ // negative - object is not clipped
+ currentObj.botClip = 0;
+ }
- // left clip = clip top left x - top left x
- currentObj.leftClip = pClip->left - x;
- if (currentObj.leftClip < 0) {
- // negative - object is not clipped
- currentObj.leftClip = 0;
- } else {
- // NOTE: This else statement is disabled in tinsel v1
- // clipped - adjust start position to left of clip rect
- x = pClip->left;
- }
+ // right clip = low right x - clip low right x
+ currentObj.rightClip = right - pClip->right;
+ if (currentObj.rightClip < 0) {
+ // negative - object is not clipped
+ currentObj.rightClip = 0;
+ }
- // calc object total horizontal clipping
- hclip = currentObj.leftClip + currentObj.rightClip;
+ // top clip = clip top left y - top left y
+ currentObj.topClip = pClip->top - y;
+ if (currentObj.topClip < 0) {
+ // negative - object is not clipped
+ currentObj.topClip = 0;
+ } else { // clipped - adjust start position to top of clip rect
+ y = pClip->top;
+ }
- // calc object total vertical clipping
- vclip = currentObj.topClip + currentObj.botClip;
+ // left clip = clip top left x - top left x
+ currentObj.leftClip = pClip->left - x;
+ if (currentObj.leftClip < 0) {
+ // negative - object is not clipped
+ currentObj.leftClip = 0;
+ } else {
+ // NOTE: This else statement is disabled in tinsel v1
+ // clipped - adjust start position to left of clip rect
+ x = pClip->left;
+ }
- if (hclip + vclip != 0) {
- // object is clipped in some way
+ // calc object total horizontal clipping
+ hclip = currentObj.leftClip + currentObj.rightClip;
- if (pObj->width <= hclip)
- // object totally clipped horizontally - ignore
- continue;
+ // calc object total vertical clipping
+ vclip = currentObj.topClip + currentObj.botClip;
- if (pObj->height <= vclip)
- // object totally clipped vertically - ignore
- continue;
+ if (hclip + vclip != 0) {
+ // object is clipped in some way
- // set clip bit in objects flags
- currentObj.flags = pObj->flags | DMA_CLIP;
- } else { // object is not clipped - copy flags
- currentObj.flags = pObj->flags;
- }
+ if (pObj->width <= hclip)
+ // object totally clipped horizontally - ignore
+ return;
- // copy objects properties to local object
- currentObj.width = pObj->width;
- currentObj.height = pObj->height;
- currentObj.xPos = (short)x;
- currentObj.yPos = (short)y;
- if (TinselVersion != 3) {
- currentObj.pPal = pObj->pPal;
- } else {
- currentObj.isRLE = pObj->isRLE;
- currentObj.colorFlags = pObj->colorFlags;
- }
- currentObj.constant = pObj->constant;
- currentObj.hBits = pObj->hBits;
+ if (pObj->height <= vclip)
+ // object totally clipped vertically - ignore
+ return;
+
+ // set clip bit in objects flags
+ currentObj.flags = pObj->flags | DMA_CLIP;
+ } else { // object is not clipped - copy flags
+ currentObj.flags = pObj->flags;
+ }
+
+ // copy objects properties to local object
+ currentObj.width = pObj->width;
+ currentObj.height = pObj->height;
+ currentObj.xPos = (short)x;
+ currentObj.yPos = (short)y;
+ if (TinselVersion != 3) {
+ currentObj.pPal = pObj->pPal;
+ } else {
+ currentObj.isRLE = pObj->isRLE;
+ currentObj.colorFlags = pObj->colorFlags;
+ }
+ currentObj.constant = pObj->constant;
+ currentObj.hBits = pObj->hBits;
- // draw the object
- DrawObject(¤tObj);
+ // draw the object
+ DrawObject(¤tObj);
+}
+
+/**
+ * Redraws all objects within this clipping rectangle.
+ * @param pObjList Object list to draw
+ * @param pWin Window top left position
+ * @param pClip Pointer to clip rectangle
+ */
+void UpdateClipRect(OBJECT **pObjList, Common::Point *pWin, Common::Rect *pClip) {
+ OBJECT *pObj; // object list iterator
+
+ for (pObj = *pObjList; pObj != NULL; pObj = pObj->pNext) {
+ UpdateClipRectSingle(pObj, pWin, pClip);
}
}
diff --git a/engines/tinsel/cliprect.h b/engines/tinsel/cliprect.h
index 48a31cb7c37..8f689df3c1e 100644
--- a/engines/tinsel/cliprect.h
+++ b/engines/tinsel/cliprect.h
@@ -62,6 +62,11 @@ void FindMovingObjects( // Creates clipping rectangles for all the objects that
void MergeClipRect(); // Merges any clipping rectangles that overlap
+void UpdateClipRectSingle( // Redraws single object within this clipping rectangle
+ OBJECT *pObj, // object to draw
+ Common::Point *pWin, // window top left position
+ Common::Rect *pClip); // pointer to clip rectangle
+
void UpdateClipRect( // Redraws all objects within this clipping rectangle
OBJECT **pObjList, // object list to draw
Common::Point *pWin, // window top left position
Commit: dc24eeca591c352015561721287018bee16298aa
https://github.com/scummvm/scummvm/commit/dc24eeca591c352015561721287018bee16298aa
Author: Peter Kohaut (peter.kohaut at gmail.com)
Date: 2024-11-17T09:32:29+02:00
Commit Message:
TINSEL: Add loading of 3D model in Noir
Changed paths:
A engines/tinsel/noir/spriter.cpp
A engines/tinsel/noir/spriter.h
engines/tinsel/background.cpp
engines/tinsel/module.mk
engines/tinsel/movers.cpp
engines/tinsel/scene.cpp
engines/tinsel/tinlib.cpp
engines/tinsel/tinsel.cpp
engines/tinsel/tinsel.h
diff --git a/engines/tinsel/background.cpp b/engines/tinsel/background.cpp
index 910208b5555..df37e090e68 100644
--- a/engines/tinsel/background.cpp
+++ b/engines/tinsel/background.cpp
@@ -29,6 +29,7 @@
#include "tinsel/object.h"
#include "tinsel/pid.h" // process identifiers
#include "tinsel/tinsel.h"
+#include "tinsel/noir/spriter.h"
namespace Tinsel {
@@ -231,7 +232,7 @@ void Background::DrawBackgnd() {
for (OBJECT* pObj = pPlay->pDispList; pObj != NULL; pObj = pObj->pNext) {
if (pObj->flags & DMA_3D) {
- //SpriterRenderModel();
+ _vm->_spriter->Draw(0, 0, 0, 0, 0);
//AddClipRect(SpriterGetModelRect());
//AddClipRect(SpriterGetShadowRect());
//MergeClipRect();
diff --git a/engines/tinsel/module.mk b/engines/tinsel/module.mk
index e7b30e8cb23..d583bcb5641 100644
--- a/engines/tinsel/module.mk
+++ b/engines/tinsel/module.mk
@@ -52,6 +52,7 @@ MODULE_OBJS := \
noir/notebook.o \
noir/notebook_page.o \
noir/sysreel.o \
+ noir/spriter.o \
# This module can be built as a plugin
ifeq ($(ENABLE_TINSEL), DYNAMIC_PLUGIN)
diff --git a/engines/tinsel/movers.cpp b/engines/tinsel/movers.cpp
index 5b47bd10ec3..4b154377678 100644
--- a/engines/tinsel/movers.cpp
+++ b/engines/tinsel/movers.cpp
@@ -42,6 +42,7 @@
#include "tinsel/timers.h"
#include "tinsel/tinsel.h"
#include "tinsel/token.h"
+#include "tinsel/noir/spriter.h"
#include "common/textconsole.h"
#include "common/util.h"
@@ -892,7 +893,7 @@ void T3SetMoverStanding(CORO_PARAM, MOVER *pMover, bool bImmediate) {
if (pMover->type == MOVER_3D) {
AnimateObjectFlags(pMover->actorObj, pMover->actorObj->flags | DMA_CHANGED, pMover->actorObj->hImg);
- // SpriterSetSequence(0, bImmediate ? 0 : 8);
+ _vm->_spriter->SetSequence(0, bImmediate ? 0 : 8);
pMover->animSpeed = 0x10000;
pMover->nextIdleAnim = DwGetCurrentTime() + 24 + _vm->getRandomNumber(216);
}
@@ -939,7 +940,7 @@ void T3MoverProcess(CORO_PARAM, const void *param) {
warning("TODO: Finish implementation of T3MoverProcess() for Noir");
AnimateObjectFlags(pMover->actorObj, pMover->actorObj->flags | DMA_CHANGED, pMover->actorObj->hImg);
- // SpriterSetSequence(0, 4);
+ _vm->_spriter->SetSequence(0, 4);
pMover->animSpeed = 0x10000;
pMover->nextIdleAnim = 0;
diff --git a/engines/tinsel/noir/spriter.cpp b/engines/tinsel/noir/spriter.cpp
new file mode 100644
index 00000000000..69ef8d16ae7
--- /dev/null
+++ b/engines/tinsel/noir/spriter.cpp
@@ -0,0 +1,1072 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Spriter - 3D Renderer
+ */
+
+#include "tinsel/noir/spriter.h"
+
+#include "tinsel/handle.h"
+#include "tinsel/tinsel.h"
+
+#include "common/memstream.h"
+#include "common/textconsole.h"
+#include "common/rect.h"
+#include "common/str.h"
+#include "common/file.h"
+
+#include "math/quat.h"
+
+namespace Tinsel {
+
+#define PALETTE_COUNT 22
+#define MERGE_VERTICES_OFFSET 0.1f // find proper ratio, this is perhaps too big?
+
+static float ConvertAngle(uint32 angle) {
+ return ((float(angle & 0xfff) / 4095.0f) * 360.0f);
+}
+
+Spriter::Spriter() {
+ _textureGenerated = false;
+ _sequencesCount = 0;
+ _animId = 0;
+ _animSpeed = 0;
+}
+
+Spriter::~Spriter() {
+}
+
+const Math::Matrix4& Spriter::MatrixCurrent() const {
+ return _currentMatrix->top();
+}
+
+void Spriter::MatrixReset() {
+ _currentMatrix->clear();
+
+ Math::Matrix4 m;
+ _currentMatrix->push(m);
+}
+
+void Spriter::MatrixPop() {
+ _currentMatrix->pop();
+}
+
+void Spriter::MatrixPush() {
+ _currentMatrix->push(_currentMatrix->top());
+}
+
+void Spriter::MatrixTranslate(float x, float y, float z) {
+ Math::Vector3d v {x,y,z};
+ _currentMatrix->top().translate(v);
+}
+
+void Spriter::MatrixScale(float x, float y, float z) {
+ Math::Matrix4 m;
+ m.setValue(0, 0, x);
+ m.setValue(1, 1, y);
+ m.setValue(2, 2, z);
+ _currentMatrix->top() = _currentMatrix->top() * m;
+}
+
+void Spriter::MatrixRotateX(float angle) {
+ Math::Matrix4 &m = _currentMatrix->top();
+ m = m * Math::Quaternion::xAxis(angle).toMatrix();
+}
+
+void Spriter::MatrixRotateY(float angle) {
+ Math::Matrix4 &m = _currentMatrix->top();
+ m = m * Math::Quaternion::yAxis(angle).toMatrix();
+}
+
+void Spriter::MatrixRotateZ(float angle) {
+ Math::Matrix4 &m = _currentMatrix->top();
+ m = m * Math::Quaternion::zAxis(angle).toMatrix();
+}
+
+void Spriter::SetViewport(int ap) {
+ _view.viewport.ap = ap;
+
+ int ratio = ((_view.screenRect.right - _view.screenRect.left) << 12) / ap;
+ _view.viewport.width = ratio;
+ _view.viewport.height = ratio;
+
+ _view.viewport.ap = (ap + 1800) / 3600;
+
+ _view.viewport.rect = _view.viewRect;
+}
+
+void Spriter::Init(int width, int height) {
+ _currentMatrix = &_modelMatrix;
+ _meshShadow.resize(50);
+
+ _view.screenRect.left = 0;
+ _view.screenRect.top = 0;
+ _view.screenRect.right = width;
+ _view.screenRect.bottom = height;
+
+ _view.centerX = width / 2;
+ _view.centerY = height / 2;
+
+ _view.viewRect.left = -_view.centerX;
+ _view.viewRect.top = -_view.centerY;
+ _view.viewRect.right = _view.screenRect.right - _view.screenRect.left - _view.centerX - 1;
+ _view.viewRect.bottom = _view.screenRect.bottom - _view.screenRect.top - _view.centerY;
+
+ SetViewport(306030);
+}
+
+void Spriter::SetCamera(int rotX, int rotY, int rotZ, int posX, int posY, int posZ, int cameraAp) {
+ _view.position.set(posX * 0.01f, posY * 0.01f, posZ * 0.01f);
+ _view.rotation.set(ConvertAngle(rotX), ConvertAngle(rotY), ConvertAngle(rotZ));
+
+ SetViewport(cameraAp);
+
+ _modelIdle = true;
+}
+
+void Spriter::TransformSceneXYZ(int x, int y, int z, int& xOut, int& yOut) {
+ MatrixReset();
+ MatrixRotateX(_view.rotation.x());
+ MatrixRotateY(_view.rotation.y());
+ MatrixRotateZ(_view.rotation.z());
+ MatrixTranslate(-_view.position.x(), -_view.position.y(), -_view.position.z());
+ MatrixTranslate((float)x / 100.0f, (float)y / 100.0f, (float)z / 100.0f);
+
+ Math::Vector3d v(0,0,0);
+
+ MatrixCurrent().transform(&v, true);
+
+ // apply viewport
+ xOut = _view.centerX + v.x();
+ yOut = _view.centerY + v.y();
+}
+
+void Spriter::LoadH(const Common::String& modelName) {
+ Common::String filename = modelName + ".h";
+
+ Common::File f;
+ f.open(Common::Path(filename));
+
+ while(!f.eos()) {
+ Common::String line = f.readLine();
+ if (!line.hasPrefix("#define")) {
+ continue;
+ }
+ line.erase(0, 8); // remove "#define "
+
+ size_t underscorePos = line.findFirstOf('_');
+
+ AnimationInfo *anim = nullptr;
+
+ Common::String name = line.substr(0, underscorePos);
+
+ line.erase(0, name.size() + 1); // remove the underscore too
+
+ if (name.hasPrefix("SHADOW")) {
+ anim = &_animShadow;
+ } else {
+ for (Common::Array<AnimationInfo>::iterator it = _animMain.begin(); it != _animMain.end(); ++it) {
+ if (it->name.equals(name)) {
+ anim = it;
+ break;
+ }
+ }
+
+ if (anim == nullptr) {
+ AnimationInfo tempAnim {};
+ tempAnim.name = name;
+ _animMain.push_back(tempAnim);
+ anim = &_animMain.back();
+ }
+ }
+
+ size_t spacePos = line.findFirstOf(' ');
+ Common::String sub = line.substr(0, spacePos);
+ auto val = atoi(line.substr(spacePos).c_str());
+
+ if (sub.equals("MESH_NUM")) {
+ anim->meshNum = val;
+ } else if (sub.equals("SCALE_NUM")) {
+ anim->scaleNum = val;
+ } else if (sub.equals("TRANSLATE_NUM")) {
+ anim->translateNum = val;
+ } else if (sub.equals("ROTATE_NUM")) {
+ anim->rotateNum = val;
+ }
+ }
+}
+
+void Spriter::LoadGBL(const Common::String& modelName) {
+ Common::String filename = modelName + ".gbl";
+
+ Common::File f;
+ f.open(Common::Path(filename));
+
+ while(!f.eos()) {
+ Common::String line = f.readLine();
+ if (!line.hasPrefix("#define")) {
+ continue;
+ }
+ line.erase(0, 8); // remove "#define "
+
+ size_t underscorePos = line.findFirstOf('_');
+
+ AnimationInfo *anim = nullptr;
+ MeshInfo *mesh = nullptr;
+
+ Common::String name = line.substr(0, underscorePos);
+
+ line.erase(0, name.size() + 1); // remove the underscore too
+
+ uint meshId = 0;
+ if (name.hasPrefix("SHADOW")) {
+ anim = &_animShadow;
+ meshId = atoi(name.substr(6).c_str());
+ assert(meshId < _meshShadow.size());
+ mesh = &_meshShadow[meshId];
+ } else {
+ for (Common::Array<AnimationInfo>::iterator it = _animMain.begin(); it != _animMain.end(); ++it) {
+ if (it->name.equals(name)) {
+ anim = it;
+ break;
+ }
+ }
+ mesh = &_meshMain;
+ }
+
+ assert(anim != nullptr);
+ assert(mesh != nullptr);
+
+ size_t spacePos = line.findFirstOf(' ');
+ Common::String sub = line.substr(0, spacePos);
+ auto val = atoi(line.substr(spacePos).c_str());
+
+ if (sub.equals("MESH_TABLES")) {
+ mesh->meshTables = val;
+ } else if (sub.equals("MESH_TABLES_hunk")) {
+ mesh->meshTablesHunk = val;
+ } else if (sub.equals("RENDER_PROGRAM")) {
+ mesh->program = val;
+ } else if (sub.equals("RENDER_PROGRAM_hunk")) {
+ mesh->programHunk = val;
+ } else if (sub.equals("TRANSLATE_TABLES")) {
+ anim->translateTables = val;
+ } else if (sub.equals("TRANSLATE_TABLES_hunk")) {
+ anim->translateTablesHunk = val;
+ } else if (sub.equals("ROTATE_TABLES")) {
+ anim->rotateTables = val;
+ } else if (sub.equals("ROTATE_TABLES_hunk")) {
+ anim->rotateTablesHunk = val;
+ } else if (sub.equals("SCALE_TABLES")) {
+ anim->scaleTables = val;
+ } else if (sub.equals("SCALE_TABLES_hunk")) {
+ anim->scaleTablesHunk = val;
+ }
+ }
+}
+
+#define kPIFF 0x46464950
+#define kRBHF 0x46484252
+#define kRBHH 0x48484252
+#define kBODY 0x59444f42
+#define kRELC 0x434c4552
+
+void Spriter::LoadRBH(const Common::String& modelName, Hunks& hunks) {
+ Common::String filename = modelName + ".rbh";
+
+ Common::File f;
+ f.open(Common::Path(filename));
+
+ uint tag = f.readUint32LE();
+ assert(tag == kPIFF);
+ uint fileSize = f.readUint32LE();
+
+ tag = f.readUint32LE();
+ assert(tag == kRBHF);
+ tag = f.readUint32LE();
+ assert(tag == kRBHH);
+ uint headerSize = f.readUint32LE();
+ uint entriesCount = headerSize / 12;
+
+ hunks.resize(entriesCount);
+ for (Hunks::iterator it = hunks.begin(); it != hunks.end(); ++it) {
+ f.skip(4); // pointer to data
+ it->size = f.readUint32LE();
+ it->flags = f.readUint16LE();
+ f.skip(2); // padding
+ it->data.resize(it->size);
+ }
+
+ uint entryIdx = 0;
+ while (f.pos() < fileSize) {
+ tag = f.readUint32LE();
+ uint size = f.readUint32LE();
+ if (tag == kBODY) {
+ f.read(hunks[entryIdx].data.data(), size);
+ ++entryIdx;
+ } else if (tag == kRELC) {
+ uint srcIdx = f.readUint32LE();
+ uint dstIdx = f.readUint32LE();
+
+ // SCUMMVM implementation does not need to read offsets
+
+ // uint entries = (size - sizeof(uint32) * 2) / sizeof(uint32);
+ // uint32* dstDataPtr = (uint32*)_rbh[dstIdx].data.data();
+ // while (entries > 0)
+ // {
+ // uint offset = f.readUint32LE();
+ // --entries;
+ // }
+ f.skip(size - 8);
+ hunks[srcIdx].mappingIdx.push_back(dstIdx);
+ } else {
+ assert(false);
+ }
+ }
+}
+
+void Spriter::LoadVMC(const Common::String& textureName) {
+ Common::String filename = textureName + ".vmc";
+
+ Common::File f;
+ f.open(Common::Path(filename));
+
+ int16 buffer[4];
+
+ _textureData.resize(4 * 256 * 256);
+
+ while (true) {
+ buffer[0] = f.readSint16LE();
+ buffer[1] = f.readSint16LE();
+ buffer[2] = f.readSint16LE();
+ buffer[3] = f.readSint16LE();
+
+ if ((buffer[3] | buffer[0] | buffer[2] | buffer[1]) == 0) break;
+
+ int a = buffer[1];
+ int size1 = buffer[2];
+ int b = buffer[0];
+ int size2 = buffer[3];
+
+ int size = size2 * size1 * 2;
+
+ uint texId = (((a >> 8) & 0xfffU) * 16 + ((b >> 7) & 0xffffU)) & 0xffff;
+ if (texId > 3) {
+ return;
+ }
+ uint aAdj = a & 0xff;
+ if (a < 0) {
+ aAdj = -(-a & 0xffU);
+ }
+ uint bAdj = b & 0x7f;
+ if (b < 0) {
+ bAdj = -(-b & 0x7fU);
+ }
+
+ uint pos = ((((aAdj & 0x1ff) * 128 + (bAdj & 0xffff)) & 0xffff) * 2) + (texId * 65536);
+
+ f.read(_textureData.data() + pos, size);
+ }
+
+ UpdateTextures();
+}
+
+void Spriter::UpdateTextures() {
+ // TODO apply pallete to textures
+}
+
+void Spriter::SetPalette(SCNHANDLE hPalette) {
+ const
+ uint32 paletteHeaderSize = 2 + 2;
+ uint32 paletteBodySize = PALETTE_COUNT * 256 * sizeof(uint16);
+
+ /* Select only one palette as they are sorded by light intensity */
+ uint32 paletteId = PALETTE_COUNT - 1;
+
+ Graphics::PixelFormat paletteFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
+
+ Common::MemoryReadStream s(_vm->_handle->LockMem(hPalette), paletteHeaderSize + paletteBodySize);
+
+ uint zero = s.readUint16LE();
+ uint size = s.readUint16LE();
+
+ if (zero == 0 && size == paletteBodySize) {
+ _palette.resize(256 * 3);
+
+ s.skip(paletteId * sizeof(uint16) * 256);
+
+ for (uint i = 0; i < 256; ++i) {
+ uint16 color = s.readUint16LE();
+ uint8 r, g, b;
+ paletteFormat.colorToRGB(color, r, g, b);
+
+ _palette[(i * 3) + 0] = r;
+ _palette[(i * 3) + 1] = g;
+ _palette[(i * 3) + 2] = b;
+ }
+
+ UpdateTextures();
+ } else {
+ warning("unknown palette data");
+ }
+}
+
+void Spriter::SetSequence(uint animId, uint delay) {
+ assert(animId < _animMain.size());
+
+ _sequencesCount = _sequencesCount + 1;
+ _animId = animId;
+
+ if ((!_modelIdle) && (delay != 0)) {
+ _modelMain.startFrame = -1;
+ _modelMain.time = 0;
+ _animDelay = delay;
+ _animDelayMax = delay;
+ if (!SetEndFrame(_modelMain, _animMain[animId], 0)) {
+ error("Spr_SetSeq: Could not set end frame");
+ }
+ } else {
+ if (!SetStartFrame(_modelMain, _animMain[animId], 0)) {
+ error("Spr_SetSeq: Could not set start frame");
+ }
+ if (!SetEndFrame(_modelMain, _animMain[animId], _animMain[animId].maxFrame != 0)) {
+ error("Spr_SetSeq: Could not set end frame");
+ }
+ }
+}
+
+Common::Rect Spriter::Draw(int direction, int x, int y, int z, int tDelta) {
+ if (_modelIdle) {
+ _modelIdle = false;
+ _direction = direction;
+ }
+
+ _modelMain.position.set((float)x * 0.01f, (float)y * 0.01f, (float)z * 0.01f);
+ _modelMain.scale.set(1.0f, 1.0f, 1.0f);
+
+ if ((_animMain[_animId].maxFrame < 2) && (_modelMain.startFrame != -1)) {
+ _modelMain.time = 0;
+ if (!SetStartFrame(_modelMain, _animMain[_animId], _modelMain.endFrame)) {
+ error("Spr_Step: Could not set start frame");
+ }
+ } else {
+ if (_animDelay == 0) {
+ _modelMain.time += tDelta;
+ } else {
+ _animDelay--;
+ _modelMain.time += (uint)tDelta / _animDelayMax;
+ }
+
+ while (_modelMain.time > 0xFFFF) {
+ _modelMain.time -= 0x10000;
+ if (!SetStartFrame(_modelMain, _animMain[_animId], _modelMain.endFrame)) {
+ error("Spr_Step: Could not set start frame");
+ }
+
+ _modelMain.endFrame++;
+ if ((int)_animMain[_animId].maxFrame < _modelMain.endFrame) {
+ _sequencesCount++;
+ _modelMain.endFrame = 0;
+ }
+ }
+ }
+
+ // do gradual direction change when model is idle - game is using 5 steps
+ _modelMain.rotation.set(0, ConvertAngle(direction), 0);
+
+ RenderModel(_modelMain);
+
+ // int shadowId = 3;
+ // if (_animId == 2) {
+ // shadowId = ((_modelMain.endFrame + 17) % 18) + 1;
+ // }
+ // _modelShadow.program = _modelShadow.hunks[_meshShadow[shadowId].programHunk].data.data() + _meshShadow[shadowId].program;
+ // _modelShadow.tables.meshes = LoadMeshes(_modelShadow.hunks, _meshShadow[shadowId].meshTablesHunk, _meshShadow[shadowId].meshTables, 1);
+ // float dx = _modelMain.position.x() - _modelMain.lightPosition[0].x;
+ // float dz = _modelMain.position.z() - _modelMain.lightPosition[0].z;
+ // float dy = _modelMain.position.y() - _modelMain.lightPosition[0].y;
+
+ // float sheerx = dz * dz + dx * dx;
+ // float sheerz = 0.0;
+
+ // if (dy * dy < sheerx) {
+ // dy = 1.0f / sqrt(sheerx);
+ // sheerx = dx * dy;
+ // sheerz = dy * dz;
+ // } else {
+ // sheerx = 0.0f;
+ // sheerz = 0.0f;
+ // if (0.0f != dy) {
+ // sheerx = dx * (1.0f / dy);
+ // sheerz = (1.0f / dy) * dz;
+ // }
+ // }
+
+ // _modelShadow.scale.set(
+ // abs(sheerx) + 1.0f,
+ // _modelMain.scale.y(),
+ // abs(sheerz) + 1.0f
+ // );
+
+ // _modelShadow.position.set(
+ // _modelMain.position.x() + sheerx * 10.0f,
+ // 0,
+ // _modelMain.position.z() + sheerz * 10.0f
+ // );
+
+ // _modelShadow.rotation.set(
+ // _modelMain.rotation.x(),
+ // _modelMain.rotation.y() + 180.0f,
+ // _modelMain.rotation.z()
+ // );
+
+ // RenderModel(_modelShadow);
+
+ return Common::Rect {0, 0};
+}
+
+Meshes Spriter::LoadMeshes(const Hunks &hunks, uint hunk, uint offset, int frame) {
+ assert(hunk < hunks.size());
+
+ Common::MemoryReadStream framesStream(hunks[hunk].data.data(), hunks[hunk].data.size());
+ framesStream.skip(offset);
+
+ uint numFrames = framesStream.readUint16LE();
+ uint numEntries = framesStream.readUint16LE();
+
+ assert(frame < (int)numFrames);
+
+ framesStream.skip(frame * 4);
+
+ uint meshListOffset = framesStream.readUint32LE();
+ uint meshListHunk = hunks[hunk].mappingIdx[0];
+ Common::MemoryReadStream meshListStream(hunks[meshListHunk].data.data(), hunks[meshListHunk].data.size());
+ meshListStream.skip(meshListOffset);
+
+ Meshes result;
+ result.vertexCount = meshListStream.readUint32LE();
+ result.normalCount = meshListStream.readUint32LE();
+ result.meshes.resize(numEntries);
+
+ // Read all meshes
+ for (auto& mesh : result.meshes) {
+ uint meshOffset = meshListStream.readUint32LE();
+ uint meshHunk = hunks[meshListHunk].mappingIdx[0];
+
+ Common::MemoryReadStream meshStream(hunks[meshHunk].data.data(), hunks[meshHunk].data.size());
+ meshStream.skip(meshOffset);
+
+ // Read vertices
+ uint verticesOffset = meshStream.readUint32LE();
+ uint verticesHunk = hunks[meshHunk].mappingIdx[1];
+
+ mesh.vertices.resize(meshStream.readUint32LE());
+
+ Common::MemoryReadStream verticesStream(hunks[verticesHunk].data.data(), hunks[verticesHunk].data.size());
+ verticesStream.skip(verticesOffset);
+
+ for (auto& v : mesh.vertices) {
+ v.readFromStream(&verticesStream);
+ }
+
+ // Read normals
+ uint normalsOffset = meshStream.readUint32LE();
+ uint normalsHunk = hunks[meshHunk].mappingIdx[1];
+
+ mesh.normals.resize(meshStream.readUint32LE());
+
+ Common::MemoryReadStream normalsStream(hunks[normalsHunk].data.data(), hunks[normalsHunk].data.size());
+ normalsStream.skip(normalsOffset);
+
+ for (auto& n : mesh.normals) {
+ n.readFromStream(&normalsStream);
+ }
+
+ // Read primitives
+ uint primitivesOffset = meshStream.readUint32LE();
+ uint primitivesHunk = hunks[meshHunk].mappingIdx[0];
+
+ Common::MemoryReadStream primitivesStream(hunks[primitivesHunk].data.data(), hunks[primitivesHunk].data.size());
+ primitivesStream.skip(primitivesOffset);
+
+ while (true) {
+ uint primitiveCount = primitivesStream.readUint16LE();
+ uint primitiveType = primitivesStream.readUint16LE();
+ uint dataSize = primitivesStream.readUint32LE();
+
+ if (primitiveCount == 0) {
+ break;
+ }
+
+ MeshPart part;
+ part.numVertices = (primitiveType & 1) ? 4 : 3;
+ part.type = static_cast<MeshPartType>((primitiveType & 0x7f) >> 1);
+ part.cull = primitiveType & 0x80;
+ part.primitives.resize(primitiveCount);
+
+ int64 start = primitivesStream.pos();
+ for (auto& prim : part.primitives) {
+ for (uint i = 0; i < 8; ++i) {
+ prim.indices[i] = primitivesStream.readUint16LE();
+ }
+
+ switch (part.type) {
+ case MESH_PART_TYPE_COLOR:
+ prim.color = primitivesStream.readUint32LE();
+ break;
+ case MESH_PART_TYPE_SOLID:
+ assert(false); //not used?
+ break;
+ case MESH_PART_TYPE_TEXTURE:
+ // Has texture
+ for (uint i = 0; i < part.numVertices; ++i) {
+ prim.uv[i].readFromStream(&primitivesStream);
+ }
+ prim.texture = primitivesStream.readUint16LE();
+ primitivesStream.skip(2); //padding
+ break;
+ }
+ }
+ int64 end = primitivesStream.pos();
+ assert(dataSize == end - start);
+
+ mesh.parts.push_back(part);
+ }
+ }
+
+ return result;
+}
+
+template<bool convert>
+AnimationData Spriter::LoadAnimationData(const Hunks& hunks, uint hunk, uint offset) {
+ assert(hunk < hunks.size());
+
+ Common::MemoryReadStream framesStream(hunks[hunk].data.data(), hunks[hunk].data.size());
+ framesStream.skip(offset);
+
+ uint numFrames = framesStream.readUint16LE();
+ uint numEntries = framesStream.readUint16LE();
+
+ AnimationData result;
+ result.resize(numFrames);
+
+ uint vectorsHunk = hunks[hunk].mappingIdx[0];
+ assert(vectorsHunk < hunks.size());
+
+ for (uint frame = 0; frame < numFrames; frame++) {
+ auto& vectors = result[frame];
+ uint vectorsOffset = framesStream.readUint32LE();
+
+ Common::MemoryReadStream vectorsStream(hunks[vectorsHunk].data.data(), hunks[vectorsHunk].data.size());
+ vectorsStream.skip(vectorsOffset);
+
+ vectors.resize(numEntries);
+
+ for (auto& v : vectors) {
+ if (convert) {
+ uint32 x = vectorsStream.readUint32LE();
+ uint32 y = vectorsStream.readUint32LE();
+ uint32 z = vectorsStream.readUint32LE();
+ v.set(ConvertAngle(x), ConvertAngle(y), ConvertAngle(z));
+ } else {
+ v.readFromStream(&vectorsStream);
+ }
+ }
+ }
+
+ return result;
+}
+
+void Spriter::InitModel(Model &model, MeshInfo &meshInfo, Common::Array<AnimationInfo> &animInfos, uint flags) {
+ model.flags = flags;
+
+ model.program = model.hunks[meshInfo.programHunk].data.data() + meshInfo.program;
+
+ AnimationInfo &animInfo = animInfos[0];
+
+ model.tables.meshes = LoadMeshes(model.hunks, meshInfo.meshTablesHunk, meshInfo.meshTables, 0);
+ model.tables.translations = LoadAnimationData<false>(model.hunks, animInfo.translateTablesHunk, animInfo.translateTables)[0];
+ model.tables.rotations = LoadAnimationData<true>(model.hunks, animInfo.rotateTablesHunk, animInfo.rotateTables)[0];
+ model.tables.scales = LoadAnimationData<false>(model.hunks, animInfo.scaleTablesHunk, animInfo.scaleTables)[0];
+
+ model.position.set(0.0f, 0.0f, 0.0f);
+ model.rotation.set(0.0f, 0.0f, 0.0f);
+ model.scale.set(1.0f, 1.0f, 1.0f);
+
+ _currentMatrix = &_modelMatrix;
+
+ MatrixReset();
+
+ // Preprocess vertices - merge vertices
+ RunRenderProgram(model, true);
+
+ bool valid = true;
+ if (model.flags & MODEL_HAS_TRANSLATION_TABLE) {
+ for (const auto &anim : animInfos) {
+ if (anim.translateNum != animInfo.translateNum) {
+ valid = false;
+ }
+ }
+ model.tables.translations.clear();
+ model.tables.translations.resize(animInfo.translateNum);
+ }
+
+ assert(valid); // Animation tables are incorrect
+
+ for (uint i = 0; i < model.animationCount; ++i) {
+ // SetStartFrame(model, animInfos[i], 0);
+ // check if animation data has same number of frames for all transformation types
+ }
+}
+
+void Spriter::RunRenderProgram(Model &model, bool preprocess) {
+ uint8* program = model.program;
+ uint ip = 0;
+
+ Vectors vertices;
+ vertices.reserve(model.tables.meshes.vertexCount);
+
+ Vectors normals;
+ normals.resize(model.tables.meshes.normalCount);
+
+ Common::Array<uint16> sameVertices;
+ sameVertices.resize(model.tables.meshes.vertexCount);
+
+ bool stop = false;
+ do {
+ RenderProgramOp opCode = (RenderProgramOp)READ_LE_UINT16(&program[ip]);
+ ip += 2;
+
+ switch(opCode) {
+ case MATRIX_DUPLICATE: {
+ MatrixPush();
+ break;
+ }
+ case MATRIX_REMOVE: {
+ MatrixPop();
+ break;
+ }
+ case UNUSED: {
+ // TODO
+ // uint16 entry = READ_LE_UINT16(&program[ip]);
+ ip += 2;
+ break;
+ }
+ case TRANSFORM: {
+ uint16 entry = READ_LE_UINT16(&program[ip]);
+
+ Mesh& mesh = model.tables.meshes.meshes[entry];
+ ip += 2;
+
+ if (preprocess) {
+ FindSimilarVertices(mesh, vertices, sameVertices);
+ MergeVertices(mesh, sameVertices);
+ } else {
+ TransformMesh(mesh, vertices);
+ CalculateNormals(mesh, vertices, normals);
+ RenderMesh(mesh, vertices, normals);
+ }
+
+ break;
+ }
+ case TRANSLATE_X: {
+ uint16 entry = READ_LE_UINT16(&program[ip]);
+ ip += 2;
+
+ Math::Vector3d& v = model.tables.translations[entry];
+ MatrixTranslate(v.x(), 0, 0);
+
+ break;
+ }
+ case TRANSLATE_Y: {
+ uint16 entry = READ_LE_UINT16(&program[ip]);
+ ip += 2;
+
+ Math::Vector3d& v = model.tables.translations[entry];
+ MatrixTranslate(0, v.y(), 0);
+
+ break;
+ }
+ case TRANSLATE_Z: {
+ uint16 entry = READ_LE_UINT16(&program[ip]);
+ ip += 2;
+
+ Math::Vector3d& v = model.tables.translations[entry];
+ MatrixTranslate(0, 0, v.z());
+
+ break;
+ }
+ case TRANSLATE_XYZ: {
+ uint16 entry = READ_LE_UINT16(&program[ip]);
+ ip += 2;
+
+ Math::Vector3d& v = model.tables.translations[entry];
+ MatrixTranslate(v.x(), v.y(), v.z());
+
+ break;
+ }
+ case ROTATE_X: {
+ uint16 entry = READ_LE_UINT16(&program[ip]);
+ ip += 2;
+
+ Math::Vector3d& v = model.tables.rotations[entry];
+ MatrixRotateX(v.x());
+
+ break;
+ }
+ case ROTATE_Y: {
+ uint16 entry = READ_LE_UINT16(&program[ip]);
+ ip += 2;
+
+ Math::Vector3d& v = model.tables.rotations[entry];
+ MatrixRotateY(v.y());
+
+ break;
+ }
+ case ROTATE_Z: {
+ uint16 entry = READ_LE_UINT16(&program[ip]);
+ ip += 2;
+
+ Math::Vector3d& v = model.tables.rotations[entry];
+ MatrixRotateZ(v.z());
+
+ break;
+ }
+ case STOP: {
+ stop = true;
+ break;
+ }
+ case ROTATE_XYZ: {
+ uint entry = READ_LE_UINT16(&program[ip]);
+ ip += 2;
+
+ Math::Vector3d& v = model.tables.rotations[entry];
+ MatrixRotateX(v.x());
+ MatrixRotateY(v.y());
+ MatrixRotateZ(v.z());
+
+ break;
+ }
+ default:
+ {
+ error("UNKNOWN RENDER OP %i\n", opCode);
+ }
+ }
+ } while (!stop);
+}
+
+void Spriter::FindSimilarVertices(Mesh& mesh, Vectors& vertices, Common::Array<uint16>& sameVertices) const {
+ const Math::Matrix4 &m = MatrixCurrent();
+
+ uint i_start = vertices.size();
+ for (uint i = 0; i < mesh.vertices.size(); ++i) {
+ Math::Vector3d& vIn = mesh.vertices[i];
+
+ Math::Vector3d vOut = vIn;
+ m.transform(&vOut, true);
+ vertices.push_back(vOut);
+
+ for (uint j = 0; j < vertices.size() - 1; ++j) {
+ float d = vOut.getDistanceTo(vertices[j]);
+ if (d < MERGE_VERTICES_OFFSET) {
+ sameVertices[i_start + i] = j + 1; // 0 is reserved for not found
+ break;
+ }
+ }
+ }
+}
+
+void Spriter::MergeVertices(Mesh &mesh, Common::Array<uint16>& sameVertices) {
+ for (auto& part : mesh.parts) {
+ for (auto& prim : part.primitives) {
+ for (uint i = 0; i < part.numVertices; ++i) {
+ if (sameVertices[prim.indices[i]] != 0) {
+ prim.indices[i] = sameVertices[prim.indices[i]] - 1;
+ }
+ }
+ }
+ }
+}
+
+void Spriter::TransformMesh(Mesh& mesh, Vectors& vertices) {
+ // Transformed vertices from previous meshes might be in the current mesh.
+ const Math::Matrix4 &m = MatrixCurrent();
+
+ for (auto& vIn : mesh.vertices) {
+ Math::Vector3d vOut = vIn;
+ m.transform(&vOut, true);
+ vertices.push_back(vOut);
+ }
+}
+
+void Spriter::CalculateNormals(Mesh& mesh, Vectors& vertices, Vectors &normals) {
+ for (auto& part : mesh.parts) {
+ for (auto& prim : part.primitives) {
+ Math::Vector3d v0 = vertices[prim.indices[0]];
+ Math::Vector3d v1 = vertices[prim.indices[1]];
+ Math::Vector3d v2 = vertices[prim.indices[2]];
+
+ Math::Vector3d norm = Math::Vector3d::crossProduct(v2 - v0, v1 - v0).getNormalized();
+
+ for (uint i = 0; i < part.numVertices; ++i) {
+ normals[prim.indices[i]] += norm;
+ }
+ }
+ }
+}
+
+void Spriter::RenderMesh(Mesh& mesh, Vectors& vertices, Vectors &normals) {
+ for(auto& part : mesh.parts) {
+ switch(part.type) {
+ case MESH_PART_TYPE_COLOR:
+ RenderMeshPartColor(part, vertices, normals);
+ break;
+ case MESH_PART_TYPE_SOLID:
+ // This is just use white color
+ //RenderMeshPartColor(part, vertices, normals);
+ break;
+ case MESH_PART_TYPE_TEXTURE:
+ RenderMeshPartTexture(part, vertices, normals);
+ break;
+ }
+ }
+ return;
+}
+
+void Spriter::RenderMeshPartColor(MeshPart& part, Vectors& vertices, Vectors &normals) {
+ // TODO
+}
+
+void Spriter::RenderMeshPartTexture(MeshPart& part, Vectors& vertices, Vectors &normals) {
+ // TODO
+}
+
+void Spriter::Load(const Common::String &modelName, const Common::String &textureName) {
+ LoadH(modelName);
+ LoadGBL(modelName);
+ LoadRBH(modelName, _modelMain.hunks);
+ LoadVMC(modelName);
+
+ InitModel(_modelMain, _meshMain, _animMain, MODEL_HAS_TRANSLATION_TABLE | MODEL_HAS_ROTATION_TABLE);
+
+ // for (uint i = 0; i < _animMain.size(); ++i) {
+ // update max frame
+ // }
+
+ _modelIdle = true;
+ _modelMain.time = 0;
+}
+
+void lerp3(Vectors &dst, const Vectors &src1, const Vectors &src2, uint t) {
+ assert(dst.size() == src1.size() && src1.size() == src2.size());
+ float interpolator = static_cast<float>(t) / 65536.0f;
+ float interpolatorInv = 1.0f - interpolator;
+ for (uint i = 0; i < dst.size(); ++i) {
+ dst[i] = src1[i] * interpolator + src2[i] * interpolatorInv;
+ }
+
+}
+
+void Spriter::RenderModel(Model &model) {
+ if (model.flags & MODEL_HAS_TRANSLATION_TABLE) {
+ if (model.startFrame == -1) {
+ lerp3(model.tables.translations, model.tables.translations, model.endTranslateTables[model.endFrame], model.time);
+ } else {
+ lerp3(model.tables.translations, model.startTranslateTables[model.startFrame], model.endTranslateTables[model.endFrame], model.time);
+ }
+ }
+
+ if (model.flags & MODEL_HAS_ROTATION_TABLE) {
+ if (model.startFrame == -1) {
+ lerp3(model.tables.rotations, model.tables.rotations, model.endRotateTables[model.endFrame], model.time);
+ } else {
+ lerp3(model.tables.rotations, model.startRotateTables[model.startFrame], model.endRotateTables[model.endFrame], model.time);
+ }
+ }
+
+ if (model.flags & MODEL_HAS_SCALE_TABLE) {
+ if (model.startFrame == -1) {
+ lerp3(model.tables.scales, model.tables.scales, model.endScaleTables[model.endFrame], model.time);
+ } else {
+ lerp3(model.tables.scales, model.startScaleTables[model.startFrame], model.endScaleTables[model.endFrame], model.time);
+ }
+ }
+
+ MatrixReset();
+ MatrixTranslate(model.position.x(), model.position.y(), model.position.z());
+ MatrixScale(model.scale.x(), model.scale.y(), model.scale.z());
+ MatrixRotateX(model.rotation.x());
+ MatrixRotateY(model.rotation.y());
+ MatrixRotateZ(model.rotation.z());
+
+ MatrixRotateX(_view.rotation.x());
+ MatrixRotateY(_view.rotation.y());
+ MatrixRotateZ(_view.rotation.z());
+ MatrixTranslate(-_view.position.x(), -_view.position.y(), -_view.position.z());
+
+ RunRenderProgram(model, false);
+}
+
+bool Spriter::SetStartFrame(Model &model, const AnimationInfo &anim, int frame) {
+ const Hunks &hunks = model.hunks;
+ uint numFrames = 0;
+ if ((model.flags & MODEL_HAS_TRANSLATION_TABLE) != 0) {
+ model.startTranslateTables = LoadAnimationData<false>(hunks, anim.translateTablesHunk, anim.translateTables);
+ numFrames = model.startTranslateTables.size();
+ }
+ if ((model.flags & MODEL_HAS_ROTATION_TABLE) != 0) {
+ model.startRotateTables = LoadAnimationData<true>(hunks, anim.rotateTablesHunk, anim.rotateTables);
+ numFrames = model.startRotateTables.size();
+ }
+ if ((model.flags & MODEL_HAS_SCALE_TABLE) != 0) {
+ model.startScaleTables = LoadAnimationData<false>(hunks, anim.scaleTablesHunk, anim.scaleTables);
+ numFrames = model.startScaleTables.size();
+ }
+ if (frame < 0 || frame >= (int)numFrames) {
+ return false;
+ }
+ model.startFrame = frame;
+ return true;
+}
+
+bool Spriter::SetEndFrame(Model &model, const AnimationInfo &anim, int frame) {
+ const Hunks &hunks = model.hunks;
+ uint numFrames = 0;
+ if ((model.flags & MODEL_HAS_TRANSLATION_TABLE) != 0) {
+ model.endTranslateTables = LoadAnimationData<false>(hunks, anim.translateTablesHunk, anim.translateTables);
+ numFrames = model.endTranslateTables.size();
+ }
+ if ((model.flags & MODEL_HAS_ROTATION_TABLE) != 0) {
+ model.endRotateTables = LoadAnimationData<true>(hunks, anim.rotateTablesHunk, anim.rotateTables);
+ numFrames = model.endRotateTables.size();
+ }
+ if ((model.flags & MODEL_HAS_SCALE_TABLE) != 0) {
+ model.endScaleTables = LoadAnimationData<false>(hunks, anim.scaleTablesHunk, anim.scaleTables);
+ numFrames = model.endScaleTables.size();
+ }
+ if (frame < 0 || frame >= (int)numFrames) {
+ return false;
+ }
+ model.endFrame = frame;
+ return true;
+}
+
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/noir/spriter.h b/engines/tinsel/noir/spriter.h
new file mode 100644
index 00000000000..a9949f82311
--- /dev/null
+++ b/engines/tinsel/noir/spriter.h
@@ -0,0 +1,294 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * Spriter - 3D Renderer
+ */
+
+#ifndef TINSEL_SPRITER_H
+#define TINSEL_SPRITER_H
+
+#include "tinsel/dw.h"
+#include "common/rect.h"
+#include "common/stack.h"
+#include "common/str.h"
+
+#include "math/vector3d.h"
+#include "math/vector2d.h"
+#include "math/matrix4.h"
+
+namespace Tinsel {
+
+typedef Common::FixedStack<Math::Matrix4, 30> MatrixStack;
+
+enum RenderProgramOp : uint16 {
+ MATRIX_DUPLICATE = 1,
+ MATRIX_REMOVE = 2,
+ UNUSED = 3,
+ TRANSFORM = 4,
+ TRANSLATE_X = 5,
+ TRANSLATE_Y = 6,
+ TRANSLATE_Z = 7,
+ TRANSLATE_XYZ = 8,
+ ROTATE_X = 9,
+ ROTATE_Y = 10,
+ ROTATE_Z = 11,
+ ROTATE_XYZ = 17,
+ STOP = 16,
+};
+
+struct AnimationInfo {
+ Common::String name;
+
+ uint meshNum;
+
+ uint translateTablesHunk;
+ uint translateTables;
+ uint translateNum;
+
+ uint rotateTablesHunk;
+ uint rotateTables;
+ uint rotateNum;
+
+ uint scaleTablesHunk;
+ uint scaleTables;
+ uint scaleNum;
+
+ uint maxFrame;
+};
+
+struct MeshInfo {
+ uint meshTablesHunk;
+ uint meshTables;
+
+ uint programHunk;
+ uint program;
+};
+
+struct Hunk {
+ Common::Array<uint8> data;
+ Common::Array<uint> mappingIdx;
+ uint size;
+ uint flags;
+};
+
+typedef Common::Array<Hunk> Hunks;
+
+typedef Common::Array<Math::Vector3d> Vectors;
+
+struct Primitive {
+ uint indices[8];
+ uint color;
+ Math::Vector2d uv[4];
+ uint texture;
+};
+
+enum MeshPartType {
+ MESH_PART_TYPE_COLOR,
+ MESH_PART_TYPE_SOLID,
+ MESH_PART_TYPE_TEXTURE,
+};
+
+struct MeshPart {
+ MeshPartType type;
+ uint cull;
+ uint numVertices;
+ Common::Array<Primitive> primitives;
+};
+
+struct Mesh {
+ Common::Array<Math::Vector3d> vertices;
+ Common::Array<Math::Vector3d> normals;
+ Common::Array<MeshPart> parts;
+ Common::Array<MeshPart> parts2;
+};
+
+struct Meshes {
+ uint vertexCount;
+ uint normalCount;
+
+ Common::Array<Mesh> meshes;
+};
+
+typedef Common::Array<Vectors> AnimationData;
+
+struct ModelTables {
+ Vectors translations;
+ Vectors rotations;
+ Vectors scales;
+ Meshes meshes;
+};
+
+enum ModelFlags {
+ MODEL_HAS_TRANSLATION_TABLE = 1,
+ MODEL_HAS_SCALE_TABLE = 2,
+ MODEL_HAS_ROTATION_TABLE = 4
+};
+
+struct Model {
+ Hunks hunks;
+ Hunks hunksU;
+ uint animationCount;
+ uint field_0xe;
+ uint field_0xf;
+ uint8* program;
+
+ // animation tables
+ AnimationData startTranslateTables;
+ AnimationData startRotateTables;
+ AnimationData startScaleTables;
+ AnimationData endTranslateTables;
+ AnimationData endRotateTables;
+ AnimationData endScaleTables;
+ int startFrame;
+ int endFrame;
+
+ uint flags;
+ uint field_0x32;
+ uint field_0x33;
+
+ ModelTables tables;
+
+ Math::Vector3d position;
+ Math::Vector3d rotation;
+ Math::Vector3d scale;
+
+ uint time; // interpolant
+};
+
+struct Viewport {
+ int ap;
+ float width;
+ float height;
+
+ Common::Rect rect;
+};
+
+struct View {
+ int centerX;
+ int centerY;
+
+ Common::Rect viewRect;
+ Common::Rect screenRect;
+
+ Viewport viewport;
+
+ Math::Vector3d position;
+ Math::Vector3d rotation;
+};
+
+class Spriter {
+private:
+ MatrixStack _modelMatrix;
+ MatrixStack* _currentMatrix;
+
+ Common::Array<AnimationInfo> _animMain;
+ AnimationInfo _animShadow;
+
+ MeshInfo _meshMain;
+ Common::Array<MeshInfo> _meshShadow;
+
+ View _view;
+
+ Common::Array<uint8> _palette;
+ Common::Array<uint8> _textureData;
+
+ bool _textureGenerated;
+ uint _texture[4];
+
+ bool _modelIdle;
+
+ uint _animId;
+ uint _animSpeed;
+ uint _animDelay;
+ uint _animDelayMax;
+
+ uint _sequencesCount;
+
+ uint _direction;
+
+public:
+ Model _modelMain;
+ Model _modelShadow;
+
+public:
+ Spriter();
+ virtual ~Spriter();
+
+ void Init(int width, int height);
+ void SetCamera(int rotX, int rotY, int rotZ, int posX, int posY, int posZ, int cameraAp);
+ void TransformSceneXYZ(int x, int y, int z, int& xOut, int& yOut);
+ void Load(const Common::String& modelName, const Common::String& textureName);
+
+ void SetPalette(SCNHANDLE hPalette);
+
+ void SetSequence(uint animId, uint delay);
+ Common::Rect Draw(int direction, int x, int y, int z, int tDelta);
+
+private:
+ const Math::Matrix4& MatrixCurrent() const;
+
+ void MatrixReset();
+
+ void MatrixPop();
+ void MatrixPush();
+ void MatrixTranslate(float x, float y, float z);
+ void MatrixScale(float x, float y, float z);
+ void MatrixRotateX(float angle);
+ void MatrixRotateY(float angle);
+ void MatrixRotateZ(float angle);
+
+ void SetViewport(int ap);
+
+ // Loading of the model
+ void LoadH(const Common::String& modelName);
+ void LoadGBL(const Common::String& modelName);
+ void LoadRBH(const Common::String& modelName, Hunks& hunks);
+ void LoadVMC(const Common::String& textureName);
+
+ void UpdateTextures();
+
+ Meshes LoadMeshes(const Hunks &hunks, uint hunk, uint offset, int frame);
+ template<bool convert>
+ AnimationData LoadAnimationData(const Hunks &hunks, uint hunk, uint offset);
+ void InitModel(Model& model, MeshInfo& meshInfo, Common::Array<AnimationInfo>& animInfo, uint flags);
+
+ // Processing of the model
+ void RunRenderProgram(Model &model, bool preprocess);
+
+ void FindSimilarVertices(Mesh& mesh, Vectors& vertices, Common::Array<uint16>& sameVertices) const;
+ void MergeVertices(Mesh& mesh, Common::Array<uint16>& sameVertices);
+
+ void TransformMesh(Mesh& mesh, Vectors& vertices);
+ void CalculateNormals(Mesh& mesh, Vectors& vertices, Vectors &normals);
+
+ // Rendering
+ void RenderModel(Model& model);
+ void RenderMesh(Mesh& mesh, Vectors& vertices, Vectors &normals);
+ void RenderMeshPartColor(MeshPart& part, Vectors& vertices, Vectors &normals);
+ void RenderMeshPartTexture(MeshPart& part, Vectors& vertices, Vectors &normals);
+
+ // Animation
+ bool SetStartFrame(Model &model, const AnimationInfo &anim, int frame);
+ bool SetEndFrame(Model &model, const AnimationInfo &anim, int frame);
+};
+
+} // End of namespace Tinsel
+
+#endif // TINSEL_SPRITER_H
diff --git a/engines/tinsel/scene.cpp b/engines/tinsel/scene.cpp
index ef5068989b7..5551b9785a2 100644
--- a/engines/tinsel/scene.cpp
+++ b/engines/tinsel/scene.cpp
@@ -46,6 +46,7 @@
#include "tinsel/sound.h" // stopAllSamples()
#include "tinsel/sysvar.h"
#include "tinsel/token.h"
+#include "tinsel/noir/spriter.h"
#include "common/memstream.h"
#include "common/textconsole.h"
@@ -543,7 +544,15 @@ void SetView(int sceneId, int scale) {
CAMERA_STRUC *pCamera = (CAMERA_STRUC *)_vm->_handle->LockMem(g_tempStruc.hCamera);
for (i = 0; i < g_tempStruc.numCameras; ++i, ++pCamera) {
if (sceneId == (int)FROM_32(pCamera->sceneId)) {
- // set camera
+ _vm->_spriter->SetCamera(
+ FROM_32(pCamera->rotX),
+ FROM_32(pCamera->rotY),
+ FROM_32(pCamera->rotZ),
+ FROM_32(pCamera->posX * SysVar(SV_SPRITER_SCALE)),
+ FROM_32(-pCamera->posY * SysVar(SV_SPRITER_SCALE)),
+ FROM_32(-pCamera->posZ * SysVar(SV_SPRITER_SCALE)),
+ FROM_32(pCamera->aperture)
+ );
SetSysVar(SV_SPRITER_SCENE_ID, sceneId);
break;
}
diff --git a/engines/tinsel/tinlib.cpp b/engines/tinsel/tinlib.cpp
index a6adef94d82..89ba5322647 100644
--- a/engines/tinsel/tinlib.cpp
+++ b/engines/tinsel/tinlib.cpp
@@ -67,6 +67,7 @@
#include "tinsel/token.h"
#include "tinsel/noir/notebook.h"
#include "tinsel/noir/sysreel.h"
+#include "tinsel/noir/spriter.h"
#include "common/textconsole.h"
@@ -154,7 +155,7 @@ enum MASTER_LIB_CODES {
TRYPLAYSAMPLE, UNDIMMUSIC, UNHOOKSCENE, UNTAGACTOR, VIBRATE, WAITFRAME, WAITKEY,
WAITSCROLL, WAITTIME, WALK, WALKED, WALKEDPOLY, WALKEDTAG, WALKINGACTOR, WALKPOLY,
WALKTAG, WALKXPOS, WALKYPOS, WHICHCD, WHICHINVENTORY, ZZZZZZ, DEC3D, DECINVMAIN,
- ADDNOTEBOOK, ADDINV3, ADDCONV, SET3DTEXTURE, FADEMUSIC, VOICEOVER, SETVIEW,
+ ADDNOTEBOOK, ADDINV3, ADDCONV, SET3DPALETTE, FADEMUSIC, VOICEOVER, SETVIEW,
HELDOBJECTORTOPIC, BOOKADDHYPERLINK, OPENNOTEBOOK, NTBPOLYENTRY, NTBPOLYPREVPAGE,
NTBPOLYNEXTPAGE, CROSSCLUE, HIGHEST_LIBCODE
};
@@ -912,6 +913,7 @@ static int CursorPos(int xory) {
* Declare 3d model for an actor.
*/
void Dec3D(int ano, SCNHANDLE hModelName, SCNHANDLE hTextureName) {
+ static SCNHANDLE hModelNameLoaded = 0;
MOVER* pMover = GetMover(ano);
assert(pMover != nullptr);
@@ -919,13 +921,13 @@ void Dec3D(int ano, SCNHANDLE hModelName, SCNHANDLE hTextureName) {
pMover->hModelName = hModelName;
pMover->hTextureName = hTextureName;
- // if (_hModelNameLoaded == 0) {
- // _hModelNameLoaded = hModelName;
- // const char* modelName = (const char *)_vm->_handle->LockMem(hModelName);
- // const char* textureName = (const char *)_vm->_handle->LockMem(hTextureName);
- // LoadModels(modelName, textureName);
- // }
- //assert(_hModelNameLoaded == hModelName);
+ if (hModelNameLoaded == 0) {
+ hModelNameLoaded = hModelName;
+ const char* modelName = (const char *)_vm->_handle->LockMem(hModelName);
+ const char* textureName = (const char *)_vm->_handle->LockMem(hTextureName);
+ _vm->_spriter->Load(modelName, textureName);
+ }
+ assert(hModelNameLoaded == hModelName);
}
/**
@@ -2722,6 +2724,13 @@ static void SendTag(CORO_PARAM, int tagno, TINSEL_EVENT event, HPOLYGON hp, int
}
}
+/**
+ * Set palette for 3D model in Noir
+ */
+static void Set3DPalette(SCNHANDLE hPalette) {
+ _vm->_spriter->SetPalette(hPalette);
+}
+
/**
* Un-kill an actor.
*/
@@ -5226,7 +5235,7 @@ NoirMapping translateNoirLibCode(int libCode, int32 *pp) {
debug(7, "%s(0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X, 0x%08X)", mapping.name, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6], pp[7]);
break;
case 214:
- mapping = NoirMapping{"SET3DTEXTURE", SET3DTEXTURE, 1};
+ mapping = NoirMapping{"SET3DPALETTE", SET3DPALETTE, 1};
pp -= mapping.numArgs - 1;
debug(7, "%s(0x%08X)", mapping.name, pp[0]);
break;
@@ -6357,9 +6366,9 @@ int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pi
}
return -1;
- case SET3DTEXTURE:
+ case SET3DPALETTE:
// Noir only
- warning("TODO: Implement SET3DTEXTURE(0x%08X)", pp[0]);
+ Set3DPalette(pp[0]);
return -1;
case SETACTOR:
diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp
index 12c87b25553..f79b8d70fa5 100644
--- a/engines/tinsel/tinsel.cpp
+++ b/engines/tinsel/tinsel.cpp
@@ -61,6 +61,7 @@
#include "tinsel/tinsel.h"
#include "tinsel/noir/notebook.h"
#include "tinsel/noir/sysreel.h"
+#include "tinsel/noir/spriter.h"
namespace Tinsel {
@@ -926,6 +927,7 @@ TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc)
TinselEngine::~TinselEngine() {
_system->getAudioCDManager()->stop();
+ delete _spriter;
delete _cursor;
delete _bg;
delete _font;
@@ -1022,6 +1024,9 @@ Common::Error TinselEngine::run() {
initGraphics(width, height, &noirFormat);
_screenSurface.create(width, 432, noirFormat);
+
+ _spriter = new Spriter();
+ _spriter->Init(width, height);
} else if (getGameID() == GID_DW2) {
if (ConfMan.getBool("crop_black_bars"))
initGraphics(640, 432);
diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h
index f1b5ac6c7dc..d47a2d20b52 100644
--- a/engines/tinsel/tinsel.h
+++ b/engines/tinsel/tinsel.h
@@ -47,6 +47,7 @@
* Games using this engine:
* - Discworld
* - Discworld 2: Missing Presumed ...!?
+ * - Discworld Noir
*/
namespace Tinsel {
@@ -66,6 +67,7 @@ class Scroll;
class Dialogs;
class Notebook;
class SystemReel;
+class Spriter;
typedef Common::List<Common::Rect> RectList;
@@ -207,6 +209,7 @@ public:
Dialogs *_dialogs;
Notebook *_notebook = nullptr;
SystemReel *_systemReel = nullptr;
+ Spriter *_spriter = nullptr;
KEYFPTR _keyHandler;
Commit: d1f69ea958d562261033d25fceb40cede8f5dc30
https://github.com/scummvm/scummvm/commit/d1f69ea958d562261033d25fceb40cede8f5dc30
Author: Peter Kohaut (peter.kohaut at gmail.com)
Date: 2024-11-17T09:32:29+02:00
Commit Message:
TINSEL: Add basic 3D rendering for Noir
Supports only TinyGL because the game draws other objects behind and in
front of the 3d model.
Changed paths:
engines/tinsel/noir/spriter.cpp
engines/tinsel/noir/spriter.h
engines/tinsel/tinsel.cpp
diff --git a/engines/tinsel/noir/spriter.cpp b/engines/tinsel/noir/spriter.cpp
index 69ef8d16ae7..195de22795d 100644
--- a/engines/tinsel/noir/spriter.cpp
+++ b/engines/tinsel/noir/spriter.cpp
@@ -34,9 +34,14 @@
#include "math/quat.h"
+#if defined(USE_TINYGL)
+#include "graphics/tinygl/tinygl.h"
+#endif
+
namespace Tinsel {
#define PALETTE_COUNT 22
+#define TEXTURE_COUNT 4
#define MERGE_VERTICES_OFFSET 0.1f // find proper ratio, this is perhaps too big?
static float ConvertAngle(uint32 angle) {
@@ -51,6 +56,11 @@ Spriter::Spriter() {
}
Spriter::~Spriter() {
+#if defined(USE_TINYGL)
+ if (_textureGenerated) {
+ tglDeleteTextures(TEXTURE_COUNT, _texture);
+ }
+#endif
}
const Math::Matrix4& Spriter::MatrixCurrent() const {
@@ -389,7 +399,39 @@ void Spriter::LoadVMC(const Common::String& textureName) {
}
void Spriter::UpdateTextures() {
- // TODO apply pallete to textures
+#if defined(USE_TINYGL)
+ if (_textureGenerated) {
+ tglDeleteTextures(TEXTURE_COUNT, _texture);
+ }
+
+ tglGenTextures(TEXTURE_COUNT, _texture);
+ Common::Array<uint8> tex(256*256*3);
+
+ bool hasPalette = _palette.size() > 0;
+
+ for (uint i = 0; i < TEXTURE_COUNT; ++i) {
+ for (uint j = 0; j < 256 * 256; ++j) {
+ uint32 index = _textureData[(i * 65536) + j];
+ if (hasPalette) {
+ tex[(j * 3) + 0] = _palette[(index * 3) + 0];
+ tex[(j * 3) + 1] = _palette[(index * 3) + 1];
+ tex[(j * 3) + 2] = _palette[(index * 3) + 2];
+ } else {
+ tex[(j * 3) + 0] = index;
+ tex[(j * 3) + 1] = index;
+ tex[(j * 3) + 2] = index;
+ }
+ }
+ tglBindTexture(TGL_TEXTURE_2D, _texture[i]);
+ tglTexImage2D(TGL_TEXTURE_2D, 0, TGL_RGB, 256, 256, 0, TGL_RGB, TGL_UNSIGNED_BYTE, tex.data());
+ tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_WRAP_S, TGL_REPEAT);
+ tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_WRAP_T, TGL_REPEAT);
+ tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_MAG_FILTER, TGL_NEAREST);
+ tglTexParameteri(TGL_TEXTURE_2D, TGL_TEXTURE_MIN_FILTER, TGL_NEAREST);
+ tglBindTexture(TGL_TEXTURE_2D, 0);
+ }
+#endif
+ _textureGenerated = true;
}
void Spriter::SetPalette(SCNHANDLE hPalette) {
@@ -951,11 +993,70 @@ void Spriter::RenderMesh(Mesh& mesh, Vectors& vertices, Vectors &normals) {
}
void Spriter::RenderMeshPartColor(MeshPart& part, Vectors& vertices, Vectors &normals) {
- // TODO
+#if defined(USE_TINYGL)
+ if(!part.cull) {
+ tglEnable(TGL_CULL_FACE);
+ }
+
+ for (auto& prim : part.primitives) {
+ TGLubyte r = (prim.color >> 0) & 0xff;
+ TGLubyte g = (prim.color >> 8) & 0xff;
+ TGLubyte b = (prim.color >> 16) & 0xff;
+
+ tglColor3ub(r, g, b);
+
+ if (part.numVertices == 4) {
+ tglBegin(TGL_QUADS);
+ } else {
+ tglBegin(TGL_TRIANGLES);
+ }
+ for (uint32 i = 0; i < part.numVertices; ++i) {
+ uint32 index = prim.indices[i];
+
+ Math::Vector3d n = normals[index].getNormalized();
+ tglNormal3f(n.x(), n.y(), n.z());
+
+ Math::Vector3d& v = vertices[index];
+ tglVertex3f(v.x(), v.y(), v.z());
+ }
+ tglEnd();
+ }
+ tglDisable(TGL_CULL_FACE);
+#endif
}
void Spriter::RenderMeshPartTexture(MeshPart& part, Vectors& vertices, Vectors &normals) {
- // TODO
+#if defined(USE_TINYGL)
+ if (!part.cull) {
+ tglEnable(TGL_CULL_FACE);
+ }
+
+ tglEnable(TGL_TEXTURE_2D);
+ tglColor3f(1.0f, 1.0f, 1.0f);
+
+ for (auto& prim : part.primitives) {
+ tglBindTexture(TGL_TEXTURE_2D, _texture[prim.texture]);
+
+ if (part.numVertices == 4) {
+ tglBegin(TGL_QUADS);
+ } else {
+ tglBegin(TGL_TRIANGLES);
+ }
+ for (uint32 i = 0; i < part.numVertices; ++i) {
+ uint index = prim.indices[i];
+ tglTexCoord2f(prim.uv[i].getX() / 256.0f, prim.uv[i].getY() / 256.0f);
+
+ Math::Vector3d n = normals[index].getNormalized();
+ tglNormal3f(n.x(), n.y(), n.z());
+
+ Math::Vector3d& v = vertices[index];
+ tglVertex3f(v.x(), v.y(), v.z());
+ }
+ tglEnd();
+ }
+ tglDisable(TGL_TEXTURE_2D);
+ tglDisable(TGL_CULL_FACE);
+#endif
}
void Spriter::Load(const Common::String &modelName, const Common::String &textureName) {
@@ -1021,7 +1122,49 @@ void Spriter::RenderModel(Model &model) {
MatrixRotateZ(_view.rotation.z());
MatrixTranslate(-_view.position.x(), -_view.position.y(), -_view.position.z());
+#if defined(USE_TINYGL)
+ tglViewport(0, 0, _vm->screen().w, _vm->screen().h);
+
+ tglMatrixMode(TGL_PROJECTION);
+ tglLoadIdentity();
+ tglFrustum(-1.0f, 1.0f, -3.0f / 4.0f, 3.0f / 4.0f, 1.0f, 1000.0f);
+ // opengl uses bottom left
+ tglScalef(1.0f, -1.0f, 1.0f);
+ // Z is inverted, and we need to invert the face orientation too
+ tglScalef(1.0f, 1.0f, -1.0f);
+ tglFrontFace(TGL_CW);
+
+ tglMatrixMode(TGL_MODELVIEW);
+ tglLoadIdentity();
+
+ // tglRotatef(_view.rotation.x(), 1.0f, 0.0f, 0.0f);
+ // tglRotatef(_view.rotation.y(), 0.0f, 1.0f, 0.0f);
+ // tglRotatef(_view.rotation.z(), 0.0f, 0.0f, 1.0f);
+ // tglTranslatef(-_view.position.x(), -_view.position.y(), -_view.position.z());
+
+ tglEnable(TGL_DEPTH_TEST);
+ tglDepthFunc(TGL_LESS);
+ tglDepthMask(TGL_TRUE);
+ tglClearDepth(1.0f);
+ tglShadeModel(TGL_SMOOTH);
+
+ tglClear(TGL_DEPTH_BUFFER_BIT);
+
+#if 0
+ // code just for debuging model rendering, to be removed
+ tglEnable(TGL_LIGHTING);
+ TGLfloat light_position[] = { 0.0, 0.0, 0.0, 1.0 };
+ tglLightfv(TGL_LIGHT0, TGL_POSITION, light_position);
+ tglLightf(TGL_LIGHT0, TGL_CONSTANT_ATTENUATION, 1000.0f);
+ tglEnable(TGL_LIGHT0);
+#endif
+#endif
+
RunRenderProgram(model, false);
+
+#if defined(USE_TINYGL)
+ TinyGL::presentBuffer();
+#endif
}
bool Spriter::SetStartFrame(Model &model, const AnimationInfo &anim, int frame) {
diff --git a/engines/tinsel/noir/spriter.h b/engines/tinsel/noir/spriter.h
index a9949f82311..92a419dce31 100644
--- a/engines/tinsel/noir/spriter.h
+++ b/engines/tinsel/noir/spriter.h
@@ -33,6 +33,10 @@
#include "math/vector2d.h"
#include "math/matrix4.h"
+#if defined(USE_TINYGL)
+#include "graphics/tinygl/tinygl.h"
+#endif
+
namespace Tinsel {
typedef Common::FixedStack<Math::Matrix4, 30> MatrixStack;
diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp
index f79b8d70fa5..cc324aab821 100644
--- a/engines/tinsel/tinsel.cpp
+++ b/engines/tinsel/tinsel.cpp
@@ -33,6 +33,10 @@
#include "engines/util.h"
+#if defined(USE_TINYGL)
+#include "graphics/tinygl/tinygl.h"
+#endif
+
#include "tinsel/actors.h"
#include "tinsel/background.h"
#include "tinsel/bmv.h"
@@ -1023,10 +1027,16 @@ Common::Error TinselEngine::run() {
initGraphics(width, height, &noirFormat);
- _screenSurface.create(width, 432, noirFormat);
+#if defined(USE_TINYGL)
+ TinyGL::createContext(width, 432, noirFormat, 256, false, false);
+ TinyGL::getSurfaceRef(_screenSurface);
_spriter = new Spriter();
- _spriter->Init(width, height);
+ _spriter->Init(width, 432);
+#else
+ error("Discworld Noir needs ScummVM with TinyGL enabled");
+ return Common::kUnknownError;
+#endif
} else if (getGameID() == GID_DW2) {
if (ConfMan.getBool("crop_black_bars"))
initGraphics(640, 432);
Commit: 7d0320fe68b59dfd32c4b64059f52ff44bbe6aa3
https://github.com/scummvm/scummvm/commit/7d0320fe68b59dfd32c4b64059f52ff44bbe6aa3
Author: Filippos Karapetis (bluegr at gmail.com)
Date: 2024-11-17T09:32:29+02:00
Commit Message:
TINSEL: Fix typos
Changed paths:
engines/tinsel/background.cpp
engines/tinsel/cliprect.cpp
engines/tinsel/noir/spriter.cpp
diff --git a/engines/tinsel/background.cpp b/engines/tinsel/background.cpp
index df37e090e68..6ab1d2ad653 100644
--- a/engines/tinsel/background.cpp
+++ b/engines/tinsel/background.cpp
@@ -220,7 +220,7 @@ void Background::DrawBackgnd() {
// redraw all playfields within the clipping rectangles
const RectList &clipRects = GetClipRects();
- // Noir 3D model's cliprect is updated the rendering.
+ // Noir 3D model's cliprect is updated for rendering.
if (TinselVersion == 3) {
for (unsigned int i = 0; i < _pCurBgnd->fieldArray.size(); i++) {
// get pointer to correct playfield
diff --git a/engines/tinsel/cliprect.cpp b/engines/tinsel/cliprect.cpp
index 0258d82e46a..9673a215f62 100644
--- a/engines/tinsel/cliprect.cpp
+++ b/engines/tinsel/cliprect.cpp
@@ -283,13 +283,13 @@ void UpdateClipRectSingle(OBJECT *pObj, Common::Point *pWin, Common::Rect *pClip
// object totally clipped vertically - ignore
return;
- // set clip bit in objects flags
+ // set clip bit in object flags
currentObj.flags = pObj->flags | DMA_CLIP;
} else { // object is not clipped - copy flags
currentObj.flags = pObj->flags;
}
- // copy objects properties to local object
+ // copy object properties to local object
currentObj.width = pObj->width;
currentObj.height = pObj->height;
currentObj.xPos = (short)x;
diff --git a/engines/tinsel/noir/spriter.cpp b/engines/tinsel/noir/spriter.cpp
index 195de22795d..98ce5a7090a 100644
--- a/engines/tinsel/noir/spriter.cpp
+++ b/engines/tinsel/noir/spriter.cpp
@@ -439,7 +439,7 @@ void Spriter::SetPalette(SCNHANDLE hPalette) {
uint32 paletteHeaderSize = 2 + 2;
uint32 paletteBodySize = PALETTE_COUNT * 256 * sizeof(uint16);
- /* Select only one palette as they are sorded by light intensity */
+ /* Select only one palette, as they are sorted by light intensity */
uint32 paletteId = PALETTE_COUNT - 1;
Graphics::PixelFormat paletteFormat(2, 5, 6, 5, 0, 11, 5, 0, 0);
@@ -981,7 +981,7 @@ void Spriter::RenderMesh(Mesh& mesh, Vectors& vertices, Vectors &normals) {
RenderMeshPartColor(part, vertices, normals);
break;
case MESH_PART_TYPE_SOLID:
- // This is just use white color
+ // This just uses white color
//RenderMeshPartColor(part, vertices, normals);
break;
case MESH_PART_TYPE_TEXTURE:
Commit: e4b4ca03fb2900aa2080786ebf8dc0203f8111d2
https://github.com/scummvm/scummvm/commit/e4b4ca03fb2900aa2080786ebf8dc0203f8111d2
Author: Peter Kohaut (peter.kohaut at gmail.com)
Date: 2024-11-17T09:32:29+02:00
Commit Message:
TINSEL: Improve comments for Noir 3D renderer
Changed paths:
engines/tinsel/background.cpp
engines/tinsel/noir/spriter.cpp
engines/tinsel/noir/spriter.h
engines/tinsel/scene.cpp
diff --git a/engines/tinsel/background.cpp b/engines/tinsel/background.cpp
index 6ab1d2ad653..c7dac05e31f 100644
--- a/engines/tinsel/background.cpp
+++ b/engines/tinsel/background.cpp
@@ -233,6 +233,7 @@ void Background::DrawBackgnd() {
for (OBJECT* pObj = pPlay->pDispList; pObj != NULL; pObj = pObj->pNext) {
if (pObj->flags & DMA_3D) {
_vm->_spriter->Draw(0, 0, 0, 0, 0);
+ // TODO: fix clip rect after the Spriter updates them
//AddClipRect(SpriterGetModelRect());
//AddClipRect(SpriterGetShadowRect());
//MergeClipRect();
diff --git a/engines/tinsel/noir/spriter.cpp b/engines/tinsel/noir/spriter.cpp
index 98ce5a7090a..a416169db19 100644
--- a/engines/tinsel/noir/spriter.cpp
+++ b/engines/tinsel/noir/spriter.cpp
@@ -42,7 +42,7 @@ namespace Tinsel {
#define PALETTE_COUNT 22
#define TEXTURE_COUNT 4
-#define MERGE_VERTICES_OFFSET 0.1f // find proper ratio, this is perhaps too big?
+#define MERGE_VERTICES_OFFSET 0.1f // TODO: find the proper ratio, this one is perhaps too big?
static float ConvertAngle(uint32 angle) {
return ((float(angle & 0xfff) / 4095.0f) * 360.0f);
@@ -163,7 +163,7 @@ void Spriter::TransformSceneXYZ(int x, int y, int z, int& xOut, int& yOut) {
MatrixCurrent().transform(&v, true);
- // apply viewport
+ // Apply the viewport
xOut = _view.centerX + v.x();
yOut = _view.centerY + v.y();
}
@@ -334,17 +334,7 @@ void Spriter::LoadRBH(const Common::String& modelName, Hunks& hunks) {
} else if (tag == kRELC) {
uint srcIdx = f.readUint32LE();
uint dstIdx = f.readUint32LE();
-
- // SCUMMVM implementation does not need to read offsets
-
- // uint entries = (size - sizeof(uint32) * 2) / sizeof(uint32);
- // uint32* dstDataPtr = (uint32*)_rbh[dstIdx].data.data();
- // while (entries > 0)
- // {
- // uint offset = f.readUint32LE();
- // --entries;
- // }
- f.skip(size - 8);
+ f.skip(size - 8); // ScummVM's implementation does not need to read the offsets for relocation
hunks[srcIdx].mappingIdx.push_back(dstIdx);
} else {
assert(false);
@@ -530,11 +520,12 @@ Common::Rect Spriter::Draw(int direction, int x, int y, int z, int tDelta) {
}
}
- // do gradual direction change when model is idle - game is using 5 steps
+ // TODO: Do a gradual direction change when the model is idle - Noir is using 5 steps.
_modelMain.rotation.set(0, ConvertAngle(direction), 0);
RenderModel(_modelMain);
+ // TODO: Add the shadow rendering after the lights are implemented
// int shadowId = 3;
// if (_animId == 2) {
// shadowId = ((_modelMain.endFrame + 17) % 18) + 1;
@@ -674,7 +665,7 @@ Meshes Spriter::LoadMeshes(const Hunks &hunks, uint hunk, uint offset, int frame
prim.color = primitivesStream.readUint32LE();
break;
case MESH_PART_TYPE_SOLID:
- assert(false); //not used?
+ assert(false); // TODO: not used? maybe in overlay model?
break;
case MESH_PART_TYPE_TEXTURE:
// Has texture
@@ -756,7 +747,7 @@ void Spriter::InitModel(Model &model, MeshInfo &meshInfo, Common::Array<Animatio
MatrixReset();
- // Preprocess vertices - merge vertices
+ // Preprocess model - merge vertices that are close to each other
RunRenderProgram(model, true);
bool valid = true;
@@ -770,11 +761,11 @@ void Spriter::InitModel(Model &model, MeshInfo &meshInfo, Common::Array<Animatio
model.tables.translations.resize(animInfo.translateNum);
}
- assert(valid); // Animation tables are incorrect
+ assert(valid); // Warn if animation tables are incorrect
for (uint i = 0; i < model.animationCount; ++i) {
+ // TODO: check if animation data has same number of frames for all transformation types
// SetStartFrame(model, animInfos[i], 0);
- // check if animation data has same number of frames for all transformation types
}
}
@@ -806,7 +797,7 @@ void Spriter::RunRenderProgram(Model &model, bool preprocess) {
break;
}
case UNUSED: {
- // TODO
+ // TODO: Check where is this used. In some model overlay?
// uint16 entry = READ_LE_UINT16(&program[ip]);
ip += 2;
break;
@@ -928,7 +919,7 @@ void Spriter::FindSimilarVertices(Mesh& mesh, Vectors& vertices, Common::Array<u
for (uint j = 0; j < vertices.size() - 1; ++j) {
float d = vOut.getDistanceTo(vertices[j]);
if (d < MERGE_VERTICES_OFFSET) {
- sameVertices[i_start + i] = j + 1; // 0 is reserved for not found
+ sameVertices[i_start + i] = j + 1; // 0 is reserved for "not found"
break;
}
}
@@ -948,7 +939,7 @@ void Spriter::MergeVertices(Mesh &mesh, Common::Array<uint16>& sameVertices) {
}
void Spriter::TransformMesh(Mesh& mesh, Vectors& vertices) {
- // Transformed vertices from previous meshes might be in the current mesh.
+ // Transformed vertices from previous meshes might be used by the current mesh.
const Math::Matrix4 &m = MatrixCurrent();
for (auto& vIn : mesh.vertices) {
@@ -981,8 +972,9 @@ void Spriter::RenderMesh(Mesh& mesh, Vectors& vertices, Vectors &normals) {
RenderMeshPartColor(part, vertices, normals);
break;
case MESH_PART_TYPE_SOLID:
+ // TODO: Check where is this used. In some model overlay?
// This just uses white color
- //RenderMeshPartColor(part, vertices, normals);
+ // RenderMeshPartColor(part, vertices, normals);
break;
case MESH_PART_TYPE_TEXTURE:
RenderMeshPartTexture(part, vertices, normals);
@@ -1067,9 +1059,8 @@ void Spriter::Load(const Common::String &modelName, const Common::String &textur
InitModel(_modelMain, _meshMain, _animMain, MODEL_HAS_TRANSLATION_TABLE | MODEL_HAS_ROTATION_TABLE);
- // for (uint i = 0; i < _animMain.size(); ++i) {
- // update max frame
- // }
+ // TODO: Check where is this used. In some model overlay?
+ // for (uint i = 0; i < _animMain.size(); ++i) { }
_modelIdle = true;
_modelMain.time = 0;
@@ -1127,16 +1118,17 @@ void Spriter::RenderModel(Model &model) {
tglMatrixMode(TGL_PROJECTION);
tglLoadIdentity();
- tglFrustum(-1.0f, 1.0f, -3.0f / 4.0f, 3.0f / 4.0f, 1.0f, 1000.0f);
- // opengl uses bottom left
+ tglFrustum(-1.0f, 1.0f, -3.0f / 4.0f, 3.0f / 4.0f, 1.0f, 1000.0f);
+ // GL uses bottom left system
tglScalef(1.0f, -1.0f, 1.0f);
- // Z is inverted, and we need to invert the face orientation too
+ // Z is inverted in GL, we need to invert the face orientation too.
tglScalef(1.0f, 1.0f, -1.0f);
tglFrontFace(TGL_CW);
tglMatrixMode(TGL_MODELVIEW);
tglLoadIdentity();
+ // TODO: Use GL's view matrix instead of the game one.
// tglRotatef(_view.rotation.x(), 1.0f, 0.0f, 0.0f);
// tglRotatef(_view.rotation.y(), 0.0f, 1.0f, 0.0f);
// tglRotatef(_view.rotation.z(), 0.0f, 0.0f, 1.0f);
@@ -1151,7 +1143,7 @@ void Spriter::RenderModel(Model &model) {
tglClear(TGL_DEPTH_BUFFER_BIT);
#if 0
- // code just for debuging model rendering, to be removed
+ // TODO: Remove. Used only for debugging of the normals calculation and rendering.
tglEnable(TGL_LIGHTING);
TGLfloat light_position[] = { 0.0, 0.0, 0.0, 1.0 };
tglLightfv(TGL_LIGHT0, TGL_POSITION, light_position);
diff --git a/engines/tinsel/noir/spriter.h b/engines/tinsel/noir/spriter.h
index 92a419dce31..6f48a47383d 100644
--- a/engines/tinsel/noir/spriter.h
+++ b/engines/tinsel/noir/spriter.h
@@ -147,7 +147,7 @@ enum ModelFlags {
struct Model {
Hunks hunks;
- Hunks hunksU;
+ Hunks hunksOverlay; // The game script can load additional data to supplement the main model. E.g. a special animation.
uint animationCount;
uint field_0xe;
uint field_0xf;
diff --git a/engines/tinsel/scene.cpp b/engines/tinsel/scene.cpp
index 5551b9785a2..a6f7ef7b390 100644
--- a/engines/tinsel/scene.cpp
+++ b/engines/tinsel/scene.cpp
@@ -566,16 +566,16 @@ void SetView(int sceneId, int scale) {
LIGHT_STRUC *pLight = (LIGHT_STRUC *)_vm->_handle->LockMem(g_tempStruc.hLight);
for (i = 0; i < g_tempStruc.numLights; ++i, ++pLight) {
if (sceneId == (int)FROM_32(pLight->sceneId)) {
- // set light
+ // TODO: Load lights
break;
}
}
if (i == g_tempStruc.numLights) {
- // use default light
+ // if no lights are present use the default light
}
- //update ground plane
+ // TODO: Update the ground plane
}
} // End of namespace Tinsel
Commit: f8e35460c9bcd9fa6e8446407b479e8e9d196cd6
https://github.com/scummvm/scummvm/commit/f8e35460c9bcd9fa6e8446407b479e8e9d196cd6
Author: Peter Kohaut (peter.kohaut at gmail.com)
Date: 2024-11-17T09:32:29+02:00
Commit Message:
TINSEL: Return correct error when TinyGL is disabled
Changed paths:
engines/tinsel/tinsel.cpp
diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp
index cc324aab821..12d625476ee 100644
--- a/engines/tinsel/tinsel.cpp
+++ b/engines/tinsel/tinsel.cpp
@@ -1034,8 +1034,7 @@ Common::Error TinselEngine::run() {
_spriter = new Spriter();
_spriter->Init(width, 432);
#else
- error("Discworld Noir needs ScummVM with TinyGL enabled");
- return Common::kUnknownError;
+ return Common::Error(Common::kUnsupportedGameidError, _s("Discworld Noir needs ScummVM with TinyGL enabled"));
#endif
} else if (getGameID() == GID_DW2) {
if (ConfMan.getBool("crop_black_bars"))
Commit: 4831f25b374be77d1574af9bd6bcae8ae1b8a6e3
https://github.com/scummvm/scummvm/commit/4831f25b374be77d1574af9bd6bcae8ae1b8a6e3
Author: Peter Kohaut (peter.kohaut at gmail.com)
Date: 2024-11-17T09:32:29+02:00
Commit Message:
TINSEL: Make TinyGL memory requirements explicit
Changed paths:
engines/tinsel/tinsel.cpp
diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp
index 12d625476ee..fdbec47ac2d 100644
--- a/engines/tinsel/tinsel.cpp
+++ b/engines/tinsel/tinsel.cpp
@@ -1028,7 +1028,9 @@ Common::Error TinselEngine::run() {
initGraphics(width, height, &noirFormat);
#if defined(USE_TINYGL)
- TinyGL::createContext(width, 432, noirFormat, 256, false, false);
+ // TODO: Find minimal viable drawcall memory
+ constexpr uint32 drawCallMemory = 1024 * 1024;
+ TinyGL::createContext(width, 432, noirFormat, 256, false, false, drawCallMemory);
TinyGL::getSurfaceRef(_screenSurface);
_spriter = new Spriter();
More information about the Scummvm-git-logs
mailing list