fix lin endings of some JS files

This commit is contained in:
Andrew Meadows 2015-10-20 13:26:19 -07:00
parent 502fb9cca5
commit 4ff05e8c2a
12 changed files with 2612 additions and 2612 deletions

View file

@ -1,26 +1,26 @@
/**
* Copyright (c) 2010 Maxim Vasiliev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author Maxim Vasiliev
* Date: 09.09.2010
* Time: 19:02:33
*/
(function(e,t){if(typeof define==="function"&&define.amd){define(t)}else{e.form2js=t()}})(this,function(){"use strict";function e(e,r,i,s,o,u){u=u?true:false;if(typeof i=="undefined"||i==null)i=true;if(typeof r=="undefined"||r==null)r=".";if(arguments.length<5)o=false;e=typeof e=="string"?document.getElementById(e):e;var a=[],f,l=0;if(e.constructor==Array||typeof NodeList!="undefined"&&e.constructor==NodeList){while(f=e[l++]){a=a.concat(n(f,s,o,u))}}else{a=n(e,s,o,u)}return t(a,i,r)}function t(e,t,n){var r={},i={},s,o,u,a,f,l,c,h,p,d,v,m,g;for(s=0;s<e.length;s++){f=e[s].value;if(t&&(f===""||f===null))continue;m=e[s].name;g=m.split(n);l=[];c=r;h="";for(o=0;o<g.length;o++){v=g[o].split("][");if(v.length>1){for(u=0;u<v.length;u++){if(u==0){v[u]=v[u]+"]"}else if(u==v.length-1){v[u]="["+v[u]}else{v[u]="["+v[u]+"]"}d=v[u].match(/([a-z_]+)?\[([a-z_][a-z0-9_]+?)\]/i);if(d){for(a=1;a<d.length;a++){if(d[a])l.push(d[a])}}else{l.push(v[u])}}}else l=l.concat(v)}for(o=0;o<l.length;o++){v=l[o];if(v.indexOf("[]")>-1&&o==l.length-1){p=v.substr(0,v.indexOf("["));h+=p;if(!c[p])c[p]=[];c[p].push(f)}else if(v.indexOf("[")>-1){p=v.substr(0,v.indexOf("["));d=v.replace(/(^([a-z_]+)?\[)|(\]$)/gi,"");h+="_"+p+"_"+d;if(!i[h])i[h]={};if(p!=""&&!c[p])c[p]=[];if(o==l.length-1){if(p==""){c.push(f);i[h][d]=c[c.length-1]}else{c[p].push(f);i[h][d]=c[p][c[p].length-1]}}else{if(!i[h][d]){if(/^[0-9a-z_]+\[?/i.test(l[o+1]))c[p].push({});else c[p].push([]);i[h][d]=c[p][c[p].length-1]}}c=i[h][d]}else{h+=v;if(o<l.length-1){if(!c[v])c[v]={};c=c[v]}else{c[v]=f}}}}return r}function n(e,t,n,s){var o=i(e,t,n,s);return o.length>0?o:r(e,t,n,s)}function r(e,t,n,r){var s=[],o=e.firstChild;while(o){s=s.concat(i(o,t,n,r));o=o.nextSibling}return s}function i(e,t,n,i){if(e.disabled&&!i)return[];var u,a,f,l=s(e,n);u=t&&t(e);if(u&&u.name){f=[u]}else if(l!=""&&e.nodeName.match(/INPUT|TEXTAREA/i)){a=o(e,i);if(null===a){f=[]}else{f=[{name:l,value:a}]}}else if(l!=""&&e.nodeName.match(/SELECT/i)){a=o(e,i);f=[{name:l.replace(/\[\]$/,""),value:a}]}else{f=r(e,t,n,i)}return f}function s(e,t){if(e.name&&e.name!="")return e.name;else if(t&&e.id&&e.id!="")return e.id;else return""}function o(e,t){if(e.disabled&&!t)return null;switch(e.nodeName){case"INPUT":case"TEXTAREA":switch(e.type.toLowerCase()){case"radio":if(e.checked&&e.value==="false")return false;case"checkbox":if(e.checked&&e.value==="true")return true;if(!e.checked&&e.value==="true")return false;if(e.checked)return e.value;break;case"button":case"reset":case"submit":case"image":return"";break;default:return e.value;break}break;case"SELECT":return u(e);break;default:break}return null}function u(e){var t=e.multiple,n=[],r,i,s;if(!t)return e.value;for(r=e.getElementsByTagName("option"),i=0,s=r.length;i<s;i++){if(r[i].selected)n.push(r[i].value)}return n}return e})
/**
* Copyright (c) 2010 Maxim Vasiliev
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @author Maxim Vasiliev
* Date: 09.09.2010
* Time: 19:02:33
*/
(function(e,t){if(typeof define==="function"&&define.amd){define(t)}else{e.form2js=t()}})(this,function(){"use strict";function e(e,r,i,s,o,u){u=u?true:false;if(typeof i=="undefined"||i==null)i=true;if(typeof r=="undefined"||r==null)r=".";if(arguments.length<5)o=false;e=typeof e=="string"?document.getElementById(e):e;var a=[],f,l=0;if(e.constructor==Array||typeof NodeList!="undefined"&&e.constructor==NodeList){while(f=e[l++]){a=a.concat(n(f,s,o,u))}}else{a=n(e,s,o,u)}return t(a,i,r)}function t(e,t,n){var r={},i={},s,o,u,a,f,l,c,h,p,d,v,m,g;for(s=0;s<e.length;s++){f=e[s].value;if(t&&(f===""||f===null))continue;m=e[s].name;g=m.split(n);l=[];c=r;h="";for(o=0;o<g.length;o++){v=g[o].split("][");if(v.length>1){for(u=0;u<v.length;u++){if(u==0){v[u]=v[u]+"]"}else if(u==v.length-1){v[u]="["+v[u]}else{v[u]="["+v[u]+"]"}d=v[u].match(/([a-z_]+)?\[([a-z_][a-z0-9_]+?)\]/i);if(d){for(a=1;a<d.length;a++){if(d[a])l.push(d[a])}}else{l.push(v[u])}}}else l=l.concat(v)}for(o=0;o<l.length;o++){v=l[o];if(v.indexOf("[]")>-1&&o==l.length-1){p=v.substr(0,v.indexOf("["));h+=p;if(!c[p])c[p]=[];c[p].push(f)}else if(v.indexOf("[")>-1){p=v.substr(0,v.indexOf("["));d=v.replace(/(^([a-z_]+)?\[)|(\]$)/gi,"");h+="_"+p+"_"+d;if(!i[h])i[h]={};if(p!=""&&!c[p])c[p]=[];if(o==l.length-1){if(p==""){c.push(f);i[h][d]=c[c.length-1]}else{c[p].push(f);i[h][d]=c[p][c[p].length-1]}}else{if(!i[h][d]){if(/^[0-9a-z_]+\[?/i.test(l[o+1]))c[p].push({});else c[p].push([]);i[h][d]=c[p][c[p].length-1]}}c=i[h][d]}else{h+=v;if(o<l.length-1){if(!c[v])c[v]={};c=c[v]}else{c[v]=f}}}}return r}function n(e,t,n,s){var o=i(e,t,n,s);return o.length>0?o:r(e,t,n,s)}function r(e,t,n,r){var s=[],o=e.firstChild;while(o){s=s.concat(i(o,t,n,r));o=o.nextSibling}return s}function i(e,t,n,i){if(e.disabled&&!i)return[];var u,a,f,l=s(e,n);u=t&&t(e);if(u&&u.name){f=[u]}else if(l!=""&&e.nodeName.match(/INPUT|TEXTAREA/i)){a=o(e,i);if(null===a){f=[]}else{f=[{name:l,value:a}]}}else if(l!=""&&e.nodeName.match(/SELECT/i)){a=o(e,i);f=[{name:l.replace(/\[\]$/,""),value:a}]}else{f=r(e,t,n,i)}return f}function s(e,t){if(e.name&&e.name!="")return e.name;else if(t&&e.id&&e.id!="")return e.id;else return""}function o(e,t){if(e.disabled&&!t)return null;switch(e.nodeName){case"INPUT":case"TEXTAREA":switch(e.type.toLowerCase()){case"radio":if(e.checked&&e.value==="false")return false;case"checkbox":if(e.checked&&e.value==="true")return true;if(!e.checked&&e.value==="true")return false;if(e.checked)return e.value;break;case"button":case"reset":case"submit":case"image":return"";break;default:return e.value;break}break;case"SELECT":return u(e);break;default:break}return null}function u(e){var t=e.multiple,n=[],r,i,s;if(!t)return e.value;for(r=e.getElementsByTagName("option"),i=0,s=r.length;i<s;i++){if(r[i].selected)n.push(r[i].value)}return n}return e})

View file

@ -244,4 +244,4 @@ B(this,"mouseOut");!c||a.stickyTracking||c.shared&&!this.noSharedTooltip||c.hide
f=(c.visible=a=c.userOptions.visible=a===u?!h:a)?"show":"hide";t(["group","dataLabelsGroup","markerGroup","tracker"],function(a){if(c[a])c[a][f]()});if(d.hoverSeries===c)c.onMouseOut();e&&d.legend.colorizeItem(c,a);c.isDirty=!0;c.options.stacking&&t(d.series,function(a){a.options.stacking&&a.visible&&(a.isDirty=!0)});t(c.linkedSeries,function(b){b.setVisible(a,!1)});g&&(d.isDirtyBox=!0);!1!==b&&d.redraw();B(c,f)},setTooltipPoints:function(a){var b=[],c,d,e=this.xAxis,f=e&&e.getExtremes(),g=e?e.tooltipLen||
e.len:this.chart.plotSizeX,h,k,l=[];if(!1!==this.options.enableMouseTracking&&!this.singularTooltips){a&&(this.tooltipPoints=null);t(this.segments||this.points,function(a){b=b.concat(a)});e&&e.reversed&&(b=b.reverse());this.orderTooltipPoints&&this.orderTooltipPoints(b);a=b.length;for(k=0;k<a;k++)if(e=b[k],c=e.x,c>=f.min&&c<=f.max)for(h=b[k+1],c=d===u?0:d+1,d=b[k+1]?T(x(0,K((e.clientX+(h?h.wrappedClientX||h.clientX:g))/2)),g):g;0<=c&&c<=d;)l[c++]=e;this.tooltipPoints=l}},show:function(){this.setVisible(!0)},
hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===u?!this.selected:a;this.checkbox&&(this.checkbox.checked=a);B(this,a?"select":"unselect")},drawTracker:$a.drawTrackerGraph});v(U,{Axis:ma,Chart:Ea,Color:za,Point:Ca,Tick:xa,Renderer:Ja,Series:fa,SVGElement:O,SVGRenderer:Ja,arrayMin:La,arrayMax:va,charts:Y,dateFormat:Ka,format:ua,pathAnim:ib,getOptions:function(){return M},hasBidiBug:Db,isTouchDevice:wb,numberFormat:ka,seriesTypes:P,setOptions:function(a){M=G(!0,M,a);pb();
return M},addEvent:F,removeEvent:L,createElement:ja,discardElement:Na,css:J,each:t,extend:v,map:kb,merge:G,pick:n,splat:ea,extendClass:nb,pInt:E,wrap:bb,svg:Z,canvas:ca,vml:!Z&&!ca,product:"Highcharts 4.0.4",version:"/Highstock 2.0.4"})})();
return M},addEvent:F,removeEvent:L,createElement:ja,discardElement:Na,css:J,each:t,extend:v,map:kb,merge:G,pick:n,splat:ea,extendClass:nb,pInt:E,wrap:bb,svg:Z,canvas:ca,vml:!Z&&!ca,product:"Highcharts 4.0.4",version:"/Highstock 2.0.4"})})();

View file

