// D. Parson's makeCustomPShape vectors taken from Shape3DDemo /* * Make and return a custom 3D PShape created using vertex() calls, * for use in Avatar-derived class VectorAvatar. The textureimg * parameter may be null; when it is non-null, use it to texture * at least one of the planar sides of the returned PShape. If the * STUDENT decides not to texture, remove the loadImage call at the * top of the sketch, allowing textureimg to be null. */ PShape makeCustomPShape(int sidelength, int sidelimit) { // STUDENT NOTE: Even though // https://processing.org/reference/vertex_.html // shows use of 3D coordinates combined with texture: // vertex(x, y, z, u, v), that did not work for my // 3D planar surfaces like the initial base that varies // the Z value. Intuitively, the limitation makes sense, // since texturing images are 2D, and varying the Z // coordinate can create a non-planar shape, even though // mine are all planar. I switched to vertex(x, y, u, v) // for the textured planar surface in the else clause below, // then used rotateX and translate to move it into position // within the GROUP PShape. PShape custom = helpMakeRecursiveShape(sidelength, sidelimit); custom.translate(sidelength,sidelength,0); // trial-and-error, slide into centered position return custom ; } final float SQRT3 = sqrt(3.0) ; final float TWOSQRT3 = 2.0 * SQRT3 ; final int [] shapefills = { color(255,255,0), color(0,255,255), color(255,0,255), color(0,255,0) }; PShape helpMakeRecursiveShape(int sidelength, int sidelimit) { PShape group = createShape(GROUP); if (sidelength > sidelimit) { // Make 4, each slightly bigger than 1/2 so it grows. Assemble 3 // around the pyramid base and stick on on the top. float ratio = .5 + .125 ; // exact binary rep int newlen = round(ratio * sidelength); PShape subshape = null ; for (int i = 0 ; i < 4 ; i++) { subshape = helpMakeRecursiveShape(newlen, sidelimit); group.addChild(subshape); switch (i) { case 0 : subshape.translate(-(sidelength-newlen)/2, (sidelength-newlen)/2, (sidelength-newlen)/2); break ; case 1: subshape.translate((sidelength-newlen)/2, (sidelength-newlen)/2, (sidelength-newlen)/2); break ; case 2: subshape.translate(0, (sidelength-newlen)/2, -(sidelength-newlen)/2.75); break ; case 3: subshape.translate(0, -(sidelength-newlen)/2,(sidelength-newlen)/5); break ; } } } else { // 1:2:sqrt(3) for a 30-60-90 triangle: // https://study.com/academy/lesson/30-60-90-triangle-theorem-properties-formula.html for (int i = 0 ; i < 3 ; i++) { PShape face = createShape(); face.beginShape(); face.vertex(-sidelength/2.0, sidelength/2.0, sidelength/TWOSQRT3); face.vertex(sidelength/2.0, sidelength/2.0, sidelength/TWOSQRT3); face.vertex(0, -sidelength/2.0, 0); if (! isstroke) { face.noStroke(); } face.endShape(); group.addChild(face); face.rotateY(TWO_PI*i/3.0); face.setFill(shapefills[i]); } PShape base = createShape(); base.beginShape(); base.vertex(-sidelength/2.0, sidelength/2.0, sidelength/TWOSQRT3); base.vertex(sidelength/2.0, sidelength/2.0, sidelength/TWOSQRT3); base.vertex(0, sidelength/2.0, -sidelength/SQRT3); if (! isstroke) { base.noStroke(); } base.endShape(); group.addChild(base); base.setFill(shapefills[3]); } return group ; }