lass-Body

Each of the figures is created randomly using the names of people from this class:

You can play with the random generation tool here:

For this project, I wanted to randomly create humanoid figures that look like they are made of garbage. I am pretty satisfied with the final product, and I think a lot of the success is thanks to these nice color palettes. I also got all of my bvh files from the CMU Graphics Lab. Finally, I used seedrandom so that each random figure is dependent on its name. I was very inspired by Generative Machines by Michael Chang, and other generative artworks shown in class.

Here are my sketches:

 

    //starter code for loading BVH from https://github.com/mrdoob/three.js/blob/master/examples/webgl_loader_bvh.html
    var clock = new THREE.Clock();
    var camera, controls, scene, renderer;
    var mixer, skeletonHelper, boneContainer;
    var head, lhand, rhand, torso, rfoot, lfoot; 
    var miscParts = []; 
    var limbs = [
    new THREE.BoxGeometry( 10, 10, 10 ), 
    new THREE.SphereGeometry( 10, 20, 15 ), 
    new THREE.ConeGeometry( 10, 10, 30 ), 
    new THREE.CylinderGeometry( 10, 10, 10, 5 ), 
    new THREE.TorusGeometry( 7, 3, 10, 30 ), 
    new THREE.TorusKnotGeometry( 7, 3, 10, 30 ), 
    new THREE.DodecahedronGeometry(7)
]
var bodies = [
    new THREE.BoxGeometry( 30, 60, 30 ), 
    new THREE.SphereGeometry( 30, 20, 15 ), 
    new THREE.ConeGeometry( 30, 60, 30 ), 
    new THREE.CylinderGeometry( 20, 30, 50, 5 ), 
    new THREE.TorusGeometry( 20, 10, 10, 30 ), 
    new THREE.TorusKnotGeometry( 20, 10, 10, 30 ), 
    new THREE.DodecahedronGeometry(20)
]
var colorArray = [new THREE.Color(0xffaaff), new THREE.Color(0xffaaff),  new THREE.Color(0xffaaff),  new THREE.Color(0xffaaff),  new THREE.Color(0xffaaff)]; 
var uniforms = {u_resolution: {type: "v2", value: new THREE.Vector2()}, 
                u_colors: {type: "v3v", value: colorArray}};
 
