Calculating the position of a parallax background (2d) - math

I'm trying to align 4 different parallax backgrounds in my game. These aren't endless tiling backgrounds like in a space shooter, they don't fill the whole screen. They're a row of trees running along the ground.
My images are bottom left aligned and I'm trying to align them all to the lowest point on the terrain.
I have the following information: The lowest point on the map that I want everything to line up at (is 300). The parallax / scroll factor of the background. The height of the background.
I've tried:
background.y = lowestPoint; // doesn't work
background.y = lowestPoint * parallaxFactor; // doesn't work, way off
background.y = lowestPoint + lowestPoint * parallaxFactor; // doesn't work
I'm obviously missing something here.
Basically, I'm trying to calculate where to put the background tile's registration point based on it's parallax factor and lowest point in the terrain.
Ideas?

If I understand this correctly; Screen position is calculated as
screenX = (positionX - cameraX) * parallaxFactor
screenY = (positionY - cameraY) * parallaxFactor
Then to align two positions with different parallax factors at some camera position, you need to match screen coordinates:
obj1.screenY = (obj1.positionY - cameraY) * obj1.parallaxFactor
obj2.screenY = (obj2.positionY - cameraY) * obj2.parallaxFactor
(obj1.positionY - cameraY) * obj1.parallaxFactor =
(obj2.positionY - cameraY) * obj2.parallaxFactor
obj1.positionY =
(obj2.positionY - cameraY) * F + cameraY
where F = obj2.parallaxFactor / obj1.parallaxFactor.
Similarly for the horizontal direction.

Related

compute mouse position within video with object-fit:contain

I am trying to convert a mouse event to pixel coordinates within a video. By pixel coordinates, I mean coordinates relative to the original video resolution.
My video element has object-fit: contain, which means that the top left corner of the video is not necessarily located at position (0,0), as this picture shows:
If I click on the top-left corner of the white section in this video then I want to get (0,0), but in order to do this I need to discover the offset of the video content (white area) relative to the video element (black border).
How can I recover this offset?
I am already aware of width, height, videoWidth, and videoHeight, but these only let me account for the scaling, not the offset.
The offset can be deduced. I think this kind of code should do the trick:
if(videoHeight/height > videoWidth/width){
scale = videoHeight/height;
offsetX = (videoWidth - width*scale)/2;
offsetY = 0;
}
else{
scale = videoWidth/width;
offsetY = (videoHeight - height*scale)/2;
offsetX = 0;
}
I was also interested in getting the actual pixel positions from mouse or touch events when using object-fit, and this is the only result I found when searching. Although I suspect it is probably too late to be helpful to you, I thought I'd answer in case anybody else comes across this in future like I did.
Because I'm working on code with other people, I needed a robust solution that would work even if someone changed or removed the object-fit or object-property in the css
The approach that I took was:
Implement the cover, contain etc algorithms myself, just functions doing math, not dependent on the DOM
Use getComputedStyle to get the element's objectFit and objectPosition properties
Use .getBoundingClientRect() to get the DOM pixel size of the element
Pass the element's current objectFit, objectPosition, its DOM pixel size and it's natural pixel size to my function to figure out where the fitted rectangle sat within the element
You then have enough information to transform the event point to a pixel location
There's more code than would comfortably fit here, but getting the size of the fitted rectangle for cover or contain is something like:
if ( fitMode === 'cover' || fitMode === 'contain' ) {
const wr = parent.width / child.width
const hr = parent.height / child.height
const ratio = fitMode === 'cover' ? Math.max( wr, hr ) : Math.min( wr, hr )
const width = child.width * ratio
const height = child.height * ratio
const size = { width, height }
return size
}
// handle other object-fit modes here
Hopefully this gives others a rough idea of how to solve this problem themselves, alternately I have published the code at the link below, it supports all object-fit modes and it includes examples showing how to get the actual pixel point that was clicked:
https://github.com/nrkn/object-fit-math

Set QGraphicsTextItem text contents of exact height and width

