Reputation: 37
I am trying to convert a normal 2D image(a simple JPEG) into a 3D Dot Diagram that the user can move around with. But upon trying to render that dot diagram, the program becomes extremely slow. Can anyone point where I am going wrong?
var x = [];
var y = [];
var z = [];
var colors = [];
var a = 0;
var counter = 0;
let img;
function preload() {
img = loadImage('https://www.paulwheeler.us/files/clooney.jpeg');
}
function setup() {
createCanvas(720, 400, WEBGL);
background(0);
img.resize(width / 3, height / 2);
for (let col = 0; col < img.width; col += 3) {
for (let row = 0; row < img.height; row += 3) {
let c = img.get(col, row);
let rgb_val = c[0] + c[1] + c[2]
colors[a] = c
x[a] = map(col, 0, 255, -125, 125)
y[a] = map(row, 0, 255, -125, 125)
z[a] = map(rgb_val, 0, 765, -50, 0)
stroke(c)
push();
a++
}
}
}
function draw() {
translate(0, 0, -50);
rotateY(frameCount * 0.1);
background(0);
for (var i = 0; i < a; i++) {
stroke(colors[i])
push();
translate(x[i], y[i], z[i]);
sphere(1);
pop();
}
orbitControl();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Upvotes: 3
Views: 456
Reputation: 20150
As I mentioned in my comment above, drawing individual primitives in large quantities is relatively slow in p5.js. 3d graphics are best optimized when triangles are drawn from large vertex buffers (basically you precompute points in 3d space, their normal vectors for lighting, and a list of triangles that reference those points by index into the buffer). So the real winner here is the second sketch which generates some p5.Geometry
for your grid of dots and uses texture coordinates to achieve the desired colors for each dot.
Experimenting with detail for the sphere, and switching between using stroke and fill for the spheres (minimal impact on my system):
var x = [];
var y = [];
var z = [];
var colors = [];
var a = 0;
let img;
let fpsDisplay;
let strokeCheckbox;
let detailSlider;
let lastTime = 0;
function preload() {
img = loadImage('https://www.paulwheeler.us/files/clooney.jpeg');
}
function setup() {
createCanvas(720, 400, WEBGL);
background(0);
img.resize(width / 3, height / 2);
for (let col = 0; col < img.width; col += 3) {
for (let row = 0; row < img.height; row += 3) {
let c = img.get(col, row);
let rgb_val = c[0] + c[1] + c[2];
colors[a] = c;
x[a] = map(col, 0, img.width, -125, 125);
y[a] = map(row, 0, img.height, -125, 125);
z[a] = map(rgb_val, 0, 765, -50, 0);
a++;
}
}
fpsDisplay = createInput('0');
fpsDisplay.position(10, 10);
strokeCheckbox = createCheckbox('Stroke', false);
strokeCheckbox.position(10, 50);
strokeCheckbox.style('color', 'red');
detailSlider = createSlider(1, 24, 24);
detailSlider.position(10, 90);
}
function draw() {
// translate(0, 0, -50);
// rotateY(frameCount * 0.1);
background(0);
orbitControl(2, 1, 0.1);
noStroke();
noFill();
let useStroke = strokeCheckbox.checked();
let detail = detailSlider.value();
for (var i = 0; i < a; i++) {
if (useStroke) {
stroke(colors[i]);
} else {
fill(colors[i]);
}
push();
translate(x[i], y[i], z[i]);
sphere(1, detail, detail);
pop();
}
let t = millis();
fpsDisplay.value(`${1000 / (t - lastTime)}`);
lastTime = t;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Using p5.Geometry, UV coordinates, and texture (this hits 60 FPS on my system):
const dotRadius = 1;
const detail = 4;
let img;
let fpsDisplay;
let lastTime = 0;
let geom;
function preload() {
img = loadImage('https://www.paulwheeler.us/files/clooney.jpeg');
}
function setup() {
console.log('Initializing');
createCanvas(720, 400, WEBGL);
// Because the spheres are so small, the default stroke makes them all black.
// You could also use strokeWeight(0.1);
noStroke();
img.resize(width / 3, height / 2);
const dotGrid = function() {
const sliceCount = this.detailX + 1;
let dotNumber = 0;
for (let col = 0; col < img.width; col += 3) {
for (let row = 0; row < img.height; row += 3) {
let c = img.get(col, row);
let rgb_val = c[0] + c[1] + c[2];
let xOff = map(col, 0, img.width, -125, 125);
let yOff = map(row, 0, img.height, -125, 125);
let zOff = map(rgb_val, 0, 765, -50, 0);
for (let i = 0; i <= this.detailY; i++) {
const v = i / this.detailY;
const phi = PI * v - PI / 2;
const cosPhi = cos(phi);
const sinPhi = sin(phi);
for (let j = 0; j <= this.detailX; j++) {
const u = j / this.detailX;
const theta = 2 * PI * u;
const cosTheta = cos(theta);
const sinTheta = sin(theta);
const p = createVector(
xOff + dotRadius * cosPhi * sinTheta,
yOff + dotRadius * sinPhi,
zOff + dotRadius * cosPhi * cosTheta
);
this.vertices.push(p);
this.vertexNormals.push(p);
// All vertices in each dot get the same UV coordinates
this.uvs.push(map(col, 0, img.width, 0, 1), map(row, 0, img.height, 0, 1));
}
}
// Generate faces for the current dot
// offset = number of vertices for previous dots.
let offset = dotNumber * (this.detailX + 1) * (this.detailY + 1);
let v1, v2, v3, v4;
for (let i = 0; i < this.detailY; i++) {
for (let j = 0; j < this.detailX; j++) {
v1 = i * sliceCount + j + offset;
v2 = i * sliceCount + j + 1 + offset;
v3 = (i + 1) * sliceCount + j + 1 + offset;
v4 = (i + 1) * sliceCount + j + offset;
this.faces.push([v1, v2, v4]);
this.faces.push([v4, v2, v3]);
}
}
dotNumber++;
}
}
console.log(`Dots: ${dotNumber}`);
console.log(`Vertices: ${this.vertices.length}`);
console.log(`Faces: ${this.faces.length}`);
};
geom = new p5.Geometry(detail, detail, dotGrid);
geom.gid = 'dot-grid';
fpsDisplay = createInput('0');
fpsDisplay.position(10, 10);
}
function draw() {
background(0);
orbitControl(2, 1, 0.1);
texture(img);
model(geom);
let t = millis();
fpsDisplay.value(`${1000 / (t - lastTime)}`);
lastTime = t;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
Upvotes: 3