var materials = [
    new THREE.ShaderMaterial( {  
        uniforms: uniforms,
        vertexShader: document.getElementById("vertex").textContent,
        fragmentShader: document.getElementById("stripeFragment").textContent,
    }), 
    new THREE.ShaderMaterial( {  
        uniforms: uniforms,
        vertexShader: document.getElementById("vertex").textContent,
        fragmentShader: document.getElementById("gradientFragment").textContent,
    }), 
    new THREE.ShaderMaterial( {  
        uniforms: uniforms,
        vertexShader: document.getElementById("vertex").textContent,
        fragmentShader: document.getElementById("plainFragment").textContent
    }), 
    new THREE.ShaderMaterial( {  
        uniforms: uniforms,
        vertexShader: document.getElementById("vertex").textContent,
        fragmentShader: document.getElementById("plainFragment").textContent,
        wireframe: true 
    })
    ]
 
    init();
    animate();
 
    var bvhs = ["02_04", "02_05", "02_06", "02_07", "02_08", "02_09", "02_10", "pirouette"]
 
    uniforms.u_resolution.value.x = renderer.domElement.width;
    uniforms.u_resolution.value.y = renderer.domElement.height;
 
    var loader = new THREE.BVHLoader();
    loader.load( "models/" + random(bvhs) + ".bvh", createSkeleton);
 
    function createSkeleton(result){
        skeletonHelper = new THREE.SkeletonHelper( result.skeleton.bones[ 0 ] );
        skeletonHelper.skeleton = result.skeleton; // allow animation mixer to bind to SkeletonHelper directly
        boneContainer = new THREE.Group();
 
        boneContainer.add( result.skeleton.bones[ 0 ] );
        var geometry = new THREE.BoxGeometry( 10, 10, 10 );
        head = new THREE.Mesh( random(limbs), materials[0] );
        lhand = new THREE.Mesh( random(limbs), materials[0] );
        rhand = new THREE.Mesh( random(limbs), materials[0] );
        lfoot = new THREE.Mesh( random(limbs), materials[0] );
        rfoot = new THREE.Mesh( random(limbs), materials[0] );
        torso = new THREE.Mesh( random(bodies), materials[0] );
        // torso.scale.set(Math.random() * 1.5, Math.random() * 1.5, Math.random() * 1.5);
 
        skeletonHelper.skeleton.bones[4].add(head); 
        skeletonHelper.skeleton.bones[12].add(rhand); 
        skeletonHelper.skeleton.bones[31].add(lhand); 
        skeletonHelper.skeleton.bones[50].add(rfoot); 
        skeletonHelper.skeleton.bones[55].add(lfoot); 
        skeletonHelper.skeleton.bones[1].add(torso); 
        for(var i=9; i<14; i++){
            var part = new THREE.Mesh(  new THREE.BoxGeometry( Math.random() * 10, Math.random() * 5, Math.random() * 5 ), materials[0] ); 
            miscParts.push(part); 
            skeletonHelper.skeleton.bones[i].add(part);
        }
        for(var i=28; i<31; i++) {
            var part = new THREE.Mesh(  new THREE.BoxGeometry( Math.random() * 10, Math.random() * 5, Math.random() * 5 ), materials[0] ); 
            miscParts.push(part); 
            skeletonHelper.skeleton.bones[i].add(part);
        }        
        for(var i=47; i<56; i++) {
            var part = new THREE.Mesh(  new THREE.BoxGeometry( Math.random() * 10, Math.random() * 5, Math.random() * 5 ), materials[0] ); 
            miscParts.push(part); 
            skeletonHelper.skeleton.bones[i].add(part);
        }
        scene.add( skeletonHelper );
        scene.add( boneContainer );
        skeletonHelper.material = new THREE.MeshBasicMaterial({
            color:"white", 
            transparent:"true", 
            opacity:"0.0"}); 
        mixer = new THREE.AnimationMixer( skeletonHelper );
        mixer.clipAction( result.clip ).setEffectiveWeight( 1.0 ).play();
        changeName(); 
    }
    function init() {
        camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, .1, 1000 );
        camera.position.set( 0, 200, 400 );
        scene = new THREE.Scene();
        scene.add( new THREE.GridHelper( 400, 10 ) );
        scene.background = new THREE.Color(0xdddddd); 
        renderer = new THREE.WebGLRenderer( { antialias: true } );
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize( window.innerWidth, window.innerHeight );
        document.body.appendChild( renderer.domElement );
        controls = new THREE.OrbitControls( camera, renderer.domElement );
        controls.minDistance = 300;
        controls.maxDistance = 700;
        window.addEventListener( 'resize', onWindowResize, false );
    }
    function onWindowResize() {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize( window.innerWidth, window.innerHeight );
    }
    function animate() {
        requestAnimationFrame( animate );
        var delta = clock.getDelta();
        if ( mixer ) mixer.update( delta );
        renderer.render( scene, camera );
    }
    function changeBody(seed) {
        console.log(seed); 
        Math.seedrandom(seed); 
 
        color = random(colors); 
        for(var i = 0; i < 5; i++){
            colorArray[i] = new THREE.Color(color[i]); 
        }
        for(var i = 0; i < materials.length; i++){
            materials[i].clone(); //idk why i do this but its the only way to make randomness match with class.html
        }
        lhand.geometry = random(limbs); 
        lhand.material = random(materials); 
        rhand.geometry = random(limbs); 
        rhand.material = random(materials); 
        torso.geometry = random(bodies); 
        torso.material = random(materials); 
        lfoot.geometry = random(limbs); 
        lfoot.material = random(materials); 
        rfoot.geometry = random(limbs); 
        rfoot.material = random(materials);
        head.geometry = random(limbs); 
        head.material = random(materials); 
        head.scale.set(2, 2,2); 
        for(var i=0; i<miscParts.length; i++){
            miscParts[i].geometry =  new THREE.BoxGeometry( Math.random() * 15, Math.random() * 15, Math.random() * 10 );
            miscParts[i].material = random(materials); 
        }
    }    function changeName() {
        changeBody(document.getElementById("nameInput").value); 
    }
    function random(arr) {
        return arr[Math.floor(Math.random() * arr.length)];
    }
    function changeBvh(){
        scene.remove(skeletonHelper); 
        scene.remove(boneContainer); 
        loader.load( "models/"+ random(bvhs) +".bvh", createSkeleton);
    }

lass-LookingOutwards03

