this is how I changed the top of the pyramid (using minus here)
and also
translate(currentPoint.x,
245+currentPoint.y, // !!!!!!!!!!!!!!!!!!!!!!
260-currentPoint.z);
this is how I changed the top of the pyramid (using minus here)
and also
translate(currentPoint.x,
245+currentPoint.y, // !!!!!!!!!!!!!!!!!!!!!!
260-currentPoint.z);
Here’s the one I posted on OpenProcessing:-
/**
* Copyright (c) 2011 Martin Prout
*
* This demo & library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* http://creativecommons.org/licenses/LGPL/2.1/
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
PVector[] pts = { // points of the unit tetrahedron
new PVector(-0.5, -0.5, -0.5),
new PVector(0.5, 0.5, -0.5),
new PVector(-0.5, 0.5, 0.5),
new PVector(0.5, -0.5, 0.5)
};
int TETRA_SIZE = 600;
void setup() {
size(800, 800, P3D);
}
void draw() {
background(0);
configureLights();
translate(width/2, height/2, -100);
rotateX(cos(radians(frameCount)));
rotateY(sin(radians(frameCount)));
noStroke();
drawSierpinski(pts);
}
void configureLights() {
ambientLight(100, 100, 100);
directionalLight(155, 0, 0, -1, -1, -1); // colored directional
directionalLight(0, 0, 155, 1, -1, -1); // lights essential
directionalLight(155, 155, 0, 1, 1, 1); // for 3D illusion
}
PVector midPoint(PVector a, PVector b) {
PVector result = PVector.add(a, b);
result.div(2);
return result;
}
void drawSierpinski(PVector[] pts) {
if (pts[0].dist(pts[1]) < 0.1) { // limits recursion on relative size
drawTetrahedron(pts, TETRA_SIZE); // render the tetrahedra
}
else {
PVector av = midPoint(pts[0], pts[1]); // a tetrahedron midpoint vertices
PVector bv = midPoint(pts[0], pts[2]); // b
PVector cv = midPoint(pts[0], pts[3]); // b
PVector dv = midPoint(pts[1], pts[2]); // d
PVector ev = midPoint(pts[1], pts[3]); // e
PVector fv = midPoint(pts[3], pts[2]); // e
PVector[] aa = {
pts[0],
av,
bv,
cv
};
PVector[] bb = {
av,
ev,
dv,
pts[1]
};
PVector[] cc = {
cv,
ev,
fv,
pts[3]
};
PVector[] dd = {
bv,
dv,
fv,
pts[2]
};
drawSierpinski(aa); // calculate further inner tetrahedra coordinates
drawSierpinski(bb);
drawSierpinski(cc);
drawSierpinski(dd);
}
}
void drawTetrahedron(PVector[] pts, float sz) {
fill(255);
beginShape(TRIANGLES);
vertex(pts[0].x*sz, pts[0].y*sz, pts[0].z*sz); // 1
vertex(pts[1].x*sz, pts[1].y*sz, pts[1].z*sz); // 2
vertex(pts[2].x*sz, pts[2].y*sz, pts[2].z*sz); // 3
vertex(pts[2].x*sz, pts[2].y*sz, pts[2].z*sz); // 3
vertex(pts[0].x*sz, pts[0].y*sz, pts[0].z*sz); // 1
vertex(pts[3].x*sz, pts[3].y*sz, pts[3].z*sz); // 4
vertex(pts[3].x*sz, pts[3].y*sz, pts[3].z*sz); // 4
vertex(pts[2].x*sz, pts[2].y*sz, pts[2].z*sz); // 3
vertex(pts[1].x*sz, pts[1].y*sz, pts[1].z*sz); // 2
vertex(pts[1].x*sz, pts[1].y*sz, pts[1].z*sz); // 2
vertex(pts[3].x*sz, pts[3].y*sz, pts[3].z*sz); // 4
vertex(pts[0].x*sz, pts[0].y*sz, pts[0].z*sz); // 1
endShape();
}
Here translated to RubyArt and making use of Vec3D
:to_vertex
method
# frozen_string_literal: true
PTS = [ # points of the unit tetrahedron
Vec3D.new(-0.5, -0.5, -0.5),
Vec3D.new(0.5, 0.5, -0.5),
Vec3D.new(-0.5, 0.5, 0.5),
Vec3D.new(0.5, -0.5, 0.5)
].freeze
SIZE = 600
def settings
size 800, 800, P3D
end
def setup
sketch_title '3D Sierpinksi'
end
def renderer
@renderer ||= GfxRender.new(self.g)
end
def draw
background(0)
configure_lights
translate(width / 2, height / 2, -100)
rotate_x(cos(frame_count.radians))
rotate_y(sin(frame_count.radians))
no_stroke
draw_sierpinski(PTS)
end
def configure_lights
ambient_light(100, 100, 100)
directional_light(155, 0, 0, -1, -1, -1) # colored directional
directional_light(0, 0, 155, 1, -1, -1) # lights essential
directional_light(155, 155, 0, 1, 1, 1) # for 3D illusion
end
def mid_point(a, b)
(a + b) / 2.0
end
def draw_sierpinski(pts)
if pts[0].dist(pts[1]) < 0.1 # limits recursion on relative size
draw_tetrahedron(pts, SIZE) # render the tetrahedra
else
av = mid_point(pts[0], pts[1]) # a tetrahedron midpoint vertices
bv = mid_point(pts[0], pts[2]) # b
cv = mid_point(pts[0], pts[3]) # b
dv = mid_point(pts[1], pts[2]) # d
ev = mid_point(pts[1], pts[3]) # e
fv = mid_point(pts[3], pts[2]) # e
aa = [pts[0], av, bv, cv]
bb = [av, ev, dv, pts[1]]
cc = [cv, ev, fv, pts[3]]
dd = [bv, dv, fv, pts[2]]
draw_sierpinski(aa) # calculate further inner tetrahedra coordinates
draw_sierpinski(bb)
draw_sierpinski(cc)
draw_sierpinski(dd)
end
end
def draw_tetrahedron(pts, sz)
fill(255)
begin_shape(TRIANGLES)
(pts[0] * sz).to_vertex(renderer) # 1
(pts[1] * sz).to_vertex(renderer) # 2
(pts[2] * sz).to_vertex(renderer) # 3
(pts[2] * sz).to_vertex(renderer) # 3
(pts[0] * sz).to_vertex(renderer) # 1
(pts[3] * sz).to_vertex(renderer) # 4
(pts[3] * sz).to_vertex(renderer) # 4
(pts[2] * sz).to_vertex(renderer) # 3
(pts[1] * sz).to_vertex(renderer) # 2
(pts[1] * sz).to_vertex(renderer) # 2
(pts[3] * sz).to_vertex(renderer) # 4
(pts[0] * sz).to_vertex(renderer) # 1
end_shape
end
These are great! Especially that shader version @monkstone posted. I used a a Shadertoy example and just replaced the shader, worked nicely! I haven’t used shaders much previously, they seem almost like magic!
@Chrisir Thanks also for the pointers about PeasyCam.
For the ArcBall variant (mouse wheel zoom-able, mouse drag rotatable variant) substitute lines:-
def setup
sketch_title '3D Sierpinksi'
ArcBall.init(self)
end
def renderer
@renderer ||= GfxRender.new(self.g)
end
def draw
background(0)
configure_lights
no_stroke
draw_sierpinski(PTS)
end
Note no need to translate sketch as ArcBall is designed to center (centre in the Queens English) sketch around the origin.
That you did! Nice!
It was in the back of my head that there were probably some redundancy in the points that were drawn. My FPS went up from ~33 to ~45 with recursion depth 6 using your method. With depth 7 it was only from about 9 FPS to 11 FPS though.
Continued your fix with negative coordinates, so the shapes line up and doesn’t need rotating:
new PVector(150, 0, -260),
new PVector(150, -245, -130)
I might have made it slightly worse again. Played with your mini tetrahedron shapes and made recursion depth depending on camera distance (kind of a mipmap).
/** Sierpinski test 8 - 3D (Tetrahedron)
All paths method (using recursion)
2020.08.31 modified Chrisir's variation
2020.08.30 raron
*/
import peasy.*;
int depth = 6; // recursion depth - try 6
int displayedDepth = depth;
float camMaxDistance = 3000.0;
// 3D Sierpinski tetrahedron vertices
PVector [] coord =
{
new PVector( 0, 0, 0),
new PVector(300, 0, 0),
new PVector(150, 0, -260),
new PVector(150, -245, -130)
};
color[] vertexColor =
{
color(255, 0, 0),
color( 0, 255, 0),
color( 0, 0, 255),
color(255, 255, 255)
};
int verts = coord.length;
PeasyCam cam;
// random start point
PVector startPoint = new PVector(150, -122.5, -130);
PShape[] ps = new PShape[depth+1];
PShape outline;
//------------------------------------------------------------------------------
// Two core functions
void setup() {
size(600, 600, P3D);
avoidClipping();
cam = new PeasyCam(this, startPoint.x, -startPoint.y, -startPoint.z, 400);
//cam.setMinimumDistance(200);
cam.setMaximumDistance(camMaxDistance);
PVector startPoint = new PVector(0, 0, 0);
PVector currentPoint=new PVector(0, 0, 0);
PVector sVertice;
// Make different sized tetrahedron shapes based on currently displayed recursion depth
for( int d=0; d<depth+1; d++)
{
ps[d] = createShape();
ps[d].beginShape(TRIANGLE_STRIP);
//for (PVector sVertice : coord) {
// currentPoint.set((startPoint.x+sVertice.x)/2, (startPoint.y+sVertice.y)/2, (startPoint.z+sVertice.z)/2);
float f = pow(2,d);
// base 1
sVertice = coord[0].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// top
sVertice = coord[3].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// base 2
sVertice = coord[1].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// top
sVertice = coord[3].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// base 3
sVertice = coord[2].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// base 1
sVertice = coord[0].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// }//for
ps[d].endShape();
}
// Outline shape
/* outline = createShape();
outline.beginShape(TRIANGLE_STRIP);
outline.noFill();
outline.stroke(255,0,0);
float f = 1;
// base 1
sVertice = coord[0].copy();
outline.vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// top
sVertice = coord[3].copy();
outline.vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// base 2
sVertice = coord[1].copy();
outline.vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// top
sVertice = coord[3].copy();
outline.vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// base 3
sVertice = coord[2].copy();
outline.vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// base 1
sVertice = coord[0].copy();
outline.vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
outline.endShape();
*/
}
void draw() {
background(32);
float ratio = 1-((float)cam.getDistance()/camMaxDistance);
displayedDepth = int((depth+1) * ratio);
if (displayedDepth > depth) displayedDepth = depth;
lights();
sierpinski(startPoint, displayedDepth);
translate(0, 245, 260);
//shape(outline, 0, 0);
// cam.beginHUD();
// text(" displayedDepth " + displayedDepth, 50, 50);
// cam.endHUD();
}
//------------------------------------------------------------------------------
// Other functions
void sierpinski(PVector currentPoint, int currentDepth) {
if (currentDepth < 0) return;
PVector temp = currentPoint.copy();
for (PVector sVertice : coord) {
currentPoint.set((currentPoint.x+sVertice.x)/2, (currentPoint.y+sVertice.y)/2, (currentPoint.z+sVertice.z)/2);
sierpinski(currentPoint, currentDepth-1);
currentPoint = temp.copy();
}//for
if (currentDepth == 0) {
stroke(colorSpace(currentPoint));
pushMatrix();
translate(currentPoint.x,
245+currentPoint.y,
260+currentPoint.z);
ps[displayedDepth].setFill(colorSpace(currentPoint));
//rotateY(PI/3);
shape(ps[displayedDepth], 0, 0);
popMatrix();
return; // Leave
}//if
}
// Return a color depending on distances from vertices
color colorSpace(PVector spacePos) {
float temp;
color tempCol = color(0, 0, 0);
for (int i=0; i<verts; i++)
{
temp = 1-(spacePos.dist(coord[i])/300.0);
tempCol = lerpColor(tempCol, vertexColor[i], temp);
}
return tempCol;
}
void avoidClipping() {
// avoid clipping (at camera):
// https : //
// forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
perspective(PI/3.0, (float) width/height, 1, 1000000);
}//func
Quick minor edit: Better depth calculation (should have tested a bit more before posting).
Added ratio = pow(ratio, 3);
to the draw() function:
void draw() {
background(32);
float ratio = 1-((float)cam.getDistance()/camMaxDistance);
ratio = pow(ratio, 3);
displayedDepth = int((depth+1) * ratio);
if (displayedDepth > depth) displayedDepth = depth;
lights();
sierpinski(startPoint, displayedDepth);
translate(0, 245, 260);
}
One more for today (last one?). Just procrastinating at this point but couldn’t let it be.
/** Sierpinski test 9 - 3D (Tetrahedron)
All paths method (using recursion)
2020.08.31 modified Chrisir's variation
2020.08.30 raron
*/
import peasy.*;
int depth = 8; // Max recursion depth - keep < 9 ish
int displayedDepth = depth;
float camMaxDistance = 3000.0;
// 3D Sierpinski tetrahedron vertices
PVector [] coord =
{
new PVector( 0, 0, 0),
new PVector( 300, 0, 0),
new PVector( 150, 0, -260),
new PVector( 150, -245, -130)
};
color[] vertexColor =
{
color(255, 0, 0),
color( 0, 255, 0),
color( 0, 0, 255),
color(255, 255, 255)
};
int verts = coord.length;
PeasyCam cam;
// random start point
PVector startPoint = new PVector(150, -122.5, -130);
PShape[] ps = new PShape[depth+1];
//------------------------------------------------------------------------------
// Two core functions
void setup() {
size(600, 600, P3D);
avoidClipping();
cam = new PeasyCam(this, startPoint.x, -startPoint.y, -startPoint.z, 400);
//cam.setMinimumDistance(200);
cam.setMaximumDistance(camMaxDistance);
PVector startPoint = new PVector(0, 0, 0);
PVector currentPoint=new PVector(0, 0, 0);
PVector sVertice;
// Make different sized tetrahedron shapes based on currently displayed recursion depth
for( int d=0; d<depth+1; d++)
{
ps[d] = createShape();
ps[d].beginShape(TRIANGLE_STRIP);
ps[d].noStroke();
//for (PVector sVertice : coord) {
// currentPoint.set((startPoint.x+sVertice.x)/2, (startPoint.y+sVertice.y)/2, (startPoint.z+sVertice.z)/2);
float f = pow(2,d);
// base 1
sVertice = coord[0].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// top
sVertice = coord[3].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// base 2
sVertice = coord[1].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// top
sVertice = coord[3].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// base 3
sVertice = coord[2].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// base 1
sVertice = coord[0].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// }//for
ps[d].endShape();
}
}
void draw() {
background(32);
float ratio = 1-((float)cam.getDistance()/camMaxDistance);
ratio = pow(ratio, 3);
displayedDepth = int((depth+1) * ratio);
if (displayedDepth > depth) displayedDepth = depth;
lights();
sierpinski(startPoint, displayedDepth, 3);
// cam.beginHUD();
// text(" displayedDepth " + displayedDepth, 50, 50);
// cam.endHUD();
}
//------------------------------------------------------------------------------
// Other functions
void sierpinski(PVector currentPoint, int currentDepth, int vert) {
//if (currentDepth < 0) return; // Leave
if (currentDepth == 0)
{
pushMatrix();
translate(currentPoint.x,
245+currentPoint.y,
260+currentPoint.z);
//stroke(colorSpace(currentPoint));
ps[displayedDepth].setFill(colorSpace(currentPoint, vert));
shape(ps[displayedDepth], 0, 0);
popMatrix();
return; // Leave
}
PVector temp = currentPoint.copy();
for (int v=0; v<verts; v++)
{
currentPoint.set((currentPoint.x+coord[v].x)/2, (currentPoint.y+coord[v].y)/2, (currentPoint.z+coord[v].z)/2);
sierpinski(currentPoint, currentDepth-1, v);
currentPoint = temp.copy();
}
}
// Return a color depending on distances from vertices
// and "direction" of point (which vertice it moves towards)
color colorSpace(PVector spacePos, int vert) {
float temp;
color tempCol = color(0, 0, 0); // overall-ish color (unless very dark)
//color tempCol = vertexColor[verts-vert-1];
for (int i=0; i<verts; i++)
{
temp = 1-(spacePos.dist(coord[i])/300.0);
if (i == vert) temp = pow(temp, 5);
tempCol = lerpColor(tempCol, vertexColor[i], temp);
}
return tempCol;
}
void avoidClipping() {
// avoid clipping (at camera):
// https : //
// forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
perspective(PI/3.0, (float) width/height, 1, 1000000);
}//func
your colorSpace() function is very time consuming
Can’t we treat the entire pyramid as having ONE color only? I wasn’t able to do it. Maybe you can do it without the for-loop? And temp = pow(temp, 5);
is just temp=temptemp…;
but here is a simple example
(I also simplified Sierpinski function again)
/** Sierpinski test 9 - 3D (Tetrahedron)
All paths method (using recursion)
2020.08.31 modified Chrisir's variation
2020.08.31 raron + 1
*/
import peasy.*;
int depth = 8; // Max recursion depth - keep < 9 ish
int displayedDepth = depth;
float camMaxDistance = 3000.0;
// 3D Sierpinski tetrahedron vertices
PVector [] coord = {
new PVector( 0, 0, 0),
new PVector( 300, 0, 0),
new PVector( 150, 0, -260),
new PVector( 150, -245, -130)
};
color[] vertexColor = {
color(255, 0, 0),
color( 0, 255, 0),
color( 0, 0, 255),
color(255, 255, 255)
};
int verts = coord.length;
PeasyCam cam;
// random start point
PVector startPoint = new PVector(150, -122.5, -130);
PShape[] ps = new PShape[depth+1];
//------------------------------------------------------------------------------
// Two core functions
void setup() {
size(600, 600, P3D);
avoidClipping();
cam = new PeasyCam(this, startPoint.x, -startPoint.y, -startPoint.z, 400);
//cam.setMinimumDistance(200);
cam.setMaximumDistance(camMaxDistance);
//PVector startPoint = new PVector(0, 0, 0);
//PVector currentPoint=new PVector(0, 0, 0);
PVector sVertice;
// Make different sized tetrahedron shapes based on currently displayed recursion depth
for ( int d=0; d<depth+1; d++) {
ps[d] = createShape();
ps[d].beginShape(TRIANGLE_STRIP);
ps[d].noStroke();
float f = pow(2, d);
// base 1
sVertice = coord[0].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// top
sVertice = coord[3].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// base 2
sVertice = coord[1].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// top
sVertice = coord[3].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// base 3
sVertice = coord[2].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
// base 1
sVertice = coord[0].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
ps[d].endShape();
}
}
void draw() {
background(32);
float ratio = 1-((float)cam.getDistance()/camMaxDistance);
ratio = pow(ratio, 3);
displayedDepth = int((depth+1) * ratio);
if (displayedDepth > depth) displayedDepth = depth;
lights();
sierpinski(startPoint, displayedDepth, 3);
}
//------------------------------------------------------------------------------
// Other functions
void sierpinski(PVector currentPoint, int currentDepth, int vert) {
if (currentDepth == 0) {
pushMatrix();
translate(currentPoint.x,
245+currentPoint.y,
260+currentPoint.z);
ps[displayedDepth].setFill(colorSpace(currentPoint, vert));
// fill(colorSpace(currentPoint, vert));
shape(ps[displayedDepth], 0, 0);
popMatrix();
return; // Leave
}
for (int v=0; v<verts; v++) {
sierpinski(new PVector((currentPoint.x+coord[v].x)/2, (currentPoint.y+coord[v].y)/2, (currentPoint.z+coord[v].z)/2),
currentDepth-1,
v);
}
}
// Return a color depending on distances from vertices
// and "direction" of point (which vertice it moves towards)
color colorSpace(PVector spacePos, int vert) {
float temp;
color tempCol = color(111, 0, 0); // overall-ish color (unless very dark)
//for (int i=0; i<verts; i++) {
//temp = 1-(spacePos.dist(coord[i])/300.0);
//if (i == vert)
//temp = pow(temp, 5);
tempCol = lerpColor(tempCol, vertexColor[0], 1-(spacePos.dist(coord[0])/300.0));
// }
return tempCol;
}
void avoidClipping() {
// avoid clipping (at camera):
// https : //
// forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
perspective(PI/3.0, (float) width/height, 1, 1000000);
} // func
//
It got room for improvements, yes Aware pow() is somewhat costly.
As for one color only, it’s only a matter of not using the colorSpace function at all (and make all shapes the same color).
(Both coloring and 3D shape “mipmapping” is just playing with it ofc)
Nice! The only thing is that the “mipmap” transitions isn’t smooth now. I’m a bit puzzled as to why. For the sake of just displaying a Sierpinski, it doesn’t matter.
/** Sierpinski test 10 - 3D (Tetrahedron)
All paths method (using recursion)
2020.09.01 modified Chrisir's variation #2
2020.08.31 modified Chrisir's variation
2020.08.31 raron + 1
*/
import peasy.*;
int depth = 8; // Max recursion depth - keep < 9 ish
color pyramidColor = color(111, 0, 0);
int displayedDepth = depth;
float camMaxDistance = 3000.0;
// 3D Sierpinski tetrahedron vertices
PVector [] coord = {
new PVector( 0, 0, 0),
new PVector( 300, 0, 0),
new PVector( 150, 0, -260),
new PVector( 150, -245, -130)
};
int verts = coord.length;
PeasyCam cam;
// random start point
PVector startPoint = new PVector(150, -122.5, -130);
PShape[] ps = new PShape[depth+1];
//------------------------------------------------------------------------------
// Two core functions
void setup() {
size(600, 600, P3D);
avoidClipping();
cam = new PeasyCam(this, startPoint.x, -startPoint.y, -startPoint.z, 400);
//cam.setMinimumDistance(200);
cam.setMaximumDistance(camMaxDistance);
// Mipmapping shapes
// Make different sized tetrahedron shapes based on currently displayed recursion depth
PVector sVertice;
// base 1, top, base 2, top, base 3, base 1
int[] vertList = {0, 3, 1, 3, 2, 0};
for (int d=0; d<depth+1; d++) {
float f = pow(2, d);
ps[d] = createShape();
ps[d].beginShape(TRIANGLE_STRIP);
ps[d].noStroke();
for (int v=0; v<vertList.length; v++) {
sVertice = coord[vertList[v]].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f);
}
ps[d].endShape();
ps[d].setFill(pyramidColor);
}
}
void draw() {
background(32);
float ratio = 1-((float)cam.getDistance()/camMaxDistance);
ratio = pow(ratio, 3);
displayedDepth = int((depth+1) * ratio);
if (displayedDepth > depth) displayedDepth = depth;
lights();
sierpinski(startPoint, displayedDepth, 0);
}
//------------------------------------------------------------------------------
// Other functions
void sierpinski(PVector currentPoint, int currentDepth, int vert) {
if (currentDepth == 0) {
pushMatrix();
translate(currentPoint.x,
245+currentPoint.y,
260+currentPoint.z);
//ps[displayedDepth].setFill(colorSpace(currentPoint, vert));
shape(ps[displayedDepth], 0, 0);
popMatrix();
return; // Leave
}
for (int v=0; v<verts; v++) {
sierpinski(new PVector(
(currentPoint.x+coord[v].x)/2,
(currentPoint.y+coord[v].y)/2,
(currentPoint.z+coord[v].z)/2),
currentDepth-1,
v);
}
}
void avoidClipping() {
// avoid clipping (at camera):
// https : //
// forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
perspective(PI/3.0, (float) width/height, 1, 1000000);
} // func
//
@noel There’s also the arrow head curve challenge at rosetta code, which I’m pretty sure I created with my LSystems library and in ruby-processing or JRubyArt. Here is my lsystems version of a regular sierpinski from my ruby-processing blog.
When using ortho(); instead of perspective(), the sketch seems to rotate more correctly around it’s center.
Or maybe it’s the startPoint?
I think one problem here is that the triangle coordinates are not around 0,0,0
monkstone‘s sketch has this better
I’m a bit puzzled by that too. startPoint is one suspect, another one is that I made an error one or two of the tetrahedron vertices (not symmetrical?).
If you hold down a Shift key before and during rotating, PeasyCam is restricted to one direction only. As far as I can see it’s pretty centered (but maybe not symmetrical, as mentioned).
Part of it I blame Chrisir’s (sorry lol) simplified Sierpinski, which adds a displacement depending on the recursion depth (but it’s not the whole story). It helps if using my older Sierpinski with a temp variable:
void sierpinski(PVector currentPoint, int currentDepth, int vert) {
if (currentDepth == 0) {
pushMatrix();
translate(currentPoint.x,
245+currentPoint.y,
260+currentPoint.z);
shape(ps[displayedDepth], 0, 0);
popMatrix();
return; // Leave
}
PVector temp = currentPoint.copy();
for (int v=0; v<verts; v++) {
currentPoint.set((currentPoint.x+coord[v].x)/2, (currentPoint.y+coord[v].y)/2, (currentPoint.z+coord[v].z)/2);
sierpinski(currentPoint, currentDepth-1, v);
currentPoint = temp.copy();
}
}
Got sidetracked a bit.
But it’s no good. It’s very, very dark (any way of improving that?).
Kind of fun to try though, mipmapping 3D shapes textured with 2D sierpinski.
A better way would probably be to have a sort of distance-dependent recursion depth for the 3D shapes, or points, instead, but currently I have no idea how to do that (not sure I want to go there either… or do I?).
Also, transparent textures on PShapes behaves very odd! The different sides (or planes) seem to have a sort of priority when deciding to which other planes they will be transparent. Some are transparent, some are not, some are transparent to some but not all others…
Anyway, the ugly code (Just trying out a few things. But feel free to suggest improvements etc.)
/** Sierpinski test 10 - 3D (Tetrahedron) with textured shapes for added details illusion
All paths method (using recursion)
2020.09.01 Monocolored with textured shapes for added details (but everything is too dark)
2020.08.31 modified Chrisir's variation
2020.08.31 raron + 1
*/
import peasy.*;
int depth = 6; // Max recursion depth - keep < 9 ish
color pyramidColor = color(255);
int displayedDepth = depth;
float camMaxDistance = 3000.0;
PImage sierpinskiTex = createImage(200,200,ARGB);
//PImage sierpinskiTex = createImage(600,600,RGB);
// 3D Sierpinski tetrahedron vertices
PVector [] coord = {
new PVector( 0, 0, 0),
new PVector( 300, 0, 0),
new PVector( 150, 0, -260),
new PVector( 150, -245, -130)
};
int verts = coord.length;
PeasyCam cam;
// "random" start point (mid point)
PVector startPoint = new PVector(150, -122.5, -130);
PShape[] ps = new PShape[depth+1];
//------------------------------------------------------------------------------
// Two core functions
void setup() {
size(600, 600, P3D);
avoidClipping();
cam = new PeasyCam(this, startPoint.x, -startPoint.y, -startPoint.z, 400);
//cam.setMinimumDistance(200);
cam.setMaximumDistance(camMaxDistance);
// Make 2D texture
sierpinski2D.plotOnImage(8, pyramidColor, sierpinskiTex);
// Mipmapping shapes
// Make different sized tetrahedron shapes based on currently displayed recursion depth
PVector sVertice;
int u = sierpinskiTex.width;
int v = sierpinskiTex.height;
for ( int d=0; d<depth+1; d++) {
ps[d] = createShape();
ps[d].beginShape(TRIANGLE_STRIP);
ps[d].noStroke();
float f = pow(2, d);
// base 1
sVertice = coord[0].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f, 0, v);
// top
sVertice = coord[3].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f, u/2, 0);
// base 2
sVertice = coord[1].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f, u, v);
// top
sVertice = coord[3].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f, u/2, 0);
// base 3
sVertice = coord[2].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f, 0, v);
// base 1
sVertice = coord[0].copy();
ps[d].vertex(sVertice.x/f, sVertice.y/f, sVertice.z/f, u, v);
ps[d].endShape();
ps[d].setTexture(sierpinskiTex);
}
}
void draw() {
background(0);
float ratio = 1-((float)cam.getDistance()/camMaxDistance);
ratio = pow(ratio, 3);
displayedDepth = int((depth+1) * ratio);
if (displayedDepth > depth) displayedDepth = depth;
lights();
//ambientLight(255, 255, 255);
sierpinski(startPoint, displayedDepth, 0);
}
//------------------------------------------------------------------------------
// Other functions
void sierpinski(PVector currentPoint, int currentDepth, int vert) {
if (currentDepth == 0) {
pushMatrix();
translate(currentPoint.x,
245+currentPoint.y,
260+currentPoint.z);
shape(ps[displayedDepth], 0, 0);
popMatrix();
return; // Leave
}
PVector temp = currentPoint.copy();
for (int v=0; v<verts; v++) {
currentPoint.set((currentPoint.x+coord[v].x)/2, (currentPoint.y+coord[v].y)/2, (currentPoint.z+coord[v].z)/2);
sierpinski(currentPoint, currentDepth-1, v);
currentPoint = temp.copy();
}
}
void avoidClipping() {
// avoid clipping (at camera):
// https : //
// forum.processing.org/two/discussion/4128/quick-q-how-close-is-too-close-why-when-do-3d-objects-disappear
//ortho();
perspective(PI/3.0, (float) width/height, 1, 1000000);
} // func
//
And the 2D class:
static class sierpinski2D
{
static int depth; // recursion depth
static color sierpinskiColor;
static PVector startPoint;
static PImage texture2D;
static PVector[] vert2D = new PVector[3];
sierpinski2D(int _depth, color _sierpinskiColor, PImage _texture2D)
{
texture2D = _texture2D;
depth = _depth;
sierpinskiColor = _sierpinskiColor;
// random start point (at the middle)
int tw = texture2D.width;
int th = texture2D.height;
startPoint = new PVector(tw/2, th/2);
vert2D[0] = new PVector( 0, th);
vert2D[1] = new PVector( tw, th);
vert2D[2] = new PVector(tw/2, 0);
sierpinski(startPoint, depth);
}
static void plotOnImage(int _depth, color _sierpinskiColor, PImage _texture2D)
{
texture2D = _texture2D;
depth = _depth;
sierpinskiColor = _sierpinskiColor;
// random start point (at the middle)
int tw = texture2D.width;
int th = texture2D.height;
startPoint = new PVector(tw/2, th/2);
vert2D[0] = new PVector( 0, th);
vert2D[1] = new PVector( tw, th);
vert2D[2] = new PVector(tw/2, 0);
sierpinski(startPoint, depth);
}
static void sierpinski(PVector currentPoint, int currentDepth)
{
if (currentDepth == 0) {
texture2D.set(int(currentPoint.x), int(currentPoint.y), sierpinskiColor);
return;
}
PVector temp = currentPoint.get();
for (PVector sVertice : vert2D) {
currentPoint.set((currentPoint.x+sVertice.x)/2, (currentPoint.y+sVertice.y)/2);
sierpinski(currentPoint, currentDepth-1);
currentPoint = temp.get();
}
}
}
I tried all your code against a light background to get more details. So maybe changing colors?
Sometimes simple is the answer, it helped.Thanks!
(I’d still like to know if there are ways of illuminating the scene better).
@Chrisir (and @noel)
Btw, I uploaded the 2D Sierpinski sketch to the Rosetta code page a couple of days ago. And updated it afterwards with your method of only drawing at the deepest recursion depth. Just FYI I guess.
@raron See the Barnsley Fern on rosetta code for an example of an iterated function.
Could you please provide the link?
I can’t find it.
Here: https://www.rosettacode.org/wiki/Sierpinski_triangle/Graphical#Processing
EDIT: Just realized it could do with one more edit with Chrisir’s simplified Sierpinski (I haven’t gotten to that yet)