Moving from xy to another point, where am I at time t? - java

How would I write a function to move from point1 to point2 given some time?
For example:
Point move(Point point1, Point point2, long timeInMilliseconds, int speedPerSecond)
{
// basic stuff
int pointsMoved = speedPerSecond * 1000 / timeInMilliseconds;
if (point1.x == point2.x && point1.y > point2.y)
return (new Point(point1.x, Math.min(point2.y, point1.y + pointsMoved)));
...
}
Yes, that's where my sad math skills end.
How do I move if the movement is not diagonal or vertical? if there's an angle?
BTW, I plan to recalculate the current point based on point1 and point2, I will not be updating point1 nor point2 from the caller, so caller would look like this:
Point currentPoint = move(originalPoint, finalPoint, getCurrentTime() - originalTime, 10);
The signature of the function doesn't have to be what I mentioned. It could very well use degrees, but that's going to be another thing to figure out, if I have to use degrees.

Here's the general motion formula:
p = k⟨p2 - p1⟩ + p1
p1 is the initial position. p2 is the final position. p is the current location. k is a ratio, usually between 0 and 1, that determines how much of the distance to p2 we've covered.
Your function receives tn, the current time, and v, the speed from the p1 to p2. Therefore we need one additional piece to calculate k: the distance from p1 to p2. tn is in time units (t), the distance |p2 - p1| is in distance units (d), and the speed is in distance per time units (d / t). k is unitless, so we have to neutralize the units in the three values.
d / (d / t) / t
= d / d · t / t
= 1
and our units are neutralized.
So we have:
k = |p2 - p1| / v / tn
The distance is easily determined via the Pythagorean Theorem, giving us k. From there we decompose the general formula into its components:
px = k(x2 - x1) + x1
py = k(y2 - y1) + y1
k, being unitless despite containing elements with components, does not get decomposed.

Related

calculating shortest grid distance