Daniel Rozin has created many mechanical "mirrors" using video cameras, motion sensors, and motors to display people's reflections. I had seen the popular pompom mirror before, but I was interested to see the other mirrors he created. One mirror that I found interesting was the penguin mirror. Rather than facing the user directly, this mirror is flat on the ground and takes the shape of a projected shadow. As the user moves, the stuffed penguins turn so that their white bellies are showing. I really enjoy how Rozin uses his mirrors to take a simple shadow and turn it into a huge mechanized process.

I think that penguins were very fitting for this mirror, because the colors of the penguin allow for a transition between black and white as they turn. The appearance of a huge group of penguins together also gives the appearance of a penguin huddle. The sound of this mirror is  also very pleasant. As you move more, the clicking sound of all the penguins turning increases. There is something very soothing about listening to an army of penguins follow your movements.

 

lass-telematic

(you will probably need to open the app in a new tab to allow webcam permissions, and as far as I know it only works on google chrome.)

My telematic environment shows the optical flow of up to nine users in a square grid. I used oflow.js to find optical flow, and also started with this template that Char made using p5.js and socket.io.

Some things that I appreciate about optical flow after doing this project are that 1) it allows more anonymity than a video chat,  and 2) it focuses on expression through movement (change), so nothing will show if you stay still. At times I was worried that the user wouldn't be able to distinguish optical flow from just a pixelated video, but I think that by staring for a bit it becomes apparent that your movements are being tracked. 

(animated gif) 

Something to note with this project is the lag. It can track the optical flow of the user at a fine rate, but transferring all of the flow data takes a while and makes the other squares become choppy. They play about one second behind in time (in the example above you can see that the orange user moves much more fluidly than the others). Since the project was meant to be synchronous, ideally this wouldn't happen, but I think it has an interesting and slightly spooky effect. 

Honestly, I struggled  with ideas for this project and I wish the final product involved more communication between users. My initial idea was to overlay the feeds on top of each other so people could collaboratively "draw" with their motions, but that was too messy and difficult to discern what was going on, which is why it is a grid now. I also tried having instructions appear on the screen for every user to follow (such as telling them to freeze, or wave at each other), but I removed that since it felt disruptive. Although I like the appearance of the uniform squares, it is a bit of a letdown that they are just 9 independent boxes.

Thank you to Char for the templates, and Golan for the project title!

 

lass-viewing04

I think that Akinori Goto's 3D Printed Zoetrope is a good example of spectacle. It takes something that has been around for many years (the zoetrope) and refines it using impressive modern technology. The project is very clean, polished, and precise. Although the project is definitely explorative, it was created by a very controlled and deliberate process, which is outlined in detail in the video. In addition to software, the piece requires lighting and a spinning mechanism to show anything of interest. The project is successful in showing off new and difficult technology, and the idea is very awe-inspiring. The combination of all these things makes this zoetrope fit in with the idea of spectacle.

In terms of the dichotomies presented by Warburton, I would say that this project is very visible, as its simple beauty can be enjoyed even without context. I don't see the project as having much waste, since it seems to have been crafted efficiently with a moderation of software. I would also say that this project is more art than commerce, and also more dysfunctional, since it holds little commercial value. Finally, I think that it has more drag than acceleration, since there is not much else to be done with this technology in the future (at least that I can think of).

lass-clock

   

For this project, I wanted to make a tidal clock using 3js and shaders. The water rises and cycles through high and low tide twice every ~25 hours. Originally, my design involved a floating island with some water pooled in the middle, but as I worked on the project it I realized that floating islands didn't really make sense and instead used the shape of a tidal pool.

One feature of the clock is that the sky changes during day, night, and sunset. I chose to have this feature because since my shapes were low poly, I wanted to be able to have a wider variety of color palettes.    

       

One of the things that I would have liked to include in this project is using location services and to find the actual tide for the user's location. Right now, I'm basing the tide off of the most recent high tide on Virginia Beach, which will work temporarily but probably require calibration in the future since the length of tidal days is approximate. The same thing goes for sunrise and sunset times, since right now they are shown at a fixed time every day.

<script id="waterVertex" type="x-shader/x-vertex">
    uniform float u_time; 
    uniform float u_height; 
    varying vec3 v_position; 
    varying vec3 v_normal; 
 
    float random(vec2 co){
        return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
    }
 
    void main() {
        vec3 newPosition = position;    
        newPosition.z = cos(u_time / 3.0) / 10.0; 
        newPosition.z += u_height + 0.1; 
        newPosition.z += random(position.xy) / 15.0; 
        v_position = newPosition; 
        gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
    }
