<div id="world"></div>
<div id="instructions">^<br/>Move the head of this bird<br/><span class="lightInstructions">and watch how the 2 others<br/>interact with him</span></div>

<div id="credits">
  <p> <a href="https://codepen.io/Yakudoo/"  target="blank">my other codepens</a> | <a href="https://www.epic.net" target="blank">epic.net</a></p>
</div>
@import url(https://fonts.googleapis.com/css?family=Open+Sans:800);
#world {
  background: #e0dacd;
  position:absolute;
  width:100%;
  height:100%;
  overflow:hidden;
}
#instructions{
  position:absolute;
  width:100%;
  top:50%;
  margin: auto;
  margin-top:50px;
  font-family:'Open Sans', sans-serif;
  color:#b75505;
  font-size:.9em;
  text-transform: uppercase;
  text-align : center;
}
.lightInstructions {
  color:#b59b63;
  font-size:.8em;
}

#credits{
  position:absolute;
  width:100%;
  margin: auto;
  bottom:0;
  margin-bottom:20px;
  font-family:'Open Sans', sans-serif;
  color:#b59b63;
  font-size:0.7em;
  text-transform: uppercase;
  text-align : center;
}
#credits a {
  color:#b59b63;
}

//THREEJS RELATED VARIABLES 

var scene, 
    camera,
    controls,
    fieldOfView,
    aspectRatio,
    nearPlane,
    farPlane,
    shadowLight, 
    backLight,
    light, 
    renderer,
    container;

//SCENE
var floor, brid1, bird2;

//SCREEN VARIABLES

var HEIGHT,
    WIDTH,
    windowHalfX,
    windowHalfY,
    mousePos = {x:0,y:0};


//INIT THREE JS, SCREEN AND MOUSE EVENTS

