// // controllerDisplay.js // // Created by Anthony J. Thibault on 10/20/16 // Originally created by Ryan Huffman on 9/21/2016 // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html /* globals createControllerDisplay:true deleteControllerDisplay:true */ function clamp(value, min, max) { if (value < min) { return min; } else if (value > max) { return max; } return value; } function resolveHardware(path) { if (typeof path === 'string') { var parts = path.split("."); function resolveInner(base, path, i) { if (i >= path.length) { return base; } return resolveInner(base[path[i]], path, ++i); } return resolveInner(Controller.Hardware, parts, 0); } return path; } var DEBUG = true; function debug() { if (DEBUG) { var args = Array.prototype.slice.call(arguments); args.unshift("controllerDisplay.js | "); print.apply(this, args); } } createControllerDisplay = function(config) { var controllerDisplay = { overlays: [], partOverlays: {}, parts: {}, mappingName: "mapping-display-" + Math.random(), partValues: {}, setVisible: function(visible) { for (var i = 0; i < this.overlays.length; ++i) { Overlays.editOverlay(this.overlays[i], { visible: visible }); } }, setPartVisible: function(partName, visible) { // Disabled /* if (partName in this.partOverlays) { for (var i = 0; i < this.partOverlays[partName].length; ++i) { Overlays.editOverlay(this.partOverlays[partName][i], { //visible: visible }); } } */ }, setLayerForPart: function(partName, layerName) { if (partName in this.parts) { var part = this.parts[partName]; if (part.textureLayers && layerName in part.textureLayers) { var layer = part.textureLayers[layerName]; var textures = {}; if (layer.defaultTextureURL) { textures[part.textureName] = layer.defaultTextureURL; } for (var i = 0; i < this.partOverlays[partName].length; ++i) { Overlays.editOverlay(this.partOverlays[partName][i], { textures: textures }); } } } }, resize: function(sensorScaleFactor) { if (this.overlays.length >= 0) { var controller = config.controllers[0]; var position = controller.position; // first overlay is main body. var overlayID = this.overlays[0]; var localPosition = Vec3.multiply(sensorScaleFactor, Vec3.sum(Vec3.multiplyQbyV(controller.rotation, controller.naturalPosition), position)); var dimensions = Vec3.multiply(sensorScaleFactor, controller.dimensions); Overlays.editOverlay(overlayID, { dimensions: dimensions, localPosition: localPosition }); if (controller.parts) { var i = 1; for (var partName in controller.parts) { overlayID = this.overlays[i++]; var part = controller.parts[partName]; localPosition = Vec3.subtract(part.naturalPosition, controller.naturalPosition); var localRotation; var value = this.partValues[partName]; var offset, rotation; if (value !== undefined) { if (part.type === "linear") { offset = Vec3.multiply(part.maxTranslation * value, part.axis); localPosition = Vec3.sum(localPosition, offset); localRotation = undefined; } else if (part.type === "joystick") { rotation = Quat.fromPitchYawRollDegrees(value.y * part.xHalfAngle, 0, value.x * part.yHalfAngle); if (part.originOffset) { offset = Vec3.multiplyQbyV(rotation, part.originOffset); offset = Vec3.subtract(part.originOffset, offset); } else { offset = { x: 0, y: 0, z: 0 }; } localPosition = Vec3.sum(offset, localPosition); localRotation = rotation; } else if (part.type === "rotational") { value = clamp(value, part.minValue, part.maxValue); var pct = (value - part.minValue) / part.maxValue; var angle = pct * part.maxAngle; rotation = Quat.angleAxis(angle, part.axis); if (part.origin) { offset = Vec3.multiplyQbyV(rotation, part.origin); offset = Vec3.subtract(offset, part.origin); } else { offset = { x: 0, y: 0, z: 0 }; } localPosition = Vec3.sum(offset, localPosition); localRotation = rotation; } } if (localRotation !== undefined) { Overlays.editOverlay(overlayID, { dimensions: Vec3.multiply(sensorScaleFactor, part.naturalDimensions), localPosition: Vec3.multiply(sensorScaleFactor, localPosition), localRotation: localRotation }); } else { Overlays.editOverlay(overlayID, { dimensions: Vec3.multiply(sensorScaleFactor, part.naturalDimensions), localPosition: Vec3.multiply(sensorScaleFactor, localPosition) }); } } } } } }; var mapping = Controller.newMapping(controllerDisplay.mappingName); for (var i = 0; i < config.controllers.length; ++i) { var controller = config.controllers[i]; var position = controller.position; var sensorScaleFactor = MyAvatar.sensorToWorldScale; if (controller.naturalPosition) { position = Vec3.sum(Vec3.multiplyQbyV(controller.rotation, controller.naturalPosition), position); } else { controller.naturalPosition = { x: 0, y: 0, z: 0 }; } var baseOverlayID = Overlays.addOverlay("model", { url: controller.modelURL, dimensions: Vec3.multiply(sensorScaleFactor, controller.dimensions), localRotation: controller.rotation, localPosition: Vec3.multiply(sensorScaleFactor, position), parentID: MyAvatar.SELF_ID, parentJointIndex: controller.jointIndex, ignoreRayIntersection: true }); controllerDisplay.overlays.push(baseOverlayID); if (controller.parts) { for (var partName in controller.parts) { var part = controller.parts[partName]; var localPosition = Vec3.subtract(part.naturalPosition, controller.naturalPosition); var localRotation = { x: 0, y: 0, z: 0, w: 1 } controllerDisplay.parts[partName] = controller.parts[partName]; var properties = { url: part.modelURL, localPosition: localPosition, localRotation: localRotation, parentID: baseOverlayID, ignoreRayIntersection: true }; if (part.defaultTextureLayer) { var textures = {}; textures[part.textureName] = part.textureLayers[part.defaultTextureLayer].defaultTextureURL; properties['textures'] = textures; } var overlayID = Overlays.addOverlay("model", properties); if (part.type === "rotational") { var input = resolveHardware(part.input); mapping.from([input]).peek().to(function(partName) { return function(value) { // insert the most recent controller value into controllerDisplay.partValues. controllerDisplay.partValues[partName] = value; controllerDisplay.resize(MyAvatar.sensorToWorldScale); }; }(partName)); } else if (part.type === "touchpad") { var visibleInput = resolveHardware(part.visibleInput); var xInput = resolveHardware(part.xInput); var yInput = resolveHardware(part.yInput); // TODO: Touchpad inputs are currently only working for half // of the touchpad. When that is fixed, it would be useful // to update these to display the current finger position. mapping.from([visibleInput]).peek().to(function(value) { }); mapping.from([xInput]).peek().to(function(value) { }); mapping.from([yInput]).peek().invert().to(function(value) { }); } else if (part.type === "joystick") { (function(part, partName) { var xInput = resolveHardware(part.xInput); var yInput = resolveHardware(part.yInput); mapping.from([xInput]).peek().to(function(value) { // insert the most recent controller value into controllerDisplay.partValues. if (controllerDisplay.partValues[partName]) { controllerDisplay.partValues[partName].x = value; } else { controllerDisplay.partValues[partName] = {x: value, y: 0}; } controllerDisplay.resize(MyAvatar.sensorToWorldScale); }); mapping.from([yInput]).peek().to(function(value) { // insert the most recent controller value into controllerDisplay.partValues. if (controllerDisplay.partValues[partName]) { controllerDisplay.partValues[partName].y = value; } else { controllerDisplay.partValues[partName] = {x: 0, y: value}; } controllerDisplay.resize(MyAvatar.sensorToWorldScale); }); })(part, partName); } else if (part.type === "linear") { (function(part, partName) { var input = resolveHardware(part.input); mapping.from([input]).peek().to(function(value) { // insert the most recent controller value into controllerDisplay.partValues. controllerDisplay.partValues[partName] = value; controllerDisplay.resize(MyAvatar.sensorToWorldScale); }); })(part, partName); } else if (part.type === "static") { // do nothing } else { debug("TYPE NOT SUPPORTED: ", part.type); } controllerDisplay.overlays.push(overlayID); if (!(partName in controllerDisplay.partOverlays)) { controllerDisplay.partOverlays[partName] = []; } controllerDisplay.partOverlays[partName].push(overlayID); } } } Controller.enableMapping(controllerDisplay.mappingName); controllerDisplay.resize(MyAvatar.sensorToWorldScale); return controllerDisplay; }; deleteControllerDisplay = function(controllerDisplay) { for (var i = 0; i < controllerDisplay.overlays.length; ++i) { Overlays.deleteOverlay(controllerDisplay.overlays[i]); } Controller.disableMapping(controllerDisplay.mappingName); };