</script>
<script id="waterFragment" type="x-shader/x-fragment">
    varying vec3 v_position; 
    uniform float u_radius; 
    uniform vec3 u_camera; 
    uniform float u_hour; 
 
    void main() {
        float fog = distance(u_camera, v_position) / 20.0; 
        float day = clamp(6.0 - abs(12.0 - u_hour), 0.0, 1.0); 
        vec3 darkBlue = vec3(fog / 4.0, 0.25, 0.5); 
        vec3 lightBlue = vec3(fog, 0.85, 0.8); 
        gl_FragColor =  vec4(day * lightBlue + (1.0 - day) * darkBlue, 0.8 - fog ); 
    }
</script>
<script id="sandVertex" type="x-shader/x-vertex">
    varying vec2 vUV;
    varying vec3 v_normal; 
    varying vec3 v_position; 
 
    void main() {  
        vUV = uv;
        vec4 pos = vec4(position, 1.0);
        gl_Position = projectionMatrix * modelViewMatrix * pos;
        v_normal = normalize(normal);
        v_position = position; 
    }
</script>
 
<script id="sandFragment" type="x-shader/x-fragment">
    varying vec3 v_normal; 
    varying vec3 v_position; 
    uniform vec3 u_camera; 
 
    void main() {
        vec3 lightSource = normalize(vec3(0.0, 1.0, 3.0)); 
        float dprod = max(dot(v_normal, lightSource), 0.0); 
        vec3 highlightColor = vec3( 0.7, 0.7, 0.2);
        vec3 shadowColor = vec3(0.3, 0.3, 0.6); 
        float fog = pow(distance(vec3(0.0), v_position) , 2.0) / 30.0; 
        gl_FragColor = vec4( shadowColor + highlightColor  * dprod * 0.4, 0.9 - fog);
    }
</script>
 
<script id="skyVertex" type="x-shader/x-fragment">  
    varying vec2 vUV;
    varying float v_z; 
 
    void main() {  
        vUV = uv;
        vec4 pos = vec4(position, 1.0);
        gl_Position = projectionMatrix * modelViewMatrix * pos;
        v_z = normalize(position).z; 
    }
</script>
 
<script id="skyFragment" type="x-shader/x-fragment">  
    varying float v_z; 
    uniform float u_hour; 
 
    void main() {  
        vec3 dayColor = vec3(0.4 - v_z, 0.7 - v_z, 0.80);
        vec3 sunsetColor = vec3(0.5 + v_z , 0.2 + v_z, 0.3);
        vec3 nightColor = vec3(0.05 - v_z / 10.0, 0.05 - v_z / 5.0, 0.20);
 
        vec3 dtn = vec3(0.0);
        dtn.x = clamp(6.0 - abs(12.0 - u_hour), 0.0, 1.0); 
        dtn.z = 1.0 - dtn.x; 
        dtn.y = (clamp(sin(u_hour * 3.14 / 12.0), 0.5, 1.0) - 0.5) * 2.0; 
        dtn = normalize(dtn); 
 
        gl_FragColor = vec4(dtn.x * dayColor + dtn.y * sunsetColor + dtn.z * nightColor, 1.0); 
    }
