Skeletal animation with threejs and Blender.
Music: "Piano miniature 003" by Blear Moon (https://www.facebook.com/blearmoon)
Forked from Sascha Sigl 's Pen cycle through the night.
A Pen by Michael Anthony Casey on CodePen.
Skeletal animation with threejs and Blender.
Music: "Piano miniature 003" by Blear Moon (https://www.facebook.com/blearmoon)
Forked from Sascha Sigl 's Pen cycle through the night.
A Pen by Michael Anthony Casey on CodePen.
| <div id="container"></div> | |
| <img src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/61062/man_texture.jpg' id="texture_man" crossOrigin="anonymous"> | |
| <img src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/61062/moon.jpg' id="texture_moon" crossOrigin="anonymous"> | |
| <img src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/61062/lamp.png' id="texture_lamp" crossOrigin="anonymous"> | |
| $(document).ready(function(){ | |
| var container; | |
| var camera, scene, controls; | |
| var raycaster = new THREE.Raycaster(); | |
| var renderer; | |
| var clock = new THREE.Clock(); | |
| var time = 0; | |
| var duration = 100; | |
| var keyframes = 4; | |
| var interpolation = duration / keyframes; | |
| var currentKeyframe = 0; | |
| var lastKeyframe = 0; | |
| var animOffset = 1; | |
| var radius = 600; | |
| var theta = 0; | |
| var prevTime = Date.now(); | |
| var lamp_light, light, fake_light; | |
| var mouseX = 0, | |
| mouseY = 0; | |
| var lamp_light,moon_light; | |
| var stones = []; | |
| var large_stones = []; | |
| var house = []; | |
| var lamp_mesh = []; | |
| var dirt; | |
| var rahmen; | |
| var videoTexture; | |
| var main_options = { | |
| speed: .8, | |
| bounds: { | |
| min: 70, | |
| max: 80 | |
| } | |
| } | |
| var helper; | |
| var mesh, circle, controller_animation, helper; | |
| //_______________________________________ | |
| var texture_man = document.getElementById('texture_man'); | |
| var texture_moon = document.getElementById('texture_moon'); | |
| var texture_lamp = document.getElementById('texture_lamp'); | |
| //_______________________________________ | |
| var å = { | |
| audio: { | |
| src: "https://s3-us-west-2.amazonaws.com/s.cdpn.io/61062/Blear_Moon_-_08_-_Piano_miniature_003.mp3" | |
| }, | |
| gravity: 0.981, | |
| ready: { | |
| count: 0 | |
| }, | |
| mouse: { | |
| x: 0, | |
| y: 0, | |
| z: 0.5 | |
| }, | |
| models: { | |
| man: "https://s3-us-west-2.amazonaws.com/s.cdpn.io/61062/stylised.json", | |
| lamp: "https://s3-us-west-2.amazonaws.com/s.cdpn.io/61062/lamp.json", | |
| }, | |
| texture: { }, | |
| targetList: [] | |
| } | |
| $(window).load(function() { | |
| å.texture.moon = new THREE.Texture(texture_moon); | |
| å.texture.character= new THREE.Texture(texture_man); | |
| å.texture.lamp= new THREE.Texture(texture_lamp); | |
| for (t in å.texture) { | |
| å.texture[t].crossOrigin = "Anonymous"; | |
| å.texture[t].minFilter = THREE.NearestFilter; | |
| å.texture[t].needsUpdate = true; | |
| } | |
| init(); | |
| animate(); | |
| }); | |
| //___________________________________________ | |
| function randomNumber(min, max, bool) { | |
| var num = Math.floor(Math.random() * max) + min; // this will get a number between 1 and 99; | |
| if (bool || typeof bool == "undefined") { | |
| num *= Math.floor(Math.random() * 2) == 1 ? 1 : -1; | |
| } | |
| return num; | |
| } | |
| function generate_color(x, y, z) { | |
| if (x < 1) { | |
| x += .01; | |
| } else { | |
| x = 0; | |
| } | |
| if (y < 1) { | |
| y += .01; | |
| } else { | |
| y = 0; | |
| } | |
| if (z < 1) { | |
| z += .01; | |
| } else { | |
| z = 0; | |
| } | |
| return [x, y, z]; | |
| } | |
| function getRandomColor() { | |
| var letters = '0123433333000ccc'.split(''); | |
| var color = '#'; | |
| for (var i = 0; i < 6; i++) { | |
| color += letters[Math.floor(Math.random() * 16)]; | |
| } | |
| return color; | |
| } | |
| function pointInCircle(point, target, radius) { | |
| var distsq = (point.x - target.x) * (point.x - target.x) + (point.y - target.y) * (point.y - target.y) + (point.z - target.z) * (point.z - target.z); | |
| // returns bool , distance to target origin | |
| return [distsq <= radius * radius * radius, distsq]; | |
| } | |
| function boneLookAt(bone, p) { | |
| //console.log(bone.name,p); | |
| var target = new THREE.Vector3( | |
| p.position.x - bone.matrixWorld.elements[12], | |
| p.position.y - bone.matrixWorld.elements[13], | |
| p.position.z - bone.matrixWorld.elements[14] | |
| ).normalize(); | |
| var v = p.position; | |
| var q = new THREE.Quaternion().setFromUnitVectors(v, target); | |
| q.x += .1; | |
| bone.quaternion.copy(q); | |
| } | |
| //___________________________________________ INIT | |
| function init() { | |
| container = document.getElementById('container'); | |
| camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, .1, 10000); | |
| camera.position.x = -50; | |
| camera.position.y = 20; | |
| camera.position.z = -135; | |
| scene = new THREE.Scene(); | |
| scene.fog = new THREE.FogExp2(0x1D1F20, 0.001); | |
| scene.add(camera); | |
| // RENDERER | |
| renderer = new THREE.WebGLRenderer({ | |
| antialias: true, | |
| transparent: true, | |
| alpha: true | |
| }); | |
| renderer.setPixelRatio(window.devicePixelRatio); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| container.appendChild(renderer.domElement); | |
| renderer.shadowMapEnabled = true; | |
| //___________________________________________ CONTROLS | |
| controls = new THREE.OrbitControls(camera); | |
| controls.damping = 0.2; | |
| controls.addEventListener('change', render); | |
| controls.maxPolarAngle = Math.PI / 2; | |
| camera.lookAt(new THREE.Vector3(0, 20, 0)); | |
| controls.target = new THREE.Vector3(0, 20, 0); | |
| controls.maxDistance = 200; | |
| //___________________________________________ HEMI | |
| var ambient = new THREE.AmbientLight(0x1A1324, 1); | |
| scene.add(ambient); | |
| //Conterlight | |
| var pointLight = new THREE.PointLight(0xffffff, .5, 300); | |
| pointLight.position.set(100, 100, 0); | |
| scene.add(pointLight); | |
| // LAMP | |
| lamp_light = new THREE.SpotLight(0xffffff, 1); // FEE191 | |
| lamp_light.position.set(100, 260, 100).multiplyScalar(1); | |
| // | |
| lamp_light.castShadow = true; | |
| // | |
| lamp_light.shadowMapWidth = 1024; | |
| lamp_light.shadowMapHeight = 1024; | |
| //lamp_light.shadowCameraVisible = true; | |
| var d = 5000; | |
| lamp_light.shadowCameraLeft = -d; | |
| lamp_light.shadowCameraRight = d; | |
| lamp_light.shadowCameraTop = d; | |
| lamp_light.shadowCameraBottom = -d; | |
| lamp_light.shadowCameraNear = 0.01; | |
| lamp_light.shadowCameraFar = 350; | |
| scene.add(lamp_light); | |
| moon_light = new THREE.SpotLight(0xffffff, .5); | |
| moon_light.position.set(0, 50, 0).multiplyScalar(1); | |
| // | |
| moon_light.castShadow = true; | |
| // | |
| moon_light.shadowMapWidth = 1024; | |
| moon_light.shadowMapHeight = 1024; | |
| moon_light.target.position.set(100, 100, 200); | |
| moon_light.target.updateMatrixWorld(); | |
| var d = 5000; | |
| moon_light.shadowCameraLeft = -d; | |
| moon_light.shadowCameraRight = d; | |
| moon_light.shadowCameraTop = d; | |
| moon_light.shadowCameraBottom = -d; | |
| moon_light.shadowCameraNear = 0.01; | |
| moon_light.shadowCameraFar = 350; | |
| scene.add(moon_light); | |
| //__________________________________ LOAD MODEL | |
| var loader = new THREE.JSONLoader(true); | |
| //___________________________________________ Little Kid | |
| loader.load(å.models.man, function(geometry, material) { | |
| var man_material = new THREE.MeshFaceMaterial(material); | |
| var man_material = new THREE.MeshPhongMaterial({ | |
| shininess: 10, | |
| map: å.texture.character, | |
| skinning: true | |
| }); | |
| man = new THREE.SkinnedMesh(geometry, man_material); //Skinned | |
| var animation = new THREE.Animation(man, geometry.animation); | |
| animation.play(); | |
| animation.timeScale = 1.8; | |
| helper = new THREE.SkeletonHelper(man); | |
| helper.material.linewidth = 3; | |
| helper.visible = false; | |
| scene.add(helper); | |
| man.receiveShadow = true; | |
| man.castShadow = true; | |
| man.position.y = 2; | |
| man.update = function(time) { | |
| this.position.x += Math.sin(0.0005 * time) * .05; | |
| } | |
| scene.add(man); | |
| man.add(front_dirt); | |
| man.add(dirt); | |
| }); | |
| //___________________________________________ Moon | |
| var moon_geometry = new THREE.SphereGeometry(85, 32, 32) | |
| var moon_material = new THREE.MeshPhongMaterial({ | |
| transparent: true, | |
| opacity: .7, | |
| map: å.texture.moon, | |
| bumpMap :å.texture.moon, | |
| bumpScale : .001 | |
| }) | |
| var moon = new THREE.Mesh(moon_geometry, moon_material); | |
| moon.position.set(100, 100, 200); | |
| scene.add(moon); | |
| //___________________________________________ | |
| var dirt_geo = new THREE.Geometry(); | |
| var kMaterial = new THREE.PointCloudMaterial({ | |
| color: 0xffffff, | |
| size: .25, | |
| transparent: true, | |
| opacity: .5, | |
| vertexColors: true | |
| }); | |
| var colors = []; | |
| var color_array = [0x363A3F, 0x846550]; | |
| var turbulence = [] | |
| for (var i = 0; i < 250; i++) { | |
| var x = randomNumber(0, 5, true); | |
| var y = 2 + randomNumber(0.1, 25, false); | |
| var z = randomNumber(0, 25, false); | |
| dirt_geo.vertices.push(new THREE.Vector3(randomNumber(0, 2.1, true), 0, randomNumber(0, .2, true))); | |
| turbulence.push(new THREE.Vector3(x, y, z)); | |
| colors.push(new THREE.Color(color_array[Math.floor(Math.random() * color_array.length)])); | |
| } | |
| dirt = new THREE.PointCloud(dirt_geo, kMaterial); | |
| dirt.turbulence = turbulence; | |
| dirt.geometry.colors = colors; | |
| dirt.geometry.colorsNeedUpdate = true; | |
| dirt.position.set(0, 0, 0); | |
| dirt.update = function(time) { | |
| var that = this; | |
| this.geometry.vertices.forEach(function(p, index) { | |
| p.x += .01 * that.turbulence[index].x; | |
| p.y += Math.sin(time) * .0001 * that.turbulence[index].y + randomNumber(.1, .8, false); | |
| p.z -= .01 * that.turbulence[index].z + randomNumber(.1, .8, false); | |
| var radius_checker = pointInCircle({ | |
| x: 0, | |
| y: 0, | |
| z: -9 | |
| }, p, 5); | |
| if (!radius_checker[0]) { | |
| p.x = 0; | |
| p.y = 0; | |
| p.z = -9; | |
| } | |
| }); | |
| this.geometry.verticesNeedUpdate = true; | |
| } | |
| front_dirt = dirt.clone(); | |
| front_dirt.position.z = 15; | |
| front_dirt.castShadow = true; | |
| //___________________________________________ | |
| loader.load(å.models.lamp, function(geometry, material) { | |
| var lamp_material = new THREE.MeshPhongMaterial({ | |
| shininess: 10, | |
| map: å.texture.lamp, | |
| side: THREE.DoubleSide, | |
| transparent: true, | |
| depthTest: true, | |
| }); | |
| var p_lamps = []; | |
| for (var l = 0; l < 2; l++) { | |
| lamp_mesh[l] = new THREE.Mesh(geometry, lamp_material); //Skinned | |
| lamp_mesh[l].position.z = -80 + (l * 65); | |
| lamp_mesh[l].position.x = -50; | |
| lamp_mesh[l].receiveShadow = true; | |
| lamp_mesh[l].castShadow = true; | |
| lamp_mesh[l].update = function(time) { | |
| this.position.z -= main_options.speed; | |
| if (this.position.z > 0 && this.position.y > 4) { | |
| this.position.y -= å.gravity; | |
| } | |
| if (this.position.z < -70) { | |
| this.position.y -= (.1 + randomNumber(0.1, 1.2, false)); | |
| this.rotation.x -= randomNumber(0.01, 0.06, false); | |
| } | |
| if (this.position.z < -(80)) { | |
| this.position.z = 80; | |
| this.position.y = (16 + randomNumber(1, 3)); | |
| this.rotation.x = randomNumber(1, 8, true) * Math.PI / 180; | |
| this.rotation.y = randomNumber(1, 8, true) * Math.PI / 180; | |
| this.rotation.z = 0; | |
| } | |
| } //end of update | |
| var pointLight = new THREE.PointLight(0xF7D747, 1, 100); | |
| pointLight.position.set(0, 55, 0); | |
| lamp_mesh[l].add(pointLight); | |
| scene.add(lamp_mesh[l]); | |
| } | |
| }); | |
| //___________________________________________ | |
| var base_material = new THREE.MeshLambertMaterial({ | |
| color: 0x333333, | |
| transparent: true | |
| }); | |
| var box = new THREE.BoxGeometry(5, 5, 5); | |
| var pool_options = { | |
| position: { | |
| x: 0, | |
| y: 0, | |
| z: 80 | |
| }, | |
| radius: 5 | |
| } | |
| var small_stone = { | |
| offset: { | |
| x: -30, | |
| z: -55 | |
| }, | |
| stone_row: 0, | |
| stone_col: 0 | |
| } | |
| for (var i = 0; i < 480; i++) { | |
| stones[i] = new THREE.Mesh(box, base_material); | |
| if (i % 15 == 0) { | |
| small_stone.stone_row++; | |
| small_stone.stone_col = 0; | |
| } | |
| stones[i].position.x = small_stone.offset.x + 5 * small_stone.stone_col + randomNumber(.1, .8, true); | |
| stones[i].position.z = small_stone.offset.z + 5 * small_stone.stone_row + randomNumber(.1, .8, true); | |
| stones[i].rotation.x = randomNumber(1, 8, true) * Math.PI / 180; | |
| stones[i].rotation.y = randomNumber(1, 8, true) * Math.PI / 180; | |
| stones[i].update = function(time) { | |
| this.position.z -= main_options.speed; | |
| pool_options.position.z -= main_options.speed; | |
| if (this.position.z > 0 && this.position.y < -(1 + randomNumber(.1, .8, true))) { | |
| this.position.y += å.gravity; | |
| } | |
| if (this.position.z < -51) { | |
| this.position.y -= (.1 + randomNumber(0.1, 1.2, false)); | |
| this.rotation.x += randomNumber(0.1, 0.6, true); | |
| this.rotation.y += randomNumber(0.1, 0.6, true); | |
| this.rotation.z += randomNumber(0.1, 0.6, true); | |
| } | |
| if (this.position.z < -(80)) { | |
| this.position.z = 80; | |
| this.position.y = -(16 + randomNumber(1, 3)); | |
| this.rotation.x = randomNumber(1, 8, true) * Math.PI / 180; | |
| this.rotation.y = randomNumber(1, 8, true) * Math.PI / 180; | |
| this.rotation.z = 0; | |
| } | |
| } | |
| scene.add(stones[i]); | |
| stones[i].receiveShadow = true; | |
| small_stone.stone_col++; | |
| } | |
| /// ___________________________________________ BIG STONES | |
| var large_box = new THREE.BoxGeometry(40, 5, 29); | |
| var large_stone = { | |
| offset: { | |
| x: -52.5, | |
| z: -70, | |
| }, | |
| stone_row: 0, | |
| stone_col: 0 | |
| } | |
| for (var i = 0; i < 12; i++) { | |
| large_stones[i] = new THREE.Mesh(large_box, base_material); | |
| if (i % 2 == 0) { | |
| large_stone.stone_row++; | |
| large_stone.stone_col = 0; | |
| } | |
| large_stones[i].position.x = large_stone.offset.x + 115 * large_stone.stone_col + randomNumber(.1, .8, true); | |
| large_stones[i].position.z = large_stone.offset.z + 27 * large_stone.stone_row; | |
| large_stones[i].rotation.x = randomNumber(1, 3, true) * Math.PI / 180; | |
| large_stones[i].rotation.z = randomNumber(1, 3, true) * Math.PI / 180; | |
| large_stones[i].position.y = 0; | |
| large_stones[i].update = function(time) { | |
| this.position.z -= main_options.speed; | |
| if (this.position.z > 0 && this.position.y < randomNumber(.1, .8, true)) { | |
| this.position.y += å.gravity; | |
| } | |
| if (this.position.z < -70) { | |
| this.position.y -= (.1 + randomNumber(0.1, 1.2, false)); | |
| this.rotation.x -= randomNumber(0.01, 0.1, false); | |
| } | |
| if (this.position.z < -(80)) { | |
| this.position.z = 80; | |
| this.position.y = -(16); | |
| this.rotation.x = randomNumber(1, 3, true) * Math.PI / 180; | |
| this.rotation.z = randomNumber(1, 3, true) * Math.PI / 180; | |
| this.rotation.y = 0; | |
| } | |
| } | |
| large_stones[i].receiveShadow = true; | |
| scene.add(large_stones[i]); | |
| large_stone.stone_col++; | |
| } | |
| //___________________________________________ HOUSE | |
| var house_geo = new THREE.BoxGeometry(5, 10, 20); | |
| var house_options = { | |
| offset: { | |
| x: -73, | |
| y: 0, | |
| z: -50 | |
| }, | |
| stone_row: 0, | |
| stone_col: 0 | |
| } | |
| for (var i = 0; i < 56; i++) { | |
| house[i] = new THREE.Mesh(house_geo, base_material); | |
| if (i % 8 == 0) { | |
| house_options.stone_row++; | |
| house_options.stone_col = 0; | |
| } | |
| house[i].position.x = house_options.offset.x + randomNumber(.1, .8, true); | |
| house[i].position.y = -5 + (house_options.stone_row * 10 + randomNumber(.1, .8, true)); | |
| house[i].position.z = house_options.offset.z + 20 * house_options.stone_col + randomNumber(.1, .8, true); | |
| house[i].rotation.y = randomNumber(1, 8, true) * Math.PI / 180; | |
| house[i].options = { | |
| row: house_options.stone_row, | |
| col: house_options.stone_col | |
| } | |
| house[i].update = function(time) { | |
| this.position.z -= main_options.speed; | |
| if (this.rotation.y < 0) { | |
| this.rotation.y += 0.05; | |
| } | |
| if (this.position.z < -51) { | |
| this.position.y -= (randomNumber(0.1, .8, false)); | |
| this.rotation.y += randomNumber(0.01, 0.06, false); | |
| } | |
| if (this.position.z < -(80)) { | |
| this.position.z = 80; | |
| this.position.y = -5 + (this.options.row * 10 + randomNumber(.1, .8, true)); | |
| this.rotation.set(0, -1, 0); | |
| } | |
| } //end of update | |
| scene.add(house[i]); | |
| house[i].receiveShadow = true; | |
| house_options.stone_col++; | |
| } | |
| //___________________________________________ Audio | |
| var audio_ = document.createElement('audio'); | |
| audio_.src = å.audio.src; | |
| audio_.loop = true; | |
| document.body.appendChild(audio_); | |
| audio_.play(); | |
| } // end of init | |
| // __________________________________ Only for Skinned Mesh Animation | |
| function ensureLoop(animation) { | |
| for (var i = 0; i < animation.hierarchy.length; i++) { | |
| var bone = animation.hierarchy[i]; | |
| var first = animation.data.hierarchy[0]; | |
| var last = animation.data.hierarchy[animation.data.hierarchy.length - 1]; | |
| last.pos = first.pos; | |
| last.rot = first.rot; | |
| last.scl = first.scl; | |
| } | |
| } | |
| //___________________________________________ Event in Space | |
| window.addEventListener('resize', onWindowResize, false); | |
| //___________________________________________ click | |
| function onWindowResize() { | |
| camera.aspect = window.innerWidth / window.innerHeight; | |
| camera.updateProjectionMatrix(); | |
| renderer.setSize(window.innerWidth, window.innerHeight); | |
| } | |
| //___________________________________________ RENDER | |
| function animate(time) { | |
| requestAnimationFrame(animate); | |
| render(time); | |
| if (typeof stones != "undefined") { | |
| stones.forEach(function(el) { | |
| el.update(time); | |
| }); | |
| large_stones.forEach(function(el) { | |
| el.update(time); | |
| }); | |
| lamp_mesh.forEach(function(el) { | |
| el.update(time); | |
| }); | |
| } | |
| if (typeof dirt != "undefined") { | |
| dirt.update(time); | |
| } | |
| if (typeof man != "undefined") { | |
| man.update(time); | |
| } | |
| if (typeof house != "undefined") { | |
| house.forEach(function(el) { | |
| el.update(time); | |
| }); | |
| } | |
| if (typeof rahmen != "undefined") { | |
| rahmen.update(time); | |
| } | |
| } | |
| function render(time) { | |
| theta += 0.1; | |
| // update morph | |
| var delta = .75 * clock.getDelta(); | |
| // update skinning | |
| THREE.AnimationHandler.update(delta); | |
| if (helper !== undefined) { | |
| helper.update(); | |
| } | |
| renderer.render(scene, camera); | |
| } | |
| }); |
| <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.min.js"></script> | |
| <script src="http://paprcraft.com/js/orbit_controls.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js"></script> |
| *{ | |
| margin:0; | |
| padding:0; | |
| box-sizing:border-box; | |
| } | |
| html,body{ | |
| width:100%; | |
| height:100%; | |
| } | |
| body{ | |
| overflow:hidden; | |
| background:#000; | |
| } | |
| img{ | |
| display:none; | |
| } | |
| .no-touch #container{ | |
| -webkit-filter: blur(.05em) sepia(0.3); | |
| -moz-filter: blur(.05em) sepia(0.3); | |
| -o-filter: blur(.05em) sepia(0.3); | |
| filter: blur(.05em) sepia(0.3); | |
| } | |
| div#container:before { | |
| content: ''; | |
| display: block; | |
| width: 100%; | |
| height: 100%; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| box-shadow: inset 0 0 5em 2em #000; | |
| -webkit-filter:blur(5px); | |
| } |