<div id="world"/>
@import url('https://fonts.googleapis.com/css?family=Voltaire');

body{
  background-color: #dbe6e6;
}

#world{
	position: absolute;
	width:calc( 100% - 220px );
	height: 100%;
  overflow:hidden;
}
//THREEJS RELATED VARIABLES 

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

// OTHER VARIABLES

var PI = Math.PI;
var hero;
var clock;
var container;
var gui = new dat.GUI();

// MATERIALS

var blackMat = new THREE.MeshPhongMaterial({
    color: 0x100707,
    shading:THREE.FlatShading,
  });
  
var orangeMat = new THREE.MeshPhongMaterial({
    color: 0xb44b39,
    shininess:0,
    shading:THREE.FlatShading,
  });

  var lightOrangeMat = new THREE.MeshPhongMaterial({
    color: 0xe07a57,
    shading:THREE.FlatShading,
  });
  
  var whiteMat = new THREE.MeshPhongMaterial({
    color: 0xa49789, 
    shading:THREE.FlatShading,
  });


// CUSTOMIZABLE PARAMETERS

var parameters = {
  speed:6,
  
  pawFRAmplitudeY:4,
  pawFRAmplitudeZ:8,
  pawFRCycleOffset:.2,
  pawFRAnkleRotationAmplitude:PI/2,
  
  pawFLAmplitudeY:4,
  pawFLAmplitudeZ:8,
  pawFLCycleOffset:-.2,
  pawFLAnkleRotationAmplitude:PI/2,
  
  pawBRAmplitudeY:4,
  pawBRAmplitudeZ:5,
  pawBRCycleOffset:-.1 + PI,
  pawBRAnkleRotationAmplitude:PI/2,
  
  pawBLAmplitudeY:4,
  pawBLAmplitudeZ:5,
  pawBLCycleOffset:.1 - PI,
  pawBLAnkleRotationAmplitude:PI/2,
  
  bodyYAmplitude:4,
  bodyCycleOffset:-PI/2,
  bodyRotationAmplitude:PI*.12,
  
  torsoYAmplitude:2,
  torsoCycleOffset:-PI/2,
  torsoRotationAmplitude:PI*.12,
  
  tailRotationAmplitude:PI/3,
  tailCycleOffset:PI/2,
  
  headYAmplitude:3,
  headZAmplitude:4,
  headCycleOffset:-PI/2,
  headRotationAmplitude:PI/8,
  
  mouthRotationAmplitude:.6,
  mouthCycleOffset:PI,
  
  earRightRotationAmplitude:.8,
  earRightCycleOffset:-PI/2 + .2,
  
  earLeftRotationAmplitude:.6,
  earLeftCycleOffset:-PI/2,
  
  eyeMinScale:.1,
  eyeMaxScale:1,
  eyeCycleOffset:-Math.PI/4,
}

