Files
2026-02-02 04:50:13 +01:00

980 lines
31 KiB
C++

/* 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "twine/renderer/redraw.h"
#include "common/memstream.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
#include "graphics/surface.h"
#include "twine/audio/sound.h"
#include "twine/debugger/debug_state.h"
#include "twine/input.h"
#include "twine/menu/interface.h"
#include "twine/menu/menu.h"
#include "twine/parser/body.h"
#include "twine/parser/sprite.h"
#include "twine/renderer/renderer.h"
#include "twine/renderer/screens.h"
#include "twine/resources/hqr.h"
#include "twine/resources/resources.h"
#include "twine/scene/actor.h"
#include "twine/scene/animations.h"
#include "twine/scene/collision.h"
#include "twine/scene/extra.h"
#include "twine/scene/grid.h"
#include "twine/scene/movements.h"
#include "twine/scene/scene.h"
#include "twine/shared.h"
#include "twine/text.h"
namespace TwinE {
Redraw::Redraw(TwinEEngine *engine) : _engine(engine), _bubbleSpriteIndex(SPRITEHQR_DIAG_BUBBLE_LEFT) {}
void Redraw::addRedrawCurrentArea(const Common::Rect &redrawArea) {
const int32 area = (redrawArea.right - redrawArea.left) * (redrawArea.bottom - redrawArea.top);
for (int32 i = 0; i < _nbOptPhysBox; ++i) {
Common::Rect &rect = _currentRedrawList[i];
const int32 leftValue = MIN<int32>(redrawArea.left, rect.left);
const int32 rightValue = MAX<int32>(redrawArea.right, rect.right);
const int32 topValue = MIN<int32>(redrawArea.top, rect.top);
const int32 bottomValue = MAX<int32>(redrawArea.bottom, rect.bottom);
const int32 areaValue = (rightValue - leftValue) * (bottomValue - topValue);
const int32 areaValueDiff = ((rect.right - rect.left) * (rect.bottom - rect.top) + area);
if (areaValue < areaValueDiff) {
rect.left = leftValue;
rect.top = topValue;
rect.right = rightValue;
rect.bottom = MIN<int32>((_engine->height() - 1), bottomValue);
assert(rect.left <= rect.right);
assert(rect.top <= rect.bottom);
return;
}
}
Common::Rect &rect = _currentRedrawList[_nbOptPhysBox];
rect.left = redrawArea.left;
rect.top = redrawArea.top;
rect.right = redrawArea.right;
rect.bottom = MIN<int32>((_engine->height() - 1), redrawArea.bottom);
assert(rect.left <= rect.right);
assert(rect.top <= rect.bottom);
_nbOptPhysBox++;
}
void Redraw::addPhysBox(const Common::Rect &rect) {
if (!rect.isValidRect()) {
return;
}
addRedrawArea(rect.left, rect.top, rect.right, rect.bottom);
}
void Redraw::addRedrawArea(int32 left, int32 top, int32 right, int32 bottom) {
if (left < 0) {
left = 0;
}
if (top < 0) {
top = 0;
}
if (right >= _engine->width()) {
right = (_engine->width() - 1);
}
if (bottom >= _engine->height()) {
bottom = (_engine->height() - 1);
}
if (left > right || top > bottom) {
return;
}
Common::Rect &rect = _nextRedrawList[_nbPhysBox];
rect.left = left;
rect.top = top;
rect.right = right;
rect.bottom = bottom;
_nbPhysBox++;
addRedrawCurrentArea(rect);
}
void Redraw::moveNextAreas() {
_nbOptPhysBox = 0;
for (int32 i = 0; i < _nbPhysBox; i++) {
addRedrawCurrentArea(_nextRedrawList[i]);
}
}
void Redraw::flipBoxes() {
for (int32 i = 0; i < _nbOptPhysBox; i++) { // redraw areas on screen
_engine->copyBlockPhys(_currentRedrawList[i]);
}
moveNextAreas();
}
void Redraw::clsBoxes() {
for (int32 i = 0; i < _nbOptPhysBox; i++) {
_engine->blitWorkToFront(_currentRedrawList[i]);
}
}
void Redraw::sortDrawingList(DrawListStruct *list, int32 listSize) const { // SmallSort
DrawListStruct* pNext;
DrawListStruct* pSmallest;
int32 n;
for (listSize--; listSize > 0; listSize--) {
pSmallest = list;
pNext = list + 1;
for (n = listSize; n > 0; n--) {
if (pNext->z < pSmallest->z) {
pSmallest = pNext;
}
pNext++;
}
if (pSmallest != list) {
DrawListStruct tmp = *pSmallest;
*pSmallest = *list;
*list = tmp;
}
list++;
}
}
void Redraw::posObjIncrust(OverlayListStruct *ptrdisp, int32 num) {
// in case we have several 3D objects rotating at the same time!
int32 x = 10;
OverlayType type = ptrdisp->type;
if (type == OverlayType::koInventory || type == OverlayType::koInventoryItem) {
for (int32 n = 0; n < ARRAYSIZE(overlayList); n++) {
OverlayListStruct *overlay = &overlayList[n];
if (n != num && overlay->num != -1) {
if (overlay->type == OverlayType::koInventory || overlay->type == OverlayType::koInventoryItem) {
x += 70;
}
}
}
ptrdisp->y = 10;
ptrdisp->x = (int16)x;
}
}
int32 Redraw::addOverlay(OverlayType type, int16 info0, int16 x, int16 y, int16 info1, OverlayPosType posType, int16 lifeTime) { // InitIncrustDisp
for (int32 i = 0; i < ARRAYSIZE(overlayList); i++) {
OverlayListStruct *overlay = &overlayList[i];
if (_engine->isLBA1()) {
if (overlay->num == -1) {
overlay->type = type;
overlay->num = info0;
overlay->x = x;
overlay->y = y;
overlay->info = info1;
overlay->move = posType;
overlay->timerEnd = _engine->timerRef + _engine->toSeconds(lifeTime);
return i;
}
} else {
if (overlay->num == -1 || (overlay->num == info0 && overlay->type == type)) {
if (overlay->num == -1 || overlay->type != type) {
overlay->x = x;
overlay->y = y;
}
if ((OverlayType)((uint8)type) == OverlayType::koNumberRange) {
// ATTENTION: Big Trickery: counters are always displayed
// at y=20, this allows using the Y to store the
// current value of the counter (see FlagAnimWhoSpeak)
overlay->y = info0;
}
overlay->type = type;
overlay->num = info0;
overlay->info = info1;
overlay->move = posType;
overlay->timerEnd = _engine->timerRef + _engine->toSeconds(lifeTime);
posObjIncrust(overlay, i);
return i;
}
}
}
return -1;
}
void Redraw::updateOverlayTypePosition(int16 x1, int16 y1, int16 x2, int16 y2) {
const int16 newX = x2 - x1;
const int16 newY = y2 - y1;
for (int32 i = 0; i < ARRAYSIZE(overlayList); i++) {
OverlayListStruct *overlay = &overlayList[i];
if (overlay->move == OverlayPosType::koFollowActor) {
overlay->x = newX;
overlay->y = newY;
}
}
}
int32 Redraw::fillActorDrawingList(DrawListStruct *drawList, bool flagflip) {
int32 drawListPos = 0;
for (int32 n = 0; n < _engine->_scene->_nbObjets; n++) {
ActorStruct *actor = _engine->_scene->getActor(n);
actor->_workFlags.bWasDrawn = 0; // reset visible state
actor->_workFlags.bIsTargetable = 0;
if (_engine->_grid->_zoneGrm != -1 && actor->_posObj.y > _engine->_scene->_sceneZones[_engine->_grid->_indexGrm].maxs.y) {
continue;
}
// no redraw required
if (actor->_flags.bIsBackgrounded && !flagflip) {
// get actor position on screen
const IVec3 &projPos = _engine->_renderer->projectPoint(actor->posObj() - _engine->_grid->_worldCube);
// check if actor is visible on screen, otherwise don't display it
if (projPos.x > VIEW_X0 && projPos.x < VIEW_X1(_engine) && projPos.y > VIEW_Y0 && projPos.y < VIEW_Y1(_engine)) {
actor->_workFlags.bWasDrawn = 1;
}
continue;
}
// if the actor isn't set as hidden
if (actor->_body == -1 || actor->_flags.bIsInvisible) {
continue;
}
// get actor position on screen
const IVec3 &projPos = _engine->_renderer->projectPoint(actor->posObj() - _engine->_grid->_worldCube);
if ((actor->_flags.bSpriteClip && projPos.x > -112 && projPos.x < _engine->width() + 112 && projPos.y > VIEW_X0 && projPos.y < _engine->height() + 171) ||
((!actor->_flags.bSpriteClip) && projPos.x > VIEW_X0 && projPos.x < VIEW_X1(_engine) && projPos.y > VIEW_Y0 && projPos.y < VIEW_Y1(_engine))) {
int32 ztri = actor->_posObj.x - _engine->_grid->_worldCube.x + actor->_posObj.z - _engine->_grid->_worldCube.z;
// if actor is above another actor
if (actor->_carryBy != -1) {
const ActorStruct *standOnActor = _engine->_scene->getActor(actor->_carryBy);
ztri = standOnActor->_posObj.x - _engine->_grid->_worldCube.x + standOnActor->_posObj.z - _engine->_grid->_worldCube.z + 2;
}
if (actor->_flags.bSprite3D) {
drawList[drawListPos].type = DrawListType::DrawActorSprites;
drawList[drawListPos].numObj = n;
if (actor->_flags.bSpriteClip) {
ztri = actor->_animStep.x - _engine->_grid->_worldCube.x + actor->_animStep.z - _engine->_grid->_worldCube.z;
}
} else {
drawList[drawListPos].type = DrawListType::DrawObject3D;
drawList[drawListPos].numObj = n;
}
drawList[drawListPos].z = ztri;
drawListPos++;
// if use shadows
if (_engine->_cfgfile.ShadowMode != 0 && !(actor->_flags.bNoShadow)) {
if (actor->_carryBy != -1) {
drawList[drawListPos].xw = actor->_posObj.x;
drawList[drawListPos].yw = actor->_posObj.y - 1;
drawList[drawListPos].zw = actor->_posObj.z;
} else {
const IVec3 shadowCoord = _engine->_movements->getShadow(actor->posObj());
drawList[drawListPos].xw = shadowCoord.x;
drawList[drawListPos].yw = shadowCoord.y;
drawList[drawListPos].zw = shadowCoord.z;
}
drawList[drawListPos].z = ztri - 1; // save the shadow entry in the _drawList
drawList[drawListPos].type = DrawListType::DrawShadows;
drawList[drawListPos].numObj = 0;
drawList[drawListPos].num = 1;
drawListPos++;
}
if (_flagMCGA && n == _engine->_scene->_numObjFollow) {
_sceneryViewX = projPos.x;
_sceneryViewY = projPos.y;
}
}
}
return drawListPos;
}
int32 Redraw::fillExtraDrawingList(DrawListStruct *drawList, int32 drawListPos) { // part of AffScene
for (int32 i = 0; i < EXTRA_MAX_ENTRIES; i++) {
ExtraListStruct *extra = &_engine->_extra->_extraList[i];
if (extra->sprite == -1) {
continue;
}
if (extra->type & ExtraType::TIME_IN) {
if (_engine->timerRef - extra->spawnTime > 35) {
extra->spawnTime = _engine->timerRef;
extra->type &= ~ExtraType::TIME_IN;
_engine->_sound->mixSample3D(Samples::ItemPopup, 0x1000, 1, extra->pos, -1);
}
continue;
}
if ((extra->type & ExtraType::TIME_OUT) && (extra->type & ExtraType::FLASH)) {
if (_engine->timerRef >= extra->spawnTime + extra->payload.lifeTime - _engine->toSeconds(3)) {
if ((_engine->timerRef + extra->spawnTime) & 8) {
continue;
}
}
}
const IVec3 &projPos = _engine->_renderer->projectPoint(extra->pos - _engine->_grid->_worldCube);
if (projPos.x > VIEW_X0 && projPos.x < VIEW_X1(_engine) && projPos.y > VIEW_Y0 && projPos.y < VIEW_Y1(_engine)) {
const int16 zVal = extra->pos.x - _engine->_grid->_worldCube.x + extra->pos.z - _engine->_grid->_worldCube.z;
drawList[drawListPos].z = zVal;
drawList[drawListPos].numObj = i;
drawList[drawListPos].type = DrawListType::DrawExtras;
drawListPos++;
if (_engine->_cfgfile.ShadowMode == 2 && !(extra->sprite & EXTRA_SPECIAL_MASK)) {
const IVec3 &shadowCoord = _engine->_movements->getShadow(extra->pos);
drawList[drawListPos].z = zVal - 1;
drawList[drawListPos].numObj = 0;
drawList[drawListPos].type = DrawListType::DrawShadows;
drawList[drawListPos].xw = shadowCoord.x;
drawList[drawListPos].yw = shadowCoord.y;
drawList[drawListPos].zw = shadowCoord.z;
drawList[drawListPos].num = 0;
drawListPos++;
}
}
}
return drawListPos;
}
void Redraw::processDrawListShadows(const DrawListStruct &drawCmd) {
// get actor position on screen
const IVec3 &projPos = _engine->_renderer->projectPoint(drawCmd.xw - _engine->_grid->_worldCube.x, drawCmd.yw - _engine->_grid->_worldCube.y, drawCmd.zw - _engine->_grid->_worldCube.z);
int32 spriteWidth = _engine->_resources->_spriteShadowPtr.surface(drawCmd.num).w;
int32 spriteHeight = _engine->_resources->_spriteShadowPtr.surface(drawCmd.num).h;
// calculate sprite size and position on screen
Common::Rect renderRect;
renderRect.left = projPos.x - (spriteWidth / 2);
renderRect.top = projPos.y - (spriteHeight / 2);
renderRect.right = projPos.x + (spriteWidth / 2);
renderRect.bottom = projPos.y + (spriteHeight / 2);
if (_engine->_interface->setClip(renderRect)) {
_engine->_grid->drawSprite(renderRect.left, renderRect.top, _engine->_resources->_spriteShadowPtr, drawCmd.num);
const int32 tmpX = (drawCmd.xw + SIZE_BRICK_Y) / SIZE_BRICK_XZ;
const int32 tmpY = drawCmd.yw / SIZE_BRICK_Y;
const int32 tmpZ = (drawCmd.zw + SIZE_BRICK_Y) / SIZE_BRICK_XZ;
_engine->_grid->drawOverBrick(tmpX, tmpY, tmpZ);
addPhysBox(_engine->_interface->_clip);
_engine->_debugState->drawClip(renderRect);
}
_engine->_interface->unsetClip();
}
void Redraw::processDrawListActors(const DrawListStruct &drawCmd, bool flagflip) {
const int32 actorIdx = drawCmd.numObj;
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
if (actor->_anim >= 0) {
const AnimData &animData = _engine->_resources->_animData[actor->_anim];
BodyData &bodyData = actor->_entityDataPtr->getBody(actor->_body);
_engine->_animations->setInterAnimObjet2(actor->_frame, animData, bodyData, &bodyData._animTimerData);
}
const IVec3 &delta = actor->posObj() - _engine->_grid->_worldCube;
Common::Rect renderRect;
if (actorIdx == OWN_ACTOR_SCENE_INDEX) {
if (_engine->_actor->_cropBottomScreen) {
_engine->_interface->_clip.bottom = _engine->_actor->_cropBottomScreen;
}
}
if (!_engine->_renderer->affObjetIso(delta.x, delta.y, delta.z, LBAAngles::ANGLE_0, actor->_beta, LBAAngles::ANGLE_0, actor->_entityDataPtr->getBody(actor->_body), renderRect)) {
return;
}
if (_engine->_interface->setClip(renderRect)) {
actor->_workFlags.bWasDrawn = 1;
const int32 tempX = (actor->_posObj.x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
int32 tempY = actor->_posObj.y / SIZE_BRICK_Y;
const int32 tempZ = (actor->_posObj.z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
if (actor->brickShape() != ShapeType::kNone) {
tempY++;
}
_engine->_grid->drawOverBrick(tempX, tempY, tempZ);
addPhysBox(_engine->_interface->_clip);
if (actor->_flags.bIsBackgrounded && flagflip) {
_engine->copyBlock(_engine->_interface->_clip);
}
_engine->_debugState->drawClip(_engine->_interface->_clip);
}
_engine->_interface->unsetClip();
}
void Redraw::processDrawListActorSprites(const DrawListStruct &drawCmd, bool bgRedraw) {
int32 actorIdx = drawCmd.numObj;
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
const SpriteData &spriteData = _engine->_resources->_spriteData[actor->_body];
// TODO: using the raw pointer and not the SpriteData surface here is a workaround for issue https://bugs.scummvm.org/ticket/12024
const uint8 *spritePtr = _engine->_resources->_spriteTable[actor->_body];
// get actor position on screen
const IVec3 &projPos = _engine->_renderer->projectPoint(actor->posObj() - _engine->_grid->_worldCube);
const int32 dx = spriteData.surface().w;
const int32 dy = spriteData.surface().h;
// calculate sprite position on screen
const SpriteDim *dim = _engine->_resources->_spriteBoundingBox.dim(actor->_body);
Common::Rect renderRect;
renderRect.left = projPos.x + dim->x;
renderRect.top = projPos.y + dim->y;
renderRect.right = renderRect.left + dx;
renderRect.bottom = renderRect.top + dy;
bool validClip;
if (actor->_flags.bSpriteClip) {
const Common::Rect rect(_projPosScreen.x + actor->_cropLeft, _projPosScreen.y + actor->_cropTop, _projPosScreen.x + actor->_cropRight, _projPosScreen.y + actor->_cropBottom);
validClip = _engine->_interface->setClip(rect);
} else {
validClip = _engine->_interface->setClip(renderRect);
}
if (validClip) {
_engine->_grid->drawGraph(0, renderRect.left, renderRect.top, spritePtr);
actor->_workFlags.bWasDrawn = 1;
if (actor->_flags.bSpriteClip) {
const int32 xm = (actor->_animStep.x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
const int32 ym = actor->_animStep.y / SIZE_BRICK_Y;
const int32 zm = (actor->_animStep.z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
_engine->_grid->drawOverBrick3(xm, ym, zm);
} else {
const int32 xm = (actor->_posObj.x + actor->_boundingBox.maxs.x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
int32 ym = actor->_posObj.y / SIZE_BRICK_Y;
const int32 zm = (actor->_posObj.z + actor->_boundingBox.maxs.z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
if (actor->brickShape() != ShapeType::kNone) {
ym++;
}
_engine->_grid->drawOverBrick3(xm, ym, zm);
}
addPhysBox(_engine->_interface->_clip);
if (actor->_flags.bIsBackgrounded && bgRedraw) {
_engine->copyBlock(_engine->_interface->_clip);
}
_engine->_debugState->drawClip(renderRect);
_engine->_interface->unsetClip();
}
}
void Redraw::processDrawListExtras(const DrawListStruct &drawCmd) {
int32 extraIdx = drawCmd.numObj;
ExtraListStruct *extra = &_engine->_extra->_extraList[extraIdx];
const IVec3 &projPos = _engine->_renderer->projectPoint(extra->pos - _engine->_grid->_worldCube);
Common::Rect renderRect;
if (extra->sprite & EXTRA_SPECIAL_MASK) {
_engine->_extra->affSpecial(extraIdx, projPos.x, projPos.y, renderRect);
} else {
const SpriteData &spritePtr = _engine->_resources->_spriteData[extra->sprite];
const int32 dx = spritePtr.surface().w;
const int32 dy = spritePtr.surface().h;
// calculate sprite position on screen
const SpriteDim *dim = _engine->_resources->_spriteBoundingBox.dim(extra->sprite);
renderRect.left = projPos.x + dim->x;
renderRect.top = projPos.y + dim->y;
renderRect.right = renderRect.left + dx;
renderRect.bottom = renderRect.top + dy;
_engine->_grid->drawSprite(renderRect.left, renderRect.top, spritePtr);
}
if (_engine->_interface->setClip(renderRect)) {
const int32 xm = (extra->pos.x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
const int32 ym = extra->pos.y / SIZE_BRICK_Y;
const int32 zm = (extra->pos.z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
_engine->_grid->drawOverBrick(xm, ym, zm);
addPhysBox(_engine->_interface->_clip);
// show clipping area
//drawRectBorders(renderRect);
_engine->_interface->unsetClip();
}
}
void Redraw::correctZLevels(DrawListStruct *listTri, int32 drawListPos) {
ActorStruct *ptrobj = _engine->_scene->getActor(OWN_ACTOR_SCENE_INDEX);
if (ptrobj->_flags.bIsInvisible || ptrobj->_body == -1) {
return;
}
IVec3 tmin = ptrobj->posObj() + ptrobj->_boundingBox.mins;
IVec3 tmax = ptrobj->posObj() + ptrobj->_boundingBox.maxs;
int32 twinsenpos = -1;
int32 twinsenz = -1;
for (int32 pos = 0; pos < drawListPos; ++pos) {
DrawListStruct &drawCmd = listTri[pos];
if (drawCmd.type == DrawListType::DrawObject3D && drawCmd.numObj == OWN_ACTOR_SCENE_INDEX) {
twinsenpos = pos;
twinsenz = drawCmd.z;
break;
}
}
if (twinsenpos == -1) {
return;
}
for (int32 n = 0; n < drawListPos; ++n) {
DrawListStruct &ptrtri = listTri[n];
uint32 typeobj = ptrtri.type;
int32 numobj = ptrtri.numObj;
ptrobj = _engine->_scene->getActor(numobj);
switch (typeobj) {
default:
break;
case DrawListType::DrawActorSprites:
if (ptrobj->_flags.bSpriteClip) {
IVec3 pmin = ptrobj->_animStep + ptrobj->_boundingBox.mins;
IVec3 pmax = ptrobj->_animStep + ptrobj->_boundingBox.maxs;
if (pmax.x > tmin.x && pmin.x < tmax.x) {
if (pmax.z <= tmin.z) {
// twinsen after
if (twinsenz < ptrtri.z) {
// correct the error
listTri[twinsenpos].z = ptrtri.z;
listTri[twinsenpos].numObj = ptrtri.numObj;
listTri[twinsenpos].type = ptrtri.type;
ptrtri.numObj = OWN_ACTOR_SCENE_INDEX;
ptrtri.type = DrawListType::DrawObject3D;
ptrtri.z = (int16)twinsenz;
twinsenpos = n;
numobj = -1;
break;
}
}
if (pmin.z >= tmax.z) {
// twinsen before
if (twinsenz > ptrtri.z) {
// correct the error
listTri[twinsenpos].z = ptrtri.z;
listTri[twinsenpos].numObj = ptrtri.numObj;
listTri[twinsenpos].type = ptrtri.type;
ptrtri.numObj = OWN_ACTOR_SCENE_INDEX;
ptrtri.type = DrawListType::DrawObject3D;
ptrtri.z = (int16)twinsenz;
twinsenpos = n;
numobj = -1;
break;
}
}
break;
}
if (pmax.z > tmin.z && pmin.z < tmax.z) {
if (pmax.x <= tmin.x) {
// twinsen after
if (twinsenz < ptrtri.z) {
// correct the error
listTri[twinsenpos].z = ptrtri.z;
listTri[twinsenpos].numObj = ptrtri.numObj;
listTri[twinsenpos].type = ptrtri.type;
ptrtri.numObj = OWN_ACTOR_SCENE_INDEX;
ptrtri.type = DrawListType::DrawObject3D;
ptrtri.z = (int16)twinsenz;
twinsenpos = n;
numobj = -1;
break;
}
} else {
// twinsen before
if (twinsenz > ptrtri.z) {
// correct the error
listTri[twinsenpos].z = ptrtri.z;
listTri[twinsenpos].numObj = ptrtri.numObj;
listTri[twinsenpos].type = ptrtri.type;
ptrtri.numObj = OWN_ACTOR_SCENE_INDEX;
ptrtri.type = DrawListType::DrawObject3D;
ptrtri.z = (int16)twinsenz;
twinsenpos = n;
numobj = -1;
break;
}
}
}
}
break;
}
if (numobj == -1) {
break;
}
}
}
void Redraw::processDrawList(DrawListStruct *drawList, int32 drawListPos, bool bgRedraw) {
bool shadowtwinsen = false;
for (int32 pos = 0; pos < drawListPos; ++pos) {
const DrawListStruct &drawCmd = drawList[pos];
const uint32 flags = drawCmd.type;
if (flags == DrawListType::DrawObject3D) {
// this is correcting a bug that came with correctZLevels() - original sources
if (_engine->_cfgfile.ShadowMode != 0 && drawCmd.numObj == OWN_ACTOR_SCENE_INDEX && !shadowtwinsen) {
for (int32 i = pos; i < drawListPos; i++) {
if (drawList[i].numObj == OWN_ACTOR_SCENE_INDEX && drawList[i].type == DrawListType::DrawShadows) {
shadowtwinsen = true;
processDrawListShadows(drawList[i]);
drawList[i].type = (uint32)-1; // invalidate shadow entry
break;
}
}
}
processDrawListActors(drawCmd, bgRedraw);
} else if (flags == DrawListType::DrawShadows && !_engine->_actor->_cropBottomScreen) {
if (drawCmd.numObj == OWN_ACTOR_SCENE_INDEX) {
shadowtwinsen = true;
}
processDrawListShadows(drawCmd);
} else if (flags == DrawListType::DrawActorSprites) {
processDrawListActorSprites(drawCmd, bgRedraw);
} else if (flags == DrawListType::DrawExtras) {
processDrawListExtras(drawCmd);
}
_engine->_interface->unsetClip();
}
}
void Redraw::renderOverlays() {
for (int32 i = 0; i < OVERLAY_MAX_ENTRIES; i++) {
OverlayListStruct *overlay = &overlayList[i];
if (overlay->num == -1) {
continue;
}
// process position overlay
switch (overlay->move) {
case OverlayPosType::koNormal: // wait number of seconds and die
if (_engine->timerRef >= overlay->timerEnd) {
overlay->num = -1;
continue;
}
break;
case OverlayPosType::koFollowActor: { // follow obj coordinates for number of seconds and die
ActorStruct *actor2 = _engine->_scene->getActor(overlay->info);
const IVec3 &projPos = _engine->_renderer->projectPoint(actor2->_posObj.x - _engine->_grid->_worldCube.x, actor2->_posObj.y + actor2->_boundingBox.maxs.y - _engine->_grid->_worldCube.y, actor2->_posObj.z - _engine->_grid->_worldCube.z);
overlay->x = projPos.x;
overlay->y = projPos.y;
if (_engine->timerRef >= overlay->timerEnd) {
overlay->num = -1;
continue;
}
break;
}
}
// process overlay type
switch (overlay->type) {
case OverlayType::koSprite: {
const SpriteData &spritePtr = _engine->_resources->_spriteData[overlay->num];
const int32 spriteWidth = spritePtr.surface().w;
const int32 spriteHeight = spritePtr.surface().h;
const SpriteDim *dim = _engine->_resources->_spriteBoundingBox.dim(overlay->num);
Common::Rect renderRect;
renderRect.left = dim->x + overlay->x;
renderRect.top = dim->y + overlay->y;
renderRect.right = renderRect.left + spriteWidth;
renderRect.bottom = renderRect.top + spriteHeight;
_engine->_grid->drawSprite(renderRect.left, renderRect.top, spritePtr);
addPhysBox(_engine->_interface->_clip);
break;
}
case OverlayType::koNumber: {
char text[10];
snprintf(text, sizeof(text), "%d", overlay->num);
const int32 textLength = _engine->_text->sizeFont(text);
const int32 textHeight = 48;
Common::Rect renderRect;
renderRect.left = overlay->x - (textLength / 2);
renderRect.top = overlay->y - 24;
renderRect.right = overlay->x + (textLength / 2);
renderRect.bottom = overlay->y + textHeight;
_engine->_interface->setClip(renderRect);
_engine->_text->setFontColor(overlay->info);
_engine->_text->drawText(renderRect.left, renderRect.top, text);
addPhysBox(_engine->_interface->_clip);
_engine->_interface->unsetClip();
break;
}
case OverlayType::koNumberRange: {
const int32 range = boundRuleThree(overlay->info, overlay->num, 100, overlay->timerEnd - _engine->timerRef - _engine->toSeconds(1));
char text[10];
Common::sprintf_s(text, "%d", range);
const int32 textLength = _engine->_text->sizeFont(text);
const int32 textHeight = 48;
Common::Rect renderRect;
renderRect.left = overlay->x - (textLength / 2);
renderRect.top = overlay->y - 24;
renderRect.right = overlay->x + (textLength / 2);
renderRect.bottom = overlay->y + textHeight;
_engine->_interface->setClip(renderRect);
_engine->_text->setFontColor(COLOR_GOLD);
_engine->_text->drawText(renderRect.left, renderRect.top, text);
addPhysBox(_engine->_interface->_clip);
_engine->_interface->unsetClip();
break;
}
case OverlayType::koInventoryItem: {
const int32 item = overlay->num;
const Common::Rect rect(10, 10, 79, 79);
_engine->_interface->box(rect, COLOR_BLACK);
_engine->_interface->setClip(rect);
const BodyData &bodyPtr = _engine->_resources->_inventoryTable[item];
_overlayRotation += 1; // overlayRotation += 8;
_engine->_renderer->draw3dObject(40, 40, bodyPtr, _overlayRotation, 16000);
_engine->_menu->drawRectBorders(rect);
addPhysBox(rect);
_engine->_gameState->init3DGame();
_engine->_interface->unsetClip();
break;
}
case OverlayType::koText: {
char text[256];
_engine->_text->getMenuText((TextId)overlay->num, text, sizeof(text));
const int32 textLength = _engine->_text->sizeFont(text);
const int32 textHeight = 48;
Common::Rect renderRect;
renderRect.left = overlay->x - (textLength / 2);
renderRect.top = overlay->y - (textHeight / 2);
renderRect.right = overlay->x + (textLength / 2);
renderRect.bottom = overlay->y + textHeight;
renderRect.clip(_engine->rect());
_engine->_interface->setClip(renderRect);
_engine->_text->setFontColor(_engine->_scene->getActor(overlay->info)->_talkColor);
_engine->_text->drawText(renderRect.left, renderRect.top, text);
addPhysBox(_engine->_interface->_clip);
_engine->_interface->unsetClip();
break;
}
case OverlayType::koSysText:
case OverlayType::koFlash:
case OverlayType::koRain:
case OverlayType::koInventory:
// TODO lba2
case OverlayType::koMax:
break;
}
}
}
void Redraw::setRenderText(const Common::String &text) {
_text = text;
if (_text.empty()) {
_textDisappearTime = -1;
} else {
_textDisappearTime = _engine->timerRef + _engine->toSeconds(1);
}
}
void Redraw::renderText() {
if (_text.empty()) {
return;
}
if (_textDisappearTime != -1 && _engine->timerRef > _textDisappearTime) {
_text.clear();
_textDisappearTime = -1;
return;
}
_engine->_text->setFontColor(COLOR_WHITE);
const int padding = 10;
const int x = padding;
const int height = _engine->_text->lineHeight;
const int y = _engine->height() - height - padding;
const int width = _engine->_text->sizeFont(_text.c_str());
_engine->_text->drawText(x, y, _text.c_str(), true);
_engine->copyBlockPhys(x, y, x + width, y + height);
const Common::Rect redraw(x, y, x + width, y + height);
addPhysBox(redraw);
}
void Redraw::fillBackground(uint8 color) {
_engine->_frontVideoBuffer.fillRect(Common::Rect(0, 0, _engine->width(), _engine->height()), color);
}
void Redraw::drawScene(bool flagflip) { // AffScene
int32 tmp_projPosX = _projPosScreen.x;
int32 tmp_projPosY = _projPosScreen.y;
_engine->_interface->unsetClip();
if (!flagflip) {
clsBoxes();
} else {
_engine->saveTimer(false);
if (_engine->_scene->_newCube != SCENE_CEILING_GRID_FADE_1 && _engine->_scene->_newCube != SCENE_CEILING_GRID_FADE_2) {
_engine->_screens->fadeToBlack(_engine->_screens->_ptrPal);
}
_engine->_grid->redrawGrid();
updateOverlayTypePosition(tmp_projPosX, tmp_projPosY, _projPosScreen.x, _projPosScreen.y);
_engine->saveFrontBuffer();
if (_engine->_scene->_newCube != SCENE_CEILING_GRID_FADE_1 && _engine->_scene->_newCube != SCENE_CEILING_GRID_FADE_2) {
_engine->_screens->fadeToPal(_engine->_screens->_ptrPal);
}
}
DrawListStruct drawList[NUM_MAX_ACTORS + EXTRA_MAX_ENTRIES]; // ListTri[MAX_OBJECTS + MAX_EXTRAS]
int32 drawListPos = fillActorDrawingList(drawList, flagflip);
drawListPos = fillExtraDrawingList(drawList, drawListPos);
_nbPhysBox = 0;
sortDrawingList(drawList, drawListPos);
correctZLevels(drawList, drawListPos);
processDrawList(drawList, drawListPos, flagflip);
_engine->_debugState->renderDebugView();
renderOverlays();
renderText();
_engine->_interface->unsetClip();
// need to be here to fade after drawing all actors in scene
if (_engine->_scene->_newCube == SCENE_CEILING_GRID_FADE_2) {
_engine->_scene->_newCube = SCENE_CEILING_GRID_FADE_1;
}
if (flagflip) {
moveNextAreas();
_engine->restoreTimer();
} else {
flipBoxes();
}
if (_engine->_screens->_flagFade) {
if (_engine->_screens->_flagPalettePcx) {
_engine->_screens->fadeToPal(_engine->_screens->_palettePcx);
} else {
_engine->_screens->fadeToPal(_engine->_screens->_ptrPal);
}
_engine->_screens->_flagFade = false;
}
}
void Redraw::drawBubble(int32 actorIdx) {
ActorStruct *actor = _engine->_scene->getActor(actorIdx);
// get actor position on screen
const IVec3 &projPos = _engine->_renderer->projectPoint(actor->_posObj.x - _engine->_grid->_worldCube.x, actor->_posObj.y + actor->_boundingBox.maxs.y - _engine->_grid->_worldCube.y, actor->_posObj.z - _engine->_grid->_worldCube.z);
if (actorIdx != _bubbleActor) {
_bubbleSpriteIndex = _bubbleSpriteIndex ^ 1;
_bubbleActor = actorIdx;
}
const SpriteData &spritePtr = _engine->_resources->_spriteData[_bubbleSpriteIndex];
const int32 spriteWidth = spritePtr.surface().w;
const int32 spriteHeight = spritePtr.surface().h;
// calculate sprite position on screen
Common::Rect renderRect;
if (_bubbleSpriteIndex == SPRITEHQR_DIAG_BUBBLE_RIGHT) {
renderRect.left = projPos.x + 10;
} else {
renderRect.left = projPos.x - 10 - spriteWidth;
}
renderRect.top = projPos.y - 20;
renderRect.right = spriteWidth + renderRect.left - 1;
renderRect.bottom = spriteHeight + renderRect.top - 1;
if (_engine->_interface->setClip(renderRect)) {
_engine->_grid->drawSprite(renderRect.left, renderRect.top, spritePtr);
_engine->_interface->unsetClip();
}
}
} // namespace TwinE