Consider a city where the streets are perfectly laid out to form an infinite square grid. In this city finding the shortest path between two given points (an origin and a destination) is much easier than in other more complex cities. As a new Uber developer, you are tasked to create an algorithm that does this calculation.
Given user's departure and destination coordinates, each of them located on some street, find the length of the shortest route between them assuming that cars can only move along the streets. You are guaranteed that at least one of the coordinates is an integer.
I am struggling a little to figure out the logic here. There are many cases and I don't know how to accommodate them all. This is what I have so far
double perfectCity(double[] departure, double[] destination) {
double yDist = Math.abs(destination[1]-departure[1]);
double xDist = Math.abs(departure[1] - departure[0] + departure[1]-destination[0]);
return xDist + yDist;
}
The algorithm is very simple if the inputs are integers, just find the absolute value between the x and y coordinates and then add them together. This is called the Manhattan distance.
int distance = Math.abs(x1 - x2) + Math.abs(y1 - y2);
With doubles, it is almost exactly the same, except for one situation. Here are some possibilities:
Both points have integer coordinates
One point has integer coordinates, and the other point has only one integer coordinate
Both points have only one integer coordinate, but they are on different axes.
Both points have only one integer coordinate, and they are on the same axis.
Possibilities 1-3 all work fine using the same algorithm as for finding distance with integers, except #4 has the possibility of the axis in common being on the same block.
For example, if the inputs were: {x: 0.5, y: 2} and {x: 0.5, y: 3} you would have to travel horizontally, vertically, and then backwards horizontally again in order to reach the destination. This is different from inputs of {x: 0.5, y: 2} and {x: 1.5, y: 3} because there is no need to travel backwards on the same axis.
So you can use the normal algorithm in all cases except for the case of when both of the Xs or Ys have floating-point values and have the same floor-ed value.
Your code should look something like this.
import static java.lang.Math.*;
public static double perfectCity(double x1, double y1, double x2, double y2) {
double xDist = abs(x1 - x2);
double yDist = abs(y1 - y2);
if (floor(x1) != x1 && floor(x2) != x2 && // both Xs are doubles
floor(x1) == floor(x2) && // on the same block
y1 != y2) { // not on the same street
xDist = min(abs(x1 - floor(x1) + x2 - floor(x2)),
abs(x1 - ceil(x1) + x2 - ceil(x2)));
} else if (floor(y1) != y1 && floor(y2) != y2 && // both Ys are doubles
floor(y1) == floor(y2) && // on the same block
x1 != x2) { // not on the same street
yDist = min(abs(y1 - floor(y1) + y2 - floor(y2)),
abs(y1 - ceil(y1) + y2 - ceil(y2)));
}
return xDist + yDist;
}
This can be much further simplified by using a helper function to calculate each axis separately.
public static double perfectCity(double x1, double y1, double x2, double y2) {
return travelOnAxis(x1, x2, y1 == y2) + travelOnAxis(y1, y2, x1 == x2);
}
private static double travelOnAxis(double from, double to, boolean travelIsStraight) {
if (Math.floor(from) == Math.floor(to) && !travelIsStraight) {
double dist = Math.abs((from % 1) + (to % 1));
return Math.min(dist, 2 - dist);
} else {
return Math.abs(from - to);
}
}
I used the trick with 2 - dist here because it's the same as calculating
Math.abs((1 - (from % 1)) + (1 - (to % 1)))
which is the same as
Math.abs(from - Math.ceil(from) + to - Math.ceil(to))
If this is a square grid, you can consider the x and y coordinates separately; the minimum distance is the sum of the minumum distances in the two directions.
In the p-direction (either x or y), you have to move from p1 to p2. From p1, you can either move to floor(p1) or ceil(p1) to get to a road (which may be equal, if p1 is an integer); from there, you can move to either floor(p2) or ceil(p2), the road on which p2 is located; from there, you can move to p2.
So, the minimum distance in the p-direction is
min(abs(p1 - ceil(p1) ) + abs(ceil(p1) - floor(p2)) + abs(floor(p2) - p2), # (1)
abs(p1 - floor(p1)) + abs(floor(p1) - ceil(p2) ) + abs(ceil(p2) - p2), # (2)
abs(p1 - floor(p1)) + abs(floor(p1) - floor(p2)) + abs(floor(p2) - p2), # (3)
abs(p1 - ceil(p1) ) + abs(ceil(p1) - ceil(p2) ) + abs(ceil(p2) - p2)) # (4)
So you can just calculate this independently for the x and y directions, and add.
To illustrate this (abbreviating floor and ceil as f and p respectively):
f(p1) p1 c(p1)
+---O>>>>+>>>>>>>>+
.
.
+>>>O----+
f(p2) p2 c(p2)
--------------------------------> p axis
The shortest route is indicated here with >. The .s are on the shortest route, but since that part of the route is orthogonal to the p direction, it "doesn't count" towards the minimum distance in that direction.
The minimum route shown here, p1 -> c(p1) -> f(p2) -> p2, is Case 1 above.
It should not be hard to visualize swapping p1 and p2, in which case the minimum route is to go from p1 ->f(p1) -> c(p2) -> p2 (Case 2).
The case of pN == f(pN) == c(pN) is not very different; then, the part of the expression abs(pN - f(pN)) or abs(pN - c(pN)) is just zero.
The slightly different case is where f(p1) == f(p2):
f(p1) p1 c(p1) f(p1) p1 c(p1)
+---O>>>>+ +<<<O----+
. .
. .
+-----O<<+ +>>>>>O--+
f(p2) p2 c(p2) f(p2) p2 c(p2)
--------------------------------> p axis
In this case, the minimum route can either be p1 -> f(p1) -> f(p2) -> p2 or p1 -> c(p1) -> c(p2) -> p2 (which are Cases 3 and 4, respectively).
As mentioned by 4castle, the problem is trivial if only integers are considered for input. You would never have to "move back" after having "moved forward" in that case since you would always reach your destinaton in a single move.
But since, at most one floating point number needs to be considered for each of departure/destination, we need to consider 3 cases, (warning: lengthy explanation). Below is a python2 implementation with explanations.
x co-ordinates of both departure and destination are the same and are not floating point numbers. In this case, the shortest distance is simply the absolute difference between y co-ordinates. The same logic applies vice-versa.
import math
class Location():
def __init__(self, cord):
self.x = cord[0]
self.y = cord[1]
def perfectCity(departure, destination):
l1 = Location(departure)
l2 = Location(destination)
if l1.x == l2.x and float(l1.x).is_integer() and float(l2.x).is_integer():
return abs(l1.y-l2.y)
if l1.y == l2.y and float(l1.y).is_integer() and float(l2.y).is_integer():
return abs(l1.x-l2.x)
When one of the co-ordinates in the departure is a floating point, then:
If x co-ordinate is floating point, we can move backwards(round down) or move front(round up).
If y co-ordinate is floating point, we can either move down (round down) or move up (round up).
The above logic should work even when there is no floating point co-ordinate since we move in either direction by zero units.
Once we calculate these, we just choose the minimum among those like below,
return min(calc_round_up_dist(l1, l2), cal_round_down_dist(l1, l2))
Lets take an example of (0.4, 1) and (0.9, 3) for below calculations.
While calculating the round_up we need to calculate 3 distances:
round_up_distance: difference between the rounded up value of the floating point co-ordinate and the original floating point co-ordinate. We return zero if there is no floating point co-ordinate. 1 - 0.4 = 0.6 in the above example
non_floating_point difference: difference between the non floating point co-ordinate of departure and the corresponding co-ordinate of destination ( note that this might be floating point or not a floating point abs(3-1) = 2 in the above example
Difference between departure's floating point counterpart in the destination co-ordinate 0.9 in the above case and the new value of departure's floating point after rounding up, 0.4 + 0.6(this is the round_up distance) = 1.0, i.e. abs(0.9 - 1.0) = 0.1
Adding all the 3 above we get 0.6 + 2 + .1 = 2.7 which is the shortest distance.
Corresponding calculation needs to be done for rounding down. And we pick the minimum among both. The code for round_up and round_down is as below,
import math
class Location():
def __init__(self, cord):
self.x = cord[0]
self.y = cord[1]
def floating_point_round_up(self):
if not float(self.x).is_integer():
return math.ceil(self.x) - self.x
if not float(self.y).is_integer():
return math.ceil(self.y) - self.y
return 0
def floating_point_round_down(self):
if not float(self.x).is_integer():
return self.x - math.floor(self.x)
if not float(self.y).is_integer():
return self.y - math.floor(self.y)
return 0
def non_floating_point_diff(self, obj):
if not float(self.x).is_integer():
return abs(self.y - obj.y)
if not float(self.y).is_integer():
return abs(self.x - obj.x)
return abs(self.y - obj.y)
def floating_point_counterpart(self, obj):
if not float(self.x).is_integer():
return obj.x
if not float(self.y).is_integer():
return obj.y
return obj.x
def floating_point(self):
if not float(self.x).is_integer():
return self.x
if not float(self.y).is_integer():
return self.y
return self.x
Round up and down functions are as below,
def calc_round_up_dist(l1, l2):
dist = l1.floating_point_round_up()
diff = l1.non_floating_point_diff(l2)
floating_point_counterpart = l1.floating_point_counterpart(l2)
new_val = dist + l1.floating_point()
return dist + diff + abs(new_val - floating_point_counterpart)
def cal_round_down_dist(l1, l2):
dist = l1.floating_point_round_down()
diff = l1.non_floating_point_diff(l2)
floating_point_counterpart = l1.floating_point_counterpart(l2)
new_val = l1.floating_point() - dist
return dist + diff + abs(floating_point_counterpart - new_val)
Finally the main function that calls the above methods,
def perfectCity(departure, destination):
l1 = Location(departure)
l2 = Location(destination)
if l1.x == l2.x and float(l1.x).is_integer() and float(l2.x).is_integer():
return abs(l1.y-l2.y)
if l1.y == l2.y and float(l1.y).is_integer() and float(l2.y).is_integer():
return abs(l1.x-l2.x)
return min(calc_round_up_dist(l1, l2), cal_round_down_dist(l1, l2))
This is a practice question on CodeSignal:
https://app.codesignal.com/company-challenges/uber/gsjPcfsuNavxhsQQ7
def solution(departure, destination):
# go to the closest integer
# I can only travel the path of an integer
# if the number is a float I need to travel to an integer first
# then travel to the destination
x1, y1 = departure
x2, y2 = destination
res = 0
# check if the coordinations are integers
a = list(map(isInteger, departure))
b = list(map(isInteger, destination))
# if all are integers or if the corresponding elements are different
if a[0] ^ b[0] or (a[0] and a[1] and b[0] and b[1]):
return abs(x1-x2) + abs(y1-y2)
if a[0]:
res += abs(x2-x1)
# cloest distance from y1 to y2
res += getClosest(y1, y2)
else:
res += abs(y2-y1)
# closes distance from x1 to x2
res += getClosest(x1, x2)
return res
def getClosest(y1, y2):
cand1 = math.floor(y1)
cand2 = math.ceil(y1)
# find the integer closer to y1 to move to
res1 = abs(y1-cand1) + abs(cand1-y2)
res2 = abs(y1-cand2) + abs(cand2-y2)
return min(res1, res2)
def isInteger(n):
return n == round(n)

Java: My object won't travel at an angle between 0 and 45 degrees

I started learning java just over a year ago so i'm still fairly new.
I'm trying to make an object travel from one point to another at a constant net velocity no matter where the second point is in the frame. Currently it's working fairly well as long as I run the method every few frames.
The only problem is that it it will only move horizontally unless the second point is approximately between 45 and 135 degrees or between 225 and 315 degrees (1/2π and 3/2π or 5/2π and 7/2π).
It may be because of the 'if' statements meant to stop it from dividing by 0 but it doesn't seem like it. Also if there is any way to simplify those equations or remove 'if' statements I wouldn't mind some advice there too.
Note: vel is the net velocity the objects travel at and Prime.mx and Prime.my is the location of the target point.
public void target()
{
if (Prime.mx > x)
{
if (Math.abs(x-Prime.mx) != 0)
x = Math.round(Math.round((x + (vel*Math.cos(Math.atan(Math.abs(y-Prime.my)/Math.abs(x-Prime.mx)))))));
}
if (Prime.mx < x)
{
if (Math.abs(x-Prime.mx) != 0)
x = Math.round(Math.round((x - (vel*Math.cos(Math.atan(Math.abs(y-Prime.my)/Math.abs(x-Prime.mx)))))));
}
if (Prime.my > y)
{
if (Math.abs(x-Prime.mx) != 0)
y = Math.round(Math.round((y + (vel*Math.sin(Math.atan(Math.abs(y-Prime.my)/Math.abs(x-Prime.mx)))))));
}
if (Prime.my < y)
{
if (Math.abs(x-Prime.mx) != 0)
y = Math.round(Math.round((y - (vel*Math.sin(Math.atan(Math.abs(y-Prime.my)/Math.abs(x-Prime.mx)))))));
}
}
I use Math.round twice because the first brings it to a float from a double and the second makes it an int. I need the x and y as ints so the paint method can draw the objects.
I found a few simillar problems on the site but the closest one was in python and and the anwer didn't seem applicable to my problem.
I believe you are overcomplicating this. If your starting point is (sx, sy) and your destination is (dx, dy) then you can easily calculate any point (x, y) that is p distance along the line (0.0 <= p <= 1.0). You can use this to move at velocity v. So I suggest finding your end point and then using simple arithmetic to move on the x and y axis.
float dx = dist * Math.cos(angle);
float dy = dist * Math.sin(angle);
for (float p = 0.0; p <= 1.0; p = Math.min(1.0, p + dist / v) {
x = sx + p * (dx - sx);
y = sy + p * (dy - sy);
}
The Math.min expression in the for loop ensures that you end up exactly at the destination point.
If you already have the destination point then it's just as easy. Instead of finding dx and dy from dist and angle you find dist from dx and dy using pythagoras.
More than solution these are some advices.
First, implement all you coordinate variables as floats to prevent rounding precision loss errors and round only right before painting.
Second, define float dx = Prime.mx - x; float dy = Prime.my - y; distance to target from current point (to use later). I would use Math.atan2(dy,dx) to compute angle between current point and target. Then use that angle to increment coordinates like this:
x += Math.cos(angle)*vel;
y += Math.sin(angle)*vel;
Third, check if your object is at target using (dx*dx + dy*dy <= radius*radius) for suitable radius (can be 1).
Also note that if the y axis goes down, then the angle will be CW (clock-wise) instead of CCW (counter-clock-wise).

move bitmap from (x,y) to (x2,y2) how to compute x,y values between start and destination

Is there a way to move a bitmap from point1 to point 2 using the angle?
x += speed * Math.sin(getAngle(pointDestination));
y += speed * Math.cos(getAngle(pointDestination));
edit:
public double getAngle(Point target) {
double angle = (Math.atan2(target.y - y, target.x - x));
double angledeg = angle*0.0174532925;
return angledeg;
}
Should getAngle() be executed on every iteration or just once at the beginning?
Unfortunately the sprite moves to a wrong direction.
Your problem is that you increment the x value and when you go to increment the y too you are using the new x that you just incremented to calculate the angle.
Change it to:
float angle=getAngle(pointDestination);
x += speed * Math.cos(angle);
y += speed * Math.sin(angle);
public double getAngle(Point target) {
return Math.atan2(target.y - y, target.x - x);
}
Instead of doing a incremental update of the bitmap position, you better define a (mathematical) function that computes the position (x, y) over time. The advantage is that this will result in very exact and predictable movements independent of CPU speed / frames per second.
Assuming that the bitmap should move at constant speed from (x1, y1) to (x2, y2) in time milliseconds, so your (time dependent) position functions are as follows:
x(t) := x1 + (x2 - x1) * t / time // t in range [0, time]
y(t) := y1 + (y2 - y1) * t / time // t in range [0, time]
(Note: By doing some physics/maths, you can define more sophisticated functions that result in more complex movements).
This two functions can then be used in your animation thread to update the position of the bitmap:
bitmap.setX(x(currentTime - animationStartTime));
bitmap.setY(y(currentTime - animationStartTime));
Have a look at Trident animation library. It supports multiple UI frameworks and seems to be exactly what you're looking for!
Update: In case you really want to do a incremental update, e.g. based on your current frame rate, you don't need trigonometric functions (sin, cos, tan, ...) at all, just vectors:
// x, y is the current position of the bitmap
// calculate vector (dx, dy) to target:
double dx = x2 - x;
double dy = y2 - y;
// calculate length of this vector:
double l = Math.hypot(dx, dy); // calculates sqrt(dx² + dy²)
// calculate unit vector of (dx, dy):
double vx = dx / l;
double vy = dy / l;
// update bitmap position:
// distance is the number of pixels to travel in this iteration (frame)
x += distance * vx;
y += distance * vy;
Note that all values should be of type double. Otherwise, if int is used for x and y and the increment is lower than 1 (e.g. due to slow movement, i.e. distance is very low), the bitmap won't move at all due to rounding errors!
Also note that in this approach you have to measure the frame rate to adjust distance accordingly to compensate deviation. The formula could be something like:
double distance = (time elapsed since last frame in sec) * (distance to travel per sec)

Create a Java Point array that moves randomly toward a defined end point

I've issued myself a sort of challenge, and thought I could stand to ask for help getting my head around it. I want to use java Graphics to draw something that looks like lightning striking a given point.
Right now I just have this, which shoots cheap "lightning" in random directions, and I don't care where it ends up.
lightning[0] = new Point(370,130); //This is a given start point.
// Start in a random direction, each line segment has a length of 25
double theta = rand.nextDouble()*2*Math.PI;
int X = (int)(25*Math.cos(theta));
int Y = (int)(25*Math.sin(theta));
//Populate the array with more points
for (int i = 1 ; i < lightning.length ; i++)
{
lightning[i] = new Point(X + lightning[i-1].x, Y + lightning[i-1].y);
boolean plusminus = rand.nextBoolean();
if (plusminus) theta = theta + rand.nextDouble()*(Math.PI/2);
else theta = theta - rand.nextDouble()*(Math.PI/2);
X = (int)(25*Math.cos(theta));
Y = (int)(25*Math.sin(theta));
}
// Draw lines connecting each point
canvas.setColor(Color.WHITE);
for (int i = 1 ; i < lightning.length ; i++)
{
int Xbegin = lightning[i-1].x;
int Xend = lightning[i].x;
int Ybegin = lightning[i-1].y;
int Yend = lightning[i].y;
canvas.drawLine(Xbegin, Ybegin, Xend, Yend);
//if (Xend != Xbegin) theta = Math.atan((Yend - Ybegin)/(Xend - Xbegin));
// Restrict the angle to 90 degrees in either direction
boolean plusminus = rand.nextBoolean();
if (plusminus) theta = theta + rand.nextDouble()*(Math.PI/2);
else theta = theta - rand.nextDouble()*(Math.PI/2);
// 50/50 chance of creating a half-length off-shoot branch on the end
if (rand.nextBoolean())
{
int Xoff = (int)(Xend+(12*Math.cos(theta)));
int Yoff = (int)(Yend+(12*Math.sin(theta)));
canvas.drawLine(Xend, Yend, Xoff, Yoff);
}
}
I'm trying to think of some similar way to create this effect, but have the last point in the array pre-defined, so that the lightning can "strike" a specific point. In other words, I want to populate a Point array in a way that is random, but still converges on one final point.
Anyone care to weigh in?
I think this is fairly simple, accurate, and elegant approach. It uses a divide and conquer strategy. Start with only 2 values:
start point
end point
Calculate the midpoint. Offset that midpoint some value variance (which can be calculated relative to the length). The offset should ideally be normal to the vector connecting start and end, but you could be cheap by making that offset horizontal, as long as your bolts travel mostly vertically, like real lightning. Repeat above procedure for both (start, offset_mid) and (offset_mid, end), but this time using a smaller number for variance. This is a recursive approach which can terminate when either a threshold variance is achieved, or a threshold line segment length. As the recursion unwinds, you can draw all the connector segments. The idea is that the largest variance happens in the center of the bolt (when the start-to-end distance is the longest), and with each recursive call, the distance between points shrinks, and so does the variance. This way, the global variance of the bolt will be much greater than any local variances (like a real lightning bolt).
Here is an image of 3 different bolts generated from the same pre-determined points with this algorithm. Those points happen to be (250,100) and (500,800). If you want bolts that travel in any direction (not just "mostly vertical"), then you'll need to add more complexity to the point shifting code, shifting both X and Y based on the angle of travel of the bolt.
And here is some Java code for this approach. I used an ArrayList since the the divide and conquer approach doesn't know ahead of time how many elements it will end up with.
// play with these values to fine-tune the appearance of your bolt
private static final double VAR_FACTOR = 0.40;
private static final double VAR_DECREASE = 0.55;
private static final int MIN_LENGTH = 50;
public static ArrayList<Point> buildBolt(Point start, Point end) {
ArrayList<Point> bolt = new ArrayList<Point>();
double dx = start.getX() - end.getX();
double dy = start.getY() - end.getY();
double length = Math.sqrt(dx*dx + dy*dy);
double variance = length * VAR_FACTOR;
bolt.add(start);
buildBolt(start, end, bolt, variance);
return bolt;
}
private static void buildBolt(Point start, Point end,
List<Point> bolt, double variance) {
double dx = start.getX() - end.getX();
double dy = start.getY() - end.getY();
double length = Math.sqrt(dx*dx + dy*dy);
if (length > MIN_LENGTH) {
int varX = (int) ((Math.random() * variance * 2) - variance);
int midX = (start.x + end.x)/2 + varX;
int midY = (start.y + end.y)/2;
Point mid = new Point(midX, midY);
buildBolt(start, mid, bolt, variance * VAR_DECREASE);
buildBolt(mid, end, bolt, variance * VAR_DECREASE);
} else {
bolt.add(end);
}
return;
}
Without any graphics experience, I have this advice: it's going to be hard to pick a specific point, then try to reach it in a "random" way. Instead, I'd recommend creating a straight line from the origin to destination, then randomly bending parts of it. You could make several passes, bending smaller and smaller segments down to a certain limit to get the desired look. Again, I'm saying this without any knowledge of the graphics API.

How to draw a smooth line through a set of points using Bezier curves?

I need to draw a smooth line through a set of vertices. The set of vertices is compiled by a user dragging their finger across a touch screen, the set tends to be fairly large and the distance between the vertices is fairly small. However, if I simply connect each vertex with a straight line, the result is very rough (not-smooth).
I found solutions to this which use spline interpolation (and/or other things I don't understand) to smooth the line by adding a bunch of additional vertices. These work nicely, but because the list of vertices is already fairly large, increasing it by 10x or so has significant performance implications.
It seems like the smoothing should be accomplishable by using Bezier curves without adding additional vertices.
Below is some code based on the solution here:
http://www.antigrain.com/research/bezier_interpolation/
It works well when the distance between the vertices is large, but doesn't work very well when the vertices are close together.
Any suggestions for a better way to draw a smooth curve through a large set of vertices, without adding additional vertices?
Vector<PointF> gesture;
protected void onDraw(Canvas canvas)
{
if(gesture.size() > 4 )
{
Path gesturePath = new Path();
gesturePath.moveTo(gesture.get(0).x, gesture.get(0).y);
gesturePath.lineTo(gesture.get(1).x, gesture.get(1).y);
for (int i = 2; i < gesture.size() - 1; i++)
{
float[] ctrl = getControlPoint(gesture.get(i), gesture.get(i - 1), gesture.get(i), gesture.get(i + 1));
gesturePath.cubicTo(ctrl[0], ctrl[1], ctrl[2], ctrl[3], gesture.get(i).x, gesture.get(i).y);
}
gesturePath.lineTo(gesture.get(gesture.size() - 1).x, gesture.get(gesture.size() - 1).y);
canvas.drawPath(gesturePath, mPaint);
}
}
}
private float[] getControlPoint(PointF p0, PointF p1, PointF p2, PointF p3)
{
float x0 = p0.x;
float x1 = p1.x;
float x2 = p2.x;
float x3 = p3.x;
float y0 = p0.y;
float y1 = p1.y;
float y2 = p2.y;
float y3 = p3.y;
double xc1 = (x0 + x1) / 2.0;
double yc1 = (y0 + y1) / 2.0;
double xc2 = (x1 + x2) / 2.0;
double yc2 = (y1 + y2) / 2.0;
double xc3 = (x2 + x3) / 2.0;
double yc3 = (y2 + y3) / 2.0;
double len1 = Math.sqrt((x1-x0) * (x1-x0) + (y1-y0) * (y1-y0));
double len2 = Math.sqrt((x2-x1) * (x2-x1) + (y2-y1) * (y2-y1));
double len3 = Math.sqrt((x3-x2) * (x3-x2) + (y3-y2) * (y3-y2));
double k1 = len1 / (len1 + len2);
double k2 = len2 / (len2 + len3);
double xm1 = xc1 + (xc2 - xc1) * k1;
double ym1 = yc1 + (yc2 - yc1) * k1;
double xm2 = xc2 + (xc3 - xc2) * k2;
double ym2 = yc2 + (yc3 - yc2) * k2;
// Resulting control points. Here smooth_value is mentioned
// above coefficient K whose value should be in range [0...1].
double k = .1;
float ctrl1_x = (float) (xm1 + (xc2 - xm1) * k + x1 - xm1);
float ctrl1_y = (float) (ym1 + (yc2 - ym1) * k + y1 - ym1);
float ctrl2_x = (float) (xm2 + (xc2 - xm2) * k + x2 - xm2);
float ctrl2_y = (float) (ym2 + (yc2 - ym2) * k + y2 - ym2);
return new float[]{ctrl1_x, ctrl1_y, ctrl2_x, ctrl2_y};
}
Bezier Curves are not designed to go through the provided points! They are designed to shape a smooth curve influenced by the control points.
Further you don't want to have your smooth curve going through all data points!
Instead of interpolating you should consider filtering your data set:
Filtering
For that case you need a sequence of your data, as array of points, in the order the finger has drawn the gesture:
You should look in wiki for "sliding average".
You should use a small averaging window. (try 5 - 10 points). This works as follows: (look for wiki for a more detailed description)
I use here an average window of 10 points:
start by calculation of the average of points 0 - 9, and output the result as result point 0
then calculate the average of point 1 - 10 and output, result 1
And so on.
to calculate the average between N points:
avgX = (x0+ x1 .... xn) / N
avgY = (y0+ y1 .... yn) / N
Finally you connect the resulting points with lines.
If you still need to interpolate between missing points, you should then use piece - wise cubic splines.
One cubic spline goes through all 3 provided points.
You would need to calculate a series of them.
But first try the sliding average. This is very easy.
Nice question. Your (wrong) result is obvious, but you can try to apply it to a much smaller dataset, maybe by replacing groups of close points with an average point. The appropriate distance in this case to tell if two or more points belong to the same group may be expressed in time, not space, so you'll need to store the whole touch event (x, y and timestamp). I was thinking of this because I need a way to let users draw geometric primitives (rectangles, lines and simple curves) by touch
What is this for? Why do you need to be so accurate? I would assume you only need something around 4 vertices stored for every inch the user drags his finger. With that in mind:
Try using one vertex out of every X to actually draw between, with the middle vertex used for specifying the weighted point of the curve.
int interval = 10; //how many points to skip
gesture.moveTo(gesture.get(0).x, gesture.get(0).y);
for(int i =0; i +interval/2 < gesture.size(); i+=interval)
{
Gesture ngp = gesture.get(i+interval/2);
gesturePath.quadTo(ngp.x,ngp.y, gp.x,gp.y);
}
You'll need to adjust this to actually work but the idea is there.

Categories