@ -1,114 +1,114 @@
//
// SunLightExample.js
// examples
// Sam Gateau
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("../../utilities/tools/cookies.js");
var panel = new Panel(10, 400);
panel.newSlider("Origin Longitude", -180, 180,
function(value) { Scene.setStageLocation(value, Scene.getStageLocationLatitude(), Scene.getStageLocationAltitude()); },
function() { return Scene.getStageLocationLongitude(); },
function(value) { return value.toFixed(0) + " deg"; }
);
panel.newSlider("Origin Latitude", -90, 90,
function(value) { Scene.setStageLocation(Scene.getStageLocationLongitude(), value, Scene.getStageLocationAltitude()); },
function() { return Scene.getStageLocationLatitude(); },
function(value) { return value.toFixed(0) + " deg"; }
);
panel.newSlider("Origin Altitude", 0, 1000,
function(value) { Scene.setStageLocation(Scene.getStageLocationLongitude(), Scene.getStageLocationLatitude(), value); },
function() { return Scene.getStageLocationAltitude(); },
function(value) { return (value).toFixed(0) + " km"; }
);
panel.newSlider("Year Time", 0, 364,
function(value) { Scene.setStageYearTime(value); },
function() { return Scene.getStageYearTime(); },
function(value) {
var numDaysPerMonth = 365.0 / 12.0;
var monthly = (value / numDaysPerMonth);
var month = Math.floor(monthly);
return (month + 1).toFixed(0) + "/" + Math.ceil(0.5 + (monthly - month)*Math.ceil(numDaysPerMonth)).toFixed(0); }
);
panel.newSlider("Day Time", 0, 24,
function(value) { Scene.setStageDayTime(value); panel.update("Light Direction"); },
function() { return Scene.getStageDayTime(); },
function(value) {
var hour = Math.floor(value);
return (hour).toFixed(0) + ":" + ((value - hour)*60.0).toFixed(0);
}
);
var tickTackPeriod = 50;
var tickTackSpeed = 0.0;
panel.newSlider("Tick tack time", -1.0, 1.0,
function(value) { tickTackSpeed = value; },
function() { return tickTackSpeed; },
function(value) { return (value).toFixed(2); }
);
function runStageTime() {
if (tickTackSpeed != 0.0) {
var hour = panel.get("Day Time");
hour += tickTackSpeed;
panel.set("Day Time", hour);
if (hour >= 24.0) {
panel.set("Year Time", panel.get("Year Time") + 1);
} else if (hour < 0.0) {
panel.set("Year Time", panel.get("Year Time") - 1);
}
}
}
Script.setInterval(runStageTime, tickTackPeriod);
panel.newCheckbox("Enable Sun Model",
function(value) { Scene.setStageSunModelEnable((value != 0)); },
function() { return Scene.isStageSunModelEnabled(); },
function(value) { return (value); }
);
panel.newDirectionBox("Light Direction",
function(value) { Scene.setKeyLightDirection(value); },
function() { return Scene.getKeyLightDirection(); },
function(value) { return value.x.toFixed(2) + "," + value.y.toFixed(2) + "," + value.z.toFixed(2); }
);
panel.newSlider("Light Intensity", 0.0, 5,
function(value) { Scene.setKeyLightIntensity(value); },
function() { return Scene.getKeyLightIntensity(); },
function(value) { return (value).toFixed(2); }
);
panel.newSlider("Ambient Light Intensity", 0.0, 1.0,
function(value) { Scene.setKeyLightAmbientIntensity(value); },
function() { return Scene.getKeyLightAmbientIntensity(); },
function(value) { return (value).toFixed(2); }
);
panel.newColorBox("Light Color",
function(value) { Scene.setKeyLightColor(value); },
function() { return Scene.getKeyLightColor(); },
function(value) { return (value); } // "(" + value.x + "," = value.y + "," + value.z + ")"; }
);
Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); });
Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); });
Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); });
function scriptEnding() {
Menu.removeMenu("Developer > Scene");
panel.destroy();
}
Script.scriptEnding.connect(scriptEnding);
//
// SunLightExample.js
// examples
// Sam Gateau
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("../../utilities/tools/cookies.js");
var panel = new Panel(10, 400);
panel.newSlider("Origin Longitude", -180, 180,
function(value) { Scene.setStageLocation(value, Scene.getStageLocationLatitude(), Scene.getStageLocationAltitude()); },
function() { return Scene.getStageLocationLongitude(); },
function(value) { return value.toFixed(0) + " deg"; }
);
panel.newSlider("Origin Latitude", -90, 90,
function(value) { Scene.setStageLocation(Scene.getStageLocationLongitude(), value, Scene.getStageLocationAltitude()); },
function() { return Scene.getStageLocationLatitude(); },
function(value) { return value.toFixed(0) + " deg"; }
);
panel.newSlider("Origin Altitude", 0, 1000,
function(value) { Scene.setStageLocation(Scene.getStageLocationLongitude(), Scene.getStageLocationLatitude(), value); },
function() { return Scene.getStageLocationAltitude(); },
function(value) { return (value).toFixed(0) + " km"; }
);
panel.newSlider("Year Time", 0, 364,
function(value) { Scene.setStageYearTime(value); },
function() { return Scene.getStageYearTime(); },
function(value) {
var numDaysPerMonth = 365.0 / 12.0;
var monthly = (value / numDaysPerMonth);
var month = Math.floor(monthly);
return (month + 1).toFixed(0) + "/" + Math.ceil(0.5 + (monthly - month)*Math.ceil(numDaysPerMonth)).toFixed(0); }
);
panel.newSlider("Day Time", 0, 24,
function(value) { Scene.setStageDayTime(value); panel.update("Light Direction"); },
function() { return Scene.getStageDayTime(); },
function(value) {
var hour = Math.floor(value);
return (hour).toFixed(0) + ":" + ((value - hour)*60.0).toFixed(0);
}
);
var tickTackPeriod = 50;
var tickTackSpeed = 0.0;
panel.newSlider("Tick tack time", -1.0, 1.0,
function(value) { tickTackSpeed = value; },
function() { return tickTackSpeed; },
function(value) { return (value).toFixed(2); }
);
function runStageTime() {
if (tickTackSpeed != 0.0) {
var hour = panel.get("Day Time");
hour += tickTackSpeed;
panel.set("Day Time", hour);
if (hour >= 24.0) {
panel.set("Year Time", panel.get("Year Time") + 1);
} else if (hour < 0.0) {
panel.set("Year Time", panel.get("Year Time") - 1);
}
}
}
Script.setInterval(runStageTime, tickTackPeriod);
panel.newCheckbox("Enable Sun Model",
function(value) { Scene.setStageSunModelEnable((value != 0)); },
function() { return Scene.isStageSunModelEnabled(); },
function(value) { return (value); }
);
panel.newDirectionBox("Light Direction",
function(value) { Scene.setKeyLightDirection(value); },
function() { return Scene.getKeyLightDirection(); },
function(value) { return value.x.toFixed(2) + "," + value.y.toFixed(2) + "," + value.z.toFixed(2); }
);
panel.newSlider("Light Intensity", 0.0, 5,
function(value) { Scene.setKeyLightIntensity(value); },
function() { return Scene.getKeyLightIntensity(); },
function(value) { return (value).toFixed(2); }
);
panel.newSlider("Ambient Light Intensity", 0.0, 1.0,
function(value) { Scene.setKeyLightAmbientIntensity(value); },
function() { return Scene.getKeyLightAmbientIntensity(); },
function(value) { return (value).toFixed(2); }
);
panel.newColorBox("Light Color",
function(value) { Scene.setKeyLightColor(value); },
function() { return Scene.getKeyLightColor(); },
function(value) { return (value); } // "(" + value.x + "," = value.y + "," + value.z + ")"; }
);
Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); });
Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); });
Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); });
function scriptEnding() {
Menu.removeMenu("Developer > Scene");
panel.destroy();
}
Script.scriptEnding.connect(scriptEnding);

View file

@ -1,331 +1,331 @@
//
// Leaves.js
// examples
//
// Created by Bing Shearer on 14 Jul 2015
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var leafName = "scriptLeaf";
var leafSquall = function (properties) {
var // Properties
squallOrigin,
squallRadius,
leavesPerMinute = 60,
leafSize = {
x: 0.1,
y: 0.1,
z: 0.1
},
leafFallSpeed = 1, // m/s
leafLifetime = 60, // Seconds
leafSpinMax = 0, // Maximum angular velocity per axis; deg/s
debug = false, // Display origin circle; don't use running on Stack Manager
// Other
squallCircle,
SQUALL_CIRCLE_COLOR = {
red: 255,
green: 0,
blue: 0
},
SQUALL_CIRCLE_ALPHA = 0.5,
SQUALL_CIRCLE_ROTATION = Quat.fromPitchYawRollDegrees(90, 0, 0),
leafProperties,
leaf_MODEL_URL = "https://hifi-public.s3.amazonaws.com/ozan/support/forBing/palmLeaf.fbx",
leafTimer,
leaves = [], // HACK: Work around leaves not always getting velocities
leafVelocities = [], // HACK: Work around leaves not always getting velocities
DEGREES_TO_RADIANS = Math.PI / 180,
leafDeleteOnTearDown = true,
maxLeaves,
leafCount,
nearbyEntities,
complexMovement = false,
movementTime = 0,
maxSpinRadians = properties.leafSpinMax * DEGREES_TO_RADIANS,
windFactor,
leafDeleteOnGround = false,
floorHeight = null;
function processProperties() {
if (!properties.hasOwnProperty("origin")) {
print("ERROR: Leaf squall origin must be specified");
return;
}
squallOrigin = properties.origin;
if (!properties.hasOwnProperty("radius")) {
print("ERROR: Leaf squall radius must be specified");
return;
}
squallRadius = properties.radius;
if (properties.hasOwnProperty("leavesPerMinute")) {
leavesPerMinute = properties.leavesPerMinute;
}
if (properties.hasOwnProperty("leafSize")) {
leafSize = properties.leafSize;
}
if (properties.hasOwnProperty("leafFallSpeed")) {
leafFallSpeed = properties.leafFallSpeed;
}
if (properties.hasOwnProperty("leafLifetime")) {
leafLifetime = properties.leafLifetime;
}
if (properties.hasOwnProperty("leafSpinMax")) {
leafSpinMax = properties.leafSpinMax;
}
if (properties.hasOwnProperty("debug")) {
debug = properties.debug;
}
if (properties.hasOwnProperty("floorHeight")) {
floorHeight = properties.floorHeight;
}
if (properties.hasOwnProperty("maxLeaves")) {
maxLeaves = properties.maxLeaves;
}
if (properties.hasOwnProperty("complexMovement")) {
complexMovement = properties.complexMovement;
}
if (properties.hasOwnProperty("leafDeleteOnGround")) {
leafDeleteOnGround = properties.leafDeleteOnGround;
}
if (properties.hasOwnProperty("windFactor")) {
windFactor = properties.windFactor;
} else if (complexMovement == true){
print("ERROR: Wind Factor must be defined for complex movement")
}
leafProperties = {
type: "Model",
name: leafName,
modelURL: leaf_MODEL_URL,
lifetime: leafLifetime,
dimensions: leafSize,
velocity: {
x: 0,
y: -leafFallSpeed,
z: 0
},
damping: 0,
angularDamping: 0,
ignoreForCollisions: true
};
}
function createleaf() {
var angle,
radius,
offset,
leaf,
spin = {
x: 0,
y: 0,
z: 0
},
i;
// HACK: Work around leaves not always getting velocities set at creation
for (i = 0; i < leaves.length; i++) {
Entities.editEntity(leaves[i], leafVelocities[i]);
}
angle = Math.random() * leafSpinMax;
radius = Math.random() * squallRadius;
offset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, angle, 0), {
x: 0,
y: -0.1,
z: radius
});
leafProperties.position = Vec3.sum(squallOrigin, offset);
if (properties.leafSpinMax > 0 && !complexMovement) {
spin = {
x: Math.random() * maxSpinRadians,
y: Math.random() * maxSpinRadians,
z: Math.random() * maxSpinRadians
};
leafProperties.angularVelocity = spin;
} else if (complexMovement) {
spin = {
x: 0,
y: 0,
z: 0
};
leafProperties.angularVelocity = spin
}
leaf = Entities.addEntity(leafProperties);
// HACK: Work around leaves not always getting velocities set at creation
leaves.push(leaf);
leafVelocities.push({
velocity: leafProperties.velocity,
angularVelocity: spin
});
if (leaves.length > 5) {
leaves.shift();
leafVelocities.shift();
}
}
function setUp() {
if (debug) {
squallCircle = Overlays.addOverlay("circle3d", {
size: {
x: 2 * squallRadius,
y: 2 * squallRadius
},
color: SQUALL_CIRCLE_COLOR,
alpha: SQUALL_CIRCLE_ALPHA,
solid: true,
visible: debug,
position: squallOrigin,
rotation: SQUALL_CIRCLE_ROTATION
});
}
leafTimer = Script.setInterval(function () {
if (leafCount <= maxLeaves - 1) {
createleaf()
}
}, 60000 / leavesPerMinute);
}
Script.setInterval(function () {
nearbyEntities = Entities.findEntities(squallOrigin, squallRadius);
newLeafMovement()
}, 100);
function newLeafMovement() { //new additions to leaf code. Operates at 10 Hz or every 100 ms
movementTime += 0.1;
var currentLeaf,
randomRotationSpeed = {
x: maxSpinRadians * Math.sin(movementTime),
y: maxSpinRadians * Math.random(),
z: maxSpinRadians * Math.sin(movementTime / 7)
};
for (var i = 0; i < nearbyEntities.length; i++) {
var entityProperties = Entities.getEntityProperties(nearbyEntities[i]);
var entityName = entityProperties.name;
if (leafName === entityName) {
currentLeaf = nearbyEntities[i];
var leafHeight = entityProperties.position.y;
if (complexMovement && leafHeight > floorHeight || complexMovement && floorHeight == null) { //actual new movement code;
var leafCurrentVel = entityProperties.velocity,
leafCurrentRot = entityProperties.rotation,
yVec = {
x: 0,
y: 1,
z: 0
},
leafYinWFVec = Vec3.multiplyQbyV(leafCurrentRot, yVec),
leafLocalHorVec = Vec3.cross(leafYinWFVec, yVec),
leafMostDownVec = Vec3.cross(leafYinWFVec, leafLocalHorVec),
leafDesiredVel = Vec3.multiply(leafMostDownVec, windFactor),
leafVelDelt = Vec3.subtract(leafDesiredVel, leafCurrentVel),
leafNewVel = Vec3.sum(leafCurrentVel, Vec3.multiply(leafVelDelt, windFactor));
Entities.editEntity(currentLeaf, {
angularVelocity: randomRotationSpeed,
velocity: leafNewVel
})
} else if (leafHeight <= floorHeight) {
if (!leafDeleteOnGround) {
Entities.editEntity(nearbyEntities[i], {
locked: false,
velocity: {
x: 0,
y: 0,
z: 0
},
angularVelocity: {
x: 0,
y: 0,
z: 0
}
})
} else {
Entity.deleteEntity(currentLeaf);
}
}
}
}
}
getLeafCount = Script.setInterval(function () {
leafCount = 0
for (var i = 0; i < nearbyEntities.length; i++) {
var entityName = Entities.getEntityProperties(nearbyEntities[i]).name;
//Stop Leaves at floorHeight
if (leafName === entityName) {
leafCount++;
if (i == nearbyEntities.length - 1) {
//print(leafCount);
}
}
}
}, 1000)
function tearDown() {
Script.clearInterval(leafTimer);
Overlays.deleteOverlay(squallCircle);
if (leafDeleteOnTearDown) {
for (var i = 0; i < nearbyEntities.length; i++) {
var entityName = Entities.getEntityProperties(nearbyEntities[i]).name;
if (leafName === entityName) {
//We have a match - delete this entity
Entities.editEntity(nearbyEntities[i], {
locked: false
});
Entities.deleteEntity(nearbyEntities[i]);
}
}
}
}
processProperties();
setUp();
Script.scriptEnding.connect(tearDown);
return {};
};
var leafSquall1 = new leafSquall({
origin: {
x: 3071.5,
y: 2170,
z: 6765.3
},
radius: 100,
leavesPerMinute: 30,
leafSize: {
x: 0.3,
y: 0.00,
z: 0.3
},
leafFallSpeed: 0.4,
leafLifetime: 100,
leafSpinMax: 30,
debug: false,
maxLeaves: 100,
leafDeleteOnTearDown: true,
complexMovement: true,
floorHeight: 2143.5,
windFactor: 0.5,
leafDeleteOnGround: false
});
// todo
//deal with depth issue
//
// Leaves.js
// examples
//
// Created by Bing Shearer on 14 Jul 2015
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var leafName = "scriptLeaf";
var leafSquall = function (properties) {
var // Properties
squallOrigin,
squallRadius,
leavesPerMinute = 60,
leafSize = {
x: 0.1,
y: 0.1,
z: 0.1
},
leafFallSpeed = 1, // m/s
leafLifetime = 60, // Seconds
leafSpinMax = 0, // Maximum angular velocity per axis; deg/s
debug = false, // Display origin circle; don't use running on Stack Manager
// Other
squallCircle,
SQUALL_CIRCLE_COLOR = {
red: 255,
green: 0,
blue: 0
},
SQUALL_CIRCLE_ALPHA = 0.5,
SQUALL_CIRCLE_ROTATION = Quat.fromPitchYawRollDegrees(90, 0, 0),
leafProperties,
leaf_MODEL_URL = "https://hifi-public.s3.amazonaws.com/ozan/support/forBing/palmLeaf.fbx",
leafTimer,
leaves = [], // HACK: Work around leaves not always getting velocities
leafVelocities = [], // HACK: Work around leaves not always getting velocities
DEGREES_TO_RADIANS = Math.PI / 180,
leafDeleteOnTearDown = true,
maxLeaves,
leafCount,
nearbyEntities,
complexMovement = false,
movementTime = 0,
maxSpinRadians = properties.leafSpinMax * DEGREES_TO_RADIANS,
windFactor,
leafDeleteOnGround = false,
floorHeight = null;
function processProperties() {
if (!properties.hasOwnProperty("origin")) {
print("ERROR: Leaf squall origin must be specified");
return;
}
squallOrigin = properties.origin;
if (!properties.hasOwnProperty("radius")) {
print("ERROR: Leaf squall radius must be specified");
return;
}
squallRadius = properties.radius;
if (properties.hasOwnProperty("leavesPerMinute")) {
leavesPerMinute = properties.leavesPerMinute;
}
if (properties.hasOwnProperty("leafSize")) {
leafSize = properties.leafSize;
}
if (properties.hasOwnProperty("leafFallSpeed")) {
leafFallSpeed = properties.leafFallSpeed;
}
if (properties.hasOwnProperty("leafLifetime")) {
leafLifetime = properties.leafLifetime;
}
if (properties.hasOwnProperty("leafSpinMax")) {
leafSpinMax = properties.leafSpinMax;
}
if (properties.hasOwnProperty("debug")) {
debug = properties.debug;
}
if (properties.hasOwnProperty("floorHeight")) {
floorHeight = properties.floorHeight;
}
if (properties.hasOwnProperty("maxLeaves")) {
maxLeaves = properties.maxLeaves;
}
if (properties.hasOwnProperty("complexMovement")) {
complexMovement = properties.complexMovement;
}
if (properties.hasOwnProperty("leafDeleteOnGround")) {
leafDeleteOnGround = properties.leafDeleteOnGround;
}
if (properties.hasOwnProperty("windFactor")) {
windFactor = properties.windFactor;
} else if (complexMovement == true){
print("ERROR: Wind Factor must be defined for complex movement")
}
leafProperties = {
type: "Model",
name: leafName,
modelURL: leaf_MODEL_URL,
lifetime: leafLifetime,
dimensions: leafSize,
velocity: {
x: 0,
y: -leafFallSpeed,
z: 0
},
damping: 0,
angularDamping: 0,
ignoreForCollisions: true
};
}
function createleaf() {
var angle,
radius,
offset,
leaf,
spin = {
x: 0,
y: 0,
z: 0
},
i;
// HACK: Work around leaves not always getting velocities set at creation
for (i = 0; i < leaves.length; i++) {
Entities.editEntity(leaves[i], leafVelocities[i]);
}
angle = Math.random() * leafSpinMax;
radius = Math.random() * squallRadius;
offset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, angle, 0), {
x: 0,
y: -0.1,
z: radius
});
leafProperties.position = Vec3.sum(squallOrigin, offset);
if (properties.leafSpinMax > 0 && !complexMovement) {
spin = {
x: Math.random() * maxSpinRadians,
y: Math.random() * maxSpinRadians,
z: Math.random() * maxSpinRadians
};
leafProperties.angularVelocity = spin;
} else if (complexMovement) {
spin = {
x: 0,
y: 0,
z: 0
};
leafProperties.angularVelocity = spin
}
leaf = Entities.addEntity(leafProperties);
// HACK: Work around leaves not always getting velocities set at creation
leaves.push(leaf);
leafVelocities.push({
velocity: leafProperties.velocity,
angularVelocity: spin
});
if (leaves.length > 5) {
leaves.shift();
leafVelocities.shift();
}
}
function setUp() {
if (debug) {
squallCircle = Overlays.addOverlay("circle3d", {
size: {
x: 2 * squallRadius,
y: 2 * squallRadius
},
color: SQUALL_CIRCLE_COLOR,
alpha: SQUALL_CIRCLE_ALPHA,
solid: true,
visible: debug,
position: squallOrigin,
rotation: SQUALL_CIRCLE_ROTATION
});
}
leafTimer = Script.setInterval(function () {
if (leafCount <= maxLeaves - 1) {
createleaf()
}
}, 60000 / leavesPerMinute);
}
Script.setInterval(function () {
nearbyEntities = Entities.findEntities(squallOrigin, squallRadius);
newLeafMovement()
}, 100);
function newLeafMovement() { //new additions to leaf code. Operates at 10 Hz or every 100 ms
movementTime += 0.1;
var currentLeaf,
randomRotationSpeed = {
x: maxSpinRadians * Math.sin(movementTime),
y: maxSpinRadians * Math.random(),
z: maxSpinRadians * Math.sin(movementTime / 7)
};
for (var i = 0; i < nearbyEntities.length; i++) {
var entityProperties = Entities.getEntityProperties(nearbyEntities[i]);
var entityName = entityProperties.name;
if (leafName === entityName) {
currentLeaf = nearbyEntities[i];
var leafHeight = entityProperties.position.y;
if (complexMovement && leafHeight > floorHeight || complexMovement && floorHeight == null) { //actual new movement code;
var leafCurrentVel = entityProperties.velocity,
leafCurrentRot = entityProperties.rotation,
yVec = {
x: 0,
y: 1,
z: 0
},
leafYinWFVec = Vec3.multiplyQbyV(leafCurrentRot, yVec),
leafLocalHorVec = Vec3.cross(leafYinWFVec, yVec),
leafMostDownVec = Vec3.cross(leafYinWFVec, leafLocalHorVec),
leafDesiredVel = Vec3.multiply(leafMostDownVec, windFactor),
leafVelDelt = Vec3.subtract(leafDesiredVel, leafCurrentVel),
leafNewVel = Vec3.sum(leafCurrentVel, Vec3.multiply(leafVelDelt, windFactor));
Entities.editEntity(currentLeaf, {
angularVelocity: randomRotationSpeed,
velocity: leafNewVel
})
} else if (leafHeight <= floorHeight) {
if (!leafDeleteOnGround) {
Entities.editEntity(nearbyEntities[i], {
locked: false,
velocity: {
x: 0,
y: 0,
z: 0
},
angularVelocity: {
x: 0,
y: 0,
z: 0
}
})
} else {
Entity.deleteEntity(currentLeaf);
}
}
}
}
}
getLeafCount = Script.setInterval(function () {
leafCount = 0
for (var i = 0; i < nearbyEntities.length; i++) {
var entityName = Entities.getEntityProperties(nearbyEntities[i]).name;
//Stop Leaves at floorHeight
if (leafName === entityName) {
leafCount++;
if (i == nearbyEntities.length - 1) {
//print(leafCount);
}
}
}
}, 1000)
function tearDown() {
Script.clearInterval(leafTimer);
Overlays.deleteOverlay(squallCircle);
if (leafDeleteOnTearDown) {
for (var i = 0; i < nearbyEntities.length; i++) {
var entityName = Entities.getEntityProperties(nearbyEntities[i]).name;
if (leafName === entityName) {
//We have a match - delete this entity
Entities.editEntity(nearbyEntities[i], {
locked: false
});
Entities.deleteEntity(nearbyEntities[i]);
}
}
}
}
processProperties();
setUp();
Script.scriptEnding.connect(tearDown);
return {};
};
var leafSquall1 = new leafSquall({
origin: {
x: 3071.5,
y: 2170,
z: 6765.3
},
radius: 100,
leavesPerMinute: 30,
leafSize: {
x: 0.3,
y: 0.00,
z: 0.3
},
leafFallSpeed: 0.4,
leafLifetime: 100,
leafSpinMax: 30,
debug: false,
maxLeaves: 100,
leafDeleteOnTearDown: true,
complexMovement: true,
floorHeight: 2143.5,
windFactor: 0.5,
leafDeleteOnGround: false
});
// todo
//deal with depth issue

