(function(){
	
	var SHOW_AVATAR = true;
	var SHOW_DEBUG_SHAPES = false;
	var SHOW_SOLID_SHAPES = false;
	var USE_COLLISIONS = false;
	
	
    var JOINT_COLLISION_PREFIX = "joint_";
	var HAND_COLLISION_PREFIX = "hand_";
	var HAND_COLLISION_RADIUS = 0.03;
	var HAND_TOUCHING_DISTANCE = 2.0;
	
	
	
	// Vec3 Utils ////////////////////////////////////
	var VEC3_UTIL_EPSILON = 0.000001;
	var VEC3_UTIL_EPSILON_SQUARED = VEC3_UTIL_EPSILON * VEC3_UTIL_EPSILON;
	var VEC3_UTIL_PI = 3.14159265358979;
	var VEC3_UTIL_ALMOST_ONE= 1.0 - VEC3_UTIL_EPSILON;
	var VEC3_UTIL_PI_OVER_TWO = 1.57079632679490;
	function vec3(x,y,z) {return {x:x,y:y,z:z};}
	function lengthVec3(V) {return Math.sqrt(V.x*V.x+V.y*V.y+V.z*V.z);}
	function addVec3s(A,B) {return {x:A.x+B.x,y:A.y+B.y,z:A.z+B.z};}
	function subtractVec3s(A,B) {return {x:A.x-B.x,y:A.y-B.y,z:A.z-B.z};}
	function scaleVec3(V, scale) {return {x:scale*V.x,y:scale*V.y,z:scale*V.z};}
	function dotVec3s(A,B) {return A.x*B.x+A.y*B.y+A.z*B.z;}
	function crossVec3s(A,B) {return {x:A.y*B.z-A.z*B.y,y:A.z*B.x-A.x*B.z,z:A.x*B.y-A.y*B.x};}
	function distanceVec3s(A,B) {return Math.sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y)+(A.z-B.z)*(A.z-B.z));}
	function normalizeVec3(V) {
		var L2=V.x*V.x+V.y*V.y+V.z*V.z;
		if(L2<VEC3_UTIL_EPSILON_SQUARED) {return {x:V.x,y:V.y,z:V.z};}
		var invL=1.0/Math.sqrt(L2);
		return {x:invL*V.x,y:invL*V.y,z:invL*V.z}; 
	}
	function angleBetweenVec3s(A,B) {
		var dot=dotVec3s(A,B);
		if(dot<VEC3_UTIL_EPSILON) {return VEC3_UTIL_PI_OVER_TWO;}
		var cosAngle=dot/(lengthVec3(A)*lengthVec3(B));
		if(cosAngle>=VEC3_UTIL_ALMOST_ONE) {return 0.0;}
		if(cosAngle<=-VEC3_UTIL_ALMOST_ONE) {return VEC3_UTIL_PI;}
		return Math.acos(cosAngle);
	}

	// Joint groups by keyword
	
	var JOINT_FILTER_KEYWORDS = ["Hair", "Skirt", "Breast"];
	
	var JOINT_FILTER_DATA = {
		"Hair": {"stiffness": 0.0, "gravity": -0.0096 ,"damping": 0.85, "inertia": 0.8, "delta": 0.55, "friction": 0.0}, 
		"Skirt": {"stiffness": 0.0, "gravity": -0.0096 ,"damping": 0.85, "inertia": 0.25, "delta": 0.45, "friction": 0.0}, 
		"Breast": {"stiffness": 1, "gravity": -0.0096 ,"damping": 0.65, "inertia": 0.8, "delta": 0.45, "friction": 0.0}
	};
	
    var COLLISION_SHAPES = {
		// sphere params type, radius, offset
		// cube params type, dimensions, offset
		/*
        "Head": {type: "sphere", radius: 0.1, offset: {x: 0, y: 0.05, z: 0.03}},
		"Spine2": {type: "sphere",radius: 0.07, attenuation: 0.03, offset: {x: 0, y: 0.08, z: 0.00}}
		*/
		"Head": {type: "sphere", radius: 0.1, offset: {x: 0, y: 0.06, z: 0.05}},
        //"LeftShoulder": {type: "sphere", radius: 0.09, offset: {x: 0, y: 0.05, z: 0.00}},
        //"RightShoulder": {type: "sphere", radius: 0.09, offset: {x: 0, y: 0.05, z: 0.00}},
        "RightArm": {type: "sphere", radius: 0.07, offset: {x: 0, y: 0.00, z: 0.00}},
        "LeftArm": {type: "sphere", radius: 0.07, offset: {x: 0, y: 0.00, z: 0.00}},
        "Spine2": {type: "sphere", radius: 0.09, offset: {x: 0, y: 0.06, z: 0.00}},
		//"Spine1": {type: "sphere", radius: 0.09, offset: {x: 0, y: 0.02, z: 0.00}}
    };
	
	
	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) {
            if (!self.showDebugShapes) 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 ? 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: self.showDebugShapes
                });
            } else if (self.showDebugShapes) {
                self.debugCubes[cubeName] = Overlays.addOverlay("cube", {
                    position: position,
					rotation: rotation,
					dimensions: dimensions,
					color: color,
					solid: self.showSolidShapes,
                    visible: self.showDebugShapes
                });
            }
        }
		
        this.setDebugLine = function(lineName, startPosition, endPosition, shapeColor) {
            if (!self.showDebugShapes) 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: self.showDebugShapes
                });
            } else if (self.showDebugShapes) {
                self.debugLines[lineName] = Overlays.addOverlay("line3d", {
                    color: color,
                    start: start,
                    end: end,
                    visible: self.showDebugShapes
                });
            }
        }
        
        this.setDebugSphere = function(sphereName, pos, diameter, shapeColor) {
            if (!self.showDebugShapes) 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,
					solid: self.showSolidShapes,
                    visible: self.showDebugShapes
                });
            } else if (self.showDebugShapes){
                self.debugSpheres[sphereName] = Overlays.addOverlay("sphere", {
                    color: color,
                    position: pos,
                    scale: {x:scale, y:scale, z:scale},
					solid: self.showSolidShapes,
                    visible: self.showDebugShapes
                });
            }
        }
		
		this.deleteSphere = function(name) {
			Overlays.deleteOverlay(self.debugSpheres[name]);
		}
		
        this.deleteLine = function(name) {
			Overlays.deleteOverlay(self.debugLines[name]);
		}
		
		this.deleteCube = function(name) {
			Overlays.deleteOverlay(self.debugCubes[name]);
		}
		
        this.cleanup = function() {
            for (lineName in self.debugLines) {
                self.deleteLine(lineName);
            }
            for (sphereName in self.debugSpheres) {
                self.deleteSphere(sphereName);
            }
			for (cubeName in self.debugCubes) {
                self.deleteCube(cubeName);
            }
			self.debugLines = {};
			self.debugSpheres = {};
			self.debugCubes = {};
        }
        
        this.setVisible = function(isVisible) {
            self.showDebugShapes = isVisible;
            for (var lineName in self.debugLines) {
                Overlays.editOverlay(self.debugLines[lineName], {
                    visible: isVisible
                });
            }
            for (var sphereName in self.debugSpheres) {
                Overlays.editOverlay(self.debugSpheres[sphereName], {
                    visible: isVisible
                });
            }
        }
        
		this.setSolid = function(isSolid) {
            self.showSolidShapes = isSolid;
            for (var lineName in self.debugLines) {
                Overlays.editOverlay(self.debugLines[lineName], {
                    solid: isSolid
                });
            }
            for (var sphereName in self.debugSpheres) {
                Overlays.editOverlay(self.debugSpheres[sphereName], {
                    solid: isSolid
                });
            }
        }       

    }


	var FlowHandSystem = function() {
		var self = this;
		this.HANDS_COLLISION_JOINTS = ["RightHandMiddle1", "RightHandMiddle3", "LeftHandMiddle1", "LeftHandMiddle3","RightHandPinky3", "LeftHandPinky3"];
		this.avatarIds = [];
		this.avatarHands = [];
		this.lastAvatarCount = 0;
		this.leftTriggerValue = 0;
		this.rightTriggerValue = 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 distanceVec3s(avatarPosition, MyAvatar.position) < distance;
			});
		}
		
		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);
				if (avatarIndex === -1) {
					var newHands = {};
					for (var i = 0; i < self.HANDS_COLLISION_JOINTS.length; i++) {
						var side = self.HANDS_COLLISION_JOINTS[i];
						var jointId = avatar.getJointIndex(side);
						var position = avatar.getJointPosition(jointId);
						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, handCollisionSettings);
					}
					self.avatarHands.push(newHands);
					avatarIndex = self.avatarIds.length;
					self.avatarIds.push(avatarID);
				}

				for (var i = 0; i < self.HANDS_COLLISION_JOINTS.length; i++) {
					var side = self.HANDS_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 (var 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 = addVec3s(collisionData.normal, collisions[i].normal);
					collisionData.position = addVec3s(collisionData.position, collisions[i].position);
					collisionData.radius += collisions[i].radius 
				}
				collisionData.offset = collisionData.offset/collisions.length;
				collisionData.normal = normalizeVec3(scaleVec3(collisionData.normal, 1/collisions.length));
				collisionData.position = scaleVec3(collisionData.position, 1/collisions.length);
				collisionData.radius = dotVec3s(scaleVec3(collisions[0].normal, collisions[0].radius), collisionData.normal);
			} else if (collisions.length == 1){
				collisionData = collisions[0];
			}
			collisionData.collisionCount = collisions.length;
			return collisionData;
		}
		
		this.checkThreadCollisions = function(thread, threadLength) {
			var threadCollisionData = Array(thread.length);
			for (var i = 0; i < thread.length; i++) {
				threadCollisionData[i] = [];
			}
			for (var i = 0; i < self.avatarHands.length; i++) {
				for (var j = 0; j < self.HANDS_COLLISION_JOINTS.length; j++) {
					side = self.HANDS_COLLISION_JOINTS[j];
					var rootCollision = self.avatarHands[i][side].checkCollision(thread[0]);
					var collisionData = [rootCollision];
					var tooFar = rootCollision.distance > threadLength;
					if (!tooFar) {
						for (var k = 1; k < thread.length; k++) {
							var prevCollision = collisionData[k-1];
							var nextCollision = self.avatarHands[i][side].checkCollision(thread[k]);
							collisionData.push(nextCollision);
							if (prevCollision.offset > 0) {
								if (k == 1) {
									threadCollisionData[k-1].push(prevCollision);
								}
							} else if (nextCollision.offset > 0) {
								threadCollisionData[k].push(nextCollision);
							} else {
								var segmentCollision = self.avatarHands[i][side].checkSegmentCollision(thread[k-1], thread[k], prevCollision, nextCollision);
								if (segmentCollision.offset > 0) {
									threadCollisionData[k-1].push(segmentCollision);
									threadCollisionData[k].push(segmentCollision);
								}
							}						
						}
					}
				}
            }
			var collisionResult = [];
			for (var i = 0; i < thread.length; i++) {
				collisionResult.push(self.computeCollision(threadCollisionData[i]));
			}
			return collisionResult;
        }
		
		this.checkCollisions = function(point) {
			var collision = new FlowCollisionData();
			var avatarId, side;
            for (var i = 0; i < self.avatarHands.length; i++) {
				for (var j = 0; j < self.HANDS_COLLISION_JOINTS.length; j++) {
					side = self.HANDS_COLLISION_JOINTS[j];
					collision = self.avatarHands[i][side].checkCollision(point);
					if (collision.offset > 0) {
						break;
					}
				}
				if (collision.offset > 0) {
					break;
				}
            }
            return collision;
        }
		
		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, settings) {
            self.collisionSpheres.push(new FlowCollisionSphere(name, jointIndex, settings));
        }
		
		this.addCollisionCube = function(name, jointIndex, settings) {
            self.collisionCubes.push(new FlowCollisionCube(name, jointIndex, settings));
        }
		
		this.addCollisionShape = function(name, jointIndex, settings) {
			switch(settings.type) {
				case "sphere":
					collisionSystem.addCollisionSphere(name, jointIndex, settings);
					break;
				case "cube":
					collisionSystem.addCollisionCube(name, jointIndex, settings);
					break;
			}
		}
		
        this.update = function() {
            for (var i = 0; i < self.collisionCubes.length; i++) {
                self.collisionCubes[i].update();
            }
			for (var 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 = addVec3s(collisionData.normal, collisions[i].normal);
					collisionData.position = addVec3s(collisionData.position, collisions[i].position);
					collisionData.radius += collisions[i].radius 
				}
				collisionData.offset = collisionData.offset/collisions.length;
				collisionData.normal = normalizeVec3(scaleVec3(collisionData.normal, 1/collisions.length));
				collisionData.position = scaleVec3(collisionData.position, 1/collisions.length);
				collisionData.radius = dotVec3s(scaleVec3(collisions[0].normal, collisions[0].radius), collisionData.normal);
			} else if (collisions.length == 1){
				collisionData = collisions[0];
			}
			collisionData.collisionCount = collisions.length;
			return collisionData;
		}
        
		this.checkCollisions = function(point) {
			var collisions = [];
            for (var i = 0; i < self.collisionSpheres.length; i++) {
                var collision = self.collisionSpheres[i].checkCollision(point);
                if (collision.offset > 0) {
                    collisions.push(collision);
                }
            }
			for (var i = 0; i < self.collisionCubes.length; i++) {
                var collision = self.collisionCubes[i].checkCollision(point);
				console.log("checking");
                if (collision.offset > 0) {
					console.log("collision");
                    collisions.push(collision);
                }
            }
			return self.computeCollision(collisions);
        }
		
		this.checkThreadCollisions = function(thread, threadLength) {
			var threadCollisionData = Array(thread.length);
			for (var i = 0; i < thread.length; i++) {
				threadCollisionData[i] = [];
			}
			for (var j = 0; j < self.collisionSpheres.length; j++) {
				var rootCollision = self.collisionSpheres[j].checkCollision(thread[0]);
				var collisionData = [rootCollision];
				var tooFar = rootCollision.distance > threadLength;
				if (!tooFar) {
					for (var i = 1; i < thread.length; i++) {
						var prevCollision = collisionData[i-1];
						var nextCollision = self.collisionSpheres[j].checkCollision(thread[i]);
						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[i-1], thread[i], prevCollision, nextCollision);
							if (segmentCollision.offset > 0) {
								threadCollisionData[i-1].push(segmentCollision);
								threadCollisionData[i].push(segmentCollision);
							}
						}						
					}
				}
			}
			var collisionResult = [];
			for (var i = 0; i < thread.length; i++) {
				collisionResult.push(self.computeCollision(threadCollisionData[i]));
			}
			return collisionResult;
        }

    }
	
	var FlowCollisionData = function(offset, position, radius, normal) {
		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};
	}
    
    var FlowCollisionSphere = function(name, jointIndex, settings) {
        var self = this;
		this.name = name;
        this.jointIndex = jointIndex;
		this.radius = settings.radius;
        this.offset = settings.offset;
		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) {
            var centerToJoint = subtractVec3s(point, self.position);
            var distance = lengthVec3(centerToJoint);
            var offset =  self.radius - distance;
			var collisionData = new FlowCollisionData(offset, self.position, self.radius, normalizeVec3(centerToJoint));
            return collisionData;
        }
		
		this.checkSegmentCollision = function(point1, point2, pointCollision1, pointCollision2) {
			var collisionData = new FlowCollisionData();
			var segment = subtractVec3s(point2, point1);
			var segmentLength = lengthVec3(segment);
			if (segmentLength > pointCollision1.distance && segmentLength > pointCollision2.distance) {
				var proj1 = dotVec3s(pointCollision1.normal, segment);
				var proj2 = dotVec3s(pointCollision2.normal, segment);
				if (proj1 < 0 && proj2 > 0) {
					var offsets = -pointCollision1.offset-pointCollision2.offset;
					var ratio1 = -pointCollision1.offset/offsets;
					var ratio2 = -pointCollision2.offset/offsets;
					var difference = ratio2-ratio1;
					var newCenter = addVec3s(self.position, scaleVec3(segment, difference));
					var sum = addVec3s(subtractVec3s(point1, newCenter), subtractVec3s(point2, newCenter));
					var distance = lengthVec3(sum)/2;
					var offset = self.radius - distance;
					var normal = normalizeVec3(sum);
					collisionData = new FlowCollisionData(offset, newCenter, self.radius, normal);
				} 
			}
			return collisionData;
        }
    }

	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);
			} else {
				self.position = MyAvatar.jointToWorldPoint(offset, jointIndex);
				self.rotation = MyAvatar.jointToWorldRotation({x:0, y:0, z:0, w:0}, jointIndex);
			}				
			collisionDebug.setDebugCube(self.name, self.position, self.rotation, self.dimensions, {red: 200, green: 10, blue: 50});
        }
        
        this.checkCollision = function(point) {
			var localPoint = MyAvatar.worldToJointPoint(point, self.jointIndex);
			var localPosition = MyAvatar.worldToJointPoint(self.position, self.jointIndex);
			var centerToJoint = subtractVec3s(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)};
							
			var 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 = addVec3s(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 = addVec3s(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 = addVec3s(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);
        }
    }
	
	var FlowNode = function(initialPosition, settings) {
		var self = this;
		
        this.gravity = settings.gravity;
		this.damping = settings.damping;
		this.inertia = settings.inertia;
		this.delta = settings.delta;
		this.friction = settings.friction;
		
        this.initialPosition = initialPosition;
        
        this.previousPosition = this.initialPosition;
        this.currentPosition = this.initialPosition;
        
        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;
		
		this.update = function(accelerationOffset) {
			self.acceleration = {x: 0, y: self.gravity, z: 0};
            self.previousVelocity = self.currentVelocity;
			self.currentVelocity = subtractVec3s(self.currentPosition, self.previousPosition);
			self.previousPosition = self.currentPosition;
            if (!self.anchored) {	
				// Add inertia
				var centrifugeVector = normalizeVec3(subtractVec3s(self.previousVelocity, self.currentVelocity));
				self.acceleration = addVec3s(self.acceleration, scaleVec3(centrifugeVector, self.inertia * lengthVec3(self.currentVelocity)));
				
				// Add offset
				self.acceleration = addVec3s(self.acceleration, accelerationOffset);
				
				// Calculate new position
				self.currentPosition = addVec3s(addVec3s(self.currentPosition, scaleVec3(self.currentVelocity, self.damping)), scaleVec3(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) {
			var constrainVector = subtractVec3s(self.currentPosition, constrainPoint);
			var difference = maxDistance/lengthVec3(constrainVector);
			self.currentPosition = difference < 1.0 ? addVec3s(constrainPoint, scaleVec3(constrainVector, difference)) : self.currentPosition;
			
			self.colliding = collision && (collision.offset > 0);
            self.collision = collision;
			
			if (self.colliding) {
				var normal = normalizeVec3(subtractVec3s(self.currentPosition, self.collision.position));
				var position = addVec3s(self.collision.position, scaleVec3(normal, self.collision.radius));
				self.currentPosition = addVec3s(scaleVec3(position, (1 - self.friction)), scaleVec3(self.previousPosition, self.friction));
			}
			
        }
	}
    
    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.initialPosition = MyAvatar.getJointPosition(index);
		this.initialRotation = MyAvatar.getJointRotation(index);
		this.initialTranslation = MyAvatar.getJointTranslation(index);
		
		this.node = new FlowNode(self.initialPosition, settings);
		
        this.stiffness = settings.stiffness;

		this.translationDirection = normalizeVec3(this.initialTranslation);
		
        this.length = lengthVec3(subtractVec3s(this.initialPosition, MyAvatar.getJointPosition(self.parentIndex)));
		
		this.computeLookAtRotation = function(jointIndex, parentIndex, point) {
			var jointPosition = MyAvatar.getJointPosition(jointIndex);
			var jointRotation = MyAvatar.getJointRotation(jointIndex);
			var localJoint = MyAvatar.worldToJointPoint(jointPosition, parentIndex);
			var localPoint = MyAvatar.worldToJointPoint(point, parentIndex);
			
			var localUp = MyAvatar.worldToJointPoint(Vec3.sum(MyAvatar.getJointPosition(parentIndex), {x: 0, y: 1, z: 0}), parentIndex);
			var distance = Vec3.distance(jointPosition, point);
			var lookAtRotation = Quat.lookAt(localPoint, localJoint, localUp);
			
			var corrector = Quat.fromVec3Degrees({x: 90, y: 0, z: 0});
			var jointRotation = Quat.multiply(lookAtRotation, corrector);
			return {rotation: jointRotation, distance: distance};
		}	
		
		this.getJointRotationToPoint2 = function(jointIndex, parentIndex, grandParentIndex, point) {
			var jointPosition = MyAvatar.getJointPosition(jointIndex);
			var parentPosition = MyAvatar.getJointPosition(parentIndex);
			var parentRotation = MyAvatar.getJointRotation(parentIndex);
			
			// local frame in grandparent since parent rotation is also grandparent frame
			var localJointPosition = MyAvatar.worldToJointPoint(jointPosition, grandParentIndex);
			var localParentPosition = MyAvatar.worldToJointPoint(parentPosition, grandParentIndex);
			var localPointPosition = MyAvatar.worldToJointPoint(point, grandParentIndex);
			var parentToJoint = Vec3.subtract(localJointPosition, localParentPosition);
			var parentToPoint = Vec3.subtract(localPointPosition, localParentPosition);
			var deltaRotation = Quat.rotationBetween(parentToJoint, parentToPoint);
			var jointRotation = Quat.multiply(deltaRotation, parentRotation);
			var distance = Vec3.distance(localParentPosition, localPointPosition);
			return {rotation: jointRotation, distance: distance};
		}	
		
		this.getJointRotationToPoint = function(jointIndex, parentIndex, grandParentIndex, point) {
			var parentRotation = MyAvatar.getJointRotation(parentIndex);
			var translation = Vec3.multiply(MyAvatar.worldToJointPoint(point, parentIndex), 100);
			return {rotation: parentRotation, translation: translation};
		}
		
		this.setJointPosition = function(jointIndex, parentIndex, grandParentIndex, position) {
			//var parentRotation = MyAvatar.getJointRotation(parentIndex);
			var translation = scaleVec3(MyAvatar.worldToJointPoint(position, parentIndex), 100);
			//MyAvatar.setJointRotation(parentIndex, parentRotation);
			MyAvatar.setJointTranslation(jointIndex, translation);
		}
        
        this.update = function () {
			var recoveryPosition = MyAvatar.jointToWorldPoint(scaleVec3(self.initialTranslation, 0.01), self.parentIndex);
			var recoveryVector = subtractVec3s(recoveryPosition, self.node.currentPosition);	
			var accelerationOffset = scaleVec3(recoveryVector, Math.pow(self.stiffness, 3));
			self.node.update(accelerationOffset);
			if (self.node.anchored) {
				self.node.currentPosition = MyAvatar.getJointPosition(self.index);
			}
        }
                
        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() {
			var parentJoint;
            if (!self.node.anchored) {
                parentJoint = flowJointData[self.parentIndex];
				if (!parentJoint) {
					return;
				}
				var grandpaJointIndex = parentJoint.parentIndex;				
				self.setJointPosition(self.index, parentJoint.index, grandpaJointIndex, self.node.currentPosition);
            }
            jointDebug.setDebugSphere(self.name, self.node.currentPosition, 0.02, {red: self.node.collision && self.node.collision.collisionCount > 1 ? 0 : 255, green:self.node.colliding ? 0 : 255, blue:0});
            jointDebug.setDebugLine(self.name, self.node.currentPosition, !parentJoint ? MyAvatar.getJointPosition(self.parentIndex) : parentJoint.node.currentPosition, {red: 255, green:(self.node.colliding ? 0 : 255), blue:0});
        }
    }
	
	var isActive, flowSkeleton, flowJointData, flowJointNameMap, flowThreads, handSystem, collisionSystem, collisionDebug, jointDebug;
    
	function initFlow() {
		stopFlow();
		flowSkeleton = undefined;
		flowJointData = [];
		flowJointNameMap = {};
		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].initialRotation);
            MyAvatar.setJointTranslation(index, flowJointData[index].initialTranslation);
        }
        collisionDebug.cleanup();
		jointDebug.cleanup();
	}
    
    function getUpHierarchyLevels(baseIndex) {
        var upLevels = 0;
        var currentIndex = baseIndex;
        var siblings = [];
        for (var i = 0; i < flowSkeleton.length; i++) {
            var parentIndex = flowJointData[currentIndex].parentIndex;
            if (flowJointData[parentIndex] != undefined) {
                upLevels++;
                currentIndex = parentIndex;
            } else {
                break;
            }
        }
        return upLevels;
    }
    
	var getJointThread = function(parentJoint) {
		var parentIndex = parentJoint;
		var childIndex = flowJointData[parentIndex].childIndex;
		var flowThread = [parentIndex];
		for (var i = 0; i < flowSkeleton.length; i++) {
			if (childIndex > -1) {
				flowThread.push(childIndex);
				childIndex = flowJointData[childIndex].childIndex;
			} else {
				break;
			}
        }
		return flowThread;
	}
	
    var calculateConstraints = function() {
		
		var filterKeys = Object.keys(JOINT_FILTER_DATA);
        flowSkeleton = MyAvatar.getSkeleton().filter(
            function(jointInfo){
                var name = jointInfo.name;
                // We need a MyAvatar.getJointName(jointIndex) function
                flowJointNameMap[name] = jointInfo.index;
                var isFlowJoint = false;
				var jointSettings = {};
				var group = "";
                for (var i = 0; i < filterKeys.length; i++) {
					var jointGroup = JOINT_FILTER_DATA[filterKeys[i]];
					var keyword = filterKeys[i].toUpperCase();
					if (name.toUpperCase().indexOf(keyword) > -1) {
						isFlowJoint = true;
						jointSettings = jointGroup;
						group = filterKeys[i];
						break;
					}
                }
                if (isFlowJoint){
                    if (flowJointData[jointInfo.index] === undefined) {
                        flowJointData[jointInfo.index] = new FlowJoint(jointInfo.index, jointInfo.parentIndex, name, group, jointSettings);
                    }
                } else if (COLLISION_SHAPES[name]){
					collisionSystem.addCollisionShape(JOINT_COLLISION_PREFIX + jointInfo.index, jointInfo.index, COLLISION_SHAPES[name]);
                }
                return isFlowJoint;
            }
        );  
        
		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 (var i = 0; i < roots.length; i++) {
			flowThreads.push(getJointThread(roots[i]));
		}
    }

    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");
		
        for (var i = 0; i < flowThreads.length; i++) {
			for (var j = 0; j < flowThreads[i].length; j++){
				var index = flowThreads[i][j];
				var joint = flowJointData[index];
				joint.update();
			}
        }
		Script.endProfileRange("JS-Update-JointsUpdate");
		Script.beginProfileRange("JS-Update-JointsSolve");

        for (var i = 0; i < flowThreads.length; i++) {
			var thread = [];
			var threadLength = 0;
			for (var j = 0; j < flowThreads[i].length; j++){
				var index = flowThreads[i][j];
				var joint = flowJointData[index];
				var jointPosition = MyAvatar.getJointPosition(joint.index);
				thread.push(jointPosition);
				threadLength += joint.length;
			}
			if (USE_COLLISIONS) {
				var bodyCollisions = collisionSystem.checkThreadCollisions(thread, threadLength);
				var handCollisions = handSystem.checkThreadCollisions(thread, threadLength);
				for (var j = 0; j < flowThreads[i].length; j++) {
					var index = flowThreads[i][j];
					var collision = (bodyCollisions[j].offset > 0) ? bodyCollisions[j] : handCollisions[j];
					flowJointData[index].solve(collision);
				}
			} else {
				for (var j = 0; j < flowThreads[i].length; j++) {
					var index = flowThreads[i][j];
					flowJointData[index].solve(new FlowCollisionData());
				}
			}
			/*
			for (var j = 0; j < flowThreads[i].length; j++){
				var index = flowThreads[i][j];
				var joint = flowJointData[index];
				var jointPosition = MyAvatar.getJointPosition(joint.index);
				var bodyCollision = USE_COLLISIONS ? collisionSystem.checkCollisions(jointPosition, joint.index) : undefined;
				var handCollision = USE_COLLISIONS ? handSystem.checkCollisions(jointPosition, joint.index) : undefined;
				var collision = (bodyCollision && bodyCollision.offset > 0) ? bodyCollision : handCollision;
				flowJointData[index].solve(collision);
			}
			*/
		}

        Script.endProfileRange("JS-Update-JointsSolve");
		Script.beginProfileRange("JS-Update-JointsApply");
        for (var i = 0; i < flowThreads.length; i++) {
			for (var j = 0; j < flowThreads[i].length; j++){
				var index = flowThreads[i][j];
				flowJointData[index].apply();
			}
        }
		Script.endProfileRange("JS-Update-JointsApply");
    });
    
    Script.scriptEnding.connect(stopFlow);
    
	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);
			}
		},
		"setFriction": function(friction) {
			COLLISION_FRICTION = friction;
		},
		"setValue": function(group, name, value) {
			if (group != "Extra Hand Nodes") {
				var keyword = group.toUpperCase();
				for (var i = 0; i < flowThreads.length; i++) {
					for (var j = 0; j < flowThreads[i].length; j++){
						var index = flowThreads[i][j];
						var joint = flowJointData[index];
						if (joint.name.toUpperCase().indexOf(keyword) > -1) {
							var floatVal = parseFloat(value);
							JOINT_FILTER_DATA[group][name] = floatVal;
							if (name === "stiffness") {
								joint.stiffness = floatVal;
							} else {
								joint.node[name] = floatVal;
							}
						}
					}
				}
			} else {
				
			}
		}, 
		"getGroupData": function() {
			var joint_filter_keys = JOINT_FILTER_DATA;
			if (isActive) {
				joint_filter_keys = {"Hair": undefined, "Skirt": undefined, "Breast": undefined};
				for (var i = 0; i < flowThreads.length; i++) {
					for (var j = 0; j < flowThreads[i].length; j++){
						var index = flowThreads[i][j];
						var joint = flowJointData[index];
						if (!joint_filter_keys[joint.group]) {
							joint_filter_keys[joint.group] = {
								"stiffness": joint.stiffness, 
								"gravity": joint.node.gravity,
								"damping": joint.node.damping, 
								"inertia": joint.node.inertia, 
								"delta": joint.node.delta,
								"friction": joint.node.friction
							};
						}
					}
				}
			}
			return joint_filter_keys;
			
		},
		"getSettingsData": function() {
			return {"avatar": SHOW_AVATAR, "collisions": USE_COLLISIONS, "debug": SHOW_DEBUG_SHAPES, "solid": SHOW_SOLID_SHAPES};
		}
	};
		
    // 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);
	console.log("Hair Script loaded");
	if (typeof FLOWAPP === 'undefined') {
		initFlow();
	}
}());