Can someone help me fix my DDA collision algorithm in processing? - processing

I have been trying to make a DDA algorithm for a raycaster for a while now. For some reason I'm having a ton of trouble. The way my code works is that it puts a player into the map that spins at a constant rate. the green circle should be landing wherever the player is looking on the nearest wall. It seems to almost work when the line is more vertical than horizontal, but it still is a bit off. the moment it passes the y=x or y=-x line it goes crazy, seeming to shoot off into infinity. I suspect it has something to do with slope, but frankly I'm not really sure.
I have been following this video closely, but to no avail. I hope someone can spot the error in my code so I can continue with this project.
Here's a condensed version of my code:
int[][] map = {
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,1,1,1,1,0,1,1,1,1,1,1,1,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}
};
float fov;
Player p;
void setup() {
size(800, 600);
background(0);
stroke(255);
fov = PI/2;
p = new Player(12.5, 12.5, -PI/4);
}
void draw() {
background(0);
p.map_();
p.ddaTest4();
p.dir += PI/512;
p.dir %= TWO_PI;
}
class Player {
float dir;
PVector pos = new PVector();
Player (float x, float y, float dir) {
this.dir = dir;
this.pos.x = x;
this.pos.y = y;
}
// this draws the map array as rectangles
void map_() {
noStroke();
float sf = height/map.length;
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map.length; j++) {
if (map[j][i] != 0) {
stroke(0);
fill(255);
rect(i*sf, j*sf, height/sf, height/sf);
} else {
stroke(255);
fill(0);
rect(i*sf, j*sf, height/sf, height/sf);
}
}
}
}
void ddaTest4() {
float sf = height/map.length;
PVector rayDir = PVector.fromAngle(dir);
PVector stepScale = new PVector(sqrt(((rayDir.y/rayDir.x) * (rayDir.y/rayDir.x)) + 1), sqrt(((rayDir.x/rayDir.y) * (rayDir.x/rayDir.y)) + 1));
PVector mapPos = new PVector((int)pos.x, (int)pos.y);
PVector rayLength = new PVector();
PVector step = new PVector(1, 1);
if (rayDir.x < 0) {
step.x = -1;
rayLength.x = (pos.x - mapPos.x) * stepScale.x;
} else {
rayLength.x = ((mapPos.x + 1) - mapPos.x) * stepScale.x;
}
if (rayDir.y < 0) {
step.y = -1;
rayLength.x = (pos.y - mapPos.y) * stepScale.y;
} else {
rayLength.x = ((mapPos.y + 1) - mapPos.y) * stepScale.y;
}
boolean hit = false;
float distance = 0;
while(!hit) {
if (rayLength.x < rayLength.y) {
mapPos.x += step.x;
distance = rayLength.x;
rayLength.x += stepScale.x;
} else {
mapPos.y += step.y;
distance = rayLength.y;
rayLength.y += stepScale.y;
}
if (map[(int)mapPos.y][(int)mapPos.x] != 0) {
hit = true;
}
}
PVector hitPoint = PVector.add(pos, PVector.mult(rayDir, distance));
fill(0, 255, 0);
stroke(0, 255, 0);
line(pos.x*sf, pos.y*sf, hitPoint.x*sf, hitPoint.y*sf);
ellipse(hitPoint.x*sf, hitPoint.y*sf, 5, 5);
}
}
PS: sorry if I have poorly phrased my question, I'm not really sure how else to put it.

Related

Processing Fix & Optimization of https://github.com/jonlit/spacestarprocessing3d

