I’m trying to move at a constant speed over a curved path in a given amount of time. I calculated the average speed needed to travel the curve by taking the derivative at various points along the curve and averaging them. Then I multiply the path’s position (t) by a ratio of the average derivative and the derivative at the current location of the curve. This method for setting constant speed works great.
The problem I’m having occurs when multiple control points (3 or more) are put in the same location. Then the speed (or derivative) at this point is 0 and dividing the average speed by a speed of 0 obviously causes problems in the calculations.
BSpline requires three control points to be placed at the ends in order to have the curve actually reach the start and end at the end points. If I only put 1 or 2 control points at the ends the path starts after the first control point and ends before the last control point. For my application it is important that the motion reaches the end points because I will be linking together multiple BSplines and it’s important for them to line up correctly and to not have any time gaps between them either.
I’ve tried a few different attempts at fixing it, but none of them were successful.
Here is my sample code and I've included comments to indicate where the problem is.
NOTE: I used CatmullRomSpline in my example instead of BSpline only because I found a bug in the BSpline’s derivative method, which has been fixed but is not yet in the stable version of LibGDX.
Test.java
public class Test extends Game {
private Stage stage;
private MyPath path;
#Override
public void create () {
Gdx.graphics.setDisplayMode(1000, 1000, false);
stage = new Stage();
stage.setViewport(new ScreenViewport(stage.getViewport().getCamera()));
Gdx.input.setInputProcessor(stage);
path = new MyPath(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
stage.addActor(path);
}
#Override
public void render () {
Gdx.gl.glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
#Override
public void dispose(){
path.dispose();
stage.dispose();
super.dispose();
}
}
MyPath.java
public class MyPath extends WidgetGroup implements Disposable {
private Path<Vector2> path;
private Vector2 result=new Vector2(), derivative=new Vector2();
private float time, t, tPrev, dt, tConst, tConstPrev, derivativeAverage;
private Array<Texture> textures = new Array<Texture>(Texture.class);
private Array<Image> points = new Array<Image>(Image.class);
private Image dot;
private final float CYCLE = 4; // path cycle time (in seconds)
private Vector2[] pointsData = {
new Vector2(100, 100),
new Vector2(100, 100),
// new Vector2(100, 100), // << UN-COMMENT TO PRODUCE BUG
new Vector2(350, 800),
new Vector2(550, 200),
new Vector2(650, 400),
new Vector2(900, 100),
new Vector2(900, 100)
};
public MyPath(int width, int height){
this.setSize(width, height);
path = new CatmullRomSpline<Vector2>(pointsData, false);
// create and add images
createImages();
for (int i=0; i<points.size; i++){
points.items[i].setPosition(pointsData[i].x - points.items[i].getWidth()/2, pointsData[i].y - points.items[i].getHeight()/2);
addActor(points.items[i]);
}
addActor(dot);
// calculate derivative average
derivativeAverage();
}
#Override
public void act(float delta){
result = getValue(delta);
dot.setPosition(result.x - dot.getWidth()/2, result.y - dot.getHeight()/2);
}
private Vector2 getValue(float delta){
// set t in the range [0,1] for path
time += delta;
if (time > CYCLE){
time = tPrev = dt = tConst = tConstPrev = 0;
}
t = time / CYCLE;
dt = t - tPrev;
tPrev = t;
// constant speed (tConst)
path.derivativeAt(derivative, tConstPrev);
tConst += dt * (derivativeAverage / derivative.len()); // << ERROR when derivative.len() is 0
tConstPrev = tConst;
path.valueAt(result, tConst);
return result;
}
private void derivativeAverage(){
float segmentCount = 20000;
derivativeAverage = 0;
for (float i=0; i<=1; i+=1.0/segmentCount) {
path.derivativeAt(result, i);
derivativeAverage += result.len();
}
derivativeAverage /= segmentCount;
if (derivativeAverage==0){ throw new GdxRuntimeException("ERROR: derivative average is zero"); }
}
private void createImages(){
dot = getImage(Color.GREEN, true);
for (int i=0; i<pointsData.length; i++){
points.add(getImage(Color.WHITE, false));
}
}
private Image getImage(Color color, boolean fillCircle){
Pixmap pixmap = new Pixmap(50, 50, Pixmap.Format.RGBA8888);
pixmap.setColor(color);
if (fillCircle){
pixmap.fillCircle(pixmap.getWidth()/2, pixmap.getHeight()/2, pixmap.getWidth()/2-1);
} else {
pixmap.drawCircle(pixmap.getWidth()/2, pixmap.getHeight()/2, pixmap.getWidth()/2-1);
}
textures.add(new Texture(pixmap));
pixmap.dispose();
return new Image(textures.peek());
}
#Override
public void dispose(){
while (textures.size > 0){
textures.pop().dispose();
}
}
}
===================================================================
EDIT
===================================================================
Here is my latest attempt at increasing t until the dot is moving.
This method does occasionally work on some frames (moving smoothly past the zero derivative). But other times the dot does weird things lie starting over at the beginning of the curve when it hits the zero derivative or extending beyond the end of the curve moving a different direction or disappearing completely (because the position gets set to negative values).
So it seems like this method is really close as it does occasionally work on some frames, but it glitches and does weird things on other frames.
Vector2 lastPoint = new Vector2();
float minSpeed = 1;
float minDerivative = 1;
float temp;
...
private Vector2 getValue(float delta){
// set t in the range [0,1] for path
time += delta;
if (time > CYCLE){
time = tPrev = dt = tConst = tConstPrev = 0;
}
t = time / CYCLE;
// CONSTANT SPEED
dt = t - tPrev;
path.derivativeAt(derivative, tConstPrev);
temp = dt * (derivativeAverage / derivative.len());
path.valueAt(result, tConst + temp);
//**************************************
// FIX FOR ZERO SPEED
// increase t in loop until speed > 0
//**************************************
while (result.dst(lastPoint)<minSpeed || derivative.len()<minDerivative){
// set t in the range [0,1] for path
time += delta;
if (time > CYCLE){
time = tPrev = dt = tConst = tConstPrev = 0;
lastPoint.set(0,0);
}
t = time / CYCLE;
// CONSTANT SPEED
dt = t - tPrev;
// new derivative
path.valueAt(derivative, t);
derivative.sub(lastPoint);
temp = dt * (speedAverage / derivative.len());
path.valueAt(result, tConst + temp);
}
tConst += temp;
lastPoint.set(result);
tPrev = t;
tConstPrev = tConst;
return result;
}
I also do a similar thing when calculating the average speed to keep the zero derivatives from affecting it. I also tried using the commented out sections with the "addedSegmentCount" variable when calculating the average, but that actually caused more glitches for some reason...even though theoretically this seems like the "correct" way to calculate the average since some segments don't get added if the distance is too small.
private void pathLength_SpeedAverage(){
float segmentCount = 20000;
// float addedSegmentCount = 0;
pathLength = 0;
path.valueAt(lastPoint, 0);
for (float i=0; i<=1; i+=1.0/segmentCount){
path.valueAt(result, i);
if (result.dst(lastPoint) >= minSpeed){
path.derivativeAt(result, i);
if (result.len() >= minDerivative){
pathLength += result.len();
lastPoint.set(result);
// ++addedSegmentCount;
}
}
}
speedAverage = pathLength / segmentCount;
// speedAverage = pathLength / addedSegmentCount;
lastPoint.set(0,0);
}
You cannot completely avoid zero first derivatives if control points could be coincident. So, what I suggest is to not use the first derivatives at all. Your purpose is to traverse the path at a constant speed, which is equivalent to sample points along the path with equal arc length. The theoretical approach to solve this involves calculus to compute arc length numerically, but we can go with an approximation approach as in the following:
Suppose you would like to traverse the path in N steps,
1) Sample M points along the path uniformly in parameter domain (i.e., t=0.0, 0.1, 0.2, 0.3, ....) where M is preferred to be greater than N. Denoting these points as P0, P1, P2,....
2) Compute the distance between P0P1, P1P2, P2P3,....
3) Compile a look-up table that maps from parameter t(i) to the cumulative chord length |P0P1|+|P1P2|+.....+|P(i-1)P(i)|. At the end, you will also obtain the overall length of the path, denoted as L.
4) Now, for each value of kL/N (where k=0 to N), you can compute the corresponding t value from the look-up table by linear interpolating the two parameter values in which the kL/N falls on.
Related
I am looking for some help with some game code i have inherited from a flight sim. The code below simulates bombs exploding on the ground, it works fine but i am trying to refine it.
At the moment it takes a random value for x and y as a start point and then adds another random value between -20 and 20 to this. It works ok, but doesn't simulate bombs dropping very well as the pattern does not lay along a straight line/
What i would like to achieve though is all x and y points after the first random values, to lay along a straight line, so that the effects called for all appear to lay in a line. It doesn't matter which way the line is orientated.
Thanks for any help
slipper
public static class BombUnit extends CandCGeneric
{
public boolean danger()
{
Point3d point3d = new Point3d();
pos.getAbs(point3d);
Vector3d vector3d = new Vector3d();
Random random = new Random();
Aircraft aircraft = War.GetNearestEnemyAircraft(this, 10000F, 9);
if(counter > 10)
{
counter = 0;
startpoint.set(point3d.x + (double)(random.nextInt(1000) - 500), point3d.y + (double)(random.nextInt(1000) - 500), point3d.z);
}
if(aircraft != null && (aircraft instanceof TypeBomber) && aircraft.getArmy() != myArmy)
{
World.MaxVisualDistance = 50000F;
counter++;
String s = "weapon.bomb_std";
startpoint.x += random.nextInt(40) - 20;
startpoint.y += random.nextInt(40) - 20;
Explosions.generate(this, startpoint, 7F, 0, 30F, !Mission.isNet());
startpoint.z = World.land().HQ(startpoint.x, startpoint.y);
MsgExplosion.send(this, s, startpoint, getOwner(), 0.0F, 7F, 0, 30F);
Engine.land();
int i = Landscape.getPixelMapT(Engine.land().WORLD2PIXX(startpoint.x), Engine.land().WORLD2PIXY(startpoint.y));
if(firecounter < 100 && i >= 16 && i < 20)
{
Eff3DActor.New(null, null, new Loc(startpoint.x, startpoint.y, startpoint.z + 5D, 0.0F, 90F, 0.0F), 1.0F, "Effects/Smokes/CityFire3.eff", 300F);
firecounter++;
}
super.setTimer(15);
}
return true;
}
private static Point3d startpoint = new Point3d();
private int counter;
private int firecounter;
public BombUnit()
{
counter = 11;
firecounter = 0;
Timer1 = Timer2 = 0.05F;
}
}
The code in the question is a mess, but ignoring this and trying to focus on the relevant parts: You can generate a random position for the first point, and a random direction, and then walk along this direction in several steps.
(This still raises the question of whether the direction is really not important. Wouldn't it matter if only the first bomb was dropped in the "valid" area, and the remaining ones outside of the screen?)
However, the relevant code could roughly look like this:
class Bombs
{
private final Random random = new Random(0);
int getScreenSizeX() { ... }
int getScreenSizeY() { ... }
// Method to drop a single bomb at the given position
void dropBombAt(double x, double y) { ... }
void dropBombs(int numberOfBombs, double distanceBetweenBombs)
{
// Create a random position in the screen
double currentX = random.nextDouble() * getScreenSizeX();
double currentY = random.nextDouble() * getScreenSizeY();
// Create a random step size
double directionX = random.nextDouble();
double directionY = random.nextDouble();
double invLength = 1.0 / Math.hypot(directionX, directionY);
double stepX = directionX * invLength * distanceBetweenBombs;
double stepY = directionY * invLength * distanceBetweenBombs;
// Drop the bombs
for (int i=0; i<numberOfBombs; i++)
{
dropBombAt(currentX, currentY);
currentX += stepX;
currentY += stepY;
}
}
}
I am assuming your startpoint is a StartPoint class with x,y,z coordinates as integers in it.
I hope I have understood your problem correctly. It looks like you either want to create a vertical explosion or a horizontal explosion. Since an explosion always occurs on ground, the z coordinate will be zero. Now you can vary one of x or y coordinate to give you a random explosion along a straight line. Whether you choose x or y could be fixed or could be randomized itself. A potential randomized solution below:
public boolean danger() {
// stuff
int orientation = Random.nextInt(2);
if(aircraft != null && (aircraft instanceof TypeBomber) && aircraft.getArmy() != myArmy)
{
// stuff
startPoint = randomizeStartPoint(orientation, startPoint);
// stuff
}
}
StartPoint randomizeStartPoint(int orientation, StartPoint startPoint) {
if(orientation == 0) {
startPoint.x += random.nextInt(40) - 20;
}
else {
startPoint.y += random.nextInt(40) - 20;
}
return startPoint;
}
In response to the image you uploaded, it seems that the orientation of the explosion need not necessarily be horizontal or vertical. So the code I posted above gives a limited solution to your problem.
Since you want any random straight line, your problem boils down to two sub parts:
1. Generate a random straight line equation.
2. Generate random point along this line.
Now, a straight line equation in coordinate geometry is y = mx + c where m is the slope and c is the constant where the line crosses the y-axis. The problem with c is that it gives rise to irrational coordinates. I am assuming you are looking for integer coordinates only, since this will ensure that your points are accurately plotted. (You could do with rational fractions, but then a fraction like 1/3 will still result in loss of accuracy). The best way to get rid of this irrational problem is to get rid of c. So now your straight line always looks like y = mx. So for step one, you have to generate a random m.
Then for step 2, you can either generate a random x or random y. It doesn't matter which one, since either one will result in random coordinates.
Here is a possible code for the solution:
int generateRandomSlope() {
return Random.nextInt(100); // arbitrarily chose 100.
}
int randomizeStartPoint(int m, StartPoint startPoint) { // takes the slope we generated earlier. without the slope, your points will never be on a straight line!
startPoint.x += random.nextInt(40) - 20;
startPoint.y += x * m; // because a line equation is y = mx
return startPoint;
}
public boolean danger() {
// stuff
int m = generateRandomSlope(); // you may want to generate this elsewhere so that it doesn't change each time danger() is called.
if(aircraft != null && (aircraft instanceof TypeBomber) && aircraft.getArmy() != myArmy)
{
// stuff
startPoint = randomizeStartPoint(m, startPoint);
// stuff
}
}
Again, this is not a complete or the best solution.
I want to draw an arc using center point,starting point,ending point on opengl surfaceview.I have tried this given below code so far. This function draws the expected arc if we give the value for start_line_angle and end_line_angle manually (like start_line_angle=0 and end_line_angle=90) in degree.
But I need to draw an arc with the given co-ordinates(center point,starting point,ending point) and calculating the start_line_angle and end_line_angle programatically.
This given function draws an arc with the given parameters but not giving the desire result. I've wasted my 2 days for this. Thanks in advance.
private void drawArc(GL10 gl, float radius, float cx, float cy, float start_point_x, float start_point_y, float end_point_x, float end_point_y) {
gl.glLineWidth(1);
int start_line_angle;
double sLine = Math.toDegrees(Math.atan((cy - start_point_y) / (cx - start_point_x))); //normal trigonometry slope = tan^-1(y2-y1)/(x2-x1) for line first
double eLine = Math.toDegrees(Math.atan((cy - end_point_y) / (cx - end_point_x))); //normal trigonometry slope = tan^-1(y2-y1)/(x2-x1) for line second
//cast from double to int after round
int start_line_Slope = (int) (sLine + 0.5);
/**
* mapping the tiriogonometric angle system to glsurfaceview angle system
* since angle system in trigonometric system starts in anti clockwise
* but in opengl glsurfaceview angle system starts in clock wise and the starting angle is 90 degree of general trigonometric angle system
**/
if (start_line_Slope <= 90) {
start_line_angle = 90 - start_line_Slope;
} else {
start_line_angle = 360 - start_line_Slope + 90;
}
// int start_line_angle = 270;
// int end_line_angle = 36;
//casting from double to int
int end_line_angle = (int) (eLine + 0.5);
if (start_line_angle > end_line_angle) {
start_line_angle = start_line_angle - 360;
}
int nCount = 0;
float[] stVertexArray = new float[2 * (end_line_angle - start_line_angle)];
float[] newStVertextArray;
FloatBuffer sampleBuffer;
// stVertexArray[0] = cx;
// stVertexArray[1] = cy;
for (int nR = start_line_angle; nR < end_line_angle; nR++) {
float fX = (float) (cx + radius * Math.sin((float) nR * (1 * (Math.PI / 180))));
float fY = (float) (cy + radius * Math.cos((float) nR * (1 * (Math.PI / 180))));
stVertexArray[nCount * 2] = fX;
stVertexArray[nCount * 2 + 1] = fY;
nCount++;
}
//taking making the stVertextArray's data in reverse order
reverseArray = new float[stVertexArray.length];//-2 so that no repeatation occurs of first value and end value
int count = 0;
for (int i = (stVertexArray.length) / 2; i > 0; i--) {
reverseArray[count] = stVertexArray[(i - 1) * 2 + 0];
count++;
reverseArray[count] = stVertexArray[(i - 1) * 2 + 1];
count++;
}
//reseting the counter to initial value
count = 0;
int finalArraySize = stVertexArray.length + reverseArray.length;
newStVertextArray = new float[finalArraySize];
/**Now adding all the values to the single newStVertextArray to draw an arc**/
//adding stVertextArray to newStVertextArray
for (float d : stVertexArray) {
newStVertextArray[count++] = d;
}
//adding reverseArray to newStVertextArray
for (float d : reverseArray) {
newStVertextArray[count++] = d;
}
Log.d("stArray", stVertexArray.length + "");
Log.d("reverseArray", reverseArray.length + "");
Log.d("newStArray", newStVertextArray.length + "");
ByteBuffer bBuff = ByteBuffer.allocateDirect(newStVertextArray.length * 4);
bBuff.order(ByteOrder.nativeOrder());
sampleBuffer = bBuff.asFloatBuffer();
sampleBuffer.put(newStVertextArray);
sampleBuffer.position(0);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(2, GL10.GL_FLOAT, 0, sampleBuffer);
gl.glDrawArrays(GL10.GL_LINE_LOOP, 0, nCount * 2);
gl.glLineWidth(1);
}
To begin with the trigonometry you may not simply use the atan to find degrees of the angle. You need to check what quadrant the vector is in and increase or decrease the result you get from atan. Better yet use atan2 which should include both dx and dy and do the job for you.
You seem to create the buffer so that a point is created per degree. This is not the best solution as for large radius that might be too small and for small radius this is way too much. Tessellation should include the radius as well such that number of points N is N = abs((int)(deltaAngle*radius*tessellationFactor)) then use angleFragment = deltaAngle/N but make sure that N is greater then 0 (N = N?N:1). The buffer size is then 2*(N+1) of floats and the iteration if for(int i=0; i<=N; i++) angle = startAngle + angleFragment*i;.
As already pointed out you need to define the radius of the arc. It is quite normal to use an outside source the way you do and simply force it to that value but use the 3 points for center and the two borders. Some other options that usually make sense are:
getting the radius from the start line
getting the radius from the shorter of the two lines
getting the average of the two
interpolate the two to get an elliptic curve (explained below)
To interpolate the radius you need to get the two radiuses startRadius and endRadius. Then you need to find the overall radius which was already used as deltaAngle above (watch out when computing this one, it is more complicated as it seems, for instance drawing from 320 degrees to 10 degrees results in deltaAngle = 50). Anyway the radius for a specific point is then simply radius = startRadius + (endRadius-startRadius)*abs((angleFragment*i)/deltaAngle). This represents a simple linear interpolation in polar coordinate system which is usually used to interpolate vector in matrices and is the core functionality to get nice animations.
There are some other ways of getting the arc points which may be better performance wise but I would not suggest them unless and until you need to optimize your code which should be very late in production. You may simply keep stepping toward the next point and correcting the radius (this is only a concept):
vec2 start, end, center; // input values
float radius; // input value
// making the start and end relative to center
start -= center;
end -= center;
vec2 current = start/length(start) * radius; // current position starts in first vector
vec2 target = end/length(end) * radius; // should be the last point
outputBuffer[0] = current+center; // insert the first point
for(int i=1;; i++) { // "break" will need to exit the loop, we need index only for the buffer
vec2 step = vec2(current.y, -(current.x)); // a tangential vector from current start point according to center
step = step/length(step) / tessellationScale; // normalize and apply tessellation
vec2 next = current + step; // move tangentially
next = next/length(next) * radius; // normalize and set the
if(dot(current-target, next-target) > .0) { // when we passed the target vector
current = next; // set the current point
outputBuffer[i] = current+center; // insert into buffer
}
else {
current = target; // simply use the target now
outputBuffer[i] = current+center; // insert into buffer
break; // exit
}
}
I know this question is similar to others, but if I have a rectangle bounded game object. Which moves position. How can I check along the line if it intersects with any items in between?
In an extreme scenario. [x = 2, x = 1, width = 1, height = 1]A moves to [x = 4, y = 1, width = 1, height = 1]. Where the rectangle B exists at [3,1,0.5,0.5] it would get missed out.
I have read about scalar and cross product but they are single lines if i read correctly. This is due to Android game development on slow devices with low frame rate. I am getting it falling into objects. I check intersects using this code below.
public boolean testIntersection(GameVector lowerLeftMain, float mainWidth, float mainHeight, GameVector lowerLeftCollider,
float colliderWidth, float colliderHeight){
boolean intersect = false;
if(lowerLeftMain.x < lowerLeftCollider.x + colliderWidth+0.08f && //checks left collision
lowerLeftMain.x + mainWidth > lowerLeftCollider.x-0.08f && //checks right collision
lowerLeftMain.y < lowerLeftCollider.y + colliderHeight+0.08f &&//checks top collision
lowerLeftMain.y + mainHeight > lowerLeftCollider.y-0.08f )//checks bottom collision
intersect = true;
return intersect;
}
Please can someone point me in the right direction should I give up on rectangles and concentrate on ray cast line collision style?
Thanks in advance.
Thanks for the links great links will post my code to help others in the future.
My Separating Axis Theorem in java. Only to test if overlaps. I went for this algorithm due to efficiency and potential to see the min and max overlap vectors.
public GameVector[] getVertices(GameObject obj){
final GameVector topLeft = new GameVector( obj.mPosition.x-0.06f - (obj.mWidth/2), obj.mPosition.y+0.06f +(obj.mHeight/2) );
final GameVector topRight = new GameVector( obj.mPosition.x+0.06f + (obj.mWidth/2),obj.mPosition.y+0.06f +(obj.mHeight/2) );
final GameVector bottomLeft = new GameVector( obj.mPosition.x-0.06f - (obj.mWidth/2), obj.mPosition.y-0.06f -(obj.mHeight/2));
final GameVector bottomRight = new GameVector( obj.mPosition.x+0.06f + (obj.mWidth/2), obj.mPosition.y-0.06f -(obj.mHeight/2));
//order here matters
GameVector[] vertices = { topLeft, topRight, bottomRight, bottomLeft };
return vertices;
}
public GameVector[] getAxis(GameObject shape){
GameVector[] vertices = getVertices(shape);
GameVector[] axes = new GameVector[vertices.length];
// loop over the vertices
for (int i = 0; i < vertices.length; i++) {
// get the current vertex
GameVector p1 = vertices[i];
// get the next vertex if i+1 == vertices length set back to vertices [0]
GameVector p2 = vertices[i + 1 == vertices.length ? 0 : i + 1];
// subtract the two to get the edge vector
GameVector edge = p1.subtract(p2.x, p2.y);
// get either perpendicular vector
GameVector normal;
//get the left side normal of the vector due to clock wise positions
normal = new GameVector(edge.y, -edge.x);//edge.perp();
axes[i] = normal;
}
return axes;
}
public float dotProduct(GameVector a, GameVector b){
float dp = a.x*b.x + a.y*b.y;
return dp;
}
public class Projection {
private final float min;
private final float max;
public Projection(float min, float max) {
this.min = min;
this.max = max;
}
public boolean doesOverlap(final Projection other) {
return !(this.min > other.max || other.min > this.max);
}
}
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.
I am making a simulation of a pendulum, but it only performs one swing before sending close to random positions for the bob to be at. Essentially, it does not go backwards.
I have tried to change the direction using the goingForward boolean, but it still doesnt work.
public class AnimationPane extends JPanel {
// START CHANGEABLE VARIABLES
private double startAngle = -60.0; // degrees
private double mass = 1; // kilogrammes
private int radius = 10; // m
private double gravity = 9.80665; // m/s^2 // on earth: 9.80665
// END CHANGEABLE VARIABLEs
private BufferedImage ball;
private BufferedImage rope;
private int pointX = 180;
private int pointY = 50;
private double endAngle = Math.abs(startAngle); // absolute value of startAngle
private double angle = startAngle; // current angle
private double circum = (2 * Math.PI * radius); // m
private double distance = 0; // m
private double velocity = 0; // m/s
private double totalEnergy = ((radius) - (Math.cos(Math.toRadians(angle)) * radius)) * gravity * mass + 0.00001;
private double previousE;
private int xPos = 0; // for program
private int yPos = 0; // for program
private boolean goingForward = true;
private double height = 0;
public AnimationPane() {
try {
ball = ImageIO.read(new File("rsz_black-circle-mask-to-fill-compass-outline.png"));
Timer timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
double angleRad = Math.toRadians(Math.abs(angle));
double potentialE = ((radius) - (Math.cos(angleRad) * radius)) * gravity * mass;
Double pE = new Double(potentialE);
height = (radius - (Math.cos(angleRad) * radius));
double kineticE = totalEnergy - pE;
if (kineticE <= 0 || angle >= endAngle) {
if (goingForward == true) {
goingForward = false;
}
else
{
goingForward = true;
}
kineticE = 0.1;
angle = 60;
}
velocity = Math.sqrt(2 * kineticE / mass);
double ratio = distance / circum;
if (goingForward == true) {
distance = distance + (velocity / 10);
angle = startAngle + (360 * ratio);
}
else {
distance = distance - (velocity / 10);
angle = startAngle - (360 * ratio);
}
double angles = Math.toRadians(angle);
double xDouble = Math.sin(angles) * (radius * 10);
Double x = new Double(xDouble);
xPos = x.intValue() + 150;
double yDouble = Math.cos(angles) * (radius * 10);
Double y = new Double(yDouble);
yPos = y.intValue() + 50;
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
} catch (IOException ex) {
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(xPos + 20, yPos + 20, pointX, pointY);
g.drawImage(ball, xPos, yPos, this);
}
}
I would really appreciate some help getting this to work.
Thank you
I could not debug your code, which was uneasy to work with and sometimes to understand (you use a lot of integer literals in your code, which hides their semantic, I have no idea what was your intention on some statements).
Therefore, I rewrote it using the solution of the differential equation for small oscillations. It works, you can take it as a clean base to implement it again the way you wanted. Note that as Andy Turner pointed it, you should not have to worry about the fact of going forward or backward. You have an equation, you solve it, it gives you the position of the ball at any time. If you want something which is accurate for large angles, I suggest you go on Wikipedia to see the movement equation in this case. Last option, you could numerically solve the differential equation although I would personally don't know how to do it at first glance.
package stackoverflow;
public class AnimationPane extends JPanel {
private static final long serialVersionUID = 1L;
private static final double GRAVITY = 9.80665;
private BufferedImage ball;
private final Point fixedCordPoint;
private final int cordLength;
private final double startAngle;
private double currentAngle;
private final double pulsation;
private final Point ballPos = new Point();
private int time = 1;
public AnimationPane(Point fixedCordPoint, int cordLength, double startAngleRadians) {
this.fixedCordPoint = new Point(fixedCordPoint);
this.cordLength = cordLength;
this.pulsation = Math.sqrt(GRAVITY / cordLength);
this.startAngle = startAngleRadians;
this.currentAngle = startAngleRadians;
this.ball = loadImage(new File("ball.jpg"));
}
private BufferedImage loadImage(File file) {
try {
return ImageIO.read(file);
} catch (IOException e) {
throw new RuntimeException("Could not load file : " + file, e);
}
}
public void start() {
Timer timer = new Timer(100, event -> {
ballPos.x = fixedCordPoint.x + (int) Math.round(Math.sin(currentAngle) * cordLength);
ballPos.y = fixedCordPoint.y + (int) Math.round(Math.cos(currentAngle) * cordLength);
repaint();
currentAngle = startAngle * Math.cos(pulsation * time);
time++;
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawLine(ballPos.x, ballPos.y, fixedCordPoint.x, fixedCordPoint.y);
g.drawImage(ball, ballPos.x - ball.getWidth() / 2, ballPos.y - ball.getHeight() / 2, this);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
AnimationPane pendulumAnimationPane = new AnimationPane(new Point(160, 25), 180, - Math.PI / 10);
frame.setContentPane(pendulumAnimationPane);
frame.setSize(400,400);
frame.setVisible(true);
pendulumAnimationPane.start();
}
}
I really can't follow your code. If I had to say where I thought the problem is, I would say that it is the distance variable, and its consequent use in ratio: this seems to be defined as being zero when the pendulum starts. When you switch the direction of the pendulum, I think that it needs to be rebased to zero... Somehow.
You should also separate out your GUI code from your simulation. You can just make it print out the x and y coordinates while you are debugging. For instance, you can write the values to a CSV file, so you can visualize them in Matlab (or whatever) to ensure that your simulation looks right over time.
I would make two suggestions to help you with the simulation:
Don't model it in terms of energy, which is a scalar quantity, meaning that you have to use additional state like a "direction flag" to model which way it is going;
You are going to need an additional state variable. Storing x and y is redundant, since these can be derived directly from the angle (and R, although that is constant). You are modelling a second-order system, so you need that second state variable which models the movement of the system at an instant, as well as its position. For example, you could model the angle and angular velocity over time.
And, of course, don't mess around with degrees - stick to radians :)
(On the matter of velocity, your variable of that name is actually speed - you don't know which direction it moves in, and that information is highly relevant to the dynamics of the system).
The derivation of the method using angle and angular velocity is quite straightforward, although my maths is rusty enough to caution its use without checking carefully!
The rotational form of Newton's second law of motion is:
Torque = moment of inertia * angular acceleration
The torque on the bob is given by -mgR sin angle (m is mass, g is gravity, R is length of pendulum, angle is the angular displacement of the pendulum from vertical). The minus sign is because the force tends to return the bob to vertical;
The moment of inertia of a point mass is mR^2 - this is a reasonable approximation if the pendulum is very long compared to the radius of the bob.
Therefore the angular acceleration is -g sin theta / R. Hopefully this should look like simple harmonic motion - an acceleration proportional to the distance from an equilibrium (for small theta, sin theta is approximately theta) and directed towards it.
You can now put this together into a numerical scheme to simulate the pendulum. For example, using Euler's method:
New angle = old angle + dt * old angular velocity
New angular velocity = old angular velocity vel - dt * g * sin(angle) / R
where dt is your time step.
You can, of course, use higher-order methods like Runge-Kutta to reduce the numerical errors in the simulation, but it is (slightly) more involved.