File diff suppressed because it is too large Load diff

View file

@ -1,54 +1,54 @@
//
// walkConstants.js
// version 1.0
//
// Created by David Wooldridge, June 2015
// Copyright © 2015 High Fidelity, Inc.
//
// Provides constants necessary for the operation of the walk.js script and the walkApi.js script
//
// Editing tools for animation data files available here: https://github.com/DaveDubUK/walkTools
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// locomotion states
STATIC = 1;
SURFACE_MOTION = 2;
AIR_MOTION = 4;
// directions
UP = 1;
DOWN = 2;
LEFT = 4;
RIGHT = 8;
FORWARDS = 16;
BACKWARDS = 32;
NONE = 64;
// waveshapes
SAWTOOTH = 1;
TRIANGLE = 2;
SQUARE = 4;
// used by walk.js and walkApi.js
MAX_WALK_SPEED = 2.9; // peak, by observation
MAX_FT_WHEEL_INCREMENT = 25; // avoid fast walk when landing
TOP_SPEED = 300;
ON_SURFACE_THRESHOLD = 0.1; // height above surface to be considered as on the surface
TRANSITION_COMPLETE = 1000;
PITCH_MAX = 60; // maximum speed induced pitch
ROLL_MAX = 80; // maximum speed induced leaning / banking
DELTA_YAW_MAX = 1.7; // maximum change in yaw in rad/s
// used by walkApi.js only
MOVE_THRESHOLD = 0.075; // movement dead zone
ACCELERATION_THRESHOLD = 0.2; // detect stop to walking
DECELERATION_THRESHOLD = -6; // detect walking to stop
FAST_DECELERATION_THRESHOLD = -150; // detect flying to stop
BOUNCE_ACCELERATION_THRESHOLD = 25; // used to ignore gravity influence fluctuations after landing
GRAVITY_THRESHOLD = 3.0; // height above surface where gravity is in effect
OVERCOME_GRAVITY_SPEED = 0.5; // reaction sensitivity to jumping under gravity
LANDING_THRESHOLD = 0.35; // metres from a surface below which need to prepare for impact
MAX_TRANSITION_RECURSION = 10; // how many nested transitions are permitted
//
// walkConstants.js
// version 1.0
//
// Created by David Wooldridge, June 2015
// Copyright © 2015 High Fidelity, Inc.
//
// Provides constants necessary for the operation of the walk.js script and the walkApi.js script
//
// Editing tools for animation data files available here: https://github.com/DaveDubUK/walkTools
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// locomotion states
STATIC = 1;
SURFACE_MOTION = 2;
AIR_MOTION = 4;
// directions
UP = 1;
DOWN = 2;
LEFT = 4;
RIGHT = 8;
FORWARDS = 16;
BACKWARDS = 32;
NONE = 64;
// waveshapes
SAWTOOTH = 1;
TRIANGLE = 2;
SQUARE = 4;
// used by walk.js and walkApi.js
MAX_WALK_SPEED = 2.9; // peak, by observation
MAX_FT_WHEEL_INCREMENT = 25; // avoid fast walk when landing
TOP_SPEED = 300;
ON_SURFACE_THRESHOLD = 0.1; // height above surface to be considered as on the surface
TRANSITION_COMPLETE = 1000;
PITCH_MAX = 60; // maximum speed induced pitch
ROLL_MAX = 80; // maximum speed induced leaning / banking
DELTA_YAW_MAX = 1.7; // maximum change in yaw in rad/s
// used by walkApi.js only
MOVE_THRESHOLD = 0.075; // movement dead zone
ACCELERATION_THRESHOLD = 0.2; // detect stop to walking
DECELERATION_THRESHOLD = -6; // detect walking to stop
FAST_DECELERATION_THRESHOLD = -150; // detect flying to stop
BOUNCE_ACCELERATION_THRESHOLD = 25; // used to ignore gravity influence fluctuations after landing
GRAVITY_THRESHOLD = 3.0; // height above surface where gravity is in effect
OVERCOME_GRAVITY_SPEED = 0.5; // reaction sensitivity to jumping under gravity
LANDING_THRESHOLD = 0.35; // metres from a surface below which need to prepare for impact
MAX_TRANSITION_RECURSION = 10; // how many nested transitions are permitted

View file

