/ vrm-ui / script.js
script.js
  1  // setup
  2  
  3    //expression setup
  4  var expressionyay = 0;
  5  var expressionoof = 0;
  6  var expressionlimityay = 0.5;
  7  var expressionlimitoof = 0.5;
  8  var expressionease = 100;
  9  var expressionintensity = 0.75;
 10  
 11    //interface values
 12  if (localStorage.localvalues) {
 13    var initvalues = true;
 14    var mouththreshold = Number(localStorage.mouththreshold) ;
 15    var mouthboost = Number(localStorage.mouthboost) ;
 16    var bodythreshold = Number(localStorage.bodythreshold) ;
 17    var bodymotion = Number(localStorage.bodymotion) ;
 18    var expression = Number(localStorage.expression) ;
 19  } else {
 20    var mouththreshold = 10;
 21    var mouthboost = 10;
 22    var bodythreshold = 10;
 23    var bodymotion = 10;
 24    var expression = 80;
 25  }
 26  
 27  // setup three-vrm
 28  
 29  // renderer
 30  const renderer = new THREE.WebGLRenderer({ alpha: true , antialias: true ,powerPreference: "low-power" });
 31  renderer.setSize(window.innerWidth, window.innerHeight);
 32  renderer.setPixelRatio(window.devicePixelRatio);
 33  document.body.appendChild(renderer.domElement);
 34  
 35  // camera
 36  const camera = new THREE.PerspectiveCamera( 30.0, window.innerWidth / window.innerHeight, 0.1, 20.0 );
 37  camera.position.set(0.0, 1.45, 0.75);
 38  
 39  // camera controls
 40  const controls = new THREE.OrbitControls(camera, renderer.domElement);
 41  controls.screenSpacePanning = true;
 42  controls.target.set(0.0, 1.45, 0.0);
 43  controls.update();
 44  
 45  // scene
 46  const scene = new THREE.Scene();
 47  
 48  // light
 49  const light = new THREE.DirectionalLight(0xffffff);
 50  light.position.set(1.0, 1.0, 1.0).normalize();
 51  scene.add(light);
 52  
 53  // lookat target
 54  const lookAtTarget = new THREE.Object3D();
 55  camera.add(lookAtTarget);
 56  
 57  // gltf and vrm
 58  let currentVrm = undefined;
 59  const loader = new THREE.GLTFLoader();
 60  
 61  function load( url ) {
 62  
 63  loader.crossOrigin = 'anonymous';
 64  loader.load(
 65  
 66  url,
 67  
 68  ( gltf ) => {
 69  
 70  //THREE.VRMUtils.removeUnnecessaryVertices( gltf.scene ); Vroid VRM can't handle this for some reason
 71  THREE.VRMUtils.removeUnnecessaryJoints( gltf.scene );
 72  
 73  THREE.VRM.from( gltf ).then( ( vrm ) => {
 74  
 75  if ( currentVrm ) {
 76  
 77   scene.remove( currentVrm.scene );
 78   currentVrm.dispose();
 79  
 80  }
 81  
 82  currentVrm = vrm;
 83  scene.add( vrm.scene );
 84  
 85  vrm.humanoid.getBoneNode( THREE.VRMSchema.HumanoidBoneName.Hips ).rotation.y = Math.PI;
 86  vrm.springBoneManager.reset();
 87  
 88    // un-T-pose
 89  
 90      vrm.humanoid.getBoneNode(
 91        THREE.VRMSchema.HumanoidBoneName.RightUpperArm
 92      ).rotation.z = 250;
 93    
 94              vrm.humanoid.getBoneNode(
 95        THREE.VRMSchema.HumanoidBoneName.RightLowerArm
 96      ).rotation.z = -0.2;
 97  
 98      vrm.humanoid.getBoneNode(
 99        THREE.VRMSchema.HumanoidBoneName.LeftUpperArm
100      ).rotation.z = -250;
101   
102      vrm.humanoid.getBoneNode(
103        THREE.VRMSchema.HumanoidBoneName.LeftLowerArm
104      ).rotation.z = 0.2;
105  
106    // randomise init positions
107  
108      function randomsomesuch() {
109        return (Math.random() - 0.5) / 10;
110      }
111  
112      vrm.humanoid.getBoneNode(
113        THREE.VRMSchema.HumanoidBoneName.Head
114      ).rotation.x = randomsomesuch();
115      vrm.humanoid.getBoneNode(
116        THREE.VRMSchema.HumanoidBoneName.Head
117      ).rotation.y = randomsomesuch();
118      vrm.humanoid.getBoneNode(
119        THREE.VRMSchema.HumanoidBoneName.Head
120      ).rotation.z = randomsomesuch();
121  
122      vrm.humanoid.getBoneNode(
123        THREE.VRMSchema.HumanoidBoneName.Neck
124      ).rotation.x = randomsomesuch();
125      vrm.humanoid.getBoneNode(
126        THREE.VRMSchema.HumanoidBoneName.Neck
127      ).rotation.y = randomsomesuch();
128      vrm.humanoid.getBoneNode(
129        THREE.VRMSchema.HumanoidBoneName.Neck
130      ).rotation.z = randomsomesuch();
131  
132      vrm.humanoid.getBoneNode(
133        THREE.VRMSchema.HumanoidBoneName.Spine
134      ).rotation.x = randomsomesuch();
135      vrm.humanoid.getBoneNode(
136        THREE.VRMSchema.HumanoidBoneName.Spine
137      ).rotation.y = randomsomesuch();
138      vrm.humanoid.getBoneNode(
139        THREE.VRMSchema.HumanoidBoneName.Spine
140      ).rotation.z = randomsomesuch();
141  
142      vrm.lookAt.target = lookAtTarget;
143      vrm.springBoneManager.reset();
144  
145      console.log(vrm);
146          } );
147  
148        },
149  
150        ( progress ) => console.log( 'Loading model...', 100.0 * ( progress.loaded / progress.total ), '%' ),
151  
152        ( error ) => console.error( error )
153  
154      );
155  
156    }
157  
158  // beware of CORS errors when using this locally. If you can't https, import the required libraries.
159  load( './assets/VU-VRM-elf.vrm' );
160  
161  // grid / axis helpers
162  //			const gridHelper = new THREE.GridHelper( 10, 10 );
163  //			scene.add( gridHelper );
164  //			const axesHelper = new THREE.AxesHelper( 5 );
165  //			scene.add( axesHelper );
166  
167  // animate
168  
169  const clock = new THREE.Clock();
170  
171  function animate() {
172  requestAnimationFrame(animate);
173  
174  const deltaTime = clock.getDelta();
175  
176  if (currentVrm) {
177    // update vrm
178    currentVrm.update(deltaTime);
179  }
180  
181  renderer.render(scene, camera);
182  }
183  
184  animate();
185  
186  
187  
188  
189  
190  //Analyser
191      analyser = audioContext.createAnalyser();
192      //microphone = audioContext.createMediaElementSource(audio);
193      javascriptNode = audioContext.createScriptProcessor(256, 1, 1);
194  
195      analyser.smoothingTimeConstant = 0.5;
196      analyser.fftSize = 1024;
197  
198      //microphone.connect(analyser);
199      analyser.connect(javascriptNode);
200      javascriptNode.connect(audioContext.destination);
201  
202      javascriptNode.onaudioprocess = function () {
203        var array = new Uint8Array(analyser.frequencyBinCount);
204        analyser.getByteFrequencyData(array);
205        var values = 0;
206  
207        var length = array.length;
208        for (var i = 0; i < length; i++) {
209          values += array[i];
210        }
211  
212        // audio in expressed as one number
213        var average = values / length;
214        var inputvolume = average;
215        
216        // audio in spectrum expressed as array
217        // console.log(array.toString());
218        // useful for mouth shape variance
219        
220        // move the interface slider
221                document.getElementById("inputlevel").value = inputvolume;
222  
223  
224  
225  // mic based / endless animations (do stuff)
226  
227        if (currentVrm != undefined){ //best to be sure
228  
229  // talk
230  
231  if (talktime == true){
232  // todo: more vowelshapes
233           var voweldamp = 53;
234           var vowelmin = 12;
235           if (inputvolume > (mouththreshold * 2)) {
236             currentVrm.blendShapeProxy.setValue(
237               THREE.VRMSchema.BlendShapePresetName.A,
238               (
239                (average - vowelmin) / voweldamp) * (mouthboost/10)
240             );
241           
242  }else{
243  currentVrm.blendShapeProxy.setValue(
244     THREE.VRMSchema.BlendShapePresetName.A, 0
245  );
246  }}
247  
248  
249  // move body
250  
251        // todo: replace with ease-to-target behaviour 
252        var damping = 750/(bodymotion/10);
253        var springback = 1.001;
254  
255        if (average > (1 * bodythreshold)) {
256          currentVrm.humanoid.getBoneNode(
257            THREE.VRMSchema.HumanoidBoneName.Head
258          ).rotation.x += (Math.random() - 0.5) / damping;
259          currentVrm.humanoid.getBoneNode(
260            THREE.VRMSchema.HumanoidBoneName.Head
261          ).rotation.x /= springback;
262          currentVrm.humanoid.getBoneNode(
263            THREE.VRMSchema.HumanoidBoneName.Head
264          ).rotation.y += (Math.random() - 0.5) / damping;
265          currentVrm.humanoid.getBoneNode(
266            THREE.VRMSchema.HumanoidBoneName.Head
267          ).rotation.y /= springback;
268          currentVrm.humanoid.getBoneNode(
269            THREE.VRMSchema.HumanoidBoneName.Head
270          ).rotation.z += (Math.random() - 0.5) / damping;
271          currentVrm.humanoid.getBoneNode(
272            THREE.VRMSchema.HumanoidBoneName.Head
273          ).rotation.z /= springback;
274  
275          currentVrm.humanoid.getBoneNode(
276            THREE.VRMSchema.HumanoidBoneName.Neck
277          ).rotation.x += (Math.random() - 0.5) / damping;
278          currentVrm.humanoid.getBoneNode(
279            THREE.VRMSchema.HumanoidBoneName.Neck
280          ).rotation.x /= springback;
281          currentVrm.humanoid.getBoneNode(
282            THREE.VRMSchema.HumanoidBoneName.Neck
283          ).rotation.y += (Math.random() - 0.5) / damping;
284          currentVrm.humanoid.getBoneNode(
285            THREE.VRMSchema.HumanoidBoneName.Neck
286          ).rotation.y /= springback;
287          currentVrm.humanoid.getBoneNode(
288            THREE.VRMSchema.HumanoidBoneName.Neck
289          ).rotation.z += (Math.random() - 0.5) / damping;
290          currentVrm.humanoid.getBoneNode(
291            THREE.VRMSchema.HumanoidBoneName.Neck
292          ).rotation.z /= springback;
293  
294          currentVrm.humanoid.getBoneNode(
295            THREE.VRMSchema.HumanoidBoneName.UpperChest
296          ).rotation.x += (Math.random() - 0.5) / damping;
297          currentVrm.humanoid.getBoneNode(
298            THREE.VRMSchema.HumanoidBoneName.UpperChest
299          ).rotation.x /= springback;
300          currentVrm.humanoid.getBoneNode(
301            THREE.VRMSchema.HumanoidBoneName.UpperChest
302          ).rotation.y += (Math.random() - 0.5) / damping;
303          currentVrm.humanoid.getBoneNode(
304            THREE.VRMSchema.HumanoidBoneName.UpperChest
305          ).rotation.y /= springback;
306          currentVrm.humanoid.getBoneNode(
307            THREE.VRMSchema.HumanoidBoneName.UpperChest
308          ).rotation.z += (Math.random() - 0.5) / damping;
309          currentVrm.humanoid.getBoneNode(
310            THREE.VRMSchema.HumanoidBoneName.UpperChest
311          ).rotation.z /= springback;
312  
313          currentVrm.humanoid.getBoneNode(
314            THREE.VRMSchema.HumanoidBoneName.RightShoulder
315          ).rotation.x += (Math.random() - 0.5) / damping;
316          currentVrm.humanoid.getBoneNode(
317            THREE.VRMSchema.HumanoidBoneName.RightShoulder
318          ).rotation.x /= springback;
319          currentVrm.humanoid.getBoneNode(
320            THREE.VRMSchema.HumanoidBoneName.RightShoulder
321          ).rotation.y += (Math.random() - 0.5) / damping;
322          currentVrm.humanoid.getBoneNode(
323            THREE.VRMSchema.HumanoidBoneName.RightShoulder
324          ).rotation.y /= springback;
325          currentVrm.humanoid.getBoneNode(
326            THREE.VRMSchema.HumanoidBoneName.RightShoulder
327          ).rotation.z += (Math.random() - 0.5) / damping;
328          currentVrm.humanoid.getBoneNode(
329            THREE.VRMSchema.HumanoidBoneName.RightShoulder
330          ).rotation.z /= springback;
331  
332          currentVrm.humanoid.getBoneNode(
333            THREE.VRMSchema.HumanoidBoneName.LeftShoulder
334          ).rotation.x += (Math.random() - 0.5) / damping;
335          currentVrm.humanoid.getBoneNode(
336            THREE.VRMSchema.HumanoidBoneName.LeftShoulder
337          ).rotation.x /= springback;
338          currentVrm.humanoid.getBoneNode(
339            THREE.VRMSchema.HumanoidBoneName.LeftShoulder
340          ).rotation.y += (Math.random() - 0.5) / damping;
341          currentVrm.humanoid.getBoneNode(
342            THREE.VRMSchema.HumanoidBoneName.LeftShoulder
343          ).rotation.y /= springback;
344          currentVrm.humanoid.getBoneNode(
345            THREE.VRMSchema.HumanoidBoneName.LeftShoulder
346          ).rotation.z += (Math.random() - 0.5) / damping;
347          currentVrm.humanoid.getBoneNode(
348            THREE.VRMSchema.HumanoidBoneName.LeftShoulder
349          ).rotation.z /= springback;
350  
351        }
352  
353  // yay/oof expression drift
354  expressionyay += (Math.random() - 0.5) / expressionease;
355  if(expressionyay > expressionlimityay){expressionyay=expressionlimityay};
356  if(expressionyay < 0){expressionyay=0};
357  currentVrm.blendShapeProxy.setValue(THREE.VRMSchema.BlendShapePresetName.Fun, expressionyay);
358  expressionoof += (Math.random() - 0.5) / expressionease;
359  if(expressionoof > expressionlimitoof){expressionoof=expressionlimitoof};
360  if(expressionoof < 0){expressionoof=0};
361  currentVrm.blendShapeProxy.setValue(THREE.VRMSchema.BlendShapePresetName.Angry, expressionoof);
362      
363      }
364  
365  
366  
367  
368  
369        //look at camera is more efficient on blink
370  lookAtTarget.position.x = camera.position.x;
371  lookAtTarget.position.y = ((camera.position.y-camera.position.y-camera.position.y)/2)+0.5;
372  
373      }; // end fn stream
374  
375  
376  
377  
378  
379  // blink
380  
381  function blink() {
382  var blinktimeout = Math.floor(Math.random() * 250) + 50;
383  lookAtTarget.position.y =
384    camera.position.y - camera.position.y * 2 + 1.25;
385    
386  setTimeout(() => {
387    currentVrm.blendShapeProxy.setValue(
388      THREE.VRMSchema.BlendShapePresetName.BlinkL,
389      0
390    );
391    currentVrm.blendShapeProxy.setValue(
392      THREE.VRMSchema.BlendShapePresetName.BlinkR,
393      0
394    );
395  }, blinktimeout);
396  
397  currentVrm.blendShapeProxy.setValue(
398    THREE.VRMSchema.BlendShapePresetName.BlinkL,
399    1
400  );
401  currentVrm.blendShapeProxy.setValue(
402    THREE.VRMSchema.BlendShapePresetName.BlinkR,
403    1
404  );
405  }
406  
407  
408  
409  // loop blink timing
410  (function loop() {
411  var rand = Math.round(Math.random() * 10000) + 1000;
412  setTimeout(function () {
413    blink();
414    loop();
415  }, rand);
416  })();
417  
418  // drag and drop + file handler
419  window.addEventListener( 'dragover', function( event ) {
420  event.preventDefault();
421  } );
422  
423  window.addEventListener( 'drop', function( event ) {
424  event.preventDefault();
425  
426  // read given file then convert it to blob url
427  const files = event.dataTransfer.files;
428  if ( !files ) { return; }
429  const file = files[0];
430  if ( !file ) { return; }
431  const blob = new Blob( [ file ], { type: "application/octet-stream" } );
432  const url = URL.createObjectURL( blob );
433   load( url );
434  } );
435  
436  
437  // handle window resizes
438  
439  
440  window.addEventListener( 'resize', onWindowResize, false );
441  
442  function onWindowResize(){
443  
444    camera.aspect = window.innerWidth / window.innerHeight;
445    camera.updateProjectionMatrix();
446  
447    renderer.setSize( window.innerWidth, window.innerHeight );
448  
449  }
450  // interface handling
451  
452  var talktime = true;
453  
454  function interface() {
455  
456    if (initvalues == true){
457    if (localStorage.localvalues) {
458      initvalues = false;
459      document.getElementById("mouththreshold").value = mouththreshold;
460      document.getElementById("mouthboost").value = mouthboost;
461      document.getElementById("bodythreshold").value = bodythreshold;
462      document.getElementById("bodymotion").value = bodymotion;
463      document.getElementById("expression").value = expression;
464    }}
465  
466      mouththreshold = document.getElementById("mouththreshold").value;
467      mouthboost = document.getElementById("mouthboost").value;
468      bodythreshold = document.getElementById("bodythreshold").value;
469      bodymotion = document.getElementById("bodymotion").value;
470  
471      expression = document.getElementById("expression").value;
472      expressionlimityay = (expression);
473      expressionlimitoof = (100 - expression); 
474      expressionlimityay = expressionlimityay/100;
475      expressionlimitoof = expressionlimitoof/100;
476      expressionlimityay = expressionlimityay*expressionintensity;
477      expressionlimitoof = expressionlimitoof*expressionintensity;    
478  
479      console.log("Expression " + expressionyay + " yay / " + expressionoof + " oof");
480      console.log("Expression mix " + expressionlimityay + " yay / " + expressionlimitoof + " oof");
481  
482      // store it too
483      localStorage.localvalues = 1;
484      localStorage.mouththreshold = mouththreshold;
485      localStorage.mouthboost = mouthboost;
486      localStorage.bodythreshold = bodythreshold;
487      localStorage.bodymotion = bodymotion;
488      localStorage.expression = expression;
489  
490  }
491  
492  // click to dismiss non-vrm divs
493    function hideinterface() {
494  
495    var a = document.getElementById("backplate");
496    var b = document.getElementById("interface");
497    var x = document.getElementById("infobar");
498    var y = document.getElementById("credits");
499    a.style.display = "none";
500    b.style.display = "none";
501    x.style.display = "none";
502    y.style.display = "none";
503  
504    }
505  
506  // click to dismiss non-interface divs
507  function hideinfo() {
508  
509    var a = document.getElementById("backplate");
510    var x = document.getElementById("infobar");
511    var y = document.getElementById("credits");
512    a.style.display = "none";
513    x.style.display = "none";
514    y.style.display = "none";
515  
516    }
517  
518  // load file from user picker
519    function dofile(){
520    var file = document.querySelector('input[type=file]').files[0];
521    if ( !file ) { return; }
522    const blob = new Blob( [ file ], { type: "application/octet-stream" } );
523    const url = URL.createObjectURL( blob );
524     load( url );
525    }
526  // end
527  
528  // wait to trigger interface and load init values
529  
530  setTimeout(() => {  interface(); }, 500);
531  
532  //ok