aboutsummaryrefslogtreecommitdiff
path: root/engines/sci
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci')
-rw-r--r--engines/sci/console.cpp18
-rw-r--r--engines/sci/console.h1
-rw-r--r--engines/sci/engine/kgraphics32.cpp186
-rw-r--r--engines/sci/engine/object.h36
-rw-r--r--engines/sci/engine/selector.cpp11
-rw-r--r--engines/sci/engine/selector.h2
-rw-r--r--engines/sci/engine/vm_types.cpp14
-rw-r--r--engines/sci/engine/vm_types.h13
-rw-r--r--engines/sci/graphics/celobj32.cpp988
-rw-r--r--engines/sci/graphics/celobj32.h577
-rw-r--r--engines/sci/graphics/frameout.cpp2355
-rw-r--r--engines/sci/graphics/frameout.h554
-rw-r--r--engines/sci/graphics/helpers.h81
-rw-r--r--engines/sci/graphics/lists32.h192
-rw-r--r--engines/sci/graphics/palette32.cpp27
-rw-r--r--engines/sci/graphics/palette32.h383
-rw-r--r--engines/sci/graphics/picture.cpp6
-rw-r--r--engines/sci/graphics/picture.h3
-rw-r--r--engines/sci/graphics/plane32.cpp841
-rw-r--r--engines/sci/graphics/plane32.h465
-rw-r--r--engines/sci/graphics/screen.h5
-rw-r--r--engines/sci/graphics/screen_item32.cpp534
-rw-r--r--engines/sci/graphics/screen_item32.h280
-rw-r--r--engines/sci/module.mk3
-rw-r--r--engines/sci/sci.cpp1
25 files changed, 6426 insertions, 1150 deletions
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 438c725324..bea67e6535 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -137,6 +137,8 @@ Console::Console(SciEngine *engine) : GUI::Debugger(),
registerCmd("wl", WRAP_METHOD(Console, cmdWindowList)); // alias
registerCmd("plane_list", WRAP_METHOD(Console, cmdPlaneList));
registerCmd("pl", WRAP_METHOD(Console, cmdPlaneList)); // alias
+ registerCmd("visible_plane_list", WRAP_METHOD(Console, cmdVisiblePlaneList));
+ registerCmd("vpl", WRAP_METHOD(Console, cmdVisiblePlaneList)); // alias
registerCmd("plane_items", WRAP_METHOD(Console, cmdPlaneItemList));
registerCmd("pi", WRAP_METHOD(Console, cmdPlaneItemList)); // alias
registerCmd("saved_bits", WRAP_METHOD(Console, cmdSavedBits));
@@ -380,6 +382,7 @@ bool Console::cmdHelp(int argc, const char **argv) {
debugPrintf(" animate_list / al - Shows the current list of objects in kAnimate's draw list (SCI0 - SCI1.1)\n");
debugPrintf(" window_list / wl - Shows a list of all the windows (ports) in the draw list (SCI0 - SCI1.1)\n");
debugPrintf(" plane_list / pl - Shows a list of all the planes in the draw list (SCI2+)\n");
+ debugPrintf(" visible_plane_list / vpl - Shows a list of all the planes in the visible draw list (SCI2+)\n");
debugPrintf(" plane_items / pi - Shows a list of all items for a plane (SCI2+)\n");
debugPrintf(" saved_bits - List saved bits on the hunk\n");
debugPrintf(" show_saved_bits - Display saved bits\n");
@@ -1766,6 +1769,21 @@ bool Console::cmdPlaneList(int argc, const char **argv) {
return true;
}
+bool Console::cmdVisiblePlaneList(int argc, const char **argv) {
+#ifdef ENABLE_SCI32
+ if (_engine->_gfxFrameout) {
+ debugPrintf("Visible plane list:\n");
+ _engine->_gfxFrameout->printVisiblePlaneList(this);
+ } else {
+ debugPrintf("This SCI version does not have a list of planes\n");
+ }
+#else
+ debugPrintf("SCI32 isn't included in this compiled executable\n");
+#endif
+ return true;
+}
+
+
bool Console::cmdPlaneItemList(int argc, const char **argv) {
if (argc != 2) {
debugPrintf("Shows the list of items for a plane\n");
diff --git a/engines/sci/console.h b/engines/sci/console.h
index 8b10912fbe..7c4de02182 100644
--- a/engines/sci/console.h
+++ b/engines/sci/console.h
@@ -96,6 +96,7 @@ private:
bool cmdAnimateList(int argc, const char **argv);
bool cmdWindowList(int argc, const char **argv);
bool cmdPlaneList(int argc, const char **argv);
+ bool cmdVisiblePlaneList(int argc, const char **argv);
bool cmdPlaneItemList(int argc, const char **argv);
bool cmdSavedBits(int argc, const char **argv);
bool cmdShowSavedBits(int argc, const char **argv);
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index 8d41393a9e..93e8b90ecc 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -80,21 +80,18 @@ reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv) {
}
reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) {
- if (g_sci->_gfxFrameout->findScreenItem(argv[0]) == NULL)
- g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]);
- else
- g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]);
- return s->r_acc;
+ g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]);
+ return NULL_REG;
}
reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv) {
g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]);
- return s->r_acc;
+ return NULL_REG;
}
reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv) {
g_sci->_gfxFrameout->kernelDeleteScreenItem(argv[0]);
- return s->r_acc;
+ return NULL_REG;
}
reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) {
@@ -115,11 +112,12 @@ reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) {
reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv) {
reg_t planeObj = argv[0];
GuiResourceId pictureId = argv[1].toUint16();
- int16 pictureX = argv[2].toSint16();
- int16 pictureY = argv[3].toSint16();
+ int16 x = argv[2].toSint16();
+ int16 y = argv[3].toSint16();
+ bool mirrorX = argc > 4 ? argv[4].toSint16() : false;
- g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, pictureX, pictureY);
- return s->r_acc;
+ g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, x, y, mirrorX);
+ return NULL_REG;
}
reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) {
@@ -127,43 +125,13 @@ reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) {
}
reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) {
-/* TODO: Transcribed from SCI engine disassembly.
- GraphicsMgr &graphicsMgr = g_sci->_graphicsMgr;
- if (graphicsMgr.palMorphNeeded) {
- graphicsMgr.PalMorphFrameOut(&g_PalStyleRanges, false);
- }
- else {
- // TODO: Not sure if this is a pointer or not yet.
- if (g_ScrollState != nullptr) {
- kFrameOutDoScroll();
- }
-
- bool showBits = true;
- if (argc == 1) {
- showBits = (bool) argv[0].toUint16();
- }
-
- rect SOL_Rect = { .left = 0, .top = 0, .right = UINT32_MAX, .bottom = UINT32_MAX };
- graphicsMgr.FrameOut(showBits, &rect);
- }
-*/
- g_sci->_gfxFrameout->kernelFrameout();
+ bool showBits = argc > 0 ? argv[0].toUint16() : true;
+ g_sci->_gfxFrameout->kernelFrameout(showBits);
return NULL_REG;
}
reg_t kSetPalStyleRange(EngineState *s, int argc, reg_t *argv) {
-/* TODO: Transcribed from SCI engine disassembly.
- uint16 start = argv[0].toUint16();
- uint16 end = argv[1].toUint16();
- if (end <= start) {
- uint16 index = start;
- while (index <= end) {
- g_PalStyleRanges[index] = 0;
- }
- }
-*/
-
- kStub(s, argc, argv);
+ g_sci->_gfxFrameout->kernelSetPalStyleRange(argv[0].toUint16(), argv[1].toUint16());
return NULL_REG;
}
@@ -266,72 +234,59 @@ reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) {
}
/**
- * Used for scene transitions, replacing (but reusing parts of) the old
- * transition code.
+ * Causes an immediate plane transition with an optional transition
+ * effect
*/
reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) {
- // Can be called with 7 or 8 parameters
- // The style defines which transition to perform. Related to the transition
- // tables inside graphics/transitions.cpp
- uint16 showStyle = argv[0].toUint16(); // 0 - 15
- reg_t planeObj = argv[1]; // the affected plane
- Common::String planeObjName = s->_segMan->getObjectName(planeObj);
- uint16 seconds = argv[2].toUint16(); // seconds that the transition lasts
- uint16 backColor = argv[3].toUint16(); // target back color(?). When fading out, it's 0x0000. When fading in, it's 0xffff
- int16 priority = argv[4].toSint16(); // always 0xc8 (200) when fading in/out
- uint16 animate = argv[5].toUint16(); // boolean, animate or not while the transition lasts
- uint16 refFrame = argv[6].toUint16(); // refFrame, always 0 when fading in/out
+ ShowStyleType type = (ShowStyleType)argv[0].toUint16();
+ reg_t planeObj = argv[1];
+ int16 seconds = argv[2].toSint16();
+ // NOTE: This value seems to indicate whether the transition is an
+ // “exit” transition (0) or an “enter” transition (-1) for fade
+ // transitions. For other types of transitions, it indicates a palette
+ // index value to use when filling the screen.
+ int16 back = argv[3].toSint16();
+ int16 priority = argv[4].toSint16();
+ int16 animate = argv[5].toSint16();
+ // TODO: Rename to frameOutNow?
+ int16 refFrame = argv[6].toSint16();
+ int16 blackScreen;
+ reg_t pFadeArray;
int16 divisions;
- // If the game has the pFadeArray selector, another parameter is used here,
- // before the optional last parameter
- bool hasFadeArray = g_sci->getKernel()->findSelector("pFadeArray") > 0;
- if (hasFadeArray) {
- // argv[7]
- divisions = (argc >= 9) ? argv[8].toSint16() : -1; // divisions (transition steps?)
- } else {
- divisions = (argc >= 8) ? argv[7].toSint16() : -1; // divisions (transition steps?)
+ // SCI 2–2.1early
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ blackScreen = 0;
+ pFadeArray = NULL_REG;
+ divisions = argc > 7 ? argv[7].toSint16() : -1;
}
-
- if (showStyle > 15) {
- warning("kSetShowStyle: Illegal style %d for plane %04x:%04x", showStyle, PRINT_REG(planeObj));
- return s->r_acc;
+ // SCI 2.1mid–2.1late
+ else if (getSciVersion() < SCI_VERSION_3) {
+ blackScreen = 0;
+ pFadeArray = argc > 7 ? argv[7] : NULL_REG;
+ divisions = argc > 8 ? argv[8].toSint16() : -1;
+ }
+ // SCI 3
+ else {
+ blackScreen = argv[7].toSint16();
+ pFadeArray = argc > 8 ? argv[8] : NULL_REG;
+ divisions = argc > 9 ? argv[9].toSint16() : -1;
}
- // GK1 calls fadeout (13) / fadein (14) with the following parameters:
- // seconds: 1
- // backColor: 0 / -1
- // fade: 200
- // animate: 0
- // refFrame: 0
- // divisions: 0 / 20
-
- // TODO: Check if the plane is in the list of planes to draw
-
- Common::String effectName = "unknown";
+// TODO: Reuse later for SCI2 and SCI3 implementation and then discard
+// warning("kSetShowStyle: effect %d, plane: %04x:%04x (%s), sec: %d, "
+// "dir: %d, prio: %d, animate: %d, ref frame: %d, black screen: %d, "
+// "pFadeArray: %04x:%04x (%s), divisions: %d",
+// type, PRINT_REG(planeObj), s->_segMan->getObjectName(planeObj), seconds,
+// back, priority, animate, refFrame, blackScreen,
+// PRINT_REG(pFadeArray), s->_segMan->getObjectName(pFadeArray), divisions);
- switch (showStyle) {
- case 0: // no transition / show
- effectName = "show";
- break;
- case 13: // fade out
- effectName = "fade out";
- // TODO
- break;
- case 14: // fade in
- effectName = "fade in";
- // TODO
- break;
- default:
- // TODO
- break;
- }
+ // NOTE: The order of planeObj and showStyle are reversed
+ // because this is how SCI3 called the corresponding method
+ // on the KernelMgr
+ g_sci->_gfxFrameout->kernelSetShowStyle(argc, planeObj, type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen);
- warning("kSetShowStyle: effect %d (%s) - plane: %04x:%04x (%s), sec: %d, "
- "back: %d, prio: %d, animate: %d, ref frame: %d, divisions: %d",
- showStyle, effectName.c_str(), PRINT_REG(planeObj), planeObjName.c_str(),
- seconds, backColor, priority, animate, refFrame, divisions);
- return s->r_acc;
+ return NULL_REG;
}
reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) {
@@ -359,6 +314,8 @@ reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) {
}
reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
+ return kStub(s, argc, argv);
+#if 0
// Used by SQ6 and LSL6 hires for the text area in the bottom of the
// screen. The relevant scripts also exist in Phantasmagoria 1, but they're
// unused. This is always called by scripts 64906 (ScrollerWindow) and
@@ -464,6 +421,7 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) {
}
return s->r_acc;
+#endif
}
reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) {
@@ -497,6 +455,8 @@ reg_t kFont(EngineState *s, int argc, reg_t *argv) {
// TODO: Eventually, all of the kBitmap operations should be put
// in a separate class
+// NOTE: This size is correct only for SCI2.1mid; the size for
+// SCI2/2.1early is 36
#define BITMAP_HEADER_SIZE 46
reg_t kBitmap(EngineState *s, int argc, reg_t *argv) {
@@ -673,6 +633,8 @@ reg_t kEditText(EngineState *s, int argc, reg_t *argv) {
}
reg_t kAddLine(EngineState *s, int argc, reg_t *argv) {
+ return kStub(s, argc, argv);
+#if 0
reg_t plane = argv[0];
Common::Point startPoint(argv[1].toUint16(), argv[2].toUint16());
Common::Point endPoint(argv[3].toUint16(), argv[4].toUint16());
@@ -681,10 +643,15 @@ reg_t kAddLine(EngineState *s, int argc, reg_t *argv) {
byte priority = (byte)argv[7].toUint16();
byte control = (byte)argv[8].toUint16();
// argv[9] is unknown (usually a small number, 1 or 2). Thickness, perhaps?
- return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, control);
+// return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, control);
+ return s->r_acc;
+#endif
}
reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) {
+ return kStub(s, argc, argv);
+
+#if 0
reg_t hunkId = argv[0];
reg_t plane = argv[1];
Common::Point startPoint(argv[2].toUint16(), argv[3].toUint16());
@@ -694,14 +661,18 @@ reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) {
byte priority = (byte)argv[8].toUint16();
byte control = (byte)argv[9].toUint16();
// argv[10] is unknown (usually a small number, 1 or 2). Thickness, perhaps?
- g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control);
+// g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control);
return s->r_acc;
+#endif
}
reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv) {
+ return kStub(s, argc, argv);
+#if 0
reg_t hunkId = argv[0];
reg_t plane = argv[1];
- g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId);
+// g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId);
return s->r_acc;
+#endif
}
reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) {
@@ -730,12 +701,7 @@ reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) {
// Used by SQ6, script 900, the datacorder reprogramming puzzle (from room 270)
reg_t kMorphOn(EngineState *s, int argc, reg_t *argv) {
- // TODO: g_sci->_gfxManager->palMorphIsOn = true
- // This function sets the palMorphIsOn flag which causes kFrameOut to use
- // an alternative FrameOut function (GraphicsMgr::PalMorphFrameOut instead
- // of GraphicsMgr::FrameOut). At the end of the frame, kFrameOut sets the
- // palMorphIsOn flag back to false.
- kStub(s, argc, argv);
+ g_sci->_gfxFrameout->_palMorphIsOn = true;
return NULL_REG;
}
diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h
index 0ae7ed2cab..cc9f5ebb52 100644
--- a/engines/sci/engine/object.h
+++ b/engines/sci/engine/object.h
@@ -41,8 +41,21 @@ enum {
};
enum infoSelectorFlags {
- kInfoFlagClone = 0x0001,
- kInfoFlagClass = 0x8000
+ kInfoFlagClone = 0x0001,
+#ifdef ENABLE_SCI32
+ /**
+ * When set, indicates to game scripts that a screen
+ * item can be updated.
+ */
+ kInfoFlagViewVisible = 0x0008, // TODO: "dirty" ?
+
+ /**
+ * When set, the object has an associated screen item in
+ * the rendering tree.
+ */
+ kInfoFlagViewInserted = 0x0010,
+#endif
+ kInfoFlagClass = 0x8000
};
enum ObjectOffsets {
@@ -120,7 +133,24 @@ public:
_infoSelectorSci3 = info;
}
- // No setter for the -info- selector
+#ifdef ENABLE_SCI32
+ void setInfoSelectorFlag(infoSelectorFlags flag) {
+ if (getSciVersion() < SCI_VERSION_3) {
+ _variables[_offset + 2] |= flag;
+ } else {
+ _infoSelectorSci3 |= flag;
+ }
+ }
+
+ // NOTE: In real engine, -info- is treated as byte size
+ void clearInfoSelectorFlag(infoSelectorFlags flag) {
+ if (getSciVersion() < SCI_VERSION_3) {
+ _variables[_offset + 2] &= ~flag;
+ } else {
+ _infoSelectorSci3 &= ~flag;
+ }
+ }
+#endif
reg_t getNameSelector() const {
if (getSciVersion() < SCI_VERSION_3)
diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp
index 910f1f885f..05a9e6c980 100644
--- a/engines/sci/engine/selector.cpp
+++ b/engines/sci/engine/selector.cpp
@@ -187,6 +187,7 @@ void Kernel::mapSelectors() {
FIND_SELECTOR(inLeft);
FIND_SELECTOR(inBottom);
FIND_SELECTOR(inRight);
+ FIND_SELECTOR(magnifier);
#endif
}
@@ -211,8 +212,16 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t
if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable)
error("Selector '%s' of object at %04x:%04x could not be"
" written to", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object));
- else
+ else {
*address.getPointer(segMan) = value;
+#ifdef ENABLE_SCI32
+ // TODO: Make this correct for all SCI versions
+ // Selectors 26 through 44 are selectors for View script objects
+ if (getSciVersion() >= SCI_VERSION_2 && selectorId >= 26 && selectorId <= 44) {
+ segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewVisible);
+ }
+#endif
+ }
}
void invokeSelector(EngineState *s, reg_t object, int selectorId,
diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h
index b3dd393708..a8b195f245 100644
--- a/engines/sci/engine/selector.h
+++ b/engines/sci/engine/selector.h
@@ -153,6 +153,8 @@ struct SelectorCache {
Selector useInsetRect;
Selector inTop, inLeft, inBottom, inRight;
+
+ Selector magnifier;
#endif
};
diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp
index cf008c45e1..53a5a5c507 100644
--- a/engines/sci/engine/vm_types.cpp
+++ b/engines/sci/engine/vm_types.cpp
@@ -210,6 +210,20 @@ reg_t reg_t::operator^(const reg_t right) const {
return lookForWorkaround(right, "bitwise XOR");
}
+#ifdef ENABLE_SCI32
+reg_t reg_t::operator&(int16 right) const {
+ return *this & make_reg(0, right);
+}
+
+reg_t reg_t::operator|(int16 right) const {
+ return *this | make_reg(0, right);
+}
+
+reg_t reg_t::operator^(int16 right) const {
+ return *this ^ make_reg(0, right);
+}
+#endif
+
int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const {
if (getSegment() == right.getSegment()) { // can compare things in the same segment
if (treatAsUnsigned || !isNumber())
diff --git a/engines/sci/engine/vm_types.h b/engines/sci/engine/vm_types.h
index af78bd0b84..a646478a8e 100644
--- a/engines/sci/engine/vm_types.h
+++ b/engines/sci/engine/vm_types.h
@@ -136,6 +136,19 @@ struct reg_t {
reg_t operator|(const reg_t right) const;
reg_t operator^(const reg_t right) const;
+#ifdef ENABLE_SCI32
+ reg_t operator&(int16 right) const;
+ reg_t operator|(int16 right) const;
+ reg_t operator^(int16 right) const;
+
+ void operator&=(const reg_t &right) { *this = *this & right; }
+ void operator|=(const reg_t &right) { *this = *this | right; }
+ void operator^=(const reg_t &right) { *this = *this ^ right; }
+ void operator&=(int16 right) { *this = *this & right; }
+ void operator|=(int16 right) { *this = *this | right; }
+ void operator^=(int16 right) { *this = *this ^ right; }
+#endif
+
private:
/**
* Compares two reg_t's.
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
new file mode 100644
index 0000000000..4b8d9af384
--- /dev/null
+++ b/engines/sci/graphics/celobj32.cpp
@@ -0,0 +1,988 @@
+/* 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.
+ *
+ */
+
+#include "sci/resource.h"
+#include "sci/engine/seg_manager.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/picture.h"
+#include "sci/graphics/view.h"
+
+namespace Sci {
+#pragma mark CelScaler
+CelScaler *CelObj::_scaler = nullptr;
+
+void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+ for (int i = 0; i < ARRAYSIZE(_scaleTables); ++i) {
+ if (_scaleTables[i].scaleX == scaleX && _scaleTables[i].scaleY == scaleY) {
+ _activeIndex = i;
+ return;
+ }
+ }
+
+ int i = 1 - _activeIndex;
+ _activeIndex = i;
+ CelScalerTable &table = _scaleTables[i];
+
+ if (table.scaleX != scaleX) {
+ assert(screenWidth <= ARRAYSIZE(table.valuesX));
+ buildLookupTable(table.valuesX, scaleX, screenWidth);
+ }
+
+ if (table.scaleY != scaleY) {
+ assert(screenHeight <= ARRAYSIZE(table.valuesY));
+ buildLookupTable(table.valuesY, scaleY, screenHeight);
+ }
+}
+
+void CelScaler::buildLookupTable(int *table, const Ratio &ratio, const int size) {
+ int value = 0;
+ int remainder = 0;
+ int num = ratio.getNumerator();
+ for (int i = 0; i < size; ++i) {
+ *table++ = value;
+ remainder += ratio.getDenominator();
+ if (remainder >= num) {
+ value += remainder / num;
+ remainder %= num;
+ }
+ }
+}
+
+const CelScalerTable *CelScaler::getScalerTable(const Ratio &scaleX, const Ratio &scaleY) {
+ activateScaleTables(scaleX, scaleY);
+ return &_scaleTables[_activeIndex];
+}
+
+#pragma mark -
+#pragma mark CelObj
+
+void CelObj::init() {
+ _nextCacheId = 1;
+ delete _scaler;
+ _scaler = new CelScaler();
+ delete _cache;
+ _cache = new CelCache;
+ _cache->resize(100);
+}
+
+void CelObj::deinit() {
+ delete _scaler;
+ _scaler = nullptr;
+ delete _cache;
+ _cache = nullptr;
+}
+
+void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const {
+ const Buffer &priorityMap = g_sci->_gfxFrameout->getPriorityMap();
+ const Common::Point &scaledPosition = screenItem._scaledPosition;
+ const Ratio &scaleX = screenItem._ratioX;
+ const Ratio &scaleY = screenItem._ratioY;
+
+ if (_remap) {
+ if (g_sci->_gfxFrameout->_hasRemappedScreenItem) {
+ const uint8 priority = MAX((uint8)0, MIN((uint8)255, (uint8)screenItem._priority));
+
+ // NOTE: In the original engine code, there was a second branch for
+ // _remap here that would then call the following functions if _remap was false:
+ //
+ // drawHzFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8)
+ // drawNoFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8)
+ // drawUncompHzFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8)
+ // drawUncompNoFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8)
+ // scaleDraw(Buffer &, Buffer &, Ratio &, Ratio &, Common::Rect &, Common::Point &, uint8)
+ // scaleDrawUncomp(Buffer &, Buffer &, Ratio &, Ratio &, Common::Rect &, Common::Point &, uint8)
+ //
+ // However, obviously, _remap cannot be false here. This dead code branch existed in
+ // at least SCI2/GK1 and SCI2.1/SQ6.
+
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlipMap(target, priorityMap, targetRect, scaledPosition, priority);
+ } else {
+ drawUncompNoFlipMap(target, priorityMap, targetRect, scaledPosition, priority);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipMap(target, priorityMap, targetRect, scaledPosition, priority);
+ } else {
+ drawNoFlipMap(target, priorityMap, targetRect, scaledPosition, priority);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompMap(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority);
+ } else {
+ scaleDrawMap(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority);
+ }
+ }
+ } else {
+ // NOTE: In the original code this check was `g_Remap_numActiveRemaps && _remap`,
+ // but since we are already in a `_remap` branch, there is no reason to check it
+ // again
+ if (/* TODO: g_Remap_numActiveRemaps */ false) {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlipMap(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipMap(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipMap(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlipMap(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlip(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlip(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlip(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlip(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncomp(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDraw(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ }
+ }
+ } else {
+ if (g_sci->_gfxFrameout->_hasRemappedScreenItem) {
+ const uint8 priority = MAX((uint8)0, MIN((uint8)255, (uint8)screenItem._priority));
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority);
+ } else {
+ drawUncompNoFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority);
+ } else {
+ drawNoFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompNoMD(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority);
+ } else {
+ scaleDrawNoMD(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority);
+ }
+ }
+ } else {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_transparent) {
+ if (_drawMirrored) {
+ drawUncompHzFlipNoMD(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipNoMD(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawUncompHzFlipNoMDNoSkip(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipNoMDNoSkip(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipNoMD(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlipNoMD(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ }
+ }
+}
+
+void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) {
+ _drawMirrored = mirrorX;
+ draw(target, screenItem, targetRect);
+}
+
+void CelObj::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) {
+ _drawMirrored = mirrorX;
+ Ratio square;
+ drawTo(target, targetRect, scaledPosition, square, square);
+}
+
+void CelObj::drawTo(Buffer &target, Common::Rect const &targetRect, Common::Point const &scaledPosition, Ratio const &scaleX, Ratio const &scaleY) const {
+ if (_remap) {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlipMap(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipMap(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipMap(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlipMap(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (scaleX.isOne() && scaleY.isOne()) {
+ if (_compressionType == kCelCompressionNone) {
+ if (_drawMirrored) {
+ drawUncompHzFlipNoMD(target, targetRect, scaledPosition);
+ } else {
+ drawUncompNoFlipNoMD(target, targetRect, scaledPosition);
+ }
+ } else {
+ if (_drawMirrored) {
+ drawHzFlipNoMD(target, targetRect, scaledPosition);
+ } else {
+ drawNoFlipNoMD(target, targetRect, scaledPosition);
+ }
+ }
+ } else {
+ if (_compressionType == kCelCompressionNone) {
+ scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+ } else {
+ scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition);
+ }
+ }
+ }
+}
+
+uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const {
+ byte *resource = getResPointer();
+ byte *celHeader = resource + _celHeaderOffset;
+
+ if (mirrorX) {
+ x = _width - x - 1;
+ }
+
+ if (_compressionType == kCelCompressionNone) {
+ byte *pixels = resource + READ_SCI11ENDIAN_UINT32(celHeader + 24);
+ return pixels[y * _width + x];
+ } else {
+ byte buffer[1024];
+
+ uint32 dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24);
+ uint32 uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28);
+ uint32 controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32);
+
+ // compressed data segment for row
+ byte *row = resource + dataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + y * 4);
+
+ // uncompressed data segment for row
+ byte *literal = resource + uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + _height * 4 + y * 4);
+
+ uint8 length;
+ byte controlByte;
+ for (uint i = 0; i <= x; i += length) {
+ controlByte = *row++;
+ length = controlByte;
+
+ // Run-length encoded
+ if (controlByte & 0x80) {
+ length &= 0x3F;
+ assert(i + length < sizeof(buffer));
+
+ // Fill with skip color
+ if (controlByte & 0x40) {
+ memset(buffer + i, _transparentColor, length);
+ // Next value is fill colour
+ } else {
+ memset(buffer + i, *literal, length);
+ ++literal;
+ }
+ // Uncompressed
+ } else {
+ assert(i + length < sizeof(buffer));
+ memcpy(buffer + i, literal, length);
+ literal += length;
+ }
+ }
+
+ return buffer[x];
+ }
+}
+
+void CelObj::submitPalette() const {
+ if (_hunkPaletteOffset) {
+ Palette palette;
+
+ byte *res = getResPointer();
+ // NOTE: In SCI engine this uses HunkPalette::Init.
+ // TODO: Use a better size value
+ g_sci->_gfxPalette32->createFromData(res + _hunkPaletteOffset, 999999, &palette);
+ g_sci->_gfxPalette32->submit(palette);
+ }
+}
+
+#pragma mark -
+#pragma mark CelObj - Caching
+int CelObj::_nextCacheId = 1;
+CelCache *CelObj::_cache = nullptr;
+
+int CelObj::searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const {
+ int oldestId = _nextCacheId + 1;
+ int oldestIndex = -1;
+
+ for (int i = 0, len = _cache->size(); i < len; ++i) {
+ CelCacheEntry &entry = (*_cache)[i];
+
+ if (entry.celObj != nullptr) {
+ if (entry.celObj->_info == celInfo) {
+ entry.id = ++_nextCacheId;
+ return i;
+ }
+
+ if (oldestId > entry.id) {
+ oldestId = entry.id;
+ oldestIndex = i;
+ }
+ } else if (oldestIndex == -1) {
+ oldestIndex = i;
+ }
+ }
+
+ // NOTE: Unlike the original SCI engine code, the out-param
+ // here is only updated if there was not a cache hit.
+ *nextInsertIndex = oldestIndex;
+ return -1;
+}
+
+void CelObj::putCopyInCache(const int cacheIndex) const {
+ if (cacheIndex == -1) {
+ error("Invalid cache index");
+ }
+
+ CelCacheEntry &entry = (*_cache)[cacheIndex];
+
+ if (entry.celObj != nullptr) {
+ delete entry.celObj;
+ }
+
+ entry.celObj = duplicate();
+ entry.id = ++_nextCacheId;
+}
+
+#pragma mark -
+#pragma mark CelObj - Drawing
+void dummyFill(Buffer &target, const Common::Rect &targetRect) {
+ target.fillRect(targetRect, 250);
+}
+
+void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawHzFlip");
+ dummyFill(target, targetRect);
+}
+void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawNoFlip");
+ dummyFill(target, targetRect);
+}
+void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawUncompNoFlip");
+ dummyFill(target, targetRect);
+}
+void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawUncompHzFlip");
+ dummyFill(target, targetRect);
+}
+void CelObj::scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("scaleDraw");
+ dummyFill(target, targetRect);
+}
+void CelObj::scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("scaleDrawUncomp");
+ dummyFill(target, targetRect);
+}
+void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawHzFlipMap");
+ dummyFill(target, targetRect);
+}
+void CelObj::drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawNoFlipMap");
+ dummyFill(target, targetRect);
+}
+void CelObj::drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawUncompNoFlipMap");
+ dummyFill(target, targetRect);
+}
+void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawUncompHzFlipMap");
+ dummyFill(target, targetRect);
+}
+void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("scaleDrawMap");
+ dummyFill(target, targetRect);
+}
+void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("scaleDrawUncompMap");
+ dummyFill(target, targetRect);
+}
+void CelObj::drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawHzFlipNoMD");
+ dummyFill(target, targetRect);
+}
+void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ const int sourceX = targetRect.left - scaledPosition.x;
+ const int sourceY = targetRect.top - scaledPosition.y;
+
+ byte *targetPixel = (byte *)target.getPixels() + (targetRect.top * target.screenWidth) + targetRect.left;
+
+ const int stride = target.screenWidth - targetRect.width();
+
+ byte *resource = getResPointer();
+ byte *celHeader = resource + _celHeaderOffset;
+
+ byte buffer[1024];
+
+ uint32 dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24);
+ uint32 uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28);
+ uint32 controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32);
+
+ for (int y = sourceY; y < sourceY + targetRect.height(); ++y) {
+ // compressed data segment for row
+ byte *row = resource + dataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + y * 4);
+
+ // uncompressed data segment for row
+ byte *literal = resource + uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + _height * 4 + y * 4);
+
+ uint8 length;
+ byte controlByte;
+ for (int i = 0; i <= targetRect.width(); i += length) {
+ controlByte = *row++;
+ length = controlByte;
+
+ // Run-length encoded
+ if (controlByte & 0x80) {
+ length &= 0x3F;
+ assert(i + length < (int)sizeof(buffer));
+
+ // Fill with skip color
+ if (controlByte & 0x40) {
+ memset(buffer + i, _transparentColor, length);
+ // Next value is fill colour
+ } else {
+ memset(buffer + i, *literal, length);
+ ++literal;
+ }
+ // Uncompressed
+ } else {
+ assert(i + length < (int)sizeof(buffer));
+ memcpy(buffer + i, literal, length);
+ literal += length;
+ }
+ }
+
+ for (int x = 0; x < targetRect.width(); ++x) {
+ byte pixel = buffer[sourceX + x];
+
+ if (pixel != _transparentColor) {
+ *targetPixel = pixel;
+ }
+
+ ++targetPixel;
+ }
+
+ targetPixel += stride;
+ }
+}
+void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ const int sourceX = targetRect.left - scaledPosition.x;
+ const int sourceY = targetRect.top - scaledPosition.y;
+
+ const int sourceStride = _width - targetRect.width();
+ const int targetStride = target.screenWidth - targetRect.width();
+ const int dataOffset = READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
+
+ byte *sourcePixel = getResPointer() + dataOffset + (sourceY * _width) + sourceX;
+ byte *targetPixel = (byte *)target.getPixels() + targetRect.top * target.screenWidth + targetRect.left;
+
+ for (int y = 0; y < targetRect.height(); ++y) {
+ for (int x = 0; x < targetRect.width(); ++x) {
+ byte pixel = *sourcePixel++;
+
+ if (pixel != _transparentColor) {
+ *targetPixel = pixel;
+ }
+
+ ++targetPixel;
+ }
+
+ sourcePixel += sourceStride;
+ targetPixel += targetStride;
+ }
+}
+void CelObj::drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ const int sourceX = targetRect.left - scaledPosition.x;
+ const int sourceY = targetRect.top - scaledPosition.y;
+ const int dataOffset = READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
+
+ byte *sourcePixel = getResPointer() + dataOffset + (sourceY * _width) + sourceX;
+
+ target.copyRectToSurface(sourcePixel, _width, targetRect.left, targetRect.top, targetRect.width(), targetRect.height());
+
+}
+void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawUncompHzFlipNoMD");
+ dummyFill(target, targetRect);
+}
+void CelObj::drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("drawUncompHzFlipNoMDNoSkip");
+ dummyFill(target, targetRect);
+}
+void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("scaleDrawNoMD %d/%d, %d/%d", scaleX.getNumerator(), scaleX.getDenominator(), scaleY.getNumerator(), scaleY.getDenominator());
+ dummyFill(target, targetRect);
+}
+
+void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const {
+ debug("scaleDrawUncompNoMD %d/%d, %d/%d", scaleX.getNumerator(), scaleX.getDenominator(), scaleY.getNumerator(), scaleY.getDenominator());
+ if (targetRect.isEmpty()) {
+ return;
+ }
+
+ const CelScalerTable *table = _scaler->getScalerTable(scaleX, scaleY);
+
+ int pixelX[1024];
+ int pixelY[1024];
+
+ bool use2xOptimisedDrawRoutine = false /* TODO: scaleX.getDenominator() * 2 == scaleX.getNumerator() */;
+
+ int16 sourceX = (scaledPosition.x * scaleX.getInverse()).toInt();
+ int16 sourceY = (scaledPosition.y * scaleY.getInverse()).toInt();
+
+ if (_drawMirrored) {
+ for (int x = targetRect.left; x < targetRect.right; ++x) {
+ pixelX[x] = _width - 1 - (table->valuesX[x] - sourceX);
+ }
+ } else {
+ for (int x = targetRect.left; x < targetRect.right; ++x) {
+ pixelX[x] = table->valuesX[x] - sourceX;
+ }
+ }
+
+ for (int y = targetRect.top; y < targetRect.bottom; ++y) {
+ pixelY[y] = table->valuesY[y] - sourceY;
+ }
+
+ byte *sourcePixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
+
+ for (int y = targetRect.top; y < targetRect.bottom; ++y) {
+ byte *targetPixel = target.getAddress(targetRect.left, y);
+ byte *sourcePixel = sourcePixels + pixelY[y] * _width;
+ const int *sourcePixelIndex = pixelX + targetRect.left;
+
+ if (/* TODO */ use2xOptimisedDrawRoutine) {
+ // WriteUncompScaleLine2();
+ } else {
+ // start WriteUncompScaleLine
+ for (int x = targetRect.left; x < targetRect.right; ++x) {
+ byte value = sourcePixel[*sourcePixelIndex++];
+ if (value != _transparentColor) {
+ *targetPixel = value;
+ }
+ ++targetPixel;
+ }
+ // end WriteUncompScaleLine
+ }
+ }
+}
+
+// TODO: These functions may all be vestigial.
+void CelObj::drawHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawUncompNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawUncompHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::scaleDrawMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::scaleDrawUncompMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::scaleDrawNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+void CelObj::scaleDrawUncompNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {}
+
+#pragma mark -
+#pragma mark CelObjView
+CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) {
+ _info.type = kCelTypeView;
+ _info.resourceId = viewId;
+ _info.loopNo = loopNo;
+ _info.celNo = celNo;
+ _mirrorX = false;
+ _compressionType = kCelCompressionInvalid;
+ _transparent = true;
+
+ int cacheInsertIndex;
+ int cacheIndex = searchCache(_info, &cacheInsertIndex);
+ if (cacheIndex != -1) {
+ CelCacheEntry &entry = (*_cache)[cacheIndex];
+ *this = *dynamic_cast<CelObjView *>(entry.celObj);
+ entry.id = ++_nextCacheId;
+ return;
+ }
+
+ // TODO: The next code should be moved to a common file that
+ // generates view resource metadata for both SCI16 and SCI32
+ // implementations
+
+ Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
+
+ // NOTE: SCI2.1/SQ6 just silently returns here.
+ if (!resource) {
+ warning("View resource %d not loaded", viewId);
+ return;
+ }
+
+ byte *data = resource->data;
+
+ _scaledWidth = READ_SCI11ENDIAN_UINT16(data + 14);
+ _scaledHeight = READ_SCI11ENDIAN_UINT16(data + 16);
+
+ if (_scaledWidth == 0 || _scaledHeight == 0) {
+ byte sizeFlag = data[5];
+ if (sizeFlag == 0) {
+ _scaledWidth = 320;
+ _scaledHeight = 200;
+ } else if (sizeFlag == 1) {
+ _scaledWidth = 640;
+ _scaledHeight = 480;
+ } else if (sizeFlag == 2) {
+ _scaledWidth = 640;
+ _scaledHeight = 400;
+ }
+ }
+
+ uint16 loopCount = data[2];
+ if (_info.loopNo >= loopCount) {
+ _info.loopNo = loopCount - 1;
+ }
+
+ // NOTE: This is the actual check, in the actual location,
+ // from SCI engine.
+ if (loopNo < 0) {
+ error("Loop is less than 0!");
+ }
+
+ const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data);
+ const uint8 loopHeaderSize = data[12];
+ const uint8 viewHeaderFieldSize = 2;
+
+ byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo);
+
+ if ((int8)loopHeader[0] != -1) {
+ if (loopHeader[1] == 1) {
+ _mirrorX = true;
+ }
+
+ loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * (int8)loopHeader[0]);
+ }
+
+ uint8 celCount = loopHeader[2];
+ if (_info.celNo >= celCount) {
+ _info.celNo = celCount - 1;
+ }
+
+ _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 8);
+ _celHeaderOffset = READ_SCI11ENDIAN_UINT32(loopHeader + 12) + (data[13] * _info.celNo);
+
+ byte *celHeader = data + _celHeaderOffset;
+
+ _width = READ_SCI11ENDIAN_UINT16(celHeader);
+ _height = READ_SCI11ENDIAN_UINT16(celHeader + 2);
+ _displace.x = _width / 2 - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4);
+ _displace.y = _height - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6) - 1;
+ _transparentColor = celHeader[8];
+ _compressionType = (CelCompressionType)celHeader[9];
+
+ if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) {
+ error("Compression type not supported - V: %d L: %d C: %d", _info.resourceId, _info.loopNo, _info.celNo);
+ }
+
+ if (celHeader[10] & 128) {
+ // NOTE: This is correct according to SCI2.1/SQ6/DOS;
+ // the engine re-reads the byte value as a word value
+ uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10);
+ _transparent = flags & 1 ? true : false;
+ _remap = flags & 2 ? true : false;
+ } else if (_compressionType == kCelCompressionNone) {
+ _remap = analyzeUncompressedForRemap();
+ } else {
+ _remap = analyzeForRemap();
+ }
+
+ putCopyInCache(cacheInsertIndex);
+}
+
+bool CelObjView::analyzeUncompressedForRemap() const {
+ byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
+ for (int i = 0; i < _width * _height; ++i) {
+ uint8 pixel = pixels[i];
+ if (/* TODO: pixel >= Remap::minRemapColor && pixel <= Remap::maxRemapColor */ false && pixel != _transparentColor) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool CelObjView::analyzeForRemap() const {
+ // TODO: Implement decompression and analysis
+ return false;
+}
+
+void CelObjView::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY) {
+ _drawMirrored = mirrorX;
+ drawTo(target, targetRect, scaledPosition, scaleX, scaleY);
+}
+
+CelObjView *CelObjView::duplicate() const {
+ return new CelObjView(*this);
+}
+
+byte *CelObjView::getResPointer() const {
+ return g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false)->data;
+}
+
+#pragma mark -
+#pragma mark CelObjPic
+CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
+ _info.type = kCelTypePic;
+ _info.resourceId = picId;
+ _info.loopNo = 0;
+ _info.celNo = celNo;
+ _mirrorX = false;
+ _compressionType = kCelCompressionInvalid;
+ _transparent = true;
+ _remap = false;
+
+ int cacheInsertIndex;
+ int cacheIndex = searchCache(_info, &cacheInsertIndex);
+ if (cacheIndex != -1) {
+ CelCacheEntry &entry = (*_cache)[cacheIndex];
+ *this = *dynamic_cast<CelObjPic *>(entry.celObj);
+ entry.id = ++_nextCacheId;
+ return;
+ }
+
+ Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false);
+
+ // NOTE: SCI2.1/SQ6 just silently returns here.
+ if (!resource) {
+ warning("Pic resource %d not loaded", picId);
+ return;
+ }
+
+ byte *data = resource->data;
+
+ _celCount = data[2];
+
+ if (_info.celNo >= _celCount) {
+ error("Cel number %d greater than cel count %d", _info.celNo, _celCount);
+ }
+
+ _celHeaderOffset = READ_SCI11ENDIAN_UINT16(data) + (READ_SCI11ENDIAN_UINT16(data + 4) * _info.celNo);
+ _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 6);
+
+ byte *celHeader = data + _celHeaderOffset;
+
+ _width = READ_SCI11ENDIAN_UINT16(celHeader);
+ _height = READ_SCI11ENDIAN_UINT16(celHeader + 2);
+ _displace.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4);
+ _displace.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6);
+ _transparentColor = celHeader[8];
+ _compressionType = (CelCompressionType)celHeader[9];
+ _priority = READ_SCI11ENDIAN_UINT16(celHeader + 36);
+ _relativePosition.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 38);
+ _relativePosition.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 40);
+
+ uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10);
+ uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12);
+
+ if (sizeFlag2) {
+ _scaledWidth = sizeFlag1;
+ _scaledHeight = sizeFlag2;
+ } else if (sizeFlag1 == 0) {
+ _scaledWidth = 320;
+ _scaledHeight = 200;
+ } else if (sizeFlag1 == 1) {
+ _scaledWidth = 640;
+ _scaledHeight = 480;
+ } else if (sizeFlag1 == 2) {
+ _scaledWidth = 640;
+ _scaledHeight = 400;
+ }
+
+ if (celHeader[10] & 128) {
+ // NOTE: This is correct according to SCI2.1/SQ6/DOS;
+ // the engine re-reads the byte value as a word value
+ uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10);
+ _transparent = flags & 1 ? true : false;
+ _remap = flags & 2 ? true : false;
+ } else {
+ _transparent = _compressionType != kCelCompressionNone ? true : analyzeUncompressedForSkip();
+
+ if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) {
+ error("Compression type not supported - P: %d C: %d", picId, celNo);
+ }
+ }
+
+ putCopyInCache(cacheInsertIndex);
+}
+
+bool CelObjPic::analyzeUncompressedForSkip() const {
+ byte *resource = getResPointer();
+ byte *pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24);
+ for (int i = 0; i < _width * _height; ++i) {
+ uint8 pixel = pixels[i];
+ if (pixel == _transparentColor) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void CelObjPic::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) {
+ Ratio square;
+ _drawMirrored = mirrorX;
+ drawTo(target, targetRect, scaledPosition, square, square);
+}
+
+CelObjPic *CelObjPic::duplicate() const {
+ return new CelObjPic(*this);
+}
+
+byte *CelObjPic::getResPointer() const {
+ return g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, _info.resourceId), false)->data;
+}
+
+#pragma mark -
+#pragma mark CelObjMem
+CelObjMem::CelObjMem(const reg_t bitmap) {
+ _info.type = kCelTypeMem;
+ _info.bitmap = bitmap;
+ _mirrorX = false;
+ _compressionType = kCelCompressionNone;
+ _celHeaderOffset = 0;
+ _transparent = true;
+
+ byte *bitmapData = g_sci->getEngineState()->_segMan->getHunkPointer(bitmap);
+ if (bitmapData == nullptr || READ_SCI11ENDIAN_UINT32(bitmapData + 28) != 46) {
+ error("Invalid Text bitmap %04x:%04x", PRINT_REG(bitmap));
+ }
+
+ _width = READ_SCI11ENDIAN_UINT16(bitmapData);
+ _height = READ_SCI11ENDIAN_UINT16(bitmapData + 2);
+ _displace.x = READ_SCI11ENDIAN_UINT16(bitmapData + 4);
+ _displace.y = READ_SCI11ENDIAN_UINT16(bitmapData + 6);
+ _transparentColor = bitmapData[8];
+ _scaledWidth = READ_SCI11ENDIAN_UINT16(bitmapData + 36);
+ _scaledHeight = READ_SCI11ENDIAN_UINT16(bitmapData + 38);
+ _hunkPaletteOffset = READ_SCI11ENDIAN_UINT16(bitmapData + 20);
+ _remap = (READ_SCI11ENDIAN_UINT16(bitmapData + 10) & 2) ? true : false;
+}
+
+CelObjMem *CelObjMem::duplicate() const {
+ return new CelObjMem(*this);
+}
+
+byte *CelObjMem::getResPointer() const {
+ return g_sci->getEngineState()->_segMan->getHunkPointer(_info.bitmap);
+}
+
+#pragma mark -
+#pragma mark CelObjColor
+CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) {
+ _info.type = kCelTypeColor;
+ _info.color = color;
+ _displace.x = 0;
+ _displace.y = 0;
+ _scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ _scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ _hunkPaletteOffset = 0;
+ _mirrorX = false;
+ _remap = false;
+ _width = width;
+ _height = height;
+}
+
+void CelObjColor::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX) {
+ // TODO: The original engine sets this flag but why? One cannot
+ // draw a solid colour mirrored.
+ _drawMirrored = mirrorX;
+ draw(target, targetRect);
+}
+void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX) {
+ error("Unsupported method");
+}
+void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect) const {
+ target.fillRect(targetRect, _info.color);
+}
+
+CelObjColor *CelObjColor::duplicate() const {
+ return new CelObjColor(*this);
+}
+
+byte *CelObjColor::getResPointer() const {
+ error("Unsupported method");
+}
+}
diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h
new file mode 100644
index 0000000000..8bda86ec7e
--- /dev/null
+++ b/engines/sci/graphics/celobj32.h
@@ -0,0 +1,577 @@
+/* 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.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_CELOBJ32_H
+#define SCI_GRAPHICS_CELOBJ32_H
+
+#include "common/rational.h"
+#include "common/rect.h"
+#include "sci/resource.h"
+#include "sci/engine/vm_types.h"
+
+namespace Sci {
+typedef Common::Rational Ratio;
+
+enum CelType {
+ kCelTypeView = 0,
+ kCelTypePic = 1,
+ kCelTypeMem = 2,
+ kCelTypeColor = 3
+};
+
+enum CelCompressionType {
+ kCelCompressionNone = 0,
+ kCelCompressionRLE = 138,
+ kCelCompressionInvalid = 1000
+};
+
+/**
+ * A CelInfo32 object describes the basic properties of a
+ * cel object.
+ */
+struct CelInfo32 {
+ /**
+ * The type of the cel object.
+ */
+ CelType type;
+
+ /**
+ * For cel objects that draw from resources, the ID of
+ * the resource to load.
+ */
+ GuiResourceId resourceId;
+
+ /**
+ * For CelObjView, the loop number to draw from the
+ * view resource.
+ */
+ int16 loopNo;
+
+ /**
+ * For CelObjView and CelObjPic, the cel number to draw
+ * from the view or pic resource.
+ */
+ int16 celNo;
+
+ /**
+ * For CelObjMem, a segment register pointing to a heap
+ * resource containing headered bitmap data.
+ */
+ reg_t bitmap;
+
+ /**
+ * For CelObjColor, the fill colour.
+ */
+ uint8 color;
+
+ // NOTE: In at least SCI2.1/SQ6, color is left
+ // uninitialised.
+ CelInfo32() :
+ type(kCelTypeMem),
+ resourceId(0),
+ loopNo(0),
+ celNo(0),
+ bitmap(NULL_REG) {}
+
+ // NOTE: This is the equivalence criteria used by
+ // CelObj::searchCache in at least SCI2.1/SQ6. Notably,
+ // it does not check the color field.
+ inline bool operator==(const CelInfo32 &other) {
+ return (
+ type == other.type &&
+ resourceId == other.resourceId &&
+ loopNo == other.loopNo &&
+ celNo == other.celNo &&
+ bitmap == other.bitmap
+ );
+ }
+};
+
+class CelObj;
+struct CelCacheEntry {
+ /**
+ * A monotonically increasing cache ID used to identify
+ * the least recently used item in the cache for
+ * replacement.
+ */
+ int id;
+ CelObj *celObj;
+ CelCacheEntry() : id(0), celObj(nullptr) {}
+};
+
+typedef Common::Array<CelCacheEntry> CelCache;
+
+#pragma mark -
+#pragma mark CelScaler
+
+struct CelScalerTable {
+ /**
+ * A lookup table of indexes that should be used to find
+ * the correct column to read from the source bitmap
+ * when drawing a scaled version of the source bitmap.
+ */
+ int valuesX[1024];
+
+ /**
+ * The ratio used to generate the x-values.
+ */
+ Ratio scaleX;
+
+ /**
+ * A lookup table of indexes that should be used to find
+ * the correct row to read from a source bitmap when
+ * drawing a scaled version of the source bitmap.
+ */
+ int valuesY[1024];
+
+ /**
+ * The ratio used to generate the y-values.
+ */
+ Ratio scaleY;
+};
+
+class CelScaler {
+ /**
+ * Cached scale tables.
+ */
+ CelScalerTable _scaleTables[2];
+
+ /**
+ * The index of the most recently used scale table.
+ */
+ int _activeIndex;
+
+ /**
+ * Activates a scale table for the given X and Y ratios.
+ * If there is no table that matches the given ratios,
+ * the least most recently used table will be replaced
+ * and activated.
+ */
+ void activateScaleTables(const Ratio &scaleX, const Ratio &scaleY);
+
+ /**
+ * Builds a pixel lookup table in `table` for the given
+ * ratio. The table will be filled up to the specified
+ * size, which should be large enough to draw across the
+ * entire target buffer.
+ */
+ void buildLookupTable(int *table, const Ratio &ratio, const int size);
+
+public:
+ CelScaler() :
+ _scaleTables(),
+ _activeIndex(0) {
+ CelScalerTable &table = _scaleTables[_activeIndex];
+ table.scaleX = Ratio();
+ table.scaleY = Ratio();
+ for (int i = 0; i < ARRAYSIZE(table.valuesX); ++i) {
+ table.valuesX[i] = i;
+ table.valuesY[i] = i;
+ }
+ }
+
+ /**
+ * Retrieves scaler tables for the given X and Y ratios.
+ */
+ const CelScalerTable *getScalerTable(const Ratio &scaleX, const Ratio &scaleY);
+};
+
+#pragma mark -
+#pragma mark CelObj
+
+class ScreenItem;
+/**
+ * A cel object is the lowest-level rendering primitive in
+ * the SCI engine and draws itself directly to a target
+ * pixel buffer.
+ */
+class CelObj {
+private:
+ static CelScaler *_scaler;
+
+protected:
+ /**
+ * When true, this cel will be horizontally mirrored
+ * when it is drawn. This is an internal flag that is
+ * set by draw methods based on the combination of the
+ * cel's `_mirrorX` property and the owner screen item's
+ * `_mirrorX` property.
+ */
+ bool _drawMirrored;
+
+public:
+ /**
+ * The basic identifying information for this cel. This
+ * information effectively acts as a composite key for
+ * a cel object, and any cel object can be recreated
+ * from this data alone.
+ */
+ CelInfo32 _info;
+
+ /**
+ * The offset to the cel header for this cel within the
+ * raw resource data.
+ */
+ uint32 _celHeaderOffset;
+
+ /**
+ * The offset to the embedded palette for this cel
+ * within the raw resource data.
+ */
+ uint32 _hunkPaletteOffset;
+
+ /**
+ * The natural dimensions of the cel.
+ */
+ uint16 _width, _height;
+
+ /**
+ * TODO: Documentation
+ */
+ Common::Point _displace;
+
+ /**
+ * The dimensions of the original coordinate system for
+ * the cel. Used to scale cels from their native size
+ * to the correct size on screen.
+ *
+ * @note This is set to scriptWidth/Height for
+ * CelObjColor. For other cel objects, the value comes
+ * from the raw resource data. For text bitmaps, this is
+ * the width/height of the coordinate system used to
+ * generate the text, which also defaults to
+ * scriptWidth/Height but seems to typically be changed
+ * to more closely match the native screen resolution.
+ */
+ uint16 _scaledWidth, _scaledHeight;
+
+ /**
+ * The skip (transparent) colour for the cel. When
+ * compositing, any pixels matching this colour will not
+ * be copied to the buffer.
+ */
+ uint8 _transparentColor;
+
+ /**
+ * Whether or not this cel has any transparent regions.
+ * This is used for optimised drawing of non-transparent
+ * cels.
+ */
+ bool _transparent; // TODO: probably "skip"?
+
+ /**
+ * The compression type for the pixel data for this cel.
+ */
+ CelCompressionType _compressionType;
+
+ /**
+ * Whether or not this cel should be palette-remapped?
+ */
+ bool _remap;
+
+ /**
+ * If true, the cel contains pre-mirrored picture data.
+ * This value comes directly from the resource data and
+ * is XORed with the `_mirrorX` property of the owner
+ * screen item when rendering.
+ */
+ bool _mirrorX;
+
+ /**
+ * Initialises static CelObj members.
+ */
+ static void init();
+
+ /**
+ * Frees static CelObj members.
+ */
+ static void deinit();
+
+ virtual ~CelObj() {};
+
+ /**
+ * Draws the cel to the target buffer using the priority
+ * and positioning information from the given screen
+ * item. The mirroring of the cel will be unchanged from
+ * any previous call to draw.
+ */
+ void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const;
+
+ /**
+ * Draws the cel to the target buffer using the priority
+ * and positioning information from the given screen
+ * item and the given mirror flag.
+ *
+ * @note In SCI engine, this function was a virtual
+ * function, but CelObjView, CelObjPic, and CelObjMem
+ * all used the same function and the compiler
+ * deduplicated the copies; we deduplicate the source by
+ * putting the implementation on CelObj instead of
+ * copying it to 3/4 of the subclasses.
+ */
+ virtual void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX);
+
+ /**
+ * Draws the cel to the target buffer using the
+ * positioning and mirroring information from the
+ * provided arguments.
+ *
+ * @note In SCI engine, this function was a virtual
+ * function, but CelObjView, CelObjPic, and CelObjMem
+ * all used the same function and the compiler
+ * deduplicated the copies; we deduplicate the source by
+ * putting the implementation on CelObj instead of
+ * copying it to 3/4 of the subclasses.
+ */
+ virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX);
+
+ /**
+ * Draws the cel to the target buffer using the given
+ * position and scaling parameters. The mirroring of the
+ * cel will be unchanged from any previous call to draw.
+ */
+ void drawTo(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const;
+
+ /**
+ * Creates a copy of this cel on the free store and
+ * returns a pointer to the new object. The new cel will
+ * point to a shared copy of bitmap/resource data.
+ */
+ virtual CelObj *duplicate() const = 0;
+
+ /**
+ * Retrieves a pointer to the raw resource data for this
+ * cel. This method cannot be used with a CelObjColor.
+ */
+ virtual byte *getResPointer() const = 0;
+
+ /**
+ * Reads the pixel at the given coordinates. This method
+ * is valid only for CelObjView and CelObjPic.
+ */
+ inline uint8 readPixel(uint16 x, uint16 y, bool mirrorX) const;
+
+ /**
+ * Submits the palette from this cel to the palette
+ * manager for integration into the master screen
+ * palette.
+ */
+ void submitPalette() const;
+
+#pragma mark -
+#pragma mark CelObj - Drawing
+private:
+ void drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ void drawNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ void drawUncompNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ void drawUncompHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ void scaleDrawMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ void scaleDrawUncompMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ void drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const;
+ void drawHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ void drawNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ void drawUncompNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ void drawUncompHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ void scaleDrawNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+ void scaleDrawUncompNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const;
+
+#pragma mark -
+#pragma mark CelObj - Caching
+protected:
+ /**
+ * A monotonically increasing cache ID used to identify
+ * the least recently used item in the cache for
+ * replacement.
+ */
+ static int _nextCacheId;
+
+ /**
+ * A cache of cel objects used to avoid reinitialisation
+ * overhead for cels with the same CelInfo32.
+ */
+ // NOTE: At least SQ6 uses a fixed cache size of 100.
+ static CelCache *_cache;
+
+ /**
+ * Searches the cel cache for a CelObj matching the
+ * provided CelInfo32. If not found, -1 is returned.
+ * nextInsertIndex will receive the index of the oldest
+ * item in the cache, which can be used to replace
+ * the oldest item with a newer item.
+ */
+ int searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const;
+
+ /**
+ * Puts a copy of this CelObj into the cache at the
+ * given cache index.
+ */
+ void putCopyInCache(int index) const;
+};
+
+#pragma mark -
+#pragma mark CelObjView
+
+/**
+ * A CelObjView is the drawing primitive for a View type
+ * resource. Each CelObjView corresponds to a single cel
+ * within a single loop of a view.
+ */
+class CelObjView : public CelObj {
+private:
+ /**
+ * Analyses resources without baked-in remap flags
+ * to determine whether or not they should be remapped.
+ */
+ bool analyzeUncompressedForRemap() const;
+
+ /**
+ * Analyses compressed resources without baked-in remap
+ * flags to determine whether or not they should be
+ * remapped.
+ */
+ bool analyzeForRemap() const;
+
+public:
+ CelObjView(GuiResourceId viewId, int16 loopNo, int16 celNo);
+ virtual ~CelObjView() override {};
+
+ using CelObj::draw;
+
+ /**
+ * Draws the cel to the target buffer using the
+ * positioning, mirroring, and scaling information from
+ * the provided arguments.
+ */
+ void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY);
+
+ virtual CelObjView *duplicate() const override;
+ virtual byte *getResPointer() const override;
+};
+
+#pragma mark -
+#pragma mark CelObjPic
+
+/**
+ * A CelObjPic is the drawing primitive for a Picture type
+ * resource. Each CelObjPic corresponds to a single cel
+ * within a picture.
+ */
+class CelObjPic : public CelObj {
+private:
+ /**
+ * Analyses uncompressed resources without baked-in skip
+ * flags to determine whether or not they can use fast
+ * blitting.
+ */
+ bool analyzeUncompressedForSkip() const;
+
+public:
+ /**
+ * The number of cels in the original picture resource.
+ */
+ uint8 _celCount;
+
+ /**
+ * The position of this cel relative to the top-left
+ * corner of the picture.
+ */
+ Common::Point _relativePosition;
+
+ /**
+ * The z-buffer priority for this cel. Higher prorities
+ * are drawn on top of lower priorities.
+ */
+ int16 _priority;
+
+ CelObjPic(GuiResourceId pictureId, int16 celNo);
+ virtual ~CelObjPic() override {};
+
+ using CelObj::draw;
+ virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) override;
+
+ virtual CelObjPic *duplicate() const override;
+ virtual byte *getResPointer() const override;
+};
+
+#pragma mark -
+#pragma mark CelObjMem
+
+/**
+ * A CelObjMem is the drawing primitive for arbitrary
+ * bitmaps generated in memory. Generated bitmaps in SCI32
+ * include text & vector drawings and per-pixel screen
+ * transitions like dissolves.
+ */
+class CelObjMem : public CelObj {
+public:
+ CelObjMem(reg_t bitmap);
+ virtual ~CelObjMem() override {};
+
+ virtual CelObjMem *duplicate() const override;
+ virtual byte *getResPointer() const override;
+};
+
+#pragma mark -
+#pragma mark CelObjColor
+
+/**
+ * A CelObjColor is the drawing primitive for fast,
+ * low-memory, flat colour fills.
+ */
+class CelObjColor : public CelObj {
+public:
+ CelObjColor(uint8 color, int16 width, int16 height);
+ virtual ~CelObjColor() override {};
+
+ using CelObj::draw;
+ /**
+ * Block fills the target buffer with the cel colour.
+ */
+ void draw(Buffer &target, const Common::Rect &targetRect) const;
+ virtual void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) override;
+ virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX) override;
+
+ virtual CelObjColor *duplicate() const override;
+ virtual byte *getResPointer() const override;
+};
+}
+
+#endif
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index cb81fe8d61..d748cc05de 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -47,962 +47,1857 @@
#include "sci/graphics/palette32.h"
#include "sci/graphics/picture.h"
#include "sci/graphics/text32.h"
+#include "sci/graphics/plane32.h"
+#include "sci/graphics/screen_item32.h"
#include "sci/graphics/frameout.h"
#include "sci/video/robot_decoder.h"
namespace Sci {
-// TODO/FIXME: This is all guesswork
+// TODO/FIXME: This is partially guesswork
-enum SciSpeciaPlanelPictureCodes {
- kPlaneTranslucent = 0xfffe, // -2
- kPlanePlainColored = 0xffff // -1
+static int dissolveSequences[2][20] = {
+ /* SCI2.1early- */ { 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080 },
+ /* SCI2.1mid+ */ { 0, 0, 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080, 73728, 132096, 466944 }
+};
+static int16 divisionsDefaults[2][16] = {
+ /* SCI2.1early- */ { 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 40, 40, 101, 101 },
+ /* SCI2.1mid+ */ { 1, 20, 20, 20, 20, 10, 10, 10, 10, 20, 20, 6, 10, 101, 101, 2 }
+};
+static int16 unknownCDefaults[2][16] = {
+ /* SCI2.1early- */ { 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 0, 0, 0, 0 },
+ /* SCI2.1mid+ */ { 0, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 0, 0, 7, 7, 0 }
};
-GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32)
- : _segMan(segMan), _resMan(resMan), _cache(cache), _screen(screen), _palette(palette), _paint32(paint32), _isHiRes(false) {
-
- _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster;
- _curScrollText = -1;
- _showScrollText = false;
- _maxScrollTexts = 0;
+GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32) :
+ _segMan(segMan),
+ _resMan(resMan),
+ _cache(cache),
+ _screen(screen),
+ _palette(palette),
+ _paint32(paint32),
+ _isHiRes(false),
+ _palMorphIsOn(false),
+ _showStyles(nullptr),
+ _remapOccurred(false),
+ _frameNowVisible(false),
+ // TODO: Stop using _gfxScreen
+ _currentBuffer(screen->getWidth(), screen->getHeight(), nullptr),
+ _priorityMap(screen->getWidth(), screen->getHeight(), nullptr),
+ _screenRect(screen->getWidth(), screen->getHeight()),
+ _overdrawThreshold(0) {
+
+ _currentBuffer.setPixels(calloc(1, screen->getWidth() * screen->getHeight()));
+
+ for (int i = 0; i < 236; i += 2) {
+ _styleRanges[i] = 0;
+ _styleRanges[i + 1] = -1;
+ }
+ for (int i = 236; i < ARRAYSIZE(_styleRanges); ++i) {
+ _styleRanges[i] = 0;
+ }
// TODO: Make hires detection work uniformly across all SCI engine
// versions (this flag is normally passed by SCI::MakeGraphicsMgr
- // to the GraphicsMgr constructor depending upon video configuration)
+ // to the GraphicsMgr constructor depending upon video configuration,
+ // so should be handled upstream based on game configuration instead
+ // of here)
if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _resMan->detectHires()) {
_isHiRes = true;
}
+
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ _dissolveSequenceSeeds = dissolveSequences[0];
+ _defaultDivisions = divisionsDefaults[0];
+ _defaultUnknownC = unknownCDefaults[0];
+ } else {
+ _dissolveSequenceSeeds = dissolveSequences[1];
+ _defaultDivisions = divisionsDefaults[1];
+ _defaultUnknownC = unknownCDefaults[1];
+ }
+
+ // TODO: Nothing in the renderer really uses this. Currently,
+ // the cursor renderer does, and kLocalToGlobal/kGlobalToLocal
+ // do, but in the real engine (1) the cursor is handled in
+ // frameOut, and (2) functions do a very simple lookup of the
+ // plane and arithmetic with the plane's gameRect. In
+ // principle, CoordAdjuster could be reused for
+ // convertGameRectToPlaneRect, but it is not super clear yet
+ // what the benefit would be to do that.
+ _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster;
+
+ // TODO: Script resolution is hard-coded per game;
+ // also this must be set or else the engine will crash
+ _coordAdjuster->setScriptsResolution(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight);
}
GfxFrameout::~GfxFrameout() {
+ CelObj::deinit();
clear();
}
+void GfxFrameout::run() {
+ CelObj::init();
+ Plane::init();
+ ScreenItem::init();
+
+ // NOTE: This happens in SCI::InitPlane in the actual engine,
+ // and is a background fill plane to ensure hidden planes
+ // (planes with a priority of -1) are never drawn
+ Plane *initPlane = new Plane(Common::Rect(_currentBuffer.scriptWidth - 1, _currentBuffer.scriptHeight - 1));
+ initPlane->_priority = 0;
+ _planes.add(initPlane);
+}
+
void GfxFrameout::clear() {
- deletePlaneItems(NULL_REG);
_planes.clear();
- deletePlanePictures(NULL_REG);
- clearScrollTexts();
-}
-
-void GfxFrameout::clearScrollTexts() {
- _scrollTexts.clear();
- _curScrollText = -1;
-}
-
-void GfxFrameout::addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace) {
- //reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow);
- // HACK: We set the container dimensions manually
- reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow, 480, 70);
- ScrollTextEntry textEntry;
- textEntry.bitmapHandle = bitmapHandle;
- textEntry.kWindow = kWindow;
- textEntry.x = x;
- textEntry.y = y;
- if (!replace || _scrollTexts.size() == 0) {
- if (_scrollTexts.size() > _maxScrollTexts) {
- _scrollTexts.remove_at(0);
- _curScrollText--;
- }
- _scrollTexts.push_back(textEntry);
- _curScrollText++;
+ _showList.clear();
+}
+
+#pragma mark -
+#pragma mark Screen items
+
+void GfxFrameout::kernelAddScreenItem(const reg_t object) {
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+
+// TODO: Remove
+// debug("Adding screen item %04x:%04x to plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject));
+
+ _segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewInserted);
+
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("Invalid plane selector passed to kAddScreenItem");
+ }
+
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+ if (screenItem != nullptr) {
+ screenItem->update(object);
} else {
- _scrollTexts.pop_back();
- _scrollTexts.push_back(textEntry);
+ screenItem = new ScreenItem(object);
+ plane->_screenItemList.add(screenItem);
}
}
-void GfxFrameout::showCurrentScrollText() {
- if (!_showScrollText || _curScrollText < 0)
- return;
+void GfxFrameout::kernelUpdateScreenItem(const reg_t object) {
+ const reg_t magnifierObject = readSelector(_segMan, object, SELECTOR(magnifier));
+ if (magnifierObject.isNull()) {
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("Invalid plane selector passed to kUpdateScreenItem");
+ }
- uint16 size = (uint16)_scrollTexts.size();
- if (size > 0) {
- assert(_curScrollText < size);
- ScrollTextEntry textEntry = _scrollTexts[_curScrollText];
- g_sci->_gfxText32->drawScrollTextBitmap(textEntry.kWindow, textEntry.bitmapHandle, textEntry.x, textEntry.y);
- }
-}
-
-extern void showScummVMDialog(const Common::String &message);
-
-void GfxFrameout::kernelAddPlane(reg_t object) {
- PlaneEntry newPlane;
-
- if (_planes.empty()) {
- // There has to be another way for sierra sci to do this or maybe script resolution is compiled into
- // interpreter (TODO)
- uint16 scriptWidth = readSelectorValue(_segMan, object, SELECTOR(resX));
- uint16 scriptHeight = readSelectorValue(_segMan, object, SELECTOR(resY));
-
- // Phantasmagoria 2 doesn't specify a script width/height
- if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
- scriptWidth = 640;
- scriptHeight = 480;
- }
-
- assert(scriptWidth > 0 && scriptHeight > 0);
- _coordAdjuster->setScriptsResolution(scriptWidth, scriptHeight);
- }
-
- // Import of QfG character files dialog is shown in QFG4.
- // Display additional popup information before letting user use it.
- // For the SCI0-SCI1.1 version of this, check kDrawControl().
- if (g_sci->inQfGImportRoom() && !strcmp(_segMan->getObjectName(object), "DSPlane")) {
- showScummVMDialog("Characters saved inside ScummVM are shown "
- "automatically. Character files saved in the original "
- "interpreter need to be put inside ScummVM's saved games "
- "directory and a prefix needs to be added depending on which "
- "game it was saved in: 'qfg1-' for Quest for Glory 1, 'qfg2-' "
- "for Quest for Glory 2, 'qfg3-' for Quest for Glory 3. "
- "Example: 'qfg2-thief.sav'.");
- }
-
- newPlane.object = object;
- newPlane.priority = readSelectorValue(_segMan, object, SELECTOR(priority));
- newPlane.lastPriority = -1; // hidden
- newPlane.planeOffsetX = 0;
- newPlane.planeOffsetY = 0;
- newPlane.pictureId = kPlanePlainColored;
- newPlane.planePictureMirrored = false;
- newPlane.planeBack = 0;
- _planes.push_back(newPlane);
-
- kernelUpdatePlane(object);
-}
-
-void GfxFrameout::kernelUpdatePlane(reg_t object) {
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- // Read some information
- it->priority = readSelectorValue(_segMan, object, SELECTOR(priority));
- GuiResourceId lastPictureId = it->pictureId;
- it->pictureId = readSelectorValue(_segMan, object, SELECTOR(picture));
- if (lastPictureId != it->pictureId) {
- // picture got changed, load new picture
- deletePlanePictures(object);
- // Draw the plane's picture if it's not a translucent/plane colored frame
- if ((it->pictureId != kPlanePlainColored) && (it->pictureId != kPlaneTranslucent)) {
- // SQ6 gives us a bad picture number for the control menu
- if (_resMan->testResource(ResourceId(kResourceTypePic, it->pictureId)))
- addPlanePicture(object, it->pictureId, 0);
- }
- }
- it->planeRect.top = readSelectorValue(_segMan, object, SELECTOR(top));
- it->planeRect.left = readSelectorValue(_segMan, object, SELECTOR(left));
- it->planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom));
- it->planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right));
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+ if (screenItem == nullptr) {
+ error("Invalid screen item passed to kUpdateScreenItem");
+ }
- _coordAdjuster->fromScriptToDisplay(it->planeRect.top, it->planeRect.left);
- _coordAdjuster->fromScriptToDisplay(it->planeRect.bottom, it->planeRect.right);
+ screenItem->update(object);
+ } else {
+ warning("TODO: Magnifier view not implemented yet!");
+ }
+}
- // We get negative left in kq7 in scrolling rooms
- if (it->planeRect.left < 0) {
- it->planeOffsetX = -it->planeRect.left;
- it->planeRect.left = 0;
- } else {
- it->planeOffsetX = 0;
- }
+void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {
+ _segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewInserted);
- if (it->planeRect.top < 0) {
- it->planeOffsetY = -it->planeRect.top;
- it->planeRect.top = 0;
- } else {
- it->planeOffsetY = 0;
- }
+ const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ // TODO: Remove
+// warning("Invalid plane selector %04x:%04x passed to kDeleteScreenItem (real engine ignores this)", PRINT_REG(object));
+ return;
+ }
- // We get bad plane-bottom in sq6
- if (it->planeRect.right > _screen->getWidth())
- it->planeRect.right = _screen->getWidth();
- if (it->planeRect.bottom > _screen->getHeight())
- it->planeRect.bottom = _screen->getHeight();
+// TODO: Remove
+// debug("Deleting screen item %04x:%04x from plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject));
- it->planeClipRect = Common::Rect(it->planeRect.width(), it->planeRect.height());
- it->upscaledPlaneRect = it->planeRect;
- it->upscaledPlaneClipRect = it->planeClipRect;
- if (_screen->getUpscaledHires()) {
- _screen->adjustToUpscaledCoordinates(it->upscaledPlaneRect.top, it->upscaledPlaneRect.left);
- _screen->adjustToUpscaledCoordinates(it->upscaledPlaneRect.bottom, it->upscaledPlaneRect.right);
- _screen->adjustToUpscaledCoordinates(it->upscaledPlaneClipRect.top, it->upscaledPlaneClipRect.left);
- _screen->adjustToUpscaledCoordinates(it->upscaledPlaneClipRect.bottom, it->upscaledPlaneClipRect.right);
- }
+ ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
+ if (screenItem == nullptr) {
+// TODO: Remove
+// warning("Invalid screen item %04x:%04x passed to kDeleteScreenItem (real engine ignores this)", PRINT_REG(object));
+ return;
+ }
- it->planePictureMirrored = readSelectorValue(_segMan, object, SELECTOR(mirrored));
- it->planeBack = readSelectorValue(_segMan, object, SELECTOR(back));
+ if (screenItem->_created == 0) {
+ screenItem->_created = 0;
+ screenItem->_updated = 0;
+ screenItem->_deleted = getScreenCount();
+ } else {
+ plane->_screenItemList.erase(screenItem);
+ plane->_screenItemList.pack();
+ }
+}
- sortPlanes();
+#pragma mark -
+#pragma mark Planes
- // Update the items in the plane
- for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
- reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane));
- if (object == itemPlane) {
- kernelUpdateScreenItem((*listIterator)->object);
- }
- }
+void GfxFrameout::kernelAddPlane(const reg_t object) {
+ Plane *plane = _planes.findByObject(object);
+ if (plane != nullptr) {
+ plane->update(object);
+ updatePlane(*plane);
+ } else {
+ plane = new Plane(object);
+ addPlane(*plane);
+ }
+}
- return;
- }
+void GfxFrameout::kernelUpdatePlane(const reg_t object) {
+ Plane *plane = _planes.findByObject(object);
+ if (plane == nullptr) {
+ error("Invalid plane selector passed to kUpdatePlane");
}
- error("kUpdatePlane called on plane that wasn't added before");
+
+ plane->update(object);
+ updatePlane(*plane);
}
-void GfxFrameout::kernelDeletePlane(reg_t object) {
- deletePlaneItems(object);
- deletePlanePictures(object);
+void GfxFrameout::kernelDeletePlane(const reg_t object) {
+ Plane *plane = _planes.findByObject(object);
+ if (plane == nullptr) {
+ error("Invalid plane selector passed to kDeletePlane");
+ }
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- _planes.erase(it);
- Common::Rect planeRect;
- planeRect.top = readSelectorValue(_segMan, object, SELECTOR(top));
- planeRect.left = readSelectorValue(_segMan, object, SELECTOR(left));
- planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom));
- planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right));
+ if (plane->_created) {
+ // NOTE: The original engine calls some `AbortPlane` function that
+ // just ends up doing this anyway so we skip the extra indirection
+ _planes.erase(plane);
+ } else {
+ // TODO: Remove
+// debug("Deleting plane %04x:%04x", PRINT_REG(object));
+ plane->_created = 0;
+ plane->_deleted = g_sci->_gfxFrameout->getScreenCount();
+ }
+}
- _coordAdjuster->fromScriptToDisplay(planeRect.top, planeRect.left);
- _coordAdjuster->fromScriptToDisplay(planeRect.bottom, planeRect.right);
+int16 GfxFrameout::kernelGetHighPlanePri() {
+ return _planes.getTopSciPlanePriority();
+}
- // Blackout removed plane rect
- _paint32->fillRect(planeRect, 0);
- return;
+void GfxFrameout::addPlane(Plane &plane) {
+ if (_planes.findByObject(plane._object) == nullptr) {
+ plane.clipScreenRect(_screenRect);
+ _planes.add(&plane);
+ } else {
+ plane._deleted = 0;
+ if (plane._created == 0) {
+ plane._moved = g_sci->_gfxFrameout->getScreenCount();
}
+ _planes.sort();
}
}
-void GfxFrameout::addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY) {
- if (pictureId == kPlanePlainColored || pictureId == kPlaneTranslucent) // sanity check
- return;
+void GfxFrameout::updatePlane(Plane &plane) {
+ // NOTE: This assertion comes from SCI engine code.
+ assert(_planes.findByObject(plane._object) == &plane);
- PlanePictureEntry newPicture;
- newPicture.object = object;
- newPicture.pictureId = pictureId;
- newPicture.picture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, pictureId, false);
- newPicture.startX = startX;
- newPicture.startY = startY;
- newPicture.pictureCels = 0;
- _planePictures.push_back(newPicture);
+ Plane *visiblePlane = _visiblePlanes.findByObject(plane._object);
+ plane.sync(visiblePlane, _screenRect);
+ // NOTE: updateScreenRect was originally called a second time here,
+ // but it is already called at the end of the Plane::Update call
+ // in the original engine anyway.
+
+ _planes.sort();
}
-void GfxFrameout::deletePlanePictures(reg_t object) {
- PlanePictureList::iterator it = _planePictures.begin();
+#pragma mark -
+#pragma mark Pics
- while (it != _planePictures.end()) {
- if (it->object == object || object.isNull()) {
- delete it->pictureCels;
- delete it->picture;
- it = _planePictures.erase(it);
- } else {
- ++it;
- }
+void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 x, const int16 y, const bool mirrorX) {
+ Plane *plane = _planes.findByObject(planeObject);
+ if (plane == nullptr) {
+ error("Invalid plane selector passed to kAddPicAt");
}
+ plane->addPic(pictureId, Common::Point(x, y), mirrorX);
}
-// Provides the same functionality as kGraph(DrawLine)
-reg_t GfxFrameout::addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) {
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- PlaneLineEntry line;
- line.hunkId = _segMan->allocateHunkEntry("PlaneLine()", 1); // we basically use this for a unique ID
- line.startPoint = startPoint;
- line.endPoint = endPoint;
- line.color = color;
- line.priority = priority;
- line.control = control;
- it->lines.push_back(line);
- return line.hunkId;
+#pragma mark -
+#pragma mark Rendering
+
+void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) {
+// TODO: Robot
+// if (_robot != nullptr) {
+// _robot.doRobot();
+// }
+
+ // NOTE: The original engine allocated these as static arrays of 100
+ // pointers to ScreenItemList / RectList
+ ScreenItemListList screenItemLists;
+ EraseListList eraseLists;
+
+ screenItemLists.resize(_planes.size());
+ eraseLists.resize(_planes.size());
+
+ // _numActiveRemaps was a global in SCI engine
+ if (/* TODO Remap::_numActiveRemaps > 0 */ false && _remapOccurred) {
+ // remapMarkRedraw();
+ }
+
+ calcLists(screenItemLists, eraseLists, rect);
+
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ list->sort();
+ }
+
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+ (*drawItem)->screenItem->getCelObj().submitPalette();
}
}
- return NULL_REG;
+ _remapOccurred = _palette->updateForFrame();
+
+ // NOTE: SCI engine set this to false on each loop through the
+ // planelist iterator below. Since that is a waste, we only set
+ // it once.
+ _frameNowVisible = false;
+
+ for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+ drawEraseList(eraseLists[i], *_planes[i]);
+ drawScreenItemList(screenItemLists[i]);
+ }
+
+// TODO: Robot
+// if (_robot != nullptr) {
+// _robot->frameAlmostVisible();
+// }
+
+ _palette->updateHardware();
+
+ if (shouldShowBits) {
+ showBits();
+ }
+
+ _frameNowVisible = true;
+
+// TODO: Robot
+// if (_robot != nullptr) {
+// robot->frameNowVisible();
+// }
}
-void GfxFrameout::updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) {
- // Check if we're asked to update a line that was never added
- if (hunkId.isNull())
- return;
+int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]) {
+ if (!r.intersects(other)) {
+ return -1;
+ }
+
+ int count = 0;
+ if (r.top < other.top) {
+ Common::Rect &t = outRects[count++];
+ t = r;
+ t.bottom = other.top;
+ r.top = other.top;
+ }
+
+ if (r.bottom > other.bottom) {
+ Common::Rect &t = outRects[count++];
+ t = r;
+ t.top = other.bottom;
+ r.bottom = other.bottom;
+ }
+
+ if (r.left < other.left) {
+ Common::Rect &t = outRects[count++];
+ t = r;
+ t.right = other.left;
+ r.left = other.left;
+ }
+
+ if (r.right > other.right) {
+ Common::Rect &t = outRects[count++];
+ t = r;
+ t.left = other.right;
+ }
+
+ return count;
+}
+
+void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect) {
+ RectList rectlist;
+ Common::Rect outRects[4];
+
+ int deletedPlaneCount = 0;
+ bool addedToRectList = false;
+ int planeCount = _planes.size();
+ bool foundTransparentPlane = false;
+
+ if (!calcRect.isEmpty()) {
+ addedToRectList = true;
+ rectlist.add(calcRect);
+ }
+
+ for (int outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) {
+ Plane *outerPlane = _planes[outerPlaneIndex];
+
+ if (outerPlane->_type == kPlaneTypeTransparent) {
+ foundTransparentPlane = true;
+ }
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) {
- if (it2->hunkId == hunkId) {
- it2->startPoint = startPoint;
- it2->endPoint = endPoint;
- it2->color = color;
- it2->priority = priority;
- it2->control = control;
- return;
+ Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object);
+
+ if (outerPlane->_deleted) {
+ if (visiblePlane != nullptr) {
+ if (!visiblePlane->_screenRect.isEmpty()) {
+ addedToRectList = true;
+ rectlist.add(visiblePlane->_screenRect);
+ }
+ }
+ ++deletedPlaneCount;
+ } else if (visiblePlane != nullptr) {
+ if (outerPlane->_updated) {
+ --outerPlane->_updated;
+
+ int splitcount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects);
+ if (splitcount) {
+ if (splitcount == -1) {
+ if (!visiblePlane->_screenRect.isEmpty()) {
+ rectlist.add(visiblePlane->_screenRect);
+ }
+ } else {
+ for (int i = 0; i < splitcount; ++i) {
+ rectlist.add(outRects[i]);
+ }
+ }
+
+ addedToRectList = true;
+ }
+
+ if (!outerPlane->_redrawAllCount) {
+ int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects);
+ if (splitCount) {
+ for (int i = 0; i < splitCount; ++i) {
+ rectlist.add(outRects[i]);
+ }
+ addedToRectList = true;
+ }
}
}
}
- }
-}
-void GfxFrameout::deletePlaneLine(reg_t object, reg_t hunkId) {
- // Check if we're asked to delete a line that was never added (happens during the intro of LSL6)
- if (hunkId.isNull())
- return;
+ if (addedToRectList) {
+ for (RectList::iterator rect = rectlist.begin(); rect != rectlist.end(); ++rect) {
+ for (int innerPlaneIndex = _planes.size() - 1; innerPlaneIndex >= 0; --innerPlaneIndex) {
+ Plane *innerPlane = _planes[innerPlaneIndex];
+
+ if (!innerPlane->_deleted && innerPlane->_type != kPlaneTypeTransparent && innerPlane->_screenRect.intersects(**rect)) {
+ if (innerPlane->_redrawAllCount == 0) {
+ eraseLists[innerPlaneIndex].add(innerPlane->_screenRect.findIntersectingRect(**rect));
+ }
+
+ int splitCount = splitRects(**rect, innerPlane->_screenRect, outRects);
+ for (int i = 0; i < splitCount; ++i) {
+ rectlist.add(outRects[i]);
+ }
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) {
- if (it->object == object) {
- for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) {
- if (it2->hunkId == hunkId) {
- _segMan->freeHunkEntry(hunkId);
- it2 = it->lines.erase(it2);
- return;
+ rectlist.erase(rect);
+ break;
+ }
}
}
+
+ rectlist.pack();
}
}
-}
-// Adapted from GfxAnimate::applyGlobalScaling()
-void GfxFrameout::applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight) {
- // Global scaling uses global var 2 and some other stuff to calculate scaleX/scaleY
- int16 maxScale = readSelectorValue(_segMan, itemEntry->object, SELECTOR(maxScale));
- int16 maxCelHeight = (maxScale * celHeight) >> 7;
- reg_t globalVar2 = g_sci->getEngineState()->variables[VAR_GLOBAL][2]; // current room object
- int16 vanishingY = readSelectorValue(_segMan, globalVar2, SELECTOR(vanishingY));
+ // clean up deleted planes
+ if (deletedPlaneCount) {
+ for (int planeIndex = planeCount - 1; planeIndex >= 0; --planeIndex) {
+ Plane *plane = _planes[planeIndex];
+
+ if (plane->_deleted) {
+ --plane->_deleted;
+ if (plane->_deleted <= 0) {
+ PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object));
+ if (visiblePlaneIt != _visiblePlanes.end()) {
+ _visiblePlanes.erase(visiblePlaneIt);
+ }
- int16 fixedPortY = planeRect.bottom - vanishingY;
- int16 fixedEntryY = itemEntry->y - vanishingY;
- if (!fixedEntryY)
- fixedEntryY = 1;
+ _planes.remove_at(planeIndex);
+ eraseLists.remove_at(planeIndex);
+ drawLists.remove_at(planeIndex);
+ }
- if ((celHeight == 0) || (fixedPortY == 0))
- error("global scaling panic");
+ if (--deletedPlaneCount <= 0) {
+ break;
+ }
+ }
+ }
+ }
- itemEntry->scaleY = (maxCelHeight * fixedEntryY) / fixedPortY;
- itemEntry->scaleY = (itemEntry->scaleY * maxScale) / celHeight;
+ planeCount = _planes.size();
+ for (int outerIndex = 0; outerIndex < planeCount; ++outerIndex) {
+ // "outer" just refers to the outer loop
+ Plane *outerPlane = _planes[outerIndex];
+ if (outerPlane->_priorityChanged) {
+ --outerPlane->_priorityChanged;
+
+ Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane->_object);
+
+ rectlist.add(outerPlane->_screenRect.findIntersectingRect(visibleOuterPlane->_screenRect));
+
+ for (int innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) {
+ // "inner" just refers to the inner loop
+ Plane *innerPlane = _planes[innerIndex];
+ Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane->_object);
+
+ int rectCount = rectlist.size();
+ for (int rectIndex = 0; rectIndex < rectCount; ++rectIndex) {
+ int splitCount = splitRects(*rectlist[rectIndex], _planes[innerIndex]->_screenRect, outRects);
+
+ if (splitCount == 0) {
+ if (visibleInnerPlane != nullptr) {
+ // same priority, or relative priority between inner/outer changed
+ if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) {
+ if (outerPlane->_priority <= innerPlane->_priority) {
+ eraseLists[innerIndex].add(*rectlist[rectIndex]);
+ } else {
+ eraseLists[outerIndex].add(*rectlist[rectIndex]);
+ }
+ }
+ }
+
+ rectlist.erase_at(rectIndex);
+ } else if (splitCount != -1) {
+ for (int i = 0; i < splitCount; ++i) {
+ rectlist.add(outRects[i]);
+ }
+
+ if (visibleInnerPlane != nullptr) {
+ // same priority, or relative priority between inner/outer changed
+ if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) {
+ *rectlist[rectIndex] = outerPlane->_screenRect.findIntersectingRect(innerPlane->_screenRect);
+ if (outerPlane->_priority <= innerPlane->_priority) {
+ eraseLists[innerIndex].add(*rectlist[rectIndex]);
+ }
+ else {
+ eraseLists[outerIndex].add(*rectlist[rectIndex]);
+ }
+ }
+ }
+ rectlist.erase_at(rectIndex);
+ }
+ }
+ rectlist.pack();
+ }
+ }
+ }
- // Make sure that the calculated value is sane
- if (itemEntry->scaleY < 1 /*|| itemEntry->scaleY > 128*/)
- itemEntry->scaleY = 128;
+ for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+ Plane *plane = _planes[planeIndex];
+ Plane *visiblePlane = nullptr;
- itemEntry->scaleX = itemEntry->scaleY;
+ PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object));
+ if (visiblePlaneIt != _visiblePlanes.end()) {
+ visiblePlane = *visiblePlaneIt;
+ }
- // and set objects scale selectors
- //writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleX), itemEntry->scaleX);
- //writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleY), itemEntry->scaleY);
-}
+ if (plane->_redrawAllCount) {
+ plane->redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+ } else {
+ if (visiblePlane == nullptr) {
+ error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane->_object));
+ }
-void GfxFrameout::kernelAddScreenItem(reg_t object) {
- // Ignore invalid items
- if (!_segMan->isObject(object)) {
- warning("kernelAddScreenItem: Attempt to add an invalid object (%04x:%04x)", PRINT_REG(object));
- return;
+ plane->calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
+ }
+
+ if (plane->_created) {
+ _visiblePlanes.add(new Plane(*plane));
+ --plane->_created;
+ } else if (plane->_moved) {
+ assert(visiblePlaneIt != _visiblePlanes.end());
+ **visiblePlaneIt = *plane;
+ --plane->_moved;
+ }
}
- FrameoutEntry *itemEntry = new FrameoutEntry();
- memset(itemEntry, 0, sizeof(FrameoutEntry));
- itemEntry->object = object;
- itemEntry->givenOrderNr = _screenItems.size();
- itemEntry->visible = true;
- _screenItems.push_back(itemEntry);
+ if (foundTransparentPlane) {
+ for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
+ for (int i = planeIndex + 1; i < planeCount; ++i) {
+ if (_planes[i]->_type == kPlaneTypeTransparent) {
+ _planes[i]->filterUpEraseRects(drawLists[i], eraseLists[planeIndex]);
+ }
+ }
+
+ if (_planes[planeIndex]->_type == kPlaneTypeTransparent) {
+ for (int i = planeIndex - 1; i >= 0; --i) {
+ _planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]);
+ }
+
+ if (eraseLists[planeIndex].size() > 0) {
+ error("Transparent plane's erase list not absorbed");
+ }
+ }
- kernelUpdateScreenItem(object);
+ for (int i = planeIndex + 1; i < planeCount; ++i) {
+ if (_planes[i]->_type == kPlaneTypeTransparent) {
+ _planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]);
+ }
+ }
+ }
+ }
}
-void GfxFrameout::kernelUpdateScreenItem(reg_t object) {
- // Ignore invalid items
- if (!_segMan->isObject(object)) {
- warning("kernelUpdateScreenItem: Attempt to update an invalid object (%04x:%04x)", PRINT_REG(object));
+void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) {
+ if (plane._type != kPlaneTypeColored) {
return;
}
- FrameoutEntry *itemEntry = findScreenItem(object);
- if (!itemEntry) {
- warning("kernelUpdateScreenItem: invalid object %04x:%04x", PRINT_REG(object));
- return;
+ for (RectList::const_iterator it = eraseList.begin(); it != eraseList.end(); ++it) {
+ mergeToShowList(**it, _showList, _overdrawThreshold);
+ _currentBuffer.fillRect(**it, plane._back);
+ }
+}
+
+void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) {
+ _hasRemappedScreenItem = false;
+ if (/* TODO: g_Remap_UnknownCounter2 */ false && !_priorityMap.isNull()) {
+ for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) {
+ if ((*it)->screenItem->getCelObj()._remap) {
+ _hasRemappedScreenItem = true;
+ break;
+ }
+ }
}
- itemEntry->viewId = readSelectorValue(_segMan, object, SELECTOR(view));
- itemEntry->loopNo = readSelectorValue(_segMan, object, SELECTOR(loop));
- itemEntry->celNo = readSelectorValue(_segMan, object, SELECTOR(cel));
- itemEntry->x = readSelectorValue(_segMan, object, SELECTOR(x));
- itemEntry->y = readSelectorValue(_segMan, object, SELECTOR(y));
- itemEntry->z = readSelectorValue(_segMan, object, SELECTOR(z));
- itemEntry->priority = readSelectorValue(_segMan, object, SELECTOR(priority));
- if (readSelectorValue(_segMan, object, SELECTOR(fixPriority)) == 0)
- itemEntry->priority = itemEntry->y;
+ for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) {
+ DrawItem &drawItem = **it;
+ mergeToShowList(drawItem.rect, _showList, _overdrawThreshold);
+ ScreenItem &screenItem = *drawItem.screenItem;
+ // TODO: Remove
+// debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), drawItem.rect.left, drawItem.rect.top, drawItem.rect.right, drawItem.rect.bottom);
+ CelObj &celObj = *screenItem._celObj;
+ celObj.draw(_currentBuffer, screenItem, drawItem.rect, screenItem._mirrorX ^ celObj._mirrorX);
+ }
+}
- itemEntry->signal = readSelectorValue(_segMan, object, SELECTOR(signal));
- itemEntry->scaleSignal = readSelectorValue(_segMan, object, SELECTOR(scaleSignal));
+void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold) {
+ Common::Rect merged(drawRect);
+
+ bool didDelete = true;
+ RectList::size_type count = showList.size();
+ while (didDelete && count) {
+ didDelete = false;
+
+ for (RectList::size_type i = 0; i < count; ++i) {
+ Common::Rect existing = *showList[i];
+ Common::Rect candidate;
+ candidate.left = MIN(merged.left, existing.left);
+ candidate.top = MIN(merged.top, existing.top);
+ candidate.right = MAX(merged.right, existing.right);
+ candidate.bottom = MAX(merged.bottom, existing.bottom);
+
+ if (candidate.height() * candidate.width() - merged.width() * merged.height() - existing.width() * existing.height() <= overdrawThreshold) {
+ merged = candidate;
+ showList.erase_at(i);
+ didDelete = true;
+ }
+ }
- if (itemEntry->scaleSignal & kScaleSignalDoScaling32) {
- itemEntry->scaleX = readSelectorValue(_segMan, object, SELECTOR(scaleX));
- itemEntry->scaleY = readSelectorValue(_segMan, object, SELECTOR(scaleY));
- } else {
- itemEntry->scaleX = 128;
- itemEntry->scaleY = 128;
+ count = showList.pack();
}
- itemEntry->visible = true;
- // Check if the entry can be hidden
- if (lookupSelector(_segMan, object, SELECTOR(visible), NULL, NULL) != kSelectorNone)
- itemEntry->visible = readSelectorValue(_segMan, object, SELECTOR(visible));
+ showList.add(merged);
}
-void GfxFrameout::kernelDeleteScreenItem(reg_t object) {
- FrameoutEntry *itemEntry = findScreenItem(object);
- // If the item could not be found, it may already have been deleted
- if (!itemEntry)
- return;
+void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) {
+ Palette sourcePalette(*_palette->getNextPalette());
+ alterVmap(sourcePalette, sourcePalette, -1, styleRanges);
- _screenItems.remove(itemEntry);
- delete itemEntry;
-}
+ // TODO: unsure if this is what this variable actually
+ // represents, but it is the correct variable number
+ int16 lastRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16();
-void GfxFrameout::deletePlaneItems(reg_t planeObject) {
- FrameoutList::iterator listIterator = _screenItems.begin();
+ Common::Rect rect(_screen->getDisplayWidth(), _screen->getDisplayHeight());
+ _showList.add(rect);
+ showBits();
- while (listIterator != _screenItems.end()) {
- bool objectMatches = false;
- if (!planeObject.isNull()) {
- reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane));
- objectMatches = (planeObject == itemPlane);
- } else {
- objectMatches = true;
+ Common::Rect calcRect(0, 0);
+
+ // NOTE: The original engine allocated these as static arrays of 100
+ // pointers to ScreenItemList / RectList
+ ScreenItemListList screenItemLists;
+ EraseListList eraseLists;
+
+ screenItemLists.resize(_planes.size());
+ eraseLists.resize(_planes.size());
+
+ // TODO: Remap
+ // _numActiveRemaps was a global in SCI engine
+ // if (Remap::_numActiveRemaps > 0 && _remapOccurred) {
+ // _screen->remapMarkRedraw();
+ // }
+
+ calcLists(screenItemLists, eraseLists, calcRect);
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ list->sort();
+ }
+
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+ (*drawItem)->screenItem->getCelObj().submitPalette();
}
+ }
- if (objectMatches) {
- FrameoutEntry *itemEntry = *listIterator;
- listIterator = _screenItems.erase(listIterator);
- delete itemEntry;
- } else {
- ++listIterator;
+ _remapOccurred = _palette->updateForFrame();
+ _frameNowVisible = false;
+
+ for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+ drawEraseList(eraseLists[i], *_planes[i]);
+ drawScreenItemList(screenItemLists[i]);
+ }
+
+ Palette nextPalette(*_palette->getNextPalette());
+
+ if (lastRoom < 1000) {
+ for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
+ if (styleRanges[i] == -1 || styleRanges[i] == 0) {
+ sourcePalette.colors[i] = nextPalette.colors[i];
+ sourcePalette.colors[i].used = true;
+ }
+ }
+ } else {
+ for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
+ if (styleRanges[i] == -1 || (styleRanges[i] == 0 && i > 71 && i < 104)) {
+ sourcePalette.colors[i] = nextPalette.colors[i];
+ sourcePalette.colors[i].used = true;
+ }
}
}
-}
-FrameoutEntry *GfxFrameout::findScreenItem(reg_t object) {
- for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
- FrameoutEntry *itemEntry = *listIterator;
- if (itemEntry->object == object)
- return itemEntry;
+ _palette->submit(sourcePalette);
+ _palette->updateFFrame();
+ _palette->updateHardware();
+ alterVmap(nextPalette, sourcePalette, 1, _styleRanges);
+
+ if (showStyle->type > 0 && showStyle->type < 15) {
+// TODO: SCI2.1mid transition effects
+// processEffects();
+ warning("Transition not implemented!");
+ } else {
+ showBits();
}
- return NULL;
-}
+ _frameNowVisible = true;
-int16 GfxFrameout::kernelGetHighPlanePri() {
- sortPlanes();
- return readSelectorValue(g_sci->getEngineState()->_segMan, _planes.back().object, SELECTOR(priority));
-}
+ for (PlaneList::iterator plane = _planes.begin(); plane != _planes.end(); ++plane) {
+// TODO:
+// plane->updateRedrawAllCount();
+ }
-void GfxFrameout::kernelAddPicAt(reg_t planeObj, GuiResourceId pictureId, int16 pictureX, int16 pictureY) {
- addPlanePicture(planeObj, pictureId, pictureX, pictureY);
-}
+ // TODO: Remap
+ // _numActiveRemaps was a global in SCI engine
+ // if (Remap::_numActiveRemaps > 0 && _remapOccurred) {
+ // _screen->remapMarkRedraw();
+ // }
-bool sortHelper(const FrameoutEntry* entry1, const FrameoutEntry* entry2) {
- if (entry1->priority == entry2->priority) {
- if (entry1->y == entry2->y)
- return (entry1->givenOrderNr < entry2->givenOrderNr);
- return (entry1->y < entry2->y);
+ calcLists(screenItemLists, eraseLists, calcRect);
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ list->sort();
}
- return (entry1->priority < entry2->priority);
-}
-bool planeSortHelper(const PlaneEntry &entry1, const PlaneEntry &entry2) {
- if (entry1.priority < 0)
- return true;
+ for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
+ for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
+ (*drawItem)->screenItem->getCelObj().submitPalette();
+ }
+ }
+
+ _remapOccurred = _palette->updateForFrame();
+ // NOTE: During this second loop, `_frameNowVisible = false` is
+ // inside the next loop in SCI2.1mid
+ _frameNowVisible = false;
+
+ for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
+ drawEraseList(eraseLists[i], *_planes[i]);
+ drawScreenItemList(screenItemLists[i]);
+ }
- if (entry2.priority < 0)
- return false;
+ _palette->submit(nextPalette);
+ _palette->updateFFrame();
+ _palette->updateHardware();
+ showBits();
- return entry1.priority < entry2.priority;
+ _frameNowVisible = true;
}
-void GfxFrameout::sortPlanes() {
- // First, remove any invalid planes
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end();) {
- if (!_segMan->isObject(it->object))
- it = _planes.erase(it);
- else
- it++;
+// TODO: What does the bit masking for the show rects do,
+// and does it cause an off-by-one error in rect calculations
+// since SOL_Rect is BR inclusive and Common::Rect is BR
+// exclusive?
+void GfxFrameout::showBits() {
+ for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
+ Common::Rect rounded(**rect);
+ // NOTE: SCI engine used BR-inclusive rects so used slightly
+ // different masking here to ensure that the width of rects
+ // was always even.
+ rounded.left &= ~1;
+ rounded.right = (rounded.right + 1) & ~1;
+
+ // TODO:
+ // _cursor->GonnaPaint(rounded);
}
- // Sort the rest of them
- Common::sort(_planes.begin(), _planes.end(), planeSortHelper);
+ // TODO:
+ // _cursor->PaintStarting();
+
+ for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
+ Common::Rect rounded(**rect);
+ // NOTE: SCI engine used BR-inclusive rects so used slightly
+ // different masking here to ensure that the width of rects
+ // was always even.
+ rounded.left &= ~1;
+ rounded.right = (rounded.right + 1) & ~1;
+
+ byte *sourceBuffer = (byte *)_currentBuffer.getPixels() + rounded.top * _currentBuffer.screenWidth + rounded.left;
+
+ g_system->copyRectToScreen(sourceBuffer, _currentBuffer.screenWidth, rounded.left, rounded.top, rounded.width(), rounded.height());
+ }
+
+ // TODO:
+ // _cursor->DonePainting();
+
+ _showList.clear();
}
-void GfxFrameout::showVideo() {
- bool skipVideo = false;
- RobotDecoder *videoDecoder = g_sci->_robotDecoder;
- uint16 x = videoDecoder->getPos().x;
- uint16 y = videoDecoder->getPos().y;
- uint16 screenWidth = _screen->getWidth();
- uint16 screenHeight = _screen->getHeight();
- uint16 outputWidth;
- uint16 outputHeight;
+void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges) {
+ uint8 clut[256];
+
+ for (int paletteIndex = 0; paletteIndex < ARRAYSIZE(palette1.colors); ++paletteIndex) {
+ int outerR = palette1.colors[paletteIndex].r;
+ int outerG = palette1.colors[paletteIndex].g;
+ int outerB = palette1.colors[paletteIndex].b;
+
+ if (styleRanges[paletteIndex] == style) {
+ int minDiff = 262140;
+ int minDiffIndex;
+
+ for (int i = 0; i < 236; ++i) {
+ if (styleRanges[i] != style) {
+ int r = palette1.colors[i].r;
+ int g = palette1.colors[i].g;
+ int b = palette1.colors[i].b;
+ int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b);
+ if (diffSquared < minDiff) {
+ minDiff = diffSquared;
+ minDiffIndex = i;
+ }
+ }
+ }
- if (videoDecoder->hasDirtyPalette())
- g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256);
+ clut[paletteIndex] = minDiffIndex;
+ }
- while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) {
- if (videoDecoder->needsUpdate()) {
- const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
- if (frame) {
- // We need to clip here
- // At least Phantasmagoria shows a 640x390 video on a 630x450 screen during the intro
- outputWidth = frame->w > screenWidth ? screenWidth : frame->w;
- outputHeight = frame->h > screenHeight ? screenHeight : frame->h;
- g_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, outputWidth, outputHeight);
+ if (style == 1 && styleRanges[paletteIndex] == 0) {
+ int minDiff = 262140;
+ int minDiffIndex;
- if (videoDecoder->hasDirtyPalette())
- g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256);
+ for (int i = 0; i < 236; ++i) {
+ int r = palette2.colors[i].r;
+ int g = palette2.colors[i].g;
+ int b = palette2.colors[i].b;
- g_system->updateScreen();
+ int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b);
+ if (diffSquared < minDiff) {
+ minDiff = diffSquared;
+ minDiffIndex = i;
+ }
}
+
+ clut[paletteIndex] = minDiffIndex;
}
+ }
- Common::Event event;
- while (g_system->getEventManager()->pollEvent(event)) {
- if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP)
- skipVideo = true;
+ // NOTE: This is currBuffer->ptr in SCI engine
+ byte *pixels = (byte *)_currentBuffer.getPixels();
+
+ // TODO: Guessing that display width/height is the correct
+ // equivalent to screen width/height in SCI engine
+ for (int pixelIndex = 0, numPixels = _currentBuffer.screenWidth * _currentBuffer.screenHeight; pixelIndex < numPixels; ++pixelIndex) {
+ byte currentValue = pixels[pixelIndex];
+ int8 styleRangeValue = styleRanges[currentValue];
+ if (styleRangeValue == -1 && styleRangeValue == style) {
+ currentValue = pixels[pixelIndex] = clut[currentValue];
+ styleRangeValue = styleRanges[clut[currentValue]];
}
- g_system->delayMillis(10);
+ if (
+ (styleRangeValue == 1 && styleRangeValue == style) ||
+ (styleRangeValue == 0 && style == 1)
+ ) {
+ pixels[pixelIndex] = clut[currentValue];
+ }
}
}
-void GfxFrameout::createPlaneItemList(reg_t planeObject, FrameoutList &itemList) {
- // Copy screen items of the current frame to the list of items to be drawn
- for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
- reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane));
- if (planeObject == itemPlane) {
- kernelUpdateScreenItem((*listIterator)->object); // TODO: Why is this necessary?
- itemList.push_back(*listIterator);
+void GfxFrameout::kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor) {
+ if (toColor > fromColor) {
+ return;
+ }
+
+ for (int i = fromColor; i < toColor; ++i) {
+ _styleRanges[i] = 0;
+ }
+}
+
+inline ShowStyleEntry * GfxFrameout::findShowStyleForPlane(const reg_t planeObj) const {
+ ShowStyleEntry *entry = _showStyles;
+ while (entry != nullptr) {
+ if (entry->plane == planeObj) {
+ break;
}
+ entry = entry->next;
}
- for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) {
- if (pictureIt->object == planeObject) {
- GfxPicture *planePicture = pictureIt->picture;
- // Allocate memory for picture cels
- pictureIt->pictureCels = new FrameoutEntry[planePicture->getSci32celCount()];
+ return entry;
+}
+
+inline ShowStyleEntry *GfxFrameout::deleteShowStyleInternal(ShowStyleEntry *const showStyle) {
+ ShowStyleEntry *lastEntry = nullptr;
- // Add following cels to the itemlist
- FrameoutEntry *picEntry = pictureIt->pictureCels;
- int planePictureCels = planePicture->getSci32celCount();
- for (int pictureCelNr = 0; pictureCelNr < planePictureCels; pictureCelNr++) {
- picEntry->celNo = pictureCelNr;
- picEntry->object = NULL_REG;
- picEntry->picture = planePicture;
- picEntry->y = planePicture->getSci32celY(pictureCelNr);
- picEntry->x = planePicture->getSci32celX(pictureCelNr);
- picEntry->picStartX = pictureIt->startX;
- picEntry->picStartY = pictureIt->startY;
- picEntry->visible = true;
+ for (ShowStyleEntry *testEntry = _showStyles; testEntry != nullptr; testEntry = testEntry->next) {
+ if (testEntry == showStyle) {
+ break;
+ }
+ lastEntry = testEntry;
+ }
- picEntry->priority = planePicture->getSci32celPriority(pictureCelNr);
+ if (lastEntry == nullptr) {
+ _showStyles = showStyle->next;
+ lastEntry = _showStyles;
+ } else {
+ lastEntry->next = showStyle->next;
+ }
- itemList.push_back(picEntry);
- picEntry++;
+ // NOTE: Differences from SCI2/2.1early engine:
+ // 1. Memory of ShowStyle-owned objects was freed before ShowStyle was
+ // removed from the linked list, but since this operation is position
+ // independent, it has been moved after removal from the list for
+ // consistency with SCI2.1mid+
+ // 2. In SCI2, `screenItems` was a pointer to an array of pointers, so
+ // extra deletes were performed here; we use an owned container object
+ // instead, which is automatically freed when ShowStyle is freed
+#if 0
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ uint8 type = showStyle->type;
+
+ if (type >= 1 && type <= 10) {
+ ScreenItemList &styleItems = showStyle->screenItems;
+ for (ScreenItemList::iterator it = styleItems.begin(); it != styleItems.end(); ++it) {
+ if (active) {
+ // TODO: _screen->deleteScreenItem(showStyle->plane, *it->id);
+ _screenItems.remove(*it);
+ }
+ delete *it;
+ }
+ } else if (type == 11 || type == 12) {
+ if (!showStyle->bitmapMemId.isNull()) {
+ _segMan->freeHunkEntry(showStyle->bitmapMemId);
+ }
+ if (showStyle->bitmapScreenItem != nullptr) {
+ // TODO: _screen->deleteScreenItem(showStyle->plane, showStyle->bitmapScreenItem->id);
+ _screenItems.remove(showStyle->bitmapScreenItem);
+ delete showStyle->bitmapScreenItem;
}
}
+ } else {
+#endif
+ delete[] showStyle->fadeColorRanges;
+#if 0
}
+#endif
+
+ delete showStyle;
- // Now sort our itemlist
- Common::sort(itemList.begin(), itemList.end(), sortHelper);
+ // TODO: Verify that this is the correct entry to return
+ // for the loop in processShowStyles to work correctly
+ return lastEntry;
}
-bool GfxFrameout::isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY) {
- // Out of view horizontally (sanity checks)
- int16 pictureCelStartX = itemEntry->picStartX + itemEntry->x;
- int16 pictureCelEndX = pictureCelStartX + itemEntry->picture->getSci32celWidth(itemEntry->celNo);
- int16 planeStartX = planeOffsetX;
- int16 planeEndX = planeStartX + planeRect.width();
- if (pictureCelEndX < planeStartX)
- return true;
- if (pictureCelStartX > planeEndX)
- return true;
+// TODO: 10-argument version is only in SCI3; argc checks are currently wrong for this version
+// and need to be fixed in future
+// TODO: SQ6 does not use 'priority' (exists since SCI2) or 'blackScreen' (exists since SCI3);
+// check to see if other versions use or if they are just always ignored
+void GfxFrameout::kernelSetShowStyle(const uint16 argc, const reg_t &planeObj, const ShowStyleType type, const int16 seconds, const int16 back, const int16 priority, const int16 animate, const int16 frameOutNow, const reg_t &pFadeArray, const int16 divisions, const int16 blackScreen) {
+
+ bool hasDivisions = false;
+ bool hasFadeArray = false;
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ hasDivisions = argc > 7;
+ hasFadeArray = false;
+ } else if (getSciVersion() < SCI_VERSION_3) {
+ hasDivisions = argc > 8;
+ hasFadeArray = argc > 7;
+ } else {
+ hasDivisions = argc > 9;
+ hasFadeArray = argc > 8;
+ }
- // Out of view vertically (sanity checks)
- int16 pictureCelStartY = itemEntry->picStartY + itemEntry->y;
- int16 pictureCelEndY = pictureCelStartY + itemEntry->picture->getSci32celHeight(itemEntry->celNo);
- int16 planeStartY = planeOffsetY;
- int16 planeEndY = planeStartY + planeRect.height();
- if (pictureCelEndY < planeStartY)
- return true;
- if (pictureCelStartY > planeEndY)
- return true;
+ bool isFadeUp;
+ int16 color;
+ if (back != -1) {
+ isFadeUp = false;
+ color = back;
+ } else {
+ isFadeUp = true;
+ color = 0;
+ }
- return false;
-}
+ if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && type == 15) || type > 15) {
+ error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj));
+ }
+
+ Plane *plane = _planes.findByObject(planeObj);
+ if (plane == nullptr) {
+ error("Plane %04x:%04x is not present in active planes list", PRINT_REG(planeObj));
+ }
+
+ // TODO: This is Plane.gameRect in SCI engine, not planeRect. Engine uses
+ // Plane::ConvGameRectToPlaneRect to convert from gameRect to planeRect.
+ // Also this never gets used by SQ6 so it is not clear what it does yet
+ // Common::Rect gameRect = plane.planeRect;
+
+ bool createNewEntry = true;
+ ShowStyleEntry *entry = findShowStyleForPlane(planeObj);
+ if (entry != nullptr) {
+ bool useExisting = true;
+
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ useExisting = plane->_planeRect.width() == entry->width && plane->_planeRect.height() == entry->height;
+ }
+
+ if (useExisting) {
+ useExisting = entry->divisions == (hasDivisions ? divisions : _defaultDivisions[type]) && entry->unknownC == _defaultUnknownC[type];
+ }
-void GfxFrameout::drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored) {
- int16 pictureOffsetX = planeOffsetX;
- int16 pictureX = itemEntry->x;
- if ((planeOffsetX) || (itemEntry->picStartX)) {
- if (planeOffsetX <= itemEntry->picStartX) {
- pictureX += itemEntry->picStartX - planeOffsetX;
- pictureOffsetX = 0;
+ if (useExisting) {
+ createNewEntry = false;
+ isFadeUp = true;
+ entry->currentStep = 0;
} else {
- pictureOffsetX = planeOffsetX - itemEntry->picStartX;
+ isFadeUp = true;
+ color = entry->color;
+ deleteShowStyleInternal(entry/*, true*/);
+ entry = nullptr;
}
}
- int16 pictureOffsetY = planeOffsetY;
- int16 pictureY = itemEntry->y;
- if ((planeOffsetY) || (itemEntry->picStartY)) {
- if (planeOffsetY <= itemEntry->picStartY) {
- pictureY += itemEntry->picStartY - planeOffsetY;
- pictureOffsetY = 0;
- } else {
- pictureOffsetY = planeOffsetY - itemEntry->picStartY;
+ if (type > 0) {
+ if (createNewEntry) {
+ entry = new ShowStyleEntry;
+ // NOTE: SCI2.1 engine tests if allocation returned a null pointer
+ // but then only avoids setting currentStep if this is so. Since
+ // this is a nonsensical approach, we do not do that here
+ entry->currentStep = 0;
+ entry->unknownC = _defaultUnknownC[type];
+ entry->processed = false;
+ entry->divisions = hasDivisions ? divisions : _defaultDivisions[type];
+ entry->plane = planeObj;
+
+#if 0
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ entry->bitmapMemId = NULL_REG;
+ entry->screenItems.empty();
+ entry->width = plane->_planeRect.width();
+ entry->height = plane->_planeRect.height();
+ } else {
+#endif
+ entry->fadeColorRanges = nullptr;
+ if (hasFadeArray) {
+ // NOTE: SCI2.1mid engine does no check to verify that an array is
+ // successfully retrieved, and SegMan will cause a fatal error
+ // if we try to use a memory segment that is not an array
+ SciArray<reg_t> *table = _segMan->lookupArray(pFadeArray);
+
+ uint32 rangeCount = table->getSize();
+ entry->fadeColorRangesCount = rangeCount;
+
+ // NOTE: SCI engine code always allocates memory even if the range
+ // table has no entries, but this does not really make sense, so
+ // we avoid the allocation call in this case
+ if (rangeCount > 0) {
+ entry->fadeColorRanges = new uint16[rangeCount];
+ for (size_t i = 0; i < rangeCount; ++i) {
+ entry->fadeColorRanges[i] = table->getValue(i).toUint16();
+ }
+ }
+ } else {
+ entry->fadeColorRangesCount = 0;
+ }
+#if 0
+ }
+#endif
+ }
+
+ // NOTE: The original engine had no nullptr check and would just crash
+ // if it got to here
+ if (entry == nullptr) {
+ error("Cannot edit non-existing ShowStyle entry");
+ }
+
+ entry->fadeUp = isFadeUp;
+ entry->color = color;
+ entry->nextTick = g_sci->getTickCount();
+ entry->type = type;
+ entry->animate = animate;
+ entry->delay = (seconds * 60 + entry->divisions - 1) / entry->divisions;
+
+ if (entry->delay == 0) {
+#if 0
+ if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE && entry->fadeColorRanges != nullptr) {
+#endif
+ if (entry->fadeColorRanges != nullptr) {
+ delete[] entry->fadeColorRanges;
+ }
+ delete entry;
+ error("ShowStyle has no duration");
+ }
+
+ if (frameOutNow) {
+ Common::Rect frameOutRect(0, 0);
+ frameOut(false, frameOutRect);
+ }
+
+ if (createNewEntry) {
+ // TODO: Implement SCI3, which may or may not actually have
+ // the same transitions as SCI2/SCI2.1early, but implemented
+ // differently
+#if 0
+ if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
+ switch (entry->type) {
+ case kShowStyleHShutterIn:
+ case kShowStyleHShutterOut:
+ prepareShowStyleWipe(entry, priority, 2, true);
+ break;
+
+ case kShowStyleVShutterIn:
+ case kShowStyleVShutterOut:
+ prepareShowStyleWipe(entry, priority, 2, false);
+ break;
+
+ case kShowStyleHWipe1:
+ case kShowStyleHWipe2:
+ prepareShowStyleWipe(entry, priority, 1, true);
+ break;
+
+ case kShowStyleVWipe1:
+ case kShowStyleVWipe2:
+ prepareShowStyleWipe(entry, priority, 1, false);
+ break;
+
+ case kShowStyleIrisIn:
+ case kShowStyleIrisOut:
+ prepareShowStyleIris(entry, priority);
+ break;
+
+ case kShowStyle11:
+ case kShowStyle12:
+ prepareShowStylePixels(entry, priority, plane->planeRect);
+ break;
+
+ default:
+ break;
+ }
+ }
+#endif
+
+ entry->next = _showStyles;
+ _showStyles = entry;
}
}
+}
- itemEntry->picture->drawSci32Vga(itemEntry->celNo, pictureX, itemEntry->y, pictureOffsetX, pictureOffsetY, planePictureMirrored);
- // warning("picture cel %d %d", itemEntry->celNo, itemEntry->priority);
+#if 0
+void addFrameoutEntryInternal(ShowStyleEntry *const showStyle, const int16 priority, const CelInfo32 &celInfo, const Common::Rect &rect) {
+ ScreenItem *screenItem = new ScreenItem;
+ screenItem->plane = showStyle->plane;
+ screenItem->celInfo = celInfo;
+ screenItem->celRect = rect;
+ screenItem->isInList = true;
+ screenItem->priority = priority;
+ screenItem->visible = true;
+ showStyle->screenItems.push_back(screenItem);
}
-/* TODO: This is the proper implementation of GraphicsMgr::FrameOut transcribed from SQ6 SCI engine disassembly.
-static DrawList* g_drawLists[100];
-static RectList* g_rectLists[100];
-void GfxFrameout::FrameOut(bool shouldShowBits, SOL_Rect *rect) {
- if (robot) {
- robot.doRobot();
+void GfxFrameout::prepareShowStyleWipe(ShowStyleEntry *const showStyle, const int16 priority, const int16 edgeCount, const bool horizontal) {
+ assert(edgeCount == 1 || edgeCount == 2);
+
+ const int numScreenItems = showStyle->divisions * edgeCount;
+ const int extra = edgeCount > 1 ? 1 : 0;
+
+ showStyle->edgeCount = edgeCount;
+ showStyle->screenItems.reserve(numScreenItems);
+
+ CelInfo32 celInfo;
+ celInfo.bitmap = NULL_REG;
+ celInfo.type = kCelObjTypeView;
+ celInfo.color = showStyle->color;
+
+ for (int i = 0; i < numScreenItems; ++i) {
+ Common::Rect rect;
+
+ if (horizontal) {
+ rect.top = 0;
+ rect.bottom = showStyle->height - 1;
+ rect.left = (showStyle->width * i) / (showStyle->divisions * edgeCount);
+ rect.right = ((i + 1) * (showStyle->width + extra)) / (showStyle->divisions * edgeCount) - 1;
+ } else {
+ rect.left = 0;
+ rect.right = showStyle->width - 1;
+ rect.top = (showStyle->height * i) / (showStyle->divisions * edgeCount);
+ rect.bottom = ((i + 1) * (showStyle->height + extra)) / (showStyle->divisions * edgeCount) - 1;
+ }
+
+ addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
+
+ if (edgeCount == 2) {
+ if (horizontal) {
+ int temp = rect.left;
+ rect.left = showStyle->width - rect.right - 1;
+ rect.right = showStyle->width - temp - 1;
+ } else {
+ int temp = rect.top;
+ rect.top = showStyle->height - rect.bottom - 1;
+ rect.bottom = showStyle->height - temp - 1;
+ }
+
+ addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
+ }
}
+}
- auto planeCount = screen.planeList.planeCount;
- if (planeCount > 0) {
- for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
- Plane plane = *screen.planeList[planeIndex];
+void GfxFrameout::prepareShowStyleIris(ShowStyleEntry *const showStyle, const int16 priority) {
+ const int edgeCount = 4;
+ const int numScreenItems = showStyle->divisions * edgeCount;
+
+ showStyle->edgeCount = edgeCount;
+ showStyle->screenItems.reserve(numScreenItems);
+
+ CelInfo32 celInfo;
+ celInfo.bitmap = NULL_REG;
+ celInfo.type = kCelObjTypeView;
+ celInfo.color = showStyle->color;
+
+ for (int i = 0; i < numScreenItems; ++i) {
+ Common::Rect rect;
+
+ rect.right = showStyle->width - ((showStyle->width * i) / (showStyle->divisions * 2)) - 1;
+ rect.left = (showStyle->width * i) / (showStyle->divisions * 2);
+ rect.top = (showStyle->height * i) / (showStyle->divisions * 2);
+ rect.bottom = ((i + 1) * (showStyle->height + 1)) / (showStyle->divisions * 2) - 1;
+
+ addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
+
+ {
+ int temp = rect.top;
+ rect.top = showStyle->height - rect.bottom - 1;
+ rect.bottom = showStyle->height - temp - 1;
+ }
+
+ addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
+
+ rect.top = ((i + 1) * (showStyle->height + 1)) / (showStyle->divisions * 2);
+ rect.right = ((i + 1) * (showStyle->width + 1)) / (showStyle->divisions * 2) - 1;
+ rect.bottom = ((i + 1) * (showStyle->height + 1)) / (showStyle->divisions * 2) - 1;
- DrawList* drawList = new DrawList();
- g_drawLists[planeIndex] = drawList;
- RectList* rectList = new RectList();
- g_rectLists[planeIndex] = rectList;
+ addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
+
+ {
+ int temp = rect.left;
+ rect.left = showStyle->width - rect.right - 1;
+ rect.right = showStyle->width - temp - 1;
}
+
+ addFrameoutEntryInternal(showStyle, priority, celInfo, rect);
}
-
- if (g_Remap_numActiveRemaps > 0 && remapNeeded) {
- screen.RemapMarkRedraw();
+}
+
+void GfxFrameout::prepareShowStylePixels(ShowStyleEntry *const showStyle, const int16 priority, const Common::Rect planeGameRect) {
+ const int bitmapSize = showStyle->width * showStyle->height;
+
+ // TODO: Verify that memory type 0x200 (what GK1 engine uses)
+ // is Hunk type
+ reg_t bitmapMemId = _segMan->allocateHunkEntry("ShowStylePixels()", bitmapSize + sizeof(GfxBitmapHeader));
+ showStyle->bitmapMemId = bitmapMemId;
+
+ // TODO: SCI2 GK1 uses a Bitmap constructor function to
+ // do this work
+ byte *bitmap = _segMan->getHunkPointer(bitmapMemId);
+ GfxBitmapHeader *header = (GfxBitmapHeader *)bitmap;
+ byte *bitmapData = bitmap + sizeof(GfxBitmapHeader);
+
+ // TODO: These are defaults from the Bitmap constructor in
+ // GK1, not specific values set by this function.
+ // TODO: This probably should not even be using a struct at
+ // all since this information is machine endian dependent
+ // and will be reversed for Mac versions or when running
+ // ScummVM on big-endian systems. GK1 used packed structs
+ // everywhere so this probably worked better there too.
+ header->field_18 = 36;
+ header->field_1c = 36;
+ memset(header, 0, sizeof(GfxBitmapHeader));
+
+ header->width = showStyle->width;
+ header->height = showStyle->height;
+ header->field_8 = 250;
+ header->size = bitmapSize;
+
+ // TODO: Scaled dimensions in bitmap headers was not added
+ // until SCI2.1mid. It is not clear what the right thing to
+ // do here is.
+ if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+ header->scaledWidth = _currentBuffer.scriptWidth;
+ header->scaledHeight = _currentBuffer.scriptHeight;
}
-
- CalcLists(&g_drawLists, &g_rectLists, rect);
- // SCI engine stores reference *after* CalcLists
- planeCount = screen.planeList.planeCount;
- if (planeCount > 0) {
- for (int drawListIndex = 0; drawListIndex < planeCount; ++i) {
- DrawList* drawList = g_drawLists[drawListIndex];
- drawList->Sort();
- }
+ Common::Rect copyRect;
+ // TODO: planeGameRect is supposedly in script coordinates,
+ // which are usually 320x200. If bitsSaveDisplayScreen is
+ // in native resolution then seemingly this function will
+ // not work properly since we will be not copy enough bits,
+ // or from the correct location.
+ copyRect.left = planeGameRect.left;
+ copyRect.top = planeGameRect.top;
+ copyRect.right = planeGameRect.left + showStyle->width;
+ copyRect.bottom = planeGameRect.top + showStyle->height;
+ _screen->bitsSaveDisplayScreen(copyRect, bitmapData);
+
+ CelInfo32 celInfo;
+ celInfo.bitmap = bitmapMemId;
+ celInfo.type = kCelObjTypeMem;
+
+ ScreenItem *screenItem = new ScreenItem;
+
+ screenItem->position.x = 0;
+ screenItem->position.y = 0;
+
+ showStyle->bitmapScreenItem = screenItem;
+ screenItem->priority = priority;
+
+ // TODO: Have not seen/identified this particular flag yet in
+ // SCI2.1mid (SQ6) engine; maybe (1) a duplicate of `created`,
+ // or (2) does not exist any more, or (3) one of the other
+ // still-unidentified fields. Probably need to look at the
+ // GK1 source for its use in drawing algorithms to determine
+ // if/how this correlates to ScreenItem members in the
+ // SCI2.1mid engine.
+// screenItem->isInList = true;
+
+ Plane *plane = _planes.findByObject(showStyle.plane);
+ plane->_screenItemList.add(screenItem);
+}
+#endif
+
+// NOTE: Different version of SCI engine support different show styles
+// SCI2 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12, 13, 14
+// SCI2.1 implements 0, 1/2/3/4/5/6/7/8/9/10/11/12/15, 13, 14
+// SCI3 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12/15, 13, 14
+// TODO: Sierra code needs to be replaced with code that uses the
+// computed entry->delay property instead of just counting divisors,
+// as the latter is machine-speed-dependent and leads to wrong
+// transition speeds
+void GfxFrameout::processShowStyles() {
+ uint32 now = g_sci->getTickCount();
+
+ bool continueProcessing;
+
+ // TODO: Change to bool? Engine uses inc to set the value to true,
+ // but there does not seem to be any reason to actually count how
+ // many times it was set
+ int doFrameOut;
+ do {
+ continueProcessing = false;
+ doFrameOut = 0;
+ ShowStyleEntry *showStyle = _showStyles;
+ while (showStyle != nullptr) {
+ bool retval = false;
+
+ if (!showStyle->animate) {
+ ++doFrameOut;
+ }
+
+ if (showStyle->nextTick < now || !showStyle->animate) {
+ // TODO: Different versions of SCI use different processors!
+ // This is the SQ6/KQ7/SCI2.1mid table.
+ switch (showStyle->type) {
+ case kShowStyleNone: {
+ retval = processShowStyleNone(showStyle);
+ break;
+ }
+ case kShowStyleHShutterOut:
+ case kShowStyleVShutterOut:
+ case kShowStyleWipeLeft:
+ case kShowStyleWipeUp:
+ case kShowStyleIrisOut:
+#if 0
+ if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+#endif
+ retval = processShowStyleMorph(showStyle);
+#if 0
+ } else {
+ retval = processShowStyleWipe(-1, showStyle);
+ }
+#endif
+ break;
+ case kShowStyleHShutterIn:
+ case kShowStyleVShutterIn:
+ case kShowStyleWipeRight:
+ case kShowStyleWipeDown:
+ case kShowStyleIrisIn:
+#if 0
+ if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+#endif
+ retval = processShowStyleMorph(showStyle);
+#if 0
+ } else {
+ retval = processShowStyleWipe(1, showStyle);
+ }
+#endif
+ break;
+ case kShowStyle11:
+#if 0
+ if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+#endif
+ retval = processShowStyleMorph(showStyle);
+#if 0
+ } else {
+ retval = processShowStyle11(showStyle);
+ }
+#endif
+ break;
+ case kShowStyle12:
+ case kShowStyleUnknown: {
+#if 0
+ if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) {
+#endif
+ retval = processShowStyleMorph(showStyle);
+#if 0
+ } else {
+ retval = processShowStyle12(showStyle);
+ }
+#endif
+ break;
+ }
+ case kShowStyleFadeOut: {
+ retval = processShowStyleFade(-1, showStyle);
+ break;
+ }
+ case kShowStyleFadeIn: {
+ retval = processShowStyleFade(1, showStyle);
+ break;
+ }
+ }
+ }
- for (int drawListIndex = 0; drawListIndex < planeCount; ++i) {
- DrawList* drawList = g_drawLists[drawListIndex];
- if (drawList == nullptr || drawList->count == 0) {
- continue;
+ if (!retval) {
+ continueProcessing = true;
}
- for (int screenItemIndex = 0, screenItemCount = drawList->count; screenItemIndex < screenItemCount; ++screenItemIndex) {
- ScreenItem* screenItem = drawList->items[screenItemIndex];
- screenItem->GetCelObj()->SubmitPalette();
+ if (retval && showStyle->processed) {
+ showStyle = deleteShowStyleInternal(showStyle);
+ } else {
+ showStyle = showStyle->next;
}
}
+
+ if (doFrameOut) {
+ Common::Rect frameOutRect(0, 0);
+ frameOut(true, frameOutRect);
+
+ // TODO: It seems like transitions without the “animate”
+ // flag are too fast in in SCI2–2.1early, but the throttle
+ // value is arbitrary. Someone on real hardware probably
+ // needs to test what the actual speed of transitions
+ // should be
+ //state->speedThrottler(30);
+ //state->_throttleTrigger = true;
+ }
+ } while(continueProcessing && doFrameOut);
+}
+
+bool GfxFrameout::processShowStyleNone(ShowStyleEntry *const showStyle) {
+ if (showStyle->fadeUp) {
+ _palette->setFade(100, 0, 255);
+ } else {
+ _palette->setFade(0, 0, 255);
}
- // UpdateForFrame is where all palette mutations occur (cycles, varies, etc.)
- bool remapNeeded = GPalette().UpdateForFrame();
- if (planeCount > 0) {
- frameNowVisible = false;
+ showStyle->processed = true;
+ return true;
+}
- for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
- Plane* plane = screen.planeList[planeIndex];
+bool GfxFrameout::processShowStyleMorph(ShowStyleEntry *const showStyle) {
+ palMorphFrameOut(_styleRanges, showStyle);
+ showStyle->processed = true;
+ return true;
+}
- DrawEraseList(g_rectLists[planeIndex], plane);
- DrawScreenItemsList(g_drawLists[planeIndex]);
+// TODO: Normalise use of 'entry' vs 'showStyle'
+bool GfxFrameout::processShowStyleFade(const int direction, ShowStyleEntry *const showStyle) {
+ bool unchanged = true;
+ if (showStyle->currentStep < showStyle->divisions) {
+ int percent;
+ if (direction <= 0) {
+ percent = showStyle->divisions - showStyle->currentStep - 1;
+ } else {
+ percent = showStyle->currentStep;
}
- }
- if (robot) {
- robot.FrameAlmostVisible();
- }
+ percent *= 100;
+ percent /= showStyle->divisions - 1;
- GPalette().UpdateHardware();
+ if (showStyle->fadeColorRangesCount > 0) {
+ for (int i = 0, len = showStyle->fadeColorRangesCount; i < len; i += 2) {
+ _palette->setFade(percent, showStyle->fadeColorRanges[i], showStyle->fadeColorRanges[i + 1]);
+ }
+ } else {
+ _palette->setFade(percent, 0, 255);
+ }
- if (shouldShowBits) {
- ShowBits();
+ ++showStyle->currentStep;
+ showStyle->nextTick += showStyle->delay;
+ unchanged = false;
}
- frameNowVisible = true;
+ if (showStyle->currentStep >= showStyle->divisions && unchanged) {
+ if (direction > 0) {
+ showStyle->processed = true;
+ }
- if (robot) {
- robot.FrameNowVisible();
+ return true;
}
- if (planeCount > 0) {
- for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
- if (g_rectLists[planeIndex] != nullptr) {
- delete g_rectLists[planeIndex];
+ return false;
+}
+
+// TODO: Rect sizes are wrong, rects in SCI are inclusive of bottom/right but
+// in ScummVM are exclusive so extra ±1 operations here are wrong
+#if 0
+bool GfxFrameout::processShowStyleWipe(const ShowStyleEntry *const style) {
+ const int16 divisions = style->divisions;
+ Common::Rect rect(divisions, divisions);
+
+ const Plane *const plane = _visibleScreen->planeList->findByObject(style->plane);
+
+ const int16 planeLeft = plane->field_4C.left;
+ const int16 planeTop = plane->field_4C.top;
+ const int16 planeRight = plane->field_4C.right;
+ const int16 planeBottom = plane->field_4C.bottom;
+ const int16 planeWidth = planeRight - planeLeft + 1;
+ const int16 planeHeight = planeBottom - planeTop + 1;
+
+ const int16 divisionWidth = planeWidth / divisions - 1;
+ int16 shutterDivisionWidth = planeWidth / (2 * divisions);
+ if (shutterDivisionWidth >= 0) {
+ const int16 heightPerDivision = planeHeight / divisions;
+ int16 shutterMiddleX = divisions * shutterDivisionWidth;
+ for (int16 x = divisionWidth - shutterDivisionWidth; x <= divisionWidth; ++x) {
+ int16 divisionTop = 0;
+ for (int16 y = 0; y < heightPerDivision; ++y, divisionTop += divisions) {
+ rect.top = planeTop + divisionTop;
+ rect.bottom = rect.top + divisions - 1;
+ rect.left = planeLeft + shutterMiddleX;
+ rect.right = rect.left + divisions - 1;
+ // _screen->rectList.clear();
+ // _screen->rectList.add(rect);
+ // showBits();
+ }
+ // number of divisions does not divide evenly into plane height,
+ // draw the remainder
+ if (planeHeight % divisions) {
+ rect.top = planeTop + divisionTop;
+ rect.bottom = rect.top + planeHeight % divisions - 1;
+ rect.left = planeLeft + shutterMiddleX;
+ rect.right = rect.left + divisions - 1;
+ // _screen->rectList.clear();
+ // _screen->rectList.add(rect);
+ // showBits();
}
- if (g_drawLists[planeIndex] != nullptr) {
- delete g_drawLists[planeIndex];
+
+ divisionTop = 0;
+ for (int16 y = 0; y < heightPerDivision; ++y, divisionTop += divisions) {
+ rect.top = planeTop + divisionTop;
+ rect.bottom = rect.top + divisions - 1;
+ rect.left = planeLeft + divisions * x;
+ rect.right = rect.left + divisions - 1;
+ // _screen->rectList.clear();
+ // _screen->rectList.add(rect);
+ // showBits();
+ }
+ if (planeHeight % divisions) {
+ rect.top = planeTop + divisionTop;
+ rect.bottom = rect.top + planeHeight % divisions - 1;
+ rect.left = planeLeft + divisions * x;
+ rect.right = rect.left + divisions - 1;
+ // _screen->rectList.clear();
+ // _screen->rectList.add(rect);
+ // showBits();
}
+
+ shutterMiddleX -= divisions;
+ --shutterDivisionWidth;
}
}
-}
-void GfxFrameout::CalcLists(DrawList **drawLists, RectList **rectLists, SOL_Rect *rect) {
- screen.CalcLists(&visibleScreen, drawLists, rectLists, rect);
-}
-*/
-void GfxFrameout::kernelFrameout() {
- if (g_sci->_robotDecoder->isVideoLoaded()) {
- showVideo();
- return;
+
+ if (planeWidth % divisions) {
+ const int16 roundedPlaneWidth = divisions * divisionWidth;
+ int16 divisionTop = 0;
+ for (int16 y = 0; y < planeHeight / divisions; ++y, divisionTop += divisions) {
+ rect.top = planeTop + divisionTop;
+ rect.bottom = rect.top + divisions - 1;
+ rect.left = planeLeft + roundedPlaneWidth;
+ rect.right = rect.left + planeWidth % divisions + divisions - 1;
+ // _screen->rectList.clear();
+ // _screen->rectList.add(rect);
+ // showBits();
+ }
+ if (planeHeight % divisions) {
+ rect.top = planeTop + divisionTop;
+ rect.bottom = rect.top + planeHeight % divisions - 1;
+ rect.left = planeLeft + roundedPlaneWidth;
+ rect.right = rect.left + planeWidth % divisions + divisions - 1;
+ // _screen->rectList.clear();
+ // _screen->rectList.add(rect);
+ // showBits();
+ }
}
- _palette->updateForFrame();
+ rect.right = planeRight;
+ rect.left = planeLeft;
+ rect.top = planeTop;
+ rect.bottom = planeBottom;
+ // _screen->rectList.clear();
+ // _screen->rectList.add(rect);
+ // showBits();
+}
+#endif
+#if 0
+bool GfxFrameout::processShowStyleWipe(const int direction, ShowStyleEntry *const showStyle) {
+ if (showStyle->currentStep < showStyle->divisions) {
+ int index;
+ if (direction <= 0) {
+ index = showStyle->divisions - showStyle->currentStep - 1;
+ } else {
+ index = showStyle->currentStep;
+ }
- // TODO: Tons of drawing stuff should be here, see commented out implementation above
+ index *= showStyle->edgeCount;
- _palette->updateHardware();
+ if (showStyle->edgeCount > 0) {
+ for (int i = 0; i < showStyle->edgeCount; ++i) {
+ if (showStyle->fadeUp) {
+ ScreenItem *screenItem = showStyle->screenItems[index + i];
+ if (screenItem != nullptr) {
+ // TODO: _screen->deleteScreenItem(screenItem);
+ _screenItems.remove(screenItem);
- for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); it++) {
- reg_t planeObject = it->object;
+ delete screenItem;
+ showStyle->screenItems[index + i] = nullptr;
+ }
+ } else {
+ ScreenItem *screenItem = showStyle->screenItems[index + i];
+ // TODO: _screen->addScreenItem(screenItem);
+ _screenItems.push_back(screenItem);
+ }
+ }
- // Draw any plane lines, if they exist
- // These are drawn on invisible planes as well. (e.g. "invisiblePlane" in LSL6 hires)
- // FIXME: Lines aren't always drawn (e.g. when the narrator speaks in LSL6 hires).
- // Perhaps something is painted over them?
- for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) {
- Common::Point startPoint = it2->startPoint;
- Common::Point endPoint = it2->endPoint;
- _coordAdjuster->kernelLocalToGlobal(startPoint.x, startPoint.y, it->object);
- _coordAdjuster->kernelLocalToGlobal(endPoint.x, endPoint.y, it->object);
- _screen->drawLine(startPoint, endPoint, it2->color, it2->priority, it2->control);
+ ++showStyle->currentStep;
+ showStyle->nextTick += showStyle->delay;
}
+ }
- int16 planeLastPriority = it->lastPriority;
-
- // Update priority here, sq6 sets it w/o UpdatePlane
- int16 planePriority = it->priority = readSelectorValue(_segMan, planeObject, SELECTOR(priority));
-
- it->lastPriority = planePriority;
- if (planePriority < 0) { // Plane currently not meant to be shown
- // If plane was shown before, delete plane rect
- if (planePriority != planeLastPriority)
- _paint32->fillRect(it->planeRect, 0);
- continue;
+ if (showStyle->currentStep >= showStyle->divisions) {
+ if (showStyle->fadeUp) {
+ showStyle->processed = true;
}
- // There is a race condition lurking in SQ6, which causes the game to hang in the intro, when teleporting to Polysorbate LX.
- // Since I first wrote the patch, the race has stopped occurring for me though.
- // I'll leave this for investigation later, when someone can reproduce.
- //if (it->pictureId == kPlanePlainColored) // FIXME: This is what SSCI does, and fixes the intro of LSL7, but breaks the dialogs in GK1 (adds black boxes)
- if (it->pictureId == kPlanePlainColored && (it->planeBack || g_sci->getGameId() != GID_GK1))
- _paint32->fillRect(it->planeRect, it->planeBack);
+ return true;
+ }
+
+ return false;
+}
- _coordAdjuster->pictureSetDisplayArea(it->planeRect);
- // Invoking drewPicture() with an invalid picture ID in SCI32 results in
- // invalidating the palVary palette when a palVary effect is active. This
- // is quite obvious in QFG4, where the day time palette is incorrectly
- // shown when exiting the caves, and the correct night time palette
- // flashes briefly each time that kPalVaryInit is called.
- if (it->pictureId != 0xFFFF)
- _palette->drewPicture(it->pictureId);
+void fillRect(byte *data, const Common::Rect &rect, const int16 color, const int16 stride) {
- FrameoutList itemList;
+}
- createPlaneItemList(planeObject, itemList);
+bool GfxFrameout::processShowStyle11(ShowStyleEntry *const showStyle) {
+ int divisions = showStyle->divisions * showStyle->divisions;
- for (FrameoutList::iterator listIterator = itemList.begin(); listIterator != itemList.end(); listIterator++) {
- FrameoutEntry *itemEntry = *listIterator;
+ byte *bitmapData = _segMan->getHunkPointer(showStyle->bitmapMemId) + sizeof(GfxBitmapHeader);
- if (!itemEntry->visible)
- continue;
+ int ebx;
- if (itemEntry->object.isNull()) {
- // Picture cel data
- _coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x);
- _coordAdjuster->fromScriptToDisplay(itemEntry->picStartY, itemEntry->picStartX);
+ if (showStyle->currentStep == 0) {
+ int ctr = 0;
+ int bloot = divisions;
+ do {
+ bloot >>= 1;
+ ++ctr;
+ } while (bloot != 1);
- if (!isPictureOutOfView(itemEntry, it->planeRect, it->planeOffsetX, it->planeOffsetY))
- drawPicture(itemEntry, it->planeOffsetX, it->planeOffsetY, it->planePictureMirrored);
+ showStyle->dissolveSeed = _dissolveSequenceSeeds[ctr];
+ ebx = 800;
+ showStyle->unknown3A = 800;
+ showStyle->dissolveInitial = 800;
+ } else {
+ int ebx = showStyle->unknown3A;
+ do {
+ int eax = ebx >> 1;
+ if (ebx & 1) {
+ ebx = showStyle->dissolveSeed ^ eax;
} else {
- GfxView *view = (itemEntry->viewId != 0xFFFF) ? _cache->getView(itemEntry->viewId) : NULL;
- int16 dummyX = 0;
-
- if (view && view->isSci2Hires()) {
- view->adjustToUpscaledCoordinates(itemEntry->y, itemEntry->x);
- view->adjustToUpscaledCoordinates(itemEntry->z, dummyX);
- } else if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
- _coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x);
- _coordAdjuster->fromScriptToDisplay(itemEntry->z, dummyX);
- }
+ ebx = eax;
+ }
+ } while (ebx >= divisions);
- // Adjust according to current scroll position
- itemEntry->x -= it->planeOffsetX;
- itemEntry->y -= it->planeOffsetY;
-
- uint16 useInsetRect = readSelectorValue(_segMan, itemEntry->object, SELECTOR(useInsetRect));
- if (useInsetRect) {
- itemEntry->celRect.top = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inTop));
- itemEntry->celRect.left = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inLeft));
- itemEntry->celRect.bottom = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inBottom));
- itemEntry->celRect.right = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inRight));
- if (view && view->isSci2Hires()) {
- view->adjustToUpscaledCoordinates(itemEntry->celRect.top, itemEntry->celRect.left);
- view->adjustToUpscaledCoordinates(itemEntry->celRect.bottom, itemEntry->celRect.right);
- }
- itemEntry->celRect.translate(itemEntry->x, itemEntry->y);
- // TODO: maybe we should clip the cels rect with this, i'm not sure
- // the only currently known usage is game menu of gk1
- } else if (view) {
- // Process global scaling, if needed.
- // TODO: Seems like SCI32 always processes global scaling for scaled objects
- // TODO: We can only process symmetrical scaling for now (i.e. same value for scaleX/scaleY)
- if ((itemEntry->scaleSignal & kScaleSignalDoScaling32) &&
- !(itemEntry->scaleSignal & kScaleSignalDisableGlobalScaling32) &&
- (itemEntry->scaleX == itemEntry->scaleY) &&
- itemEntry->scaleX != 128)
- applyGlobalScaling(itemEntry, it->planeRect, view->getHeight(itemEntry->loopNo, itemEntry->celNo));
-
- if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128))
- view->getCelRect(itemEntry->loopNo, itemEntry->celNo,
- itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->celRect);
- else
- view->getCelScaledRect(itemEntry->loopNo, itemEntry->celNo,
- itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->scaleX,
- itemEntry->scaleY, itemEntry->celRect);
-
- Common::Rect nsRect = itemEntry->celRect;
- // Translate back to actual coordinate within scrollable plane
- nsRect.translate(it->planeOffsetX, it->planeOffsetY);
-
- if (g_sci->getGameId() == GID_PHANTASMAGORIA2) {
- // HACK: Some (?) objects in Phantasmagoria 2 have no NS rect. Skip them for now.
- // TODO: Remove once we figure out how Phantasmagoria 2 draws objects on screen.
- if (lookupSelector(_segMan, itemEntry->object, SELECTOR(nsLeft), NULL, NULL) != kSelectorVariable)
- continue;
- }
+ if (ebx == showStyle->dissolveInitial) {
+ ebx = 0;
+ }
+ }
- if (view && view->isSci2Hires()) {
- view->adjustBackUpscaledCoordinates(nsRect.top, nsRect.left);
- view->adjustBackUpscaledCoordinates(nsRect.bottom, nsRect.right);
- g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
- } else if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _isHiRes) {
- _coordAdjuster->fromDisplayToScript(nsRect.top, nsRect.left);
- _coordAdjuster->fromDisplayToScript(nsRect.bottom, nsRect.right);
- g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
- }
+ Common::Rect rect;
- // TODO: For some reason, the top left nsRect coordinates get
- // swapped in the GK1 inventory screen, investigate why.
- // This is also needed for GK1 rooms 710 and 720 (catacombs, inner and
- // outer circle), for handling the tiles and talking to Wolfgang.
- // HACK: Fix the coordinates by explicitly setting them here for GK1.
- // Also check bug #6729, for another case where this is needed.
- if (g_sci->getGameId() == GID_GK1)
- g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect);
- }
+ rect.left = (showStyle->width + showStyle->divisions - 1) / showStyle->divisions;
+ rect.top = (showStyle->height + showStyle->divisions - 1) / showStyle->divisions;
- // Don't attempt to draw sprites that are outside the visible
- // screen area. An example is the random people walking in
- // Jackson Square in GK1.
- if (itemEntry->celRect.bottom < 0 || itemEntry->celRect.top >= _screen->getDisplayHeight() ||
- itemEntry->celRect.right < 0 || itemEntry->celRect.left >= _screen->getDisplayWidth())
- continue;
+ if (showStyle->currentStep <= showStyle->divisions) {
+ int ebp = 0;
+ do {
+ int ecx = ebx % showStyle->divisions;
- Common::Rect clipRect, translatedClipRect;
- clipRect = itemEntry->celRect;
+ Common::Rect drawRect;
+ drawRect.left = rect.left * ecx;
+ drawRect.right = drawRect.left + rect.left - 1;
+ drawRect.top = rect.top * ebx;
+ drawRect.bottom = rect.top * ebx + rect.top - 1;
- if (view && view->isSci2Hires()) {
- clipRect.clip(it->upscaledPlaneClipRect);
- translatedClipRect = clipRect;
- translatedClipRect.translate(it->upscaledPlaneRect.left, it->upscaledPlaneRect.top);
- } else {
- // QFG4 passes invalid rectangles when a battle is starting
- if (!clipRect.isValidRect())
- continue;
- clipRect.clip(it->planeClipRect);
- translatedClipRect = clipRect;
- translatedClipRect.translate(it->planeRect.left, it->planeRect.top);
+ bool doit;
+ if (drawRect.right >= 0 && drawRect.bottom >= 0 && drawRect.left <= rect.right && drawRect.top <= rect.bottom) {
+ doit = true;
+ } else {
+ doit = false;
+ }
+
+ if (doit) {
+ if (drawRect.left < 0) {
+ drawRect.left = 0;
}
- if (view) {
- if (!clipRect.isEmpty()) {
- if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128))
- view->draw(itemEntry->celRect, clipRect, translatedClipRect,
- itemEntry->loopNo, itemEntry->celNo, 255, 0, view->isSci2Hires());
- else
- view->drawScaled(itemEntry->celRect, clipRect, translatedClipRect,
- itemEntry->loopNo, itemEntry->celNo, 255, itemEntry->scaleX, itemEntry->scaleY);
- }
+ if (drawRect.top < 0) {
+ drawRect.top = 0;
}
- // Draw text, if it exists
- if (lookupSelector(_segMan, itemEntry->object, SELECTOR(text), NULL, NULL) == kSelectorVariable) {
- g_sci->_gfxText32->drawTextBitmap(itemEntry->x, itemEntry->y, it->planeRect, itemEntry->object);
+ if (drawRect.right > rect.right) {
+ drawRect.right = rect.right;
}
+
+ if (drawRect.bottom > rect.bottom) {
+ drawRect.bottom = rect.bottom;
+ }
+ } else {
+ drawRect.right = 0;
+ drawRect.bottom = 0;
+ drawRect.left = 0;
+ drawRect.top = 0;
}
- }
- for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) {
- if (pictureIt->object == planeObject) {
- delete[] pictureIt->pictureCels;
- pictureIt->pictureCels = 0;
+ fillRect(bitmapData, drawRect, showStyle->color, showStyle->width);
+
+ int eax = ebx;
+ do {
+ eax >>= 1;
+ if (ebx & 1) {
+ ebx = showStyle->dissolveSeed;
+ ebx ^= eax;
+ } else {
+ ebx = eax;
+ }
+ } while (ebx >= divisions);
+
+ if (showStyle->currentStep != showStyle->divisions) {
+ ebp++;
+ } else {
+ drawRect.left = 0;
+ drawRect.top = 0;
+ drawRect.right = showStyle->width - 1;
+ drawRect.bottom = showStyle->height - 1;
+ fillRect(bitmapData, drawRect, showStyle->color, showStyle->width);
}
- }
+
+ } while (ebp <= showStyle->divisions);
+
+ showStyle->unknown3A = ebx;
+ ++showStyle->currentStep;
+ showStyle->nextTick += showStyle->delay;
+ // _screen->updateScreenItem(showStyle->bitmapScreenItem);
}
- showCurrentScrollText();
+ if (showStyle->currentStep >= showStyle->divisions) {
+ if (showStyle->fadeUp) {
+ showStyle->processed = true;
+ }
- _screen->copyToScreen();
+ return true;
+ }
- g_sci->getEngineState()->_throttleTrigger = true;
+ return false;
}
-void GfxFrameout::printPlaneList(Console *con) {
- for (PlaneList::const_iterator it = _planes.begin(); it != _planes.end(); ++it) {
- PlaneEntry p = *it;
- Common::String curPlaneName = _segMan->getObjectName(p.object);
- Common::Rect r = p.upscaledPlaneRect;
- Common::Rect cr = p.upscaledPlaneClipRect;
+bool GfxFrameout::processShowStyle12(ShowStyleEntry *const showStyle) {
+ return true;
+}
+#endif
+
+void GfxFrameout::kernelFrameout(const bool shouldShowBits) {
+ if (_showStyles != nullptr) {
+ processShowStyles();
+ } else if (_palMorphIsOn) {
+ palMorphFrameOut(_styleRanges, nullptr);
+ _palMorphIsOn = false;
+ } else {
+// TODO: Window scroll
+// if (g_ScrollWindow) {
+// doScroll();
+// }
- con->debugPrintf("%04x:%04x (%s): prio %d, lastprio %d, offsetX %d, offsetY %d, pic %d, mirror %d, back %d\n",
- PRINT_REG(p.object), curPlaneName.c_str(),
- (int16)p.priority, (int16)p.lastPriority,
- p.planeOffsetX, p.planeOffsetY, p.pictureId,
- p.planePictureMirrored, p.planeBack);
- con->debugPrintf(" rect: (%d, %d, %d, %d), clip rect: (%d, %d, %d, %d)\n",
- r.left, r.top, r.right, r.bottom,
- cr.left, cr.top, cr.right, cr.bottom);
+ Common::Rect frameOutRect(0, 0);
+ frameOut(shouldShowBits, frameOutRect);
+ }
+}
- if (p.pictureId != 0xffff && p.pictureId != 0xfffe) {
- con->debugPrintf("Pictures:\n");
+#pragma mark -
+#pragma mark Debugging
- for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) {
- if (pictureIt->object == p.object) {
- con->debugPrintf(" Picture %d: x %d, y %d\n", pictureIt->pictureId, pictureIt->startX, pictureIt->startY);
- }
- }
- }
+void GfxFrameout::printPlaneListInternal(Console *con, const PlaneList &planeList) const {
+ for (PlaneList::const_iterator it = planeList.begin(); it != planeList.end(); ++it) {
+ Plane *p = *it;
+ p->printDebugInfo(con);
}
}
-void GfxFrameout::printPlaneItemList(Console *con, reg_t planeObject) {
- for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) {
- FrameoutEntry *e = *listIterator;
- reg_t itemPlane = readSelector(_segMan, e->object, SELECTOR(plane));
+void GfxFrameout::printPlaneList(Console *con) const {
+ printPlaneListInternal(con, _planes);
+}
+
+void GfxFrameout::printVisiblePlaneList(Console *con) const {
+ printPlaneListInternal(con, _visiblePlanes);
+}
- if (planeObject == itemPlane) {
- Common::String curItemName = _segMan->getObjectName(e->object);
- Common::Rect icr = e->celRect;
- GuiResourceId picId = e->picture ? e->picture->getResourceId() : 0;
+void GfxFrameout::printPlaneItemList(Console *con, const reg_t planeObject) const {
+ Plane *p = _planes.findByObject(planeObject);
- con->debugPrintf("%d: %04x:%04x (%s), view %d, loop %d, cel %d, x %d, y %d, z %d, "
- "signal %d, scale signal %d, scaleX %d, scaleY %d, rect (%d, %d, %d, %d), "
- "pic %d, picX %d, picY %d, visible %d\n",
- e->givenOrderNr, PRINT_REG(e->object), curItemName.c_str(),
- e->viewId, e->loopNo, e->celNo, e->x, e->y, e->z,
- e->signal, e->scaleSignal, e->scaleX, e->scaleY,
- icr.left, icr.top, icr.right, icr.bottom,
- picId, e->picStartX, e->picStartY, e->visible);
- }
+ if (p == nullptr) {
+ con->debugPrintf("Plane does not exist");
+ return;
+ }
+
+ ScreenItemList::size_type i = 0;
+ for (ScreenItemList::iterator sit = p->_screenItemList.begin(); sit != p->_screenItemList.end(); sit++) {
+ ScreenItem *screenItem = *sit;
+ con->debugPrintf("%2d: ", i++);
+ screenItem->printDebugInfo(con);
}
}
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index d1a706e8de..5323a2ad04 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -23,86 +23,190 @@
#ifndef SCI_GRAPHICS_FRAMEOUT_H
#define SCI_GRAPHICS_FRAMEOUT_H
-namespace Sci {
+#include "sci/graphics/plane32.h"
+#include "sci/graphics/screen_item32.h"
-class GfxPicture;
+namespace Sci {
+// TODO: Don't do this this way
+int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]);
-struct PlaneLineEntry {
- reg_t hunkId;
- Common::Point startPoint;
- Common::Point endPoint;
- byte color;
- byte priority;
- byte control;
+// TODO: Verify display styles and adjust names appropriately for
+// types 1 through 12 & 15 (others are correct)
+// Names should be:
+// * VShutterIn, VShutterOut
+// * HShutterIn, HShutterOut
+// * WipeLeft, WipeRight, WipeDown, WipeUp
+// * PixelDissolve
+// * ShutDown and Kill? (and Plain and Fade?)
+enum ShowStyleType /* : uint8 */ {
+ kShowStyleNone = 0,
+ kShowStyleHShutterOut = 1,
+ kShowStyleHShutterIn = 2,
+ kShowStyleVShutterOut = 3,
+ kShowStyleVShutterIn = 4,
+ kShowStyleWipeLeft = 5,
+ kShowStyleWipeRight = 6,
+ kShowStyleWipeUp = 7,
+ kShowStyleWipeDown = 8,
+ kShowStyleIrisOut = 9,
+ kShowStyleIrisIn = 10,
+ kShowStyle11 = 11,
+ kShowStyle12 = 12,
+ kShowStyleFadeOut = 13,
+ kShowStyleFadeIn = 14,
+ // TODO: Only in SCI3
+ kShowStyleUnknown = 15
};
-typedef Common::List<PlaneLineEntry> PlaneLineList;
-
-struct PlaneEntry {
- reg_t object;
- int16 priority;
- int16 lastPriority;
- int16 planeOffsetX;
- int16 planeOffsetY;
- GuiResourceId pictureId;
- Common::Rect planeRect;
- Common::Rect planeClipRect;
- Common::Rect upscaledPlaneRect;
- Common::Rect upscaledPlaneClipRect;
- bool planePictureMirrored;
- byte planeBack;
- PlaneLineList lines;
-};
+/**
+ * Show styles represent transitions applied to draw planes.
+ * One show style per plane can be active at a time.
+ */
+struct ShowStyleEntry {
+ /**
+ * The ID of the plane this show style belongs to.
+ * In SCI2.1mid (at least SQ6), per-plane transitions
+ * were removed and a single plane ID is used.
+ */
+ reg_t plane;
-typedef Common::List<PlaneEntry> PlaneList;
-
-struct FrameoutEntry {
- uint16 givenOrderNr;
- reg_t object;
- GuiResourceId viewId;
- int16 loopNo;
- int16 celNo;
- int16 x, y, z;
- int16 priority;
- uint16 signal;
- uint16 scaleSignal;
- int16 scaleX;
- int16 scaleY;
- Common::Rect celRect;
- GfxPicture *picture;
- int16 picStartX;
- int16 picStartY;
- bool visible;
-};
+ /**
+ * The type of the transition.
+ */
+ ShowStyleType type;
-typedef Common::List<FrameoutEntry *> FrameoutList;
+ // TODO: This name is probably incorrect
+ bool fadeUp;
-struct PlanePictureEntry {
- reg_t object;
- int16 startX;
- int16 startY;
- GuiResourceId pictureId;
- GfxPicture *picture;
- FrameoutEntry *pictureCels; // temporary
-};
+ /**
+ * The number of steps for the show style.
+ */
+ int16 divisions;
-typedef Common::List<PlanePictureEntry> PlanePictureList;
+ // NOTE: This property exists from SCI2 through at least
+ // SCI2.1mid but is never used in the actual processing
+ // of the styles?
+ int unknownC;
-struct ScrollTextEntry {
- reg_t bitmapHandle;
- reg_t kWindow;
- uint16 x;
- uint16 y;
-};
+ /**
+ * The colour used by transitions that draw CelObjColor
+ * screen items. -1 for transitions that do not draw
+ * screen items.
+ */
+ int16 color;
+
+ // TODO: Probably uint32
+ // TODO: This field probably should be used in order to
+ // provide time-accurate processing of show styles. In the
+ // actual SCI engine (at least 2–2.1mid) it appears that
+ // style transitions are drawn “as fast as possible”, one
+ // step per loop, even though this delay field exists
+ int delay;
+
+ // TODO: Probably bool, but never seems to be true?
+ int animate;
+
+ /**
+ * The wall time at which the next step of the animation
+ * should execute.
+ */
+ uint32 nextTick;
+
+ /**
+ * During playback of the show style, the current step
+ * (out of divisions).
+ */
+ int currentStep;
+
+ /**
+ * The next show style.
+ */
+ ShowStyleEntry *next;
+
+ /**
+ * Whether or not this style has finished running and
+ * is ready for disposal.
+ */
+ bool processed;
+
+ //
+ // Engine-specific properties for SCI2 through 2.1early
+ //
+
+ // TODO: Could union this stuff to save literally
+ // several bytes of memory.
+
+ /**
+ * The width of the plane. Used to determine the correct
+ * size of screen items for wipes.
+ */
+ int width;
+
+ /**
+ * The height of the plane. Used to determine the correct
+ * size of screen items for wipes.
+ */
+ int height;
+
+ /**
+ * The number of edges that a transition operates on.
+ * Slide wipe: 1 edge
+ * Reveal wipe: 2 edges
+ * Iris wipe: 4 edges
+ */
+ // TODO: I have no idea why SCI engine stores this instead
+ // of a screenItems count
+ int edgeCount;
+
+ /**
+ * Used by transition types 1 through 10.
+ * One screen item per division per edge.
+ */
+ ScreenItemList screenItems;
+
+ /**
+ * Used by transition types 11 and 12. A copy of the
+ * visible frame buffer.
+ */
+ // TODO: This is a reg_t in SCI engine; not sure if
+ // we can avoid allocation through SegMan or not.
+ reg_t bitmapMemId;
+
+ /**
+ * Used by transition types 11 and 12. A screen item
+ * used to display the associated bitmap data.
+ */
+ ScreenItem *bitmapScreenItem;
-typedef Common::Array<ScrollTextEntry> ScrollTextList;
+ /**
+ * A number used to pick pixels to dissolve by types
+ * 11 and 12.
+ */
+ int dissolveSeed;
+ int unknown3A;
+ // max?
+ int dissolveInitial;
-enum ViewScaleSignals32 {
- kScaleSignalDoScaling32 = 0x0001, // enables scaling when drawing that cel (involves scaleX and scaleY)
- kScaleSignalUnk1 = 0x0002, // unknown
- kScaleSignalDisableGlobalScaling32 = 0x0004
+ //
+ // Engine specific properties for SCI2.1mid through SCI3
+ //
+
+ /**
+ * The number of entries in the fadeColorRanges array.
+ */
+ uint8 fadeColorRangesCount;
+
+ /**
+ * A pointer to an dynamically sized array of palette
+ * indexes, in the order [ fromColor, toColor, ... ].
+ * Only colors within this range are transitioned.
+ */
+ uint16 *fadeColorRanges;
};
+typedef Common::Array<DrawList> ScreenItemListList;
+typedef Common::Array<RectList> EraseListList;
+
class GfxCache;
class GfxCoordAdjuster32;
class GfxPaint32;
@@ -114,69 +218,285 @@ class GfxScreen;
* Roughly equivalent to GraphicsMgr in the actual SCI engine.
*/
class GfxFrameout {
+private:
+ bool _isHiRes;
+ GfxCache *_cache;
+ GfxCoordAdjuster32 *_coordAdjuster;
+ GfxPalette32 *_palette;
+ ResourceManager *_resMan;
+ GfxScreen *_screen;
+ SegManager *_segMan;
+ GfxPaint32 *_paint32;
+
public:
GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32);
~GfxFrameout();
- void kernelAddPlane(reg_t object);
- void kernelUpdatePlane(reg_t object);
- void kernelDeletePlane(reg_t object);
- void applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight);
- void kernelAddScreenItem(reg_t object);
- void kernelUpdateScreenItem(reg_t object);
- void kernelDeleteScreenItem(reg_t object);
- void deletePlaneItems(reg_t planeObject);
- FrameoutEntry *findScreenItem(reg_t object);
- int16 kernelGetHighPlanePri();
- void kernelAddPicAt(reg_t planeObj, GuiResourceId pictureId, int16 pictureX, int16 pictureY);
- void kernelFrameout();
-
- void addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY = 0);
- void deletePlanePictures(reg_t object);
- reg_t addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control);
- void updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control);
- void deletePlaneLine(reg_t object, reg_t hunkId);
void clear();
+ void run();
- // Scroll text functions
- void addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace);
- void showCurrentScrollText();
- void initScrollText(uint16 maxItems) { _maxScrollTexts = maxItems; }
- void clearScrollTexts();
- void firstScrollText() { if (_scrollTexts.size() > 0) _curScrollText = 0; }
- void lastScrollText() { if (_scrollTexts.size() > 0) _curScrollText = _scrollTexts.size() - 1; }
- void prevScrollText() { if (_curScrollText > 0) _curScrollText--; }
- void nextScrollText() { if (_curScrollText + 1 < (uint16)_scrollTexts.size()) _curScrollText++; }
- void toggleScrollText(bool show) { _showScrollText = show; }
+#pragma mark -
+#pragma mark Screen items
+private:
+ void deleteScreenItem(ScreenItem *screenItem, const reg_t plane);
- void printPlaneList(Console *con);
- void printPlaneItemList(Console *con, reg_t planeObject);
+public:
+ void kernelAddScreenItem(const reg_t object);
+ void kernelUpdateScreenItem(const reg_t object);
+ void kernelDeleteScreenItem(const reg_t object);
+#pragma mark -
+#pragma mark Planes
private:
- bool _isHiRes;
+ /**
+ * The list of planes (i.e. layers) that have been added
+ * to the screen.
+ *
+ * @note This field is on `GraphicsMgr.screen` in SCI
+ * engine.
+ */
+ PlaneList _planes;
- void showVideo();
- void createPlaneItemList(reg_t planeObject, FrameoutList &itemList);
- bool isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY);
- void drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored);
+ /**
+ * Creates and adds a new plane to the plane list, or
+ * cancels deletion and updates an already-existing
+ * plane if a plane matching the given plane VM object
+ * already exists within the current plane list.
+ *
+ * @note This method is on Screen in SCI engine, but it
+ * is only ever called on `GraphicsMgr.screen`.
+ */
+ void addPlane(Plane &plane);
- SegManager *_segMan;
- ResourceManager *_resMan;
- GfxCoordAdjuster32 *_coordAdjuster;
- GfxCache *_cache;
- GfxPalette32 *_palette;
- GfxScreen *_screen;
- GfxPaint32 *_paint32;
+ /**
+ * Updates an existing plane with properties from the
+ * given VM object.
+ */
+ void updatePlane(Plane &plane);
- FrameoutList _screenItems;
- PlaneList _planes;
- PlanePictureList _planePictures;
- ScrollTextList _scrollTexts;
- int16 _curScrollText;
- bool _showScrollText;
- uint16 _maxScrollTexts;
+public:
+ const PlaneList &getPlanes() const {
+ return _planes;
+ }
+ void kernelAddPlane(const reg_t object);
+ void kernelUpdatePlane(const reg_t object);
+ void kernelDeletePlane(const reg_t object);
+ int16 kernelGetHighPlanePri();
+
+#pragma mark -
+#pragma mark Pics
+public:
+ void kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 pictureX, const int16 pictureY, const bool mirrorX);
+
+#pragma mark -
+
+ // TODO: Remap-related?
+ void kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor);
+
+#pragma mark -
+#pragma mark Transitions
+private:
+ int *_dissolveSequenceSeeds;
+ int16 *_defaultDivisions;
+ int16 *_defaultUnknownC;
+
+ /**
+ * TODO: Documentation
+ */
+ ShowStyleEntry *_showStyles;
+
+ inline ShowStyleEntry *findShowStyleForPlane(const reg_t planeObj) const;
+ inline ShowStyleEntry *deleteShowStyleInternal(ShowStyleEntry *const showStyle);
+ void processShowStyles();
+ bool processShowStyleNone(ShowStyleEntry *showStyle);
+ bool processShowStyleMorph(ShowStyleEntry *showStyle);
+ bool processShowStyleFade(const int direction, ShowStyleEntry *showStyle);
+#if 0
+ bool processShowStyleWipe(const int direction, ShowStyleEntry *const showStyle);
+#endif
+
+public:
+ // NOTE: This signature is taken from SCI3 Phantasmagoria 2
+ // and is valid for all implementations of SCI32
+ void kernelSetShowStyle(const uint16 argc, const reg_t &planeObj, const ShowStyleType type, const int16 seconds, const int16 direction, const int16 priority, const int16 animate, const int16 frameOutNow, const reg_t &pFadeArray, const int16 divisions, const int16 blackScreen);
+
+#pragma mark -
+#pragma mark Rendering
+private:
+ /**
+ * TODO: Documentation
+ */
+ int8 _styleRanges[256];
+
+ /**
+ * The internal display pixel buffer. During frameOut,
+ * this buffer is drawn into according to the draw and
+ * erase rects calculated by `calcLists`, then drawn out
+ * to the hardware surface according to the `_showList`
+ * rects (which are also calculated by `calcLists`).
+ */
+ Buffer _currentBuffer;
+
+ // TODO: In SCI2.1/SQ6, priority map pixels are not allocated
+ // by default. In SCI2/GK1, pixels are allocated, but not used
+ // anywhere except within CelObj::Draw in seemingly the same
+ // way they are used in SCI2.1/SQ6: that is, never read, only
+ // written.
+ Buffer _priorityMap;
+
+ /**
+ * TODO: Documentation
+ */
+ bool _remapOccurred;
- void sortPlanes();
+ /**
+ * Whether or not the data in the current buffer is what
+ * is visible to the user. During rendering updates,
+ * this flag is set to false.
+ */
+ bool _frameNowVisible;
+
+ /**
+ * TODO: Document
+ * TODO: Depending upon if the engine ever modifies this
+ * rect, it may be stupid to store it separately instead
+ * of just getting width/height from GfxScreen.
+ *
+ * @note This field is on `GraphicsMgr.screen` in SCI
+ * engine.
+ */
+ Common::Rect _screenRect;
+
+ /**
+ * A list of rectangles, in display coordinates, that
+ * represent portions of the internal screen buffer that
+ * should be drawn to the hardware display surface.
+ *
+ * @note This field is on `GraphicsMgr.screen` in SCI
+ * engine.
+ */
+ RectList _showList;
+
+ /**
+ * The amount of extra overdraw that is acceptable when
+ * merging two show list rectangles together into a
+ * single larger rectangle.
+ *
+ * @note This field is on `GraphicsMgr.screen` in SCI
+ * engine.
+ */
+ int _overdrawThreshold;
+
+ /**
+ * A list of planes that are currently drawn to the
+ * hardware display surface. Used to calculate
+ * differences in plane properties between the last
+ * frame and current frame.
+ *
+ * @note This field is on `GraphicsMgr.visibleScreen` in
+ * SCI engine.
+ */
+ PlaneList _visiblePlanes;
+
+ /**
+ * Calculates the location and dimensions of dirty rects
+ * over the entire screen for rendering the next frame.
+ * The draw and erase lists in `drawLists` and
+ * `eraseLists` each represent one plane on the screen.
+ */
+ void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect);
+
+ /**
+ * Erases the areas in the given erase list from the
+ * visible screen buffer by filling them with the color
+ * from the corresponding plane. This is an optimisation
+ * for coloured-type planes only; other plane types have
+ * to be redrawn from pixel data.
+ */
+ void drawEraseList(const RectList &eraseList, const Plane &plane);
+
+ /**
+ * Draws all screen items from the given draw list to
+ * the visible screen buffer.
+ */
+ void drawScreenItemList(const DrawList &screenItemList);
+
+ /**
+ * Updates the internal screen buffer for the next
+ * frame. If `shouldShowBits` is true, also sends the
+ * buffer to hardware.
+ */
+ void frameOut(const bool shouldShowBits, const Common::Rect &rect);
+
+ /**
+ * Adds a new rectangle to the list of regions to write
+ * out to the hardware. The provided rect may be merged
+ * into an existing rectangle to reduce the number of
+ * blit operations.
+ */
+ void mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold);
+
+ /**
+ * TODO: Documentation
+ */
+ void palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle);
+
+ /**
+ * Writes the internal frame buffer out to hardware and
+ * clears the show list.
+ */
+ void showBits();
+
+public:
+ /**
+ * TODO: Document
+ * This is used by CelObj::Draw.
+ */
+ bool _hasRemappedScreenItem;
+
+ /**
+ * Whether palMorphFrameOut should be used instead of
+ * frameOut for rendering. Used by kMorphOn to
+ * explicitly enable palMorphFrameOut for one frame.
+ */
+ bool _palMorphIsOn;
+
+ inline Buffer &getCurrentBuffer() {
+ return _currentBuffer;
+ }
+
+ void kernelFrameout(const bool showBits);
+
+ /**
+ * Modifies the raw pixel data for the next frame with
+ * new palette indexes based on matched style ranges.
+ */
+ void alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges);
+
+ // TODO: SCI2 engine never uses priority map?
+ inline Buffer &getPriorityMap() {
+ return _priorityMap;
+ }
+
+ // NOTE: This function is used within ScreenItem subsystem and assigned
+ // to various booleanish fields that seem to represent the state of the
+ // screen item (created, updated, deleted). In GK1/DOS, Phant1/m68k,
+ // SQ6/DOS, SQ6/Win, and Phant2/Win, this function simply returns 1. If
+ // you know of any game/environment where this function returns some
+ // value other than 1, or if you used to work at Sierra and can explain
+ // why this is a thing (and if anyone needs to care about it), please
+ // open a ticket!!
+ inline int getScreenCount() const {
+ return 1;
+ };
+
+#pragma mark -
+#pragma mark Debugging
+public:
+ void printPlaneList(Console *con) const;
+ void printVisiblePlaneList(Console *con) const;
+ void printPlaneListInternal(Console *con, const PlaneList &planeList) const;
+ void printPlaneItemList(Console *con, const reg_t planeObject) const;
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h
index e5b9f2aaed..c48ad4c8bf 100644
--- a/engines/sci/graphics/helpers.h
+++ b/engines/sci/graphics/helpers.h
@@ -26,6 +26,11 @@
#include "common/endian.h" // for READ_LE_UINT16
#include "common/rect.h"
#include "common/serializer.h"
+#ifdef ENABLE_SCI32
+#include "common/rational.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+#endif
#include "sci/engine/vm_types.h"
namespace Sci {
@@ -45,6 +50,9 @@ typedef int16 TextAlignment;
#define PORTS_FIRSTWINDOWID 2
#define PORTS_FIRSTSCRIPTWINDOWID 3
+#ifdef ENABLE_SCI32
+#define PRINT_RECT(x) (x).left,(x).top,(x).right,(x).bottom
+#endif
struct Port {
uint16 id;
@@ -118,6 +126,79 @@ struct Window : public Port, public Common::Serializable {
}
};
+#ifdef ENABLE_SCI32
+/**
+ * Multiplies a number by a rational number, rounding up to
+ * the nearest whole number.
+ */
+inline int mulru(const int value, const Common::Rational &ratio, const int extra = 0) {
+ int num = (value + extra) * ratio.getNumerator();
+ int result = num / ratio.getDenominator();
+ if (num > ratio.getDenominator() && num % ratio.getDenominator()) {
+ ++result;
+ }
+ return result - extra;
+}
+
+/**
+ * Multiplies a point by two rational numbers for X and Y,
+ * rounding up to the nearest whole number. Modifies the
+ * point directly.
+ */
+inline void mulru(Common::Point &point, const Common::Rational &ratioX, const Common::Rational &ratioY) {
+ point.x = mulru(point.x, ratioX);
+ point.y = mulru(point.y, ratioY);
+}
+
+/**
+ * Multiplies a point by two rational numbers for X and Y,
+ * rounding up to the nearest whole number. Modifies the
+ * rect directly.
+ */
+inline void mulru(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY, const int brExtra = 0) {
+ rect.left = mulru(rect.left, ratioX);
+ rect.top = mulru(rect.top, ratioY);
+ rect.right = mulru(rect.right, ratioX, brExtra);
+ rect.bottom = mulru(rect.bottom, ratioY, brExtra);
+}
+
+struct Buffer : public Graphics::Surface {
+ uint16 screenWidth;
+ uint16 screenHeight;
+ uint16 scriptWidth;
+ uint16 scriptHeight;
+
+ Buffer(const uint16 width, const uint16 height, uint8 *const pix) :
+ screenWidth(width),
+ screenHeight(height),
+ // TODO: These values are not correct for all games. Script
+ // dimensions were hard-coded per game in the original
+ // interpreter. Search all games for their internal script
+ // dimensions and set appropriately. (This code does not
+ // appear to exist at all in SCI3, which uses 640x480.)
+ scriptWidth(320),
+ scriptHeight(200) {
+ init(width, height, width, pix, Graphics::PixelFormat::createFormatCLUT8());
+ }
+
+ void clear(const uint8 value) {
+ memset(pixels, value, w * h);
+ }
+
+ inline uint8 *getAddress(const uint16 x, const uint16 y) {
+ return (uint8 *)getBasePtr(x, y);
+ }
+
+ inline uint8 *getAddressSimRes(const uint16 x, const uint16 y) {
+ return (uint8*)pixels + (y * w * screenHeight / scriptHeight) + (x * screenWidth / scriptWidth);
+ }
+
+ bool isNull() {
+ return pixels == nullptr;
+ }
+};
+#endif
+
struct Color {
byte used;
byte r, g, b;
diff --git a/engines/sci/graphics/lists32.h b/engines/sci/graphics/lists32.h
new file mode 100644
index 0000000000..b12cd55897
--- /dev/null
+++ b/engines/sci/graphics/lists32.h
@@ -0,0 +1,192 @@
+/* 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.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_LISTS32_H
+#define SCI_GRAPHICS_LISTS32_H
+
+#include "common/array.h"
+
+namespace Sci {
+
+/**
+ * StablePointerArray holds pointers in a fixed-size array
+ * that maintains position of erased items until `pack` is
+ * called. It is used by DrawList, RectList, and
+ * ScreenItemList. StablePointerArray takes ownership of
+ * all pointers that are passed to it and deletes them when
+ * calling `erase` or when destroying the
+ * StablePointerArray.
+ */
+template <class T, uint N>
+class StablePointerArray {
+ uint _size;
+ T *_items[N];
+
+public:
+ typedef T **iterator;
+ typedef T *const *const_iterator;
+ typedef T *value_type;
+ typedef uint size_type;
+
+ StablePointerArray() : _size(0), _items() {}
+ StablePointerArray(const StablePointerArray &other) : _size(other._size) {
+ for (size_type i = 0; i < _size; ++i) {
+ if (other._items[i] == nullptr) {
+ _items[i] = nullptr;
+ } else {
+ _items[i] = new T(*other._items[i]);
+ }
+ }
+ }
+ ~StablePointerArray() {
+ for (size_type i = 0; i < _size; ++i) {
+ delete _items[i];
+ }
+ }
+
+ void operator=(const StablePointerArray &other) {
+ clear();
+ _size = other._size;
+ for (size_type i = 0; i < _size; ++i) {
+ if (other._items[i] == nullptr) {
+ _items[i] = nullptr;
+ } else {
+ _items[i] = new T(*other._items[i]);
+ }
+ }
+ }
+
+ T *const &operator[](size_type index) const {
+ assert(index >= 0 && index < _size);
+ return _items[index];
+ }
+
+ T *&operator[](size_type index) {
+ assert(index >= 0 && index < _size);
+ return _items[index];
+ }
+
+ /**
+ * Adds a new pointer to the array.
+ */
+ void add(T *item) {
+ assert(_size < N);
+ _items[_size++] = item;
+ }
+
+ iterator begin() {
+ return _items;
+ }
+
+ const_iterator begin() const {
+ return _items;
+ }
+
+ void clear() {
+ for (size_type i = 0; i < _size; ++i) {
+ delete _items[i];
+ _items[i] = nullptr;
+ }
+
+ _size = 0;
+ }
+
+ iterator end() {
+ return _items + _size;
+ }
+
+ const_iterator end() const {
+ return _items + _size;
+ }
+
+ /**
+ * Erases the object pointed to by the given iterator.
+ */
+ void erase(T *item) {
+ for (iterator it = begin(); it != end(); ++it) {
+ if (*it == item) {
+ delete *it;
+ *it = nullptr;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Erases the object pointed to by the given iterator.
+ */
+ void erase(iterator &it) {
+ assert(it >= _items && it < _items + _size);
+ delete *it;
+ *it = nullptr;
+ }
+
+ /**
+ * Erases the object pointed to at the given index.
+ */
+ void erase_at(size_type index) {
+ assert(index >= 0 && index < _size);
+
+ delete _items[index];
+ _items[index] = nullptr;
+ }
+
+ /**
+ * Removes freed pointers from the pointer list.
+ */
+ size_type pack() {
+ iterator freePtr = begin();
+ size_type newSize = 0;
+
+ for (iterator it = begin(), last = end(); it != last; ++it) {
+ if (*it != nullptr) {
+ *freePtr = *it;
+ ++freePtr;
+ ++newSize;
+ }
+ }
+
+ _size = newSize;
+ return newSize;
+ }
+
+ /**
+ * The number of populated slots in the array. The size
+ * of the array will only go down once `pack` is called.
+ */
+ size_type size() const {
+ return _size;
+ }
+};
+
+template <typename T>
+class FindByObject {
+ const reg_t &_object;
+public:
+ FindByObject(const reg_t &object) : _object(object) {}
+ bool operator()(const T entry) const {
+ return entry->_object == _object;
+ }
+};
+
+}
+#endif
diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index e61ac5dac6..40e4021e72 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -47,11 +47,10 @@ GfxPalette32::GfxPalette32(ResourceManager *resMan, GfxScreen *screen)
_version(1), _versionUpdated(false) {
_varyPercent = _varyTargetPercent;
memset(_fadeTable, 100, sizeof(_fadeTable));
-
// NOTE: In SCI engine, the palette manager constructor loads
// the default palette, but in ScummVM this initialisation
// is performed by SciEngine::run; see r49523 for details
- }
+}
GfxPalette32::~GfxPalette32() {
unloadClut();
@@ -67,6 +66,10 @@ inline void mergePaletteInternal(Palette *const to, const Palette *const from) {
}
}
+const Palette *GfxPalette32::getNextPalette() const {
+ return &_nextPalette;
+}
+
void GfxPalette32::submit(Palette &palette) {
// TODO: The resource manager in SCI32 retains raw data of palettes from
// the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and
@@ -206,10 +209,20 @@ int16 GfxPalette32::matchColor(const byte r, const byte g, const byte b, const i
return bestIndex;
}
-void GfxPalette32::updateForFrame() {
+bool GfxPalette32::updateForFrame() {
applyAll();
_versionUpdated = false;
// TODO: Implement remapping
+ // return g_sci->_gfxFrameout->remapAllTables(_nextPalette != _sysPalette);
+ return false;
+}
+
+void GfxPalette32::updateFFrame() {
+ for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) {
+ _nextPalette.colors[i] = _sourcePalette.colors[i];
+ }
+ _versionUpdated = false;
+ // TODO: Implement remapping
// g_sci->_gfxFrameout->remapAllTables(_nextPalette != _sysPalette);
}
@@ -410,7 +423,7 @@ void GfxPalette32::setVaryPercent(const int16 percent, const int time, const int
}
int16 GfxPalette32::getVaryPercent() const {
- return abs(_varyPercent);
+ return ABS(_varyPercent);
}
void GfxPalette32::varyOff() {
@@ -773,6 +786,12 @@ void GfxPalette32::applyCycles() {
// Palette fading
//
+// NOTE: There are some game scripts (like SQ6 Sierra logo and main menu) that call
+// setFade with numColorsToFade set to 256, but other parts of the engine like
+// processShowStyleNone use 255 instead of 256. It is not clear if this is because
+// the last palette entry is intentionally left unmodified, or if this is a bug
+// in the engine. It certainly seems confused because all other places that accept
+// colour ranges typically receive values in the range of 0–255.
void GfxPalette32::setFade(uint8 percent, uint8 fromColor, uint16 numColorsToFade) {
if (fromColor > numColorsToFade) {
return;
diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h
index 8744687e4c..9110a8a6f4 100644
--- a/engines/sci/graphics/palette32.h
+++ b/engines/sci/graphics/palette32.h
@@ -25,6 +25,7 @@
#include "sci/graphics/palette.h"
+namespace Sci {
enum PalCyclerDirection {
PalCycleBackward = 0,
PalCycleForward = 1
@@ -71,188 +72,206 @@ struct PalCycler {
uint16 numTimesPaused;
};
-namespace Sci {
- class GfxPalette32 : public GfxPalette {
- public:
- GfxPalette32(ResourceManager *resMan, GfxScreen *screen);
- ~GfxPalette32();
-
- protected:
- /**
- * The palette revision version. Increments once per game
- * loop that changes the source palette. TODO: Possibly
- * other areas also change _version, double-check once it
- * is all implemented.
- */
- uint32 _version;
-
- /**
- * Whether or not the palette manager version was updated
- * during this loop.
- */
- bool _versionUpdated;
-
- /**
- * The unmodified source palette loaded by kPalette. Additional
- * palette entries may be mixed into the source palette by
- * CelObj objects, which contain their own palettes.
- */
- Palette _sourcePalette;
-
- /**
- * The palette to be used when the hardware is next updated.
- * On update, _nextPalette is transferred to _sysPalette.
- */
- Palette _nextPalette;
-
- // SQ6 defines 10 cyclers
- PalCycler *_cyclers[10];
-
- /**
- * The cycle map is used to detect overlapping cyclers.
- * According to SCI engine code, when two cyclers overlap,
- * a fatal error has occurred and the engine will display
- * an error and then exit.
- */
- bool _cycleMap[256];
- inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear);
- inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear);
- inline PalCycler *getCycler(uint16 fromColor);
-
- /**
- * The fade table records the expected intensity level of each pixel
- * in the palette that will be displayed on the next frame.
- */
- byte _fadeTable[256];
-
- /**
- * An optional lookup table used to remap RGB565 colors to a palette
- * index. Used by Phantasmagoria 2 in 8-bit color environments.
- */
- byte *_clutTable;
-
- /**
- * An optional palette used to describe the source colors used
- * in a palette vary operation. If this palette is not specified,
- * sourcePalette is used instead.
- */
- Palette *_varyStartPalette;
-
- /**
- * An optional palette used to describe the target colors used
- * in a palette vary operation.
- */
- Palette *_varyTargetPalette;
-
- /**
- * The minimum palette index that has been varied from the
- * source palette. 0–255
- */
- uint8 _varyFromColor;
-
- /**
- * The maximum palette index that is has been varied from the
- * source palette. 0-255
- */
- uint8 _varyToColor;
-
- /**
- * The tick at the last time the palette vary was updated.
- */
- uint32 _varyLastTick;
-
- /**
- * TODO: Document
- * The velocity of change in percent?
- */
- int _varyTime;
-
- /**
- * TODO: Better documentation
- * The direction of change, -1, 0, or 1.
- */
- int16 _varyDirection;
-
- /**
- * The amount, in percent, that the vary color is currently
- * blended into the source color.
- */
- int16 _varyPercent;
-
- /**
- * The target amount that a vary color will be blended into
- * the source color.
- */
- int16 _varyTargetPercent;
-
- /**
- * The number of time palette varying has been paused.
- */
- uint16 _varyNumTimesPaused;
-
- /**
- * Submits a palette to display. Entries marked as “used” in the
- * submitted palette are merged into the existing entries of
- * _sourcePalette.
- */
- void submit(Palette &palette);
-
- public:
- virtual void saveLoadWithSerializer(Common::Serializer &s) override;
-
- bool kernelSetFromResource(GuiResourceId resourceId, bool force) override;
- int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override;
- void set(Palette *newPalette, bool force, bool forceRealMerge = false) override;
- int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable);
-
- void updateForFrame();
- void updateHardware();
- void applyAll();
-
- bool loadClut(uint16 clutId);
- byte matchClutColor(uint16 color);
- void unloadClut();
-
- void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
- void kernelPalVaryMergeTarget(const GuiResourceId paletteId);
- void kernelPalVarySetTarget(const GuiResourceId paletteId);
- void kernelPalVarySetStart(const GuiResourceId paletteId);
- void kernelPalVaryMergeStart(const GuiResourceId paletteId);
- virtual void kernelPalVaryPause(bool pause) override;
-
- void setVary(const Palette *const targetPalette, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
- void setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate);
- int16 getVaryPercent() const;
- void varyOff();
- void mergeTarget(const Palette *const palette);
- void varyPause();
- void varyOn();
- void setVaryTime(const int time);
- void setTarget(const Palette *const palette);
- void setStart(const Palette *const palette);
- void mergeStart(const Palette *const palette);
- private:
- bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const;
- Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const;
- void setVaryTimeInternal(const int16 percent, const int time);
- public:
- void applyVary();
-
- void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay);
- void doCycle(const uint8 fromColor, const int16 speed);
- void cycleOn(const uint8 fromColor);
- void cyclePause(const uint8 fromColor);
- void cycleAllOn();
- void cycleAllPause();
- void cycleOff(const uint8 fromColor);
- void cycleAllOff();
- void applyAllCycles();
- void applyCycles();
-
- void setFade(const uint8 percent, const uint8 fromColor, const uint16 toColor);
- void fadeOff();
- void applyFade();
- };
+class GfxPalette32 : public GfxPalette {
+public:
+ GfxPalette32(ResourceManager *resMan, GfxScreen *screen);
+ ~GfxPalette32();
+
+private:
+ // NOTE: currentPalette in SCI engine is called _sysPalette
+ // here.
+
+ /**
+ * The palette revision version. Increments once per game
+ * loop that changes the source palette. TODO: Possibly
+ * other areas also change _version, double-check once it
+ * is all implemented.
+ */
+ uint32 _version;
+
+ /**
+ * Whether or not the palette manager version was updated
+ * during this loop.
+ */
+ bool _versionUpdated;
+
+ /**
+ * The unmodified source palette loaded by kPalette. Additional
+ * palette entries may be mixed into the source palette by
+ * CelObj objects, which contain their own palettes.
+ */
+ Palette _sourcePalette;
+
+ /**
+ * The palette to be used when the hardware is next updated.
+ * On update, _nextPalette is transferred to _sysPalette.
+ */
+ Palette _nextPalette;
+
+ bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const;
+ Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const;
+
+public:
+ virtual void saveLoadWithSerializer(Common::Serializer &s) override;
+ const Palette *getNextPalette() const;
+
+ bool kernelSetFromResource(GuiResourceId resourceId, bool force) override;
+ int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override;
+ void set(Palette *newPalette, bool force, bool forceRealMerge = false) override;
+ int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable);
+
+ /**
+ * Submits a palette to display. Entries marked as “used” in the
+ * submitted palette are merged into the existing entries of
+ * _sourcePalette.
+ */
+ void submit(Palette &palette);
+
+ bool updateForFrame();
+ void updateFFrame();
+ void updateHardware();
+ void applyAll();
+
+#pragma mark -
+#pragma mark Colour look-up
+private:
+ /**
+ * An optional lookup table used to remap RGB565 colors to a palette
+ * index. Used by Phantasmagoria 2 in 8-bit color environments.
+ */
+ byte *_clutTable;
+
+public:
+ bool loadClut(uint16 clutId);
+ byte matchClutColor(uint16 color);
+ void unloadClut();
+
+#pragma mark -
+#pragma mark Varying
+private:
+ /**
+ * An optional palette used to describe the source colors used
+ * in a palette vary operation. If this palette is not specified,
+ * sourcePalette is used instead.
+ */
+ Palette *_varyStartPalette;
+
+ /**
+ * An optional palette used to describe the target colors used
+ * in a palette vary operation.
+ */
+ Palette *_varyTargetPalette;
+
+ /**
+ * The minimum palette index that has been varied from the
+ * source palette. 0–255
+ */
+ uint8 _varyFromColor;
+
+ /**
+ * The maximum palette index that is has been varied from the
+ * source palette. 0-255
+ */
+ uint8 _varyToColor;
+
+ /**
+ * The tick at the last time the palette vary was updated.
+ */
+ uint32 _varyLastTick;
+
+ /**
+ * The amount of time to elapse, in ticks, between each cycle
+ * of a palette vary animation.
+ */
+ int _varyTime;
+
+ /**
+ * The direction of change: -1, 0, or 1.
+ */
+ int16 _varyDirection;
+
+ /**
+ * The amount, in percent, that the vary color is currently
+ * blended into the source color.
+ */
+ int16 _varyPercent;
+
+ /**
+ * The target amount that a vary color will be blended into
+ * the source color.
+ */
+ int16 _varyTargetPercent;
+
+ /**
+ * The number of time palette varying has been paused.
+ */
+ uint16 _varyNumTimesPaused;
+
+public:
+ void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
+ void kernelPalVaryMergeTarget(const GuiResourceId paletteId);
+ void kernelPalVarySetTarget(const GuiResourceId paletteId);
+ void kernelPalVarySetStart(const GuiResourceId paletteId);
+ void kernelPalVaryMergeStart(const GuiResourceId paletteId);
+ virtual void kernelPalVaryPause(bool pause) override;
+
+ void setVary(const Palette *const targetPalette, const int16 percent, const int time, const int16 fromColor, const int16 toColor);
+ void setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate);
+ int16 getVaryPercent() const;
+ void varyOff();
+ void mergeTarget(const Palette *const palette);
+ void varyPause();
+ void varyOn();
+ void setVaryTime(const int time);
+ void setTarget(const Palette *const palette);
+ void setStart(const Palette *const palette);
+ void mergeStart(const Palette *const palette);
+ void setVaryTimeInternal(const int16 percent, const int time);
+ void applyVary();
+
+#pragma mark -
+#pragma mark Cycling
+private:
+ // SQ6 defines 10 cyclers
+ PalCycler *_cyclers[10];
+
+ /**
+ * The cycle map is used to detect overlapping cyclers.
+ * According to SCI engine code, when two cyclers overlap,
+ * a fatal error has occurred and the engine will display
+ * an error and then exit.
+ */
+ bool _cycleMap[256];
+ inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear);
+ inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear);
+ inline PalCycler *getCycler(uint16 fromColor);
+
+public:
+ void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay);
+ void doCycle(const uint8 fromColor, const int16 speed);
+ void cycleOn(const uint8 fromColor);
+ void cyclePause(const uint8 fromColor);
+ void cycleAllOn();
+ void cycleAllPause();
+ void cycleOff(const uint8 fromColor);
+ void cycleAllOff();
+ void applyAllCycles();
+ void applyCycles();
+
+#pragma mark -
+#pragma mark Fading
+private:
+ /**
+ * The fade table records the expected intensity level of each pixel
+ * in the palette that will be displayed on the next frame.
+ */
+ byte _fadeTable[256];
+
+public:
+ void setFade(const uint8 percent, const uint8 fromColor, const uint16 toColor);
+ void fadeOff();
+ void applyFade();
+};
}
#endif
diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp
index d7ef84dc1e..2eab391afd 100644
--- a/engines/sci/graphics/picture.cpp
+++ b/engines/sci/graphics/picture.cpp
@@ -209,12 +209,12 @@ void GfxPicture::drawSci32Vga(int16 celNo, int16 drawX, int16 drawY, int16 pictu
}
// Header
- // [headerSize:WORD] [celCount:BYTE] [Unknown:BYTE] [Unknown:WORD] [paletteOffset:DWORD] [Unknown:DWORD]
+ // 0[headerSize:WORD] 2[celCount:BYTE] 3[Unknown:BYTE] 4[celHeaderSize:WORD] 6[paletteOffset:DWORD] 10[Unknown:WORD] 12[Unknown:WORD]
// cel-header follow afterwards, each is 42 bytes
// Cel-Header
- // [width:WORD] [height:WORD] [displaceX:WORD] [displaceY:WORD] [clearColor:BYTE] [compressed:BYTE]
+ // 0[width:WORD] 2[height:WORD] 4[displaceX:WORD] 6[displaceY:WORD] 8[clearColor:BYTE] 9[compressed:BYTE]
// offset 10-23 is unknown
- // [rleOffset:DWORD] [literalOffset:DWORD] [Unknown:WORD] [Unknown:WORD] [priority:WORD] [relativeXpos:WORD] [relativeYpos:WORD]
+ // 24[rleOffset:DWORD] 28[literalOffset:DWORD] 32[Unknown:WORD] 34[Unknown:WORD] 36[priority:WORD] 38[relativeXpos:WORD] 40[relativeYpos:WORD]
cel_headerPos += 42 * celNo;
diff --git a/engines/sci/graphics/picture.h b/engines/sci/graphics/picture.h
index 2404f99b41..942fa0f107 100644
--- a/engines/sci/graphics/picture.h
+++ b/engines/sci/graphics/picture.h
@@ -38,6 +38,9 @@ enum {
class GfxPorts;
class GfxScreen;
class GfxPalette;
+class GfxCoordAdjuster;
+class ResourceManager;
+class Resource;
/**
* Picture class, handles loading and displaying of picture resources
diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp
new file mode 100644
index 0000000000..548e7e9d17
--- /dev/null
+++ b/engines/sci/graphics/plane32.cpp
@@ -0,0 +1,841 @@
+/* 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.
+ *
+ */
+
+#include "sci/console.h"
+#include "sci/engine/kernel.h"
+#include "sci/engine/selector.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/lists32.h"
+#include "sci/graphics/plane32.h"
+#include "sci/graphics/screen.h"
+#include "sci/graphics/screen_item32.h"
+
+namespace Sci {
+#pragma mark DrawList
+void DrawList::add(ScreenItem *screenItem, const Common::Rect &rect) {
+ DrawItem *drawItem = new DrawItem;
+ drawItem->screenItem = screenItem;
+ drawItem->rect = rect;
+ DrawListBase::add(drawItem);
+}
+
+#pragma mark -
+#pragma mark Plane
+uint16 Plane::_nextObjectId = 20000;
+
+Plane::Plane(const Common::Rect &gameRect) :
+_gameRect(gameRect),
+_object(make_reg(0, _nextObjectId++)),
+_back(0),
+_pictureId(kPlanePicColored),
+_mirrored(false),
+_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
+_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
+_deleted(0),
+_updated(0),
+_priorityChanged(0),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()) {
+ convertGameRectToPlaneRect();
+ _priority = MAX(10000, g_sci->_gfxFrameout->getPlanes().getTopPlanePriority() + 1);
+ setType();
+ _screenRect = _planeRect;
+}
+
+Plane::Plane(reg_t object) :
+_object(object),
+_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth),
+_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()),
+_deleted(0),
+_updated(0),
+_priorityChanged(false),
+_moved(0) {
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+ _vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX));
+ _vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY));
+
+ _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+ _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+ _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight));
+ _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom));
+ convertGameRectToPlaneRect();
+
+ _back = readSelectorValue(segMan, object, SELECTOR(back));
+ _priority = readSelectorValue(segMan, object, SELECTOR(priority));
+ _pictureId = readSelectorValue(segMan, object, SELECTOR(picture));
+ setType();
+
+ _mirrored = readSelectorValue(segMan, object, SELECTOR(mirrored));
+ _screenRect = _planeRect;
+ changePic();
+}
+
+Plane::Plane(const Plane &other) :
+_object(other._object),
+_priority(other._priority),
+_pictureId(other._pictureId),
+_mirrored(other._mirrored),
+_back(other._back),
+_field_34(other._field_34), _field_38(other._field_38),
+_field_3C(other._field_3C), _field_40(other._field_40),
+_planeRect(other._planeRect),
+_gameRect(other._gameRect),
+_screenRect(other._screenRect),
+_screenItemList(other._screenItemList) {}
+
+void Plane::operator=(const Plane &other) {
+ _gameRect = other._gameRect;
+ _planeRect = other._planeRect;
+ _vanishingPoint = other._vanishingPoint;
+ _pictureId = other._pictureId;
+ _type = other._type;
+ _mirrored = other._mirrored;
+ _priority = other._priority;
+ _back = other._back;
+ _width = other._width;
+ _field_34 = other._field_34;
+ _height = other._height;
+ _screenRect = other._screenRect;
+ _field_3C = other._field_3C;
+ _priorityChanged = other._priorityChanged;
+}
+
+void Plane::init() {
+ _nextObjectId = 20000;
+}
+
+void Plane::convertGameRectToPlaneRect() {
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ const Ratio ratioX = Ratio(screenWidth, scriptWidth);
+ const Ratio ratioY = Ratio(screenHeight, scriptHeight);
+
+ _planeRect = _gameRect;
+ mulru(_planeRect, ratioX, ratioY, 1);
+}
+
+void Plane::printDebugInfo(Console *con) const {
+ Common::String name;
+
+ if (_object.isNumber()) {
+ name = "-scummvm-";
+ } else {
+ name = g_sci->getEngineState()->_segMan->getObjectName(_object);
+ }
+
+ con->debugPrintf("%04x:%04x (%s): type %d, prio %d, pic %d, mirror %d, back %d\n",
+ PRINT_REG(_object),
+ name.c_str(),
+ _type,
+ _priority,
+ _pictureId,
+ _mirrored,
+ _back
+ );
+ con->debugPrintf(" game rect: (%d, %d, %d, %d), plane rect: (%d, %d, %d, %d)\n screen rect: (%d, %d, %d, %d)\n",
+ PRINT_RECT(_gameRect),
+ PRINT_RECT(_planeRect),
+ PRINT_RECT(_screenRect)
+ );
+ con->debugPrintf(" # screen items: %d\n", _screenItemList.size());
+}
+
+#pragma mark -
+#pragma mark Plane - Pic
+
+void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX) {
+
+ uint16 celCount = 1000;
+ for (uint16 celNo = 0; celNo < celCount; ++celNo) {
+ CelObjPic *celObj = new CelObjPic(pictureId, celNo);
+ if (celCount == 1000) {
+ celCount = celObj->_celCount;
+ }
+
+ ScreenItem *screenItem = new ScreenItem(_object, celObj->_info);
+ screenItem->_pictureId = pictureId;
+ screenItem->_mirrorX = mirrorX;
+ screenItem->_priority = celObj->_priority;
+ screenItem->_fixPriority = true;
+ if (position != nullptr) {
+ screenItem->_position = *position;
+ } else {
+ screenItem->_position = celObj->_relativePosition;
+ }
+ _screenItemList.add(screenItem);
+
+ delete screenItem->_celObj;
+ screenItem->_celObj = celObj;
+ }
+}
+
+void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX) {
+ deletePic(pictureId);
+ addPicInternal(pictureId, &position, mirrorX);
+ // NOTE: In SCI engine this method returned the pictureId of the
+ // plane, but this return value was never used
+}
+
+void Plane::changePic() {
+ _pictureChanged = false;
+
+ if (_type != kPlaneTypePicture) {
+ return;
+ }
+
+ addPicInternal(_pictureId, nullptr, _mirrored);
+}
+
+void Plane::deletePic(const GuiResourceId pictureId) {
+ for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) {
+ ScreenItem *screenItem = *it;
+ if (screenItem->_pictureId == pictureId) {
+ screenItem->_created = 0;
+ screenItem->_updated = 0;
+ screenItem->_deleted = g_sci->_gfxFrameout->getScreenCount();
+ }
+ }
+}
+
+void Plane::deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId) {
+ deletePic(oldPictureId);
+ _pictureId = newPictureId;
+}
+
+void Plane::deleteAllPics() {
+ for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) {
+ ScreenItem *screenItem = *it;
+ if (screenItem != nullptr && screenItem->_celInfo.type == kCelTypePic) {
+ if (screenItem->_created == 0) {
+ screenItem->_created = 0;
+ screenItem->_updated = 0;
+ screenItem->_deleted = g_sci->_gfxFrameout->getScreenCount();
+ } else {
+ _screenItemList.erase(it);
+ }
+ }
+ }
+
+ _screenItemList.pack();
+}
+
+#pragma mark -
+#pragma mark Plane - Rendering
+
+void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const {
+ int index = planeList.findIndexByObject(_object);
+
+ for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
+ for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) {
+ if (planeList[j]->_type != kPlaneTypeTransparent) {
+ Common::Rect ptr[4];
+ int count = splitRects(drawList[i]->rect, planeList[j]->_screenRect, ptr);
+ if (count != -1) {
+ for (int k = count - 1; k >= 0; --k) {
+ drawList.add(drawList[i]->screenItem, ptr[k]);
+ }
+
+ drawList.erase_at(i);
+ break;
+ }
+ }
+ }
+ }
+ drawList.pack();
+}
+
+void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const {
+ int index = planeList.findIndexByObject(_object);
+
+ for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+ for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) {
+ if (planeList[j]->_type != kPlaneTypeTransparent) {
+ Common::Rect ptr[4];
+
+ int count = splitRects(*eraseList[i], planeList[j]->_screenRect, ptr);
+ if (count != -1) {
+ for (int k = count - 1; k >= 0; --k) {
+ eraseList.add(ptr[k]);
+ }
+
+ eraseList.erase_at(i);
+ break;
+ }
+ }
+ }
+ }
+ eraseList.pack();
+}
+
+void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
+ ScreenItemList::size_type planeItemCount = _screenItemList.size();
+ ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size();
+
+ for (PlaneList::size_type i = 0; i < planeItemCount; ++i) {
+ ScreenItem *vitem = nullptr;
+ // NOTE: The original engine used an array without bounds checking
+ // so could just get the visible screen item directly; we need to
+ // verify that the index is actually within the valid range for
+ // the visible plane before accessing the item to avoid a range
+ // error.
+ if (i < visiblePlaneItemCount) {
+ vitem = visiblePlane._screenItemList[i];
+ }
+ ScreenItem *item = _screenItemList[i];
+
+ if (i < _screenItemList.size() && item != nullptr) {
+ if (item->_deleted) {
+ // add item's rect to erase list
+ if (i < visiblePlane._screenItemList.size() && vitem != nullptr) {
+ if (!vitem->_screenRect.isEmpty()) {
+ if (/* TODO: g_Remap_numActiveRemaps */ false) { // active remaps?
+ mergeToRectList(vitem->_screenRect, eraseList);
+ } else {
+ eraseList.add(vitem->_screenRect);
+ }
+ }
+ }
+ } else if (item->_created) {
+ // add item to draw list
+ item->getCelObj();
+ item->calcRects(*this);
+
+ if(!item->_screenRect.isEmpty()) {
+ if (/* TODO: g_Remap_numActiveRemaps */ false) { // active remaps?
+ drawList.add(item, item->_screenRect);
+ mergeToRectList(item->_screenRect, eraseList);
+ } else {
+ drawList.add(item, item->_screenRect);
+ }
+ }
+ } else if (item->_updated) {
+ // add old rect to erase list, new item to draw list
+ item->getCelObj();
+ item->calcRects(*this);
+ if (/* TODO: g_Remap_numActiveRemaps */ false) { // active remaps
+ // if item and vitem don't overlap, ...
+ if (item->_screenRect.isEmpty() ||
+ i >= visiblePlaneItemCount ||
+ vitem == nullptr ||
+ vitem->_screenRect.isEmpty() ||
+ !vitem->_screenRect.intersects(item->_screenRect)
+ ) {
+ // add item to draw list, and old rect to erase list
+ if (!item->_screenRect.isEmpty()) {
+ drawList.add(item, item->_screenRect);
+ mergeToRectList(item->_screenRect, eraseList);
+ }
+ if (i < visiblePlaneItemCount && vitem != nullptr && !vitem->_screenRect.isEmpty()) {
+ mergeToRectList(vitem->_screenRect, eraseList);
+ }
+ } else {
+ // otherwise, add bounding box of old+new to erase list,
+ // and item to draw list
+
+ // TODO: This was changed from disasm, verify please!
+ Common::Rect extendedScreenItem = vitem->_screenRect;
+ extendedScreenItem.extend(item->_screenRect);
+ drawList.add(item, item->_screenRect);
+ mergeToRectList(extendedScreenItem, eraseList);
+ }
+ } else {
+ // if no active remaps, just add item to draw list and old rect
+ // to erase list
+ if (!item->_screenRect.isEmpty()) {
+ drawList.add(item, item->_screenRect);
+ }
+ if (i < visiblePlaneItemCount && vitem != nullptr && !vitem->_screenRect.isEmpty()) {
+ eraseList.add(vitem->_screenRect);
+ }
+ }
+ }
+ }
+ }
+
+ breakEraseListByPlanes(eraseList, planeList);
+ breakDrawListByPlanes(drawList, planeList);
+
+ if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"????
+ _screenItemList.sort();
+ bool encounteredPic = false;
+ bool v81 = false;
+
+ for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+ Common::Rect *rect = eraseList[i];
+
+ for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ ScreenItem *item = _screenItemList[j];
+
+ if (j < _screenItemList.size() && item != nullptr) {
+ if (rect->intersects(item->_screenRect)) {
+ Common::Rect intersection = rect->findIntersectingRect(item->_screenRect);
+ if (!item->_deleted) {
+ if (encounteredPic) {
+ if (item->_celInfo.type == kCelTypePic) {
+ if (v81 || item->_celInfo.celNo == 0) {
+ drawList.add(item, intersection);
+ }
+ } else {
+ if (!item->_updated && !item->_created) {
+ drawList.add(item, intersection);
+ }
+ v81 = true;
+ }
+ } else {
+ if (!item->_updated && !item->_created) {
+ drawList.add(item, intersection);
+ }
+ if (item->_celInfo.type == kCelTypePic) {
+ encounteredPic = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ _screenItemList.unsort();
+ } else {
+ // add all items overlapping the erase list to the draw list
+ for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+ for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ ScreenItem *item = _screenItemList[j];
+ if (j < _screenItemList.size() && item != nullptr && !item->_updated && !item->_deleted && !item->_created && eraseList[i]->intersects(item->_screenRect)) {
+ drawList.add(item, eraseList[i]->findIntersectingRect(item->_screenRect));
+ }
+ }
+ }
+ }
+ if (/* TODO: g_Remap_numActiveRemaps */ false) { // no remaps active?
+ // Add all items that overlap with items in the drawlist and have higher
+ // priority
+ for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
+ DrawItem *dli = drawList[i];
+
+ for (PlaneList::size_type j = 0; j < planeItemCount; ++j) {
+ ScreenItem *sli = _screenItemList[j];
+
+ if (i < drawList.size() && dli) {
+ if (j < _screenItemList.size() && sli) {
+ if (!sli->_updated && !sli->_deleted && !sli->_created) {
+ ScreenItem *item = dli->screenItem;
+ if (sli->_priority > item->_priority || (sli->_priority == item->_priority && sli->_object > item->_object)) {
+ if (dli->rect.intersects(sli->_screenRect)) {
+ drawList.add(sli, dli->rect.findIntersectingRect(sli->_screenRect));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ decrementScreenItemArrayCounts(&visiblePlane, false);
+ _screenItemList.pack();
+ visiblePlane._screenItemList.pack();
+}
+
+void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate) {
+ // The size of the screenItemList may change, so it is
+ // critical to re-check the size on each iteration
+ for (ScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) {
+ ScreenItem *item = _screenItemList[i];
+
+ if (item != nullptr) {
+ // update item in visiblePlane if item is updated
+ if (
+ item->_updated ||
+ (
+ forceUpdate &&
+ visiblePlane != nullptr &&
+ Common::find(visiblePlane->_screenItemList.begin(), visiblePlane->_screenItemList.end(), item) != visiblePlane->_screenItemList.end()
+ )
+ ) {
+ *visiblePlane->_screenItemList[i] = *_screenItemList[i];
+ }
+
+ if (item->_updated) {
+ item->_updated--;
+ }
+
+ // create new item in visiblePlane if item was added
+ if (item->_created) {
+ item->_created--;
+ if (visiblePlane != nullptr) {
+ ScreenItem *n = new ScreenItem(*item);
+ visiblePlane->_screenItemList.add(n);
+ }
+ }
+
+ // delete item from both planes if it was deleted
+ if (item->_deleted) {
+ item->_deleted--;
+ if (!item->_deleted) {
+ visiblePlane->_screenItemList.erase_at(i);
+ _screenItemList.erase_at(i);
+ }
+ }
+ }
+ }
+}
+
+void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const {
+ if (_type == kPlaneTypeTransparent) {
+ for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) {
+ Common::Rect *r = transparentEraseList[i];
+ for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ ScreenItem *item = _screenItemList[j];
+ if (item != nullptr) {
+ if (r->intersects(item->_screenRect)) {
+ mergeToDrawList(j, *r, drawList);
+ }
+ }
+ }
+ }
+ } else {
+ for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) {
+ Common::Rect *r = transparentEraseList[i];
+ if (r->intersects(_screenRect)) {
+ r->clip(_screenRect);
+ mergeToRectList(*r, eraseList);
+
+ for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ ScreenItem *item = _screenItemList[j];
+
+ if (item != nullptr) {
+ if (r->intersects(item->_screenRect)) {
+ mergeToDrawList(j, *r, drawList);
+ }
+ }
+ }
+
+ Common::Rect ptr[4];
+ Common::Rect *r2 = transparentEraseList[i];
+ int count = splitRects(*r2, *r, ptr);
+ for (int k = count - 1; k >= 0; --k) {
+ transparentEraseList.add(ptr[k]);
+ }
+ transparentEraseList.erase_at(i);
+ }
+ }
+
+ transparentEraseList.pack();
+ }
+}
+
+void Plane::filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const {
+ for (DrawList::size_type i = 0; i < drawList.size(); ++i) {
+ Common::Rect &r = drawList[i]->rect;
+
+ for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ ScreenItem *item = _screenItemList[j];
+ if (item != nullptr) {
+ if (r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, transparentDrawList);
+ }
+ }
+ }
+ }
+}
+
+void Plane::filterUpEraseRects(DrawList &drawList, RectList &eraseList) const {
+ for (RectList::size_type i = 0; i < eraseList.size(); ++i) {
+ Common::Rect &r = *eraseList[i];
+ for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) {
+ ScreenItem *item = _screenItemList[j];
+
+ if (item != nullptr) {
+ if (r.intersects(item->_screenRect)) {
+ mergeToDrawList(j, r, drawList);
+ }
+ }
+ }
+ }
+}
+
+void Plane::mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const {
+ RectList rects;
+
+ Common::Rect r = _screenItemList[index]->_screenRect;
+ r.clip(rect);
+
+ rects.add(r);
+ ScreenItem *item = _screenItemList[index];
+
+ for (RectList::size_type i = 0; i < rects.size(); ++i) {
+ r = *rects[i];
+
+ for (DrawList::size_type j = 0; j < drawList.size(); ++j) {
+ DrawItem *drawitem = drawList[j];
+ if (item->_object == drawitem->screenItem->_object) {
+ if (drawitem->rect.contains(r)) {
+ rects.erase_at(i);
+ break;
+ }
+
+ Common::Rect outRects[4];
+ int count = splitRects(r, drawitem->rect, outRects);
+ if (count != -1) {
+ for (int k = count - 1; k >= 0; --k) {
+ rects.add(outRects[k]);
+ }
+
+ rects.erase_at(i);
+
+ // proceed to the next rect
+ r = *rects[++i];
+ }
+ }
+ }
+ }
+
+ rects.pack();
+
+ for (RectList::size_type i = 0; i < rects.size(); ++i) {
+ drawList.add(item, *rects[i]);
+ }
+}
+
+void Plane::mergeToRectList(const Common::Rect &rect, RectList &rectList) const {
+ RectList temp;
+ temp.add(rect);
+
+ for (RectList::size_type i = 0; i < temp.size(); ++i) {
+ Common::Rect *outerRect = temp[i];
+ for (RectList::size_type j = 0; j < rectList.size(); ++j) {
+ Common::Rect *innerRect = rectList[i];
+ if (innerRect->intersects(*outerRect)) {
+ Common::Rect out[4];
+ int count = splitRects(*outerRect, *innerRect, out);
+ for (int k = count - 1; k >= 0; --k) {
+ temp.add(out[k]);
+ }
+ temp.erase_at(i);
+ } else {
+ temp.erase_at(i);
+ }
+ }
+ }
+
+ temp.pack();
+
+ for (RectList::size_type i = 0; i < temp.size(); ++i) {
+ rectList.add(*temp[i]);
+ }
+}
+
+void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) {
+ for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) {
+ if (*screenItemPtr != nullptr) {
+ ScreenItem &screenItem = **screenItemPtr;
+ if (!screenItem._deleted) {
+ screenItem.getCelObj();
+ screenItem.calcRects(*this);
+ if (!screenItem._screenRect.isEmpty()) {
+ drawList.add(&screenItem, screenItem._screenRect);
+ }
+ }
+ }
+ }
+
+ eraseList.clear();
+
+ if (!_screenRect.isEmpty() && _type != kPlaneTypePicture && _type != kPlaneTypeOpaque) {
+ eraseList.add(_screenRect);
+ }
+ breakEraseListByPlanes(eraseList, planeList);
+ breakDrawListByPlanes(drawList, planeList);
+ --_redrawAllCount;
+ decrementScreenItemArrayCounts(visiblePlane, true);
+ _screenItemList.pack();
+ if (visiblePlane != nullptr) {
+ visiblePlane->_screenItemList.pack();
+ }
+}
+
+void Plane::setType() {
+ if (_pictureId == kPlanePicOpaque) {
+ _type = kPlaneTypeOpaque;
+ } else if (_pictureId == kPlanePicTransparent) {
+ _type = kPlaneTypeTransparent;
+ } else if (_pictureId == kPlanePicColored) {
+ _type = kPlaneTypeColored;
+ } else {
+ _type = kPlaneTypePicture;
+ }
+}
+
+void Plane::sync(const Plane *other, const Common::Rect &screenRect) {
+ if (other == nullptr) {
+ if (_pictureChanged) {
+ deleteAllPics();
+ setType();
+ changePic();
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+ } else {
+ setType();
+ }
+ } else {
+ if (
+ _planeRect.top != other->_planeRect.top ||
+ _planeRect.left != other->_planeRect.left ||
+ _planeRect.right > other->_planeRect.right ||
+ _planeRect.bottom > other->_planeRect.bottom
+ ) {
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+ _updated = g_sci->_gfxFrameout->getScreenCount();
+ } else if (_planeRect != other->_planeRect) {
+ _updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ if (_priority != other->_priority) {
+ _priorityChanged = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ if (_pictureId != other->_pictureId || _mirrored != other->_mirrored || _pictureChanged) {
+ deleteAllPics();
+ setType();
+ changePic();
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ if (_back != other->_back) {
+ _redrawAllCount = g_sci->_gfxFrameout->getScreenCount();
+ }
+ }
+
+ _deleted = 0;
+ if (_created == 0) {
+ _moved = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ convertGameRectToPlaneRect();
+ _width = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ _height = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ _screenRect = _planeRect;
+ // NOTE: screenRect originally was retrieved through globals
+ // instead of being passed into the function
+ clipScreenRect(screenRect);
+}
+
+void Plane::update(const reg_t object) {
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+ _vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX));
+ _vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY));
+ _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+ _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+ _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight));
+ _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom));
+ convertGameRectToPlaneRect();
+
+ _priority = readSelectorValue(segMan, object, SELECTOR(priority));
+ GuiResourceId pictureId = readSelectorValue(segMan, object, SELECTOR(picture));
+ if (_pictureId != pictureId) {
+ _pictureId = pictureId;
+ _pictureChanged = true;
+ }
+
+ _mirrored = readSelectorValue(segMan, object, SELECTOR(mirrored));
+ _back = readSelectorValue(segMan, object, SELECTOR(back));
+}
+
+#pragma mark -
+#pragma mark PlaneList
+void PlaneList::clear() {
+ for (iterator it = begin(); it != end(); ++it) {
+ delete *it;
+ }
+
+ PlaneListBase::clear();
+}
+
+void PlaneList::erase(Plane *plane) {
+ for (iterator it = begin(); it != end(); ++it) {
+ if (*it == plane) {
+ erase(it);
+ break;
+ }
+ }
+}
+
+int PlaneList::findIndexByObject(const reg_t object) const {
+ for (size_type i = 0; i < size(); ++i) {
+ if ((*this)[i] != nullptr && (*this)[i]->_object == object) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+Plane *PlaneList::findByObject(const reg_t object) const {
+ const_iterator planeIt = Common::find_if(begin(), end(), FindByObject<Plane *>(object));
+
+ if (planeIt == end()) {
+ return nullptr;
+ }
+
+ return *planeIt;
+}
+
+int16 PlaneList::getTopPlanePriority() const {
+ if (size() > 0) {
+ return (*this)[size() - 1]->_priority;
+ }
+
+ return 0;
+}
+
+int16 PlaneList::getTopSciPlanePriority() const {
+ int16 priority = 0;
+
+ for (const_iterator it = begin(); it != end(); ++it) {
+ if ((*it)->_priority >= 10000) {
+ break;
+ }
+
+ priority = (*it)->_priority;
+ }
+
+ return priority;
+}
+
+void PlaneList::add(Plane *plane) {
+ for (iterator it = begin(); it != end(); ++it) {
+ if ((*it)->_priority < plane->_priority) {
+ insert(it, plane);
+ return;
+ }
+ }
+
+ push_back(plane);
+}
+
+}
diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h
new file mode 100644
index 0000000000..a68700a031
--- /dev/null
+++ b/engines/sci/graphics/plane32.h
@@ -0,0 +1,465 @@
+/* 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.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_PLANE32_H
+#define SCI_GRAPHICS_PLANE32_H
+
+#include "common/array.h"
+#include "common/rect.h"
+#include "sci/engine/vm_types.h"
+#include "sci/graphics/helpers.h"
+#include "sci/graphics/lists32.h"
+#include "sci/graphics/screen_item32.h"
+
+namespace Sci {
+enum PlaneType {
+ kPlaneTypeColored = 0,
+ kPlaneTypePicture = 1,
+ kPlaneTypeTransparent = 2,
+ kPlaneTypeOpaque = 3
+};
+
+enum PlanePictureCodes {
+ // NOTE: Any value at or below 65532 means the plane
+ // is a kPlaneTypePicture.
+ kPlanePic = 65532,
+ kPlanePicOpaque = 65533,
+ kPlanePicTransparent = 65534,
+ kPlanePicColored = 65535
+};
+
+#pragma mark -
+#pragma mark RectList
+
+typedef StablePointerArray<Common::Rect, 200> RectListBase;
+class RectList : public RectListBase {
+public:
+ void add(const Common::Rect &rect) {
+ RectListBase::add(new Common::Rect(rect));
+ }
+};
+
+#pragma mark -
+#pragma mark DrawList
+
+struct DrawItem {
+ ScreenItem *screenItem;
+ Common::Rect rect;
+
+ inline bool operator<(const DrawItem &other) const {
+ return *screenItem < *other.screenItem;
+ }
+};
+
+typedef StablePointerArray<DrawItem, 250> DrawListBase;
+class DrawList : public DrawListBase {
+private:
+ inline static bool sortHelper(const DrawItem *a, const DrawItem *b) {
+ return *a < *b;
+ }
+public:
+ void add(ScreenItem *screenItem, const Common::Rect &rect);
+ inline void sort() {
+ pack();
+ Common::sort(begin(), end(), sortHelper);
+ }
+};
+
+class PlaneList;
+
+#pragma mark -
+#pragma mark Plane
+
+/**
+ * A plane is a grouped layer of screen items.
+ */
+class Plane {
+private:
+ /**
+ * A serial used for planes that are generated inside
+ * the graphics engine, rather than the interpreter.
+ */
+ static uint16 _nextObjectId;
+
+ /**
+ * The dimensions of the plane, in game script
+ * coordinates.
+ * TODO: These are never used and are always
+ * scriptWidth x scriptHeight in SCI engine? The actual
+ * dimensions of the plane are always in
+ * gameRect/planeRect.
+ */
+ int16 _width, _height;
+
+ /**
+ * For planes that are used to render picture data, the
+ * resource ID of the picture to be displayed. This
+ * value may also be one of the special
+ * PlanePictureCodes, in which case the plane becomes a
+ * non-picture plane.
+ */
+ GuiResourceId _pictureId;
+
+ /**
+ * Whether or not the contents of picture planes should
+ * be drawn horizontally mirrored. Only applies to
+ * planes of type kPlaneTypePicture.
+ */
+ bool _mirrored;
+
+ /**
+ * Whether the picture ID for this plane has changed.
+ * This flag is set when the plane is created or updated
+ * from a VM object, and is cleared when the plane is
+ * synchronised to another plane (which calls
+ * changePic).
+ */
+ bool _pictureChanged; // ?
+
+ // TODO: Are these ever actually used?
+ int _field_34, _field_38; // probably a point or ratio
+ int _field_3C, _field_40; // probably a point or ratio
+
+ /**
+ * Converts the dimensions of the game rect used by
+ * scripts to the dimensions of the plane rect used to
+ * render content to the screen. Coordinates with
+ * remainders are rounded up to the next whole pixel.
+ */
+ void convertGameRectToPlaneRect();
+
+ /**
+ * Sets the type of the plane according to its assigned
+ * picture resource ID.
+ */
+ void setType();
+
+public:
+ /**
+ * The colour to use when erasing the plane. Only
+ * applies to planes of type kPlaneTypeColored.
+ */
+ byte _back;
+
+ /**
+ * Whether the priority of this plane has changed.
+ * This flag is set when the plane is updated from
+ * another plane and cleared when draw list calculation
+ * occurs.
+ */
+ int _priorityChanged; // ?
+
+ /**
+ * A handle to the VM object corresponding to this
+ * plane. Some planes are generated purely within the
+ * graphics engine and have a numeric object value.
+ */
+ reg_t _object;
+
+ /**
+ * The rendering priority of the plane. Higher
+ * priorities are drawn above lower priorities.
+ */
+ int16 _priority;
+
+ /**
+ * TODO: Document
+ */
+ int _redrawAllCount;
+
+ PlaneType _type;
+
+ /**
+ * Flags indicating the state of the plane.
+ * - `created` is set when the plane is first created,
+ * either from a VM object or from within the engine
+ * itself
+ * - `updated` is set when the plane is updated from
+ * another plane and the two planes' `planeRect`s do
+ * not match
+ * - `deleted` is set when the plane is deleted by a
+ * kernel call
+ * - `moved` is set when the plane is synchronised from
+ * another plane and is not already in the "created"
+ * state
+ */
+ int _created, _updated, _deleted, _moved;
+
+ /**
+ * The vanishing point for the plane. Used when
+ * calculating the correct scaling of the plane's screen
+ * items according to their position.
+ */
+ Common::Point _vanishingPoint;
+
+ /**
+ * The position & dimensions of the plane in screen
+ * coordinates. This rect is not clipped to the screen,
+ * so may include coordinates that are offscreen.
+ */
+ Common::Rect _planeRect;
+
+ /**
+ * The position & dimensions of the plane in game script
+ * coordinates.
+ */
+ Common::Rect _gameRect;
+
+ /**
+ * The position & dimensions of the plane in screen
+ * coordinates. This rect is clipped to the screen.
+ */
+ Common::Rect _screenRect;
+
+ /**
+ * The list of screen items grouped within this plane.
+ */
+ ScreenItemList _screenItemList;
+
+public:
+ /**
+ * Initialises static Plane members.
+ */
+ static void init();
+
+ Plane(const Common::Rect &gameRect);
+ Plane(const reg_t object);
+ Plane(const Plane &other);
+ void operator=(const Plane &other);
+ inline bool operator<(const Plane &other) const {
+ // TODO: In SCI engine, _object is actually a uint16 and can either
+ // contain a MemID (a handle to MemoryMgr, similar to reg_t) or
+ // a serial (Plane::_nextObjectId). These numbers can be compared
+ // directly in the real engine and the lowest MemID wins, but in
+ // ScummVM reg_t pointers are not comparable so we have to use a
+ // different strategy when two planes generated by scripts conflict.
+ // For now we just don't check if the priority is below 0, since
+ // that priority is used to represent hidden planes and is guaranteed
+ // to generate conflicts with script-generated planes. If there are
+ // other future conflicts with script-generated planes then we need
+ // to come up with a solution that works, similar to
+ // reg_t::pointerComparisonWithInteger used by SCI16.
+ return _priority < other._priority || (_priority == other._priority && _priority > -1 && _object < other._object);
+ }
+
+ /**
+ * Clips the screen rect of this plane to fit within the
+ * given screen rect.
+ */
+ inline void clipScreenRect(const Common::Rect &screenRect) {
+ if (_screenRect.intersects(screenRect)) {
+ _screenRect.clip(screenRect);
+ } else {
+ _screenRect.left = 0;
+ _screenRect.top = 0;
+ _screenRect.right = 0;
+ _screenRect.bottom = 0;
+ }
+ }
+
+ void printDebugInfo(Console *con) const;
+
+ /**
+ * Compares the properties of the current plane against
+ * the properties of the `other` plane (which is the
+ * corresponding plane from the visible plane list) to
+ * discover which properties have been changed on this
+ * plane by a call to `update(reg_t)`.
+ *
+ * @note This method was originally called UpdatePlane
+ * in SCI engine.
+ */
+ void sync(const Plane *other, const Common::Rect &screenRect);
+
+ /**
+ * Updates the plane to match the state of the plane
+ * object from the virtual machine.
+ *
+ * @note This method was originally called UpdatePlane
+ * in SCI engine.
+ */
+ void update(const reg_t object);
+
+#pragma mark -
+#pragma mark Plane - Pic
+private:
+ /**
+ * Adds all cels from the specified picture resource to
+ * the plane as screen items. If a position is provided,
+ * the screen items will be given that position;
+ * otherwise, the default relative positions for each
+ * cel will be taken from the picture resource data.
+ */
+ inline void addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX);
+
+ /**
+ * If the plane is a picture plane, re-adds all cels
+ * from its picture resource to the plane.
+ */
+ void changePic();
+
+ /**
+ * Marks all screen items to be deleted that are within
+ * this plane and match the given picture ID.
+ */
+ void deletePic(const GuiResourceId pictureId);
+
+ /**
+ * Marks all screen items to be deleted that are within
+ * this plane and match the given picture ID, then sets
+ * the picture ID of the plane to the new picture ID
+ * without adding any screen items.
+ */
+ void deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId);
+
+ /**
+ * Marks all screen items to be deleted that are within
+ * this plane and are picture cels.
+ */
+ void deleteAllPics();
+
+public:
+ /**
+ * Marks all existing screen items matching the current
+ * picture to be deleted, then adds all cels from the
+ * new picture resource to the plane at the given
+ * position.
+ */
+ void addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX);
+
+#pragma mark -
+#pragma mark Plane - Rendering
+private:
+ /**
+ * Splits all rects in the given draw list at the edges
+ * of all non-transparent planes above the current
+ * plane.
+ */
+ void breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const;
+
+ /**
+ * Splits all rects in the given erase list rects at the
+ * edges of all non-transparent planes above the current
+ * plane.
+ */
+ void breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const;
+
+ /**
+ * Synchronises changes to screen items from the current
+ * plane to the visible plane and deletes screen items
+ * from the current plane that have been marked as
+ * deleted. If `forceUpdate` is true, all screen items
+ * on the visible plane will be updated, even if they
+ * are not marked as having changed.
+ */
+ void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate);
+
+ /**
+ * Merges the screen item from this plane at the given
+ * index into the given draw list, clipped to the given
+ * rect. TODO: Finish documenting
+ */
+ void mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const;
+
+ /**
+ * Adds the given rect into the given rect list,
+ * merging it with other rects already inside the list,
+ * if possible, to avoid overdraw. TODO: Finish
+ * documenting
+ */
+ void mergeToRectList(const Common::Rect &rect, RectList &rectList) const;
+
+public:
+ /**
+ * Calculates the location and dimensions of dirty rects
+ * of the screen items in this plane and adds them to
+ * the given draw and erase lists, and synchronises this
+ * plane's list of screen items to the given visible
+ * plane.
+ */
+ void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);
+
+ /**
+ * TODO: Documentation
+ */
+ void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const;
+
+ /**
+ * TODO: Documentation
+ */
+ void filterUpEraseRects(DrawList &drawList, RectList &eraseList) const;
+
+ /**
+ * TODO: Documentation
+ */
+ void filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const;
+
+ /**
+ * Updates all of the plane's non-deleted screen items
+ * and adds them to the given draw and erase lists.
+ */
+ void redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList);
+};
+
+#pragma mark -
+#pragma mark PlaneList
+
+typedef Common::Array<Plane *> PlaneListBase;
+class PlaneList : public PlaneListBase {
+private:
+ inline static bool sortHelper(const Plane *a, const Plane *b) {
+ return *a < *b;
+ }
+
+ using PlaneListBase::push_back;
+
+public:
+ // A method for finding the index of a plane inside a
+ // PlaneList is used because entries in the main plane
+ // list and visible plane list of GfxFrameout are
+ // synchronised by index
+ int findIndexByObject(const reg_t object) const;
+ Plane *findByObject(const reg_t object) const;
+
+ /**
+ * Gets the priority of the top plane in the plane list.
+ */
+ int16 getTopPlanePriority() const;
+
+ /**
+ * Gets the priority of the top plane in the plane list
+ * created by a game script.
+ */
+ int16 getTopSciPlanePriority() const;
+
+ void add(Plane *plane);
+ void clear();
+ using PlaneListBase::erase;
+ void erase(Plane *plane);
+ inline void sort() {
+ Common::sort(begin(), end(), sortHelper);
+ }
+};
+
+}
+
+#endif
diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h
index 49c63d4681..65416252f6 100644
--- a/engines/sci/graphics/screen.h
+++ b/engines/sci/graphics/screen.h
@@ -76,6 +76,11 @@ public:
byte getColorWhite() { return _colorWhite; }
byte getColorDefaultVectorData() { return _colorDefaultVectorData; }
+#ifdef ENABLE_SCI32
+ byte *getDisplayScreen() { return _displayScreen; }
+ byte *getPriorityScreen() { return _priorityScreen; }
+#endif
+
void clearForRestoreGame();
void copyToScreen();
void copyFromScreen(byte *buffer);
diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp
new file mode 100644
index 0000000000..300912f110
--- /dev/null
+++ b/engines/sci/graphics/screen_item32.cpp
@@ -0,0 +1,534 @@
+/* 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.
+ *
+ */
+
+#include "sci/console.h"
+#include "sci/resource.h"
+#include "sci/engine/kernel.h"
+#include "sci/engine/selector.h"
+#include "sci/engine/state.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/screen_item32.h"
+#include "sci/graphics/view.h"
+
+namespace Sci {
+#pragma mark ScreenItem
+
+uint16 ScreenItem::_nextObjectId = 20000;
+
+ScreenItem::ScreenItem(const reg_t object) :
+_mirrorX(false),
+_pictureId(-1),
+_celObj(nullptr),
+_object(object),
+_deleted(0),
+_updated(0),
+_created(g_sci->_gfxFrameout->getScreenCount()) {
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+
+ setFromObject(segMan, object, true, true);
+ _plane = readSelector(segMan, object, SELECTOR(plane));
+}
+
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo) :
+_position(0, 0),
+_z(0),
+_object(make_reg(0, _nextObjectId++)),
+_celInfo(celInfo),
+_plane(plane),
+_celObj(nullptr),
+_useInsetRect(false),
+_fixPriority(false),
+_mirrorX(false),
+_pictureId(-1),
+_updated(0),
+_deleted(0),
+_created(g_sci->_gfxFrameout->getScreenCount()) {}
+
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect) :
+_position(rect.left, rect.top),
+_z(0),
+_object(make_reg(0, _nextObjectId++)),
+_celInfo(celInfo),
+_plane(plane),
+_celObj(nullptr),
+_useInsetRect(false),
+_fixPriority(false),
+_mirrorX(false),
+_pictureId(-1),
+_updated(0),
+_deleted(0),
+_created(g_sci->_gfxFrameout->getScreenCount()) {
+ if (celInfo.type == kCelTypeColor) {
+ _insetRect = rect;
+ }
+}
+
+ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect, const ScaleInfo &scaleInfo) :
+_position(rect.left, rect.top),
+_z(0),
+_object(make_reg(0, _nextObjectId++)),
+_celInfo(celInfo),
+_plane(plane),
+_celObj(nullptr),
+_useInsetRect(false),
+_fixPriority(false),
+_mirrorX(false),
+_pictureId(-1),
+_updated(0),
+_deleted(0),
+_created(g_sci->_gfxFrameout->getScreenCount()),
+_scale(scaleInfo) {}
+
+ScreenItem::ScreenItem(const ScreenItem &other) :
+_object(other._object),
+_plane(other._plane),
+_celInfo(other._celInfo),
+_celObj(nullptr),
+_screenRect(other._screenRect),
+_mirrorX(other._mirrorX),
+_useInsetRect(other._useInsetRect),
+_scale(other._scale),
+_scaledPosition(other._scaledPosition) {
+ if (other._useInsetRect) {
+ _insetRect = other._insetRect;
+ }
+}
+
+void ScreenItem::operator=(const ScreenItem &other) {
+ _celInfo = other._celInfo;
+ _screenRect = other._screenRect;
+ _mirrorX = other._mirrorX;
+ _useInsetRect = other._useInsetRect;
+ if (other._useInsetRect) {
+ _insetRect = other._insetRect;
+ }
+ _scale = other._scale;
+ _scaledPosition = other._scaledPosition;
+}
+
+void ScreenItem::init() {
+ _nextObjectId = 20000;
+}
+
+void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap) {
+ _position.x = readSelectorValue(segMan, object, SELECTOR(x));
+ _position.y = readSelectorValue(segMan, object, SELECTOR(y));
+ _scale.x = readSelectorValue(segMan, object, SELECTOR(scaleX));
+ _scale.y = readSelectorValue(segMan, object, SELECTOR(scaleY));
+ _scale.max = readSelectorValue(segMan, object, SELECTOR(maxScale));
+ _scale.signal = (ScaleSignals32)(readSelectorValue(segMan, object, SELECTOR(scaleSignal)) & 3);
+
+ if (updateCel) {
+ _celInfo.resourceId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(view));
+ _celInfo.loopNo = readSelectorValue(segMan, object, SELECTOR(loop));
+ _celInfo.celNo = readSelectorValue(segMan, object, SELECTOR(cel));
+
+ if (_celInfo.resourceId <= kPlanePic) {
+ // TODO: Enhance GfxView or ResourceManager to allow
+ // metadata for resources to be retrieved once, from a
+ // single location
+ Resource *view = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _celInfo.resourceId), false);
+ if (!view) {
+ error("Failed to load resource %d", _celInfo.resourceId);
+ }
+
+ // NOTE: +2 because the header size field itself is excluded from
+ // the header size in the data
+ const uint16 headerSize = READ_SCI11ENDIAN_UINT16(view->data) + 2;
+ const uint8 loopCount = view->data[2];
+ const uint8 loopSize = view->data[12];
+
+ if (_celInfo.loopNo >= loopCount) {
+ const int maxLoopNo = loopCount - 1;
+ _celInfo.loopNo = maxLoopNo;
+ writeSelectorValue(segMan, object, SELECTOR(loop), maxLoopNo);
+ }
+
+ byte *loopData = view->data + headerSize + (_celInfo.loopNo * loopSize);
+ const int8 seekEntry = loopData[0];
+ if (seekEntry != -1) {
+ loopData = view->data + headerSize + (seekEntry * loopSize);
+ }
+ const uint8 celCount = loopData[2];
+ if (_celInfo.celNo >= celCount) {
+ const int maxCelNo = celCount - 1;
+ _celInfo.celNo = maxCelNo;
+ writeSelectorValue(segMan, object, SELECTOR(cel), maxCelNo);
+ }
+ }
+ }
+
+ if (updateBitmap) {
+ const reg_t bitmap = readSelector(segMan, object, SELECTOR(bitmap));
+ if (!bitmap.isNull()) {
+ _celInfo.bitmap = bitmap;
+ _celInfo.type = kCelTypeMem;
+ } else {
+ _celInfo.bitmap = NULL_REG;
+ _celInfo.type = kCelTypeView;
+ }
+ }
+
+ if (updateCel || updateBitmap) {
+ delete _celObj;
+ _celObj = nullptr;
+ }
+
+ if (readSelectorValue(segMan, object, SELECTOR(fixPriority))) {
+ _fixPriority = true;
+ _priority = readSelectorValue(segMan, object, SELECTOR(priority));
+ } else {
+ _fixPriority = false;
+ writeSelectorValue(segMan, object, SELECTOR(priority), _position.y);
+ }
+
+ _z = readSelectorValue(segMan, object, SELECTOR(z));
+ _position.y -= _z;
+
+ if (readSelectorValue(segMan, object, SELECTOR(useInsetRect))) {
+ _useInsetRect = true;
+ _insetRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft));
+ _insetRect.top = readSelectorValue(segMan, object, SELECTOR(inTop));
+ _insetRect.right = readSelectorValue(segMan, object, SELECTOR(inRight));
+ _insetRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom));
+ } else {
+ _useInsetRect = false;
+ }
+
+ // TODO: SCI2.1/SQ6 engine clears this flag any time ScreenItem::Update(MemID)
+ // or ScreenItem::ScreenItem(MemID) are called, but doing this breaks
+ // view cycling because the flag isn't being set again later. There are over
+ // 100 places in the engine code where this flag is set, so it is probably
+ // a matter of figuring out what all of those calls are that re-set it. For
+ // now, since these are the *only* calls that clear this flag, we can just
+ // leave it set all the time.
+ // segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewVisible);
+}
+
+void ScreenItem::calcRects(const Plane &plane) {
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+ Common::Rect celRect(_celObj->_width, _celObj->_height);
+ if (_useInsetRect) {
+ if (_insetRect.intersects(celRect)) {
+ _insetRect.clip(celRect);
+ } else {
+ _insetRect = Common::Rect();
+ }
+ } else {
+ _insetRect = celRect;
+ }
+
+ Ratio newRatioX;
+ Ratio newRatioY;
+
+ if (_scale.signal & kScaleSignalDoScaling32) {
+ if (_scale.signal & kScaleSignalUseVanishingPoint) {
+ int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y);
+ newRatioX = Ratio(num, 128);
+ newRatioY = Ratio(num, 128);
+ } else {
+ newRatioX = Ratio(_scale.x, 128);
+ newRatioY = Ratio(_scale.y, 128);
+ }
+ }
+
+ if (newRatioX.getNumerator() && newRatioY.getNumerator()) {
+ _screenItemRect = _insetRect;
+
+ if (_celObj->_scaledWidth != scriptWidth || _celObj->_scaledHeight != scriptHeight) {
+ if (_useInsetRect) {
+ Ratio celScriptXRatio(_celObj->_scaledWidth, scriptWidth);
+ Ratio celScriptYRatio(_celObj->_scaledHeight, scriptHeight);
+ mulru(_screenItemRect, celScriptXRatio, celScriptYRatio);
+
+ if (_screenItemRect.intersects(celRect)) {
+ _screenItemRect.clip(celRect);
+ } else {
+ _screenItemRect = Common::Rect();
+ }
+ }
+
+ int displaceX = _celObj->_displace.x;
+ int displaceY = _celObj->_displace.y;
+
+ if (_mirrorX != _celObj->_mirrorX && _celInfo.type != kCelTypePic) {
+ displaceX = _celObj->_width - _celObj->_displace.x - 1;
+ }
+
+ if (!newRatioX.isOne() || !newRatioY.isOne()) {
+ mulru(_screenItemRect, newRatioX, newRatioY);
+ displaceX = (displaceX * newRatioX).toInt();
+ displaceY = (displaceY * newRatioY).toInt();
+ }
+
+ Ratio celXRatio(screenWidth, _celObj->_scaledWidth);
+ Ratio celYRatio(screenHeight, _celObj->_scaledHeight);
+
+ displaceX = (displaceX * celXRatio).toInt();
+ displaceY = (displaceY * celYRatio).toInt();
+
+ mulru(_screenItemRect, celXRatio, celYRatio);
+
+ if (/* TODO: dword_C6288 */ false && _celInfo.type == kCelTypePic) {
+ _scaledPosition.x = _position.x;
+ _scaledPosition.y = _position.y;
+ } else {
+ _scaledPosition.x = (_position.x * screenWidth / scriptWidth) - displaceX;
+ _scaledPosition.y = (_position.y * screenHeight / scriptHeight) - displaceY;
+ }
+
+ _screenItemRect.translate(_scaledPosition.x, _scaledPosition.y);
+
+ if (_mirrorX != _celObj->_mirrorX && _celInfo.type == kCelTypePic) {
+ Common::Rect temp(_insetRect);
+
+ if (!newRatioX.isOne()) {
+ mulru(temp, newRatioX, Ratio());
+ }
+
+ mulru(temp, celXRatio, Ratio());
+
+ CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj);
+
+ temp.translate(celObjPic->_relativePosition.x * screenWidth / scriptWidth - displaceX, 0);
+
+ // TODO: This is weird, and probably wrong calculation of widths
+ // due to BR-inclusion
+ int deltaX = plane._planeRect.right - plane._planeRect.left + 1 - temp.right - 1 - temp.left;
+
+ _scaledPosition.x += deltaX;
+ _screenItemRect.translate(deltaX, 0);
+ }
+
+ _scaledPosition.x += plane._planeRect.left;
+ _scaledPosition.y += plane._planeRect.top;
+ _screenItemRect.translate(plane._planeRect.left, plane._planeRect.top);
+
+ _ratioX = newRatioX * Ratio(screenWidth, _celObj->_scaledWidth);
+ _ratioY = newRatioY * Ratio(screenHeight, _celObj->_scaledHeight);
+ } else {
+ int displaceX = _celObj->_displace.x;
+ if (_mirrorX != _celObj->_mirrorX && _celInfo.type != kCelTypePic) {
+ displaceX = _celObj->_width - _celObj->_displace.x - 1;
+ }
+
+ if (!newRatioX.isOne() || !newRatioY.isOne()) {
+ mulru(_screenItemRect, newRatioX, newRatioY);
+ // TODO: This was in the original code, baked into the
+ // multiplication though it is not immediately clear
+ // why this is the only one that reduces the BR corner
+ _screenItemRect.right -= 1;
+ _screenItemRect.bottom -= 1;
+ }
+
+ _scaledPosition.x = _position.x - (displaceX * newRatioX).toInt();
+ _scaledPosition.y = _position.y - (_celObj->_displace.y * newRatioY).toInt();
+ _screenItemRect.translate(_scaledPosition.x, _scaledPosition.y);
+
+ if (_mirrorX != _celObj->_mirrorX && _celInfo.type == kCelTypePic) {
+ Common::Rect temp(_insetRect);
+
+ if (!newRatioX.isOne()) {
+ mulru(temp, newRatioX, Ratio());
+ temp.right -= 1;
+ }
+
+ CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj);
+ temp.translate(celObjPic->_relativePosition.x - (displaceX * newRatioX).toInt(), celObjPic->_relativePosition.y - (_celObj->_displace.y * newRatioY).toInt());
+
+ // TODO: This is weird, and probably wrong calculation of widths
+ // due to BR-inclusion
+ int deltaX = plane._gameRect.right - plane._gameRect.left + 1 - temp.right - 1 - temp.left;
+
+ _scaledPosition.x += deltaX;
+ _screenItemRect.translate(deltaX, 0);
+ }
+
+ _scaledPosition.x += plane._gameRect.left;
+ _scaledPosition.y += plane._gameRect.top;
+ _screenItemRect.translate(plane._gameRect.left, plane._gameRect.top);
+
+ if (screenWidth != _celObj->_scaledWidth || _celObj->_scaledHeight != screenHeight) {
+ Ratio celXRatio(screenWidth, _celObj->_scaledWidth);
+ Ratio celYRatio(screenHeight, _celObj->_scaledHeight);
+ mulru(_scaledPosition, celXRatio, celYRatio);
+ mulru(_screenItemRect, celXRatio, celYRatio, 1);
+ }
+
+ _ratioX = newRatioX * Ratio(screenWidth, _celObj->_scaledWidth);
+ _ratioY = newRatioY * Ratio(screenHeight, _celObj->_scaledHeight);
+ }
+
+ _screenRect = _screenItemRect;
+
+ if (_screenRect.intersects(plane._screenRect)) {
+ _screenRect.clip(plane._screenRect);
+ } else {
+ _screenRect.right = 0;
+ _screenRect.bottom = 0;
+ _screenRect.left = 0;
+ _screenRect.top = 0;
+ }
+
+ if (!_fixPriority) {
+ _priority = _z + _position.y;
+ }
+ } else {
+ _screenRect.left = 0;
+ _screenRect.top = 0;
+ _screenRect.right = 0;
+ _screenRect.bottom = 0;
+ }
+}
+
+CelObj &ScreenItem::getCelObj() {
+ if (_celObj == nullptr) {
+ switch (_celInfo.type) {
+ case kCelTypeView:
+ _celObj = new CelObjView(_celInfo.resourceId, _celInfo.loopNo, _celInfo.celNo);
+ break;
+ case kCelTypePic:
+ error("Internal error, pic screen item with no cel.");
+ break;
+ case kCelTypeMem:
+ _celObj = new CelObjMem(_celInfo.bitmap);
+ break;
+ case kCelTypeColor:
+ _celObj = new CelObjColor(_celInfo.color, _insetRect.width(), _insetRect.height());
+ break;
+ }
+ }
+
+ return *_celObj;
+}
+
+void ScreenItem::printDebugInfo(Console *con) const {
+ con->debugPrintf("prio %d, x %d, y %d, z: %d, scaledX: %d, scaledY: %d flags: %d\n",
+ _priority,
+ _position.x,
+ _position.y,
+ _z,
+ _scaledPosition.x,
+ _scaledPosition.y,
+ _created | (_updated << 1) | (_deleted << 2)
+ );
+ con->debugPrintf(" screen rect (%d, %d, %d, %d)\n", PRINT_RECT(_screenRect));
+ if (_useInsetRect) {
+ con->debugPrintf(" inset rect: (%d, %d, %d, %d)\n", PRINT_RECT(_insetRect));
+ }
+
+ Common::String celType;
+ switch (_celInfo.type) {
+ case kCelTypePic:
+ celType = "pic";
+ break;
+ case kCelTypeView:
+ celType = "view";
+ break;
+ case kCelTypeColor:
+ celType = "color";
+ break;
+ case kCelTypeMem:
+ celType = "mem";
+ break;
+ }
+
+ con->debugPrintf(" type: %s, res %d, loop %d, cel %d, bitmap %04x:%04x, color: %d\n",
+ celType.c_str(),
+ _celInfo.resourceId,
+ _celInfo.loopNo,
+ _celInfo.celNo,
+ PRINT_REG(_celInfo.bitmap),
+ _celInfo.color
+ );
+ if (_celObj != nullptr) {
+ con->debugPrintf(" width %d, height %d, scaledWidth %d, scaledHeight %d\n",
+ _celObj->_width,
+ _celObj->_height,
+ _celObj->_scaledWidth,
+ _celObj->_scaledHeight
+ );
+ }
+}
+
+void ScreenItem::update(const reg_t object) {
+ SegManager *segMan = g_sci->getEngineState()->_segMan;
+
+ const GuiResourceId view = readSelectorValue(segMan, object, SELECTOR(view));
+ const int16 loopNo = readSelectorValue(segMan, object, SELECTOR(loop));
+ const int16 celNo = readSelectorValue(segMan, object, SELECTOR(cel));
+
+ const bool updateCel = (
+ _celInfo.resourceId != view ||
+ _celInfo.loopNo != loopNo ||
+ _celInfo.celNo != celNo
+ );
+
+ const bool updateBitmap = !readSelector(segMan, object, SELECTOR(bitmap)).isNull();
+
+ setFromObject(segMan, object, updateCel, updateBitmap);
+
+ if (!_created) {
+ _updated = g_sci->_gfxFrameout->getScreenCount();
+ }
+
+ _deleted = 0;
+}
+
+#pragma mark -
+#pragma mark ScreenItemList
+ScreenItem *ScreenItemList::findByObject(const reg_t object) const {
+ const_iterator screenItemIt = Common::find_if(begin(), end(), FindByObject<ScreenItem *>(object));
+
+ if (screenItemIt == end()) {
+ return nullptr;
+ }
+
+ return *screenItemIt;
+}
+void ScreenItemList::sort() {
+ // TODO: SCI engine used _unsorted as an array of indexes into the
+ // list itself and then performed the same swap operations on the
+ // _unsorted array as the _storage array during sorting, but the
+ // only reason to do this would be if some of the pointers in the
+ // list were replaced so the pointer values themselves couldn’t
+ // simply be recorded and then restored later. It is not yet
+ // verified whether this simplification of the sort/unsort is
+ // safe.
+ for (size_type i = 0; i < size(); ++i) {
+ _unsorted[i] = (*this)[i];
+ }
+
+ Common::sort(begin(), end(), sortHelper);
+}
+void ScreenItemList::unsort() {
+ for (size_type i = 0; i < size(); ++i) {
+ (*this)[i] = _unsorted[i];
+ }
+}
+
+}
diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h
new file mode 100644
index 0000000000..d3968efeef
--- /dev/null
+++ b/engines/sci/graphics/screen_item32.h
@@ -0,0 +1,280 @@
+/* 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.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_SCREEN_ITEM32_H
+#define SCI_GRAPHICS_SCREEN_ITEM32_H
+
+#include "common/rect.h"
+#include "sci/graphics/celobj32.h"
+#include "sci/graphics/lists32.h"
+
+namespace Sci {
+
+enum ScaleSignals32 {
+ kScaleSignalNone = 0,
+ kScaleSignalDoScaling32 = 1, // enables scaling when drawing that cel (involves scaleX and scaleY)
+ kScaleSignalUseVanishingPoint = 2,
+ // TODO: Is this actually a thing? I have not seen it and
+ // the original engine masks &3 where it uses scale signals.
+ kScaleSignalDisableGlobalScaling32 = 4
+};
+
+struct ScaleInfo {
+ int x, y, max;
+ ScaleSignals32 signal;
+ ScaleInfo() : x(128), y(128), max(100), signal(kScaleSignalNone) {}
+};
+
+class CelObj;
+class Plane;
+class SegManager;
+
+#pragma mark -
+#pragma mark ScreenItem
+
+/**
+ * A ScreenItem is the engine-side representation of a
+ * game script View.
+ */
+class ScreenItem {
+private:
+ /**
+ * A serial used for screen items that are generated
+ * inside the graphics engine, rather than the
+ * interpreter.
+ */
+ static uint16 _nextObjectId;
+
+ /**
+ * The parent plane of this screen item.
+ */
+ reg_t _plane;
+
+ /**
+ * Scaling data used to calculate the final screen
+ * dimensions of the screen item as well as the scaling
+ * ratios used when drawing the item to screen.
+ */
+ ScaleInfo _scale;
+
+ /**
+ * The position & dimensions of the screen item in
+ * screen coordinates. This rect includes the offset
+ * of the parent plane, but is not clipped to the
+ * screen, so may include coordinates that are
+ * offscreen.
+ */
+ Common::Rect _screenItemRect;
+
+ /**
+ * TODO: Document
+ */
+ bool _useInsetRect;
+
+ /**
+ * TODO: Documentation
+ * The insetRect is also used to describe the fill
+ * rectangle of a screen item that is drawn using
+ * CelObjColor.
+ */
+ Common::Rect _insetRect;
+
+ /**
+ * The z-index of the screen item in pseudo-3D space.
+ * Higher values are drawn on top of lower values.
+ */
+ int _z;
+
+ /**
+ * Sets the common properties of a screen item that must
+ * be set both during creation and update of a screen
+ * item.
+ */
+ void setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap);
+
+public:
+ /**
+ * A descriptor for the cel object represented by the
+ * screen item.
+ */
+ CelInfo32 _celInfo;
+
+ /**
+ * The cel object used to actually render the screen
+ * item. This member is populated by calling
+ * `getCelObj`.
+ */
+ CelObj *_celObj;
+
+ /**
+ * If set, the priority for this screen item is fixed
+ * in place. Otherwise, the priority of the screen item
+ * is calculated from its y-position + z-index.
+ */
+ bool _fixPriority;
+
+ /**
+ * The rendering priority of the screen item, relative
+ * only to the other screen items within the same plane.
+ * Higher priorities are drawn above lower priorities.
+ */
+ int16 _priority;
+
+ /**
+ * The top-left corner of the screen item, in game
+ * script coordinates, relative to the parent plane.
+ */
+ Common::Point _position;
+
+ /**
+ * The associated View script object that was
+ * used to create the ScreenItem, or a numeric
+ * value in the case of a ScreenItem that was
+ * generated outside of the VM.
+ */
+ reg_t _object;
+
+ /**
+ * For screen items representing picture resources,
+ * the resource ID of the picture.
+ */
+ GuiResourceId _pictureId;
+
+ /**
+ * Flags indicating the state of the screen item.
+ * - `created` is set when the screen item is first
+ * created, either from a VM object or from within the
+ * engine itself
+ * - `updated` is set when `created` is not already set
+ * and the screen item is updated from a VM object
+ * - `deleted` is set by the parent plane, if the parent
+ * plane is a pic type and its picture resource ID has
+ * changed
+ */
+ int _created, _updated, _deleted; // ?
+
+ /**
+ * For screen items that represent picture cels, this
+ * value is set to match the `_mirrorX` property of the
+ * parent plane and indicates that the cel should be
+ * drawn horizontally mirrored. For final drawing, it is
+ * XORed with the `_mirrorX` property of the cel object.
+ * The cel object's `_mirrorX` property comes from the
+ * resource data itself.
+ */
+ bool _mirrorX;
+
+ /**
+ * The scaling ratios to use when drawing this screen
+ * item. These values are calculated according to the
+ * scale info whenever the screen item is updated.
+ */
+ Ratio _ratioX, _ratioY;
+
+ /**
+ * The top-left corner of the screen item, in screen
+ * coordinates.
+ */
+ Common::Point _scaledPosition;
+
+ /**
+ * The position & dimensions of the screen item in
+ * screen coordinates. This rect includes the offset of
+ * the parent plane and is clipped to the screen.
+ */
+ Common::Rect _screenRect;
+
+ /**
+ * Initialises static Plane members.
+ */
+ static void init();
+
+ ScreenItem(const reg_t screenItem);
+ ScreenItem(const reg_t plane, const CelInfo32 &celInfo);
+ ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect);
+ ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect, const ScaleInfo &scaleInfo);
+ ScreenItem(const ScreenItem &other);
+ void operator=(const ScreenItem &);
+
+ inline bool operator<(const ScreenItem &other) const {
+ if (_priority < other._priority) {
+ return true;
+ }
+
+ if (_priority == other._priority) {
+ if (_position.y + _z < other._position.y + other._z) {
+ return true;
+ }
+
+ if (_position.y + _z == other._position.y + other._z) {
+ return false;
+ // TODO: Failure in SQ6 room 220
+// return _object < other._object;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Calculates the dimensions and scaling parameters for
+ * the screen item, using the given plane as the parent
+ * plane for screen rect positioning.
+ *
+ * @note This method was called Update in SCI engine.
+ */
+ void calcRects(const Plane &plane);
+
+ /**
+ * Retrieves the corresponding cel object for this
+ * screen item. If a cel object does not already exist,
+ * one will be created and assigned.
+ */
+ CelObj &getCelObj();
+
+ void printDebugInfo(Console *con) const;
+
+ /**
+ * Updates the properties of the screen item from a
+ * VM object.
+ */
+ void update(const reg_t object);
+};
+
+#pragma mark -
+#pragma mark ScreenItemList
+
+typedef StablePointerArray<ScreenItem, 250> ScreenItemListBase;
+class ScreenItemList : public ScreenItemListBase {
+ static bool inline sortHelper(const ScreenItem *a, const ScreenItem *b) {
+ return *a < *b;
+ }
+public:
+ ScreenItem *_unsorted[250];
+
+ ScreenItem *findByObject(const reg_t object) const;
+ void sort();
+ void unsort();
+};
+}
+
+#endif
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index 08e5ea84d8..0b5587483b 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -81,10 +81,13 @@ MODULE_OBJS := \
ifdef ENABLE_SCI32
MODULE_OBJS += \
engine/kgraphics32.o \
+ graphics/celobj32.o \
graphics/controls32.o \
graphics/frameout.o \
graphics/paint32.o \
+ graphics/plane32.o \
graphics/palette32.o \
+ graphics/screen_item32.o\
graphics/text32.o \
video/robot_decoder.o
endif
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 93cafadc52..9ef28b214b 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -694,6 +694,7 @@ void SciEngine::initGraphics() {
_gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32);
_robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh);
_gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette32, _gfxPaint32);
+ _gfxFrameout->run();
} else {
#endif
// SCI0-SCI1.1 graphic objects creation