Sign in to follow this  
Finalspace

Letterbox for negative ranges (Dimension analysis?)

Recommended Posts

I want to compute a new letterboxed destination rectangle from the following example:

 Source rectangle (-10, -5 to 10, 5)

Destination rectangle (0, 1000, 800, 0)

Aspect ratio is determined by source rectangle sizes (w / h)

 

Source coordinate system is bottom up, target coordinate system is up bottom in Y.

 

My current code which i have used for many years to compute a letterbox rectangle cannot handle such a case:

 

		struct RenderArea {
			Vec2f sourceMin;
			Vec2f sourceMax;
			Vec2f targetMin;
			Vec2f targetMax;
			Vec2f sourceScale;

			inline Vec2f Project(const Vec2f &sourcePos) {
				Vec2f invScale = Vec2f(1.0f / sourceScale.x, 1.0f / sourceScale.y);
				Vec2f result = targetMin + Hadamard(sourcePos - sourceMin, invScale);
				return(result);
			}

			inline Vec2f Unproject(const Vec2f &targetPos) {
				Vec2f result = sourceMin + Vec2f((targetPos.x - targetMin.x) * sourceScale.x, (targetPos.y - targetMin.y) * sourceScale.y);
				return(result);
			}
		};

		inline RenderArea CalculateRenderArea(const Vec2f &sourceMin, const Vec2f &sourceMax, const Vec2f &targetMin, const Vec2f &targetMax, const bool letterBoxed) {
			RenderArea result = {};

			const Vec2f sourceRange = sourceMax - sourceMin;
			Vec2f targetRange = targetMax - targetMin;

			result.sourceMin = sourceMin;
			result.sourceMax = sourceMax;

			if (letterBoxed) {
				const f32 sourceAspect = sourceRange.w / sourceRange.h;
				Vec2f newTargetSize = Vec2f(targetRange.w, targetRange.w / sourceAspect);
				if (newTargetSize.h > targetRange.h) {
					newTargetSize.h = targetRange.h;
					newTargetSize.w = targetRange.h * sourceAspect;
				}
				Vec2f newTargetOffset = Vec2f((targetRange.w - newTargetSize.w) / 2.0f, (targetRange.h - newTargetSize.h) / 2.0f);
				targetRange = newTargetSize;

				result.targetMin = targetMin + newTargetOffset;
				result.targetMax = targetMin + newTargetOffset + newTargetSize;
			} else {
				result.targetMin = targetMin;
				result.targetMax = targetMax;
			}

			f32 scaleX = (sourceRange.x) / (targetRange.x);
			f32 scaleY = (sourceRange.y) / (targetRange.y);
			result.sourceScale = Vec2f(scaleX, scaleY);

			return(result);
		}

 

How can i to get it to work for negative ranges as well?

Edited by Finalspace

Share this post


Link to post
Share on other sites

I figured it out using dimensional analysis.

Basically what i did was to convert the incoming source vector into it a percentage and then convert that percentage lerp that back to the targets min/max.

 

Looks like that - if one is curious:

 

		struct RenderArea {
			Vec2f sourceMin;
			Vec2f sourceMax;
			Vec2f targetMin;
			Vec2f targetMax;
			Vec2f sourceScale;
			Vec2f targetScale;

			inline Vec2f Project(const Vec2f &sourcePos) {
				Vec2f percentage = Vec2f((sourcePos.x - sourceMin.x) * sourceScale.x, (sourcePos.y - sourceMin.y) * sourceScale.y);
				f32 x = Lerp(targetMin.x, percentage.x, targetMax.x);
				f32 y = Lerp(targetMin.y, percentage.y, targetMax.y);
				Vec2f result = Vec2f(x, y);
				return(result);
			}

			inline Vec2f Unproject(const Vec2f &targetPos) {
				Vec2f percentage = Vec2f((targetPos.x - targetMin.x) * targetScale.x, (targetPos.y - targetMin.y) * targetScale.y);
				f32 x = Lerp(sourceMin.x, percentage.x, sourceMax.x);
				f32 y = Lerp(sourceMin.y, percentage.y, sourceMax.y);
				Vec2f result = Vec2f(x, y);
				return(result);
			}
		};

		inline RenderArea CalculateRenderArea(const Vec2f &sourceMin, const Vec2f &sourceMax, const Vec2f &targetMin, const Vec2f &targetMax, const bool letterBoxed) {
			// @TODO: Do we want to allow source min/max to be flipped as well
			assert(sourceMin.x < sourceMax.x);
			assert(sourceMin.y < sourceMax.y);

			RenderArea result = {};

			Vec2f sourceRange = Absolute(sourceMax - sourceMin);
			Vec2f targetRange = Absolute(targetMax - targetMin);

			result.sourceMin = sourceMin;
			result.sourceMax = sourceMax;


			if (letterBoxed) {
				// @NOTE: Letterbox calculation is done in absolute range always
				const f32 sourceAspect = sourceRange.w / sourceRange.h;
				Vec2f newTargetSize = Vec2f(targetRange.w, targetRange.w / sourceAspect);
				if (newTargetSize.h > targetRange.h) {
					newTargetSize.h = targetRange.h;
					newTargetSize.w = targetRange.h * sourceAspect;
				}
				Vec2f newTargetOffset = Vec2f((targetRange.w - newTargetSize.w) / 2.0f, (targetRange.h - newTargetSize.h) / 2.0f);
				targetRange = newTargetSize;

				// Nuisance, but this works
				if (targetMin.x < targetMax.x) {
					result.targetMin.x = targetMin.x + newTargetOffset.x;
					result.targetMax.x = targetMax.x - newTargetOffset.x;
					targetRange.x = newTargetSize.w;
				} else {
					result.targetMin.x = targetMin.x - newTargetOffset.x;
					result.targetMax.x = targetMax.x + newTargetOffset.x;
					targetRange.y = -newTargetSize.h;
				}

				if (targetMin.y < targetMax.y) {
					result.targetMin.y = targetMin.y + newTargetOffset.y;
					result.targetMax.y = targetMax.y - newTargetOffset.y;
					targetRange.y = newTargetSize.h;
				} else {
					result.targetMin.y = targetMin.y - newTargetOffset.y;
					result.targetMax.y = targetMax.y + newTargetOffset.y;
					targetRange.y = -newTargetSize.h;
				}
			} else {
				result.targetMin = targetMin;
				result.targetMax = targetMax;
			}

			result.sourceScale = Vec2f(1.0f / sourceRange.x, 1.0f / sourceRange.y);
			result.targetScale = Vec2f(1.0f / targetRange.x, 1.0f / targetRange.y);

			return(result);
		}

I am pretty sure, that i could go the same thing with a inverse/transponse matrix, but i never tried that. Also i dont have a inverse function...

Edited by Finalspace

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this