I previously had an issue with a model not loading correctly (see Processing - loading obj File)
https://stackoverflow.com/users/89766/george-profenza helped me solve the problem in chat, and he wanted to post his optimizations to my code publically.
This also solved the original problem described in the question mentioned above.
You can check out the game at https://github.com/jonlit/spacestarprocessing3d
As mentioned in chat there were a few things slightly off with the existing approach and for visiblity, this are the steps taken to address the issues.
Hope this helps other to debug Processing P3D / OBJ issues:
The first step was to identify the slowest pieces of code. This was done using VisualVM.
This highlighted shape() calls were slow (not not why):
Step 2 was to isolate the problem. Why is loading/displaying a couple of obj files slow.
For reference these are the assets:
rock.obj using rockTexture.png (but currently missing .mtl)
cirno_low.obj using cirno_low_u1_v1.jpeg
This is a test sketch loading/display the .obj files as they are:
PShape rock;
PShape cirno;
void setup(){
size(900, 900, P3D);
cirno = loadShape("cirno_low.obj");
rock = loadShape("rock.obj");
int faces = 0;
int vertices = 0;
for(int i = 0 ; i < rock.getChildCount(); i++){
PShape c = rock.getChild(i);
vertices += c.getVertexCount();
faces++;
}
println("rock faces", faces, "vertices", vertices);
}
void draw(){
background(0);
lights();
translate(width * 0.5, height * 0.5, 0);
rotateY(map(mouseX, 0, width, -PI, PI));
rotateX(map(mouseY, 0, height, PI, -PI));
for(int i = 0 ; i < 81; i++){
pushMatrix();
translate(i % 9 * 100 - width * 0.5,
i / 9 * 100 - height * 0.5, -100);
rotate(map(i, 0, 80, -PI, PI), 0.5, 0.5, 0);
scale(0.5);
shape(rock);
popMatrix();
}
pushMatrix();
scale(10);
shape(cirno);
popMatrix();
surface.setTitle((int)frameRate + "fps");
}
It renders pretty fast, without textures though:
The game uses setTexture() and interestingly enough this drops the frame rate:
PShape rock;
PShape cirno;
void setup(){
size(900, 900, P3D);
cirno = loadShape("cirno_low.obj");
cirno.setTexture(loadImage("cirno_low_u1_v1.jpeg"));
rock = loadShape("rock.obj");
rock.setTexture(loadImage("rockTexture.png"));
int faces = 0;
int vertices = 0;
for(int i = 0 ; i < rock.getChildCount(); i++){
PShape c = rock.getChild(i);
vertices += c.getVertexCount();
faces++;
}
println("rock faces", faces, "vertices", vertices);
}
void draw(){
background(0);
lights();
translate(width * 0.5, height * 0.5, 0);
rotateY(map(mouseX, 0, width, -PI, PI));
rotateX(map(mouseY, 0, height, PI, -PI));
for(int i = 0 ; i < 81; i++){
pushMatrix();
translate(i % 9 * 100 - width * 0.5,
i / 9 * 100 - height * 0.5, -100);
rotate(map(i, 0, 80, -PI, PI), 0.5, 0.5, 0);
scale(0.5);
shape(rock);
popMatrix();
}
pushMatrix();
scale(10);
shape(cirno);
popMatrix();
surface.setTitle((int)frameRate + "fps");
}
Without checking the PShape source code, the assumption is behind the scenes the PShape has to do more work behind the scenes, because loading an .obj file with an .mtl (which helps load the texture as well) render just fine.
Here's the Processing > Examples > Basics > Shape > LoadDisplayOBJ example tweaked: it renders 1250 instances at 60fps:
/**
* Load and Display an OBJ Shape.
*
* The loadShape() command is used to read simple SVG (Scalable Vector Graphics)
* files and OBJ (Object) files into a Processing sketch. This example loads an
* OBJ file of a rocket and displays it to the screen.
*/
PShape rocket;
float ry;
public void setup() {
size(900, 900, P3D);
rocket = loadShape("rocket.obj");
}
public void draw() {
background(0);
lights();
translate(width/2, height/2 + 100, -200);
rotateY(map(mouseX, 0, width, -PI, PI));
rotateX(map(mouseY, 0, height, PI, -PI));
int nc = 1250;
float nr = sqrt(nc);
float sp = 150;
for(int i = 0 ; i < nc; i++){
pushMatrix();
translate(i % nr * sp - width * 0.5,
i / nr * sp - height * 0.5, -sp);
//rotate(map(i, 0, 80, -PI, PI), 0.5, 0.5, 0);
rotateZ(PI + radians(i));
rotateY(ry);
scale(0.5);
shape(rocket);
popMatrix();
}
//rotateZ(PI);
//rotateY(ry);
//shape(rocket);
ry += 0.02;
surface.setTitle((int)frameRate + "fps");
}
This pointed out another issue with how the obj files were used in the game:
each new Star() for example would load the .obj again.
public class Star extends UFO {
public Star (int x, int y, int spd) {
posX = x;
posY = y;
rot = int(random(0, 360));
speed = spd;
symbol = loadShape("rock.obj");
symbol.setTexture(rockTexture); //<>//
...
Ideally these meshes would be loaded once in setup(), with .mtl files and references passed to each instance needing to render them via shape().
This would allow instancing to work as it's the same geometry rendered multiple times. Reloading the same obj file into new memory addresses for each instance would result in many duplicated resources.
One quick fix for the .mtl issue is to simply import the obj in Blender, select it, apply the texture and export it:
(This would also be a good opportunity to rotate/scale models so when they're loaded in Processing, no additional transforms are required and they all can live an in easy to understand coordinate system)
The contents of the exported files I've used are:
cirno_lowWithMTL.mtl
cirno_lowWithMTL.obj
cirno_lowWithMTLDecimated.mtl
cirno_lowWithMTLDecimated.obj
They load/display (with textures) at 60fps (due to the .mtl files)
The recommended optimisation steps (other than using .obj with .mtl files and loading once and re-using mulitple times) are:
avoid extending fixed length arrays (e.g. kryptonit = (Kryptonit[]) append(kryptonit, new Kryptonit(int(random(50, width-150)), int(random(-300, 0))));). ArrayLists are better suited for resizing. In this case in particular a fixed length array is great, as long as it's objects are pre-allocated once, then the positions / states of the objects are updated (e.g. outside of screen objects are marked for re-use and hidden and instead of new objects, existing objects have positions visiblity/reset): in other words Object Pooling)
if meshes are displayed from a single point of view with only rotation on Z axis and position affecting them, they could be images (sprites) instead. (e.g. exporting a static image with alpha channel from Blender at the right scale (or using PGraphics to do this at runtime))
once meshes are loaded, instead of using transformations on them in draw() (e.g. symbol.rotateX(value), which will affect every single vertex in the PShape, use pushMatrix()/popMatrix() call with shape() so simply render the same geometry with different tranformations.
For reference this is the full program with minimal tweaks around loading/using .obj files efficiently (with the old approach commented out and few notes around those regions):
import com.dhchoi.CountdownTimer;
import com.dhchoi.CountdownTimerService;
import controlP5.*;
int zeit;
int punkte;
int leben;
int schwierigkeit = 20;
int zustand = 1;
int boost = 0;
int highscore = 0;
int minuten = 0;
int changeLevel = 0;
boolean paused = true;
boolean gameOver;
JSONArray saves = new JSONArray();
PFont gameOverFont;
PFont gameOverFontSmall;
CountdownTimer timer1 = CountdownTimerService.getNewCountdownTimer(this).configure(1000, 60000);
CountdownTimer kryptonitAnimationTimer1 = CountdownTimerService.getNewCountdownTimer(this).configure(10, 250);
boolean[] keysPressed = new boolean[65536];
ControlP5 cp5;
Star[] stars;
Kryptonit[] kryptonit;
Raumschiff raumschiff;
Player[] players;
String textValue = "";
PImage cirnoTexture;
PImage rockTexture;
PShape cirno;
PShape rock;
void loadMeshes(){
rock = loadShape("rockWithMTL.obj");
rock.scale(0.2);
cirno = loadShape("cirno_lowWithMTL.obj");
cirno.rotateY(HALF_PI);
cirno.rotateZ(HALF_PI * -1);
cirno.scale(5);
}
void settings()
{
//size(800, 400, P3D);
fullScreen(P3D);
smooth(8);
System.setProperty("jogl.disable.openglcore", "true");
}
void setup() {
surface.setResizable(true);
//println("loading textures");
//cirnoTexture = loadImage("cirno_low_u1_v1.jpeg");
//rockTexture = loadImage("rockTexture.png");
//println("finished loading textures: " + cirnoTexture);
loadMeshes();
stars = new Star[0];
kryptonit = new Kryptonit[0];
raumschiff = new Raumschiff(width/2, height/4*3, cirno);
leben = 5;
gameOverFont = createFont("Arial", 36, true);
gameOverFontSmall = createFont("Arial", 16, true);
for (int i = 0; i < schwierigkeit; i++) {
stars = (Star[]) append(stars, new Star(int(random(50, width-150)), int(random(50, height-100)), int(random(5, 15)), rock));
}
players = new Player[0];
cp5 = new ControlP5(this);
cp5.addTextfield("Name")
.setPosition(width/2-100, height/3*2-20)
.setSize(200, 40)
.setFont(gameOverFontSmall)
.setFocus(false)
.setColor(color(255))
.setAutoClear(false)
.setText("Name")
.setLabel("")
.hide()
.lock()
;
}
void draw() {
background(0);
lights();
switch (zustand) {
case 0:
break;
case 1:
fill(255);
text("Zeit:\t" + minuten + ":" + zeit, width-100, 50);
text("Punkte:\t" + punkte, width-100 , 100);
text("Leben:\t" + leben, width-100, 150);
text("Highscore:\t" + highscore, width-100, 200);
text("schwierigkeit:\t" + schwierigkeit, width-100, 250);
try {
for (int i = 0; i < players.length; i++) {
text(players[i].getName() + " " + players[i].getScore(), width-100, 300+15*i);
}
for (int i = 0; i < stars.length; i++) {
stars[i].zeichnen();
stars[i].drehen(random(0, 0.05), random(0, 0.05), random(0, 0.05));
}
for (int i = 0; i < kryptonit.length; i++) {
kryptonit[i].zeichnen();
}
if (kryptonitAnimationTimer1.getTimeLeftUntilFinish() != .0f) {
raumschiff.zeichnen(color(350-kryptonitAnimationTimer1.getTimeLeftUntilFinish(), 100, 100));
} else {
raumschiff.zeichnen(color(100, 100, 100));
}
} catch (Exception e) { e.printStackTrace(); }
noFill();
stroke(100);
rect(50, 50, width-200, height-150);
fill(0);
noStroke();
rect(0, 0, width-150, 48);
if (gameOver) {
pushMatrix();
fill(255);
textAlign(CENTER, CENTER);
textFont(gameOverFont, 36);
textSize(34);
text("GAME OVER!", width/2, height/2);
textFont(gameOverFontSmall, 16);
textSize(16);
text("Press ENTER to resume", width/2, height/2+30);
cp5.get(Textfield.class, "Name").unlock();
cp5.get(Textfield.class, "Name").show();
popMatrix();
}
else if (paused) {
pushMatrix();
fill(255);
textAlign(CENTER, CENTER);
textFont(gameOverFont, 36);
textSize(34);
text("PAUSED!", width/2, height/2);
textFont(gameOverFontSmall, 16);
textSize(16);
text("PRESS ANY KEY TO RESUME", width/2, height/2+30);
popMatrix();
}
break;
default :
background(0);
break;
}
for (int i = 0; i < stars.length; i++) {
if (stars[i].isVisible && sqrt((stars[i].posX - raumschiff.posX) * (stars[i].posX - raumschiff.posX) + (stars[i].posY - raumschiff.posY) * (stars[i].posY - raumschiff.posY) ) < 25){
stars[i].isVisible = false;
punkte+=stars[i].speed;
if (changeLevel > 0) {
changeLevel--;
}
}
}
if (punkte > highscore) {
highscore = punkte;
}
if (kryptonit.length < schwierigkeit / 5) {
//kryptonit = (Kryptonit[]) append(kryptonit, new Kryptonit(int(random(50, width-150)), int(random(-300, 0))));
}
if (stars.length < schwierigkeit) {
stars = (Star[]) append(stars, new Star(int(random(50, width-150)), int(random(-300, 0)), int(random(5, 15)), rock));
}
for (int i = 0; i < kryptonit.length; i++) {
if (kryptonit[i].isVisible && sqrt((kryptonit[i].posX - raumschiff.posX) * (kryptonit[i].posX - raumschiff.posX) + (kryptonit[i].posY - raumschiff.posY) * (kryptonit[i].posY - raumschiff.posY) ) < 25){
kryptonit[i].isVisible = false;
leben-=1;
kryptonitAnimationTimer1.start();
}
}
if (leben < 1){
gameOver = true;
}
if (punkte % 500 <= 20 && punkte % 500 >= 0 && changeLevel == 0 && zustand == 1) {
schwierigkeit+=5;
changeLevel = 5;
}
if (punkte % 500 > 20) {
changeLevel = 0;
}
if (!paused) {
try {
if (!gameOver) {
for (int i = 0; i < stars.length; i++) {
stars[i].bewegen(schwierigkeit/stars[i].speed+boost);
if (stars[i].posY > height-100){
stars[i] = null;
stars[i] = new Star(int(random(58, width-202)), int(random(-300, 0)), int(random(5, 15)), rock);
}
}
for (int i = 0; i < kryptonit.length; i++){
kryptonit[i].bewegen(schwierigkeit/10+boost);
if (kryptonit[i].posY > height-100){
kryptonit[i] = null;
kryptonit[i] = new Kryptonit(int(random(58, width-202)), int(random(-300, 0)));
}
}
}
} catch (Exception e) { e.printStackTrace(); }
}
if (keysPressed[56]){
boost = 5;
}
else {
boost = 0;
}
if (keysPressed[52] && !gameOver && !paused){
try {
raumschiff.bewegen(-7);
if (keysPressed[32]) {
raumschiff.bewegen(-10);
}
} catch (Exception e) { e.printStackTrace(); }
}
if (keysPressed[54] && !gameOver && !paused){
try {
raumschiff.bewegen(7);
if (keysPressed[32]) {
raumschiff.bewegen(10);
}
} catch (Exception e) { e.printStackTrace(); }
}
surface.setTitle((int)frameRate+"fps");
}
void keyPressed() {
if (gameOver && key == ENTER) {
players = (Player[]) append(players, new Player(cp5.get(Textfield.class, "Name").getText(), punkte));
cp5.get(Textfield.class, "Name").lock();
cp5.get(Textfield.class, "Name").hide();
for (int i = 0; i < saves.size(); i++) {
JSONObject playerJSONObject = new JSONObject();
playerJSONObject.setInt("id", i);
playerJSONObject.setString(cp5.get("Name", cp5.get(Textfield.class, "Name").getText()).toString(), "");
playerJSONObject.setInt("score", punkte);
}
saveJSONArray(saves, "data/highscores.json");
schwierigkeit = 20;
paused = true;
gameOver = false;
leben = 5;
punkte = 0;
timer1.reset(CountdownTimer.StopBehavior.STOP_IMMEDIATELY);
timer1.start();
zeit = 0;
stars = null;
stars = new Star[0];
for (int i = 0; i < schwierigkeit; i++) {
stars = (Star[]) append(stars, new Star(int(random(50, width-150)), int(random(50, height-100)), int(random(5, 15)), rock));
}
kryptonit = null;
kryptonit = new Kryptonit[0];
}
keysPressed[key] = true;
}
void keyReleased() {
keysPressed[key] = false;
}
void keyTyped() {
if (key == 'p' || key == 'P') {
if (!gameOver) {
paused = !paused;
if (paused) {
timer1.stop(CountdownTimer.StopBehavior.STOP_IMMEDIATELY);
}
else {
timer1.start();
}
}
}
if (paused && !gameOver && key != 'p' && key != 'P') {
paused = false;
timer1.start();
}
}
void onTickEvent(CountdownTimer t, long timeLeftUntilFinish) {
if (t == timer1) {
zeit++;
}
}
void onFinishEvent(CountdownTimer t) {
if (t == timer1) {
timer1.reset(CountdownTimer.StopBehavior.STOP_AFTER_INTERVAL);
timer1.start();
zeit = 0;
minuten++;
}
}
abstract class Flugobjekt {
public int posX;
public int posY;
public int rot;
public int speed;
boolean isVisible = true;
PShape symbol;
abstract void bewegen (int amount);
}
abstract class UFO extends Flugobjekt {
}
public class Star extends UFO {
float rotationX, rotationY, rotationZ;
public Star (int x, int y, int spd, PShape symbol) {
posX = x;
posY = y;
rot = int(random(0, 360));
speed = spd;
// use a reference to the preloaded PShape (instead of loading a the .obj again for each instance)
this.symbol = symbol;
//symbol = loadShape("rockWithMTL.obj");
//symbol.setTexture(rockTexture);
//symbol.scale(0.2);
/*
fill(255);
stroke(255);
strokeWeight(2);
symbol = createShape();
symbol.beginShape();
symbol.vertex(0, -5);
symbol.vertex(1.4, -2);
symbol.vertex(4.7, -1.5);
symbol.vertex(2.3, 0.7);
symbol.vertex(2.9, 4.0);
symbol.vertex(0, 2.5);
symbol.vertex(-2.9, 4);
symbol.vertex(-2.3, 0.7);
symbol.vertex(-4.7, -1.5);
symbol.vertex(-1.4, -2);
symbol.endShape(CLOSE);
/*/
}
public void zeichnen (){
// skip if PShape (or it's texture) isn't loaded yet)
if(symbol == null){
return;
}
if (isVisible) {
pushMatrix();
translate(posX, posY);
//rotate(rot);
rotateX(rotationX);
rotateY(rotationY);
rotateZ(rotationZ);
//scale(0.2);
shape(symbol);
popMatrix();
}
}
public void bewegen (int amount) {
posY = posY + amount;
}
public void drehen (float xAmount, float yAmount, float zAmount) {
rotationX += xAmount;
rotationY += yAmount;
rotationZ += zAmount;
// symbol.rotateX means all vertices inside the shape will be updated
// use rotateX() then shape() to simply render the same underlying PShape vertex data without updating it all the time
//symbol.rotateX(xAmount);
//symbol.rotateY(yAmount);
//symbol.rotateZ(zAmount);
}
}
public class Kryptonit extends UFO {
public Kryptonit (int x, int y) {
posX = x;
posY = y;
rot = int(random(0, 360));
fill(0);
stroke(255, 0, 0);
strokeWeight(2);
symbol = createShape();
symbol.beginShape();
symbol.vertex(0, -5);
symbol.vertex(1.4, -2);
symbol.vertex(4.7, -1.5);
symbol.vertex(2.3, 0.7);
symbol.vertex(2.9, 4.0);
symbol.vertex(0, 2.5);
symbol.vertex(-2.9, 4);
symbol.vertex(-2.3, 0.7);
symbol.vertex(-4.7, -1.5);
symbol.vertex(-1.4, -2);
symbol.endShape(CLOSE);
}
public void zeichnen (){
if (isVisible) {
pushMatrix();
translate(posX, posY);
rotate(rot);
shape(symbol);
popMatrix();
}
}
public void bewegen (int amount) {
posY = posY + amount;
}
}
public class Raumschiff extends Flugobjekt {
public Raumschiff (int x, int y, PShape symbol) {
posX = x;
posY = y;
fill(100);
noStroke();
this.symbol = symbol;
//symbol = loadShape("cirno_lowWithMTL.obj");//createShape(ELLIPSE, 0, 0, 50, 50);
//symbol.setTexture(cirnoTexture);
//symbol.rotateY(HALF_PI);
//symbol.rotateZ(HALF_PI * -1);
////symbol.rotateX(0.5);
//symbol.scale(5);
/* (Raumschiff)
symbol = createShape();
symbol.beginShape();
symbol.vertex(25, 0);
symbol.vertex(30, 5);
symbol.vertex(30, 5);
symbol.vertex(32, 12);
symbol.vertex(28, 20);
symbol.vertex(31, 28);
symbol.vertex(27, 25);
symbol.vertex(25, 29);
symbol.vertex(23, 25);
symbol.vertex(19, 28);
symbol.vertex(22, 20);
symbol.vertex(18, 12);
symbol.vertex(20, 5);
symbol.endShape(CLOSE);
//*/
}
public void zeichnen (color farbe){
if (isVisible) {
pushMatrix();
symbol.setFill(farbe);
translate(posX, posY);
rotate(rot);
shape(symbol);
popMatrix();
}
}
public void bewegen (int amount) {
posX+=amount;
if (posX < 50) posX = 50;
if (posX > width-150) posX = width-150;
}
}
public class Player {
private String name;
private int score;
public Player (String n, int s) {
name = n;
score = s;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
}
Here's an example of pre-allocating a number of objects to be reused (a-la object pooling), instead of constant reinstantiation (which has it's costs):
PShape rock;
int numRocks = 25;
Rock[] rocks = new Rock[numRocks];
float halfWidth;
float halfHeight;
void setup(){
size(900, 900, P3D);
rock = loadShape("rockWithMTL.obj");
// ideally the mesh would already been scaled down to avoid this
rock.scale(0.2);
halfWidth = width * 0.5;
halfHeight = height * 0.5;
for(int i = 0 ; i < numRocks; i++){
rocks[i] = new Rock(rock, random(-halfWidth, halfWidth), random(-halfHeight, halfHeight));
}
}
void draw(){
background(0);
lights();
translate(width * 0.5, height * 0.5, 0);
for(int i = 0 ; i < numRocks; i++){
rocks[i].draw();
}
surface.setTitle((int)frameRate + "fps");
}
class Rock{
PShape mesh;
PVector position = new PVector();
PVector velocity = new PVector();
PVector rotationAxis = new PVector();
float rotationAngle = 0;
Rock(PShape mesh, float x, float y){
this.mesh = mesh;
position.x = x;
position.y = y;
velocity.y = random(1, 10);
// pick a random rotation axis
rotationAxis.set(random(1), random(1), random(1));
}
void draw(){
// update
// increment position
position.add(velocity);
// increment rotation
rotationAngle += 0.1;
// object pool behaviour: reset if off screen (no need to re-allocate a new instance)
if(position.y > halfHeight + 100){
position.x = random(-halfWidth, halfWidth);
position.y = -halfHeight - 100;
}
// draw
pushMatrix();
translate(position.x, position.y, position.z);
rotate(rotationAngle, rotationAxis.x, rotationAxis.y, rotationAxis.z);
shape(mesh);
popMatrix();
}
}
Also, here's a super basic demo on encapsulating states. It's a bit hacky because each state know of the other, but shows each could behave as it's own "sketch" that can live in it's own tab and only override it's specific behaviour:
StartScreen start;
GameScreen game;
HighScoreScreen highScore;
StateScreen currentScreen;
void setup(){
size(300, 300);
textAlign(CENTER, CENTER);
textSize(18);
start = new StartScreen();
game = new GameScreen();
highScore = new HighScoreScreen();
currentScreen = start;
}
void draw(){
background(0);
currentScreen.draw();
}
void keyPressed(){
currentScreen.keyPressed();
}
class StateScreen {
StateScreen(){
setup();
}
void setup(){ println(this,"setup()"); }
void draw(){}
void keyPressed(){}
}
class StartScreen extends StateScreen{
void draw(){
fill(sin(frameCount * 0.1) * 127);
text("push any key to\nstart", width * 0.5, height * 0.5);
}
void keyPressed(){
currentScreen = game;
}
}
class GameScreen extends StateScreen{
void draw(){
fill(0, sin(frameCount * 0.1) * 127, 0);
text("push SPACE key to go to\nhigh score screen", width * 0.5, height * 0.5);
}
void keyPressed(){
currentScreen = highScore;
}
}
class HighScoreScreen extends StateScreen{
void draw(){
fill(random(255), random(255), random(255));
text("push SPACE key to go to\nstart screen", width * 0.5, height * 0.5);
}
void keyPressed(){
currentScreen = start;
}
}

Automation of selection in Processing

I am currently using a processing sketch to work through a large number of images I have in a folder. I have set an onClick command to advance to the next image in the string. It is quite time consuming and I would like to automate the action that once the sketch is completed the sketch would repeat it's self selecting the next image from the string. The onClick command also save the image the export folder but each time saves with the same file name, I've tried to set up sequential numbering but it hasn't worked so far. Any help would be greatly appreciated.
String[] imgNames = {"1.jpg", "2.jpg", "3.jpg"};
PImage img;
int imgIndex = 0;
void nextImage() {
background(255);
frameRate(10000);
loop();
frameCount = 0;
img = loadImage(imgNames[imgIndex]);
img.loadPixels();
imgIndex += 1;
if (imgIndex >= imgNames.length) {
imgIndex = 0;
}
}
void paintStroke(float strokeLength, color strokeColor, int strokeThickness) {
float stepLength = strokeLength/4.0;
// Determines if the stroke is curved. A straight line is 0.
float tangent1 = 0;
float tangent2 = 0;
float odds = random(1.0);
if (odds < 0.7) {
tangent1 = random(-strokeLength, strokeLength);
tangent2 = random(-strokeLength, strokeLength);
}
// Draw a big stroke
noFill();
stroke(strokeColor);
strokeWeight(strokeThickness);
curve(tangent1, -stepLength*2, 0, -stepLength, 0, stepLength, tangent2, stepLength*2);
int z = 1;
// Draw stroke's details
for (int num = strokeThickness; num > 0; num --) {
float offset = random(-50, 25);
color newColor = color(red(strokeColor)+offset, green(strokeColor)+offset, blue(strokeColor)+offset, random(100, 255));
stroke(newColor);
strokeWeight((int)random(0, 3));
curve(tangent1, -stepLength*2, z-strokeThickness/2, -stepLength*random(0.9, 1.1), z-strokeThickness/2, stepLength*random(0.9, 1.1), tangent2, stepLength*2);
z += 1;
}
}
void setup() {
size(1600, 700);
nextImage();
}
void draw() {
translate(width/2, height/2);
int index = 0;
for (int y = 0; y < img.height; y+=1) {
for (int x = 0; x < img.width; x+=1) {
int odds = (int)random(20000);
if (odds < 1) {
color pixelColor = img.pixels[index];
pixelColor = color(red(pixelColor), green(pixelColor), blue(pixelColor), 100);
pushMatrix();
translate(x-img.width/2, y-img.height/2);
rotate(radians(random(-90, 90)));
// Paint by layers from rough strokes to finer details
if (frameCount < 20) {
// Big rough strokes
paintStroke(random(150, 250), pixelColor, (int)random(20, 40));
} else if (frameCount < 1000) {
// Thick strokes
paintStroke(random(75, 125), pixelColor, (int)random(8, 12));
} else if (frameCount < 1500) {
// Small strokes
paintStroke(random(20, 30), pixelColor, (int)random(1, 4));
} else if (frameCount < 10000) {
// Big dots
paintStroke(random(5, 10), pixelColor, (int)random(5, 8));
} else if (frameCount < 10000) {
// Small dots
paintStroke(random(1, 2), pixelColor, (int)random(1, 3));
}
popMatrix();
}
index += 1;
}
}
if (frameCount > 10000) {
noLoop();
}
// if(key == 's'){
// println("Saving...");
// saveFrame("screen-####.jpg");
// println("Done saving.");
// }
}
void mousePressed() {
save("001.tif");
nextImage();
}
Can't you just call the nextImage() function from inside this if statement?
if (frameCount > 10000) {
noLoop();
}
Would be:
if (frameCount > 10000) {
nextImage();
}
As for using different filenames, just use an int that you increment whenever you save the file, and use that value in the save() function. Or you could use the frameCount variable:
save("img" + frameCount + ".tif");
If you have follow-up questions, please post a MCVE instead of your whole project. Try to isolate one problem at a time in a small example program that only does that one thing. Good luck.

Array is only displaying 1 object instead of the full amount of variables

I'm new to coding (as I'm sure you might be able to tell). The program runs and generates 1 Tubble, but the additional objects don't show.
Here's my class:
class Tubble {
float x;
float y;
float diameter;
float yspeed;
float tempD = random(2,86);
Tubble() {
x = random(width);
y = height-60;
diameter = tempD;
yspeed = random(0.1, 3);
}
void ascend() {
y -= yspeed;
x = x + random(-2, 2);
}
void dis() {
stroke(0);
fill(127, 100);
ellipse(x, y, diameter, diameter);
}
void top() {
if (y < -diameter/2) {
y = height+diameter/2;
}
}
}
Class
class Particle {
float x, y;
float r;
float speed = 2.2;
int direction = 1;
PImage pbubbles;
Particle(float x_, float y_, float r_) {
x = x_;
y = y_;
r = r_;
}
void display() {
stroke(#F5D7D7);
strokeWeight(2.2);
noFill();
ellipse(x, y, r*2, r*2);
}
boolean overlaps(Particle other) {
float d = dist(x, y, other.x, other.y);
if (d < r + other.r) {
return true;
} else {
return false;
}
}
void move() { //testing
x += (speed * direction);
if ((x > (width - r)) || (x < r)) {
direction *= -1;
}
}
void updown() {
y += (speed * direction);
if ((y > (height - r)) || (y < r)) {
direction *= -1;
}
}
}
Main sketch:
Tubble[] tubbles = new Tubble [126];
PImage bubbles;
Particle p1;
Particle p2;
Particle p3;
Particle p4;
Particle p5;
float x,y;
float speed;
int total = 0;
int i;
//int direction = 1;
void setup() {
size(800, 925);
bubbles = loadImage("purple_bubbles.png");
p1 = new Particle (100, 100, 50);
p2 = new Particle (500, 200, 100);
p3 = new Particle (600, 600, 82);
p4 = new Particle (height/2, width/2, 200);
p5 = new Particle ((height/3), (width/3), 20);
for (int i = 0; i < tubbles.length; i++); {
tubbles[i] = new Tubble();
}
}
void mousePressed() {
total = total + 1;
}
void keyPressed() {
total = total - 1;
}
void draw() {
image(bubbles, 0, 0, 800, 925);
//background(0);
for (int i = 0; i < tubbles.length; i++); {
tubbles[i].ascend();
tubbles[i].dis();
tubbles[i].top();
}
if ((p2.overlaps(p1)) && (p2.overlaps(p4))) {
background(#BF55AB, 25);
}
if ((p3.overlaps(p2)) && (p3.overlaps(p4))) {
background(#506381, 80);
}
p2.x = mouseX;
p3.y = mouseY;
p1.display();
p2.display();
p3.display();
p4.display();
p5.display();
p4.move();
p5.updown();
// for (int i = 0; i < tubbles.length; i++); {
// tubbles[i].dis();
// tubbles[i].ascend();
// tubbles[i].top();
// }
}
There are extra semicolons in the lines
for (int i = 0; i < tubbles.length; i++); {
for (int i = 0; i < tubbles.length; i++); {
// for (int i = 0; i < tubbles.length; i++); {
It make the for loop do virtually nothing.
Then the block after the for loop will read the global variable i and do the action only once.
Remove the semicolons like this to put the block in the loop.
for (int i = 0; i < tubbles.length; i++) {
for (int i = 0; i < tubbles.length; i++) {
// for (int i = 0; i < tubbles.length; i++) {

Array index out of bounds exception: 100

//----------------------------------------------\\
float x = 300;
float y = 300;
float direction = 0;
float increment = 1;
float speed = 5;
boolean toggle = true; // - For spaceship reversal
float wormX = random(0, 600); // - For wormHole v
float wormY = random(0, 600);
float wormGrowth = 0;
boolean growthSwitch = true; // - for wormHole ^
float[] starXpos = new float[100]; //starsRandom
float[] starYpos = new float[100]; //starsRandom
float d = dist(x, y, wormX, wormY);
int score = 0;
//----------------------------------------------\\
//----------------------------------------------\\ Setup
void setup (){
size (600, 600);
starsP1();
}
//----------------------------------------------\\ Draw
void draw (){
background (0);
spaceShip();
starsP2();
wormHole ();
score();
warpInitial();
blackHoleAt(100, 40);
blackHoleAt(400, 500);
}
//----------------------------------------------\\
//----------------------------------------------\\ starsRandom
void starsP1(){
int i = 0;
while (i < 100){
starXpos[i] = random(0, width);
starYpos[i] = random(0, height);
i = i + 1;
}
}
void starsP2(){
stroke(255);
strokeWeight(5);
int i = 0;
while (i < 100){
point(starXpos[i], starYpos[i]);
i = i + 1;
}
if (key == 'w'){
starYpos[i] = starYpos[i] + 1;
}
}
I'm trying to create a form of parallax for the stars in my code. When the user presses w,a,s,d the array of stars should correspond to the direction. I don't understand how this should work as I keep getting this error.
Try formatting your code to better see what's going on:
void starsP2(){
stroke(255);
strokeWeight(5);
int i = 0;
while (i < 100){
point(starXpos[i], starYpos[i]);
i = i + 1;
}
if (key == 'w'){
starYpos[i] = starYpos[i] + 1;
}
}
Your while loop executes until i == 100. Then after the while loop exits, you use that i variable again. Since i is 100, and your starYpos array only has up to index 99, you get an error.
The fix is to either move that if statement to inside the while loop, or to refactor your code so i doesn't go outside the bounds of the array.

why do the circles disappear from the screen?

Processing code as below, one quick question: why do the circles disappear from the screen when my mouse is playing with them? i've already add the boundary check however it does not seem to work. why???
int maxCircle = 200;
float minDistance=30;
float distance1;
float distance2;
Circle [] circles= new Circle[maxCircle];
void setup() {
size(800,800);
smooth();
for(int i=0;i<maxCircle;i++){
circles[i] = new Circle(random(width),random(height),random(2,20));
}
}
void draw() {
background(255,255);
for(int i=0;i<maxCircle;i++) {
circles[i].update(width,height);
for (int j=0; j<maxCircle; j++) {
distance1 = dist(circles[i].x,circles[i].y,circles[j].x,circles[j].y);
if ( distance1 < minDistance ) {
stroke(0,10);
noFill();
line(circles[i].x,circles[i].y,circles[j].x,circles[j].y);
}
}
circles[i].display();
}
}
void mouseMoved() {
for(int i = 0; i<maxCircle;i++) {
distance2 = dist(mouseX,mouseY,circles[i].x,circles[i].y);
circles[i].x-=(mouseX-circles[i].x)/distance2;
circles[i].y-=(mouseX-circles[i].y)/distance2;
if(circles[i].x<circles[i].r || circles[i].x>width-circles[i].r) {
circles[i].vx*=-1;
};
if(circles[i].y<circles[i].r || circles[i].y> height-circles[i].r) {
circles[i].vy*=-1;
}
}
}
class Circle {
float x,y,vx,vy,r,speed;
Circle(float tempx, float tempy, float tempr) {
x=tempx;
y=tempy;
vx=random(-1,1);
vy=random(-1,1);
r=tempr;
}
void update(int w,int h) {
x+=vx;
y+=vy;
if(x<r || x>w-r) {
vx*=-1;
}
if(y<r || y>h-r) {
vy*=-1;
}
}
void display() {
fill(0,50);
noStroke();
ellipse(x,y,r,r);
}
}
Oh i found the solution:
int maxCircle = 200;
float minDistance = 20;
Circle [] circles = new Circle[maxCircle];
void setup() {
size(800, 800);
smooth();
for (int i = 0; i < maxCircle; i++) {
circles[i] = new Circle();
}
}
void draw() {
background(255, 255);
for (int i = 0; i < maxCircle; i++) {
circles[i].update();
noFill();
for (int j = 0; j < maxCircle; j++) {
if (i == j)
continue;
float distance = dist(circles[i].x, circles[i].y, circles[j].x, circles[j].y);
if (distance < minDistance) {
stroke(0, 20);
line(circles[i].x, circles[i].y, circles[j].x, circles[j].y);
}
}
circles[i].display();
}
}
void mouseMoved() {
for (int i = 0; i < maxCircle; i++) {
float distance = dist(mouseX, mouseY, circles[i].x, circles[i].y);
circles[i].x -= (mouseX - circles[i].x) / distance;
circles[i].y -= (mouseX - circles[i].y) / distance;
circles[i].checkBounds();
}
}
class Circle {
float x, y, vx, vy, r, speed;
Circle() {
vx = random(-1, 1);
vy = random(-1, 1);
r = random(1, 10); // See below
x = random(r, width - r);
y = random(r, height - r);
}
void checkBounds() {
if (x < r || x > width - r) {
vx *= -1;
if (x < r) {
x = r;
} else {
x = width - r;
}
}
if (y <= r || y >= height - r) {
vy *= -1;
if (y < r) {
y = r;
} else {
y = width - r;
}
}
}
void update() {
x += vx;
y += vy;
checkBounds();
}
void display() {
fill(0, 50);
noStroke();
ellipse(x, y, r * 2, r * 2);
}
}
in your update() method, when you calculate a new coordinate with your vector, you are potentially setting the coordinate offscreen. I have added 4 conditional statements that reset the value to the bounds of the screen if it exceeds it.
void update(int w,int h) {
x+=vx;
y+=vy;
if(x<r || x>w-r) {
vx*=-1;
if (x>w-r) x = w-r;
if (x<r) x = r;
}
if(y<r || y>h-r) {
vy*=-1;
if (y>h-r) y = h-r;
if (y<r) y = r;
}
}

Resources