// Racetrack 1.0 by Eric Withrow, August 2005 // Polygon editing adapted from Polygon subdivision, Jarek Rossignac, June 2005 // Click and drag vertices to move them // Move them out of the window to delete them // Click and drag mid-edge (smaller) points to insert new vertices // Editable Variables int pixels_per_second = 100; // Number of pixels cars will travel every second // NOTE: this is an approximation for parametric speed int frames_per_second = 30; // Number of times the screen will be refreshed per second int curve_iterations = 3; // Number of time to subdivide the polygon boolean editmode = true; // Start in editor mode if true int window_width = 800; // Width of window int window_height = 600; // Height of window // Colors & Fonts color black = color(0, 0, 0); color red = color(200, 0, 0); color green = color(0, 200, 0); color blue = color(0, 0, 200); color magenta = color(250, 100, 250); color orange = color(250, 200, 50); color yellow = color(250, 250, 50); color white = color(250, 250, 250); PFont myFont; // Car variables float car1_posx; // x position of car 1 in the window float car1_posy; // y position of car 1 in the window int car1_vert; // The "id" of the vertex that the car is on int car1_stepnum; // The "id" of the step the car is on within the side float car2_posx; // x position of car 2 in the window float car2_posy; // y position of car 2 in the window int car2_vert; // The "id" of the vertex that the car is on // Polygon and Curve variables int vert_num; // number of original vertices int curve_vert_num = 0; // number of vertices in subdivided curve int bi = 0; // index of selected vertex float s = 1.0; // parameter defining subdivision float curvelen = 0; // total length of all segments of B-spline // Arrays that will store the Bspline Curve int cap = 4096; // size of arrays float Px = 0.0; float Py = 0.0; float[] x = new float [cap]; float[] y = new float [cap]; float[] qx = new float [cap]; float[] qy = new float [cap]; float[] qqx = new float [cap]; float[] qqy = new float [cap]; float[] mx = new float [cap]; float[] my = new float [cap]; float[] dx = new float [cap]; float[] dy = new float [cap]; ///////////////////// BEGIN PROGRAM ///////////////////// void setup() { size(window_width, window_height); // window size and 3D x[1]=80; x[2]=330; x[3]=615; x[4]=750; x[5]=75; // initial x control polygon coords y[1]=100; y[2]=300; y[3]=35; y[4]=370; y[5]=515; // initial y control polygon coords vert_num = 5; // number of original vertices (must match above) strokeJoin(ROUND); strokeCap(ROUND); // makes vertices rounded framerate(frames_per_second); // initialize window refresh rate myFont = loadFont("Eureka90.vlw"); // loads the font } void keyPressed() { if (key == 'r' || key == 'R') { editmode = !editmode; } } void draw() { background(white); x[bi] = mouseX; y[bi] = mouseY; // snap selected vertex to mouse position x[0] = x[vert_num]; y[0] = y[vert_num]; // replicate tail up front avoiding end-conditions if (editmode) { computeRoad(); // performs subdivisions of the polygon to calculate a B-spline curve drawRoad(); // draws the road so the editor can see what it looks like before racing mode drawPolygon(); // draws the polygon which is used for editing resetCars(); // puts the cars back at the first vertex } else { // go into racing mode!!! drawRoad(); // draw the road moveCar(); // move the cars to the next step drawCar(); // draw the cars } drawLegend(); // draws the legend in the corner of the screen } // this method draws the initial editing polygon used to edit the racetrack void drawPolygon() { // first draw the lines stroke(green); strokeWeight(1); smooth(); beginShape(LINE_STRIP); for (int i=0; i<=vert_num; i++) {vertex(x[i], y[i]);} endShape(); // then draw the vertices / control points strokeWeight(8); beginShape(POINTS); for (int i=1; i<=vert_num; i++) {vertex(x[i], y[i]);} endShape(); // then draw the midpoints of the vertices strokeWeight(4); beginShape(POINTS); for (int i=1; i<=vert_num; i++) {vertex((x[i]+x[i-1])/2, (y[i]+y[i-1])/2);} endShape(); } // This method computes the Cubic B-spline curve of the current edit polygon and stores the vertices in qx[], qy[] void computeRoad() { curve_vert_num = vert_num; for (int i = 0; i <= vert_num; i++) { qx[i] = x[i]; qy[i] = y[i]; }; // copy current polygon into qx[], qy[] for (int i = 0; i <= curve_iterations; i++) { subdivide(s); } // Subdivied curve_iterations times // determine the total curve length (in pixels) curvelen = 0; for (int i = 0; i < curve_vert_num; i++) { // loop through and add up the length of all the sides curvelen += linelen(qx[i], qy[i], qx[nextVertex(i)], qy[nextVertex(i)]); } } // this will draw the legend in the corner void drawLegend() { textFont(myFont, 15); fill(black); if(editmode) { text(" Drag the vertices to edit the track. Drag vertices off the screen to delete them.", 30, 30); text(" Drag midpoints (small dots) to create a new vertex.", 30, 50); text(" Press R to enable/disable racing mode.", 30, 70); } else { stroke(red); strokeWeight(12.0); smooth(); beginShape(POINTS); vertex(20, 25); endShape(); stroke(blue); strokeWeight(12.0); smooth(); beginShape(POINTS); vertex(20, 45); endShape(); text(" - Parametric Speed Car", 30, 30); text(" - Constant Speed Car", 30, 50); } } // This method draws the road, which is the Cubic B-spline curve we computed earlier void drawRoad() { int road_width = 40; // Width of race track int lane_width = 1; // Width of lane marker int lane_length = 10; // Length of lane marker (NOT USED YET) // Draw the track stroke(black); strokeWeight(road_width); smooth(); beginShape(LINE_STRIP); for (int i=0; i<=curve_vert_num; i++) {vertex(qx[i], qy[i]);} endShape(); // Draw the center-line stroke(white); strokeWeight(lane_width); noSmooth(); //lane size beginShape(LINE_STRIP); for (int i=0; i<=curve_vert_num; i++) {vertex(qx[i], qy[i]);} endShape(); } // This method draws the "cars" at the correct position at the current time. void drawCar() { // Draw the first car (parametric speed) stroke(red); strokeWeight(12.0); smooth(); beginShape(POINTS); vertex(car1_posx, car1_posy); endShape(); // draw // Draw the second car (constant speed) stroke(blue); strokeWeight(12.0); smooth(); beginShape(POINTS); vertex(car2_posx, car2_posy); endShape(); // draw } // This method calculcated the next position for car1 (paramentric speed) and car2 (constant speed) void moveCar() { // CAR 1 == PARAMETRIC SPEED float seconds_per_side = (curvelen / pixels_per_second) / curve_vert_num; int steps_per_side = (int) Math.ceil(seconds_per_side * frames_per_second); // calculat delta x & y float deltax = (qx[nextVertex(car1_vert)] - qx[car1_vert]) / steps_per_side; float deltay = (qy[nextVertex(car1_vert)] - qy[car1_vert]) / steps_per_side; // update car position car1_posx = qx[car1_vert] + (car1_stepnum * deltax); car1_posy = qy[car1_vert] + (car1_stepnum * deltay); // increase the step number by 1, or go to next vertex if finished with this one car1_stepnum++; if(car1_stepnum == steps_per_side) { car1_vert++; if(car1_vert == curve_vert_num) { car1_vert = 0; } car1_stepnum = 0; } // CAR 2 == CONSTANT SPEED float pixels_per_frame = pixels_per_second / frames_per_second; float constant_frames = curvelen / pixels_per_frame; // calculate the distance between important points and the distance covered in each step float PB_dist = linelen(car2_posx, car2_posy, qx[nextVertex(car2_vert)], qy[nextVertex(car2_vert)]); float AB_dist = linelen(qx[car2_vert], qy[car2_vert], qx[nextVertex(car2_vert)], qy[nextVertex(car2_vert)]); float step_distance = curvelen / constant_frames; if(PB_dist > step_distance) { // if the step can be completed without changing sides then do it car2_posx = car2_posx + (step_distance / AB_dist) * (qx[nextVertex(car2_vert)] - qx[car2_vert]); car2_posy = car2_posy + (step_distance / AB_dist) * (qy[nextVertex(car2_vert)] - qy[car2_vert]); } else { // otherwise we have to change sides step_distance = step_distance - PB_dist; // the remainder after what was left over from the original side float temp_distance = 0; do { //go into loop here adding up length sides until we know we can fit car2_vert++; if(car2_vert == curve_vert_num) { car2_vert = 0; } AB_dist = linelen(qx[car2_vert], qy[car2_vert], qx[nextVertex(car2_vert)], qy[nextVertex(car2_vert)]); temp_distance= step_distance; step_distance = step_distance - AB_dist; } while(step_distance > 0); // if there is enough room break out of loop and use the last step_dist (stored in temp) // move the car to next determined position car2_posx = qx[car2_vert] + (temp_distance / AB_dist) * (qx[nextVertex(car2_vert)] - qx[car2_vert]); car2_posy = qy[car2_vert] + (temp_distance / AB_dist) * (qy[nextVertex(car2_vert)] - qy[car2_vert]); } } // This moves both cars back to the first vertex. void resetCars() { car1_posx = qx[0]; // reset car 1 car1_posy = qy[0]; car1_vert = 0; car1_stepnum = 0; car2_posx = qx[0]; // reset car 2 car2_posy = qy[0]; car2_vert = 0; } // returns the next highest vertex, or zero if current_vertex is the higest int nextVertex(int current_vertex) { if(current_vertex == curve_vert_num - 1) { return 0; } else { return current_vertex + 1; } } void mousePressed() { // to do when mouse is pressed if(editmode) { float bd=window_width*window_height; // init square of smallest distance to selected point Px=mouseX; Py=mouseY; // save current mouse location for (int i=1; i<=vert_num; i++) {if (d2(i)100) { // if closest vertex is too far bd=window_width*window_height; // reinitilize distance squared for (int i=0; ibi; i--) {x[i+1]=x[i]; y[i+1]=y[i]; }; // shift down the rest bi++; x[bi]=Px; y[bi]=Py; vert_num++; // insert new vertex at mouse position } else { bi =0; } // nothing selected } x[0]=x[vert_num]; y[0]=y[vert_num]; // replicate tail up-front } } void mouseReleased() { // do this when mouse released if(editmode) { if ((bi!=0) && ((x[bi]<0)||(x[bi]>window_width)||(y[bi]<0)||(y[bi]>window_height))) { // if outside of port for (int i=bi; i=1; i--) {qx[2*i]=qx[i]; qy[2*i]=qy[i]; qx[2*i-1]=mx[i]; qy[2*i-1]=my[i]; }; curve_vert_num *=2; qx[0]=qx[curve_vert_num]; qy[0]=qy[curve_vert_num]; }