function init(){
  scene = new THREE.Scene();
  HEIGHT = window.innerHeight;
  WIDTH = window.innerWidth;
  aspectRatio = WIDTH / HEIGHT;
  fieldOfView = 60;
  nearPlane = 1;
  farPlane = 2000; 
  camera = new THREE.PerspectiveCamera(
    fieldOfView,
    aspectRatio,
    nearPlane,
    farPlane);
  camera.position.z = 1000;  
  camera.position.y = 300;
  camera.lookAt(new THREE.Vector3(0,0,0));    
  renderer = new THREE.WebGLRenderer({alpha: true, antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio); 
  renderer.setSize(WIDTH, HEIGHT);
  renderer.shadowMapEnabled = true;
  
  container = document.getElementById('world');
  container.appendChild(renderer.domElement);

  windowHalfX = WIDTH / 2;
  windowHalfY = HEIGHT / 2;
    
  window.addEventListener('resize', onWindowResize, false);
  document.addEventListener('mousemove', handleMouseMove, false);
  document.addEventListener('touchstart', handleTouchStart, false);
  document.addEventListener('touchend', handleTouchEnd, false);
  document.addEventListener('touchmove',handleTouchMove, false);
  /*
  controls = new THREE.OrbitControls( camera, renderer.domElement);
  //*/
}

function onWindowResize() {
  HEIGHT = window.innerHeight;
  WIDTH = window.innerWidth;
  windowHalfX = WIDTH / 2;
  windowHalfY = HEIGHT / 2;
  renderer.setSize(WIDTH, HEIGHT);
  camera.aspect = WIDTH / HEIGHT;
  camera.updateProjectionMatrix();
}

function handleMouseMove(event) {
  mousePos = {x:event.clientX, y:event.clientY};
}

function handleTouchStart(event) {
  if (event.touches.length > 1) {
    event.preventDefault();
    mousePos = {x:event.touches[0].pageX, y:event.touches[0].pageY};
  }
}

function handleTouchEnd(event) {
    mousePos = {x:windowHalfX, y:windowHalfY};
}

function handleTouchMove(event) {
  if (event.touches.length == 1) {
    event.preventDefault();
    mousePos = {x:event.touches[0].pageX, y:event.touches[0].pageY};
  }
}

function createLights() {
  light = new THREE.HemisphereLight(0xffffff, 0xffffff, .5)
  
  shadowLight = new THREE.DirectionalLight(0xffffff, .8);
  shadowLight.position.set(200, 200, 200);
  shadowLight.castShadow = true;
  shadowLight.shadowDarkness = .2;
  
  backLight = new THREE.DirectionalLight(0xffffff, .4);
  backLight.position.set(-100, 200, 50);
  backLight.shadowDarkness = .1;
  backLight.castShadow = true;
  
  scene.add(backLight);
  scene.add(light);
  scene.add(shadowLight);
}

//BIRD

Bird = function(){
  
  this.rSegments = 4;
  this.hSegments = 3;
  this.cylRay = 120;
  this.bodyBirdInitPositions = [];
  this.vAngle = this.hAngle = 0;
  this.normalSkin = {r:255/255, g:222/255, b:121/255};
  this.shySkin = {r:255/255, g:157/255, b:101/255};
  this.color = {r:this.normalSkin.r, g:this.normalSkin.g, b:this.normalSkin.b};
  this.side = "left";
  
  this.shyAngles = {h:0, v:0};
  this.behaviourInterval;
  this.intervalRunning = false;
  
  this.threegroup = new THREE.Group();
  
  // materials
  this.yellowMat = new THREE.MeshLambertMaterial ({
    color: 0xffde79, 
    shading:THREE.FlatShading
  });
  this.whiteMat = new THREE.MeshLambertMaterial ({
    color: 0xffffff,
    shading: THREE.FlatShading
  });
  this.blackMat = new THREE.MeshLambertMaterial ({
    color: 0x000000,
    shading: THREE.FlatShading
  });
  this.orangeMat = new THREE.MeshLambertMaterial ({
    color: 0xff5535,
    shading: THREE.FlatShading
  });
  
  //WINGS
  
  this.wingLeftGroup = new THREE.Group();
  this.wingRightGroup = new THREE.Group();
 
  var wingGeom = new THREE.BoxGeometry(60,60, 5);
  var wingLeft = new THREE.Mesh(wingGeom, this.yellowMat);
  this.wingLeftGroup.add(wingLeft);
  this.wingLeftGroup.position.x = 70;
  this.wingLeftGroup.position.z = 0;
  this.wingLeftGroup.rotation.y = Math.PI/2;
  wingLeft.rotation.x = -Math.PI/4;
  var wingRight = new THREE.Mesh(wingGeom, this.yellowMat);
  this.wingRightGroup.add(wingRight);
  this.wingRightGroup.position.x = -70;
  this.wingRightGroup.position.z = 0;
  this.wingRightGroup.rotation.y = -Math.PI/2;
  wingRight.rotation.x = -Math.PI/4;
  
  //BODY
  
  var bodyGeom = new THREE.CylinderGeometry(40, 70, 200, this.rSegments, this.hSegments);
  this.bodyBird = new THREE.Mesh(bodyGeom, this.yellowMat);
  this.bodyBird.position.y = 70;
  
  this.bodyVerticesLength = (this.rSegments+1)*(this.hSegments);
  for (var i=0;i<this.bodyVerticesLength; i++){
    var tv = this.bodyBird.geometry.vertices[i];
    this.bodyBirdInitPositions.push({x:tv.x, y:tv.y, z:tv.z});
  }
  
  this.threegroup.add(this.bodyBird);
  this.threegroup.add(this.wingLeftGroup);
  this.threegroup.add(this.wingRightGroup);
  
  
  // EYES
  
  this.face = new THREE.Group();
  var eyeGeom = new THREE.BoxGeometry(60,60,10);
  var irisGeom = new THREE.BoxGeometry(10,10,10);
  
  this.leftEye = new THREE.Mesh(eyeGeom, this.whiteMat);
  this.leftEye.position.x = -30;
  this.leftEye.position.y = 120;
  this.leftEye.position.z = 35;
  this.leftEye.rotation.y = -Math.PI/4;
  
  this.leftIris = new THREE.Mesh(irisGeom, this.blackMat);
  this.leftIris.position.x = -30;
  this.leftIris.position.y = 120;
  this.leftIris.position.z = 40;
  this.leftIris.rotation.y = -Math.PI/4;
  
  
  this.rightEye = new THREE.Mesh(eyeGeom, this.whiteMat);
  this.rightEye.position.x = 30;
  this.rightEye.position.y = 120;
  this.rightEye.position.z = 35;
  this.rightEye.rotation.y = Math.PI/4;
  
  this.rightIris = new THREE.Mesh(irisGeom, this.blackMat);
  this.rightIris.position.x = 30;
  this.rightIris.position.y = 120;
  this.rightIris.position.z = 40;
  this.rightIris.rotation.y = Math.PI/4;
  
  // BEAK
  
  var beakGeom = new THREE.CylinderGeometry(0,20,20, 4,1);
  this.beak = new THREE.Mesh(beakGeom, this.orangeMat);
  this.beak.position.z = 65;
  this.beak.position.y = 70;
  this.beak.rotation.x = Math.PI/2;
  
  this.face.add(this.rightEye);
  this.face.add(this.rightIris);
  this.face.add(this.leftEye);
  this.face.add(this.leftIris);
  this.face.add(this.beak);
  
  //FEATHERS
  
  var featherGeom = new THREE.BoxGeometry(10,20,5);
  this.feather1 = new THREE.Mesh(featherGeom, this.yellowMat);
  this.feather1.position.z = 55;
  this.feather1.position.y = 185;
  this.feather1.rotation.x = Math.PI/4;
  this.feather1.scale.set(1.5,1.5,1);
  
  this.feather2 = new THREE.Mesh(featherGeom, this.yellowMat);
  this.feather2.position.z = 50;
  this.feather2.position.y = 180;
  this.feather2.position.x = 20;
  this.feather2.rotation.x = Math.PI/4;
  this.feather2.rotation.z = -Math.PI/8;
    
  this.feather3 = new THREE.Mesh(featherGeom, this.yellowMat);
  this.feather3.position.z = 50;
  this.feather3.position.y = 180;
  this.feather3.position.x = -20;
  this.feather3.rotation.x = Math.PI/4;
  this.feather3.rotation.z = Math.PI/8;
  
  this.face.add(this.feather1);
  this.face.add(this.feather2);
  this.face.add(this.feather3);
  this.threegroup.add(this.face);
  
  this.threegroup.traverse( function ( object ) {
    if ( object instanceof THREE.Mesh ) {
      object.castShadow = true;
      object.receiveShadow = true;
    }
  } );
  
}

Bird.prototype.look = function(hAngle,vAngle){
  this.hAngle = hAngle;
  this.vAngle = vAngle;
  
  this.leftIris.position.y = 120 - this.vAngle*30;
  this.leftIris.position.x = -30 + this.hAngle*10;
  this.leftIris.position.z = 40 + this.hAngle*10;
  
  this.rightIris.position.y = 120 - this.vAngle*30;
  this.rightIris.position.x = 30 + this.hAngle*10;
  this.rightIris.position.z = 40 - this.hAngle*10;
  
  this.leftEye.position.y = this.rightEye.position.y = 120 - this.vAngle*10;
  
  this.beak.position.y = 70 - this.vAngle*20;
  this.beak.rotation.x = Math.PI/2 + this.vAngle/3;
  
  this.feather1.rotation.x = (Math.PI/4) + (this.vAngle/2);
  this.feather1.position.y = 185 - this.vAngle*10;
  this.feather1.position.z = 55 + this.vAngle*10;
  
  this.feather2.rotation.x = (Math.PI/4) + (this.vAngle/2);
  this.feather2.position.y = 180 - this.vAngle*10;
  this.feather2.position.z = 50 + this.vAngle*10;
  
  this.feather3.rotation.x = (Math.PI/4) + (this.vAngle/2);
  this.feather3.position.y = 180 - this.vAngle*10;
  this.feather3.position.z = 50 + this.vAngle*10;
  
  
  for (var i=0;i<this.bodyVerticesLength; i++){
    var line = Math.floor(i/(this.rSegments+1));
    var tv = this.bodyBird.geometry.vertices[i];
    var tvInitPos = this.bodyBirdInitPositions[i];
    var a, dy;
    if (line >= this.hSegments-1){
      a = 0;
    }else{
      a = this.hAngle/(line+1); 
    }
    var tx = tvInitPos.x*Math.cos(a) + tvInitPos.z*Math.sin(a);
    var tz = -tvInitPos.x*Math.sin(a) + tvInitPos.z*Math.cos(a);
    tv.x = tx;
    tv.z = tz;
  }
  this.face.rotation.y = this.hAngle;
  this.bodyBird.geometry.verticesNeedUpdate = true;
  
}
Bird.prototype.lookAway = function(fastMove){
  var speed = fastMove? .4 : 2;
  var ease = fastMove? Strong.easeOut : Strong.easeInOut;
  var delay = fastMove? .2 : 0;
  var col = fastMove? this.shySkin : this.normalSkin;
  var tv = (-1 + Math.random()*2) * Math.PI/3;
  var beakScaleX = .75 + Math.random()*.25;
  var beakScaleZ = .5 + Math.random()*.5;
  
  if (this.side == "right"){
    var th = (-1 + Math.random()) * Math.PI/4;  
  }else{
    var th = Math.random() * Math.PI/4; 
  }  
  _this = this;
  TweenMax.killTweensOf(this.shyAngles);
  TweenMax.to(this.shyAngles, speed, {v:tv, h:th, ease:ease, delay:delay});
  TweenMax.to (this.color, speed, {r:col.r, g:col.g, b:col.b, ease:ease, delay:delay});
  TweenMax.to(this.beak.scale, speed, {z:beakScaleZ, x:beakScaleX, ease:ease, delay:delay});
      
}

Bird.prototype.stare = function(){
  _this = this;
  var col = this.normalSkin;
  if (this.side == "right"){
    var th = Math.PI/3;  
  }else{
    var th = -Math.PI/3;
  }  
  TweenMax.to(this.shyAngles, 2, {v:-.5, h:th, ease:Strong.easeInOut});
  TweenMax.to (this.color, 2, {r:col.r, g:col.g, b:col.b, ease:Strong.easeInOut});
  TweenMax.to(this.beak.scale, 2, {z:.8, x:1.5, ease:Strong.easeInOut});
 
}

 //*   
function createFloor(){ 
  floor = new THREE.Mesh(new THREE.PlaneBufferGeometry(1000,1000), new THREE.MeshBasicMaterial({color: 0xe0dacd}));
  floor.rotation.x = -Math.PI/2;
  floor.position.y = -33;
  floor.receiveShadow = true;
  scene.add(floor);
}

function createBirds(){
  bird1 = new Bird();
  bird1.threegroup.position.x = 0;
  scene.add(bird1.threegroup);
  
  bird2 = new Bird();
  bird2.threegroup.position.x = -250;
  bird2.side = "right";
  bird2.threegroup.scale.set(.8,.8,.8);
  bird2.threegroup.position.y = -8;
  scene.add(bird2.threegroup);
  
  bird3 = new Bird();
  bird3.threegroup.position.x = 250;
  bird3.side = "left";
  bird3.threegroup.scale.set(.8,.8,.8);
  bird3.threegroup.position.y = -8;
  scene.add(bird3.threegroup);
}


function loop(){
  var tempHA = (mousePos.x-windowHalfX)/200;
  var tempVA = (mousePos.y - windowHalfY)/200;
  var userHAngle = Math.min(Math.max(tempHA, -Math.PI/3), Math.PI/3);
  var userVAngle = Math.min(Math.max(tempVA, -Math.PI/3), Math.PI/3);
  bird1.look(userHAngle,userVAngle);
  
  if (bird1.hAngle < -Math.PI/5 && !bird2.intervalRunning){
      bird2.lookAway(true);
      bird2.intervalRunning = true;
      bird2.behaviourInterval = setInterval(function(){
        bird2.lookAway(false);
      }, 1500);
  }else if (bird1.hAngle > 0 && bird2.intervalRunning){
    bird2.stare();
    clearInterval(bird2.behaviourInterval);
    bird2.intervalRunning = false;

  }else if (bird1.hAngle > Math.PI/5 && !bird3.intervalRunning){
    bird3.lookAway(true);
    bird3.intervalRunning = true;
    bird3.behaviourInterval = setInterval(function(){
      bird3.lookAway(false);
    }, 1500);
  }else if (bird1.hAngle < 0 && bird3.intervalRunning){
    bird3.stare();
    clearInterval(bird3.behaviourInterval);
    bird3.intervalRunning = false;
  }
  
  bird2.look(bird2.shyAngles.h, bird2.shyAngles.v);
  bird2.bodyBird.material.color.setRGB(bird2.color.r,bird2.color.g,bird2.color.b);
  
  bird3.look(bird3.shyAngles.h, bird3.shyAngles.v);
  bird3.bodyBird.material.color.setRGB(bird3.color.r,bird3.color.g,bird3.color.b);
  
  render();
  requestAnimationFrame(loop);
}

function render(){
  //controls.update();
  renderer.render(scene, camera);
}


init();
createLights();
createFloor();
createBirds();
loop();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. //cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js
  2. //cdnjs.cloudflare.com/ajax/libs/gsap/1.16.1/TweenMax.min.js
  3. https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/OrbitControls.js