Related
https://imgur.com/a/hDRx3SI
The lines (muscles) start out in the center as I want. (This video starts out a second or two into the sketch.) Why do the muscles not line up with the center of part3 (Blue circle)? Is it simply a trigonometric issue where I'm trying to force them into an impossible position given the constraints?
Part part1;
Part part2;
Part part3;
Muscle muscle1;
Muscle muscle2;
Muscle muscle3;
void setup() {
size (800, 800);
frameRate(1);
part1 = new Part(width/2, height/2, 50, color(255, 0, 0));
part2 = new Part(width/2 + 100, height/2, 50, color(0, 255, 0));
part3 = new Part(width/2 + 50, height/2 - 75, 50, color(0, 0, 255));
muscle1 = new Muscle(part1.x, part1.y, part2.x, part2.y, dist(part1.x, part1.y, part2.x,part2.y), color(0, 255, 0));
muscle2 = new Muscle(part1.x, part1.y, part3.x, part3.y, dist(part1.x, part1.y, part3.x, part3.y), color(0, 255, 0));
muscle3 = new Muscle(part2.x, part2.y, part3.x, part3.y, dist(part2.x, part2.y, part3.x, part3.y), color(0, 255, 0));
}
void draw() {
background(255);
part1.drawpart();
part2.drawpart();
part3.drawpart();
muscle1.drawmuscle(part1, part2);
muscle2.drawmuscle(part1, part3);
muscle3.drawmuscle(part2, part3);
part2.movepart();
}
class Muscle{
float leftx;
float lefty;
float rightx;
float righty;
float size = 100;
int musclecolor;
Muscle(float leftpositionx, float leftpositiony, float rightpositionx, float rightpositiony, float musclesize, int musclemusclecolor) {
leftx = leftpositionx;
lefty = leftpositiony;
rightx = rightpositionx;
righty = rightpositiony;
size = musclesize;
musclecolor = musclemusclecolor;
}
void drawmuscle(Part obj1, Part obj2) {
strokeWeight(5);
float dx = obj2.x - obj1.x;
float dy = obj2.y - obj1.y;
float angle = atan2(dy, dx);
obj2.x = obj1.x + cos(angle) * size;
obj2.y = obj1.y + sin(angle) * size;
line(obj1.x, obj1.y, obj2.x, obj2.y);
}
}
class Part{
float x;
float y;
float size;
int partcolor;
Part(float positionx, float positiony, float partsize, int partpartcolor) {
x = positionx;
y = positiony;
size = partsize;
partcolor = partpartcolor;
}
void drawpart() {
fill(partcolor);
strokeWeight(1);
ellipseMode(CENTER);
ellipse(x, y, size, size);
}
void movepart() {
y += 10;
}
}
There are two different problems here which interacts with one another, which is why this is hard to solve. You'll be happy to notice, though, that your math are irreproachable.
First issue is in the drawmuscle() method. You modify coordinates while drawing them, which isn't necessarily an issue. The problem is that you're doing this in cascade for 3 different parts, which depend on each other to be calculated. The variables all end up all right - so mathematically it works - but as you draw some parts before others have been calculated, they end up with unexpected coordinates that are neither the old ones nor the new ones.
To fix this, I modified the drawmuscle() method so it only draws the muscles, and I added a new method to update the muscles/parts coordinates. It's still all your code, just displaced into different containers. Then I modified the draw() method to reflect this change: the coordinates have to be calculated first, then the parts can be drawn.
Now, the blue circle was still misplaced. That's also because of a matter of order in the draw() method: as the circles were drawn before their coordinates were updated, they were subject to be misdrawn. Again, at the end of every frame, your calculations were correct, but in the way the operations to get there were applied and drawn they would appear off.
As a rule of thumb, I would say that you need to remember from this project the following rule: calculate first, draw last.
So here are the changes I made to your methods:
void draw() {
background(255);
// calculating new positions
muscle1.moveMuscle(part1, part2);
muscle2.moveMuscle(part1, part3);
muscle3.moveMuscle(part2, part3);
// drawing
part1.drawpart();
part2.drawpart();
part3.drawpart();
muscle1.drawmuscle(part1, part2);
muscle2.drawmuscle(part1, part3);
muscle3.drawmuscle(part2, part3);
muscle1.growmuscle(part1, part2);
}
void drawmuscle(Part obj1, Part obj2) {
// no calculations here
strokeWeight(5);
line(obj1.x, obj1.y, obj2.x, obj2.y);
}
void moveMuscle(Part obj1, Part obj2) {
// every calculations here
float dx = obj2.x - obj1.x;
float dy = obj2.y - obj1.y;
float angle = atan2(dy, dx);
obj2.x = obj1.x + cos(angle) * size;
obj2.y = obj1.y + sin(angle) * size;
rightx = obj2.x;
righty = obj2.y;
}
I hope this will help. Have fun!
I want to detect separate shapes as random-generated lines split the canvas. I saved line intersection points in separate arrays for x and y positions (same order), but don't know how to connect
points that complete multiple pieces of shapes .
Is there any way to detect nearby points to close a minimal possible shape whether it be a triangle, rectangle, or polygon (e.g., by using beginShape and endShape)?
If 1) is too complicated, is there any method to select 3 or more random points from an array?
Here's a sample image that has 4 lines splitting the canvas with their intersection points marked in red. I also saved the top and bottom points (marked in black) of each random-generated line, plus the four corners of the canvas in the same arrays for x and y positions separately (px, py).
Multiple lines split the canvas.
How to get shapes split by lines in Processing?
I was able to get all the intersection points, but having a problem with connecting them into separate shapes. Here's the Processing code that I am working on:
//Run in Processing.
//Press r to refresh.
//Top and bottom points are added to px and py when refreshed (filled in black).
//Intersection points are added to px and py when detected (filled in red).
int l = 4; //set number of lines
float[] r1 = new float[l];
float[] r2 = new float[l];
float[] px = {}; //array to save x positions of all possible points
float[] py = {}; //array to save y positions of all possible points
boolean added = false;
void setup(){
size(800, 800);
background(255);
refresh();
}
void draw(){
background(255);
stroke(0, 150, 255, 150);
strokeWeight(1);
for(int i=0; i < r1.length; i++){
for(int j=0; j < r1.length; j++){
if(i>j){
boolean hit = lineLine(r1[i], 0, r2[i], height, r1[j], 0, r2[j], height);
if (hit) stroke(255, 150, 0, 150);
else stroke(0, 150, 255, 150);
}
line(r1[i], 0, r2[i], height);
}
}
added = true;
print(px.length);
}
//source: http://jeffreythompson.org/collision-detection/line-line.php
boolean lineLine(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {
// calculate the distance to intersection point
float uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
float uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
// if uA and uB are between 0-1, lines are colliding
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
// optionally, draw a circle where the lines meet
float intersectionX = x1 + (uA * (x2-x1));
float intersectionY = y1 + (uA * (y2-y1));
fill(255,0,0);
noStroke();
ellipse(intersectionX,intersectionY, 20,20);
if(added==false){
px = append(px, intersectionX);
py = append(py, intersectionY);
}
return true;
}
return false;
}
void refresh(){
added = false;
px = new float[0];
py = new float[0];
r1 = new float[l];
r2 = new float[l];
px = append(px, 0);
py = append(py, 0);
px = append(px, 0);
py = append(py, height);
px = append(px, width);
py = append(py, 0);
px = append(px, width);
py = append(py, height);
for(int i=0; i< r1.length; i++){
r1[i] = random(800);
}
for(int i=0; i< r2.length; i++){
r2[i] = random(800);
}
for(int i=0; i < r1.length; i++){
stroke(0);
line(r1[i], 0, r2[i], height);
px = append(px, r1[i]);
py = append(py, 0);
px = append(px, r2[i]);
py = append(py, height);
}
}
void keyReleased() {
if (key == 'r') refresh();
}
If you want to draw a shape made of the intersection points only you're on the right track with beginShape()/endShape().
Currently it looks like you're placing all the points in px, py: the intersection points and also the points defining the lines used to compute the intersections in the first place.
You might want to separate the two, for example a couply of arrays for points defining lines only and another pair of x,y arrays for the intersection points only. You'd only need to iterated through the intersected coordinates to place vertex(x, y) calls inbetween beginShape()/endShape(). Here's a modified version of you code to illustrate the idea:
//Run in Processing.
//Press r to refresh.
//Top and bottom points are added to px and py when refreshed (filled in black).
//Intersection points are added to px and py when detected (filled in red).
int l = 4; //set number of lines
float[] r1 = new float[l];
float[] r2 = new float[l];
float[] px = {}; //array to save x positions of all possible points
float[] py = {}; //array to save y positions of all possible points
float[] ipx = {}; // array to save x for intersections only
float[] ipy = {}; // array to save y for intersections only
boolean added = false;
void setup(){
size(800, 800);
background(255);
refresh();
}
void draw(){
background(255);
stroke(0, 150, 255, 150);
strokeWeight(1);
for(int i=0; i < r1.length; i++){
for(int j=0; j < r1.length; j++){
if(i>j){
boolean hit = lineLine(r1[i], 0, r2[i], height, r1[j], 0, r2[j], height);
if (hit) stroke(255, 150, 0, 150);
else stroke(0, 150, 255, 150);
}
line(r1[i], 0, r2[i], height);
}
}
added = true;
// draw intersections
beginShape();
for(int i = 0 ; i < ipx.length; i++){
vertex(ipx[i], ipy[i]);
}
endShape();
//print(px.length);
//println(px.length, py.length);
}
//source: http://jeffreythompson.org/collision-detection/line-line.php
boolean lineLine(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) {
// calculate the distance to intersection point
float uA = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
float uB = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1));
// if uA and uB are between 0-1, lines are colliding
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
// optionally, draw a circle where the lines meet
float intersectionX = x1 + (uA * (x2-x1));
float intersectionY = y1 + (uA * (y2-y1));
fill(255,0,0);
noStroke();
ellipse(intersectionX,intersectionY, 20,20);
if(added==false){
px = append(px, intersectionX);
py = append(py, intersectionY);
// store intersections
ipx = append(ipx, intersectionX);
ipy = append(ipy, intersectionY);
}
return true;
}
return false;
}
void refresh(){
added = false;
px = new float[0];
py = new float[0];
ipx = new float[0];
ipy = new float[0];
r1 = new float[l];
r2 = new float[l];
px = append(px, 0);
py = append(py, 0);
px = append(px, 0);
py = append(py, height);
px = append(px, width);
py = append(py, 0);
px = append(px, width);
py = append(py, height);
for(int i=0; i< r1.length; i++){
r1[i] = random(800);
}
for(int i=0; i< r2.length; i++){
r2[i] = random(800);
}
for(int i=0; i < r1.length; i++){
stroke(0);
line(r1[i], 0, r2[i], height);
px = append(px, r1[i]);
py = append(py, 0);
px = append(px, r2[i]);
py = append(py, height);
}
}
void keyReleased() {
if (key == 'r') refresh();
}
Bare in mind this simlpy draws the points in the order in which the intersections were computed. On a good day you'll get something like this:
It doesn't exclude the possiblity of polygons with the wrong vertex order (winding):
and you might be get convave polygons too.
If you only need the outer 'shell' of these intersection points you might need something like a convex hull algorithm
One option to at least visually split shapes might to use beginShape(TRIANGLES); with endShape(CLOSE); which should iterate through points and draw a triangle for every coordinate triplate, however given random points and number of interesections you might end up with a missing triangle or two (e.g. 6 points = 2 triangles, 7 points = 2 triangles and 1 point with no missing pairs)
The only other note I have is around syntax: arrays are ok to get started with but you might want to look into ArrayList and PVector. This would allow you to use a single dynamic array of PVector instances which have x, y properties.
Update
Overall the code can be simplified. If we take out the line intersection related code we can get away with something like:
int l = 4; //set number of random lines
float[] r1 = new float[l]; // random x top
float[] r2 = new float[l]; // random x bottom
void setup() {
size(800, 800);
strokeWeight(3);
stroke(0, 150, 255, 150);
refresh();
}
void draw() {
background(255);
// random lines
for (int i=0; i < r1.length; i++) {
line(r1[i], 0, r2[i], height);
}
// borders
line(0, 0, width, 0);
line(width, 0, width - 1, height - 1);
line(0, height - 1, width - 1, height - 1);
line(0, 0, 0, height - 1);
}
void refresh() {
r1 = new float[l];
r2 = new float[l];
for (int i=0; i< r1.length; i++) {
r1[i] = random(800);
r2[i] = random(800);
}
}
void keyReleased() {
if (key == 'r') refresh();
}
If we were to use a basic Line class and make use of PVector and ArrayList we could rewrite the above as:
int numRandomLines = 4;
ArrayList<PVector> points = new ArrayList<PVector>();
void setup() {
size(800, 800);
stroke(0, 150, 255, 150);
strokeWeight(3);
refresh();
}
void refresh(){
// remove previous points
points.clear();
//add borders
points.add(new PVector(0, 0)); points.add(new PVector(width, 0));
points.add(new PVector(width, 0));points.add(new PVector(width - 1, height - 1));
points.add(new PVector(0, height - 1));points.add(new PVector(width - 1, height - 1));
points.add(new PVector(0, 0)); points.add(new PVector(0, height - 1));
// add random lines
for (int i=0; i< numRandomLines; i++) {
points.add(new PVector(random(800), 0)); points.add(new PVector(random(800), height));
}
}
void draw(){
background(255);
beginShape(LINES);
for(PVector point : points) vertex(point.x, point.y);
endShape();
}
void keyReleased() {
if (key == 'r') refresh();
}
and grouping a pair of points (PVector) into a Line class:
int numRandomLines = 4;
ArrayList<Line> lines = new ArrayList<Line>();
void setup() {
size(800, 800);
stroke(0, 150, 255, 150);
strokeWeight(3);
refresh();
}
void refresh(){
// remove previous points
lines.clear();
//add borders
lines.add(new Line(new PVector(0, 0), new PVector(width, 0)));
lines.add(new Line(new PVector(width, 0), new PVector(width - 1, height - 1)));
lines.add(new Line(new PVector(0, height - 1), new PVector(width - 1, height - 1)));
lines.add(new Line(new PVector(0, 0), new PVector(0, height - 1)));
// add random lines
for (int i=0; i< numRandomLines; i++) {
lines.add(new Line(new PVector(random(800), 0), new PVector(random(800), height)));
}
}
void draw(){
background(255);
for(Line line : lines) line.draw();
}
void keyReleased() {
if (key == 'r') refresh();
}
class Line{
PVector start;
PVector end;
Line(PVector start, PVector end){
this.start = start;
this.end = end;
}
void draw(){
line(start.x, start.y, end.x, end.y);
}
}
At this stage to get the individual shapes as your diagram describes, we could cheat and use a computer vision library like OpenCV. This is if course overkill (as we'd get() a PImage copy of the drawing, convert that to an OpenCV image) then simply use findContours() to get each shape/contour.
Going back to the original approach, the line to line intersection function could be integrated into the Line class:
int numRandomLines = 4;
ArrayList<Line> lines = new ArrayList<Line>();
ArrayList<PVector> intersections = new ArrayList<PVector>();
void setup() {
size(800, 800);
strokeWeight(3);
refresh();
}
void refresh(){
// remove previous points
lines.clear();
intersections.clear();
//add borders
lines.add(new Line(new PVector(0, 0), new PVector(width, 0)));
lines.add(new Line(new PVector(width, 0), new PVector(width - 1, height - 1)));
lines.add(new Line(new PVector(0, height - 1), new PVector(width - 1, height - 1)));
lines.add(new Line(new PVector(0, 0), new PVector(0, height - 1)));
// add random lines
for (int i=0; i< numRandomLines; i++) {
lines.add(new Line(new PVector(random(800), 0), new PVector(random(800), height)));
}
// compute intersections
int numLines = lines.size();
// when looping only check if lineA intersects lineB but not also if lineB intersects lineA (redundant)
for (int i = 0; i < numLines - 1; i++){
Line lineA = lines.get(i);
for (int j = i + 1; j < numLines; j++){
Line lineB = lines.get(j);
// check intersection
PVector intersection = lineA.intersect(lineB);
// if there is one, append the intersection point to the list
if(intersection != null){
intersections.add(intersection);
}
}
}
}
void draw(){
background(255);
stroke(0, 150, 255, 150);
// draw lines
for(Line line : lines) line.draw();
stroke(255, 0, 0, 150);
// draw intersections
for(PVector intersection : intersections) ellipse(intersection.x, intersection.y, 9, 9);
}
void keyReleased() {
if (key == 'r') refresh();
}
class Line{
PVector start;
PVector end;
Line(PVector start, PVector end){
this.start = start;
this.end = end;
}
void draw(){
line(start.x, start.y, end.x, end.y);
}
//source: http://jeffreythompson.org/collision-detection/line-line.php
//boolean lineLine(float this.start.x, float this.start.y, float this.end.x, float this.end.y,
//float other.start.x, float other.start.y, float other.end.x, float other.end.y) {
PVector intersect(Line other) {
// calculate the distance to intersection point
float uA = ((other.end.x-other.start.x)*(this.start.y-other.start.y) - (other.end.y-other.start.y)*(this.start.x-other.start.x)) / ((other.end.y-other.start.y)*(this.end.x-this.start.x) - (other.end.x-other.start.x)*(this.end.y-this.start.y));
float uB = ((this.end.x-this.start.x)*(this.start.y-other.start.y) - (this.end.y-this.start.y)*(this.start.x-other.start.x)) / ((other.end.y-other.start.y)*(this.end.x-this.start.x) - (other.end.x-other.start.x)*(this.end.y-this.start.y));
// if uA and uB are between 0-1, lines are colliding
if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
// optionally, draw a circle where the lines meet
float intersectionX = this.start.x + (uA * (this.end.x-this.start.x));
float intersectionY = this.start.y + (uA * (this.end.y-this.start.y));
return new PVector(intersectionX, intersectionY);
}
return null;
}
}
The next step would be a more complex algorithm to sort the points based on x, y position (e.g. top to bottom , left to right), iterate though points comparing the first to the rest by distance and angle and trying to work out if consecutive points with minimal distance and angle changes connect.
Having a quick look online I can see such algorithms for example:
Polygon Detection from a Set of Lines
Bentley Ottman algorithm (one of the algorithms mentioned in the paper above) is actually implemented in CGAL. (While there are CGAL Java bindings building these and either interfacing or making a wrapper for Processing isn't trivial).
I can see your code isn't javascript but since you didn't specify a language I assume you just want a method and can convert to your language.
The way I handled this was to assign each line a line number. If I can identify 2 adjacent points on one line then I will know if the third point exist by checking if there is a point at the crossing of the lines they are not sharing.
Example:
There's 3 lines (line 1, 2, 3)
I have an intersection point between lines 3 & 1 now I walk down line 3 for an adjacent point. I find one and its intersection is 3 & 2. Well the only way I could have a triangle is by lines 1 & 2 crossing somewhere. So we can programmatically check that.
Keep in mind that I never actually use and angles for this. I do calculate them in the functions but decided not to use them as I went with the method explained above. I have colored the triangles using an alpha value of 0.1 so you can see where there is overlap.
This is only check triangles
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;
let lines = []; //holds each line
let points = []; //all intersection point are pushed here [{x: num, y: num}, {x: num, y: num},...]
let sortedPts = []; //all points sorted bu first number are pushed here in 2d array.
let lineNum = 15;
class Lines {
constructor(num) {
this.x = Math.round(Math.random() * canvas.width);
this.x2 = Math.round(Math.random() * canvas.width);
this.pt1 = {
x: this.x,
y: 0
};
this.pt2 = {
x: this.x2,
y: canvas.height
};
this.num = num;
this.rads = Math.atan2(this.pt2.y - this.pt1.y, this.pt2.x - this.pt1.x);
this.angle = this.rads * (180 / Math.PI);
}
draw() {
ctx.beginPath();
ctx.moveTo(this.pt1.x, this.pt1.y);
ctx.lineTo(this.pt2.x, this.pt2.y);
ctx.stroke();
}
}
//creates the lines. I also use this function to prepare the 2d array by pushing an empty array for each line into sortedPts.
function createLines() {
for (let i = 0; i < lineNum; i++) {
lines.push(new Lines(i + 1));
sortedPts.push([])
}
}
createLines();
//Visually draws lines on screen
function drawLines() {
for (let i = 0; i < lines.length; i++) {
lines[i].draw();
}
}
drawLines();
//intersecting formula
function lineSegmentsIntersect(line1, line2) {
let a_dx = line1.pt2.x - line1.pt1.x;
let a_dy = line1.pt2.y - line1.pt1.y;
let b_dx = line2.pt2.x - line2.pt1.x;
let b_dy = line2.pt2.y - line2.pt1.y;
let s =
(-a_dy * (line1.pt1.x - line2.pt1.x) + a_dx * (line1.pt1.y - line2.pt1.y)) /
(-b_dx * a_dy + a_dx * b_dy);
let t =
(+b_dx * (line1.pt1.y - line2.pt1.y) - b_dy * (line1.pt1.x - line2.pt1.x)) /
(-b_dx * a_dy + a_dx * b_dy);
if (s >= 0 && s <= 1 && t >= 0 && t <= 1) {
//this is where we create our array but we also add the line number of where each point intersects. I also add the angle but have not used it throughout the rest of this...yet.
points.push({
x: Math.round(line1.pt1.x + t * (line1.pt2.x - line1.pt1.x)),
y: Math.round(line1.pt1.y + t * (line1.pt2.y - line1.pt1.y)),
num: {
first: line1.num,
second: line2.num
},
angle: {
a1: line1.angle,
a2: line2.angle
}
});
}
}
//just checks each line against the others by passing to lineSegmentsIntersect() function
function callIntersect() {
for (let i = 0; i < lines.length; i++) {
for (let j = i + 1; j < lines.length; j++) {
lineSegmentsIntersect(lines[i], lines[j]);
}
}
}
callIntersect();
function drawPoints() {
//just draws the black points for reference
for (let i = 0; i < points.length; i++) {
ctx.beginPath();
ctx.arc(points[i].x, points[i].y, 2, 0, Math.PI * 2);
ctx.fill();
}
}
drawPoints();
function createSortedArray() {
//Now we take the points array and sort the points by the first number to make using i and j below possible
points.sort((a, b) => a.num.first - b.num.first)
//We push each group of points into an array inside sortedPts creating the 2d array
for (let i = 0; i < lineNum; i++) {
for (let j = 0; j < points.length; j++) {
if (points[j].num.first == (i + 1)) {
sortedPts[i].push(points[j]);
}
}
}
//now sort the 2d arrays by y value. This allows or next check to go in order from point to point per line.
sortedPts.forEach(arr => arr.sort((a, b) => a.y - b.y));
fillTriangles();
}
createSortedArray();
/*
The last step iterates through each point in the original points array
and check to see if either the first or second number matches the second
number of a point in our sortedPts array AND do the first or second number
match the next points in the sortedPtsd array. If so then we must have a
triangle.
Quick breakdown. If we have 3 lines (line 1, 2, 3) and I have a points on lines
2 & 3. I also have another point on lines 2 & 1. Then in order to have a triangle
the last point must be on lines 1 & 3.
That's all this is doing.
*/
function fillTriangles() {
//iterate through each array inside sortedPts array
for (let i = 0; i < sortedPts.length; i++) {
//iterate through all points inside each array of points inside the sortedPts array
for (let j = 0; j < sortedPts[i].length - 1; j++) {
//iterate over the original points and compare
for (let k = 0; k < points.length; k++) {
if (
(points[k].num.first == sortedPts[i][j].num.second ||
points[k].num.second == sortedPts[i][j].num.second) &&
(points[k].num.first == sortedPts[i][j + 1].num.second ||
points[k].num.second == sortedPts[i][j + 1].num.second)
) {
ctx.fillStyle = "rgba(200, 100, 0, 0.1)";
ctx.beginPath();
ctx.moveTo(sortedPts[i][j].x, sortedPts[i][j].y);
ctx.lineTo(sortedPts[i][j + 1].x, sortedPts[i][j + 1].y);
ctx.lineTo(points[k].x, points[k].y);
ctx.closePath();
ctx.fill();
}
}
}
}
}
<canvas id="canvas"></canvas>
I also think there's a good way to do this with the angles of the crossing lines and am working on something to do it that way. I am hoping I can get it to determine the type of shape based on the number of sides but I don't see that being a quick project.
Your goal is not clear to me. You can connect any arbitrary set of points in any arbitrary order and call it a shape. What are your criteria?
If you want to find the shortest path that connects all the points of a given subset, I suggest looking for travelling salesman problem.
Currently I try to write code for calculating the parts of the screen you can see and those who can't because of objects that block light in 2d, like in Among Us:
The code should run on a processor with very low specs (at least in 2020), the C64. On such a simple CPU it's not possible to do such complex math fast enough for a game, so I came up with an idea: First of all, I make everything tile based, that makes processing easier and also means that I can just change entire characters or their color cells. Then I just write code for the PC in Processing (that's a coding language similar to Java but easier to use) to calculate how rays of light would move (the following graphic should make that more understandable), first just with a rectangle (and a single quadrant):
Then I wrote some completely messy assembler code for using the recorded coordinates to just keep filling the tiles with an inverted character based on the number of the ray currently being drawn on the ray until they hit an object (/ the tile it wants to fill is not inverted and not a space) and then just go to the next ray. I reduced the radius to 7 so it just takes up 256 bytes, useful for ASM. And that totally worked, I was able to fix every single bug and the result was quite impressive, since I needed to add pause statements or everything ran so fast that you couldn't see anything.
After that worked, I tried it with a circle, setting the points using this code:
int pointNum = ceil(radius * PI * 2); // calculates the circumference
for(int i = 0;i < pointNum;i++){
float angle = map(i, 0, pointNum, 0, PI*2);
setPixel(sin(angle) * radius, cos(angle) * radius);
}
I previously used the Bresenham circle algorithm but that didn't quite work so I tried a more simple way. So ...
All the marked black tiles never get hit by any light, which is a pretty big issue, because it wouldn't make much sense in a game that you just can't see those tiles. The code I used, written in Processing, is:
float[] xPoints = new float[0];
float[] yPoints = new float[0];
float[] xPointsT;
float[] yPointsT;
float[] xPointsHad = new float[0];
float[] yPointsHad = new float[0];
int pos = 0;
float interpolPos = 0;
int radius = 12;
float tileSize = 800.0 / (2*radius+1);
String output = " !byte ";
int pointNum = ceil(radius * PI * 2);
void setup() {
size(800, 800);
frameRate(60);
xPointsT = new float[0];
yPointsT = new float[0];
/*for(int i = 0;i <= radius;i++){
setPixel(radius, i);
setPixel(i, radius);
}*/ //Uncomment this and comment the next 4 lines to get the rectangle version
for(int i = 0;i < pointNum;i++){
float angle = map(i, 0, pointNum, 0, PI*2);
setPixel(sin(angle) * radius, cos(angle) * radius);
}
xPoints = concat(xPoints, xPointsT);
yPoints = concat(yPoints, yPointsT);
}
void draw(){
if(interpolPos > radius){
pos++;
interpolPos = 0;
println(output);
output = " !byte ";
}
float x=0, y=0;
float interpolMul = interpolPos / radius;
x = xPoints[pos] * interpolMul;
y = yPoints[pos] * interpolMul;
interpolPos+=1;//sorta the resolution
background(0);
stroke(255);
for(int i = 0;i < 2*radius+1;i++){
for(int j = 0;j < 2*radius+1;j++){
if((round(x) + radius) == i && (round(y) + radius) == j){
fill(0, 255, 0);
if(output != " !byte ")
output += ", ";
output += i-radius;
output += ", ";
output += j-radius;
xPointsHad = append(xPointsHad, i);
yPointsHad = append(yPointsHad, j);
}
else{
int fillVal = 0;
for(int k = 0; k < xPoints.length;k++){
if(round(xPoints[k])+radius == i && round(yPoints[k])+radius == j){
fillVal += 64;
}
}
fill(0, 0, fillVal);
if(fillVal == 0){
for(int k = 0; k < xPointsHad.length;k++){
if(round(xPointsHad[k]) == i && round(yPointsHad[k]) == j){
fill(128, 0, 0);
}
}
}
}
rect(i * tileSize, j * tileSize, tileSize, tileSize);
}
}
strokeWeight(3);
stroke(0, 255, 255, 64);
for(int i = 0;i < xPoints.length;i++){
line((float(radius)+0.5) * tileSize, (float(radius)+0.5) * tileSize, (float(radius)+0.5+xPoints[i]) * tileSize, (float(radius)+0.5+yPoints[i]) * tileSize);
}
strokeWeight(1);
fill(255, 255, 0);
ellipse((x + radius + 0.5) * tileSize, (y + radius + 0.5) * tileSize, 10, 10);
}
void setPixel(float _x, float _y){
for(int i = 0; i < xPoints.length;i++){
if(_x == xPoints[i] && _y == yPoints[i]){
return;
}
}
for(int i = 0; i < xPointsT.length;i++){
if(_x == xPointsT[i] && _y == yPointsT[i]){
return;
}
}
xPointsT = append(xPointsT, _x);
yPointsT = append(yPointsT, _y);
}
(Instructions to get the rectangle are in the code)
Those mentioned tiles seem to be never hit because the rays on them just jump over them, but what can I do to prevent that? You can decrease interpolPos+=x; to hit more tiles because that way your steps are smaller, but that wastes quite some space, so I don't think that's a good solution. Ideally you could also just decrease the number of coordinates you draw to get a smaller vision. Has anyone a good idea how to do that?
You have chosen wrong method to find all touched cells - instead of point-based way you need cell(squares)-based approach - ray intersects rectangle rather than point.
There is article of Amanatides and Woo "A Fast Voxel Traversal Algorithm for Ray Tracing" for 2D.
Practical implementation.
Example:
Quick-made tracing example. Rays emitted from left top corner go to blue points. If ray meets black cell obstacle, it stops. Pink cells are lighted by rays, grey ones are not.
Okay, I found something that worked for me in my situation: I just used the part that totally works (the rectangle) and then just make that a circle by ignoring every tile hit that's further away from the light source then the radius + 0.5, because without + .5 the circle looks weird. You can try it yourself, here's the code:
float[] xPoints = new float[0];
float[] yPoints = new float[0];
float[] xPointsT;
float[] yPointsT;
float[] xPointsHad = new float[0];
float[] yPointsHad = new float[0];
int pos = 0;
float interpolPos = 0;
int radius = 7;
float tileSize = 800.0 / (2*radius+1);
int pointNum = ceil(radius * PI * 2);
String standardOutput = " !align 15,0\n !byte ";
void setup() {
size(800, 800);
frameRate(60);
xPointsT = new float[0];
yPointsT = new float[0];
for(int i = 0;i <= radius;i++){
setPixel(radius, i);
setPixel(i, radius);
} //Uncomment this and comment the next 4 lines to get the rectangle version
/*for(int i = 0;i < pointNum;i++){
float angle = map(i, 0, pointNum, 0, PI*2);
setPixel(sin(angle) * radius, cos(angle) * radius);
}*/
xPoints = concat(xPoints, xPointsT);
yPoints = concat(yPoints, yPointsT);
xPointsT = new float[0];
yPointsT = new float[0];
}
void draw(){
if(interpolPos > radius){
pos++;
interpolPos = 0;
String output = standardOutput;
for(int i = 0;i < radius + 1;i++){
int indexPos = floor(map(i, 0, radius + 1, 0, xPointsT.length));
output += round(xPointsT[indexPos]);
output += ",";
output += round(yPointsT[indexPos]);
if(i < radius){
output += ", ";
}
}
println(output);
xPointsT = new float[0];
yPointsT = new float[0];
}
float x=0, y=0;
float interpolMul = interpolPos / radius;
x = xPoints[pos] * interpolMul;
y = yPoints[pos] * interpolMul;
interpolPos+=1;//sorta the resolution
background(0);
stroke(255);
for(int i = 0;i < 2*radius+1;i++){
for(int j = 0;j < 2*radius+1;j++){
if((round(x) + radius) == i && (round(y) + radius) == j && sqrt(sq(round(x)) + sq(round(y))) < radius + 0.5){
fill(0, 255, 0);
xPointsT = append(xPointsT, i-radius);
yPointsT = append(yPointsT, j-radius);
xPointsHad = append(xPointsHad, i);
yPointsHad = append(yPointsHad, j);
}
else{
int fillVal = 0;
for(int k = 0; k < xPoints.length;k++){
if(round(xPoints[k])+radius == i && round(yPoints[k])+radius == j){
fillVal += 64;
}
}
fill(0, 0, fillVal);
if(fillVal == 0){
for(int k = 0; k < xPointsHad.length;k++){
if(round(xPointsHad[k]) == i && round(yPointsHad[k]) == j){
fill(128, 0, 0);
}
}
}
}
rect(i * tileSize, j * tileSize, tileSize, tileSize);
}
}
strokeWeight(3);
stroke(0, 255, 255, 64);
for(int i = 0;i < xPoints.length;i++){
line((float(radius)+0.5) * tileSize, (float(radius)+0.5) * tileSize, (float(radius)+0.5+xPoints[i]) * tileSize, (float(radius)+0.5+yPoints[i]) * tileSize);
}
strokeWeight(1);
fill(255, 255, 0);
ellipse((x + radius + 0.5) * tileSize, (y + radius + 0.5) * tileSize, 10, 10);
}
void setPixel(float _x, float _y){
for(int i = 0; i < xPoints.length;i++){
if(_x == xPoints[i] && _y == yPoints[i]){
return;
}
}
for(int i = 0; i < xPointsT.length;i++){
if(_x == xPointsT[i] && _y == yPointsT[i]){
return;
}
}
xPointsT = append(xPointsT, _x);
yPointsT = append(yPointsT, _y);
}
Besides the main difference to ignore tiles that are not in the circle, I also changed that I store the coordinates not in a String but in two arrays, because then I use code to stretch them when there are fewer then radius + 1 points, so I don't have to store multiple circles with different sizes in the C64's RAM, so it meets my main requirements: It should fill every tile and it should be downscalable by ignoring some points at the end of rays. And is if efficient? Uh ... there could be a better solution that fills the circle with fewer rays, but I don't care too much. Still, if you have an idea, it would be nice if you could tell me, but otherwise this question is solved.
Edit: I forgot to add a picture. Don't be confused, I modified the code after posting it so you can also see the blue tiles on the circle.
I would like to "mirror" a PShape object like in the picture below:
I know how to display multiple shapes and how to invert them (screenshot below) but things get complicated when I have to rotate them (and probably translating them) so as they "stick" to the preceding shapes (first picture).
I've been trying to compute an angle with the first 2 vertices of the original shape (irregular quadrilateral) and the atan2() function but to no avail.
I would really appreciate if someone could help figuring how to solve this problem.
int W = 20;
int H = 20;
int D = 20;
PShape object;
void setup(){
size(600, 600, P2D);
smooth();
}
void draw(){
background(255);
pushMatrix();
translate(width/2, height/1.3);
int td = -1;
for (int i = 0; i < 6; i++){
translate(0, td*H*2);
scale(-1, 1);
rotate(PI);
object();
td *= -1;
}
popMatrix();
}
void object() {
beginShape(QUADS);
vertex(-20, 20);
vertex(20, 0);
vertex(20, -20);
vertex(-20, -20);
endShape();
}
To do what you want you have to create a shape by 2 given angles for the top and the bottom of the shape angleT and `angleB´. The origin (0,0) is in the center of the shape. This causes that the pivots for the rotations are in the middle of the slopes of the shape :
int W = 40;
int H = 40;
float angleT = -PI/18;
float angleB = PI/15;
PShape object;
void object() {
float H1 = -H/2 + W*tan(angleB);
float H2 = H/2 + W*tan(angleT);
beginShape(QUADS);
vertex(-W/2, -H/2);
vertex(W/2, H1);
vertex(W/2, H2);
vertex(-W/2, H/2);
endShape();
}
When you draw the parts, then you should distinguish between even and odd parts. The parts have to be flipped horizontal by inverting the y axis (scale(1, -1)). The even parts have to be rotated by the double of angleB and the odd parts have to be rotated by the doubled of angleT. For the rotation, the center of the slopes (pivots) have to be translated to the origin:
void setup(){
size(600, 600, P2D);
smooth();
}
void draw(){
background(255);
translate(width/2, height/2);
float HC1 = -H/2 + W*tan(angleB)/2;
float HC2 = H/2 + W*tan(angleT)/2;
for (int i = 0; i < 15; i++){
float angle = (i % 2 == 0) ? -angleB : -angleT;
float HC = (i % 2 == 0) ? HC1 : HC2;
translate(0, -HC);
rotate(angle*2);
translate(0, -HC);
object();
scale(1, -1);
}
}
The algorithm works for any angle, positive and negative including 0.
This algorithm can be further improved. Let's assume you have a quad, defined by 4 points (p0, p1, p2, p3):
float[] p0 = {10, 0};
float[] p1 = {40, 10};
float[] p2 = {60, 45};
float[] p3 = {0, 60};
PShape object;
void object() {
beginShape(QUADS);
vertex(p0[0], p0[1]);
vertex(p1[0], p1[1]);
vertex(p2[0], p2[1]);
vertex(p3[0], p3[1]);
endShape();
}
Calculate the the minimum, maximum, centerpoint, pivots and angles:
float minX = min( min(p0[0], p1[0]), min(p2[0], p3[0]) );
float maxX = max( max(p0[0], p1[0]), max(p2[0], p3[0]) );
float minY = min( min(p0[1], p1[1]), min(p2[1], p3[1]) );
float maxY = max( max(p0[1], p1[1]), max(p2[1], p3[1]) );
float cptX = (minX+maxX)/2;
float cptY = (minY+maxY)/2;
float angleB = atan2(p1[1]-p0[1], p1[0]-p0[0]);
float angleT = atan2(p2[1]-p3[1], p2[0]-p3[0]);
float HC1 = p0[1] + (p1[1]-p0[1])*(cptX-p0[0])/(p1[0]-p0[0]);
float HC2 = p3[1] + (p2[1]-p3[1])*(cptX-p3[0])/(p2[0]-p3[0]);
Draw the shape like before:
for (int i = 0; i < 6; i++){
float angle = (i % 2 == 0) ? -angleB : -angleT;
float HC = (i % 2 == 0) ? HC1 : HC2;
translate(cptX, -HC);
rotate(angle*2);
translate(-cptX, -HC);
object();
scale(1, -1);
}
Another approach would be to stack the shape on both sides:
For this you have to know the heights of the pivots (HC1, HC2) and the angles (angleB, angleT). So this can be implemented based on both of the above approaches.
Define the pivot points and the directions of the top and bottom edge:
PVector dir1 = new PVector(cos(angleB), sin(angleB));
PVector dir2 = new PVector(cos(angleT), sin(angleT));
PVector pv1 = new PVector(0, HC1); // or PVector(cptX, HC1)
PVector pv2 = new PVector(0, HC2); // or PVector(cptX, HC2)
Calculate the intersection point (X) of the both edges. Of course this will work only if the
edges are not parallel:
PVector v12 = pv2.copy().sub(pv1);
PVector nDir = new PVector(dir2.y, -dir2.x);
float d = v12.dot(nDir) / dir1.dot(nDir);
PVector X = pv1.copy().add( dir1.copy().mult(d) );
The stack algorithm works as follows:
for (int i = 0; i < 8; i++){
float fullAngle = angleT-angleB;
float angle = fullAngle * floor(i/2);
if ((i/2) % 2 != 0)
angle += fullAngle;
if (i % 2 != 0)
angle = -angle;
float flip = 1.0;
if (i % 2 != 0)
flip *= -1.0;
if ((i/2) % 2 != 0)
flip *= -1.0;
pushMatrix();
translate(X.x, X.y);
rotate(angle);
scale(1, flip);
rotate(-angleB);
translate(-X.x, -X.y);
object();
popMatrix();
}
I’m looking for a proper way of finding points along a PShape contour.
My goal is to generate the same number of points along the two distances from a given point to another (right distance and left distance), then mark a point in the exact center between the the two points that are the same step number on each side. (I’m not sure if I’m being easily understandable, and I cannot attach img already, so I attach processing code).
I imagine that the first step for getting it done is to calculate the exact distance between the start and end points, following the path. Maybe I’m wrong.
Any help on this matter would be very very welcome.
PGraphics g ;
PVector[] values = new PVector[7];
void setup(){
size(1024,768,P3D);
fillVal();
smooth();
}
void draw(){
background(0);
drawSiluette(g);
}
void fillVal(){
values[0]=new PVector ( 336.0, 272.0, 0.0 );
values[1]=new PVector ( 305.0, 428.0, 0.0 );
values[2]=new PVector ( 489.0, 516.0, 0.0 );
values[3]=new PVector ( 639.0, 400.0, 0.0);
values[4]=new PVector ( 565.0, 283.0, 0.0 );
values[5]=new PVector ( 469.0, 227.0, 0.0 );
values[6]=new PVector ( 403.0, 216.0, 0.0 );
}
void drawSiluette(PGraphics _s){
_s = createGraphics(width,height);
pushMatrix();
_s.beginDraw();
_s.noFill();
_s.strokeWeight(3);
_s.stroke(255);
_s.beginShape();
for(int i = 0; i <values.length;i++){
if(i==0 || i==values.length-1){
for(int it = 0; it<2;it++)
_s.curveVertex(values[0].x,values[0].y);
}else
_s.curveVertex(values[i].x,values[i].y);
}
_s.endShape(CLOSE);
popMatrix();
_s.endDraw();
image(_s,0,0);
//start and end points
pushMatrix();
noStroke();
fill(255,0,0);
ellipseMode(CENTER);
ellipse(values[0].x,values[0].y,10,10);
ellipse(values[int(values.length/2)].x,values[int(values.length/2)].y,10,10);
popMatrix();
}
The question is a little unclear. To
generate the same number of points along the two distances from a
given point to another
you can simply linearly interpolate between two points (lerp for short).
This functionality is built into the PVector's lerp() function.
The function takes three parameters:
the start point
the end point
a normalised value, which is a value between 0.0 and 1.0
You can think of the normalised value as a percentage:
0.0 = 0%
0.25 = 25%
1.0 = 100%
etc.
Here's a basic example demonstration interpolation between two points with a given number of points in between:
PVector from = new PVector(100,100);
PVector to = new PVector(300,300);
int numPoints = 10;
void setup(){
size(400,400);
fill(0);
}
void draw(){
background(255);
for(int i = 0; i <= numPoints; i++){
//compute interpolation amount = a number from 0.0 and 1.0 , where 0 = 0% along the line and 1.0 = 100 % along the line (0.5 = 50%, etc.)
float interpolationAmount = (float)i / numPoints;
//float interpolationAmount = map(i,0,numPoints,0.0,1.0);
//linearly interpolate point based on the interpolation amount
PVector interpolatedPoint = PVector.lerp(from,to,interpolationAmount);
//render the point on screen
ellipse(interpolatedPoint.x,interpolatedPoint.y,10,10);
}
text("numPoints: " + numPoints,10,15);
}
void mouseDragged(){
if(keyPressed) {
to.set(mouseX,mouseY);
}else{
from.set(mouseX,mouseY);
}
}
void keyPressed(){
if(keyCode == UP) numPoints++;
if(keyCode == DOWN && numPoints > 0) numPoints--;
}
You can run this as a demo bellow:
var from,to;
var numPoints = 10;
function setup(){
createCanvas(400,400);
fill(0);
from = createVector(100,100);
to = createVector(300,300);
}
function draw(){
background(255);
for(var i = 0; i <= numPoints; i++){
//compute interpolation amount = a number from 0.0 and 1.0 , where 0 = 0% along the line and 1.0 = 100 % along the line (0.5 = 50%, etc.)
//var interpolationAmount = (float)i / numPoints;
var interpolationAmount = map(i,0,numPoints,0.0,1.0);
//linearly interpolate point based on the interpolation amount
var interpolatedPoint = p5.Vector.lerp(from,to,interpolationAmount);//PVector.lerp(from,to,interpolationAmount);
//render the point on screen
ellipse(interpolatedPoint.x,interpolatedPoint.y,10,10);
}
text("usage:\nclick & drag to move start point\nhold a key pressed while clicking and drag to move end point\nuse LEFT/RIGHT arrow to change number of points: " + numPoints,10,15);
}
function mouseDragged(){
if(keyIsPressed) {
to.set(mouseX,mouseY);
}else{
from.set(mouseX,mouseY);
}
}
function keyPressed(){
if(keyCode == LEFT_ARROW) numPoints++;
if(keyCode == RIGHT_ARROW && numPoints > 0) numPoints--;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.4/p5.min.js"></script>
The functionality could be encapsulated into a reusable function:
void drawPointsInbetween(PVector from,PVector to,int numPoints){
for(int i = 0; i <= numPoints; i++){
//compute interpolation amount = a number from 0.0 and 1.0 , where 0 = 0% along the line and 1.0 = 100 % along the line (0.5 = 50%, etc.)
float interpolationAmount = (float)i / numPoints;
//float interpolationAmount = map(i,0,numPoints,0.0,1.0);
//linearly interpolate point based on the interpolation amount
PVector interpolatedPoint = PVector.lerp(from,to,interpolationAmount);
//render the point on screen
ellipse(interpolatedPoint.x,interpolatedPoint.y,10,10);
}
}
Back to your code, one thing that sticks out, although it's not related your main question is the fact that you're creating a new PGraphics instance multiple times per second. You probably don't want to do that. Currently, you should be able to draw straight into Processing with no need for PGraphics.
PVector[] values = new PVector[7];
void setup(){
size(1024,768,P3D);
fillVal();
smooth();
}
void draw(){
background(0);
drawSiluette(g);
}
void fillVal(){
values[0]=new PVector ( 336.0, 272.0, 0.0 );
values[1]=new PVector ( 305.0, 428.0, 0.0 );
values[2]=new PVector ( 489.0, 516.0, 0.0 );
values[3]=new PVector ( 639.0, 400.0, 0.0);
values[4]=new PVector ( 565.0, 283.0, 0.0 );
values[5]=new PVector ( 469.0, 227.0, 0.0 );
values[6]=new PVector ( 403.0, 216.0, 0.0 );
}
void drawSiluette(PGraphics _s){
//_s = createGraphics(width,height);
pushMatrix();
//_s.beginDraw();
noFill();
strokeWeight(3);
stroke(255);
beginShape();
for(int i = 0; i <values.length;i++){
if(i==0 || i==values.length-1){
for(int it = 0; it<2;it++)
curveVertex(values[0].x,values[0].y);
}else
curveVertex(values[i].x,values[i].y);
}
endShape(CLOSE);
popMatrix();
//start and end points
pushMatrix();
noStroke();
fill(255,0,0);
ellipseMode(CENTER);
ellipse(values[0].x,values[0].y,10,10);
ellipse(values[int(values.length/2)].x,values[int(values.length/2)].y,10,10);
popMatrix();
}
Adding the points in between function would be as simple as this:
PVector[] values = new PVector[7];
int numPoints = 10;
void setup(){
size(1024,768,P3D);
fillVal();
smooth();
}
void draw(){
background(0);
drawSiluette(g);
}
void fillVal(){
values[0]=new PVector ( 336.0, 272.0, 0.0 );
values[1]=new PVector ( 305.0, 428.0, 0.0 );
values[2]=new PVector ( 489.0, 516.0, 0.0 );
values[3]=new PVector ( 639.0, 400.0, 0.0);
values[4]=new PVector ( 565.0, 283.0, 0.0 );
values[5]=new PVector ( 469.0, 227.0, 0.0 );
values[6]=new PVector ( 403.0, 216.0, 0.0 );
}
void drawSiluette(PGraphics _s){
//_s = createGraphics(width,height);
pushMatrix();
//_s.beginDraw();
noFill();
strokeWeight(3);
stroke(255);
beginShape();
for(int i = 0; i <values.length;i++){
if(i==0 || i==values.length-1){
for(int it = 0; it<2;it++)
curveVertex(values[0].x,values[0].y);
}else
curveVertex(values[i].x,values[i].y);
}
endShape(CLOSE);
popMatrix();
//start and end points
pushMatrix();
noStroke();
fill(255,0,0);
ellipseMode(CENTER);
ellipse(values[0].x,values[0].y,10,10);
ellipse(values[int(values.length/2)].x,values[int(values.length/2)].y,10,10);
popMatrix();
//draw inbetween points
for(int i = 1 ; i < values.length; i++){
drawPointsInbetween(values[i-1],values[i],numPoints);
}
//draw last to first
drawPointsInbetween(values[values.length-1],values[0],numPoints);
}
void drawPointsInbetween(PVector from,PVector to,int numPoints){
for(int i = 0; i <= numPoints; i++){
//compute interpolation amount = a number from 0.0 and 1.0 , where 0 = 0% along the line and 1.0 = 100 % along the line (0.5 = 50%, etc.)
float interpolationAmount = (float)i / numPoints;
//float interpolationAmount = map(i,0,numPoints,0.0,1.0);
//linearly interpolate point based on the interpolation amount
PVector interpolatedPoint = PVector.lerp(from,to,interpolationAmount);
//render the point on screen
ellipse(interpolatedPoint.x,interpolatedPoint.y,10,10);
}
}
Here's a preview:
Notice that the interpolation is linear. For curves you might want to look at
higher order interpolation functions such as quadratic or cubic.
Hermite curves are an example of cubic curve.
Here's a basic the formula:
and here's a basic Processing demo interpolating points on a Hermite curve:
float percent = 0;
PVector P0 = new PVector(10,90);//1st control pt
PVector T0 = new PVector(300,200);//1st anchor pt - NOTE! The anchors are relative to the controls
PVector P1 = new PVector(400,90);//2nd control pt
PVector T1 = new PVector(-100,400);//2nd anchor pt
PVector[] points = {P0,T0,P1,T1};
PVector pointAtPercent;
void setup(){
size(500,500);
reset();
}
void reset(){
P1.x = 200 + random(200);//randomize a wee bit
T1.x = random(-100,100);
percent = 0;
background(255);
loop();
}
void draw() {
pointAtPercent = hermite(percent, points);//compute point
//render on screen
ellipse(pointAtPercent.x,pointAtPercent.y,10,10);
//update percentage of traversal along curve
percent += .015;
//if the curve has been drawn, stop drawing
if(percent >= 1) noLoop();
}
void mousePressed(){
reset();
}
PVector hermite(float t,PVector[] points){
PVector result = new PVector();
result.x = (2 * pow(t,3) - 3 * t * t + 1) * points[0].x+
(pow(t,3) - 2 * t * t + t) * points[1].x +
(- 2 * pow(t,3) + 3*t*t) * points[2].x +
( pow(t,3) - t*t) * points[3].x;
result.y = (2 * pow(t,3) - 3 * t * t + 1) * points[0].y+
(pow(t,3) - 2 * t * t + t) * points[1].y +
(- 2 * pow(t,3) + 3*t*t) * points[2].y +
( pow(t,3) - t*t) * points[3].y;
return result;
}
It's unclear how exactly you're aiming to interpolate between your points, but hopefully the above concepts should help you achieve your goal.