@ -1,204 +1,204 @@
//
// walkFilters.js
// version 1.1
//
// Created by David Wooldridge, June 2015
// Copyright © 2014 - 2015 High Fidelity, Inc.
//
// Provides a variety of filters for use by the walk.js script v1.2+
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// simple averaging (LP) filter for damping / smoothing
AveragingFilter = function(length) {
// initialise the array of past values
this.pastValues = [];
for (var i = 0; i < length; i++) {
this.pastValues.push(0);
}
// single arg is the nextInputValue
this.process = function() {
if (this.pastValues.length === 0 && arguments[0]) {
return arguments[0];
} else if (arguments[0] !== null) {
this.pastValues.push(arguments[0]);
this.pastValues.shift();
var nextOutputValue = 0;
for (var value in this.pastValues) nextOutputValue += this.pastValues[value];
return nextOutputValue / this.pastValues.length;
} else {
return 0;
}
};
};
// 2nd order 2Hz Butterworth LP filter
ButterworthFilter = function() {
// coefficients calculated at: http://www-users.cs.york.ac.uk/~fisher/mkfilter/trad.html
this.gain = 104.9784742;
this.coeffOne = -0.7436551950;
this.coeffTwo = 1.7055521455;
// initialise the arrays
this.xv = [];
this.yv = [];
for (var i = 0; i < 3; i++) {
this.xv.push(0);
this.yv.push(0);
}
// process values
this.process = function(nextInputValue) {
this.xv[0] = this.xv[1];
this.xv[1] = this.xv[2];
this.xv[2] = nextInputValue / this.gain;
this.yv[0] = this.yv[1];
this.yv[1] = this.yv[2];
this.yv[2] = (this.xv[0] + this.xv[2]) +
2 * this.xv[1] +
(this.coeffOne * this.yv[0]) +
(this.coeffTwo * this.yv[1]);
return this.yv[2];
};
}; // end Butterworth filter constructor
// Add harmonics to a given sine wave to form square, sawtooth or triangle waves
// Geometric wave synthesis fundamentals taken from: http://hyperphysics.phy-astr.gsu.edu/hbase/audio/geowv.html
WaveSynth = function(waveShape, numHarmonics, smoothing) {
this.numHarmonics = numHarmonics;
this.waveShape = waveShape;
this.smoothingFilter = new AveragingFilter(smoothing);
// NB: frequency in radians
this.calculate = function(frequency) {
// make some shapes
var harmonics = 0;
var multiplier = 0;
var iterations = this.numHarmonics * 2 + 2;
if (this.waveShape === TRIANGLE) {
iterations++;
}
for (var n = 1; n < iterations; n++) {
switch (this.waveShape) {
case SAWTOOTH: {
multiplier = 1 / n;
harmonics += multiplier * Math.sin(n * frequency);
break;
}
case TRIANGLE: {
if (n % 2 === 1) {
var mulitplier = 1 / (n * n);
// multiply (4n-1)th harmonics by -1
if (n === 3 || n === 7 || n === 11 || n === 15) {
mulitplier *= -1;
}
harmonics += mulitplier * Math.sin(n * frequency);
}
break;
}
case SQUARE: {
if (n % 2 === 1) {
multiplier = 1 / n;
harmonics += multiplier * Math.sin(n * frequency);
}
break;
}
}
}
// smooth the result and return
return this.smoothingFilter.process(harmonics);
};
};
// Create a motion wave by summing pre-calculated harmonics (Fourier synthesis)
HarmonicsFilter = function(magnitudes, phaseAngles) {
this.magnitudes = magnitudes;
this.phaseAngles = phaseAngles;
this.calculate = function(twoPiFT) {
var harmonics = 0;
var numHarmonics = magnitudes.length;
for (var n = 0; n < numHarmonics; n++) {
harmonics += this.magnitudes[n] * Math.cos(n * twoPiFT - this.phaseAngles[n]);
}
return harmonics;
};
};
// the main filter object literal
filter = (function() {
const HALF_CYCLE = 180;
// Bezier private variables
var _C1 = {x:0, y:0};
var _C4 = {x:1, y:1};
// Bezier private functions
function _B1(t) { return t * t * t };
function _B2(t) { return 3 * t * t * (1 - t) };
function _B3(t) { return 3 * t * (1 - t) * (1 - t) };
function _B4(t) { return (1 - t) * (1 - t) * (1 - t) };
return {
// helper methods
degToRad: function(degrees) {
var convertedValue = degrees * Math.PI / HALF_CYCLE;
return convertedValue;
},
radToDeg: function(radians) {
var convertedValue = radians * HALF_CYCLE / Math.PI;
return convertedValue;
},
// these filters need instantiating, as they hold arrays of previous values
// simple averaging (LP) filter for damping / smoothing
createAveragingFilter: function(length) {
var newAveragingFilter = new AveragingFilter(length);
return newAveragingFilter;
},
// provides LP filtering with improved frequency / phase response
createButterworthFilter: function() {
var newButterworthFilter = new ButterworthFilter();
return newButterworthFilter;
},
// generates sawtooth, triangle or square waves using harmonics
createWaveSynth: function(waveShape, numHarmonics, smoothing) {
var newWaveSynth = new WaveSynth(waveShape, numHarmonics, smoothing);
return newWaveSynth;
},
// generates arbitrary waveforms using pre-calculated harmonics
createHarmonicsFilter: function(magnitudes, phaseAngles) {
var newHarmonicsFilter = new HarmonicsFilter(magnitudes, phaseAngles);
return newHarmonicsFilter;
},
// the following filters do not need separate instances, as they hold no previous values
// Bezier response curve shaping for more natural transitions
bezier: function(input, C2, C3) {
// based on script by Dan Pupius (www.pupius.net) http://13thparallel.com/archive/bezier-curves/
input = 1 - input;
return _C1.y * _B1(input) + C2.y * _B2(input) + C3.y * _B3(input) + _C4.y * _B4(input);
},
// simple clipping filter (special case for hips y-axis skeleton offset for walk animation)
clipTrough: function(inputValue, peak, strength) {
var outputValue = inputValue * strength;
if (outputValue < -peak) {
outputValue = -peak;
}
return outputValue;
}
}
})();
//
// walkFilters.js
// version 1.1
//
// Created by David Wooldridge, June 2015
// Copyright © 2014 - 2015 High Fidelity, Inc.
//
// Provides a variety of filters for use by the walk.js script v1.2+
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// simple averaging (LP) filter for damping / smoothing
AveragingFilter = function(length) {
// initialise the array of past values
this.pastValues = [];
for (var i = 0; i < length; i++) {
this.pastValues.push(0);
}
// single arg is the nextInputValue
this.process = function() {
if (this.pastValues.length === 0 && arguments[0]) {
return arguments[0];
} else if (arguments[0] !== null) {
this.pastValues.push(arguments[0]);
this.pastValues.shift();
var nextOutputValue = 0;
for (var value in this.pastValues) nextOutputValue += this.pastValues[value];
return nextOutputValue / this.pastValues.length;
} else {
return 0;
}
};
};
// 2nd order 2Hz Butterworth LP filter
ButterworthFilter = function() {
// coefficients calculated at: http://www-users.cs.york.ac.uk/~fisher/mkfilter/trad.html
this.gain = 104.9784742;
this.coeffOne = -0.7436551950;
this.coeffTwo = 1.7055521455;
// initialise the arrays
this.xv = [];
this.yv = [];
for (var i = 0; i < 3; i++) {
this.xv.push(0);
this.yv.push(0);
}
// process values
this.process = function(nextInputValue) {
this.xv[0] = this.xv[1];
this.xv[1] = this.xv[2];
this.xv[2] = nextInputValue / this.gain;
this.yv[0] = this.yv[1];
this.yv[1] = this.yv[2];
this.yv[2] = (this.xv[0] + this.xv[2]) +
2 * this.xv[1] +
(this.coeffOne * this.yv[0]) +
(this.coeffTwo * this.yv[1]);
return this.yv[2];
};
}; // end Butterworth filter constructor
// Add harmonics to a given sine wave to form square, sawtooth or triangle waves
// Geometric wave synthesis fundamentals taken from: http://hyperphysics.phy-astr.gsu.edu/hbase/audio/geowv.html
WaveSynth = function(waveShape, numHarmonics, smoothing) {
this.numHarmonics = numHarmonics;
this.waveShape = waveShape;
this.smoothingFilter = new AveragingFilter(smoothing);
// NB: frequency in radians
this.calculate = function(frequency) {
// make some shapes
var harmonics = 0;
var multiplier = 0;
var iterations = this.numHarmonics * 2 + 2;
if (this.waveShape === TRIANGLE) {
iterations++;
}
for (var n = 1; n < iterations; n++) {
switch (this.waveShape) {
case SAWTOOTH: {
multiplier = 1 / n;
harmonics += multiplier * Math.sin(n * frequency);
break;
}
case TRIANGLE: {
if (n % 2 === 1) {
var mulitplier = 1 / (n * n);
// multiply (4n-1)th harmonics by -1
if (n === 3 || n === 7 || n === 11 || n === 15) {
mulitplier *= -1;
}
harmonics += mulitplier * Math.sin(n * frequency);
}
break;
}
case SQUARE: {
if (n % 2 === 1) {
multiplier = 1 / n;
harmonics += multiplier * Math.sin(n * frequency);
}
break;
}
}
}
// smooth the result and return
return this.smoothingFilter.process(harmonics);
};
};
// Create a motion wave by summing pre-calculated harmonics (Fourier synthesis)
HarmonicsFilter = function(magnitudes, phaseAngles) {
this.magnitudes = magnitudes;
this.phaseAngles = phaseAngles;
this.calculate = function(twoPiFT) {
var harmonics = 0;
var numHarmonics = magnitudes.length;
for (var n = 0; n < numHarmonics; n++) {
harmonics += this.magnitudes[n] * Math.cos(n * twoPiFT - this.phaseAngles[n]);
}
return harmonics;
};
};
// the main filter object literal
filter = (function() {
const HALF_CYCLE = 180;
// Bezier private variables
var _C1 = {x:0, y:0};
var _C4 = {x:1, y:1};
// Bezier private functions
function _B1(t) { return t * t * t };
function _B2(t) { return 3 * t * t * (1 - t) };
function _B3(t) { return 3 * t * (1 - t) * (1 - t) };
function _B4(t) { return (1 - t) * (1 - t) * (1 - t) };
return {
// helper methods
degToRad: function(degrees) {
var convertedValue = degrees * Math.PI / HALF_CYCLE;
return convertedValue;
},
radToDeg: function(radians) {
var convertedValue = radians * HALF_CYCLE / Math.PI;
return convertedValue;
},
// these filters need instantiating, as they hold arrays of previous values
// simple averaging (LP) filter for damping / smoothing
createAveragingFilter: function(length) {
var newAveragingFilter = new AveragingFilter(length);
return newAveragingFilter;
},
// provides LP filtering with improved frequency / phase response
createButterworthFilter: function() {
var newButterworthFilter = new ButterworthFilter();
return newButterworthFilter;
},
// generates sawtooth, triangle or square waves using harmonics
createWaveSynth: function(waveShape, numHarmonics, smoothing) {
var newWaveSynth = new WaveSynth(waveShape, numHarmonics, smoothing);
return newWaveSynth;
},
// generates arbitrary waveforms using pre-calculated harmonics
createHarmonicsFilter: function(magnitudes, phaseAngles) {
var newHarmonicsFilter = new HarmonicsFilter(magnitudes, phaseAngles);
return newHarmonicsFilter;
},
// the following filters do not need separate instances, as they hold no previous values
// Bezier response curve shaping for more natural transitions
bezier: function(input, C2, C3) {
// based on script by Dan Pupius (www.pupius.net) http://13thparallel.com/archive/bezier-curves/
input = 1 - input;
return _C1.y * _B1(input) + C2.y * _B2(input) + C3.y * _B3(input) + _C4.y * _B4(input);
},
// simple clipping filter (special case for hips y-axis skeleton offset for walk animation)
clipTrough: function(inputValue, peak, strength) {
var outputValue = inputValue * strength;
if (outputValue < -peak) {
outputValue = -peak;
}
return outputValue;
}
}
})();

View file

