/* 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 . * */ #include "twine/renderer/renderer.h" #include "common/util.h" #include "twine/menu/interface.h" #include "twine/renderer/redraw.h" #include "twine/renderer/shadeangletab.h" #include "twine/resources/resources.h" #include "twine/scene/actor.h" #include "twine/scene/grid.h" #include "twine/scene/movements.h" #include "twine/shared.h" #include "twine/twine.h" namespace TwinE { #define RENDERTYPE_DRAWLINE 0 #define RENDERTYPE_DRAWPOLYGON 1 #define RENDERTYPE_DRAWSPHERE 2 Renderer::Renderer(TwinEEngine *engine) : _engine(engine) { } Renderer::~Renderer() { free(_tabVerticG); free(_tabVerticD); free(_tabCoulG); free(_tabCoulD); free(_taby0); free(_taby1); } void Renderer::init(int32 w, int32 h) { size_t size = _engine->height() * sizeof(int16); _tabVerticG = (int16 *)malloc(size); memset(_tabVerticG, 0, size); _tabVerticD = (int16 *)malloc(size); memset(_tabVerticD, 0, size); _tabCoulG = (int16 *)malloc(size); memset(_tabCoulG, 0, size); _tabCoulD = (int16 *)malloc(size); memset(_tabCoulD, 0, size); _taby0 = (int16 *)malloc(size); memset(_taby0, 0, size); _taby1 = (int16 *)malloc(size); memset(_taby1, 0, size); _tabx0 = _tabCoulG; _tabx1 = _tabCoulD; } void Renderer::projIso(IVec3 &pos, int32 x, int32 y, int32 z) { pos.x = (int16)((((x - z) * 24) / ISO_SCALE) + _projectionCenter.x); pos.y = (int16)(((((x + z) * 12) - (y * 30)) / ISO_SCALE) + _projectionCenter.y); pos.z = 0; } IVec3 Renderer::projectPoint(int32 cX, int32 cY, int32 cZ) { // ProjettePoint IVec3 pos; if (_typeProj == TYPE_ISO) { projIso(pos, cX, cY, cZ); return pos; } if (_cameraRot.z - cZ < 0) { pos.x = 0; pos.y = 0; pos.z = 0; return pos; } cX -= _cameraRot.x; cY -= _cameraRot.y; cZ = _cameraRot.z - cZ; int32 posZ = cZ + _kFactor; if (posZ <= 0) { posZ = 0x7FFF; } pos.x = (cX * _lFactorX) / posZ + _projectionCenter.x; pos.y = (-cY * _lFactorY) / posZ + _projectionCenter.y; pos.z = posZ; return pos; } void Renderer::setProjection(int32 x, int32 y, int32 kfact, int32 lfactx, int32 lfacty) { _projectionCenter.x = x; _projectionCenter.y = y; _kFactor = kfact; _lFactorX = lfactx; _lFactorY = lfacty; _typeProj = TYPE_3D; } void Renderer::setPosCamera(int32 x, int32 y, int32 z) { _cameraPos.x = x; _cameraPos.y = y; _cameraPos.z = z; } void Renderer::setIsoProjection(int32 x, int32 y, int32 scale) { _projectionCenter.x = x; _projectionCenter.y = y; _projectionCenter.z = scale; // not used - IsoScale is always 512 _typeProj = TYPE_ISO; } void Renderer::flipMatrix() { // FlipMatrice SWAP(_matrixWorld.row1.y, _matrixWorld.row2.x); SWAP(_matrixWorld.row1.z, _matrixWorld.row3.x); SWAP(_matrixWorld.row2.z, _matrixWorld.row3.y); } IVec3 Renderer::setInverseAngleCamera(int32 alpha, int32 beta, int32 gamma) { setAngleCamera(alpha, beta, gamma); flipMatrix(); _cameraRot = longWorldRot(_cameraPos.x, _cameraPos.y, _cameraPos.z); return _cameraRot; } IVec3 Renderer::setAngleCamera(int32 alpha, int32 beta, int32 gamma) { const int32 cAlpha = ClampAngle(alpha); const int32 cAlpha2 = ClampAngle(alpha + LBAAngles::ANGLE_90); const int32 nSin = sinTab[cAlpha]; const int32 nCos = sinTab[cAlpha2]; const int32 cGamma = ClampAngle(gamma); const int32 cGamma2 = ClampAngle(gamma + LBAAngles::ANGLE_90); int32 nSin2 = sinTab[cGamma]; int32 nCos2 = sinTab[cGamma2]; _matrixWorld.row1.x = nCos2; _matrixWorld.row1.y = -nSin2; _matrixWorld.row2.x = (nSin2 * nCos) >> 14; _matrixWorld.row2.y = (nCos2 * nCos) >> 14; _matrixWorld.row3.x = (nSin2 * nSin) >> 14; _matrixWorld.row3.y = (nCos2 * nSin) >> 14; const int32 cBeta = ClampAngle(beta); const int32 cBeta2 = ClampAngle(beta + LBAAngles::ANGLE_90); nSin2 = sinTab[cBeta]; nCos2 = sinTab[cBeta2]; int32 h = _matrixWorld.row1.x; _matrixWorld.row1.x = (nCos2 * h) >> 14; _matrixWorld.row1.z = (nSin2 * h) >> 14; h = _matrixWorld.row2.x; _matrixWorld.row2.x = ((nCos2 * h) + (nSin2 * nSin)) >> 14; _matrixWorld.row2.z = ((nSin2 * h) - (nCos2 * nSin)) >> 14; h = _matrixWorld.row3.x; _matrixWorld.row3.x = ((nCos2 * h) - (nSin2 * nCos)) >> 14; _matrixWorld.row3.z = ((nCos2 * nCos) + (nSin2 * h)) >> 14; _cameraRot = longWorldRot(_cameraPos.x, _cameraPos.y, _cameraPos.z); return _cameraRot; } IVec3 Renderer::worldRotatePoint(const IVec3& vec) { const int32 vx = (_matrixWorld.row1.x * vec.x + _matrixWorld.row1.y * vec.y + _matrixWorld.row1.z * vec.z) / SCENE_SIZE_HALF; const int32 vy = (_matrixWorld.row2.x * vec.x + _matrixWorld.row2.y * vec.y + _matrixWorld.row2.z * vec.z) / SCENE_SIZE_HALF; const int32 vz = (_matrixWorld.row3.x * vec.x + _matrixWorld.row3.y * vec.y + _matrixWorld.row3.z * vec.z) / SCENE_SIZE_HALF; return IVec3(vx, vy, vz); } IVec3 Renderer::longWorldRot(int32 x, int32 y, int32 z) { const int64 vx = ((int64)_matrixWorld.row1.x * (int64)x + (int64)_matrixWorld.row1.y * (int64)y + (int64)_matrixWorld.row1.z * (int64)z) / SCENE_SIZE_HALF; const int64 vy = ((int64)_matrixWorld.row2.x * (int64)x + (int64)_matrixWorld.row2.y * (int64)y + (int64)_matrixWorld.row2.z * (int64)z) / SCENE_SIZE_HALF; const int64 vz = ((int64)_matrixWorld.row3.x * (int64)x + (int64)_matrixWorld.row3.y * (int64)y + (int64)_matrixWorld.row3.z * (int64)z) / SCENE_SIZE_HALF; return IVec3((int32)vx, (int32)vy, (int32)vz); } IVec3 Renderer::longInverseRot(int32 x, int32 y, int32 z) { const int64 vx = ((int64)_matrixWorld.row1.x * (int64)x + (int64)_matrixWorld.row2.x * (int64)y + (int64)_matrixWorld.row3.x * (int64)z) / SCENE_SIZE_HALF; const int64 vy = ((int64)_matrixWorld.row1.y * (int64)x + (int64)_matrixWorld.row2.y * (int64)y + (int64)_matrixWorld.row3.y * (int64)z) / SCENE_SIZE_HALF; const int64 vz = ((int64)_matrixWorld.row1.z * (int64)x + (int64)_matrixWorld.row2.z * (int64)y + (int64)_matrixWorld.row3.z * (int64)z) / SCENE_SIZE_HALF; return IVec3((int32)vx, (int32)vy, (int32)vz); } IVec3 Renderer::rot(const IMatrix3x3 &matrix, int32 x, int32 y, int32 z) { const int32 vx = (matrix.row1.x * x + matrix.row1.y * y + matrix.row1.z * z) / SCENE_SIZE_HALF; const int32 vy = (matrix.row2.x * x + matrix.row2.y * y + matrix.row2.z * z) / SCENE_SIZE_HALF; const int32 vz = (matrix.row3.x * x + matrix.row3.y * y + matrix.row3.z * z) / SCENE_SIZE_HALF; return IVec3(vx, vy, vz); } void Renderer::setFollowCamera(int32 targetX, int32 targetY, int32 targetZ, int32 cameraAlpha, int32 cameraBeta, int32 cameraGamma, int32 cameraZoom) { _cameraPos.x = targetX; _cameraPos.y = targetY; _cameraPos.z = targetZ; setAngleCamera(cameraAlpha, cameraBeta, cameraGamma); _cameraRot.z += cameraZoom; _cameraPos = longInverseRot(_cameraRot.x, _cameraRot.y, _cameraRot.z); } IVec2 Renderer::rotate(int32 side, int32 forward, int32 angle) const { if (angle) { const int32 nSin = sinTab[ClampAngle(angle)]; const int32 nCos = sinTab[ClampAngle((angle + LBAAngles::ANGLE_90))]; const int32 x0 = ((side * nCos) + (forward * nSin)) >> 14; const int32 y0 = ((forward * nCos) - (side * nSin)) >> 14; return IVec2(x0, y0); } return IVec2(side, forward); } void Renderer::rotMatIndex2(IMatrix3x3 *pDest, const IMatrix3x3 *pSrc, const IVec3 &angleVec) { IMatrix3x3 tmp; const int32 lAlpha = angleVec.x; const int32 lBeta = angleVec.y; const int32 lGamma = angleVec.z; if (lAlpha) { int32 nSin = sinTab[ClampAngle(lAlpha)]; int32 nCos = sinTab[ClampAngle(lAlpha + LBAAngles::ANGLE_90)]; pDest->row1.x = pSrc->row1.x; pDest->row2.x = pSrc->row2.x; pDest->row3.x = pSrc->row3.x; pDest->row1.y = (pSrc->row1.z * nSin + pSrc->row1.y * nCos) / SCENE_SIZE_HALF; pDest->row1.z = (pSrc->row1.z * nCos - pSrc->row1.y * nSin) / SCENE_SIZE_HALF; pDest->row2.y = (pSrc->row2.z * nSin + pSrc->row2.y * nCos) / SCENE_SIZE_HALF; pDest->row2.z = (pSrc->row2.z * nCos - pSrc->row2.y * nSin) / SCENE_SIZE_HALF; pDest->row3.y = (pSrc->row3.z * nSin + pSrc->row3.y * nCos) / SCENE_SIZE_HALF; pDest->row3.z = (pSrc->row3.z * nCos - pSrc->row3.y * nSin) / SCENE_SIZE_HALF; pSrc = pDest; } if (lGamma) { int32 nSin = sinTab[ClampAngle(lGamma)]; int32 nCos = sinTab[ClampAngle(lGamma + LBAAngles::ANGLE_90)]; tmp.row1.z = pSrc->row1.z; tmp.row2.z = pSrc->row2.z; tmp.row3.z = pSrc->row3.z; tmp.row1.x = (pSrc->row1.y * nSin + pSrc->row1.x * nCos) / SCENE_SIZE_HALF; tmp.row1.y = (pSrc->row1.y * nCos - pSrc->row1.x * nSin) / SCENE_SIZE_HALF; tmp.row2.x = (pSrc->row2.y * nSin + pSrc->row2.x * nCos) / SCENE_SIZE_HALF; tmp.row2.y = (pSrc->row2.y * nCos - pSrc->row2.x * nSin) / SCENE_SIZE_HALF; tmp.row3.x = (pSrc->row3.y * nSin + pSrc->row3.x * nCos) / SCENE_SIZE_HALF; tmp.row3.y = (pSrc->row3.y * nCos - pSrc->row3.x * nSin) / SCENE_SIZE_HALF; pSrc = &tmp; } if (lBeta) { int32 nSin = sinTab[ClampAngle(lBeta)]; int32 nCos = sinTab[ClampAngle(lBeta + LBAAngles::ANGLE_90)]; if (pSrc == pDest) { tmp.row1.x = pSrc->row1.x; tmp.row1.z = pSrc->row1.z; tmp.row2.x = pSrc->row2.x; tmp.row2.z = pSrc->row2.z; tmp.row3.x = pSrc->row3.x; tmp.row3.z = pSrc->row3.z; pSrc = &tmp; } else { pDest->row1.y = pSrc->row1.y; pDest->row2.y = pSrc->row2.y; pDest->row3.y = pSrc->row3.y; } pDest->row1.x = (pSrc->row1.x * nCos - pSrc->row1.z * nSin) / SCENE_SIZE_HALF; pDest->row1.z = (pSrc->row1.x * nSin + pSrc->row1.z * nCos) / SCENE_SIZE_HALF; pDest->row2.x = (pSrc->row2.x * nCos - pSrc->row2.z * nSin) / SCENE_SIZE_HALF; pDest->row2.z = (pSrc->row2.x * nSin + pSrc->row2.z * nCos) / SCENE_SIZE_HALF; pDest->row3.x = (pSrc->row3.x * nCos - pSrc->row3.z * nSin) / SCENE_SIZE_HALF; pDest->row3.z = (pSrc->row3.x * nSin + pSrc->row3.z * nCos) / SCENE_SIZE_HALF; } else if (pSrc != pDest) { *pDest = *pSrc; } } bool isPolygonVisible(const ComputedVertex *vertices) { // TestVuePoly const int32 a = ((int32)vertices[0].y - (int32)vertices[2].y) * ((int32)vertices[1].x - (int32)vertices[0].x); const int32 b = ((int32)vertices[1].y - (int32)vertices[0].y) * ((int32)vertices[0].x - (int32)vertices[2].x); if (a <= b) { return false; } return true; } void Renderer::rotList(const Common::Array &vertices, int32 firstPoint, int32 numPoints, I16Vec3 *destPoints, const IMatrix3x3 *rotationMatrix, const IVec3 &destPos) { for (int32 i = 0; i < numPoints; ++i) { const BodyVertex &vertex = vertices[i + firstPoint]; destPoints->x = (int16)(((rotationMatrix->row1.x * vertex.x + rotationMatrix->row1.y * vertex.y + rotationMatrix->row1.z * vertex.z) / SCENE_SIZE_HALF) + destPos.x); destPoints->y = (int16)(((rotationMatrix->row2.x * vertex.x + rotationMatrix->row2.y * vertex.y + rotationMatrix->row2.z * vertex.z) / SCENE_SIZE_HALF) + destPos.y); destPoints->z = (int16)(((rotationMatrix->row3.x * vertex.x + rotationMatrix->row3.y * vertex.y + rotationMatrix->row3.z * vertex.z) / SCENE_SIZE_HALF) + destPos.z); destPoints++; } } // RotateGroupe void Renderer::processRotatedElement(IMatrix3x3 *targetMatrix, const Common::Array &vertices, int32 alpha, int32 beta, int32 gamma, const BodyBone &bone, ModelData *modelData) { const int32 firstPoint = bone.firstVertex; const int32 numOfPoints = bone.numVertices; const IVec3 renderAngle(alpha, beta, gamma); const IMatrix3x3 *currentMatrix; IVec3 destPos; // if its the first point if (bone.isRoot()) { currentMatrix = &_matrixWorld; } else { const int32 pointIdx = bone.vertex; const int32 matrixIndex = bone.parent; assert(matrixIndex >= 0 && matrixIndex < ARRAYSIZE(_matricesTable)); currentMatrix = &_matricesTable[matrixIndex]; destPos = modelData->computedPoints[pointIdx]; } rotMatIndex2(targetMatrix, currentMatrix, renderAngle); if (!numOfPoints) { warning("RENDER WARNING: No points in this model!"); } rotList(vertices, firstPoint, numOfPoints, &modelData->computedPoints[firstPoint], targetMatrix, destPos); } void Renderer::transRotList(const Common::Array &vertices, int32 firstPoint, int32 numPoints, I16Vec3 *destPoints, const IMatrix3x3 *translationMatrix, const IVec3 &angleVec, const IVec3 &destPos) { for (int32 i = 0; i < numPoints; ++i) { const BodyVertex &vertex = vertices[i + firstPoint]; const int16 tmpX = (int16)(vertex.x + angleVec.x); const int16 tmpY = (int16)(vertex.y + angleVec.y); const int16 tmpZ = (int16)(vertex.z + angleVec.z); destPoints->x = ((translationMatrix->row1.x * tmpX + translationMatrix->row1.y * tmpY + translationMatrix->row1.z * tmpZ) / SCENE_SIZE_HALF) + destPos.x; destPoints->y = ((translationMatrix->row2.x * tmpX + translationMatrix->row2.y * tmpY + translationMatrix->row2.z * tmpZ) / SCENE_SIZE_HALF) + destPos.y; destPoints->z = ((translationMatrix->row3.x * tmpX + translationMatrix->row3.y * tmpY + translationMatrix->row3.z * tmpZ) / SCENE_SIZE_HALF) + destPos.z; destPoints++; } } // TranslateGroupe void Renderer::translateGroup(IMatrix3x3 *targetMatrix, const Common::Array &vertices, int32 rotX, int32 rotY, int32 rotZ, const BodyBone &bone, ModelData *modelData) { IVec3 renderAngle; renderAngle.x = rotX; renderAngle.y = rotY; renderAngle.z = rotZ; IVec3 destPos; if (bone.isRoot()) { // base point *targetMatrix = _matrixWorld; } else { // dependent const int32 pointsIdx = bone.vertex; destPos = modelData->computedPoints[pointsIdx]; const int32 matrixIndex = bone.parent; assert(matrixIndex >= 0 && matrixIndex < ARRAYSIZE(_matricesTable)); *targetMatrix = _matricesTable[matrixIndex]; } transRotList(vertices, bone.firstVertex, bone.numVertices, &modelData->computedPoints[bone.firstVertex], targetMatrix, renderAngle, destPos); } void Renderer::setLightVector(int32 angleX, int32 angleY, int32 angleZ) { const int32 normalUnit = 64; const IVec3 renderAngle(angleX, angleY, angleZ); IMatrix3x3 rotationMatrix; rotMatIndex2(&rotationMatrix, &_matrixWorld, renderAngle); _normalLight = rot(rotationMatrix, 0, 0, normalUnit - 5); } int16 Renderer::leftClip(int16 polyRenderType, ComputedVertex** offTabPoly, int32 numVertices) { const Common::Rect &clip = _engine->_interface->_clip; ComputedVertex *pTabPolyClip = offTabPoly[1]; ComputedVertex *pTabPoly = offTabPoly[0]; int16 newNbPoints = 0; // invert the pointers to continue on the clipped vertices in the next method offTabPoly[0] = pTabPolyClip; offTabPoly[1] = pTabPoly; for (; numVertices > 0; --numVertices, pTabPoly++) { const ComputedVertex *p0 = pTabPoly; const ComputedVertex *p1 = p0 + 1; // clipFlag : // 0x00 : none clipped // 0x01 : point 0 clipped // 0x02 : point 1 clipped // 0x03 : both clipped uint8 clipFlag = (p1->x < clip.left) ? 2 : 0; if (p0->x < clip.left) { if (clipFlag) { continue; // both clipped, skip point 0 } clipFlag |= 1; } else { // point 0 not clipped, store it *pTabPolyClip++ = *pTabPoly; ++newNbPoints; } if (clipFlag) { // point 0 or 1 is clipped, apply clipping if (p1->x >= p0->x) { p0 = p1; p1 = pTabPoly; } const int32 dx = p1->x - p0->x; const int32 dy = p1->y - p0->y; const int32 dxClip = clip.left - p0->x; pTabPolyClip->y = (int16)(p0->y + ((dxClip * dy) / dx)); pTabPolyClip->x = (int16)clip.left; if (polyRenderType >= POLYGONTYPE_GOURAUD) { pTabPolyClip->intensity = (int16)(p0->intensity + (((p1->intensity - p0->intensity) * dxClip) / dx)); } ++pTabPolyClip; ++newNbPoints; } } // copy first vertex to the end *pTabPolyClip = *offTabPoly[0]; return newNbPoints; } int16 Renderer::rightClip(int16 polyRenderType, ComputedVertex** offTabPoly, int32 numVertices) { const Common::Rect &clip = _engine->_interface->_clip; ComputedVertex *pTabPolyClip = offTabPoly[1]; ComputedVertex *pTabPoly = offTabPoly[0]; int16 newNbPoints = 0; // invert the pointers to continue on the clipped vertices in the next method offTabPoly[0] = pTabPolyClip; offTabPoly[1] = pTabPoly; for (; numVertices > 0; --numVertices, pTabPoly++) { const ComputedVertex *p0 = pTabPoly; const ComputedVertex *p1 = p0 + 1; // clipFlag : // 0x00 : none clipped // 0x01 : point 0 clipped // 0x02 : point 1 clipped // 0x03 : both clipped uint8 clipFlag = (p1->x > clip.right) ? 2 : 0; if (p0->x > clip.right) { if (clipFlag) { continue; // both clipped, skip point 0 } clipFlag |= 1; } else { // point 0 not clipped, store it *pTabPolyClip++ = *pTabPoly; ++newNbPoints; } if (clipFlag) { // point 0 or 1 is clipped, apply clipping if (p1->x >= p0->x) { p0 = p1; p1 = pTabPoly; } const int32 dx = p1->x - p0->x; const int32 dy = p1->y - p0->y; const int32 dxClip = clip.right - p0->x; pTabPolyClip->y = (int16)(p0->y + ((dxClip * dy) / dx)); pTabPolyClip->x = (int16)clip.right; if (polyRenderType >= POLYGONTYPE_GOURAUD) { pTabPolyClip->intensity = (int16)(p0->intensity + (((p1->intensity - p0->intensity) * dxClip) / dx)); } ++pTabPolyClip; ++newNbPoints; } } // copy first vertex to the end *pTabPolyClip = *offTabPoly[0]; return newNbPoints; } int16 Renderer::topClip(int16 polyRenderType, ComputedVertex** offTabPoly, int32 numVertices) { const Common::Rect &clip = _engine->_interface->_clip; ComputedVertex *pTabPolyClip = offTabPoly[1]; ComputedVertex *pTabPoly = offTabPoly[0]; int16 newNbPoints = 0; // invert the pointers to continue on the clipped vertices in the next method offTabPoly[0] = pTabPolyClip; offTabPoly[1] = pTabPoly; for (; numVertices > 0; --numVertices, pTabPoly++) { const ComputedVertex *p0 = pTabPoly; const ComputedVertex *p1 = p0 + 1; // clipFlag : // 0x00 : none clipped // 0x01 : point 0 clipped // 0x02 : point 1 clipped // 0x03 : both clipped uint8 clipFlag = (p1->y < clip.top) ? 2 : 0; if (p0->y < clip.top) { if (clipFlag) { continue; // both clipped, skip point 0 } clipFlag |= 1; } else { // point 0 not clipped, store it *pTabPolyClip++ = *pTabPoly; ++newNbPoints; } if (clipFlag) { // point 0 or 1 is clipped, apply clipping if (p1->y >= p0->y) { p0 = p1; p1 = pTabPoly; } const int32 dx = p1->x - p0->x; const int32 dy = p1->y - p0->y; const int32 dyClip = clip.top - p0->y; pTabPolyClip->x = (int16)(p0->x + ((dyClip * dx) / dy)); pTabPolyClip->y = (int16)clip.top; if (polyRenderType >= POLYGONTYPE_GOURAUD) { pTabPolyClip->intensity = (int16)(p0->intensity + (((p1->intensity - p0->intensity) * dyClip) / dy)); } ++pTabPolyClip; ++newNbPoints; } } // copy first vertex to the end *pTabPolyClip = *offTabPoly[0]; return newNbPoints; } int16 Renderer::bottomClip(int16 polyRenderType, ComputedVertex** offTabPoly, int32 numVertices) { const Common::Rect &clip = _engine->_interface->_clip; ComputedVertex *pTabPolyClip = offTabPoly[1]; ComputedVertex *pTabPoly = offTabPoly[0]; int16 newNbPoints = 0; // invert the pointers to continue on the clipped vertices in the next method offTabPoly[0] = pTabPolyClip; offTabPoly[1] = pTabPoly; for (; numVertices > 0; --numVertices, pTabPoly++) { const ComputedVertex *p0 = pTabPoly; const ComputedVertex *p1 = p0 + 1; // clipFlag : // 0x00 : none clipped // 0x01 : point 0 clipped // 0x02 : point 1 clipped // 0x03 : both clipped uint8 clipFlag = (p1->y > clip.bottom) ? 2 : 0; if (p0->y > clip.bottom) { if (clipFlag) { continue; // both clipped, skip point 0 } clipFlag |= 1; } else { // point 0 not clipped, store it *pTabPolyClip++ = *pTabPoly; ++newNbPoints; } if (clipFlag) { // point 0 or 1 is clipped, apply clipping if (p1->y >= p0->y) { p0 = p1; p1 = pTabPoly; } const int32 dx = p1->x - p0->x; const int32 dy = p1->y - p0->y; const int32 dyClip = clip.bottom - p0->y; pTabPolyClip->x = (int16)(p0->x + ((dyClip * dx) / dy)); pTabPolyClip->y = (int16)clip.bottom; if (polyRenderType >= POLYGONTYPE_GOURAUD) { pTabPolyClip->intensity = (int16)(p0->intensity + (((p1->intensity - p0->intensity) * dyClip) / dy)); } ++pTabPolyClip; ++newNbPoints; } } // copy first vertex to the end *pTabPolyClip = *offTabPoly[0]; return newNbPoints; } int32 Renderer::computePolyMinMax(int16 polyRenderType, ComputedVertex **offTabPoly, int32 numVertices, int16 &ymin, int16 &ymax) { int16 xmin = SCENE_SIZE_MAX; int16 xmax = SCENE_SIZE_MIN; ymin = SCENE_SIZE_MAX; ymax = SCENE_SIZE_MIN; ComputedVertex* pTabPoly = offTabPoly[0]; for (int32 i = 0; i < numVertices; i++) { if (pTabPoly[i].x < xmin) { xmin = pTabPoly[i].x; } if (pTabPoly[i].x > xmax) { xmax = pTabPoly[i].x; } if (pTabPoly[i].y < ymin) { ymin = pTabPoly[i].y; } if (pTabPoly[i].y > ymax) { ymax = pTabPoly[i].y; } } const Common::Rect &clip = _engine->_interface->_clip; // no vertices if (ymin > ymax || xmax < clip.left || xmin > clip.right || ymax < clip.top || ymin > clip.bottom) { debug(10, "Clipped %i:%i:%i:%i, clip rect(%i:%i:%i:%i)", xmin, ymin, xmax, ymax, clip.left, clip.top, clip.right, clip.bottom); return 0; } pTabPoly[numVertices] = *offTabPoly[0]; bool hasBeenClipped = false; int32 clippedNumVertices = numVertices; if (xmin < clip.left) { clippedNumVertices = leftClip(polyRenderType, offTabPoly, clippedNumVertices); if (!clippedNumVertices) { return 0; } hasBeenClipped = true; } if (xmax > clip.right) { clippedNumVertices = rightClip(polyRenderType, offTabPoly, clippedNumVertices); if (!clippedNumVertices) { return 0; } hasBeenClipped = true; } if (ymin < clip.top) { clippedNumVertices = topClip(polyRenderType, offTabPoly, clippedNumVertices); if (!clippedNumVertices) { return 0; } hasBeenClipped = true; } if (ymax > clip.bottom) { clippedNumVertices = bottomClip(polyRenderType, offTabPoly, clippedNumVertices); if (!clippedNumVertices) { return 0; } hasBeenClipped = true; } if (hasBeenClipped) { // search the new Ymin or Ymax ymin = 32767; ymax = -32768; for (int32 n = 0; n < clippedNumVertices; ++n) { if (offTabPoly[0][n].y < ymin) { ymin = offTabPoly[0][n].y; } if (offTabPoly[0][n].y > ymax) { ymax = offTabPoly[0][n].y; } } if (ymin >= ymax) { return 0; // No valid polygon after clipping } } return clippedNumVertices; } bool Renderer::computePoly(int16 polyRenderType, const ComputedVertex *vertices, int32 numVertices, int16 &vtop, int16 &vbottom) { assert(numVertices < ARRAYSIZE(_clippedPolygonVertices1)); for (int i = 0; i < numVertices; ++i) { _clippedPolygonVertices1[i] = vertices[i]; } ComputedVertex *offTabPoly[] = {_clippedPolygonVertices1, _clippedPolygonVertices2}; numVertices = computePolyMinMax(polyRenderType, offTabPoly, numVertices, vtop, vbottom); if (numVertices == 0) { return false; } ComputedVertex *pTabPoly = offTabPoly[0]; ComputedVertex *p0; ComputedVertex *p1; int16 *pVertic = nullptr; int16 *pCoul; int32 incY = -1; int32 dx, dy, x, y, dc; int32 step, reminder; // Drawing lines between vertices for (; numVertices > 0; --numVertices, pTabPoly++) { pCoul = nullptr; p0 = pTabPoly; p1 = p0 + 1; dy = p1->y - p0->y; if (dy == 0) { // forget same Y points continue; } else if (dy > 0) { // Y therefore goes down left buffer if (p0->x <= p1->x) { incY = 1; } else { p0 = p1; p1 = pTabPoly; incY = -1; } pVertic = &_tabVerticG[p0->y]; if (polyRenderType >= POLYGONTYPE_GOURAUD) { pCoul = &_tabCoulG[p0->y]; } } else if (dy < 0) { dy = -dy; if (p0->x <= p1->x) { p0 = p1; p1 = pTabPoly; incY = 1; } else { incY = -1; } pVertic = &_tabVerticD[p0->y]; if (polyRenderType >= POLYGONTYPE_GOURAUD) { pCoul = &_tabCoulD[p0->y]; } } dx = (p1->x - p0->x) << 16; step = dx / dy; reminder = ((dx % dy) >> 1) + 0x7FFF; dx = step >> 16; // recovery part high division (entire) step &= 0xFFFF; // preserves lower part (mantissa) x = p0->x; for (y = dy; y >= 0; --y) { *pVertic = (int16)x; pVertic += incY; x += dx; reminder += step; if (reminder & 0xFFFF0000) { x += reminder >> 16; reminder &= 0xFFFF; } } if (pCoul) { dc = (p1->intensity - p0->intensity) << 8; step = dc / dy; reminder = ((((dc % dy) >> 1) + 0x7F) & 0xFF) | (p0->intensity << 8); for (y = dy; y >= 0; --y) { *pCoul = (int16)reminder; pCoul += incY; reminder += step; } } } return true; } void Renderer::svgaPolyCopper(int16 vtop, int16 Ymax, uint16 color) const { const int screenWidth = _engine->width(); int16 xMin, xMax; int16 y = vtop; byte *pDestLine = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(0, y); byte *pDest; int16 *pVerticG = &_tabVerticG[y]; int16 *pVerticD = &_tabVerticD[y]; int32 sens = 1; for (; y <= Ymax; y++) { xMin = *pVerticG++; xMax = *pVerticD++; pDest = pDestLine + xMin; for (; xMin <= xMax; xMin++) { *pDest++ = (byte)color; } color += sens; if (!(color & 0xF)) { sens = -sens; if (sens < 0) { color += sens; } } pDestLine += screenWidth; } } void Renderer::svgaPolyBopper(int16 vtop, int16 Ymax, uint16 color) const { const int screenWidth = _engine->width(); int16 xMin, xMax; int16 y = vtop; byte *pDestLine = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(0, y); byte *pDest; int16 *pVerticG = &_tabVerticG[y]; int16 *pVerticD = &_tabVerticD[y]; int32 sens = 1; int32 line = 2; for (; y <= Ymax; y++) { xMin = *pVerticG++; xMax = *pVerticD++; pDest = pDestLine + xMin; for (; xMin <= xMax; xMin++) { *pDest++ = (byte)color; } line--; if (!line) { line = 2; color += sens; if (!(color & 0xF)) { sens = -sens; if (sens < 0) { color += sens; } } } pDestLine += screenWidth; } } void Renderer::svgaPolyTriste(int16 vtop, int16 Ymax, uint16 color) const { const int screenWidth = _engine->width(); int16 xMin, xMax; int16 y = vtop; byte *pDestLine = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(0, vtop); byte *pDest; int16 *pVerticG = &_tabVerticG[y]; int16 *pVerticD = &_tabVerticD[y]; for (; y <= Ymax; y++) { xMin = *pVerticG++; xMax = *pVerticD++; pDest = pDestLine + xMin; for (; xMin <= xMax; xMin++) { *pDest++ = (byte)color; } pDestLine += screenWidth; } } #define ROL8(x,b) (byte)(((x) << (b)) | ((x) >> (8 - (b)))) #define ROL16(x, b) (((x) << (b)) | ((x) >> (16 - (b)))) void Renderer::svgaPolyTele(int16 vtop, int16 Ymax, uint16 color) const { const int screenWidth = _engine->width(); int16 xMin, xMax; int16 y = vtop; int16 acc = 17371; byte *pDestLine = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(0, vtop); byte *pDest; int16 *pVerticG = &_tabVerticG[y]; int16 *pVerticD = &_tabVerticD[y]; uint16 col; color &= 0xFF; for (; y <= Ymax; y++) { xMin = *pVerticG++; xMax = *pVerticD++; pDest = pDestLine + xMin; col = xMin; for (; xMin <= xMax; xMin++) { col = ((col + acc) & 0xFF03) + (uint16)color; acc = ROL16(acc, 2) + 1; *pDest++ = (byte)col; } pDestLine += screenWidth; } } void Renderer::svgaPolyTrans(int16 vtop, int16 Ymax, uint16 color) const { const int screenWidth = _engine->width(); int16 xMin, xMax; int16 y = vtop; byte *pDestLine = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(0, vtop); byte *pDest; int16 *pVerticG = &_tabVerticG[y]; int16 *pVerticD = &_tabVerticD[y]; color &= 0xF0; for (; y <= Ymax; y++) { xMin = *pVerticG++; xMax = *pVerticD++; pDest = pDestLine + xMin; for (; xMin <= xMax; xMin++) { *pDest = (byte)color | (*pDest & 0x0F); pDest++; } pDestLine += screenWidth; } } // Used e.g for the legs of the horse or the ears of most characters void Renderer::svgaPolyTrame(int16 vtop, int16 Ymax, uint16 color) const { const int screenWidth = _engine->width(); int16 xMin, xMax; int16 y = vtop; byte *pDestLine = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(0, vtop); byte *pDest; int16 *pVerticG = &_tabVerticG[y]; int16 *pVerticD = &_tabVerticD[y]; int32 pair = 0; for (; y <= Ymax; y++) { xMin = *pVerticG++; xMax = *pVerticD++; pDest = pDestLine + xMin; xMax = ((xMax - xMin) + 1) / 2; if (xMax > 0) { pair ^= 1; // paire/impair if ((xMin & 1) ^ pair) { pDest++; } for (; xMax > 0; xMax--) { *pDest = (byte)color; pDest += 2; } } pDestLine += screenWidth; } } void Renderer::svgaPolyGouraud(int16 vtop, int16 Ymax) const { const int screenWidth = _engine->width(); int16 xMin, xMax; int16 y = vtop; int16 start, end, step; byte *pDestLine = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(0, y); byte *pDest; int16 *pVerticG = &_tabVerticG[y]; int16 *pVerticD = &_tabVerticD[y]; int16 *pCoulG = &_tabCoulG[y]; int16 *pCoulD = &_tabCoulD[y]; for (; y <= Ymax; y++) { xMin = *pVerticG++; xMax = *pVerticD++; start = *pCoulG++; end = *pCoulD++; pDest = pDestLine + xMin; xMax -= xMin; if (xMax == 0) { *pDest = (byte)((end + start) >> 9); } else if (xMax <= 2) { pDest[xMax--] = (byte)(end >> 8); if (xMax) { pDest[xMax--] = (byte)((end + start) >> 9); } *pDest = (byte)(start >> 8); } else { step = (end - start) / xMax; for (; xMax >= 0; xMax--) { *pDest++ = (byte)(start >> 8); start += step; } } pDestLine += screenWidth; } } // used for the most of the heads of the characters and the horse body void Renderer::svgaPolyDith(int16 vtop, int16 Ymax) const { const int screenWidth = _engine->width(); int16 xMin, xMax; int16 y = vtop; int16 start, end, step, delta, impair; byte *pDestLine = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(0, y); byte *pDest; int16 *pVerticG = &_tabVerticG[y]; int16 *pVerticD = &_tabVerticD[y]; int16 *pCoulG = &_tabCoulG[y]; int16 *pCoulD = &_tabCoulD[y]; for (; y <= Ymax; y++) { xMin = *pVerticG++; xMax = *pVerticD++; start = *pCoulG++; end = *pCoulD++; pDest = pDestLine + xMin; xMax -= xMin; delta = end - start; if (xMax == 0) { // rcr ax,1 *pDest = (byte)(((int32)end + start) >> 9); } else if (xMax <= 2) { step = start; if (xMax == 2) // if( !(xMax & 1) ) { delta = (delta >> 1) | (delta & 0x8000); // sar ax,1 step &= 0xFF; step = start + ROL8(step, 1); *pDest++ = (byte)(step >> 8); start += delta; } step = start + (step & 0xFF); *pDest++ = (byte)(step >> 8); start += delta; step &= 0xFF; step = start + ROL8(step, 1); *pDest = (byte)(step >> 8); } else { delta /= xMax; step = start; impair = xMax & 1; xMax = (xMax + 1) >> 1; if (!impair) { step &= 0xFF; step = start + ROL8(step, xMax & 7); *pDest++ = (byte)(step >> 8); start += delta; } for (; xMax > 0; xMax--) { step &= 0xFF; step += start; *pDest++ = (byte)(step >> 8); start += delta; step &= 0xFF; step = start + ROL8(step, xMax & 7); *pDest++ = (byte)(step >> 8); start += delta; } } pDestLine += screenWidth; } } void Renderer::svgaPolyMarbre(int16 vtop, int16 Ymax, uint16 color) const { const int screenWidth = _engine->width(); int16 xMin, xMax; int16 y = vtop; byte *pDestLine = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(0, y); byte *pDest; int16 *pVerticG = &_tabVerticG[y]; int16 *pVerticD = &_tabVerticD[y]; // color contains 2 colors: 0xFF start, 0xFF00 end uint16 start = (color & 0xFF) << 8; uint16 end = color & 0xFF00; uint16 delta = end - start + 1; // delta intensity int32 step, dc; for (; y <= Ymax; y++) { xMin = *pVerticG++; xMax = *pVerticD++; pDest = pDestLine + xMin; dc = xMax - xMin; if (dc == 0) { // just one *pDest++ = (byte)(end >> 8); } else if (dc > 0) { step = delta / (dc + 1); color = start; for (; xMin <= xMax; xMin++) { *pDest++ = (byte)(color >> 8); color += step; } } pDestLine += screenWidth; } } void Renderer::svgaPolyTriche(int16 vtop, int16 Ymax, uint16 color) const { const int screenWidth = _engine->width(); int16 xMin, xMax; int16 y = vtop; byte *pDestLine = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(0, y); byte *pDest; int16 *pVerticG = &_tabVerticG[y]; int16 *pVerticD = &_tabVerticD[y]; int16 *pCoulG = &_tabCoulG[y]; for (; y <= Ymax; y++) { xMin = *pVerticG++; xMax = *pVerticD++; pDest = pDestLine + xMin; color = (*pCoulG++) >> 8; for (; xMin <= xMax; xMin++) { *pDest++ = (byte)color; } pDestLine += screenWidth; } } void Renderer::renderPolygons(const CmdRenderPolygon &polygon, ComputedVertex *vertices) { int16 vtop, vbottom; uint8 renderType = polygon.renderType; if (computePoly(renderType, vertices, polygon.numVertices, vtop, vbottom)) { fillVertices(vtop, vbottom, renderType, polygon.colorIndex); } } void Renderer::fillVertices(int16 vtop, int16 vbottom, uint8 renderType, uint16 color) { switch (renderType) { case POLYGONTYPE_FLAT: svgaPolyTriste(vtop, vbottom, color); break; case POLYGONTYPE_TELE: if (_engine->_cfgfile.PolygonDetails == 0) { svgaPolyTriste(vtop, vbottom, color); } else { svgaPolyTele(vtop, vbottom, color); } break; case POLYGONTYPE_COPPER: svgaPolyCopper(vtop, vbottom, color); break; case POLYGONTYPE_BOPPER: svgaPolyBopper(vtop, vbottom, color); break; case POLYGONTYPE_TRANS: svgaPolyTrans(vtop, vbottom, color); break; case POLYGONTYPE_TRAME: // raster svgaPolyTrame(vtop, vbottom, color); break; case POLYGONTYPE_GOURAUD: if (_engine->_cfgfile.PolygonDetails == 0) { svgaPolyTriche(vtop, vbottom, color); } else { svgaPolyGouraud(vtop, vbottom); } break; case POLYGONTYPE_DITHER: if (_engine->_cfgfile.PolygonDetails == 0) { svgaPolyTriche(vtop, vbottom, color); } else if (_engine->_cfgfile.PolygonDetails == 1) { svgaPolyGouraud(vtop, vbottom); } else { svgaPolyDith(vtop, vbottom); } break; case POLYGONTYPE_MARBLE: svgaPolyMarbre(vtop, vbottom, color); break; default: warning("RENDER WARNING: Unsupported render type %d", renderType); break; } } bool Renderer::computeSphere(int32 x, int32 y, int32 radius, int &vtop, int &vbottom) { if (radius <= 0) { return false; } int16 left = (int16)(x - radius); int16 right = (int16)(x + radius); int16 bottom = (int16)(y + radius); int16 top = (int16)(y - radius); const Common::Rect &clip = _engine->_interface->_clip; int16 cleft = clip.left; int16 cright = clip.right; int16 ctop = clip.top; int16 cbottom = clip.bottom; if (left <= cright && right >= cleft && bottom <= cbottom && top >= ctop) { if (left < cleft) { left = cleft; } if (bottom > cbottom) { bottom = cbottom; } if (right > cright) { right = cright; } if (top < ctop) { top = ctop; } int32 r = 0; int32 acc = -radius; while (r <= radius) { int32 x1 = x - radius; if (x1 < cleft) { x1 = cleft; } int32 x2 = x + radius; if (x2 > cright) { x2 = cright; } int32 ny = y - r; if ((ny >= ctop) && (ny <= cbottom)) { _tabVerticG[ny] = (int16)x1; _tabVerticD[ny] = (int16)x2; } ny = y + r; if ((ny >= ctop) && (ny <= cbottom)) { _tabVerticG[ny] = (int16)x1; _tabVerticD[ny] = (int16)x2; } if (acc < 0) { acc += r; if (acc >= 0) { x1 = x - r; if (x1 < cleft) { x1 = cleft; } x2 = x + r; if (x2 > cright) { x2 = cright; } ny = y - radius; if ((ny >= ctop) && (ny <= cbottom)) { _tabVerticG[ny] = (int16)x1; _tabVerticD[ny] = (int16)x2; } ny = y + radius; if ((ny >= ctop) && (ny <= cbottom)) { _tabVerticG[ny] = (int16)x1; _tabVerticD[ny] = (int16)x2; } --radius; acc -= radius; } } ++r; } vtop = top; vbottom = bottom; return true; } return false; } uint8 *Renderer::prepareSpheres(const Common::Array &spheres, int32 &numOfPrimitives, RenderCommand **renderCmds, uint8 *renderBufferPtr, ModelData *modelData) { for (const BodySphere &sphere : spheres) { CmdRenderSphere *cmd = (CmdRenderSphere *)(void*)renderBufferPtr; cmd->color = sphere.color; cmd->polyRenderType = sphere.fillType; cmd->radius = sphere.radius; const int16 centerIndex = sphere.vertex; cmd->x = modelData->flattenPoints[centerIndex].x; cmd->y = modelData->flattenPoints[centerIndex].y; cmd->z = modelData->flattenPoints[centerIndex].z; (*renderCmds)->depth = modelData->flattenPoints[centerIndex].z; (*renderCmds)->renderType = RENDERTYPE_DRAWSPHERE; (*renderCmds)->dataPtr = renderBufferPtr; (*renderCmds)++; renderBufferPtr += sizeof(CmdRenderSphere); } numOfPrimitives += spheres.size(); return renderBufferPtr; } uint8 *Renderer::prepareLines(const Common::Array &lines, int32 &numOfPrimitives, RenderCommand **renderCmds, uint8 *renderBufferPtr, ModelData *modelData) { for (const BodyLine &line : lines) { CmdRenderLine *cmd = (CmdRenderLine *)(void*)renderBufferPtr; cmd->colorIndex = line.color; const int32 point1Index = line.vertex1; const int32 point2Index = line.vertex2; cmd->x1 = modelData->flattenPoints[point1Index].x; cmd->y1 = modelData->flattenPoints[point1Index].y; cmd->x2 = modelData->flattenPoints[point2Index].x; cmd->y2 = modelData->flattenPoints[point2Index].y; (*renderCmds)->depth = MAX(modelData->flattenPoints[point1Index].z, modelData->flattenPoints[point2Index].z); (*renderCmds)->renderType = RENDERTYPE_DRAWLINE; (*renderCmds)->dataPtr = renderBufferPtr; (*renderCmds)++; renderBufferPtr += sizeof(CmdRenderLine); } numOfPrimitives += lines.size(); return renderBufferPtr; } uint8 *Renderer::preparePolygons(const Common::Array &polygons, int32 &numOfPrimitives, RenderCommand **renderCmds, uint8 *renderBufferPtr, ModelData *modelData) { for (const BodyPolygon &polygon : polygons) { const uint8 materialType = polygon.materialType; const uint8 numVertices = polygon.indices.size(); assert(numVertices <= 16); int16 zMax = -32000; CmdRenderPolygon *destinationPolygon = (CmdRenderPolygon *)(void*)renderBufferPtr; destinationPolygon->numVertices = numVertices; renderBufferPtr += sizeof(CmdRenderPolygon); ComputedVertex *const vertices = (ComputedVertex *)(void*)renderBufferPtr; renderBufferPtr += destinationPolygon->numVertices * sizeof(ComputedVertex); ComputedVertex *vertex = vertices; if (materialType >= MAT_GOURAUD) { destinationPolygon->renderType = polygon.materialType - (MAT_GOURAUD - MAT_FLAT); destinationPolygon->colorIndex = polygon.intensity; for (int16 idx = 0; idx < numVertices; ++idx) { const uint16 shadeEntry = polygon.normals[idx]; const int16 shadeValue = polygon.intensity + modelData->normalTable[shadeEntry]; const uint16 vertexIndex = polygon.indices[idx]; const I16Vec3 *point = &modelData->flattenPoints[vertexIndex]; vertex->intensity = shadeValue; vertex->x = point->x; vertex->y = point->y; zMax = MAX(zMax, point->z); ++vertex; } } else { if (materialType >= MAT_FLAT) { // only 1 shade value is used destinationPolygon->renderType = materialType - MAT_FLAT; const uint16 normalIndex = polygon.normals[0]; const int16 shadeValue = polygon.intensity + modelData->normalTable[normalIndex]; destinationPolygon->colorIndex = shadeValue; } else { // no shade is used destinationPolygon->renderType = materialType; destinationPolygon->colorIndex = polygon.intensity; } for (int16 idx = 0; idx < numVertices; ++idx) { const uint16 vertexIndex = polygon.indices[idx]; const I16Vec3 *point = &modelData->flattenPoints[vertexIndex]; vertex->intensity = destinationPolygon->colorIndex; vertex->x = point->x; vertex->y = point->y; zMax = MAX(zMax, point->z); ++vertex; } } if (!isPolygonVisible(vertices)) { renderBufferPtr = (uint8 *)destinationPolygon; continue; } numOfPrimitives++; (*renderCmds)->depth = zMax; (*renderCmds)->renderType = RENDERTYPE_DRAWPOLYGON; (*renderCmds)->dataPtr = (uint8 *)destinationPolygon; (*renderCmds)++; } return renderBufferPtr; } const Renderer::RenderCommand *Renderer::depthSortRenderCommands(int32 numOfPrimitives) { Common::sort(&_renderCmds[0], &_renderCmds[numOfPrimitives], [](const RenderCommand &lhs, const RenderCommand &rhs) { return lhs.depth > rhs.depth; }); return _renderCmds; } bool Renderer::renderObjectIso(const BodyData &bodyData, RenderCommand **renderCmds, ModelData *modelData, Common::Rect &modelRect) { int32 numOfPrimitives = 0; uint8 *renderBufferPtr = _renderCoordinatesBuffer; renderBufferPtr = preparePolygons(bodyData.getPolygons(), numOfPrimitives, renderCmds, renderBufferPtr, modelData); renderBufferPtr = prepareLines(bodyData.getLines(), numOfPrimitives, renderCmds, renderBufferPtr, modelData); prepareSpheres(bodyData.getSpheres(), numOfPrimitives, renderCmds, renderBufferPtr, modelData); if (numOfPrimitives == 0) { return false; } const RenderCommand *cmds = depthSortRenderCommands(numOfPrimitives); int32 primitiveCounter = numOfPrimitives; do { int16 type = cmds->renderType; uint8 *pointer = cmds->dataPtr; switch (type) { case RENDERTYPE_DRAWLINE: { const CmdRenderLine *lineCoords = (const CmdRenderLine *)(const void*)pointer; const int32 x1 = lineCoords->x1; const int32 y1 = lineCoords->y1; const int32 x2 = lineCoords->x2; const int32 y2 = lineCoords->y2; _engine->_interface->drawLine(x1, y1, x2, y2, lineCoords->colorIndex); break; } case RENDERTYPE_DRAWPOLYGON: { const CmdRenderPolygon *header = (const CmdRenderPolygon *)(const void*)pointer; ComputedVertex *vertices = (ComputedVertex *)(void*)(pointer + sizeof(CmdRenderPolygon)); renderPolygons(*header, vertices); break; } case RENDERTYPE_DRAWSPHERE: { const CmdRenderSphere *sphere = (const CmdRenderSphere *)(const void*)pointer; int32 radius = sphere->radius; if (_typeProj == TYPE_ISO) { // * sqrt(sx+sy) / 512 (isometric scale) radius = (radius * 34) / ISO_SCALE; } else { int32 delta = _kFactor + sphere->z; if (delta == 0) { break; } radius = (sphere->radius * _lFactorX) / delta; } if (sphere->x + radius > modelRect.right) { modelRect.right = sphere->x + radius; } if (sphere->x - radius < modelRect.left) { modelRect.left = sphere->x - radius; } if (sphere->y + radius > modelRect.bottom) { modelRect.bottom = sphere->y + radius; } if (sphere->y - radius < modelRect.top) { modelRect.top = sphere->y - radius; } int vtop = -1; int vbottom = -1; if (computeSphere(sphere->x, sphere->y, radius, vtop, vbottom)) { fillVertices(vtop, vbottom, sphere->polyRenderType, sphere->color); } break; } default: break; } cmds++; } while (--primitiveCounter); return true; } void Renderer::animModel(ModelData *modelData, const BodyData &bodyData, RenderCommand *renderCmds, const IVec3 &angleVec, const IVec3 &poswr, Common::Rect &modelRect) { const int32 numVertices = bodyData.getNumVertices(); const int32 numBones = bodyData.getNumBones(); const Common::Array &vertices = bodyData.getVertices(); IMatrix3x3 *modelMatrix = &_matricesTable[0]; const BodyBone &firstBone = bodyData.getBone(0); processRotatedElement(modelMatrix, vertices, angleVec.x, angleVec.y, angleVec.z, firstBone, modelData); int32 numOfPrimitives = 0; if (numBones - 1 != 0) { numOfPrimitives = numBones - 1; int boneIdx = 1; modelMatrix = &_matricesTable[boneIdx]; do { const BodyBone &bone = bodyData.getBone(boneIdx); const BoneFrame *boneData = bodyData.getBoneState(boneIdx); if (boneData->type == BoneType::TYPE_ROTATE) { processRotatedElement(modelMatrix, vertices, boneData->x, boneData->y, boneData->z, bone, modelData); } else if (boneData->type == BoneType::TYPE_TRANSLATE) { translateGroup(modelMatrix, vertices, boneData->x, boneData->y, boneData->z, bone, modelData); } else if (boneData->type == BoneType::TYPE_ZOOM) { // unsupported type } ++modelMatrix; ++boneIdx; } while (--numOfPrimitives); } numOfPrimitives = numVertices; const I16Vec3 *pointPtr = &modelData->computedPoints[0]; I16Vec3 *pointPtrDest = &modelData->flattenPoints[0]; if (_typeProj == TYPE_ISO) { do { const int32 coX = pointPtr->x + poswr.x; const int32 coY = pointPtr->y + poswr.y; const int32 coZ = -(pointPtr->z + poswr.z); pointPtrDest->x = (int16)((coX + coZ) * 24 / ISO_SCALE + _projectionCenter.x); pointPtrDest->y = (int16)((((coX - coZ) * 12) - coY * 30) / ISO_SCALE + _projectionCenter.y); pointPtrDest->z = (int16)(coZ - coX - coY); if (pointPtrDest->x < modelRect.left) { modelRect.left = pointPtrDest->x; } if (pointPtrDest->x > modelRect.right) { modelRect.right = pointPtrDest->x; } if (pointPtrDest->y < modelRect.top) { modelRect.top = pointPtrDest->y; } if (pointPtrDest->y > modelRect.bottom) { modelRect.bottom = pointPtrDest->y; } pointPtr++; pointPtrDest++; } while (--numOfPrimitives); } else { do { int32 coZ = _kFactor - (pointPtr->z + poswr.z); if (coZ <= 0) { coZ = 0x7FFFFFFF; } int32 coX = (((pointPtr->x + poswr.x) * _lFactorX) / coZ) + _projectionCenter.x; if (coX > 0xFFFF) { coX = 0x7FFF; } pointPtrDest->x = (int16)coX; if (pointPtrDest->x < modelRect.left) { modelRect.left = pointPtrDest->x; } if (pointPtrDest->x > modelRect.right) { modelRect.right = pointPtrDest->x; } int32 coY = _projectionCenter.y - (((pointPtr->y + poswr.y) * _lFactorY) / coZ); if (coY > 0xFFFF) { coY = 0x7FFF; } pointPtrDest->y = (int16)coY; if (pointPtrDest->y < modelRect.top) { modelRect.top = pointPtrDest->y; } if (pointPtrDest->y > modelRect.bottom) { modelRect.bottom = pointPtrDest->y; } if (coZ > 0xFFFF) { coZ = 0x7FFF; } pointPtrDest->z = (int16)coZ; pointPtr++; pointPtrDest++; } while (--numOfPrimitives); } int32 numNormals = (int32)bodyData.getNormals().size(); if (numNormals) { // process normal data uint16 *currentShadeDestination = (uint16 *)modelData->normalTable; IMatrix3x3 *lightMatrix = &_matricesTable[0]; numOfPrimitives = numBones; int16 shadeIndex = 0; int16 boneIdx = 0; do { // for each element numNormals = bodyData.getBone(boneIdx).numNormals; if (numNormals) { const IMatrix3x3 matrix = *lightMatrix * _normalLight; for (int32 i = 0; i < numNormals; ++i) { // for each normal const BodyNormal &normalPtr = bodyData.getNormal(shadeIndex); const int32 x = (int32)normalPtr.x; const int32 y = (int32)normalPtr.y; const int32 z = (int32)normalPtr.z; int32 intensity = 0; intensity += matrix.row1.x * x + matrix.row1.y * y + matrix.row1.z * z; intensity += matrix.row2.x * x + matrix.row2.y * y + matrix.row2.z * z; intensity += matrix.row3.x * x + matrix.row3.y * y + matrix.row3.z * z; if (intensity > 0) { intensity >>= 14; intensity /= normalPtr.prenormalizedRange; } else { intensity = 0; } *currentShadeDestination++ = (uint16)intensity; ++shadeIndex; }; } ++boneIdx; ++lightMatrix; } while (--numOfPrimitives); } } bool Renderer::affObjetIso(int32 x, int32 y, int32 z, int32 alpha, int32 beta, int32 gamma, const BodyData &bodyData, Common::Rect &modelRect) { IVec3 renderAngle; renderAngle.x = alpha; renderAngle.y = beta; renderAngle.z = gamma; // model render size reset modelRect.left = SCENE_SIZE_MAX; modelRect.top = SCENE_SIZE_MAX; modelRect.right = SCENE_SIZE_MIN; modelRect.bottom = SCENE_SIZE_MIN; IVec3 poswr; // PosXWr, PosYWr, PosZWr if (_typeProj == TYPE_3D) { poswr = longWorldRot(x, y, z) - _cameraRot; } else { poswr.x = x; poswr.y = y; poswr.z = z; } if (!bodyData.isAnimated()) { #if 0 // TODO: fill modeldata.flattenedpoints // not used in original source release int32 numOfPrimitives = 0; RenderCommand* renderCmds = _renderCmds; return renderModelElements(numOfPrimitives, bodyData, &renderCmds, &_modelData, modelRect); #else error("Unsupported unanimated model render for model index %i!", bodyData.hqrIndex()); #endif } // restart at the beginning of the renderTable RenderCommand *renderCmds = _renderCmds; if (bodyData.isAnimated()) { animModel(&_modelData, bodyData, renderCmds, renderAngle, poswr, modelRect); } if (!renderObjectIso(bodyData, &renderCmds, &_modelData, modelRect)) { modelRect.right = -1; modelRect.bottom = -1; modelRect.left = -1; modelRect.top = -1; return false; } return true; } void Renderer::drawObj3D(const Common::Rect &rect, int32 y, int32 angle, const BodyData &bodyData, RealValue &move) { int32 boxLeft = rect.left; int32 boxTop = rect.top; int32 boxRight = rect.right; int32 boxBottom = rect.bottom; const int32 ypos = (boxBottom + boxTop) / 2; const int32 xpos = (boxRight + boxLeft) / 2; setIsoProjection(xpos, ypos, 0); _engine->_interface->setClip(rect); Common::Rect dummy; if (angle == -1) { angle = move.getRealAngle(_engine->timerRef); if (move.timeValue == 0) { _engine->_movements->initRealAngle(angle, angle - LBAAngles::ANGLE_90, LBAAngles::ANGLE_17, &move); } } affObjetIso(0, y, 0, LBAAngles::ANGLE_0, angle, LBAAngles::ANGLE_0, bodyData, dummy); } void Renderer::draw3dObject(int32 x, int32 y, const BodyData &bodyData, int32 angle, int32 cameraZoom) { setProjection(x, y, 128, 200, 200); setFollowCamera(0, 0, 0, 60, 0, 0, cameraZoom); Common::Rect dummy; affObjetIso(0, 0, 0, LBAAngles::ANGLE_0, angle, LBAAngles::ANGLE_0, bodyData, dummy); } void Renderer::fillHolomapTriangle(int16 *pDest, int32 x0, int32 y0, int32 x1, int32 y1) { uint32 dx, step, reminder; if (y0 > y1) { SWAP(x0, x1); SWAP(y0, y1); } y1 -= y0; pDest += y0; if (x0 <= x1) { dx = (x1 - x0) << 16; step = dx / y1; reminder = ((dx % y1) >> 1) + 0x7FFF; x1 = step >> 16; step &= 0xFFFF; for (; y1 >= 0; --y1) { *pDest++ = (int16)x0; x0 += x1; if (reminder & 0xFFFF0000) { x0 += reminder >> 16; reminder &= 0xFFFF; } reminder += step; } } else { dx = (x0 - x1) << 16; step = dx / y1; reminder = ((dx % y1) >> 1) + 0x7FFF; x1 = step >> 16; step &= 0xFFFF; for (; y1 >= 0; --y1) { *pDest++ = (int16)x0; x0 -= x1; if (reminder & 0xFFFF0000) { x0 += reminder >> 16; reminder &= 0xFFFF; } reminder -= step; } } } void Renderer::fillHolomapTriangles(const ComputedVertex &vertex0, const ComputedVertex &vertex1, const ComputedVertex &texCoord0, const ComputedVertex &texCoord1, int32 &lymin, int32 &lymax) { const int32 y0 = vertex0.y; const int32 y1 = vertex1.y; if (y0 < y1) { if (y0 < lymin) { lymin = y0; } if (y1 > lymax) { lymax = y1; } fillHolomapTriangle(_tabVerticG, vertex0.x, y0, vertex1.x, y1); fillHolomapTriangle(_tabx0, (int32)(uint16)texCoord0.x, y0, (int32)(uint16)texCoord1.x, y1); fillHolomapTriangle(_taby0, (int32)(uint16)texCoord0.y, y0, (int32)(uint16)texCoord1.y, y1); } else if (y0 > y1) { if (y0 > lymax) { lymax = y0; } if (y1 < lymin) { lymin = y1; } fillHolomapTriangle(_tabVerticD, vertex0.x, y0, vertex1.x, y1); fillHolomapTriangle(_tabx1, (int32)(uint16)texCoord0.x, y0, (int32)(uint16)texCoord1.x, y1); fillHolomapTriangle(_taby1, (int32)(uint16)texCoord0.y, y0, (int32)(uint16)texCoord1.y, y1); } } void Renderer::asmTexturedTriangleNoClip(const ComputedVertex vertexCoordinates[3], const ComputedVertex textureCoordinates[3], const uint8 *holomapImage, uint32 holomapImageSize) { int32 lymin = 32000; int32 lymax = -32000; fillHolomapTriangles(vertexCoordinates[0], vertexCoordinates[1], textureCoordinates[0], textureCoordinates[1], lymin, lymax); fillHolomapTriangles(vertexCoordinates[1], vertexCoordinates[2], textureCoordinates[1], textureCoordinates[2], lymin, lymax); fillHolomapTriangles(vertexCoordinates[2], vertexCoordinates[0], textureCoordinates[2], textureCoordinates[0], lymin, lymax); fillTextPolyNoClip(lymin, lymax, holomapImage, holomapImageSize); } void Renderer::fillTextPolyNoClip(int32 yMin, int32 yMax, const uint8 *holomapImage, uint32 holomapImageSize) { if (yMin < 0 || yMin >= _engine->_frontVideoBuffer.h) { return; } const int screenWidth = _engine->width(); uint8 *pDestLine = (uint8 *)_engine->_frontVideoBuffer.getBasePtr(0, yMin); const int16 *pVerticG = &_tabVerticG[yMin]; const int16 *pVerticD = &_tabVerticD[yMin]; const uint16 *pU0 = (const uint16 *)&_tabx0[yMin]; const uint16 *pV0 = (const uint16 *)&_taby0[yMin]; const uint16 *pU1 = (const uint16 *)&_tabx1[yMin]; const uint16 *pV1 = (const uint16 *)&_taby1[yMin]; yMax -= yMin; for (; yMax >= 0; yMax--) { int16 xMin = *pVerticG++; int16 xMax = *pVerticD++; xMax -= xMin; uint32 u0, v0; int32 u, v; u = u0 = *pU0++; v = v0 = *pV0++; uint32 u1 = *pU1++; uint32 v1 = *pV1++; if (xMax > 0) { byte *pDest = pDestLine + xMin; int32 ustep = ((int32)u1 - (int32)u0 + 1) / xMax; int32 vstep = ((int32)v1 - (int32)v0 + 1) / xMax; for (; xMax > 0; xMax--) { uint32 idx = ((u >> 8) & 0xFF) | (v & 0xFF00); // u0&0xFF00=column*256, v0&0xFF00 = line*256 *pDest++ = holomapImage[idx]; u += ustep; v += vstep; } } pDestLine += screenWidth; } } } // namespace TwinE