How to write titles and legends in 2D fashion when working in P3D? - processing

I have completed my sketch in working in P3D mode apart from titles and legends. How do I write these texts normally like 2D when working from this mode. I've introduced a camera in the sketch to enable panning and zooming functionality. This causes the titles to transform according to the camera. I've tried transforming the title to correct position but it still looks somewhat off
Also how can I make it free from mouse zoom and panning. ie it holds its position even though other parts of the sketch moves.
Here is my code:
PImage worldMap;
PShape box;
Table table;
int rowCount;
float x1, y1, z1, x2, y2, z2;
//Interaction Zoom and Pan Variables:
float zoom = 600;
float panLeft = 0;
float panTop = 0;
void setup() {
size(1400, 800, P3D);
table = new Table("From_USA_Coordinates.tsv");
rowCount = table.getRowCount();
worldMap = loadImage("world.topo.bathy.200406.3x5400x2700.jpg");
worldMap.resize(500, 300);
textureMode(NORMAL);
fill(255);
//stroke(color(44,48,32));
box = createShape(BOX, -650, 300, 0);
box.beginShape(BOX);
box.endShape();
box.setTexture(worldMap);
}
void draw() {
background(255);
fill(0);
titlesAndLegends();
display();
}
void mouseDragged() {
if((mouseX- pmouseX) > 0){
panLeft -= 5;
} else {
panLeft += 5;
}
if((mouseY-pmouseY) > 0) {
//panTop -=5;
}
}
void mouseWheel(MouseEvent event) {
float e = event.getCount();
if(e == 1) {
zoom += 30;
}else {
zoom -= 30;
}
}
public void display() {
camera(panLeft, zoom , zoom, panLeft, 0, panTop, 0, 1, 0); // eye(0, 600, 600) center(0, 0, 0) up(0, 1, 0)
pushMatrix();
translate(-350, 0, 0);
shape(box);
//box.scale(zoom);
popMatrix();
pushMatrix();
translate(350, 0, 0);
shape(box);
popMatrix();
pushMatrix();
// To use the center of the left box as center coordinates
translate(-350, 0, 0);
textSize(15);
for(int row = 1; row < rowCount; row++) {
float USALatitude = table.getFloat(row, 3);
float USALongitude = table.getFloat(row, 4);
String countryName = table.getString(row,1);
//Mapped Within X-axis of the left side map box
float mappedLatitude = map(USALatitude,-90, 90, 150, -150); // Latitude is in Y axis
float mappedLongitude = map(USALongitude, -180, 180, -325, 325); // Longitude in X Axis
x1 = mappedLongitude;
y1 = mappedLatitude;
z1 = 0;
float tripCountryLatitude = table.getFloat(row, 5);
float tripCountryLongitude = table.getFloat(row, 6);
float tripLatitude = map(tripCountryLatitude, -90, 90, 150, -150); // In Y, same mapping
float tripLongitude = map(tripCountryLongitude, -180, 180, 375, 1025);
// In X, having the position of the right box depending on the coordinates center
x2 = tripLongitude;
y2 = tripLatitude;
z2 = 0;
float zOff = map(0, 0, height, 300, 0); // Move mouse up and down
float a = (x2-x1)/3;
float b = (y2-y1)/3;
stroke(#F05252);
noFill();
bezier(x1, y1, z1, // First point
x1 + a, y1 + b, z1 + zOff, // First intermediate point
x2 - a, y2 - b, z2 + zOff, // Second intermediate point
x2, y2, z2);
//rotate(PI);
stroke(255);
fill(255);
text(countryName, x2-10, y2+10, z2+20);
}
popMatrix();
}
void titlesAndLegends() {
pushMatrix();
translate(-1000, -900, 0);
//rotateY(-PI/4);
textSize(50);
fill(0);
text("From USA to Other Parts of the World - 2019", width/2, 50);
popMatrix();
}
Code for table:
class Table {
int rowCount;
String[][] data;
Table(String filename) {
String[] rows = loadStrings(filename);
data = new String[rows.length][];
for (int i = 0; i < rows.length; i++) {
if (trim(rows[i]).length() == 0) {
continue; // skip empty rows
}
if (rows[i].startsWith("#")) {
continue; // skip comment lines
}
// split the row on the tabs
String[] pieces = split(rows[i], TAB);
// copy to the table array
data[rowCount] = pieces;
rowCount++;
// this could be done in one fell swoop via:
//data[rowCount++] = split(rows[i], TAB);
}
// resize the 'data' array as necessary
data = (String[][]) subset(data, 0, rowCount);
}
int getRowCount() {
return rowCount;
}
// find a row by its name, returns -1 if no row found
int getRowIndex(String name) {
for (int i = 0; i < rowCount; i++) {
if (data[i][0].equals(name)) {
return i;
}
}
println("No row named '" + name + "' was found");
return -1;
}
String getRowName(int row) {
return getString(row, 0);
}
String getString(int rowIndex, int column) {
return data[rowIndex][column];
}
String getString(String rowName, int column) {
return getString(getRowIndex(rowName), column);
}
int getInt(String rowName, int column) {
return parseInt(getString(rowName, column));
}
int getInt(int rowIndex, int column) {
return parseInt(getString(rowIndex, column));
}
float getFloat(String rowName, int column) {
return parseFloat(getString(rowName, column));
}
float getFloat(int rowIndex, int column) {
return parseFloat(getString(rowIndex, column));
}
void setRowName(int row, String what) {
data[row][0] = what;
}
void setString(int rowIndex, int column, String what) {
data[rowIndex][column] = what;
}
void setString(String rowName, int column, String what) {
int rowIndex = getRowIndex(rowName);
data[rowIndex][column] = what;
}
void setInt(int rowIndex, int column, int what) {
data[rowIndex][column] = str(what);
}
void setInt(String rowName, int column, int what) {
int rowIndex = getRowIndex(rowName);
data[rowIndex][column] = str(what);
}
void setFloat(int rowIndex, int column, float what) {
data[rowIndex][column] = str(what);
}
void setFloat(String rowName, int column, float what) {
int rowIndex = getRowIndex(rowName);
data[rowIndex][column] = str(what);
}
}
The links to my image files and datatable
https://drive.google.com/file/d/1sZw5Gc3xf_RTM_Ti6FeJ-_PVj49b0mlT/view
https://drive.google.com/file/d/111Tmhy6YDBQBmI2AaJuTPrrtdGZZpJSy/view

You should be able to isolate coordinate systems using pushMatrix()/popMatrix() calls. See the 2D Transformations tutorial as the same principle applies in 3D.
In your particular case it looks like display() is responsible for the 3D view.
You might want to isolate the camera() call into it's own coordinate system so it doesn't affect the legend:
public void display() {
// begin isolate camera transformed coordinate system
pushMatrix();
camera(panLeft, zoom , zoom, panLeft, 0, panTop, 0, 1, 0); // eye(0, 600, 600) center(0, 0, 0) up(0, 1, 0)
pushMatrix();
translate(-350, 0, 0);
shape(box);
//box.scale(zoom);
popMatrix();
pushMatrix();
translate(350, 0, 0);
shape(box);
popMatrix();
pushMatrix();
// To use the center of the left box as center coordinates
translate(-350, 0, 0);
textSize(15);
for(int row = 1; row < rowCount; row++) {
float USALatitude = table.getFloat(row, 3);
float USALongitude = table.getFloat(row, 4);
String countryName = table.getString(row,1);
//Mapped Within X-axis of the left side map box
float mappedLatitude = map(USALatitude,-90, 90, 150, -150); // Latitude is in Y axis
float mappedLongitude = map(USALongitude, -180, 180, -325, 325); // Longitude in X Axis
x1 = mappedLongitude;
y1 = mappedLatitude;
z1 = 0;
float tripCountryLatitude = table.getFloat(row, 5);
float tripCountryLongitude = table.getFloat(row, 6);
float tripLatitude = map(tripCountryLatitude, -90, 90, 150, -150); // In Y, same mapping
float tripLongitude = map(tripCountryLongitude, -180, 180, 375, 1025);
// In X, having the position of the right box depending on the coordinates center
x2 = tripLongitude;
y2 = tripLatitude;
z2 = 0;
float zOff = map(0, 0, height, 300, 0); // Move mouse up and down
float a = (x2-x1)/3;
float b = (y2-y1)/3;
stroke(#F05252);
noFill();
bezier(x1, y1, z1, // First point
x1 + a, y1 + b, z1 + zOff, // First intermediate point
x2 - a, y2 - b, z2 + zOff, // Second intermediate point
x2, y2, z2);
//rotate(PI);
stroke(255);
fill(255);
text(countryName, x2-10, y2+10, z2+20);
}
popMatrix();
// end isolate camera
popMatrix();
}
As an illustration, here's a modifider version of the camera() example, printing the transformation matrix before and after transformations:
size(100, 100, P3D);
noFill();
background(204);
// reset all transformations
resetMatrix();
println("before camera");
printMatrix();
pushMatrix();
camera(70.0, 35.0, 120.0, 50.0, 50.0, 0.0,
0.0, 1.0, 0.0);
println("after camera");
printMatrix();
translate(50, 50, 0);
rotateX(-PI/6);
rotateY(PI/3);
noFill();
box(45);
popMatrix();
// back to no transformations
printMatrix();
fill(0);
text("text2D",-50,-40,-100);

Related

How can add interaction and animation to shapes drawn in Processing?

I'm trying to code a canvas full of shapes(houses) and animate them in processing.
Here's an example of shape:
void house(int x, int y) {
pushMatrix();
translate(x, y);
fill(0, 200, 0);
triangle(15, 0, 0, 15, 30, 15);
rect(0, 15, 30, 30);
rect(12, 30, 10, 15);
popMatrix();
}
By animation I mean moving them in random directions.
I would also like to add basic interaction: when hovering over a house it's colour would change.
At the moment I've managed to render a canvas full of houses:
void setup() {
size(500, 500);
background(#74F5E9);
for (int i = 30; i < 500; i = i + 100) {
for (int j = 30; j < 500; j = j + 100) {
house(i, j);
}
}
}
void house(int x, int y) {
pushMatrix();
translate(x, y);
fill(0, 200, 0);
triangle(15, 0, 0, 15, 30, 15);
rect(0, 15, 30, 30);
rect(12, 30, 10, 15);
popMatrix();
}
Without seeing source code: your attempted sketch it's very hard to tell.
They can be animated in many ways and it's unclear what you mean. For example, is that the position/rotation/scale of each square, is it the corners/vertices of each square, both ?
You might have a clear idea in your mind, but the current form of the question is ambiguous. We also don't know you're comfort level with various notions such as classes/objects/PVector/PShape/etc. If you were to 'story board' this animation what would it look like ? Breaking the problem down and explaining it in a way that anyone can understand might actually help you figure out a solution on your own as well.
Processing has plenty of examples. Here are a few I find relevant based on what my understanding is of your problem.
You can have a look at the Objects and Create Shapes examples:
File > Examples > Basics > Objects > Objects: Demonstrates grouping drawing/animation (easing, damping). You can tweak this example draw a single square and once you're happy with the look/motion you can animate multiple using an array or ArrayList
File > Examples > Topics > Create Shapes > PolygonPShapeOOP3: Great example using PShape to animate objects.
File > Examples > Topics > Create Shapes > WigglePShape: This example demonstrates how to access and modify the vertices of a PShape
For reference I'm simply copy/pasting the examples mentioned above here as well:
Objects
/**
* Objects
* by hbarragan.
*
* Move the cursor across the image to change the speed and positions
* of the geometry. The class MRect defines a group of lines.
*/
MRect r1, r2, r3, r4;
void setup()
{
size(640, 360);
fill(255, 204);
noStroke();
r1 = new MRect(1, 134.0, 0.532, 0.1*height, 10.0, 60.0);
r2 = new MRect(2, 44.0, 0.166, 0.3*height, 5.0, 50.0);
r3 = new MRect(2, 58.0, 0.332, 0.4*height, 10.0, 35.0);
r4 = new MRect(1, 120.0, 0.0498, 0.9*height, 15.0, 60.0);
}
void draw()
{
background(0);
r1.display();
r2.display();
r3.display();
r4.display();
r1.move(mouseX-(width/2), mouseY+(height*0.1), 30);
r2.move((mouseX+(width*0.05))%width, mouseY+(height*0.025), 20);
r3.move(mouseX/4, mouseY-(height*0.025), 40);
r4.move(mouseX-(width/2), (height-mouseY), 50);
}
class MRect
{
int w; // single bar width
float xpos; // rect xposition
float h; // rect height
float ypos ; // rect yposition
float d; // single bar distance
float t; // number of bars
MRect(int iw, float ixp, float ih, float iyp, float id, float it) {
w = iw;
xpos = ixp;
h = ih;
ypos = iyp;
d = id;
t = it;
}
void move (float posX, float posY, float damping) {
float dif = ypos - posY;
if (abs(dif) > 1) {
ypos -= dif/damping;
}
dif = xpos - posX;
if (abs(dif) > 1) {
xpos -= dif/damping;
}
}
void display() {
for (int i=0; i<t; i++) {
rect(xpos+(i*(d+w)), ypos, w, height*h);
}
}
}
PolygonPShapeOOP3:
/**
* PolygonPShapeOOP.
*
* Wrapping a PShape inside a custom class
* and demonstrating how we can have a multiple objects each
* using the same PShape.
*/
// A list of objects
ArrayList<Polygon> polygons;
// Three possible shapes
PShape[] shapes = new PShape[3];
void setup() {
size(640, 360, P2D);
shapes[0] = createShape(ELLIPSE,0,0,100,100);
shapes[0].setFill(color(255, 127));
shapes[0].setStroke(false);
shapes[1] = createShape(RECT,0,0,100,100);
shapes[1].setFill(color(255, 127));
shapes[1].setStroke(false);
shapes[2] = createShape();
shapes[2].beginShape();
shapes[2].fill(0, 127);
shapes[2].noStroke();
shapes[2].vertex(0, -50);
shapes[2].vertex(14, -20);
shapes[2].vertex(47, -15);
shapes[2].vertex(23, 7);
shapes[2].vertex(29, 40);
shapes[2].vertex(0, 25);
shapes[2].vertex(-29, 40);
shapes[2].vertex(-23, 7);
shapes[2].vertex(-47, -15);
shapes[2].vertex(-14, -20);
shapes[2].endShape(CLOSE);
// Make an ArrayList
polygons = new ArrayList<Polygon>();
for (int i = 0; i < 25; i++) {
int selection = int(random(shapes.length)); // Pick a random index
Polygon p = new Polygon(shapes[selection]); // Use corresponding PShape to create Polygon
polygons.add(p);
}
}
void draw() {
background(102);
// Display and move them all
for (Polygon poly : polygons) {
poly.display();
poly.move();
}
}
// A class to describe a Polygon (with a PShape)
class Polygon {
// The PShape object
PShape s;
// The location where we will draw the shape
float x, y;
// Variable for simple motion
float speed;
Polygon(PShape s_) {
x = random(width);
y = random(-500, -100);
s = s_;
speed = random(2, 6);
}
// Simple motion
void move() {
y+=speed;
if (y > height+100) {
y = -100;
}
}
// Draw the object
void display() {
pushMatrix();
translate(x, y);
shape(s);
popMatrix();
}
}
WigglePShape:
/**
* WigglePShape.
*
* How to move the individual vertices of a PShape
*/
// A "Wiggler" object
Wiggler w;
void setup() {
size(640, 360, P2D);
w = new Wiggler();
}
void draw() {
background(255);
w.display();
w.wiggle();
}
// An object that wraps the PShape
class Wiggler {
// The PShape to be "wiggled"
PShape s;
// Its location
float x, y;
// For 2D Perlin noise
float yoff = 0;
// We are using an ArrayList to keep a duplicate copy
// of vertices original locations.
ArrayList<PVector> original;
Wiggler() {
x = width/2;
y = height/2;
// The "original" locations of the vertices make up a circle
original = new ArrayList<PVector>();
for (float a = 0; a < radians(370); a += 0.2) {
PVector v = PVector.fromAngle(a);
v.mult(100);
original.add(new PVector());
original.add(v);
}
// Now make the PShape with those vertices
s = createShape();
s.beginShape(TRIANGLE_STRIP);
s.fill(80, 139, 255);
s.noStroke();
for (PVector v : original) {
s.vertex(v.x, v.y);
}
s.endShape(CLOSE);
}
void wiggle() {
float xoff = 0;
// Apply an offset to each vertex
for (int i = 1; i < s.getVertexCount(); i++) {
// Calculate a new vertex location based on noise around "original" location
PVector pos = original.get(i);
float a = TWO_PI*noise(xoff,yoff);
PVector r = PVector.fromAngle(a);
r.mult(4);
r.add(pos);
// Set the location of each vertex to the new one
s.setVertex(i, r.x, r.y);
// increment perlin noise x value
xoff+= 0.5;
}
// Increment perlin noise y value
yoff += 0.02;
}
void display() {
pushMatrix();
translate(x, y);
shape(s);
popMatrix();
}
}
Update
Based on your comments here's an version of your sketch modified so the color of the hovered house changes:
// store house bounding box dimensions for mouse hover check
int houseWidth = 30;
// 30 px rect height + 15 px triangle height
int houseHeight = 45;
void setup() {
size(500, 500);
}
void draw(){
background(#74F5E9);
for (int i = 30; i < 500; i = i + 100) {
for (int j = 30; j < 500; j = j + 100) {
// check if the cursor is (roughly) over a house
// and render with a different color
if(overHouse(i, j)){
house(i, j, color(0, 0, 200));
}else{
house(i, j, color(0, 200, 0));
}
}
}
}
void house(int x, int y, color fillColor) {
pushMatrix();
translate(x, y);
fill(fillColor);
triangle(15, 0, 0, 15, 30, 15);
rect(0, 15, 30, 30);
rect(12, 30, 10, 15);
popMatrix();
}
// from Processing RollOver example
// https://processing.org/examples/rollover.html
boolean overRect(int x, int y, int width, int height) {
if (mouseX >= x && mouseX <= x+width &&
mouseY >= y && mouseY <= y+height) {
return true;
} else {
return false;
}
}
// check if the mouse is within the bounding box of a house
boolean overHouse(int x, int y){
// offset half the house width since the pivot is at the tip of the house
// the horizontal center
return overRect(x - (houseWidth / 2), y, houseWidth, houseHeight);
}
The code is commented, but here are the main takeaways:
the house() function has been changed so you can specify a color
the overRect() function has been copied from the Rollover example
the overHouse() function uses overRect(), but adds a horizontal offset to take into account the house is drawn from the middle top point (the house tip is the shape's pivot point)
Regarding animation, Processing has tons of examples:
https://processing.org/examples/sinewave.html
https://processing.org/examples/additivewave.html
https://processing.org/examples/noise1d.html
https://processing.org/examples/noisewave.html
https://processing.org/examples/arrayobjects.html
and well as the Motion / Simulate / Vectors sections:
Let's start take sine motion as an example.
The sin() function takes an angle (in radians by default) and returns a value between -1.0 and 1.0
Since you're already calculating positions for each house within a 2D grid, you can offset each position using sin() to animate it. The nice thing about it is cyclical: no matter what angle you provide you always get values between -1.0 and 1.0. This would save you the trouble of needing to store the current x, y positions of each house in arrays so you can increment them in a different directions.
Here's a modified version of the above sketch that uses sin() to animate:
// store house bounding box dimensions for mouse hover check
int houseWidth = 30;
// 30 px rect height + 15 px triangle height
int houseHeight = 45;
void setup() {
size(500, 500);
}
void draw(){
background(#74F5E9);
for (int i = 30; i < 500; i = i + 100) {
for (int j = 30; j < 500; j = j + 100) {
// how fast should each module move around a circle (angle increment)
// try changing i with j, adding i + j or trying other mathematical expressions
// also try changing 0.05 to other values
float phase = (i + frameCount) * 0.05;
// try changing amplitude to other values
float amplitude = 30.0;
// map the sin() result from it's range to a pixel range (-30px to 30px for example)
float xOffset = map(sin(phase), -1.0, 1.0, -amplitude, amplitude);
// offset each original grid horizontal position (i) by the mapped sin() result
float x = i + xOffset;
// check if the cursor is (roughly) over a house
// and render with a different color
if(overHouse(i, j)){
house(x, j, color(0, 0, 200));
}else{
house(x, j, color(0, 200, 0));
}
}
}
}
void house(float x, float y, color fillColor) {
pushMatrix();
translate(x, y);
fill(fillColor);
triangle(15, 0, 0, 15, 30, 15);
rect(0, 15, 30, 30);
rect(12, 30, 10, 15);
popMatrix();
}
// from Processing RollOver example
// https://processing.org/examples/rollover.html
boolean overRect(int x, int y, int width, int height) {
if (mouseX >= x && mouseX <= x+width &&
mouseY >= y && mouseY <= y+height) {
return true;
} else {
return false;
}
}
// check if the mouse is within the bounding box of a house
boolean overHouse(int x, int y){
// offset half the house width since the pivot is at the tip of the house
// the horizontal center
return overRect(x - (houseWidth / 2), y, houseWidth, houseHeight);
}
Read through the comments and try to tweak the code to get a better understanding of how it works and have fun coming up with different animations.
The main changes are:
modifying the house() function to use float x,y positions (instead of int): this is to avoid converting float to int when using sin(), map() and get smoother motions (instead of motion that "snaps" to whole pixels)
Mapped sine to positions which can be used to animate
Wrapping the 3 instructions that calculate the x offset into a reusable function would allow you do further experiment. What if you used a similar technique the y position of each house ? What about both x and y ?
Go through the code step by step. Try to understand it, change it, break it, fix it and make new sketches reusing code.

Processing - Multiple Image-Buttons That will Shape a Circle Together

I have several buttons that I created like this:
In the setup()
PImage[] imgs1 = {loadImage("AREA1_1.png"),loadImage("AREA1_2.png"),loadImage("AREA1_3.png")};
cp5.addButton("AREA_1") // The button
.setValue(128)
.setPosition(-60,7) // x and y relative to the group
.setImages(imgs1)
.updateSize()
.moveTo(AreaRingGroup); // add it to the group
;
After draw()
void AREA_1(){
println("AREA_1");
if (port != null){
port.write("a\n");
}
Now, I have to add about 24 small buttons from images that are going to be seen as 1 circle. But instead of adding them using cp5.addButton like above, I thought I should create a class.
I want to extend the existing CP5 button class because I want to be able to reach the cp5 parameters; like using .setImages() for 3 mouseClick conditions in the button images.
My problem is, I couldn't figure out
How to extend the CP5 button class, so that I can use angles (since I want to place buttons in a circular way) instead of giving x,y positions.
How to use serial port writings using this class, like the above void (AREA_1) example.
Here is my silly attempt:
class myButton extends Button
{
myButton(ControlP5 cp5, String theName)
{
super(cp5, cp5.getTab("default"), theName, 0,0,0, autoWidth, autoHeight);
}
PVector pos = new PVector (0,0);
PVector center = new PVector(500,500);
PImage[] img = new PImage[3];
String lable;
int sizeX = 10, sizeY=10;
color imgColor;
Boolean pressed = false;
Boolean clicked = false;
//Constructor
myButton(float a, String theName)
{
//Angle between the center I determined and the position of the
button.
a = degrees (PVector.angleBetween(center, pos));
//Every button will have 3 images for the 3 mouseClick conditions.
for (int i = 0; i < 2; ++i)
(img[i] = loadImage(lable + i + ".png")).resize(sizeX, sizeY);
}
}
Laancelot's idea isn't bad, however, due to controlP5's OOP architecture it might not be possible, or at least trivial, to extend the controlP5's Button class to achieve your goal from a controlP5 sketch.
I'm making the assumption this is connected to other LED ring related questions you posted around this time (such as this one).
It might be wrong (and I'd be interested in seeing a solution), but without forking/modifying the controlP5 library itself, the inheritance chain gets a bit tricky.
Ideally you'd just need to extend Button and be done with it, however the method used to tell states (if a button over/pressed/etc.) is the Controller superclass's inside() method. To work with your image you'd need override this method to and swap the logic so it takes into account the alpha transparency of your png button skin (e.g. if the pixel under the cursor is opaque then it's inside otherwise it's not)
Here's a rough illustration you were constrained to using a Processing sketch:
public abstract class CustomController< T > extends Controller< T> {
public CustomController( ControlP5 theControlP5 , String theName ) {
super( theControlP5 , theControlP5.getDefaultTab( ) , theName , 0 , 0 , autoWidth , autoHeight );
theControlP5.register( theControlP5.papplet , theName , this );
}
protected CustomController( final ControlP5 theControlP5 , final ControllerGroup< ? > theParent , final String theName , final float theX , final float theY , final int theWidth , final int theHeight ) {
super(theControlP5, theParent, theName, theX, theY, theWidth, theHeight);
}
boolean inside( ) {
/* constrain the bounds of the controller to the dimensions of the cp5 area, required since PGraphics as render
* area has been introduced. */
//float x0 = PApplet.max( 0 , x( position ) + x( _myParent.getAbsolutePosition( ) ) );
//float x1 = PApplet.min( cp5.pgw , x( position ) + x( _myParent.getAbsolutePosition( ) ) + getWidth( ) );
//float y0 = PApplet.max( 0 , y( position ) + y( _myParent.getAbsolutePosition( ) ) );
//float y1 = PApplet.min( cp5.pgh , y( position ) + y( _myParent.getAbsolutePosition( ) ) + getHeight( ) );
//return ( _myControlWindow.mouseX > x0 && _myControlWindow.mouseX < x1 && _myControlWindow.mouseY > y0 && _myControlWindow.mouseY < y1 );
// FIXME: add alpha pixel logic here
return true;
}
}
public class CustomButton<Button> extends CustomController< Button > {
protected CustomButton( ControlP5 theControlP5 , ControllerGroup< ? > theParent , String theName , float theDefaultValue , int theX , int theY , int theWidth , int theHeight ) {
super( theControlP5 , theParent , theName , theX , theY , theWidth , theHeight );
_myValue = theDefaultValue;
_myCaptionLabel.align( CENTER , CENTER );
}
// isn't this fun ?
//public CustomButton setImage( PImage theImage ) {
// return super.setImage( theImage , DEFAULT );
//}
}
I would suggest a simpler workaround: simply add smaller buttons in a ring layout (using polar to cartesian coordinate conversion):
import controlP5.*;
ControlP5 cp5;
void setup(){
size(600, 600);
background(0);
cp5 = new ControlP5(this);
int numLEDs = 24;
float radius = 240;
for(int i = 0; i < numLEDs; i++){
float angle = (TWO_PI / numLEDs * i) - HALF_PI;
cp5.addButton(nf(i, 2))
.setPosition(width * 0.5 + cos(angle) * radius, height * 0.5 + sin(angle) * radius)
.setSize(30, 30);
}
}
void draw(){}
Sure, not as pretty, but much much simpler.
Hopefully rendering the ring image drawn behind as a background and changing the button colour might be enough to get the point across.
It might be simpler to skip ControlP5 altogether and make your own button class for such a custom behaviour.
Let's say you want to have a square button in a ring layout that also rotates along the ring. This rotation will make checking the bounds tricker, but not impossible. You could hold track of the button's 2D transformation matrix using PMatrix2D) to render the button, but use the inverse of this transformation matrix to convert between Processing's global coordinate system to the button's local coordinate system. This would make checking if the button hovered trivial again (as the mouse converted to local coordinates would be within the button's bounding box). Here's an example:
TButton btn;
void setup(){
size(600, 600);
btn = new TButton(0, 300, 300, 90, 90, radians(45));
}
void draw(){
background(0);
btn.update(mouseX, mouseY, mousePressed);
btn.draw();
}
class TButton{
PMatrix2D transform = new PMatrix2D();
PMatrix2D inverseTransform;
int index;
int x, y, w, h;
float angle;
boolean isOver;
boolean isPressed;
PVector cursorGlobal = new PVector();
PVector cursorLocal = new PVector();
color fillOver = color(192);
color fillOut = color(255);
color fillOn = color(127);
TButton(int index, int x, int y, int w, int h, float angle){
this.index = index;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.angle = angle;
transform.translate(x, y);
transform.rotate(angle);
inverseTransform = transform.get();
inverseTransform.invert();
}
void update(int mx, int my, boolean mPressed){
cursorGlobal.set(mx, my);
inverseTransform.mult(cursorGlobal, cursorLocal);
isOver = ((cursorLocal.x >= 0 && cursorLocal.x <= w) &&
(cursorLocal.y >= 0 && cursorLocal.y <= h));
isPressed = mPressed && isOver;
}
void draw(){
pushMatrix();
applyMatrix(transform);
pushStyle();
if(isOver) fill(fillOver);
if(isPressed) fill(fillOn);
rect(0, 0, w, h);
popStyle();
popMatrix();
}
}
If you can draw one button, you can draw many buttons, in a ring layout:
int numLEDs = 24;
TButton[] ring = new TButton[numLEDs];
TButton selectedLED;
void setup(){
size(600, 600);
float radius = 240;
float ledSize= 30;
float offsetX = width * 0.5;
float offsetY = height * 0.5;
for(int i = 0 ; i < numLEDs; i++){
float angle = (TWO_PI / numLEDs * i) - HALF_PI;
float x = offsetX + (cos(angle) * radius);
float y = offsetY + (sin(angle) * radius);
ring[i] = new TButton(i, x, y, ledSize, ledSize, angle);
}
println("click to select");
println("click and drag to change colour");
}
void draw(){
background(0);
for(int i = 0 ; i < numLEDs; i++){
ring[i].update(mouseX, mouseY);
ring[i].draw();
}
}
void onButtonClicked(TButton button){
selectedLED = button;
println("selected", selectedLED);
}
void mouseReleased(){
for(int i = 0 ; i < numLEDs; i++){
ring[i].mouseReleased();
}
}
void mouseDragged(){
if(selectedLED != null){
float r = map(mouseX, 0, width, 0, 255);
float g = map(mouseY, 0, height, 0, 255);
float b = 255 - r;
selectedLED.fillColor = color(r, g, b);
}
}
class TButton{
PMatrix2D transform = new PMatrix2D();
PMatrix2D inverseTransform;
int index;
float x, y, w, h;
float angle;
boolean isOver;
PVector cursorGlobal = new PVector();
PVector cursorLocal = new PVector();
color fillColor = color(255);
TButton(int index, float x, float y, float w, float h, float angle){
this.index = index;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.angle = angle;
transform.translate(x, y);
transform.rotate(angle);
inverseTransform = transform.get();
inverseTransform.invert();
}
void update(int mx, int my){
cursorGlobal.set(mx, my);
inverseTransform.mult(cursorGlobal, cursorLocal);
isOver = ((cursorLocal.x >= 0 && cursorLocal.x <= w) &&
(cursorLocal.y >= 0 && cursorLocal.y <= h));
}
void mouseReleased(){
if(isOver) onButtonClicked(this);
}
void draw(){
pushMatrix();
applyMatrix(transform);
pushStyle();
if(isOver) {
fill(red(fillColor) * .5, green(fillColor) * .5, blue(fillColor) * .5);
}else{
fill(fillColor);
}
rect(0, 0, w, h);
popStyle();
popMatrix();
}
String toString(){
return "[TButton index=" + index + "]";
}
}
This isn't bad, but it easily gets complicated, right ?
What is you apply a global transformation in draw(): that may need to be handled by the button ? What if the button is nested in a bunch of pushMatrix()/popMatrix() calls when .draw() is called ? etc...
Could this be simpler ? Sure, the LEDs appear as rotated squares on a ring layout, however the important part that lights up is circular in shape. A rotated circle looks the same regardless of the angle. Checking if the mouse is over is trivial, as you can see in the Processing RollOver example : simply check if the distance between a circle's position and the cursor is smaller than the circle's radius.
Here's an example using circular buttons (removing the need for handling coordinate space transformations):
int numLEDs = 24;
CButton[] ring = new CButton[numLEDs];
CButton selectedLED;
void setup(){
size(600, 600);
float radius = 240;
float ledSize= 30;
float offsetX = width * 0.5;
float offsetY = height * 0.5;
for(int i = 0 ; i < numLEDs; i++){
float angle = (TWO_PI / numLEDs * i) - HALF_PI;
float x = offsetX + (cos(angle) * radius);
float y = offsetY + (sin(angle) * radius);
ring[i] = new CButton(i, x, y, ledSize);
}
println("click to select");
println("click and drag to change colour");
}
void draw(){
background(0);
for(int i = 0 ; i < numLEDs; i++){
ring[i].update(mouseX, mouseY);
ring[i].draw();
}
}
void onButtonClicked(CButton button){
selectedLED = button;
println("selected", selectedLED);
}
void mouseReleased(){
for(int i = 0 ; i < numLEDs; i++){
ring[i].mouseReleased();
}
}
void mouseDragged(){
if(selectedLED != null){
float r = map(mouseX, 0, width, 0, 255);
float g = map(mouseY, 0, height, 0, 255);
float b = 255 - r;
selectedLED.fillColor = color(r, g, b);
}
}
class CButton{
int index;
float x, y, radius, diameter;
boolean isOver;
color fillColor = color(255);
CButton(int index, float x, float y, float radius){
this.index = index;
this.x = x;
this.y = y;
this.radius= radius;
diameter = radius * 2;
}
void update(int mx, int my){
isOver = dist(x, y, mx, my) < radius;
}
void mouseReleased(){
if(isOver) onButtonClicked(this);
}
void draw(){
pushMatrix();
pushStyle();
if(isOver) {
fill(red(fillColor) * .5, green(fillColor) * .5, blue(fillColor) * .5);
}else{
fill(fillColor);
}
ellipse(x, y, diameter, diameter);
popStyle();
popMatrix();
}
String toString(){
return "[CButton index=" + index + "]";
}
}
Using these circular buttons on top of your NeoPixel ring image and perhaps with a bit of glow might look pretty good and simpler to achieve in a Processing sketch than taking the ControlP5 route. Don't get me wrong, it's a great library, but for custom behaviours it sometimes makes more sense to use your own custom code.
One other aspect to think is the overall interaction.
If this interface would be used to set the colors of an LED ring once, then it might ok, although it would take numLEDs * 3 interactions to set all the LEDs with a custom colour. If this was for a custom frame by frame LED animation tool then the interaction would get exhausting after a few frames.

Carousel of three ellipses in Processing

I am trying to create a vertical carousel in processing with three ellipses.
I am able to get this done with two ellipses – that the carousel repeats itself over and over.
So far so good – it thought I could use the same logic for three but I am wrong. I started to think it works with more variables but wrong again… what am I missing in the logic? I really can not figure out how to set the values to make it seamless repeating itself…
Here is the example with two (this one is "seamless"):
float yspeed = 5;
float circleY;
int d = 720;
void setup() {
size(1080, 1620 );
circleY = 0;
}
void draw() {
background(255);
fill(0);
noStroke();
ellipse(width/2, circleY+height/2, d,d);
ellipse(width/2, circleY-height/2, d,d );
circleY = circleY + yspeed;
if (circleY > height) {
circleY=0;}
}
Here is my WIP with three … (BTW colors are only to see better):
float yspeed1 = 5;
float yspeed2 = 5;
float yspeed3 = 5;
float circleY1;
float circleY2;
float circleY3;
int d = 720;
void setup() {
size(1080, 1620);
circleY1 = 0;
circleY2 = 0;
circleY3 = 0;
}
void draw() {
background(255);
noStroke();
fill(255, 0, 0);
ellipse(width/2, circleY1, d, d);
circleY1= circleY1 + yspeed1;
if (circleY1 > height+d/2 ) {
circleY1=0;
}
fill(0, 255, 0);
ellipse(width/2, circleY2-810, d, d);
circleY2= circleY2 + yspeed2;
if (circleY2 > height+d/2 ) {
circleY2=0 ;
}
fill(0, 0, 255);
ellipse(width/2, circleY2-1620, d, d);
circleY3= circleY3 + yspeed3;
if (circleY3 > height+d/2 ) {
circleY3=0 ;
}
}
The one with three always starts at the intital point for all of them – but I thought that using individual variables would change it?
Thank you for any kind of help!
I found a solution myself! I used a trick – I did it with a loop and used four instead of three – three is in the end the only ones you see anyway!
float yspeed = 1;
float circleY;
int d = 360;
void setup() {
size(540, 810 );
circleY = 0;
}
void draw() {
background(255);
fill(0);
noStroke();
// fill(255,0,0);
translate(0, - height/2);
for (int i = 0; i< 5; i++) {
ellipse(width/2, circleY+height/2*i, d, d);
}
circleY = circleY + yspeed;
if (circleY > 405) {
circleY= 0;
}
}

Moving Generated Images Down in Processing Sketch

I'm new to Processing and want to make the following sketch move images down, rather than up. I have tried adjusting the y to y+= speed in void move(), which does this but the images don't reload, they just do one cycle and the screen goes blank.
Your help would be appreciated :-)
Spot[] sp = new Spot[60];
PImage myImage;
/* #pjs preload="http://mathatelle.appspot.com/imgs/drawing_circle.png"; */
void setup() {
size(800, 400);
imageMode(CENTER);
myImage = loadImage("http://mathatelle.appspot.com/imgs/drawing_circle.png"); //100x100px
for (int i = 0; i < sp.length; i++) {
sp[i] = new Spot(random(width), random(height), random(0.2,1.0), random(0.1,1.0));
}
}
void draw() {
background(0);
for (int i = 0; i < sp.length; i++) {
sp[i].move();
sp[i].display();
}
}
class Spot {
float x, y, diameter, speed; // x座標, y座標, 直径, 速さ
Spot(float _x, float _y, float _diameter, float _speed) {
x = _x;
y = _y;
diameter = _diameter;
speed = _speed;
}
void move() {
speed *= 1.01;
y -= speed;
if (y < - myImage.width*diameter/2) {
x = random(width);
y = height + myImage.width*diameter/2;
speed = random(0.5,3);
}
}
void display() {
pushMatrix();
translate(x, y);
rotate(TWO_PI*diameter);
scale(diameter);
//tint(255, 255, 255, 153);
image(myImage, 0, 0);
popMatrix();
}
}
The logical is kind of inverted already,but just invert it again :)
here there are comments in the code:
Spot[] sp = new Spot[60];
PImage myImage;
/* #pjs preload="http://mathatelle.appspot.com/imgs/drawing_circle.png"; */
void setup() {
size(800, 400);
imageMode(CENTER);
myImage = loadImage("http://mathatelle.appspot.com/imgs/drawing_circle.png"); //100x100px
for (int i = 0; i < sp.length; i++) {
// a little change where spots are created...
sp[i] = new Spot(random(width), random(-myImage.height, height - 100), random(0.2, 1.0), random(0.1, 1.0));
}
}
void draw() {
background(0);
for (int i = 0; i < sp.length; i++) {
sp[i].move();
sp[i].display();
}
}
class Spot {
float x, y, diameter, speed; // x座標, y座標, 直径, 速さ
Spot(float _x, float _y, float _diameter, float _speed) {
x = _x;
y = _y;
diameter = _diameter;
speed = _speed;
}
void move() {
speed *= 1.01;
// invert movement direction
y += speed;
// also invert the test...
if (y > height + myImage.width*diameter/2) {
x = random(width);
y = height + myImage.width*diameter/2;
speed = random(0.5, 3);
}
}
void display() {
pushMatrix();
translate(x, y);
rotate(TWO_PI*diameter);
scale(diameter);
//tint(255, 255, 255, 153);
image(myImage, 0, 0);
popMatrix();
}
}
A friend helped me out, this is the solution to my problem :-)
Spot[] sp = new Spot[60];
PImage myImage;
/* #pjs preload="http://mathatelle.appspot.com/imgs/drawing_circle.png"; */
void setup() {
size(800, 400);
imageMode(CENTER);
myImage = loadImage("http://mathatelle.appspot.com/imgs/drawing_circle.png"); //100x100px
for (int i = 0; i < sp.length; i++) {
// a little change where spots are created...
sp[i] = new Spot(random(width), random(-myImage.height, height - 100), random(0.2, 1.0), random(0.1, 1.0));
}
}
void draw() {
background(0);
for (int i = 0; i < sp.length; i++) {
sp[i].move();
sp[i].display();
}
}
class Spot {
float x, y, diameter, speed; // x座標, y座標, 直径, 速さ
Spot(float _x, float _y, float _diameter, float _speed) {
x = _x;
y = _y;
diameter = _diameter;
speed = _speed;
}
void move() {
speed *= 1.01;
y += speed;
// change y = 0 - ...
if (y > height + myImage.width*diameter/2) {
x = random(width);
y = 0 - myImage.width*diameter/2;
speed = random(0.5, 3);
}
}
void display() {
pushMatrix();
translate(x, y);
rotate(TWO_PI*diameter);
scale(diameter);
//tint(255, 255, 255, 153);
image(myImage, 0, 0);
popMatrix();
}
}

Processing: Creating shapes that change size through draw loops

I am trying to create a visualization where the nodes of my network change size in a loop as the visualization progresses (I have stripped out the interactions between the nodes for simplicity here). I have the array sizes that is looped over in the draw function with index j. I am not sure why the nodes are not changing size. Any insight into this problem would be appreciated.
int numBalls = 5;
Ball[] balls = new Ball[numBalls];
float[] sizes = {15,25,35,45,55,65};
void setup() {
size(800, 400);
int l = 0 ;
for (int i = 0; i < numBalls; i++) {
balls[i] = new Ball(random(width),random(height), random(30, 50), i, balls);
}
noStroke();
fill(255, 204);
}
void draw() {
background(0);
for (int j = 0; j < 6; j++){
for (int i = 0; i < numBalls; i++) {
print("\nNEW ID\n");
print(i);
print("\n");
print("Diameter in balls\n");
print(balls[i].diameter);
print("\n");
balls[i].diameter = sizes[j];
print("Diameter in balls after fix\n");
print(balls[i].diameter);
balls[i].display();
}
}
}
class Ball {
float x, y;
float diameter;
float mass;
float vx = 0;
float vy = 0;
int id;
Ball[] others;
Ball(float xin, float yin, float din, int idin, Ball[] oin) {
x = xin;
y = yin;
diameter = din;
mass = 50;
id = idin;
others = oin;
}
void display() {
textSize(32);
fill(0,255,0,255);
print("\nDiameter in display\n");
print(diameter);
print("\n");
ellipse(x, y, diameter, diameter);
print("\nDiameter in display\n");
print(diameter);
print("\n");
fill(255, 0, 0, 255);
text(id,x,y);
}
}
The thing is, in your draw() function you are running over the array of sizes with the first for-loop and assigning the value of that size to the balls. This way in each draw() you subsequently attach each size on each ball, and every time the size you attach overwrites the previous one... Remember, the window of Processing only refreshes after the draw() has finished! Instead of looping over all the sizes in each draw() you probably want a different size in each draw(). So a way to do that would be:
int numBalls = 5;
int sizeCounter = 0;
int everySoManyFramesChange = 3;
Ball[] balls = new Ball[numBalls];
float[] sizes = {
15, 25, 35, 45, 55, 65
};
void setup() {
size(800, 400);
int l = 0 ;
for (int i = 0; i < numBalls; i++) {
balls[i] = new Ball(random(width), random(height), random(30, 50), i, balls);
}
noStroke();
fill(255, 204);
}
void draw() {
background(0);
for (int i = 0; i < numBalls; i++) {
balls[i].diameter = sizes[sizeCounter];
balls[i].display();
}
if (frameCount%everySoManyFramesChange == 0) sizeCounter=(sizeCounter+1)%sizes.length;
}
class Ball {
float x, y;
float diameter;
float mass;
float vx = 0;
float vy = 0;
int id;
Ball[] others;
Ball(float xin, float yin, float din, int idin, Ball[] oin) {
x = xin;
y = yin;
diameter = din;
mass = 50;
id = idin;
others = oin;
}
void display() {
textSize(32);
fill(0, 255, 0, 255);
ellipse(x, y, diameter, diameter);
fill(255, 0, 0, 255);
text(id, x, y);
}
}
By the way I removed all those print statements because they are making the sketch horribly slow, but be my guest and re-introduce them!

Resources