[Scummvm-git-logs] scummvm master -> 5ec760c55ec9e902fdaad23ace1e040e1f678685

neuromancer noreply at scummvm.org
Sun Feb 8 14:17:25 UTC 2026


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

Summary:
5ec760c55e FREESCAPE: add a debugger.


Commit: 5ec760c55ec9e902fdaad23ace1e040e1f678685
    https://github.com/scummvm/scummvm/commit/5ec760c55ec9e902fdaad23ace1e040e1f678685
Author: dhruv (dhruvranger97 at gmail.com)
Date: 2026-02-08T15:17:21+01:00

Commit Message:
FREESCAPE: add a debugger.

added a debugger class for freescape engine with basic commands like info, toggle wireframe, visualize normals, isolate objs etc., this can help to solve specific rendering related artifacts.

Changed paths:
  A engines/freescape/debugger.cpp
  A engines/freescape/debugger.h
    engines/freescape/area.cpp
    engines/freescape/freescape.cpp
    engines/freescape/freescape.h
    engines/freescape/gfx.cpp
    engines/freescape/gfx.h
    engines/freescape/gfx_opengl_shaders.cpp
    engines/freescape/gfx_opengl_shaders.h


diff --git a/engines/freescape/area.cpp b/engines/freescape/area.cpp
index 7dd57ee3efd..90bb56e99b0 100644
--- a/engines/freescape/area.cpp
+++ b/engines/freescape/area.cpp
@@ -361,11 +361,33 @@ void Area::draw(Freescape::Renderer *gfx, uint32 animationTicks, Math::Vector3d
 	}
 
 	for (auto &obj : nonPlanarObjects) {
+		if (gfx->_debugHighlightObjectID != -1 && obj->getObjectID() != gfx->_debugHighlightObjectID)
+			continue;
+
 		obj->draw(gfx);
+
+		// draw bounding boxes
+		if (gfx->_debugRenderBoundingBoxes) {
+			if (gfx->_debugBoundingBoxFilterID == -1 || gfx->_debugBoundingBoxFilterID == obj->getObjectID()) {
+				gfx->drawAABB(obj->_boundingBox, 0, 255, 0);
+			}
+		}
 	}
 
 	for (auto &pair : offsetMap) {
-		pair._key->draw(gfx, pair._value);
+		Object *obj = pair._key;
+
+		if (gfx->_debugHighlightObjectID != -1 && obj->getObjectID() != gfx->_debugHighlightObjectID)
+			continue;
+
+		obj->draw(gfx, pair._value);
+
+		// draw bounding boxes
+		if (gfx->_debugRenderBoundingBoxes) {
+			if (gfx->_debugBoundingBoxFilterID == -1 || gfx->_debugBoundingBoxFilterID == obj->getObjectID()) {
+				gfx->drawAABB(obj->_boundingBox, 0, 255, 0);
+			}
+		}
 	}
 
 	_lastTick = animationTicks;
