diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js index 923a01a8a9..9d586c8436 100644 --- a/domain-server/resources/web/js/settings.js +++ b/domain-server/resources/web/js/settings.js @@ -152,7 +152,6 @@ $(document).ready(function(){ }) $('#settings-form').on('change', 'select', function(){ - console.log("Changed" + $(this)) $("input[name='" + $(this).attr('data-hidden-input') + "']").val($(this).val()).change() }) @@ -474,21 +473,24 @@ function deleteTableRow(delete_glyphicon) { if (!isArray) { // this is a hash row, so we empty it but leave the hidden input blank so it is cleared when we save row.empty() - row.html(""); - } else if (table.find('.' + Settings.DATA_ROW_CLASS).length > 1) { - updateDataChangedForSiblingRows(row) - - // this isn't the last row - we can just remove it - row.remove() + row.html(""); } else { - // this is the last row, we can't remove it completely since we need to post an empty array - row.empty() + if (table.find('.' + Settings.DATA_ROW_CLASS).length) { + updateDataChangedForSiblingRows(row) - row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS) - row.addClass('empty-array-row') + // this isn't the last row - we can just remove it + row.remove() + } else { + // this is the last row, we can't remove it completely since we need to post an empty array + row.empty() - row.html(""); + row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS) + row.addClass('empty-array-row') + + row.html(""); + } } // we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated diff --git a/examples/gamepad.js b/examples/gamepad.js new file mode 100644 index 0000000000..4ec0309511 --- /dev/null +++ b/examples/gamepad.js @@ -0,0 +1,280 @@ +// +// controller.js +// examples +// +// Created by Ryan Huffman on 10/9/14. +// Copyright 2014 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 +// + +// TODO Update to work with any controller that is plugged in. +var CONTROLLER_NAMES = [ + "Wireless 360 Controller", + "Controller (XBOX 360 For Windows)", + "Controller", // Wired 360 controller +] + +for (var i = 0; i < CONTROLLER_NAMES.length; i++) { + gamepad = Joysticks.joystickWithName(CONTROLLER_NAMES[i]); + if (gamepad) { + print("Found controller: " + CONTROLLER_NAMES[i]); + break; + } +} + +if (!gamepad) { + print("No gamepad found."); +} + +// Controller axis/button mappings +var GAMEPAD = { + AXES: { + LEFT_JOYSTICK_X: 0, + LEFT_JOYSTICK_Y: 1, + + RIGHT_JOYSTICK_X: 2, + RIGHT_JOYSTICK_Y: 3, + + LEFT_TRIGGER: 4, + RIGHT_TRIGGER: 5, + }, + BUTTONS: { + DPAD_UP: 0, + DPAD_DOWN: 1, + DPAD_LEFT: 2, + DPAD_RIGHT: 3, + + LEFT_JOYSTICK: 6, + RIGHT_JOYSTICK: 7, + + LEFT_BUMPER: 8, + RIGHT_BUMPER: 9, + + // Face buttons, ABXY on an XBOX controller + FACE_BOTTOM: 11, + FACE_RIGHT: 12, + FACE_LEFT: 13, + FACE_TOP: 14, + } +} + +// Button/axis mappings +var AXIS_STRAFE = GAMEPAD.AXES.LEFT_JOYSTICK_X; +var AXIS_FORWARD = GAMEPAD.AXES.LEFT_JOYSTICK_Y; +var AXIS_ROTATE = GAMEPAD.AXES.RIGHT_JOYSTICK_X; + +var BUTTON_TURN_AROUND = GAMEPAD.BUTTONS.RIGHT_JOYSTICK; + +var BUTTON_FLY_UP = GAMEPAD.BUTTONS.RIGHT_BUMPER; +var BUTTON_FLY_DOWN = GAMEPAD.BUTTONS.LEFT_BUMPER +var BUTTON_WARP = GAMEPAD.BUTTONS.FACE_BOTTOM; + +var BUTTON_WARP_FORWARD = GAMEPAD.BUTTONS.DPAD_UP; +var BUTTON_WARP_BACKWARD = GAMEPAD.BUTTONS.DPAD_DOWN; +var BUTTON_WARP_LEFT = GAMEPAD.BUTTONS.DPAD_LEFT; +var BUTTON_WARP_RIGHT = GAMEPAD.BUTTONS.DPAD_RIGHT; + +// Distance in meters to warp via BUTTON_WARP_* +var WARP_DISTANCE = 1; + +// Walk speed in m/s +var MOVE_SPEED = 2; + +// Amount to rotate in radians +var ROTATE_INCREMENT = Math.PI / 8; + +// Pick from above where we want to warp +var WARP_PICK_OFFSET = { x: 0, y: 10, z: 0 }; + +// When warping, the warp position will snap to a target below the current warp position. +// This is the max distance it will snap to. +var WARP_PICK_MAX_DISTANCE = 100; + +var flyDownButtonState = false; +var flyUpButtonState = false; + +// Current move direction, axis aligned - that is, looking down and moving forward +// will not move you into the ground, but instead will keep you on the horizontal plane. +var moveDirection = { x: 0, y: 0, z: 0 }; + +var warpActive = false; +var warpPosition = { x: 0, y: 0, z: 0 }; + +var WARP_SPHERE_SIZE = 1; +var warpSphere = Overlays.addOverlay("sphere", { + position: { x: 0, y: 0, z: 0 }, + size: WARP_SPHERE_SIZE, + color: { red: 0, green: 255, blue: 0 }, + alpha: 1.0, + solid: true, + visible: false, +}); + +var WARP_LINE_HEIGHT = 10; +var warpLine = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z:0 }, + end: { x: 0, y: 0, z: 0 }, + color: { red: 0, green: 255, blue: 255}, + alpha: 1, + lineWidth: 5, + visible: false, +}); + +function copyVec3(vec) { + return { x: vec.x, y: vec.y, z: vec.z }; +} + +function activateWarp() { + if (warpActive) return; + warpActive = true; + + updateWarp(); +} + +function updateWarp() { + if (!warpActive) return; + + var look = Quat.getFront(Camera.getOrientation()); + var pitch = Math.asin(look.y); + + // Get relative to looking straight down + pitch += Math.PI / 2; + + // Scale up + pitch *= 2; + var distance = pitch * pitch * pitch; + + var warpDirection = Vec3.normalize({ x: look.x, y: 0, z: look.z }); + warpPosition = Vec3.multiply(warpDirection, distance); + warpPosition = Vec3.sum(MyAvatar.position, warpPosition); + + var pickRay = { + origin: Vec3.sum(warpPosition, WARP_PICK_OFFSET), + direction: { x: 0, y: -1, z: 0 } + }; + + var intersection = Voxels.findRayIntersection(pickRay); + + if (intersection.intersects && intersection.distance < WARP_PICK_MAX_DISTANCE) { + // Warp 1 meter above the object - this is an approximation + // TODO Get the actual offset to the Avatar's feet and plant them to + // the object. + warpPosition = Vec3.sum(intersection.intersection, { x: 0, y: 1, z:0 }); + } + + // Adjust overlays to match warp position + Overlays.editOverlay(warpSphere, { + position: warpPosition, + visible: true, + }); + Overlays.editOverlay(warpLine, { + position: warpPosition, + end: Vec3.sum(warpPosition, { x: 0, y: WARP_LINE_HEIGHT, z: 0 }), + visible: true, + }); +} + +function finishWarp() { + if (!warpActive) return; + warpActive = false; + Overlays.editOverlay(warpSphere, { + visible: false, + }); + Overlays.editOverlay(warpLine, { + visible: false, + }); + MyAvatar.position = warpPosition; +} + +function reportAxisValue(axis, newValue, oldValue) { + if (Math.abs(oldValue) < 0.2) oldValue = 0; + if (Math.abs(newValue) < 0.2) newValue = 0; + + if (axis == AXIS_FORWARD) { + moveDirection.z = newValue; + } else if (axis == AXIS_STRAFE) { + moveDirection.x = newValue; + } else if (axis == AXIS_ROTATE) { + if (oldValue == 0 && newValue != 0) { + var rotateRadians = newValue > 0 ? -ROTATE_INCREMENT : ROTATE_INCREMENT; + var orientation = MyAvatar.orientation; + orientation = Quat.multiply(Quat.fromPitchYawRollRadians(0, rotateRadians, 0), orientation) ; + MyAvatar.orientation = orientation; + } + } +} + +function reportButtonValue(button, newValue, oldValue) { + if (button == BUTTON_FLY_DOWN) { + flyDownButtonState = newValue; + } else if (button == BUTTON_FLY_UP) { + flyUpButtonState = newValue; + } else if (button == BUTTON_WARP) { + if (newValue) { + activateWarp(); + } else { + finishWarp(); + } + } else if (button == BUTTON_TURN_AROUND) { + if (newValue) { + MyAvatar.orientation = Quat.multiply( + Quat.fromPitchYawRollRadians(0, Math.PI, 0), MyAvatar.orientation); + } + } else if (newValue) { + var direction = null; + + if (button == BUTTON_WARP_FORWARD) { + direction = Quat.getFront(Camera.getOrientation()); + } else if (button == BUTTON_WARP_BACKWARD) { + direction = Quat.getFront(Camera.getOrientation()); + direction = Vec3.multiply(-1, direction); + } else if (button == BUTTON_WARP_LEFT) { + direction = Quat.getRight(Camera.getOrientation()); + direction = Vec3.multiply(-1, direction); + } else if (button == BUTTON_WARP_RIGHT) { + direction = Quat.getRight(Camera.getOrientation()); + } + + if (direction) { + direction.y = 0; + direction = Vec3.multiply(Vec3.normalize(direction), WARP_DISTANCE); + MyAvatar.position = Vec3.sum(MyAvatar.position, direction); + } + } + + if (flyUpButtonState && !flyDownButtonState) { + moveDirection.y = 1; + } else if (!flyUpButtonState && flyDownButtonState) { + moveDirection.y = -1; + } else { + moveDirection.y = 0; + } +} + +function update(dt) { + var velocity = { x: 0, y: 0, z: 0 }; + var move = copyVec3(moveDirection); + move.y = 0; + if (Vec3.length(move) > 0) { + velocity = Vec3.multiplyQbyV(Camera.getOrientation(), move); + velocity.y = 0; + velocity = Vec3.multiply(Vec3.normalize(velocity), MOVE_SPEED); + } + + if (moveDirection.y != 0) { + velocity.y = moveDirection.y * MOVE_SPEED; + } + + MyAvatar.setVelocity(velocity); + + updateWarp(); +} + +if (gamepad) { + gamepad.axisValueChanged.connect(reportAxisValue); + gamepad.buttonStateChanged.connect(reportButtonValue); + + Script.update.connect(update); +} diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js index fbac30e796..becc8c86b2 100644 --- a/examples/libraries/entityPropertyDialogBox.js +++ b/examples/libraries/entityPropertyDialogBox.js @@ -244,7 +244,7 @@ EntityPropertyDialogBox = (function () { properties.color.blue = array[index++].value; } Entities.editEntity(editModelID, properties); - selectionDisplay.highlightSelectable(editModelID, propeties); + selectionDisplay.select(editModelID, false); } modelSelected = false; }); diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index d9cf2c54fd..b00876c969 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -31,10 +31,33 @@ SelectionDisplay = (function () { var handleHoverColor = { red: 224, green: 67, blue: 36 }; var handleHoverAlpha = 1.0; + var rotateOverlayTargetSize = 10000; // really big target + var innerSnapAngle = 22.5; // the angle which we snap to on the inner rotation tool + var innerRadius; + var outerRadius; + var yawHandleRotation; + var pitchHandleRotation; + var rollHandleRotation; + var yawCenter; + var pitchCenter; + var rollCenter; + var yawZero; + var pitchZero; + var rollZero; + var yawNormal; + var pitchNormal; + var rollNormal; + var rotationNormal; + + var originalRotation; + var originalPitch; + var originalYaw; + var originalRoll; + + var rotateHandleColor = { red: 0, green: 0, blue: 0 }; var rotateHandleAlpha = 0.7; - var grabberSizeCorner = 0.025; var grabberSizeEdge = 0.015; var grabberSizeFace = 0.025; @@ -151,7 +174,8 @@ SelectionDisplay = (function () { alpha: 0.5, solid: true, visible: false, - rotation: baseOverlayRotation + rotation: baseOverlayRotation, + ignoreRayIntersection: true, // always ignore this }); var yawOverlayAngles = { x: 90, y: 0, z: 0 }; @@ -161,6 +185,34 @@ SelectionDisplay = (function () { var rollOverlayAngles = { x: 0, y: 180, z: 0 }; var rollOverlayRotation = Quat.fromVec3Degrees(rollOverlayAngles); + var rotateZeroOverlay = Overlays.addOverlay("line3d", { + visible: false, + lineWidth: 2.0, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, + color: { red: 255, green: 0, blue: 0 }, + ignoreRayIntersection: true, // always ignore this + }); + + var rotateCurrentOverlay = Overlays.addOverlay("line3d", { + visible: false, + lineWidth: 2.0, + start: { x: 0, y: 0, z: 0 }, + end: { x: 0, y: 0, z: 0 }, + color: { red: 0, green: 0, blue: 255 }, + ignoreRayIntersection: true, // always ignore this + }); + + + var rotateOverlayTarget = Overlays.addOverlay("circle3d", { + position: { x:0, y: 0, z: 0}, + size: rotateOverlayTargetSize, + color: { red: 0, green: 0, blue: 0 }, + alpha: 0.0, + solid: true, + visible: false, + rotation: yawOverlayRotation, + }); var rotateOverlayInner = Overlays.addOverlay("circle3d", { position: { x:0, y: 0, z: 0}, @@ -171,12 +223,13 @@ SelectionDisplay = (function () { visible: false, rotation: yawOverlayRotation, hasTickMarks: true, - majorTickMarksAngle: 12.5, + majorTickMarksAngle: innerSnapAngle, minorTickMarksAngle: 0, majorTickMarksLength: -0.25, minorTickMarksLength: 0, majorTickMarksColor: { red: 0, green: 0, blue: 0 }, minorTickMarksColor: { red: 0, green: 0, blue: 0 }, + ignoreRayIntersection: true, // always ignore this }); var rotateOverlayOuter = Overlays.addOverlay("circle3d", { @@ -195,6 +248,7 @@ SelectionDisplay = (function () { minorTickMarksLength: 0.1, majorTickMarksColor: { red: 0, green: 0, blue: 0 }, minorTickMarksColor: { red: 0, green: 0, blue: 0 }, + ignoreRayIntersection: true, // always ignore this }); var rotateOverlayCurrent = Overlays.addOverlay("circle3d", { @@ -205,10 +259,11 @@ SelectionDisplay = (function () { solid: true, visible: false, rotation: yawOverlayRotation, + ignoreRayIntersection: true, // always ignore this }); var yawHandle = Overlays.addOverlay("billboard", { - url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/HRRhkMk8ueLk8ku/rotate-arrow.png", + url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png", position: { x:0, y: 0, z: 0}, color: rotateHandleColor, alpha: rotateHandleAlpha, @@ -220,7 +275,7 @@ SelectionDisplay = (function () { var pitchHandle = Overlays.addOverlay("billboard", { - url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/HRRhkMk8ueLk8ku/rotate-arrow.png", + url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png", position: { x:0, y: 0, z: 0}, color: rotateHandleColor, alpha: rotateHandleAlpha, @@ -232,7 +287,7 @@ SelectionDisplay = (function () { var rollHandle = Overlays.addOverlay("billboard", { - url: "https://s3.amazonaws.com/uploads.hipchat.com/33953/231323/HRRhkMk8ueLk8ku/rotate-arrow.png", + url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png", position: { x:0, y: 0, z: 0}, color: rotateHandleColor, alpha: rotateHandleAlpha, @@ -279,11 +334,14 @@ SelectionDisplay = (function () { overlayNames[pitchHandle] = "pitchHandle"; overlayNames[rollHandle] = "rollHandle"; + overlayNames[rotateOverlayTarget] = "rotateOverlayTarget"; overlayNames[rotateOverlayInner] = "rotateOverlayInner"; overlayNames[rotateOverlayOuter] = "rotateOverlayOuter"; overlayNames[rotateOverlayCurrent] = "rotateOverlayCurrent"; - + overlayNames[rotateZeroOverlay] = "rotateZeroOverlay"; + overlayNames[rotateCurrentOverlay] = "rotateCurrentOverlay"; + that.cleanup = function () { Overlays.deleteOverlay(highlightBox); Overlays.deleteOverlay(selectionBox); @@ -322,10 +380,15 @@ SelectionDisplay = (function () { Overlays.deleteOverlay(pitchHandle); Overlays.deleteOverlay(rollHandle); + Overlays.deleteOverlay(rotateOverlayTarget); Overlays.deleteOverlay(rotateOverlayInner); Overlays.deleteOverlay(rotateOverlayOuter); Overlays.deleteOverlay(rotateOverlayCurrent); + Overlays.deleteOverlay(rotateZeroOverlay); + Overlays.deleteOverlay(rotateCurrentOverlay); + + }; that.highlightSelectable = function(entityID) { @@ -377,8 +440,8 @@ SelectionDisplay = (function () { var diagonal = (Vec3.length(properties.dimensions) / 2) * 1.1; var halfDimensions = Vec3.multiply(properties.dimensions, 0.5); - var innerRadius = diagonal; - var outerRadius = diagonal * 1.15; + innerRadius = diagonal; + outerRadius = diagonal * 1.15; var innerActive = false; var innerAlpha = 0.2; var outerAlpha = 0.2; @@ -391,31 +454,33 @@ SelectionDisplay = (function () { var rotateHandleOffset = 0.05; var grabberMoveUpOffset = 0.1; - var left = properties.position.x - halfDimensions.x; - var right = properties.position.x + halfDimensions.x; - var bottom = properties.position.y - halfDimensions.y; - var top = properties.position.y + halfDimensions.y; - var near = properties.position.z - halfDimensions.z; - var far = properties.position.z + halfDimensions.z; - var center = { x: properties.position.x, y: properties.position.y, z: properties.position.z }; + var top, far, left, bottom, near, right, boundsCenter, objectCenter, BLN, BRN, BLF, TLN, TRN, TLF, TRF; - var BLN = { x: left, y: bottom, z: near }; - var BRN = { x: right, y: bottom, z: near }; - var BLF = { x: left, y: bottom, z: far }; - var BRF = { x: right, y: bottom, z: far }; - var TLN = { x: left, y: top, z: near }; - var TRN = { x: right, y: top, z: near }; - var TLF = { x: left, y: top, z: far }; - var TRF = { x: right, y: top, z: far }; + objectCenter = { x: properties.position.x, y: properties.position.y, z: properties.position.z }; + + top = properties.boundingBox.tfl.y; + far = properties.boundingBox.tfl.z; + left = properties.boundingBox.tfl.x; + + bottom = properties.boundingBox.brn.y; + right = properties.boundingBox.brn.x; + near = properties.boundingBox.brn.z; + + boundsCenter = { x: properties.boundingBox.center.x, y: properties.boundingBox.center.y, z: properties.boundingBox.center.z }; + + BLN = { x: left, y: bottom, z: near }; + BRN = { x: right, y: bottom, z: near }; + BLF = { x: left, y: bottom, z: far }; + BRF = { x: right, y: bottom, z: far }; + TLN = { x: left, y: top, z: near }; + TRN = { x: right, y: top, z: near }; + TLF = { x: left, y: top, z: far }; + TRF = { x: right, y: top, z: far }; var yawCorner; var pitchCorner; var rollCorner; - var yawHandleRotation; - var pitchHandleRotation; - var rollHandleRotation; - // determine which bottom corner we are closest to /*------------------------------ example: @@ -429,124 +494,189 @@ SelectionDisplay = (function () { ------------------------------*/ - if (MyAvatar.position.x > center.x) { + if (MyAvatar.position.x > objectCenter.x) { // must be BRF or BRN - if (MyAvatar.position.z < center.z) { - yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 0, z: 0 }); - pitchHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 180, z: 180 }); - rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 90, z: 180 }); + if (MyAvatar.position.z < objectCenter.z) { - yawCorner = { x: right + rotateHandleOffset, + yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 90, z: 0 }); + pitchHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 90, z: 0 }); + rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }); + + yawNormal = { x: 0, y: 1, z: 0 }; + pitchNormal = { x: 1, y: 0, z: 0 }; + rollNormal = { x: 0, y: 0, z: 1 }; + + yawCorner = { x: left + rotateHandleOffset, y: bottom - rotateHandleOffset, z: near - rotateHandleOffset }; - pitchCorner = { x: right + rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + rotateHandleOffset }; - - rollCorner = { x: left - rotateHandleOffset, + pitchCorner = { x: right - rotateHandleOffset, y: top + rotateHandleOffset, z: near - rotateHandleOffset}; + rollCorner = { x: left + rotateHandleOffset, + y: top + rotateHandleOffset, + z: far + rotateHandleOffset }; + + yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z }; + pitchCenter = { x: right, y: boundsCenter.y, z: boundsCenter.z}; + rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: far }; + + + Overlays.editOverlay(pitchHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-south.png" }); + Overlays.editOverlay(rollHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-south.png" }); + + } else { - yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 270, z: 0 }); + + yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 0, z: 0 }); pitchHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 }); rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 90 }); - yawCorner = { x: right + rotateHandleOffset, + yawNormal = { x: 0, y: 1, z: 0 }; + pitchNormal = { x: 1, y: 0, z: 0 }; + rollNormal = { x: 0, y: 0, z: 1 }; + + + yawCorner = { x: left + rotateHandleOffset, y: bottom - rotateHandleOffset, z: far + rotateHandleOffset }; - pitchCorner = { x: left - rotateHandleOffset, + pitchCorner = { x: right - rotateHandleOffset, y: top + rotateHandleOffset, z: far + rotateHandleOffset }; - rollCorner = { x: right + rotateHandleOffset, + rollCorner = { x: left + rotateHandleOffset, y: top + rotateHandleOffset, z: near - rotateHandleOffset}; + + yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z }; + pitchCenter = { x: right, y: boundsCenter.y, z: boundsCenter.z }; + rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: near}; + + Overlays.editOverlay(pitchHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" }); + Overlays.editOverlay(rollHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" }); } } else { + // must be BLF or BLN - if (MyAvatar.position.z < center.z) { - yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 90, z: 0 }); + if (MyAvatar.position.z < objectCenter.z) { + + yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 180, z: 0 }); pitchHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 0, z: 90 }); rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); - yawCorner = { x: left - rotateHandleOffset, + yawNormal = { x: 0, y: 1, z: 0 }; + pitchNormal = { x: 1, y: 0, z: 0 }; + rollNormal = { x: 0, y: 0, z: 1 }; + + yawCorner = { x: right - rotateHandleOffset, y: bottom - rotateHandleOffset, z: near - rotateHandleOffset }; - pitchCorner = { x: right + rotateHandleOffset, + pitchCorner = { x: left + rotateHandleOffset, y: top + rotateHandleOffset, z: near - rotateHandleOffset }; - rollCorner = { x: left - rotateHandleOffset, + rollCorner = { x: right - rotateHandleOffset, y: top + rotateHandleOffset, z: far + rotateHandleOffset}; - - } else { - yawHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 180, z: 0 }); - pitchHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); - rollHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 }); + yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z }; + pitchCenter = { x: left, y: boundsCenter.y, z: boundsCenter.z }; + rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: far}; - yawCorner = { x: left - rotateHandleOffset, + Overlays.editOverlay(pitchHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" }); + Overlays.editOverlay(rollHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" }); + + } else { + + yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 270, z: 0 }); + rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); + pitchHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 }); + + yawNormal = { x: 0, y: 1, z: 0 }; + rollNormal = { x: 0, y: 0, z: 1 }; + pitchNormal = { x: 1, y: 0, z: 0 }; + + yawCorner = { x: right - rotateHandleOffset, y: bottom - rotateHandleOffset, z: far + rotateHandleOffset }; - pitchCorner = { x: left - rotateHandleOffset, + rollCorner = { x: right - rotateHandleOffset, y: top + rotateHandleOffset, z: near - rotateHandleOffset }; - rollCorner = { x: right + rotateHandleOffset, + pitchCorner = { x: left + rotateHandleOffset, y: top + rotateHandleOffset, z: far + rotateHandleOffset}; + + yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z }; + rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: near }; + pitchCenter = { x: left, y: boundsCenter.y, z: boundsCenter.z}; + + Overlays.editOverlay(pitchHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" }); + Overlays.editOverlay(rollHandle, { url: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/rotate-arrow-west-north.png" }); + } } + + var rotateHandlesVisible = true; + var translateHandlesVisible = true; + var stretchHandlesVisible = true; + var selectionBoxVisible = true; + if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL" || mode == "TRANSLATE_XZ") { + rotateHandlesVisible = false; + translateHandlesVisible = false; + stretchHandlesVisible = false; + selectionBoxVisible = false; + } else if (mode == "TRANSLATE_UP_DOWN") { + rotateHandlesVisible = false; + stretchHandlesVisible = false; + } else if (mode != "UNKNOWN") { + // every other mode is a stretch mode... + rotateHandlesVisible = false; + translateHandlesVisible = false; + } Overlays.editOverlay(highlightBox, { visible: false }); - Overlays.editOverlay(selectionBox, - { - visible: true, - position: center, - dimensions: properties.dimensions, - rotation: properties.rotation, - }); + Overlays.editOverlay(selectionBox, { visible: selectionBoxVisible, position: objectCenter, dimensions: properties.dimensions, + rotation: properties.rotation,}); - Overlays.editOverlay(grabberMoveUp, { visible: true, position: { x: center.x, y: top + grabberMoveUpOffset, z: center.z } }); + Overlays.editOverlay(grabberMoveUp, { visible: translateHandlesVisible, position: { x: boundsCenter.x, y: top + grabberMoveUpOffset, z: boundsCenter.z } }); - Overlays.editOverlay(grabberLBN, { visible: true, position: { x: left, y: bottom, z: near } }); - Overlays.editOverlay(grabberRBN, { visible: true, position: { x: right, y: bottom, z: near } }); - Overlays.editOverlay(grabberLBF, { visible: true, position: { x: left, y: bottom, z: far } }); - Overlays.editOverlay(grabberRBF, { visible: true, position: { x: right, y: bottom, z: far } }); - Overlays.editOverlay(grabberLTN, { visible: true, position: { x: left, y: top, z: near } }); - Overlays.editOverlay(grabberRTN, { visible: true, position: { x: right, y: top, z: near } }); - Overlays.editOverlay(grabberLTF, { visible: true, position: { x: left, y: top, z: far } }); - Overlays.editOverlay(grabberRTF, { visible: true, position: { x: right, y: top, z: far } }); + Overlays.editOverlay(grabberLBN, { visible: stretchHandlesVisible, position: { x: left, y: bottom, z: near } }); + Overlays.editOverlay(grabberRBN, { visible: stretchHandlesVisible, position: { x: right, y: bottom, z: near } }); + Overlays.editOverlay(grabberLBF, { visible: stretchHandlesVisible, position: { x: left, y: bottom, z: far } }); + Overlays.editOverlay(grabberRBF, { visible: stretchHandlesVisible, position: { x: right, y: bottom, z: far } }); + Overlays.editOverlay(grabberLTN, { visible: stretchHandlesVisible, position: { x: left, y: top, z: near } }); + Overlays.editOverlay(grabberRTN, { visible: stretchHandlesVisible, position: { x: right, y: top, z: near } }); + Overlays.editOverlay(grabberLTF, { visible: stretchHandlesVisible, position: { x: left, y: top, z: far } }); + Overlays.editOverlay(grabberRTF, { visible: stretchHandlesVisible, position: { x: right, y: top, z: far } }); - Overlays.editOverlay(grabberTOP, { visible: true, position: { x: center.x, y: top, z: center.z } }); - Overlays.editOverlay(grabberBOTTOM, { visible: true, position: { x: center.x, y: bottom, z: center.z } }); - Overlays.editOverlay(grabberLEFT, { visible: true, position: { x: left, y: center.y, z: center.z } }); - Overlays.editOverlay(grabberRIGHT, { visible: true, position: { x: right, y: center.y, z: center.z } }); - Overlays.editOverlay(grabberNEAR, { visible: true, position: { x: center.x, y: center.y, z: near } }); - Overlays.editOverlay(grabberFAR, { visible: true, position: { x: center.x, y: center.y, z: far } }); + Overlays.editOverlay(grabberTOP, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: top, z: boundsCenter.z } }); + Overlays.editOverlay(grabberBOTTOM, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: bottom, z: boundsCenter.z } }); + Overlays.editOverlay(grabberLEFT, { visible: stretchHandlesVisible, position: { x: left, y: boundsCenter.y, z: boundsCenter.z } }); + Overlays.editOverlay(grabberRIGHT, { visible: stretchHandlesVisible, position: { x: right, y: boundsCenter.y, z: boundsCenter.z } }); + Overlays.editOverlay(grabberNEAR, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: boundsCenter.y, z: near } }); + Overlays.editOverlay(grabberFAR, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: boundsCenter.y, z: far } }); - Overlays.editOverlay(grabberEdgeTR, { visible: true, position: { x: right, y: top, z: center.z } }); - Overlays.editOverlay(grabberEdgeTL, { visible: true, position: { x: left, y: top, z: center.z } }); - Overlays.editOverlay(grabberEdgeTF, { visible: true, position: { x: center.x, y: top, z: far } }); - Overlays.editOverlay(grabberEdgeTN, { visible: true, position: { x: center.x, y: top, z: near } }); - Overlays.editOverlay(grabberEdgeBR, { visible: true, position: { x: right, y: bottom, z: center.z } }); - Overlays.editOverlay(grabberEdgeBL, { visible: true, position: { x: left, y: bottom, z: center.z } }); - Overlays.editOverlay(grabberEdgeBF, { visible: true, position: { x: center.x, y: bottom, z: far } }); - Overlays.editOverlay(grabberEdgeBN, { visible: true, position: { x: center.x, y: bottom, z: near } }); - Overlays.editOverlay(grabberEdgeNR, { visible: true, position: { x: right, y: center.y, z: near } }); - Overlays.editOverlay(grabberEdgeNL, { visible: true, position: { x: left, y: center.y, z: near } }); - Overlays.editOverlay(grabberEdgeFR, { visible: true, position: { x: right, y: center.y, z: far } }); - Overlays.editOverlay(grabberEdgeFL, { visible: true, position: { x: left, y: center.y, z: far } }); + Overlays.editOverlay(grabberEdgeTR, { visible: stretchHandlesVisible, position: { x: right, y: top, z: boundsCenter.z } }); + Overlays.editOverlay(grabberEdgeTL, { visible: stretchHandlesVisible, position: { x: left, y: top, z: boundsCenter.z } }); + Overlays.editOverlay(grabberEdgeTF, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: top, z: far } }); + Overlays.editOverlay(grabberEdgeTN, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: top, z: near } }); + Overlays.editOverlay(grabberEdgeBR, { visible: stretchHandlesVisible, position: { x: right, y: bottom, z: boundsCenter.z } }); + Overlays.editOverlay(grabberEdgeBL, { visible: stretchHandlesVisible, position: { x: left, y: bottom, z: boundsCenter.z } }); + Overlays.editOverlay(grabberEdgeBF, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: bottom, z: far } }); + Overlays.editOverlay(grabberEdgeBN, { visible: stretchHandlesVisible, position: { x: boundsCenter.x, y: bottom, z: near } }); + Overlays.editOverlay(grabberEdgeNR, { visible: stretchHandlesVisible, position: { x: right, y: boundsCenter.y, z: near } }); + Overlays.editOverlay(grabberEdgeNL, { visible: stretchHandlesVisible, position: { x: left, y: boundsCenter.y, z: near } }); + Overlays.editOverlay(grabberEdgeFR, { visible: stretchHandlesVisible, position: { x: right, y: boundsCenter.y, z: far } }); + Overlays.editOverlay(grabberEdgeFL, { visible: stretchHandlesVisible, position: { x: left, y: boundsCenter.y, z: far } }); Overlays.editOverlay(baseOfEntityProjectionOverlay, @@ -562,14 +692,12 @@ SelectionDisplay = (function () { rotation: properties.rotation, }); + + Overlays.editOverlay(rotateOverlayTarget, { visible: false }); Overlays.editOverlay(rotateOverlayInner, { visible: false, - position: { x: properties.position.x, - y: properties.position.y - (properties.dimensions.y / 2), - z: properties.position.z}, - size: innerRadius, innerRadius: 0.9, alpha: innerAlpha @@ -578,10 +706,6 @@ SelectionDisplay = (function () { Overlays.editOverlay(rotateOverlayOuter, { visible: false, - position: { x: properties.position.x, - y: properties.position.y - (properties.dimensions.y / 2), - z: properties.position.z}, - size: outerRadius, innerRadius: 0.9, startAt: 0, @@ -592,20 +716,19 @@ SelectionDisplay = (function () { Overlays.editOverlay(rotateOverlayCurrent, { visible: false, - position: { x: properties.position.x, - y: properties.position.y - (properties.dimensions.y / 2), - z: properties.position.z}, - size: outerRadius, startAt: 0, endAt: 0, innerRadius: 0.9, }); + + Overlays.editOverlay(rotateZeroOverlay, { visible: false }); + Overlays.editOverlay(rotateCurrentOverlay, { visible: false }); // TODO: we have not implemented the rotating handle/controls yet... so for now, these handles are hidden - Overlays.editOverlay(yawHandle, { visible: false, position: yawCorner, rotation: yawHandleRotation}); - Overlays.editOverlay(pitchHandle, { visible: false, position: pitchCorner, rotation: pitchHandleRotation}); - Overlays.editOverlay(rollHandle, { visible: false, position: rollCorner, rotation: rollHandleRotation}); + Overlays.editOverlay(yawHandle, { visible: rotateHandlesVisible, position: yawCorner, rotation: yawHandleRotation}); + Overlays.editOverlay(pitchHandle, { visible: rotateHandlesVisible, position: pitchCorner, rotation: pitchHandleRotation}); + Overlays.editOverlay(rollHandle, { visible: rotateHandlesVisible, position: rollCorner, rotation: rollHandleRotation}); Entities.editEntity(entityID, { localRenderAlpha: 0.1 }); }; @@ -655,10 +778,14 @@ SelectionDisplay = (function () { Overlays.editOverlay(pitchHandle, { visible: false }); Overlays.editOverlay(rollHandle, { visible: false }); + Overlays.editOverlay(rotateOverlayTarget, { visible: false }); Overlays.editOverlay(rotateOverlayInner, { visible: false }); Overlays.editOverlay(rotateOverlayOuter, { visible: false }); Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); + Overlays.editOverlay(rotateZeroOverlay, { visible: false }); + Overlays.editOverlay(rotateCurrentOverlay, { visible: false }); + Entities.editEntity(entityID, { localRenderAlpha: 1.0 }); currentSelection = { id: -1, isKnownID: false }; @@ -1440,7 +1567,185 @@ SelectionDisplay = (function () { Entities.editEntity(currentSelection, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); that.select(currentSelection, false); // TODO: this should be more than highlighted - }; + }; + + that.rotateYaw = function(event) { + if (!entitySelected || mode !== "ROTATE_YAW") { + return; // not allowed + } + + var pickRay = Camera.computePickRay(event.x, event.y); + Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false}); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false }); + Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false }); + Overlays.editOverlay(rotateOverlayInner, { ignoreRayIntersection: true }); + Overlays.editOverlay(rotateOverlayOuter, { ignoreRayIntersection: true }); + Overlays.editOverlay(rotateOverlayCurrent, { ignoreRayIntersection: true }); + + var result = Overlays.findRayIntersection(pickRay); + if (result.intersects) { + var properties = Entities.getEntityProperties(currentSelection); + var center = yawCenter; + var zero = yawZero; + var centerToZero = Vec3.subtract(center, zero); + var centerToIntersect = Vec3.subtract(center, result.intersection); + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + + var distanceFromCenter = Vec3.distance(center, result.intersection); + var snapToInner = false; + if (distanceFromCenter < innerRadius) { + angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle; + snapToInner = true; + } + + // for debugging + //Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: center, end: result.intersection }); + + var yawChange = Quat.fromVec3Degrees({ x: 0, y: angleFromZero, z: 0 }); + var newRotation = Quat.multiply(yawChange, originalRotation); + + Entities.editEntity(currentSelection, { rotation: newRotation }); + + // update the rotation display accordingly... + var startAtCurrent = 0; + var endAtCurrent = angleFromZero; + var startAtRemainder = angleFromZero; + var endAtRemainder = 360; + if (angleFromZero < 0) { + startAtCurrent = 360 + angleFromZero; + endAtCurrent = 360; + startAtRemainder = 0; + endAtRemainder = startAtCurrent; + } + if (snapToInner) { + Overlays.editOverlay(rotateOverlayOuter, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius }); + } else { + Overlays.editOverlay(rotateOverlayInner, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius }); + } + + } + }; + + that.rotatePitch = function(event) { + if (!entitySelected || mode !== "ROTATE_PITCH") { + return; // not allowed + } + var pickRay = Camera.computePickRay(event.x, event.y); + Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false}); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false }); + Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false }); + Overlays.editOverlay(rotateOverlayInner, { ignoreRayIntersection: true }); + Overlays.editOverlay(rotateOverlayOuter, { ignoreRayIntersection: true }); + Overlays.editOverlay(rotateOverlayCurrent, { ignoreRayIntersection: true }); + var result = Overlays.findRayIntersection(pickRay); + + if (result.intersects) { + var properties = Entities.getEntityProperties(currentSelection); + var center = pitchCenter; + var zero = pitchZero; + var centerToZero = Vec3.subtract(center, zero); + var centerToIntersect = Vec3.subtract(center, result.intersection); + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + + var distanceFromCenter = Vec3.distance(center, result.intersection); + var snapToInner = false; + if (distanceFromCenter < innerRadius) { + angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle; + snapToInner = true; + } + + // for debugging + //Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: center, end: result.intersection }); + + var pitchChange = Quat.fromVec3Degrees({ x: angleFromZero, y: 0, z: 0 }); + var newRotation = Quat.multiply(pitchChange, originalRotation); + + Entities.editEntity(currentSelection, { rotation: newRotation }); + + // update the rotation display accordingly... + var startAtCurrent = 0; + var endAtCurrent = angleFromZero; + var startAtRemainder = angleFromZero; + var endAtRemainder = 360; + if (angleFromZero < 0) { + startAtCurrent = 360 + angleFromZero; + endAtCurrent = 360; + startAtRemainder = 0; + endAtRemainder = startAtCurrent; + } + if (snapToInner) { + Overlays.editOverlay(rotateOverlayOuter, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius }); + } else { + Overlays.editOverlay(rotateOverlayInner, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius }); + } + } + }; + + that.rotateRoll = function(event) { + if (!entitySelected || mode !== "ROTATE_ROLL") { + return; // not allowed + } + var pickRay = Camera.computePickRay(event.x, event.y); + Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false}); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false }); + Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false }); + Overlays.editOverlay(rotateOverlayInner, { ignoreRayIntersection: true }); + Overlays.editOverlay(rotateOverlayOuter, { ignoreRayIntersection: true }); + Overlays.editOverlay(rotateOverlayCurrent, { ignoreRayIntersection: true }); + var result = Overlays.findRayIntersection(pickRay); + if (result.intersects) { + var properties = Entities.getEntityProperties(currentSelection); + var center = rollCenter; + var zero = rollZero; + var centerToZero = Vec3.subtract(center, zero); + var centerToIntersect = Vec3.subtract(center, result.intersection); + var angleFromZero = Vec3.orientedAngle(centerToZero, centerToIntersect, rotationNormal); + + var distanceFromCenter = Vec3.distance(center, result.intersection); + var snapToInner = false; + if (distanceFromCenter < innerRadius) { + angleFromZero = Math.floor(angleFromZero/innerSnapAngle) * innerSnapAngle; + snapToInner = true; + } + + // for debugging + //Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: center, end: result.intersection }); + + var rollChange = Quat.fromVec3Degrees({ x: 0, y: 0, z: angleFromZero }); + var newRotation = Quat.multiply(rollChange, originalRotation); + + Entities.editEntity(currentSelection, { rotation: newRotation }); + + // update the rotation display accordingly... + var startAtCurrent = 0; + var endAtCurrent = angleFromZero; + var startAtRemainder = angleFromZero; + var endAtRemainder = 360; + if (angleFromZero < 0) { + startAtCurrent = 360 + angleFromZero; + endAtCurrent = 360; + startAtRemainder = 0; + endAtRemainder = startAtCurrent; + } + if (snapToInner) { + Overlays.editOverlay(rotateOverlayOuter, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius }); + } else { + Overlays.editOverlay(rotateOverlayInner, { startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder }); + Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius }); + } + } + }; that.checkMove = function() { if (currentSelection.isKnownID && @@ -1477,6 +1782,36 @@ SelectionDisplay = (function () { case grabberMoveUp: mode = "TRANSLATE_UP_DOWN"; somethingClicked = true; + + // in translate mode, we hide our stretch handles... + Overlays.editOverlay(grabberLBN, { visible: false }); + Overlays.editOverlay(grabberLBF, { visible: false }); + Overlays.editOverlay(grabberRBN, { visible: false }); + Overlays.editOverlay(grabberRBF, { visible: false }); + Overlays.editOverlay(grabberLTN, { visible: false }); + Overlays.editOverlay(grabberLTF, { visible: false }); + Overlays.editOverlay(grabberRTN, { visible: false }); + Overlays.editOverlay(grabberRTF, { visible: false }); + + Overlays.editOverlay(grabberTOP, { visible: false }); + Overlays.editOverlay(grabberBOTTOM, { visible: false }); + Overlays.editOverlay(grabberLEFT, { visible: false }); + Overlays.editOverlay(grabberRIGHT, { visible: false }); + Overlays.editOverlay(grabberNEAR, { visible: false }); + Overlays.editOverlay(grabberFAR, { visible: false }); + + Overlays.editOverlay(grabberEdgeTR, { visible: false }); + Overlays.editOverlay(grabberEdgeTL, { visible: false }); + Overlays.editOverlay(grabberEdgeTF, { visible: false }); + Overlays.editOverlay(grabberEdgeTN, { visible: false }); + Overlays.editOverlay(grabberEdgeBR, { visible: false }); + Overlays.editOverlay(grabberEdgeBL, { visible: false }); + Overlays.editOverlay(grabberEdgeBF, { visible: false }); + Overlays.editOverlay(grabberEdgeBN, { visible: false }); + Overlays.editOverlay(grabberEdgeNR, { visible: false }); + Overlays.editOverlay(grabberEdgeNL, { visible: false }); + Overlays.editOverlay(grabberEdgeFR, { visible: false }); + Overlays.editOverlay(grabberEdgeFL, { visible: false }); break; case grabberRBN: @@ -1559,20 +1894,131 @@ SelectionDisplay = (function () { } } + // if one of the items above was clicked, then we know we are in translate or stretch mode, and we + // should hide our rotate handles... + if (somethingClicked) { + Overlays.editOverlay(yawHandle, { visible: false }); + Overlays.editOverlay(pitchHandle, { visible: false }); + Overlays.editOverlay(rollHandle, { visible: false }); + + if (mode != "TRANSLATE_UP_DOWN") { + Overlays.editOverlay(grabberMoveUp, { visible: false }); + } + } + if (!somethingClicked) { + + print("rotate handle case..."); + // After testing our stretch handles, then check out rotate handles Overlays.editOverlay(yawHandle, { ignoreRayIntersection: false }); Overlays.editOverlay(pitchHandle, { ignoreRayIntersection: false }); Overlays.editOverlay(rollHandle, { ignoreRayIntersection: false }); var result = Overlays.findRayIntersection(pickRay); + + var overlayOrientation; + var overlayCenter; + + var properties = Entities.getEntityProperties(currentSelection); + var angles = Quat.safeEulerAngles(properties.rotation); + var pitch = angles.x; + var yaw = angles.y; + var roll = angles.z; + + originalRotation = properties.rotation; + originalPitch = pitch; + originalYaw = yaw; + originalRoll = roll; + if (result.intersects) { switch(result.overlayID) { + case yawHandle: + mode = "ROTATE_YAW"; + somethingClicked = true; + overlayOrientation = yawHandleRotation; + overlayCenter = yawCenter; + yawZero = result.intersection; + rotationNormal = yawNormal; + break; + + case pitchHandle: + mode = "ROTATE_PITCH"; + somethingClicked = true; + overlayOrientation = pitchHandleRotation; + overlayCenter = pitchCenter; + pitchZero = result.intersection; + rotationNormal = pitchNormal; + break; + + case rollHandle: + mode = "ROTATE_ROLL"; + somethingClicked = true; + overlayOrientation = rollHandleRotation; + overlayCenter = rollCenter; + rollZero = result.intersection; + rotationNormal = rollNormal; + break; + default: print("mousePressEvent()...... " + overlayNames[result.overlayID]); mode = "UNKNOWN"; break; } } + + print(" somethingClicked:" + somethingClicked); + print(" mode:" + mode); + + + if (somethingClicked) { + + Overlays.editOverlay(rotateOverlayTarget, { visible: true, rotation: overlayOrientation, position: overlayCenter }); + Overlays.editOverlay(rotateOverlayInner, { visible: true, rotation: overlayOrientation, position: overlayCenter }); + Overlays.editOverlay(rotateOverlayOuter, { visible: true, rotation: overlayOrientation, position: overlayCenter, startAt: 0, endAt: 360 }); + Overlays.editOverlay(rotateOverlayCurrent, { visible: true, rotation: overlayOrientation, position: overlayCenter, startAt: 0, endAt: 0 }); + + // for debugging + //Overlays.editOverlay(rotateZeroOverlay, { visible: true, start: overlayCenter, end: result.intersection }); + //Overlays.editOverlay(rotateCurrentOverlay, { visible: true, start: overlayCenter, end: result.intersection }); + + Overlays.editOverlay(yawHandle, { visible: false }); + Overlays.editOverlay(pitchHandle, { visible: false }); + Overlays.editOverlay(rollHandle, { visible: false }); + + + Overlays.editOverlay(yawHandle, { visible: false }); + Overlays.editOverlay(pitchHandle, { visible: false }); + Overlays.editOverlay(rollHandle, { visible: false }); + Overlays.editOverlay(grabberMoveUp, { visible: false }); + Overlays.editOverlay(grabberLBN, { visible: false }); + Overlays.editOverlay(grabberLBF, { visible: false }); + Overlays.editOverlay(grabberRBN, { visible: false }); + Overlays.editOverlay(grabberRBF, { visible: false }); + Overlays.editOverlay(grabberLTN, { visible: false }); + Overlays.editOverlay(grabberLTF, { visible: false }); + Overlays.editOverlay(grabberRTN, { visible: false }); + Overlays.editOverlay(grabberRTF, { visible: false }); + + Overlays.editOverlay(grabberTOP, { visible: false }); + Overlays.editOverlay(grabberBOTTOM, { visible: false }); + Overlays.editOverlay(grabberLEFT, { visible: false }); + Overlays.editOverlay(grabberRIGHT, { visible: false }); + Overlays.editOverlay(grabberNEAR, { visible: false }); + Overlays.editOverlay(grabberFAR, { visible: false }); + + Overlays.editOverlay(grabberEdgeTR, { visible: false }); + Overlays.editOverlay(grabberEdgeTL, { visible: false }); + Overlays.editOverlay(grabberEdgeTF, { visible: false }); + Overlays.editOverlay(grabberEdgeTN, { visible: false }); + Overlays.editOverlay(grabberEdgeBR, { visible: false }); + Overlays.editOverlay(grabberEdgeBL, { visible: false }); + Overlays.editOverlay(grabberEdgeBF, { visible: false }); + Overlays.editOverlay(grabberEdgeBN, { visible: false }); + Overlays.editOverlay(grabberEdgeNR, { visible: false }); + Overlays.editOverlay(grabberEdgeNL, { visible: false }); + Overlays.editOverlay(grabberEdgeFR, { visible: false }); + Overlays.editOverlay(grabberEdgeFL, { visible: false }); + } } if (!somethingClicked) { @@ -1614,6 +2060,15 @@ SelectionDisplay = (function () { that.mouseMoveEvent = function(event) { //print("mouseMoveEvent()... mode:" + mode); switch (mode) { + case "ROTATE_YAW": + that.rotateYaw(event); + break; + case "ROTATE_PITCH": + that.rotatePitch(event); + break; + case "ROTATE_ROLL": + that.rotateRoll(event); + break; case "TRANSLATE_UP_DOWN": that.translateUpDown(event); break; @@ -1671,14 +2126,34 @@ SelectionDisplay = (function () { }; that.mouseReleaseEvent = function(event) { + var showHandles = false; + // hide our rotation overlays..., and show our handles + if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL") { + Overlays.editOverlay(rotateOverlayTarget, { visible: false }); + Overlays.editOverlay(rotateOverlayInner, { visible: false }); + Overlays.editOverlay(rotateOverlayOuter, { visible: false }); + Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); + showHandles = true; + } + + if (mode != "UNKNOWN") { + showHandles = true; + } + mode = "UNKNOWN"; // if something is selected, then reset the "original" properties for any potential next click+move operation if (entitySelected) { + + if (showHandles) { + that.select(currentSelection, event); + } + selectedEntityProperties = Entities.getEntityProperties(currentSelection); selectedEntityPropertiesOriginalPosition = properties.position; selectedEntityPropertiesOriginalDimensions = properties.dimensions; } + }; Controller.mousePressEvent.connect(that.mousePressEvent); diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index a223813347..b753537631 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -570,6 +570,19 @@ function handeMenuEvent(menuItem) { } } else if (menuItem == "Edit Properties...") { // good place to put the properties dialog + + editModelID = -1; + if (entitySelected) { + print(" Edit Properties.... selectedEntityID="+ selectedEntityID); + editModelID = selectedEntityID; + } else { + print(" Edit Properties.... not holding..."); + } + if (editModelID != -1) { + print(" Edit Properties.... about to edit properties..."); + entityPropertyDialogBox.openDialog(editModelID); + } + } else if (menuItem == "Paste Models") { modelImporter.paste(); } else if (menuItem == "Export Models") { diff --git a/examples/xbox.js b/examples/xbox.js deleted file mode 100644 index 603e0dbf56..0000000000 --- a/examples/xbox.js +++ /dev/null @@ -1,19 +0,0 @@ -// -// xbox.js -// examples -// -// Created by Stephen Birarda on September 23, 2014 -// -// Copyright 2014 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 -// - -gamepad = Joysticks.joystickWithName("Wireless 360 Controller"); - -function reportAxisValue(axis, newValue, oldValue) { - print("The value for axis " + axis + " has changed to " + newValue + ". It was " + oldValue); -} - -gamepad.axisValueChanged.connect(reportAxisValue); \ No newline at end of file diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 19f8ef6d5f..1202b36a9f 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -41,7 +41,7 @@ configure_file(InterfaceVersion.h.in "${PROJECT_BINARY_DIR}/includes/InterfaceVe # grab the implementation and header files from src dirs file(GLOB INTERFACE_SRCS src/*.cpp src/*.h) -foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles entities) +foreach(SUBDIR avatar devices renderer ui starfield location scripting voxels particles entities gpu) file(GLOB_RECURSE SUBDIR_SRCS src/${SUBDIR}/*.cpp src/${SUBDIR}/*.h) set(INTERFACE_SRCS ${INTERFACE_SRCS} "${SUBDIR_SRCS}") endforeach(SUBDIR) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 718360864a..55fe438cfe 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -605,10 +605,14 @@ void Application::paintGL() { if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { if (!OculusManager::isConnected()) { + // If there isn't an HMD, match exactly to avatar's head _myCamera.setPosition(_myAvatar->getHead()->getEyePosition()); _myCamera.setRotation(_myAvatar->getHead()->getCameraOrientation()); + } else { + // For an HMD, set the base position and orientation to that of the avatar body + _myCamera.setPosition(_myAvatar->getDefaultEyePosition()); + _myCamera.setRotation(_myAvatar->getWorldAlignedOrientation()); } - // OculusManager::display() updates camera position and rotation a bit further on. } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { static const float THIRD_PERSON_CAMERA_DISTANCE = 1.5f; @@ -664,7 +668,6 @@ void Application::paintGL() { _viewFrustumOffsetCamera.setRotation(_myCamera.getRotation() * frustumRotation); - _viewFrustumOffsetCamera.initialize(); // force immediate snap to ideal position and orientation _viewFrustumOffsetCamera.update(1.f/_fps); whichCamera = &_viewFrustumOffsetCamera; } diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index 79d66568bf..a8138363fa 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -22,13 +22,16 @@ Camera::Camera() : - _needsToInitialize(true), _mode(CAMERA_MODE_THIRD_PERSON), _position(0.0f, 0.0f, 0.0f), _fieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES), _aspectRatio(16.0f/9.0f), _nearClip(DEFAULT_NEAR_CLIP), // default _farClip(DEFAULT_FAR_CLIP), // default + _hmdPosition(), + _hmdRotation(), + _targetPosition(), + _targetRotation(), _scale(1.0f) { } @@ -64,26 +67,6 @@ void Camera::setFarClip(float f) { _farClip = f; } -void Camera::setEyeOffsetPosition(const glm::vec3& p) { - _eyeOffsetPosition = p; -} - -void Camera::setEyeOffsetOrientation(const glm::quat& o) { - _eyeOffsetOrientation = o; - -} - -void Camera::setScale(float s) { - _scale = s; - _needsToInitialize = true; - -} - -void Camera::initialize() { - _needsToInitialize = true; -} - - CameraScriptableObject::CameraScriptableObject(Camera* camera, ViewFrustum* viewFrustum) : _camera(camera), _viewFrustum(viewFrustum) { diff --git a/interface/src/Camera.h b/interface/src/Camera.h index ee4930272d..80454a969e 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -38,17 +38,23 @@ public: void setPosition(const glm::vec3& p) { _position = p; } void setRotation(const glm::quat& rotation) { _rotation = rotation; }; + void setHmdPosition(const glm::vec3& hmdPosition) { _hmdPosition = hmdPosition; } + void setHmdRotation(const glm::quat& hmdRotation) { _hmdRotation = hmdRotation; }; + void setMode(CameraMode m); void setFieldOfView(float f); void setAspectRatio(float a); void setNearClip(float n); void setFarClip(float f); - void setEyeOffsetPosition(const glm::vec3& p); - void setEyeOffsetOrientation(const glm::quat& o); - void setScale(const float s); + void setEyeOffsetPosition(const glm::vec3& p) { _eyeOffsetPosition = p; } + void setEyeOffsetOrientation(const glm::quat& o) { _eyeOffsetOrientation = o; } + void setScale(const float s) { _scale = s; } + + glm::vec3 getPosition() const { return _position + _hmdPosition; } + glm::quat getRotation() const { return _rotation * _hmdRotation; } + const glm::vec3& getHmdPosition() const { return _hmdPosition; } + const glm::quat& getHmdRotation() const { return _hmdRotation; } - const glm::vec3& getPosition() const { return _position; } - const glm::quat& getRotation() const { return _rotation; } CameraMode getMode() const { return _mode; } float getFieldOfView() const { return _fieldOfView; } float getAspectRatio() const { return _aspectRatio; } @@ -60,7 +66,6 @@ public: private: - bool _needsToInitialize; CameraMode _mode; glm::vec3 _position; float _fieldOfView; // degrees @@ -70,7 +75,10 @@ private: glm::vec3 _eyeOffsetPosition; glm::quat _eyeOffsetOrientation; glm::quat _rotation; - + glm::vec3 _hmdPosition; + glm::quat _hmdRotation; + glm::vec3 _targetPosition; + glm::quat _targetRotation; float _scale; }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index c0ce474d16..36035880fd 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1151,7 +1151,7 @@ const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f; bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const { const Head* head = getHead(); - return (renderMode != NORMAL_RENDER_MODE) || + return (renderMode != NORMAL_RENDER_MODE) || (Application::getInstance()->getCamera()->getMode() != CAMERA_MODE_FIRST_PERSON) || (glm::length(cameraPosition - head->getEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale); } diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index eb48e3d463..ad6a914112 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -337,15 +337,20 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p #else ovrEyeType eye = _ovrHmdDesc.EyeRenderOrder[eyeIndex]; #endif - //Set the camera rotation for this eye + // Set the camera rotation for this eye eyeRenderPose[eye] = ovrHmd_GetEyePose(_ovrHmd, eye); orientation.x = eyeRenderPose[eye].Orientation.x; orientation.y = eyeRenderPose[eye].Orientation.y; orientation.z = eyeRenderPose[eye].Orientation.z; orientation.w = eyeRenderPose[eye].Orientation.w; - _camera->setRotation(bodyOrientation * orientation); - _camera->setPosition(position + trackerPosition); + // Update the application camera with the latest HMD position + whichCamera.setHmdPosition(trackerPosition); + whichCamera.setHmdRotation(orientation); + + // Update our camera to what the application camera is doing + _camera->setRotation(whichCamera.getRotation()); + _camera->setPosition(whichCamera.getPosition()); // Store the latest left and right eye render locations for things that need to know glm::vec3 thisEyePosition = position + trackerPosition + @@ -407,10 +412,7 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p renderDistortionMesh(eyeRenderPose); glBindTexture(GL_TEXTURE_2D, 0); - - // Update camera for use by rest of Interface. - whichCamera.setPosition((_leftEyePosition + _rightEyePosition) / 2.f); - whichCamera.setRotation(_camera->getRotation()); + #endif } diff --git a/interface/src/gpu/Resource.cpp b/interface/src/gpu/Resource.cpp new file mode 100644 index 0000000000..23ea12e6e8 --- /dev/null +++ b/interface/src/gpu/Resource.cpp @@ -0,0 +1,220 @@ +// +// Resource.cpp +// interface/src/gpu +// +// Created by Sam Gateau on 10/8/2014. +// Copyright 2014 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 +// +#include "Resource.h" + +#include + +using namespace gpu; + +Resource::Size Resource::Sysmem::allocateMemory(Byte** dataAllocated, Size size) { + if ( !dataAllocated ) { + qWarning() << "Buffer::Sysmem::allocateMemory() : Must have a valid dataAllocated pointer."; + return NOT_ALLOCATED; + } + + // Try to allocate if needed + Size newSize = 0; + if (size > 0) { + // Try allocating as much as the required size + one block of memory + newSize = size; + (*dataAllocated) = new Byte[newSize]; + // Failed? + if (!(*dataAllocated)) { + qWarning() << "Buffer::Sysmem::allocate() : Can't allocate a system memory buffer of " << newSize << "bytes. Fails to create the buffer Sysmem."; + return NOT_ALLOCATED; + } + } + + // Return what's actually allocated + return newSize; +} + +void Resource::Sysmem::deallocateMemory(Byte* dataAllocated, Size size) { + if (dataAllocated) { + delete[] dataAllocated; + } +} + +Resource::Sysmem::Sysmem() : + _data(NULL), + _size(0), + _stamp(0) +{ +} + +Resource::Sysmem::Sysmem(Size size, const Byte* bytes) : + _data(NULL), + _size(0), + _stamp(0) +{ + if (size > 0) { + _size = allocateMemory(&_data, size); + if (_size >= size) { + if (bytes) { + memcpy(_data, bytes, size); + } + } + } +} + +Resource::Sysmem::~Sysmem() { + deallocateMemory( _data, _size ); + _data = NULL; + _size = 0; +} + +Resource::Size Resource::Sysmem::allocate(Size size) { + if (size != _size) { + Byte* newData = 0; + Size newSize = 0; + if (size > 0) { + Size allocated = allocateMemory(&newData, size); + if (allocated == NOT_ALLOCATED) { + // early exit because allocation failed + return 0; + } + newSize = allocated; + } + // Allocation was successful, can delete previous data + deallocateMemory(_data, _size); + _data = newData; + _size = newSize; + _stamp++; + } + return _size; +} + +Resource::Size Resource::Sysmem::resize(Size size) { + if (size != _size) { + Byte* newData = 0; + Size newSize = 0; + if (size > 0) { + Size allocated = allocateMemory(&newData, size); + if (allocated == NOT_ALLOCATED) { + // early exit because allocation failed + return _size; + } + newSize = allocated; + // Restore back data from old buffer in the new one + if (_data) { + Size copySize = ((newSize < _size)? newSize: _size); + memcpy( newData, _data, copySize); + } + } + // Reallocation was successful, can delete previous data + deallocateMemory(_data, _size); + _data = newData; + _size = newSize; + _stamp++; + } + return _size; +} + +Resource::Size Resource::Sysmem::setData( Size size, const Byte* bytes ) { + if (allocate(size) == size) { + if (bytes) { + memcpy( _data, bytes, _size ); + _stamp++; + } + } + return _size; +} + +Resource::Size Resource::Sysmem::setSubData( Size offset, Size size, const Byte* bytes) { + if (((offset + size) <= getSize()) && bytes) { + memcpy( _data + offset, bytes, size ); + _stamp++; + return size; + } + return 0; +} + +Resource::Size Resource::Sysmem::append(Size size, const Byte* bytes) { + if (size > 0) { + Size oldSize = getSize(); + Size totalSize = oldSize + size; + if (resize(totalSize) == totalSize) { + return setSubData(oldSize, size, bytes); + } + } + return 0; +} + +Buffer::Buffer() : + Resource(), + _sysmem(NULL), + _gpuObject(NULL) { + _sysmem = new Sysmem(); +} + +Buffer::~Buffer() { + if (_sysmem) { + delete _sysmem; + _sysmem = 0; + } + if (_gpuObject) { + delete _gpuObject; + _gpuObject = 0; + } +} + +Buffer::Size Buffer::resize(Size size) { + return editSysmem().resize(size); +} + +Buffer::Size Buffer::setData(Size size, const Byte* data) { + return editSysmem().setData(size, data); +} + +Buffer::Size Buffer::setSubData(Size offset, Size size, const Byte* data) { + return editSysmem().setSubData( offset, size, data); +} + +Buffer::Size Buffer::append(Size size, const Byte* data) { + return editSysmem().append( size, data); +} + +namespace gpu { +namespace backend { + +BufferObject::~BufferObject() { + if (_buffer!=0) { + glDeleteBuffers(1, &_buffer); + } +} + +void syncGPUObject(const Buffer& buffer) { + BufferObject* object = buffer.getGPUObject(); + + if (object && (object->_stamp == buffer.getSysmem().getStamp())) { + return; + } + + // need to have a gpu object? + if (!object) { + object = new BufferObject(); + glGenBuffers(1, &object->_buffer); + buffer.setGPUObject(object); + } + + // Now let's update the content of the bo with the sysmem version + // TODO: in the future, be smarter about when to actually upload the glBO version based on the data that did change + //if () { + glBindBuffer(GL_ARRAY_BUFFER, object->_buffer); + glBufferData(GL_ARRAY_BUFFER, buffer.getSysmem().getSize(), buffer.getSysmem().readData(), GL_DYNAMIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, 0); + object->_stamp = buffer.getSysmem().getStamp(); + object->_size = buffer.getSysmem().getSize(); + //} +} + +}; +}; diff --git a/interface/src/gpu/Resource.h b/interface/src/gpu/Resource.h new file mode 100644 index 0000000000..fbbbe7f7fd --- /dev/null +++ b/interface/src/gpu/Resource.h @@ -0,0 +1,166 @@ +// +// Resource.h +// interface/src/gpu +// +// Created by Sam Gateau on 10/8/2014. +// Copyright 2014 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 +// +#ifndef hifi_gpu_Resource_h +#define hifi_gpu_Resource_h + +#include +#include "InterfaceConfig.h" + +namespace gpu { + +class Buffer; +typedef int Stamp; + +// TODO: move the backend namespace into dedicated files, for now we keep it close to the gpu objects definition for convenience +namespace backend { + + class BufferObject { + public: + Stamp _stamp; + GLuint _buffer; + GLuint _size; + + BufferObject() : + _stamp(0), + _buffer(0), + _size(0) + {} + + ~BufferObject(); + }; + void syncGPUObject(const Buffer& buffer); +}; + +class Resource { +public: + typedef unsigned char Byte; + typedef unsigned int Size; + + static const Size NOT_ALLOCATED = -1; + + // The size in bytes of data stored in the resource + virtual Size getSize() const = 0; + +protected: + + Resource() {} + virtual ~Resource() {} + + // Sysmem is the underneath cache for the data in ram of a resource. + class Sysmem { + public: + + Sysmem(); + Sysmem(Size size, const Byte* bytes); + ~Sysmem(); + + Size getSize() const { return _size; } + + // Allocate the byte array + // \param pSize The nb of bytes to allocate, if already exist, content is lost. + // \return The nb of bytes allocated, nothing if allready the appropriate size. + Size allocate(Size pSize); + + // Resize the byte array + // Keep previous data [0 to min(pSize, mSize)] + Size resize(Size pSize); + + // Assign data bytes and size (allocate for size, then copy bytes if exists) + Size setData(Size size, const Byte* bytes ); + + // Update Sub data, + // doesn't allocate and only copy size * bytes at the offset location + // only if all fits in the existing allocated buffer + Size setSubData(Size offset, Size size, const Byte* bytes); + + // Append new data at the end of the current buffer + // do a resize( size + getSIze) and copy the new data + // \return the number of bytes copied + Size append(Size size, const Byte* data); + + // Access the byte array. + // The edit version allow to map data. + inline const Byte* readData() const { return _data; } + inline Byte* editData() { _stamp++; return _data; } + + template< typename T > + const T* read() const { return reinterpret_cast< T* > ( _data ); } + template< typename T > + T* edit() { _stamp++; return reinterpret_cast< T* > ( _data ); } + + // Access the current version of the sysmem, used to compare if copies are in sync + inline Stamp getStamp() const { return _stamp; } + + static Size allocateMemory(Byte** memAllocated, Size size); + static void deallocateMemory(Byte* memDeallocated, Size size); + + private: + Sysmem(const Sysmem& sysmem) {} + Sysmem &operator=(const Sysmem& other) {return *this;} + + Stamp _stamp; + Size _size; + Byte* _data; + }; + +}; + +class Buffer : public Resource { +public: + + Buffer(); + Buffer(const Buffer& buf); + ~Buffer(); + + // The size in bytes of data stored in the buffer + inline Size getSize() const { return getSysmem().getSize(); } + inline const Byte* getData() const { return getSysmem().readData(); } + + // Resize the buffer + // Keep previous data [0 to min(pSize, mSize)] + Size resize(Size pSize); + + // Assign data bytes and size (allocate for size, then copy bytes if exists) + // \return the size of the buffer + Size setData(Size size, const Byte* data); + + // Assign data bytes and size (allocate for size, then copy bytes if exists) + // \return the number of bytes copied + Size setSubData(Size offset, Size size, const Byte* data); + + // Append new data at the end of the current buffer + // do a resize( size + getSize) and copy the new data + // \return the number of bytes copied + Size append(Size size, const Byte* data); + + // this is a temporary hack so the current rendering code can access the underneath gl Buffer Object + // TODO: remove asap, when the backend is doing more of the gl features + inline GLuint getGLBufferObject() const { backend::syncGPUObject(*this); return getGPUObject()->_buffer; } + +protected: + + Sysmem* _sysmem; + + typedef backend::BufferObject GPUObject; + mutable backend::BufferObject* _gpuObject; + + inline const Sysmem& getSysmem() const { assert(_sysmem); return (*_sysmem); } + inline Sysmem& editSysmem() { assert(_sysmem); return (*_sysmem); } + + inline GPUObject* getGPUObject() const { return _gpuObject; } + inline void setGPUObject(GPUObject* gpuObject) const { _gpuObject = gpuObject; } + + friend void backend::syncGPUObject(const Buffer& buffer); +}; + +}; + +#endif diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 6b7d499bf9..dd9ac67837 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -690,7 +690,6 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color); } - } void Stats::setMetavoxelStats(int internal, int leaves, int sendProgress, diff --git a/interface/src/ui/TextRenderer.cpp b/interface/src/ui/TextRenderer.cpp index c4f6b328ef..8c78a06d55 100644 --- a/interface/src/ui/TextRenderer.cpp +++ b/interface/src/ui/TextRenderer.cpp @@ -21,6 +21,10 @@ #include "InterfaceConfig.h" #include "TextRenderer.h" +#include "glm/glm.hpp" +#include + + // the width/height of the cached glyph textures const int IMAGE_SIZE = 256; @@ -63,8 +67,17 @@ int TextRenderer::calculateHeight(const char* str) { } int TextRenderer::draw(int x, int y, const char* str) { - glEnable(GL_TEXTURE_2D); - + // Grab the current color + float currentColor[4]; + glGetFloatv(GL_CURRENT_COLOR, currentColor); + int compactColor = ((int( currentColor[0] * 255.f) & 0xFF)) | + ((int( currentColor[1] * 255.f) & 0xFF) << 8) | + ((int( currentColor[2] * 255.f) & 0xFF) << 16) | + ((int( currentColor[3] * 255.f) & 0xFF) << 24); + +// TODO: Remove that code once we test for performance improvments + //glEnable(GL_TEXTURE_2D); + int maxHeight = 0; for (const char* ch = str; *ch != 0; ch++) { const Glyph& glyph = getGlyph(*ch); @@ -76,20 +89,24 @@ int TextRenderer::draw(int x, int y, const char* str) { if (glyph.bounds().height() > maxHeight) { maxHeight = glyph.bounds().height(); } - - glBindTexture(GL_TEXTURE_2D, glyph.textureID()); + //glBindTexture(GL_TEXTURE_2D, glyph.textureID()); int left = x + glyph.bounds().x(); int right = x + glyph.bounds().x() + glyph.bounds().width(); int bottom = y + glyph.bounds().y(); int top = y + glyph.bounds().y() + glyph.bounds().height(); - + + glm::vec2 leftBottom = glm::vec2(float(left), float(bottom)); + glm::vec2 rightTop = glm::vec2(float(right), float(top)); + float scale = QApplication::desktop()->windowHandle()->devicePixelRatio() / IMAGE_SIZE; float ls = glyph.location().x() * scale; float rs = (glyph.location().x() + glyph.bounds().width()) * scale; float bt = glyph.location().y() * scale; float tt = (glyph.location().y() + glyph.bounds().height()) * scale; - + +// TODO: Remove that code once we test for performance improvments +/* glBegin(GL_QUADS); glTexCoord2f(ls, bt); glVertex2f(left, bottom); @@ -100,12 +117,39 @@ int TextRenderer::draw(int x, int y, const char* str) { glTexCoord2f(ls, tt); glVertex2f(left, top); glEnd(); - +*/ + + const int NUM_COORDS_SCALARS_PER_GLYPH = 16; + float vertexBuffer[NUM_COORDS_SCALARS_PER_GLYPH] = { leftBottom.x, leftBottom.y, ls, bt, + rightTop.x, leftBottom.y, rs, bt, + rightTop.x, rightTop.y, rs, tt, + leftBottom.x, rightTop.y, ls, tt, }; + + const int NUM_COLOR_SCALARS_PER_GLYPH = 4; + unsigned int colorBuffer[NUM_COLOR_SCALARS_PER_GLYPH] = { compactColor, compactColor, compactColor, compactColor }; + + gpu::Buffer::Size offset = sizeof(vertexBuffer) * _numGlyphsBatched; + gpu::Buffer::Size colorOffset = sizeof(colorBuffer) * _numGlyphsBatched; + if ((offset + sizeof(vertexBuffer)) > _glyphsBuffer.getSize()) { + _glyphsBuffer.append(sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer); + _glyphsColorBuffer.append(sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer); + } else { + _glyphsBuffer.setSubData(offset, sizeof(vertexBuffer), (gpu::Buffer::Byte*) vertexBuffer); + _glyphsColorBuffer.setSubData(colorOffset, sizeof(colorBuffer), (gpu::Buffer::Byte*) colorBuffer); + } + _numGlyphsBatched++; + x += glyph.width(); } - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); - + + // TODO: remove these calls once we move to a full batched rendering of the text, for now, one draw call per draw() function call + drawBatch(); + clearBatch(); + +// TODO: Remove that code once we test for performance improvments + // glBindTexture(GL_TEXTURE_2D, 0); + // glDisable(GL_TEXTURE_2D); + return maxHeight; } @@ -131,8 +175,10 @@ TextRenderer::TextRenderer(const Properties& properties) : _x(IMAGE_SIZE), _y(IMAGE_SIZE), _rowHeight(0), - _color(properties.color) { - + _color(properties.color), + _glyphsBuffer(), + _numGlyphsBatched(0) +{ _font.setKerning(false); } @@ -228,9 +274,74 @@ const Glyph& TextRenderer::getGlyph(char c) { return glyph; } +void TextRenderer::drawBatch() { + if (_numGlyphsBatched <= 0) { + return; + } + + // TODO: Right now the drawBatch is called while calling the draw() function but in the future we'll need to apply the correct transform stack + /* + GLint matrixMode; + glGetIntegerv(GL_MATRIX_MODE, &matrixMode); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + */ + + glEnable(GL_TEXTURE_2D); + // TODO: Apply the correct font atlas texture, for now only one texture per TextRenderer so it should be good + glBindTexture(GL_TEXTURE_2D, _currentTextureID); + + GLuint vbo = _glyphsBuffer.getGLBufferObject(); + GLuint colorvbo = _glyphsColorBuffer.getGLBufferObject(); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + + const int NUM_POS_COORDS = 2; + const int NUM_TEX_COORDS = 2; + const int VERTEX_STRIDE = (NUM_POS_COORDS + NUM_TEX_COORDS) * sizeof(float); + const int VERTEX_TEXCOORD_OFFSET = NUM_POS_COORDS * sizeof(float); + + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glVertexPointer(2, GL_FLOAT, VERTEX_STRIDE, 0); + glTexCoordPointer(2, GL_FLOAT, VERTEX_STRIDE, (GLvoid*) VERTEX_TEXCOORD_OFFSET ); + + glBindBuffer(GL_ARRAY_BUFFER, colorvbo); + glColorPointer(4, GL_UNSIGNED_BYTE, 0, (GLvoid*) 0 ); + + glDrawArrays(GL_QUADS, 0, _numGlyphsBatched * 4); + + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_COLOR_ARRAY); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + + // TODO: Right now the drawBatch is called while calling the draw() function but in the future we'll need to apply the correct transform stack + /* + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(matrixMode); + */ +} + +void TextRenderer::clearBatch() { + _numGlyphsBatched = 0; +} + QHash TextRenderer::_instances; Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) : _textureID(textureID), _location(location), _bounds(bounds), _width(width) { } - diff --git a/interface/src/ui/TextRenderer.h b/interface/src/ui/TextRenderer.h index d3340462d5..1953dda422 100644 --- a/interface/src/ui/TextRenderer.h +++ b/interface/src/ui/TextRenderer.h @@ -19,6 +19,8 @@ #include #include +#include "gpu/Resource.h" + #include "InterfaceConfig.h" // a special "character" that renders as a solid block @@ -64,6 +66,8 @@ public: int computeWidth(char ch); int computeWidth(const char* str); + void drawBatch(); + void clearBatch(); private: TextRenderer(const Properties& properties); @@ -100,6 +104,11 @@ private: // text color QColor _color; + // Graphics Buffer containing the current accumulated glyphs to render + gpu::Buffer _glyphsBuffer; + gpu::Buffer _glyphsColorBuffer; + int _numGlyphsBatched; + static QHash _instances; }; diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 4f9e7c84f6..c7511ca466 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -18,7 +18,8 @@ BillboardOverlay::BillboardOverlay() : _fromImage(-1,-1,-1,-1), _scale(1.0f), - _isFacingAvatar(true) { + _isFacingAvatar(true), + _newTextureNeeded(true) { _isLoaded = false; } @@ -28,6 +29,9 @@ void BillboardOverlay::render() { } if (!_billboard.isEmpty()) { + if (_newTextureNeeded && _billboardTexture) { + _billboardTexture.reset(); + } if (!_billboardTexture) { QImage image = QImage::fromData(_billboard); if (image.format() != QImage::Format_ARGB32) { @@ -38,6 +42,7 @@ void BillboardOverlay::render() { _fromImage.setRect(0, 0, _size.width(), _size.height()); } _billboardTexture.reset(new Texture()); + _newTextureNeeded = false; glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID()); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, _size.width(), _size.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, image.constBits()); @@ -107,9 +112,10 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) { QScriptValue urlValue = properties.property("url"); if (urlValue.isValid()) { - _url = urlValue.toVariant().toString(); - - setBillboardURL(_url); + QString newURL = urlValue.toVariant().toString(); + if (newURL != _url) { + setBillboardURL(newURL); + } } QScriptValue subImageBounds = properties.property("subImage"); @@ -150,9 +156,16 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) { } } -void BillboardOverlay::setBillboardURL(const QUrl url) { +void BillboardOverlay::setBillboardURL(const QString& url) { + _url = url; + QUrl actualURL = url; _isLoaded = false; - QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(url)); + + // clear the billboard if previously set + _billboard.clear(); + _newTextureNeeded = true; + + QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(actualURL)); connect(reply, &QNetworkReply::finished, this, &BillboardOverlay::replyFinished); } diff --git a/interface/src/ui/overlays/BillboardOverlay.h b/interface/src/ui/overlays/BillboardOverlay.h index a0b76869b3..fd594abe67 100644 --- a/interface/src/ui/overlays/BillboardOverlay.h +++ b/interface/src/ui/overlays/BillboardOverlay.h @@ -33,12 +33,13 @@ private slots: void replyFinished(); private: - void setBillboardURL(const QUrl url); + void setBillboardURL(const QString& url); - QUrl _url; + QString _url; QByteArray _billboard; QSize _size; QScopedPointer _billboardTexture; + bool _newTextureNeeded; QRect _fromImage; // where from in the image to sample diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index 6ff256d48e..62ddee4cf2 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "Circle3DOverlay.h" #include "renderer/GlowEffect.h" @@ -39,13 +40,18 @@ void Circle3DOverlay::render() { if (!_visible) { return; // do nothing if we're not visible } + + float alpha = getAlpha(); + + if (alpha == 0.0) { + return; // do nothing if our alpha is 0, we're not visible + } const float FULL_CIRCLE = 360.0f; const float SLICES = 180.0f; // The amount of segment to create the circle const float SLICE_ANGLE = FULL_CIRCLE / SLICES; //const int slices = 15; - float alpha = getAlpha(); xColor color = getColor(); const float MAX_COLOR = 255.0f; glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); @@ -291,6 +297,25 @@ void Circle3DOverlay::setProperties(const QScriptValue &properties) { } } +bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, + const glm::vec3& direction, float& distance, BoxFace& face) const { + + bool intersects = Planar3DOverlay::findRayIntersection(origin, direction, distance, face); + if (intersects) { + glm::vec3 hitAt = origin + (direction * distance); + float distanceToHit = glm::distance(hitAt, _position); + + float maxDimension = glm::max(_dimensions.x, _dimensions.y); + float innerRadius = maxDimension * getInnerRadius(); + float outerRadius = maxDimension * getOuterRadius(); + + // TODO: this really only works for circles, we should be handling the ellipse case as well... + if (distanceToHit < innerRadius || distanceToHit > outerRadius) { + intersects = false; + } + } + return intersects; +} diff --git a/interface/src/ui/overlays/Circle3DOverlay.h b/interface/src/ui/overlays/Circle3DOverlay.h index 791d951105..289781cdd7 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.h +++ b/interface/src/ui/overlays/Circle3DOverlay.h @@ -45,6 +45,8 @@ public: void setMinorTickMarksLength(float value) { _minorTickMarksLength = value; } void setMajorTickMarksColor(const xColor& value) { _majorTickMarksColor = value; } void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; } + + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; protected: float _startAt; diff --git a/interface/src/ui/overlays/Planar3DOverlay.cpp b/interface/src/ui/overlays/Planar3DOverlay.cpp index 42e059c3ca..91a3a023f7 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.cpp +++ b/interface/src/ui/overlays/Planar3DOverlay.cpp @@ -12,6 +12,8 @@ #include "InterfaceConfig.h" #include +#include +#include #include #include @@ -74,3 +76,29 @@ void Planar3DOverlay::setProperties(const QScriptValue& properties) { } } } + +bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face) const { + + RayIntersectionInfo rayInfo; + rayInfo._rayStart = origin; + rayInfo._rayDirection = direction; + rayInfo._rayLength = std::numeric_limits::max(); + + PlaneShape plane; + + const glm::vec3 UNROTATED_NORMAL(0.0f, 0.0f, -1.0f); + glm::vec3 normal = _rotation * UNROTATED_NORMAL; + plane.setNormal(normal); + plane.setPoint(_position); // the position is definitely a point on our plane + + bool intersects = plane.findRayIntersection(rayInfo); + + if (intersects) { + distance = rayInfo._hitDistance; + // TODO: if it intersects, we want to check to see if the intersection point is within our dimensions + // glm::vec3 hitAt = origin + direction * distance; + // _dimensions + } + return intersects; +} diff --git a/interface/src/ui/overlays/Planar3DOverlay.h b/interface/src/ui/overlays/Planar3DOverlay.h index c1733bc0fb..ee4bb3e05a 100644 --- a/interface/src/ui/overlays/Planar3DOverlay.h +++ b/interface/src/ui/overlays/Planar3DOverlay.h @@ -37,6 +37,8 @@ public: virtual void setProperties(const QScriptValue& properties); + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + protected: glm::vec2 _dimensions; }; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 1080a866f5..2690bcd45b 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -174,6 +174,16 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons sittingPoints.setProperty("length", _sittingPoints.size()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(sittingPoints, sittingPoints); // gettable, but not settable + AABox aaBox = getAABoxInMeters(); + QScriptValue boundingBox = engine->newObject(); + QScriptValue bottomRightNear = vec3toScriptValue(engine, aaBox.getCorner()); + QScriptValue topFarLeft = vec3toScriptValue(engine, aaBox.calcTopFarLeft()); + QScriptValue center = vec3toScriptValue(engine, aaBox.calcCenter()); + boundingBox.setProperty("brn", bottomRightNear); + boundingBox.setProperty("tfl", topFarLeft); + boundingBox.setProperty("center", center); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(boundingBox, boundingBox); // gettable, but not settable + return properties; } @@ -643,3 +653,20 @@ AACube EntityItemProperties::getMaximumAACubeInMeters() const { return AACube(minimumCorner, diameter); } + +// The minimum bounding box for the entity. +AABox EntityItemProperties::getAABoxInMeters() const { + + // _position represents the position of the registration point. + glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint; + + glm::vec3 unrotatedMinRelativeToEntity = glm::vec3(0.0f, 0.0f, 0.0f) - (_dimensions * _registrationPoint); + glm::vec3 unrotatedMaxRelativeToEntity = _dimensions * registrationRemainder; + Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; + Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation()); + + // shift the extents to be relative to the position/registration point + rotatedExtentsRelativeToRegistrationPoint.shiftBy(_position); + + return AABox(rotatedExtentsRelativeToRegistrationPoint); +} diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 3c77b63ab6..6e1594fb9b 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -101,6 +101,7 @@ public: AACube getMaximumAACubeInTreeUnits() const; AACube getMaximumAACubeInMeters() const; + AABox getAABoxInMeters() const; void debugDump() const; diff --git a/libraries/script-engine/src/Vec3.cpp b/libraries/script-engine/src/Vec3.cpp index 689ce2c747..f88df3b7c0 100644 --- a/libraries/script-engine/src/Vec3.cpp +++ b/libraries/script-engine/src/Vec3.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include "Vec3.h" @@ -37,7 +39,7 @@ glm::vec3 Vec3::sum(const glm::vec3& v1, const glm::vec3& v2) { return v1 + v2; } glm::vec3 Vec3::subtract(const glm::vec3& v1, const glm::vec3& v2) { - return v1 - v2; + return v1 - v2; } float Vec3::length(const glm::vec3& v) { @@ -48,6 +50,10 @@ float Vec3::distance(const glm::vec3& v1, const glm::vec3& v2) { return glm::distance(v1, v2); } +float Vec3::orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3) { + return glm::degrees(glm::orientedAngle(glm::normalize(v1), glm::normalize(v2), glm::normalize(v3))); +} + glm::vec3 Vec3::normalize(const glm::vec3& v) { return glm::normalize(v); } diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index 2af1350e4a..693fd604f7 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -34,6 +34,7 @@ public slots: glm::vec3 subtract(const glm::vec3& v1, const glm::vec3& v2); float length(const glm::vec3& v); float distance(const glm::vec3& v1, const glm::vec3& v2); + float orientedAngle(const glm::vec3& v1, const glm::vec3& v2, const glm::vec3& v3); glm::vec3 normalize(const glm::vec3& v); void print(const QString& lable, const glm::vec3& v); bool equal(const glm::vec3& v1, const glm::vec3& v2);