I am implementing a zoom feature of a 20x20 grid that also has a 640x480 grid on top, where each made up square is called a resolution. The entire space is for instance 3x3 resolutions (640 * 3) in width and (480 * 3) in height and there is a scrollbar that can scroll over this area.
The first zoom level is 100% and so:
I set the grid size to *2, the resolutions also twice as big and the entire space * 2.
However, I'll also have to re-adjust the scrollbar position so that it remains on the same visual location after the zoom and I am really not that good in math. How do I do that?
A diagram:
*where the visible area should be after zoom
Edit: I create this simulator to quickly and easily do real-time simulations:
https://youtu.be/RfiaUXWNjQE
this one works with the following:
hNew = hCurrent * 2 + resolution.width / 2;
it obviously works with the first zoom level.
later update:
hNew = hCurrent * (1 + zoomLevels) + resolution.width / 2;
appears to be working for every zoom level.
The aforementioned approach works, but it can only map from a no-zoomed world to any zoom level rather than from every zoom level to every zoom level.
A better more conventional way is to convert current (before zoom) values of the horizontal/vertical scrollbar to %
then after zoom calculations and resizing get the value of that % of the new world size.
so for horizontal adjustment it would look like this:
h% = hValue * 100.0 / hValueMax;
// ...zoom...
hValueNew = h% * hValueNewMax / 100.0;
Related
I am creating the "perfect" sprite packer. This is a sprite packer that makes sure the output sprite is compatible with most if not all game engines and animation software. It is a program that merges images into a horizontal sprite sheet.
It converts (if needed) the source frames to BMP in memory
It considers the top-left pixel fully transparent for the entire image (can be configured)
It parses the frames each individually to find the real coordinates rect (where the actual frame starts, ends, its width and height (sometimes images may have a lot of extra transparent pixels).
It determines the frame box, which have the width and height of the frame with the largest width/height so that it is long enough to contain every frame. (For extra compatibility, every frame must have the same dimensions).
Creates output sprite with width of nFrames * wFrameBox
The problem is - anchor alignment. Currently, it tries to align each frame so that its center is on the frame box center.
if((wBox / 2) > (frame->realCoordinates.w / 2))
{
xpos = xBoxOffset + ((wBox / 2) - (frame->realCoordinates.w / 2));
}
else
{
xpos = xBoxOffset + ((frame->realCoordinates.w / 2) - (wBox / 2));
}
When animated, it looks better with it, but there is still this inconsistent horizontal frame position so that a walking animation looks like walking and shaking.
I also tried the following:
store the real x pixel position of the widest frame and use it as a reference point:
xpos = xBoxOffset + (frame->realCoordinates.x - xRef);
It also gives a little better results, showing that this is still not the correct algorithm.
Honestly, I don't know what am I doing.
What will be the correct way to align sprite frames (obtain the appropriate x position for drawing the next frame) given that the output sprite sheet have width of the number of frames multiplied by the width of the widest frame?
Your problem is that you first calculate the center then calculate the size of the required bounding box. That is why your image 'shakes' because in each image that center is different to the original center.
You should use the center of the original bounding box as your origin, then find out the size of each sprite, keeping track of the leftmost, rightmost, topmost and bottommost non transparent pixels. That would give you the bounding box you need to use to avoid the shaking.
The problem is that you will find that most sprites are already done that way, so the original bounding box is actually defined as to the minimum space to paint the whole sprite's sequence covering these non transparent pixels.
The only way to remove unused sprite space is to store the first sprite complete, and then the origin and dimensions of each other sprite, like is done in animated GIF and APNG ( Animated PNG -> https://en.wikipedia.org/wiki/APNG )
I've been working on a C Mandelbrot set program for the past few days and I managed to make it work fine, however, my end goal is to be able to smoothly zoom in the set with my mouse and that's something I haven't yet been able to do yet so I might need a bit of help !
Here's part of my code (well, the full mandelbrot function) :
//removed to free space
Here's a picture of the output :
(Sorry, it's not very pretty, colors were not my priority but I'll be sure to work on them as soon as I figure out the zoom !)
Mandelbrot
What I want to be able to do :
left click -> center of the image becomes mouse_x an mouse_y. Then, it starts zooming in as long as left click is held
right click -> [...] it starts zooming out as long as right click is held
move mouse -> if currently zooming in/out, the center of the image moves to the mouse's coordinates with it. Else nothing happens.
(already have a function that gets mouse's position and button being pressed)
Thanks a lot for your help !
The visible area is a rectangle defined by (Re.min, Im.min) and (Re.max, Im.max). When you click on a particular point, you can map the mouse position to a point (mouseRe, mouseIm) by using the same mapping as you use when rendering:
double mouseRe = (double)mouse_x / (WIN_L / (e->Re.max - e->Re.min)) + e->Re.min;
double mouseIm = (double)mouse_y / (WIN_H / (e->Im.max - e->Im.min)) + e->Im.min;
To zoom in, imagine drawing a line from the (mouseRe, mouseIm) zooming centerpoint to each of the corners of the visible area, forming a lopsided X. Based on the zoom amount, find 4 new points a certain fraction of the distance along these lines, these points will give you your new rectangle. For example, if you are zooming in by a factor of 3, find a point 1/3rd of the way from the centerpoint to the corners. This will produce a new rectangle with sides 1/3rd the size of the original and an area 1/9th the size.
To do this you can define a simple interpolation function:
double interpolate(double start, double end, double interpolation)
{
return start + ((end - start) * interpolation);
}
Then use the function to find your new points:
void applyZoom(t_fractal* e, double mouseRe, double mouseIm, double zoomFactor)
{
double interpolation = 1.0 / zoomFactor;
e->Re.min = interpolate(mouseRe, e->Re.min, interpolation);
e->Im.min = interpolate(mouseIm, e->Im.min, interpolation);
e->Re.max = interpolate(mouseRe, e->Re.max, interpolation);
e->Im.max = interpolate(mouseIm, e->Im.max, interpolation);
}
Based on my description, you might think you need to find 8 values (4 points for the 4 legs of the X with 2 dimension each) but in practise there are only 4 unique values because each of the sides is axis aligned.
For a smooth zoom, call it with a zoom factor of a little over 1.0 e.g. 1.01. To zoom out, pass the inverse e.g. 1.0 / 1.01.
Alternatively, if you want the center of the view to jump to a certain position when you click the mouse, calculate mouseRe and mouseIm as above and then offset the corners of the view rectangle by the difference between these values and the center of the view rectangle. You could store these values at the time the mouse button was first pressed down, and use them to zoom in as long as it is held.
I'd like to shrink a Leaflet map's bounds by some factor (say 50px or 5%).
I've tried the following:
marginPoint = new L.Point(50, 50);
bounds = map.getPixelBounds();
bottomLeft = bounds.getBottomLeft();
topRight = bounds.getTopRight();
bounds = new L.LatLngBounds(
map.unproject(bottomLeft.subtract(marginPoint)),
map.unproject(topRight.subtract(marginPoint))
);
However, this does not shrink the map, it just moves/translates the current box / current map's bounds.
Since you subtract the same amount to both map containers' corners, the result is that you just shift the map view to the South West (bottom left).
If you are trying to "zoom in" (i.e. the map displays a smaller portion of geographic area, but within the same container dimensions), you would add some pixels to your bottom left corners, and remove some to your top left.
But be noted that you could also simply use the latLngBounds.pad() method (with a negative bufferRatio argument) to achieve the same result.
See, I'm not posting code because I need logic, math, algorithms. Well:
I'm trying to achieve a 3d-looking visual for a top-down tile map using layers and parallax scrolling. The thing is: At the moment I simply set different "speeds" for each layer. But that would only work with some very specific camera positions, also, it makes so that the blocks have virtually an infinite height (as they will "increase in height" until they are out of the camera's FOV).
Is there a better (should be) to achieve the effect? Oh, and I'm using C with Allegro 5.
I thought about limiting each layer's offset, but I have no idea how.
My current method:
That's my current code for the layer "speed" (it repeats for up, down, left and right, changing coordinates):
if (key[ALLEGRO_KEY_UP])
camera_y[0] -= 1;
camera_y[1] -= 2;
camera_y[2] -= 3;
Then I run a loop to draw the map with the tiles relative to the current layer's offset.
By the way, that's the desired effect (example with 3 layers):
For parallax scrolling, layers that scroll faster must be correspondingly larger:
You can use unscaled tiles stacked on top of each other, offset by a fixed fraction of the distance from the center of the tile to the center of the viewport,
but the tops will not be continuous (unless the bottoms overlap). If all layer tiles are hand-drawn or rendered images, this is not an issue.
If the walls are box-shaped, and you have images of the top and each of the four sides, you can draw them in almost 3D,
where at most two sides of each box wall is drawn, skewed.
In all cases:
If the center of the viewport is at world coordinates (xc, yc), point (x, y, z) maps to coordinates (x', y') relative to the center of the viewport:
x' = (x - xc) × (z + z0) / z0
y' = (y - yc) × (z + z0) / z0
where z0 is a constant that determines the "size" of the parallax or depth effect.
I think you're on the right lines, but the "infinite height" issue can be solved by simply giving the camera an "altitude" property, and adjust the "speed" of each layer by calculating ...
layer.speed = (layer.altitude / camera.altitude) * ZOOM_FACTOR; //gives a float value.
Can't really suggest anything more until you show us some of your math code.
I need to implement a simple plotting component in C#(WPF to be more precise). What i have is a collection of data samples containing time (X axis) and a value (both double types).
I have a drawing canvas of a fixed size (Width x Height) and a DrawLine method/function that can draw on it. The problem I am facing now is how do I draw the plot so that it is autoscaled? In other words how do I map the samples I have to actual pixels on my Width x Height canvas?
One hacky method that may work is to use a Viewbox control. This control will scale the rendering of its content to fit the size available. However, this might lead to your lines and labels looking too thick or thin.
The more sensible method that you're probably after, though, is how to work out at what scale to draw your graph at in the first place. To do that, work out the range of values on a given axis (for example, your Y-axis value might range from 0 to 100). Work out the available drawing space on that axis (for example, your canvas might have 400 pixels of height). Your Y-axis "scale factor" when drawing the graph would be <available space> / <data range> - or, in this case, 4.
Your canvas' coordinates start from zero in the top-left so, to calculate the Y-position for a given data point, you would calculate like this:
double availableSpace = 400.0; // the size of your canvas
double dataRange = 100.0; // the range of your values
double scaleFactor = availableSpace / dataRange;
double currentValue = 42.0; // the value we're trying to plot
double plottableY = availableSpace - (currentValue * scaleFactor); // the position on screen to draw at
The value of plottableY is the y-coordinate that you would use to draw this point on the canvas.
(Obviously this code would need to be spread out across your drawing method so you're not recalculating all of the values for each point, but it demonstrates the math).