1482 lines
No EOL
62 KiB
JavaScript
1482 lines
No EOL
62 KiB
JavaScript
//
|
|
// Created by Luis Cuenca on 1/31/18
|
|
// Copyright 2018 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
|
|
//
|
|
|
|
/* jslint bitwise: true */
|
|
|
|
/* global Script, Overlays, Controller, MyAvatar, QUAT, VEC3, AvatarList, Xform
|
|
*/
|
|
|
|
Script.include("/~/system/libraries/Xform.js");
|
|
Script.include(Script.resolvePath("./VectorMath.js"));
|
|
|
|
(function(){
|
|
var SHOW_AVATAR = true;
|
|
var SHOW_DEBUG_SHAPES = false;
|
|
var SHOW_SOLID_SHAPES = false;
|
|
var SHOW_DUMMY_JOINTS = false;
|
|
var USE_COLLISIONS = true;
|
|
|
|
var HAPTIC_TOUCH_STRENGTH = 0.25;
|
|
var HAPTIC_TOUCH_DURATION = 10.0;
|
|
var HAPTIC_SLOPE = 0.18;
|
|
|
|
var LEFT_HAND = 0;
|
|
var RIGHT_HAND = 1;
|
|
|
|
var HAPTIC_THRESHOLD = 40;
|
|
|
|
var FLOW_JOINT_PREFIX = "flow";
|
|
var SIM_JOINT_PREFIX = "sim";
|
|
|
|
var JOINT_COLLISION_PREFIX = "joint_";
|
|
var HAND_COLLISION_PREFIX = "hand_";
|
|
var HAND_COLLISION_RADIUS = 0.03;
|
|
var HAND_TOUCHING_DISTANCE = 2.0;
|
|
|
|
var COLLISION_SHAPES_LIMIT = 4;
|
|
|
|
var DUMMY_KEYWORD = "Extra";
|
|
var DUMMY_JOINT_COUNT = 8;
|
|
var DUMMY_JOINT_DISTANCE = 0.05;
|
|
|
|
var ISOLATED_JOINT_STIFFNESS = 0.85;
|
|
var ISOLATED_JOINT_LENGTH = 0.05;
|
|
|
|
// Joint groups by keyword
|
|
|
|
|
|
var FLOW_JOINT_KEYWORDS = [];
|
|
var FLOW_JOINT_DATA = {};
|
|
|
|
|
|
var DEFAULT_JOINT_SETTINGS = { "get": function() {
|
|
return {"active": true, "stiffness": 0.0, "gravity": -0.0096 ,"damping": 0.85, "inertia": 0.8, "delta": 0.55, "radius": 0.01};
|
|
}};
|
|
|
|
var DEFAULT_COLLISION_SETTINGS = {type: "sphere", radius: 0.05, offset: {x: 0, y: 0, z: 0}};
|
|
|
|
var PRESET_FLOW_DATA = {
|
|
"hair": {"active": true, "stiffness": 0.0, "gravity": -0.0096 ,"damping": 0.85, "inertia": 0.8, "delta": 0.55, "radius": 0.01},
|
|
"skirt": {"active": true, "stiffness": 0.0, "gravity": -0.0096 ,"damping": 0.85, "inertia": 0.25, "delta": 0.45, "radius": 0.01},
|
|
"breasts": {"active": true, "stiffness": 1, "gravity": -0.0096 ,"damping": 0.65, "inertia": 0.8, "delta": 0.45, "radius": 0.01}
|
|
};
|
|
|
|
var PRESET_COLLISION_DATA = {
|
|
"Head": {type: "sphere", radius: 0.08, offset: {x: 0, y: 0.06, z: 0.00}},
|
|
"RightArm": {type: "sphere", radius: 0.05, offset: {x: 0.0, y: 0.02, z: 0.00}},
|
|
"LeftArm": {type: "sphere", radius: 0.05, offset: {x: 0.0, y: 0.02, z: 0.00}},
|
|
"Spine2": {type: "sphere", radius: 0.09, offset: {x: 0, y: 0.04, z: 0.00}}
|
|
};
|
|
|
|
var CUSTOM_FLOW_DATA, CUSTOM_COLLISION_DATA;
|
|
|
|
// CUSTOM DATA STARTS HERE
|
|
|
|
|
|
CUSTOM_FLOW_DATA = {
|
|
"tail": {
|
|
"active": true,
|
|
"stiffness": 0,
|
|
"radius": 0.01,
|
|
"gravity": -0.0096,
|
|
"damping": 0.85,
|
|
"inertia": 0.8,
|
|
"delta": 0.55
|
|
},
|
|
"cape": {
|
|
"active": true,
|
|
"stiffness": 0,
|
|
"radius": 0.01,
|
|
"gravity": -0.0096,
|
|
"damping": 0.85,
|
|
"inertia": 0.8,
|
|
"delta": 0.55
|
|
},
|
|
"breast": {
|
|
"active": true,
|
|
"stiffness": 0,
|
|
"radius": 0.01,
|
|
"gravity": -0.0096,
|
|
"damping": 0.85,
|
|
"inertia": 0.8,
|
|
"delta": 0.55
|
|
},
|
|
"ear": {
|
|
"active": true,
|
|
"stiffness": 0,
|
|
"radius": 0.01,
|
|
"gravity": -0.0096,
|
|
"damping": 0.85,
|
|
"inertia": 0.8,
|
|
"delta": 0.55
|
|
}
|
|
};
|
|
|
|
CUSTOM_COLLISION_DATA = {
|
|
"Spine2": {
|
|
"type": "sphere",
|
|
"radius": 0.14,
|
|
"offset": {
|
|
"x": 0,
|
|
"y": 0.2,
|
|
"z": 0
|
|
}
|
|
},
|
|
"RightArm": {
|
|
"type": "sphere",
|
|
"radius": 0.03,
|
|
"offset": {
|
|
"x": 0,
|
|
"y": 0.02,
|
|
"z": 0
|
|
}
|
|
},
|
|
"LeftArm": {
|
|
"type": "sphere",
|
|
"radius": 0.03,
|
|
"offset": {
|
|
"x": 0,
|
|
"y": 0.02,
|
|
"z": 0
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
// CUSTOM DATA ENDS HERE
|
|
|
|
var HAND_COLLISION_JOINTS = ["RightHandMiddle1", "RightHandThumb3", "LeftHandMiddle1", "LeftHandThumb3","RightHandMiddle3", "LeftHandMiddle3"];
|
|
|
|
if (SHOW_DUMMY_JOINTS) {
|
|
FLOW_JOINT_KEYWORDS.push(DUMMY_KEYWORD);
|
|
FLOW_JOINT_DATA[DUMMY_KEYWORD] = DEFAULT_JOINT_SETTINGS.get();
|
|
}
|
|
|
|
var FlowDebug = function() {
|
|
var self = this;
|
|
this.debugLines = {};
|
|
this.debugSpheres = {};
|
|
this.debugCubes = {};
|
|
this.showDebugShapes = false;
|
|
this.showSolidShapes = false;
|
|
|
|
this.setDebugCube = function(cubeName, cubePosition, cubeRotation, cubeDimensions, shapeColor, forceRendering) {
|
|
var doRender = self.showDebugShapes || forceRendering;
|
|
if (!doRender) return;
|
|
var position = cubePosition ? cubePosition : {x: 0, y: 0, z: 0};
|
|
var rotation = cubeRotation ? cubeRotation : {x: 0, y: 0, z: 0, w: 0};
|
|
var dimensions = cubeDimensions ? cubeDimensions : {x: 1, y: 1, z: 1};
|
|
var color = shapeColor !== undefined ? shapeColor : { red: 0, green: 255, blue: 255 };
|
|
if (self.debugCubes[cubeName] !== undefined) {
|
|
Overlays.editOverlay(self.debugCubes[cubeName], {
|
|
position: position,
|
|
rotation: rotation,
|
|
dimensions: dimensions,
|
|
color: color,
|
|
solid: self.showSolidShapes,
|
|
visible: true
|
|
});
|
|
} else {
|
|
self.debugCubes[cubeName] = Overlays.addOverlay("cube", {
|
|
position: position,
|
|
rotation: rotation,
|
|
dimensions: dimensions,
|
|
color: color,
|
|
solid: self.showSolidShapes,
|
|
visible: true
|
|
});
|
|
}
|
|
};
|
|
|
|
this.setDebugLine = function(lineName, startPosition, endPosition, shapeColor, forceRendering) {
|
|
var doRender = self.showDebugShapes || forceRendering;
|
|
if (!doRender) return;
|
|
var start = startPosition ? startPosition : {x: 0, y: 0, z: 0};
|
|
var end = endPosition ? endPosition : {x: 0, y: 1, z: 0};
|
|
var color = shapeColor ? shapeColor : { red: 0, green: 255, blue: 255 };
|
|
if (self.debugLines[lineName] !== undefined) {
|
|
Overlays.editOverlay(self.debugLines[lineName], {
|
|
color: color,
|
|
start: start,
|
|
end: end,
|
|
visible: true
|
|
});
|
|
} else {
|
|
self.debugLines[lineName] = Overlays.addOverlay("line3d", {
|
|
color: color,
|
|
start: start,
|
|
end: end,
|
|
visible: true
|
|
});
|
|
}
|
|
};
|
|
|
|
this.setDebugSphere = function(sphereName, pos, diameter, shapeColor, forceRendering) {
|
|
var doRender = self.showDebugShapes || forceRendering;
|
|
if (!doRender) return;
|
|
var scale = diameter ? diameter : 0.01;
|
|
var color = shapeColor ? shapeColor : { red: 255, green: 0, blue: 255 };
|
|
if (self.debugSpheres[sphereName] !== undefined) {
|
|
Overlays.editOverlay(self.debugSpheres[sphereName], {
|
|
color: color,
|
|
position: pos,
|
|
scale: {x:scale, y:scale, z:scale},
|
|
solid: self.showSolidShapes,
|
|
visible: true
|
|
});
|
|
} else {
|
|
self.debugSpheres[sphereName] = Overlays.addOverlay("sphere", {
|
|
color: color,
|
|
position: pos,
|
|
scale: {x:scale, y:scale, z:scale},
|
|
solid: self.showSolidShapes,
|
|
visible: true
|
|
});
|
|
}
|
|
};
|
|
|
|
this.deleteSphere = function(name) {
|
|
Overlays.deleteOverlay(self.debugSpheres[name]);
|
|
self.debugSpheres[name] = undefined;
|
|
};
|
|
|
|
this.deleteLine = function(name) {
|
|
Overlays.deleteOverlay(self.debugLines[name]);
|
|
self.debugLines[name] = undefined;
|
|
};
|
|
|
|
this.deleteCube = function(name) {
|
|
Overlays.deleteOverlay(self.debugCubes[name]);
|
|
self.debugCubes[name] = undefined;
|
|
};
|
|
|
|
this.cleanup = function() {
|
|
for (var lineName in self.debugLines) {
|
|
if (lineName !== undefined) {
|
|
self.deleteLine(lineName);
|
|
}
|
|
}
|
|
for (var sphereName in self.debugSpheres) {
|
|
if (sphereName !== undefined) {
|
|
self.deleteSphere(sphereName);
|
|
}
|
|
}
|
|
for (var cubeName in self.debugCubes) {
|
|
if (cubeName!== undefined) {
|
|
self.deleteCube(cubeName);
|
|
}
|
|
}
|
|
self.debugLines = {};
|
|
self.debugSpheres = {};
|
|
self.debugCubes = {};
|
|
};
|
|
|
|
this.setVisible = function(isVisible) {
|
|
self.showDebugShapes = isVisible;
|
|
for (var lineName in self.debugLines) {
|
|
if (lineName !== undefined) {
|
|
Overlays.editOverlay(self.debugLines[lineName], {
|
|
visible: isVisible
|
|
});
|
|
}
|
|
}
|
|
for (var sphereName in self.debugSpheres) {
|
|
if (sphereName !== undefined) {
|
|
Overlays.editOverlay(self.debugSpheres[sphereName], {
|
|
visible: isVisible
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
this.setSolid = function(isSolid) {
|
|
self.showSolidShapes = isSolid;
|
|
for (var lineName in self.debugLines) {
|
|
if (lineName !== undefined) {
|
|
Overlays.editOverlay(self.debugLines[lineName], {
|
|
solid: isSolid
|
|
});
|
|
}
|
|
}
|
|
for (var sphereName in self.debugSpheres) {
|
|
if (sphereName !== undefined) {
|
|
Overlays.editOverlay(self.debugSpheres[sphereName], {
|
|
solid: isSolid
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
|
|
var FlowHandSystem = function() {
|
|
var self = this;
|
|
this.avatarIds = [];
|
|
this.avatarHands = [];
|
|
this.lastAvatarCount = 0;
|
|
this.leftTriggerValue = 0;
|
|
this.rightTriggerValue = 0;
|
|
this.isLeftHandTouching = false;
|
|
this.leftHandTouchDelta = 0;
|
|
this.leftHandTouchThreshold = 0;
|
|
this.isRightHandTouching = false;
|
|
this.rightHandTouchDelta = 0;
|
|
this.rightHandTouchThreshold = 0;
|
|
|
|
this.getNearbyAvatars = function(distance) {
|
|
return AvatarList.getAvatarIdentifiers().filter(function(avatarID){
|
|
if (!avatarID) {
|
|
return false;
|
|
}
|
|
var avatar = AvatarList.getAvatar(avatarID);
|
|
var avatarPosition = avatar && avatar.position;
|
|
if (!avatarPosition) {
|
|
return false;
|
|
}
|
|
return VEC3.distance(avatarPosition, MyAvatar.position) < distance;
|
|
});
|
|
};
|
|
|
|
this.setScale = function(scale) {
|
|
for (var j = 0; j < self.avatarHands.length; j++) {
|
|
for (var i = 0; i < HAND_COLLISION_JOINTS.length; i++) {
|
|
var side = HAND_COLLISION_JOINTS[i];
|
|
self.avatarHands[j][side].radius = self.avatarHands[j][side].initialRadius * scale;
|
|
}
|
|
}
|
|
};
|
|
|
|
this.update = function() {
|
|
var nearbyAvatars = self.getNearbyAvatars(HAND_TOUCHING_DISTANCE);
|
|
nearbyAvatars.push(MyAvatar.SELF_ID);
|
|
|
|
nearbyAvatars.forEach(function(avatarID) {
|
|
|
|
var avatar = AvatarList.getAvatar(avatarID);
|
|
var avatarIndex = self.avatarIds.indexOf(avatarID);
|
|
var i, side;
|
|
if (avatarIndex === -1) {
|
|
var newHands = {};
|
|
for (i = 0; i < HAND_COLLISION_JOINTS.length; i++) {
|
|
side = HAND_COLLISION_JOINTS[i];
|
|
var jointId = avatar.getJointIndex(side);
|
|
var name = avatarID + "_" + HAND_COLLISION_PREFIX + side;
|
|
var handCollisionSettings = {type: "sphere", radius: HAND_COLLISION_RADIUS, offset: {x: 0, y: 0, z: 0}, avatarId: avatarID};
|
|
newHands[side] = new FlowCollisionSphere(name, jointId, side, handCollisionSettings);
|
|
}
|
|
self.avatarHands.push(newHands);
|
|
avatarIndex = self.avatarIds.length;
|
|
self.avatarIds.push(avatarID);
|
|
}
|
|
|
|
for (i = 0; i < HAND_COLLISION_JOINTS.length; i++) {
|
|
side = HAND_COLLISION_JOINTS[i];
|
|
self.avatarHands[avatarIndex][side].update();
|
|
}
|
|
});
|
|
|
|
if (nearbyAvatars.length < self.lastAvatarCount) {
|
|
var avatarsToUntrack = [];
|
|
for (var i = 0; i < self.avatarIds.length; i++) {
|
|
var index = nearbyAvatars.indexOf(self.avatarIds[i]);
|
|
if (index == -1) {
|
|
avatarsToUntrack.push(i);
|
|
}
|
|
}
|
|
avatarsToUntrack.sort();
|
|
for (i = 0; i < avatarsToUntrack.length; i++) {
|
|
self.avatarIds.splice(avatarsToUntrack[i]-i, 1);
|
|
self.avatarHands.splice(avatarsToUntrack[i]-i, 1);
|
|
}
|
|
}
|
|
self.lastAvatarCount = nearbyAvatars.length;
|
|
};
|
|
|
|
this.computeCollision = function(collisions) {
|
|
var collisionData = new FlowCollisionData();
|
|
if (collisions.length > 1) {
|
|
for (var i = 0; i < collisions.length; i++) {
|
|
collisionData.offset += collisions[i].offset;
|
|
collisionData.normal = VEC3.sum(collisionData.normal, VEC3.multiply(collisions[i].normal, collisions[i].distance));
|
|
collisionData.position = VEC3.sum(collisionData.position, collisions[i].position);
|
|
collisionData.radius += collisions[i].radius;
|
|
collisionData.distance += collisions[i].distance;
|
|
}
|
|
collisionData.offset = collisionData.offset/collisions.length;
|
|
collisionData.radius = VEC3.length(collisionData.normal)/2;
|
|
collisionData.normal = VEC3.normalize(collisionData.normal);
|
|
collisionData.position = VEC3.multiply(collisionData.position, 1/collisions.length);
|
|
collisionData.distance = collisionData.distance/collisions.length;
|
|
} else if (collisions.length == 1){
|
|
collisionData = collisions[0];
|
|
}
|
|
collisionData.collisionCount = collisions.length;
|
|
return collisionData;
|
|
};
|
|
|
|
this.manageHapticPulse = function() {
|
|
if (self.isLeftHandTouching) {
|
|
self.leftHandTouchDelta += HAPTIC_SLOPE;
|
|
self.leftHandTouchThreshold = 0;
|
|
Controller.triggerHapticPulse(HAPTIC_TOUCH_STRENGTH/self.leftHandTouchDelta, HAPTIC_TOUCH_DURATION, LEFT_HAND);
|
|
} else {
|
|
if (self.leftHandTouchThreshold++ > HAPTIC_THRESHOLD) {
|
|
self.leftHandTouchDelta = 0;
|
|
}
|
|
}
|
|
if (self.isRightHandTouching) {
|
|
self.rightHandTouchDelta += HAPTIC_SLOPE;
|
|
self.rightHandTouchThreshold = 0;
|
|
Controller.triggerHapticPulse(HAPTIC_TOUCH_STRENGTH/self.rightHandTouchDelta, HAPTIC_TOUCH_DURATION, RIGHT_HAND);
|
|
} else {
|
|
if (self.rightHandTouchThreshold++ > HAPTIC_THRESHOLD) {
|
|
self.rightHandTouchDelta = 0;
|
|
}
|
|
}
|
|
};
|
|
|
|
this.checkThreadCollisions = function(thread) {
|
|
|
|
var threadCollisionData = Array(thread.joints.length);
|
|
for (var i = 0; i < threadCollisionData.length; i++) {
|
|
threadCollisionData[i] = [];
|
|
}
|
|
self.isRightHandTouching = false;
|
|
self.isLeftHandTouching = false;
|
|
for (i = 0; i < self.avatarHands.length; i++) {
|
|
for (var j = 0; j < HAND_COLLISION_JOINTS.length; j++) {
|
|
var side = HAND_COLLISION_JOINTS[j];
|
|
var rootCollision = self.avatarHands[i][side].checkCollision(thread.positions[0], thread.radius);
|
|
var collisionData = [rootCollision];
|
|
var tooFar = rootCollision.distance > (thread.length + rootCollision.radius);
|
|
if (!tooFar) {
|
|
for (var k = 1; k < thread.joints.length; k++) {
|
|
var prevCollision = collisionData[k-1];
|
|
var nextCollision = self.avatarHands[i][side].checkCollision(thread.positions[k], thread.radius);
|
|
collisionData.push(nextCollision);
|
|
var isTouching = false;
|
|
if (prevCollision.offset > 0) {
|
|
if (k == 1) {
|
|
threadCollisionData[k-1].push(prevCollision);
|
|
isTouching = true;
|
|
}
|
|
} else if (nextCollision.offset > 0) {
|
|
threadCollisionData[k].push(nextCollision);
|
|
isTouching = true;
|
|
} else {
|
|
var segmentCollision = self.avatarHands[i][side].checkSegmentCollision(thread.positions[k-1], thread.positions[k], prevCollision, nextCollision);
|
|
if (segmentCollision.offset > 0) {
|
|
threadCollisionData[k-1].push(segmentCollision);
|
|
threadCollisionData[k].push(segmentCollision);
|
|
isTouching = true;
|
|
}
|
|
}
|
|
|
|
if (isTouching) {
|
|
if (side.indexOf("RightHand") > -1) {
|
|
self.isRightHandTouching = true;
|
|
} else {
|
|
self.isLeftHandTouching = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
self.manageHapticPulse();
|
|
|
|
var collisionResult = [];
|
|
for (i = 0; i < thread.joints.length; i++) {
|
|
collisionResult.push(self.computeCollision(threadCollisionData[i]));
|
|
}
|
|
return collisionResult;
|
|
};
|
|
|
|
this.setRightTriggerValue = function(value) {
|
|
self.rightTriggerValue = value;
|
|
};
|
|
|
|
this.setLeftTriggerValue = function(value) {
|
|
self.leftTriggerValue = value;
|
|
};
|
|
};
|
|
|
|
var FlowCollisionSystem = function() {
|
|
var self = this;
|
|
this.collisionSpheres = [];
|
|
this.collisionCubes = [];
|
|
|
|
this.addCollisionSphere = function(name, jointIndex, jointName, settings) {
|
|
self.collisionSpheres.push(new FlowCollisionSphere(name, jointIndex, jointName, settings));
|
|
};
|
|
|
|
this.addCollisionCube = function(name, jointIndex, jointName, settings) {
|
|
self.collisionCubes.push(new FlowCollisionCube(name, jointIndex, jointName, settings));
|
|
};
|
|
|
|
this.addCollisionShape = function(jointIndex, jointName, settings) {
|
|
var name = JOINT_COLLISION_PREFIX + jointIndex;
|
|
switch(settings.type) {
|
|
case "sphere":
|
|
self.addCollisionSphere(name, jointIndex, jointName, settings);
|
|
break;
|
|
case "cube":
|
|
self.addCollisionCube(name, jointIndex, jointName, settings);
|
|
break;
|
|
}
|
|
};
|
|
|
|
this.addCollisionToJoint = function(jointName) {
|
|
if (self.collisionSpheres.length >= COLLISION_SHAPES_LIMIT) {
|
|
return false;
|
|
}
|
|
var jointIndex = MyAvatar.getJointIndex(jointName);
|
|
var collisionIndex = self.findCollisionWithJoint(jointIndex);
|
|
if (collisionIndex === -1) {
|
|
self.addCollisionShape(jointIndex, jointName, DEFAULT_COLLISION_SETTINGS);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
this.removeCollisionFromJoint = function(jointName) {
|
|
var jointIndex = MyAvatar.getJointIndex(jointName);
|
|
var collisionIndex = self.findCollisionWithJoint(jointIndex);
|
|
if (collisionIndex > -1) {
|
|
self.collisionSpheres[collisionIndex].clean();
|
|
self.collisionSpheres.splice(collisionIndex, 1);
|
|
}
|
|
};
|
|
|
|
this.update = function() {
|
|
for (var i = 0; i < self.collisionCubes.length; i++) {
|
|
self.collisionCubes[i].update();
|
|
}
|
|
for (i = 0; i < self.collisionSpheres.length; i++) {
|
|
self.collisionSpheres[i].update();
|
|
}
|
|
};
|
|
|
|
this.computeCollision = function(collisions) {
|
|
var collisionData = new FlowCollisionData();
|
|
if (collisions.length > 1) {
|
|
for (var i = 0; i < collisions.length; i++) {
|
|
collisionData.offset += collisions[i].offset;
|
|
collisionData.normal = VEC3.sum(collisionData.normal, VEC3.multiply(collisions[i].normal, collisions[i].distance));
|
|
collisionData.position = VEC3.sum(collisionData.position, collisions[i].position);
|
|
collisionData.radius += collisions[i].radius;
|
|
collisionData.distance += collisions[i].distance;
|
|
}
|
|
collisionData.offset = collisionData.offset/collisions.length;
|
|
collisionData.radius = VEC3.length(collisionData.normal)/2;
|
|
collisionData.normal = VEC3.normalize(collisionData.normal);
|
|
collisionData.position = VEC3.multiply(collisionData.position, 1/collisions.length);
|
|
collisionData.distance = collisionData.distance/collisions.length;
|
|
} else if (collisions.length == 1){
|
|
collisionData = collisions[0];
|
|
}
|
|
collisionData.collisionCount = collisions.length;
|
|
return collisionData;
|
|
};
|
|
|
|
this.setScale = function(scale) {
|
|
for (var j = 0; j < self.collisionSpheres.length; j++) {
|
|
self.collisionSpheres[j].radius = self.collisionSpheres[j].initialRadius * scale;
|
|
self.collisionSpheres[j].offset = VEC3.multiply(self.collisionSpheres[j].initialOffset, scale);
|
|
}
|
|
};
|
|
|
|
this.checkThreadCollisions = function(thread, checkSegments) {
|
|
var threadCollisionData = Array(thread.joints.length);
|
|
for (var i = 0; i < threadCollisionData.length; i++) {
|
|
threadCollisionData[i] = [];
|
|
}
|
|
for (var j = 0; j < self.collisionSpheres.length; j++) {
|
|
|
|
var rootCollision = self.collisionSpheres[j].checkCollision(thread.positions[0], thread.radius);
|
|
var collisionData = [rootCollision];
|
|
var tooFar = rootCollision.distance > (thread.length + rootCollision.radius);
|
|
var nextCollision;
|
|
if (!tooFar) {
|
|
if (checkSegments) {
|
|
for (i = 1; i < thread.joints.length; i++) {
|
|
var prevCollision = collisionData[i-1];
|
|
nextCollision = self.collisionSpheres[j].checkCollision(thread.positions[i], thread.radius);
|
|
collisionData.push(nextCollision);
|
|
if (prevCollision.offset > 0) {
|
|
if (i == 1) {
|
|
threadCollisionData[i-1].push(prevCollision);
|
|
}
|
|
} else if (nextCollision.offset > 0) {
|
|
threadCollisionData[i].push(nextCollision);
|
|
} else {
|
|
var segmentCollision = self.collisionSpheres[j].checkSegmentCollision(thread.positions[i-1], thread.positions[i], prevCollision, nextCollision);
|
|
if (segmentCollision.offset > 0) {
|
|
threadCollisionData[i-1].push(segmentCollision);
|
|
threadCollisionData[i].push(segmentCollision);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (rootCollision.offset > 0) {
|
|
threadCollisionData[0].push(rootCollision);
|
|
}
|
|
for (i = 1; i < thread.joints.length; i++) {
|
|
nextCollision = self.collisionSpheres[j].checkCollision(thread.positions[i], thread.radius);
|
|
if (nextCollision.offset > 0) {
|
|
threadCollisionData[i].push(nextCollision);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var collisionResult = [];
|
|
for (i = 0; i < thread.joints.length; i++) {
|
|
collisionResult.push(self.computeCollision(threadCollisionData[i]));
|
|
}
|
|
return collisionResult;
|
|
};
|
|
|
|
this.findCollisionWithJoint = function (jointIndex) {
|
|
for (var i = 0; i < self.collisionSpheres.length; i++) {
|
|
if (self.collisionSpheres[i].jointIndex == jointIndex) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
};
|
|
|
|
this.modifyCollision = function(jointName, parameter, value) {
|
|
var jointIndex = MyAvatar.getJointIndex(jointName);
|
|
var collisionIndex = self.findCollisionWithJoint(jointIndex);
|
|
var avatarScale = MyAvatar.scale;
|
|
if (collisionIndex > -1) {
|
|
switch(parameter) {
|
|
case "radius": {
|
|
self.collisionSpheres[collisionIndex].initialRadius = value;
|
|
self.collisionSpheres[collisionIndex].radius = avatarScale*value;
|
|
break;
|
|
}
|
|
case "offset": {
|
|
var offset = self.collisionSpheres[collisionIndex].offset;
|
|
self.collisionSpheres[collisionIndex].initialOffset = {x: offset.x, y: value, z: offset.z};
|
|
self.collisionSpheres[collisionIndex].offset = VEC3.multiply(self.collisionSpheres[collisionIndex].initialOffset, avatarScale);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
this.getCollisionData = function() {
|
|
var collisionData = {};
|
|
for (var i = 0; i < self.collisionSpheres.length; i++) {
|
|
var data = {type: "sphere", radius: self.collisionSpheres[i].initialRadius, offset: self.collisionSpheres[i].initialOffset};
|
|
collisionData[self.collisionSpheres[i].jointName] = data;
|
|
}
|
|
return collisionData;
|
|
};
|
|
|
|
};
|
|
|
|
var FlowCollisionData = function(offset, position, radius, normal, distance) {
|
|
this.collisionCount = 0;
|
|
this.offset = offset !== undefined ? offset : 0;
|
|
this.position = position !== undefined ? position : {x: 0, y: 0, z: 0};
|
|
this.radius = radius !== undefined ? radius : 0;
|
|
this.normal = normal !== undefined ? normal : {x: 0, y: 0, z: 0};
|
|
this.distance = distance !== undefined ? distance : 0;
|
|
};
|
|
|
|
var FlowCollisionSphere = function(name, jointIndex, jointName, settings) {
|
|
var self = this;
|
|
this.name = name;
|
|
this.jointIndex = jointIndex;
|
|
this.jointName = jointName;
|
|
this.radius = this.initialRadius = settings.radius;
|
|
this.offset = settings.offset;
|
|
this.initialOffset = {x: self.offset.x, y: self.offset.y, z: self.offset.z};
|
|
this.attenuation = settings.attenuation;
|
|
this.avatarId = settings.avatarId;
|
|
|
|
this.position = {x:0, y:0, z:0};
|
|
|
|
this.update = function() {
|
|
if (self.avatarId !== undefined){
|
|
var avatar = AvatarList.getAvatar(self.avatarId);
|
|
self.position = avatar.getJointPosition(self.jointIndex);
|
|
} else {
|
|
self.position = MyAvatar.jointToWorldPoint(self.offset, self.jointIndex);
|
|
}
|
|
collisionDebug.setDebugSphere(self.name, self.position, 2*self.radius, {red: 200, green: 10, blue: 50});
|
|
if (self.attenuation && self.attenuation > 0) {
|
|
collisionDebug.setDebugSphere(self.name + "_att", self.position, 2*(self.radius + self.attenuation), {red: 120, green: 200, blue: 50});
|
|
}
|
|
};
|
|
|
|
this.checkCollision = function(point, radius) {
|
|
var centerToJoint = VEC3.subtract(point, self.position);
|
|
var distance = VEC3.length(centerToJoint) - radius;
|
|
var offset = self.radius - distance;
|
|
var collisionData = new FlowCollisionData(offset, self.position, self.radius, VEC3.normalize(centerToJoint), distance);
|
|
return collisionData;
|
|
};
|
|
|
|
this.checkSegmentCollision = function(point1, point2, pointCollision1, pointCollision2) {
|
|
var collisionData = new FlowCollisionData();
|
|
var segment = VEC3.subtract(point2, point1);
|
|
var segmentLength = VEC3.length(segment);
|
|
var maxDistance = Math.sqrt(Math.pow(pointCollision1.radius,2) + Math.pow(segmentLength,2));
|
|
if (pointCollision1.distance < maxDistance && pointCollision2.distance < maxDistance) {
|
|
var segmentPercent = pointCollision1.distance/(pointCollision1.distance + pointCollision2.distance);
|
|
var collisionPoint = VEC3.sum(point1, VEC3.multiply(segment, segmentPercent));
|
|
var centerToSegment = VEC3.subtract(collisionPoint, self.position);
|
|
var distance = VEC3.length(centerToSegment);
|
|
if (distance < self.radius) {
|
|
var offset = self.radius - distance;
|
|
collisionData = new FlowCollisionData(offset, self.position, self.radius, VEC3.normalize(centerToSegment), distance);
|
|
}
|
|
}
|
|
return collisionData;
|
|
};
|
|
|
|
this.clean = function() {
|
|
collisionDebug.deleteSphere(self.name);
|
|
};
|
|
};
|
|
|
|
var FlowCollisionCube = function(name, jointIndex, settings) {
|
|
var self = this;
|
|
this.name = name;
|
|
this.jointIndex = jointIndex;
|
|
this.dimensions = settings.dimensions;
|
|
this.offset = settings.offset;
|
|
this.attenuation = settings.attenuation;
|
|
this.avatarId = settings.avatarId;
|
|
|
|
this.position = {x:0, y:0, z:0};
|
|
this.rotation = {x:0, y:0, z:0, w:0};
|
|
|
|
this.update = function() {
|
|
if (self.avatarId !== undefined){
|
|
var avatar = AvatarList.getAvatar(self.avatarId);
|
|
self.position = avatar.getJointPosition(self.jointIndex);
|
|
self.rotation = avatar.getJointRotation(self.jointIndex);
|
|
}
|
|
collisionDebug.setDebugCube(self.name, self.position, self.rotation, self.dimensions, {red: 200, green: 10, blue: 50});
|
|
};
|
|
|
|
this.checkCollision = function(point, radius) {
|
|
var localPoint = MyAvatar.worldToJointPoint(point, self.jointIndex);
|
|
var localPosition = MyAvatar.worldToJointPoint(self.position, self.jointIndex);
|
|
var centerToJoint = VEC3.subtract(localPoint, localPosition);
|
|
var offsets = { x: (self.dimensions.x/2) - Math.abs(centerToJoint.x),
|
|
y: (self.dimensions.y/2) - Math.abs(centerToJoint.y),
|
|
z: (self.dimensions.z/2) - Math.abs(centerToJoint.z)};
|
|
|
|
radius = 0;
|
|
var normal = {x: 0, y: 0, z: 0};
|
|
var position = {x: 0, y: 0, z: 0};
|
|
var offset = 0;
|
|
if (offsets.x > 0 && offsets.y > 0 && offsets.z > 0) {
|
|
var offsetOrder = [{"coordinate": "x", "value": offsets.x}, {"coordinate": "y", "value": offsets.y}, {"coordinate": "z", "value": offsets.z}];
|
|
offsetOrder.sort(function(a, b){
|
|
return a.value - b.value;
|
|
});
|
|
switch (offsetOrder[0].coordinate) {
|
|
case "x" :
|
|
normal = {x: centerToJoint.x > 0 ? 1 : -1, y: 0, z: 0};
|
|
radius = self.dimensions.x/2;
|
|
position = VEC3.sum(self.offset, {x: 0, y: centerToJoint.y, z: centerToJoint.z});
|
|
offset = offsets.x;
|
|
break;
|
|
case "y" :
|
|
normal = {x: 0, y: centerToJoint.y > 0 ? 1 : -1, z: 0};
|
|
radius = self.dimensions.y/2;
|
|
position = VEC3.sum(self.offset, {x: centerToJoint.x, y: 0, z: centerToJoint.z});
|
|
offset = offsets.y;
|
|
break;
|
|
case "z" :
|
|
normal = {x: 0, y: 0, z: centerToJoint.z > 0 ? 1 : -1};
|
|
radius = self.dimensions.z/2;
|
|
position = VEC3.sum(self.offset, {x: centerToJoint.x, y: centerToJoint.y, z: 0});
|
|
offset = offsets.z;
|
|
break;
|
|
}
|
|
normal = MyAvatar.jointToWorldDirection(normal, self.jointIndex);
|
|
position = MyAvatar.jointToWorldPoint(position, self.jointIndex);
|
|
}
|
|
return new FlowCollisionData(offset, position, radius, normal, offset);
|
|
};
|
|
};
|
|
|
|
var FlowNode = function(initialPosition, settings) {
|
|
var self = this;
|
|
|
|
this.active = settings.active;
|
|
|
|
this.radius = this.initialRadius = settings.radius;
|
|
this.gravity = settings.gravity;
|
|
this.damping = settings.damping;
|
|
this.inertia = settings.inertia;
|
|
this.delta = settings.delta;
|
|
|
|
this.initialPosition = initialPosition;
|
|
|
|
this.previousPosition = this.initialPosition;
|
|
this.currentPosition = this.initialPosition;
|
|
|
|
this.previousCollision = new FlowCollisionData();
|
|
|
|
this.currentVelocity = {x:0, y:0, z:0};
|
|
this.previousVelocity = {x:0, y:0, z:0};
|
|
this.acceleration = {x:0, y:0, z:0};
|
|
|
|
this.anchored = false;
|
|
this.colliding = false;
|
|
this.collision = undefined;
|
|
|
|
this.update = function(accelerationOffset) {
|
|
self.acceleration = {x: 0, y: self.gravity, z: 0};
|
|
self.previousVelocity = self.currentVelocity;
|
|
self.currentVelocity = VEC3.subtract(self.currentPosition, self.previousPosition);
|
|
self.previousPosition = self.currentPosition;
|
|
if (!self.anchored) {
|
|
// Add inertia
|
|
var centrifugeVector = VEC3.normalize(VEC3.subtract(self.previousVelocity, self.currentVelocity));
|
|
self.acceleration = VEC3.sum(self.acceleration, VEC3.multiply(centrifugeVector, self.inertia * VEC3.length(self.currentVelocity)));
|
|
|
|
// Add offset
|
|
self.acceleration = VEC3.sum(self.acceleration, accelerationOffset);
|
|
|
|
// Calculate new position
|
|
self.currentPosition = VEC3.sum(
|
|
VEC3.sum(self.currentPosition, VEC3.multiply(self.currentVelocity, self.damping)),
|
|
VEC3.multiply(self.acceleration, Math.pow(self.delta, 2))
|
|
);
|
|
} else {
|
|
self.acceleration = {x:0, y:0, z:0};
|
|
self.currentVelocity = {x:0, y:0, z:0};
|
|
}
|
|
};
|
|
|
|
|
|
this.solve = function(constrainPoint, maxDistance, collision) {
|
|
self.solveConstraints(constrainPoint, maxDistance);
|
|
self.solveCollisions(collision);
|
|
};
|
|
|
|
this.solveConstraints = function(constrainPoint, maxDistance) {
|
|
var constrainVector = VEC3.subtract(self.currentPosition, constrainPoint);
|
|
var difference = maxDistance/VEC3.length(constrainVector);
|
|
self.currentPosition = difference < 1.0 ? VEC3.sum(constrainPoint, VEC3.multiply(constrainVector, difference)) : self.currentPosition;
|
|
};
|
|
|
|
this.solveCollisions = function(collision) {
|
|
self.colliding = collision && (collision.offset > 0);
|
|
self.collision = collision;
|
|
if (self.colliding) {
|
|
self.currentPosition = VEC3.sum(self.currentPosition, VEC3.multiply(collision.normal, collision.offset));
|
|
self.previousCollision = collision;
|
|
} else {
|
|
self.previousCollision = undefined;
|
|
}
|
|
};
|
|
|
|
this.apply = function(name, forceRendering) {
|
|
jointDebug.setDebugSphere(name, self.currentPosition, 2*self.radius, { red: self.collision && self.collision.collisionCount > 1 ? 0 : 255,
|
|
green:self.colliding ? 0 : 255,
|
|
blue:0 }, forceRendering);
|
|
};
|
|
};
|
|
|
|
|
|
var FlowJoint = function(index, parentIndex, name, group, settings){
|
|
var self = this;
|
|
|
|
this.index = index;
|
|
this.name = name;
|
|
this.group = group;
|
|
this.parentIndex = parentIndex;
|
|
this.childIndex = -1;
|
|
|
|
this.isDummy = false;
|
|
|
|
this.initialPosition = MyAvatar.getJointPosition(index);
|
|
this.initialXform = new Xform(MyAvatar.getJointRotation(index), MyAvatar.getJointTranslation(index));
|
|
|
|
this.currentRotation = undefined;
|
|
this.recoveryPosition = undefined;
|
|
|
|
this.node = new FlowNode(self.initialPosition, settings);
|
|
|
|
this.stiffness = settings.stiffness;
|
|
|
|
this.translationDirection = VEC3.normalize(this.initialXform.pos);
|
|
|
|
this.length = VEC3.length(VEC3.subtract(this.initialPosition, MyAvatar.getJointPosition(self.parentIndex)));
|
|
|
|
this.update = function () {
|
|
var accelerationOffset = {x: 0, y: 0, z: 0};
|
|
if (self.recoveryPosition) {
|
|
var recoveryVector = VEC3.subtract(self.recoveryPosition, self.node.currentPosition);
|
|
accelerationOffset = VEC3.multiply(recoveryVector, Math.pow(self.stiffness, 3));
|
|
}
|
|
self.node.update(accelerationOffset);
|
|
if (self.node.anchored) {
|
|
if (!self.isDummy) {
|
|
self.node.currentPosition = MyAvatar.getJointPosition(self.index);
|
|
} else {
|
|
self.node.currentPosition = MyAvatar.getJointPosition(self.parentIndex);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.solve = function(collision) {
|
|
var parentPosition = flowJointData[self.parentIndex] ? flowJointData[self.parentIndex].node.currentPosition : MyAvatar.getJointPosition(self.parentIndex);
|
|
self.node.solve(parentPosition, self.length, collision);
|
|
};
|
|
|
|
this.apply = function() {
|
|
|
|
if (self.currentRotation) {
|
|
MyAvatar.setJointRotation(self.index, self.currentRotation);
|
|
}
|
|
self.node.apply(self.name, self.isDummy);
|
|
};
|
|
};
|
|
|
|
var FlowJointDummy = function(initialPosition, index, parentIndex, childIndex, settings) {
|
|
var group = DUMMY_KEYWORD;
|
|
var name = DUMMY_KEYWORD + "_" + index;
|
|
FlowJoint.call(this, index, parentIndex, name, group, settings);
|
|
this.isDummy = true;
|
|
this.childIndex = childIndex;
|
|
this.initialPosition = initialPosition;
|
|
this.node = new FlowNode(initialPosition, settings);
|
|
this.length = DUMMY_JOINT_DISTANCE;
|
|
};
|
|
|
|
var FlowThread = function(root) {
|
|
var self = this;
|
|
this.joints = [];
|
|
this.positions = [];
|
|
this.radius = 0;
|
|
this.length = 0;
|
|
|
|
this.computeThread = function(rootIndex) {
|
|
var parentIndex = rootIndex;
|
|
var childIndex = flowJointData[parentIndex].childIndex;
|
|
var indexes = [parentIndex];
|
|
for (var i = 0; i < flowSkeleton.length; i++) {
|
|
if (childIndex > -1) {
|
|
indexes.push(childIndex);
|
|
childIndex = flowJointData[childIndex].childIndex;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
for (i = 0; i < indexes.length; i++) {
|
|
var index = indexes[i];
|
|
self.joints.push(index);
|
|
if (i > 0) {
|
|
self.length += flowJointData[index].length;
|
|
}
|
|
}
|
|
};
|
|
|
|
this.computeRecovery = function() {
|
|
var parentIndex = self.joints[0];
|
|
var parentJoint = flowJointData[parentIndex];
|
|
parentJoint.recoveryPosition = parentJoint.node.currentPosition;
|
|
var parentRotation = QUAT.multiply(MyAvatar.jointToWorldRotation(QUAT.IDENTITY(), parentJoint.parentIndex), parentJoint.initialXform.rot);
|
|
for (var i = 1; i < self.joints.length; i++) {
|
|
var joint = flowJointData[self.joints[i]];
|
|
var rotation = i === 1 ? parentRotation : QUAT.multiply(rotation, parentJoint.initialXform.rot);
|
|
joint.recoveryPosition = VEC3.sum(parentJoint.recoveryPosition, VEC3.multiplyQbyV(rotation, VEC3.multiply(joint.initialXform.pos, 0.01)));
|
|
parentJoint = joint;
|
|
}
|
|
|
|
};
|
|
|
|
this.update = function() {
|
|
if (!self.getActive()) {
|
|
return;
|
|
}
|
|
self.positions = [];
|
|
self.radius = flowJointData[self.joints[0]].node.radius;
|
|
self.computeRecovery();
|
|
for (var i = 0; i < self.joints.length; i++){
|
|
var joint = flowJointData[self.joints[i]];
|
|
joint.update();
|
|
self.positions.push(joint.node.currentPosition);
|
|
}
|
|
};
|
|
|
|
this.solve = function(useCollisions) {
|
|
if (!self.getActive()) {
|
|
return;
|
|
}
|
|
var i, index;
|
|
if (useCollisions) {
|
|
var handCollisions = handSystem.checkThreadCollisions(self);
|
|
var bodyCollisions = collisionSystem.checkThreadCollisions(self);
|
|
var handTouchedJoint = -1;
|
|
for (i = 0; i < self.joints.length; i++) {
|
|
index = self.joints[i];
|
|
if (bodyCollisions[i].offset > 0) {
|
|
flowJointData[index].solve(bodyCollisions[i]);
|
|
} else {
|
|
handTouchedJoint = (handCollisions[i].offset > 0) ? i : -1;
|
|
flowJointData[index].solve(handCollisions[i]);
|
|
}
|
|
}
|
|
} else {
|
|
for (i = 0; i < self.joints.length; i++) {
|
|
index = self.joints[i];
|
|
flowJointData[index].solve(new FlowCollisionData());
|
|
}
|
|
}
|
|
};
|
|
|
|
this.computeJointRotations = function() {
|
|
var rootIndex = flowJointData[self.joints[0]].parentIndex;
|
|
var rootFramePositions = [];
|
|
for (var i = 0; i < self.joints.length; i++){
|
|
rootFramePositions.push(MyAvatar.worldToJointPoint(flowJointData[self.joints[i]].node.currentPosition, rootIndex));
|
|
}
|
|
var pos0 = rootFramePositions[0];
|
|
var pos1 = rootFramePositions[1];
|
|
|
|
var joint0 = flowJointData[self.joints[0]];
|
|
var joint1 = flowJointData[self.joints[1]];
|
|
|
|
var initial_pos1 = VEC3.sum(pos0, VEC3.multiplyQbyV(joint0.initialXform.rot, VEC3.multiply(joint1.initialXform.pos, 0.01)));
|
|
|
|
var vec0 = VEC3.subtract(initial_pos1, pos0);
|
|
var vec1 = VEC3.subtract(pos1, pos0);
|
|
|
|
var delta = QUAT.rotationBetween(vec0, vec1);
|
|
|
|
joint0.currentRotation = QUAT.multiply(delta, joint0.initialXform.rot);
|
|
|
|
for (i = 1; i < self.joints.length-1; i++){
|
|
var nextJoint = flowJointData[self.joints[i+1]];
|
|
for (var j = i; j < self.joints.length; j++){
|
|
rootFramePositions[j] = VEC3.multiplyQbyV(
|
|
QUAT.inverse(joint0.currentRotation),
|
|
VEC3.subtract(rootFramePositions[j], VEC3.multiply(joint0.initialXform.pos, 0.01))
|
|
);
|
|
}
|
|
pos0 = rootFramePositions[i];
|
|
pos1 = rootFramePositions[i+1];
|
|
initial_pos1 = VEC3.sum(pos0, VEC3.multiplyQbyV(joint1.initialXform.rot, VEC3.multiply(nextJoint.initialXform.pos, 0.01)));
|
|
|
|
vec0 = VEC3.subtract(initial_pos1, pos0);
|
|
vec1 = VEC3.subtract(pos1, pos0);
|
|
|
|
delta = QUAT.rotationBetween(vec0, vec1);
|
|
|
|
joint1.currentRotation = QUAT.multiply(delta, joint1.initialXform.rot);
|
|
joint0 = joint1;
|
|
joint1 = nextJoint;
|
|
}
|
|
};
|
|
|
|
this.apply = function() {
|
|
if (!self.getActive()) {
|
|
return;
|
|
}
|
|
self.computeJointRotations();
|
|
|
|
for (var i = 0; i < self.joints.length; i++){
|
|
var joint = flowJointData[self.joints[i]];
|
|
var parentJoint = flowJointData[joint.parentIndex];
|
|
jointDebug.setDebugLine(
|
|
joint.name,
|
|
joint.node.currentPosition,
|
|
!parentJoint ? MyAvatar.getJointPosition(joint.parentIndex) : parentJoint.node.currentPosition,
|
|
{
|
|
red: 255,
|
|
green:(joint.colliding ? 0 : 255),
|
|
blue:0
|
|
},
|
|
joint.isDummy
|
|
);
|
|
joint.apply();
|
|
}
|
|
};
|
|
|
|
this.getActive = function() {
|
|
return flowJointData[self.joints[0]].node.active;
|
|
};
|
|
|
|
self.computeThread(root);
|
|
};
|
|
|
|
var isActive, flowSkeleton, flowJointData, flowThreads, handSystem, collisionSystem, collisionDebug, jointDebug;
|
|
|
|
function initFlow() {
|
|
stopFlow();
|
|
flowSkeleton = undefined;
|
|
flowJointData = [];
|
|
flowThreads = [];
|
|
|
|
handSystem = new FlowHandSystem();
|
|
collisionSystem = new FlowCollisionSystem();
|
|
|
|
collisionDebug = new FlowDebug();
|
|
jointDebug = new FlowDebug();
|
|
|
|
collisionDebug.setVisible(SHOW_DEBUG_SHAPES);
|
|
collisionDebug.setSolid(SHOW_SOLID_SHAPES);
|
|
|
|
MyAvatar.setEnableMeshVisible(SHOW_AVATAR);
|
|
jointDebug.setVisible(SHOW_DEBUG_SHAPES);
|
|
jointDebug.setSolid(SHOW_SOLID_SHAPES);
|
|
|
|
calculateConstraints();
|
|
isActive = true;
|
|
}
|
|
|
|
function stopFlow() {
|
|
isActive = false;
|
|
if (!flowSkeleton) {
|
|
return;
|
|
}
|
|
for (var i = 0; i < flowSkeleton.length; i++) {
|
|
var index = flowSkeleton[i].index;
|
|
MyAvatar.setJointRotation(index, flowJointData[index].initialXform.rot);
|
|
MyAvatar.setJointTranslation(index, flowJointData[index].initialXform.pos);
|
|
}
|
|
collisionDebug.cleanup();
|
|
jointDebug.cleanup();
|
|
Script.requestGarbageCollection();
|
|
MyAvatar.clearJointsData();
|
|
}
|
|
|
|
var setFlowScale = function(scale) {
|
|
collisionSystem.setScale(scale);
|
|
handSystem.setScale(scale);
|
|
for (var i = 0; i < flowThreads.length; i++) {
|
|
for (var j = 0; j < flowThreads[i].joints.length; j++){
|
|
var joint = flowJointData[flowThreads[i].joints[j]];
|
|
joint.node.radius = joint.node.initialRadius * scale;
|
|
}
|
|
}
|
|
};
|
|
|
|
var calculateConstraints = function() {
|
|
var collisionKeys = Object.keys(CUSTOM_COLLISION_DATA);
|
|
flowSkeleton = MyAvatar.getSkeleton().filter(
|
|
function(jointInfo){
|
|
var name = jointInfo.name;
|
|
var namesplit = name.split("_");
|
|
console.log("FLOW checking: " + name);
|
|
var isSimJoint = (name.substring(0, 3).toUpperCase() === SIM_JOINT_PREFIX.toUpperCase());
|
|
var isFlowJoint = (namesplit.length > 2 &&
|
|
namesplit[0].toUpperCase() === FLOW_JOINT_PREFIX.toUpperCase());
|
|
if (isFlowJoint || isSimJoint) {
|
|
var group = undefined;
|
|
if (isSimJoint) {
|
|
console.log("FLOW is sim: " + name);
|
|
for (var k = 1; k < name.length-1; k++) {
|
|
var subname = parseFloat(name.substring(name.length-k));
|
|
if (isNaN(subname) && name.length-k > SIM_JOINT_PREFIX.length) {
|
|
group = name.substring(SIM_JOINT_PREFIX.length, name.length - k + 1);
|
|
break;
|
|
}
|
|
}
|
|
if (group === undefined) {
|
|
group = name.substring(SIM_JOINT_PREFIX.length, name.length - 1);
|
|
}
|
|
} else {
|
|
group = namesplit[1];
|
|
}
|
|
if (group !== undefined) {
|
|
FLOW_JOINT_KEYWORDS.push(group);
|
|
var jointSettings;
|
|
if (CUSTOM_FLOW_DATA[group] !== undefined) {
|
|
jointSettings = CUSTOM_FLOW_DATA[group];
|
|
} else if (PRESET_FLOW_DATA[group] !== undefined){
|
|
jointSettings = PRESET_FLOW_DATA[group];
|
|
} else {
|
|
jointSettings = DEFAULT_JOINT_SETTINGS.get();
|
|
}
|
|
|
|
FLOW_JOINT_DATA[group] = jointSettings;
|
|
if (flowJointData[jointInfo.index] === undefined) {
|
|
flowJointData[jointInfo.index] = new FlowJoint(jointInfo.index, jointInfo.parentIndex, name, group, jointSettings);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
var collisionSettings = (collisionKeys.length > 0) ? CUSTOM_COLLISION_DATA[name] : PRESET_COLLISION_DATA[name];
|
|
if (collisionSettings) {
|
|
collisionSystem.addCollisionShape(jointInfo.index, name, collisionSettings);
|
|
}
|
|
}
|
|
return (isFlowJoint || isSimJoint);
|
|
}
|
|
);
|
|
|
|
var roots = [];
|
|
|
|
for (var i = 0; i < flowSkeleton.length; i++) {
|
|
var index = flowSkeleton[i].index;
|
|
var flowJoint = flowJointData[index];
|
|
var parentFlowJoint = flowJointData[flowJoint.parentIndex];
|
|
if (parentFlowJoint !== undefined) {
|
|
parentFlowJoint.childIndex = index;
|
|
} else {
|
|
flowJoint.node.anchored = true;
|
|
roots.push(index);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < roots.length; i++) {
|
|
var thread = new FlowThread(roots[i]);
|
|
// add threads with at least 2 joints
|
|
if (thread.joints.length > 0) {
|
|
if (thread.joints.length == 1) {
|
|
var jointIndex = roots[i];
|
|
var joint = flowJointData[jointIndex];
|
|
var jointPosition = MyAvatar.getJointPosition(jointIndex);
|
|
var settings = {
|
|
"active": joint.node.active,
|
|
"radius": joint.node.radius,
|
|
"gravity": joint.node.gravity,
|
|
"damping": joint.node.damping,
|
|
"inertia": joint.node.inertia,
|
|
"delta": joint.node.delta,
|
|
"stiffness": ISOLATED_JOINT_STIFFNESS
|
|
}
|
|
var extraIndex = flowJointData.length;
|
|
flowJointData[extraIndex] = new FlowJointDummy(jointPosition, extraIndex, jointIndex, -1, settings);
|
|
flowJointData[extraIndex].isDummy = false;
|
|
flowJointData[extraIndex].length = ISOLATED_JOINT_LENGTH;
|
|
flowJointData[jointIndex].childIndex = extraIndex;
|
|
flowJointData[extraIndex].group = flowJointData[jointIndex].group;
|
|
thread = new FlowThread(jointIndex);
|
|
}
|
|
flowThreads.push(thread);
|
|
}
|
|
}
|
|
if (flowThreads.length === 0) {
|
|
MyAvatar.clearJointsData();
|
|
}
|
|
|
|
if (SHOW_DUMMY_JOINTS) {
|
|
var jointCount = flowJointData.length;
|
|
var extraIndex = flowJointData.length;
|
|
var rightHandIndex = MyAvatar.getJointIndex("RightHand");
|
|
var rightHandPosition = MyAvatar.getJointPosition(rightHandIndex);
|
|
var parentIndex = rightHandIndex;
|
|
for (i = 0; i < DUMMY_JOINT_COUNT; i++) {
|
|
var childIndex = (i == (DUMMY_JOINT_COUNT - 1)) ? -1 : extraIndex + 1;
|
|
flowJointData[extraIndex] = new FlowJointDummy(rightHandPosition, extraIndex, parentIndex, childIndex, FLOW_JOINT_DATA[DUMMY_KEYWORD]);
|
|
parentIndex = extraIndex;
|
|
extraIndex++;
|
|
}
|
|
flowJointData[jointCount].node.anchored = true;
|
|
|
|
var extraThread = new FlowThread(jointCount);
|
|
flowThreads.push(extraThread);
|
|
}
|
|
setFlowScale(MyAvatar.scale);
|
|
};
|
|
|
|
|
|
Script.update.connect(function(){
|
|
if (!isActive || !flowSkeleton) return;
|
|
|
|
if (USE_COLLISIONS) {
|
|
Script.beginProfileRange("JS-Update-Collisions");
|
|
collisionSystem.update();
|
|
handSystem.update();
|
|
Script.endProfileRange("JS-Update-Collisions");
|
|
}
|
|
|
|
Script.beginProfileRange("JS-Update-JointsUpdate");
|
|
|
|
flowThreads.forEach(function(thread){
|
|
thread.update();
|
|
});
|
|
|
|
Script.endProfileRange("JS-Update-JointsUpdate");
|
|
Script.beginProfileRange("JS-Update-JointsSolve");
|
|
|
|
flowThreads.forEach(function(thread){
|
|
thread.solve(USE_COLLISIONS);
|
|
});
|
|
|
|
Script.endProfileRange("JS-Update-JointsSolve");
|
|
Script.beginProfileRange("JS-Update-JointsApply");
|
|
|
|
flowThreads.forEach(function(thread){
|
|
thread.apply();
|
|
});
|
|
|
|
Script.endProfileRange("JS-Update-JointsApply");
|
|
});
|
|
|
|
Script.scriptEnding.connect(stopFlow);
|
|
|
|
MyAvatar.skeletonChanged.connect(function(){
|
|
Script.setTimeout(function() {
|
|
stopFlow();
|
|
MyAvatar.clearJointsData();
|
|
initFlow();
|
|
}, 200);
|
|
});
|
|
|
|
MyAvatar.scaleChanged.connect(function(){
|
|
if (isActive) {
|
|
setFlowScale(MyAvatar.scale);
|
|
}
|
|
});
|
|
|
|
var varsToDebug = {
|
|
"init": function() {
|
|
initFlow();
|
|
},
|
|
"stop": function() {
|
|
stopFlow();
|
|
},
|
|
"isActive": function() {
|
|
return isActive;
|
|
},
|
|
"toggleAvatarVisible": function() {
|
|
SHOW_AVATAR = !SHOW_AVATAR;
|
|
MyAvatar.setEnableMeshVisible(SHOW_AVATAR);
|
|
},
|
|
"toggleDebugShapes": function() {
|
|
SHOW_DEBUG_SHAPES = !SHOW_DEBUG_SHAPES;
|
|
if (USE_COLLISIONS) {
|
|
collisionDebug.setVisible(SHOW_DEBUG_SHAPES);
|
|
}
|
|
jointDebug.setVisible(SHOW_DEBUG_SHAPES);
|
|
},
|
|
"toggleSolidShapes": function() {
|
|
SHOW_SOLID_SHAPES = !SHOW_SOLID_SHAPES;
|
|
collisionDebug.setSolid(SHOW_SOLID_SHAPES);
|
|
jointDebug.setSolid(SHOW_SOLID_SHAPES);
|
|
},
|
|
"toggleCollisions": function() {
|
|
USE_COLLISIONS = !USE_COLLISIONS;
|
|
if (USE_COLLISIONS && SHOW_DEBUG_SHAPES) {
|
|
collisionDebug.setVisible(true);
|
|
} else {
|
|
collisionDebug.setVisible(false);
|
|
}
|
|
},
|
|
"toggleDummyJoints": function() {
|
|
SHOW_DUMMY_JOINTS = !SHOW_DUMMY_JOINTS;
|
|
stopFlow();
|
|
initFlow();
|
|
},
|
|
"setJointDataValue": function(group, name, value) {
|
|
var keyword = group.toUpperCase();
|
|
for (var i = 0; i < flowThreads.length; i++) {
|
|
for (var j = 0; j < flowThreads[i].joints.length; j++){
|
|
var index = flowThreads[i].joints[j];
|
|
var joint = flowJointData[index];
|
|
if (joint.name.toUpperCase().indexOf(keyword) > -1) {
|
|
var floatVal = (typeof value) != "boolean" ? parseFloat(value) : value;
|
|
FLOW_JOINT_DATA[group][name] = floatVal;
|
|
if (name === "stiffness") {
|
|
joint.stiffness = floatVal;
|
|
} else if (name === "radius") {
|
|
joint.node.initialRadius = floatVal;
|
|
joint.node.radius = MyAvatar.scale*floatVal;
|
|
}
|
|
else {
|
|
joint.node[name] = floatVal;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"setCollisionDataValue": function(group, name, value) {
|
|
var jointName = group;
|
|
var floatVal = parseFloat(value);
|
|
collisionSystem.modifyCollision(jointName, name, floatVal);
|
|
},
|
|
"getGroupData": function() {
|
|
var joint_filter_keys = FLOW_JOINT_DATA;
|
|
if (isActive) {
|
|
joint_filter_keys = {};
|
|
for (var i = 0; i < FLOW_JOINT_KEYWORDS.length; i++) {
|
|
joint_filter_keys[FLOW_JOINT_KEYWORDS[i]] = undefined;
|
|
}
|
|
for (i = 0; i < flowThreads.length; i++) {
|
|
for (var j = 0; j < flowThreads[i].joints.length; j++){
|
|
var index = flowThreads[i].joints[j];
|
|
var joint = flowJointData[index];
|
|
if (!joint_filter_keys[joint.group]) {
|
|
joint_filter_keys[joint.group] = {
|
|
"active": joint.node.active,
|
|
"stiffness": joint.stiffness,
|
|
"radius": joint.node.initialRadius,
|
|
"gravity": joint.node.gravity,
|
|
"damping": joint.node.damping,
|
|
"inertia": joint.node.inertia,
|
|
"delta": joint.node.delta
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return joint_filter_keys;
|
|
|
|
},
|
|
"getDisplayData": function() {
|
|
return {"avatar": SHOW_AVATAR, "collisions": USE_COLLISIONS, "debug": SHOW_DEBUG_SHAPES, "solid": SHOW_SOLID_SHAPES};
|
|
},
|
|
"getDefaultCollisionData": function() {
|
|
return DEFAULT_COLLISION_SETTINGS;
|
|
},
|
|
"getCollisionData": function() {
|
|
return collisionSystem.getCollisionData();
|
|
},
|
|
"addCollision": function(jointName) {
|
|
return collisionSystem.addCollisionToJoint(jointName);
|
|
},
|
|
"removeCollision": function(jointName) {
|
|
collisionSystem.removeCollisionFromJoint(jointName);
|
|
}
|
|
};
|
|
|
|
// Register GlobalDebugger for API Debugger
|
|
Script.registerValue("GlobalDebugger", varsToDebug);
|
|
|
|
// Capture the controller values
|
|
|
|
var leftTriggerPress = function (val) {
|
|
var value = (val <= 1) ? val : 0;
|
|
handSystem.setLeftTriggerValue(value);
|
|
};
|
|
|
|
var rightTriggerPress = function (val) {
|
|
var value = (val <= 1) ? val : 0;
|
|
handSystem.setRightTriggerValue(value);
|
|
};
|
|
|
|
var MAPPING_NAME = "com.highfidelity.hairTouch";
|
|
var mapping = Controller.newMapping(MAPPING_NAME);
|
|
mapping.from([Controller.Standard.RT]).peek().to(rightTriggerPress);
|
|
mapping.from([Controller.Standard.LT]).peek().to(leftTriggerPress);
|
|
|
|
Controller.enableMapping(MAPPING_NAME);
|
|
if (typeof FLOWAPP === 'undefined') {
|
|
initFlow();
|
|
} |