</script>  
 
 
<script>
 
    //https://thebookofshaders.com/04/
    var container;
    var camera, scene, renderer, controls;
    var waterUniforms, skyUniforms; 
    var water, tidehand, sand, clockface; 
    var angle, date, height; 
    var pastHighTide = new Date("September 20, 2018 5:12:00")
 
    init();
    animate();
 
    function init() {
        container = document.getElementById( "container" );
 
        camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
        camera.position.z = 6;
        controls = new THREE.OrbitControls( camera );
        controls.maxDistance = 7; 
        controls.minDistance = 1; 
        controls.enableDamping = true; 
        controls.dampingFactor = 0.2;
        controls.maxAzimuthAngle = Math.PI / 4; 
        controls.minAzimuthAngle = Math.PI / -4; 
        controls.minPolarAngle = Math.PI / 4; 
        controls.maxPolarAngle = Math.PI * 3 / 4; 
 
        scene = new THREE.Scene();
 
        date = new Date(); 
        height = Math.cos(-1 * angle) / 7.0; 
 
        var geometry = new THREE.PlaneGeometry(50, 50, 100, 100); 
 
 
        waterUniforms = {
            u_time: { type: "f", value: 1.0 },
            u_height: { type: "f", value: height},
            u_camera: {type:"v3", value: camera.position}, 
            u_hour: {type: "f", value: date.getHours() + date.getMinutes() / 60}
        };
 
        var material = new THREE.ShaderMaterial( {
            uniforms: waterUniforms,
            vertexShader: document.getElementById("waterVertex").textContent,
            fragmentShader: document.getElementById("waterFragment").textContent, 
            transparent: true, 
            depthWrite: false, 
            side: THREE.DoubleSide
 
        } );
        water = new THREE.Mesh( geometry, material );
        scene.add( water );
 
        //sand geometry made in blender
        var loader = new THREE.JSONLoader();
        loader.load("tallsand.json", 
            function(geometry, materials){
                var sandUniforms = ({
                    u_camera: {type:"v3", value: camera.position}, 
                    u_hour: {type: "f", value: date.getHours() + date.getMinutes() / 60}
                }); 
                material = new THREE.ShaderMaterial( {
                    uniforms: sandUniforms, 
                    vertexShader: document.getElementById("sandVertex").textContent,
                    fragmentShader: document.getElementById("sandFragment").textContent, 
                    transparent: true, 
                } );
                var sandy = new THREE.Mesh(geometry, material); 
                sandy.rotation.x += Math.PI / 2; 
                sandy.scale.set(2, 2, 2); 
                sandy.position.z = -0.001; 
                scene.add(sandy); 
            }, 
            function(xhr){console.log("loaded json")},
            function(err){console.log("error loading json")}
        );
 
        geometry = new THREE.PlaneGeometry( .1, 2);
        material = new THREE.MeshBasicMaterial( { color: 0xffffff, side: THREE.DoubleSide, transparent:true} );
        stick = new THREE.Mesh( geometry, material );
        stick.position.y += .9; 
        tidehand = new THREE.Group(); 
        tidehand.add(stick); 
        tidehand.rotation.z = angle; 
        tidehand.position.z += 1.01; 
        scene.add( tidehand );
 
        var texture = new THREE.TextureLoader().load("clockface.png"); 
        geometry = new THREE.PlaneGeometry(6, 6); 
        material = new THREE.MeshBasicMaterial({map:texture, transparent: true, depthWrite: false}); 
        clockface = new THREE.Mesh(geometry, material); 
        clockface.position.z += 1;
        scene.add(clockface); 
 
        //skydome code from Ian Webster http://www.ianww.com/blog/2014/02/17/making-a-skydome-in-three-dot-js/
        //we are inside of a sphere! 
        geometry = new THREE.SphereGeometry(300, 60, 40);  
        skyUniforms = {
            u_hour: {type: "f", value: date.getHours() + date.getMinutes() / 60}
        }
        material = new THREE.ShaderMaterial( {  
            uniforms: skyUniforms,
            vertexShader:   document.getElementById("skyVertex").textContent,
            fragmentShader: document.getElementById("skyFragment").textContent,
            side: THREE.DoubleSide
        });
        skyBox = new THREE.Mesh(geometry, material);  
        skyBox.eulerOrder = "XZY";  
        skyBox.renderDepth = 1000.0;  
        scene.add(skyBox);  
 
        renderer = new THREE.WebGLRenderer();
        renderer.setPixelRatio( window.devicePixelRatio );
        container.appendChild( renderer.domElement );
        onWindowResize();
        window.addEventListener( "resize", onWindowResize, false );
    }
 
    function onWindowResize(event) {
        renderer.setSize( window.innerWidth, window.innerHeight );
    }
 
    function animate() {
        updateTide(); 
        waterUniforms.u_time.value += 0.05;
 
        controls.update(); 
        requestAnimationFrame( animate );
        render();
    }
 
    function updateTide() {
        date = new Date(); 
        //date.setMinutes(date.getMinutes() + 1); //artificial fast forward
        document.getElementById("info").innerHTML = date.toLocaleString(); 
        var diff = (date - pastHighTide) / 60000; 
        diff = diff % 745; 
        angle = -1 * Math.PI * 2 * diff / 745;
        height = Math.cos(-1 * angle) / 7.0; 
        waterUniforms.u_height.value = height; 
        skyUniforms.u_hour.value = date.getHours() + date.getMinutes() / 60; 
        waterUniforms.u_hour.value = date.getHours() + date.getMinutes() / 60; 
 
        tidehand.rotation.z = angle; 
    }
 
    function render() {
        renderer.render( scene, camera );
    }
 
    document.addEventListener("keydown", function(e){
        switch(e.keyCode){
            case 32: 
                controls.reset(); 
                break; 
        } 
    });
