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.
Related
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.
Can anyone help me?
So, I'm supposed to have a ball that is moving horizontally, such that every time I press the mouse, a ball would get shoot vertically, then slows down due to friction. The vertical ball would stay in the old position but the player would reset.
How do I go about doing that without using classes?
Here my code so far:
boolean circleupdatetostop = true;
float x = 100;
float yshot = 880;
float speedshot = random(4,10);
float speedx = 6;
void setup() {
size(1280,960);
}
void draw() {
background(255);
stroke(0);
fill(0);
circle(x,880,80);
if (x > width || x < 0 ) {
speedx = speedx * -1;
}
if (circleupdatetostop) {
x = x + speedx;
}
if (circleupdatetostop == false) {
float locationx = x;
stroke(0);
fill(255,0,255);
circle(locationx,yshot,30);
yshot = yshot - speedshot;
}
}
void mousePressed () {
circleupdatetostop = !circleupdatetostop;
}
I'm not entirely sure if this is what you meant, but you could achieve shooting multiple balls by using ArrayList as well as processing's PVector to better handle the x and y coordinate pairs. If you wanted to look at classes, see this post.
import java.util.*;
// Whether the ball is moving or not
boolean circleupdatetostop = true;
// Information about the main_ball
PVector position = new PVector(100, 880);
PVector speed = new PVector(6, 0);
float diameter = 80;
// Information about the sot balls
ArrayList<PVector> balls_loc = new ArrayList<PVector>();
ArrayList<PVector> balls_speed = new ArrayList<PVector>();
float diameter_shot = 30;
float friction = 0.994;
void setup() {
size(1280, 960);
}
void draw() {
background(255);
stroke(0);
fill(0);
circle(position.x, position.y, diameter);
// Remember to consider the radius of the ball when bouncing off the edges
if (position.x + diameter/2 > width || position.x - diameter/2 < 0 ) {
speed.mult(-1);
}
if (circleupdatetostop) {
position.add(speed);
}
// Cycle through the list updating their speed and draw each ball
for (int i = 0; i<balls_loc.size(); i++) {
balls_speed.get(i).mult(friction+random(-0.05, 0.05));
balls_loc.get(i).add(balls_speed.get(i));
stroke(0);
fill(255, 0, 255);
circle(balls_loc.get(i).x, balls_loc.get(i).y, diameter_shot);
}
}
void mousePressed(){
// Add a new ball to be drawn
if(circleupdatetostop){
balls_loc.add(new PVector(position.x, position.y));
balls_speed.add(new PVector(0, random(-4, -10)));
}
circleupdatetostop = !circleupdatetostop;
}
I am working on making a single ball bounce all over the screen but I have an issue with making the ball bounce the bounce() function is responsible for making the ball bounce but I run into an array out of bounds exception despite only going through 1 element in the array. I have provided the code below. This code utilizes 2 classes (Ball_Usage and Ball).
Ball_Usage code:
Ball ball1;
void setup(){
size(500,500);
ball1 = new Ball();
}
void draw(){
background(255);
ball1.bounce();
}
Ball:
class Ball{
float [] xPos = {};
float [] yPos = {};
float [] dia = {};
float [] xSpeed = {};
float [] ySpeed = {};
Ball(){
}
Ball(float x, float y, float argDia){
xPos = append(xPos, x);
yPos = append(yPos, y);
dia = append(dia, argDia);
}
void bounce(){
for(int i=0; i<1; i++){
ellipse(xPos[i], yPos[i], 50, 50);
xPos[i] += xSpeed[i];
yPos[i] += ySpeed[i];
if(xPos[i]<0 || xPos[i]>=width){
xSpeed[i] = -xSpeed[i];
}
if(yPos[i]<0 || yPos[i]>=height){
ySpeed[i] = -ySpeed[i];
}
}
}
}
I believe the confusion comes from the fact that your class has two constructors:
an empty constructor (that takes no arguments): Ball()
a constructor with position and argDia (guessing diameter ?) arguments: Ball(float x, float y, float argDia)
In setup() you call the empty constructor:
ball1 = new Ball();
This means the five float arrays still have a length of 0, hence the out of bounds exception.
Even if you call the position + diameter version of the constructor, the xSpeed, ySpeed arrays will still have length 0.
You could fix this by initialising the two two arrays as well as using this version of the constructor:
Ball ball1;
void setup() {
size(500, 500);
//ball1 = new Ball();
ball1 = new Ball(250, 250, 50);
}
void draw() {
background(255);
ball1.bounce();
}
class Ball {
float [] xPos = {};
float [] yPos = {};
float [] dia = {};
float [] xSpeed = {};
float [] ySpeed = {};
Ball() {
}
Ball(float x, float y, float argDia) {
xPos = append(xPos, x);
yPos = append(yPos, y);
dia = append(dia, argDia);
xSpeed = append(xSpeed, random(-1, 1));
ySpeed = append(ySpeed, random(-1, 1));
}
void bounce() {
for (int i=0; i<1; i++) {
ellipse(xPos[i], yPos[i], 50, 50);
xPos[i] += xSpeed[i];
yPos[i] += ySpeed[i];
if (xPos[i]<0 || xPos[i]>=width) {
xSpeed[i] = -xSpeed[i];
}
if (yPos[i]<0 || yPos[i]>=height) {
ySpeed[i] = -ySpeed[i];
}
}
}
}
This will work, however there is still a bit of confusion: why use the arrays at all if you're only looping once for the first element ? It makes the arrays and for loop mostly redundant.
You might choose to keep if you plan to change the diameter over time (which in your code is hardcoded to 50), maybe positions and velocities and render a changing history of a ball.
If you don't, you could simply use float properties instead of arrays:
Ball ball1;
void setup() {
size(500, 500);
ball1 = new Ball();
}
void draw() {
background(255);
ball1.bounce();
}
class Ball {
float xPos;
float yPos;
float diameter = 50;
float xSpeed;
float ySpeed;
Ball() {
xPos = width / 2;
yPos = height / 2;
xSpeed = random(-1, 1);
ySpeed = random(-1, 1);
}
void bounce() {
ellipse(xPos, yPos, diameter, diameter);
xPos += xSpeed;
yPos += ySpeed;
if (xPos < 0 || xPos >= width) {
xSpeed = -xSpeed;
}
if (yPos < 0 || yPos >= height) {
ySpeed = -ySpeed;
}
}
}
This looks more similar to the Bounce Processing Example.
You can at a later stage make an array of Ball objects.
Additionally it's worth formatting code as it saves you time reading/scrolling through it and visually it's easier to scan the structure of the program (how each part fits in) and therefore makes it easier to debug/run mentally. It takes no effort as you can simply press Ctrl+T on Windows/Linux or CMD+T on OSX. On the long run this will pay off, especially as programs get longer and more complex as you spend more time reading code than writing code. It's a good habit to pickup early while learning to code. Have fun!
My problem is as in the title. I am trying to write a simple game in processing with a car that you can drive on a 2D plane. I wanted to create a rotation of the car since it seems crucial so I did it as described here:Rotating points in 2D
But my implementation seems to fail a bit. You see, when I hit left of right arrow the car actually rotates but shrinks in size as it is rotating and after few turns it completely dissapears. Can you show me what am I missing here? Thanks in advance! Code of my functions:
class Point
{
float x, y;
Point(float xx, float yy)
{
x = xx;
y = yy;
}
Point()
{
x = y = 0.0;
}
void Rotate(Point center, float angle)
{
float s = sin(angle);
float c = cos(angle);
y = center.y + ((y-center.y) * c + (x-center.x) * s);
x = center.x + ((x-center.x) * c - (y-center.y) * s);
}
}
class Car
{
Point LT;
Point RT;
Point LB;
Point RB;
Point center;
float r;
float acceleration;
Car()
{
LT = new Point(10, 10);
RT = new Point (30, 10);
LB = new Point(10, 50);
RB = new Point(30, 50);
r = sqrt(pow(15-30, 2) + pow(25-10, 2));
}
Car(Point lt, Point rt, Point lb, Point rb)
{
LT = lt;
RT = rt;
LB = lb;
RB = rb;
center = new Point(abs((LT.x - RT.x)/2), abs((LT.y - LB.y)/2));
r = sqrt(pow(center.x -LT.x, 2) + pow(center.y - LT.y, 2));
}
Car(Point Center, float w, float h)
{
center = Center;
LT = new Point(center.x - w/2, center.y - h/2);
RT = new Point (center.x + w/2, center.y - h/2);
LB = new Point(center.x - w/2, center.y + h/2);
RB = new Point(center.x + w/2, center.y + h/2);
r = sqrt(pow(center.x -LT.x, 2) + pow(center.y - LT.y, 2));
}
void Show()
{
fill(45, 128, 156);
beginShape();
vertex(LT.x, LT.y);
vertex(RT.x, RT.y);
vertex(RB.x, RB.y);
vertex(LB.x, LB.y);
endShape();
}
void Update()
{
}
void Turn(float angle)
{
LT.Rotate(center, angle);
RT.Rotate(center, angle);
RB.Rotate(center, angle);
LB.Rotate(center, angle);
}
void Accelerate(float accel)
{
}
}
In main I only use car.Show() and I turn by -0.1 per left cliock and 0.1 per right click
EDIT
If you want to see whole code visit my github repo
Unfortunately I can't explain more at the moment, but here's a simpler option using one of the formulas you've pointed to:
Car car = new Car();
void setup(){
size(300,300);
// this helps draw rectangles from centre (as opposed to corner (default))
rectMode(CENTER);
car.position.set(150,150);
}
void draw(){
background(255);
if(keyPressed){
if(keyCode == UP){
car.speed = 1;
}
}
car.draw();
}
void keyPressed(){
if(keyCode == LEFT){
car.steer -= radians(10);
}
if(keyCode == RIGHT){
car.steer += radians(10);
}
}
void keyReleased(){
if(keyCode == UP){
car.speed = 0;
}
}
class Car{
PVector position = new PVector();
PVector velocity = new PVector();
float speed;
float steer;
void update(){
// use the same polar to cartesian coordinates formulate for quick'n'dirty steering
velocity.set(cos(steer) * speed,sin(steer) * speed);
// update position based on velocity
position.add(velocity);
}
void draw(){
update();
// use a nested coordinate system to handle translation and rotation for us
// order of operations is important
pushMatrix();
translate(position.x,position.y);
rotate(steer);
rect(0,0,30,15);
popMatrix();
}
}
Update
The main issue with points shrinking is you're cumulatively transforming the points when you rotate them. After each transformation there is no history of what the x,y were. Instead you should return a new point that is transformed, thus "remembering" the old x,y position.
Bellow is a tweaked version of your code, minus the two constructor variants.
Hopefully the comments will help:
Car car = new Car();
void setup(){
size(300,300);
}
void draw(){
if(keyCode == UP){
if(keyPressed){
car.Accelerate(1);
}else{
car.Accelerate(0);
}
}
car.Update();
background(255);
car.Show();
}
void keyPressed(){
if(keyCode == LEFT){
car.Turn(radians(-3));
}
if(keyCode == RIGHT){
car.Turn(radians(+3));
}
}
class Point
{
float x, y;
Point(float xx, float yy)
{
x = xx;
y = yy;
}
Point()
{
x = y = 0.0;
}
Point Rotate(Point center, float angle)
{
float s = sin(angle);
float c = cos(angle);
// return a new point (a rotated copy), rather than overwriting this one
return new Point(center.x + ((x-center.x) * c - (y-center.y) * s),
center.y + ((y-center.y) * c + (x-center.x) * s));
}
// translate by another point
void AddToSelf(Point point){
this.x += point.x;
this.y += point.y;
}
// pretty print info when using println()
String toString(){
return "[Point x=" + x + " y="+ y +"]";
}
}
class Car
{
Point LT;
Point RT;
Point LB;
Point RB;
Point center;
float r;
float acceleration;
// car angle: used to compute velocity and update vertices
float angle;
// car position: used to offset rendering position of the corners
Point position;
// car velocity: amount by which position translates
Point velocity = new Point();
Car()
{
float x = 10;
float y = 10;
float w = 40;
float h = 20;
// setup corners with no translation
LT = new Point(0 , 0 );
RT = new Point(0 + w, 0 );
LB = new Point(0 , 0 + h);
RB = new Point(0 + w, 0 + h);
// setup initial position
position = new Point(x,y);
center = new Point(w / 2, h / 2);
r = sqrt(pow(15-30, 2) + pow(25-10, 2));
}
//Car(Point lt, Point rt, Point lb, Point rb)
//{
// LT = lt;
// RT = rt;
// LB = lb;
// RB = rb;
// center = new Point(abs((LT.x - RT.x)/2), abs((LT.y - LB.y)/2));
// r = sqrt(pow(center.x -LT.x, 2) + pow(center.y - LT.y, 2));
//}
//Car(Point Center, float w, float h)
//{
// center = Center;
// LT = new Point(center.x - w/2, center.y - h/2);
// RT = new Point (center.x + w/2, center.y - h/2);
// LB = new Point(center.x - w/2, center.y + h/2);
// RB = new Point(center.x + w/2, center.y + h/2);
// r = sqrt(pow(center.x -LT.x, 2) + pow(center.y - LT.y, 2));
//}
void Show()
{
fill(45, 128, 156);
beginShape();
// render corners offset by the car position
vertex(position.x + LT.x, position.y + LT.y);
vertex(position.x + RT.x, position.y + RT.y);
vertex(position.x + RB.x, position.y + RB.y);
vertex(position.x + LB.x, position.y + LB.y);
endShape(CLOSE);
}
void Update()
{
// update velocity based on car angle and acceleration
velocity.x = cos(angle) * acceleration;
velocity.y = sin(angle) * acceleration;
// update position based on velocity
position.AddToSelf(velocity);
}
void Turn(float angle)
{
this.angle += angle;
// replace the old point with the transformed points
// (rather than continuosly transforming the same point)
LT = LT.Rotate(center, angle);
RT = RT.Rotate(center, angle);
RB = RB.Rotate(center, angle);
LB = LB.Rotate(center, angle);
}
void Accelerate(float accel)
{
acceleration = accel;
}
}
I have a grid of ellipses generated by two for() loops. What I'd like to do is have all of these ellipses ease into mouseX and mouseY when mousePressed == true, otherwise return to their position in the grid. How can I go about this? I've started with this template, which doesn't work as I can't figure out how affect the position of the ellipses, but the easing is set up.
float x;
float y;
float easeIn = 0.01;
float easeOut = 0.08;
float targetX;
float targetY;
void setup() {
size(700, 700);
pixelDensity(2);
background(255);
noStroke();
}
void draw() {
fill(255, 255, 255, 80);
rect(0, 0, width, height);
for (int i = 50; i < width-50; i += 30) {
for (int j = 50; j < height-50; j += 30) {
fill(0, 0, 0);
ellipse(i, j, 5, 5);
if (mousePressed == true) {
// go to mouseX
targetX = mouseX;
// ease in
float dx = targetX - x;
if (abs(dx) > 1) {
x += dx * easeIn;
}
//go to mouseY
targetY = mouseY;
// ease in
float dy = targetY - y;
if (abs(dy) > 1) {
y += dy * easeIn;
}
} else {
// return to grid
targetX = i;
// ease out
float dx = targetX - x;
if (abs(dx) > 1) {
x += dx * easeOut;
}
// return to grid
targetY = j;
// ease out
float dy = targetY - y;
if (abs(dy) > 1) {
y += dy * easeOut;
}
}
}
}
}
Any help would be greatly appreciated. I'm not sure in which order to do things/which elements should be contained in the loop.
Thanks!
You're going to have to keep track of a few things for each dot: its "home" position, its current position,its speed, etc.
The easiest way to do this would be to create a class that encapsulates all of that information for a single dot. Then you'd just need an ArrayList of instances of the class, and iterate over them to update or draw them.
Here's an example:
ArrayList<Dot> dots = new ArrayList<Dot>();
void setup() {
size(700, 700);
background(255);
noStroke();
//create your Dots
for (int i = 50; i < width-50; i += 30) {
for (int j = 50; j < height-50; j += 30) {
dots.add(new Dot(i, j));
}
}
}
void draw() {
background(255);
//iterate over your Dots and move and draw each
for (Dot dot : dots) {
dot.stepAndRender();
}
}
class Dot {
//the point to return to when the mouse is not pressed
float homeX;
float homeY;
//current position
float currentX;
float currentY;
public Dot(float homeX, float homeY) {
this.homeX = homeX;
this.homeY = homeY;
currentX = homeX;
currentY = homeY;
}
void stepAndRender() {
if (mousePressed) {
//use a weighted average to chase the mouse
//you could use whatever logic you want here
currentX = (mouseX+currentX*99)/100;
currentY = (mouseY+currentY*99)/100;
} else {
//use a weighted average to return home
//you could use whatever logic you want here
currentX = (homeX+currentX*99)/100;
currentY = (homeY+currentY*99)/100;
}
//draw the ellipse
fill(0, 0, 0);
ellipse(currentX, currentY, 5, 5);
}
}
Note that I'm just using a weighted average to determine the position of each ellipse, but you could change that to whatever you want. You could give each ellipse a different speed, or use your easing logic, whatever. But the idea is the same: encapsulate everything you need into a class, and then put the logic for one dot into that class.
I'd recommend getting this working for a single dot first, and then moving up to getting it working with multiple dots. Then if you have another question you can post the code for just a single dot instead of a bunch. Good luck.