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
Hi I am confused as to what is wrong with my code related to the "edges" filter portion of the problem.
I am able to apply a filter that detects edges. For some reason I fail the check50. I am only able to apply the filter to middle pixels. Any guidance would be much appreciated. I am wondering if I am approaching this problem the incorrect way.
With this code I am just ignoring the calculations for the "black pixels" or the pixels outside of the range of height/width.
Here is my code:
void edges(int height, int width, RGBTRIPLE image[height][width])
{
//create temporary array
RGBTRIPLE temp[height][width];
for (int i = 0; i < height; i ++)
{
for (int j = 0; j < width; j++)
{
temp[i][j] = image[i][j];
}
}
//initialize sobel arrays
int gxarray[3][3] = {{-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1}};
int gyarray[3][3] = {{-1, -2, -1}, {0, 0, 0}, {1, 2, 1}};
//loop through each ith pixel in jth column
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j ++)
{
float gx_red = 0;
float gx_blue = 0;
float gx_green = 0;
float gy_red = 0;
float gy_blue = 0;
float gy_green = 0;
//use the temporary array grid to calculate each gx value
//check if it is a corner or side pixel - and treat that pixel as black pixel
for (int k = -1; k < 2; k ++)
{
for (int l = -1; l < 2; l ++)
{
//calculate the gx and gy for each color by multiply each of
//check if they are corner or sidepixels
if (i + k < 0 || i + k >= height)
{
continue;
}
if (j + l < 0 || j + l >= width)
{
continue;
}
//otherwise calculate each color value
gx_red += temp[i + k][j + l].rgbtRed * gxarray[k + 1][l + 1];
gx_blue += temp[i + k][j + l].rgbtBlue * gxarray[k + 1][l + 1];
gx_green += temp[i + k][j + l].rgbtGreen * gxarray[k + 1][l + 1];
gy_red += temp[i + k][j + l].rgbtRed * gyarray[k + 1][l + 1];
gy_blue += temp[i + k][j + l].rgbtBlue * gyarray[k + 1][l + 1];
gy_green += temp[i + k][j + l].rgbtGreen * gyarray[k + 1][l + 1];
}
}
//times each number by itself then, add them, then square root them
int red = 0 + round(sqrt(gx_red * gx_red + gy_red * gy_red));
int blue = 0 + round(sqrt(gx_blue * gx_blue + gy_blue * gy_blue));
int green = 0 + round(sqrt(gx_green * gx_green + gy_green * gy_green));
image[i][j].rgbtRed = red;
image[i][j].rgbtBlue = blue;
image[i][j].rgbtGreen = green;
//cap it by 255
if (image[i][j].rgbtRed > 255)
{
image[i][j].rgbtRed = 255;
}
if (image[i][j].rgbtBlue > 255)
{
image[i][j].rgbtBlue = 255;
}
if (image[i][j].rgbtGreen > 255)
{
image[i][j].rgbtGreen = 255;
}
}
}
return;
}
```[enter image description here][1]
[1]: https://i.stack.imgur.com/3bExI.png
I am trying to make a little algorithm that colors a point a certain color based on what side of a line the point is on. This is what i have at the moment. The code doesnt give any errors, but the colors also arent correct for the dots.. Could someone point out to me what i am doing wrong?
See the code:
PVector[] points;
void setup() {
size(500, 500);
points = new PVector[10];
for (int i = 0; i < points.length; i++) {
points[i] = new PVector(random(0, width), random(0, height));
}
ExtremesLine(points);
}
void ExtremesLine(PVector[] pts) {
float maxx = 0, minx = width+1;
PVector min = new PVector(), max = new PVector();
ArrayList<PVector> groupA = new ArrayList<PVector>(), groupB = new ArrayList<PVector>();
for (int i = 0; i < pts.length; i++) {
if (pts[i].x > maxx) {
maxx = pts[i].x;
max = pts[i];
}
if (pts[i].x < minx) {
minx = pts[i].x;
min = pts[i];
}
}
PVector divisionLine = new PVector();
PVector.sub(max, min, divisionLine);
PVector normal = new PVector(-divisionLine.y, divisionLine.x).normalize();
for (int i = 0; i < pts.length; i++) {
float s = PVector.dot(normal, pts[i].copy().normalize());
if ( s < 0) groupA.add(pts[i]);
else if ( s > 0) groupB.add(pts[i]);
}
fill(0);
line(min.x, min.y, max.x, max.y);
for (int i = 0; i < groupA.size(); i++) {
fill(255, 0, 0);
ellipse(groupA.get(i).x, groupA.get(i).y, 10, 10);
}
for (int i = 0; i < groupB.size(); i++) {
fill(0, 0, 255);
ellipse(groupB.get(i).x, groupB.get(i).y, 10, 10);
}
}
As you can see from the images below sometimes it works but 90% of the time it doesnt. First image is the correct result, second image is the incorrect result
If there is anything unclear pls let me know so i can clarify!
You need to dot with vectors from min:
replace
float s = PVector.dot(normal, pts[i].copy().normalize());
by
float s = PVector.dot(normal, pts[i].copy().sub(min).normalize());
and it will work as expected:
Tangentially, since min and max are Processing built-ins, are you sure that you want to use them as variable names?
I've been working on an LED wall and came into a RAM issue. Basically I am using teensy 3.0 and trying to load the following script, however, the script errors out with .bss will not fit into region 'RAM'
Please help! Any information would be greatly appreciated! Thanks!
/*
Nike NFL draft LED wall program
OctoWS2811 BasicTest.ino - Basic RGB LED Test
http://www.pjrc.com/teensy/td_libs_OctoWS2811.html
Copyright (c) 2013 Paul Stoffregen, PJRC.COM, LLC
*/
#include <OctoWS2811.h>
const int ledsPerStrip = 290;
DMAMEM int displayMemory[ledsPerStrip*6];
int drawingMemory[ledsPerStrip*6];
const int config = WS2811_GRB | WS2811_800kHz;
OctoWS2811 leds(ledsPerStrip, displayMemory, drawingMemory, config);
#define ORANGE 0xE05800
#define WHITE 0xFFFFFF
#define BLACK 0x000000
#define BLACK2 0x1E1E1E
void setup() {
leds.begin();
leds.show();
}
static int widths[] = { 30, 30, 50, 90, 40, 60 };
static int speeds[] = { 5, 5, 10, 16, 11, 13 };
static int locations[] = { 0, 0, 0, 0, 0, 0 };
static int counter = 0;
//static int location = 0;
static boolean reverse = false;
int blend(int source , float alpha) {
int source_r = (source >> 16);
int source_g = ((source >> 8) & 0x00FF);
int source_b = (source & 0x0000FF);
source_r = source_r * alpha;
source_g = source_g * alpha;
source_b = source_b * alpha;
return source_b | (source_g << 8) | (source_r << 16);
}
void loop() {
int microsec = 2000000 / leds.numPixels(); // change them all in 2 seconds
int location;
int offset;
int width;
int current;
int min;
int color;
float alpha = 0.95; // Set brightness of head
int head_width = 3; // Set width of head
delay(20);
int i;
for(i = 0; i < 6; ++i) {
location = locations[i];
width = widths[i];
color = 0xFFFFFF;
offset = i*ledsPerStrip;
location = location + speeds[i];
if(location > ledsPerStrip + width) {
location = 0;
}
locations[i] = location;
if(location < width) {
current = location;
min = 0;
} else if(location >= width) {
current = location;
min = location - width + 1;
}
for(current; current >= 0; --current) {
if(current >= min) {
if(current < ledsPerStrip) {
if(!reverse) {
leds.setPixel(current + offset, color);
} else {
leds.setPixel((ledsPerStrip - current) + offset, color);
}
}
if(current < (location - head_width)) {
color = blend(color, alpha);
}
} else {
if(!reverse) {
leds.setPixel(current + offset, BLACK);
} else {
leds.setPixel((ledsPerStrip - current) + offset, BLACK);
}
}
}
}
leds.show();
counter++;
}
And the returned error:
This report would have more information with
"Show verbose output during compilation"
enabled in File > Preferences.
Arduino: 1.0.5 (Windows 7), Board: "Teensy 3.0"
c:/program files/arduino/hardware/tools/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/4.7.2/../../../../arm-none-eabi/bin/ld.exe: Nike_NFL_Program.cpp.elf section .bss' will not fit in regionRAM'
c:/program files/arduino/hardware/tools/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/4.7.2/../../../../arm-none-eabi/bin/ld.exe: region `RAM' overflowed by 1028 bytes
collect2.exe: error: ld returned 1 exit status
Thanks!
Your simply running out of memory. sizing it down from 6 to 5 compiles. Note that the 3.0 has 16384 of SRAM. Each multiple of consumes a large chunk of the limited 16384 of SRAM.
I think you should put the code of static or variable to DRAM.
Such as the code you supply.
From:
const int ledsPerStrip = 290;
DMAMEM int displayMemory[ledsPerStrip*6];
int drawingMemory[ledsPerStrip*6];
const int config = WS2811_GRB | WS2811_800kHz;
static int widths[] = { 30, 30, 50, 90, 40, 60 };
static int speeds[] = { 5, 5, 10, 16, 11, 13 };
static int locations[] = { 0, 0, 0, 0, 0, 0 };
static int counter = 0;
To:
#include "link_defs.h"
__SECTION(dram.rodata) const int ledsPerStrip = 290;
__SECTION(dram.bss) DMAMEM int displayMemory[ledsPerStrip*6];
__SECTION(dram.bss) int drawingMemory[ledsPerStrip*6];
__SECTION(dram.rodata) const int config = WS2811_GRB | WS2811_800kHz;
__SECTION(dram.data) static int widths[] = { 30, 30, 50, 90, 40, 60 };
__SECTION(dram.data) static int speeds[] = { 5, 5, 10, 16, 11, 13 };
__SECTION(dram.data) static int locations[] = { 0, 0, 0, 0, 0, 0 };
__SECTION(dram.data) static int counter = 0;
I'm having a dilemma with this code and have no clue what to do. I'm pretty new to processing. This is a project from this link...
http://blog.makezine.com/2012/08/10/build-a-touchless-3d-tracking-interface-with-everyday-materials/
any help is massively appreciated... Thanks in advance
import processing.serial.*;
import processing.opengl.*;
Serial serial;
int serialPort = 1;
int sen = 3; // sensors
int div = 3; // board sub divisions
Normalize n[] = new Normalize[sen];
MomentumAverage cama[] = new MomentumAverage[sen];
MomentumAverage axyz[] = new MomentumAverage[sen];
float[] nxyz = new float[sen];
int[] ixyz = new int[sen];
float w = 256; // board size
boolean[] flip = {
false, true, false};
int player = 0;
boolean moves[][][][];
PFont font;
void setup() {
size(800, 600, P3D);
frameRate(25);
font = loadFont("TrebuchetMS-Italic-20.vlw");
textFont(font);
textMode(SCREEN);
println(Serial.list());
serial = new Serial(this, Serial.list()[serialPort], 115200);
for(int i = 0; i < sen; i++) {
n[i] = new Normalize();
cama[i] = new MomentumAverage(.01);
axyz[i] = new MomentumAverage(.15);
}
reset();
}
void draw() {
updateSerial();
drawBoard();
}
void updateSerial() {
String cur = serial.readStringUntil('\n');
if(cur != null) {
String[] parts = split(cur, " ");
if(parts.length == sensors) {
float[] xyz = new float[sen];
for(int i = 0; i < sen; i++)
xyz[i] = float(parts[i]);
if(mousePressed && mouseButton == LEFT)
for(int i = 0; i < sen; i++)
n[i].note(xyz[i]);
nxyz = new float[sen];
for(int i = 0; i < sen; i++) {
float raw = n[i].choose(xyz[i]);
nxyz[i] = flip[i] ? 1 - raw : raw;
cama[i].note(nxyz[i]);
axyz[i].note(nxyz[i]);
ixyz[i] = getPosition(axyz[i].avg);
}
}
}
}
float cutoff = .2;
int getPosition(float x) {
if(div == 3) {
if(x < cutoff)
return 0;
if(x < 1 - cutoff)
return 1;
else
return 2;
}
else {
return x == 1 ? div - 1 : (int) x * div;
}
}
void drawBoard() {
background(255);
float h = w / 2;
camera(
h + (cama[0].avg - cama[2].avg) * h,
h + (cama[1].avg - 1) * height / 2,
w * 2,
h, h, h,
0, 1, 0);
pushMatrix();
noStroke();
fill(0, 40);
translate(w/2, w/2, w/2);
rotateY(-HALF_PI/2);
box(w);
popMatrix();
float sw = w / div;
translate(h, sw / 2, 0);
rotateY(-HALF_PI/2);
pushMatrix();
float sd = sw * (div - 1);
translate(
axyz[0].avg * sd,
axyz[1].avg * sd,
axyz[2].avg * sd);
fill(255, 160, 0);
noStroke();
sphere(18);
popMatrix();
for(int z = 0; z < div; z++) {
for(int y = 0; y < div; y++) {
for(int x = 0; x < div; x++) {
pushMatrix();
translate(x * sw, y * sw, z * sw);
noStroke();
if(moves[0][x][y][z])
fill(255, 0, 0, 200);
else if(moves[1][x][y][z])
fill(0, 0, 255, 200);
else if(
x == ixyz[0] &&
y == ixyz[1] &&
z == ixyz[2])
if(player == 0)
fill(255, 0, 0, 200);
else
fill(0, 0, 255, 200);
else
fill(0, 100);
box(sw / 3);
popMatrix();
}
}
}
fill(0);
if(mousePressed && mouseButton == LEFT)
msg("defining boundaries");
}
void keyPressed() {
if(key == TAB) {
moves[player][ixyz[0]][ixyz[1]][ixyz[2]] = true;
player = player == 0 ? 1 : 0;
}
}
void mousePressed() {
if(mouseButton == RIGHT)
reset();
}
void reset() {
moves = new boolean[2][div][div][div];
for(int i = 0; i < sen; i++) {
n[i].reset();
cama[i].reset();
axyz[i].reset();
}
}
void msg(String msg) {
text(msg, 10, height - 10);
}
You are missing a class, in fact, more than one. Go back to the github and download, or copy and paste, all three codes, placing each one in a new tab named same name of the class (well this is not required, but is a good practice). The TicTacToe3D.pde is the main code. To make a new tab choose "new tab" from the arrow menu in Processing IDE (just below the standard button at the right). The code should run. WIll need an Arduino though to really get it working.