I am required to create text items with exact width and height of text contents.
The height of the text is the most important requirement.
The position of the text should be relative to the text itself.
I also have to be able to place it on canvas in an exact spot.
Assuming a (printable) canvas (on a larger QGraphicsScene), say 5 inch width and 1 inch height, my text should be able to stretch top-bottom-left-right - and be placed on canvas, not part in part out.
I am sub-classing QGraphicsTextItem for my item type. I am resizing it, using QTransform(), to required size - in inches or mm or pixels (72*in).
Also setting the document() margin to 0, and anything inside (like QTextBlockFormat margins) also to 0.
I have implemented a setItemSize(QSizeF sz) (with sz in pixels), that resizes the QGraphicsTextItem as required.
The sz is initialized using the item bounding rect.
Assuming no wrap, single line text (multi-line could be solved separately once this issue is resolved).
When adding the item to canvas, I still see a top and bottom margin - and this varies based on font choice.
I drew a rectangle around the item to see it.
The top/bottom distances depend on font choices.
I have tried to use font metrics to determine these distances (in paint() I have been drawing lines to try to determine the position and rectangle in which the text fits).
I would be happy to at least be able to determine correct size to use for upper case, no accents or special characters fonts (it would be a start, though naturally I would need to be able to use any characters).
But at least some way to determine the size and position (relative to the (0,0) of item) of the text content even in the simplest case.....
The font metrics tightBoundingRect() seems the most accurate for size, but it seems impossible to determine its position so that I can somehow create my items correctly, and maybe resize/shift them correctly to fit on canvas.
Here are some examples of my struggle to determine at least exact size and position of text, relative to the (0,0) of the item (assuming that once I do that, I am able to expose that info to outside or include the shift in the item transform on resize).
Notice that the size of the text advertised by font metrics does not always cover the text, and for different fonts I am not able to position the tight bounding rect (magenta) around the text itself. (I did multiple guesses, the code below is just one - the lines are trying to show different font metrics sizes).
The above were experiments in paint function of the text item inheriting QGraphicsTextItem:
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
// draw text
QGraphicsTextItem::paint(painter, option, widget);
QPen p;
p.setWidthF(0);
QFontMetricsF fm(this->font());
qreal ascent = fm.ascent(),
descent = fm.descent(),
hheight = fm.height();
QRectF r = QGraphicsTextItem::boundingRect();
QRectF rFont= fm.tightBoundingRect(toPlainText());
qreal xmax = r.right();
painter->save();
painter->setBrush(Qt::NoBrush);
// where is "ascent + descent"
p.setColor(Qt::green);
painter->setPen(p);
painter->drawLine(QPointF(2, ascent), QPointF(2, ascent + descent));
painter->drawLine(QPointF(2, ascent + descent), QPointF(xmax/2, ascent + descent));
// where is "height"
p.setColor(Qt::red);
painter->setPen(p);
painter->drawLine(QPointF(xmax/2, 0), QPointF(xmax/2, hheight));
painter->drawLine(QPointF(xmax/2, ascent + descent), QPointF(xmax, ascent + descent));
// where is "ascent"
p.setColor(Qt::yellow);
painter->setPen(p);
painter->drawLine(QPointF(6, 0), QPointF(6, ascent));
painter->drawLine(QPointF(6, ascent), QPointF(xmax, ascent));
// something that may look like top of the text
p.setColor(Qt::blue);
painter->setPen(p);
qreal yyy = ascent + rFont.y() + 1;
painter->drawLine(QPointF(5, yyy), QPointF(xmax, yyy));
// this should be useful... should be the natural offset
qreal yoffset = (r.height() - rFont.height()) / 2;
// qDebug() << yoffset << r << rFont;
//qreal y0 = (r.height() - fm.height())/2;
p.setColor(Qt::darkGreen);
painter->drawEllipse(10, yoffset, 1, 1);
// where is the font rect
p.setColor(Qt::magenta);
painter->setPen(p);
yoffset = (r.height() + rFont.height()) / 2;
painter->translate(0, yoffset);
painter->drawRect(rFont);
painter->restore();
}
I have also tried not using QGraphicsTextItem, but paint text inside a rectangle. The same thing happens.
(Qt 4.7 - 5.x)
This is not a good solution. This is an attempt to solve my own problem - in a first iteration - of setting text with given width and height, using font metrics.
Reasons for not being good -
Even after resize, text is smaller than desired, I don't understand why
The position is incorrect, based on font style the text can be above or below the canvas, meaning it gets clipped.
I resize it using a factor calculated from the item bounding rect size and the font metrics bounding rect (I used the tight bounding rect for more accurate size).
myText->setItemFontSize(12); // If I use font metrics I need to reset text size on every change, because resizing loses font info
QFontMetricsF fm(myText->font());
QRectF fmRect = fm.tightBoundingRect(myText.toPlainText().toUpper());
// without toUpper() the size is too small - even so it is a bit small
// I read tightBoundingRect is slow - but boundingRect and height and ascent all give values that result in even smaller size
//qreal absH = fm.ascent();
qreal absH = fmRect.height();
qreal absW = fmRect.width();
qreal absHeightRatio = myText->getItemSize().height() / absH;
qreal absWidthRatio = myText->getItemSize().width() / absW;
Then setting size:
myText->setItemSize(QSizeF(absWidthRatio * textLength, absHeightRatio * fontHeight));
// This function scales the `QTransform` on item
// but since I request a final position dependent on item size
// (including blank space around it) - it has no chance of being accurate.....
// This is where my next effort will go, figuring out how to get rid of the fluff
// around the item inside the scaling
The function for setting position: trying to center text:
// leftShift = 10, rightShift = 10 in example
myText->setPos(0,0);
QRectF r = myText->mapToScene(myText->boundingRect()).boundingRect();
QSizeF sz = r.size();
qreal w = sz.width();
qreal h = sz.height();
qreal cx = (m_docLength - w + leftShift - rightShift)/2 - r.left();
qreal cy = (m_docHeight - h)/2 - r.top();
myText->setPos(cx, cy);
The images below are for fontHeight = m_docHeight -
Desirable:
- either the entire size of text (ascent + descent) equals doc height, and text is centered vertically based on content
- or the upper case size text equals doc height and the descent is below document (text centered based on only upper case) - this would seem easier based on how QGraphicsTextItem seems to position it
Actual:
- the text is smaller no matter which parameters I use to scale, and centered based on upper case text
As shown above - I have no idea how I could center vertically based on content (so for edge-to-edge text the descent would fit in) - and in lieu of that, all I really want is edge-to-edge uppercase letters, but I can't seem able to achieve that.
Oh and these are for Arial type font - one of the best-behaved. Other fonts jump all over the place, either above or below the canvas. And for some fonts, the resulting text is actually smaller - which is inexplicable to me, because how can the tight bounding rectangle be smaller than the item bounding rectangle...
Still this is as far as I got to getting my text as close to "true" size and placed on a canvas that matches its size.

