Forked from: Lo-Th's Speech Character View Diff (1) Talking Character with Three JS and Google Speech API ELHS Follow 2014-05-24 19:24:40 License: MIT License Fork0 Fav0 View2189 Play Stop Reload Fullscreen Smart Phone Readme JavaScript 601 lines HTML 10 lines CSS 10 lines Talking Character with Three JS and Google Speech API // forked from Lo-Th's "Speech Character" http://jsdo.it/Lo-Th/jTTY var isBestQuality; var container = document.getElementById('container'); var editor = document.getElementById('editor'); var output = document.getElementById('output'); var play = document.getElementById('play'); var exp = document.getElementById('exp'); window.onload = init; function init(){ var touchable = 'createTouch' in document; if(touchable) isBestQuality = false; else isBestQuality = true; var ph = new Phoneme.Base(editor,output,play); //if ( ! Detector.webgl ) Detector.addGetWebGLMessage(); init3D(); } //=============================================== // PHONEME & SPEECH //=============================================== var Phoneme = {DEV:0.1}; Phoneme.Base = function(Editor,Output,Play){ this.editor = Editor; this.output = Output; this.play = Play; this.xml = null; this.msg = null; this.initSynthesis(); this.init(); this.timer = null; } Phoneme.Base.prototype ={ constructor: Phoneme.Base, init:function(){ var _this = this; if (window.XDomainRequest) this.xml = new XDomainRequest(); else if (window.XMLHttpRequest) this.xml = new XMLHttpRequest(); else this.xml = new ActiveXObject("Microsoft.XMLHTTP"); this.editor.addEventListener( 'keyup', function ( e ) { _this.periodicUpdate(_this); }, false ); this.play.addEventListener( 'click', function ( e ) { _this.say(); }, false ); }, initSynthesis:function(){ this.fallbackSpeechSynthesis = window.speechSynthesisPolyfill; this.fallbackSpeechSynthesisUtterance = window.SpeechSynthesisUtterancePolyfill; this.msg = new this.fallbackSpeechSynthesisUtterance(); }, periodicUpdate:function(t){ setTimeout(t.update, 150, t); }, update:function(t){ var message = t.editor.value; var data = { text: message }; t.xml.open('POST', "http://api.corrasable.com/phonemes", true); t.xml.setRequestHeader("Content-type", "application/json"); t.xml.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200){ var str = JSON.parse(this.responseText); var res = str.map(function(line) { return t.warp(line).join(" ") + "<br>"; }) t.output.innerHTML = res; t.morphTarget(); } } t.xml.send(JSON.stringify(data)); }, warp: function(words) { return words.map( function(word){ return "<span class='word'>" + word + "</span>"; }); }, morphTarget:function(){ var out = this.output.childNodes; if( out.length > 0 ){ phonemesSequency = []; for(var i=0; i< out.length; i++){ if(out[i].nodeName=="#text") convert(0); else if(out[i].nodeName=="SPAN") convert( out[i].textContent ); } if(phonemesSequency.length>0)this.play.style.visibility = 'visible'; else this.play.style.visibility = 'hidden'; } }, testSpeaking:function(t){ if(t.msg.speaking) exp.value = "i'm talk"; }, say:function(){ if(this.editor.value!== ''){ if(this.msg!==null){ this.msg = new this.fallbackSpeechSynthesisUtterance(this.editor.value); //this.msg.text = this.editor.value; this.msg.rate = 1; this.msg.volume = 1 this.msg.lang = 'en-GB'; if(window.speechSynthesis){ var voices = window.speechSynthesis.getVoices(); this.msg.voice = voices.filter(function(voice) { return voice.name == 'Google UK English Female'; })[0]; } this.msg.onend = function(event) { console.log('Finished in ' + event.elapsedTime + ' seconds.'); }; this.msg.onpause = function(event) { console.log('Paused in ' + event.elapsedTime + ' seconds.'); }; this.msg.onresume = function(event) { console.log('Resumed in ' + event.elapsedTime + ' seconds.'); }; this.msg.onstart = function(event) { console.log('Started in ' + event.elapsedTime + ' seconds.'); } this.fallbackSpeechSynthesis.speak(this.msg); //this.fallbackSpeechSynthesis.pause(); // this.fallbackSpeechSynthesis.resume(); // var _this = this; if(isBestQuality) setTimeout( function ( e ) {saySequence()}, 100); else setTimeout( function ( e ) {saySequence()}, 1500); //this.timer = setInterval(function ( e ) { _this.testSpeaking(_this); }, 100 ); } } } } //=============================================== // 3D SIDE: THREE.JS & SEA3D //=============================================== var camera, scene, renderer, center, cloc, cam, mouse; var eyeLeft, eyeRight; var headMeshs = []; var morphsTables = []; var fullLoaded = false; var headBone; var headBoneRef; var eyes; var bestMaterial = []; var lowMaterial = []; var lights = []; function init3D(){ cam = { horizontal:95, vertical:85, distance:15 }; mouse = { ox:0, oy:0, h:0, v:0, mx:0, my:0, dx:0, dy:0, down:false, over:false, moving:true }; renderer = new THREE.WebGLRenderer({antialias:isBestQuality}); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.autoClear = false; renderer.gammaInput = true; renderer.gammaOutput = true; container.appendChild( renderer.domElement ); scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera( 50, window.innerWidth/window.innerHeight, 0.5, 10000 ); var d = 20; var hemiLight = new THREE.HemisphereLight( 0xfffffe, 0x4488ff, 1 ); hemiLight.position.set( 0, 5, 0 ); var light = new THREE.DirectionalLight( 0xfffffe, 1.2 ); light.position.set( d/3, d, d*1.6); light.target = new THREE.Object3D(0,5,0); light.shadowMapWidth = 1024; light.shadowMapHeight = 1024; light.shadowCameraLeft = -d; light.shadowCameraRight = d; light.shadowCameraTop = d; light.shadowCameraBottom = -d; light.shadowCameraFar = d*2; light.shadowCameraNear = d; light.shadowDarkness = 0.35; light.shadowBias = -0.002; light.castShadow = true; var lightPoint = new THREE.PointLight ( 0x4488ff, 0.5 ); lightPoint.position.set( -d/3, -d/2, d*1.3); var lightPoint2 = new THREE.PointLight ( 0xFFCC44, 0.5 ); lightPoint2.position.set( d/2, -d, d*1.3); lights = [hemiLight, lightPoint, lightPoint2, light ]; if(isBestQuality){ renderer.shadowMapEnabled = true; //renderer.shadowMapCullFace = THREE.CullFaceBack; var i = lights.length; while(i--){ scene.add( lights[i] );} output.innerHTML = "engine best"; } else { output.innerHTML = "engine low"; } var back = new THREE.Mesh( new THREE.IcosahedronGeometry(300,1), new THREE.MeshBasicMaterial( { map:gradTexture([[0.75,0.5,0.45, 0.2], ['#000004','#2e3032','#69757e', '#86a4bc']]), side:THREE.BackSide, depthWrite: false } )); back.geometry.applyMatrix(new THREE.Matrix4().makeRotationZ(5*ToRad)); scene.add( back ); window.addEventListener( 'resize', resize, false ); container.addEventListener( 'mousemove', onMouseMove, false ); container.addEventListener( 'mousedown', onMouseDown, false ); container.addEventListener( 'mouseup', onMouseUp, false ); container.addEventListener( 'mouseout', onMouseUp, false ); container.addEventListener( 'touchstart', onMouseDown, false ); container.addEventListener( 'touchend', onMouseUp, false ); container.addEventListener( 'touchmove', onMouseMove, false ); var body = document.body; if( body.addEventListener ){ body.addEventListener( 'mousewheel', onMouseWheel, false ); //chrome body.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox }else if( body.attachEvent ){ body.attachEvent("onmousewheel" , onMouseWheel); // ie } loop(); loadSea3d(); } function loop() { requestAnimationFrame( loop, renderer.domElement ); updateAnimation(); renderer.render( scene, camera ); } function resize() { camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth,window.innerHeight); } function loadSea3d(){ var size = 1; var loader = new THREE.SEA3D( true ); loader.onComplete = function( e ) { var m, mat, oldmat; var i = loader.meshes.length; while(i--){ m = loader.meshes[i]; oldmat = m.material; bestMaterial[i] = oldmat; lowMaterial[i] = new THREE.MeshBasicMaterial({map:oldmat.map,transparent:oldmat.transparent, opacity:oldmat.opacity, skinning:oldmat.skinning, morphTargets:oldmat.morphTargets, side:oldmat.side }); if(!isBestQuality)m.material = lowMaterial[i]; if(m.name == 'eyeL_lo' || m.name == 'cils' || m.name == 'hair' || m.name == 'teethUpper' || m.name == 'teethLower' ) m.material.transparent = true; if(m.name == 'necklace' ) m.material.color.setHex(0x2e3032); if(m.name == 'bodyLow2' ) m.visible = false; headMeshs[i] = m; //console.log(m.name, i) } // add the head other mesh are linked to him m = headMeshs[0]; m.setWeight("neck", 1); m.setWeight("earOut", 0.6); m.scale.set(size,size,-size); m.position.set(0,0,0); scene.add(m); // eye contener fixed to head bone eyes = new THREE.Object3D(); headBone = m.skeleton.bones[1]; eyes.matrix = headBone.skinMatrix; eyes.matrixAutoUpdate = false; m.add( eyes ); headBone.rotation.y = -15*ToRad; // get the rotation referency headBoneRef = headBone.rotation.clone(); // add eyes geometry var eyeGeo = new THREE.SphereGeometry(0.64,30,28); var eyeMap = gradTexture([[0.5,0.15,0.12,0.07, 0.02], ['#ff6060','#ffffff','#5599ff','#4488ff', '#000000']]); var eyeMat; if(isBestQuality) eyeMat= new THREE.MeshPhongMaterial({ map:eyeMap, specular:0xFFFFFF, shininess:220 }); else eyeMat = new THREE.MeshBasicMaterial({ map:eyeMap }); eyeGeo.applyMatrix(new THREE.Matrix4().makeRotationX(-90*ToRad)); eyeLeft = new THREE.Mesh( eyeGeo, eyeMat); eyeRight = new THREE.Mesh( eyeGeo, eyeMat); eyeLeft.scale.set(1,1,-1); eyeRight.scale.set(1,1,-1); eyeLeft.position.set(3.82, -1.162, 2.92); eyeRight.position.set(3.82, 1.162, 2.92); eyes.add( eyeLeft ); eyes.add( eyeRight ); // camera position init center = new THREE.Vector3(0,5,0); moveCamera(); // find all morphs target var mName; for (var j=0; j < m.geometry.morphTargets.length; j++){ mName = m.geometry.morphTargets[j].name; morphsTables[mName] = { teethLower:testMorph( headMeshs[2], mName), sock:testMorph( headMeshs[1], mName), eye:testMorph( headMeshs[6], mName), tongue:testMorph( headMeshs[7], mName), cils:testMorph( headMeshs[5], mName), } } exp.addEventListener( 'click', function ( e ) { expression(); }, false ); fullLoaded = true; } // THREE.SEA3D.BUFFER is not compatible with morph loader.parser = THREE.SEA3D.DEFAULT; loader.load( 'http://jsrun.it/assets/3/E/J/2/3EJ26' ); } function testMorph(m, name){ var result = false; for (var j=0; j < m.geometry.morphTargets.length; j++){ if(m.geometry.morphTargets[j].name == name) result = true; } return result; } function fullMorph( name , value) { if(!morphsTables[name])return; headMeshs[0].setWeight(name, value); if( morphsTables[name].teethLower ) headMeshs[2].setWeight(name, value); if( morphsTables[name].sock ) headMeshs[1].setWeight(name, value); if( morphsTables[name].eye ) headMeshs[6].setWeight(name, value); if( morphsTables[name].tongue ) headMeshs[7].setWeight(name, value); if( morphsTables[name].cils ) headMeshs[5].setWeight(name, value); } //=============================================== // ANIMATION //=============================================== var count = [0,0,0]; var phonemesSequency = []; var startSequence = false; var finalSequence = false; var currentWord = ""; var prevWord = ""; var currentNum = 0; var changeExpression = false; var currentExpression = 0; var newExpression = 0; var morphExpressions = ['expression', 'anger','sad', 'disgust', 'fear', 'surprise', 'smileClose', 'smileOpen']; function updateAnimation(){ if(fullLoaded){ count[0]++; if(count[0]<=10)blinkEyes(count[0]); else if(count[0]<=20)blinkEyes(20-count[0]); else if(count[0] == 200)count[0]=0; if(startSequence){ count[1]++; if(count[1]<=5)sayWord(count[1]); else if(count[1] == 6){ count[1]=0; if(finalSequence)startSequence=false; prevWord = phonemesSequency[currentNum]; currentNum++; if(currentNum < phonemesSequency.length) currentWord = phonemesSequency[currentNum]; else finalSequence = true; } } if(changeExpression){ count[2]++; if(count[2]<=50)switchExpression(count[2]); else if(count[2] ==51){ count[2]=0; changeExpression = false; currentExpression = newExpression; } } } } function blinkEyes(N){ var n = N*0.1; headMeshs[0].setWeight("blinkLeft", n); headMeshs[0].setWeight("blinkRight", n); headMeshs[5].setWeight("blinkLeft", n); headMeshs[5].setWeight("blinkRight", n); } function switchExpression(N){ var n = N*0.02; if(newExpression!==0)fullMorph(morphExpressions[newExpression], n); fullMorph(morphExpressions[currentExpression], 1-n); } function expression(){ changeExpression = true; newExpression++; if(newExpression == morphExpressions.length) newExpression =0; exp.value = morphExpressions[newExpression]; } function saySequence(){ startSequence = true; finalSequence = false; currentNum = 0 currentWord = phonemesSequency[currentNum]; prevWord=""; } function sayWord(N){ var n = N*0.2; if(currentWord!=="")fullMorph(phonemeNumber(currentWord), n); if(prevWord!=="")fullMorph(phonemeNumber(prevWord), 1-n); } //=============================================== // PHONEME TO MORPH //=============================================== function phonemeNumber(Value) { var t; switch ( Value ) { case 0 : t = ''; break; case 1 : t = 'aah'; break; case 2 : t = 'bigaah'; break; case 3 : t = 'ch.j.sh';break; case 4 : t = 'f.v'; break; case 5 : t = 'i'; break; case 6 : t = 'k'; break; case 7 : t = 'ee'; break; case 8 : t = 'b.m.p'; break; case 9 : t = 'n'; break; case 10: t = 'oh'; break; case 11: t = 'r'; break; case 12: t = 'd.s.t'; break; case 13: t = 'th'; break; case 14: t = 'w'; break; case 15: t = 'eh'; break; case 16: t = 'ooh.q'; break; } return t; } function convert(n) { if(n==0)phonemesSequency.push(n); else { var ph = n.split(' '); var num = 1; for (var i=0;i<ph.length;i++){ switch ( ph[i] ) { case 'HH' : num = 3; break; case 'AH0': num = 1; break; case 'AH1': num = 1; break; case 'AY1': num = 5; break; case 'AY2': num = 5; break; case 'L' : num = 15; break; case 'W' : num = 14; break; case 'ER1': num = 11; break; case 'ER0': num = 11; break; case 'D' : num = 12; break; case 'M' : num = 8; break; case 'EY1': num = 7; break; case 'EH1': num = 7; break; case 'IH1': num = 5; break; case 'IH0': num = 5; break; case 'IY1': num = 5; break; case 'Z' : num = 12; break; case 'F' : num = 4; break; case 'K' : num = 6; break; case 'B' : num = 8; break; case 'R' : num = 11; break; case 'Y' : num = 15; break; case 'TH' : num = 13; break; case 'S' : num = 12; break; case 'T' : num = 13; break; case 'G' : num = 6; break; case 'OW1': num = 10; break; case 'OY1': num = 10; break; case 'UW1': num = 14; break; case 'V' : num = 4; break; case 'JH' : num = 3; break; case 'P' : num = 8; break; } phonemesSequency.push(num); } } } //=============================================== // MATH //=============================================== var ToRad = Math.PI / 180; function Orbit(origine, horizontal, vertical, distance) { var p = new THREE.Vector3(); var phi = vertical*ToRad; var theta = horizontal*ToRad; p.x = (distance * Math.sin(phi) * Math.cos(theta)) + origine.x; p.z = (distance * Math.sin(phi) * Math.sin(theta)) + origine.z; p.y = (distance * Math.cos(phi)) + origine.y; return p; } //=============================================== // MOUSE & NAVIGATION //=============================================== function moveCamera() { camera.position.copy(Orbit(center, cam.horizontal, cam.vertical, cam.distance)); camera.lookAt(center); var midL = new THREE.Vector3(camera.position.y, camera.position.x, camera.position.z); var midR = midL.clone(); eyeLeft.lookAt(midL.add(new THREE.Vector3(0,-2.25,0))); eyeRight.lookAt(midR.add(new THREE.Vector3(0,2.25,0))); } function onMouseDown(e) { e.preventDefault(); var px, py; if(e.touches){ px = e.clientX || e.touches[ 0 ].pageX; py = e.clientY || e.touches[ 0 ].pageY; } else { px = e.clientX; py = e.clientY; } mouse.ox = px; mouse.oy = py; mouse.h = cam.horizontal; mouse.v = cam.vertical; mouse.down = true; } function onMouseUp(e) { mouse.down = false; document.body.style.cursor = 'auto'; } function onMouseMove(e) { e.preventDefault(); var px, py; if(e.touches){ px = e.clientX || e.touches[ 0 ].pageX; py = e.clientY || e.touches[ 0 ].pageY; } else { px = e.clientX; py = e.clientY; } if (mouse.down) { document.body.style.cursor = 'move'; cam.horizontal = ((px - mouse.ox) * 0.3) + mouse.h; cam.vertical = (-(py - mouse.oy) * 0.3) + mouse.v; moveCamera(); } else { mouse.dx = -(px - window.innerWidth*0.5)*0.1; mouse.dy = -(py - window.innerHeight*0.5)*0.1; headBone.rotation.set(headBoneRef.x+(((mouse.dx*0.5))*ToRad), headBoneRef.y+(((mouse.dy*0.5))*ToRad), headBoneRef.z); } } function onMouseWheel(e) { e.preventDefault(); var delta = 0; if(e.wheelDelta){delta=e.wheelDelta*-1;} else if(e.detail){delta=e.detail*20;} cam.distance+=(delta/80); if(cam.distance<0.01)cam.distance = 0.01; if(cam.distance>150)cam.distance = 150; moveCamera(); } //=============================================== // AUTO TEXTURE //=============================================== function gradTexture(color) { var c = document.createElement("canvas"); var ct = c.getContext("2d"); c.width = 16; c.height = 256; var gradient = ct.createLinearGradient(0,0,0,256); var i = color[0].length; while(i--){ gradient.addColorStop(color[0][i],color[1][i]); } ct.fillStyle = gradient; ct.fillRect(0,0,16,256); var texture = new THREE.Texture(c); texture.needsUpdate = true; return texture; } <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <script src="http://lo-th.github.io/root/build/three.min.js"></script> <script src="http://lo-th.github.io/root/build/sea3d.min.js"></script> <script src="http://lo-th.github.io/root/build/polyfill.min.js"></script> <div id="container"></div> <input type="submit" value="PLAY" id="play"> <input type="submit" value="expression" id="exp"> <textarea id="editor" autofocus="true" placeholder="Begin typing"></textarea> <div id="output"></div> Talking Character with Three JS and Google Speech API * { margin:0; padding:0; border:0; } body { background:#000; font:14px monospace; overflow:hidden; } textarea { font: 14px monospace; } textarea:focus { outline: none; } #container { position:absolute; top:0; left:0; } #play { position:absolute; top:24%; left:6px; width:100px; height:30px; border:1px solid #fff; background:rgba(255,255,255,0.5); visibility:hidden; border-radius:5px; } #exp { position:absolute; top:6px; right:6px; width:100px; height:30px; border:1px solid #fff; background:rgba(255,255,255,0.5); border-radius:5px;; } #editor { position:absolute; top:0; left:0px; width:30%; height:20%; margin:5px; padding:3px; display:inline; background:rgba(255,255,255,0.5);border:1px solid #fff; border-radius:5px; } #output { position:absolute; bottom:0; left:0px; width:90%; color:#ff757e; height:40px; padding:3px; text-align:right; vertical-align:text-bottom; display:inline-block;; } // forked from Lo-Th's "Speech Character" http://jsdo.it/Lo-Th/jTTY var isBestQuality; var container = document.getElementById('container'); var editor = document.getElementById('editor'); var output = document.getElementById('output'); var play = document.getElementById('play'); var exp = document.getElementById('exp'); window.onload = init; function init(){ var touchable = 'createTouch' in document; if(touchable) isBestQuality = false; else isBestQuality = true; var ph = new Phoneme.Base(editor,output,play); //if ( ! Detector.webgl ) Detector.addGetWebGLMessage(); init3D(); } //=============================================== // PHONEME & SPEECH //=============================================== var Phoneme = {DEV:0.1}; Phoneme.Base = function(Editor,Output,Play){ this.editor = Editor; this.output = Output; this.play = Play; this.xml = null; this.msg = null; this.initSynthesis(); this.init(); this.timer = null; } Phoneme.Base.prototype ={ constructor: Phoneme.Base, init:function(){ var _this = this; if (window.XDomainRequest) this.xml = new XDomainRequest(); else if (window.XMLHttpRequest) this.xml = new XMLHttpRequest(); else this.xml = new ActiveXObject("Microsoft.XMLHTTP"); this.editor.addEventListener( 'keyup', function ( e ) { _this.periodicUpdate(_this); }, false ); this.play.addEventListener( 'click', function ( e ) { _this.say(); }, false ); }, initSynthesis:function(){ this.fallbackSpeechSynthesis = window.speechSynthesisPolyfill; this.fallbackSpeechSynthesisUtterance = window.SpeechSynthesisUtterancePolyfill; this.msg = new this.fallbackSpeechSynthesisUtterance(); }, periodicUpdate:function(t){ setTimeout(t.update, 150, t); }, update:function(t){ var message = t.editor.value; var data = { text: message }; t.xml.open('POST', "http://api.corrasable.com/phonemes", true); t.xml.setRequestHeader("Content-type", "application/json"); t.xml.onreadystatechange = function() { if (this.readyState == 4 && this.status == 200){ var str = JSON.parse(this.responseText); var res = str.map(function(line) { return t.warp(line).join(" ") + "<br>"; }) t.output.innerHTML = res; t.morphTarget(); } } t.xml.send(JSON.stringify(data)); }, warp: function(words) { return words.map( function(word){ return "<span class='word'>" + word + "</span>"; }); }, morphTarget:function(){ var out = this.output.childNodes; if( out.length > 0 ){ phonemesSequency = []; for(var i=0; i< out.length; i++){ if(out[i].nodeName=="#text") convert(0); else if(out[i].nodeName=="SPAN") convert( out[i].textContent ); } if(phonemesSequency.length>0)this.play.style.visibility = 'visible'; else this.play.style.visibility = 'hidden'; } }, testSpeaking:function(t){ if(t.msg.speaking) exp.value = "i'm talk"; }, say:function(){ if(this.editor.value!== ''){ if(this.msg!==null){ this.msg = new this.fallbackSpeechSynthesisUtterance(this.editor.value); //this.msg.text = this.editor.value; this.msg.rate = 1; this.msg.volume = 1 this.msg.lang = 'en-GB'; if(window.speechSynthesis){ var voices = window.speechSynthesis.getVoices(); this.msg.voice = voices.filter(function(voice) { return voice.name == 'Google UK English Female'; })[0]; } this.msg.onend = function(event) { console.log('Finished in ' + event.elapsedTime + ' seconds.'); }; this.msg.onpause = function(event) { console.log('Paused in ' + event.elapsedTime + ' seconds.'); }; this.msg.onresume = function(event) { console.log('Resumed in ' + event.elapsedTime + ' seconds.'); }; this.msg.onstart = function(event) { console.log('Started in ' + event.elapsedTime + ' seconds.'); } this.fallbackSpeechSynthesis.speak(this.msg); //this.fallbackSpeechSynthesis.pause(); // this.fallbackSpeechSynthesis.resume(); // var _this = this; if(isBestQuality) setTimeout( function ( e ) {saySequence()}, 100); else setTimeout( function ( e ) {saySequence()}, 1500); //this.timer = setInterval(function ( e ) { _this.testSpeaking(_this); }, 100 ); } } } } //=============================================== // 3D SIDE: THREE.JS & SEA3D //=============================================== var camera, scene, renderer, center, cloc, cam, mouse; var eyeLeft, eyeRight; var headMeshs = []; var morphsTables = []; var fullLoaded = false; var headBone; var headBoneRef; var eyes; var bestMaterial = []; var lowMaterial = []; var lights = []; function init3D(){ cam = { horizontal:95, vertical:85, distance:15 }; mouse = { ox:0, oy:0, h:0, v:0, mx:0, my:0, dx:0, dy:0, down:false, over:false, moving:true }; renderer = new THREE.WebGLRenderer({antialias:isBestQuality}); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.autoClear = false; renderer.gammaInput = true; renderer.gammaOutput = true; container.appendChild( renderer.domElement ); scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera( 50, window.innerWidth/window.innerHeight, 0.5, 10000 ); var d = 20; var hemiLight = new THREE.HemisphereLight( 0xfffffe, 0x4488ff, 1 ); hemiLight.position.set( 0, 5, 0 ); var light = new THREE.DirectionalLight( 0xfffffe, 1.2 ); light.position.set( d/3, d, d*1.6); light.target = new THREE.Object3D(0,5,0); light.shadowMapWidth = 1024; light.shadowMapHeight = 1024; light.shadowCameraLeft = -d; light.shadowCameraRight = d; light.shadowCameraTop = d; light.shadowCameraBottom = -d; light.shadowCameraFar = d*2; light.shadowCameraNear = d; light.shadowDarkness = 0.35; light.shadowBias = -0.002; light.castShadow = true; var lightPoint = new THREE.PointLight ( 0x4488ff, 0.5 ); lightPoint.position.set( -d/3, -d/2, d*1.3); var lightPoint2 = new THREE.PointLight ( 0xFFCC44, 0.5 ); lightPoint2.position.set( d/2, -d, d*1.3); lights = [hemiLight, lightPoint, lightPoint2, light ]; if(isBestQuality){ renderer.shadowMapEnabled = true; //renderer.shadowMapCullFace = THREE.CullFaceBack; var i = lights.length; while(i--){ scene.add( lights[i] );} output.innerHTML = "engine best"; } else { output.innerHTML = "engine low"; } var back = new THREE.Mesh( new THREE.IcosahedronGeometry(300,1), new THREE.MeshBasicMaterial( { map:gradTexture([[0.75,0.5,0.45, 0.2], ['#000004','#2e3032','#69757e', '#86a4bc']]), side:THREE.BackSide, depthWrite: false } )); back.geometry.applyMatrix(new THREE.Matrix4().makeRotationZ(5*ToRad)); scene.add( back ); window.addEventListener( 'resize', resize, false ); container.addEventListener( 'mousemove', onMouseMove, false ); container.addEventListener( 'mousedown', onMouseDown, false ); container.addEventListener( 'mouseup', onMouseUp, false ); container.addEventListener( 'mouseout', onMouseUp, false ); container.addEventListener( 'touchstart', onMouseDown, false ); container.addEventListener( 'touchend', onMouseUp, false ); container.addEventListener( 'touchmove', onMouseMove, false ); var body = document.body; if( body.addEventListener ){ body.addEventListener( 'mousewheel', onMouseWheel, false ); //chrome body.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox }else if( body.attachEvent ){ body.attachEvent("onmousewheel" , onMouseWheel); // ie } loop(); loadSea3d(); } function loop() { requestAnimationFrame( loop, renderer.domElement ); updateAnimation(); renderer.render( scene, camera ); } function resize() { camera.aspect = window.innerWidth/window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth,window.innerHeight); } function loadSea3d(){ var size = 1; var loader = new THREE.SEA3D( true ); loader.onComplete = function( e ) { var m, mat, oldmat; var i = loader.meshes.length; while(i--){ m = loader.meshes[i]; oldmat = m.material; bestMaterial[i] = oldmat; lowMaterial[i] = new THREE.MeshBasicMaterial({map:oldmat.map,transparent:oldmat.transparent, opacity:oldmat.opacity, skinning:oldmat.skinning, morphTargets:oldmat.morphTargets, side:oldmat.side }); if(!isBestQuality)m.material = lowMaterial[i]; if(m.name == 'eyeL_lo' || m.name == 'cils' || m.name == 'hair' || m.name == 'teethUpper' || m.name == 'teethLower' ) m.material.transparent = true; if(m.name == 'necklace' ) m.material.color.setHex(0x2e3032); if(m.name == 'bodyLow2' ) m.visible = false; headMeshs[i] = m; //console.log(m.name, i) } // add the head other mesh are linked to him m = headMeshs[0]; m.setWeight("neck", 1); m.setWeight("earOut", 0.6); m.scale.set(size,size,-size); m.position.set(0,0,0); scene.add(m); // eye contener fixed to head bone eyes = new THREE.Object3D(); headBone = m.skeleton.bones[1]; eyes.matrix = headBone.skinMatrix; eyes.matrixAutoUpdate = false; m.add( eyes ); headBone.rotation.y = -15*ToRad; // get the rotation referency headBoneRef = headBone.rotation.clone(); // add eyes geometry var eyeGeo = new THREE.SphereGeometry(0.64,30,28); var eyeMap = gradTexture([[0.5,0.15,0.12,0.07, 0.02], ['#ff6060','#ffffff','#5599ff','#4488ff', '#000000']]); var eyeMat; if(isBestQuality) eyeMat= new THREE.MeshPhongMaterial({ map:eyeMap, specular:0xFFFFFF, shininess:220 }); else eyeMat = new THREE.MeshBasicMaterial({ map:eyeMap }); eyeGeo.applyMatrix(new THREE.Matrix4().makeRotationX(-90*ToRad)); eyeLeft = new THREE.Mesh( eyeGeo, eyeMat); eyeRight = new THREE.Mesh( eyeGeo, eyeMat); eyeLeft.scale.set(1,1,-1); eyeRight.scale.set(1,1,-1); eyeLeft.position.set(3.82, -1.162, 2.92); eyeRight.position.set(3.82, 1.162, 2.92); eyes.add( eyeLeft ); eyes.add( eyeRight ); // camera position init center = new THREE.Vector3(0,5,0); moveCamera(); // find all morphs target var mName; for (var j=0; j < m.geometry.morphTargets.length; j++){ mName = m.geometry.morphTargets[j].name; morphsTables[mName] = { teethLower:testMorph( headMeshs[2], mName), sock:testMorph( headMeshs[1], mName), eye:testMorph( headMeshs[6], mName), tongue:testMorph( headMeshs[7], mName), cils:testMorph( headMeshs[5], mName), } } exp.addEventListener( 'click', function ( e ) { expression(); }, false ); fullLoaded = true; } // THREE.SEA3D.BUFFER is not compatible with morph loader.parser = THREE.SEA3D.DEFAULT; loader.load( 'http://jsrun.it/assets/3/E/J/2/3EJ26' ); } function testMorph(m, name){ var result = false; for (var j=0; j < m.geometry.morphTargets.length; j++){ if(m.geometry.morphTargets[j].name == name) result = true; } return result; } function fullMorph( name , value) { if(!morphsTables[name])return; headMeshs[0].setWeight(name, value); if( morphsTables[name].teethLower ) headMeshs[2].setWeight(name, value); if( morphsTables[name].sock ) headMeshs[1].setWeight(name, value); if( morphsTables[name].eye ) headMeshs[6].setWeight(name, value); if( morphsTables[name].tongue ) headMeshs[7].setWeight(name, value); if( morphsTables[name].cils ) headMeshs[5].setWeight(name, value); } //=============================================== // ANIMATION //=============================================== var count = [0,0,0]; var phonemesSequency = []; var startSequence = false; var finalSequence = false; var currentWord = ""; var prevWord = ""; var currentNum = 0; var changeExpression = false; var currentExpression = 0; var newExpression = 0; var morphExpressions = ['expression', 'anger','sad', 'disgust', 'fear', 'surprise', 'smileClose', 'smileOpen']; function updateAnimation(){ if(fullLoaded){ count[0]++; if(count[0]<=10)blinkEyes(count[0]); else if(count[0]<=20)blinkEyes(20-count[0]); else if(count[0] == 200)count[0]=0; if(startSequence){ count[1]++; if(count[1]<=5)sayWord(count[1]); else if(count[1] == 6){ count[1]=0; if(finalSequence)startSequence=false; prevWord = phonemesSequency[currentNum]; currentNum++; if(currentNum < phonemesSequency.length) currentWord = phonemesSequency[currentNum]; else finalSequence = true; } } if(changeExpression){ count[2]++; if(count[2]<=50)switchExpression(count[2]); else if(count[2] ==51){ count[2]=0; changeExpression = false; currentExpression = newExpression; } } } } function blinkEyes(N){ var n = N*0.1; headMeshs[0].setWeight("blinkLeft", n); headMeshs[0].setWeight("blinkRight", n); headMeshs[5].setWeight("blinkLeft", n); headMeshs[5].setWeight("blinkRight", n); } function switchExpression(N){ var n = N*0.02; if(newExpression!==0)fullMorph(morphExpressions[newExpression], n); fullMorph(morphExpressions[currentExpression], 1-n); } function expression(){ changeExpression = true; newExpression++; if(newExpression == morphExpressions.length) newExpression =0; exp.value = morphExpressions[newExpression]; } function saySequence(){ startSequence = true; finalSequence = false; currentNum = 0 currentWord = phonemesSequency[currentNum]; prevWord=""; } function sayWord(N){ var n = N*0.2; if(currentWord!=="")fullMorph(phonemeNumber(currentWord), n); if(prevWord!=="")fullMorph(phonemeNumber(prevWord), 1-n); } //=============================================== // PHONEME TO MORPH //=============================================== function phonemeNumber(Value) { var t; switch ( Value ) { case 0 : t = ''; break; case 1 : t = 'aah'; break; case 2 : t = 'bigaah'; break; case 3 : t = 'ch.j.sh';break; case 4 : t = 'f.v'; break; case 5 : t = 'i'; break; case 6 : t = 'k'; break; case 7 : t = 'ee'; break; case 8 : t = 'b.m.p'; break; case 9 : t = 'n'; break; case 10: t = 'oh'; break; case 11: t = 'r'; break; case 12: t = 'd.s.t'; break; case 13: t = 'th'; break; case 14: t = 'w'; break; case 15: t = 'eh'; break; case 16: t = 'ooh.q'; break; } return t; } function convert(n) { if(n==0)phonemesSequency.push(n); else { var ph = n.split(' '); var num = 1; for (var i=0;i<ph.length;i++){ switch ( ph[i] ) { case 'HH' : num = 3; break; case 'AH0': num = 1; break; case 'AH1': num = 1; break; case 'AY1': num = 5; break; case 'AY2': num = 5; break; case 'L' : num = 15; break; case 'W' : num = 14; break; case 'ER1': num = 11; break; case 'ER0': num = 11; break; case 'D' : num = 12; break; case 'M' : num = 8; break; case 'EY1': num = 7; break; case 'EH1': num = 7; break; case 'IH1': num = 5; break; case 'IH0': num = 5; break; case 'IY1': num = 5; break; case 'Z' : num = 12; break; case 'F' : num = 4; break; case 'K' : num = 6; break; case 'B' : num = 8; break; case 'R' : num = 11; break; case 'Y' : num = 15; break; case 'TH' : num = 13; break; case 'S' : num = 12; break; case 'T' : num = 13; break; case 'G' : num = 6; break; case 'OW1': num = 10; break; case 'OY1': num = 10; break; case 'UW1': num = 14; break; case 'V' : num = 4; break; case 'JH' : num = 3; break; case 'P' : num = 8; break; } phonemesSequency.push(num); } } } //=============================================== // MATH //=============================================== var ToRad = Math.PI / 180; function Orbit(origine, horizontal, vertical, distance) { var p = new THREE.Vector3(); var phi = vertical*ToRad; var theta = horizontal*ToRad; p.x = (distance * Math.sin(phi) * Math.cos(theta)) + origine.x; p.z = (distance * Math.sin(phi) * Math.sin(theta)) + origine.z; p.y = (distance * Math.cos(phi)) + origine.y; return p; } //=============================================== // MOUSE & NAVIGATION //=============================================== function moveCamera() { camera.position.copy(Orbit(center, cam.horizontal, cam.vertical, cam.distance)); camera.lookAt(center); var midL = new THREE.Vector3(camera.position.y, camera.position.x, camera.position.z); var midR = midL.clone(); eyeLeft.lookAt(midL.add(new THREE.Vector3(0,-2.25,0))); eyeRight.lookAt(midR.add(new THREE.Vector3(0,2.25,0))); } function onMouseDown(e) { e.preventDefault(); var px, py; if(e.touches){ px = e.clientX || e.touches[ 0 ].pageX; py = e.clientY || e.touches[ 0 ].pageY; } else { px = e.clientX; py = e.clientY; } mouse.ox = px; mouse.oy = py; mouse.h = cam.horizontal; mouse.v = cam.vertical; mouse.down = true; } function onMouseUp(e) { mouse.down = false; document.body.style.cursor = 'auto'; } function onMouseMove(e) { e.preventDefault(); var px, py; if(e.touches){ px = e.clientX || e.touches[ 0 ].pageX; py = e.clientY || e.touches[ 0 ].pageY; } else { px = e.clientX; py = e.clientY; } if (mouse.down) { document.body.style.cursor = 'move'; cam.horizontal = ((px - mouse.ox) * 0.3) + mouse.h; cam.vertical = (-(py - mouse.oy) * 0.3) + mouse.v; moveCamera(); } else { mouse.dx = -(px - window.innerWidth*0.5)*0.1; mouse.dy = -(py - window.innerHeight*0.5)*0.1; headBone.rotation.set(headBoneRef.x+(((mouse.dx*0.5))*ToRad), headBoneRef.y+(((mouse.dy*0.5))*ToRad), headBoneRef.z); } } function onMouseWheel(e) { e.preventDefault(); var delta = 0; if(e.wheelDelta){delta=e.wheelDelta*-1;} else if(e.detail){delta=e.detail*20;} cam.distance+=(delta/80); if(cam.distance<0.01)cam.distance = 0.01; if(cam.distance>150)cam.distance = 150; moveCamera(); } //=============================================== // AUTO TEXTURE //=============================================== function gradTexture(color) { var c = document.createElement("canvas"); var ct = c.getContext("2d"); c.width = 16; c.height = 256; var gradient = ct.createLinearGradient(0,0,0,256); var i = color[0].length; while(i--){ gradient.addColorStop(color[0][i],color[1][i]); } ct.fillStyle = gradient; ct.fillRect(0,0,16,256); var texture = new THREE.Texture(c); texture.needsUpdate = true; return texture; } <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> <script src="http://lo-th.github.io/root/build/three.min.js"></script> <script src="http://lo-th.github.io/root/build/sea3d.min.js"></script> <script src="http://lo-th.github.io/root/build/polyfill.min.js"></script> <div id="container"></div> <input type="submit" value="PLAY" id="play"> <input type="submit" value="expression" id="exp"> <textarea id="editor" autofocus="true" placeholder="Begin typing"></textarea> <div id="output"></div> * { margin:0; padding:0; border:0; } body { background:#000; font:14px monospace; overflow:hidden; } textarea { font: 14px monospace; } textarea:focus { outline: none; } #container { position:absolute; top:0; left:0; } #play { position:absolute; top:24%; left:6px; width:100px; height:30px; border:1px solid #fff; background:rgba(255,255,255,0.5); visibility:hidden; border-radius:5px; } #exp { position:absolute; top:6px; right:6px; width:100px; height:30px; border:1px solid #fff; background:rgba(255,255,255,0.5); border-radius:5px;; } #editor { position:absolute; top:0; left:0px; width:30%; height:20%; margin:5px; padding:3px; display:inline; background:rgba(255,255,255,0.5);border:1px solid #fff; border-radius:5px; } #output { position:absolute; bottom:0; left:0px; width:90%; color:#ff757e; height:40px; padding:3px; text-align:right; vertical-align:text-bottom; display:inline-block;; } use an iframe compat browser, deer Play on jsdo.it games Author Share ブログに埋め込む QR Tag Download Complete! Description What kind of game? Control Device Smartphone Controllerjsdo.it WebSocket Controller» Mouse Keyboard Touch Device Fullscreen Activated Inactivated jsdo.it games から削除する Submit Author ELHS URLhttp://guam.github.io/ Tweet Default Panel Auto play Screenshot Readme JavaScript HTML CSS Size Width: px Height: px code <script type="text/javascript" src="http://jsdo.it/blogparts/3ddv/js"></script> art&design library&test morph phoneme smartphones&tablets speech Discussion Questions on this code? Tags art&design library&test morph phoneme smartphones&tablets speech