Related
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’m new at coding and I have a problem, I would like to draw a program where there is a curve and but for now I just have the static drawing but I don’t know how to generate a tangent line on the curve thanks to his derivate, as the mouse move…
This is all I have for now
void draw(){
background(255);
noFill();
stroke(0);
beginShape();
for(float a=0; a < TWO_PI; a+=0.01) {
float r=78;
float x=sin (a);
float y=(pow(cos(a),2)/(2-cos(a)))
my idea was to make a cursor, that moved by the curve, and every tima generated is own tangent.
THANK YOU SO MUCH !!!!!
I recommend to use PVector for the computation.
Create a function which computes a point on the shape:
PVector fSahpe(float a) {
float r = 200;
float x = r * sin(a);
float y = -r *(pow(cos(a),2)/(2-cos(a)));
return new PVector(x, y);
}
You have to find the point on the shape which is closest to the mouse position. Find the nearest point while you draw the shape. Note, since the shape is translated, the mouse position which is used to compare the mouse position to a point on the shape has to be shifted in the opposite direction:
PVector m = new PVector(mouseX-width/2, mouseY-height/2);
dist() can be used to compute the Euclidean distance between 2 points:
float mindist = 1000;
float mina = 0;
for(float a=0; a < TWO_PI; a+=0.01) {
PVector p = fSahpe(a);
// [...]
float dist = PVector.dist(p, m);
if (dist < mindist) {
mindist = dist;
mina = a;
}
}
Define a threshold distance. If the distance of the mouse to the closest point on the curve falls below the distance, the draw the tangent:
if (mindist < 10) {
// [...] draw tangent
}
Compute 2 points on the curve, which are close to each another, where one point is the point which is closest to the mouse cursor:
PVector p0 = fSahpe(mina);
PVector p1 = fSahpe(mina+0.01);
This 2 points ar on the approximated tangent. Compute the vector from on point to the other and scale it to a certain length (the length is the half length of the tangent):
PVector dir = PVector.sub(p1, p0);
dir.normalize().mult(100);
Compute the start point and the end point of the tangent:
PVector l0 = PVector.add(p0, dir);
PVector l1 = PVector.sub(p0, dir);
See the complete example:
void setup() {
size(500, 500);
}
PVector fSahpe(float a) {
float r = 200;
float x = r * sin(a);
float y = -r *(pow(cos(a),2)/(2-cos(a)));
return new PVector(x, y);
}
void draw(){
background(0);
translate(width/2, height/2);
noFill();
strokeWeight(1);
stroke(255);
float mindist = 1000;
float mina = 0;
PVector m = new PVector(mouseX-width/2, mouseY-height/2);
beginShape();
for(float a=0; a < TWO_PI; a+=0.01) {
PVector p = fSahpe(a);
vertex(p.x, p.y);
float dist = PVector.dist(p, m);
if (dist < mindist) {
mindist = dist;
mina = a;
}
}
endShape();
if (mindist < 10) {
PVector p0 = fSahpe(mina);
PVector p1 = fSahpe(mina+0.01);
PVector dir = PVector.sub(p1, p0);
dir.normalize().mult(100);
PVector l0 = PVector.add(p0, dir);
PVector l1 = PVector.sub(p0, dir);
strokeWeight(3);
stroke(255, 0, 0);
line(l0.x, l0.y, l1.x, l1.y);
}
}
A couple of days ago I asked a question about translations and rotations in Processing.
I wanted to:
translate, invert and rotate a single quadrilateral (PShape object) multiple times
then change the height of one of its 2 top vertices
so as the whole thing act as an articulated arm that can be bent either to the right or the left.
Thanks to the help of #Rabbid76 I was able to achieve this effect but I am now facing another issue when translating the last 5 top horizontally inverted quads.
When bending the object, the first 3 quads get separated from the last 5 and. And the more the bending leg is curved, the farther they get apart.
I would really appreciate if someone could help me fix the translation part (from line 65 to 68) so as the quads stay attached to each other to matter how strong the bending is.
Any suggestion regarding that matter would be also greatly appreciated.
SCRIPT
int W = 40;
int H = 40;
int nQuads = 8;
int xOffset = 27;
float[] p0 = {-W/2 + xOffset, -H/2};
float[] p1 = {-W/2, H/2};
float[] p2 = {W/2, H/2};
float[] p3 = {W/2, -H/2};
PShape object;
void setup(){
size(600, 600, P2D);
smooth(8);
}
void draw(){
background(255);
// Bending to the left
float bending = sin(frameCount*.05) * .1;
p0[1] -= bending;
pushMatrix();
translate(width/2, height/2);
float minX = min( min(p0[0], p3[0]), min(p2[0], p1[0]) );
float maxX = max( max(p0[0], p3[0]), max(p2[0], p1[0]) );
float cptX = (minX+maxX)/2;
//Rotation Angle
float angle = atan2(p3[1]-p0[1], p3[0]-p0[0]);
//Pivot Height
float PH = p0[1] + (p3[1]-p0[1]) * (cptX-p0[0])/(p3[0]-p0[0]);
for (int i = 0; i < nQuads; i++){
float PivotHeight = (i % 2 == 1) ? PH : H/2;
//Height translation
if (i > 0){
translate(0, PivotHeight);
}
//Rotate once every 2 quads
if (i%2 == 1){
rotate(angle*2);
}
//Height translation
//Flip all quads except 1st one
if (i > 0){
translate(0, PivotHeight);
scale(1, -1);
}
//NOT working --> Flipping horizontally the last 5 top QUADS
if (i == 3){
scale(-1, 1);
translate(- xOffset, 0); //trying to align the quads on the X axis. Y translation is missing
rotate(-angle*2);
}
object();
}
popMatrix();
}
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();
}
Just providing a workaround to my own question but won't accept it as a valid answer as I don't really understand what I'm doing and it's probably not the most efficient solution.
int W = 40;
int H = 40;
int nQuads = 8;
int xOffset = 27;
float[] p0 = {-W/2 + xOffset, -H/2};
float[] p1 = {-W/2, H/2};
float[] p2 = {W/2, H/2};
float[] p3 = {W/2, -H/2};
PShape object;
void setup(){
size(600, 600, P2D);
smooth(8);
}
void draw(){
background(255);
// Bending to the left
float bending = sin(frameCount*.05) * .3;
p0[1] -= bending;
pushMatrix();
translate(width/2, height/2);
float minX = min( min(p0[0], p3[0]), min(p2[0], p1[0]) );
float maxX = max( max(p0[0], p3[0]), max(p2[0], p1[0]) );
float cptX = (minX+maxX)/2;
//Rotation Angle
float angle = atan2(p3[1]-p0[1], p3[0]-p0[0]);
//Pivot Height
float PH = p0[1] + (p3[1]-p0[1]) * (cptX-p0[0])/(p3[0]-p0[0]);
for (int i = 0; i < nQuads; i++){
float PivotHeight = (i % 2 == 1) ? PH : H/2;
//Height translation
if (i > 0){
translate(0, PivotHeight);
}
//Rotate once every 2 quads
if (i%2 == 1){
rotate(angle*2);
}
//Height translation
//Flip all quads except 1st one
if (i > 0){
translate(0, PivotHeight);
scale(1, -1);
}
//Flipping horizontally the last 5 top QUADS
if (i == 3){
scale(-1, 1);
translate(0, PivotHeight);
rotate(-angle*2);
translate(0, PivotHeight);
translate(-xOffset , H/2 - p0[1]);
}
object();
}
popMatrix();
}
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();
}
I've recently been looking into Zonohedrons and Rob Bell made beautiful ones. I had a play with the free Polar Zonohedron Sketchup Plugin and thought about playing with the geometry using Processing. So far I've open up the plugin/Ruby script and tried to port it directly, but I am not experienced with Ruby and have been using the Sketchup Ruby API reference.
The geometry part of the code is mostly in the polar_zonohedron function:
def polar_zonohedron #frequency, pitch = atan(sqrt(2)/2), len = 1.0 # frequency,pitch,length
mo = Sketchup.active_model
mo.start_operation "polar_zonohedron"
prompts = ["Frequency", "Pitch in radians", "Length"]
values = [8, "atan( sqrt(2)/2 )", 12.inch]
results = inputbox prompts, values, "Polar Zonohedron"
return if not results # This means that the user canceld the operation
ents = mo.active_entities
grp = ents.add_group
ents = grp.entities
grp.frequency = results[0]
grp.pitch = eval( results[1] )
grp.length = results[2]
pts=[]
#we begin by setting pts[0] to the origin
pts[0] = Geom::Point3d.new(0,0,0)
vector = Geom::Vector3d.new(cos(grp.pitch),0,sin(grp.pitch) ) #tilt pitch vector up the xz plane
vector.length = grp.length
#Using the origin as the initial generator we iterate thru each zone of the zonohedron
#our first task is to define the four points of the base rhomb for this zone
#at the end the pts[3] becomes our new origin for the rhomb of the next zone
1.upto(grp.frequency-1){ |i|
p_rotate = Geom::Transformation.rotation( pts[0] , Geom::Vector3d.new(0,0,1), i*2*PI/grp.frequency )
#obtain the other three points of the rhomb face
pts[1] = pts[0].transform vector
pts[3] = pts[1].transform( p_rotate )
pts[2] = pts[3].transform( vector )
#we now have the 4 points which make this zone's base rhomb
#so we rotate around the origin frequency times making a star pattern of faces
0.upto(grp.frequency-1){ |j|
f_rotate = Geom::Transformation.rotation( Geom::Point3d.new(0,0,0) , Geom::Vector3d.new(0,0,1), j*2*PI/grp.frequency )
ents.add_face( pts.collect{|p| p.transform(f_rotate)} )
}
#set the origin for the rhomb of the next zone
pts[0] = pts[3]
}
mo.commit_operation
end
I've understood the loops but am slightly confused by transforms:
pts[1] = pts[0].transform vector
pts[3] = pts[1].transform( p_rotate )
pts[2] = pts[3].transform( vector )
As far as I can tell pts[1] is the vector addiction of pts[0] and vector,
and pts[3] is pts[1] multiplied by the p_rotate rotation matrix. Would pts[2] also be an addition (between pts[3] and vector )?
Here's what my attempt looks like so far:
//a port attempt of Rob Bell's polar_zonohedron.rb script - http://zomebuilder.com/
int frequency = 3;
float pitch = atan(sqrt(2)/2);
float length = 24;
ArrayList<Face> faces = new ArrayList<Face>();
void setup(){
size(400,400,P3D);
strokeWeight(3);
setupZome();
}
void setupZome(){
faces.clear();
PVector[] pts = new PVector[4];
pts[0] = new PVector();
PVector vector = new PVector(cos(pitch),0,sin(pitch));
vector.mult(length);
for(int i = 1 ; i < frequency; i++){
PMatrix3D p_rotate = new PMatrix3D();
p_rotate.rotate(i * TWO_PI / frequency, 0,0,1);
//PVector v = new PVector();
//p_rotate.mult(pts[0],v);
//pts[0] = v;
pts[1] = PVector.add(pts[0],vector);
pts[3] = new PVector();
p_rotate.mult(pts[1],pts[3]);
pts[2] = PVector.add(pts[3],vector);
for(int j = 0; j < frequency; j++){
PMatrix3D f_rotate = new PMatrix3D();
f_rotate.rotate(j*2*PI/frequency , 0,0,1);
Face f = new Face();
for(PVector pt : pts){
PVector p = new PVector();
f_rotate.mult(pt,p);
f.add(p.get());
}
faces.add(f);
}
pts[0] = pts[3];
}
}
void draw(){
background(255);
lights();
translate(width * .5, height * .5,0);
rotateY(map(mouseX,0,width,-PI,PI));
rotateX(map(mouseY,0,height,-PI,PI));
drawAxes(100);
pushMatrix();
translate(0,0,-frequency * length * .25);
for(Face f : faces){
beginShape(mousePressed ? QUADS : POINTS);
for(PVector p : f.pts) vertex(p.x,p.y,p.z);
endShape();
}
popMatrix();
}
void keyPressed(){
if(keyCode == UP && frequency < 32) frequency++;
if(keyCode == DOWN && frequency > 2) frequency--;
setupZome();
}
void drawAxes(int size){
stroke(192,0,0);
line(0,0,0,size,0,0);
stroke(0,192,0);
line(0,0,0,0,size,0);
stroke(0,0,192);
line(0,0,0,0,0,size);
}
class Face{
ArrayList<PVector> pts = new ArrayList<PVector>();
Face(){}
void add(PVector p){
if(pts.size() <= 4) pts.add(p);
}
}
I feel I'm close, but I'm getting the loop conditionals and vertex indices wrong.
Any tips on how to fix this?
I was very close, but not paying attention to all the details.
Turns out I get the correct mesh if I don't increment the rotation on p_rotate:
p_rotate.rotate(TWO_PI / frequency, 0,0,1);
instead of
p_rotate.rotate(i * TWO_PI / frequency, 0,0,1);
Here is the full code listing:
//a port attempt of Rob Bell's polar_zonohedron.rb script - http://zomebuilder.com/
int frequency = 3;
float pitch = atan(sqrt(2)/2);
float length = 24;
ArrayList<Face> faces = new ArrayList<Face>();
void setup(){
size(400,400,P3D);
strokeWeight(3);
setupZome();
}
void setupZome(){
faces.clear();
PVector[] pts = new PVector[4];
pts[0] = new PVector();
PVector vector = new PVector(cos(pitch),0,sin(pitch));
vector.mult(length);
for(int i = 1 ; i < frequency-1; i++){
PMatrix3D p_rotate = new PMatrix3D();
p_rotate.rotate(TWO_PI / frequency, 0,0,1);
pts[1] = PVector.add(pts[0],vector);
pts[3] = new PVector();
p_rotate.mult(pts[1],pts[3]);
pts[2] = PVector.add(pts[3],vector);
for(int j = 0; j < frequency; j++){
PMatrix3D f_rotate = new PMatrix3D();
f_rotate.rotate(j*2*PI/frequency , 0,0,1);
Face f = new Face();
for(PVector pt : pts){
PVector p = new PVector();
f_rotate.mult(pt,p);
f.add(p.get());
}
faces.add(f);
}
pts[0] = pts[3];
}
}
void draw(){
background(255);
lights();
translate(width * .5, height * .5,0);
rotateY(map(mouseX,0,width,-PI,PI));
rotateX(map(mouseY,0,height,-PI,PI));
drawAxes(100);
pushMatrix();
translate(0,0,-frequency * length * .25);
for(Face f : faces){
beginShape(mousePressed ? QUADS : POINTS);
for(PVector p : f.pts) vertex(p.x,p.y,p.z);
endShape();
}
popMatrix();
}
void keyPressed(){
if(keyCode == UP && frequency < 32) frequency++;
if(keyCode == DOWN && frequency > 3) frequency--;
setupZome();
}
void drawAxes(int size){
stroke(192,0,0);
line(0,0,0,size,0,0);
stroke(0,192,0);
line(0,0,0,0,size,0);
stroke(0,0,192);
line(0,0,0,0,0,size);
}
class Face{
ArrayList<PVector> pts = new ArrayList<PVector>();
Face(){}
void add(PVector p){
if(pts.size() <= 4) pts.add(p);
}
}
And here a couple of screenshots: