Can't get pinch zoom and rotate gestures working on a container in Nativescript - gestures

I've been trying to get an image zoom, pinch and rotate working for a while now based off of this github project: https://github.com/vakrilov/native-script-pan-scale-demo/blob/master/app/main-page.js . But I can't quite get the trigonometry right. Also I'm unsure why there needs to be a translation during the onPinch method.
//initial touch during onPinch method
//args has the touch positions and item is the item to scale
if (args.state === 1) {
var newOriginX = args.getFocusX() - item.translateX;
var newOriginY = args.getFocusY() - item.translateY;
var oldOriginX = item.originX * item.width;
var oldOriginY = item.originY * item.height;
item.translateX += (oldOriginX - newOriginX) * (1 - item.scaleX);
item.translateY += (oldOriginY - newOriginY) * (1 - item.scaleY);
item.originX = newOriginX / item.width;
item.originY = newOriginY / item.height;
startScale = item.scaleX;
}
Any help would be much appreciated.

instead of using item.width and item.height, you should use item.getMeasuredWidth() and item.getMeasuredHeight()

Related

Three.js calculate object distance required to fill screen

I've seen lots of questions on how to zoom the camera so an object fills the screen, but I'm trying to move the object to fill the screen.
I've been trying something like this using the original photos pixel size, and these objects have been scaled:
var dist = object.originalSize.height > $(window).height()
|| object.originalSize.width > $(window).width()
? ( $(window).height() / object.originalSize.height ) * 100
: 10;
var pLocal = new THREE.Vector3( 0, 0, -dist);
var target = pLocal.applyMatrix4( camera.matrixWorld );
var tweenMove = new TWEEN.Tween(object.position).to(target, 1500).easing(TWEEN.Easing.Cubic.InOut);
To come up with a vector to move the object to, however, I can't get the object to fill the screen. Any idea of the maths I need to calculate how far an object needs to be to fill the screen?
The object is a Object3D with different children depending on it's type.
I know the original photographs dimensions (object.originalSize.height) and I know the geometry that has been scaled up to fit with power of 2.
Any clue gratefully received on how to calculate the distance required from the camera to ensure the object fits inside the bounds of the screen.
I also know the bounding box of the item, i.e. from 1024 to 128.
This works, not sure why..
var vFOV = camera.fov * Math.PI / 180;
var ratio = 2 * Math.tan( vFOV / 2 );
var screen = ratio * (window.innerWidth / window.innerHeight) ;
var size = getCompoundBoundingBox( object ).max.y;
var dist = (size/screen) / 4;

Find which tiles are currently visible in the viewport of a Google Maps v3 map

