[Scummvm-git-logs] scummvm master -> d89b3a457ff5af1acc32d4f29ad630b536f1ff90

sev- noreply at scummvm.org
Fri Feb 7 10:55:46 UTC 2025


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

Summary:
d89b3a457f VIDEO: QTVR: Improve projection rendering math


Commit: d89b3a457ff5af1acc32d4f29ad630b536f1ff90
    https://github.com/scummvm/scummvm/commit/d89b3a457ff5af1acc32d4f29ad630b536f1ff90
Author: elasota (1137273+elasota at users.noreply.github.com)
Date: 2025-02-07T11:55:14+01:00

Commit Message:
VIDEO: QTVR: Improve projection rendering math

Changed paths:
    video/qtvr_decoder.cpp


diff --git a/video/qtvr_decoder.cpp b/video/qtvr_decoder.cpp
index 0de419cfe85..13436f9e03a 100644
--- a/video/qtvr_decoder.cpp
+++ b/video/qtvr_decoder.cpp
@@ -555,6 +555,9 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
 
 	uint16 w = _decoder->getWidth(), h = _decoder->getHeight();
 
+	Graphics::Surface planarProjection;
+	planarProjection.create(w, h, _constructedPano->format);
+
 	if (!_projectedPano) {
 		_projectedPano = new Graphics::Surface();
 		_projectedPano->create(w, h, _constructedPano->format);
@@ -562,31 +565,179 @@ void QuickTimeDecoder::PanoTrackHandler::projectPanorama() {
 
 	PanoSampleDesc *desc = (PanoSampleDesc *)_parent->sampleDescs[0];
 
+	float cornerVectors[2][3];
+
+	float *topRightVector = cornerVectors[0];
+	float *bottomRightVector = cornerVectors[1];
+
+	bottomRightVector[1] = tan(_decoder->_fov * M_PI / 360.0);
+	bottomRightVector[0] = bottomRightVector[1] * (float)w / (float)h;
+	bottomRightVector[2] = 1.0f;
+
+	topRightVector[0] = bottomRightVector[0];
+	topRightVector[1] = -bottomRightVector[1];
+	topRightVector[2] = bottomRightVector[2];
+
+	// Apply pitch (tilt) rotation
+	float cosTilt = cos(_curTiltAngle * M_PI / 180.0);
+	float sinTilt = sin(_curTiltAngle * M_PI / 180.0);
+
+	for (int v = 0; v < 2; v++) {
+		float y = cornerVectors[v][1];
+		float z = cornerVectors[v][2];
+
+		float newZ = z * cosTilt - y * sinTilt;
+		float newY = y * cosTilt + z * sinTilt;
+
+		cornerVectors[v][1] = newY;
+		cornerVectors[v][2] = newZ;
+	}
+
+	float minTiltY = tan(desc->_vPanBottom * M_PI / 180.0f);
+	float maxTiltY = tan(desc->_vPanTop * M_PI / 180.0f);
+
+	// Compute the largest projected X value, which determines the horizontal angle range
+	float maxProjectedX = 0.0f;
+
+	// X coords are the same here so whichever has the lower Z coord will have the maximum projected X
+	if (topRightVector[2] < bottomRightVector[2])
+		maxProjectedX = topRightVector[0] / topRightVector[2];
+	else
+		maxProjectedX = bottomRightVector[0] / bottomRightVector[2];
+
+	float minProjectedY = topRightVector[1] / topRightVector[2];
+	float maxProjectedY = bottomRightVector[1] / bottomRightVector[2];
+
+	Common::Array<float> sideEdgeXYInterpolators;
+
+	// The X interpolators are from 0 to maxProjectedX
+	// The Y interpolators are the interpolator from minProjectedY to maxProjectedY
+	sideEdgeXYInterpolators.resize(h * 2);
+
 	for (uint16 y = 0; y < h; y++) {
-		for (uint16 x = 0; x < w; x++) {
-			float panAngle = _curPanAngle + (x - w / 2) * _decoder->_hfov / (float)w - desc->_hPanStart;
+		float t = ((float)y + 0.5f) / (float)h;
+
+		float vector[3];
+		for (int v = 0; v < 3; v++) {
+			vector[v] = cornerVectors[0][v] * (1.0f - t) + cornerVectors[1][v] * t;
+
+			float projectedX = vector[0] / vector[2];
+			float projectedY = vector[1] / vector[2];
+
+			sideEdgeXYInterpolators[y * 2 + 0] = projectedX / maxProjectedX;
+			sideEdgeXYInterpolators[y * 2 + 1] = (projectedY - minProjectedY) / (maxProjectedY - minProjectedY);
+		}
+	}
+
+	const bool isWidthOdd = ((w % 2) == 1);
+	uint16 halfWidthRoundedUp = (w + 1) / 2;
+
+	float halfWidthFloat = (float)w * 0.5f;
+
+	Common::Array<float> cylinderProjectionRanges;
+	Common::Array<float> cylinderAngleOffsets;
+
+	cylinderProjectionRanges.resize(halfWidthRoundedUp * 2);
+	cylinderAngleOffsets.resize(halfWidthRoundedUp);
 
-			if (panAngle < 0.0f)
-				panAngle += 360.0f;
+	for (uint16 x = 0; x < halfWidthRoundedUp; x++) {
+		float xFloat = (float)x;
 
-			panAngle = panAngle * M_PI / 180.0;
+		// If width is odd, then the first column is on the pixel center
+		// If width is even, then the first column is on the pixel boundary
+		if (!isWidthOdd)
+			xFloat += 0.5f;
 
-			// It is flipped 90 degrees
-			int u = desc->_sceneSizeY - 1 - ((float)desc->_sceneSizeY) / (desc->_hPanEnd - desc->_hPanStart) / M_PI * 180.0 * panAngle;
+		float t = xFloat / halfWidthFloat;
+		float xCoord = t * maxProjectedX;
 
-			float tiltAngle = _curTiltAngle + (y - h / 2) * _decoder->_fov / (float)h;
-			tiltAngle = tan(tiltAngle * M_PI / 180.0);
+		float yCoords[2] = {minProjectedY, maxProjectedY};
 
-			if (tiltAngle > 1.0)
-				tiltAngle = 1.0;
-			if (tiltAngle < -1.0)
-				tiltAngle = -1.00;
+		// Compute projection ranges
+		for (int v = 0; v < 2; v++) {
+			// Intersect (xCoord, yCoord[v], 1) with a 1-radius cylinder
+			float length = sqrt(xCoord * xCoord + 1.0f);
 
-			tiltAngle = (tiltAngle + 1.0f) / 2.0f;
+			float newY = yCoords[v] / length;
 
-			int v = desc->_sceneSizeX * tiltAngle;
+			cylinderProjectionRanges[x * 2 + v] = (newY - minTiltY) / (maxTiltY - minTiltY);
+		}
+
+		cylinderAngleOffsets[x] = atan(xCoord) * 0.5f / M_PI;
+	}
+
+	float angleT = fmod(_curPanAngle / 360.0, 1.0);
+	if (angleT < 0.0f)
+		angleT += 1.0f;
+
+	int32 panoWidth = _constructedPano->h;
+	int32 panoHeight = _constructedPano->w;
+
+	uint16 angleOffset = static_cast<uint32>(angleT * panoWidth);
+
+	// Convert cylinder projection into planar projection
+	for (uint16 projectionCol = 0; projectionCol < halfWidthRoundedUp; projectionCol++) {
+		int32 centerXImageCoord = static_cast<int32>(angleOffset);
+
+		int32 edgeCoordOffset = static_cast<int32>(cylinderAngleOffsets[projectionCol] * panoWidth);
+
+		int32 leftSrcCoord = centerXImageCoord - edgeCoordOffset;
+		int32 rightSrcCoord = centerXImageCoord + edgeCoordOffset;
+
+		int32 topSrcCoord = static_cast<int32>(cylinderProjectionRanges[projectionCol * 2 + 0] * panoHeight);
+		int32 bottomSrcCoord = static_cast<int32>(cylinderProjectionRanges[projectionCol * 2 + 1] * panoHeight);
+
+		if (topSrcCoord < 0)
+			topSrcCoord = 0;
+
+		// Should never happen
+		if (topSrcCoord >= panoHeight)
+			topSrcCoord = panoHeight - 1;
+
+		if (bottomSrcCoord >= panoHeight)
+			bottomSrcCoord = panoHeight - 1;
 
-			uint32 pixel = _constructedPano->getPixel(v, u);
+		// Should never happen
+		if (bottomSrcCoord < 0)
+			bottomSrcCoord = 0;
+
+		leftSrcCoord = leftSrcCoord % static_cast<int32>(panoWidth);
+		if (leftSrcCoord < 0)
+			leftSrcCoord += panoWidth;
+
+		rightSrcCoord = rightSrcCoord % static_cast<int32>(panoWidth);
+		if (rightSrcCoord < 0)
+			rightSrcCoord += w;
+
+		uint16 x1 = halfWidthRoundedUp - 1 - projectionCol;
+		uint16 x2 = w - halfWidthRoundedUp + projectionCol;
+
+		for (uint16 y = 0; y < h; y++) {
+			int32 sourceYCoord = (2 * y + 1) * (bottomSrcCoord - topSrcCoord) / (2 * h) + topSrcCoord;
+
+			uint32 pixel1 = _constructedPano->getPixel(sourceYCoord, leftSrcCoord);
+			uint32 pixel2 = _constructedPano->getPixel(sourceYCoord, rightSrcCoord);
+
+			planarProjection.setPixel(x1, y, pixel1);
+			planarProjection.setPixel(x2, y, pixel2);
+		}
+	}
+
+	// Convert planar projection into perspective projection
+	for (uint16 y = 0; y < h; y++) {
+		float xInterpolator = sideEdgeXYInterpolators[y * 2 + 0];
+		float yInterpolator = sideEdgeXYInterpolators[y * 2 + 1];
+
+		int32 srcY = static_cast<int32>(yInterpolator * (float)h);
+		int32 scanlineWidth = static_cast<int32>(xInterpolator * w);
+		int32 startX = (w - scanlineWidth) / 2;
+		int32 endX = startX + scanlineWidth;
+
+		// It would be better to compute a reciprocal and do this as a multiply instead,
+		// doing divides per pixel is SLOW!
+		for (uint16 x = 0; x < w; x++) {
+			int32 srcX = (2 * x + 1) * scanlineWidth / (2 * w) + startX;
+			uint32 pixel = planarProjection.getPixel(srcX, srcY);
 			_projectedPano->setPixel(x, y, pixel);
 		}
 	}




More information about the Scummvm-git-logs mailing list