@ -1,97 +1,97 @@
//
// walkSettings.js
// version 0.1
//
// Created by David Wooldridge, June 2015
// Copyright © 2015 High Fidelity, Inc.
//
// Presents settings for walk.js
//
// Editing tools for animation data files available here: https://github.com/DaveDubUK/walkTools
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
WalkSettings = function() {
var _visible = false;
var _innerWidth = Window.innerWidth;
const MARGIN_RIGHT = 58;
const MARGIN_TOP = 145;
const ICON_SIZE = 50;
const ICON_ALPHA = 0.9;
var minimisedTab = Overlays.addOverlay("image", {
x: _innerWidth - MARGIN_RIGHT, y: Window.innerHeight - MARGIN_TOP,
width: ICON_SIZE, height: ICON_SIZE,
imageURL: pathToAssets + 'overlay-images/ddpa-minimised-ddpa-tab.png',
visible: true, alpha: ICON_ALPHA
});
function mousePressEvent(event) {
if (Overlays.getOverlayAtPoint(event) === minimisedTab) {
_visible = !_visible;
_webWindow.setVisible(_visible);
}
}
Controller.mousePressEvent.connect(mousePressEvent);
Script.update.connect(function(deltaTime) {
if (window.innerWidth !== _innerWidth) {
_innerWidth = window.innerWidth;
Overlays.EditOverlay(minimisedTab, {x: _innerWidth - MARGIN_RIGHT});
}
});
function cleanup() {
Overlays.deleteOverlay(minimisedTab);
}
Script.scriptEnding.connect(cleanup);
var _shift = false;
function keyPressEvent(event) {
if (event.text === "SHIFT") {
_shift = true;
}
if (_shift && (event.text === 'o' || event.text === 'O')) {
_visible = !_visible;
_webWindow.setVisible(_visible);
}
}
function keyReleaseEvent(event) {
if (event.text === "SHIFT") {
_shift = false;
}
}
Controller.keyPressEvent.connect(keyPressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
// web window
const PANEL_WIDTH = 200;
const PANEL_HEIGHT = 180;
var _url = Script.resolvePath('html/walkSettings.html');
var _webWindow = new WebWindow('Walk Settings', _url, PANEL_WIDTH, PANEL_HEIGHT, false);
_webWindow.setVisible(false);
_webWindow.eventBridge.webEventReceived.connect(function(data) {
data = JSON.parse(data);
if (data.type == "init") {
// send the current settings to the window
_webWindow.eventBridge.emitScriptEvent(JSON.stringify({
type: "update",
armsFree: avatar.armsFree,
makesFootStepSounds: avatar.makesFootStepSounds,
blenderPreRotations: avatar.blenderPreRotations
}));
} else if (data.type == "powerToggle") {
motion.isLive = !motion.isLive;
} else if (data.type == "update") {
// receive settings from the window
avatar.armsFree = data.armsFree;
avatar.makesFootStepSounds = data.makesFootStepSounds;
avatar.blenderPreRotations = data.blenderPreRotations;
}
});
};
walkSettings = WalkSettings();
//
// walkSettings.js
// version 0.1
//
// Created by David Wooldridge, June 2015
// Copyright © 2015 High Fidelity, Inc.
//
// Presents settings for walk.js
//
// Editing tools for animation data files available here: https://github.com/DaveDubUK/walkTools
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
WalkSettings = function() {
var _visible = false;
var _innerWidth = Window.innerWidth;
const MARGIN_RIGHT = 58;
const MARGIN_TOP = 145;
const ICON_SIZE = 50;
const ICON_ALPHA = 0.9;
var minimisedTab = Overlays.addOverlay("image", {
x: _innerWidth - MARGIN_RIGHT, y: Window.innerHeight - MARGIN_TOP,
width: ICON_SIZE, height: ICON_SIZE,
imageURL: pathToAssets + 'overlay-images/ddpa-minimised-ddpa-tab.png',
visible: true, alpha: ICON_ALPHA
});
function mousePressEvent(event) {
if (Overlays.getOverlayAtPoint(event) === minimisedTab) {
_visible = !_visible;
_webWindow.setVisible(_visible);
}
}
Controller.mousePressEvent.connect(mousePressEvent);
Script.update.connect(function(deltaTime) {
if (window.innerWidth !== _innerWidth) {
_innerWidth = window.innerWidth;
Overlays.EditOverlay(minimisedTab, {x: _innerWidth - MARGIN_RIGHT});
}
});
function cleanup() {
Overlays.deleteOverlay(minimisedTab);
}
Script.scriptEnding.connect(cleanup);
var _shift = false;
function keyPressEvent(event) {
if (event.text === "SHIFT") {
_shift = true;
}
if (_shift && (event.text === 'o' || event.text === 'O')) {
_visible = !_visible;
_webWindow.setVisible(_visible);
}
}
function keyReleaseEvent(event) {
if (event.text === "SHIFT") {
_shift = false;
}
}
Controller.keyPressEvent.connect(keyPressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
// web window
const PANEL_WIDTH = 200;
const PANEL_HEIGHT = 180;
var _url = Script.resolvePath('html/walkSettings.html');
var _webWindow = new WebWindow('Walk Settings', _url, PANEL_WIDTH, PANEL_HEIGHT, false);
_webWindow.setVisible(false);
_webWindow.eventBridge.webEventReceived.connect(function(data) {
data = JSON.parse(data);
if (data.type == "init") {
// send the current settings to the window
_webWindow.eventBridge.emitScriptEvent(JSON.stringify({
type: "update",
armsFree: avatar.armsFree,
makesFootStepSounds: avatar.makesFootStepSounds,
blenderPreRotations: avatar.blenderPreRotations
}));
} else if (data.type == "powerToggle") {
motion.isLive = !motion.isLive;
} else if (data.type == "update") {
// receive settings from the window
avatar.armsFree = data.armsFree;
avatar.makesFootStepSounds = data.makesFootStepSounds;
avatar.blenderPreRotations = data.blenderPreRotations;
}
});
};
walkSettings = WalkSettings();

View file

@ -1,32 +1,32 @@
//
// createFlashlight.js
// examples/entityScripts
//
// Created by Sam Gateau on 9/9/15.
// Copyright 2015 High Fidelity, Inc.
//
// This is a toy script that create a flashlight entity that lit when grabbed
// This can be run from an interface and the flashlight will get deleted from the domain when quitting
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
Script.include("https://hifi-public.s3.amazonaws.com/scripts/utilities.js");
var scriptURL = Script.resolvePath('flashlight.js?123123');
var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx";
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0, y: 0.5, z: 0}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
var flashlight = Entities.addEntity({
type: "Model",
modelURL: modelURL,
position: center,
dimensions: { x: 0.08, y: 0.30, z: 0.08},
collisionsWillMove: true,
shapeType: 'box',
script: scriptURL
});
//
// createFlashlight.js
// examples/entityScripts
//
// Created by Sam Gateau on 9/9/15.
// Copyright 2015 High Fidelity, Inc.
//
// This is a toy script that create a flashlight entity that lit when grabbed
// This can be run from an interface and the flashlight will get deleted from the domain when quitting
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
Script.include("https://hifi-public.s3.amazonaws.com/scripts/utilities.js");
var scriptURL = Script.resolvePath('flashlight.js?123123');
var modelURL = "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx";
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {x: 0, y: 0.5, z: 0}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
var flashlight = Entities.addEntity({
type: "Model",
modelURL: modelURL,
position: center,
dimensions: { x: 0.08, y: 0.30, z: 0.08},
collisionsWillMove: true,
shapeType: 'box',
script: scriptURL
});

View file

@ -1,269 +1,269 @@
//
// flashlight.js
//
// Script Type: Entity
//
// Created by Sam Gateau on 9/9/15.
// Additions by James B. Pollack @imgntn on 9/21/2015
// Copyright 2015 High Fidelity, Inc.
//
// This is a toy script that can be added to the Flashlight model entity:
// "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx"
// that creates a spotlight attached with the flashlight model while the entity is grabbed
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
(function() {
Script.include("../../libraries/utils.js");
var ON_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/flashlight_on.wav';
var OFF_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/flashlight_off.wav';
//we are creating lights that we don't want to get stranded so lets make sure that we can get rid of them
var startTime = Date.now();
//if you're going to be using this in a dungeon or something and holding it for a long time, increase this lifetime value.
var LIFETIME = 25;
var MSEC_PER_SEC = 1000.0;
// this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember
// our this object, so we can access it in cases where we're called without a this (like in the case of various global signals)
function Flashlight() {
return;
}
//if the trigger value goes below this while held, the flashlight will turn off. if it goes above, it will
var DISABLE_LIGHT_THRESHOLD = 0.7;
// These constants define the Spotlight position and orientation relative to the model
var MODEL_LIGHT_POSITION = {
x: 0,
y: -0.3,
z: 0
};
var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, {
x: 1,
y: 0,
z: 0
});
var GLOW_LIGHT_POSITION = {
x: 0,
y: -0.1,
z: 0
};
// Evaluate the world light entity positions and orientations from the model ones
function evalLightWorldTransform(modelPos, modelRot) {
return {
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)),
q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)
};
}
function glowLightWorldTransform(modelPos, modelRot) {
return {
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, GLOW_LIGHT_POSITION)),
q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)
};
}
Flashlight.prototype = {
lightOn: false,
hand: null,
whichHand: null,
hasSpotlight: false,
spotlight: null,
setRightHand: function() {
this.hand = 'RIGHT';
},
setLeftHand: function() {
this.hand = 'LEFT';
},
startNearGrab: function() {
if (!this.hasSpotlight) {
//this light casts the beam
this.spotlight = Entities.addEntity({
type: "Light",
isSpotlight: true,
dimensions: {
x: 2,
y: 2,
z: 20
},
color: {
red: 255,
green: 255,
blue: 255
},
intensity: 2,
exponent: 0.3,
cutoff: 20,
lifetime: LIFETIME
});
//this light creates the effect of a bulb at the end of the flashlight
this.glowLight = Entities.addEntity({
type: "Light",
dimensions: {
x: 0.25,
y: 0.25,
z: 0.25
},
isSpotlight: false,
color: {
red: 255,
green: 255,
blue: 255
},
exponent: 0,
cutoff: 90, // in degrees
lifetime: LIFETIME
});
this.hasSpotlight = true;
}
},
setWhichHand: function() {
this.whichHand = this.hand;
},
continueNearGrab: function() {
if (this.whichHand === null) {
//only set the active hand once -- if we always read the current hand, our 'holding' hand will get overwritten
this.setWhichHand();
} else {
this.updateLightPositions();
this.changeLightWithTriggerPressure(this.whichHand);
}
},
releaseGrab: function() {
//delete the lights and reset state
if (this.hasSpotlight) {
Entities.deleteEntity(this.spotlight);
Entities.deleteEntity(this.glowLight);
this.hasSpotlight = false;
this.glowLight = null;
this.spotlight = null;
this.whichHand = null;
this.lightOn = false;
}
},
updateLightPositions: function() {
var modelProperties = Entities.getEntityProperties(this.entityID, ['position', 'rotation']);
//move the two lights along the vectors we set above
var lightTransform = evalLightWorldTransform(modelProperties.position, modelProperties.rotation);
var glowLightTransform = glowLightWorldTransform(modelProperties.position, modelProperties.rotation);
//move them with the entity model
Entities.editEntity(this.spotlight, {
position: lightTransform.p,
rotation: lightTransform.q,
lifetime: (Date.now() - startTime) / MSEC_PER_SEC + LIFETIME
});
Entities.editEntity(this.glowLight, {
position: glowLightTransform.p,
rotation: glowLightTransform.q,
lifetime: (Date.now() - startTime) / MSEC_PER_SEC + LIFETIME
});
},
changeLightWithTriggerPressure: function(flashLightHand) {
var handClickString = flashLightHand + "_HAND_CLICK";
var handClick = Controller.findAction(handClickString);
this.triggerValue = Controller.getActionValue(handClick);
if (this.triggerValue < DISABLE_LIGHT_THRESHOLD && this.lightOn === true) {
this.turnLightOff();
} else if (this.triggerValue >= DISABLE_LIGHT_THRESHOLD && this.lightOn === false) {
this.turnLightOn();
}
return;
},
turnLightOff: function() {
this.playSoundAtCurrentPosition(false);
Entities.editEntity(this.spotlight, {
intensity: 0
});
Entities.editEntity(this.glowLight, {
intensity: 0
});
this.lightOn = false;
},
turnLightOn: function() {
this.playSoundAtCurrentPosition(true);
Entities.editEntity(this.glowLight, {
intensity: 2
});
Entities.editEntity(this.spotlight, {
intensity: 2
});
this.lightOn = true;
},
playSoundAtCurrentPosition: function(playOnSound) {
var position = Entities.getEntityProperties(this.entityID, "position").position;
var audioProperties = {
volume: 0.25,
position: position
};
if (playOnSound) {
Audio.playSound(this.ON_SOUND, audioProperties);
} else {
Audio.playSound(this.OFF_SOUND, audioProperties);
}
},
preload: function(entityID) {
// preload() will be called when the entity has become visible (or known) to the interface
// it gives us a chance to set our local JavaScript object up. In this case it means:
// * remembering our entityID, so we can access it in cases where we're called without an entityID
// * preloading sounds
this.entityID = entityID;
this.ON_SOUND = SoundCache.getSound(ON_SOUND_URL);
this.OFF_SOUND = SoundCache.getSound(OFF_SOUND_URL);
},
unload: function() {
// unload() will be called when our entity is no longer available. It may be because we were deleted,
// or because we've left the domain or quit the application.
if (this.hasSpotlight) {
Entities.deleteEntity(this.spotlight);
Entities.deleteEntity(this.glowLight);
this.hasSpotlight = false;
this.glowLight = null;
this.spotlight = null;
this.whichHand = null;
this.lightOn = false;
}
},
};
// entity scripts always need to return a newly constructed object of our type
return new Flashlight();
});
//
// flashlight.js
//
// Script Type: Entity
//
// Created by Sam Gateau on 9/9/15.
// Additions by James B. Pollack @imgntn on 9/21/2015
// Copyright 2015 High Fidelity, Inc.
//
// This is a toy script that can be added to the Flashlight model entity:
// "https://hifi-public.s3.amazonaws.com/models/props/flashlight.fbx"
// that creates a spotlight attached with the flashlight model while the entity is grabbed
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
(function() {
Script.include("../../libraries/utils.js");
var ON_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/flashlight_on.wav';
var OFF_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Switches%20and%20sliders/flashlight_off.wav';
//we are creating lights that we don't want to get stranded so lets make sure that we can get rid of them
var startTime = Date.now();
//if you're going to be using this in a dungeon or something and holding it for a long time, increase this lifetime value.
var LIFETIME = 25;
var MSEC_PER_SEC = 1000.0;
// this is the "constructor" for the entity as a JS object we don't do much here, but we do want to remember
// our this object, so we can access it in cases where we're called without a this (like in the case of various global signals)
function Flashlight() {
return;
}
//if the trigger value goes below this while held, the flashlight will turn off. if it goes above, it will
var DISABLE_LIGHT_THRESHOLD = 0.7;
// These constants define the Spotlight position and orientation relative to the model
var MODEL_LIGHT_POSITION = {
x: 0,
y: -0.3,
z: 0
};
var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, {
x: 1,
y: 0,
z: 0
});
var GLOW_LIGHT_POSITION = {
x: 0,
y: -0.1,
z: 0
};
// Evaluate the world light entity positions and orientations from the model ones
function evalLightWorldTransform(modelPos, modelRot) {
return {
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)),
q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)
};
}
function glowLightWorldTransform(modelPos, modelRot) {
return {
p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, GLOW_LIGHT_POSITION)),
q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION)
};
}
Flashlight.prototype = {
lightOn: false,
hand: null,
whichHand: null,
hasSpotlight: false,
spotlight: null,
setRightHand: function() {
this.hand = 'RIGHT';
},
setLeftHand: function() {
this.hand = 'LEFT';
},
startNearGrab: function() {
if (!this.hasSpotlight) {
//this light casts the beam
this.spotlight = Entities.addEntity({
type: "Light",
isSpotlight: true,
dimensions: {
x: 2,
y: 2,
z: 20
},
color: {
red: 255,
green: 255,
blue: 255
},
intensity: 2,
exponent: 0.3,
cutoff: 20,
lifetime: LIFETIME
});
//this light creates the effect of a bulb at the end of the flashlight
this.glowLight = Entities.addEntity({
type: "Light",
dimensions: {
x: 0.25,
y: 0.25,
z: 0.25
},
isSpotlight: false,
color: {
red: 255,
green: 255,
blue: 255
},
exponent: 0,
cutoff: 90, // in degrees
lifetime: LIFETIME
});
this.hasSpotlight = true;
}
},
setWhichHand: function() {
this.whichHand = this.hand;
},
continueNearGrab: function() {
if (this.whichHand === null) {
//only set the active hand once -- if we always read the current hand, our 'holding' hand will get overwritten
this.setWhichHand();
} else {
this.updateLightPositions();
this.changeLightWithTriggerPressure(this.whichHand);
}
},
releaseGrab: function() {
//delete the lights and reset state
if (this.hasSpotlight) {
Entities.deleteEntity(this.spotlight);
Entities.deleteEntity(this.glowLight);
this.hasSpotlight = false;
this.glowLight = null;
this.spotlight = null;
this.whichHand = null;
this.lightOn = false;
}
},
updateLightPositions: function() {
var modelProperties = Entities.getEntityProperties(this.entityID, ['position', 'rotation']);
//move the two lights along the vectors we set above
var lightTransform = evalLightWorldTransform(modelProperties.position, modelProperties.rotation);
var glowLightTransform = glowLightWorldTransform(modelProperties.position, modelProperties.rotation);
//move them with the entity model
Entities.editEntity(this.spotlight, {
position: lightTransform.p,
rotation: lightTransform.q,
lifetime: (Date.now() - startTime) / MSEC_PER_SEC + LIFETIME
});
Entities.editEntity(this.glowLight, {
position: glowLightTransform.p,
rotation: glowLightTransform.q,
lifetime: (Date.now() - startTime) / MSEC_PER_SEC + LIFETIME
});
},
changeLightWithTriggerPressure: function(flashLightHand) {
var handClickString = flashLightHand + "_HAND_CLICK";
var handClick = Controller.findAction(handClickString);
this.triggerValue = Controller.getActionValue(handClick);
if (this.triggerValue < DISABLE_LIGHT_THRESHOLD && this.lightOn === true) {
this.turnLightOff();
} else if (this.triggerValue >= DISABLE_LIGHT_THRESHOLD && this.lightOn === false) {
this.turnLightOn();
}
return;
},
turnLightOff: function() {
this.playSoundAtCurrentPosition(false);
Entities.editEntity(this.spotlight, {
intensity: 0
});
Entities.editEntity(this.glowLight, {
intensity: 0
});
this.lightOn = false;
},
turnLightOn: function() {
this.playSoundAtCurrentPosition(true);
Entities.editEntity(this.glowLight, {
intensity: 2
});
Entities.editEntity(this.spotlight, {
intensity: 2
});
this.lightOn = true;
},
playSoundAtCurrentPosition: function(playOnSound) {
var position = Entities.getEntityProperties(this.entityID, "position").position;
var audioProperties = {
volume: 0.25,
position: position
};
if (playOnSound) {
Audio.playSound(this.ON_SOUND, audioProperties);
} else {
Audio.playSound(this.OFF_SOUND, audioProperties);
}
},
preload: function(entityID) {
// preload() will be called when the entity has become visible (or known) to the interface
// it gives us a chance to set our local JavaScript object up. In this case it means:
// * remembering our entityID, so we can access it in cases where we're called without an entityID
// * preloading sounds
this.entityID = entityID;
this.ON_SOUND = SoundCache.getSound(ON_SOUND_URL);
this.OFF_SOUND = SoundCache.getSound(OFF_SOUND_URL);
},
unload: function() {
// unload() will be called when our entity is no longer available. It may be because we were deleted,
// or because we've left the domain or quit the application.
if (this.hasSpotlight) {
Entities.deleteEntity(this.spotlight);
Entities.deleteEntity(this.glowLight);
this.hasSpotlight = false;
this.glowLight = null;
this.spotlight = null;
this.whichHand = null;
this.lightOn = false;
}
},
};
// entity scripts always need to return a newly constructed object of our type
return new Flashlight();
});

