[Scummvm-git-logs] scummvm master -> 4289094bc23006d4f414f2f96a195beb7eb2c392

mgerhardy noreply at scummvm.org
Sun May 24 19:31:03 UTC 2026


This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://api.github.com/repos/scummvm/scummvm .

Summary:
9b95d14482 TWINE: LBA2: holomap and palette improvements
9641a7a19a TWINE: LBA2: implemented body parser
4289094bc2 TWINE: fixed soft lock of bulldozer driven by twinsen


Commit: 9b95d144827017541b08f3c85bb824a742e6daad
    https://github.com/scummvm/scummvm/commit/9b95d144827017541b08f3c85bb824a742e6daad
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-05-24T21:30:55+02:00

Commit Message:
TWINE: LBA2: holomap and palette improvements

Changed paths:
    engines/twine/holomap_v2.cpp
    engines/twine/holomap_v2.h
    engines/twine/menu/menu.cpp
    engines/twine/renderer/screens.cpp
    engines/twine/renderer/screens.h
    engines/twine/scene/scene.cpp
    engines/twine/script/script_life_v2.cpp


diff --git a/engines/twine/holomap_v2.cpp b/engines/twine/holomap_v2.cpp
index ed0c17ed4c5..6c9dc9b6835 100644
--- a/engines/twine/holomap_v2.cpp
+++ b/engines/twine/holomap_v2.cpp
@@ -20,37 +20,445 @@
  */
 
 #include "twine/holomap_v2.h"
+#include "common/algorithm.h"
+#include "common/debug.h"
+#include "common/memstream.h"
+#include "common/stream.h"
+#include "twine/audio/sound.h"
+#include "twine/menu/interface.h"
+#include "twine/renderer/renderer.h"
+#include "twine/renderer/screens.h"
 #include "twine/resources/hqr.h"
 #include "twine/resources/resources.h"
