Trouble with particle attraction using PVectors - vector

I'm making a particle-attractor-thing in Processing 3.0, and I'm having difficulty when adding multiples of what I've deemed 'Nodes'; objects that automatically attract or repel particles without mouse input. Normally left-clicking the mouse attracts these particles and right-clicking repels them. I made Nodes so that I could have multiple locations attracting/repelling at once.
The main problem I'm having atm is when I add more than one Node, only the last-added one actually works; all others do nothing.
Here is all my code including two other classes (Particle and Node). It's a lot of code, but I'd really appreciate it if someone could point out my error(s).
http://pastebin.com/iKELuVJ7
I believe the trouble is in the Particle class in the Node for loop where I am setting acc = d, but I don't know a good workaround.
As an additional question, does anyone know a good way to make the strength of the attraction scale inversely with distance from the attracting object? I tried a backwards map() function with arbitrary upper/lower limits which works for now, but I'd prefer not to hardcode it. Any other general improvements/recommendations are appreciated.

That's a nice looking sketch. You are totally right about acc = d.
This part:
for (Node n: nodes) {
PVector d = PVector.sub(n.loc, loc);
d.normalize();
d.mult(n.strength);
acc = d;
}
means you're overwriting the acc vector which d.
Since you run loop, in the end acc will be equal to the last node in your list.
If you want each node to have a certain influence, simply add the node based vector to acc:
for (Node n: nodes) {
PVector d = PVector.sub(n.loc, loc);
d.normalize();
d.mult(n.strength);
acc.add(d);
}
You did this already on the mouse interaction.
In terms of other recommendations, you could try to cache PVector instances more often. Bellow I supply a basic example of how to cache the mousePosition (as a single top level variable, rather than multiple instances, per particle, per frame). mag() uses sqrt() which can be costly. Try using magSq() instead (and using squared ranges instead). You can spot these this in chapter 6.15 A Few Last Notes: Optimization Tricks of Nature of Code by Daniel Shiffman. I warmly recommend this book.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
PVector m = new PVector(mouseX, mouseY);
ArrayList<Particle> parts = new ArrayList<Particle>();
ArrayList<Node> nodes = new ArrayList<Node>();
float speed = 2;
float grav = 4;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup() {
size(800, 800, P2D);
// fullScreen(P2D);
noStroke();
smooth(4);
blendMode(ADD);
frameRate(60);
for (int i = 0; i < 1000; i++) {
parts.add(new Particle(new PVector(random(width), random(height)), (int)random(1, 25)));
}
nodes.add(new Node(new PVector(200, 400), 0.75));
nodes.add(new Node(new PVector(400, 400), 0.50));
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void draw() {
background(0);
if(mousePressed){
m.set(mouseX, mouseY);
}
for (Node n: nodes) {
n.draw();
}
for (int i = 0; i < parts.size(); i++) {
Particle p = (Particle) parts.get(i);
p.draw();
p.boundary();
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class Particle {
PVector loc, vel, acc;
int size = 5;
Particle(PVector loc, int size) {
this.loc = loc;
vel = new PVector();
acc = new PVector();
this.size = size;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void draw() {
acc.mult(0);
for (Node n: nodes) {
PVector d = PVector.sub(n.loc, loc);
d.normalize();
d.mult(n.strength);
//acc = d;
acc.add(d);
}
if (mousePressed && mouseButton == LEFT) {
PVector d = PVector.sub(m, loc);
//d.normalize();
d.setMag(map(d.mag(), 0, 2200, speed, 0));
d.mult(speed);
//acc = d;
acc.add(d);
} else if (mousePressed && mouseButton == RIGHT) {
PVector d = PVector.sub(m, loc);
d.normalize();
d.mult(-1*speed);
//acc = d;
acc.add(d);
}
acc.set(acc);
acc.mult(acc.mag());
vel.add(acc);
//vel.add(new PVector(0, grav));
loc.add(vel);
acc.mult(0);
//vel.mult(pow(0.90, 0.1*size));
vel.mult(0.97);
colorScheme(0);//"RedYellow"
ellipse(loc.x, loc.y, size, size);
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void boundary() {
if(loc.x < 0)
vel.x *= -1;
if(loc.x > width)
vel.x *= -1;
if(loc.y < 0)
vel.y *= -1;
if(loc.y > height)
vel.y *= -1;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
String[] patterns = {"RedYellow","RedMagenta","GreenYellow","GreenCyan","BlueMagenta","BlueCyan","Cyclic","BWCyclic","PROTOTYPE"};
//void colorScheme(String pattern) {
void colorScheme(int pattern) {
//compute this once, rather than multiple times per case
//also check out magSq() https://processing.org/reference/PVector_magSq_.html
float mag = vel.mag();
switch (pattern) {
case 0:
fill(255, map(mag, 0, 20, 0, 255), map(mag - 20, 0, 20, 0, 255)); //Red Yellow White
break;
case 1:
fill(255, map(mag - 20, 0, 20, 0, 255), map(mag, 0, 20, 0, 255)); //Red Magenta White
break;
case 2:
fill(map(mag, 0, 20, 0, 255), 255, map(mag - 20, 0, 20, 0, 255)); //Green Yellow White
break;
case 3:
fill(map(mag - 20, 0, 20, 0, 255), 255, map(mag, 0, 20, 0, 255)); //Green Cyan White
break;
case 4:
fill(map(mag, 0, 20, 0, 255), map(mag - 20, 0, 20, 0, 255), 255); //Blue Magenta White
break;
case 5:
fill(map(mag - 20, 0, 20, 0, 255), map(mag, 0, 20, 0, 255), 255); //Blue Cyan White
break;
case 6:
fill(cos(map(mag, 0, 20, 0, 360))*255, cos(map(mag + 20/3, 0, 20, 0, 360))*255, cos(map(mag + 40/3, 0, 20, 0, 360))*255); //Cyclic
break;
case 7:
fill(cos(map(mag, 0, 20, 0, 360))*255, cos(map(mag, 0, 20, 0, 360))*255, cos(map(mag, 0, 20, 0, 360))*255); //B&W Cyclic
break;
case 8:
fill(0, 0, 0); //Cyclic
break;
default:
stroke(255, 255, 255);
fill(255, 255, 255);
break;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
class Node {
PVector loc;
float strength;
Node(PVector loc, float strength) {
this.loc = loc;
this.strength = strength;
}
void draw() {
fill(255, 255, 255,64);
ellipse(loc.x, loc.y, 50, 50);
}
}

Related

Get selected index from 2d array grid in Processing

I want to create a peg solitaire game based on a 2d array in Processing.
I've succeeded to create the board and pawns.
Now i want to get the index from a pawn by clicking on it and be able to move it around the board.
Here is what i have now to create the image above.
/**
* Gameboard array
* 0 = non playable squares
* 1 = empty square
* 2 = pawn
**/
int[][] gameboard = {
{0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 0, 2, 2, 2, 0, 0, 0},
{0, 0, 0, 2, 2, 2, 0, 0, 0},
{0, 2, 2, 2, 2, 2, 2, 2, 0},
{0, 2, 2, 2, 1, 2, 2, 2, 0},
{0, 2, 2, 2, 2, 2, 2, 2, 0},
{0, 0, 0, 2, 2, 2, 0, 0, 0},
{0, 0, 0, 2, 2, 2, 0, 0, 0},
{0, 0, 0, 0, 0, 0, 0, 0, 0},
};
boolean checkIfGameboardFits() {
return AMOUNT * SIDE < SCREENWIDTH && AMOUNT * SIDE < SCREENHEIGHT;
}
void giveErrorMessage() {
println("/ Gameboard doesnt fit the screen");
}
void drawGameboard(int xPos, int yPos, int[][] gameboard) {
for (int row = 0; row < AMOUNT; row++) {
for (int column = 0; column < AMOUNT; column++) {
int xPosSquare = xPos + (column * SIDE);
int yPosSquare = yPos + (row * SIDE);
switch (gameboard[row][column]) {
case 0:
fill(#760000);
break;
case 1:
fill(#FFFFFF);
break;
case 2:
fill(#9B9999);
break;
}
rect(xPosSquare, yPosSquare, SIDE, SIDE);
}
}
}
void drawBoardPieces(int xPos, int yPos, int[][] gameboard) {
for (int row = 0; row < AMOUNT; row++) {
for (int column = 0; column < AMOUNT; column++) {
if (gameboard[row][column] == 2) {
fill(#FFFFFF);
circle(xPos + (column * SIDE) + PAWNMARGE, yPos + (row * SIDE) + PAWNMARGE, PAWNSIZE);
}
}
}
}
void startGame() {
background(255);
gameState = 1;
if (checkIfGameboardFits()) {
int startXposGameboard = (width - (AMOUNT * SIDE)) /2;
int startYposGameboard = (height - (AMOUNT * SIDE)) /2;
drawGameboard(startXposGameboard, startYposGameboard, gameboard);
drawBoardPieces(startXposGameboard, startYposGameboard, gameboard);
} else {
giveErrorMessage();
}
}
And the rest of the code.
final int SIDE = 60;
final int AMOUNT = 9;
final int SCREENWIDTH = 800;
final int SCREENHEIGHT = 800;
final int PAWNSIZE = 40;
final int PAWNMARGE = 30;
int gameState;
void settings() {
size(SCREENWIDTH, SCREENHEIGHT);
}
void setup() {
startGame();
}
void draw() {
}
void mousePressed() {
// get index and value from clicked square
}
Any and all tips, examples or thinking processes would be welcome.
You can determine the index by dividing the relative mouse position by the size of a tile:
void mousePressed() {
int column = (mouseX - xPos) / SIDE;
int row = (mouseY - yPos) / SIDE;
if (column >= 0 && column < AMOUNT && row >= 0 && row < AMOUNT) {
// mouse is on the board at column and row
// ...
}
}

p5.js how to correctly compute the 3D rotation of a point in respect of the origin

I'm really struggling here and I can't get it right, not even knowing why.
I'm using p5.js in WEBGL mode, I want to compute the position of on point rotated on the 3 axes around the origin in order to follow the translation and the rotation given to object through p5.js, translation and rotatation on X axis, Y axis and Z axis.
The fact is that drawing a sphere in 3d space, withing p5.js, is obtained by translating and rotating, since the sphere is created at the center in the origin, and there is no internal model giving the 3d-coordinates.
After hours of wandering through some math too high for my knowledge, I understood that the rotation over 3-axis is not as simple as I thought, and I ended up using Quaternion.js. But I'm still not able to match the visual position of the sphere in the 3d world with the coordinates I have computed out of the original point on the 2d-plane (150, 0, [0]).
For example, here the sphere is rotated on 3 axis. At the beginning the coordinates are good (if I ignore the fact that Z is negated) but at certain point it gets completely out of sync. The computed position of the sphere seems to be completely unrelated:
It's really hours that I'm trying to solve this issue, with no result, what did I miss?
Here it follows my code:
//font for WEBGL
var robotoFont;
var dotId = 0;
var rotating = true;
var orbits = [];
var dotsData = [];
function preload() {
robotoFont = loadFont('./assets/Roboto-Regular.ttf');
}
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
textFont(robotoFont);
background(0);
let orbit1 = new Orbit(0, 0, 0, 0.5, 0.5, 0.5);
orbit1.obj.push(new Dot(0, 0));
orbits.push(orbit1);
// let orbit2 = new Orbit(90, 45, 0);
// orbit2.obj.push(new Dot(0, 0));
// orbits.push(orbit2);
}
function draw() {
angleMode(DEGREES);
background(0);
orbitControl();
let len = 200;
fill('white');
stroke('white');
sphere(2);
stroke('red');
line(0, 0, 0, len, 0, 0);
text('x', len, 0)
stroke('green');
line(0, 0, 0, 0, len, 0);
text('y', 0, len)
push();
rotateX(90);
stroke('yellow');
line(0, 0, 0, 0, len, 0);
text('z', 0, len)
pop();
dotsData = [];
orbits.forEach(o => o.draw());
textSize(14);
push();
for (let i = 0; i < 2; i++) {
let yPos = -(windowHeight / 2) + 15;
for (let i = 0; i < dotsData.length; i++) {
let [id, pos, pos3d] = dotsData[i];
let [x1, y1, z1] = [pos[0].toFixed(0), pos[1].toFixed(0), pos[2].toFixed(0)];
let [x2, y2, z2] = [pos3d.x.toFixed(0), pos3d.y.toFixed(0), pos3d.z.toFixed(0)];
text(`${id}: (${x1}, ${y1}, ${z1}) -> (${x2}, ${y2}, ${z2})`, -windowWidth / 2 + 5, yPos);
yPos += 18;
}
rotateX(-90);
}
pop();
}
function mouseClicked() {
// controls.mousePressed();
}
function keyPressed() {
// controls.keyPressed(keyCode);
if (keyCode === 32) {
rotating = !rotating;
}
}
class Orbit {
constructor(x, y, z, xr, yr, zr) {
this.obj = [];
this.currentRot = [
x ? x : 0,
y ? y : 0,
z ? z : 0
]
this.rot = [
xr ? xr : 0,
yr ? yr : 0,
zr ? zr : 0
]
}
draw() {
push();
if (rotating) {
this.currentRot[0] += this.rot[0];
this.currentRot[1] += this.rot[1];
this.currentRot[2] += this.rot[2];
}
rotateY(this.currentRot[1]);
rotateX(this.currentRot[0]);
rotateZ(this.currentRot[2]);
noFill();
stroke('white');
ellipse(0, 0, 300, 300);
for (let i = 0; i < this.obj.length; i++) {
let o = this.obj[i];
o.draw();
dotsData.push([o.id, o.getPosition(), this.#get3DPos(o)]);
}
pop();
}
#get3DPos(o) {
let [x, y, z] = o.getPosition();
let w = 0;
let rotX = this.currentRot[0] * PI / 180;
let rotY = this.currentRot[1] * PI / 180;
let rotZ = this.currentRot[2] * PI / 180;
let rotation = Quaternion.fromEuler(rotZ, rotX, rotY, 'ZXY').conjugate();
[x, y, z] = rotation.rotateVector([x, y, z]);
return createVector(x, y, z);
}
}
class Dot {
constructor(angle) {
this.id = ++dotId;
this.x = cos(angle) * 150;
this.y = sin(angle) * 150;
}
draw() {
push();
fill('gray');
translate(this.x, this.y);
noStroke();
sphere(15);
pop();
}
getPosition() {
return [this.x, this.y, 0];
}
}
It doesn't work in stackoverflow because I need local asset like the font.
Here the working code: https://editor.p5js.org/cigno5/sketches/_ZVq0kjJL
I've finally sorted out. I can't really understand why works this way but I didn't need quaternion at all, and my first intuition of using matrix multiplications to apply rotation on 3-axis was correct.
What I did miss in first instance (and made my life miserable) is that matrix multiplication is not commutative. This means that applying rotation on x, y and z-axis is not equivalent to apply same rotation angle on z, y and x.
The working solution has been achieved with 3 simple steps:
Replace quaternion with matrix multiplications using vectors (method #resize2)
Rotating the drawing plane with Z-Y-X order
Doing the math of rotation in X-Y-Z order
//font for WEBGL
var robotoFont;
var dotId = 0;
var rotating = true;
var orbits = [];
var dotsData = [];
function preload() {
robotoFont = loadFont('./assets/Roboto-Regular.ttf');
}
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
textFont(robotoFont);
background(0);
let orbit1 = new Orbit(0, 0, 0, 0.5, 0.5, 0.5);
orbit1.obj.push(new Dot(0, 0.5));
orbits.push(orbit1);
// let orbit2 = new Orbit(90, 45, 0);
// orbit2.obj.push(new Dot(0, 0));
// orbits.push(orbit2);
}
function draw() {
angleMode(DEGREES);
background(0);
orbitControl();
let len = 200;
fill('white');
stroke('white');
sphere(2);
stroke('red');
line(0, 0, 0, len, 0, 0);
text('x', len, 0)
stroke('green');
line(0, 0, 0, 0, len, 0);
text('y', 0, len)
push();
rotateX(90);
stroke('yellow');
line(0, 0, 0, 0, len, 0);
text('z', 0, len)
pop();
dotsData = [];
orbits.forEach(o => o.draw());
textSize(14);
push();
for (let i = 0; i < 2; i++) {
let yPos = -(windowHeight / 2) + 15;
for (let i = 0; i < dotsData.length; i++) {
let [id, pos, pos3d] = dotsData[i];
let [x1, y1, z1] = [pos[0].toFixed(0), pos[1].toFixed(0), pos[2].toFixed(0)];
let [x2, y2, z2] = [pos3d.x.toFixed(0), pos3d.y.toFixed(0), pos3d.z.toFixed(0)];
text(`${id}: (${x1}, ${y1}, ${z1}) -> (${x2}, ${y2}, ${z2})`, -windowWidth / 2 + 5, yPos);
yPos += 18;
}
rotateX(-90);
}
pop();
}
function mouseClicked() {
// controls.mousePressed();
}
function keyPressed() {
// controls.keyPressed(keyCode);
if (keyCode === 32) {
rotating = !rotating;
}
}
class Orbit {
constructor(x, y, z, xr, yr, zr) {
this.obj = [];
this.currentRot = [
x ? x : 0,
y ? y : 0,
z ? z : 0
]
this.rot = [
xr ? xr : 0,
yr ? yr : 0,
zr ? zr : 0
]
}
draw() {
push();
if (rotating) {
this.currentRot[0] += this.rot[0];
this.currentRot[1] += this.rot[1];
this.currentRot[2] += this.rot[2];
}
rotateZ(this.currentRot[2]);
rotateY(this.currentRot[1]);
rotateX(this.currentRot[0]);
noFill();
stroke('white');
ellipse(0, 0, 300, 300);
for (let i = 0; i < this.obj.length; i++) {
let o = this.obj[i];
o.draw();
dotsData.push([o.id, o.getPosition(), this.#get3DPos(o)]);
}
pop();
}
#get3DPos(o) {
let [x, y, z] = o.getPosition();
let pos = createVector(x, y, z);
pos = this.#rotate2(pos, createVector(1, 0, 0), this.currentRot[0]);
pos = this.#rotate2(pos, createVector(0, 1, 0), this.currentRot[1]);
pos = this.#rotate2(pos, createVector(0, 0, 1), this.currentRot[2]);
return pos;
}
//https://stackoverflow.com/questions/67458592/how-would-i-rotate-a-vector-in-3d-space-p5-js
#rotate2(vect, axis, angle) {
// Make sure our axis is a unit vector
axis = p5.Vector.normalize(axis);
return p5.Vector.add(
p5.Vector.mult(vect, cos(angle)),
p5.Vector.add(
p5.Vector.mult(
p5.Vector.cross(axis, vect),
sin(angle)
),
p5.Vector.mult(
p5.Vector.mult(
axis,
p5.Vector.dot(axis, vect)
),
(1 - cos(angle))
)
)
);
}
}
class Dot {
constructor(angle, speed) {
this.id = ++dotId;
this.angle = angle;
this.speed = speed
}
draw() {
this.angle += this.speed;
this.x = cos(this.angle) * 150;
this.y = sin(this.angle) * 150;
push();
fill('gray');
translate(this.x, this.y);
noStroke();
sphere(15);
pop();
}
getPosition() {
return [this.x, this.y, 0];
}
}
And now it works like a charm:
https://editor.p5js.org/cigno5/sketches/PqB9CEnBp

OpenTK. Trouble with mirror reflection

I have some problems with rendering of mirror reflection in OpenTK. I need to see reflection of objects only from one side from my mirror, but my program also gives reflection from another side and it hides the first one. Could you help me?
private void glControl1_Load(object sender, EventArgs e)
{
loaded = true;
GL.ClearColor(Color.SkyBlue);
initRendering();
Matrix4 p = Matrix4.CreatePerspectiveFieldOfView((float)(80 * Math.PI / 180), 1, 20, 500);
GL.MatrixMode(MatrixMode.Projection);
GL.LoadMatrix(ref p);
Matrix4 modelview = Matrix4.LookAt(70, 70, 70, 0, 0, 0, 0, 1, 0);
GL.MatrixMode(MatrixMode.Modelview);
GL.LoadMatrix(ref modelview);
}
private void glControl1_Paint(object sender, PaintEventArgs e)
{
if (!loaded)
return;
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
drawScene();
/*axes*/
GL.Color3(Color.Black);
GL.Begin(BeginMode.Lines);
GL.Vertex3(0, 0, 0);
GL.Vertex3(70, 0, 0);
GL.Vertex3(0, 0, 0);
GL.Vertex3(0, 70, 0);
GL.Vertex3(0, 0, 0);
GL.Vertex3(0, 0, 70);
GL.End();
glControl1.SwapBuffers();
}
void initRendering()
{
GL.ShadeModel(ShadingModel.Flat);
GL.ClearDepth(1.0f);
GL.ClearStencil(0);
GL.Enable(EnableCap.DepthTest);
GL.DepthFunc(DepthFunction.Lequal);
GL.Hint(HintTarget.PerspectiveCorrectionHint, HintMode.Nicest);
GL.Enable(EnableCap.AutoNormal);
// Setup the drawing area and shading mode
GL.MatrixMode(MatrixMode.Projection);
GL.LoadIdentity();
GL.Enable(EnableCap.PointSmooth);
GL.MatrixMode(MatrixMode.Modelview);
GL.LoadIdentity();
GL.Enable(EnableCap.Lighting);
GL.LightModel(LightModelParameter.LightModelAmbient, ambientLight);
GL.Enable(EnableCap.Normalize);
GL.Enable(EnableCap.ColorMaterial);
}
public class CustomOpenGLControl : GLControl
{
public CustomOpenGLControl()
: base(new OpenTK.Graphics.GraphicsMode(32, 24, 8))
{
}
}
private void drawFrame()
{
GL.Color3(Color.Red);
GL.Begin(BeginMode.Quads);
GL.Normal3(0.0f, -1.0f, 0.0f);
for (int i = 0; i < upWall.Length; i++)
{
GL.Vertex3(upWall[i].x, upWall[i].y, upWall[i].z);
}
GL.End();
GL.Color3(Color.Yellow);
GL.Begin(BeginMode.Quads);
GL.Normal3(0.0f, -1.0f, 0.0f);
for (int i = 0; i < downWall.Length; i++)
{
GL.Vertex3(downWall[i].x, downWall[i].y, downWall[i].z);
}
GL.End();
GL.Color3(Color.Green);
GL.Begin(BeginMode.Quads);
GL.Normal3(0.0f, 0.0f, -1.0f);
for (int i = 0; i < backWall.Length; i++)
{
GL.Vertex3(backWall[i].x, backWall[i].y, backWall[i].z);
}
GL.End();
GL.Color3(Color.Blue);
GL.Begin(BeginMode.Quads);
GL.Normal3(-1.0f, 0.0f, 0.0f);
for (int i = 0; i < rightWall.Length; i++)
{
GL.Vertex3(rightWall[i].x, rightWall[i].y, rightWall[i].z);
}
GL.End();
GL.Color3(Color.Orange);
GL.Begin(BeginMode.Quads);
GL.Normal3(1.0f, 0.0f, 0.0f);
for (int i = 0; i < leftWall.Length; i++)
{
GL.Vertex3(leftWall[i].x, leftWall[i].y, leftWall[i].z);
}
GL.End();
}
private void drawMirror()
{
GL.PushMatrix();
GL.Begin(BeginMode.Quads);
GL.Normal3(0.0f, -1.0f, 0.0f);
for (int i = 0; i < mirror.Length; i++)
{
GL.Vertex3(mirror[i].x, mirror[i].y, mirror[i].z);
}
GL.End();
GL.PopMatrix();
}
private void drawScene()
{
GL.Clear(ClearBufferMask.AccumBufferBit | ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit | ClearBufferMask.ColorBufferBit);
double[] eqr = { 0f, y0 + height / 6f, 0f, 0.0f };
GL.PushMatrix();
GL.ColorMask(false, false, false, false);
GL.Enable(EnableCap.StencilTest);
GL.StencilFunc(StencilFunction.Always, 1, 1);
GL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Replace);
GL.Disable(EnableCap.DepthTest);
drawMirror();
GL.Enable(EnableCap.DepthTest);
GL.ColorMask(true, true, true, true);
GL.StencilFunc(StencilFunction.Equal, 1, 1);
GL.StencilOp(StencilOp.Keep, StencilOp.Keep, StencilOp.Keep);
GL.Enable(EnableCap.ClipPlane0);
GL.ClipPlane(ClipPlaneName.ClipPlane0, eqr);
GL.PushMatrix();
GL.Scale(1, -1, 1);
drawFrame();
GL.PopMatrix();
GL.Disable(EnableCap.ClipPlane0);
GL.Disable(EnableCap.StencilTest);
GL.Enable(EnableCap.Blend);
GL.Disable(EnableCap.Lighting);
GL.Color4(1, 1, 1, 0.3f);
GL.BlendFunc(BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
drawMirror();
GL.Enable(EnableCap.Lighting);
GL.Disable(EnableCap.Blend);
drawFrame();
GL.PopMatrix();
}
}
Sorry, I cant help too much with this. I would run your code, but unfortunately I am not at my programming computer right now, and I don't have any experience with opentk.

the function display() does not exist

I keep getting the error "the function display() does not exist. I'm just trying to put my own image in instead of a pre drawn ellipse in processing. Here is my code:
import processing.serial.*;
import cc.arduino.*;
import com.tinkerkit.*;
Arduino arduino;
//declare the button
TKButton but;
TKButton but1;
{
arduino = new Arduino(this, Arduino.list()[2], 57600);
}
PFont myFont;
PImage ball1;
int pScore;
// don't touch em!
int gameState; //0= pre game 1= in game 2= game over
//setting up perimeter to contain ball character
int width = 600;
int height = 600;
void setup() {
size(width, height);
smooth();
//myFont = loadFont("MyFutura.vlw");
//textFont(myFont);
gameState = 0;
ball1 = loadImage("ball.gif");
pScore = 0;
}
{
but = new TKButton(arduino, TK.I0);
but1 = new TKButton(arduino, TK.I1);
}
void draw() {
background(0);
if (gameState==0) {
fill(255, 255, 255, 70);
rect(-10, 30, 370, 70, 7);
fill(255, 255, 255, 70);
rect(-10, 120, 330, 50, 7);
fill(255);
textSize(60);
text("Ball Game", 30, 85);
textSize(40);
text("Press B to Start", 30, 157);
if (keyPressed && key == 'b') {
gameState = 1;
}
}
if (gameState == 2) {
fill(255, 255, 255, 70);
rect(-10, 30, 370, 70, 7);
fill(255, 255, 255, 70);
rect(-10, 120, 250, 50, 7);
fill(255, 255, 255, 70);
rect(-10, 190, 330, 50, 7);
fill(255);
textSize(50);
text("Final Score:", 20, 85);
text(pScore, 280, 85);
textSize(30);
text("Play Again?", 30, 157);
textSize(30);
text("Press R to Restart", 30, 225);
if (keyPressed && key == 'r') {
gameState = 0;
setup();
}
}
ball1.display();
ball1.keyPressed();
}
void display() {
fill(255);
noStroke();
}
void reset() {
...
}
class ball1 {
float x;
float y;
float speed;
float r; //radius
color c = color(255, 20, 245);
ball1(float tempX, float tempY, float tempR) {
x = tempX;
y = tempY;
r = tempR;
speed = 0;
}
void change() {
c = color(random(255), random(220), random(245));
}
void display() {
fill (c);
noStroke();
ellipse(x, y, r, r);
}
//key commands
//ball flies off page in response to key command
void keyPressed() {
if (key == CODED) {
}
if (but.read ()== TK.HIGH) {
x = x+5;
if (x >= width - 25) {
x = width - 25;
}
println(but.read());
} else if (but1.read() == TK.HIGH) {
x = x-5;
if (x <= 25) {
x = 25;
}
println(but1.read());
}
}
}
Your ball1 variable is of type PImage.
The PImage class does not have a display() method.
Maybe your ball1 variable should be inside the ball1 class? If so, rename your class to something sane like Ball (in other words, follow the standard naming conventions and don't use the same name for a class and variable). Then rename your ball1 variable to something sane like ballImage. Then inside your Ball class's display() function, call image(ballImage, x, y) or one of the other image() functions.
Recommended reading:
https://processing.org/reference/PImage.html
https://processing.org/reference/image_.html

Artifacts when using Serial data to trigger sketch

I am writing a small program in Processing to display the values of a speed trap and elapsed time(like a drag strip, but for a waterslide for Australia Day) coming from a microcontroller over serial.
I have had the Processing sketch running and have done most of the development with just a mouse click to imitate new data and run the program, I also have set up the hardware and had it running with the microcontroller sending the serial data and the program displaying it.
I have done some further development, and now when it runs I am getting some strange things happening, boxes sometimes come through in the wrong color, some artifacts in the displayed numbers, and a lot of wrong colors for only some of the graph(the graph is made with a for loop and the color is set outside of it).
I have tried adding in some delays to make sure its getting a full string of data and I have also tried moving around the sub routines but nothing works.
The faults only appeared after I added in the elapsed time code to the serial data, before it was just one string, now it is two numbers separated by a colon.
I have put my code in here as well, its pretty straight forward, I am only new to programming, please excuse(and point out!) any errors.
import processing.serial.*;
Serial myPort;
float inString;
float inFloat0, inFloat1;
float[] list = new float[0];
float[] etlist = new float[0];
PFont f1, f2, f3, f4, f5, f6;
int index;
float rank;
float percentage;
int trapDistance = 1000;
int SgraphH = 38; //Speed graph scale
int ETgraphH = 15; //ET graph scale
int valueX = 0;
int valueY = 0;
boolean graph30 = false, graph690 = false;
color c1, c2, c3 = color(220, 0, 0), c4 = color(220, 0, 0);
int boxX = 295, boxY = 430, boxSize = 15; //Graph 30 box
void setup () {
size(1280, 700);
index = 0;
//Load list from textfile
String loadlist[] = loadStrings("list.txt");
for (int i = 0; i < loadlist.length; i++) {
String[] split = split(loadlist[i], ',');
list = append(list, float(split[0]));
etlist = append(etlist, float(split[1]));
index++;
}
println(Serial.list());
myPort = new Serial(this, Serial.list()[0], 9600);
myPort.bufferUntil('\n');
//Create fonts
f1 = createFont("Arial Unicode MS", 20);
f2 = createFont("Arial Unicode MS", 240);
f3 = createFont("Arial Unicode MS", 35);
f4 = createFont("Arial Unicode MS", 15);
f5 = createFont("Arial Unicode MS", 15);
f6 = createFont("Arial Unicode MS", 12);
}
void draw () {
stroke(225);
fill(225);
rectMode(CORNER);
rect(0, 0, 500, 20);
fill(0);
text(mouseX, 20, 20);
text(mouseY, 50, 20);
text(mouseX - valueX, 80, 20);
text(mouseY - valueY, 110, 20);
}
void mousePressed() {
//Check if Mouse is over button and toggle Graph on
if (mouseX > boxX && mouseX < boxX+boxSize && mouseY >boxY && mouseY < boxY+boxSize) {
if (graph30) {
graph30 = false;
c3 = color(220, 0, 0);
}
else {
graph30 = true;
c3 = color(0, 220, 0);
}
create();
graphCheck();
graph();
}
//Check if Mouse is over button and toggle Graph on
if (mouseX > boxX+100 && mouseX < boxX+100+boxSize && mouseY >boxY && mouseY < boxY+boxSize) {
if (graph690) {
graph690 = false;
c4 = color(220, 0, 0);
}
else {
graph690 = true;
c4 = color(0, 220, 0);
}
create();
graphCheck();
graph();
}
}
void serialEvent (Serial myPort) {
delay(100);
String inString = myPort.readStringUntil('\n');
delay(100);
if (inString != null) {
inString = trim(inString);
String[] split = split(inString, ',');
inFloat0 = float(split[0]);
inFloat1 = float(split[1]);
index++;
float speed = trapDistance / inFloat0 * 3.6;
list = append(list, speed);
float et = inFloat1;
etlist = append(etlist, et);
//Clear screen
background(225);
//Run sub routines
create();
graphCheck();
graph();
}
}
void delay(int delay)
{
int time = millis();
while(millis() - time <= delay);
}
void create() {
//Clear screen
background(225);
//Sorting
float[] sortlist = new float[index+1];
sortlist = sort(list);
float[] sortetlist = new float[index+1];
sortetlist = sort(etlist);
//Calculate rank
int pos = 1;
while (list[index-1] > sortlist[pos - 1]) {
pos++;
}
if (pos == 1) {
rank = 0;
}
else {
rank = (pos / float(sortlist.length));
}
//Calculate Percentage of Speed
percentage = list[index-1] / sortlist[sortlist.length -1];
//Speed colour for fastest and slowest
if (index != 0) {
if (list[index-1] < sortlist[1]) {
c1 = color(240, 0, 0);
}
else if (list[index-1] > sortlist[index-1]) {
c1 = color(0, 240, 0);
}
else {
c1 = color(255);
}
}
else {
c1 = color(255);
}
//ET colour for fastest and slowest
if (index != 0) {
if (etlist[index-1] < sortetlist[1]) {
c2 = color(240, 0, 0);
}
else if (etlist[index-1] > sortetlist[index-1]) {
c2 = color(0, 240, 0);
}
else {
c2 = color(255);
}
}
else {
c2 = color(255);
}
//Boxes
rectMode(CORNER);
stroke(0);
fill(c1);
rect(15, 15, width-30, 200); //Speed
fill(c2);
rect(15, 230, width-30, 200); //ET
fill(255);
rect(15, height - 255, 125, 240); //Left
rect(155, height -255, 125, 240); //Left
rect(width - 140, height - 255, 125, 240); //Right
rect(width - 280, height - 255, 125, 240); //Right
//Text
fill(0);
textFont(f3);
textAlign(CENTER);
text("Speed", 78, height - 220);
text("ET", width - 78, height - 220);
text("ET", 218, height - 220);
text("Speed", width - 218, height - 220);
textFont(f4);
textAlign(LEFT);
text("Trap Distance: " + trapDistance + "mm", 2, height - 2);
//Big Numbers
textAlign(CENTER);
textFont(f2);
text(String.format("%.2f", list[index-1])+"km/h", width/2, 200); //Speed
text(String.format("%.2f", etlist[index-1])+"sec", width/2, 415); //ET
//Last 10
for (int i = 0; i <= index-1 && i < 10; i++) {
textAlign(LEFT);
textFont(f5);
text((index-1 - i + 1)+". "+String.format("%.3f", list[index-1 - i])+"km/h", 23, (height - 200 + (i * 20)));
}
//Last 10 ET
for (int i = 0; i <= index-1 && i < 10; i++) {
textAlign(LEFT);
textFont(f5);
text((index-1 - i + 1)+". "+String.format("%.3f", etlist[index-1 - i])+"sec", 163, (height - 200 + (i * 20)));
}
//Fastest
for (int i = 0; i <= index-1 && i < 10; i++) {
textAlign(CENTER);
textFont(f5);
text((i+1)+". "+String.format("%.3f", sortlist[sortlist.length - (i+1)])+"km/h", width - 220, (height - 200 + (i * 20)));
}
//Fastest ET
for (int i = 0; i <= index-1 && i < 10; i++) {
textAlign(CENTER);
textFont(f5);
text((i+1)+". "+String.format("%.3f", sortetlist[i])+"sec", width - 80, (height - 200 + (i * 20)));
}
//Create string for saving to text file
String[] listString = new String[index-1+1];
for (int i = 0; i < index-1+1; i++) {
listString[i] = (Float.toString(list[i]) + ',' + Float.toString(etlist[i]));
}
//Save to text file
saveStrings("list.txt", listString);
//Graph with curves
// beginShape();
// stroke(125, 125, 255);
// for (int i = 1; i <= index && i < 34; i++) {
// float y = map(list[index - (i - 1)], 0, SgraphH, 150, 0);
// curveVertex((width + 25 - (i * 25)), 25 + y);
// }
// endShape();
}
void graph() {
//Create graph area
fill(255);
rectMode(CENTER);
rect(width/2, height - 135, 690, 240);
stroke(192);
for (int i = 1; i < 30.; i++) {
line((((width - 690)/2) + (i * (690/30))), 446, ((((width - 690)/2) + (i * (690/30)))), 684);
}
for (int i = 1; i < 10; i++) {
line(((width - 690)/2)+1, (445 + (i * 24)), (width - (width - 690)/2)-1, (445 + (i * 24)));
}
//Graph last 690
if (graph690) {
stroke(0, 170, 0);
for (int i = 1; i <= index-1 && i < 690; i++) {
float start = map(list[index-1 - (i - 1)], 0, SgraphH, 240, 0);
float end = map(list[index-1 - i], 0, SgraphH, 240, 0);
line(((width - (width - 690)/2) + 1 - (i)), 445 + start, ((width- (width - 690)/2) + 1 - ((i + 1))), 445 + end);
}
}
//Graph last 690
if (graph690) {
stroke(120, 0, 120);
for (int i = 1; i <= index-1 && i < 690; i++) {
float start = map(etlist[index-1 - (i - 1)], 0, ETgraphH, 240, 0);
float end = map(etlist[index-1 - i], 0, ETgraphH, 240, 0);
line(((width - (width - 690)/2) + 1 - (i)), 445 + start, ((width- (width - 690)/2) + 1 - ((i + 1))), 445 + end);
}
}
//Graph last 30 speeds
if (graph30) {
stroke(0, 0, 240);
for (int i = 1; i <= index-1 && i < 31; i++) {
float start = map(list[index-1 - (i - 1)], 0, SgraphH, 240, 0);
float end = map(list[index-1 - i], 0, SgraphH, 240, 0);
line(((width - (width - 690)/2) + 23 - (i * 23)), 445 + start, ((width- (width - 690)/2) + 23 - ((i + 1) * 23)), 445 + end);
}
}
//Graph last 30 ETs
if (graph30) {
stroke(240, 0, 0);
for (int i = 1; i <= index-1 && i < 31; i++) {
float start = map(etlist[index-1 - (i - 1)], 0, ETgraphH, 240, 0);
float end = map(etlist[index-1 - i], 0, ETgraphH, 240, 0);
line(((width - (width - 690)/2) + 23 - (i * 23)), 445 + start, ((width- (width - 690)/2) + 23 - ((i + 1) * 23)), 445 + end);
}
}
fill(0);
textAlign(LEFT);
text("0", width / 2 - 345, height - 15);
text(SgraphH, width / 2 - 345, height - 243);
}
void graphCheck() {
//Draw box and text for graph selection
rectMode(CORNER);
textFont(f6);
textAlign(LEFT);
stroke(0);
fill(0);
text("Graph last 30", boxX+20, boxY+13);
fill(c3);
rect(boxX, boxY, boxSize, boxSize);
stroke(0);
fill(0);
text("Graph last 690", boxX+120, boxY+13);
fill(c4);
rect(boxX+100, boxY, boxSize, boxSize);
}
void keyPressed() {
valueX = mouseX;
valueY = mouseY;
}
Also, when triggered from a mouse click (turning on the graph's) it works fine.
Cheers,
Matt
I fixed it.
When the subroutines were being called by the serial trigger, it some how still allowed the looped code in the draw() function to still run, which was causing the issues.
I just put noLoop() and loop() in before and after the calls for the subroutines.
All good now.

Resources