I am trying to build support for tiled vector data into some of our Google Maps v3 web maps, and I'm having a hard time figuring out how to find out which 256 x 256 tiles are visible in the current map viewport. I know that the information needed to figure this out is available if you create a google.maps.ImageMapType like here: Replacing GTileLayer in Google Maps v3, with ImageMapType, Tile bounding box?, but I'm obviously not doing this to bring in traditional pre-rendered map tiles.
So, a two part question:
What is the best way to find out which tiles are visible in the current viewport?
Once I have this information, what is the best way to go about converting it into lat/lng bounding boxes that can be used to request the necessary data? I know I could store this information on the server, but if there is an easy way to convert on the client it would be nice.
Here's what I came up with, with help from the documentation (http://code.google.com/apis/maps/documentation/javascript/maptypes.html, especially the "Map Coordinates" section) and a number of different sources:
function loadData() {
var bounds = map.getBounds(),
boundingBoxes = [],
boundsNeLatLng = bounds.getNorthEast(),
boundsSwLatLng = bounds.getSouthWest(),
boundsNwLatLng = new google.maps.LatLng(boundsNeLatLng.lat(), boundsSwLatLng.lng()),
boundsSeLatLng = new google.maps.LatLng(boundsSwLatLng.lat(), boundsNeLatLng.lng()),
zoom = map.getZoom(),
tiles = [],
tileCoordinateNw = pointToTile(boundsNwLatLng, zoom),
tileCoordinateSe = pointToTile(boundsSeLatLng, zoom),
tileColumns = tileCoordinateSe.x - tileCoordinateNw.x + 1;
tileRows = tileCoordinateSe.y - tileCoordinateNw.y + 1;
zfactor = Math.pow(2, zoom),
minX = tileCoordinateNw.x,
minY = tileCoordinateNw.y;
while (tileRows--) {
while (tileColumns--) {
tiles.push({
x: minX + tileColumns,
y: minY
});
}
minY++;
tileColumns = tileCoordinateSe.x - tileCoordinateNw.x + 1;
}
$.each(tiles, function(i, v) {
boundingBoxes.push({
ne: projection.fromPointToLatLng(new google.maps.Point(v.x * 256 / zfactor, v.y * 256 / zfactor)),
sw: projection.fromPointToLatLng(new google.maps.Point((v.x + 1) * 256 / zfactor, (v.y + 1) * 256 / zfactor))
});
});
$.each(boundingBoxes, function(i, v) {
var poly = new google.maps.Polygon({
map: map,
paths: [
v.ne,
new google.maps.LatLng(v.sw.lat(), v.ne.lng()),
v.sw,
new google.maps.LatLng(v.ne.lat(), v.sw.lng())
]
});
polygons.push(poly);
});
}
function pointToTile(latLng, z) {
var projection = new MercatorProjection();
var worldCoordinate = projection.fromLatLngToPoint(latLng);
var pixelCoordinate = new google.maps.Point(worldCoordinate.x * Math.pow(2, z), worldCoordinate.y * Math.pow(2, z));
var tileCoordinate = new google.maps.Point(Math.floor(pixelCoordinate.x / MERCATOR_RANGE), Math.floor(pixelCoordinate.y / MERCATOR_RANGE));
return tileCoordinate;
};
An explanation: Basically, everytime the map is panned or zoomed, I call the loadData function. This function calculates which tiles are in the map view, then iterates through the tiles that are already loaded and deletes the ones that are no longer in the view (I took this portion of code out, so you won't see it above). I then use the LatLngBounds stored in the boundingBoxes array to request data from the server.
Hope this helps someone else...
For more recent users, it's possible to get tile images from the sample code in the documentation on this page of the Google Maps Javascript API documentation.
Showing Pixel and Tile Coordinates

How can I increase a GLatLngBounds (rectangle) by 10% in google maps API?

I have a map with a bunch of points on it, and I want to zoom such that all points fit on the screen. The code for this is fairly simple:
var bounds = new GLatLngBounds();
for (var n = 0; n < points.length; n++) {
bounds.extend(points[n].getLatLng());
}
zoomLevel = map.getBoundsZoomLevel(bounds);
to get the zoom level, you have to pass in a bounds object. To create a bounds object, you either give it two points like a rectangle, or you pass it a point and it increases in size (if necessary) to incorporate the point which is what I do above.
The problem is that the points appear right on the edge of the screen. I'd like to increase the size of the bounds by say, 10%. I can get the two points out of my bounds object using bounds.getNorthEast(); and bounds.getSouthWest(); but to increase them by 10% of the size of the bounds is proving tricky. I tried toSpan() on the bounds which gives you a LatLng containing the size of the bounds, and then did 10% of the Lat and Lng and added it to my coordinates. However, it did not seem to work. My code for this attempt was:
var pointNorthEast = bounds.getNorthEast();
var pointSouthWest = bounds.getSouthWest();
var latAdjustment = ((pointNorthEast.lat() - pointSouthWest.lat()) * increasePercentage) / 2;
var lngAdjustment = ((pointNorthEast.lng() - pointSouthWest.lng()) * increasePercentage) / 2;
var newPointNorthEast = new GLatLng(pointNorthEast.lat() + latAdjustment, pointNorthEast.lng() + lngAdjustment);
var newPointSouthWest = new GLatLng(pointSouthWest.lat() - latAdjustment, pointSouthWest.lng() - lngAdjustment);
bounds = new GLatLngBounds();
bounds.extend(newPointNorthEast);
bounds.extend(newPointSouthWest);
By my reckoning, bounds should then be 10% bigger. I'm not certain it represents just a rectangle or stores any coordinate data or not so I didn't take chances by adding/subtracting half of the increase on each side to keep it centered.
However, trying various increasePercentage values such as 1.1 or 1.5 it does not seem to make a difference.
Therefore I've either cocked up my logic, my maths, or the fact that zoomLevel is not a smooth scale is hampering my attempts.
I think I might have to just do zoomLevel -= 1 and have done with it, but I wanted to see if anyone had any ideas first.
here's updated code using current api version (v3.0):
var increasePercentage = 1.10; //10%
var pointSouthWest = mb.getSouthWest();
var latAdjustment = (pointNorthEast.lat() - pointSouthWest.lat()) * (increasePercentage - 1);
var lngAdjustment = (pointNorthEast.lng() - pointSouthWest.lng()) * (increasePercentage - 1);
var newPointNorthEast = new google.maps.LatLng(pointNorthEast.lat() + latAdjustment, pointNorthEast.lng() + lngAdjustment);
var newPointSouthWest = new google.maps.LatLng(pointSouthWest.lat() - latAdjustment, pointSouthWest.lng() - lngAdjustment);
bounds = new google.maps.LatLngBounds();
bounds.extend(newPointNorthEast);
bounds.extend(newPointSouthWest);
map.fitBounds(bounds);
The problem is in your Math, here's how you need to calculate adjustments:
var latAdjustment = ((pointNorthEast.lat() - pointSouthWest.lat()) * (increasePercentage - 1);
var lngAdjustment = ((pointNorthEast.lng() - pointSouthWest.lng()) * (increasePercentage - 1);

How do I get the visual width and height of a rotated component?

I'm playing around with code like this:
<s:Button id="test" label="test" transformX="{Math.floor(test.width/2)}" rotationY="20" x="20" y="20" />
The button is rotated on the Y axis and the rotate pivot is in the middle of the button.
This will create a button that looks something like this:
(source: jeffryhouser.com)
The rotated button is, visually, filling a different space than the x, y, height, and width values would you have believe.
The "A" value in my image is the height of the button. But, what I want to use for calculation and placement purposes is the B value.
Additionally, I'd like to perform similar calculations with the width; getting the width from the top right corner to the bottom left corner.
How do I do this?
I put together a sample to show off the various approaches for calculating this that people are suggesting. The source code is also available. Nothing is quite working like I'd expect. For example, turn the rotationSlider to 85. The button is effectively invisible, yet all approaches are still giving it height and width.
My math may be a bit rusty, but this is how I would find the answer :
You would extend a right-triangle from the right edge of the button to the bottom-most point of the diagram you have (A-B). You can then use the Law of Sines to get three angles : 90', 20' and 70' (90 will always be there, and then your variable - 180 for the third angle).
You can then use the following formula to find your answer :
B = ((button.width * sin(button.rotationY)) / (sin(90 -button.rotationY)) + (button.height)
getBounds(..) and getRect(..) are supposed to be the methods for getting the width and height of transformed objects.
Not tried them in Flex 4 yet, but they always worked for me in Flex 3.
The answer was in one of the comments from James Ward on this question and is located at this blog post.
The one thing the blog post doesn't say is that in many cases, the perspectiveProjection property of the transform property on the class in question will be null. The linked to example took care of this by setting the maintainProjectionCenter property to true. But, you could also create a new perspectiveProjection object like this:
object.transform.perspectiveProjection = new PerspectiveProjection();
I wrapped up the function from evtimmy into a class:
/**
* DotComIt/Flextras
* Utils3D.as
* Utils3D
* jhouser
* Aug 5, 2010
*/
package com.flextras.coverflow
{
import flash.geom.Matrix3D;
import flash.geom.PerspectiveProjection;
import flash.geom.Rectangle;
import flash.geom.Utils3D;
import flash.geom.Vector3D;
public class TransformUtilities
{
public function TransformUtilities()
{
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
//----------------------------------
// projectBounds
//----------------------------------
// info from
// http://evtimmy.com/2009/12/calculating-the-projected-bounds-using-utils3dprojectvector/
/**
* Method retrieved from
* http://evtimmy.com/2009/12/calculating-the-projected-bounds-using-utils3dprojectvector/
*
* #param bounds: The rectangle that makes up the object
* #param matrix The 3D Matrix of the item
* #param the projection of the item's parent.
*/
public static function projectBounds(bounds:Rectangle,
matrix:Matrix3D,
projection:PerspectiveProjection):Rectangle
{
// Setup the matrix
var centerX:Number = projection.projectionCenter.x;
var centerY:Number = projection.projectionCenter.y;
matrix.appendTranslation(-centerX, -centerY, projection.focalLength);
matrix.append(projection.toMatrix3D());
// Project the corner points
var pt1:Vector3D = new Vector3D(bounds.left, bounds.top, 0);
var pt2:Vector3D = new Vector3D(bounds.right, bounds.top, 0)
var pt3:Vector3D = new Vector3D(bounds.left, bounds.bottom, 0);
var pt4:Vector3D = new Vector3D(bounds.right, bounds.bottom, 0);
pt1 = Utils3D.projectVector(matrix, pt1);
pt2 = Utils3D.projectVector(matrix, pt2);
pt3 = Utils3D.projectVector(matrix, pt3);
pt4 = Utils3D.projectVector(matrix, pt4);
// Find the bounding box in 2D
var maxX:Number = Math.max(Math.max(pt1.x, pt2.x), Math.max(pt3.x, pt4.x));
var minX:Number = Math.min(Math.min(pt1.x, pt2.x), Math.min(pt3.x, pt4.x));
var maxY:Number = Math.max(Math.max(pt1.y, pt2.y), Math.max(pt3.y, pt4.y));
var minY:Number = Math.min(Math.min(pt1.y, pt2.y), Math.min(pt3.y, pt4.y));
// Add back the projection center
bounds.x = minX + centerX;
bounds.y = minY + centerY;
bounds.width = maxX - minX;
bounds.height = maxY - minY;
return bounds;
}
}
}
Although that is the answer to my question, I'm not sure if it was the solution to my problem. Thanks everyone!

Flex/ActionScript - rotate Sprite around its center

I have created a Sprite in Actionscript and rendered it to a Flex Canvas. Suppose:
var fooShape:Sprite = new FooSpriteSubclass();
fooCanvas.rawChildren.addChild(myshape);
//Sprite shape renders on screen
fooShape.rotation = fooNumber;
This will rotate my shape, but seems to rotate it around the upper-left
point of its parent container(the canvas).
How can I force the Sprite to rotate about is own center point? I could obviously
write code to calculate the rotation, and then have it re-render, but I think there
must be a built-in way to do this, and certainly do not want to 'reinvent the wheel'
if possible.
I am using FlexBuilder, and therefore do not have access to the full Flash API.
Thank you much!
The following steps are required to rotate objects based on a reference point (using Matrix object and getBounds):
Matrix translation (moving to the reference point)
Matrix rotation
Matrix translation (back to original position)
For example to rotate an object 90 degrees around its center:
// Get the matrix of the object
var matrix:Matrix = myObject.transform.matrix;
// Get the rect of the object (to know the dimension)
var rect:Rectangle = myObject.getBounds(parentOfMyObject);
// Translating the desired reference point (in this case, center)
matrix.translate(- (rect.left + (rect.width/2)), - (rect.top + (rect.height/2)));
// Rotation (note: the parameter is in radian)
matrix.rotate((90/180)*Math.PI);
// Translating the object back to the original position.
matrix.translate(rect.left + (rect.width/2), rect.top + (rect.height/2));
Key methods used:
Matrix.rotate
Matrix.translate
DisplayObject.getBounds
Didn't have much luck with the other examples. This one worked for me. I used it on a UIComponent.
http://www.selikoff.net/2010/03/17/solution-to-flex-image-rotation-and-flipping-around-center/
private static function rotateImage(image:Image, degrees:Number):void {
// Calculate rotation and offsets
var radians:Number = degrees * (Math.PI / 180.0);
var offsetWidth:Number = image.contentWidth/2.0;
var offsetHeight:Number = image.contentHeight/2.0;
// Perform rotation
var matrix:Matrix = new Matrix();
matrix.translate(-offsetWidth, -offsetHeight);
matrix.rotate(radians);
matrix.translate(+offsetWidth, +offsetHeight);
matrix.concat(image.transform.matrix);
image.transform.matrix = matrix;
}
Actually I had to add this code to make above solutions work for me.
private var _rotateCount = 0;
var _origginalMatrix:Matrix=new Matrix();
.........
if (_rotateCount++ >= 360 / angleDegrees)
{
myObject.transform.matrix = _origginalMatrix;
_rotateCount = 0;
return;
}
var matrix:Matrix = myObject.transform.matrix;
....
Without that after some long time rotated object slowly moves somewhere right top.
An alternative solution is to put your object inside another View, move it so that your image's center is at the container's top-left corner, and then rotate the container.
import spark.components.*;
var myContainer:View = new View();
var myImage:Image = new Image();
myContainer.addElement(myImage);
myImage.x = myImage.width / -2;
myImage.y = myImage.height / -2;
addElement(myContainer);
myContainer.rotation = whateverAngle;
One issue might be that the width of the image isn't know at the moment it is created, so you might want to find a way around that. (Hardcode it, or see if myImage.preliminaryWidth works)
/**
* Rotates the object based on its center
* Parameters: #obj => the object to rotate
* # rotation => angle to rotate
* */
public function RotateAroundCenter(obj:Object, rotation:Number):void
{
var bound:Rectangle = new Rectangle();
// get the bounded rectangle of objects
bound = obj.getRect(this);
// calculate mid poits
var midx1:Number = bound.x + bound.width/2;
var midy1:Number = bound.y + bound.height/2;
// assign the rotation
obj.rotation = rotation;
// assign the previous mid point as (x,y)
obj.x = midx1;
obj.y = midy1;
// get the new bounded rectangle of objects
bound = obj.getRect(this);
// calculate new mid points
var midx2:Number = bound.x + bound.width/2;
var midy2:Number = bound.y + bound.height/2;
// calculate differnece between the current mid and (x,y) and subtract
//it to position the object in the previous bound.
var diff:Number = midx2 - obj.x;
obj.x -= diff;
diff = midy2 - obj.y;
obj.y -= diff;
}
//////////////////
Usage:
you can use the above function as described below,
var img:Canvas = new Canvas()
RotateAroundCenter(img, rotation);
This will help you
REf: http://subashflash.blogspot.in/2010/08/rotation-of-object-based-on-center.html
If you want to rotate around the center, merely center the asset inside your sprite by setting the internal assets x and y to half of the width and height of the asset. This swill center your content and allow it to rotate around a center point.
An example of runtime loaded assets is as follows:
var loader:Loader = new Loader():
var request:URLRequest = new URLRequest(path/to/asset.ext);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, _onLoaderComplete);
loader.load(request);
private function _onLoaderComplete(e:Event):void
{
var mc:MovieClip = e.target.content as MovieClip;
mc.x = -mc.width * 0.5;
mc.y = -mc.height * 0.5;
mc.rotation = 90;
addChild(mc);
}

Resources