+#include "twine/scene/gamestate.h"
+#include "twine/scene/scene.h"
+#include "twine/shared.h"
+#include "twine/text.h"
+#include "twine/twine.h"
 
 namespace TwinE {
 
+// HQR indices in holomap.hqr for LBA2
+#define HQR_COORMAPP_HMM 0
+#define HQR_TWINSUN_HMT 1
+#define HQR_TWINSUN_HMG 2
+
+#define HOLO_FLAG_ACTIVE 1
+#define HOLO_FLAG_ASKED 2
+
+int32 HolomapV2::distance(float dist) const {
+	const float w = (float)_engine->width() / (float)_engine->originalWidth();
+	const float h = (float)_engine->height() / (float)_engine->originalHeight();
+	const float f = MIN<float>(w, h);
+	return (int32)(dist / f);
+}
+
+int32 HolomapV2::scale(float val) const {
+	const float w = (float)_engine->width() / (float)_engine->originalWidth();
+	const float h = (float)_engine->height() / (float)_engine->originalHeight();
+	const float f = MIN<float>(w, h);
+	return (int32)(val * f);
+}
+
 bool HolomapV2::setHoloPos(int32 locationIdx) {
-	return false;
+	assert(locationIdx >= 0 && locationIdx < HOLO_MAX_CUBE);
+	Location &loc = _locations[HOLO_MAX_OBJECTIF + locationIdx];
+	if (loc.FlagHolo & HOLO_FLAG_ASKED) {
+		return false; // already asked
+	}
+	loc.FlagHolo |= HOLO_FLAG_ACTIVE;
+	return true;
+}
+
+void HolomapV2::clrHoloPos(int32 locationIdx) {
+	assert(locationIdx >= 0 && locationIdx < HOLO_MAX_CUBE);
+	Location &loc = _locations[HOLO_MAX_OBJECTIF + locationIdx];
+	loc.FlagHolo &= ~HOLO_FLAG_ACTIVE;
+	loc.FlagHolo |= HOLO_FLAG_ASKED;
 }
 
 bool HolomapV2::loadLocations() {
-	// _locations[MAX_OBJECTIF + 67].FlagHolo = 1; //	Desert Globe
 	return HQR::getEntry((uint8 *)_locations, Resources::HQR_HOLOMAP_FILE, RESSHQR_ARROWBIN) != 0;
 }
 
 const char *HolomapV2::getLocationName(int index) const {
-	if (index >= 0 && index < ARRAYSIZE(_locations)) {
-		// TODO: return _locations[index].;
-	}
 	return "";
 }
 
-void HolomapV2::clrHoloPos(int32 locationIdx) {
+void HolomapV2::computeCoorMapping() {
+	int idx = 0;
+	for (int32 alpha = -LBAAngles::ANGLE_90; alpha <= LBAAngles::ANGLE_90; alpha += HOLO_STEP_ANGLE) {
+		for (int32 beta = 0; beta < LBAAngles::ANGLE_360; beta += HOLO_STEP_ANGLE) {
+			_projectedSurfacePositions[idx].x2 = (uint16)ruleThree32(0, 255 * 256 + 255, LBAAngles::ANGLE_360 - 1, beta);
+			if (alpha == LBAAngles::ANGLE_90) {
+				_projectedSurfacePositions[idx].y2 = 255 * 256 + 255;
+			} else {
+				_projectedSurfacePositions[idx].y2 = (uint16)(((alpha + LBAAngles::ANGLE_90) * 256) / 2);
+			}
+			++idx;
+		}
+		// wrap column
+		_projectedSurfacePositions[idx].x2 = 255 * 256 + 255;
+		if (alpha == LBAAngles::ANGLE_90) {
+			_projectedSurfacePositions[idx].y2 = 255 * 256 + 255;
+		} else {
+			_projectedSurfacePositions[idx].y2 = (uint16)(((alpha + LBAAngles::ANGLE_90) * 256) / 2);
+		}
+		++idx;
+	}
 }
 
-void HolomapV2::holoTraj(int32 trajectoryIndex) {
+void HolomapV2::computeCoorGlobe(Common::SeekableReadStream *surfaceStream) {
+	int idx = 0;
+	_engine->_renderer->setAngleCamera(0, 0, 0);
+	for (int32 alpha = -LBAAngles::ANGLE_90; alpha <= LBAAngles::ANGLE_90; alpha += HOLO_STEP_ANGLE) {
+		const int64 startPos = surfaceStream->pos();
+		const int32 firstAlt = surfaceStream->readByte();
+		surfaceStream->seek(startPos, SEEK_SET);
+
+		for (int32 beta = 0; beta < LBAAngles::ANGLE_360; beta += HOLO_STEP_ANGLE) {
+			const int32 alt = surfaceStream->readByte();
+			const int32 normal = HOLO_RAYON_PLANET + alt * 2;
+			const IVec2 &rotVec = _engine->_renderer->rotate(normal, 0, alpha);
+			const IVec2 &rotVec2 = _engine->_renderer->rotate(rotVec.x, 0, beta);
+			const IVec3 &rotVec3 = _engine->_renderer->worldRotatePoint(IVec3(rotVec2.x, rotVec.y, rotVec2.y));
+			_holomapSurface[idx].x = rotVec3.x;
+			_holomapSurface[idx].y = rotVec3.y;
+			_holomapSurface[idx].z = rotVec3.z;
+			++idx;
+		}
+		// wrap: use first altitude of this row
+		const int32 normal = HOLO_RAYON_PLANET + firstAlt * 2;
+		const IVec2 &rotVec = _engine->_renderer->rotate(normal, 0, alpha);
+		const IVec2 &rotVec2 = _engine->_renderer->rotate(rotVec.x, 0, 0);
+		const IVec3 &rotVec3 = _engine->_renderer->worldRotatePoint(IVec3(rotVec2.x, rotVec.y, rotVec2.y));
+		_holomapSurface[idx].x = rotVec3.x;
+		_holomapSurface[idx].y = rotVec3.y;
+		_holomapSurface[idx].z = rotVec3.z;
+		++idx;
+	}
+}
+
+void HolomapV2::computeGlobeProj() {
+	int sortIdx = 0;
+	int surfIdx = 0;
+	int projIdx = 0;
+	for (int32 alpha = -LBAAngles::ANGLE_90; alpha <= LBAAngles::ANGLE_90; alpha += HOLO_STEP_ANGLE) {
+		for (int32 beta = 0; beta < HOLO_GLOBE_COLS; ++beta) {
+			const IVec3 &destPos = _engine->_renderer->worldRotatePoint(_holomapSurface[surfIdx]);
+			if (alpha != LBAAngles::ANGLE_90) {
+				_holomapSort[sortIdx].z = (int16)destPos.z;
+				_holomapSort[sortIdx].projectedPosIdx = projIdx;
+				++sortIdx;
+			}
+			const IVec3 &projPos = _engine->_renderer->projectPoint(destPos);
+			_projectedSurfacePositions[projIdx].x1 = (int16)projPos.x;
+			_projectedSurfacePositions[projIdx].y1 = (int16)projPos.y;
+			++projIdx;
+			++surfIdx;
+		}
+	}
+	Common::sort(_holomapSort, _holomapSort + HOLO_GLOBE_QUADS,
+		[](const HolomapSort &a, const HolomapSort &b) { return a.z < b.z; });
+}
+
+void HolomapV2::drawHoloMap() {
+	computeGlobeProj();
+	for (int32 i = 0; i < HOLO_GLOBE_QUADS; ++i) {
+		const int base = _holomapSort[i].projectedPosIdx;
+		const HolomapProjectedPos &pos1 = _projectedSurfacePositions[base];
+		const HolomapProjectedPos &pos2 = _projectedSurfacePositions[base + HOLO_GLOBE_COLS];
+		const HolomapProjectedPos &pos3 = _projectedSurfacePositions[base + 1];
+
+		ComputedVertex vertexCoordinates[3];
+		vertexCoordinates[0].x = pos1.x1;
+		vertexCoordinates[0].y = pos1.y1;
+		vertexCoordinates[1].x = pos2.x1;
+		vertexCoordinates[1].y = pos2.y1;
+		vertexCoordinates[2].x = pos3.x1;
+		vertexCoordinates[2].y = pos3.y1;
+		if (isPolygonVisible(vertexCoordinates)) {
+			ComputedVertex textureCoordinates[3];
+			textureCoordinates[0].x = pos1.x2;
+			textureCoordinates[0].y = pos1.y2;
+			textureCoordinates[1].x = pos2.x2;
+			textureCoordinates[1].y = pos2.y2;
+			textureCoordinates[2].x = pos3.x2;
+			textureCoordinates[2].y = pos3.y2;
+			_engine->_renderer->asmTexturedTriangleNoClip(vertexCoordinates, textureCoordinates, _holomapImagePtr, _holomapImageSize);
+		}
+
+		const HolomapProjectedPos &pos4 = _projectedSurfacePositions[base + HOLO_GLOBE_COLS];
+		const HolomapProjectedPos &pos5 = _projectedSurfacePositions[base + HOLO_GLOBE_COLS + 1];
+		const HolomapProjectedPos &pos6 = _projectedSurfacePositions[base + 1];
+		vertexCoordinates[0].x = pos4.x1;
+		vertexCoordinates[0].y = pos4.y1;
+		vertexCoordinates[1].x = pos5.x1;
+		vertexCoordinates[1].y = pos5.y1;
+		vertexCoordinates[2].x = pos6.x1;
+		vertexCoordinates[2].y = pos6.y1;
+		if (isPolygonVisible(vertexCoordinates)) {
+			ComputedVertex textureCoordinates[3];
+			textureCoordinates[0].x = pos4.x2;
+			textureCoordinates[0].y = pos4.y2;
+			textureCoordinates[1].x = pos5.x2;
+			textureCoordinates[1].y = pos5.y2;
+			textureCoordinates[2].x = pos6.x2;
+			textureCoordinates[2].y = pos6.y2;
+			_engine->_renderer->asmTexturedTriangleNoClip(vertexCoordinates, textureCoordinates, _holomapImagePtr, _holomapImageSize);
+		}
+	}
+}
+
+void HolomapV2::drawListHoloGlobe(bool frontFace) {
+	struct SortEntry {
+		int32 z;
+		int32 numObj;
+		int16 xw, yw, zw;
+	};
+	SortEntry sortList[HOLO_MAX_ARROW];
+	int nbobjets = 0;
+
+	const int numCube = _engine->_scene->_numCube;
+
+	for (int n = HOLO_MAX_OBJECTIF; n < HOLO_MAX_ARROW; ++n) {
+		if (!((_locations[n].FlagHolo & HOLO_FLAG_ACTIVE) || n == (HOLO_MAX_OBJECTIF + numCube))) {
+			continue;
+		}
+		const Location &loc = _locations[n];
+		if (loc.Planet != 0) { // only Twinsun for now
+			continue;
+		}
+
+		_engine->_renderer->setAngleCamera(loc.Alpha, loc.Beta, 0);
+		const IVec3 &m = _engine->_renderer->worldRotatePoint(IVec3(0, 0, HOLO_RAYON_PLANET + loc.Alt));
+		const IVec3 &m1 = _engine->_renderer->worldRotatePoint(IVec3(0, 0, HOLO_RAYON_PLANET + 500));
+
+		_engine->_renderer->setInverseAngleCamera(_holoAlpha, _holoBeta, _holoGamma);
+		_engine->_renderer->setCameraRotation(0, 0, distance((float)_zoomPlanet));
+
+		const IVec3 &destPos = _engine->_renderer->worldRotatePoint(m);
+		const IVec3 &destPos2 = _engine->_renderer->worldRotatePoint(m1);
+
+		if (!frontFace) {
+			if (destPos2.z > destPos.z) continue;
+		} else {
+			if (destPos2.z < destPos.z) continue;
+		}
+
+		sortList[nbobjets].z = destPos.z;
+		sortList[nbobjets].numObj = n;
+		sortList[nbobjets].xw = (int16)m.x;
+		sortList[nbobjets].yw = (int16)m.y;
+		sortList[nbobjets].zw = (int16)m.z;
+		++nbobjets;
+	}
+
+	if (nbobjets == 0) return;
+
+	// sort back to front
+	Common::sort(sortList, sortList + nbobjets,
+		[](const SortEntry &a, const SortEntry &b) { return a.z < b.z; });
+
+	for (int i = 0; i < nbobjets; ++i) {
+		const SortEntry &entry = sortList[i];
+		const Location &loc = _locations[entry.numObj];
+		Common::Rect dummy;
+		_engine->_renderer->affObjetIso(entry.xw, entry.yw, entry.zw,
+			loc.Alpha, loc.Beta, LBAAngles::ANGLE_0,
+			_engine->_resources->_holomapArrowPtr, dummy);
+	}
+}
+
+void HolomapV2::drawReticule() {
+	const int32 cx = _engine->width() / 2;
+	const int32 cy = scale(220);
+	const int32 sz = 16;
+	_engine->_menu->drawRectBorders(cx - sz, cy - sz, cx + sz, cy + sz, 15, 15);
+}
+
+bool HolomapV2::goToArrow() {
+	const int32 dt = _engine->timerRef - _moveTimer;
+	_holoAlpha = boundRuleThree(_holoAlpha, _destAlpha, 75, dt);
+	_holoBeta = boundRuleThree(_holoBeta, _destBeta, 75, dt);
+	return (_holoAlpha == _destAlpha && _holoBeta == _destBeta);
 }
 
 void HolomapV2::initHoloDatas() {
+	Common::SeekableReadStream *surfaceStream = HQR::makeReadStream(
+		TwineResource(Resources::HQR_HOLOMAP_FILE, HQR_TWINSUN_HMT));
+	if (surfaceStream == nullptr) {
+		error("Failed to load holomap surface for LBA2");
+	}
+	computeCoorMapping();
+	computeCoorGlobe(surfaceStream);
+	delete surfaceStream;
+}
+
+void HolomapV2::holoTraj(int32 trajectoryIndex) {
+	// TODO: implement trajectory animation for LBA2
+	warning("HolomapV2::holoTraj(%d) not yet implemented", trajectoryIndex);
 }
 
 void HolomapV2::holoMap() {
+	const int32 alphaLightTmp = _engine->_scene->_alphaLight;
+	const int32 betaLightTmp = _engine->_scene->_betaLight;
+
+	_engine->saveTimer(false);
+	_engine->_screens->fadeToBlack(_engine->_screens->_ptrPal);
+	_engine->_sound->stopSamples();
+	_engine->_interface->unsetClip();
+	_engine->_screens->clearScreen();
+
+	initHoloDatas();
+
+	const int32 cameraPosX = _engine->width() / 2;
+	const int32 cameraPosY = scale(220);
+	_engine->_renderer->setProjection(cameraPosX, cameraPosY, 128, 1024, 1024);
+
+	// Load globe texture
+	_holomapImagePtr = nullptr;
+	_holomapImageSize = HQR::getAllocEntry(&_holomapImagePtr,
+		TwineResource(Resources::HQR_HOLOMAP_FILE, HQR_TWINSUN_HMG));
+	if (_holomapImageSize == 0) {
+		error("Failed to load holomap image for LBA2");
+	}
+
+	// Initial camera pointing at current location
+	const int numCube = _engine->_scene->_numCube;
+	if (numCube >= 0 && numCube < HOLO_MAX_CUBE) {
+		const Location &loc = _locations[HOLO_MAX_OBJECTIF + numCube];
+		_holoAlpha = loc.Alpha & (LBAAngles::ANGLE_360 - 1);
+		_holoBeta = loc.Beta & (LBAAngles::ANGLE_360 - 1);
+	} else {
+		_holoAlpha = 0;
+		_holoBeta = 0;
+	}
+	_holoGamma = 0;
+	_zoomPlanet = HOLO_ZOOM_INIT_PLANET;
+	_zoomPlanetDest = HOLO_ZOOM_PLANET;
+	_automove = false;
+	_flagRedraw = true;
+	_flagPal = true;
+	_flagHoloEnd = false;
+	_numObjectif = -1;
+
+	_engine->_input->enableKeyMap(holomapKeyMapId);
+
+	for (;;) {
+		FrameMarker frame(_engine);
+		_engine->_input->readKeys();
+		if (_engine->shouldQuit() || _engine->_input->toggleAbortAction()) {
+			break;
+		}
+
+		// Navigation
+		if (!_automove) {
+			if (_engine->_input->isActionActive(TwinEActionType::HolomapUp)) {
+				_holoAlpha -= LBAAngles::ANGLE_2;
+				_holoAlpha = ClampAngle(_holoAlpha);
+				_flagRedraw = true;
+			} else if (_engine->_input->isActionActive(TwinEActionType::HolomapDown)) {
+				_holoAlpha += LBAAngles::ANGLE_2;
+				_holoAlpha = ClampAngle(_holoAlpha);
+				_flagRedraw = true;
+			}
+			if (_engine->_input->isActionActive(TwinEActionType::HolomapLeft)) {
+				_holoBeta -= LBAAngles::ANGLE_2;
+				_holoBeta = ClampAngle(_holoBeta);
+				_flagRedraw = true;
+			} else if (_engine->_input->isActionActive(TwinEActionType::HolomapRight)) {
+				_holoBeta += LBAAngles::ANGLE_2;
+				_holoBeta = ClampAngle(_holoBeta);
+				_flagRedraw = true;
+			}
+		}
+
+		if (_engine->_input->toggleActionIfActive(TwinEActionType::HolomapPrev)) {
+			// search prev active arrow
+			for (int n = HOLO_MAX_OBJECTIF; n < HOLO_MAX_ARROW; ++n) {
+				if (_locations[n].FlagHolo & HOLO_FLAG_ACTIVE) {
+					_destAlpha = _locations[n].Alpha & (LBAAngles::ANGLE_360 - 1);
+					_destBeta = _locations[n].Beta & (LBAAngles::ANGLE_360 - 1);
+					_moveTimer = _engine->timerRef;
+					_automove = true;
+					_flagRedraw = true;
+					break;
+				}
+			}
+		} else if (_engine->_input->toggleActionIfActive(TwinEActionType::HolomapNext)) {
+			// search next active arrow
+			for (int n = HOLO_MAX_ARROW - 1; n >= HOLO_MAX_OBJECTIF; --n) {
+				if (_locations[n].FlagHolo & HOLO_FLAG_ACTIVE) {
+					_destAlpha = _locations[n].Alpha & (LBAAngles::ANGLE_360 - 1);
+					_destBeta = _locations[n].Beta & (LBAAngles::ANGLE_360 - 1);
+					_moveTimer = _engine->timerRef;
+					_automove = true;
+					_flagRedraw = true;
+					break;
+				}
+			}
+		}
+
+		if (_automove) {
+			if (goToArrow()) {
+				_automove = false;
+			}
+			_flagRedraw = true;
+		}
+
+		// Zoom animation
+		if (_zoomPlanet < _zoomPlanetDest) {
+			_zoomPlanet += 100;
+			if (_zoomPlanet > _zoomPlanetDest) _zoomPlanet = _zoomPlanetDest;
+			_flagRedraw = true;
+		}
+
+		// Render
+		if (_flagRedraw) {
+			_flagRedraw = false;
+			const Common::Rect rect(0, 0, _engine->width() - 1, _engine->height() - 1);
+			_engine->_interface->box(rect, COLOR_BLACK);
+
+			_engine->_renderer->setInverseAngleCamera(_holoAlpha, _holoBeta, _holoGamma);
+			_engine->_renderer->setLightVector(_holoAlpha, _holoBeta, 0);
+
+			// Draw objects behind globe
+			drawListHoloGlobe(false);
+
+			// Draw globe
+			_engine->_renderer->setInverseAngleCamera(_holoAlpha, _holoBeta, _holoGamma);
+			_engine->_renderer->setCameraRotation(0, 0, distance((float)_zoomPlanet));
+			drawHoloMap();
+
+			// Draw objects in front of globe
+			drawListHoloGlobe(true);
+
+			if (_automove) {
+				drawReticule();
+			}
+
+			_engine->copyBlockPhys(rect);
+		}
+
+		// Fade in first time
+		if (_flagPal) {
+			_flagPal = false;
+			_engine->_screens->fadeToPal(_engine->_screens->_palettePcx);
+		}
+
+		++_engine->timerRef;
+	}
+
+	_engine->_screens->fadeToBlack(_engine->_screens->_palettePcx);
+	_engine->_scene->_alphaLight = alphaLightTmp;
+	_engine->_scene->_betaLight = betaLightTmp;
+	_engine->_gameState->init3DGame();
+	_engine->_input->enableKeyMap(mainKeyMapId);
+	_engine->restoreTimer();
+
+	free(_holomapImagePtr);
+	_holomapImagePtr = nullptr;
 }
 
 } // namespace TwinE
diff --git a/engines/twine/holomap_v2.h b/engines/twine/holomap_v2.h
index 7984c6f990c..e3d6f0da795 100644
--- a/engines/twine/holomap_v2.h
+++ b/engines/twine/holomap_v2.h
@@ -23,63 +23,115 @@
 #define TWINE_HOLOMAPV2_H
 
 #include "twine/holomap.h"
-
-#define MAX_OBJECTIF 50
-#define MAX_CUBE 255
+#include "twine/shared.h"
 
 namespace TwinE {
 
-/**
- * The Holomap shows the hero position. The arrows (@c RESSHQR_HOLOARROWMDL) represent important places in your quest - they automatically disappear once that part of
- * the quest is done (@c clrHoloPos()). You can rotate the holoamp by pressing ctrl+cursor keys - but only using the cursor keys, you can scroll through the
- * text for the visible arrows.
- */
+#define HOLO_MAX_OBJECTIF 50
+#define HOLO_MAX_CUBE 255
+#define HOLO_MAX_ARROW (HOLO_MAX_OBJECTIF + HOLO_MAX_CUBE)
+
+// Globe mesh parameters (LBA2 uses 4x angle factor)
+#define HOLO_STEP_ANGLE 128
+#define HOLO_GLOBE_ALPHA_STEPS ((LBAAngles::ANGLE_360 / HOLO_STEP_ANGLE) + 1) // 33
+#define HOLO_GLOBE_BETA_STEPS ((LBAAngles::ANGLE_180 / HOLO_STEP_ANGLE) + 1)  // 17 (alpha from -90 to +90 = 180 degrees)
+// Actually: alpha from -1024 to 1024 step 128 = 17 rows, beta from 0 to 4096 step 128 = 32 cols + 1 wrap = 33
+#define HOLO_GLOBE_COLS 33
+#define HOLO_GLOBE_ROWS 17
+#define HOLO_GLOBE_VERTICES (HOLO_GLOBE_COLS * HOLO_GLOBE_ROWS)
+#define HOLO_GLOBE_QUADS ((HOLO_GLOBE_COLS - 1) * (HOLO_GLOBE_ROWS - 1)) // 512
+
+#define HOLO_RAYON_PLANET 1000
+#define HOLO_ZOOM_PLANET 8000
+#define HOLO_ZOOM_INIT_PLANET 3000
+
 class HolomapV2 : public Holomap {
 private:
 	using Super = Holomap;
 
 public:
-	HolomapV2(TwinEEngine *engine) : Super(engine) {}
-	virtual ~HolomapV2() = default;
-
 	struct Location {
-		int32 X = 0; // Position Island X Y Z
+		int32 X = 0;
 		int32 Y = 0;
 		int32 Z = 0;
-		int32 Alpha = 0; // Position Planet Alpha, Beta and Altitude
+		int32 Alpha = 0;
 		int32 Beta = 0;
 		int32 Alt = 0;
 		int32 Mess = 0;
-		int8 ObjFix = 0;    // Eventual Obj Inventory 3D (FREE NOT USED!)
-		uint8 FlagHolo = 0u; // Flag for Planet display, active, etc.
+		int8 ObjFix = 0;
+		uint8 FlagHolo = 0u;
 		uint8 Planet = 0u;
 		uint8 Island = 0u;
 	};
 	static_assert(sizeof(Location) == 32, "Invalid Location size");
-	Location _locations[MAX_OBJECTIF + MAX_CUBE];
 
-	/**
-	 * Set Holomap location position
-	 * @param locationIdx Scene where position must be set
-	 */
-	bool setHoloPos(int32 locationIdx) override;
+private:
+	Location _locations[HOLO_MAX_ARROW];
 
-	bool loadLocations() override;
+	// Globe mesh data
+	IVec3 _holomapSurface[HOLO_GLOBE_VERTICES];
 
-	const char *getLocationName(int index) const override;
+	struct HolomapProjectedPos {
+		int16 x1 = 0; // screen X
+		int16 y1 = 0; // screen Y
+		uint16 x2 = 0; // texture U
+		uint16 y2 = 0; // texture V
+	};
+	HolomapProjectedPos _projectedSurfacePositions[HOLO_GLOBE_VERTICES];
 
-	/**
-	 * Clear Holomap location position
-	 * @param locationIdx Scene where position must be cleared
-	 */
-	void clrHoloPos(int32 locationIdx) override;
+	struct HolomapSort {
+		int16 z = 0;
+		uint16 projectedPosIdx = 0;
+	};
+	HolomapSort _holomapSort[HOLO_GLOBE_QUADS];
+
+	// Globe rendering state
+	int32 _holoAlpha = 0;
+	int32 _holoBeta = 0;
+	int32 _holoGamma = 0;
+	int32 _zoomPlanet = HOLO_ZOOM_INIT_PLANET;
+	int32 _zoomPlanetDest = HOLO_ZOOM_PLANET;
+
+	// Camera interpolation
+	int32 _destAlpha = 0;
+	int32 _destBeta = 0;
+	int32 _moveTimer = 0;
+	bool _automove = false;
+
+	// Objective/selection state
+	int32 _numObjectif = -1;
+	int32 _oldObjectif = -1;
+
+	// UI state
+	bool _flagRedraw = true;
+	bool _flagPal = true;
+	bool _flagHoloEnd = false;
+
+	// Holomap image
+	uint8 *_holomapImagePtr = nullptr;
+	int32 _holomapImageSize = 0;
+
+	void computeCoorMapping();
+	void computeCoorGlobe(Common::SeekableReadStream *surfaceStream);
+	void computeGlobeProj();
+	void drawHoloMap();
+	void drawListHoloGlobe(bool frontFace);
+	void drawReticule();
+	bool goToArrow();
+
+	int32 distance(float dist) const;
+	int32 scale(float val) const;
 
-	void holoTraj(int32 trajectoryIndex) override;
+public:
+	HolomapV2(TwinEEngine *engine) : Super(engine) {}
+	virtual ~HolomapV2() = default;
 
-	/** Load Holomap content */
+	bool setHoloPos(int32 locationIdx) override;
+	bool loadLocations() override;
+	const char *getLocationName(int index) const override;
+	void clrHoloPos(int32 locationIdx) override;
+	void holoTraj(int32 trajectoryIndex) override;
 	void initHoloDatas() override;
-
-	/** Main holomap process loop */
 	void holoMap() override;
 };
 
diff --git a/engines/twine/menu/menu.cpp b/engines/twine/menu/menu.cpp
index 1dc33c214a8..9529262e66d 100644
--- a/engines/twine/menu/menu.cpp
+++ b/engines/twine/menu/menu.cpp
@@ -251,8 +251,8 @@ void Menu::plasmaEffectRenderFrame() {
 
 void Menu::processPlasmaEffect(const Common::Rect &rect, int32 color) {
 	if (_engine->isLBA2()) {
-		// TODO: effects are handled differently here.
-		return;
+		// LBA2 uses palette bank 12 (192) for selected items
+		color = 192;
 	}
 	const int32 max_value = color + 15;
 
@@ -453,7 +453,12 @@ int16 Menu::drawButtons(MenuSettings *menuSettings, bool hover) {
 
 void Menu::menuDemo() {
 	// TODO: lba2 only show the credits only in the main menu and you could force it by pressing shift+c
-	// TODO: lba2 has a cd audio track (2) for the credits
+	if (_engine->isLBA2()) {
+		_engine->_music->playCdTrack(2);
+		_engine->_menuOptions->showCredits();
+		_engine->_screens->loadMenuImage(false);
+		return;
+	}
 	_engine->_menuOptions->showCredits();
 	if (_engine->_movie->playMovie(FLA_DRAGON3)) {
 		if (!_engine->_screens->loadImageDelay(TwineImage(Resources::HQR_RESS_FILE, 15, 16), 3)) {
diff --git a/engines/twine/renderer/screens.cpp b/engines/twine/renderer/screens.cpp
index 1d801a80c93..f9d0482a85f 100644
--- a/engines/twine/renderer/screens.cpp
+++ b/engines/twine/renderer/screens.cpp
@@ -34,17 +34,24 @@
 #include "twine/audio/music.h"
 #include "twine/resources/hqr.h"
 #include "twine/resources/resources.h"
+#include "twine/scene/gamestate.h"
+#include "twine/scene/scene.h"
 #include "twine/shared.h"
 #include "twine/twine.h"
 
 namespace TwinE {
 
 int32 Screens::mapLba2Palette(int32 palIndex) {
+	// LBA2 palette indices:
+	// 0 = PtrPalNormal (main palette from ress.hqr index 0)
+	// 1 = PtrPalCurrent (current island palette - already in _ptrPal after ChoicePalette)
+	// 2 = PtrPalEclair (lightning palette from ress.hqr index 10)
+	// 3 = PtrPalBlack (black palette from ress.hqr index 9)
 	static const int32 palettes[] {
 		RESSHQR_MAINPAL,
-		-1, // TODO: current palette
-		RESSHQR_BLACKPAL,
-		RESSHQR_ECLAIRPAL
+		-1, // current palette (special case: use _ptrPal as-is)
+		RESSHQR_ECLAIRPAL,
+		RESSHQR_BLACKPAL
 	};
 	if (palIndex < 0 || palIndex >= ARRAYSIZE(palettes)) {
 		return -1;
@@ -52,6 +59,46 @@ int32 Screens::mapLba2Palette(int32 palIndex) {
 	return palettes[palIndex];
 }
 
+// LBA2 XPL header structure
+#define RESS_XPL0 27
+#define RESS_XPL00 42
+
+void Screens::choicePalette() {
+	if (!_engine->isLBA2()) {
+		return;
+	}
+
+	int32 xplIndex;
+	// Interior scenes or Citadel after storm (chapter >= 2) use the interior palette
+	const bool tempeteFinie = _engine->_gameState->hasGameFlag(253) >= 2;
+	if (_engine->_scene->_island == 0 && tempeteFinie) {
+		xplIndex = RESS_XPL00;
+	} else {
+		xplIndex = RESS_XPL0 + _engine->_scene->_island;
+	}
+
+	uint8 *xplData = nullptr;
+	const int32 xplSize = HQR::getAllocEntry(&xplData, Resources::HQR_RESS_FILE, xplIndex);
+	if (xplSize == 0 || xplData == nullptr) {
+		warning("Failed to load XPL palette data for index %i", xplIndex);
+		return;
+	}
+
+	// XPL header: field at offset 4 is OffsetPalette
+	const int32 offsetPalette = READ_LE_INT32(xplData + 4);
+	if (offsetPalette + 768 > xplSize) {
+		warning("XPL palette offset out of bounds");
+		free(xplData);
+		return;
+	}
+
+	// Load palette from XPL data
+	const uint8 *palData = xplData + offsetPalette;
+	_ptrPal = Graphics::Palette(palData, NUMOFCOLORS);
+
+	free(xplData);
+}
+
 bool Screens::adelineLogo() {
 	_engine->_music->playMidiFile(31);
 
diff --git a/engines/twine/renderer/screens.h b/engines/twine/renderer/screens.h
index 10505c7b612..941664953e5 100644
--- a/engines/twine/renderer/screens.h
+++ b/engines/twine/renderer/screens.h
@@ -50,6 +50,9 @@ public:
 
 	int32 mapLba2Palette(int32 palIndex);
 
+	/** LBA2: Load the per-island XPL palette for the current scene */
+	void choicePalette();
+
 	/** main palette */
 	Graphics::Palette _ptrPal{0};
 	Graphics::Palette _palettePcx{0};
diff --git a/engines/twine/scene/scene.cpp b/engines/twine/scene/scene.cpp
index d79f2562068..90ab8e99f8f 100644
--- a/engines/twine/scene/scene.cpp
+++ b/engines/twine/scene/scene.cpp
@@ -613,6 +613,11 @@ void Scene::changeCube() {
 
 	_engine->_grid->initGrid(_newCube);
 
+	// LBA2: load per-island palette from XPL data
+	if (_engine->isLBA2()) {
+		_engine->_screens->choicePalette();
+	}
+
 	if (_flagChgCube == ScenePositionType::kZone) {
 		_sceneStart = _zoneHeroPos;
 	} else if (_flagChgCube == ScenePositionType::kScene || _flagChgCube == ScenePositionType::kNoPosition) {
diff --git a/engines/twine/script/script_life_v2.cpp b/engines/twine/script/script_life_v2.cpp
index a2586d3a26d..e23289df3b9 100644
--- a/engines/twine/script/script_life_v2.cpp
+++ b/engines/twine/script/script_life_v2.cpp
@@ -204,22 +204,36 @@ int32 ScriptLifeV2::lPALETTE(TwinEEngine *engine, LifeScriptContext &ctx) {
 	const int32 palIndex = engine->_screens->mapLba2Palette(ctx.stream.readByte());
 	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::PALETTE(%i)", palIndex);
 	engine->saveTimer(false);
-	HQR::getPaletteEntry(engine->_screens->_ptrPal, Resources::HQR_RESS_FILE, palIndex);
-	engine->setPalette(engine->_screens->_ptrPal);
-	engine->_screens->_flagPalettePcx = true;
+	if (palIndex == -1) {
+		// Index 1 = current palette (already in _ptrPal from ChoicePalette)
+		engine->setPalette(engine->_screens->_ptrPal);
+	} else {
+		HQR::getPaletteEntry(engine->_screens->_ptrPal, Resources::HQR_RESS_FILE, palIndex);
+		engine->setPalette(engine->_screens->_ptrPal);
+	}
 	engine->restoreTimer();
 	return 0;
 }
 
 int32 ScriptLifeV2::lFADE_TO_PAL(TwinEEngine *engine, LifeScriptContext &ctx) {
-	const int32 palIndex = engine->_screens->mapLba2Palette(ctx.stream.readByte());
+	const int32 rawIndex = ctx.stream.readByte();
+	const int32 palIndex = engine->_screens->mapLba2Palette(rawIndex);
 	debugC(3, kDebugLevels::kDebugScriptsLife, "LIFE::FADE_TO_PAL(%i)", palIndex);
 	engine->saveTimer(false);
-	HQR::getPaletteEntry(engine->_screens->_ptrPal, Resources::HQR_RESS_FILE, palIndex);
-	engine->_screens->fadeToPal(engine->_screens->_ptrPal);
-	engine->_screens->_flagPalettePcx = true;
-	if (palIndex == 3) { // Black palette?
+	if (rawIndex == 3) {
+		// Black palette requested - fade to black
+		engine->_screens->fadeToBlack(engine->_screens->_ptrPal);
+		HQR::getPaletteEntry(engine->_screens->_ptrPal, Resources::HQR_RESS_FILE, palIndex);
 		engine->_screens->_flagFade = true;
+	} else if (palIndex == -1) {
+		// Index 1 = current palette (already in _ptrPal)
+		engine->_screens->fadeToPal(engine->_screens->_ptrPal);
+	} else {
+		// Cross-fade from current to target palette
+		Graphics::Palette targetPal{0};
+		HQR::getPaletteEntry(targetPal, Resources::HQR_RESS_FILE, palIndex);
+		engine->_screens->fadePalToPal(engine->_screens->_ptrPal, targetPal);
+		engine->_screens->_ptrPal = targetPal;
 	}
 	engine->restoreTimer();
 	return 0;
@@ -616,10 +630,8 @@ int32 ScriptLifeV2::lPLAY_ACF(TwinEEngine *engine, LifeScriptContext &ctx) {
 	engine->_screens->_flagFade = true;
 
 	engine->_movie->playMovie(movie);
-	// TODO: lba2 is doing more stuff here - reset the cinema mode, init the scene and palette stuff
-	// if (CubeMode==CUBE_INTERIEUR) InitGrille( NumCube ) ;
-	// RazListPartFlow() ;
-	// ChoicePalette() ;
+	// Restore scene palette after movie
+	engine->_screens->choicePalette();
 	engine->setPalette(engine->_screens->_ptrPal);
 	engine->_screens->_flagFade = true;
 	engine->restoreTimer();


Commit: 9641a7a19acc1bdc3fd869ab910f13711c726fdd
    https://github.com/scummvm/scummvm/commit/9641a7a19acc1bdc3fd869ab910f13711c726fdd
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-05-24T21:30:55+02:00

Commit Message:
TWINE: LBA2: implemented body parser

Changed paths:
    engines/twine/parser/body.cpp


diff --git a/engines/twine/parser/body.cpp b/engines/twine/parser/body.cpp
index cee214d5558..258a4e9a5de 100644
--- a/engines/twine/parser/body.cpp
+++ b/engines/twine/parser/body.cpp
@@ -26,6 +26,9 @@
 #define	INFO_TRI	1
 #define	INFO_ANIM	2
 
+// LBA2 body flags
+#define MASK_OBJECT_ANIMATED (1 << 8)
+
 namespace TwinE {
 
 void BodyData::reset() {
@@ -206,33 +209,140 @@ bool BodyData::loadFromStream(Common::SeekableReadStream &stream, bool lba1) {
 	} else {
 		// T_BODY_HEADER (lba2)
 		const uint32 flags = stream.readUint32LE();
-		animated = (flags & INFO_ANIM) != 0;
-		stream.skip(4); // int16 size of header and int16 dummy
+		animated = (flags & MASK_OBJECT_ANIMATED) != 0;
+		stream.skip(4); // int16 SizeHeader and int16 Dummy
 		bbox.mins.x = stream.readSint32LE();
 		bbox.maxs.x = stream.readSint32LE();
 		bbox.mins.y = stream.readSint32LE();
 		bbox.maxs.y = stream.readSint32LE();
 		bbox.mins.z = stream.readSint32LE();
 		bbox.maxs.z = stream.readSint32LE();
-		stream.seek(0x20);
-#if 0
-		const uint32 bonesSize = stream.readUint32LE();
-		const uint32 bonesOffset = stream.readUint32LE();
-		const uint32 verticesSize = stream.readUint32LE();
-		const uint32 verticesOffset = stream.readUint32LE();
-		const uint32 normalsSize = stream.readUint32LE();
-		const uint32 normalsOffset = stream.readUint32LE();
-		const uint32 unk1Size = stream.readUint32LE();
-		const uint32 unk1Offset = stream.readUint32LE();
-		const uint32 polygonsSize = stream.readUint32LE();
-		const uint32 polygonsOffset = stream.readUint32LE();
-		const uint32 linesSize = stream.readUint32LE();
-		const uint32 linesOffset = stream.readUint32LE();
-		const uint32 spheresSize = stream.readUint32LE();
-		const uint32 spheresOffset = stream.readUint32LE();
-		const uint32 uvGroupsSize = stream.readUint32LE();
-		const uint32 uvGroupsOffset = stream.readUint32LE();
-#endif
+
+		// Offset table
+		const int32 nbGroupes = stream.readSint32LE();
+		const int32 offGroupes = stream.readSint32LE();
+		const int32 nbPoints = stream.readSint32LE();
+		const int32 offPoints = stream.readSint32LE();
+		const int32 nbNormals = stream.readSint32LE();
+		const int32 offNormals = stream.readSint32LE();
+		/*const int32 nbNormFaces =*/ stream.readSint32LE();
+		/*const int32 offNormFaces =*/ stream.readSint32LE();
+		const int32 nbPolys = stream.readSint32LE();
+		const int32 offPolys = stream.readSint32LE();
+		const int32 nbLines = stream.readSint32LE();
+		const int32 offLines = stream.readSint32LE();
+		const int32 nbSpheres = stream.readSint32LE();
+		const int32 offSpheres = stream.readSint32LE();
+		/*const int32 nbTextures =*/ stream.readSint32LE();
+		/*const int32 offTextures =*/ stream.readSint32LE();
+
+		// Load vertices (4 x int16 per point: x, y, z, pad)
+		stream.seek(offPoints);
+		_vertices.reserve(nbPoints);
+		for (int32 i = 0; i < nbPoints; ++i) {
+			const int16 x = stream.readSint16LE();
+			const int16 y = stream.readSint16LE();
+			const int16 z = stream.readSint16LE();
+			stream.skip(2); // padding
+			_vertices.push_back({x, y, z, 0});
+		}
+
+		// Load bones/groupes (4 x uint16: OrgGroupe, OrgPoint, NbPts, NbNorm)
+		stream.seek(offGroupes);
+		_bones.reserve(nbGroupes);
+		int16 vertexOffset = 0;
+		for (int32 i = 0; i < nbGroupes; ++i) {
+			const uint16 orgGroupe = stream.readUint16LE();
+			const uint16 orgPoint = stream.readUint16LE();
+			const uint16 nbPts = stream.readUint16LE();
+			const uint16 nbNorm = stream.readUint16LE();
+
+			BodyBone bone;
+			bone.parent = (i == 0) ? 0xffff : orgGroupe;
+			bone.vertex = orgPoint;
+			bone.firstVertex = vertexOffset;
+			bone.numVertices = nbPts;
+			bone.numNormals = nbNorm;
+			bone.initalBoneState.type = BoneType::TYPE_ROTATE;
+			bone.initalBoneState.x = 0;
+			bone.initalBoneState.y = 0;
+			bone.initalBoneState.z = 0;
+
+			for (int j = 0; j < nbPts; ++j) {
+				if (vertexOffset + j < (int)_vertices.size()) {
+					_vertices[vertexOffset + j].bone = i;
+				}
+			}
+			vertexOffset += nbPts;
+
+			_bones.push_back(bone);
+			_boneStates[i] = bone.initalBoneState;
+		}
+
+		// Load normals (4 x int16: x, y, z, prenormalizedRange)
+		stream.seek(offNormals);
+		_normals.reserve(nbNormals);
+		for (int32 i = 0; i < nbNormals; ++i) {
+			BodyNormal normal;
+			normal.x = stream.readSint16LE();
+			normal.y = stream.readSint16LE();
+			normal.z = stream.readSint16LE();
+			normal.prenormalizedRange = stream.readUint16LE();
+			_normals.push_back(normal);
+		}
+
+		// Load polygons
+		stream.seek(offPolys);
+		_polygons.reserve(nbPolys);
+		for (int32 i = 0; i < nbPolys; ++i) {
+			BodyPolygon poly;
+			poly.materialType = stream.readByte();
+			const uint8 numVerts = stream.readByte();
+			poly.intensity = stream.readSint16LE();
+
+			int16 normal = -1;
+			if (poly.materialType == MAT_FLAT || poly.materialType == MAT_GRANIT) {
+				normal = stream.readSint16LE();
+			}
+
+			poly.indices.reserve(numVerts);
+			poly.normals.reserve(numVerts);
+			for (int k = 0; k < numVerts; ++k) {
+				if (poly.materialType >= MAT_GOURAUD) {
+					normal = stream.readSint16LE();
+				}
+				const uint16 vertexIndex = stream.readUint16LE() / 6;
+				poly.indices.push_back(vertexIndex);
+				poly.normals.push_back(normal);
+			}
+			_polygons.push_back(poly);
+		}
+
+		// Load lines
+		stream.seek(offLines);
+		_lines.reserve(nbLines);
+		for (int32 i = 0; i < nbLines; ++i) {
+			BodyLine line;
+			stream.skip(1);
+			line.color = stream.readByte();
+			stream.skip(2);
+			line.vertex1 = stream.readUint16LE() / 6;
+			line.vertex2 = stream.readUint16LE() / 6;
+			_lines.push_back(line);
+		}
+
+		// Load spheres
+		stream.seek(offSpheres);
+		_spheres.reserve(nbSpheres);
+		for (int32 i = 0; i < nbSpheres; ++i) {
+			BodySphere sphere;
+			sphere.fillType = stream.readByte();
+			sphere.color = stream.readUint16LE();
+			stream.readByte();
+			sphere.radius = stream.readUint16LE();
+			sphere.vertex = stream.readUint16LE() / 6;
+			_spheres.push_back(sphere);
+		}
 	}
 
 	return !stream.err();


Commit: 4289094bc23006d4f414f2f96a195beb7eb2c392
    https://github.com/scummvm/scummvm/commit/4289094bc23006d4f414f2f96a195beb7eb2c392
Author: Martin Gerhardy (martin.gerhardy at gmail.com)
Date: 2026-05-24T21:30:55+02:00

Commit Message:
TWINE: fixed soft lock of bulldozer driven by twinsen

https://bugs.scummvm.org/ticket/15388

Changed paths:
    engines/twine/renderer/renderer.cpp
    engines/twine/scene/grid.cpp


diff --git a/engines/twine/renderer/renderer.cpp b/engines/twine/renderer/renderer.cpp
index 60df7dd9171..5cf26a976e5 100644
--- a/engines/twine/renderer/renderer.cpp
+++ b/engines/twine/renderer/renderer.cpp
@@ -182,30 +182,30 @@ IVec3 Renderer::setAngleCamera(int32 alpha, int32 beta, int32 gamma) {
 }
 
 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;
+	const int32 vx = (_matrixWorld.row1.x * vec.x + _matrixWorld.row1.y * vec.y + _matrixWorld.row1.z * vec.z) >> 14;
+	const int32 vy = (_matrixWorld.row2.x * vec.x + _matrixWorld.row2.y * vec.y + _matrixWorld.row2.z * vec.z) >> 14;
+	const int32 vz = (_matrixWorld.row3.x * vec.x + _matrixWorld.row3.y * vec.y + _matrixWorld.row3.z * vec.z) >> 14;
 	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;
+	const int64 vx = ((int64)_matrixWorld.row1.x * (int64)x + (int64)_matrixWorld.row1.y * (int64)y + (int64)_matrixWorld.row1.z * (int64)z) >> 14;
+	const int64 vy = ((int64)_matrixWorld.row2.x * (int64)x + (int64)_matrixWorld.row2.y * (int64)y + (int64)_matrixWorld.row2.z * (int64)z) >> 14;
+	const int64 vz = ((int64)_matrixWorld.row3.x * (int64)x + (int64)_matrixWorld.row3.y * (int64)y + (int64)_matrixWorld.row3.z * (int64)z) >> 14;
 	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;
+	const int64 vx = ((int64)_matrixWorld.row1.x * (int64)x + (int64)_matrixWorld.row2.x * (int64)y + (int64)_matrixWorld.row3.x * (int64)z) >> 14;
+	const int64 vy = ((int64)_matrixWorld.row1.y * (int64)x + (int64)_matrixWorld.row2.y * (int64)y + (int64)_matrixWorld.row3.y * (int64)z) >> 14;
+	const int64 vz = ((int64)_matrixWorld.row1.z * (int64)x + (int64)_matrixWorld.row2.z * (int64)y + (int64)_matrixWorld.row3.z * (int64)z) >> 14;
 	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;
+	const int32 vx = (matrix.row1.x * x + matrix.row1.y * y + matrix.row1.z * z) >> 14;
+	const int32 vy = (matrix.row2.x * x + matrix.row2.y * y + matrix.row2.z * z) >> 14;
+	const int32 vz = (matrix.row3.x * x + matrix.row3.y * y + matrix.row3.z * z) >> 14;
 	return IVec3(vx, vy, vz);
 }
 
@@ -246,12 +246,12 @@ void Renderer::rotMatIndex2(IMatrix3x3 *pDest, const IMatrix3x3 *pSrc, const IVe
 		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;
+		pDest->row1.y = (pSrc->row1.z * nSin + pSrc->row1.y * nCos) >> 14;
+		pDest->row1.z = (pSrc->row1.z * nCos - pSrc->row1.y * nSin) >> 14;
+		pDest->row2.y = (pSrc->row2.z * nSin + pSrc->row2.y * nCos) >> 14;
+		pDest->row2.z = (pSrc->row2.z * nCos - pSrc->row2.y * nSin) >> 14;
+		pDest->row3.y = (pSrc->row3.z * nSin + pSrc->row3.y * nCos) >> 14;
+		pDest->row3.z = (pSrc->row3.z * nCos - pSrc->row3.y * nSin) >> 14;
 		pSrc = pDest;
 	}
 
@@ -263,12 +263,12 @@ void Renderer::rotMatIndex2(IMatrix3x3 *pDest, const IMatrix3x3 *pSrc, const IVe
 		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;
+		tmp.row1.x = (pSrc->row1.y * nSin + pSrc->row1.x * nCos) >> 14;
+		tmp.row1.y = (pSrc->row1.y * nCos - pSrc->row1.x * nSin) >> 14;
+		tmp.row2.x = (pSrc->row2.y * nSin + pSrc->row2.x * nCos) >> 14;
+		tmp.row2.y = (pSrc->row2.y * nCos - pSrc->row2.x * nSin) >> 14;
+		tmp.row3.x = (pSrc->row3.y * nSin + pSrc->row3.x * nCos) >> 14;
+		tmp.row3.y = (pSrc->row3.y * nCos - pSrc->row3.x * nSin) >> 14;
 
 		pSrc = &tmp;
 	}
@@ -291,12 +291,12 @@ void Renderer::rotMatIndex2(IMatrix3x3 *pDest, const IMatrix3x3 *pSrc, const IVe
 			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;
+		pDest->row1.x = (pSrc->row1.x * nCos - pSrc->row1.z * nSin) >> 14;
+		pDest->row1.z = (pSrc->row1.x * nSin + pSrc->row1.z * nCos) >> 14;
+		pDest->row2.x = (pSrc->row2.x * nCos - pSrc->row2.z * nSin) >> 14;
+		pDest->row2.z = (pSrc->row2.x * nSin + pSrc->row2.z * nCos) >> 14;
+		pDest->row3.x = (pSrc->row3.x * nCos - pSrc->row3.z * nSin) >> 14;
+		pDest->row3.z = (pSrc->row3.x * nSin + pSrc->row3.z * nCos) >> 14;
 	} else if (pSrc != pDest) {
 		*pDest = *pSrc;
 	}
@@ -314,9 +314,9 @@ bool isPolygonVisible(const ComputedVertex *vertices) { // TestVuePoly
 void Renderer::rotList(const Common::Array<BodyVertex> &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->x = (int16)(((rotationMatrix->row1.x * vertex.x + rotationMatrix->row1.y * vertex.y + rotationMatrix->row1.z * vertex.z) >> 14) + destPos.x);
+		destPoints->y = (int16)(((rotationMatrix->row2.x * vertex.x + rotationMatrix->row2.y * vertex.y + rotationMatrix->row2.z * vertex.z) >> 14) + destPos.y);
+		destPoints->z = (int16)(((rotationMatrix->row3.x * vertex.x + rotationMatrix->row3.y * vertex.y + rotationMatrix->row3.z * vertex.z) >> 14) + destPos.z);
 
 		destPoints++;
 	}
@@ -358,9 +358,9 @@ void Renderer::transRotList(const Common::Array<BodyVertex> &vertices, int32 fir
 		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->x = ((translationMatrix->row1.x * tmpX + translationMatrix->row1.y * tmpY + translationMatrix->row1.z * tmpZ) >> 14) + destPos.x;
+		destPoints->y = ((translationMatrix->row2.x * tmpX + translationMatrix->row2.y * tmpY + translationMatrix->row2.z * tmpZ) >> 14) + destPos.y;
+		destPoints->z = ((translationMatrix->row3.x * tmpX + translationMatrix->row3.y * tmpY + translationMatrix->row3.z * tmpZ) >> 14) + destPos.z;
 
 		destPoints++;
 	}
diff --git a/engines/twine/scene/grid.cpp b/engines/twine/scene/grid.cpp
index 8cec35ef901..06005a19587 100644
--- a/engines/twine/scene/grid.cpp
+++ b/engines/twine/scene/grid.cpp
@@ -635,19 +635,15 @@ BlockEntry Grid::getBlockEntry(int32 xmap, int32 ymap, int32 zmap) const {
 ShapeType Grid::worldColBrick(int32 x, int32 y, int32 z) {
 	const IVec3 &collision = updateCollisionCoordinates(x, y, z);
 
-	if (collision.y <= -1) {
-		return ShapeType::kSolid;
-	}
-
-	if (collision.x < 0 || collision.x >= SIZE_CUBE_X) {
+	if (collision.x < 0 || collision.x >= SIZE_CUBE_X || collision.z < 0 || collision.z >= SIZE_CUBE_Z) {
 		return ShapeType::kNone;
 	}
 
-	if (collision.y < 0 || collision.y >= SIZE_CUBE_Y) {
-		return ShapeType::kNone;
+	if (collision.y <= -1) {
+		return ShapeType::kSolid;
 	}
 
-	if (collision.z < 0 || collision.z >= SIZE_CUBE_Z) {
+	if (collision.y >= SIZE_CUBE_Y) {
 		return ShapeType::kNone;
 	}
 
@@ -660,9 +656,9 @@ ShapeType Grid::worldColBrick(int32 x, int32 y, int32 z) {
 }
 
 const IVec3 &Grid::updateCollisionCoordinates(int32 x, int32 y, int32 z) {
-	_engine->_collision->_collision.x = (x + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
-	_engine->_collision->_collision.y = y / SIZE_BRICK_Y;
-	_engine->_collision->_collision.z = (z + DEMI_BRICK_XZ) / SIZE_BRICK_XZ;
+	_engine->_collision->_collision.x = (x + DEMI_BRICK_XZ) >> 9;
+	_engine->_collision->_collision.y = y >> 8;
+	_engine->_collision->_collision.z = (z + DEMI_BRICK_XZ) >> 9;
 	return _engine->_collision->_collision;
 }
 
@@ -684,14 +680,14 @@ bool Grid::shouldCheckWaterCol(int32 actorIdx) const {
 ShapeType Grid::worldColBrickFull(int32 x, int32 y, int32 z, int32 y2, int32 actorIdx) {
 	const IVec3 &collision = updateCollisionCoordinates(x, y, z);
 
-	if (collision.y <= -1) {
-		return ShapeType::kSolid;
-	}
-
 	if (collision.x < 0 || collision.x >= SIZE_CUBE_X || collision.z < 0 || collision.z >= SIZE_CUBE_Z) {
 		return ShapeType::kNone;
 	}
 
+	if (collision.y <= -1) {
+		return ShapeType::kSolid;
+	}
+
 	bool checkWater = shouldCheckWaterCol(actorIdx);
 	uint8 *pCube = _bufCube;
 	pCube += collision.x * SIZE_CUBE_Y * 2;




More information about the Scummvm-git-logs mailing list