diff --git a/examples/ControlACs.js b/examples/ControlACs.js index 1b60a9e848..4ccf5cff59 100644 --- a/examples/ControlACs.js +++ b/examples/ControlACs.js @@ -18,7 +18,7 @@ var controlVoxelSize = 0.25; var controlVoxelPosition = { x: 2000 , y: 0, z: 0 }; // Script. DO NOT MODIFY BEYOND THIS LINE. -Script.include("toolBars.js"); +Script.include("libraries/toolBars.js"); var DO_NOTHING = 0; var PLAY = 1; diff --git a/examples/Recorder.js b/examples/Recorder.js index 40bf2d2ed1..533f9f7fab 100644 --- a/examples/Recorder.js +++ b/examples/Recorder.js @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("toolBars.js"); +Script.include("libraries/toolBars.js"); var recordingFile = "recording.rec"; diff --git a/examples/editModels.js b/examples/editModels.js index 25c69f93f5..d8fa72ff60 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -22,10 +22,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("toolBars.js"); -Script.include("entitySelectionTool.js"); -var selectionDisplay = SelectionDisplay; - +Script.include("libraries/toolBars.js"); var windowDimensions = Controller.getViewportDimensions(); var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; var toolHeight = 50; @@ -2022,7 +2019,6 @@ function controller(wichSide) { if (this.glowedIntersectingModel.isKnownID) { Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 }); - selectionDisplay.hideSelection(this.glowedIntersectingModel); this.glowedIntersectingModel.isKnownID = false; } if (!this.grabbing) { @@ -2042,7 +2038,6 @@ function controller(wichSide) { if (wantEntityGlow) { Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 }); } - selectionDisplay.showSelection(this.glowedIntersectingModel, intersection.properties); } } } @@ -2113,7 +2108,6 @@ function controller(wichSide) { }); this.oldModelRotation = newRotation; this.oldModelPosition = newPosition; - selectionDisplay.showSelection(this.entityID, Entities.getEntityProperties(this.entityID)); var indicesToRemove = []; for (var i = 0; i < this.jointsIntersectingFromStart.length; ++i) { @@ -2342,7 +2336,6 @@ function moveEntities() { }); - selectionDisplay.showSelection(leftController.entityID, Entities.getEntityProperties(leftController.entityID)); leftController.oldModelPosition = newPosition; leftController.oldModelRotation = rotation; leftController.oldModelHalfDiagonal *= ratio; @@ -2572,7 +2565,6 @@ function mousePressEvent(event) { print("Clicked on " + selectedEntityID.id + " " + entitySelected); tooltip.updateText(selectedEntityProperties); tooltip.show(true); - selectionDisplay.showSelection(selectedEntityID, selectedEntityProperties); } } @@ -2591,7 +2583,6 @@ function mouseMoveEvent(event) { if (entityIntersection.accurate) { if(glowedEntityID.isKnownID && glowedEntityID.id != entityIntersection.entityID.id) { Entities.editEntity(glowedEntityID, { glowLevel: 0.0 }); - selectionDisplay.hideSelection(glowedEntityID); glowedEntityID.id = -1; glowedEntityID.isKnownID = false; } @@ -2609,7 +2600,6 @@ function mouseMoveEvent(event) { Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 }); } glowedEntityID = entityIntersection.entityID; - selectionDisplay.showSelection(entityIntersection.entityID, entityIntersection.properties); } } @@ -2732,7 +2722,6 @@ function mouseMoveEvent(event) { Entities.editEntity(selectedEntityID, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); - selectionDisplay.showSelection(selectedEntityID, selectedEntityProperties); } @@ -2807,7 +2796,6 @@ function scriptEnding() { cleanupModelMenus(); tooltip.cleanup(); modelImporter.cleanup(); - selectionDisplay.cleanup(); if (exportMenu) { exportMenu.close(); } @@ -3037,7 +3025,6 @@ Controller.keyPressEvent.connect(function (event) { Entities.editEntity(selectedEntityID, selectedEntityProperties); tooltip.updateText(selectedEntityProperties); somethingChanged = true; - selectionDisplay.showSelection(selectedEntityID, selectedEntityProperties); } }); @@ -3155,7 +3142,6 @@ Window.nonBlockingFormClosed.connect(function() { properties.color.blue = array[index++].value; } Entities.editEntity(editModelID, properties); - selectionDisplay.showSelection(editModelID, propeties); } modelSelected = false; }); diff --git a/examples/frisbee.js b/examples/frisbee.js index e893a29309..c069d03275 100644 --- a/examples/frisbee.js +++ b/examples/frisbee.js @@ -14,7 +14,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -Script.include("toolBars.js"); +Script.include("libraries/toolBars.js"); const LEFT_PALM = 0; const LEFT_TIP = 1; diff --git a/examples/libraries/ExportMenu.js b/examples/libraries/ExportMenu.js new file mode 100644 index 0000000000..fb148b8150 --- /dev/null +++ b/examples/libraries/ExportMenu.js @@ -0,0 +1,214 @@ +var ExportMenu = function (opts) { + var self = this; + + var windowDimensions = Controller.getViewportDimensions(); + var pos = { x: windowDimensions.x / 2, y: windowDimensions.y - 100 }; + + this._onClose = opts.onClose || function () { }; + this._position = { x: 0.0, y: 0.0, z: 0.0 }; + this._scale = 1.0; + + var minScale = 1; + var maxScale = 32768; + var titleWidth = 120; + var locationWidth = 100; + var scaleWidth = 144; + var exportWidth = 100; + var cancelWidth = 100; + var margin = 4; + var height = 30; + var outerHeight = height + (2 * margin); + var buttonColor = { red: 128, green: 128, blue: 128 }; + + var SCALE_MINUS = scaleWidth * 40.0 / 100.0; + var SCALE_PLUS = scaleWidth * 63.0 / 100.0; + + var fullWidth = locationWidth + scaleWidth + exportWidth + cancelWidth + (2 * margin); + var offset = fullWidth / 2; + pos.x -= offset; + + var background = Overlays.addOverlay("text", { + x: pos.x, + y: pos.y, + opacity: 1, + width: fullWidth, + height: outerHeight, + backgroundColor: { red: 200, green: 200, blue: 200 }, + text: "", + }); + + var titleText = Overlays.addOverlay("text", { + x: pos.x, + y: pos.y - height, + font: { size: 14 }, + width: titleWidth, + height: height, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 255, green: 255, blue: 255 }, + text: "Export Models" + }); + + var locationButton = Overlays.addOverlay("text", { + x: pos.x + margin, + y: pos.y + margin, + width: locationWidth, + height: height, + color: { red: 255, green: 255, blue: 255 }, + text: "0, 0, 0", + }); + var scaleOverlay = Overlays.addOverlay("image", { + x: pos.x + margin + locationWidth, + y: pos.y + margin, + width: scaleWidth, + height: height, + subImage: { x: 0, y: 3, width: 144, height: height }, + imageURL: toolIconUrl + "voxel-size-selector.svg", + alpha: 0.9, + }); + var scaleViewWidth = 40; + var scaleView = Overlays.addOverlay("text", { + x: pos.x + margin + locationWidth + SCALE_MINUS, + y: pos.y + margin, + width: scaleViewWidth, + height: height, + alpha: 0.0, + color: { red: 255, green: 255, blue: 255 }, + text: "1" + }); + var exportButton = Overlays.addOverlay("text", { + x: pos.x + margin + locationWidth + scaleWidth, + y: pos.y + margin, + width: exportWidth, + height: height, + color: { red: 0, green: 255, blue: 255 }, + text: "Export" + }); + var cancelButton = Overlays.addOverlay("text", { + x: pos.x + margin + locationWidth + scaleWidth + exportWidth, + y: pos.y + margin, + width: cancelWidth, + height: height, + color: { red: 255, green: 255, blue: 255 }, + text: "Cancel" + }); + + var voxelPreview = Overlays.addOverlay("cube", { + position: { x: 0, y: 0, z: 0 }, + size: this._scale, + color: { red: 255, green: 255, blue: 0 }, + alpha: 1, + solid: false, + visible: true, + lineWidth: 4 + }); + + this.parsePosition = function (str) { + var parts = str.split(','); + if (parts.length == 3) { + var x = parseFloat(parts[0]); + var y = parseFloat(parts[1]); + var z = parseFloat(parts[2]); + if (isFinite(x) && isFinite(y) && isFinite(z)) { + return { x: x, y: y, z: z }; + } + } + return null; + }; + + this.showPositionPrompt = function () { + var positionStr = self._position.x + ", " + self._position.y + ", " + self._position.z; + while (1) { + positionStr = Window.prompt("Position to export form:", positionStr); + if (positionStr == null) { + break; + } + var position = self.parsePosition(positionStr); + if (position != null) { + self.setPosition(position.x, position.y, position.z); + break; + } + Window.alert("The position you entered was invalid."); + } + }; + + this.setScale = function (scale) { + self._scale = Math.min(maxScale, Math.max(minScale, scale)); + Overlays.editOverlay(scaleView, { text: self._scale }); + Overlays.editOverlay(voxelPreview, { size: self._scale }); + } + + this.decreaseScale = function () { + self.setScale(self._scale /= 2); + } + + this.increaseScale = function () { + self.setScale(self._scale *= 2); + } + + this.exportEntities = function() { + var x = self._position.x; + var y = self._position.y; + var z = self._position.z; + var s = self._scale; + var filename = "models__" + Window.location.hostname + "__" + x + "_" + y + "_" + z + "_" + s + "__.svo"; + filename = Window.save("Select where to save", filename, "*.svo") + if (filename) { + var success = Clipboard.exportEntities(filename, x, y, z, s); + if (!success) { + Window.alert("Export failed: no models found in selected area."); + } + } + self.close(); + }; + + this.getPosition = function () { + return self._position; + }; + + this.setPosition = function (x, y, z) { + self._position = { x: x, y: y, z: z }; + var positionStr = x + ", " + y + ", " + z; + Overlays.editOverlay(locationButton, { text: positionStr }); + Overlays.editOverlay(voxelPreview, { position: self._position }); + + }; + + this.mouseReleaseEvent = function (event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + if (clickedOverlay == locationButton) { + self.showPositionPrompt(); + } else if (clickedOverlay == exportButton) { + self.exportEntities(); + } else if (clickedOverlay == cancelButton) { + self.close(); + } else if (clickedOverlay == scaleOverlay) { + var x = event.x - pos.x - margin - locationWidth; + print(x); + if (x < SCALE_MINUS) { + self.decreaseScale(); + } else if (x > SCALE_PLUS) { + self.increaseScale(); + } + } + }; + + this.close = function () { + this.cleanup(); + this._onClose(); + }; + + this.cleanup = function () { + Overlays.deleteOverlay(background); + Overlays.deleteOverlay(titleText); + Overlays.deleteOverlay(locationButton); + Overlays.deleteOverlay(exportButton); + Overlays.deleteOverlay(cancelButton); + Overlays.deleteOverlay(voxelPreview); + Overlays.deleteOverlay(scaleOverlay); + Overlays.deleteOverlay(scaleView); + }; + + print("CONNECTING!"); + Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent); +}; diff --git a/examples/libraries/ModelImporter.js b/examples/libraries/ModelImporter.js new file mode 100644 index 0000000000..868d6d7d46 --- /dev/null +++ b/examples/libraries/ModelImporter.js @@ -0,0 +1,189 @@ +ModelImporter = function (opts) { + var self = this; + + var windowDimensions = Controller.getViewportDimensions(); + + var height = 30; + var margin = 4; + var outerHeight = height + (2 * margin); + var titleWidth = 120; + var cancelWidth = 100; + var fullWidth = titleWidth + cancelWidth + (2 * margin); + + var localModels = Overlays.addOverlay("localmodels", { + position: { x: 1, y: 1, z: 1 }, + scale: 1, + visible: false + }); + var importScale = 1; + var importBoundaries = Overlays.addOverlay("cube", { + position: { x: 0, y: 0, z: 0 }, + size: 1, + color: { red: 128, blue: 128, green: 128 }, + lineWidth: 4, + solid: false, + visible: false + }); + + var pos = { x: windowDimensions.x / 2 - (fullWidth / 2), y: windowDimensions.y - 100 }; + + var background = Overlays.addOverlay("text", { + x: pos.x, + y: pos.y, + opacity: 1, + width: fullWidth, + height: outerHeight, + backgroundColor: { red: 200, green: 200, blue: 200 }, + visible: false, + text: "", + }); + + var titleText = Overlays.addOverlay("text", { + x: pos.x + margin, + y: pos.y + margin, + font: { size: 14 }, + width: titleWidth, + height: height, + backgroundColor: { red: 255, green: 255, blue: 255 }, + color: { red: 255, green: 255, blue: 255 }, + visible: false, + text: "Import Models" + }); + var cancelButton = Overlays.addOverlay("text", { + x: pos.x + margin + titleWidth, + y: pos.y + margin, + width: cancelWidth, + height: height, + color: { red: 255, green: 255, blue: 255 }, + visible: false, + text: "Close" + }); + this._importing = false; + + this.setImportVisible = function (visible) { + Overlays.editOverlay(importBoundaries, { visible: visible }); + Overlays.editOverlay(localModels, { visible: visible }); + Overlays.editOverlay(cancelButton, { visible: visible }); + Overlays.editOverlay(titleText, { visible: visible }); + Overlays.editOverlay(background, { visible: visible }); + }; + + var importPosition = { x: 0, y: 0, z: 0 }; + this.moveImport = function (position) { + importPosition = position; + Overlays.editOverlay(localModels, { + position: { x: importPosition.x, y: importPosition.y, z: importPosition.z } + }); + Overlays.editOverlay(importBoundaries, { + position: { x: importPosition.x, y: importPosition.y, z: importPosition.z } + }); + } + + this.mouseMoveEvent = function (event) { + if (self._importing) { + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = Voxels.findRayIntersection(pickRay); + + var distance = 2;// * self._scale; + + if (false) {//intersection.intersects) { + var intersectionDistance = Vec3.length(Vec3.subtract(pickRay.origin, intersection.intersection)); + if (intersectionDistance < distance) { + distance = intersectionDistance * 0.99; + } + + } + + var targetPosition = { + x: pickRay.origin.x + (pickRay.direction.x * distance), + y: pickRay.origin.y + (pickRay.direction.y * distance), + z: pickRay.origin.z + (pickRay.direction.z * distance) + }; + + if (targetPosition.x < 0) targetPosition.x = 0; + if (targetPosition.y < 0) targetPosition.y = 0; + if (targetPosition.z < 0) targetPosition.z = 0; + + var nudgeFactor = 1; + var newPosition = { + x: Math.floor(targetPosition.x / nudgeFactor) * nudgeFactor, + y: Math.floor(targetPosition.y / nudgeFactor) * nudgeFactor, + z: Math.floor(targetPosition.z / nudgeFactor) * nudgeFactor + } + + self.moveImport(newPosition); + } + } + + this.mouseReleaseEvent = function (event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + if (clickedOverlay == cancelButton) { + self._importing = false; + self.setImportVisible(false); + } + }; + + // Would prefer to use {4} for the coords, but it would only capture the last digit. + var fileRegex = /__(.+)__(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)_(\d+(?:\.\d+)?)__/; + this.doImport = function () { + if (!self._importing) { + var filename = Window.browse("Select models to import", "", "*.svo") + if (filename) { + parts = fileRegex.exec(filename); + if (parts == null) { + Window.alert("The file you selected does not contain source domain or location information"); + } else { + var hostname = parts[1]; + var x = parts[2]; + var y = parts[3]; + var z = parts[4]; + var s = parts[5]; + importScale = s; + if (hostname != location.hostname) { + if (!Window.confirm(("These models were not originally exported from this domain. Continue?"))) { + return; + } + } else { + if (Window.confirm(("Would you like to import back to the source location?"))) { + var success = Clipboard.importEntities(filename); + if (success) { + Clipboard.pasteEntities(x, y, z, 1); + } else { + Window.alert("There was an error importing the entity file."); + } + return; + } + } + } + var success = Clipboard.importEntities(filename); + if (success) { + self._importing = true; + self.setImportVisible(true); + Overlays.editOverlay(importBoundaries, { size: s }); + } else { + Window.alert("There was an error importing the entity file."); + } + } + } + } + + this.paste = function () { + if (self._importing) { + // self._importing = false; + // self.setImportVisible(false); + Clipboard.pasteEntities(importPosition.x, importPosition.y, importPosition.z, 1); + } + } + + this.cleanup = function () { + Overlays.deleteOverlay(localModels); + Overlays.deleteOverlay(importBoundaries); + Overlays.deleteOverlay(cancelButton); + Overlays.deleteOverlay(titleText); + Overlays.deleteOverlay(background); + } + + Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent); + Controller.mouseMoveEvent.connect(this.mouseMoveEvent); +}; diff --git a/examples/libraries/ToolTip.js b/examples/libraries/ToolTip.js new file mode 100644 index 0000000000..0070f4676f --- /dev/null +++ b/examples/libraries/ToolTip.js @@ -0,0 +1,71 @@ +function Tooltip() { + this.x = 285; + this.y = 115; + this.width = 500; + this.height = 180; // 145; + this.margin = 5; + this.decimals = 3; + + this.textOverlay = Overlays.addOverlay("text", { + x: this.x, + y: this.y, + width: this.width, + height: this.height, + margin: this.margin, + text: "", + color: { red: 128, green: 128, blue: 128 }, + alpha: 0.2, + visible: false + }); + this.show = function (doShow) { + Overlays.editOverlay(this.textOverlay, { visible: doShow }); + } + this.updateText = function(properties) { + var angles = Quat.safeEulerAngles(properties.rotation); + var text = "Entity Properties:\n" + text += "type: " + properties.type + "\n" + text += "X: " + properties.position.x.toFixed(this.decimals) + "\n" + text += "Y: " + properties.position.y.toFixed(this.decimals) + "\n" + text += "Z: " + properties.position.z.toFixed(this.decimals) + "\n" + text += "Pitch: " + angles.x.toFixed(this.decimals) + "\n" + text += "Yaw: " + angles.y.toFixed(this.decimals) + "\n" + text += "Roll: " + angles.z.toFixed(this.decimals) + "\n" + text += "Dimensions: " + properties.dimensions.x.toFixed(this.decimals) + ", " + + properties.dimensions.y.toFixed(this.decimals) + ", " + + properties.dimensions.z.toFixed(this.decimals) + "\n"; + + text += "Natural Dimensions: " + properties.naturalDimensions.x.toFixed(this.decimals) + ", " + + properties.naturalDimensions.y.toFixed(this.decimals) + ", " + + properties.naturalDimensions.z.toFixed(this.decimals) + "\n"; + + text += "ID: " + properties.id + "\n" + if (properties.type == "Model") { + text += "Model URL: " + properties.modelURL + "\n" + text += "Animation URL: " + properties.animationURL + "\n" + text += "Animation is playing: " + properties.animationIsPlaying + "\n" + if (properties.sittingPoints && properties.sittingPoints.length > 0) { + text += properties.sittingPoints.length + " Sitting points: " + for (var i = 0; i < properties.sittingPoints.length; ++i) { + text += properties.sittingPoints[i].name + " " + } + } else { + text += "No sitting points" + "\n" + } + } + if (properties.lifetime > -1) { + text += "Lifetime: " + properties.lifetime + "\n" + } + text += "Age: " + properties.ageAsText + "\n" + text += "Mass: " + properties.mass + "\n" + text += "Script: " + properties.script + "\n" + + + Overlays.editOverlay(this.textOverlay, { text: text }); + } + + this.cleanup = function () { + Overlays.deleteOverlay(this.textOverlay); + } +} + +tooltip = new Tooltip(); diff --git a/examples/libraries/dataViewHelpers.js b/examples/libraries/dataViewHelpers.js new file mode 100644 index 0000000000..6e60ba8bdc --- /dev/null +++ b/examples/libraries/dataViewHelpers.js @@ -0,0 +1,52 @@ +if (typeof DataView.prototype.indexOf !== "function") { + DataView.prototype.indexOf = function (searchString, position) { + var searchLength = searchString.length, + byteArrayLength = this.byteLength, + maxSearchIndex = byteArrayLength - searchLength, + searchCharCodes = [], + found, + i, + j; + + searchCharCodes[searchLength] = 0; + for (j = 0; j < searchLength; j += 1) { + searchCharCodes[j] = searchString.charCodeAt(j); + } + + i = position; + found = false; + while (i < maxSearchIndex && !found) { + j = 0; + while (j < searchLength && this.getUint8(i + j) === searchCharCodes[j]) { + j += 1; + } + found = (j === searchLength); + i += 1; + } + + return found ? i - 1 : -1; + }; +} + +if (typeof DataView.prototype.string !== "function") { + DataView.prototype.string = function (start, length) { + var charCodes = [], + end, + i; + + if (start === undefined) { + start = 0; + } + if (length === undefined) { + length = this.length; + } + + end = start + length; + for (i = start; i < end; i += 1) { + charCodes.push(this.getUint8(i)); + } + + return String.fromCharCode.apply(String, charCodes); + }; +} + diff --git a/examples/libraries/entityPropertyDialogBox.js b/examples/libraries/entityPropertyDialogBox.js new file mode 100644 index 0000000000..fbac30e796 --- /dev/null +++ b/examples/libraries/entityPropertyDialogBox.js @@ -0,0 +1,255 @@ +// +// entityPropertyDialogBox.js +// examples +// +// Created by Brad hefta-Gaub on 10/1/14. +// Copyright 2014 High Fidelity, Inc. +// +// This script implements a class useful for building tools for editing entities. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +EntityPropertyDialogBox = (function () { + var that = {}; + + var propertiesForEditedEntity; + var editEntityFormArray; + var decimals = 3; + var dimensionX; + var dimensionY; + var dimensionZ; + var rescalePercentage; + var editModelID = -1; + + that.cleanup = function () { + }; + + that.openDialog = function (entityID) { + print(" Edit Properties.... about to edit properties..."); + + editModelID = entityID; + propertiesForEditedEntity = Entities.getEntityProperties(editModelID); + var properties = propertiesForEditedEntity; + + var array = new Array(); + var index = 0; + if (properties.type == "Model") { + array.push({ label: "Model URL:", value: properties.modelURL }); + index++; + array.push({ label: "Animation URL:", value: properties.animationURL }); + index++; + array.push({ label: "Animation is playing:", value: properties.animationIsPlaying }); + index++; + array.push({ label: "Animation FPS:", value: properties.animationFPS }); + index++; + array.push({ label: "Animation Frame:", value: properties.animationFrameIndex }); + index++; + } + array.push({ label: "Position:", type: "header" }); + index++; + array.push({ label: "X:", value: properties.position.x.toFixed(decimals) }); + index++; + array.push({ label: "Y:", value: properties.position.y.toFixed(decimals) }); + index++; + array.push({ label: "Z:", value: properties.position.z.toFixed(decimals) }); + index++; + + array.push({ label: "Registration X:", value: properties.registrationPoint.x.toFixed(decimals) }); + index++; + array.push({ label: "Registration Y:", value: properties.registrationPoint.y.toFixed(decimals) }); + index++; + array.push({ label: "Registration Z:", value: properties.registrationPoint.z.toFixed(decimals) }); + index++; + + array.push({ label: "Rotation:", type: "header" }); + index++; + var angles = Quat.safeEulerAngles(properties.rotation); + array.push({ label: "Pitch:", value: angles.x.toFixed(decimals) }); + index++; + array.push({ label: "Yaw:", value: angles.y.toFixed(decimals) }); + index++; + array.push({ label: "Roll:", value: angles.z.toFixed(decimals) }); + index++; + + array.push({ label: "Dimensions:", type: "header" }); + index++; + array.push({ label: "Width:", value: properties.dimensions.x.toFixed(decimals) }); + dimensionX = index; + index++; + array.push({ label: "Height:", value: properties.dimensions.y.toFixed(decimals) }); + dimensionY = index; + index++; + array.push({ label: "Depth:", value: properties.dimensions.z.toFixed(decimals) }); + dimensionZ = index; + index++; + array.push({ label: "", type: "inlineButton", buttonLabel: "Reset to Natural Dimensions", name: "resetDimensions" }); + index++; + array.push({ label: "Rescale Percentage:", value: 100 }); + rescalePercentage = index; + index++; + array.push({ label: "", type: "inlineButton", buttonLabel: "Rescale", name: "rescaleDimensions" }); + index++; + + array.push({ label: "Velocity:", type: "header" }); + index++; + array.push({ label: "Linear X:", value: properties.velocity.x.toFixed(decimals) }); + index++; + array.push({ label: "Linear Y:", value: properties.velocity.y.toFixed(decimals) }); + index++; + array.push({ label: "Linear Z:", value: properties.velocity.z.toFixed(decimals) }); + index++; + array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) }); + index++; + array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) }); + index++; + array.push({ label: "Angular Yaw:", value: properties.angularVelocity.y.toFixed(decimals) }); + index++; + array.push({ label: "Angular Roll:", value: properties.angularVelocity.z.toFixed(decimals) }); + index++; + array.push({ label: "Angular Damping:", value: properties.angularDamping.toFixed(decimals) }); + index++; + + array.push({ label: "Gravity X:", value: properties.gravity.x.toFixed(decimals) }); + index++; + array.push({ label: "Gravity Y:", value: properties.gravity.y.toFixed(decimals) }); + index++; + array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) }); + index++; + + array.push({ label: "Collisions:", type: "header" }); + index++; + array.push({ label: "Mass:", value: properties.mass.toFixed(decimals) }); + index++; + array.push({ label: "Ignore for Collisions:", value: properties.ignoreForCollisions }); + index++; + array.push({ label: "Collisions Will Move:", value: properties.collisionsWillMove }); + index++; + + array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) }); + index++; + + array.push({ label: "Visible:", value: properties.visible }); + index++; + + if (properties.type == "Box" || properties.type == "Sphere") { + array.push({ label: "Color:", type: "header" }); + index++; + array.push({ label: "Red:", value: properties.color.red }); + index++; + array.push({ label: "Green:", value: properties.color.green }); + index++; + array.push({ label: "Blue:", value: properties.color.blue }); + index++; + } + array.push({ button: "Cancel" }); + index++; + + editEntityFormArray = array; + Window.nonBlockingForm("Edit Properties", array); + }; + + Window.inlineButtonClicked.connect(function (name) { + if (name == "resetDimensions") { + Window.reloadNonBlockingForm([ + { value: propertiesForEditedEntity.naturalDimensions.x.toFixed(decimals), oldIndex: dimensionX }, + { value: propertiesForEditedEntity.naturalDimensions.y.toFixed(decimals), oldIndex: dimensionY }, + { value: propertiesForEditedEntity.naturalDimensions.z.toFixed(decimals), oldIndex: dimensionZ } + ]); + } + + if (name == "rescaleDimensions") { + var peekValues = editEntityFormArray; + Window.peekNonBlockingFormResult(peekValues); + var peekX = peekValues[dimensionX].value; + var peekY = peekValues[dimensionY].value; + var peekZ = peekValues[dimensionZ].value; + var peekRescale = peekValues[rescalePercentage].value; + var rescaledX = peekX * peekRescale / 100.0; + var rescaledY = peekY * peekRescale / 100.0; + var rescaledZ = peekZ * peekRescale / 100.0; + + Window.reloadNonBlockingForm([ + { value: rescaledX.toFixed(decimals), oldIndex: dimensionX }, + { value: rescaledY.toFixed(decimals), oldIndex: dimensionY }, + { value: rescaledZ.toFixed(decimals), oldIndex: dimensionZ }, + { value: 100, oldIndex: rescalePercentage } + ]); + } + + }); + Window.nonBlockingFormClosed.connect(function() { + array = editEntityFormArray; + if (Window.getNonBlockingFormResult(array)) { + var properties = propertiesForEditedEntity; + var index = 0; + if (properties.type == "Model") { + properties.modelURL = array[index++].value; + properties.animationURL = array[index++].value; + properties.animationIsPlaying = array[index++].value; + properties.animationFPS = array[index++].value; + properties.animationFrameIndex = array[index++].value; + } + index++; // skip header + properties.position.x = array[index++].value; + properties.position.y = array[index++].value; + properties.position.z = array[index++].value; + properties.registrationPoint.x = array[index++].value; + properties.registrationPoint.y = array[index++].value; + properties.registrationPoint.z = array[index++].value; + + index++; // skip header + var angles = Quat.safeEulerAngles(properties.rotation); + angles.x = array[index++].value; + angles.y = array[index++].value; + angles.z = array[index++].value; + properties.rotation = Quat.fromVec3Degrees(angles); + + index++; // skip header + properties.dimensions.x = array[index++].value; + properties.dimensions.y = array[index++].value; + properties.dimensions.z = array[index++].value; + index++; // skip reset button + index++; // skip rescale percentage + index++; // skip rescale button + + index++; // skip header + properties.velocity.x = array[index++].value; + properties.velocity.y = array[index++].value; + properties.velocity.z = array[index++].value; + properties.damping = array[index++].value; + + properties.angularVelocity.x = array[index++].value; + properties.angularVelocity.y = array[index++].value; + properties.angularVelocity.z = array[index++].value; + properties.angularDamping = array[index++].value; + + properties.gravity.x = array[index++].value; + properties.gravity.y = array[index++].value; + properties.gravity.z = array[index++].value; + + index++; // skip header + properties.mass = array[index++].value; + properties.ignoreForCollisions = array[index++].value; + properties.collisionsWillMove = array[index++].value; + + properties.lifetime = array[index++].value; + properties.visible = array[index++].value; + + if (properties.type == "Box" || properties.type == "Sphere") { + index++; // skip header + properties.color.red = array[index++].value; + properties.color.green = array[index++].value; + properties.color.blue = array[index++].value; + } + Entities.editEntity(editModelID, properties); + selectionDisplay.highlightSelectable(editModelID, propeties); + } + modelSelected = false; + }); + + return that; + +}()); + diff --git a/examples/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js similarity index 100% rename from examples/entitySelectionTool.js rename to examples/libraries/entitySelectionTool.js diff --git a/examples/libraries/httpMultiPart.js b/examples/libraries/httpMultiPart.js new file mode 100644 index 0000000000..72f3bf6417 --- /dev/null +++ b/examples/libraries/httpMultiPart.js @@ -0,0 +1,108 @@ +httpMultiPart = (function () { + var that = {}, + parts, + byteLength, + boundaryString, + crlf; + + function clear() { + boundaryString = "--boundary_" + String(Uuid.generate()).slice(1, 36) + "="; + parts = []; + byteLength = 0; + crlf = ""; + } + that.clear = clear; + + function boundary() { + return boundaryString.slice(2); + } + that.boundary = boundary; + + function length() { + return byteLength; + } + that.length = length; + + function add(object) { + // - name, string + // - name, buffer + var buffer, + string, + stringBuffer, + compressedBuffer; + + if (object.name === undefined) { + + throw new Error("Item to add to HttpMultiPart must have a name"); + + } else if (object.string !== undefined) { + //--= + //Content-Disposition: form-data; name="model_name" + // + // + + string = crlf + boundaryString + "\r\n" + + "Content-Disposition: form-data; name=\"" + object.name + "\"\r\n" + + "\r\n" + + object.string; + buffer = string.toArrayBuffer(); + + } else if (object.buffer !== undefined) { + //--= + //Content-Disposition: form-data; name="fbx"; filename="" + //Content-Type: application/octet-stream + // + // + + string = crlf + boundaryString + "\r\n" + + "Content-Disposition: form-data; name=\"" + object.name + + "\"; filename=\"" + object.buffer.filename + "\"\r\n" + + "Content-Type: application/octet-stream\r\n" + + "\r\n"; + stringBuffer = string.toArrayBuffer(); + + compressedBuffer = object.buffer.buffer.compress(); + buffer = new Uint8Array(stringBuffer.byteLength + compressedBuffer.byteLength); + buffer.set(new Uint8Array(stringBuffer)); + buffer.set(new Uint8Array(compressedBuffer), stringBuffer.byteLength); + + } else { + + throw new Error("Item to add to HttpMultiPart not recognized"); + } + + byteLength += buffer.byteLength; + parts.push(buffer); + + crlf = "\r\n"; + + return true; + } + that.add = add; + + function response() { + var buffer, + index, + str, + i; + + str = crlf + boundaryString + "--\r\n"; + buffer = str.toArrayBuffer(); + byteLength += buffer.byteLength; + parts.push(buffer); + + buffer = new Uint8Array(byteLength); + index = 0; + for (i = 0; i < parts.length; i += 1) { + buffer.set(new Uint8Array(parts[i]), index); + index += parts[i].byteLength; + } + + return buffer; + } + that.response = response; + + clear(); + + return that; +}()); \ No newline at end of file diff --git a/examples/libraries/modelUploader.js b/examples/libraries/modelUploader.js new file mode 100644 index 0000000000..fb3db0958f --- /dev/null +++ b/examples/libraries/modelUploader.js @@ -0,0 +1,690 @@ +modelUploader = (function () { + var that = {}, + modelFile, + modelName, + modelURL, + modelCallback, + isProcessing, + fstBuffer, + fbxBuffer, + //svoBuffer, + mapping, + geometry, + API_URL = "https://data.highfidelity.io/api/v1/models", + MODEL_URL = "http://public.highfidelity.io/models/content", + NAME_FIELD = "name", + SCALE_FIELD = "scale", + FILENAME_FIELD = "filename", + TEXDIR_FIELD = "texdir", + MAX_TEXTURE_SIZE = 1024; + + function info(message) { + if (progressDialog.isOpen()) { + progressDialog.update(message); + } else { + progressDialog.open(message); + } + print(message); + } + + function error(message) { + if (progressDialog.isOpen()) { + progressDialog.close(); + } + print(message); + Window.alert(message); + } + + function randomChar(length) { + var characters = "0123457689abcdefghijklmnopqrstuvwxyz", + string = "", + i; + + for (i = 0; i < length; i += 1) { + string += characters[Math.floor(Math.random() * 36)]; + } + + return string; + } + + function resetDataObjects() { + fstBuffer = null; + fbxBuffer = null; + //svoBuffer = null; + mapping = {}; + geometry = {}; + geometry.textures = []; + geometry.embedded = []; + } + + function readFile(filename) { + var url = "file:///" + filename, + req = new XMLHttpRequest(); + + req.open("GET", url, false); + req.responseType = "arraybuffer"; + req.send(); + if (req.status !== 200) { + error("Could not read file: " + filename + " : " + req.statusText); + return null; + } + + return { + filename: filename.fileName(), + buffer: req.response + }; + } + + function readMapping(buffer) { + var dv = new DataView(buffer.buffer), + lines, + line, + tokens, + i, + name, + value, + remainder, + existing; + + mapping = {}; // { name : value | name : { value : [remainder] } } + lines = dv.string(0, dv.byteLength).split(/\r\n|\r|\n/); + for (i = 0; i < lines.length; i += 1) { + line = lines[i].trim(); + if (line.length > 0 && line[0] !== "#") { + tokens = line.split(/\s*=\s*/); + if (tokens.length > 1) { + name = tokens[0]; + value = tokens[1]; + if (tokens.length > 2) { + remainder = tokens.slice(2, tokens.length).join(" = "); + } else { + remainder = null; + } + if (tokens.length === 2 && mapping[name] === undefined) { + mapping[name] = value; + } else { + if (mapping[name] === undefined) { + mapping[name] = {}; + + } else if (typeof mapping[name] !== "object") { + existing = mapping[name]; + mapping[name] = { existing : null }; + } + + if (mapping[name][value] === undefined) { + mapping[name][value] = []; + } + mapping[name][value].push(remainder); + } + } + } + } + } + + function writeMapping(buffer) { + var name, + value, + remainder, + i, + string = ""; + + for (name in mapping) { + if (mapping.hasOwnProperty(name)) { + if (typeof mapping[name] === "object") { + for (value in mapping[name]) { + if (mapping[name].hasOwnProperty(value)) { + remainder = mapping[name][value]; + if (remainder === null) { + string += (name + " = " + value + "\n"); + } else { + for (i = 0; i < remainder.length; i += 1) { + string += (name + " = " + value + " = " + remainder[i] + "\n"); + } + } + } + } + } else { + string += (name + " = " + mapping[name] + "\n"); + } + } + } + + buffer.buffer = string.toArrayBuffer(); + } + + function readGeometry(fbxBuffer) { + var textures, + view, + index, + EOF, + previousNodeFilename; + + // Reference: + // http://code.blender.org/index.php/2013/08/fbx-binary-file-format-specification/ + + textures = {}; + view = new DataView(fbxBuffer.buffer); + EOF = false; + + function parseBinaryFBX() { + var endOffset, + numProperties, + propertyListLength, + nameLength, + name, + filename; + + endOffset = view.getUint32(index, true); + numProperties = view.getUint32(index + 4, true); + propertyListLength = view.getUint32(index + 8, true); + nameLength = view.getUint8(index + 12); + index += 13; + + if (endOffset === 0) { + return; + } + if (endOffset < index || endOffset > view.byteLength) { + EOF = true; + return; + } + + name = view.string(index, nameLength).toLowerCase(); + index += nameLength; + + if (name === "content" && previousNodeFilename !== "") { + // Blender 2.71 exporter "embeds" external textures as empty binary blobs so ignore these + if (propertyListLength > 5) { + geometry.embedded.push(previousNodeFilename); + } + } + + if (name === "relativefilename") { + filename = view.string(index + 5, view.getUint32(index + 1, true)).fileName(); + if (!textures.hasOwnProperty(filename)) { + textures[filename] = ""; + geometry.textures.push(filename); + } + previousNodeFilename = filename; + } else { + previousNodeFilename = ""; + } + + index += (propertyListLength); + + while (index < endOffset && !EOF) { + parseBinaryFBX(); + } + } + + function readTextFBX() { + var line, + view, + viewLength, + charCode, + charCodes, + numCharCodes, + filename, + relativeFilename = "", + MAX_CHAR_CODES = 250; + + view = new Uint8Array(fbxBuffer.buffer); + viewLength = view.byteLength; + charCodes = []; + numCharCodes = 0; + + for (index = 0; index < viewLength; index += 1) { + charCode = view[index]; + if (charCode !== 9 && charCode !== 32) { + if (charCode === 10) { // EOL. Can ignore EOF. + line = String.fromCharCode.apply(String, charCodes).toLowerCase(); + // For embedded textures, "Content:" line immediately follows "RelativeFilename:" line. + if (line.slice(0, 8) === "content:" && relativeFilename !== "") { + geometry.embedded.push(relativeFilename); + } + if (line.slice(0, 17) === "relativefilename:") { + filename = line.slice(line.indexOf("\""), line.lastIndexOf("\"") - line.length).fileName(); + if (!textures.hasOwnProperty(filename)) { + textures[filename] = ""; + geometry.textures.push(filename); + } + relativeFilename = filename; + } else { + relativeFilename = ""; + } + charCodes = []; + numCharCodes = 0; + } else { + if (numCharCodes < MAX_CHAR_CODES) { // Only interested in start of line + charCodes.push(charCode); + numCharCodes += 1; + } + } + } + } + } + + if (view.string(0, 18) === "Kaydara FBX Binary") { + previousNodeFilename = ""; + + index = 27; + while (index < view.byteLength - 39 && !EOF) { + parseBinaryFBX(); + } + + } else { + + readTextFBX(); + + } + } + + function readModel() { + var fbxFilename, + //svoFilename, + fileType; + + info("Reading model file"); + print("Model file: " + modelFile); + + if (modelFile.toLowerCase().fileType() === "fst") { + fstBuffer = readFile(modelFile); + if (fstBuffer === null) { + return false; + } + readMapping(fstBuffer); + fileType = mapping[FILENAME_FIELD].toLowerCase().fileType(); + if (mapping.hasOwnProperty(FILENAME_FIELD)) { + if (fileType === "fbx") { + fbxFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; + //} else if (fileType === "svo") { + // svoFilename = modelFile.path() + "\\" + mapping[FILENAME_FIELD]; + } else { + error("Unrecognized model type in FST file!"); + return false; + } + } else { + error("Model file name not found in FST file!"); + return false; + } + } else { + fstBuffer = { + filename: "Interface." + randomChar(6), // Simulate avatar model uploading behaviour + buffer: null + }; + + if (modelFile.toLowerCase().fileType() === "fbx") { + fbxFilename = modelFile; + mapping[FILENAME_FIELD] = modelFile.fileName(); + + //} else if (modelFile.toLowerCase().fileType() === "svo") { + // svoFilename = modelFile; + // mapping[FILENAME_FIELD] = modelFile.fileName(); + + } else { + error("Unrecognized file type: " + modelFile); + return false; + } + } + + if (!isProcessing) { return false; } + + if (fbxFilename) { + fbxBuffer = readFile(fbxFilename); + if (fbxBuffer === null) { + return false; + } + + if (!isProcessing) { return false; } + + readGeometry(fbxBuffer); + } + + //if (svoFilename) { + // svoBuffer = readFile(svoFilename); + // if (svoBuffer === null) { + // return false; + // } + //} + + // Add any missing basic mappings + if (!mapping.hasOwnProperty(NAME_FIELD)) { + mapping[NAME_FIELD] = modelFile.fileName().fileBase(); + } + if (!mapping.hasOwnProperty(TEXDIR_FIELD)) { + mapping[TEXDIR_FIELD] = "."; + } + if (!mapping.hasOwnProperty(SCALE_FIELD)) { + mapping[SCALE_FIELD] = 1.0; + } + + return true; + } + + function setProperties() { + var form = [], + directory, + displayAs, + validateAs; + + progressDialog.close(); + print("Setting model properties"); + + form.push({ label: "Name:", value: mapping[NAME_FIELD] }); + + directory = modelFile.path() + "/" + mapping[TEXDIR_FIELD]; + displayAs = new RegExp("^" + modelFile.path().regExpEscape() + "[\\\\\\\/](.*)"); + validateAs = new RegExp("^" + modelFile.path().regExpEscape() + "([\\\\\\\/].*)?"); + + form.push({ + label: "Texture directory:", + directory: modelFile.path() + "/" + mapping[TEXDIR_FIELD], + title: "Choose Texture Directory", + displayAs: displayAs, + validateAs: validateAs, + errorMessage: "Texture directory must be subdirectory of the model directory." + }); + + form.push({ button: "Cancel" }); + + if (!Window.form("Set Model Properties", form)) { + print("User cancelled uploading model"); + return false; + } + + mapping[NAME_FIELD] = form[0].value; + mapping[TEXDIR_FIELD] = form[1].directory.slice(modelFile.path().length + 1); + if (mapping[TEXDIR_FIELD] === "") { + mapping[TEXDIR_FIELD] = "."; + } + + writeMapping(fstBuffer); + + return true; + } + + function createHttpMessage(callback) { + var multiparts = [], + lodCount, + lodFile, + lodBuffer, + textureBuffer, + textureSourceFormat, + textureTargetFormat, + embeddedTextures, + i; + + info("Preparing to send model"); + + // Model name + if (mapping.hasOwnProperty(NAME_FIELD)) { + multiparts.push({ + name : "model_name", + string : mapping[NAME_FIELD] + }); + } else { + error("Model name is missing"); + httpMultiPart.clear(); + return; + } + + // FST file + if (fstBuffer) { + multiparts.push({ + name : "fst", + buffer: fstBuffer + }); + } + + // FBX file + if (fbxBuffer) { + multiparts.push({ + name : "fbx", + buffer: fbxBuffer + }); + } + + // SVO file + //if (svoBuffer) { + // multiparts.push({ + // name : "svo", + // buffer: svoBuffer + // }); + //} + + // LOD files + lodCount = 0; + for (lodFile in mapping.lod) { + if (mapping.lod.hasOwnProperty(lodFile)) { + lodBuffer = readFile(modelFile.path() + "\/" + lodFile); + if (lodBuffer === null) { + return; + } + multiparts.push({ + name: "lod" + lodCount, + buffer: lodBuffer + }); + lodCount += 1; + } + if (!isProcessing) { return; } + } + + // Textures + embeddedTextures = "|" + geometry.embedded.join("|") + "|"; + for (i = 0; i < geometry.textures.length; i += 1) { + if (embeddedTextures.indexOf("|" + geometry.textures[i].fileName() + "|") === -1) { + textureBuffer = readFile(modelFile.path() + "\/" + + (mapping[TEXDIR_FIELD] !== "." ? mapping[TEXDIR_FIELD] + "\/" : "") + + geometry.textures[i]); + if (textureBuffer === null) { + return; + } + + textureSourceFormat = geometry.textures[i].fileType().toLowerCase(); + textureTargetFormat = (textureSourceFormat === "jpg" ? "jpg" : "png"); + textureBuffer.buffer = + textureBuffer.buffer.recodeImage(textureSourceFormat, textureTargetFormat, MAX_TEXTURE_SIZE); + textureBuffer.filename = textureBuffer.filename.slice(0, -textureSourceFormat.length) + textureTargetFormat; + + multiparts.push({ + name: "texture" + i, + buffer: textureBuffer + }); + } + + if (!isProcessing) { return; } + } + + // Model category + multiparts.push({ + name : "model_category", + string : "content" + }); + + // Create HTTP message + httpMultiPart.clear(); + Script.setTimeout(function addMultipart() { + var multipart = multiparts.shift(); + httpMultiPart.add(multipart); + + if (!isProcessing) { return; } + + if (multiparts.length > 0) { + Script.setTimeout(addMultipart, 25); + } else { + callback(); + } + }, 25); + } + + function sendToHighFidelity() { + var req, + uploadedChecks, + HTTP_GET_TIMEOUT = 60, // 1 minute + HTTP_SEND_TIMEOUT = 900, // 15 minutes + UPLOADED_CHECKS = 30, + CHECK_UPLOADED_TIMEOUT = 1, // 1 second + handleCheckUploadedResponses, + handleUploadModelResponses, + handleRequestUploadResponses; + + function uploadTimedOut() { + error("Model upload failed: Internet request timed out!"); + } + + function debugResponse() { + print("req.errorCode = " + req.errorCode); + print("req.readyState = " + req.readyState); + print("req.status = " + req.status); + print("req.statusText = " + req.statusText); + print("req.responseType = " + req.responseType); + print("req.responseText = " + req.responseText); + print("req.response = " + req.response); + print("req.getAllResponseHeaders() = " + req.getAllResponseHeaders()); + } + + function checkUploaded() { + if (!isProcessing) { return; } + + info("Checking uploaded model"); + + req = new XMLHttpRequest(); + req.open("HEAD", modelURL, true); + req.timeout = HTTP_GET_TIMEOUT * 1000; + req.onreadystatechange = handleCheckUploadedResponses; + req.ontimeout = uploadTimedOut; + req.send(); + } + + handleCheckUploadedResponses = function () { + //debugResponse(); + if (req.readyState === req.DONE) { + if (req.status === 200) { + // Note: Unlike avatar models, for content models we don't need to refresh texture cache. + print("Model uploaded: " + modelURL); + progressDialog.close(); + if (Window.confirm("Your model has been uploaded as: " + modelURL + "\nDo you want to rez it?")) { + modelCallback(modelURL); + } + } else if (req.status === 404) { + if (uploadedChecks > 0) { + uploadedChecks -= 1; + Script.setTimeout(checkUploaded, CHECK_UPLOADED_TIMEOUT * 1000); + } else { + print("Error: " + req.status + " " + req.statusText); + error("We could not verify that your model was successfully uploaded but it may have been at: " + + modelURL); + } + } else { + print("Error: " + req.status + " " + req.statusText); + error("There was a problem with your upload, please try again later."); + } + } + }; + + function uploadModel(method) { + var url; + + if (!isProcessing) { return; } + + req = new XMLHttpRequest(); + if (method === "PUT") { + url = API_URL + "\/" + modelName; + req.open("PUT", url, true); //print("PUT " + url); + } else { + url = API_URL; + req.open("POST", url, true); //print("POST " + url); + } + req.setRequestHeader("Content-Type", "multipart/form-data; boundary=\"" + httpMultiPart.boundary() + "\""); + req.timeout = HTTP_SEND_TIMEOUT * 1000; + req.onreadystatechange = handleUploadModelResponses; + req.ontimeout = uploadTimedOut; + req.send(httpMultiPart.response().buffer); + } + + handleUploadModelResponses = function () { + //debugResponse(); + if (req.readyState === req.DONE) { + if (req.status === 200) { + uploadedChecks = UPLOADED_CHECKS; + checkUploaded(); + } else { + print("Error: " + req.status + " " + req.statusText); + error("There was a problem with your upload, please try again later."); + } + } + }; + + function requestUpload() { + var url; + + if (!isProcessing) { return; } + + url = API_URL + "\/" + modelName; // XMLHttpRequest automatically handles authorization of API requests. + req = new XMLHttpRequest(); + req.open("GET", url, true); //print("GET " + url); + req.responseType = "json"; + req.timeout = HTTP_GET_TIMEOUT * 1000; + req.onreadystatechange = handleRequestUploadResponses; + req.ontimeout = uploadTimedOut; + req.send(); + } + + handleRequestUploadResponses = function () { + var response; + + //debugResponse(); + if (req.readyState === req.DONE) { + if (req.status === 200) { + if (req.responseType === "json") { + response = JSON.parse(req.responseText); + if (response.status === "success") { + if (response.exists === false) { + uploadModel("POST"); + } else if (response.can_update === true) { + uploadModel("PUT"); + } else { + error("This model file already exists and is owned by someone else!"); + } + return; + } + } + } else { + print("Error: " + req.status + " " + req.statusText); + } + error("Model upload failed! Something went wrong at the data server."); + } + }; + + info("Sending model to High Fidelity"); + + requestUpload(); + } + + that.upload = function (file, callback) { + + modelFile = file; + modelCallback = callback; + + isProcessing = true; + + progressDialog.onCancel = function () { + print("User cancelled uploading model"); + isProcessing = false; + }; + + resetDataObjects(); + + if (readModel()) { + if (setProperties()) { + modelName = mapping[NAME_FIELD]; + modelURL = MODEL_URL + "\/" + mapping[NAME_FIELD] + ".fst"; // All models are uploaded as an FST + + createHttpMessage(sendToHighFidelity); + } + } + + resetDataObjects(); + }; + + return that; +}()); \ No newline at end of file diff --git a/examples/libraries/progressDialog.js b/examples/libraries/progressDialog.js new file mode 100644 index 0000000000..349808131d --- /dev/null +++ b/examples/libraries/progressDialog.js @@ -0,0 +1,132 @@ +progressDialog = (function () { + var that = {}, + progressBackground, + progressMessage, + cancelButton, + displayed = false, + backgroundWidth = 300, + backgroundHeight = 100, + messageHeight = 32, + cancelWidth = 70, + cancelHeight = 32, + textColor = { red: 255, green: 255, blue: 255 }, + textBackground = { red: 52, green: 52, blue: 52 }, + backgroundUrl = toolIconUrl + "progress-background.svg", + windowDimensions; + + progressBackground = Overlays.addOverlay("image", { + width: backgroundWidth, + height: backgroundHeight, + imageURL: backgroundUrl, + alpha: 0.9, + visible: false + }); + + progressMessage = Overlays.addOverlay("text", { + width: backgroundWidth - 40, + height: messageHeight, + text: "", + textColor: textColor, + backgroundColor: textBackground, + alpha: 0.9, + visible: false + }); + + cancelButton = Overlays.addOverlay("text", { + width: cancelWidth, + height: cancelHeight, + text: "Cancel", + textColor: textColor, + backgroundColor: textBackground, + alpha: 0.9, + visible: false + }); + + function move() { + var progressX, + progressY; + + if (displayed) { + + if (windowDimensions.x === Window.innerWidth && windowDimensions.y === Window.innerHeight) { + return; + } + windowDimensions.x = Window.innerWidth; + windowDimensions.y = Window.innerHeight; + + progressX = (windowDimensions.x - backgroundWidth) / 2; // Center. + progressY = windowDimensions.y / 2 - backgroundHeight; // A little up from center. + + Overlays.editOverlay(progressBackground, { x: progressX, y: progressY }); + Overlays.editOverlay(progressMessage, { x: progressX + 20, y: progressY + 15 }); + Overlays.editOverlay(cancelButton, { + x: progressX + backgroundWidth - cancelWidth - 20, + y: progressY + backgroundHeight - cancelHeight - 15 + }); + } + } + that.move = move; + + that.onCancel = undefined; + + function open(message) { + if (!displayed) { + windowDimensions = { x: 0, y : 0 }; + displayed = true; + move(); + Overlays.editOverlay(progressBackground, { visible: true }); + Overlays.editOverlay(progressMessage, { visible: true, text: message }); + Overlays.editOverlay(cancelButton, { visible: true }); + } else { + throw new Error("open() called on progressDialog when already open"); + } + } + that.open = open; + + function isOpen() { + return displayed; + } + that.isOpen = isOpen; + + function update(message) { + if (displayed) { + Overlays.editOverlay(progressMessage, { text: message }); + } else { + throw new Error("update() called on progressDialog when not open"); + } + } + that.update = update; + + function close() { + if (displayed) { + Overlays.editOverlay(cancelButton, { visible: false }); + Overlays.editOverlay(progressMessage, { visible: false }); + Overlays.editOverlay(progressBackground, { visible: false }); + displayed = false; + } else { + throw new Error("close() called on progressDialog when not open"); + } + } + that.close = close; + + function mousePressEvent(event) { + if (Overlays.getOverlayAtPoint({ x: event.x, y: event.y }) === cancelButton) { + if (typeof this.onCancel === "function") { + close(); + this.onCancel(); + } + return true; + } + return false; + } + that.mousePressEvent = mousePressEvent; + + function cleanup() { + Overlays.deleteOverlay(cancelButton); + Overlays.deleteOverlay(progressMessage); + Overlays.deleteOverlay(progressBackground); + } + that.cleanup = cleanup; + + return that; +}()); \ No newline at end of file diff --git a/examples/libraries/stringHelpers.js b/examples/libraries/stringHelpers.js new file mode 100644 index 0000000000..0fae9035f0 --- /dev/null +++ b/examples/libraries/stringHelpers.js @@ -0,0 +1,66 @@ + + + +if (typeof String.prototype.fileName !== "function") { + String.prototype.fileName = function () { + return this.replace(/^(.*[\/\\])*/, ""); + }; +} + +if (typeof String.prototype.fileBase !== "function") { + String.prototype.fileBase = function () { + var filename = this.fileName(); + return filename.slice(0, filename.indexOf(".")); + }; +} + +if (typeof String.prototype.fileType !== "function") { + String.prototype.fileType = function () { + return this.slice(this.lastIndexOf(".") + 1); + }; +} + +if (typeof String.prototype.path !== "function") { + String.prototype.path = function () { + return this.replace(/[\\\/][^\\\/]*$/, ""); + }; +} + +if (typeof String.prototype.regExpEscape !== "function") { + String.prototype.regExpEscape = function () { + return this.replace(/([$\^.+*?|\\\/{}()\[\]])/g, '\\$1'); + }; +} + +if (typeof String.prototype.toArrayBuffer !== "function") { + String.prototype.toArrayBuffer = function () { + var length, + buffer, + view, + charCode, + charCodes, + i; + + charCodes = []; + + length = this.length; + for (i = 0; i < length; i += 1) { + charCode = this.charCodeAt(i); + if (charCode <= 255) { + charCodes.push(charCode); + } else { + charCodes.push(charCode / 256); + charCodes.push(charCode % 256); + } + } + + length = charCodes.length; + buffer = new ArrayBuffer(length); + view = new Uint8Array(buffer); + for (i = 0; i < length; i += 1) { + view[i] = charCodes[i]; + } + + return buffer; + }; +} diff --git a/examples/toolBars.js b/examples/libraries/toolBars.js similarity index 100% rename from examples/toolBars.js rename to examples/libraries/toolBars.js