diff --git a/examples/clipboardExample.js b/examples/clipboardExample.js new file mode 100644 index 0000000000..81f0daae10 --- /dev/null +++ b/examples/clipboardExample.js @@ -0,0 +1,151 @@ +// +// clipboardExample.js +// hifi +// +// Created by Brad Hefta-Gaub on 1/28/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// This is an example script that demonstrates use of the Clipboard class +// +// + +var selectedVoxel = { x: 0, y: 0, z: 0, s: 0 }; +var selectedSize = 4; + +function printKeyEvent(eventName, event) { + print(eventName); + print(" event.key=" + event.key); + print(" event.text=" + event.text); + print(" event.isShifted=" + event.isShifted); + print(" event.isControl=" + event.isControl); + print(" event.isMeta=" + event.isMeta); + print(" event.isAlt=" + event.isAlt); + print(" event.isKeypad=" + event.isKeypad); +} + + +function keyPressEvent(event) { + var debug = false; + if (debug) { + printKeyEvent("keyPressEvent", event); + } +} + +function keyReleaseEvent(event) { + var debug = false; + if (debug) { + printKeyEvent("keyReleaseEvent", event); + } + + // Note: this sample uses Alt+ as the key codes for these clipboard items + if ((event.key == 199 || event.key == 67 || event.text == "C" || event.text == "c") && event.isAlt) { + print("the Alt+C key was pressed"); + Clipboard.copyVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s); + } + if ((event.key == 8776 || event.key == 88 || event.text == "X" || event.text == "x") && event.isAlt) { + print("the Alt+X key was pressed"); + Clipboard.cutVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s); + } + if ((event.key == 8730 || event.key == 86 || event.text == "V" || event.text == "v") && event.isAlt) { + print("the Alt+V key was pressed"); + Clipboard.pasteVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s); + } + if (event.text == "DELETE" || event.text == "BACKSPACE") { + print("the DELETE/BACKSPACE key was pressed"); + Clipboard.deleteVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s); + } + + if ((event.text == "E" || event.text == "e") && event.isMeta) { + print("the Ctl+E key was pressed"); + Clipboard.exportVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s); + } + if ((event.text == "I" || event.text == "i") && event.isMeta) { + print("the Ctl+I key was pressed"); + Clipboard.importVoxels(); + } + if ((event.key == 78 || event.text == "N" || event.text == "n") && event.isMeta) { + print("the Ctl+N key was pressed, nudging to left 1 meter"); + Clipboard.nudgeVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s, { x: -1, y: 0, z: 0 }); + } +} + +// Map keyPress and mouse move events to our callbacks +Controller.keyPressEvent.connect(keyPressEvent); +Controller.keyReleaseEvent.connect(keyReleaseEvent); + +var selectCube = Overlays.addOverlay("cube", { + position: { x: 0, y: 0, z: 0}, + size: selectedSize, + color: { red: 255, green: 255, blue: 0}, + alpha: 1, + solid: false, + visible: false, + lineWidth: 4 + }); + + +function mouseMoveEvent(event) { + + var pickRay = Camera.computePickRay(event.x, event.y); + + var debug = false; + if (debug) { + print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y); + print("called Camera.computePickRay()"); + print("computePickRay origin=" + pickRay.origin.x + ", " + pickRay.origin.y + ", " + pickRay.origin.z); + print("computePickRay direction=" + pickRay.direction.x + ", " + pickRay.direction.y + ", " + pickRay.direction.z); + } + + var intersection = Voxels.findRayIntersection(pickRay); + + if (intersection.intersects) { + if (debug) { + print("intersection voxel.red/green/blue=" + intersection.voxel.red + ", " + + intersection.voxel.green + ", " + intersection.voxel.blue); + print("intersection voxel.x/y/z/s=" + intersection.voxel.x + ", " + + intersection.voxel.y + ", " + intersection.voxel.z+ ": " + intersection.voxel.s); + print("intersection face=" + intersection.face); + print("intersection distance=" + intersection.distance); + print("intersection intersection.x/y/z=" + intersection.intersection.x + ", " + + intersection.intersection.y + ", " + intersection.intersection.z); + } + + + + var x = Math.floor(intersection.voxel.x / selectedSize) * selectedSize; + var y = Math.floor(intersection.voxel.y / selectedSize) * selectedSize; + var z = Math.floor(intersection.voxel.z / selectedSize) * selectedSize; + selectedVoxel = { x: x, y: y, z: z, s: selectedSize }; + Overlays.editOverlay(selectCube, { position: selectedVoxel, size: selectedSize, visible: true } ); + } else { + Overlays.editOverlay(selectCube, { visible: false } ); + selectedVoxel = { x: 0, y: 0, z: 0, s: 0 }; + } +} + +Controller.mouseMoveEvent.connect(mouseMoveEvent); + +function wheelEvent(event) { + var debug = false; + if (debug) { + print("wheelEvent"); + print(" event.x,y=" + event.x + ", " + event.y); + print(" event.delta=" + event.delta); + print(" event.orientation=" + event.orientation); + print(" event.isLeftButton=" + event.isLeftButton); + print(" event.isRightButton=" + event.isRightButton); + print(" event.isMiddleButton=" + event.isMiddleButton); + print(" event.isShifted=" + event.isShifted); + print(" event.isControl=" + event.isControl); + print(" event.isMeta=" + event.isMeta); + print(" event.isAlt=" + event.isAlt); + } +} + +Controller.wheelEvent.connect(wheelEvent); + +function scriptEnding() { + Overlays.deleteOverlay(selectCube); +} + +Script.scriptEnding.connect(scriptEnding); diff --git a/examples/editVoxels.js b/examples/editVoxels.js index c67ff0dcfa..81e3000566 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -72,6 +72,10 @@ audioOptions.volume = 0.5; var editToolsOn = false; // starts out off +// previewAsVoxel - by default, we will preview adds/deletes/recolors as just 4 lines on the intersecting face. But if you +// the preview to show a full voxel then set this to true and the voxel will be displayed for voxel editing +var previewAsVoxel = false; + var voxelPreview = Overlays.addOverlay("cube", { position: { x: 0, y: 0, z: 0}, size: 1, @@ -81,14 +85,54 @@ var voxelPreview = Overlays.addOverlay("cube", { visible: false, lineWidth: 4 }); + +var linePreviewTop = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false, + lineWidth: 1 + }); +var linePreviewBottom = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false, + lineWidth: 1 + }); + +var linePreviewLeft = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false, + lineWidth: 1 + }); + +var linePreviewRight = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false, + lineWidth: 1 + }); + + +// these will be used below +var sliderWidth = 158; +var sliderHeight = 35; // These will be our "overlay IDs" var swatches = new Array(); var swatchHeight = 54; var swatchWidth = 31; var swatchesWidth = swatchWidth * numColors; -var swatchesX = (windowDimensions.x - swatchesWidth) / 2; +var swatchesX = (windowDimensions.x - (swatchesWidth + sliderWidth)) / 2; var swatchesY = windowDimensions.y - swatchHeight; // create the overlays, position them in a row, set their colors, and for the selected one, use a different source image @@ -115,6 +159,143 @@ for (s = 0; s < numColors; s++) { } +// These will be our tool palette overlays +var numberOfTools = 5; +var toolHeight = 40; +var toolWidth = 62; +var toolsHeight = toolHeight * numberOfTools; +var toolsX = 0; +var toolsY = (windowDimensions.y - toolsHeight) / 2; + +var addToolAt = 0; +var deleteToolAt = 1; +var recolorToolAt = 2; +var eyedropperToolAt = 3; +var selectToolAt = 4; +var toolSelectedColor = { red: 255, green: 255, blue: 255 }; +var notSelectedColor = { red: 128, green: 128, blue: 128 }; + +var addTool = Overlays.addOverlay("image", { + x: 0, y: 0, width: toolWidth, height: toolHeight, + subImage: { x: 0, y: toolHeight * addToolAt, width: toolWidth, height: toolHeight }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg", + color: toolSelectedColor, + visible: false, + alpha: 0.9 + }); + +var deleteTool = Overlays.addOverlay("image", { + x: 0, y: 0, width: toolWidth, height: toolHeight, + subImage: { x: 0, y: toolHeight * deleteToolAt, width: toolWidth, height: toolHeight }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg", + color: toolSelectedColor, + visible: false, + alpha: 0.9 + }); + +var recolorTool = Overlays.addOverlay("image", { + x: 0, y: 0, width: toolWidth, height: toolHeight, + subImage: { x: 0, y: toolHeight * recolorToolAt, width: toolWidth, height: toolHeight }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg", + color: toolSelectedColor, + visible: false, + alpha: 0.9 + }); + +var eyedropperTool = Overlays.addOverlay("image", { + x: 0, y: 0, width: toolWidth, height: toolHeight, + subImage: { x: 0, y: toolHeight * eyedropperToolAt, width: toolWidth, height: toolHeight }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg", + color: toolSelectedColor, + visible: false, + alpha: 0.9 + }); + +var selectTool = Overlays.addOverlay("image", { + x: 0, y: 0, width: toolWidth, height: toolHeight, + subImage: { x: 0, y: toolHeight * selectToolAt, width: toolWidth, height: toolHeight }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/hifi-interface-tools.svg", + color: toolSelectedColor, + visible: false, + alpha: 0.9 + }); + + +// This will create a couple of image overlays that make a "slider", we will demonstrate how to trap mouse messages to +// move the slider + +// see above... +//var sliderWidth = 158; +//var sliderHeight = 35; + +var sliderX = swatchesX + swatchesWidth; +var sliderY = windowDimensions.y - sliderHeight; +var slider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: sliderX, y: sliderY, width: sliderWidth, height: sliderHeight}, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); + + +// The slider is handled in the mouse event callbacks. +var isMovingSlider = false; +var thumbClickOffsetX = 0; + +// This is the thumb of our slider +var minThumbX = 30; // relative to the x of the slider +var maxThumbX = minThumbX + 65; +var thumbExtents = maxThumbX - minThumbX; +var thumbX = (minThumbX + maxThumbX) / 2; +var thumbY = sliderY + 9; +var thumb = Overlays.addOverlay("image", { + x: sliderX + thumbX, + y: thumbY, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false + }); + +var pointerVoxelScale = 0; // this is the voxel scale used for click to add or delete +var pointerVoxelScaleSet = false; // if voxel scale has not yet been set, we use the intersection size + +var pointerVoxelScaleSteps = 8; // the number of slider position steps +var pointerVoxelScaleOriginStep = 3; // the position of slider for the 1 meter size voxel +var pointerVoxelScaleMin = Math.pow(2, (1-pointerVoxelScaleOriginStep)); +var pointerVoxelScaleMax = Math.pow(2, (pointerVoxelScaleSteps-pointerVoxelScaleOriginStep)); +var thumbDeltaPerStep = thumbExtents / (pointerVoxelScaleSteps - 1); + +function calcThumbFromScale(scale) { + var scaleLog = Math.log(scale)/Math.log(2); + var thumbStep = scaleLog + pointerVoxelScaleOriginStep; + if (thumbStep < 1) { + thumbStep = 1; + } + if (thumbStep > pointerVoxelScaleSteps) { + thumbStep = pointerVoxelScaleSteps; + } + thumbX = (thumbDeltaPerStep * (thumbStep - 1)) + minThumbX; + Overlays.editOverlay(thumb, { x: thumbX + sliderX } ); +} + +function calcScaleFromThumb(newThumbX) { + // newThumbX is the pixel location relative to start of slider, + // we need to figure out the actual offset in the allowed slider area + thumbAt = newThumbX - minThumbX; + thumbStep = Math.floor((thumbAt/ thumbExtents) * (pointerVoxelScaleSteps-1)) + 1; + pointerVoxelScale = Math.pow(2, (thumbStep-pointerVoxelScaleOriginStep)); + // now reset the display accordingly... + calcThumbFromScale(pointerVoxelScale); + + // if the user moved the thumb, then they are fixing the voxel scale + pointerVoxelScaleSet = true; +} + function setAudioPosition() { var camera = Camera.getPosition(); var forwardVector = Quat.getFront(MyAvatar.orientation); @@ -144,85 +325,225 @@ var trackLastMouseX = 0; var trackLastMouseY = 0; var trackAsDelete = false; var trackAsRecolor = false; +var trackAsEyedropper = false; +var trackAsOrbit = false; -function showPreviewVoxel() { - if (editToolsOn) { - var voxelColor; +function calculateVoxelFromIntersection(intersection, operation) { + //print("calculateVoxelFromIntersection() operation="+operation); + var resultVoxel; + + var voxelSize; + if (pointerVoxelScaleSet) { + voxelSize = pointerVoxelScale; + } else { + voxelSize = intersection.voxel.s; + } - var pickRay = Camera.computePickRay(trackLastMouseX, trackLastMouseY); - var intersection = Voxels.findRayIntersection(pickRay); + // first, calculate the enclosed voxel of size voxelSize that the intersection point falls inside of. + // if you have a voxelSize that's smaller than the voxel you're intersecting, this calculation will result + // in the subvoxel that the intersection point falls in + var x = Math.floor(intersection.intersection.x / voxelSize) * voxelSize; + var y = Math.floor(intersection.intersection.y / voxelSize) * voxelSize; + var z = Math.floor(intersection.intersection.z / voxelSize) * voxelSize; + resultVoxel = { x: x, y: y, z: z, s: voxelSize }; + highlightAt = { x: x, y: y, z: z, s: voxelSize }; - if (whichColor == -1) { - // Copy mode - use clicked voxel color - voxelColor = { red: intersection.voxel.red, - green: intersection.voxel.green, - blue: intersection.voxel.blue }; - } else { - voxelColor = { red: colors[whichColor].red, - green: colors[whichColor].green, - blue: colors[whichColor].blue }; + // now we also want to calculate the "edge square" for the face for this voxel + if (intersection.face == "MIN_X_FACE") { + highlightAt.x = intersection.voxel.x; + resultVoxel.x = intersection.voxel.x; + if (operation == "add") { + resultVoxel.x -= voxelSize; + } + + resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y, z: highlightAt.z }; + resultVoxel.bottomRight = {x: highlightAt.x, y: highlightAt.y, z: highlightAt.z + voxelSize }; + resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y + voxelSize, z: highlightAt.z }; + resultVoxel.topRight = {x: highlightAt.x, y: highlightAt.y + voxelSize, z: highlightAt.z + voxelSize }; + + } else if (intersection.face == "MAX_X_FACE") { + highlightAt.x = intersection.voxel.x + intersection.voxel.s; + resultVoxel.x = intersection.voxel.x + intersection.voxel.s; + if (operation != "add") { + resultVoxel.x -= voxelSize; } - var guidePosition; + resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y, z: highlightAt.z }; + resultVoxel.bottomRight = {x: highlightAt.x, y: highlightAt.y, z: highlightAt.z + voxelSize }; + resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y + voxelSize, z: highlightAt.z }; + resultVoxel.topRight = {x: highlightAt.x, y: highlightAt.y + voxelSize, z: highlightAt.z + voxelSize }; + + } else if (intersection.face == "MIN_Y_FACE") { + + highlightAt.y = intersection.voxel.y; + resultVoxel.y = intersection.voxel.y; - if (trackAsDelete) { - guidePosition = { x: intersection.voxel.x, - y: intersection.voxel.y, - z: intersection.voxel.z }; - Overlays.editOverlay(voxelPreview, { - position: guidePosition, - size: intersection.voxel.s, - visible: true, - color: { red: 255, green: 0, blue: 0 }, - solid: false, - alpha: 1 - }); - } else if (trackAsRecolor) { - guidePosition = { x: intersection.voxel.x - 0.001, - y: intersection.voxel.y - 0.001, - z: intersection.voxel.z - 0.001 }; + if (operation == "add") { + resultVoxel.y -= voxelSize; + } + + resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y, z: highlightAt.z }; + resultVoxel.bottomRight = {x: highlightAt.x + voxelSize , y: highlightAt.y, z: highlightAt.z}; + resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y, z: highlightAt.z + voxelSize }; + resultVoxel.topRight = {x: highlightAt.x + voxelSize , y: highlightAt.y, z: highlightAt.z + voxelSize}; - Overlays.editOverlay(voxelPreview, { - position: guidePosition, - size: intersection.voxel.s + 0.002, - visible: true, - color: voxelColor, - solid: true, - alpha: 0.8 - }); + } else if (intersection.face == "MAX_Y_FACE") { - } else if (!isExtruding) { - guidePosition = { x: intersection.voxel.x, - y: intersection.voxel.y, - z: intersection.voxel.z }; - - if (intersection.face == "MIN_X_FACE") { - guidePosition.x -= intersection.voxel.s; - } else if (intersection.face == "MAX_X_FACE") { - guidePosition.x += intersection.voxel.s; - } else if (intersection.face == "MIN_Y_FACE") { - guidePosition.y -= intersection.voxel.s; - } else if (intersection.face == "MAX_Y_FACE") { - guidePosition.y += intersection.voxel.s; - } else if (intersection.face == "MIN_Z_FACE") { - guidePosition.z -= intersection.voxel.s; - } else if (intersection.face == "MAX_Z_FACE") { - guidePosition.z += intersection.voxel.s; - } + highlightAt.y = intersection.voxel.y + intersection.voxel.s; + resultVoxel.y = intersection.voxel.y + intersection.voxel.s; + if (operation != "add") { + resultVoxel.y -= voxelSize; + } + + resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y, z: highlightAt.z }; + resultVoxel.bottomRight = {x: highlightAt.x + voxelSize , y: highlightAt.y, z: highlightAt.z}; + resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y, z: highlightAt.z + voxelSize }; + resultVoxel.topRight = {x: highlightAt.x + voxelSize , y: highlightAt.y, z: highlightAt.z + voxelSize}; - Overlays.editOverlay(voxelPreview, { - position: guidePosition, - size: intersection.voxel.s, - visible: true, - color: voxelColor, - solid: true, - alpha: 0.7 - }); - } else if (isExtruding) { + } else if (intersection.face == "MIN_Z_FACE") { + + highlightAt.z = intersection.voxel.z; + resultVoxel.z = intersection.voxel.z; + + if (operation == "add") { + resultVoxel.z -= voxelSize; + } + + resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y, z: highlightAt.z }; + resultVoxel.bottomRight = {x: highlightAt.x + voxelSize , y: highlightAt.y, z: highlightAt.z}; + resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y + voxelSize, z: highlightAt.z }; + resultVoxel.topRight = {x: highlightAt.x + voxelSize , y: highlightAt.y + voxelSize, z: highlightAt.z}; + + } else if (intersection.face == "MAX_Z_FACE") { + + highlightAt.z = intersection.voxel.z + intersection.voxel.s; + resultVoxel.z = intersection.voxel.z + intersection.voxel.s; + if (operation != "add") { + resultVoxel.z -= voxelSize; + } + + resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y, z: highlightAt.z }; + resultVoxel.bottomRight = {x: highlightAt.x + voxelSize , y: highlightAt.y, z: highlightAt.z}; + resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y + voxelSize, z: highlightAt.z }; + resultVoxel.topRight = {x: highlightAt.x + voxelSize , y: highlightAt.y + voxelSize, z: highlightAt.z}; + + } + + return resultVoxel; +} + +function showPreviewVoxel() { + var voxelColor; + + var pickRay = Camera.computePickRay(trackLastMouseX, trackLastMouseY); + var intersection = Voxels.findRayIntersection(pickRay); + + // if the user hasn't updated the + if (!pointerVoxelScaleSet) { + calcThumbFromScale(intersection.voxel.s); + } + + if (whichColor == -1) { + // Copy mode - use clicked voxel color + voxelColor = { red: intersection.voxel.red, + green: intersection.voxel.green, + blue: intersection.voxel.blue }; + } else { + voxelColor = { red: colors[whichColor].red, + green: colors[whichColor].green, + blue: colors[whichColor].blue }; + } + + var guidePosition; + + if (trackAsDelete) { + guidePosition = calculateVoxelFromIntersection(intersection,"delete"); + Overlays.editOverlay(voxelPreview, { + position: guidePosition, + size: guidePosition.s, + visible: true, + color: { red: 255, green: 0, blue: 0 }, + solid: false, + alpha: 1 + }); + } else if (trackAsRecolor || trackAsEyedropper) { + guidePosition = calculateVoxelFromIntersection(intersection,"recolor"); + + Overlays.editOverlay(voxelPreview, { + position: guidePosition, + size: guidePosition.s + 0.002, + visible: true, + color: voxelColor, + solid: true, + alpha: 0.8 + }); + } else if (trackAsOrbit) { + Overlays.editOverlay(voxelPreview, { visible: false }); + } else if (!isExtruding) { + guidePosition = calculateVoxelFromIntersection(intersection,"add"); + + Overlays.editOverlay(voxelPreview, { + position: guidePosition, + size: guidePosition.s, + visible: true, + color: voxelColor, + solid: true, + alpha: 0.7 + }); + } else if (isExtruding) { + Overlays.editOverlay(voxelPreview, { visible: false }); + } +} + +function showPreviewLines() { + + var pickRay = Camera.computePickRay(trackLastMouseX, trackLastMouseY); + var intersection = Voxels.findRayIntersection(pickRay); + + if (intersection.intersects) { + + // if the user hasn't updated the + if (!pointerVoxelScaleSet) { + calcThumbFromScale(intersection.voxel.s); + } + + resultVoxel = calculateVoxelFromIntersection(intersection,""); + Overlays.editOverlay(linePreviewTop, { position: resultVoxel.topLeft, end: resultVoxel.topRight, visible: true }); + Overlays.editOverlay(linePreviewBottom, { position: resultVoxel.bottomLeft, end: resultVoxel.bottomRight, visible: true }); + Overlays.editOverlay(linePreviewLeft, { position: resultVoxel.topLeft, end: resultVoxel.bottomLeft, visible: true }); + Overlays.editOverlay(linePreviewRight, { position: resultVoxel.topRight, end: resultVoxel.bottomRight, visible: true }); + + } else { + Overlays.editOverlay(linePreviewTop, { visible: false }); + Overlays.editOverlay(linePreviewBottom, { visible: false }); + Overlays.editOverlay(linePreviewLeft, { visible: false }); + Overlays.editOverlay(linePreviewRight, { visible: false }); + } +} + +function showPreviewGuides() { + if (editToolsOn) { + if (previewAsVoxel) { + showPreviewVoxel(); + + // make sure alternative is hidden + Overlays.editOverlay(linePreviewTop, { visible: false }); + Overlays.editOverlay(linePreviewBottom, { visible: false }); + Overlays.editOverlay(linePreviewLeft, { visible: false }); + Overlays.editOverlay(linePreviewRight, { visible: false }); + } else { + showPreviewLines(); + + // make sure alternative is hidden Overlays.editOverlay(voxelPreview, { visible: false }); } } else { + // make sure all previews are off Overlays.editOverlay(voxelPreview, { visible: false }); + Overlays.editOverlay(linePreviewTop, { visible: false }); + Overlays.editOverlay(linePreviewBottom, { visible: false }); + Overlays.editOverlay(linePreviewLeft, { visible: false }); + Overlays.editOverlay(linePreviewRight, { visible: false }); } } @@ -231,27 +552,58 @@ function trackMouseEvent(event) { trackLastMouseY = event.y; trackAsDelete = event.isControl; trackAsRecolor = event.isShifted; - showPreviewVoxel(); + trackAsEyedropper = event.isMeta; + trackAsOrbit = event.isAlt; + showPreviewGuides(); } function trackKeyPressEvent(event) { if (event.text == "CONTROL") { trackAsDelete = true; - showPreviewVoxel(); + moveTools(); } if (event.text == "SHIFT") { trackAsRecolor = true; + moveTools(); } - showPreviewVoxel(); + if (event.text == "META") { + trackAsEyedropper = true; + moveTools(); + } + if (event.text == "ALT") { + trackAsOrbit = true; + moveTools(); + } + showPreviewGuides(); } function trackKeyReleaseEvent(event) { + if (event.text == "ESC") { + pointerVoxelScaleSet = false; + } + if (event.text == "-") { + thumbX -= thumbDeltaPerStep; + calcScaleFromThumb(thumbX); + } + if (event.text == "+") { + thumbX += thumbDeltaPerStep; + calcScaleFromThumb(thumbX); + } if (event.text == "CONTROL") { trackAsDelete = false; - showPreviewVoxel(); + moveTools(); } if (event.text == "SHIFT") { trackAsRecolor = false; + moveTools(); + } + if (event.text == "META") { + trackAsEyedropper = false; + moveTools(); + } + if (event.text == "ALT") { + trackAsOrbit = false; + moveTools(); } // on TAB release, toggle our tool state @@ -260,7 +612,13 @@ function trackKeyReleaseEvent(event) { moveTools(); Audio.playSound(clickSound, audioOptions); } - showPreviewVoxel(); + + // on F1 toggle the preview mode between cubes and lines + if (event.text == "F1") { + previewAsVoxel = !previewAsVoxel; + } + + showPreviewGuides(); } function mousePressEvent(event) { @@ -269,11 +627,30 @@ function mousePressEvent(event) { if (!editToolsOn) { return; } + + var clickedOnSwatch = false; + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); - if (event.isRightButton) { - // debugging of right button click on mac... - print(">>>> RIGHT BUTTON <<<<<"); + // If the user clicked on the thumb, handle the slider logic + if (clickedOverlay == thumb) { + isMovingSlider = true; + thumbClickOffsetX = event.x - (sliderX + thumbX); // this should be the position of the mouse relative to the thumb + return; // no further processing + } else { + // if the user clicked on one of the color swatches, update the selectedSwatch + for (s = 0; s < numColors; s++) { + if (clickedOverlay == swatches[s]) { + whichColor = s; + moveTools(); + clickedOnSwatch = true; + } + } + if (clickedOnSwatch) { + return; // no further processing + } } + + trackMouseEvent(event); // used by preview support mouseX = event.x; mouseY = event.y; @@ -281,6 +658,11 @@ function mousePressEvent(event) { var intersection = Voxels.findRayIntersection(pickRay); audioOptions.position = Vec3.sum(pickRay.origin, pickRay.direction); if (intersection.intersects) { + // if the user hasn't updated the + if (!pointerVoxelScaleSet) { + calcThumbFromScale(intersection.voxel.s); + } + if (event.isAlt) { // start orbit camera! var cameraPosition = Camera.getPosition(); @@ -295,18 +677,27 @@ function mousePressEvent(event) { orbitAzimuth = Math.atan2(orbitVector.z, orbitVector.x); orbitAltitude = Math.asin(orbitVector.y / Vec3.length(orbitVector)); - } else if (trackAsDelete || event.isRightButton) { + } else if (trackAsDelete || (event.isRightButton && !trackAsEyedropper)) { // Delete voxel - Voxels.eraseVoxel(intersection.voxel.x, intersection.voxel.y, intersection.voxel.z, intersection.voxel.s); + voxelDetails = calculateVoxelFromIntersection(intersection,"delete"); + Voxels.eraseVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s); Audio.playSound(deleteSound, audioOptions); Overlays.editOverlay(voxelPreview, { visible: false }); - + } else if (trackAsEyedropper) { + if (whichColor != -1) { + colors[whichColor].red = intersection.voxel.red; + colors[whichColor].green = intersection.voxel.green; + colors[whichColor].blue = intersection.voxel.blue; + moveTools(); + } + } else if (trackAsRecolor) { // Recolor Voxel - Voxels.setVoxel(intersection.voxel.x, - intersection.voxel.y, - intersection.voxel.z, - intersection.voxel.s, + voxelDetails = calculateVoxelFromIntersection(intersection,"recolor"); + + // doing this erase then set will make sure we only recolor just the target voxel + Voxels.eraseVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s); + Voxels.setVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s, colors[whichColor].red, colors[whichColor].green, colors[whichColor].blue); Audio.playSound(changeColorSound, audioOptions); Overlays.editOverlay(voxelPreview, { visible: false }); @@ -314,43 +705,23 @@ function mousePressEvent(event) { // Add voxel on face if (whichColor == -1) { // Copy mode - use clicked voxel color - var newVoxel = { - x: intersection.voxel.x, - y: intersection.voxel.y, - z: intersection.voxel.z, - s: intersection.voxel.s, + newColor = { red: intersection.voxel.red, green: intersection.voxel.green, blue: intersection.voxel.blue }; } else { - var newVoxel = { - x: intersection.voxel.x, - y: intersection.voxel.y, - z: intersection.voxel.z, - s: intersection.voxel.s, + newColor = { red: colors[whichColor].red, green: colors[whichColor].green, blue: colors[whichColor].blue }; } - if (intersection.face == "MIN_X_FACE") { - newVoxel.x -= newVoxel.s; - } else if (intersection.face == "MAX_X_FACE") { - newVoxel.x += newVoxel.s; - } else if (intersection.face == "MIN_Y_FACE") { - newVoxel.y -= newVoxel.s; - } else if (intersection.face == "MAX_Y_FACE") { - newVoxel.y += newVoxel.s; - } else if (intersection.face == "MIN_Z_FACE") { - newVoxel.z -= newVoxel.s; - } else if (intersection.face == "MAX_Z_FACE") { - newVoxel.z += newVoxel.s; - } - - Voxels.setVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s, newVoxel.red, newVoxel.green, newVoxel.blue); - lastVoxelPosition = { x: newVoxel.x, y: newVoxel.y, z: newVoxel.z }; - lastVoxelColor = { red: newVoxel.red, green: newVoxel.green, blue: newVoxel.blue }; - lastVoxelScale = newVoxel.s; + voxelDetails = calculateVoxelFromIntersection(intersection,"add"); + Voxels.setVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s, + newColor.red, newColor.green, newColor.blue); + lastVoxelPosition = { x: voxelDetails.x, y: voxelDetails.y, z: voxelDetails.z }; + lastVoxelColor = { red: newColor.red, green: newColor.green, blue: newColor.blue }; + lastVoxelScale = voxelDetails.s; Audio.playSound(addSound, audioOptions); Overlays.editOverlay(voxelPreview, { visible: false }); @@ -410,8 +781,20 @@ function keyReleaseEvent(event) { key_alt = false; key_shift = false; } + + function mouseMoveEvent(event) { - if (isOrbiting) { + if (isMovingSlider) { + thumbX = (event.x - thumbClickOffsetX) - sliderX; + if (thumbX < minThumbX) { + thumbX = minThumbX; + } + if (thumbX > maxThumbX) { + thumbX = maxThumbX; + } + calcScaleFromThumb(thumbX); + + } else if (isOrbiting) { var cameraOrientation = Camera.getOrientation(); var origEulers = Quat.safeEulerAngles(cameraOrientation); var newEulers = fixEulerAngles(Quat.safeEulerAngles(cameraOrientation)); @@ -426,8 +809,7 @@ function mouseMoveEvent(event) { Camera.setPosition(orbitPosition); mouseX = event.x; mouseY = event.y; - } - if (isAdding) { + } else if (isAdding) { // Watch the drag direction to tell which way to 'extrude' this voxel if (!isExtruding) { var pickRay = Camera.computePickRay(event.x, event.y); @@ -476,6 +858,10 @@ function mouseReleaseEvent(event) { return; } + if (isMovingSlider) { + isMovingSlider = false; + } + if (isOrbiting) { var cameraOrientation = Camera.getOrientation(); var eulers = Quat.safeEulerAngles(cameraOrientation); @@ -491,7 +877,8 @@ function mouseReleaseEvent(event) { } function moveTools() { - swatchesX = (windowDimensions.x - swatchesWidth) / 2; + // move the swatches + swatchesX = (windowDimensions.x - (swatchesWidth + sliderWidth)) / 2; swatchesY = windowDimensions.y - swatchHeight; // create the overlays, position them in a row, set their colors, and for the selected one, use a different source image @@ -513,6 +900,66 @@ function moveTools() { visible: editToolsOn }); } + + // move the tools + toolsY = (windowDimensions.y - toolsHeight) / 2; + addToolColor = notSelectedColor; + deleteToolColor = notSelectedColor; + recolorToolColor = notSelectedColor; + eyedropperToolColor = notSelectedColor; + selectToolColor = notSelectedColor; + + if (trackAsDelete) { + deleteToolColor = toolSelectedColor; + } else if (trackAsRecolor) { + recolorToolColor = toolSelectedColor; + } else if (trackAsEyedropper) { + eyedropperToolColor = toolSelectedColor; + } else if (trackAsOrbit) { + // nothing gets selected in this case... + } else { + addToolColor = toolSelectedColor; + } + + Overlays.editOverlay(addTool, { + x: 0, y: toolsY + (toolHeight * addToolAt), width: toolWidth, height: toolHeight, + color: addToolColor, + visible: editToolsOn + }); + + Overlays.editOverlay(deleteTool, { + x: 0, y: toolsY + (toolHeight * deleteToolAt), width: toolWidth, height: toolHeight, + color: deleteToolColor, + visible: editToolsOn + }); + + Overlays.editOverlay(recolorTool, { + x: 0, y: toolsY + (toolHeight * recolorToolAt), width: toolWidth, height: toolHeight, + color: recolorToolColor, + visible: editToolsOn + }); + + Overlays.editOverlay(eyedropperTool, { + x: 0, y: toolsY + (toolHeight * eyedropperToolAt), width: toolWidth, height: toolHeight, + color: eyedropperToolColor, + visible: editToolsOn + }); + + Overlays.editOverlay(selectTool, { + x: 0, y: toolsY + (toolHeight * selectToolAt), width: toolWidth, height: toolHeight, + color: selectToolColor, + visible: editToolsOn + }); + + + sliderX = swatchesX + swatchesWidth; + sliderY = windowDimensions.y - sliderHeight; + Overlays.editOverlay(slider, { x: sliderX, y: sliderY, visible: editToolsOn }); + + // This is the thumb of our slider + thumbY = sliderY + 9; + Overlays.editOverlay(thumb, { x: sliderX + thumbX, y: thumbY, visible: editToolsOn }); + } @@ -520,7 +967,6 @@ function update() { var newWindowDimensions = Controller.getViewportDimensions(); if (newWindowDimensions.x != windowDimensions.x || newWindowDimensions.y != windowDimensions.y) { windowDimensions = newWindowDimensions; - print("window resized..."); moveTools(); } } @@ -533,9 +979,18 @@ Controller.keyReleaseEvent.connect(keyReleaseEvent); function scriptEnding() { Overlays.deleteOverlay(voxelPreview); + Overlays.deleteOverlay(linePreviewTop); + Overlays.deleteOverlay(linePreviewBottom); + Overlays.deleteOverlay(linePreviewLeft); + Overlays.deleteOverlay(linePreviewRight); for (s = 0; s < numColors; s++) { Overlays.deleteOverlay(swatches[s]); } + Overlays.deleteOverlay(addTool); + Overlays.deleteOverlay(deleteTool); + Overlays.deleteOverlay(recolorTool); + Overlays.deleteOverlay(eyedropperTool); + Overlays.deleteOverlay(selectTool); } Script.scriptEnding.connect(scriptEnding); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f98a599ebf..c3845657cc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -62,6 +62,7 @@ #include #include "Application.h" +#include "ClipboardScriptingInterface.h" #include "DataServerClient.h" #include "InterfaceVersion.h" #include "Menu.h" @@ -1699,6 +1700,10 @@ bool Application::sendVoxelsOperation(OctreeElement* element, void* extraData) { } void Application::exportVoxels() { + exportVoxels(_mouseVoxel); +} + +void Application::exportVoxels(const VoxelDetail& sourceVoxel) { QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); QString suggestedName = desktopLocation.append("/voxels.svo"); @@ -1706,7 +1711,7 @@ void Application::exportVoxels() { tr("Sparse Voxel Octree Files (*.svo)")); QByteArray fileNameAscii = fileNameString.toLocal8Bit(); const char* fileName = fileNameAscii.data(); - VoxelTreeElement* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + VoxelTreeElement* selectedNode = _voxels.getVoxelAt(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s); if (selectedNode) { VoxelTree exportTree; _voxels.copySubTreeIntoNewTree(selectedNode, &exportTree, true); @@ -1740,11 +1745,19 @@ void Application::importVoxels() { } void Application::cutVoxels() { - copyVoxels(); - deleteVoxelUnderCursor(); + cutVoxels(_mouseVoxel); +} + +void Application::cutVoxels(const VoxelDetail& sourceVoxel) { + copyVoxels(sourceVoxel); + deleteVoxelAt(sourceVoxel); } void Application::copyVoxels() { + copyVoxels(_mouseVoxel); +} + +void Application::copyVoxels(const VoxelDetail& sourceVoxel) { // switch to and clear the clipboard first... _sharedVoxelSystem.killLocalVoxels(); if (_sharedVoxelSystem.getTree() != &_clipboard) { @@ -1753,7 +1766,7 @@ void Application::copyVoxels() { } // then copy onto it if there is something to copy - VoxelTreeElement* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + VoxelTreeElement* selectedNode = _voxels.getVoxelAt(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s); if (selectedNode) { _voxels.copySubTreeIntoNewTree(selectedNode, &_sharedVoxelSystem, true); } @@ -1775,8 +1788,12 @@ void Application::pasteVoxelsToOctalCode(const unsigned char* octalCodeDestinati } void Application::pasteVoxels() { + pasteVoxels(_mouseVoxel); +} + +void Application::pasteVoxels(const VoxelDetail& sourceVoxel) { unsigned char* calculatedOctCode = NULL; - VoxelTreeElement* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + VoxelTreeElement* selectedNode = _voxels.getVoxelAt(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s); // we only need the selected voxel to get the newBaseOctCode, which we can actually calculate from the // voxel size/position details. If we don't have an actual selectedNode then use the mouseVoxel to create a @@ -1785,7 +1802,7 @@ void Application::pasteVoxels() { if (selectedNode) { octalCodeDestination = selectedNode->getOctalCode(); } else { - octalCodeDestination = calculatedOctCode = pointToVoxel(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + octalCodeDestination = calculatedOctCode = pointToVoxel(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s); } pasteVoxelsToOctalCode(octalCodeDestination); @@ -1830,12 +1847,14 @@ void Application::nudgeVoxels() { // calculate nudgeVec glm::vec3 nudgeVec(_nudgeGuidePosition.x - _nudgeVoxel.x, _nudgeGuidePosition.y - _nudgeVoxel.y, _nudgeGuidePosition.z - _nudgeVoxel.z); - VoxelTreeElement* nodeToNudge = _voxels.getVoxelAt(_nudgeVoxel.x, _nudgeVoxel.y, _nudgeVoxel.z, _nudgeVoxel.s); + nudgeVoxelsByVector(_nudgeVoxel, nudgeVec); + } +} - if (nodeToNudge) { - _voxels.getTree()->nudgeSubTree(nodeToNudge, nudgeVec, _voxelEditSender); - _nudgeStarted = false; - } +void Application::nudgeVoxelsByVector(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec) { + VoxelTreeElement* nodeToNudge = _voxels.getVoxelAt(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s); + if (nodeToNudge) { + _voxels.getTree()->nudgeSubTree(nodeToNudge, nudgeVec, _voxelEditSender); } } @@ -3843,18 +3862,27 @@ bool Application::maybeEditVoxelUnderCursor() { } void Application::deleteVoxelUnderCursor() { - if (_mouseVoxel.s != 0) { + deleteVoxelAt(_mouseVoxel); +} + +void Application::deleteVoxels(const VoxelDetail& voxel) { + deleteVoxelAt(voxel); +} + +void Application::deleteVoxelAt(const VoxelDetail& voxel) { + if (voxel.s != 0) { // sending delete to the server is sufficient, server will send new version so we see updates soon enough - _voxelEditSender.sendVoxelEditMessage(PacketTypeVoxelErase, _mouseVoxel); + _voxelEditSender.sendVoxelEditMessage(PacketTypeVoxelErase, voxel); // delete it locally to see the effect immediately (and in case no voxel server is present) - _voxels.deleteVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); + _voxels.deleteVoxelAt(voxel.x, voxel.y, voxel.z, voxel.s); } // remember the position for drag detection _justEditedVoxel = true; } + void Application::eyedropperVoxelUnderCursor() { VoxelTreeElement* selectedNode = _voxels.getVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s); if (selectedNode && selectedNode->isColored()) { @@ -4193,6 +4221,10 @@ void Application::loadScript(const QString& fileNameString) { scriptEngine->registerGlobalObject("Camera", cameraScriptable); connect(scriptEngine, SIGNAL(finished(const QString&)), cameraScriptable, SLOT(deleteLater())); + ClipboardScriptingInterface* clipboardScriptable = new ClipboardScriptingInterface(); + scriptEngine->registerGlobalObject("Clipboard", clipboardScriptable); + connect(scriptEngine, SIGNAL(finished(const QString&)), clipboardScriptable, SLOT(deleteLater())); + scriptEngine->registerGlobalObject("Overlays", &_overlays); QThread* workerThread = new QThread(this); diff --git a/interface/src/Application.h b/interface/src/Application.h index 759572f019..4aca6093b6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -230,13 +230,20 @@ public slots: void nodeKilled(SharedNodePointer node); void packetSent(quint64 length); - void exportVoxels(); - void importVoxels(); void cutVoxels(); void copyVoxels(); void pasteVoxels(); - void nudgeVoxels(); void deleteVoxels(); + void exportVoxels(); + void importVoxels(); + void nudgeVoxels(); + + void cutVoxels(const VoxelDetail& sourceVoxel); + void copyVoxels(const VoxelDetail& sourceVoxel); + void pasteVoxels(const VoxelDetail& sourceVoxel); + void deleteVoxels(const VoxelDetail& sourceVoxel); + void exportVoxels(const VoxelDetail& sourceVoxel); + void nudgeVoxelsByVector(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec); void setRenderVoxels(bool renderVoxels); void doKillLocalVoxels(); @@ -331,6 +338,7 @@ private: bool maybeEditVoxelUnderCursor(); void deleteVoxelUnderCursor(); + void deleteVoxelAt(const VoxelDetail& voxel); void eyedropperVoxelUnderCursor(); void setMenuShortcutsEnabled(bool enabled); diff --git a/interface/src/ClipboardScriptingInterface.cpp b/interface/src/ClipboardScriptingInterface.cpp new file mode 100644 index 0000000000..644ea84cdb --- /dev/null +++ b/interface/src/ClipboardScriptingInterface.cpp @@ -0,0 +1,94 @@ +// +// ClipboardScriptingInterface.cpp +// interface +// +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#include "Application.h" +#include "ClipboardScriptingInterface.h" + +ClipboardScriptingInterface::ClipboardScriptingInterface() { +} + +void ClipboardScriptingInterface::cutVoxel(const VoxelDetail& sourceVoxel) { + cutVoxel(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s); +} + +void ClipboardScriptingInterface::cutVoxel(float x, float y, float z, float s) { + VoxelDetail sourceVoxel = { x / (float)TREE_SCALE, + y / (float)TREE_SCALE, + z / (float)TREE_SCALE, + s / (float)TREE_SCALE }; + Application::getInstance()->cutVoxels(sourceVoxel); +} + +void ClipboardScriptingInterface::copyVoxel(const VoxelDetail& sourceVoxel) { + copyVoxel(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s); +} + +void ClipboardScriptingInterface::copyVoxel(float x, float y, float z, float s) { + VoxelDetail sourceVoxel = { x / (float)TREE_SCALE, + y / (float)TREE_SCALE, + z / (float)TREE_SCALE, + s / (float)TREE_SCALE }; + Application::getInstance()->copyVoxels(sourceVoxel); +} + +void ClipboardScriptingInterface::pasteVoxel(const VoxelDetail& destinationVoxel) { + pasteVoxel(destinationVoxel.x, destinationVoxel.y, destinationVoxel.z, destinationVoxel.s); +} + +void ClipboardScriptingInterface::pasteVoxel(float x, float y, float z, float s) { + VoxelDetail sourceVoxel = { x / (float)TREE_SCALE, + y / (float)TREE_SCALE, + z / (float)TREE_SCALE, + s / (float)TREE_SCALE }; + + Application::getInstance()->pasteVoxels(sourceVoxel); +} + +void ClipboardScriptingInterface::deleteVoxel(const VoxelDetail& sourceVoxel) { + deleteVoxel(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s); +} + +void ClipboardScriptingInterface::deleteVoxel(float x, float y, float z, float s) { + VoxelDetail sourceVoxel = { x / (float)TREE_SCALE, + y / (float)TREE_SCALE, + z / (float)TREE_SCALE, + s / (float)TREE_SCALE }; + Application::getInstance()->deleteVoxels(sourceVoxel); +} + +void ClipboardScriptingInterface::exportVoxel(const VoxelDetail& sourceVoxel) { + exportVoxel(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s); +} + +void ClipboardScriptingInterface::exportVoxel(float x, float y, float z, float s) { + VoxelDetail sourceVoxel = { x / (float)TREE_SCALE, + y / (float)TREE_SCALE, + z / (float)TREE_SCALE, + s / (float)TREE_SCALE }; + + // TODO: should we be calling invokeMethod() in all these cases? + Application::getInstance()->exportVoxels(sourceVoxel); +} + +void ClipboardScriptingInterface::importVoxels() { + QMetaObject::invokeMethod(Application::getInstance(), "importVoxels"); +} + +void ClipboardScriptingInterface::nudgeVoxel(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec) { + nudgeVoxel(sourceVoxel.x, sourceVoxel.y, sourceVoxel.z, sourceVoxel.s, nudgeVec); +} + +void ClipboardScriptingInterface::nudgeVoxel(float x, float y, float z, float s, const glm::vec3& nudgeVec) { + glm::vec3 nudgeVecInTreeSpace = nudgeVec / (float)TREE_SCALE; + VoxelDetail sourceVoxel = { x / (float)TREE_SCALE, + y / (float)TREE_SCALE, + z / (float)TREE_SCALE, + s / (float)TREE_SCALE }; + + Application::getInstance()->nudgeVoxelsByVector(sourceVoxel, nudgeVecInTreeSpace); +} + diff --git a/interface/src/ClipboardScriptingInterface.h b/interface/src/ClipboardScriptingInterface.h new file mode 100644 index 0000000000..99747f56f6 --- /dev/null +++ b/interface/src/ClipboardScriptingInterface.h @@ -0,0 +1,43 @@ +// +// ClipboardScriptingInterface.h +// interface +// +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Scriptable interface for the Application clipboard +// + +#ifndef __interface__Clipboard__ +#define __interface__Clipboard__ + +#include +#include + +class ClipboardScriptingInterface : public QObject { + Q_OBJECT +public: + ClipboardScriptingInterface(); + +public slots: + void cutVoxel(const VoxelDetail& sourceVoxel); + void cutVoxel(float x, float y, float z, float s); + + void copyVoxel(const VoxelDetail& sourceVoxel); + void copyVoxel(float x, float y, float z, float s); + + void pasteVoxel(const VoxelDetail& destinationVoxel); + void pasteVoxel(float x, float y, float z, float s); + + void deleteVoxel(const VoxelDetail& sourceVoxel); + void deleteVoxel(float x, float y, float z, float s); + + void exportVoxel(const VoxelDetail& sourceVoxel); + void exportVoxel(float x, float y, float z, float s); + + void importVoxels(); + + void nudgeVoxel(const VoxelDetail& sourceVoxel, const glm::vec3& nudgeVec); + void nudgeVoxel(float x, float y, float z, float s, const glm::vec3& nudgeVec); +}; + +#endif // __interface__Clipboard__ diff --git a/libraries/script-engine/src/EventTypes.cpp b/libraries/script-engine/src/EventTypes.cpp index d0ba160add..c8451d84a7 100644 --- a/libraries/script-engine/src/EventTypes.cpp +++ b/libraries/script-engine/src/EventTypes.cpp @@ -104,6 +104,16 @@ KeyEvent::KeyEvent(const QKeyEvent& event) { text = "HELP"; } else if (key == Qt::Key_CapsLock) { text = "CAPS LOCK"; + } else if (key >= Qt::Key_A && key <= Qt::Key_Z && (isMeta || isControl || isAlt)) { + // this little bit of hackery will fix the text character keys like a-z in cases of control/alt/meta where + // qt doesn't always give you the key characters and will sometimes give you crazy non-printable characters + const int lowerCaseAdjust = 0x20; + QString unicode; + if (isShifted) { + text = QString(QChar(key)); + } else { + text = QString(QChar(key + lowerCaseAdjust)); + } } } diff --git a/libraries/voxels/src/VoxelEditPacketSender.cpp b/libraries/voxels/src/VoxelEditPacketSender.cpp index a6d3668207..90884f19f4 100644 --- a/libraries/voxels/src/VoxelEditPacketSender.cpp +++ b/libraries/voxels/src/VoxelEditPacketSender.cpp @@ -22,7 +22,7 @@ /// PacketTypeVoxelSet, PacketTypeVoxelSetDestructive, or PacketTypeVoxelErase. The buffer is returned to caller becomes /// responsibility of caller and MUST be deleted by caller. bool createVoxelEditMessage(PacketType command, short int sequence, - int voxelCount, VoxelDetail* voxelDetails, unsigned char*& bufferOut, int& sizeOut) { + int voxelCount, const VoxelDetail* voxelDetails, unsigned char*& bufferOut, int& sizeOut) { bool success = true; // assume the best int messageSize = MAXIMUM_EDIT_VOXEL_MESSAGE_SIZE; // just a guess for now @@ -102,7 +102,7 @@ bool encodeVoxelEditMessageDetails(PacketType, int voxelCount, VoxelDetail* voxe return success; } -void VoxelEditPacketSender::sendVoxelEditMessage(PacketType type, VoxelDetail& detail) { +void VoxelEditPacketSender::sendVoxelEditMessage(PacketType type, const VoxelDetail& detail) { // allows app to disable sending if for example voxels have been disabled if (!_shouldSend) { return; // bail early diff --git a/libraries/voxels/src/VoxelEditPacketSender.h b/libraries/voxels/src/VoxelEditPacketSender.h index 90085635b0..4a1aa87a1c 100644 --- a/libraries/voxels/src/VoxelEditPacketSender.h +++ b/libraries/voxels/src/VoxelEditPacketSender.h @@ -19,7 +19,7 @@ class VoxelEditPacketSender : public OctreeEditPacketSender { Q_OBJECT public: /// Send voxel edit message immediately - void sendVoxelEditMessage(PacketType type, VoxelDetail& detail); + void sendVoxelEditMessage(PacketType type, const VoxelDetail& detail); /// Queues a single voxel edit message. Will potentially send a pending multi-command packet. Determines which voxel-server /// node or nodes the packet should be sent to. Can be called even before voxel servers are known, in which case up to