function initGUI(){
  
  gui.width = 250;
  gui.add(parameters, 'speed').min(0).max(20).step(1).name('Cycle speed');
  
  var fr = gui.addFolder('Front Right');
  fr.add(parameters, 'pawFRCycleOffset').min(-PI).max(PI).step(.1).name('Cycle Offset');
  fr.add(parameters, 'pawFRAmplitudeY').min(0).max(12).step(.1).name('Leg Amp Y');
  fr.add(parameters, 'pawFRAmplitudeZ').min(0).max(12).step(.1).name('Leg Amp Z');
  fr.add(parameters, 'pawFRAnkleRotationAmplitude').min(0).max(PI).step(0.01).name('Ankle Amp');
  //fr.open();
  
  var fl = gui.addFolder('Front Left');
  fl.add(parameters, 'pawFLCycleOffset').min(-PI).max(PI).step(.1).name('Cycle Offset');
  fl.add(parameters, 'pawFLAmplitudeY').min(0).max(12).step(.1).name('Leg Amp Y');
  fl.add(parameters, 'pawFLAmplitudeZ').min(0).max(12).step(.1).name('Leg Amp Z');
  fl.add(parameters, 'pawFLAnkleRotationAmplitude').min(0).max(PI).step(0.01).name('Ankle Amp');
  //fl.open();
  
  var br = gui.addFolder('Back Right');
  br.add(parameters, 'pawBRCycleOffset').min(-PI).max(PI).step(.1).name('Cycle Offset');
  br.add(parameters, 'pawBRAmplitudeY').min(0).max(12).step(.1).name('Leg Amp Y');
  br.add(parameters, 'pawBRAmplitudeZ').min(0).max(12).step(.1).name('Leg Amp Z');
  br.add(parameters, 'pawBRAnkleRotationAmplitude').min(0).max(PI).step(0.01).name('Ankle Amp');
  //br.open();
  
  var bl = gui.addFolder('Back Left');
  bl.add(parameters, 'pawBLCycleOffset').min(-PI).max(PI).step(.1).name('Cycle Offset');
  bl.add(parameters, 'pawBLAmplitudeY').min(0).max(12).step(.1).name('Leg Amp Y');
  bl.add(parameters, 'pawBLAmplitudeZ').min(0).max(12).step(.1).name('Leg Amp Z');
  bl.add(parameters, 'pawBLAnkleRotationAmplitude').min(0).max(PI).step(0.01).name('Ankle Amp');
  //bl.open();
  
  var bdy = gui.addFolder('Body');
  bdy.add(parameters, 'bodyCycleOffset').min(-PI).max(PI).step(.1).name('Cycle Offset');
  bdy.add(parameters, 'bodyYAmplitude').min(0).max(5).step(.1).name('Y Amp');
  bdy.add(parameters, 'bodyRotationAmplitude').min(0).max(PI).step(0.01).name('Rotation Amp');
  //bdy.open();
  
  var trso = gui.addFolder('Torso');
  trso.add(parameters, 'torsoCycleOffset').min(-PI).max(PI).step(.1).name('Cycle Offset');
  trso.add(parameters, 'torsoYAmplitude').min(0).max(5).step(.1).name('Y Amp');
  trso.add(parameters, 'torsoRotationAmplitude').min(0).max(PI).step(0.01).name('Rotation Amp');
  //trso.open();
  
  var tail = gui.addFolder('Tail');
  tail.add(parameters, 'tailCycleOffset').min(-PI).max(PI).step(.1).name('Cycle Offset');
  tail.add(parameters, 'tailRotationAmplitude').min(0).max(PI).step(0.01).name('Rotation Amp');
  //tail.open();
  
  var mouth = gui.addFolder('Mouth');
  mouth.add(parameters, 'mouthCycleOffset').min(-PI).max(PI).step(.1).name('Cycle Offset');
  mouth.add(parameters, 'mouthRotationAmplitude').min(0).max(PI).step(0.01).name('Rotation Amp');
  //mouth.open();
  
  
  var head = gui.addFolder('Head');
  head.add(parameters, 'headCycleOffset').min(-PI).max(PI).step(.1).name('Cycle Offset');
  head.add(parameters, 'headYAmplitude').min(0).max(5).step(.1).name('Y Amp');
  head.add(parameters, 'headZAmplitude').min(0).max(5).step(.1).name('Z Amp');
  head.add(parameters, 'headRotationAmplitude').min(0).max(PI).step(0.01).name('Rotation Amp');
  //head.open();
  
  
  var earRight = gui.addFolder('Ear Right');
  earRight.add(parameters, 'earRightCycleOffset').min(-PI).max(PI).step(.1).name('Cycle Offset');
  earRight.add(parameters, 'earRightRotationAmplitude').min(0).max(PI).step(0.01).name('Rotation Amp');
  //earRight.open();
  
  var earLeft = gui.addFolder('Ear Left');
  earLeft.add(parameters, 'earLeftCycleOffset').min(-PI).max(PI).step(.1).name('Cycle Offset');
  earLeft.add(parameters, 'earLeftRotationAmplitude').min(0).max(PI).step(0.01).name('Rotation Amp');
  //earLeft.open();
  
  var eyes = gui.addFolder('Eyes');
  eyes.add(parameters, 'eyeCycleOffset').min(-PI).max(PI).step(.1).name('Cycle Offset');
  eyes.add(parameters, 'eyeMinScale').min(0).max(1).step(0.01).name('Scale Min');
  eyes.add(parameters, 'eyeMaxScale').min(0).max(1).step(0.01).name('Scale Max');
  //earLeft.open();
  
  gui.remember(parameters);
}

function resetAnimation(){
  for(var i = 0; i<gui.__controllers.length;i++){
    gui.__controllers[i].setValue(0);
  }
}

//INIT THREE JS, SCREEN AND MOUSE EVENTS