diff --git a/engines/freescape/debugger.cpp b/engines/freescape/debugger.cpp
new file mode 100644
index 00000000000..f9d1e4e80d2
--- /dev/null
+++ b/engines/freescape/debugger.cpp
@@ -0,0 +1,167 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "freescape/debugger.h"
+#include "freescape/freescape.h"
+#include "freescape/gfx.h"
+#include "freescape/objects/object.h"
+#include "freescape/area.h"
+#include "math/ray.h"
+
+namespace Freescape {
+Debugger *g_debugger;
+
+Debugger::Debugger(FreescapeEngine *vm) : GUI::Debugger(), _vm(vm) {
+	g_debugger = this;
+	// register commands
+	registerCmd("info", WRAP_METHOD(Debugger, cmdInfo)); // inspect object under cursor
+	registerCmd("bbox", WRAP_METHOD(Debugger, cmdShowBBox)); // toggle bboxes
+	registerCmd("wire", WRAP_METHOD(Debugger, cmdWireframe)); // toggle wireframe
+	registerCmd("normals", WRAP_METHOD(Debugger, cmdShowNormals)); // toggle normals
+	registerCmd("iso", WRAP_METHOD(Debugger, cmdHighlightID)); // isolate object
+	registerCmd("obj_pos", WRAP_METHOD(Debugger, cmdObjPos)); // get object pos
+	registerCmd("obj_mov", WRAP_METHOD(Debugger, cmdSetObjPos)); // move object
+	registerCmd("goto", WRAP_METHOD(Debugger, cmdGoto)); // teleport to a position
+}
+
+Debugger::~Debugger() {}
+
+bool Debugger::cmdShowBBox(int argc, const char **argv) {
+	if (argc < 2) {
+		debugPrintf("Usage: bbox <0/1> [id]\n");
+		return true;
+	}
+	_vm->_gfx->_debugRenderBoundingBoxes = atoi(argv[1]);
+	if (argc > 2)
+		_vm->_gfx->_debugBoundingBoxFilterID = atoi(argv[2]);
+	else
+		_vm->_gfx->_debugBoundingBoxFilterID = -1;
+	return true;
+}
+
+bool Debugger::cmdWireframe(int argc, const char **argv) {
+	if (argc < 2) {
+		debugPrintf("Usage: wire <0/1>\n");
+		return true;
+	}
+	_vm->_gfx->_debugRenderWireframe = atoi(argv[1]);
+	return true;
+}
+
+bool Debugger::cmdShowNormals(int argc, const char **argv) {
+	if (argc < 2) {
+		debugPrintf("Usage: normals <0/1>\n");
+		return true;
+	}
+	_vm->_gfx->_debugRenderNormals = atoi(argv[1]);
+	return true;
+}
+
+bool Debugger::cmdHighlightID(int argc, const char **argv) {
+	if (argc < 2) {
+		debugPrintf("Usage: iso <id> (-1 for all)\n");
+		return true;
+	}
+	_vm->_gfx->_debugHighlightObjectID = atoi(argv[1]);
+	return true;
+}
+
+bool Debugger::cmdInfo(int argc, const char **argv) {
+	if (!_vm->_currentArea)
+		return true;
+	Math::Vector3d pos = _vm->_position;
+	Math::Vector3d dir = _vm->_cameraFront;
+
+	// shoot a long raycast
+	Math::Ray ray(pos, dir);
+	Object *obj = _vm->_currentArea->checkCollisionRay(ray, 100000);
+
+	if (obj) {
+		debugPrintf("Object %d\n", obj->getObjectID());
+		debugPrintf("Type: %d | Flags: %x\n", obj->getType(), obj->getObjectFlags());
+		debugPrintf("Origin: %f %f %f\n", obj->getOrigin().x(), obj->getOrigin().y(), obj->getOrigin().z());
+		debugPrintf("Size: %f %f %f\n", obj->getSize().x(), obj->getSize().y(), obj->getSize().z());
+		if (obj->isGeometric()) {
+			debugPrintf("BBox: min(%f %f %f) max(%f %f %f)\n",
+						obj->_boundingBox.getMin().x(),
+						obj->_boundingBox.getMin().y(),
+						obj->_boundingBox.getMin().z(),
+						obj->_boundingBox.getMax().x(),
+						obj->_boundingBox.getMax().y(),
+						obj->_boundingBox.getMax().z());
+		}
+		if (obj->_partOfGroup) {
+			debugPrintf("Grouped: Yes (Group ID: %d)\n", obj->_partOfGroup->getObjectID());
+		}
+	} else {
+		debugPrintf("Raycast hit nothing.\n");
+	}
+	return true;
+}
+
+bool Debugger::cmdObjPos(int argc, const char **argv) {
+	if (argc < 2) {
+		debugPrintf("Usage: obj_pos <id>\n");
+		return true;
+	}
+	int id = atoi(argv[1]);
+	Object *obj = _vm->_currentArea->objectWithID(id);
+	if (!obj) {
+		debugPrintf("Object %d not found.\n", id);
+		return true;
+	}
+
+	debugPrintf("Origin: %f %f %f\n", obj->getOrigin().x(), obj->getOrigin().y(), obj->getOrigin().z());
+	return true;
+}
+
+bool Debugger::cmdSetObjPos(int argc, const char **argv) {
+	if (argc < 5) {
+		debugPrintf("Usage: obj_mov <id> <x> <y> <z>\n");
+		return true;
+	}
+	int id = atoi(argv[1]);
+	Object *obj = _vm->_currentArea->objectWithID(id);
+	if (!obj) {
+		debugPrintf("Object %d not found.\n", id);
+		return true;
+	}
+
+	Math::Vector3d newPos(atof(argv[2]), atof(argv[3]), atof(argv[4]));
+	obj->setOrigin(newPos);
+	debugPrintf("Moved Object %d to %f %f %f\n", id, newPos.x(), newPos.y(), newPos.z());
+	return true;
+}
+
+bool Debugger::cmdGoto(int argc, const char **argv) {
+	if (argc < 4) {
+		debugPrintf("Usage: goto <x> <y> <z>\n");
+		return true;
+	}
+	float x = atof(argv[1]);
+	float y = atof(argv[2]);
+	float z = atof(argv[3]);
+	_vm->_position = Math::Vector3d(x, y, z);
+	debugPrintf("Teleported to %f %f %f\n", x, y, z);
+	return true;
+}
+
+}
diff --git a/engines/freescape/debugger.h b/engines/freescape/debugger.h
new file mode 100644
index 00000000000..fa719919440
--- /dev/null
+++ b/engines/freescape/debugger.h
@@ -0,0 +1,51 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef FREESCAPE_DEBUGGER_H
+#define FREESCAPE_DEBUGGER_H
+
+#include "gui/debugger.h"
+
+namespace Freescape {
+
+class FreescapeEngine;
+
+class Debugger : public GUI::Debugger {
+public:
+	Debugger(FreescapeEngine *vm);
+	~Debugger();
+
+private:
+	FreescapeEngine *_vm;
+	bool cmdShowBBox(int argc, const char **argv);
+	bool cmdWireframe(int argc, const char **argv);
+	bool cmdShowNormals(int argc, const char **argv);
+	bool cmdHighlightID(int agrc, const char **argv);
+
+	bool cmdInfo(int argc, const char **argv);
+	bool cmdGoto(int argc, const char **argv);
+	bool cmdObjPos(int argc, const char **argv);
+	bool cmdSetObjPos(int argc, const char **argv);
+};
+
+}
+
+#endif
diff --git a/engines/freescape/freescape.cpp b/engines/freescape/freescape.cpp
index 3bc5dc64791..3a08393d147 100644
--- a/engines/freescape/freescape.cpp
+++ b/engines/freescape/freescape.cpp
@@ -33,6 +33,7 @@
 #include "freescape/objects/sensor.h"
 #include "freescape/sweepAABB.h"
 #include "freescape/doodle.h"
