(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(); } }());