</script>

 

lass-LookingOutwards02

For this Looking Outwards, I am focusing on the work of Glen Marshall. When browsing his gallery, I was immediately drawn to his music video for Clouds in Cloudless Skies (2014). Honestly, I think this is just because I really like cubes and squares. The music video shows travel through an infinite world, where cubes are constantly being generated. The generation of these cubes is different in each scene of the music video. I appreciate this because each scene has a different quality to it--in some scenes, cubes are disappearing and reappearing, and in others they are constantly being distorted. I think that the choice to have a theme between these scenes, yet make them distinct from each other, makes it more believable as a "world". Each scene follows a theme, yet they show different aspects of life with different generative methods. It is clear that Marshall used a large variety of algorithms to create this work.

As for effective complexity, it is clear that the cube shapes have a high amount of order. They are all perfect cubes of the same size that tessellate perfectly. However, there is a randomness in their patterns of creation and movement that makes it so believable as a living world. 

lass-Reading03

I think that good first word art requires a special kind of person to create. It's difficult to move away from the norm and create something completely groundbreaking. While I would be interested in creating this kind of art, I simply don't think that I have the ability. Maybe this is an overly defeatist way of thinking, but considering just the number of people on Earth, the probability of a good original thought is really small, and I don't think I'm creative enough.

Instead, I find it easier to focus on concepts that already exist. I think that expanding on and combining existing ideas create really successful works of art that last through time. I often hear the Picasso quote "Good artists copy, great artists steal". To me, this means that you should take inspiration from other people's work, but make it your own in some way. I appreciate this quote because it reminds me that ownership and personality are more important than 100% originality. I think that this places me in between first and old word art, since I don't necessarily pursue complete originality, but I'm also not trying to follow any well established model (at least consciously). 

lass-AnimatedLoop

For my looping gif, I was really interested in doing a pinching/pulling motion, similar to cell division. At first I was very unsure of how to do this, since I had seen it before but didn't know a technical term. Connie Ye told me to look into metaballs, and I learned the process for making them from this coding challenge by Dan Shiffman. My initial plan involved several components, but after I had gotten the metaballs to work initially, many people urged me to keep it simple instead. I ended up only creating the first main component from the sketches below.   

In the end, I'm glad I went with the simple route. It allowed me to have more flexibility with colors, and it resulted in something much cleaner and not so chaotic. In the future I might try to create something more complex with the code that I wrote, since I planned it out to allow for several "groupings" of metaballs. I think it might be very laggy.

Thank you Golan, Connie, and Lauren for helping me out with this project!

debug view: 

BallGroup metaballs;
int numFrames = 30;
int currentFrame = 0;
int colorChoice = 0;
color[] ballColors, backgroundColors;
 
void setup() {
 size(640, 640);
 noStroke();
 
 ballColors = new color[] {
  color(250, 192, 133), color(169, 67, 117), color(50, 70, 171)
 };
 backgroundColors = new color[] {
  color(200, 94, 141), color(250, 200, 113), color(169, 200, 117)
 };
 
 metaballs = new BallGroup();
 metaballs.addBall(new Metaball(width / 2, height - 40, width / 2, -100, 80, 80));
 metaballs.addBall(new Metaball(width / 2, -100, width / 2, -100, 80, 0));
 metaballs.addBall(new Metaball(width / 2 - 50, height - 50, width / 2 - 50, height - 50, 130, 130));
 metaballs.addBall(new Metaball(width / 2 + 50, height - 50, width / 2 + 50, height - 50, 130, 130));
 metaballs.addBall(new Metaball(width / 2, height * 2, width / 2, height - 40, 0, 80));
 
 metaballs.setColors(ballColors[0], ballColors[1]);
}
 