+#include "freescape/debugger.h"
 
 namespace Freescape {
 
@@ -232,6 +233,8 @@ FreescapeEngine::FreescapeEngine(OSystem *syst, const ADGameDescription *gd)
 	ConfMan.setInt("gamepad_controller_directional_input", 1 /* kDirectionalInputDpad */, gameDomain);
 #endif
 	g_freescape = this;
+	g_debugger = new Debugger(g_freescape);
+	setDebugger(g_debugger);
 }
 
 FreescapeEngine::~FreescapeEngine() {
diff --git a/engines/freescape/freescape.h b/engines/freescape/freescape.h
index 42b89a947a8..be13fd65f01 100644
--- a/engines/freescape/freescape.h
+++ b/engines/freescape/freescape.h
@@ -51,6 +51,7 @@ class RandomSource;
 namespace Freescape {
 
 class Renderer;
+class Debugger;
 
 #define FREESCAPE_DATA_BUNDLE "freescape.dat"
 
@@ -654,6 +655,7 @@ enum GameReleaseFlags {
 };
 
 extern FreescapeEngine *g_freescape;
+extern Debugger *g_debugger;
 
 } // namespace Freescape
 
diff --git a/engines/freescape/gfx.cpp b/engines/freescape/gfx.cpp
index 872b065ccc7..c777b01b514 100644
--- a/engines/freescape/gfx.cpp
+++ b/engines/freescape/gfx.cpp
@@ -50,6 +50,11 @@ Renderer::Renderer(int screenW, int screenH, Common::RenderMode renderMode, bool
 	_colorRemaps = nullptr;
 	_renderMode = renderMode;
 	_isAccelerated = false;
+	_debugRenderBoundingBoxes = false;
+	_debugBoundingBoxFilterID = -1;
+	_debugRenderWireframe = false;
+	_debugRenderNormals = false;
+	_debugHighlightObjectID = -1;
 	_authenticGraphics = authenticGraphics;
 
 	for (int i = 0; i < 16; i++) {
diff --git a/engines/freescape/gfx.h b/engines/freescape/gfx.h
index 11354aa0eba..77895cb0258 100644
--- a/engines/freescape/gfx.h
+++ b/engines/freescape/gfx.h
@@ -267,6 +267,16 @@ public:
 
 	int _scale;
 
+	// debug flags
+	bool _debugRenderBoundingBoxes;
+	int _debugBoundingBoxFilterID;
+	bool _debugRenderWireframe;
+	bool _debugRenderNormals;
+	int _debugHighlightObjectID;
+
+	// for drawing bounding boxes
+	virtual void drawAABB(const Math::AABB &aabb, uint8 r, uint8 g, uint8 b) {}
+
 	/**
 	 * Select the window where to render
 	 *
diff --git a/engines/freescape/gfx_opengl_shaders.cpp b/engines/freescape/gfx_opengl_shaders.cpp
index 5dbea5080b8..d84ce9069fd 100644
--- a/engines/freescape/gfx_opengl_shaders.cpp
+++ b/engines/freescape/gfx_opengl_shaders.cpp
@@ -459,6 +459,68 @@ void OpenGLShaderRenderer::drawCelestialBody(const Math::Vector3d position, floa
 	_triangleShader->unbind();
 }
 
+void OpenGLShaderRenderer::drawAABB(const Math::AABB &aabb, uint8 r, uint8 g, uint8 b) {
+	if (!aabb.isValid())
+		return;
+
+	Math::Vector3d min = aabb.getMin();
+	Math::Vector3d max = aabb.getMax();
+
+	// calculate the 8 corners of the box
+	Math::Vector3d c[8];
+	c[0] = Math::Vector3d(min.x(), min.y(), min.z());
+	c[1] = Math::Vector3d(max.x(), min.y(), min.z());
+	c[2] = Math::Vector3d(max.x(), max.y(), min.z());
+	c[3] = Math::Vector3d(min.x(), max.y(), min.z());
+	c[4] = Math::Vector3d(min.x(), min.y(), max.z());
+	c[5] = Math::Vector3d(max.x(), min.y(), max.z());
+	c[6] = Math::Vector3d(max.x(), max.y(), max.z());
+	c[7] = Math::Vector3d(min.x(), max.y(), max.z());
+
+	_triangleShader->use();
+	_triangleShader->setUniform("mvpMatrix", _mvpMatrix);
+	useColor(r, g, b);
+
+	// disable depth so we can see the box through walls
+	glDisable(GL_DEPTH_TEST);
+	glDisable(GL_CULL_FACE);
+	glLineWidth(2.0f);
+
+	// build lines (12 lines * 2 vertices = 24 vertices)
+	int idx = 0;
+
+	auto pushLine = [&](int i, int j) {
+		copyToVertexArray(idx++, c[i]);
+		copyToVertexArray(idx++, c[j]);
+	};
+
+	// bottom
+	pushLine(0, 1);
+	pushLine(1, 2);
+	pushLine(2, 3);
+	pushLine(3, 0);
+	// top
+	pushLine(4, 5);
+	pushLine(5, 6);
+	pushLine(6, 7);
+	pushLine(7, 4);
+	// sides
+	pushLine(0, 4);
+	pushLine(1, 5);
+	pushLine(2, 6);
+	pushLine(3, 7);
+
+	glBindBuffer(GL_ARRAY_BUFFER, _triangleVBO);
+	glBufferData(GL_ARRAY_BUFFER, idx * 3 * sizeof(float), _verts, GL_DYNAMIC_DRAW);
+	glEnableVertexAttribArray(0);
+	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
+	glDrawArrays(GL_LINES, 0, idx);
+
+	// restore state
+	glLineWidth(1.0f);
+	glEnable(GL_DEPTH_TEST);
+}
+
 void OpenGLShaderRenderer::renderCrossair(const Common::Point &crossairPosition) {
 	Math::Matrix4 identity;
 	identity(0, 0) = 1.0;
@@ -511,6 +573,11 @@ void OpenGLShaderRenderer::renderFace(const Common::Array<Math::Vector3d> &verti
 	_triangleShader->use();
 	_triangleShader->setUniform("mvpMatrix", _mvpMatrix);
 
+	if (_debugRenderWireframe) {
+		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+		useColor(0, 255, 0);
+	}
+
 	if (vertices.size() == 2) {
 		const Math::Vector3d &v1 = vertices[1];
 		if (v0 == v1)
@@ -527,6 +594,8 @@ void OpenGLShaderRenderer::renderFace(const Common::Array<Math::Vector3d> &verti
 		glDrawArrays(GL_LINES, 0, 2);
 
 		glLineWidth(1);
+		if (_debugRenderWireframe)
+			glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
 		return;
 	}
 
@@ -544,6 +613,32 @@ void OpenGLShaderRenderer::renderFace(const Common::Array<Math::Vector3d> &verti
 	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
 
 	glDrawArrays(GL_TRIANGLES, 0, vi + 3);
+
+	if (_debugRenderNormals && vertices.size() >= 3) {
+		// calculate center
+		Math::Vector3d center(0, 0, 0);
+		for (auto &v : vertices) center += v;
+		center /= (float)vertices.size();
+
+		// calculate normal vector
+		Math::Vector3d v1 = vertices[1] - vertices[0];
+		Math::Vector3d v2 = vertices[2] - vertices[0];
+		Math::Vector3d normal = Math::Vector3d::crossProduct(v1, v2);
+		normal.normalize();
+		Math::Vector3d tip = center + (normal * 50.0f);
+
+		// upload normal line
+		copyToVertexArray(0, center);
+		copyToVertexArray(1, tip);
+
+		useColor(255, 0, 255); // pink
+
+		glBufferData(GL_ARRAY_BUFFER, 2 * 3 * sizeof(float), _verts, GL_DYNAMIC_DRAW);
+		glDrawArrays(GL_LINES, 0, 2);
+	}
+
+	if (_debugRenderWireframe)
+		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
 }
 
 void OpenGLShaderRenderer::depthTesting(bool enabled) {
diff --git a/engines/freescape/gfx_opengl_shaders.h b/engines/freescape/gfx_opengl_shaders.h
index a748d4dac55..c259fc5d4dc 100644
--- a/engines/freescape/gfx_opengl_shaders.h
+++ b/engines/freescape/gfx_opengl_shaders.h
@@ -73,6 +73,7 @@ public:
 	int _variableStippleArray[128];
 
 	virtual void init() override;
+	virtual void drawAABB(const Math::AABB &aabb, uint8 r, uint8 g, uint8 b) override;
 	virtual void clear(uint8 r, uint8 g, uint8 b, bool ignoreViewport = false) override;
 	virtual void setViewport(const Common::Rect &rect) override;
 	virtual Common::Point nativeResolution() override;




More information about the Scummvm-git-logs mailing list