function initScreenAnd3D() {
  container = document.getElementById('world');
  HEIGHT = container.offsetHeight;
  WIDTH = container.width;
  windowHalfX = WIDTH / 2;
  windowHalfY = HEIGHT / 2;

  scene = new THREE.Scene();
  
  scene.fog = new THREE.Fog(0xd6eae6, 150,300);
  
  aspectRatio = WIDTH / HEIGHT;
  fieldOfView = 50;
  nearPlane = 1;
  farPlane = 2000;
  camera = new THREE.PerspectiveCamera(
    fieldOfView,
    aspectRatio,
    nearPlane,
    farPlane
  );
  camera.position.x = 0;
  camera.position.z = 100;
  camera.position.y = 0;
  //camera.lookAt(new THREE.Vector3(0, 30, 0));

  renderer = new THREE.WebGLRenderer({
    alpha: true,
    antialias: true
  });
  renderer.setSize(WIDTH, HEIGHT);
  renderer.shadowMap.enabled = true;

  
  container.appendChild(renderer.domElement);

  window.addEventListener('resize', handleWindowResize, false);

  controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.noPan = true;
  controls.noZoom = true;
  
  clock = new THREE.Clock();
  handleWindowResize();
}

function handleWindowResize() {
  HEIGHT = container.offsetHeight;
  WIDTH = container.offsetWidth;
  windowHalfX = WIDTH / 2;
  windowHalfY = HEIGHT / 2;
  renderer.setSize(WIDTH, HEIGHT);
  camera.aspect = WIDTH / HEIGHT;
  camera.updateProjectionMatrix();
}

function createLights() {
  globalLight = new THREE.AmbientLight(0xffffff, 1);
  shadowLight = new THREE.DirectionalLight(0xffffff, .9);
  shadowLight.position.set(-8, 8, 8);
  shadowLight.castShadow = true;
  shadowLight.shadow.camera.left = -40;
  shadowLight.shadow.camera.right = 40;
  shadowLight.shadow.camera.top = 40;
  shadowLight.shadow.camera.bottom = -40;
  shadowLight.shadow.camera.near = 1;
  shadowLight.shadow.camera.far = 1000;
  shadowLight.shadow.mapSize.width = shadowLight.shadow.mapSize.height = 2048;
  scene.add(globalLight);
  scene.add(shadowLight);
}