void draw() {
 currentFrame = (currentFrame + 1) % 30;
 if (currentFrame == 0) {
  colorChoice = (colorChoice + 1) % 3;
  metaballs.setColors(ballColors[colorChoice], ballColors[(colorChoice + 1) % 3]);
 }
 float movement = easing(1.0 * currentFrame / numFrames);
 metaballs.fadeCol = (lerpColor(ballColors[(colorChoice + 1) % 3], ballColors[(colorChoice + 2) % 3], movement));
 background(lerpColor(ballColors[(colorChoice + 0) % 3], ballColors[(colorChoice + 1) % 3], movement));
 //background(255, 200, 200); 
 metaballs.update(movement);
 metaballs.show();
}
 
float easing(float x) {
 //this is where i would put an easing function if i had one 
 return x;
}
 
class Metaball {
 PVector position;
 PVector beginning;
 PVector destination;
 float radius;
 float radius1;
 float radius2;
 Metaball(float x1, float y1, float x2, float y2, float r1, float r2) {
  position = new PVector(x1, y1);
  beginning = new PVector(x1, y1);
  destination = new PVector(x2, y2);
  radius = 100;
  radius1 = r1;
  radius2 = r2;
 }
 void show() { //shows the centers (for debugging) 
  noFill();
  stroke(0);
  strokeWeight(1);
  //ellipse(position.x, position.y, radius, radius);
 }
 void update(float movement) {
  position = position.lerp(beginning, destination, movement);
  radius = lerp(radius1, radius2, movement);
 }
}
 
class BallGroup {
 ArrayList < Metaball > arr;
 color startCol;
 color endCol;
 color col;
 color fadeCol;
 
 BallGroup() {
  arr = new ArrayList < Metaball > ();
  col = color(0, 0, 0);
  startCol = color(0, 0, 0);
  endCol = color(0, 0, 0);
 }
 void setColor(color c) {
  col = c;
 }
 void setColors(color c1, color c2) {
  startCol = c1;
  endCol = c2;
 }
 void addBall(Metaball mb) {
  arr.add(mb);
 }
 void show() {//metaball code from Dan Shiffman: https://www.youtube.com/watch?v=ccYLb7cLB1I
  loadPixels();
  for (int x = 0; x < width; x++) {
   for (int y = 0; y < height; y++) {
    float sum = 0;
    for (Metaball mb: arr) {
     float dst = dist(x, y, mb.position.x, mb.position.y);
     sum += 100 * mb.radius / dst;
    }
    if (sum > 200) {
     //adds a border
     //if (sum < 220)
     //  pixels[x + y * width] = col; 
     //else
     pixels[x + y * width] = lerpColor(col, fadeCol, y * 1.0 / height);
    }
   }
  }
  updatePixels();
  for (Metaball mb: arr)
   mb.show();
 }
 void update(float movement) {
  for (Metaball mb: arr)
   mb.update(movement);
  col = lerpColor(startCol, endCol, movement);
 }
}

lass-Scope

download png 

For my praxinoscope, I decided to create an animation of a russian matryoshka doll opening. I liked this idea because it loops easily.
I drew the doll using p5's beginShape() and curveVertex(), but in hindsight I probably could have done it a lot more easily if I just uploaded png images for the top and bottom halves. Still, I got to experiment with drawing curves and using the p5 transformations, which was fun.

Initially, I couldn't decide between doing matryoshkas or the same concept with eggs. I think eggs would have been cool too.

function drawArtFrame ( whichFrame ) { 
  push(); 
  rotate(Math.PI); //because i made it upside down on accident. haha
  fill(255); 
  //vertices form the upper and lower matryoshka halves
  var upperHalf = [ [1.3, 2], [1.3, 0], [1.2, 1.3], [.8, 2], [0, 2.3]];
  var lowerHalf = [[1.3, 2], [1.3, 0], [1.5, -1.8], [0, -2.3]];
 
  //drawing the outer matryoshka
  sWidth = map((whichFrame ) % 10, 0, 9, 6, 12); 
  sHeight = map((whichFrame ) % 10, 0, 9, 7, 14); 
  var heightChange = 0
  fill(200);
 
  strokeWeight(1); 
  stroke(0); 
  fill(0);
  drawMatryoshka(lowerHalf, sWidth, sHeight, -1 * heightChange); 
  fill(255);
  drawMatryoshka(upperHalf, sWidth, sHeight, heightChange); 
  drawDetails(heightChange, sHeight, sWidth, 255); 
 
  //drawing the inner matryoshka
  whichFrame = (whichFrame + 0) % 10; 
  sWidth = map(whichFrame, 0, 9, 12, 15); 
  sHeight = map(whichFrame , 0, 9, 14, 20); 
  var heightChange = map(whichFrame, 0, 9, 6, 80); 
 
  var opacity =   map(whichFrame, 0, 10, 255, 0);
 
  fill(0, opacity);
  fill(0, opacity);
  stroke(0, opacity); 
  drawMatryoshka(lowerHalf, sWidth, sHeight, -1 * heightChange); 
  fill(255, opacity);
  drawMatryoshka(upperHalf, sWidth, sHeight, heightChange); 
  drawDetails(heightChange, sHeight, sWidth, opacity); 
  pop(); 
}
 
