For my current project it is necessary, that I compute the screen coordinates of a given point in the world space in Unity.
I used this tutorial to write a methode to do so.
After some debugging the x and y screen coordinate are correct, but my z coordinates looks wrong and I have some more questions:
static public Vector3 convertWorldToScreenCoordinates (Vector3 point, PhotoData photoData)
{
// get the camera
Camera camera = GameObject.Find (photoData.cameraName).camera;
/*
* 1 convert P_world to P_camera
*/
Vector4 pointInCameraCoodinates = convertWorldToCameraCoordinates (point, photoData);
/*
* 2 convert P_camera to P_clipped
*/
Vector4 pointInClipCoordinates = camera.projectionMatrix * pointInCameraCoodinates;
/*
* 3 convert P_clipped to P_ndc
* Normalized Device Coordinates
*/
Vector3 pointInNdc = pointInClipCoordinates / pointInClipCoordinates.w;
/*
* 4 convert P_ndc to P_screen
*/
Vector3 pointInScreenCoordinates;
pointInScreenCoordinates.x = camera.pixelWidth / 2.0f * (pointInNdc.x + 1);
pointInScreenCoordinates.y = camera.pixelHeight / 2.0f * (pointInNdc.y + 1);
pointInScreenCoordinates.z = ((camera.farClipPlane - camera.nearClipPlane) * pointInNdc.z + (camera.farClipPlane + camera.nearClipPlane)) / 2.0f;
// return screencoordinates
return pointInScreenCoordinates;
}
PhotoData is a class, that contains some information about the camera. The important part here is that I can access the camera.
static public Vector4 convertWorldToCameraCoordinates (Vector3 point, PhotoData photoData)
{
// translate the point by the negative camera-offset
//and convert to Vector4
Vector4 translatedPoint = point - photoData.cameraPosition;
// by default translatedPoint.w is 0
translatedPoint.w = 1.0f;
// create transformation matrix
Matrix4x4 transformationMatrix = Matrix4x4.identity;
transformationMatrix.SetRow (0, photoData.camRight);
transformationMatrix.SetRow (1, photoData.camUp);
transformationMatrix.SetRow (2, - photoData.camForward);
Vector4 transformedPoint = transformationMatrix * translatedPoint;
return transformedPoint;
}
First of all, the tutorial mentions, that after computing the ndc-values, "the range of values is now normalized from -1 to 1 in all 3 axes". This is not true in my case and I do not see what I am doing wrong.
My second question is, does pointInClipCoordinates.z < 0 mean the world point is "behind" my camera?
And the last question so far is why do I have to use - photoData.camForward?
// edit: updated code + questions
For editor scripts, use HandleUtility.WorldToGUIPoint (available since Unity3D 4.12) to convert a world space point to a 2D GUI position.
Related
I'll just start by saying I'm looking at the "projectile" code found in the game "Runescape" originally wrote in Javascript. The original code can be found somewhere half way down the page of this link https://github.com/zg/317-client titled "Projectile". Ultimately what it does is track an object a move towards it at a certain speed relative to the targets distance and the amount of ticks left. The reason I'm racking my brains on this, originally was because of the tan function for the direction of the velocity, but now I'm trying to figure out part of an equation used in calculating the up and downwards force.
I've translated a basic form of this code to Unity in C# to try and reverse engineer it.
void Update()
{
if(x >= distance)
{
x = 0;
z = 0;
duration = 20;
velocityX = 0; velocityZ = 0; acceleration = 0f;
mobile = false;
target();
}
sim();
duration-= interval;
}
void target()
{
velocityX = distance / duration;
if (!mobile) {
velocityZ = -velocityX * Mathf.Tan(elevationPitch * 0.02454369f);
}
}
void sim()
{
acceleration = 2f * (- z - velocityZ * duration) / (duration * duration);
mobile = true;
x += velocityX * interval;
z += velocityZ * interval + 0.5f * acceleration * interval * interval;
velocityZ += acceleration * interval;
cube.transform.position = new Vector3(x,z,0);
}
}
I understand how the velocity addition works in regard to interval, and that velocityX is set to move along x distance linearly over a given duration. If I understand the tan function correctly also, it is used to determine the angle of trajectory and thus the direction of velocity.
In the past couple of days that I have been analyzing this, I was googling a lot about parabolic trajectory and anything related to this topic. I found that the 0.5f * acceleration * interval * interval is the formula for calculating distance and gravity - with the google image here...
enter image description here
Now what I really don't understand is the acceleration formula :
acceleration = 2f * (- z - velocityZ * duration) / (duration * duration);
Does this look familiar to anyone? or can you figure out the maths behind it? I basically was hoping someone could explain this to me. I would also say that I don't fully understand the gravity formula use in this code, it looks like acceleration is being used in place of gravity.
Thanks for your time, guys!
I have a requirement that the bullets on a specific implementation of a scatterplot needs to have labels next to them, however, it is known that many of the datapoints in the set are identical or very close to one another, so if I were to set labels on a fixed coordinate relative to the bullet, the labels would stack on top of eachother and not be readable.
I want to implement this so that the labels will give way for eachother - moving around, so they don't overlap - and I am thinking that this is a common enough idea that some approach already exists, but I have no idea what to search for. Does this concept have a name?
I would ofcource appreciate an implementation example, but that is not the most important thing. I am sure I can solve it myself, but I'd rather not reinvent something that someone else has already done better.
The image above displays examples of bullets on top of and close to each other
I ended up finding inspiration in Simulated Annealing.
My solution looks like this
/**
* Implements an algorithm for placing labels on a chart in a way so that they
* do not overlap as much.
* The approach is inspired by Simulated Annealing
* (https://en.wikipedia.org/wiki/Simulated_annealing)
*/
export class Placer {
private knownPositions: Coordinate[];
private START_RADIUS = 20;
private RUNS = 15;
private ORIGIN_WEIGHT = 2;
constructor() {
this.knownPositions = []
}
/**
* Get a good spot to place the object.
*
* Given a start coordinate, this method tries to find the best place
* that is close to that point but not too close to other known points.
*
* #param {Coordinate} coordinate
* #returns {Coordinate}
*/
getPlacement(coordinate: Coordinate) : Coordinate {
let radius = this.START_RADIUS;
let lastPosition = coordinate;
let lastScore = 0;
while (radius > 0) {
const newPosition = this.getRandomPosition(coordinate, radius);
const newScore = this.getScore(newPosition, coordinate);
if (newScore > lastScore) {
lastPosition = newPosition;
lastScore = newScore;
}
radius -= this.START_RADIUS / this.RUNS;
}
this.knownPositions.push(lastPosition);
return lastPosition;
}
/**
* Return a random point on the radius around the position
*
* #param {Coordinate} position Center point
* #param {number} radius Distance from `position` to find a point
* #returns {Coordinate} A random point `radius` distance away from
* `position`
*/
private getRandomPosition(position: Coordinate, radius:number) : Coordinate {
const randomRotation = radians(Math.random() * 360);
const xOffset = Math.cos(randomRotation) * radius;
const yOffset = Math.sin(randomRotation) * radius;
return {
x: position.x + xOffset,
y: position.y + yOffset,
}
}
/**
* Returns a number score of a position. The further away it is from any
* other known point, the better the score (bigger number), however, it
* suffers a subtraction in score the further away it gets from its origin
* point.
*
* #param {Coordinate} position The position to score
* #param {Coordinate} origin The initial position before looking for
* better ones
* #returns {number} The representation of the score
*/
private getScore(position: Coordinate, origin: Coordinate) : number {
let closest: number = null;
this.knownPositions.forEach((knownPosition) => {
const distance = Math.abs(Math.sqrt(
Math.pow(knownPosition.x - position.x, 2) +
Math.pow(knownPosition.y - position.y, 2)
));
if (closest === null || distance < closest) {
closest = distance;
}
});
const distancetoOrigin = Math.abs(Math.sqrt(
Math.pow(origin.x - position.x, 2) +
Math.pow(origin.y - position.y, 2)
));
return closest - (distancetoOrigin / this.ORIGIN_WEIGHT);
}
}
There is room for improvement in the getScore method, but the results are good enough for my case.
Basically, all points try to move to a random position in a given radius and sees if that position is "better" than the original. The algorithm keeps doing that for a smaller and smaller radius until radius = 0.
The class keeps track of all known points, so that when you try to place point number two, the scoring can account for the presence of point number one.
Intro
I've created a spaceship sprite in my Unity project, I wanted it to rotate towards the cursor via angular velocity, because I'd like make my game to be heavily physics based.
Problem
Now my problem with rotating the sprite via by angular velocity is the following:
At -180° / 180° rotation my ship spins around, because while my mouse's angle is already 180°, while my ship's rotation is still -180°, or the other way around.
I tried
I tried to solve it mathematically, wasn't too successful, I could make it spin the right way just much slower/faster, I could fix the 180/-180 point, but made two different ones instead.
Looked for different solutions, but couldn't find a more fitting one.
Code
So I have this code for the rotation:
// Use this for initialization
void Start () {
rb = gameObject.GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update () {
//getting mouse position in world units
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
//getting the angle of the ship -> cursor vector
angle = Mathf.Atan2(mousePos.y - transform.position.y, mousePos.x - transform.position.x) * Mathf.Rad2Deg;
//getting the angle between the ship -> cursor and the rigidbody.rotation vector
diffAngle = angle - (rb.rotation + 90);
//Increasing angular velocity scaling with the diffAngle
rb.angularVelocity = diffAngle * Time.deltaTime * PlayerShipStats.Instance.speed * 100f;
Thank you for your contribution in advance
Solution for Problem 1
Inserting this code made it work, not for long :
if(diffAngle > 180) {
diffAngle -= 360;
} else if (diffAngle < -180) {
diffAngle += 360;
}
Problem 2 and Solution for Problem 2
The new problem is:
rigidbody.rotation can exceed it's boundaries, it can be rotated for more than 360 degrees.
this code patched this bug:
if(rb.rotation + 90 >= 180) {
rb.rotation = -270;
} else if (rb.rotation + 90 <= -180) {
rb.rotation = 90;
}
The perfect code
void AimAtTarget(Vector2 target, float aimSpeed) {
//getting the angle of the this -> target vector
float targetAngle = Mathf.Atan2(target.y - transform.position.y, target.x - transform.position.x) * Mathf.Rad2Deg;
if (rb.rotation + 90 >= 180) {
rb.rotation = -270;
} else if (rb.rotation + 90 <= -180) {
rb.rotation = 90;
}
//getting the angle between the this -> target and the rigidbody.rotation vector
float diffAngle = targetAngle - (rb.rotation - 90);
if (diffAngle > 180) {
diffAngle -= 360;
} else if (diffAngle < -180) {
diffAngle += 360;
}
//Increasing angular velocity scaling with the diffAngle
rb.angularVelocity = diffAngle * Time.deltaTime * aimSpeed * 100;
}
There are two problems I see here:
Problem 1
angle is always going to be between -180 and 180, while rb.rotation is between 0 and 360. So you are comparing angles using two different notations. The first step is to get both angles returning -180 to 180 or 0 to 360. I chose to do the following which puts both angles between -180 and 180:
//getting the angle of the ship -> cursor vector
float targetAngle = Mathf.Atan2(
mousePos.y - transform.position.y,
mousePos.x - transform.position.x) * Mathf.Rad2Deg;
//get the current angle of the ship
float sourceAngle = Mathf.Atan2(
this.transform.up.y,
this.transform.up.x) * Mathf.Rad2Deg;
Problem 2
If you fix problem 1 and tried your app you would notice that the ship sometimes rotates the wrong way, although it will eventually get to its target. The problem is that diffAngle can sometimes give a result that is greater than +180 degrees (or less than -180). When this happens we actually want the ship to rotate the other direction. That code looks like this:
//getting the angle between the ship -> cursor and the rigidbody.rotation vector
float diffAngle = targetAngle - sourceAngle;
//use the smaller of the two angles to ensure we always turn the correct way
if (Mathf.Abs(diffAngle) > 180f)
{
diffAngle = sourceAngle - targetAngle;
}
I made a simple Unity to verify this works. I was able to rotate my ship in either direction smoothly.
One thing you may have to handle, if you don't already, is appropriately stopping the rotation of the ship when the it is facing the cursor. In my test I noticed that the ship would jitter slightly when it reached its target because it would (very) slightly overshoot the cursor's angle in one direction and then the other. The larger the value of PlayerShipStats.Instance.speed the more pronounced this effect will likely be.
So, like the title said, I'm making a 2D platform game with multi direction physics, but my lack of trigonometry skills are killing me.
When my character hits something, it returns a Vector2 hitPoint.
If this was a one-way gravity platform game, I would do the following:
// considering the player's origin point in the middle
public void HitGround(Vector2 hitPoint)
{
this.position.y = hitPoint.y + height /2;
}
But since this have multi gravity physics, this simple method can't be done. So I've worked with relative positions, suppose the gravity is (-3, -3), the player would fall southwest.
// considering the player's origin point in the middle
public void HitGround(Vector2 hitPoint)
{
this.position = hitPoint + GetRelativeYPos(height / 2);
}
// returns the relative Y position based on its rotation
public Vector2 GetRelativeYPos(float offset)
{
// rotation is a Quaternion
return this.rotation * new Vector2(0, 1) * offset;
}
So far, so good. It works perfectly!!! But wait... this is only working when the CENTER of the player hits the ground. In fact, the player has 3 raycasts when he is falling. If the center raycast hits the ground, the code WORKS in EVERY direction he falls.
But when the EDGE RAYCASTS hits the ground. The Player tilts to the center, because the code do not allow him to move right. Now, my last code idea was born:
public void HitGround(Vector2 hitPoint, Vector2 raycastOrigin)
{
// Considering that the raycastOrigin is in the same "relative X" as the player's origin
float signedDistance = SignedDistance(hitPoint, raycastOrigin);
this.position = hitPoint + GetRelativeYPos(height / 2) + GetRelativeXPos(signedDistance);
}
// returns the relative Y position based on its rotation
public Vector2 GetRelativeYPos(float offset)
{
return this.rotation * new Vector2(0, 1) * offset;
}
// returns the relative X position based on its rotation
public Vector2 GetRelativeXPos(float offset)
{
return this.rotation * new Vector2(1, 0) * offset;
}
public float SignedDistance(Vector2 p1, Vector2 p2)
{
float distance = Distance(p1, p2);
float angle = Atan2(p2.y, p2.x) + Atan2(p1.y, p1.x);
// Returns the magnitude AND direction of the vector
return distance * Sign(angle);
}
And there you go!
When the code doesn't work, its just because of the sign of the angle, that, for some reason, it's being calculated wrong, When the angle has to be positive, it returns negative, and vice versa, but it only returns the wrong value when the "player's relative north position" is below 0.
For example:
if the player is falling down, when the Y position is higher than 0, it works, when it is lower, it doesn't.
If the player is falling up, when the Y position is lower than 0, it works, when it is higher, it doesn't.
If the player is falling right, when the X position is lower than 0, it works, when it is
higher, it doesn't.
If the player is falling left, when the X position is greater than 0, it works, when it is lower, it doesn't.
Just one more:
If the player is falling southeast, when the Y position is higher than 0 AND the X position is lower than 0, it works, otherwise, it doesn't
The following method is the responsible for calculating the "signed distance" of two points. I mean, the magnitude of the resulting vector and its direction
public float SignedDistance(Vector2 p1, Vector2 p2)
{
float distance = Distance(p1, p2);
// THIS LINE HERE!!!
float angle = Atan2(p2.y, p2.x) + Atan2(p1.y, p1.x);
return distance * Sign(angle);
}
There is something wrong with that line that I CAN'T FIGURE IT OUT. Is there someone kind enough to answer this question?
I hope I was clear. Bye
Similar questions to this have been asked a number of times here, but none of them seem to give me exactly what I want. I am working with the Bing Map control on Windows Phone and I'd like to add an ellipse that scales properly with zoom changes. This can be done with poly lines and polygons, but there is no ellipse type derived from MapShapeBase. I've tried various ways of doing it, but they require playing around with pixel sizes and fudging the math to make it line up with geo coordinates. I want to create an Ellipse with a center and x/y sizes in meters and have the framework do the rest. It seems so simple. Have I missed it somewhere? My other approach is to draw 365 line segments in a poly line, but that seems horribly ugly, and since the center can move, I'd need to bind the Location of every segment. That seems very heavy-weight. Any other thoughts?
[To be specific, I want to add a "GPS Accuracy" indicator as a circle around the current location.]
Update
In Mango, the phone automatically shows such a circle.
Orginal Post
It's fairly easy. You just use a Pushpin control to do the drawing with.
1) Add a MapLayer to your control:
<maps:MapLayer>
<maps:MapPolygon Fill="Gray"
IsHitTestVisible="False"
Locations="{Binding AccuracyLocationCollection}"
Opacity="0.6"
Stroke="Black"
StrokeThickness="2" />
</maps:MapLayer>
2) Add the AccuracyLocationCollection property in your ViewModel
public LocationCollection AccuracyLocationCollection
{
get;
set;
}
3) In the GeoCoordinateWatcher_PositionChanged event handler, calculate the size of the circle, and set the value to the AccuracyLocationCollection
ViewModel.AccuracyLocationCollection = DrawMapsCircle(e.Position.Location);
4) The code for the DrawMapsCircle goes like this:
private static double ToRadian(double degrees)
{
return degrees * (Math.PI / 180);
}
private static double ToDegrees(double radians)
{
return radians * (180 / Math.PI);
}
public static LocationCollection DrawMapsCircle(GeoCoordinate location)
{
double earthRadiusInMeters = 6367.0 * 1000.0;
var lat = ToRadian(location.Latitude);
var lng = ToRadian(location.Longitude);
var d = location.HorizontalAccuracy / earthRadiusInMeters;
var locations = new LocationCollection();
for (var x = 0; x <= 360; x++)
{
var brng = ToRadian(x);
var latRadians = Math.Asin(Math.Sin(lat) * Math.Cos(d) + Math.Cos(lat) * Math.Sin(d) * Math.Cos(brng));
var lngRadians = lng + Math.Atan2(Math.Sin(brng) * Math.Sin(d) * Math.Cos(lat), Math.Cos(d) - Math.Sin(lat) * Math.Sin(latRadians));
locations.Add(new Location()
{
Latitude = ToDegrees(latRadians),
Longitude = ToDegrees(lngRadians)
});
}
return locations;
}
Result: (This is next to my home, I can confirm there's about 3 meters between the roads the grey circle is displaying between)