Hero = function() {
  this.status = "running";
  this.runningCycle = 0;
  this.mesh = new THREE.Group();
  this.body = new THREE.Group();
  this.mesh.add(this.body);
  
  var torsoGeom = new THREE.CubeGeometry(7, 7, 10, 1);
  
  this.torso = new THREE.Mesh(torsoGeom, orangeMat);
  this.torso.position.z = 0;
  this.torso.position.y = 7;
  this.torso.rotation.x = -Math.PI/8;
  this.torso.castShadow = true;
  this.body.add(this.torso);
  
  var pantsGeom = new THREE.CubeGeometry(9, 9, 5, 1);
  this.pants = new THREE.Mesh(pantsGeom, whiteMat);
  this.pants.position.z = -3;
  this.pants.position.y = 0;
  this.pants.castShadow = true;
  this.torso.add(this.pants);
  
  var tailGeom = new THREE.CubeGeometry(3, 3, 3, 1);
  tailGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0,0,-2));
  this.tail = new THREE.Mesh(tailGeom, lightOrangeMat);
  this.tail.position.z = -4;
  this.tail.position.y = 5;
  this.tail.castShadow = true;
  this.torso.add(this.tail);
  
  var headGeom = new THREE.CubeGeometry(10, 10, 13, 1);
  headGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0,0,7.5));
  this.head = new THREE.Mesh(headGeom, orangeMat);
  this.head.position.z = 2;
  this.head.position.y = 11;
  this.head.castShadow = true;
  this.body.add(this.head);
  
  var cheekGeom = new THREE.CubeGeometry(1, 4, 4, 1);
  this.cheekR = new THREE.Mesh(cheekGeom, lightOrangeMat);
  this.cheekR.position.x = -5;
  this.cheekR.position.z = 7;
  this.cheekR.position.y = -2.5;
  this.cheekR.castShadow = true;
  this.head.add(this.cheekR);
  
  this.cheekL = this.cheekR.clone();
  this.cheekL.position.x = - this.cheekR.position.x;
  this.head.add(this.cheekL);
  
  var noseGeom = new THREE.CubeGeometry(6, 6, 3, 1);
  this.nose = new THREE.Mesh(noseGeom, lightOrangeMat);
  this.nose.position.z = 13.5;
  this.nose.position.y = 2.6;
  this.nose.castShadow = true;
  this.head.add(this.nose);
  
  var mouthGeom = new THREE.CubeGeometry(4, 2, 4, 1);
  mouthGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0,0,3));
  mouthGeom.applyMatrix(new THREE.Matrix4().makeRotationX(Math.PI/12));
  this.mouth = new THREE.Mesh(mouthGeom, orangeMat);
  this.mouth.position.z = 8;
  this.mouth.position.y = -4;
  this.mouth.castShadow = true;
  this.head.add(this.mouth);
  
  var pawFGeom = new THREE.CubeGeometry(3,4,3, 1);
  this.pawFR = new THREE.Mesh(pawFGeom, lightOrangeMat);
  this.pawFR.position.x = -2.5;
  this.pawFR.position.z = 6;
  this.pawFR.position.y = 1.5;
  this.pawFR.castShadow = true;
  this.body.add(this.pawFR);
  
  this.pawFL = this.pawFR.clone();
  this.pawFL.position.x = - this.pawFR.position.x;
  this.pawFL.castShadow = true;
  this.body.add(this.pawFL);
  
  var pawBGeom = new THREE.CubeGeometry(3,3,6, 1);
  this.pawBL = new THREE.Mesh(pawBGeom, lightOrangeMat);
  this.pawBL.position.y = 1.5;
  this.pawBL.position.z = 0;
  this.pawBL.position.x = 5;
  this.pawBL.castShadow = true;
  this.body.add(this.pawBL);
  
  this.pawBR = this.pawBL.clone();
  this.pawBR.position.x = - this.pawBL.position.x;
  this.pawBR.castShadow = true;
  this.body.add(this.pawBR);
  
  var earGeom = new THREE.CubeGeometry(7, 18, 2, 1);
  earGeom.vertices[6].x+=2;
  earGeom.vertices[6].z+=.5;
  
  earGeom.vertices[7].x+=2;
  earGeom.vertices[7].z-=.5;
  
  earGeom.vertices[2].x-=2;
  earGeom.vertices[2].z-=.5;
  
  earGeom.vertices[3].x-=2;
  earGeom.vertices[3].z+=.5;
  earGeom.applyMatrix(new THREE.Matrix4().makeTranslation(0,9,0));
  
  this.earL = new THREE.Mesh(earGeom, orangeMat);
  this.earL.position.x = 2;
  this.earL.position.z = 2.5;
  this.earL.position.y = 5;
  this.earL.rotation.z = -Math.PI/12;
  this.earL.castShadow = true;
  this.head.add(this.earL);
  
  this.earR = this.earL.clone();
  this.earR.position.x = -this.earL.position.x;
  this.earR.rotation.z = -this.earL.rotation.z;
  this.earR.castShadow = true;
  this.head.add(this.earR);
  
  var eyeGeom = new THREE.CubeGeometry(2,4,4);
  
  this.eyeL = new THREE.Mesh(eyeGeom, whiteMat);
  this.eyeL.position.x = 5;
  this.eyeL.position.z = 5.5;
  this.eyeL.position.y = 2.9;
  this.eyeL.castShadow = true;
  this.head.add(this.eyeL);
  
  var irisGeom = new THREE.CubeGeometry(.6,2,2);
  
  this.iris = new THREE.Mesh(irisGeom, blackMat);
  this.iris.position.x = 1.2;
  this.iris.position.y = 1;
  this.iris.position.z = 1;
  this.eyeL.add(this.iris);
  this.eyeR = this.eyeL.clone();
  this.eyeR.children[0].position.x = -this.iris.position.x;
  this.eyeR.position.x = -this.eyeL.position.x;
  this.head.add(this.eyeR);

  this.body.traverse(function(object) {
    if (object instanceof THREE.Mesh) {
      object.castShadow = true;
      object.receiveShadow = true;
    }
  });
}