Draw linear gradient at a certain angle

I'm trying to draw linear gradients with libpixman using the pixman_image_create_linear_gradient() function. It works fine for drawing gradients that run from left to right and from top to bottom but I don't see how I can draw gradients at a specific angle (0-360 degrees) like they're possible in CSS. For example, a linear gradient that is rotated by 45 degrees.
I think one has to use the arguments p1 and p2 for this because they define the gradient direction but there is no documentation at all and I can't really figure out how to use these two parameters to rotate the gradient.
For vertical gradients I simply set them to
p1.x = 0; p1.y = 0;
p2.x = 0; p2.y = height - 1;
And for horizontal gradients I use
p1.x = 0; p1.y = 0;
p2.x = width - 1; p2.y = 0;
But which values should I use for arbitrary rotation? Simply applying a 2D rotation matrix to the points doesn't look right, e.g. when drawing a gradient that is 640x480 and rotating this by 45 degrees I end up with the points
p1.x = 81; p1.y = 560;
p2.x = 559; p2.y = 559;
which draws the gradient in the right direction but there are about 80 pixels of blank space on either side of the gradient so I must be doing something wrong.
Could anybody tell me how to get this right?
Thanks!
I guess that Pixman implements linear gradients in the same way that Cairo does, given that Cairo's image backend uses Pixman, so look at some docs for Cairo. For example, in http://www.cairographics.org/tutorial/ in the section "Drawing with Cairo", subsection "Preparing and Selecting a Source" there is an explanation of linear gradients.
For your 45 degree rotation, I would try the following (one point in the top left corner, the other one in the bottom right one):
p1.x = 0; p1.y = 0;
p2.x = width - 1; p2.y = height - 1;
P.S.: No, I do not know how gradients with an angle are specified in CSS.

