I have a plane with scale (64,1,36) and rotation is (90,-180,0) and need the local coordinate of a raycast hit in the 2d coordinates format:
(0,0)-------(64,0)
| |
| |
| |
(0,36)------(64,36)
with my current code:
RaycastHit hit;
Vector3 coords = new Vector3();
if (Physics.Raycast(GazeOriginCombinedLocal, GazeDirectionCombined, out hit, Mathf.Infinity))
{
if (!hit.transform.Equals("Cube"))
{
Pointer.transform.position = hit.point; //Green cube for visualization of hit in worldspace
// coords = RectTransformUtility.ScreenPointToLocalPointInRectangle(Plane, hit.point, Camera.main, out coords);// no result at all
}
}
Trying this:
hit.transform.InverseTransformPoint(hit.point)
gives me this
(5,-5)---(-5,-5)
| |
| (0,0) |
| |
(5,5)----(-5,5)
Does some have an idea to get the needed format?
Thats how my plane which is a child of the main camera and my hierarchy looks like:
Thanks in advance
I think you could use the Transform.InverseTransformPoint which
Transforms position from world space to local space.
And then since this also is affected by the scale multiple it again by the scale of the plane using Vector3.Scale.
So your coords should probably be something like
coords = hit.transform.localScale / 2f + Vector3.Scale(hit.transform.InverseTransformPoint(hit.point), hit.transform.localScale);
can't test it right now though since typing on smartphone. You might e.g. need to invert the y/z component according to your needs and depending how the plane is rotated etc. But I hope this gives you an idea
In order to debug what's wrong you should probably print out the values step by step
var scale = hit.transform.localScale; // 64, 1, 36
var halfScale = scale / 2f; // 32, 0.5, 18
var localHitPoint = hit.transform.InverseTransformPoint(hit.point);
Debug.Log($"{nameof(localHitPoint)}:{localHitPoint:0.000}");
So what I had expected originally here would be values like
(-0.5, 0.5, 0)----(0.5, 0.5, 0)
| |
| (0, 0, 0) |
| |
(-0.5, -0.5, 0)---(0.5, -0.5, 0)
BUT as you now added: Your plane is rotated!
The 90° on X actually makes that Y and Z switch places. So in order to get the desired Y coordinate you would rather read the localHitPoint.z.
Then the 180° on Y basically inverts both X and Z.
So I would now expect the values to look like
(0.5, 0, -0.5)----(-0.5, 0, -0.5)
| |
| (0, 0, 0) |
| |
(0.5, 0, 0.5)---(-0.5, 0, 0.5)
Which looks pretty much like the values you describe you are getting. Not sure though why you have a factor of 10 and why you didn't need to switch Y and Z.
However since you actually want the 0,0 to be in the top-left corner you only need to flip the X axis and use Z instead of Y so
fixedLocalHitPoint = new Vector2(-localHitPoint.x, localHitPoint.z);
Debug.Log($"{nameof(fixedLocalHitPoint)}:{fixedLocalHitPoint:0.000}");
Which should now give you values like
(-0.5, -0.5)----(0.5, -0.5)
| |
| (0, 0) |
| |
(-0.5, 0.5)----(0.5, 0.5)
And still you need to scale it up again
var scaledHitPoint = Vector2.Scale(fixedLocalHitPoint, new Vector2 (scale.x, scale.z));
Debug.Log($"{nameof(scaledHitPoint)}:{scaledHitPoint:0.000}");
Which should now give values like
(-32, -18)----(32, -18)
| |
| (0, 0) |
| |
(-32, 18)-----(32, 18)
That's why you need to add the center point as a reference
coords = new Vector2(halfScale.x, halfScale.z) + scaledHitPoint;
Debug.Log($"{nameof(coords)}:{coords:0.000}");
Which now should be
(0, 0)------(64, 0)
| |
| (32, 18) |
| |
(0, 36)-----(64, 36)
I hope this brings a bit more light into where these "strange" values come from.
Since your camera is scaled 1,1,1 and there is nothing else involved I have a hard time finding where the factor of 10 would have sneaked its way into the calculation to be honest.
If you want to convert this:
hit.transform.InverseTransformPoint(hit.point)
which gives this:
(5,-5)---(-5,-5)
| |
| (0,0) |
| |
(5,5)----(-5,5)
to this:
(0,0)-------(64,0)
| |
| |
| |
(0,36)------(64,36)
Why not do this:
Vector2.Scale(
hit.transform.InverseTransformPoint(hit.point) - new Vector2(5,-5),
new Vector2(-6.4, 3.6)
);
This answer hardcodes the (5,-5) and (-6.4, 3.6) terms because the question doesn't include enough information to use variables instead.
Assuming the scale of the parent of the plane (Main Camera) is (10,10), then this should suffice:
Vector3 planeScale = hit.transform.localScale;
Vector3 cameraScale = hit.transform.parent.localScale;
result = Vector2.Scale(
hit.transform.InverseTransformPoint(hit.point)
- new Vector2(cameraScale * 0.5f ,-cameraScale * 0.5f),
new Vector2(-planeScale.x * 0.5f/cameraScale.x, planeScale.y * 0.5f / cameraScale.y)
);
Related
I am plotting a line on a MapboxGL (js) world map that follows the path of orbital objects. I do this by adding a new set of decimal longitude/latitude coordinates to a line geometry array as the orbit of the object updates.
There is a known issue with Mapbox (and others) that when drawing a line that crosses the 180° meridian (longitude), we do not get a nice straight line from a to b, we get a very long line that wraps around the whole globe from a to b:
instead of: we get:
/ /
/ /___
.../..... ......... 180° meridian
/ ___
/ /
/ /
"Accepted" answers here and at Mapbox suggest shifting to a 0°/360° longitude range, but this just moves the problem from the equator to the pole. This is fine for most general proposes, but is still an issue for orbital tracking where we may be crossing the 0°/360° meridian.
My solution is to use MultiLine geometry and break up my coords into new arrays when I cross this meridian, however, this will always leave a wee gap, or, if I "180, lat" either side, we get a "kink" at the meridian:
gap: or kink:
/ /
/ /
........ .....|... 180° meridian
/
/ /
/ /
So I need to figure out what the exact latitude would be if the longitude is on the meridian, knowing the start and end points either side:
+170 | p2 /:
| / :
| / :
180 -|-----/ pX? -- 180° meridian
| /: :
(lng) | / : :
| / : :
-170 |_/___:___:___
p1 x?
(lat)
I need to solve for latitude x so I can generate pX (knowing p1 and p2 if longitude where 180). Once I have pX, I can add this to the end of the last line and to the beginning of the next, thus closing the gap (or smoothing the "kink").
I know this is basic Trig, but my old-man-brain has failed me .. again ....
The simple way to split a line in this way would be to use Turf's lineSplit function. Something like:
const meridian = turf.lineString([[180, -90], [180, 90]]);
const linePieces = turf.lineSplit(myline, meridian);
I haven't tried this, so not sure if Turf itself has any weirdness at the meridian. If it does, you might have to temporarily translate the coordinates elsewhere or something.
Better than doing your own trigonometry in any case, especially since it may introduce errors with the world not being flat.
SOLVED! With basic Trig (while writing the question - so I am posting it anyway, just incase it helps someone else):
We are basically playing with two right triangles: p1 to p2, and the smaller right triangle where the opposite side stops at the meridian, both with the same hypotenuse angle. So, we have:
+170 | p2 /|
| / |
| / |
180 -|-----/ pX? -- 180° meridian
| /: |
(lng) | / : A |
| / B: |
-170 |_/___:___|___
p1 x?
(lat)
Where A is our p1 to p2 right angle triangle and B is the triangle from p1 longitude to the meridian, whose adjacent side we need to work out.
Pythagoras basically teaches us that all we need is two points of data (other then the right angle) of a right triangle to solve any other.
We already have the opposite and adjacent lengths of A:
+170 | p2 /|
| /α|
| / |
180 -|-- / | -- 180° meridian
| / |
(lng) | / A | oppositeA
| / |
-170 |_/β______|___
p1 adjacentA
(lat)
So from here we need to calculate the hypotenuse of A to get the angle of the hypotenuse of A (α) so we can use it later:
// add 360 to a negative longitude to shift lng from -180/+180, to 0/360
p1 = { lng: p1.lng < 0 ? p1.lng + 360 : p1.lng, lat: p1.lat }
p2 = { lng: p2.lng < 0 ? p2.lng + 360 : p2.lng, lat: p2.lat }
let oppositeA = Math.abs(p2.lng - p1.lng) // get A opposite length
let adjacentA = Math.abs(p2.lat - p1.lat) // get A adjacent length
let hypotenuseA = Math.sqrt(Math.pow(oppositeA,2) + Math.pow(adjacentA,2)) // calc A hypotenuse
let angleA = Math.asin(oppositeA / hypotenuseA) // calc A hypotenuse angle
Now we need the new opposite of B (p1.lng to 180) and our calculated angle of A to work out the new hypotenuse of B so we can get the new adjacent of B:
+170 | p2 /
| /
| /
180 -|-- / -- 180° meridian
| /: B
(lng) | /α:
| / : oppositeB
-170 |_/___:___ ___
p1 adjacentB
(lat)
let oppositeB = Math.abs(180 - p1.lng) // get B opposite
let hypotenuseB = oppositeB / Math.cos(angleA) // calc B hypotenuse using A angle
let adjacentB = Math.sqrt(Math.pow(oppositeB,2) + Math.pow(hypotenuseB,2)); calc B adjacent
Now we add the new adjacent to p1 latitude, and we have x! So:
let pX = { lng: 180, lat: p1.lat + adjacentB }
End the last line array and start the next with pX, and the gap is perfectly closed!
Highschool math (well, the genius of Pythagoras) to the rescue! I knew it was rattling around in that old-man-brain somewhere .....
Is it possible to get tiles LngLat bounding box? (and center/width if possible)
I.e given any tile "id" (e.g 6/33/24), calculate wanted coordinates. I'm so desperate to get an answer that I don't even care in what language it's written.
Context
Tile "id" has 3 parts: 6/33/24 (z/x/y).
z being floored zoom (0-24) and x/y tile number from left/top origin point.
When zoom is 1, whole map is divided into 4 equal tiles (shown in graphic). Every time zoom (z) increases, each tile is subdivided into 4 equal tiles (e.g zoom 2 = 16 tiles).
_______________________
| | |
| 1/0/0 | 1/1/0 |
| | |
|__________|___________|
| | |
| 1/0/1 | 1/1/1 |
| | |
|__________|___________|
Why?
I want to implement client-side marker cache and binding them to tiles seems to be the most reasonable solution. I know how to get tiles (loop over sourceCaches tiles or use few transform methods) but I have no idea how to get LngLat data from tile matrices or tile IDs.
Super basic JavaScript concept of marker cache (for context):
const markerCache = {
cache: {},
getMarkersForTile: function(key) { // tiles have unique keys
if (this.cache[key]) {
return Promise.resolve(this.cache[key]);
}
// ??? what should be in "getTileBounds"?
const bounds = getTileBounds(key);
return fetchMarkersForTile(bounds).then(markers => {
this.cache[key] = markers;
return markers;
});
}
};
I believe you are looking for this library:
https://github.com/mapbox/tilebelt
It includes the tileToBBOX(tile) function, which will return you a bounding box for the given tile.
usage:
var tilebelt = require('#mapbox/tilebelt');
var tile = [10,15,8] // x,y,z
var bbox = tilebelt.tileToBBOX(tile);
I believe you are looking for the Slippy Map Tilenames specs as mentioned in https://docs.mapbox.com/api/maps/vector-tiles/
There are many programming languages implementations in the link
Java Example
class BoundingBox {
double north;
double south;
double east;
double west;
}
BoundingBox tile2boundingBox(final int x, final int y, final int zoom) {
BoundingBox bb = new BoundingBox();
bb.north = tile2lat(y, zoom);
bb.south = tile2lat(y + 1, zoom);
bb.west = tile2lon(x, zoom);
bb.east = tile2lon(x + 1, zoom);
return bb;
}
static double tile2lon(int x, int z) {
return x / Math.pow(2.0, z) * 360.0 - 180;
}
static double tile2lat(int y, int z) {
double n = Math.PI - (2.0 * Math.PI * y) / Math.pow(2.0, z);
return Math.toDegrees(Math.atan(Math.sinh(n)));
}
I have a few square-shaped nodes (like floor tiles) going along the screen and I'd like to restrict my player (P) node to moving within these nodes.
---------------------------------
| | P | | | | | | | <- Want no movement allowed
--------------------------------- outside of these squares.
| |
-------------
| | | | ...
-------------
I'm wondering if there's an elegant way to do this with SpriteKit Physics, that doesn't involve putting invisible blocks all the way around the floor.
Thanks!
An SKConstraint object describes a mathematical constraint on a node’s position or orientation.
You can use SKConstraint to keep a node a certain distance from a specific point in horizontal axis:
let center = size.width/2.0, difference = CGFloat(170.0)
let leftConstraint = SKConstraint.positionX(SKRange(constantValue: center - difference))
let rightConstraint = SKConstraint.positionX(SKRange(constantValue: center + difference))
player.constraints = [leftConstraint, rightConstraint]
You can also decide to enable or disable a certain constraint during the game:
leftConstraint.enabled = false
You use edge based physics bodies, not volume based physics bodies. So in your construction of the physics body, look for anything with edge in the constructor. Now, if you want to be able to walk between the tiles, you will have to create 1 physics body for the outer wall of your floor, because doing it tile by tile will mean you will be stuck in individual tiles.
My game is a sidescroller, so the Hero has the constant speed, which is set to his velocity every update. When I form a floor with several "boxes", Hero stops where the first one ends and the second begins. They're of the same size and are on the same y-axis, why does this happen?
PhysicsBody of Hero:
self.physicsBody = SKPhysicsBody(rectangleOfSize: rect.size, center: center)
self.physicsBody?.usesPreciseCollisionDetection = true
self.physicsBody?.allowsRotation = false
self.physicsBody?.restitution = 0
self.physicsBody?.categoryBitMask = Category.Hero
self.physicsBody?.collisionBitMask = Category.Floor | Category.Hero | Category.Glass | Category.Obstacle
self.physicsBody?.contactTestBitMask = Category.Obstacle | Category.Glass | Category.Collision | Category.Doors | Category.EBullet | Category.Enemy | Category.Explosion | Category.Plyuha | Category.Slime
PhysicsBody for obstacle:
self.physicsBody = SKPhysicsBody(rectangleOfSize: size_sprite)
self.physicsBody?.usesPreciseCollisionDetection = true
self.physicsBody?.restitution = 0
self.physicsBody?.allowsRotation = false
self.physicsBody?.pinned = true
self.physicsBody?.categoryBitMask = Category.Obstacle
self.physicsBody?.contactTestBitMask = Category.Bullet | Category.EBullet
self.physicsBody?.collisionBitMask = Category.Hero | Category.Bullet | Category.EBullet
I would create a edge-based physics body that stretches the length of the screen to simulate the floor. This should keep the player from stopping at the edges of your floor pieces because it's a continues edge. you could then try putting any obstacles in front of the player.
Set the physics body like this:
let startPoint = CGPoint() // fill in the desired x and y for start point
let endPoint = CGPoint() // fill in the desired x and y for start point
self.floor.physicsBody = SKPhysicsBody(edgeFromPoint: CGPoint(x: startPoint.x, y: startPoint.y), toPoint: CGPoint(x: endPoint.x, y: endPoint.y))
For other obstacles that might be stopping the player, try debugging by showing where the physics bodies actually are. There might be something you're not expecting to see blocking the player from advancing.
In the viewController, put
skView.showsPhysics = true
I am experimenting a little with AR. I have got the angle of the direction I am looking to from a compass in degrees. I know my own position and the position of another object (POI), the position is giving in form of latitude and longitude.
Now I would like to know how I can calculate the angle between the direction I am looking to and the POI.
Dot Product:
a . b = ||a|| ||b|| cos(t)
t = acos( (a.b)/(||a|| ||b||) )
||vector|| = length of vector (magnitude)
t = angle between the two vectors
Your probably going to need to do this a couple times for each plane you have. (1x for 2D, 2x for 3D)
Or:
/|
/ |
h / | y
/ |
/____|
x
t is the lower left corner, which we'll assume is your object, the upper right corner is going to be your other object
x = obj2.x - obj1.x
y = obj2.y - obj1.y
h = sqrt( (x*x) + (y*y) )
sin(t) = y/h
cos(t) = x/h
tan(t) = y/x
t = asin(y/h)
t = acos(x/h)
t = atan(y/x)
What makes the first method better, is that it account's for you're current rotation. The second method (using atan, asin, and acos) doesn't.