I am very new to coding and my aim is to make and object (a lorry) change destination when it reaches the first one (site).
As for now, the lorry leaves its original place (concrete plant) and moves to the first site created. The user can add sites with mousePress (I used an array of sites). But then my lorry gets stuck on the first site and doesn't go to the next one. I also want the lorry to go to 2 sites and then come back to the concrete plant to then again leave for 2 sites etc...
Can somebody help me, I am desperate and my deadline is next Monday.
This is my code:
Lorry lorry;
int xCoord;
int yCoord;
ArrayList<Site> sites;
int siteSize = 30;
void setup() // What is called once at the beginning
{
size (500, 500);
xCoord = int(width/2);
yCoord = int(height/2);
//Creating empty Array List where store sites objects
sites = new ArrayList<Site>();
//Adding first site
sites.add(new Site(random(width), random(height), siteSize));
//storing lorries
lorry = new Lorry(xCoord, yCoord);
}
void draw() // Draw the background and concrete plant
{
background (235, 247, 255);
ellipse(xCoord, yCoord, 60, 60);
//Calling the sites
for (int i = sites.size () - 1; i>=0; i--) {
Site site = sites.get(i);
site.displaySites();
}
//calling the lorry functions
lorry.updateLorry();
}
void mousePressed() {
sites.add(new Site(mouseX, mouseY, siteSize));
}
class Site
{
float x,y;
float size;
Site (float xin, float yin, float sin)
{
x = xin;
y = yin;
size = sin;
}
void displaySites()
{
rectangle(x, y, 60, 60);
}
}
class Lorry
{
PVector location;
PVector concretePlant;
PVector velocity;
boolean changeDirection;
int siteNumber = 0;
Site destination;
Lorry(float xCoord, float yCoord)
{
concretePlant = new PVector(xCoord, yCoord); //Initial start point
location = new PVector(xCoord, yCoord); //Initial start point
velocity = new PVector(2, 2);
destination = sites.get(siteNumber);
changeDirection = false;
}
void displayLorry()
{
rectangle(location.x, location.y, 30, 30);
}
void Move()
{
float xdir = destination.x - location.x;
float ydir = destination.y - location.y;
PVector dir = new PVector (xdir, ydir);
dir.normalize();
location.add(dir);
}
void reachDestination()
{
if ((destination.x == location.x) && (destination.y == location.y)) {
siteNumber++; // siteNumber = siteNumber + 1;
destination = sites.get(siteNumber);
changeDirection = true;
}
}
void updateLorry()
{
displayLorry();
Move();
reachDestination();
}
}
You're super close Lily, literally.
If you print the values of destination and location you'll notice they're getting super close, however, due to the increments they never "meet". The values never match (aren't equal).
You could easily swap your equals conditions for a more practical threshold distance condition (e.g. if the distance between the destination and location are smaller than 1px):
void reachDestination()
{
println(destination,location);
//if ((destination.x == location.x) && (destination.y == location.y)) {
if(dist(destination.x,destination.y,location.x,location.y) < 1){
if(siteNumber < sites.size() -1){
siteNumber++; // siteNumber = siteNumber + 1;
destination = sites.get(siteNumber);
changeDirection = true;
}else{
println("reached final site");
}
println("reachDestination");
}
}
Bare in mind that dist() uses the square root which could become a slow calculation for a large number of sites (due to sqrt), however you could use the squared distance instead.
Additionally, PVector has a lerp() function that returns the interpolated position between two given positions (e.g. destination and site) and an interpolation amount (a value between 0.0 (start position) and 1.0 (end position)).
Here's a proof of concept sketch:
PVector[] destinations = {
new PVector(10,10),
new PVector(90,10),
new PVector(90,90),
new PVector(10,90)
};
float traversal = 0.0;
void setup(){
}
void draw(){
background(255);
//draw destinations
for(PVector d : destinations){
ellipse(d.x,d.y,9,9);
}
//calculate traversal
PVector traversed = traversePoints(destinations,0.01);
//draw traversal
ellipse(traversed.x,traversed.y,3,3);
}
PVector traversePoints(PVector[] destinations,float speed){
if(speed < 0){
speed = 0.05;
}
//increment full path traversal (the higher the increment, the faster the move)
traversal += speed;
//loop back to 0 when fully traversed
if(traversal > destinations.length) traversal = 0.0;
//compute the current point index
int pointIndex = (int)traversal;
//compute the local traversal (0.0 -> 1.0 = 0% to 100% in between two points: current and next)
float pointT = traversal - pointIndex;
//compute the next current point index
int pointIndexNext = (pointIndex + 1) % destinations.length;
//interpolate between current and next points using above local traversal, offsetting by the last mainHingeition)
return PVector.lerp(destinations[pointIndex],
destinations[pointIndexNext],
pointT);
}
You can actually run this as demo bellow:
var destinations;
var traversal = 0.0;
function setup(){
createCanvas(100,100);
destinations = [
createVector(10,10),
createVector(90,10),
createVector(90,90),
createVector(10,90)
];
}
function draw(){
background(255);
//draw destinations
for(var i = 0 ; i < destinations.length; i++){
var d = destinations[i];
ellipse(d.x,d.y,9,9);
}
//calculate traversal
var traversed = traversePoints(destinations,0.01);
//draw traversal
ellipse(traversed.x,traversed.y,3,3);
}
function traversePoints(destinations,speed){
if(speed < 0){
speed = 0.05;
}
//increment full path traversal (the higher the increment, the faster the move)
traversal += speed;
//loop back to 0 when fully traversed
if(traversal > destinations.length) traversal = 0.0;
//compute the current point index
var pointIndex = Math.floor(traversal);
//compute the local traversal (0.0 -> 1.0 = 0% to 100% in between two points: current and next)
var pointT = traversal - pointIndex;
//compute the next current point index
var pointIndexNext = (pointIndex + 1) % destinations.length;
//interpolate between current and next points using above local traversal, offsetting by the last mainHingeition)
return p5.Vector.lerp(destinations[pointIndex],
destinations[pointIndexNext],
pointT);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.6.1/p5.min.js"></script>
Related
so I am trying to make lorries move towards an entity (site) only if it meets certain criteria (using "ifs").
So the plan is that I have 2 lorries (created inside an array in case I want to add more in the future and for easier manipulation in the script), and they are originally placed at a concrete plant in the center. The user makes a site appears on the screen when he clicks (other sites are added at every click).
There are three criteria in order to choose which truck to move to the site when a site is created:
If the truck is available (not in delivery, delivery = deliv);
If the truck as enough cement (each lorry start with 2 units (u), and every time it goes to a site, it looses 1 unit. When it reaches 0 it needs to go back to the concrete plant to "fill up")
The truck that is the nearest (by checking the distance);
I have created a series of loops to check these conditions in the draw function. Starting by saying that if the lorries are available, then check if it has units of cement. If no then destinations are changed to concreteplant, if yes then calculate the distance between the lorry and the site.
The I have another loop that will allow to select the lorry with the smallest distance (smallD).
Another loop to say to take that lorry with the smallest distance and assign the destination as site and go to the next site etc + setting delivery to true as it is become unavailable (so it doesnt change direction while it is going to the first site when a new site is clicked).
The I have created a final loop to say to update the lorry which means to move it, display it and checked progress (functions in the lorry class).
This is my script:
/*preload = "factory_12.png";*/
/*preload = "sign.png";*/
/*preload = "simple_truck.png";*/
ArrayList<Lorry> lorries;
ArrayList<Site> sites;
PImage concretePlant;
PFont aFont;
int xCoord;
int yCoord;
int siteSize = 30;
int siteNumber = 0;
void setup() // What is called once at the beginning
{
size (500, 500);
concretePlant = loadImage("factory_12.png");
aFont = createFont("IndustrialRevolution-Regular", 12);
textFont(aFont);
xCoord = int(width/2);
yCoord = int(height/2);
//Creating empty Array List where store sites objects
sites = new ArrayList<Site>();
//Adding first site
sites.add(new Site(random(width), random(height), siteSize));
//storing lorries
lorries = new ArrayList<Lorry>();
lorries.add(new Lorry(xCoord, yCoord));
lorries.add(new Lorry(xCoord, yCoord));
}
void draw() // Draw the background and concrete plant
{
background (235, 247, 255);
image(concretePlant, xCoord, yCoord, 60, 60);
fill(1);
text("Concrete Plant", xCoord-20, yCoord+70);
//Calling the sites
for (int i = sites.size () - 1; i>=0; i--) {
Site site = sites.get(i);
site.displaySites();
}
float[] distanceCheck = new float[lorries.size()];
for (int i = 0; i< lorries.size (); i++) {
if (lorries.get(i).deliv == false) {
println ("delivery check");
if (lorries.get(i).u > 0) {
PVector siteTemp = new PVector (sites.get(siteNumber).x, sites.get(siteNumber).y);
PVector lorryTemp = new PVector (lorries.get(i).location.x, lorries.get(i).location.y);
distanceCheck[i] = PVector.dist(siteTemp, lorryTemp);
println ("distanceChecked");
} else {
lorries.get(i).destination.x = xCoord;
lorries.get(i).destination.y = yCoord;
println("goingBack");
}
println("first for done");
}
}
int smallestD = -1;
for (int i = 0; i <lorries.size (); i++) {
if ((distanceCheck[i] > 0) && (distanceCheck[i] == min(distanceCheck))) {
smallestD = i;
}
println("smallest found");
}
if (smallestD >= 0) {
lorries.get(smallestD).destination.x = sites.get(siteNumber).x;
lorries.get(smallestD).destination.y = sites.get(siteNumber).y;
if (siteNumber < sites.size() -1) {
siteNumber++; // siteNumber = siteNumber + 1;
}
lorries.get(smallestD).deliv = true;
}
for (int i = 0; i < lorries.size (); i++) {
lorries.get(i).updateLorry();
}
}
void mousePressed() {
sites.add(new Site(mouseX, mouseY, siteSize));
}
class Lorry
{
PVector location;
PVector concretePlant;
PVector velocity;
float d; //distance
int u; //unit of cement
boolean r; //Reserved lorry
boolean deliv; //
PImage mixer;
boolean changeDirection;
PVector destination;
Lorry(float xCoord, float yCoord)
{
concretePlant = new PVector(xCoord, yCoord); //Initial start point
location = new PVector(xCoord, yCoord, 0); //Initial start point
velocity = new PVector(2, 2);
u = 2;
r = false;
deliv = false;
mixer = loadImage("simple_truck.png");
//destination = sites.get(siteNumber);
destination = concretePlant;
//PVector temp = new PVector (destination.x, destination.y);
changeDirection = false;
//d = PVector.dist(location, temp);
}
void displayLorry()
{
image(mixer, location.x, location.y, 30, 30);
}
void move()
{
float xdir = destination.x - location.x;
float ydir = destination.y - location.y;
PVector dir = new PVector (xdir, ydir);
dir.normalize();
location.add(dir);
print("going");
//deliv = true;
}
void checkProgress()
{
if (dist(destination.x, destination.y, location.x, location.y) < 1) {
deliv = false;
//u --;
// if (siteNumber <sites.size() -1) {
// siteNumber++; // siteNumber = siteNumber + 1;
// }
}
}
void updateLorry()
{
displayLorry();
move();
checkProgress();
}
}
class Site
{
float x,y;
float size;
PImage picture;
Site (float xin, float yin, float sin)
{
x = xin;
y = yin;
size = sin;
picture = loadImage("sign.png");
}
void displaySites()
{
image(picture, x, y, 60, 60);
}
}
Site
Lorry
Concrete Plant
I made a few modifications:
first it was not compiling
then I added a few utilities on the Lorry class which simplifies the code in the draw method
And finally, I tweaked the algorithm in the draw method to pick the correct Lorry
I think the following code works as expected.
You still need to make a Site for the plant, this way you can set it as destination for a Lorry when it needs to reload. Then when it reaches the plant, you set the units back to 2, and it should keep going to the next site after that. Good luck!
/*preload = "factory_12.png";*/
/*preload = "sign.png";*/
/*preload = "simple_truck.png";*/
ArrayList<Lorry> lorries;
ArrayList<Site> sites;
PImage concretePlant;
PFont aFont;
int xCoord;
int yCoord;
int siteSize = 30;
int siteNumber = 0; // keeps track of the index of the nest site to deliver to
void setup() // What is called once at the beginning
{
size (500, 500);
concretePlant = loadImage("factory_12.png");
aFont = createFont("IndustrialRevolution-Regular", 12);
textFont(aFont);
xCoord = int(width/2);
yCoord = int(height/2);
//Creating empty Array List where store sites objects
sites = new ArrayList<Site>();
//Adding first site
sites.add(new Site(random(width), random(height), siteSize));
//storing lorries
lorries = new ArrayList<Lorry>();
lorries.add(new Lorry("Lorry 1", xCoord, yCoord));
lorries.add(new Lorry("Lorry 2", xCoord, yCoord));
}
void mousePressed() {
sites.add(new Site(mouseX, mouseY, siteSize));
}
// This returns the next site to deliver to
// Returns null if no more sites
Site getNextSite() {
Site site = null;
if(siteNumber < sites.size()) {
site = sites.get(siteNumber);
}
return site;
}
void draw() {
// Draw the background and concrete plant
background (235, 247, 255);
image(concretePlant, xCoord, yCoord, 60, 60);
fill(1);
text("Concrete Plant", xCoord-20, yCoord+70);
// Displaying the sites
for (int i = sites.size () - 1; i>=0; i--) {
sites.get(i).display();
}
// Ckeck if there is a next site to deliver to
Site nextSite = getNextSite();
if(nextSite!=null) {
// If yes, find out which lorry should go
Lorry lorryToGo = null;
// List all available lorries (not delivering or out of units)
ArrayList<Lorry> availableLorries = new ArrayList();
for(Lorry l : lorries) {
if(l.isAvailableForDelivery()) {
availableLorries.add(l);
}
}
// if any, find the closest
if(!availableLorries.isEmpty()) {
lorryToGo = availableLorries.get(0);
for(Lorry l : availableLorries) {
if(l.checkDistanceToSite(nextSite) < lorryToGo.checkDistanceToSite(nextSite)) {
lorryToGo = l;
}
}
}
// If a lorry has to go, set its new destination
// and increase the siteNumber
if(lorryToGo != null) {
println(lorryToGo.name + " to go");
lorryToGo.destination = nextSite;
lorryToGo.delivering = true;
siteNumber++;
}
}
// update all lorries
for(Lorry l : lorries) {
l.updateLorry();
}
}
class Lorry {
String name;
PVector location;
PVector concretePlant;
PVector velocity;
int units; //unit of cement
boolean reserved; //Reserved lorry
boolean delivering; //
PImage mixer;
boolean changeDirection;
Site destination;
Lorry(String name, float xCoord, float yCoord) {
this.name = name;
concretePlant = new PVector(xCoord, yCoord); //Initial start point
location = new PVector(xCoord, yCoord); //Initial start point
velocity = new PVector(2, 2);
units = 2;
reserved = false;
delivering = false;
mixer = loadImage("simple_truck.png");
destination = getNextSite();
changeDirection = false;
}
boolean isAvailableForDelivery() {
return !delivering && units > 0;
}
boolean needsToReload() {
return units <= 0;
}
void displayLorry() {
image(mixer, location.x, location.y, 30, 30);
}
float checkDistanceToSite(Site site) {
return PVector.dist(location, new PVector(site.x, site.y));
}
void move() {
float xdir = destination.x - location.x;
float ydir = destination.y - location.y;
PVector dir = new PVector (xdir, ydir);
dir.normalize();
location.add(dir);
}
void checkProgress() {
if (destination != null && checkDistanceToSite(destination) < 1) {
println(name + " reached destination");
delivering = false;
units--;
if(needsToReload()) {
println(name + " needs to reload");
destination = null;
// put some code here to send the truck back to the plant
//destination = plant
} else {
destination = getNextSite();
println("assigning new destination with index: "+siteNumber);
println("assigning new destination: "+destination);
}
}
}
void updateLorry() {
if(delivering)
move();
displayLorry();
checkProgress();
}
}
class Site {
float x,y;
float size;
PImage picture;
Site (float xin, float yin, float sin) {
x = xin;
y = yin;
size = sin;
picture = loadImage("sign.png");
}
void display() {
image(picture, x, y, 60, 60);
}
}
(new to java). So I am have created the following script and I would like my object called "site" to appear only when the mouse is pressed and then stay on the screen. Then at every mouse pressed it created a new "site".
What it does now is that it creates a site at the beginning and the adds one when the mouse is pressed. But I don't want that site in the beginning, I just want it when I click.
Is anyone could help me that would be great!
/*preload = "factory_12.png";*/
/*preload = "sign.png";*/
/*preload = "simple_truck.png";*/
Lorry lorry;
PImage concretePlant;
PFont aFont;
int xCoord;
int yCoord;
ArrayList<Site> sites;
int siteSize = 30;
void setup() // What is called once at the beginning
{
size (500, 500);
concretePlant = loadImage("factory_12.png");
aFont = createFont("IndustrialRevolution-Regular", 12);
textFont(aFont);
xCoord = int(width/2);
yCoord = int(height/2);
//Creating empty Array List where store sites objects
sites = new ArrayList<Site>();
//Adding first site
sites.add(new Site(random(width), random(height), siteSize));
//storing lorries
lorry = new Lorry(xCoord, yCoord);
}
void draw() // Draw the background and concrete plant
{
background (235, 247, 255); //light blue background, not in draw as it wouldn't allow the site image to stay
image(concretePlant, xCoord, yCoord, 60, 60);
fill(1);
text("Concrete Plant", xCoord-20, yCoord+70);
//Calling the sites
for (int i = sites.size () - 1; i>=0; i--) {
Site site = sites.get(i);
site.displaySites();
}
//calling the lorry functions
lorry.updateLorry();
}
void mousePressed() {
sites.add(new Site(mouseX, mouseY, siteSize));
}
class Site
{
float x,y;
float size;
PImage picture;
Site (float xin, float yin, float sin)
{
x = xin;
y = yin;
size = sin;
picture = loadImage("sign.png");
}
void displaySites()
{
image(picture, x, y, 60, 60);
}
}
class Lorry
{
PVector location;
PVector concretePlant;
PVector velocity;
PImage mixer;
boolean changeDirection;
int siteNumber = 0;
Site destination;
Lorry(float xCoord, float yCoord)
{
concretePlant = new PVector(xCoord, yCoord); //Initial start point
location = new PVector(xCoord, yCoord); //Initial start point
velocity = new PVector(2, 2);
mixer = loadImage("simple_truck.png");
destination = sites.get(siteNumber);
changeDirection = false;
}
void displayLorry()
{
image(mixer, location.x, location.y, 30, 30);
}
void Move()
{
float xdir = destination.x - location.x;
float ydir = destination.y - location.y;
PVector dir = new PVector (xdir, ydir);
dir.normalize();
location.add(dir);
print("going");
}
void reachDestination()
{
//println(destination,location);
//if ((destination.x == location.x) && (destination.y == location.y)) {
if (dist(destination.x, destination.y, location.x, location.y) < 1) {
if (siteNumber <sites.size() -1) {
siteNumber++; // siteNumber = siteNumber + 1;
destination = sites.get(siteNumber);
changeDirection = true;
} else {
println("reached final site");
}
println("reachedDestination");
}
}
void updateLorry()
{
displayLorry();
Move();
reachDestination();
}
}
Thank you :)
Get rid of this line:
//Adding first site
sites.add(new Site(random(width), random(height), siteSize));
This line in your setup() function adds a Site. If you don't want to add a Site at startup, remove this line.
You should also try to go through the process of debugging your problem. For example, step through the code line-by-line with a piece of paper and a pencil, or add print statements, or use the debugger that comes with the Processing editor. That will help you figure out problems like this in the future. Good luck!
i have tried to move my curve but it is not moving well, when it changes its direction from left to right or right to left then the movement is quite awkward.
i want to move my curve like this video
video of curve movement what i actually want.
In this video when a it change its direction it is so graceful but in my case it change its direction and the curve gives a crazy shape at newly added point.
Experts please solve this problem.
here is my code
//create paths
private Bezier<Vector2> path1;
private CatmullRomSpline<Vector2> path2;
private ShapeRenderer sr;
int height,width;
Vector2 starting,ending,endingControl;
ArrayList<Vector2> listOfPoints;
Vector3 touchPos;
float timeDifference;
Boolean leftPos=false,rightPos=false;
Boolean isTouch=false,isTouchUp=false;
Vector2 mVector2;
private OrthographicCamera cam;
Vector2[] controlPoints;
#Override
public void create () {
width = Gdx.graphics.getWidth();
height = Gdx.graphics.getHeight();
ending=new Vector2(width/2,height/2);
endingControl=new Vector2(ending.x,ending.y+10);
starting=new Vector2(width/2,0);
controlPoints = new Vector2[]{starting,starting,ending,ending};
// set up the curves
path2 = new CatmullRomSpline<Vector2>(controlPoints, false);
listOfPoints=new ArrayList<Vector2>();
// setup ShapeRenderer
sr = new ShapeRenderer();
sr.setAutoShapeType(true);
sr.setColor(Color.BLACK);
cam=new OrthographicCamera();
cam.setToOrtho(false);
listOfPoints.add(new Vector2(width/2,0)); //starting
listOfPoints.add(new Vector2(width/2,0)); //starting
}
#Override
public void resize(int width, int height) {
// TODO Auto-generated method stub
super.resize(width, height);
cam.update();
}
#Override
public void render () {
cam.update();
Gdx.gl.glClearColor(1f, 1f, 1f, 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
sr.begin();
sr.set(ShapeType.Filled);
if(Gdx.input.isTouched())
{
if(!isTouch){
listOfPoints.add(new Vector2(ending.x+2, ending.y-4));
int s=listOfPoints.size();
controlPoints=new Vector2[s+2];
listOfPoints.toArray(controlPoints);
controlPoints[s]=ending;
//endingControl.x=ending.y;
controlPoints[s+1]=ending;
path2 = new CatmullRomSpline<Vector2>(controlPoints, false);
}
isTouch=true;
ending.x+=3;
}
else {
if(isTouch){
listOfPoints.add(new Vector2(ending.x-2, ending.y-4));
int s=listOfPoints.size();
controlPoints=new Vector2[s+2];
listOfPoints.toArray(controlPoints);
controlPoints[s]=ending;
controlPoints[s+1]=ending;
path2 = new CatmullRomSpline<Vector2>(controlPoints, false);
}
isTouch=false;
ending.x-=3;
}
moveAndReduce();
for(int i = 0; i < 100; ++i){
float t = i /100f;
Vector2 st = new Vector2();
Vector2 end = new Vector2();
path2.valueAt(st,t);
path2.valueAt(end, t-0.01f);
sr.rectLine(st.x, st.y, end.x, end.y,3);
}
sr.end();
}
#Override
public void dispose () {
sr.dispose();
}
public void moveAndReduce()
{
for(Vector2 vector2:listOfPoints)
{
vector2.y-=3 ;
}
if(listOfPoints.size()>3 && listOfPoints.get(3).y<-1)
{
listOfPoints.remove(0);
listOfPoints.set(0, listOfPoints.get(1));
int s=listOfPoints.size();
controlPoints=new Vector2[s+2];
listOfPoints.toArray(controlPoints);
controlPoints[s]=ending;
controlPoints[s+1]=ending;
path2 = new CatmullRomSpline<Vector2>(controlPoints, false);
}
}
Going by the video the curve does not look like it is constrained by control points but just a simple trace of and accelerating point.
You create an array of floats the length in pixels matching the length of the line in the x direction. For example if screen is 200 pixels wide the line can be 100 so the array is 100 in length. Set each float in the array to the start value half the screen height. I call the array line in this answer. You call it what you like.
The you assign a head index that is the index of the rightmost point. Each frame you move the head index up by one. If it is over the array length-1 you set it to zero (beginning of array)
Rendering the Path
When you draw the line you draw all the points from head + 1
Path p = new Path();
for(int i = 0; i < 100; ++i){
p.lineTo(i, line[(i + head + 1) % 100]); // add path points
}
// draw the path;
Moving up and down
To make it move you have a movement float move that is 0 for no movement or positive and negative values the move up or down.
When you want it to move increase the move amount by a fixed value.
// moving down
if(move < maxMove){ // set a max move amount eg 10
move += moveAmount; // moveAmount 0.2 just as an example
}
Same for moving up, but subtract
When there is no input you move the move amount back to zero by a fixed rate
// assume this is code run when no input
if(move != 0){
if(Math.abs(move) < moveAmount){ // if close to zero set to zero
move = 0;
}else{
move -= Math.sign(move) * moveAmount; // else move towards zero at
// fixed rate
}
}
Moving forward
The line does not move forward, just appears to do so as we move the head position up the array each frame.
Back to moving the line's head the following move the line head position up or down (but is not complete the last line is modified to create a smoother curve)
float pos = line[head]; // get the pos of line at head
head += 1; // move the head forward 1
head %= 100; // if past end of array move to 0
line[head] = pos + move; // set the new head position
A better curve
This will move the head of the line up or down depending on move. The curve we get is not that nice so to make it a little smoother you need to change the rate the move value changes the head position.
// an sCurve for any value of move the result is from -1 to 1
// the greater or smaller move the closer to 1 or -1 the value gets
// the value -1.2 controls the rate at which the value moves to 1 or -1
// the closer to -1 the value is the slower the value moves to 1 or -1
float res = (2 / (1 + Math.pow(move,-1.2))) -1;
This in effect changes the shape of the lines curve to a almost sine wave when moving up and down
// so instead of
//line[head] = pos + move; // set the new head position
line[head] = pos + ( (2 / (1 + Math.pow(move,-1.2))) -1 ) * maxSpeed;
// max speed is the max speed the line head can move up or down
// per frame in pixels.
Example to show the curve
Below is a Javascript implementation that does it as outlined above (is not intended as answer code). Use the keyboard Arrow Up and Arrow down to move the line
If you are using a tablet or phone then the following image is what you will see as way to late for me to add and test touch for the example
const doFor = (count, callback) => {var i = 0; while (i < count) { callback(i ++) } };
const keys = {
ArrowUp : false,
ArrowDown : false,
};
function keyEvents(e){
if(keys[e.code] !== undefined){
keys[e.code] = event.type === "keydown";
e.preventDefault();
}
}
addEventListener("keyup", keyEvents);
addEventListener("keydown", keyEvents);
focus();
var gameOver = 0;
var gameOverWait = 100;
var score = 0;
var nextWallIn = 500
var nextWallCount = nextWallIn;
var wallHole = 50;
const wallWidth = 5;
const walls = [];
function addWall(){
var y = (Math.random() * (H - wallHole * 2)) + wallHole *0.5;
walls.push({
x : W,
top : y,
bottom : y + wallHole,
point : 1, // score point
});
}
function updateWalls(){
nextWallCount += 1;
if(nextWallCount >= nextWallIn){
addWall();
nextWallCount = 0;
nextWallIn -= 1;
wallHole -= 1;
}
for(var i = 0; i < walls.length; i ++){
var w = walls[i];
w.x -= 1;
if(w.x < -wallWidth){
walls.splice(i--,1);
}
if(w.x >= line.length- + wallWidth && w.x < line.length){
var pos = line[head];
if(pos < w.top || pos > w.bottom){
gameOver = gameOverWait;
}
}
if(w.point > 0 && w.x <= line.length){
score += w.point;
w.point = 0;
}
}
}
function drawWalls(){
for(var i = 0; i < walls.length; i ++){
var w = walls[i];
ctx.fillStyle = "red";
ctx.fillRect(w.x,0,wallWidth,w.top);
ctx.fillRect(w.x,w.bottom,wallWidth,H-w.bottom);
}
}
const sCurve = (x,p) => (2 / (1 + Math.pow(p,-x))) -1;
const ctx = canvas.getContext("2d");
var W,H; // canvas width and height
const line = [];
var move = 0;
var curvePower = 1.2;
var curveSpeed = 0.2;
var maxSpeed = 10;
var headMoveMultiply = 2;
var head;
function init(){
line.length = 0;
doFor(W / 2,i => line[i] = H / 2);
head = line.length - 1;
move = 0;
walls.length = 0;
score = 0;
nextWallIn = 500
nextWallCount = nextWallIn;
wallHole = 50;
ctx.font = "30px arial black";
}
function stepLine(){
var pos = line[head];
head += 1;
head %= line.length;
line[head] = pos + sCurve(move,curvePower)*headMoveMultiply ;
}
function drawLine(){
ctx.beginPath();
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
ctx.lineJoin = "round";
ctx.lineCap = "round";
for(var i = 0; i <line.length; i++){
ctx.lineTo(i,line[(i + head + 1) % line.length]);
}
ctx.stroke();
}
function mainLoop(time){
if(canvas.width !== innerWidth || canvas.height !== innerHeight){
W = canvas.width = innerWidth;
H = canvas.height = innerHeight;
init();
}
if(gameOver === 1){
gameOver = 0;
init();
}
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,W,H);
if(keys.ArrowUp){
if(move > - maxSpeed){
move -= curveSpeed;
}
}else if(keys.ArrowDown){
if(move < maxSpeed){
move += curveSpeed;
}
}else{
move -= Math.sign(move)*curveSpeed;
if(Math.abs(move) < curveSpeed){
move = 0;
}
}
if(gameOver === 0){
stepLine();
updateWalls();
}
drawLine();
drawWalls();
ctx.fillStyle = "Black";
ctx.textAlign = "left";
ctx.fillText("Score : " + score, 10,30);
if(gameOver > 0){
ctx.textAlign = "center";
ctx.fillText("Crashed !!", W / 2,H * 0.4);
gameOver -= 1;
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas {
position : absolute;
top : 0px;
left : 0px;
z-index : -10;
}
<br><br><br>Up down arrow keys to move line.
<canvas id=canvas></canvas>
I have an object that I am trying to get to move in a smooth "arc". The x value of the object does not actually change, but the y value does.
The y value starts at -108, and needs to get to -37.5. Every iteration I want it to move the same distance in a shorter time. The issue is that it needs to decelerate and stop at -37.5 (this would be for half of the motion). When the current code runs the time stays the same, the distance increases. this does work for the first iteration.
This is what I have:
private void jump() {
int startV = 10;
float yVal;
float velocity;
int airTime = 1;
float currY = -108;
Main main = New Main();
// main.iter is the iteration
velocity = startV * (1 + (main.iter / 10));
while (airtime != 0) {
yVal = yVal + velocity;
velocity = velocity - (1 + (main.iter / 10));
if (currY < yVal) {
airtime++;
}
else {
airtime--;
}
}
yVal = -108;
}
EDIT:
main.iter is an int that increments by one
I have a program where an entity moves around in two-dimensional space. To move one step, the entity picks its next point, and then sets it as his current point.
Sometimes, however, the entity's next point lies in an Area (java.awt.geom.Area) that is forbidden (the "forbidden area" is actually a velocity obstacle).
How can the entity pick the point outside the Area which is closest to the entity's preferred point?
The Area is composed of different shapes (sometimes, the shapes are not touching).
My initial plan was to simply draw a line to the preferred point. Wherever the line intersected the Area first, this would be the next-best point. However, finding the intersection between a line and an Area turns out to be quite complex.
EDIT: This wouldn't necessarily find the closest point. This would just find the closet point on the same trajectory. I'm looking for the closest possible point.
Perhaps Area isn't the best class to use. All I require is something that can add multiple shapes, even when the shapes aren't touching.
I've solved the problem:
First, find all the line segments that constrain the Area. I've written code to do that on a different answer.
Then, it's just a matter of iterating through each line segment, and recording the point on the segment that's closest to the entity's desired point. Store these in the data structure of your choice (e.g., an ArrayList).
See: Shortest distance between a point and a line segment
Lastly, determine which of the points is closest to the desired point. VoilĂ !
Here's a demonstration:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Random;
import javax.swing.JFrame;
public class AreaTest extends JFrame{
private static final long serialVersionUID = -2221432546854106311L;
Area area = new Area();
ArrayList<Line2D.Double> areaSegments = new ArrayList<Line2D.Double>();
Point2D.Double insidePoint = new Point2D.Double(225, 225);
Point2D.Double closestPoint = new Point2D.Double(-1, -1);
Point2D.Double bestPoint = new Point2D.Double(-1, -1);
ArrayList<Point2D.Double> closestPointList = new ArrayList<Point2D.Double>();
AreaTest() {
Path2D.Double triangle = new Path2D.Double();
Random random = new Random();
// Draw three random triangles
for (int i = 0; i < 3; i++) {
triangle.moveTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.lineTo(random.nextInt(400) + 50, random.nextInt(400) + 50);
triangle.closePath();
area.add(new Area(triangle));
triangle.reset();
}
// Place a point inside the area
if (!area.contains(insidePoint)); {
while (!area.contains(insidePoint)) {
insidePoint.setLocation(random.nextInt(400) + 50, random.nextInt(400) + 50);
}
}
// Note: we're storing double[] and not Point2D.Double
ArrayList<double[]> areaPoints = new ArrayList<double[]>();
double[] coords = new double[6];
for (PathIterator pi = area.getPathIterator(null); !pi.isDone(); pi.next()) {
// Because the Area is composed of straight lines
int type = pi.currentSegment(coords);
// We record a double array of {segment type, x coord, y coord}
double[] pathIteratorCoords = {type, coords[0], coords[1]};
areaPoints.add(pathIteratorCoords);
}
double[] start = new double[3]; // To record where each polygon starts
for (int i = 0; i < areaPoints.size(); i++) {
// If we're not on the last point, return a line from this point to the next
double[] currentElement = areaPoints.get(i);
// We need a default value in case we've reached the end of the ArrayList
double[] nextElement = {-1, -1, -1};
if (i < areaPoints.size() - 1) {
nextElement = areaPoints.get(i + 1);
}
// Make the lines
if (currentElement[0] == PathIterator.SEG_MOVETO) {
start = currentElement; // Record where the polygon started to close it later
}
if (nextElement[0] == PathIterator.SEG_LINETO) {
areaSegments.add(
new Line2D.Double(
currentElement[1], currentElement[2],
nextElement[1], nextElement[2]
)
);
} else if (nextElement[0] == PathIterator.SEG_CLOSE) {
areaSegments.add(
new Line2D.Double(
currentElement[1], currentElement[2],
start[1], start[2]
)
);
}
}
// Calculate the nearest point on the edge
for (Line2D.Double line : areaSegments) {
// From: https://stackoverflow.com/questions/6176227
double u =
((insidePoint.getX() - line.x1) * (line.x2 - line.x1) + (insidePoint.getY() - line.y1) * (line.y2 - line.y1))
/ ((line.x2 - line.x1) * (line.x2 - line.x1) + (line.y2 - line.y1) * (line.y2 - line.y1));
double xu = line.x1 + u * (line.x2 - line.x1);
double yu = line.y1 + u * (line.y2 - line.y1);
if (u < 0) {
closestPoint.setLocation(line.getP1());
} else if (u > 1) {
closestPoint.setLocation(line.getP2());
} else {
closestPoint.setLocation(xu, yu);
}
closestPointList.add((Point2D.Double) closestPoint.clone());
if (closestPoint.distance(insidePoint) < bestPoint.distance(insidePoint)) {
bestPoint.setLocation(closestPoint);
}
}
setSize(new Dimension(500, 500));
setLocationRelativeTo(null); // To center the JFrame on screen
setDefaultCloseOperation(EXIT_ON_CLOSE);
setResizable(false);
setVisible(true);
}
public void paint(Graphics g) {
// Fill the area
Graphics2D g2d = (Graphics2D) g;
g.setColor(Color.lightGray);
g2d.fill(area);
// Draw the border line by line
g.setColor(Color.black);
for (Line2D.Double line : areaSegments) {
g2d.draw(line);
}
// Draw the inside point
g.setColor(Color.red);
g2d.fill(
new Ellipse2D.Double(
insidePoint.getX() - 3,
insidePoint.getY() - 3,
6,
6
)
);
// Draw the other close points
for (Point2D.Double point : closestPointList) {
g.setColor(Color.black);
g2d.fill(
new Ellipse2D.Double(
point.getX() - 3,
point.getY() - 3,
6,
6
)
);
}
// Draw the outside point
g.setColor(Color.green);
g2d.fill(
new Ellipse2D.Double(
bestPoint.getX() - 3,
bestPoint.getY() - 3,
6,
6
)
);
}
public static void main(String[] args) {
new AreaTest();
}
}
Here's the result:
And again:
View my answer on this post
You can get the closest point outside of a polygon with a simple and lightweight approach:
Simply find the closest line segment, and find the perpendicular angle to that segment that intercepts the input point.
Example Code:
Vector2 is 2 doubles, x and y (Like Unity)
public class PolyCollisions {
// Call this function...
public static Vector2 doCollisions (Vector2[] polygon, Vector2 point) {
if(!pointIsInPoly(polygon, point)) {
// The point is not colliding with the polygon, so it does not need to change location
return point;
}
// Get the closest point off the polygon
return closestPointOutsidePolygon(polygon, point);
}
// Check if the given point is within the given polygon (Vertexes)
//
// If so, call on collision if required, and move the point to the
// closest point outside of the polygon
public static boolean pointIsInPoly(Vector2[] verts, Vector2 p) {
int nvert = verts.length;
double[] vertx = new double[nvert];
double[] verty = new double[nvert];
for(int i = 0; i < nvert; i++) {
Vector2 vert = verts[i];
vertx[i] = vert.x;
verty[i] = vert.y;
}
double testx = p.x;
double testy = p.y;
int i, j;
boolean c = false;
for (i = 0, j = nvert-1; i < nvert; j = i++) {
if ( ((verty[i]>testy) != (verty[j]>testy)) &&
(testx < (vertx[j]-vertx[i]) * (testy-verty[i]) / (verty[j]-verty[i]) + vertx[i]) )
c = !c;
}
return c;
}
// Gets the closed point that isn't inside the polygon...
public static Vector2 closestPointOutsidePolygon (Vector2[] poly, Vector2 point) {
return getClosestPointInSegment(closestSegment(poly, point), point);
}
public static Vector2 getClosestPointInSegment (Vector2[] segment, Vector2 point) {
return newPointFromCollision(segment[0], segment[1], point);
}
public static Vector2 newPointFromCollision (Vector2 aLine, Vector2 bLine, Vector2 p) {
return nearestPointOnLine(aLine.x, aLine.y, bLine.x, bLine.y, p.x, p.y);
}
public static Vector2 nearestPointOnLine(double ax, double ay, double bx, double by, double px, double py) {
// https://stackoverflow.com/questions/1459368/snap-point-to-a-line-java
double apx = px - ax;
double apy = py - ay;
double abx = bx - ax;
double aby = by - ay;
double ab2 = abx * abx + aby * aby;
double ap_ab = apx * abx + apy * aby;
double t = ap_ab / ab2;
if (t < 0) {
t = 0;
} else if (t > 1) {
t = 1;
}
return new Vector2(ax + abx * t, ay + aby * t);
}
public static Vector2[] closestSegment (Vector2[] points, Vector2 point) {
Vector2[] returns = new Vector2[2];
int index = closestPointIndex(points, point);
returns[0] = points[index];
Vector2[] neighbors = new Vector2[] {
points[(index+1+points.length)%points.length],
points[(index-1+points.length)%points.length]
};
double[] neighborAngles = new double[] {
getAngle(new Vector2[] {point, returns[0], neighbors[0]}),
getAngle(new Vector2[] {point, returns[0], neighbors[1]})
};
if(neighborAngles[0] < neighborAngles[1]) {
returns[1] = neighbors[0];
} else {
returns[1] = neighbors[0];
}
return returns;
}
public static double getAngle (Vector2[] abc) {
// https://stackoverflow.com/questions/1211212/how-to-calculate-an-angle-from-three-points
// atan2(P2.y - P1.y, P2.x - P1.x) - atan2(P3.y - P1.y, P3.x - P1.x)
return Math.atan2(abc[2].y - abc[0].y, abc[2].x - abc[0].x) - Math.atan2(abc[1].y - abc[0].y, abc[1].x - abc[0].x);
}
//public static Vector2 lerp (Vector2 a, Vector2 b, double c) {
//
// return new Vector2(c*(a.x-b.x)+b.x, c*(a.y-b.y)+b.y);
//
//}
/*public static Vector2 closestPoint (Vector2[] points, Vector2 point) {
int leastDistanceIndex = 0;
double leastDistance = Double.MAX_VALUE;
for(int i = 0; i < points.length; i++) {
double dist = distance(points[i], point);
if(dist < leastDistance) {
leastDistanceIndex = i;
leastDistance = dist;
}
}
return points[leastDistanceIndex];
}*/
public static int closestPointIndex (Vector2[] points, Vector2 point) {
int leastDistanceIndex = 0;
double leastDistance = Double.MAX_VALUE;
for(int i = 0; i < points.length; i++) {
double dist = distance(points[i], point);
if(dist < leastDistance) {
leastDistanceIndex = i;
leastDistance = dist;
}
}
return leastDistanceIndex;
}
public static double distance (Vector2 a, Vector2 b) {
return Math.sqrt(Math.pow(Math.abs(a.x-b.x), 2)+Math.pow(Math.abs(a.y-b.y), 2));
}
}
Useful Links / Answers
Snap Point to Line
How to calculate an angle from 3 points
The most easy (and most inefficient) approach would be a brute force.
You have a preferred point inside an area. to find the closest point to it: hold two variables, one for minimal distance and one for current closest point. now simply step over every other point in your two dimensional space: if that point is not inside the forbidden area (or any forbidden area if there are many), then calculate the distance between it and the preferred point. If that distance is less than the current minimal distance, then make it become the current minimal distance and make the point become the current closest point.
when you finish, you will have the closest point outside the area and if none was found, you stay on your original point.
I am not specialist in geometry algorithms, but if the two dimensional space is very big and the calculation is not finishing fast enough, maybe you can try to improve it with the following: the Area class has a contains method that "tests if the interior of the Shape entirely contains the specified rectangular area". therefore, start creating rectangles(or squares) around the preferred point. you start with the minimal rectangle surrounding the point and on every loop you increase it by one point in each direction. for every rectangle that you create, check if it is contained in the area. you stop calculating rectangles when you hit the first rectangle that is not entirely contained in the area. then, you use the above algorithm (the brute force) but only on points contained in this rectangle and that are not inside the area.
The formula for distance between two points is (javascript):
var xDiff = ( point1x - point2x ),
yDiff = ( point1y - point2y ),
distance = Math.sqrt( ( xDiff * xDiff ) + ( yDiff * yDiff ) );
Loop around your "proposed new point", starting at one x-1, y-1 to x+1, y+1. At each point check to see that it's not a forbidden point, not the point you just came from, and not off the boundaries of the map. If it meets all those criteria, use the above formula to measure the distance and add it to an array. At the end of your "1-point out" loop, check if there are any distances in that array. If so, take the smallest one and you're done. If there aren't any, move onto x-2, y-2 to x+2, y+2 (2 points out).
This will be extremely fast for the small area you are referring to.
Demo: http://jsfiddle.net/ThinkingStiff/V7Bqm/
var X = 0,
Y = 1,
currentPoint = [5,5],
proposedPoint = [5,6],
forbiddenPoints = [[5,6],[6,6],[4,7],[5,7],[6,7],[4,8],[5,8]],
map = { left:1, top:1, right:10, bottom:10 };
function closestSafePoint( point ) {
var x = point[X], y = point[Y], safePoints = [];
for( var left = x - 1, top = y - 1, right = x + 1, bottom = y + 1;
left <= map.left || top <= map.top || right <= map.right || bottom <= map.bottom;
left--, top--, right++, bottom++) {
checkHorizontalPoints( safePoints, point, left, right, top );
checkHorizontalPoints( safePoints, point, left, right, bottom );
checkVerticalPoints( safePoints, point, top + 1, bottom - 1, left );
checkVerticalPoints( safePoints, point, top + 1, bottom - 1, right );
safePoints.sort( function( a, b ){ return a[1] - b[1] } );
return safePoints.length ? safePoints[0] : point;
};
};
function checkHorizontalPoints( points, fromPoint, startX, endX, y ) {
for( var x = startX; x <= endX ; x++ ) {
var toPoint = [x, y];
if( !isForbidden( toPoint ) && !isCurrent( toPoint) && onMap( toPoint ) ) {
points.push( [toPoint, distance( fromPoint, toPoint )] );
};
};
};
function checkVerticalPoints( points, fromPoint, startY, endY, x ) {
for( var y = startY; y <= endY ; y++ ) {
var toPoint = [x, y];
if( !isForbidden( toPoint ) && !isCurrent( toPoint) && onMap( toPoint ) ) {
points.push( [toPoint, distance( fromPoint, toPoint )] );
};
};
};
function isForbidden( point ) {
for( var index = 0; index < forbiddenPoints.length; index++ ) {
if( forbiddenPoints[index].toString() == point.toString() ) return true;
};
};
function isCurrent( point ) {
return currentPoint.toString() == point.toString() ? true : false;
};
function onMap( point ) {
var x = point[X], y = point[Y];
return x >= map.left && y >= map.top && x <= map.right && y <= map.bottom;
};
function distance( pointA, pointB ) {
var xDiff = ( pointA[X] - pointB[X] ),
yDiff = ( pointA[Y] - pointB[Y] );
return Math.sqrt( ( xDiff * xDiff ) + ( yDiff * yDiff ) );
};
console.log(
'current: ' + currentPoint + ', '
+ 'proposed: ' + proposedPoint + ', '
+ 'closest: ' + closestSafePoint( proposedPoint )[0]
);
One optimization you could make to this, if you're fairly sure most of your safe spots will be one or two points away is to break out as soon as you get to a point thats distance is the same as the level you're on. So if you're on loop one, and you get a point that is distance = 1, stop, since you'll never get closer than that.
UPDATE: I noticed you added "same trajectory" to your question. But in one of the comments, you also say it can't jump over the forbidden area. Those statements seem to conflict.
Same trajectory is a little more tricky and requires some trig. Check out my demo of circular divs at http://jsfiddle.net/ThinkingStiff/uLu7v/. There is a "point on ray" function halfway down at:
$this.siblings( ".circle" ).each( function()
This calculates the distance to move the surrounding circles on a ray away from the selected circle. This could be used to calculate a point on your trajectory. But, I think my original function is actually what you're looking for and you didn't mean same trajectory.