View file

@ -1,87 +1,87 @@
//
// SunLightExample.js
// examples
// Sam Gateau
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("cookies.js");
var panel = new Panel(10, 100);
function CounterWidget(parentPanel, name, feedGetter, drawGetter, capSetter, capGetter) {
this.subPanel = panel.newSubPanel(name);
this.subPanel.newSlider("Num Feed", 0, 1,
function(value) { },
feedGetter,
function(value) { return (value); });
this.subPanel.newSlider("Num Drawn", 0, 1,
function(value) { },
drawGetter,
function(value) { return (value); });
this.subPanel.newSlider("Max Drawn", -1, 1,
capSetter,
capGetter,
function(value) { return (value); });
this.update = function () {
var numFeed = this.subPanel.get("Num Feed");
this.subPanel.set("Num Feed", numFeed);
this.subPanel.set("Num Drawn", this.subPanel.get("Num Drawn"));
var numMax = Math.max(numFeed, 1);
this.subPanel.getWidget("Num Feed").setMaxValue(numMax);
this.subPanel.getWidget("Num Drawn").setMaxValue(numMax);
this.subPanel.getWidget("Max Drawn").setMaxValue(numMax);
};
};
var opaquesCounter = new CounterWidget(panel, "Opaques",
function () { return Scene.getEngineNumFeedOpaqueItems(); },
function () { return Scene.getEngineNumDrawnOpaqueItems(); },
function(value) { Scene.setEngineMaxDrawnOpaqueItems(value); },
function () { return Scene.getEngineMaxDrawnOpaqueItems(); }
);
var transparentsCounter = new CounterWidget(panel, "Transparents",
function () { return Scene.getEngineNumFeedTransparentItems(); },
function () { return Scene.getEngineNumDrawnTransparentItems(); },
function(value) { Scene.setEngineMaxDrawnTransparentItems(value); },
function () { return Scene.getEngineMaxDrawnTransparentItems(); }
);
var overlaysCounter = new CounterWidget(panel, "Overlays",
function () { return Scene.getEngineNumFeedOverlay3DItems(); },
function () { return Scene.getEngineNumDrawnOverlay3DItems(); },
function(value) { Scene.setEngineMaxDrawnOverlay3DItems(value); },
function () { return Scene.getEngineMaxDrawnOverlay3DItems(); }
);
panel.newCheckbox("Display status",
function(value) { Scene.setEngineDisplayItemStatus(value); },
function() { return Scene.doEngineDisplayItemStatus(); },
function(value) { return (value); }
);
var tickTackPeriod = 500;
function updateCounters() {
opaquesCounter.update();
transparentsCounter.update();
overlaysCounter.update();
}
Script.setInterval(updateCounters, tickTackPeriod);
Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); });
Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); });
Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); });
function scriptEnding() {
panel.destroy();
}
Script.scriptEnding.connect(scriptEnding);
//
// SunLightExample.js
// examples
// Sam Gateau
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("cookies.js");
var panel = new Panel(10, 100);
function CounterWidget(parentPanel, name, feedGetter, drawGetter, capSetter, capGetter) {
this.subPanel = panel.newSubPanel(name);
this.subPanel.newSlider("Num Feed", 0, 1,
function(value) { },
feedGetter,
function(value) { return (value); });
this.subPanel.newSlider("Num Drawn", 0, 1,
function(value) { },
drawGetter,
function(value) { return (value); });
this.subPanel.newSlider("Max Drawn", -1, 1,
capSetter,
capGetter,
function(value) { return (value); });
this.update = function () {
var numFeed = this.subPanel.get("Num Feed");
this.subPanel.set("Num Feed", numFeed);
this.subPanel.set("Num Drawn", this.subPanel.get("Num Drawn"));
var numMax = Math.max(numFeed, 1);
this.subPanel.getWidget("Num Feed").setMaxValue(numMax);
this.subPanel.getWidget("Num Drawn").setMaxValue(numMax);
this.subPanel.getWidget("Max Drawn").setMaxValue(numMax);
};
};
var opaquesCounter = new CounterWidget(panel, "Opaques",
function () { return Scene.getEngineNumFeedOpaqueItems(); },
function () { return Scene.getEngineNumDrawnOpaqueItems(); },
function(value) { Scene.setEngineMaxDrawnOpaqueItems(value); },
function () { return Scene.getEngineMaxDrawnOpaqueItems(); }
);
var transparentsCounter = new CounterWidget(panel, "Transparents",
function () { return Scene.getEngineNumFeedTransparentItems(); },
function () { return Scene.getEngineNumDrawnTransparentItems(); },
function(value) { Scene.setEngineMaxDrawnTransparentItems(value); },
function () { return Scene.getEngineMaxDrawnTransparentItems(); }
);
var overlaysCounter = new CounterWidget(panel, "Overlays",
function () { return Scene.getEngineNumFeedOverlay3DItems(); },
function () { return Scene.getEngineNumDrawnOverlay3DItems(); },
function(value) { Scene.setEngineMaxDrawnOverlay3DItems(value); },
function () { return Scene.getEngineMaxDrawnOverlay3DItems(); }
);
panel.newCheckbox("Display status",
function(value) { Scene.setEngineDisplayItemStatus(value); },
function() { return Scene.doEngineDisplayItemStatus(); },
function(value) { return (value); }
);
var tickTackPeriod = 500;
function updateCounters() {
opaquesCounter.update();
transparentsCounter.update();
overlaysCounter.update();
}
Script.setInterval(updateCounters, tickTackPeriod);
Controller.mouseMoveEvent.connect(function panelMouseMoveEvent(event) { return panel.mouseMoveEvent(event); });
Controller.mousePressEvent.connect( function panelMousePressEvent(event) { return panel.mousePressEvent(event); });
Controller.mouseReleaseEvent.connect(function(event) { return panel.mouseReleaseEvent(event); });
function scriptEnding() {
panel.destroy();
}
Script.scriptEnding.connect(scriptEnding);

View file