Hero.prototype.run = function(){
  var s = parameters.speed;
  this.runningCycle += delta * s;
  var t = this.runningCycle;
  
  var amp = 4;
  var disp = .2;
  
  var p = parameters;
  
  // BODY

  
  this.body.position.y = 6+ Math.sin(t + p.bodyCycleOffset) * p.bodyYAmplitude;
  this.body.rotation.x = .2 + Math.sin(t + p.bodyCycleOffset) * p.bodyRotationAmplitude;
  
  // TORSO
  this.torso.rotation.x =  Math.sin(t + p.torsoCycleOffset ) * p.torsoRotationAmplitude;
  this.torso.position.y =  7 + Math.sin(t + p.torsoCycleOffset )*p.torsoYAmplitude;
  
  // MOUTH
  this.mouth.rotation.x = Math.PI/16 + Math.cos(t) * p.mouthRotationAmplitude;
  
  // HEAD
  this.head.position.z = 2 + Math.sin(t + p.headCycleOffset) * p.headZAmplitude;
  this.head.position.y = 8 + Math.cos(t + p.headCycleOffset) * p.headYAmplitude;
  this.head.rotation.x = -.2 + Math.sin(t + p.headCycleOffset + PI*1.5) * p.headRotationAmplitude;
  
  // EARS
  this.earL.rotation.x = Math.cos(t + p.earLeftCycleOffset) * p.earLeftRotationAmplitude;
  this.earR.rotation.x = Math.cos(t + p.earRightCycleOffset)*p.earRightRotationAmplitude;
  
  /*
  eyeMinScale:.1,
  eyeMaxScale:1,
  */
  // EYES
  this.eyeR.scale.y = this.eyeL.scale.y = p.eyeMinScale +  Math.abs( Math.cos(t*.5 + p.eyeCycleOffset)) * (p.eyeMaxScale - p.eyeMinScale);
  
  // TAIL
  this.tail.rotation.x = Math.cos(t + p.tailCycleOffset) * p.tailRotationAmplitude;
  
  
  // FRONT RIGHT PAW
  this.pawFR.position.y = 1.5 + Math.sin(t + p.pawFRCycleOffset) * p.pawFRAmplitudeY;
  this.pawFR.position.z = 6 - Math.cos(t + p.pawFRCycleOffset) * p.pawFRAmplitudeZ;
  this.pawFR.rotation.x = Math.cos(t + p.pawFRCycleOffset ) * p.pawFRAnkleRotationAmplitude;
  
  // FRONT LEFT PAW
  
  this.pawFL.position.y = 1.5 + Math.sin(t + p.pawFLCycleOffset ) * p.pawFLAmplitudeY;
  this.pawFL.position.z = 6 - Math.cos(t + p.pawFLCycleOffset ) * p.pawFLAmplitudeZ;
  this.pawFL.rotation.x = Math.cos( t + p.pawFLCycleOffset ) * p.pawFLAnkleRotationAmplitude;
  
  
  // BACK RIGHT PAW
  this.pawBR.position.y = 1.5 + Math.sin( t + p.pawBRCycleOffset) * p.pawBRAmplitudeY;
  this.pawBR.position.z = - Math.cos( t + p.pawBRCycleOffset) * p.pawBRAmplitudeZ;
  this.pawBR.rotation.x = Math.cos(t + p.pawBRCycleOffset + PI *.25) * p.pawBRAnkleRotationAmplitude;
  
  // BACK LEFT PAW
  this.pawBL.position.y = 1.5 + Math.sin(t + p.pawBLCycleOffset)*p.pawBLAmplitudeY;
  this.pawBL.position.z = - Math.cos(t + p.pawBLCycleOffset)*p.pawBLAmplitudeZ; 
  this.pawBL.rotation.x = Math.cos(t + p.pawBLCycleOffset + PI *.25 ) * p.pawBLAnkleRotationAmplitude; 
}

function createHero() {
  hero = new Hero();
  hero.mesh.position.y=-15;
  hero.mesh.rotation.y = Math.PI/2;
  scene.add(hero.mesh);
}

function loop(){
  delta = clock.getDelta();
  hero.run();
  render();  
  requestAnimationFrame(loop);
}

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



window.addEventListener('load', init, false);

function init(event){
  initScreenAnd3D();
  createLights();
  createHero();
  initGUI();
  loop();
  
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/three.js/r80/three.js
  2. https://s3-us-west-2.amazonaws.com/s.cdpn.io/264161/OrbitControls.js
  3. https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.6.5/dat.gui.min.js