How to create Vector3 for all resolutions

I'm trying to fit my interface for all resolutions. I have this code
private void OnEnable() {
GameObject back = Instantiate(Background) as GameObject;
BoxCollider2D back_bc2d = back.GetComponent<BoxCollider2D>();
float x = ((Screen.currentResolution.width) / back_bc2d.size.x) / 2;
float y = (Screen.currentResolution.height / back_bc2d.size.y);
back.transform.localScale = new Vector3(x, y, back.transform.localScale.z);
Destroy(back_bc2d);
}
As you can see, I'm trying to create something like GUI.DrawTexture (I don't want to use GUI.DrawTexture because of some reasons)
My texture have to draw on a half screen.
But on different resolutions it doesn't work. What I'm doing wrong?
Screen resolution does not mean anything in world space, only the aspect ratio is important there. The in game geometry size is the same in every resolution, only the viewport may be wider showing more of it - this is how the camera works.
Also, you should not derive localScale used in world space from screen space coordinates - they don't work this way.
If you want to position some geometry in worldspace relative to the camera, use http://docs.unity3d.com/ScriptReference/Camera.ScreenToWorldPoint.html

Choosing circle radius to fully fill a rectangle

the pixman image library can draw radial color gradients between two circles. I'd like the radial gradient to fill a rectangular area defined by "width" and "height" completely. Now my question, how should I choose the radius of the outer circle?
My current parameters are the following:
A) inner circle (start of gradient)
center pointer of inner circle: (width*0.5|height*0.5)
radius of inner circle: 1
color: black
B) outer circle (end of gradient)
center pointer of outer circle: (width*0.5|height*0.5)
radius of outer circle: ???
color: white
How should I choose the radius of the outer circle to make sure that the outer circle will entirely fill my bounding rectangle defined by width*height. There shall be no empty areas in the corners, the area shall be completely covered by the circle. In other words, the bounding rectangle width,height must fit entirely into the outer circle. Choosing
outer_radius = max(width, height) * 0.5
as the radius for the outer circle is obviously not enough. It must be bigger, but how much bigger?
Thanks!
The diameter of the circle should be the diagonal of the rectangle, which you can easily calculate from Pythagoras' Theorem. ie:
outer_radius = 0.5 * sqrt(width * width + height * height)
It's just Pythagoras:
outer_radius = sqrt((width / 2)^2 + (height / 2)^2);
or more simply:
outer_radius = sqrt(width^2 + height^2) / 2;
Your question isn't clear, but perhaps you want sqrt(w^2 + h^2) / 2
This is the distance from the center of the rectangle to its corner.
Use Pythagoras:
outer_radius = sqrt(width*width + height*height)*0.5
You want the length of the hypotenuse of a right triangle with sides equal width/2 and height/2. Alternatively, 1/2 the length of the diagonal of the rectangle.
Square root of (h/2 ^ 2 + w/2 ^ 2)
or 1/2 * Square root of (h^2 + w^2)
Make a little sketch, and apply Pythagoras's Theorem:
[sketch image used to go here; link is broken, and the host is flagged as malware now anyway]
In code:
outer_radius = sqrt(0.25 * (width*width + height*height))

Resources