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
// ...
}
}
Related
I have a grid. Which should be in 3d space. The grid must be dimensionless. That is, when the camera is rotated 90 degrees in x, the edge of the grid should not be visible. To solve the problem, before multiplying by the look at and perspective matrix, I increase it in scale, that is:
scale grid
matrix representation:
typedef std::array<std::array<double, 4>, 4> M4x4;
I implemented the code for working with matrices:
M4x4 Matrix::multiply(const M4x4 &a, const M4x4 &b)
{
M4x4 m;
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
m[i][j] = a[i][0] * b[0][j] +
a[i][1] * b[1][j] +
a[i][2] * b[2][j] +
a[i][3] * b[3][j];
}
}
return m;
}
M4x4 Matrix::getTranslation(double dx, double dy, double dz)
{
return {
std::array<double, 4>{1, 0, 0, dx},
std::array<double, 4>{0, 1, 0, dy},
std::array<double, 4>{0, 0, 1, dz},
std::array<double, 4>{0, 0, 0, 1}
};
}
M4x4 Matrix::getLookAt(const Vector &eye, const Vector &target, const Vector &up)
{
Vector vz = Vector::substruct(eye, target).normalize();
Vector vx = Vector::crossProduct(up, vz).normalize();
Vector vy = Vector::crossProduct(vz, vx).normalize();
CoorVector eyeCoor = eye.getCoorVector();
CoorVector vzCoor = vz.getCoorVector();
CoorVector vxCoor = vx.getCoorVector();
CoorVector vyCoor = vy.getCoorVector();
M4x4 m = {
std::array<double, 4>{vxCoor.x, vxCoor.y, vxCoor.z, 0},
std::array<double, 4>{vyCoor.x, vyCoor.y, vyCoor.z, 0},
std::array<double, 4>{vzCoor.x, vzCoor.y, vzCoor.z, 0},
std::array<double, 4>{0, 0, 0, 1}
};
return Matrix::multiply(
Matrix::getTranslation(-eyeCoor.x, -eyeCoor.y, -eyeCoor.z),
m
);
}
M4x4 Matrix::getGeneralScale(double val)
{
return {
std::array<double, 4>{1, 0, 0, 0},
std::array<double, 4>{0, 1, 0, 0},
std::array<double, 4>{0, 0, 1, 0},
std::array<double, 4>{0, 0, 0, 1 / val}
};
}
M4x4 Matrix::getScale(double sx, double sy, double sz)
{
return {
std::array<double, 4>{sx, 0, 0, 0},
std::array<double, 4>{0, sy, 0, 0},
std::array<double, 4>{0, 0, sz, 0},
std::array<double, 4>{0, 0, 0, 1}
};
}
M4x4 Matrix::getRotationZ(double angle)
{
return {
std::array<double, 4>{std::cos(angle), -std::sin(angle), 0, 0},
std::array<double, 4>{std::sin(angle), std::cos(angle), 0, 0},
std::array<double, 4>{0, 0, 1, 0},
std::array<double, 4>{0, 0, 0, 1}
};
}
Vector Matrix::multiplyVector(const M4x4 &m, const Vector &v)
{
CoorVector coorV = v.getCoorVector();
return Vector({
m[0][0] * coorV.x + m[0][1] * coorV.y + m[0][2] * coorV.z + m[0][3] * coorV.w,
m[1][0] * coorV.x + m[1][1] * coorV.y + m[1][2] * coorV.z + m[1][3] * coorV.w,
m[2][0] * coorV.x + m[2][1] * coorV.y + m[2][2] * coorV.z + m[2][3] * coorV.w,
m[3][0] * coorV.x + m[3][1] * coorV.y + m[3][2] * coorV.z + m[3][3] * coorV.w
});
}
M4x4 Matrix::getPerspectiveProjection(double fovy, double aspect, double n, double f)
{
const double PI = 3.141592653589793238463;
const double radians = PI / 180 * fovy;
const double sx = (1 / std::tan(radians / 2)) / aspect;
const double sy = (1 / std::tan(radians / 2));
const double sz = (f + n) / (f - n);
const double dz = (-2 * f * n) / (f - n);
return {
std::array<double, 4>{sx, 0, 0, 0},
std::array<double, 4>{0, sy, 0, 0},
std::array<double, 4>{0, 0, sz, dz},
std::array<double, 4>{0, 0, -1, 0}
};
}
M4x4 Matrix::getFrustum(double left, double right, double bottom, double top, double near, double far)
{
return {
std::array<double, 4>{2 * near / (right - left), 0, (right + left) / (right - left), 0},
std::array<double, 4>{0, 2 * near / (top - bottom), (top + bottom) / (top - bottom), 0},
std::array<double, 4>{0, 0, -(far + near) / (far - near), -2*far*near/(far- near)},
std::array<double, 4>{0, 0, -1, 0}
};
}
vector representation:
struct CoorVector {
double x = 0;
double y = 0;
double z = 0;
double w = 1;
};
Working with vectors:
Vector Vector::substruct(const Vector &v1, const Vector &v2)
{
CoorVector coorV1 = v1.getCoorVector();
CoorVector coorV2 = v2.getCoorVector();
return Vector({coorV1.x - coorV2.x, coorV1.y - coorV2.y, coorV1.z - coorV2.z, 1});
}
Vector Vector::crossProduct(const Vector &v1, const Vector &v2)
{
CoorVector coorV1 = v1.getCoorVector();
CoorVector coorV2 = v2.getCoorVector();
return Vector({coorV1.y * coorV2.z - coorV1.z * coorV2.y,
coorV1.z * coorV2.x - coorV1.x * coorV2.z,
coorV1.x * coorV2.y - coorV1.y * coorV2.x});
}
double Vector::getLength()
{
return std::sqrt(
coorV.x * coorV.x + coorV.y * coorV.y + coorV.z * coorV.z
);
}
Vector Vector::normalize()
{
const double length = getLength();
coorV.x /= length;
coorV.y /= length;
coorV.z /= length;
return *this;
}
CoorVector Vector::getCoorVector() const
{
return coorV;
}
And I use matrices to multiply the vertices when drawing:
void Grid::customMatrixFrustum(QPainter &p)
{
M4x4 m = Matrix::getRotationZ(degreesToRadians(gridData.rotationZ));
//does not work with getPerspectiveProjection function
double scale = gridData.scale;
m = Matrix::multiply(
Matrix::getScale(scale, scale, scale),
m);
//or
// m = Matrix::multiply(
// Matrix::getGeneralScale(scale),
// m);
m = Matrix::multiply(
Matrix::getTranslation(0, 0, gridData.height),
m);
m = Matrix::multiply(
Matrix::getLookAt(
Vector({0, 0, 0, 1}), // where is the observer
Vector({-degreesToRadians(gridData.x), degreesToRadians(gridData.y), -1, 1}), // where to look
Vector({0, 1, 0, 1})
),
m);
m = Matrix::multiply(
Matrix::getPerspectiveProjection(
90, 1, -1, -1000),
m);
m = Matrix::multiply(
Matrix::getFrustum(-1, 1, -1, 1, -1, -1000),
m
);
//doesn't work as it should. the camera's view goes beyond the grid.
// m = Matrix::multiply(
// Matrix::getScale(scale, scale, scale),
// m);
QList<Vector> sceneVertices;
for (int i = 0; i < vertices.size(); i++) {
Vector vertex = Matrix::multiplyVector(
m,
vertices[i]);
CoorVector coorVertex = vertex.getCoorVector();
coorVertex.x = coorVertex.x / coorVertex.w * (this->width() / 2);
coorVertex.y = coorVertex.y / coorVertex.w * (this->width() / 2);
sceneVertices.append(Vector({coorVertex}));
}
for (int i = 0, l = edges.size(); i < l; i++) {
std::array<int, 2> e = edges[i];
CoorVector coorP1 = sceneVertices[e[0]].getCoorVector();
CoorVector coorP2 = sceneVertices[e[1]].getCoorVector();
if (e[0] == 2 && e[1] == 3) {
qDebug() << "points:" << e[0] << e[1]
<< coorP1.x << coorP1.y << coorP1.w
<< coorP2.x << coorP2.y << coorP2.w;
qDebug() << "lines:" << e[0] << e[1]
<< QPoint(coorP1.x + this->width() / 2, -(coorP1.y - this->height() / 2)) << coorP1.w
<< QPoint(coorP2.x + this->width() / 2, -(coorP2.y - this->height() / 2)) << coorP2.w;
}
p.drawLine(coorP1.x + this->width() / 2, coorP1.y + (this->height() / 2),
coorP2.x + this->width() / 2, coorP2.y + (this->height() / 2));
}
}
I create mesh vertices using the following algorithm:
void Grid::init()
{
//create vertices - points
countHorizontal = 100;//number of cells horizontally (counting from top to bottom)
countVertiacal = 100;//number of cells vertically (counting from left to right)
const int pointHorizontal = countHorizontal * 2 + 2;
const int pointVertiacal = countVertiacal * 2 - 2;
const int pointCount = pointHorizontal + pointVertiacal;
const double intervalHorizontal = 2 / (double)countHorizontal;
const double intervalVertical = 2 / (double)countVertiacal;
double currentIntHor = 1;// Descent from top to bottom
double currentIntVer = -1 + intervalVertical;//descent from left to right
for (int i = 0; i < pointCount; ++i) {
if (i < pointHorizontal) {
if (i % 2) {
vertices.append(Vector({1, currentIntHor, 0, 1}));//performed second
currentIntHor -= intervalHorizontal;
} else {
vertices.append(Vector({-1, currentIntHor, 0, 1}));//runs first
}
} else {
if (i % 2) {
vertices.append(Vector({currentIntVer, -1, 0, 1}));//performed second
currentIntVer += intervalVertical;
} else {
vertices.append(Vector({currentIntVer, 1, 0, 1})); //runs first
}
}
}
//Create pairs of vertex indices for lines
const int vertical = countHorizontal + 1;
const int horizontal = countVertiacal + 1;
for (int i = 0; i < vertical; ++i) {
edges.append({i*2, i*2 + 1});
}
const int lastHorIndex = edges.size() - 1;
std::array<int, 2> lastHorVal = edges[lastHorIndex];
for (int i = 0; i < horizontal; ++i){
if (i == 0) {//first line
edges.append({0, lastHorVal[0]});
} else if (i == horizontal - 1) {//last line
edges.append({1, lastHorVal[1]});
} else {
const int magic = 2 * i;
edges.append({lastHorVal[1] + magic - 1, lastHorVal[1] + magic});
}
}
}
In the figure it looks like this:
create grid
With a scale of 3 and a camera angle of 90 degrees in x, the grid looks like this:
norm grid
When you increase the camera rotation in x by 1 degree, the grid already breaks:
broken grid
And if you increase the scale of the grid, then the angle of rotation of the camera when drawing the grid becomes smaller. I assume that the problem is with the w factor, since it does not change for the better when the grid is scaled up. And I don't know how to change it so that the grid after scaling and perspective is drawn normally.
I posted the problem code on github:
grid project
P.S. I tried QMatrix4x4 but when passing the values from the example, perspective didn't work. If someone solves my problem through QMatrix4x4 I will be glad.
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
Here is the solution: Using recursion tree method, looks like it should be exponential i.e 4 power n. But solving it is not clear. Like how one would describe in an interview. Any help is appreciated.
class Solution {
public static boolean hasPath(int[][] maze, int[] start, int[] destination) {
boolean result = false;
result = moveNext(maze, start[0], start[1], destination);
return result;
}
public static boolean moveNext(int[][] graph, int i, int j, int[] destination) {
if (i < 0 || i == graph.length ||
j < 0 || j == graph[0].length ||
graph[i][j] == 1)
return false;
graph[i][j] = 1;
if (i == destination[0] && j == destination[1])
return true;
return moveNext(graph, i, j + 1, destination)
|| moveNext(graph, i + 1, j, destination)
|| moveNext(graph, i, j - 1, destination)
|| moveNext(graph, i - 1, j, destination);
}
public static void main(String args[]) {
int[][] maze = {
{ 0, 1, 1, 1 },
{ 1, 0, 1, 0 },
{ 1, 0, 1, 1 },
{ 0, 0, 0, 0 }
};
int start[] = { 0, 0 };
int destination[] = { 3, 3 };
System.out.println(hasPath(maze, start, destination));
}
}
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);
}
}
I have gaussian filter mask nxn computed for n = 5. That looks like this:
int gauss5[] = {
1, 4, 7, 4, 1,
4, 20, 33, 20, 4,
7, 33, 55, 33, 7,
4, 20, 33, 20, 4,
1, 4, 7, 4, 1
};
int gauss5_summ = 331;
After computation in opencl image become darker. What is the reason?
size_t global_wblur[2];
size_t local_wblur[2];
global_wblur[0] = h;
global_wblur[1] = w;
local_wblur[0] = local_wblur[1] = 32;
err = clEnqueueNDRangeKernel(queue, cl_img_gaussian_blur, 2, NULL, global_wblur, local_wblur, 0, NULL, NULL);
if (err != CL_SUCCESS) {
fprintf(stderr, "error: clEnqueueNDRangeKernel() blur %d %s\n", err, cl_strerror(err));
exit(EXIT_FAILURE);
}
kernel source:
__kernel void cl_img_gaussian_blur(__global const uchar *gray, __global uchar *out, __global const uchar *gbox, uint n, uint sum, uint w, uint h)
{
int i, j, offset;
uint x, y, summ;
y = get_global_id(0);
x = get_global_id(1);
offset = n/2;
/* ignore border pixels
*/
if (y - offset < 0 || y + offset > h || x - offset < 0 || x + offset > w) {
out[y*w + x] = gray[y*w + x];
return;
}
summ = 0;
for (j = -offset; j <= offset; j++) {
for (i = -offset; i <= offset; i++) {
summ += gray[(y + j)*w + x + i]*gbox[(j + offset)*n + i + offset];
}
}
out[y*w + x] = summ/sum;
}
Blured images:
dark(opencl) -- https://github.com/apetrunev/imgalg-opencl/blob/master/dark.png
normal -- https://github.com/apetrunev/imgalg-opencl/blob/master/test-blur/out.png
You said that the gbox data is initialized like:
clEnqueueWriteBuffer(queue, gauss_buf, CL_FALSE, 0, 5*5, gauss5, 0, NULL, NULL);
That is wrong, since you are copying 1/4th of the real amount of memory. The proper way is:
clEnqueueWriteBuffer(queue, gauss_buf, CL_FALSE, 0, 5*5*sizeof(cl_int), gauss5, 0, NULL, NULL);
Otherwise, the rest is 0s, leading to a low value in the output.