//draws shape based on the vertices w/ vertical symmetry 
function drawMatryoshka(verts, sWidth, sHeight, heightChange){
  beginShape();
  for(var i = 0; i  < verts.length; i++ ){
    curveVertex(verts[i][0] * sWidth, verts[i][1] * sHeight + heightChange); 
  }
  for(var i = verts.length - 2; i  >=0; i-- ){
    curveVertex(verts[i][0] * sWidth * -1, verts[i][1] * sHeight + heightChange); 
  }
  endShape(); 
  line(-1.3 * sWidth + .5,  heightChange, sWidth * 1.3 - .5,  heightChange);
}
 
function drawDetails(heightChange, sHeight, sWidth, opacity){
  //face
  strokeWeight(1); 
  fill(255, opacity); 
  ellipse(0, heightChange + sHeight * 1.3, sWidth * 1.7, sWidth * 1.7); 
 
  //hair
  fill(0, opacity);
  arc(0, heightChange + sHeight * 1.3, sWidth * 1.7, sWidth * 1.7, PI * 2, HALF_PI, CHORD); 
  arc(0, heightChange + sHeight * 1.3, sWidth * 1.7, sWidth * 1.7, HALF_PI, PI , CHORD); 
  strokeWeight(0); 
 
  //blush
  fill(255, 150, 150, opacity); 
  ellipse(.4 * sWidth, heightChange + sHeight * 1.2,sWidth * .4, sWidth * .4); 
  ellipse(- .4 * sWidth, heightChange + sHeight * 1.2, sWidth * .4, sWidth * .4); 
 
  //eyes and mouth
  fill(0,  opacity); 
  ellipse(.25 * sWidth, heightChange + sHeight * 1.4, sWidth * .2, sWidth * .2); 
  ellipse(- .25 * sWidth, heightChange + sHeight * 1.4, sWidth * .2, sWidth * .2); 
  ellipse(0, heightChange + sHeight , sWidth * .5, sHeight * .05);
 
  //bow
  fill(255, opacity);
  push(); 
  translate(0, -1 * heightChange);
  rotate(10); 
  ellipse(.2 * sWidth,  -.1 * sHeight, sWidth * .6, sWidth * .3); 
  rotate(-20); 
  ellipse(-.2 * sWidth,  -.1 * sHeight, sWidth * .6, sWidth * .3); 
  pop(); 
 
  //flower
  fill(255, opacity); 
  push();
  translate(0, -1.1 * sHeight -1 * heightChange);
  rotate(sWidth * .2); 
  for(var i = 0; i < 3; i++){
    ellipse(0,  0, sWidth * .3, sWidth * 1.2); 
    rotate(PI / 1.5 ); 
  }
  pop(); 
  fill(0, opacity);
  ellipse(0, -1.1 * sHeight -1 * heightChange, sWidth * .4, sWidth * .4); 
  fill(255); 
  strokeWeight(1); 
}

lass-Reading02

1A) Something I like that exhibits effective complexity is the circle patterns made in sand by pufferfish. These patterns are highly ordered as they always follow a specific geometric procedure that seems to create nearly identical circles each time. I find this interesting because the pattern is created so instinctively, but cannot be perfect as it was created by a living being under varying underwater conditions.

(photo from National Geographic)

1B) The Problem of Dynamics

This problem stood out to me because I thought it was strange that people would try to put a restriction on generative art, saying that it MUST exhibit change over time in order to be truly generative, otherwise it is just an artifact. While I think that being dynamic is an interesting quality to have, I don't think that it should be what qualifies art as generative or not. Even when results are "frozen", the process through which they were created was generative.