@ -1,454 +1,454 @@
//
// walk.js
// version 1.25
//
// Created by David Wooldridge, June 2015
// Copyright © 2014 - 2015 High Fidelity, Inc.
//
// Animates an avatar using procedural animation techniques.
//
// Editing tools for animation data files available here: https://github.com/DaveDubUK/walkTools
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// animations, reach poses, reach pose parameters, transitions, transition parameters, sounds, image/s and reference files
const HIFI_PUBLIC_BUCKET = "https://hifi-public.s3.amazonaws.com/";
var pathToAssets = HIFI_PUBLIC_BUCKET + "procedural-animator/assets/";
Script.include([
"./libraries/walkConstants.js",
"./libraries/walkFilters.js",
"./libraries/walkApi.js",
pathToAssets + "walkAssets.js"
]);
// construct Avatar, Motion and (null) Transition
var avatar = new Avatar();
var motion = new Motion();
var nullTransition = new Transition();
motion.currentTransition = nullTransition;
// create settings (gets initial values from avatar)
Script.include("./libraries/walkSettings.js");
// Main loop
Script.update.connect(function(deltaTime) {
if (motion.isLive) {
// assess current locomotion state
motion.assess(deltaTime);
// decide which animation should be playing
selectAnimation();
// advance the animation cycle/s by the correct amount/s
advanceAnimations();
// update the progress of any live transitions
updateTransitions();
// apply translation and rotations
renderMotion();
// save this frame's parameters
motion.saveHistory();
}
});
// helper function for selectAnimation()
function setTransition(nextAnimation, playTransitionReachPoses) {
var lastTransition = motion.currentTransition;
var lastAnimation = avatar.currentAnimation;
// if already transitioning from a blended walk need to maintain the previous walk's direction
if (lastAnimation.lastDirection) {
switch(lastAnimation.lastDirection) {
case FORWARDS:
lastAnimation = avatar.selectedWalk;
break;
case BACKWARDS:
lastAnimation = avatar.selectedWalkBackwards;
break;
case LEFT:
lastAnimation = avatar.selectedSideStepLeft;
break;
case RIGHT:
lastAnimation = avatar.selectedSideStepRight;
break;
}
}
motion.currentTransition = new Transition(nextAnimation, lastAnimation, lastTransition, playTransitionReachPoses);
avatar.currentAnimation = nextAnimation;
// reset default first footstep
if (nextAnimation === avatar.selectedWalkBlend && lastTransition === nullTransition) {
avatar.nextStep = RIGHT;
}
}
// fly animation blending: smoothing / damping filters
const FLY_BLEND_DAMPING = 50;
var flyUpFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING);
var flyDownFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING);
var flyForwardFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING);
var flyBackwardFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING);
// select / blend the appropriate animation for the current state of motion
function selectAnimation() {
var playTransitionReachPoses = true;
// select appropriate animation. create transitions where appropriate
switch (motion.nextState) {
case STATIC: {
if (avatar.distanceFromSurface < ON_SURFACE_THRESHOLD &&
avatar.currentAnimation !== avatar.selectedIdle) {
setTransition(avatar.selectedIdle, playTransitionReachPoses);
} else if (!(avatar.distanceFromSurface < ON_SURFACE_THRESHOLD) &&
avatar.currentAnimation !== avatar.selectedHover) {
setTransition(avatar.selectedHover, playTransitionReachPoses);
}
motion.state = STATIC;
avatar.selectedWalkBlend.lastDirection = NONE;
break;
}
case SURFACE_MOTION: {
// walk transition reach poses are currently only specified for starting to walk forwards
playTransitionReachPoses = (motion.direction === FORWARDS);
var isAlreadyWalking = (avatar.currentAnimation === avatar.selectedWalkBlend);
switch (motion.direction) {
case FORWARDS:
if (avatar.selectedWalkBlend.lastDirection !== FORWARDS) {
animationOperations.deepCopy(avatar.selectedWalk, avatar.selectedWalkBlend);
avatar.calibration.strideLength = avatar.selectedWalk.calibration.strideLength;
}
avatar.selectedWalkBlend.lastDirection = FORWARDS;
break;
case BACKWARDS:
if (avatar.selectedWalkBlend.lastDirection !== BACKWARDS) {
animationOperations.deepCopy(avatar.selectedWalkBackwards, avatar.selectedWalkBlend);
avatar.calibration.strideLength = avatar.selectedWalkBackwards.calibration.strideLength;
}
avatar.selectedWalkBlend.lastDirection = BACKWARDS;
break;
case LEFT:
animationOperations.deepCopy(avatar.selectedSideStepLeft, avatar.selectedWalkBlend);
avatar.selectedWalkBlend.lastDirection = LEFT;
avatar.calibration.strideLength = avatar.selectedSideStepLeft.calibration.strideLength;
break
case RIGHT:
animationOperations.deepCopy(avatar.selectedSideStepRight, avatar.selectedWalkBlend);
avatar.selectedWalkBlend.lastDirection = RIGHT;
avatar.calibration.strideLength = avatar.selectedSideStepRight.calibration.strideLength;
break;
default:
// condition occurs when the avi goes through the floor due to collision hull errors
animationOperations.deepCopy(avatar.selectedWalk, avatar.selectedWalkBlend);
avatar.selectedWalkBlend.lastDirection = FORWARDS;
avatar.calibration.strideLength = avatar.selectedWalk.calibration.strideLength;
break;
}
if (!isAlreadyWalking && !motion.isComingToHalt) {
setTransition(avatar.selectedWalkBlend, playTransitionReachPoses);
}
motion.state = SURFACE_MOTION;
break;
}
case AIR_MOTION: {
// blend the up, down, forward and backward flying animations relative to motion speed and direction
animationOperations.zeroAnimation(avatar.selectedFlyBlend);
// calculate influences based on velocity and direction
var velocityMagnitude = Vec3.length(motion.velocity);
var verticalProportion = motion.velocity.y / velocityMagnitude;
var thrustProportion = motion.velocity.z / velocityMagnitude / 2;
// directional components
var upComponent = motion.velocity.y > 0 ? verticalProportion : 0;
var downComponent = motion.velocity.y < 0 ? -verticalProportion : 0;
var forwardComponent = motion.velocity.z < 0 ? -thrustProportion : 0;
var backwardComponent = motion.velocity.z > 0 ? thrustProportion : 0;
// smooth / damp directional components to add visual 'weight'
upComponent = flyUpFilter.process(upComponent);
downComponent = flyDownFilter.process(downComponent);
forwardComponent = flyForwardFilter.process(forwardComponent);
backwardComponent = flyBackwardFilter.process(backwardComponent);
// normalise directional components
var normaliser = upComponent + downComponent + forwardComponent + backwardComponent;
upComponent = upComponent / normaliser;
downComponent = downComponent / normaliser;
forwardComponent = forwardComponent / normaliser;
backwardComponent = backwardComponent / normaliser;
// blend animations proportionally
if (upComponent > 0) {
animationOperations.blendAnimation(avatar.selectedFlyUp,
avatar.selectedFlyBlend,
upComponent);
}
if (downComponent > 0) {
animationOperations.blendAnimation(avatar.selectedFlyDown,
avatar.selectedFlyBlend,
downComponent);
}
if (forwardComponent > 0) {
animationOperations.blendAnimation(avatar.selectedFly,
avatar.selectedFlyBlend,
Math.abs(forwardComponent));
}
if (backwardComponent > 0) {
animationOperations.blendAnimation(avatar.selectedFlyBackwards,
avatar.selectedFlyBlend,
Math.abs(backwardComponent));
}
if (avatar.currentAnimation !== avatar.selectedFlyBlend) {
setTransition(avatar.selectedFlyBlend, playTransitionReachPoses);
}
motion.state = AIR_MOTION;
avatar.selectedWalkBlend.lastDirection = NONE;
break;
}
} // end switch next state of motion
}
// determine the length of stride. advance the frequency time wheels. advance frequency time wheels for any live transitions
function advanceAnimations() {
var wheelAdvance = 0;
// turn the frequency time wheel
if (avatar.currentAnimation === avatar.selectedWalkBlend) {
// Using technique described here: http://www.gdcvault.com/play/1020583/Animation-Bootcamp-An-Indie-Approach
// wrap the stride length around a 'surveyor's wheel' twice and calculate the angular speed at the given (linear) speed
// omega = v / r , where r = circumference / 2 PI and circumference = 2 * stride length
var speed = Vec3.length(motion.velocity);
motion.frequencyTimeWheelRadius = avatar.calibration.strideLength / Math.PI;
var ftWheelAngularVelocity = speed / motion.frequencyTimeWheelRadius;
// calculate the degrees turned (at this angular speed) since last frame
wheelAdvance = filter.radToDeg(motion.deltaTime * ftWheelAngularVelocity);
} else {
// turn the frequency time wheel by the amount specified for this animation
wheelAdvance = filter.radToDeg(avatar.currentAnimation.calibration.frequency * motion.deltaTime);
}
if (motion.currentTransition !== nullTransition) {
// the last animation is still playing so we turn it's frequency time wheel to maintain the animation
if (motion.currentTransition.lastAnimation === motion.selectedWalkBlend) {
// if at a stop angle (i.e. feet now under the avi) hold the wheel position for remainder of transition
var tolerance = motion.currentTransition.lastFrequencyTimeIncrement + 0.1;
if ((motion.currentTransition.lastFrequencyTimeWheelPos >
(motion.currentTransition.stopAngle - tolerance)) &&
(motion.currentTransition.lastFrequencyTimeWheelPos <
(motion.currentTransition.stopAngle + tolerance))) {
motion.currentTransition.lastFrequencyTimeIncrement = 0;
}
}
motion.currentTransition.advancePreviousFrequencyTimeWheel(motion.deltaTime);
}
// avoid unnaturally fast walking when landing at speed - simulates skimming / skidding
if (Math.abs(wheelAdvance) > MAX_FT_WHEEL_INCREMENT) {
wheelAdvance = 0;
}
// advance the walk wheel the appropriate amount
motion.advanceFrequencyTimeWheel(wheelAdvance);
// walking? then see if it's a good time to measure the stride length (needs to be at least 97% of max walking speed)
const ALMOST_ONE = 0.97;
if (avatar.currentAnimation === avatar.selectedWalkBlend &&
(Vec3.length(motion.velocity) / MAX_WALK_SPEED > ALMOST_ONE)) {
var strideMaxAt = avatar.currentAnimation.calibration.strideMaxAt;
const TOLERANCE = 1.0;
if (motion.frequencyTimeWheelPos < (strideMaxAt + TOLERANCE) &&
motion.frequencyTimeWheelPos > (strideMaxAt - TOLERANCE) &&
motion.currentTransition === nullTransition) {
// measure and save stride length
var footRPos = MyAvatar.getJointPosition("RightFoot");
var footLPos = MyAvatar.getJointPosition("LeftFoot");
avatar.calibration.strideLength = Vec3.distance(footRPos, footLPos);
avatar.currentAnimation.calibration.strideLength = avatar.calibration.strideLength;
} else {
// use the previously saved value for stride length
avatar.calibration.strideLength = avatar.currentAnimation.calibration.strideLength;
}
} // end get walk stride length
}
// initialise a new transition. update progress of a live transition
function updateTransitions() {
if (motion.currentTransition !== nullTransition) {
// is this a new transition?
if (motion.currentTransition.progress === 0) {
// do we have overlapping transitions?
if (motion.currentTransition.lastTransition !== nullTransition) {
// is the last animation for the nested transition the same as the new animation?
if (motion.currentTransition.lastTransition.lastAnimation === avatar.currentAnimation) {
// then sync the nested transition's frequency time wheel for a smooth animation blend
motion.frequencyTimeWheelPos = motion.currentTransition.lastTransition.lastFrequencyTimeWheelPos;
}
}
}
if (motion.currentTransition.updateProgress() === TRANSITION_COMPLETE) {
motion.currentTransition = nullTransition;
}
}
}
// helper function for renderMotion(). calculate the amount to lean forwards (or backwards) based on the avi's velocity
var leanPitchSmoothingFilter = filter.createButterworthFilter();
function getLeanPitch() {
var leanProgress = 0;
if (motion.direction === DOWN ||
motion.direction === FORWARDS ||
motion.direction === BACKWARDS) {
leanProgress = -motion.velocity.z / TOP_SPEED;
}
// use filters to shape the walking acceleration response
leanProgress = leanPitchSmoothingFilter.process(leanProgress);
return PITCH_MAX * leanProgress;
}
// helper function for renderMotion(). calculate the angle at which to bank into corners whilst turning
var leanRollSmoothingFilter = filter.createButterworthFilter();
function getLeanRoll() {
var leanRollProgress = 0;
var linearContribution = 0;
const LOG_SCALER = 8;
if (Vec3.length(motion.velocity) > 0) {
linearContribution = (Math.log(Vec3.length(motion.velocity) / TOP_SPEED) + LOG_SCALER) / LOG_SCALER;
}
var angularContribution = Math.abs(motion.yawDelta) / DELTA_YAW_MAX;
leanRollProgress = linearContribution;
leanRollProgress *= angularContribution;
// shape the response curve
leanRollProgress = filter.bezier(leanRollProgress, {x: 1, y: 0}, {x: 1, y: 0});
// which way to lean?
var turnSign = (motion.yawDelta >= 0) ? 1 : -1;
if (motion.direction === BACKWARDS ||
motion.direction === LEFT) {
turnSign *= -1;
}
// filter progress
leanRollProgress = leanRollSmoothingFilter.process(turnSign * leanRollProgress);
return ROLL_MAX * leanRollProgress;
}
// animate the avatar using sine waves, geometric waveforms and harmonic generators
function renderMotion() {
// leaning in response to speed and acceleration
var leanPitch = motion.state === STATIC ? 0 : getLeanPitch();
var leanRoll = motion.state === STATIC ? 0 : getLeanRoll();
var lastDirection = motion.lastDirection;
// hips translations from currently playing animations
var hipsTranslations = {x:0, y:0, z:0};
if (motion.currentTransition !== nullTransition) {
// maintain previous direction when transitioning from a walk
if (motion.currentTransition.lastAnimation === avatar.selectedWalkBlend) {
motion.lastDirection = motion.currentTransition.lastDirection;
}
hipsTranslations = motion.currentTransition.blendTranslations(motion.frequencyTimeWheelPos,
motion.lastDirection);
} else {
hipsTranslations = animationOperations.calculateTranslations(avatar.currentAnimation,
motion.frequencyTimeWheelPos,
motion.direction);
}
// factor any leaning into the hips offset
hipsTranslations.z += avatar.calibration.hipsToFeet * Math.sin(filter.degToRad(leanPitch));
hipsTranslations.x += avatar.calibration.hipsToFeet * Math.sin(filter.degToRad(leanRoll));
// ensure skeleton offsets are within the 1m limit
hipsTranslations.x = hipsTranslations.x > 1 ? 1 : hipsTranslations.x;
hipsTranslations.x = hipsTranslations.x < -1 ? -1 : hipsTranslations.x;
hipsTranslations.y = hipsTranslations.y > 1 ? 1 : hipsTranslations.y;
hipsTranslations.y = hipsTranslations.y < -1 ? -1 : hipsTranslations.y;
hipsTranslations.z = hipsTranslations.z > 1 ? 1 : hipsTranslations.z;
hipsTranslations.z = hipsTranslations.z < -1 ? -1 : hipsTranslations.z;
// apply translations
MyAvatar.setSkeletonOffset(hipsTranslations);
// play footfall sound?
var producingFootstepSounds = (avatar.currentAnimation === avatar.selectedWalkBlend) && avatar.makesFootStepSounds;
if (motion.currentTransition !== nullTransition && avatar.makesFootStepSounds) {
if (motion.currentTransition.nextAnimation === avatar.selectedWalkBlend ||
motion.currentTransition.lastAnimation === avatar.selectedWalkBlend) {
producingFootstepSounds = true;
}
}
if (producingFootstepSounds) {
const QUARTER_CYCLE = 90;
const THREE_QUARTER_CYCLE = 270;
var ftWheelPosition = motion.frequencyTimeWheelPos;
if (motion.currentTransition !== nullTransition &&
motion.currentTransition.lastAnimation === avatar.selectedWalkBlend) {
ftWheelPosition = motion.currentTransition.lastFrequencyTimeWheelPos;
}
if (avatar.nextStep === LEFT && ftWheelPosition > THREE_QUARTER_CYCLE) {
avatar.makeFootStepSound();
} else if (avatar.nextStep === RIGHT && (ftWheelPosition < THREE_QUARTER_CYCLE && ftWheelPosition > QUARTER_CYCLE)) {
avatar.makeFootStepSound();
}
}
// apply joint rotations
for (jointName in avatar.currentAnimation.joints) {
var joint = walkAssets.animationReference.joints[jointName];
var jointRotations = undefined;
// ignore arms / head rotations if options are selected in the settings
if (avatar.armsFree && (joint.IKChain === "LeftArm" || joint.IKChain === "RightArm")) {
continue;
}
if (avatar.headFree && joint.IKChain === "Head") {
continue;
}
// if there's a live transition, blend the rotations with the last animation's rotations
if (motion.currentTransition !== nullTransition) {
jointRotations = motion.currentTransition.blendRotations(jointName,
motion.frequencyTimeWheelPos,
motion.lastDirection);
} else {
jointRotations = animationOperations.calculateRotations(jointName,
avatar.currentAnimation,
motion.frequencyTimeWheelPos,
motion.direction);
}
// apply angular velocity and speed induced leaning
if (jointName === "Hips") {
jointRotations.x += leanPitch;
jointRotations.z += leanRoll;
}
// apply rotations
MyAvatar.setJointRotation(jointName, Quat.fromVec3Degrees(jointRotations));
}
}
//
// walk.js
// version 1.25
//
// Created by David Wooldridge, June 2015
// Copyright © 2014 - 2015 High Fidelity, Inc.
//
// Animates an avatar using procedural animation techniques.
//
// Editing tools for animation data files available here: https://github.com/DaveDubUK/walkTools
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// animations, reach poses, reach pose parameters, transitions, transition parameters, sounds, image/s and reference files
const HIFI_PUBLIC_BUCKET = "https://hifi-public.s3.amazonaws.com/";
var pathToAssets = HIFI_PUBLIC_BUCKET + "procedural-animator/assets/";
Script.include([
"./libraries/walkConstants.js",
"./libraries/walkFilters.js",
"./libraries/walkApi.js",
pathToAssets + "walkAssets.js"
]);
// construct Avatar, Motion and (null) Transition
var avatar = new Avatar();
var motion = new Motion();
var nullTransition = new Transition();
motion.currentTransition = nullTransition;
// create settings (gets initial values from avatar)
Script.include("./libraries/walkSettings.js");
// Main loop
Script.update.connect(function(deltaTime) {
if (motion.isLive) {
// assess current locomotion state
motion.assess(deltaTime);
// decide which animation should be playing
selectAnimation();
// advance the animation cycle/s by the correct amount/s
advanceAnimations();
// update the progress of any live transitions
updateTransitions();
// apply translation and rotations
renderMotion();
// save this frame's parameters
motion.saveHistory();
}
});
// helper function for selectAnimation()
function setTransition(nextAnimation, playTransitionReachPoses) {
var lastTransition = motion.currentTransition;
var lastAnimation = avatar.currentAnimation;
// if already transitioning from a blended walk need to maintain the previous walk's direction
if (lastAnimation.lastDirection) {
switch(lastAnimation.lastDirection) {
case FORWARDS:
lastAnimation = avatar.selectedWalk;
break;
case BACKWARDS:
lastAnimation = avatar.selectedWalkBackwards;
break;
case LEFT:
lastAnimation = avatar.selectedSideStepLeft;
break;
case RIGHT:
lastAnimation = avatar.selectedSideStepRight;
break;
}
}
motion.currentTransition = new Transition(nextAnimation, lastAnimation, lastTransition, playTransitionReachPoses);
avatar.currentAnimation = nextAnimation;
// reset default first footstep
if (nextAnimation === avatar.selectedWalkBlend && lastTransition === nullTransition) {
avatar.nextStep = RIGHT;
}
}
// fly animation blending: smoothing / damping filters
const FLY_BLEND_DAMPING = 50;
var flyUpFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING);
var flyDownFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING);
var flyForwardFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING);
var flyBackwardFilter = filter.createAveragingFilter(FLY_BLEND_DAMPING);
// select / blend the appropriate animation for the current state of motion
function selectAnimation() {
var playTransitionReachPoses = true;
// select appropriate animation. create transitions where appropriate
switch (motion.nextState) {
case STATIC: {
if (avatar.distanceFromSurface < ON_SURFACE_THRESHOLD &&
avatar.currentAnimation !== avatar.selectedIdle) {
setTransition(avatar.selectedIdle, playTransitionReachPoses);
} else if (!(avatar.distanceFromSurface < ON_SURFACE_THRESHOLD) &&
avatar.currentAnimation !== avatar.selectedHover) {
setTransition(avatar.selectedHover, playTransitionReachPoses);
}
motion.state = STATIC;
avatar.selectedWalkBlend.lastDirection = NONE;
break;
}
case SURFACE_MOTION: {
// walk transition reach poses are currently only specified for starting to walk forwards
playTransitionReachPoses = (motion.direction === FORWARDS);
var isAlreadyWalking = (avatar.currentAnimation === avatar.selectedWalkBlend);
switch (motion.direction) {
case FORWARDS:
if (avatar.selectedWalkBlend.lastDirection !== FORWARDS) {
animationOperations.deepCopy(avatar.selectedWalk, avatar.selectedWalkBlend);
avatar.calibration.strideLength = avatar.selectedWalk.calibration.strideLength;
}
avatar.selectedWalkBlend.lastDirection = FORWARDS;
break;
case BACKWARDS:
if (avatar.selectedWalkBlend.lastDirection !== BACKWARDS) {
animationOperations.deepCopy(avatar.selectedWalkBackwards, avatar.selectedWalkBlend);
avatar.calibration.strideLength = avatar.selectedWalkBackwards.calibration.strideLength;
}
avatar.selectedWalkBlend.lastDirection = BACKWARDS;
break;
case LEFT:
animationOperations.deepCopy(avatar.selectedSideStepLeft, avatar.selectedWalkBlend);
avatar.selectedWalkBlend.lastDirection = LEFT;
avatar.calibration.strideLength = avatar.selectedSideStepLeft.calibration.strideLength;
break
case RIGHT:
animationOperations.deepCopy(avatar.selectedSideStepRight, avatar.selectedWalkBlend);
avatar.selectedWalkBlend.lastDirection = RIGHT;
avatar.calibration.strideLength = avatar.selectedSideStepRight.calibration.strideLength;
break;
default:
// condition occurs when the avi goes through the floor due to collision hull errors
animationOperations.deepCopy(avatar.selectedWalk, avatar.selectedWalkBlend);
avatar.selectedWalkBlend.lastDirection = FORWARDS;
avatar.calibration.strideLength = avatar.selectedWalk.calibration.strideLength;
break;
}
if (!isAlreadyWalking && !motion.isComingToHalt) {
setTransition(avatar.selectedWalkBlend, playTransitionReachPoses);
}
motion.state = SURFACE_MOTION;
break;
}
case AIR_MOTION: {
// blend the up, down, forward and backward flying animations relative to motion speed and direction
animationOperations.zeroAnimation(avatar.selectedFlyBlend);
// calculate influences based on velocity and direction
var velocityMagnitude = Vec3.length(motion.velocity);
var verticalProportion = motion.velocity.y / velocityMagnitude;
var thrustProportion = motion.velocity.z / velocityMagnitude / 2;
// directional components
var upComponent = motion.velocity.y > 0 ? verticalProportion : 0;
var downComponent = motion.velocity.y < 0 ? -verticalProportion : 0;
var forwardComponent = motion.velocity.z < 0 ? -thrustProportion : 0;
var backwardComponent = motion.velocity.z > 0 ? thrustProportion : 0;
// smooth / damp directional components to add visual 'weight'
upComponent = flyUpFilter.process(upComponent);
downComponent = flyDownFilter.process(downComponent);
forwardComponent = flyForwardFilter.process(forwardComponent);
backwardComponent = flyBackwardFilter.process(backwardComponent);
// normalise directional components
var normaliser = upComponent + downComponent + forwardComponent + backwardComponent;
upComponent = upComponent / normaliser;
downComponent = downComponent / normaliser;
forwardComponent = forwardComponent / normaliser;
backwardComponent = backwardComponent / normaliser;
// blend animations proportionally
if (upComponent > 0) {
animationOperations.blendAnimation(avatar.selectedFlyUp,
avatar.selectedFlyBlend,
upComponent);
}
if (downComponent > 0) {
animationOperations.blendAnimation(avatar.selectedFlyDown,
avatar.selectedFlyBlend,
downComponent);
}
if (forwardComponent > 0) {
animationOperations.blendAnimation(avatar.selectedFly,
avatar.selectedFlyBlend,
Math.abs(forwardComponent));
}
if (backwardComponent > 0) {
animationOperations.blendAnimation(avatar.selectedFlyBackwards,
avatar.selectedFlyBlend,
Math.abs(backwardComponent));
}
if (avatar.currentAnimation !== avatar.selectedFlyBlend) {
setTransition(avatar.selectedFlyBlend, playTransitionReachPoses);
}
motion.state = AIR_MOTION;
avatar.selectedWalkBlend.lastDirection = NONE;
break;
}
} // end switch next state of motion
}
// determine the length of stride. advance the frequency time wheels. advance frequency time wheels for any live transitions
function advanceAnimations() {
var wheelAdvance = 0;
// turn the frequency time wheel
if (avatar.currentAnimation === avatar.selectedWalkBlend) {
// Using technique described here: http://www.gdcvault.com/play/1020583/Animation-Bootcamp-An-Indie-Approach
// wrap the stride length around a 'surveyor's wheel' twice and calculate the angular speed at the given (linear) speed
// omega = v / r , where r = circumference / 2 PI and circumference = 2 * stride length
var speed = Vec3.length(motion.velocity);
motion.frequencyTimeWheelRadius = avatar.calibration.strideLength / Math.PI;
var ftWheelAngularVelocity = speed / motion.frequencyTimeWheelRadius;
// calculate the degrees turned (at this angular speed) since last frame
wheelAdvance = filter.radToDeg(motion.deltaTime * ftWheelAngularVelocity);
} else {
// turn the frequency time wheel by the amount specified for this animation
wheelAdvance = filter.radToDeg(avatar.currentAnimation.calibration.frequency * motion.deltaTime);
}
if (motion.currentTransition !== nullTransition) {
// the last animation is still playing so we turn it's frequency time wheel to maintain the animation
if (motion.currentTransition.lastAnimation === motion.selectedWalkBlend) {
// if at a stop angle (i.e. feet now under the avi) hold the wheel position for remainder of transition
var tolerance = motion.currentTransition.lastFrequencyTimeIncrement + 0.1;
if ((motion.currentTransition.lastFrequencyTimeWheelPos >
(motion.currentTransition.stopAngle - tolerance)) &&
(motion.currentTransition.lastFrequencyTimeWheelPos <
(motion.currentTransition.stopAngle + tolerance))) {
motion.currentTransition.lastFrequencyTimeIncrement = 0;
}
}
motion.currentTransition.advancePreviousFrequencyTimeWheel(motion.deltaTime);
}
// avoid unnaturally fast walking when landing at speed - simulates skimming / skidding
if (Math.abs(wheelAdvance) > MAX_FT_WHEEL_INCREMENT) {
wheelAdvance = 0;
}
// advance the walk wheel the appropriate amount
motion.advanceFrequencyTimeWheel(wheelAdvance);
// walking? then see if it's a good time to measure the stride length (needs to be at least 97% of max walking speed)
const ALMOST_ONE = 0.97;
if (avatar.currentAnimation === avatar.selectedWalkBlend &&
(Vec3.length(motion.velocity) / MAX_WALK_SPEED > ALMOST_ONE)) {
var strideMaxAt = avatar.currentAnimation.calibration.strideMaxAt;
const TOLERANCE = 1.0;
if (motion.frequencyTimeWheelPos < (strideMaxAt + TOLERANCE) &&
motion.frequencyTimeWheelPos > (strideMaxAt - TOLERANCE) &&
motion.currentTransition === nullTransition) {
// measure and save stride length
var footRPos = MyAvatar.getJointPosition("RightFoot");
var footLPos = MyAvatar.getJointPosition("LeftFoot");
avatar.calibration.strideLength = Vec3.distance(footRPos, footLPos);
avatar.currentAnimation.calibration.strideLength = avatar.calibration.strideLength;
} else {
// use the previously saved value for stride length
avatar.calibration.strideLength = avatar.currentAnimation.calibration.strideLength;
}
} // end get walk stride length
}
// initialise a new transition. update progress of a live transition
function updateTransitions() {
if (motion.currentTransition !== nullTransition) {
// is this a new transition?
if (motion.currentTransition.progress === 0) {
// do we have overlapping transitions?
if (motion.currentTransition.lastTransition !== nullTransition) {
// is the last animation for the nested transition the same as the new animation?
if (motion.currentTransition.lastTransition.lastAnimation === avatar.currentAnimation) {
// then sync the nested transition's frequency time wheel for a smooth animation blend
motion.frequencyTimeWheelPos = motion.currentTransition.lastTransition.lastFrequencyTimeWheelPos;
}
}
}
if (motion.currentTransition.updateProgress() === TRANSITION_COMPLETE) {
motion.currentTransition = nullTransition;
}
}
}
// helper function for renderMotion(). calculate the amount to lean forwards (or backwards) based on the avi's velocity
var leanPitchSmoothingFilter = filter.createButterworthFilter();
function getLeanPitch() {
var leanProgress = 0;
if (motion.direction === DOWN ||
motion.direction === FORWARDS ||
motion.direction === BACKWARDS) {
leanProgress = -motion.velocity.z / TOP_SPEED;
}
// use filters to shape the walking acceleration response
leanProgress = leanPitchSmoothingFilter.process(leanProgress);
return PITCH_MAX * leanProgress;
}
// helper function for renderMotion(). calculate the angle at which to bank into corners whilst turning
var leanRollSmoothingFilter = filter.createButterworthFilter();
function getLeanRoll() {
var leanRollProgress = 0;
var linearContribution = 0;
const LOG_SCALER = 8;
if (Vec3.length(motion.velocity) > 0) {
linearContribution = (Math.log(Vec3.length(motion.velocity) / TOP_SPEED) + LOG_SCALER) / LOG_SCALER;
}
var angularContribution = Math.abs(motion.yawDelta) / DELTA_YAW_MAX;
leanRollProgress = linearContribution;
leanRollProgress *= angularContribution;
// shape the response curve
leanRollProgress = filter.bezier(leanRollProgress, {x: 1, y: 0}, {x: 1, y: 0});
// which way to lean?
var turnSign = (motion.yawDelta >= 0) ? 1 : -1;
if (motion.direction === BACKWARDS ||
motion.direction === LEFT) {
turnSign *= -1;
}
// filter progress
leanRollProgress = leanRollSmoothingFilter.process(turnSign * leanRollProgress);
return ROLL_MAX * leanRollProgress;
}
// animate the avatar using sine waves, geometric waveforms and harmonic generators
function renderMotion() {
// leaning in response to speed and acceleration
var leanPitch = motion.state === STATIC ? 0 : getLeanPitch();
var leanRoll = motion.state === STATIC ? 0 : getLeanRoll();
var lastDirection = motion.lastDirection;
// hips translations from currently playing animations
var hipsTranslations = {x:0, y:0, z:0};
if (motion.currentTransition !== nullTransition) {
// maintain previous direction when transitioning from a walk
if (motion.currentTransition.lastAnimation === avatar.selectedWalkBlend) {
motion.lastDirection = motion.currentTransition.lastDirection;
}
hipsTranslations = motion.currentTransition.blendTranslations(motion.frequencyTimeWheelPos,
motion.lastDirection);
} else {
hipsTranslations = animationOperations.calculateTranslations(avatar.currentAnimation,
motion.frequencyTimeWheelPos,
motion.direction);
}
// factor any leaning into the hips offset
hipsTranslations.z += avatar.calibration.hipsToFeet * Math.sin(filter.degToRad(leanPitch));
hipsTranslations.x += avatar.calibration.hipsToFeet * Math.sin(filter.degToRad(leanRoll));
// ensure skeleton offsets are within the 1m limit
hipsTranslations.x = hipsTranslations.x > 1 ? 1 : hipsTranslations.x;
hipsTranslations.x = hipsTranslations.x < -1 ? -1 : hipsTranslations.x;
hipsTranslations.y = hipsTranslations.y > 1 ? 1 : hipsTranslations.y;
hipsTranslations.y = hipsTranslations.y < -1 ? -1 : hipsTranslations.y;
hipsTranslations.z = hipsTranslations.z > 1 ? 1 : hipsTranslations.z;
hipsTranslations.z = hipsTranslations.z < -1 ? -1 : hipsTranslations.z;
// apply translations
MyAvatar.setSkeletonOffset(hipsTranslations);
// play footfall sound?
var producingFootstepSounds = (avatar.currentAnimation === avatar.selectedWalkBlend) && avatar.makesFootStepSounds;
if (motion.currentTransition !== nullTransition && avatar.makesFootStepSounds) {
if (motion.currentTransition.nextAnimation === avatar.selectedWalkBlend ||
motion.currentTransition.lastAnimation === avatar.selectedWalkBlend) {
producingFootstepSounds = true;
}
}
if (producingFootstepSounds) {
const QUARTER_CYCLE = 90;
const THREE_QUARTER_CYCLE = 270;
var ftWheelPosition = motion.frequencyTimeWheelPos;
if (motion.currentTransition !== nullTransition &&
motion.currentTransition.lastAnimation === avatar.selectedWalkBlend) {
ftWheelPosition = motion.currentTransition.lastFrequencyTimeWheelPos;
}
if (avatar.nextStep === LEFT && ftWheelPosition > THREE_QUARTER_CYCLE) {
avatar.makeFootStepSound();
} else if (avatar.nextStep === RIGHT && (ftWheelPosition < THREE_QUARTER_CYCLE && ftWheelPosition > QUARTER_CYCLE)) {
avatar.makeFootStepSound();
}
}
// apply joint rotations
for (jointName in avatar.currentAnimation.joints) {
var joint = walkAssets.animationReference.joints[jointName];
var jointRotations = undefined;
// ignore arms / head rotations if options are selected in the settings
if (avatar.armsFree && (joint.IKChain === "LeftArm" || joint.IKChain === "RightArm")) {
continue;
}
if (avatar.headFree && joint.IKChain === "Head") {
continue;
}
// if there's a live transition, blend the rotations with the last animation's rotations
if (motion.currentTransition !== nullTransition) {
jointRotations = motion.currentTransition.blendRotations(jointName,
motion.frequencyTimeWheelPos,
motion.lastDirection);
} else {
jointRotations = animationOperations.calculateRotations(jointName,
avatar.currentAnimation,
motion.frequencyTimeWheelPos,
motion.direction);
}
// apply angular velocity and speed induced leaning
if (jointName === "Hips") {
jointRotations.x += leanPitch;
jointRotations.z += leanRoll;
}
// apply rotations
MyAvatar.setJointRotation(jointName, Quat.fromVec3Degrees(jointRotations));
}
}