BUGZ-657; BUGZ-713: Add a copy of the system library JS files because lots of system scripts depend on them being in the default scripts location

This commit is contained in:
Zach Fox 2019-06-18 17:13:38 -07:00
parent 99feabd4c9
commit 7a4aa1e98c
25 changed files with 8352 additions and 0 deletions

View file

@ -0,0 +1,12 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtWebChannel 1.0
import QtGraphicalEffects 1.0
import "qrc:///qml/controls" as HifiControls
HifiControls.WebView {
id: entityListToolWebView
url: Qt.resolvedUrl("../html/entityList.html")
enabled: true
blurOnCtrlShift: false
}

View file

@ -0,0 +1,85 @@
//
// ToolTip.js
// examples/libraries
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
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,
backgroundAlpha: 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 += "Shape Type: " + properties.shapeType + "\n"
text += "Compound Shape URL: " + properties.compoundShapeURL + "\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 += "Density: " + properties.density + "\n"
text += "Script: " + properties.script + "\n"
Overlays.editOverlay(this.textOverlay, { text: text });
}
this.cleanup = function () {
Overlays.deleteOverlay(this.textOverlay);
}
}
tooltip = new Tooltip();

View file

@ -0,0 +1,87 @@
"use strict";
/*jslint vars: true, plusplus: true*/
/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print*/
Trigger = function(properties) {
properties = properties || {};
var that = this;
that.label = properties.label || Math.random();
that.SMOOTH_RATIO = properties.smooth || 0.1; // Time averaging of trigger - 0.0 disables smoothing
that.DEADZONE = properties.deadzone || 0.10; // Once pressed, a trigger must fall below the deadzone to be considered un-pressed once pressed.
that.HYSTERESIS = properties.hystersis || 0.05; // If not pressed, a trigger must go above DEADZONE + HYSTERSIS to be considered pressed
that.value = 0;
that.pressed = false;
that.clicked = false;
// Handlers
that.onPress = properties.onPress || function(){
print("Pressed trigger " + that.label)
};
that.onRelease = properties.onRelease || function(){
print("Released trigger " + that.label)
};
that.onClick = properties.onClick || function(){
print("Clicked trigger " + that.label)
};
that.onUnclick = properties.onUnclick || function(){
print("Unclicked trigger " + that.label)
};
// Getters
that.isPressed = function() {
return that.pressed;
}
that.isClicked = function() {
return that.clicked;
}
that.getValue = function() {
return that.value;
}
// Private values
var controller = properties.controller || Controller.Standard.LT;
var controllerClick = properties.controllerClick || Controller.Standard.LTClick;
that.mapping = Controller.newMapping('com.highfidelity.controller.trigger.' + controller + '-' + controllerClick + '.' + that.label + Math.random());
Script.scriptEnding.connect(that.mapping.disable);
// Setup mapping,
that.mapping.from(controller).peek().to(function(value) {
that.value = (that.value * that.SMOOTH_RATIO) +
(value * (1.0 - that.SMOOTH_RATIO));
var oldPressed = that.pressed;
if (!that.pressed && that.value >= (that.DEADZONE + that.HYSTERESIS)) {
that.pressed = true;
that.onPress();
}
if (that.pressed && that.value < that.HYSTERESIS) {
that.pressed = false;
that.onRelease();
}
});
that.mapping.from(controllerClick).peek().to(function(value){
if (!that.clicked && value > 0.0) {
that.clicked = true;
that.onClick();
}
if (that.clicked && value == 0.0) {
that.clicked = false;
that.onUnclick();
}
});
that.enable = function() {
that.mapping.enable();
}
that.disable = function() {
that.mapping.disable();
}
}

View file

@ -0,0 +1,606 @@
//
// WebTablet.js
//
// Created by Anthony J. Thibault on 8/8/2016
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global getControllerWorldLocation, Tablet, WebTablet:true, HMD, Settings, Script,
Vec3, Quat, MyAvatar, Entities, Overlays, Camera, Messages, Xform, clamp, Controller, Mat4, resizeTablet */
Script.include(Script.resolvePath("../libraries/utils.js"));
Script.include(Script.resolvePath("../libraries/controllers.js"));
Script.include(Script.resolvePath("../libraries/Xform.js"));
var Y_AXIS = {x: 0, y: 1, z: 0};
var X_AXIS = {x: 1, y: 0, z: 0};
var DEFAULT_DPI = 31;
var DEFAULT_WIDTH = 0.4375;
var DEFAULT_VERTICAL_FIELD_OF_VIEW = 45; // degrees
var SENSOR_TO_ROOM_MATRIX = -2;
var CAMERA_MATRIX = -7;
var ROT_Y_180 = {x: 0.0, y: 1.0, z: 0, w: 0};
var ROT_LANDSCAPE = {x: 1.0, y: 1.0, z: 0, w: 0};
var TABLET_TEXTURE_RESOLUTION = { x: 480, y: 706 };
var INCHES_TO_METERS = 1 / 39.3701;
var NO_HANDS = -1;
var DELAY_FOR_30HZ = 33; // milliseconds
var TABLET_MATERIAL_ENTITY_NAME = 'Tablet-Material-Entity';
// will need to be recaclulated if dimensions of fbx model change.
var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269};
var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "images/button-close.png";
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png";
// var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx";
var LOCAL_TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button-small-bezel.fbx";
var HIGH_PRIORITY = 1;
var LOW_PRIORITY = 0;
var SUBMESH = 2;
// returns object with two fields:
// * position - position in front of the user
// * rotation - rotation of entity so it faces the user.
function calcSpawnInfo(hand, landscape) {
var finalPosition;
var LEFT_HAND = Controller.Standard.LeftHand;
var sensorToWorldScale = MyAvatar.sensorToWorldScale;
var headPos = (HMD.active && Camera.mode === "first person") ? HMD.position : Camera.position;
var headRot = Quat.cancelOutRollAndPitch((HMD.active && Camera.mode === "first person") ?
HMD.orientation : Camera.orientation);
var right = Quat.getRight(headRot);
var forward = Quat.getForward(headRot);
var up = Quat.getUp(headRot);
var FORWARD_OFFSET = 0.5 * sensorToWorldScale;
var UP_OFFSET = -0.16 * sensorToWorldScale;
var RIGHT_OFFSET = ((hand === LEFT_HAND) ? -0.18 : 0.18) * sensorToWorldScale;
var forwardPosition = Vec3.sum(headPos, Vec3.multiply(FORWARD_OFFSET, forward));
var lateralPosition = Vec3.sum(forwardPosition, Vec3.multiply(RIGHT_OFFSET, right));
finalPosition = Vec3.sum(lateralPosition, Vec3.multiply(UP_OFFSET, up));
var MY_EYES = { x: 0.0, y: 0.15, z: 0.0 };
var lookAtEndPosition = Vec3.sum(Vec3.multiply(RIGHT_OFFSET, right), Vec3.multiply(FORWARD_OFFSET, forward));
var orientation = Quat.lookAt(MY_EYES, lookAtEndPosition, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.UNIT_Y));
return {
position: finalPosition,
rotation: landscape ? Quat.multiply(orientation, ROT_LANDSCAPE) : Quat.multiply(orientation, ROT_Y_180)
};
}
cleanUpOldMaterialEntities = function() {
var avatarEntityData = MyAvatar.getAvatarEntityData();
for (var entityID in avatarEntityData) {
var entityName = Entities.getEntityProperties(entityID, ["name"]).name;
if (entityName === TABLET_MATERIAL_ENTITY_NAME) {
Entities.deleteEntity(entityID);
}
}
};
/**
* WebTablet
* @param url [string] url of content to show on the tablet.
* @param width [number] width in meters of the tablet model
* @param dpi [number] dpi of web surface used to show the content.
* @param hand [number] -1 indicates no hand, Controller.Standard.RightHand or Controller.Standard.LeftHand
*/
WebTablet = function (url, width, dpi, hand, location, visible) {
var _this = this;
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
// scale factor of natural tablet dimensions.
var tabletWidth = (width || DEFAULT_WIDTH) * sensorScaleFactor;
var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x;
var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor;
var tabletDepth = TABLET_NATURAL_DIMENSIONS.z * tabletScaleFactor;
this.landscape = false;
visible = visible === true;
var tabletDpi;
if (dpi) {
tabletDpi = dpi;
} else {
tabletDpi = DEFAULT_DPI * (DEFAULT_WIDTH / tabletWidth);
}
var modelURL = LOCAL_TABLET_MODEL_PATH;
var tabletProperties = {
name: "WebTablet Tablet",
type: "Model",
modelURL: modelURL,
url: modelURL, // for overlay
grabbable: true, // for overlay
loadPriority: 10.0, // for overlay
grab: { grabbable: true },
dimensions: { x: tabletWidth, y: tabletHeight, z: tabletDepth },
parentID: MyAvatar.SELF_ID,
visible: visible,
isGroupCulled: true
};
// compute position, rotation & parentJointIndex of the tablet
this.calculateTabletAttachmentProperties(hand, true, tabletProperties);
if (location) {
tabletProperties.localPosition = location.localPosition;
tabletProperties.localRotation = location.localRotation;
}
this.cleanUpOldTablets();
cleanUpOldMaterialEntities();
this.tabletEntityID = Overlays.addOverlay("model", tabletProperties);
if (this.webOverlayID) {
Overlays.deleteOverlay(this.webOverlayID);
}
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.5) * sensorScaleFactor;
var WEB_ENTITY_Y_OFFSET = 1.25 * tabletScaleFactor;
var screenWidth = 0.9367 * tabletWidth;
var screenHeight = 0.9000 * tabletHeight;
this.webOverlayID = Overlays.addOverlay("web3d", {
name: "WebTablet Web",
url: url,
localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET },
localRotation: Quat.angleAxis(180, Y_AXIS),
dimensions: {x: screenWidth, y: screenHeight, z: 1.0},
dpi: tabletDpi,
color: { red: 255, green: 255, blue: 255 },
alpha: 1.0,
parentID: this.tabletEntityID,
parentJointIndex: -1,
showKeyboardFocusHighlight: false,
visible: visible
});
var homeButtonDim = 4.0 * tabletScaleFactor / 1.5;
var HOME_BUTTON_X_OFFSET = 0.00079 * sensorScaleFactor;
var HOME_BUTTON_Y_OFFSET = -1 * ((tabletHeight / 2) - (4.0 * tabletScaleFactor / 2));
var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleFactor;
this.homeButtonID = Overlays.addOverlay("circle3d", {
name: "homeButton",
localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET },
localRotation: Quat.fromVec3Degrees({ x: 180, y: 180, z: 0}),
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim },
solid: true,
alpha: 0.0,
visible: visible,
drawInFront: false,
parentID: this.tabletEntityID,
parentJointIndex: -1
});
this.homeButtonHighlightID = Overlays.addOverlay("circle3d", {
name: "homeButtonHighlight",
localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET },
localRotation: Quat.fromVec3Degrees({ x: 180, y: 180, z: 0}),
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim },
color: {red: 255, green: 255, blue: 255},
solid: true,
innerRadius: 0.9,
ignorePickIntersection: true,
alpha: 0.0,
visible: visible,
drawInFront: false,
parentID: this.tabletEntityID,
parentJointIndex: -1
});
this.receive = function (channel, senderID, senderUUID, localOnly) {
if (_this.homeButtonID === senderID) {
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var onHomeScreen = tablet.onHomeScreen();
var isMessageOpen;
if (onHomeScreen) {
isMessageOpen = tablet.isMessageDialogOpen();
if (isMessageOpen === false) {
HMD.closeTablet();
}
} else {
isMessageOpen = tablet.isMessageDialogOpen();
if (isMessageOpen === false) {
tablet.gotoHomeScreen();
_this.setHomeButtonTexture();
}
}
}
};
this.state = "idle";
this.getRoot = function() {
return Entities.getWebViewRoot(_this.tabletEntityID);
};
this.clicked = false;
this.myOnHmdChanged = function () {
_this.onHmdChanged();
};
HMD.displayModeChanged.connect(this.myOnHmdChanged);
this.myMousePressEvent = function (event) {
_this.mousePressEvent(event);
};
this.myMouseMoveEvent = function (event) {
_this.mouseMoveEvent(event);
};
this.myMouseReleaseEvent = function (event) {
_this.mouseReleaseEvent(event);
};
Controller.mousePressEvent.connect(this.myMousePressEvent);
Controller.mouseMoveEvent.connect(this.myMouseMoveEvent);
Controller.mouseReleaseEvent.connect(this.myMouseReleaseEvent);
this.dragging = false;
this.initialLocalIntersectionPoint = {x: 0, y: 0, z: 0};
this.initialLocalPosition = {x: 0, y: 0, z: 0};
this.myGeometryChanged = function (geometry) {
_this.geometryChanged(geometry);
};
Window.geometryChanged.connect(this.myGeometryChanged);
this.myCameraModeChanged = function(newMode) {
_this.cameraModeChanged(newMode);
};
Camera.modeUpdated.connect(this.myCameraModeChanged);
};
WebTablet.prototype.getTabletTextureResolution = function() {
if (this.landscape) {
return { x: TABLET_TEXTURE_RESOLUTION.y , y: TABLET_TEXTURE_RESOLUTION.x };
} else {
return TABLET_TEXTURE_RESOLUTION;
}
};
WebTablet.prototype.getLandscape = function() {
return this.landscape;
}
WebTablet.prototype.setLandscape = function(newLandscapeValue) {
if (this.landscape === newLandscapeValue) {
return;
}
this.landscape = newLandscapeValue;
var cameraOrientation = Quat.cancelOutRollAndPitch(Camera.orientation);
var tabletRotation = Quat.multiply(cameraOrientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180);
Overlays.editOverlay(this.tabletEntityID, {
rotation: tabletRotation
});
var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale;
var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x;
var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor;
var screenWidth = 0.9275 * tabletWidth;
var screenHeight = 0.8983 * tabletHeight;
var screenRotation = Quat.angleAxis(180, Vec3.UP);
Overlays.editOverlay(this.webOverlayID, {
localRotation: this.landscape ? Quat.multiply(screenRotation, Quat.angleAxis(-90, Vec3.FRONT)) : screenRotation,
dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1}
});
};
WebTablet.prototype.getLocation = function() {
var location = Overlays.getProperty(this.tabletEntityID, "localPosition");
var orientation = Overlays.getProperty(this.tabletEntityID, "localOrientation");
return {
localPosition: location,
localRotation: orientation
};
};
WebTablet.prototype.setHomeButtonTexture = function() {
// TODO - is this still needed?
// Entities.editEntity(this.tabletEntityID, {textures: JSON.stringify({"tex.close": HOME_BUTTON_TEXTURE})});
};
WebTablet.prototype.setURL = function (url) {
Overlays.editOverlay(this.webOverlayID, { url: url });
};
WebTablet.prototype.setScriptURL = function (scriptURL) {
Overlays.editOverlay(this.webOverlayID, { scriptURL: scriptURL });
};
WebTablet.prototype.getOverlayObject = function () {
return Overlays.getOverlayObject(this.webOverlayID);
};
WebTablet.prototype.setWidth = function (width) {
// imported from libraries/utils.js
resizeTablet(width);
};
WebTablet.prototype.destroy = function () {
Overlays.deleteOverlay(this.webOverlayID);
Overlays.deleteOverlay(this.tabletEntityID);
Overlays.deleteOverlay(this.homeButtonID);
Overlays.deleteOverlay(this.homeButtonHighlightID);
HMD.displayModeChanged.disconnect(this.myOnHmdChanged);
Controller.mousePressEvent.disconnect(this.myMousePressEvent);
Controller.mouseMoveEvent.disconnect(this.myMouseMoveEvent);
Controller.mouseReleaseEvent.disconnect(this.myMouseReleaseEvent);
Window.geometryChanged.disconnect(this.myGeometryChanged);
Camera.modeUpdated.disconnect(this.myCameraModeChanged);
};
WebTablet.prototype.geometryChanged = function (geometry) {
if (!HMD.active && HMD.tabletID) {
var tabletProperties = {};
// compute position, rotation & parentJointIndex of the tablet
this.calculateTabletAttachmentProperties(NO_HANDS, false, tabletProperties);
Overlays.editOverlay(HMD.tabletID, tabletProperties);
}
};
function gluPerspective(fovy, aspect, zNear, zFar) {
var cotan = 1 / Math.tan(fovy / 2);
var alpha = -(zFar + zNear) / (zFar - zNear);
var beta = -(2 * zFar * zNear) / (zFar - zNear);
var col0 = {x: cotan / aspect, y: 0, z: 0, w: 0};
var col1 = {x: 0, y: cotan, z: 0, w: 0};
var col2 = {x: 0, y: 0, z: alpha, w: -1};
var col3 = {x: 0, y: 0, z: beta, w: 0};
return Mat4.createFromColumns(col0, col1, col2, col3);
}
// calclulate the appropriate position of the tablet in world space, such that it fits in the center of the screen.
// with a bit of padding on the top and bottom.
// windowPos is used to position the center of the tablet at the given position.
WebTablet.prototype.calculateWorldAttitudeRelativeToCamera = function (windowPos) {
var DEFAULT_DESKTOP_TABLET_SCALE = 75;
var DESKTOP_TABLET_SCALE = Settings.getValue("desktopTabletScale") || DEFAULT_DESKTOP_TABLET_SCALE;
// clamp window pos so 2d tablet is not off-screen.
var TABLET_TEXEL_PADDING = {x: 60, y: 90};
var X_CLAMP = (DESKTOP_TABLET_SCALE / 100) * ((this.getTabletTextureResolution().x / 2) + TABLET_TEXEL_PADDING.x);
var Y_CLAMP = (DESKTOP_TABLET_SCALE / 100) * ((this.getTabletTextureResolution().y / 2) + TABLET_TEXEL_PADDING.y);
windowPos.x = clamp(windowPos.x, X_CLAMP, Window.innerWidth - X_CLAMP);
windowPos.y = clamp(windowPos.y, Y_CLAMP, Window.innerHeight - Y_CLAMP);
var fov = (Settings.getValue('fieldOfView') || DEFAULT_VERTICAL_FIELD_OF_VIEW) * (Math.PI / 180);
// scale factor of natural tablet dimensions.
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
var tabletWidth = getTabletWidthFromSettings() * sensorScaleFactor;
var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x;
var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor;
var tabletDepth = TABLET_NATURAL_DIMENSIONS.z * tabletScaleFactor;
var tabletDpi = DEFAULT_DPI * (DEFAULT_WIDTH / tabletWidth);
var MAX_PADDING_FACTOR = 2.2;
var PADDING_FACTOR = Math.min(Window.innerHeight / this.getTabletTextureResolution().y, MAX_PADDING_FACTOR);
var TABLET_HEIGHT = (this.getTabletTextureResolution().y / tabletDpi) * INCHES_TO_METERS;
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2);
// calcualte distance from camera
var dist = (PADDING_FACTOR * TABLET_HEIGHT) / (2 * Math.tan(fov / 2) * (DESKTOP_TABLET_SCALE / 100)) - WEB_ENTITY_Z_OFFSET;
var Z_NEAR = 0.01;
var Z_FAR = 100.0;
// calculate mouse position in clip space
var alpha = -(Z_FAR + Z_NEAR) / (Z_FAR - Z_NEAR);
var beta = -(2 * Z_FAR * Z_NEAR) / (Z_FAR - Z_NEAR);
var clipZ = (beta / dist) - alpha;
var clipMousePosition = {x: (2 * windowPos.x / Window.innerWidth) - 1,
y: (2 * ((Window.innerHeight - windowPos.y) / Window.innerHeight)) - 1,
z: clipZ};
// calculate projection matrix
var aspect = Window.innerWidth / Window.innerHeight;
var projMatrix = gluPerspective(fov, aspect, Z_NEAR, Z_FAR);
// transform mouse clip position into view coordinates.
var viewMousePosition = Mat4.transformPoint(Mat4.inverse(projMatrix), clipMousePosition);
// transform view mouse position into world coordinates.
var viewToWorldMatrix = Mat4.createFromRotAndTrans(Camera.orientation, Camera.position);
var worldMousePosition = Mat4.transformPoint(viewToWorldMatrix, viewMousePosition);
return {
position: worldMousePosition,
rotation: this.landscape ? Quat.multiply(Camera.orientation, ROT_LANDSCAPE) : Quat.multiply(Camera.orientation, ROT_Y_180)
};
};
// compute position, rotation & parentJointIndex of the tablet
WebTablet.prototype.calculateTabletAttachmentProperties = function (hand, useMouse, tabletProperties) {
if (HMD.active) {
// in HMD mode, the tablet should be relative to the sensor to world matrix.
tabletProperties.parentJointIndex = SENSOR_TO_ROOM_MATRIX;
// compute the appropriate position of the tablet, near the hand controller that was used to spawn it.
var spawnInfo = calcSpawnInfo(hand, this.landscape);
tabletProperties.position = spawnInfo.position;
tabletProperties.rotation = spawnInfo.rotation;
} else {
// in desktop mode, the tablet should be relative to the camera
tabletProperties.parentJointIndex = CAMERA_MATRIX;
var windowPos;
if (useMouse) {
// compute the appropriate postion of the tablet such that it fits in the center of the screen nicely.
windowPos = {x: Controller.getValue(Controller.Hardware.Keyboard.MouseX),
y: Controller.getValue(Controller.Hardware.Keyboard.MouseY)};
} else {
windowPos = {x: Window.innerWidth / 2,
y: Window.innerHeight / 2};
}
var attitude = this.calculateWorldAttitudeRelativeToCamera(windowPos);
tabletProperties.position = attitude.position;
tabletProperties.rotation = attitude.rotation;
}
};
WebTablet.prototype.onHmdChanged = function () {
if (!HMD.tabletID) {
return;
}
var tabletProperties = {};
// compute position, rotation & parentJointIndex of the tablet
this.calculateTabletAttachmentProperties(NO_HANDS, false, tabletProperties);
Overlays.editOverlay(HMD.tabletID, tabletProperties);
};
WebTablet.prototype.pickle = function () {
return JSON.stringify({ webOverlayID: this.webOverlayID, tabletEntityID: this.tabletEntityID });
};
WebTablet.prototype.register = function() {
Messages.subscribe("home");
Messages.messageReceived.connect(this.receive);
};
WebTablet.prototype.cleanUpOldTabletsOnJoint = function(jointIndex) {
var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, jointIndex);
children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.SELF_ID, jointIndex));
children.forEach(function(childID) {
var props = Entities.getEntityProperties(childID, ["name"]);
if (props.name === "WebTablet Tablet") {
Entities.deleteEntity(childID);
}
});
};
WebTablet.prototype.cleanUpOldTablets = function() {
this.cleanUpOldTabletsOnJoint(-1);
this.cleanUpOldTabletsOnJoint(SENSOR_TO_ROOM_MATRIX);
this.cleanUpOldTabletsOnJoint(CAMERA_MATRIX);
this.cleanUpOldTabletsOnJoint(65529);
this.cleanUpOldTabletsOnJoint(65534);
};
WebTablet.prototype.unregister = function() {
Messages.unsubscribe("home");
Messages.messageReceived.disconnect(this.receive);
};
WebTablet.unpickle = function (string) {
if (!string) {
return;
}
var tablet = JSON.parse(string);
tablet.__proto__ = WebTablet.prototype;
return tablet;
};
WebTablet.prototype.getPosition = function () {
return Overlays.getProperty(this.webOverlayID, "position");
};
WebTablet.prototype.mousePressEvent = function (event) {
if (!HMD.active) {
var pickRay = Camera.computePickRay(event.x, event.y);
var tabletBackPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]);
if (tabletBackPickResults.intersects) {
var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.webOverlayID, this.homeButtonID]);
if (!overlayPickResults.intersects) {
this.dragging = true;
var invCameraXform = new Xform(Camera.orientation, Camera.position).inv();
this.initialLocalIntersectionPoint = invCameraXform.xformPoint(tabletBackPickResults.intersection);
this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition");
}
}
}
};
WebTablet.prototype.cameraModeChanged = function (newMode) {
;
};
function rayIntersectPlane(planePosition, planeNormal, rayStart, rayDirection) {
var rayDirectionDotPlaneNormal = Vec3.dot(rayDirection, planeNormal);
if (rayDirectionDotPlaneNormal > 0.00001 || rayDirectionDotPlaneNormal < -0.00001) {
var rayStartDotPlaneNormal = Vec3.dot(Vec3.subtract(planePosition, rayStart), planeNormal);
var distance = rayStartDotPlaneNormal / rayDirectionDotPlaneNormal;
return {hit: true, distance: distance};
} else {
// ray is parallel to the plane
return {hit: false, distance: 0};
}
}
WebTablet.prototype.scheduleMouseMoveProcessor = function() {
var _this = this;
if (!this.moveEventTimer) {
this.moveEventTimer = Script.setTimeout(function() {
_this.mouseMoveProcessor();
}, DELAY_FOR_30HZ);
}
};
WebTablet.prototype.handleHomeButtonHover = function(x, y) {
var pickRay = Camera.computePickRay(x, y);
var homePickResult = Overlays.findRayIntersection(pickRay, true, [this.homeButtonID]);
Overlays.editOverlay(this.homeButtonHighlightID, { alpha: homePickResult.intersects ? 1.0 : 0.0 });
};
WebTablet.prototype.mouseMoveEvent = function (event) {
if (this.dragging) {
this.currentMouse = {
x: event.x,
y: event.y
};
this.scheduleMouseMoveProcessor();
} else {
this.handleHomeButtonHover(event.x, event.y);
}
};
WebTablet.prototype.mouseMoveProcessor = function () {
this.moveEventTimer = null;
if (this.dragging) {
var pickRay = Camera.computePickRay(this.currentMouse.x, this.currentMouse.y);
// transform pickRay into camera local coordinates
var invCameraXform = new Xform(Camera.orientation, Camera.position).inv();
var localPickRay = {
origin: invCameraXform.xformPoint(pickRay.origin),
direction: invCameraXform.xformVector(pickRay.direction)
};
var NORMAL = {x: 0, y: 0, z: -1};
var result = rayIntersectPlane(this.initialLocalIntersectionPoint, NORMAL, localPickRay.origin, localPickRay.direction);
if (result.hit) {
var localIntersectionPoint = Vec3.sum(localPickRay.origin, Vec3.multiply(localPickRay.direction, result.distance));
var localOffset = Vec3.subtract(localIntersectionPoint, this.initialLocalIntersectionPoint);
var localPosition = Vec3.sum(this.initialLocalPosition, localOffset);
Overlays.editOverlay(this.tabletEntityID, {
localPosition: localPosition
});
}
this.scheduleMouseMoveProcessor();
} else {
this.handleHomeButtonHover(this.currentMouse.x, this.currentMouse.y);
}
};
WebTablet.prototype.mouseReleaseEvent = function (event) {
this.dragging = false;
};

View file

@ -0,0 +1,48 @@
//
// Created by Anthony J. Thibault on 2016/06/21
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// ctor
Xform = function(rot, pos) {
this.rot = rot;
this.pos = pos;
}
Xform.ident = function() {
return new Xform({x: 0, y: 0, z: 0, w: 1}, {x: 0, y: 0, z: 0});
};
Xform.mul = function(lhs, rhs) {
var rot = Quat.multiply(lhs.rot, rhs.rot);
var pos = Vec3.sum(lhs.pos, Vec3.multiplyQbyV(lhs.rot, rhs.pos));
return new Xform(rot, pos);
};
Xform.prototype.inv = function() {
var invRot = Quat.inverse(this.rot);
var invPos = Vec3.multiply(-1, this.pos);
return new Xform(invRot, Vec3.multiplyQbyV(invRot, invPos));
};
Xform.prototype.mirrorX = function() {
return new Xform({x: this.rot.x, y: -this.rot.y, z: -this.rot.z, w: this.rot.w},
{x: -this.pos.x, y: this.pos.y, z: this.pos.z});
};
Xform.prototype.xformVector = function (vector) {
return Vec3.multiplyQbyV(this.rot, vector);
}
Xform.prototype.xformPoint = function (point) {
return Vec3.sum(Vec3.multiplyQbyV(this.rot, point), this.pos);
}
Xform.prototype.toString = function() {
var rot = this.rot;
var pos = this.pos;
return "Xform rot = (" + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "), pos = (" + pos.x + ", " + pos.y + ", " + pos.z + ")";
};

View file

@ -0,0 +1,10 @@
//
// accountUtils.js
// scripts/system/libraries/libraries
//
// Copyright 2017 High Fidelity, Inc.
//
openLoginWindow = function openLoginWindow() {
Menu.triggerOption("Login/Sign Up");
};

View file

@ -0,0 +1,57 @@
"use strict";
// cloneEntity.js
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global entityIsCloneable:true, cloneEntity:true, propsAreCloneDynamic:true, Script,
propsAreCloneDynamic:true, Entities, Uuid */
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
// Object assign polyfill
if (typeof Object.assign !== 'function') {
Object.assign = function(target, varArgs) {
if (target === null) {
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource !== null) {
for (var nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
};
}
entityIsCloneable = function(props) {
if (props) {
return props.cloneable;
}
return false;
};
propsAreCloneDynamic = function(props) {
var cloneable = entityIsCloneable(props);
if (cloneable) {
return props.cloneDynamic;
}
return false;
};
cloneEntity = function(props) {
var entityIDToClone = props.id;
if (entityIsCloneable(props) &&
(Uuid.isNull(props.certificateID) || props.certificateType.indexOf('domainUnlimited') >= 0)) {
var cloneID = Entities.cloneEntity(entityIDToClone);
return cloneID;
}
return null;
};

View file

@ -0,0 +1,94 @@
//
// connectionUtils.js
// scripts/system/libraries
//
// Copyright 2018 High Fidelity, Inc.
//
// Function Names:
// - requestJSON
// - getAvailableConnections
// - getInfoAboutUser
// - getConnectionData
//
// Description:
// - Update all the usernames that I am entitled to see, using my login but not dependent on canKick.
var request = Script.require('request').request;
var METAVERSE_BASE = Account.metaverseServerURL;
function requestJSON(url, callback) { // callback(data) if successfull. Logs otherwise.
request({
uri: url
}, function (error, response) {
if (error || (response.status !== 'success')) {
print("Error: unable to get URL", error || response.status);
return;
}
callback(response.data);
});
}
function getAvailableConnections(domain, callback) { // callback([{usename, location}...]) if successful. (Logs otherwise)
url = METAVERSE_BASE + '/api/v1/users?per_page=400&'
if (domain) {
url += 'status=' + domain.slice(1, -1); // without curly braces
} else {
url += 'filter=connections'; // regardless of whether online
}
requestJSON(url, function (connectionsData) {
callback(connectionsData.users);
});
}
function getInfoAboutUser(specificUsername, callback) {
url = METAVERSE_BASE + '/api/v1/users?filter=connections'
requestJSON(url, function (connectionsData) {
for (user in connectionsData.users) {
if (connectionsData.users[user].username === specificUsername) {
callback(connectionsData.users[user]);
return;
}
}
callback(false);
});
}
getConnectionData = function getConnectionData(specificUsername, domain) {
function frob(user) { // get into the right format
var formattedSessionId = user.location.node_id || '';
if (formattedSessionId !== '' && formattedSessionId.indexOf("{") != 0) {
formattedSessionId = "{" + formattedSessionId + "}";
}
return {
sessionId: formattedSessionId,
userName: user.username,
connection: user.connection,
profileUrl: user.images.thumbnail,
placeName: (user.location.root || user.location.domain || {}).name || ''
};
}
if (specificUsername) {
getInfoAboutUser(specificUsername, function (user) {
if (user) {
updateUser(frob(user));
} else {
print('Error: Unable to find information about ' + specificUsername + ' in connectionsData!');
}
});
} else {
getAvailableConnections(domain, function (users) {
if (domain) {
users.forEach(function (user) {
updateUser(frob(user));
});
} else {
sendToQml({ method: 'updateConnections', connections: users.map(frob) });
}
});
}
}
// Function Name: sendToQml()
//
// Description:
// -Use this function to send a message to the QML (i.e. to change appearances). The "message" argument is what is sent to
// the QML in the format "{method, params}", like json-rpc. See also fromQml().
function sendToQml(message) {
Tablet.getTablet("com.highfidelity.interface.tablet.system").sendToQml(message);
}

View file

@ -0,0 +1,629 @@
"use strict";
// controllerDispatcherUtils.js
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global module, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, Mat4,
Selection, Uuid,
MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, FORBIDDEN_GRAB_TYPES:true,
HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true,
DEFAULT_REGISTRATION_POINT:true, INCHES_TO_METERS:true,
TRIGGER_OFF_VALUE:true,
TRIGGER_ON_VALUE:true,
PICK_MAX_DISTANCE:true,
DEFAULT_SEARCH_SPHERE_DISTANCE:true,
NEAR_GRAB_PICK_RADIUS:true,
COLORS_GRAB_SEARCHING_HALF_SQUEEZE:true,
COLORS_GRAB_SEARCHING_FULL_SQUEEZE:true,
COLORS_GRAB_DISTANCE_HOLD:true,
NEAR_GRAB_RADIUS:true,
DISPATCHER_PROPERTIES:true,
HAPTIC_PULSE_STRENGTH:true,
HAPTIC_PULSE_DURATION:true,
DISPATCHER_HOVERING_LIST:true,
DISPATCHER_HOVERING_STYLE:true,
Entities,
makeDispatcherModuleParameters:true,
makeRunningValues:true,
enableDispatcherModule:true,
disableDispatcherModule:true,
getEnabledModuleByName:true,
getGrabbableData:true,
isAnothersAvatarEntity:true,
isAnothersChildEntity:true,
entityIsEquippable:true,
entityIsGrabbable:true,
entityIsDistanceGrabbable:true,
getControllerJointIndexCacheTime:true,
getControllerJointIndexCache:true,
getControllerJointIndex:true,
propsArePhysical:true,
controllerDispatcherPluginsNeedSort:true,
projectOntoXYPlane:true,
projectOntoEntityXYPlane:true,
projectOntoOverlayXYPlane:true,
makeLaserLockInfo:true,
entityHasActions:true,
ensureDynamic:true,
findGrabbableGroupParent:true,
BUMPER_ON_VALUE:true,
getEntityParents:true,
findHandChildEntities:true,
findFarGrabJointChildEntities:true,
makeLaserParams:true,
TEAR_AWAY_DISTANCE:true,
TEAR_AWAY_COUNT:true,
TEAR_AWAY_CHECK_TIME:true,
NEAR_GRAB_DISTANCE: true,
distanceBetweenPointAndEntityBoundingBox:true,
entityIsEquipped:true,
highlightTargetEntity:true,
clearHighlightedEntities:true,
unhighlightTargetEntity:true,
distanceBetweenEntityLocalPositionAndBoundingBox: true,
worldPositionToRegistrationFrameMatrix: true
*/
MSECS_PER_SEC = 1000.0;
INCHES_TO_METERS = 1.0 / 39.3701;
HAPTIC_PULSE_STRENGTH = 1.0;
HAPTIC_PULSE_DURATION = 13.0;
ZERO_VEC = { x: 0, y: 0, z: 0 };
ONE_VEC = { x: 1, y: 1, z: 1 };
LEFT_HAND = 0;
RIGHT_HAND = 1;
FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"];
HAPTIC_PULSE_STRENGTH = 1.0;
HAPTIC_PULSE_DURATION = 13.0;
DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 };
TRIGGER_OFF_VALUE = 0.1;
TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab
BUMPER_ON_VALUE = 0.5;
PICK_MAX_DISTANCE = 500; // max length of pick-ray
DEFAULT_SEARCH_SPHERE_DISTANCE = 1000; // how far from camera to search intersection?
NEAR_GRAB_PICK_RADIUS = 0.25; // radius used for search ray vs object for near grabbing.
COLORS_GRAB_SEARCHING_HALF_SQUEEZE = { red: 10, green: 10, blue: 255 };
COLORS_GRAB_SEARCHING_FULL_SQUEEZE = { red: 250, green: 10, blue: 10 };
COLORS_GRAB_DISTANCE_HOLD = { red: 238, green: 75, blue: 214 };
NEAR_GRAB_RADIUS = 1.0;
TEAR_AWAY_DISTANCE = 0.15; // ungrab an entity if its bounding-box moves this far from the hand
TEAR_AWAY_COUNT = 2; // multiply by TEAR_AWAY_CHECK_TIME to know how long the item must be away
TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks
TELEPORT_DEADZONE = 0.15;
NEAR_GRAB_DISTANCE = 0.14; // Grab an entity if its bounding box is within this distance.
// Smaller than TEAR_AWAY_DISTANCE for hysteresis.
DISPATCHER_HOVERING_LIST = "dispatcherHoveringList";
DISPATCHER_HOVERING_STYLE = {
isOutlineSmooth: true,
outlineWidth: 0,
outlineUnoccludedColor: {red: 255, green: 128, blue: 128},
outlineUnoccludedAlpha: 0.0,
outlineOccludedColor: {red: 255, green: 128, blue: 128},
outlineOccludedAlpha:0.0,
fillUnoccludedColor: {red: 255, green: 255, blue: 255},
fillUnoccludedAlpha: 0.12,
fillOccludedColor: {red: 255, green: 255, blue: 255},
fillOccludedAlpha: 0.0
};
DISPATCHER_PROPERTIES = [
"position",
"registrationPoint",
"rotation",
"gravity",
"collidesWith",
"dynamic",
"collisionless",
"locked",
"name",
"shapeType",
"parentID",
"parentJointIndex",
"density",
"dimensions",
"type",
"href",
"cloneable",
"cloneDynamic",
"localPosition",
"localRotation",
"grab.grabbable",
"grab.grabKinematic",
"grab.grabFollowsController",
"grab.triggerable",
"grab.equippable",
"grab.grabDelegateToParent",
"grab.equippableLeftPosition",
"grab.equippableLeftRotation",
"grab.equippableRightPosition",
"grab.equippableRightRotation",
"grab.equippableIndicatorURL",
"grab.equippableIndicatorScale",
"grab.equippableIndicatorOffset",
"userData",
"avatarEntity",
"owningAvatarID",
"certificateID",
"certificateType"
];
// priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step
// activitySlots -- indicates which "slots" must not yet be in use for this module to start
// requiredDataForReady -- which "situation" parts this module looks at to decide if it will start
// sleepMSBetweenRuns -- how long to wait between calls to this module's "run" method
makeDispatcherModuleParameters = function (priority, activitySlots, requiredDataForReady, sleepMSBetweenRuns, enableLaserForHand) {
if (enableLaserForHand === undefined) {
enableLaserForHand = -1;
}
return {
priority: priority,
activitySlots: activitySlots,
requiredDataForReady: requiredDataForReady,
sleepMSBetweenRuns: sleepMSBetweenRuns,
handLaser: enableLaserForHand
};
};
makeLaserLockInfo = function(targetID, isOverlay, hand, offset) {
return {
targetID: targetID,
isOverlay: isOverlay,
hand: hand,
offset: offset
};
};
makeLaserParams = function(hand, alwaysOn) {
if (alwaysOn === undefined) {
alwaysOn = false;
}
return {
hand: hand,
alwaysOn: alwaysOn
};
};
makeRunningValues = function (active, targets, requiredDataForRun, laserLockInfo) {
return {
active: active,
targets: targets,
requiredDataForRun: requiredDataForRun,
laserLockInfo: laserLockInfo
};
};
enableDispatcherModule = function (moduleName, module, priority) {
if (!controllerDispatcherPlugins) {
controllerDispatcherPlugins = {};
}
controllerDispatcherPlugins[moduleName] = module;
controllerDispatcherPluginsNeedSort = true;
};
disableDispatcherModule = function (moduleName) {
delete controllerDispatcherPlugins[moduleName];
controllerDispatcherPluginsNeedSort = true;
};
getEnabledModuleByName = function (moduleName) {
if (controllerDispatcherPlugins.hasOwnProperty(moduleName)) {
return controllerDispatcherPlugins[moduleName];
}
return null;
};
getGrabbableData = function (ggdProps) {
// look in userData for a "grabbable" key, return the value or some defaults
var grabbableData = {};
var userDataParsed = null;
try {
if (!ggdProps.userDataParsed) {
ggdProps.userDataParsed = JSON.parse(ggdProps.userData);
}
userDataParsed = ggdProps.userDataParsed;
} catch (err) {
userDataParsed = {};
}
if (userDataParsed.grabbableKey) {
grabbableData = userDataParsed.grabbableKey;
} else {
grabbableData = ggdProps.grab;
}
// extract grab-related properties, provide defaults if any are missing
if (!grabbableData.hasOwnProperty("grabbable")) {
grabbableData.grabbable = true;
}
// kinematic has been renamed to grabKinematic
if (!grabbableData.hasOwnProperty("grabKinematic") &&
!grabbableData.hasOwnProperty("kinematic")) {
grabbableData.grabKinematic = true;
}
if (!grabbableData.hasOwnProperty("grabKinematic")) {
grabbableData.grabKinematic = grabbableData.kinematic;
}
// ignoreIK has been renamed to grabFollowsController
if (!grabbableData.hasOwnProperty("grabFollowsController") &&
!grabbableData.hasOwnProperty("ignoreIK")) {
grabbableData.grabFollowsController = true;
}
if (!grabbableData.hasOwnProperty("grabFollowsController")) {
grabbableData.grabFollowsController = grabbableData.ignoreIK;
}
// wantsTrigger has been renamed to triggerable
if (!grabbableData.hasOwnProperty("triggerable") &&
!grabbableData.hasOwnProperty("wantsTrigger")) {
grabbableData.triggerable = false;
}
if (!grabbableData.hasOwnProperty("triggerable")) {
grabbableData.triggerable = grabbableData.wantsTrigger;
}
if (!grabbableData.hasOwnProperty("equippable")) {
grabbableData.equippable = false;
}
if (!grabbableData.hasOwnProperty("equippableLeftPosition")) {
grabbableData.equippableLeftPosition = { x: 0, y: 0, z: 0 };
}
if (!grabbableData.hasOwnProperty("equippableLeftRotation")) {
grabbableData.equippableLeftPosition = { x: 0, y: 0, z: 0, w: 1 };
}
if (!grabbableData.hasOwnProperty("equippableRightPosition")) {
grabbableData.equippableRightPosition = { x: 0, y: 0, z: 0 };
}
if (!grabbableData.hasOwnProperty("equippableRightRotation")) {
grabbableData.equippableRightPosition = { x: 0, y: 0, z: 0, w: 1 };
}
return grabbableData;
};
isAnothersAvatarEntity = function (iaaeProps) {
if (!iaaeProps.avatarEntity) {
return false;
}
if (iaaeProps.owningAvatarID === MyAvatar.sessionUUID) {
return false;
}
if (iaaeProps.owningAvatarID === MyAvatar.SELF_ID) {
return false;
}
return true;
};
isAnothersChildEntity = function (iaceProps) {
while (iaceProps.parentID && iaceProps.parentID !== Uuid.NULL) {
if (Entities.getNestableType(iaceProps.parentID) == "avatar") {
if (iaceProps.parentID == MyAvatar.SELF_ID || iaceProps.parentID == MyAvatar.sessionUUID) {
return false; // not another's, it's mine.
}
return true;
}
// Entities.getNestableType(iaceProps.parentID) == "entity") {
var parentProps = Entities.getEntityProperties(iaceProps.parentID, DISPATCHER_PROPERTIES);
if (!parentProps) {
break;
}
parentProps.id = iaceProps.parentID;
iaceProps = parentProps;
}
return false;
};
entityIsEquippable = function (eieProps) {
var grabbable = getGrabbableData(eieProps).grabbable;
if (!grabbable ||
isAnothersAvatarEntity(eieProps) ||
isAnothersChildEntity(eieProps) ||
FORBIDDEN_GRAB_TYPES.indexOf(eieProps.type) >= 0) {
return false;
}
return true;
};
entityIsGrabbable = function (eigProps) {
var grabbable = getGrabbableData(eigProps).grabbable;
if (!grabbable ||
eigProps.locked ||
FORBIDDEN_GRAB_TYPES.indexOf(eigProps.type) >= 0) {
return false;
}
return true;
};
clearHighlightedEntities = function() {
Selection.clearSelectedItemsList(DISPATCHER_HOVERING_LIST);
};
highlightTargetEntity = function(entityID) {
Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", entityID);
};
unhighlightTargetEntity = function(entityID) {
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", entityID);
};
entityIsDistanceGrabbable = function(eidgProps) {
if (!entityIsGrabbable(eidgProps)) {
return false;
}
// we can't distance-grab non-physical
var isPhysical = propsArePhysical(eidgProps);
if (!isPhysical) {
return false;
}
return true;
};
getControllerJointIndexCacheTime = [0, 0];
getControllerJointIndexCache = [-1, -1];
getControllerJointIndex = function (hand) {
var GET_CONTROLLERJOINTINDEX_CACHE_REFRESH_TIME = 3000; // msecs
var now = Date.now();
if (now - getControllerJointIndexCacheTime[hand] > GET_CONTROLLERJOINTINDEX_CACHE_REFRESH_TIME) {
if (HMD.isHandControllerAvailable()) {
var controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ?
"_CONTROLLER_RIGHTHAND" :
"_CONTROLLER_LEFTHAND");
getControllerJointIndexCacheTime[hand] = now;
getControllerJointIndexCache[hand] = controllerJointIndex;
return controllerJointIndex;
}
} else {
return getControllerJointIndexCache[hand];
}
return -1;
};
propsArePhysical = function (papProps) {
if (!papProps.dynamic) {
return false;
}
var isPhysical = (papProps.shapeType && papProps.shapeType !== 'none');
return isPhysical;
};
projectOntoXYPlane = function (worldPos, position, rotation, dimensions, registrationPoint) {
var invRot = Quat.inverse(rotation);
var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(worldPos, position));
var invDimensions = {
x: 1 / dimensions.x,
y: 1 / dimensions.y,
z: 1 / dimensions.z
};
var normalizedPos = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), registrationPoint);
return {
x: normalizedPos.x * dimensions.x,
y: (1 - normalizedPos.y) * dimensions.y // flip y-axis
};
};
projectOntoEntityXYPlane = function (entityID, worldPos, popProps) {
return projectOntoXYPlane(worldPos, popProps.position, popProps.rotation,
popProps.dimensions, popProps.registrationPoint);
};
projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldPos) {
var position = Overlays.getProperty(overlayID, "position");
var rotation = Overlays.getProperty(overlayID, "rotation");
var dimensions = Overlays.getProperty(overlayID, "dimensions");
dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension
return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT);
};
entityHasActions = function (entityID) {
return Entities.getActionIDs(entityID).length > 0;
};
ensureDynamic = function (entityID) {
// if we distance hold something and keep it very still before releasing it, it ends up
// non-dynamic in bullet. If it's too still, give it a little bounce so it will fall.
var edProps = Entities.getEntityProperties(entityID, ["velocity", "dynamic", "parentID"]);
if (edProps.dynamic && edProps.parentID === Uuid.NULL) {
var velocity = edProps.velocity;
if (Vec3.length(velocity) < 0.05) { // see EntityMotionState.cpp DYNAMIC_LINEAR_VELOCITY_THRESHOLD
velocity = { x: 0.0, y: 0.2, z: 0.0 };
Entities.editEntity(entityID, { velocity: velocity });
}
}
};
findGrabbableGroupParent = function (controllerData, targetProps) {
while (targetProps.grab.grabDelegateToParent &&
targetProps.parentID &&
targetProps.parentID !== Uuid.NULL &&
Entities.getNestableType(targetProps.parentID) == "entity") {
var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES);
if (!parentProps) {
break;
}
if (!entityIsGrabbable(parentProps)) {
break;
}
parentProps.id = targetProps.parentID;
targetProps = parentProps;
controllerData.nearbyEntityPropertiesByID[targetProps.id] = targetProps;
}
return targetProps;
};
getEntityParents = function(targetProps) {
var parentProperties = [];
while (targetProps.parentID &&
targetProps.parentID !== Uuid.NULL &&
Entities.getNestableType(targetProps.parentID) == "entity") {
var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES);
if (!parentProps) {
break;
}
parentProps.id = targetProps.parentID;
targetProps = parentProps;
parentProperties.push(parentProps);
}
return parentProperties;
};
findHandChildEntities = function(hand) {
// find children of avatar's hand joint
var handJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? "RightHand" : "LeftHand");
var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex);
children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.SELF_ID, handJointIndex));
// find children of faux controller joint
var controllerJointIndex = getControllerJointIndex(hand);
children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerJointIndex));
children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.SELF_ID, controllerJointIndex));
// find children of faux camera-relative controller joint
var controllerCRJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ?
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" :
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND");
children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, controllerCRJointIndex));
children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.SELF_ID, controllerCRJointIndex));
return children.filter(function (childID) {
var childType = Entities.getNestableType(childID);
return childType == "entity";
});
};
findFarGrabJointChildEntities = function(hand) {
// find children of avatar's far-grab joint
var farGrabJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? "_FARGRAB_RIGHTHAND" : "_FARGRAB_LEFTHAND");
var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, farGrabJointIndex);
children = children.concat(Entities.getChildrenIDsOfJoint(MyAvatar.SELF_ID, farGrabJointIndex));
return children.filter(function (childID) {
var childType = Entities.getNestableType(childID);
return childType == "entity";
});
};
distanceBetweenEntityLocalPositionAndBoundingBox = function(entityProps, jointGrabOffset) {
var DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 };
var rotInv = Quat.inverse(entityProps.localRotation);
var localPosition = Vec3.sum(entityProps.localPosition, jointGrabOffset);
var localPoint = Vec3.multiplyQbyV(rotInv, Vec3.multiply(localPosition, -1.0));
var halfDims = Vec3.multiply(entityProps.dimensions, 0.5);
var regRatio = Vec3.subtract(DEFAULT_REGISTRATION_POINT, entityProps.registrationPoint);
var entityCenter = Vec3.multiplyVbyV(regRatio, entityProps.dimensions);
var localMin = Vec3.subtract(entityCenter, halfDims);
var localMax = Vec3.sum(entityCenter, halfDims);
var v = {x: localPoint.x, y: localPoint.y, z: localPoint.z};
v.x = Math.max(v.x, localMin.x);
v.x = Math.min(v.x, localMax.x);
v.y = Math.max(v.y, localMin.y);
v.y = Math.min(v.y, localMax.y);
v.z = Math.max(v.z, localMin.z);
v.z = Math.min(v.z, localMax.z);
return Vec3.distance(v, localPoint);
};
distanceBetweenPointAndEntityBoundingBox = function(point, entityProps) {
var entityXform = new Xform(entityProps.rotation, entityProps.position);
var localPoint = entityXform.inv().xformPoint(point);
var minOffset = Vec3.multiplyVbyV(entityProps.registrationPoint, entityProps.dimensions);
var maxOffset = Vec3.multiplyVbyV(Vec3.subtract(ONE_VEC, entityProps.registrationPoint), entityProps.dimensions);
var localMin = Vec3.subtract(entityXform.trans, minOffset);
var localMax = Vec3.sum(entityXform.trans, maxOffset);
var v = {x: localPoint.x, y: localPoint.y, z: localPoint.z};
v.x = Math.max(v.x, localMin.x);
v.x = Math.min(v.x, localMax.x);
v.y = Math.max(v.y, localMin.y);
v.y = Math.min(v.y, localMax.y);
v.z = Math.max(v.z, localMin.z);
v.z = Math.min(v.z, localMax.z);
return Vec3.distance(v, localPoint);
};
entityIsEquipped = function(entityID) {
var rightEquipEntity = getEnabledModuleByName("RightEquipEntity");
var leftEquipEntity = getEnabledModuleByName("LeftEquipEntity");
var equippedInRightHand = rightEquipEntity ? rightEquipEntity.targetEntityID === entityID : false;
var equippedInLeftHand = leftEquipEntity ? leftEquipEntity.targetEntityID === entityID : false;
return equippedInRightHand || equippedInLeftHand;
};
worldPositionToRegistrationFrameMatrix = function(wptrProps, pos) {
// get world matrix for intersection point
var intersectionMat = new Xform({ x: 0, y: 0, z:0, w: 1 }, pos);
// calculate world matrix for registrationPoint addjusted entity
var DEFAULT_REGISTRATION_POINT = { x: 0.5, y: 0.5, z: 0.5 };
var regRatio = Vec3.subtract(DEFAULT_REGISTRATION_POINT, wptrProps.registrationPoint);
var regOffset = Vec3.multiplyVbyV(regRatio, wptrProps.dimensions);
var regOffsetRot = Vec3.multiplyQbyV(wptrProps.rotation, regOffset);
var modelMat = new Xform(wptrProps.rotation, Vec3.sum(wptrProps.position, regOffsetRot));
// get inverse of model matrix
var modelMatInv = modelMat.inv();
// transform world intersection point into object's registrationPoint frame
var xformMat = Xform.mul(modelMatInv, intersectionMat);
// convert to Mat4
var offsetMat = Mat4.createFromRotAndTrans(xformMat.rot, xformMat.pos);
return offsetMat;
};
if (typeof module !== 'undefined') {
module.exports = {
makeDispatcherModuleParameters: makeDispatcherModuleParameters,
enableDispatcherModule: enableDispatcherModule,
disableDispatcherModule: disableDispatcherModule,
highlightTargetEntity: highlightTargetEntity,
unhighlightTargetEntity: unhighlightTargetEntity,
clearHighlightedEntities: clearHighlightedEntities,
makeRunningValues: makeRunningValues,
findGrabbableGroupParent: findGrabbableGroupParent,
LEFT_HAND: LEFT_HAND,
RIGHT_HAND: RIGHT_HAND,
BUMPER_ON_VALUE: BUMPER_ON_VALUE,
TEAR_AWAY_DISTANCE: TEAR_AWAY_DISTANCE,
propsArePhysical: propsArePhysical,
entityIsEquippable: entityIsEquippable,
entityIsGrabbable: entityIsGrabbable,
NEAR_GRAB_RADIUS: NEAR_GRAB_RADIUS,
projectOntoOverlayXYPlane: projectOntoOverlayXYPlane,
projectOntoEntityXYPlane: projectOntoEntityXYPlane,
TRIGGER_OFF_VALUE: TRIGGER_OFF_VALUE,
TRIGGER_ON_VALUE: TRIGGER_ON_VALUE,
DISPATCHER_HOVERING_LIST: DISPATCHER_HOVERING_LIST,
worldPositionToRegistrationFrameMatrix: worldPositionToRegistrationFrameMatrix
};
}

View file

@ -0,0 +1,81 @@
// handControllerGrab.js
//
// Created by Seth Alves on 2016-9-7
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global MyAvatar, Vec3, HMD, Controller, Camera, Quat, Settings,
getGrabPointSphereOffset:true,
setGrabCommunications:true,
getGrabCommunications:true,
getControllerWorldLocation:true
*/
var GRAB_COMMUNICATIONS_SETTING = "io.highfidelity.isFarGrabbing";
setGrabCommunications = function setFarGrabCommunications(on) {
Settings.setValue(GRAB_COMMUNICATIONS_SETTING, on ? "on" : "");
};
getGrabCommunications = function getFarGrabCommunications() {
return !!Settings.getValue(GRAB_COMMUNICATIONS_SETTING, "");
};
// this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378
getGrabPointSphereOffset = function(handController, ignoreSensorToWorldScale) {
var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral
var offset = GRAB_POINT_SPHERE_OFFSET;
if (handController === Controller.Standard.LeftHand) {
offset = {
x: -GRAB_POINT_SPHERE_OFFSET.x,
y: GRAB_POINT_SPHERE_OFFSET.y,
z: GRAB_POINT_SPHERE_OFFSET.z
};
}
if (ignoreSensorToWorldScale) {
return offset;
} else {
return Vec3.multiply(MyAvatar.sensorToWorldScale, offset);
}
};
// controllerWorldLocation is where the controller would be, in-world, with an added offset
getControllerWorldLocation = function (handController, doOffset) {
var orientation;
var position;
var valid = false;
if (handController >= 0) {
var pose = Controller.getPoseValue(handController);
valid = pose.valid;
var controllerJointIndex;
if (pose.valid) {
if (handController === Controller.Standard.RightHand) {
controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND");
} else {
controllerJointIndex = MyAvatar.getJointIndex("_CAMERA_RELATIVE_CONTROLLER_LEFTHAND");
}
orientation = Quat.multiply(MyAvatar.orientation, MyAvatar.getAbsoluteJointRotationInObjectFrame(controllerJointIndex));
position = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, MyAvatar.getAbsoluteJointTranslationInObjectFrame(controllerJointIndex)));
// add to the real position so the grab-point is out in front of the hand, a bit
if (doOffset) {
var offset = getGrabPointSphereOffset(handController);
position = Vec3.sum(position, Vec3.multiplyQbyV(orientation, offset));
}
} else if (!HMD.isHandControllerAvailable()) {
// NOTE: keep this offset in sync with scripts/system/controllers/handControllerPointer.js:493
var VERTICAL_HEAD_LASER_OFFSET = 0.1 * MyAvatar.sensorToWorldScale;
position = Vec3.sum(Camera.position, Vec3.multiplyQbyV(Camera.orientation, {x: 0, y: VERTICAL_HEAD_LASER_OFFSET, z: 0}));
orientation = Quat.multiply(Camera.orientation, Quat.angleAxis(-90, { x: 1, y: 0, z: 0 }));
valid = true;
}
}
return {position: position,
translation: position,
orientation: orientation,
rotation: orientation,
valid: valid};
};

View file

@ -0,0 +1,62 @@
//
// dataViewHelpers.js
// examples/libraries
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
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);
};
}

View file

@ -0,0 +1,657 @@
//
// entityCameraTool.js
// examples
//
// Created by Ryan Huffman on 10/14/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Script.include("overlayUtils.js");
var MOUSE_SENSITIVITY = 0.9;
var SCROLL_SENSITIVITY = 0.05;
var PAN_ZOOM_SCALE_RATIO = 0.4;
var KEY_ORBIT_SENSITIVITY = 90;
var KEY_ZOOM_SENSITIVITY = 3;
// Scaling applied based on the size of the object being focused (Larger values focus further away)
var FOCUS_ZOOM_SCALE = 2.3;
// Minimum zoom level when focusing on an object
var FOCUS_MIN_ZOOM = 0.5;
// Scaling applied based on the current zoom level
var ZOOM_SCALING = 0.02;
var MIN_ZOOM_DISTANCE = 0.01;
// The maximum usable zoom level is somewhere around 14km, further than that the edit handles will fade-out. (FIXME: MS17493)
var MAX_ZOOM_DISTANCE = 14000;
var MODE_INACTIVE = 'inactive';
var MODE_ORBIT = 'orbit';
var MODE_PAN = 'pan';
var EASING_MULTIPLIER = 8;
var INITIAL_ZOOM_DISTANCE = 2;
var INITIAL_ZOOM_DISTANCE_FIRST_PERSON = 3;
var easeOutCubic = function(t) {
t--;
return t * t * t + 1;
};
EASE_TIME = 0.5;
function clamp(value, minimum, maximum) {
return Math.min(Math.max(value, minimum), maximum);
}
function mergeObjects(obj1, obj2) {
var newObj = {};
for (key in obj1) {
newObj[key] = obj1[key];
}
for (key in obj2) {
newObj[key] = obj2[key];
}
return newObj;
}
CameraManager = function() {
var that = {};
that.enabled = false;
that.mode = MODE_INACTIVE;
var actions = {
orbitLeft: 0,
orbitRight: 0,
orbitUp: 0,
orbitDown: 0,
orbitForward: 0,
orbitBackward: 0,
}
var keyToActionMapping = {
65: "orbitLeft", // "a"
68: "orbitRight", // "d"
87: "orbitForward", // "w"
83: "orbitBackward", // "s"
69: "orbitUp", // "e"
67: "orbitDown", // "c"
16777234: "orbitLeft", // "LEFT"
16777236: "orbitRight", // "RIGHT"
16777235: "orbitForward", // "UP"
16777237: "orbitBackward", // "DOWN"
}
var CAPTURED_KEYS = [];
for (var key in keyToActionMapping) {
CAPTURED_KEYS.push(key);
}
function getActionForKeyEvent(event) {
if (!event.isControl) {
var action = keyToActionMapping[event.key];
if (action !== undefined) {
if (event.isShifted) {
if (action === "orbitForward") {
action = "orbitUp";
} else if (action === "orbitBackward") {
action = "orbitDown";
}
}
return action;
}
}
return null;
}
that.zoomDistance = INITIAL_ZOOM_DISTANCE;
that.targetZoomDistance = INITIAL_ZOOM_DISTANCE;
that.yaw = 0;
that.pitch = 0;
that.targetYaw = 0;
that.targetPitch = 0;
that.focalPoint = Vec3.ZERO;
that.targetFocalPoint = Vec3.ZERO;
easing = false;
easingTime = 0;
startOrientation = Quat.IDENTITY;
that.previousCameraMode = null;
that.lastMousePosition = {
x: 0,
y: 0
};
that.enable = function() {
if (Camera.mode === "independent" || that.enabled || HMD.active) {
return;
}
for (var i = 0; i < CAPTURED_KEYS.length; i++) {
Controller.captureKeyEvents({
text: CAPTURED_KEYS[i]
});
}
for (var action in actions) {
actions[action] = 0;
}
that.enabled = true;
that.mode = MODE_INACTIVE;
// Pick a point INITIAL_ZOOM_DISTANCE in front of the camera to use as a focal point
that.zoomDistance = INITIAL_ZOOM_DISTANCE;
that.targetZoomDistance = that.zoomDistance + 3.0;
var focalPoint = Vec3.sum(Camera.getPosition(),
Vec3.multiply(that.zoomDistance, Quat.getForward(Camera.getOrientation())));
// Determine the correct yaw and pitch to keep the camera in the same location
var dPos = Vec3.subtract(focalPoint, Camera.getPosition());
var xzDist = Math.sqrt(dPos.x * dPos.x + dPos.z * dPos.z);
that.targetPitch = -Math.atan2(dPos.y, xzDist) * 180 / Math.PI;
that.targetPitch += (90 - that.targetPitch) / 3.0; // Swing camera "up" to look down at the focal point
that.targetYaw = Math.atan2(dPos.x, dPos.z) * 180 / Math.PI;
that.pitch = that.targetPitch;
that.yaw = that.targetYaw;
that.focalPoint = focalPoint;
that.setFocalPoint(focalPoint);
that.previousCameraMode = Camera.mode;
Camera.mode = "independent";
that.updateCamera();
cameraTool.setVisible(true);
}
that.disable = function(ignoreCamera) {
if (!that.enabled) {
return;
}
for (var i = 0; i < CAPTURED_KEYS.length; i++) {
Controller.releaseKeyEvents({
text: CAPTURED_KEYS[i]
});
}
that.enabled = false;
that.mode = MODE_INACTIVE;
if (!ignoreCamera) {
Camera.mode = that.previousCameraMode;
}
cameraTool.setVisible(false);
}
that.focus = function(position, dimensions, easeOrientation) {
if (dimensions) {
var size = Math.max(dimensions.x, Math.max(dimensions.y, dimensions.z));
that.targetZoomDistance = Math.max(size * FOCUS_ZOOM_SCALE, FOCUS_MIN_ZOOM);
} else {
that.targetZoomDistance = Vec3.length(Vec3.subtract(Camera.getPosition(), position));
}
if (easeOrientation) {
// Do eased turning towards target
that.focalPoint = that.targetFocalPoint = position;
that.zoomDistance = that.targetZoomDistance = Vec3.length(Vec3.subtract(Camera.getPosition(), position));
var dPos = Vec3.subtract(that.focalPoint, Camera.getPosition());
var xzDist = Math.sqrt(dPos.x * dPos.x + dPos.z * dPos.z);
that.targetPitch = -Math.atan2(dPos.y, xzDist) * 180 / Math.PI;
that.targetYaw = Math.atan2(dPos.x, dPos.z) * 180 / Math.PI;
that.pitch = that.targetPitch;
that.yaw = that.targetYaw;
startOrientation = Camera.getOrientation();
easing = true;
easingTime = 0;
} else {
that.setFocalPoint(position);
}
that.updateCamera();
}
that.setTargetPitchYaw = function(pitch, yaw) {
that.targetPitch = pitch;
that.targetYaw = yaw;
}
that.moveFocalPoint = function(dPos) {
that.setFocalPoint(Vec3.sum(that.focalPoint, dPos));
}
that.setFocalPoint = function(pos) {
that.targetFocalPoint = pos;
that.updateCamera();
}
that.addYaw = function(yaw) {
that.targetYaw += yaw;
that.updateCamera();
}
that.addPitch = function(pitch) {
that.targetPitch += pitch;
that.updateCamera();
}
that.addZoom = function(zoom) {
zoom *= that.targetZoomDistance * ZOOM_SCALING;
that.targetZoomDistance = Math.min(Math.max(that.targetZoomDistance + zoom, MIN_ZOOM_DISTANCE), MAX_ZOOM_DISTANCE);
that.updateCamera();
}
that.pan = function(offset) {
var up = Quat.getUp(Camera.getOrientation());
var right = Quat.getRight(Camera.getOrientation());
up = Vec3.multiply(up, offset.y * 0.01 * PAN_ZOOM_SCALE_RATIO * that.zoomDistance);
right = Vec3.multiply(right, offset.x * 0.01 * PAN_ZOOM_SCALE_RATIO * that.zoomDistance);
var dPosition = Vec3.sum(up, right);
that.moveFocalPoint(dPosition);
}
that.mouseMoveEvent = function(event) {
if (that.enabled && that.mode !== MODE_INACTIVE) {
var x = Reticle.getPosition().x;
var y = Reticle.getPosition().y;
if (!hasDragged) {
that.lastMousePosition.x = x;
that.lastMousePosition.y = y;
hasDragged = true;
}
if (that.mode === MODE_ORBIT) {
var diffX = x - that.lastMousePosition.x;
var diffY = y - that.lastMousePosition.y;
that.targetYaw -= MOUSE_SENSITIVITY * (diffX / 5.0);
that.targetPitch += MOUSE_SENSITIVITY * (diffY / 10.0);
while (that.targetYaw > 180.0) that.targetYaw -= 360;
while (that.targetYaw < -180.0) that.targetYaw += 360;
if (that.targetPitch > 90) that.targetPitch = 90;
if (that.targetPitch < -90) that.targetPitch = -90;
that.updateCamera();
} else if (that.mode === MODE_PAN) {
var diffX = x - that.lastMousePosition.x;
var diffY = y - that.lastMousePosition.y;
var up = Quat.getUp(Camera.getOrientation());
var right = Quat.getRight(Camera.getOrientation());
up = Vec3.multiply(up, diffY * 0.01 * PAN_ZOOM_SCALE_RATIO * that.zoomDistance);
right = Vec3.multiply(right, -diffX * 0.01 * PAN_ZOOM_SCALE_RATIO * that.zoomDistance);
var dPosition = Vec3.sum(up, right);
that.moveFocalPoint(dPosition);
}
var newX = x;
var newY = y;
var updatePosition = false;
if (x <= 0) {
newX = Window.innerWidth;
updatePosition = true;
} else if (x >= Window.innerWidth) {
newX = 0;
updatePosition = true;
}
if (y <= 0) {
newY = Window.innerHeight;
updatePosition = true;
} else if (y >= Window.innerHeight) {
newY = 0;
updatePosition = true;
}
if (updatePosition) {
Reticle.setPosition({ x: newX, y: newY});
}
that.lastMousePosition.x = newX;
that.lastMousePosition.y = newY;
return true;
}
return false;
}
var hasDragged = false;
that.mousePressEvent = function(event) {
if (cameraTool.mousePressEvent(event)) {
return true;
}
if (!that.enabled) {
return;
}
if (event.isRightButton || (event.isLeftButton && event.isControl && !event.isShifted)) {
that.mode = MODE_ORBIT;
} else if (event.isMiddleButton || (event.isLeftButton && event.isControl && event.isShifted)) {
that.mode = MODE_PAN;
}
if (that.mode !== MODE_INACTIVE) {
hasDragged = false;
return true;
}
return false;
}
that.mouseReleaseEvent = function(event) {
if (!that.enabled) {
return;
}
that.mode = MODE_INACTIVE;
Reticle.setVisible(true);
}
that.keyPressEvent = function(event) {
var action = getActionForKeyEvent(event);
if (action) {
actions[action] = 1;
}
};
that.keyReleaseEvent = function(event) {
var action = getActionForKeyEvent(event);
if (action) {
actions[action] = 0;
}
};
that.wheelEvent = function(event) {
if (!that.enabled) {
return;
}
var dZoom = -event.delta * SCROLL_SENSITIVITY;
// Scale based on current zoom level
dZoom *= that.targetZoomDistance * ZOOM_SCALING;
that.targetZoomDistance = Math.min(Math.max(that.targetZoomDistance + dZoom, MIN_ZOOM_DISTANCE), MAX_ZOOM_DISTANCE);
that.updateCamera();
}
that.updateCamera = function() {
if (!that.enabled || Camera.mode !== "independent") {
cameraTool.update();
return;
}
var yRot = Quat.angleAxis(that.yaw, {
x: 0,
y: 1,
z: 0
});
var xRot = Quat.angleAxis(that.pitch, {
x: 1,
y: 0,
z: 0
});
var q = Quat.multiply(yRot, xRot);
var pos = Vec3.multiply(Quat.getForward(q), that.zoomDistance);
Camera.setPosition(Vec3.sum(that.focalPoint, pos));
yRot = Quat.angleAxis(that.yaw - 180, {
x: 0,
y: 1,
z: 0
});
xRot = Quat.angleAxis(-that.pitch, {
x: 1,
y: 0,
z: 0
});
q = Quat.multiply(yRot, xRot);
if (easing) {
var t = easeOutCubic(easingTime / EASE_TIME);
q = Quat.slerp(startOrientation, q, t);
}
Camera.setOrientation(q);
cameraTool.update();
}
function normalizeDegrees(degrees) {
while (degrees > 180) {
degrees -= 360;
}
while (degrees < -180) {
degrees += 360;
}
return degrees;
}
// Ease the position and orbit of the camera
that.update = function(dt) {
if (Camera.mode !== "independent") {
that.updateCamera();
return;
}
// Update based on current actions
that.targetYaw += (actions.orbitRight - actions.orbitLeft) * dt * KEY_ORBIT_SENSITIVITY;
that.targetPitch += (actions.orbitUp - actions.orbitDown) * dt * KEY_ORBIT_SENSITIVITY;
that.targetPitch = clamp(that.targetPitch, -90, 90);
var dZoom = actions.orbitBackward - actions.orbitForward;
if (dZoom) {
dZoom *= that.targetZoomDistance * dt * KEY_ZOOM_SENSITIVITY;
that.targetZoomDistance += dZoom;
that.targetZoomDistance = clamp(that.targetZoomDistance, MIN_ZOOM_DISTANCE, MAX_ZOOM_DISTANCE);
}
if (easing) {
easingTime = Math.min(EASE_TIME, easingTime + dt);
}
var scale = Math.min(dt * EASING_MULTIPLIER, 1.0);
var dYaw = normalizeDegrees(that.targetYaw - that.yaw);
var dPitch = normalizeDegrees(that.targetPitch - that.pitch);
that.yaw += scale * dYaw;
that.pitch += scale * dPitch;
// Normalize between [-180, 180]
that.yaw = normalizeDegrees(that.yaw);
that.pitch = normalizeDegrees(that.pitch);
var dFocal = Vec3.subtract(that.targetFocalPoint, that.focalPoint);
that.focalPoint = Vec3.sum(that.focalPoint, Vec3.multiply(scale, dFocal));
var dZoom = that.targetZoomDistance - that.zoomDistance;
that.zoomDistance += scale * dZoom;
that.updateCamera();
if (easingTime >= 1) {
easing = false;
}
}
// Last mode that was first or third person
var lastAvatarCameraMode = "first person";
Camera.modeUpdated.connect(function(newMode) {
if (newMode != "independent") {
lastAvatarCameraMode = newMode;
that.disable(true);
} else {
that.enable();
}
});
Controller.keyReleaseEvent.connect(function(event) {
if (event.text == "ESC" && that.enabled) {
Camera.mode = lastAvatarCameraMode;
cameraManager.disable(true);
}
});
Script.update.connect(that.update);
Script.scriptEnding.connect(that.disable);
Controller.wheelEvent.connect(that.wheelEvent);
var cameraTool = new CameraTool(that);
return that;
}
CameraTool = function(cameraManager) {
var that = {};
var RED = {
red: 191,
green: 78,
blue: 38
};
var GREEN = {
red: 26,
green: 193,
blue: 105
};
var BLUE = {
red: 0,
green: 131,
blue: 204
};
var BORDER_WIDTH = 1;
var ORIENTATION_OVERLAY_SIZE = 26;
var ORIENTATION_OVERLAY_HALF_SIZE = ORIENTATION_OVERLAY_SIZE / 2;
var ORIENTATION_OVERLAY_CUBE_SIZE = 10.5;
var ORIENTATION_OVERLAY_OFFSET = {
x: 30,
y: 30,
}
var UI_WIDTH = 70;
var UI_HEIGHT = 70;
var UI_PADDING = 10;
var lastKnownWidth = Window.innerWidth;
var uiPosition = {
x: lastKnownWidth - UI_WIDTH - UI_PADDING,
y: UI_PADDING,
};
var backgroundBorder = Overlays.addOverlay("text", {
x: uiPosition.x - BORDER_WIDTH,
y: uiPosition.y - BORDER_WIDTH,
width: UI_WIDTH + BORDER_WIDTH * 2,
height: UI_HEIGHT + BORDER_WIDTH * 2,
alpha: 0,
text: "",
backgroundColor: {
red: 101,
green: 101,
blue: 101
},
backgroundAlpha: 1.0,
visible: false,
});
var background = Overlays.addOverlay("text", {
x: uiPosition.x,
y: uiPosition.y,
width: UI_WIDTH,
height: UI_HEIGHT,
alpha: 0,
text: "",
backgroundColor: {
red: 51,
green: 51,
blue: 51
},
backgroundAlpha: 1.0,
visible: false,
});
Script.scriptEnding.connect(function() {
Overlays.deleteOverlay(background);
Overlays.deleteOverlay(backgroundBorder);
});
var flip = Quat.fromPitchYawRollDegrees(0, 180, 0);
that.update = function() {
if (Window.innerWidth != lastKnownWidth) {
lastKnownWidth = Window.innerWidth;
uiPosition = {
x: lastKnownWidth - UI_WIDTH - UI_PADDING,
y: UI_PADDING,
};
Overlays.editOverlay(backgroundBorder, {
x: uiPosition.x - BORDER_WIDTH,
y: uiPosition.y - BORDER_WIDTH,
});
Overlays.editOverlay(background, {
x: uiPosition.x,
y: uiPosition.y,
});
}
}
that.mousePressEvent = function(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({
x: event.x,
y: event.y
});
};
that.setVisible = function(visible) {
Overlays.editOverlay(background, {
visible: visible
});
Overlays.editOverlay(backgroundBorder, {
visible: visible
});
};
that.setVisible(false);
return that;
};

View file

@ -0,0 +1,155 @@
/* globals EntityIconOverlayManager:true */
EntityIconOverlayManager = function(entityTypes, getOverlayPropertiesFunc) {
var visible = false;
// List of all created overlays
var allOverlays = [];
// List of overlays not currently being used
var unusedOverlays = [];
// Map from EntityItemID to overlay id
var entityOverlays = {};
// Map from EntityItemID to EntityItemID object
var entityIDs = {};
this.updatePositions = function(ids) {
for (var id in entityIDs) {
var entityID = entityIDs[id];
var properties = Entities.getEntityProperties(entityID);
var overlayProperties = {
position: properties.position
};
if (getOverlayPropertiesFunc) {
var customProperties = getOverlayPropertiesFunc(entityID, properties);
for (var key in customProperties) {
overlayProperties[key] = customProperties[key];
}
}
Overlays.editOverlay(entityOverlays[entityID], overlayProperties);
}
};
// Finds the id for the corresponding entity that is associated with an overlay id.
// Returns null if the overlay id is not contained in this manager.
this.findEntity = function(overlayId) {
for (var id in entityOverlays) {
if (overlayId === entityOverlays[id]) {
return entityIDs[id];
}
}
return null;
};
this.findRayIntersection = function(pickRay) {
var result = Overlays.findRayIntersection(pickRay);
if (result.intersects) {
result.entityID = this.findEntity(result.overlayID);
if (result.entityID === null) {
result.intersects = false;
}
}
return result;
};
this.setVisible = function(isVisible) {
if (visible !== isVisible) {
visible = isVisible;
for (var id in entityOverlays) {
Overlays.editOverlay(entityOverlays[id], {
visible: visible,
ignorePickIntersection: !visible
});
}
}
};
// Allocate or get an unused overlay
function getOverlay() {
var overlay;
if (unusedOverlays.length === 0) {
overlay = Overlays.addOverlay("image3d", {});
allOverlays.push(overlay);
} else {
overlay = unusedOverlays.pop();
}
return overlay;
}
function releaseOverlay(overlay) {
unusedOverlays.push(overlay);
Overlays.editOverlay(overlay, {
visible: false,
ignorePickIntersection: true
});
}
function addEntity(entityID) {
var properties = Entities.getEntityProperties(entityID, ['position', 'type']);
if (entityTypes.indexOf(properties.type) > -1 && !(entityID in entityOverlays)) {
var overlay = getOverlay();
entityOverlays[entityID] = overlay;
entityIDs[entityID] = entityID;
var overlayProperties = {
position: properties.position,
rotation: Quat.fromPitchYawRollDegrees(0, 0, 270),
visible: visible,
ignorePickIntersection: !visible,
alpha: 0.9,
scale: 0.5,
drawInFront: true,
isFacingAvatar: true,
color: {
red: 255,
green: 255,
blue: 255
}
};
if (getOverlayPropertiesFunc) {
var customProperties = getOverlayPropertiesFunc(entityID, properties);
for (var key in customProperties) {
overlayProperties[key] = customProperties[key];
}
}
Overlays.editOverlay(overlay, overlayProperties);
}
}
function deleteEntity(entityID) {
if (entityID in entityOverlays) {
releaseOverlay(entityOverlays[entityID]);
delete entityOverlays[entityID];
}
}
function clearEntities() {
for (var id in entityOverlays) {
releaseOverlay(entityOverlays[id]);
}
entityOverlays = {};
entityIDs = {};
}
Entities.addingEntity.connect(addEntity);
Entities.deletingEntity.connect(deleteEntity);
Entities.clearingEntities.connect(clearEntities);
// Add existing entities
var ids = Entities.findEntities(MyAvatar.position, 64000);
for (var i = 0; i < ids.length; i++) {
addEntity(ids[i]);
}
Script.scriptEnding.connect(function() {
for (var i = 0; i < allOverlays.length; i++) {
Overlays.deleteOverlay(allOverlays[i]);
}
});
};

View file

@ -0,0 +1,330 @@
"use strict";
// entityList.js
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages,
cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible,
keyUpEventFromUIWindow, Script, SelectionDisplay, SelectionManager, Clipboard */
var PROFILING_ENABLED = false;
var profileIndent = '';
const PROFILE_NOOP = function(_name, fn, args) {
fn.apply(this, args);
};
const PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) {
console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin");
var previousIndent = profileIndent;
profileIndent += ' ';
var before = Date.now();
fn.apply(this, args);
var delta = Date.now() - before;
profileIndent = previousIndent;
console.log("PROFILE-Script " + profileIndent + "(" + name + ") End " + delta + "ms");
};
EntityListTool = function(shouldUseEditTabletApp) {
var that = {};
var CreateWindow = Script.require('../modules/createWindow.js');
var TITLE_OFFSET = 60;
var ENTITY_LIST_WIDTH = 495;
var MAX_DEFAULT_CREATE_TOOLS_HEIGHT = 778;
var entityListWindow = new CreateWindow(
Script.resolvePath("EditEntityList.qml"),
'Entity List',
'com.highfidelity.create.entityListWindow',
function () {
var windowHeight = Window.innerHeight - TITLE_OFFSET;
if (windowHeight > MAX_DEFAULT_CREATE_TOOLS_HEIGHT) {
windowHeight = MAX_DEFAULT_CREATE_TOOLS_HEIGHT;
}
return {
size: {
x: ENTITY_LIST_WIDTH,
y: windowHeight
},
position: {
x: Window.x,
y: Window.y + TITLE_OFFSET
}
};
},
false
);
var webView = null;
webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
webView.setVisible = function(value){ };
var filterInView = false;
var searchRadius = 100;
var visible = false;
that.webView = webView;
that.setVisible = function(newVisible) {
visible = newVisible;
webView.setVisible(shouldUseEditTabletApp() && visible);
entityListWindow.setVisible(!shouldUseEditTabletApp() && visible);
};
that.isVisible = function() {
return entityListWindow.isVisible();
};
that.setVisible(false);
function emitJSONScriptEvent(data) {
var dataString;
PROFILE("Script-JSON.stringify", function() {
dataString = JSON.stringify(data);
});
PROFILE("Script-emitScriptEvent", function() {
webView.emitScriptEvent(dataString);
if (entityListWindow.window) {
entityListWindow.window.emitScriptEvent(dataString);
}
});
}
that.toggleVisible = function() {
that.setVisible(!visible);
};
selectionManager.addEventListener(function(isSelectionUpdate, caller) {
if (caller === that) {
// ignore events that we emitted from the entity list itself
return;
}
var selectedIDs = [];
for (var i = 0; i < selectionManager.selections.length; i++) {
selectedIDs.push(selectionManager.selections[i]);
}
emitJSONScriptEvent({
type: 'selectionUpdate',
selectedIDs: selectedIDs
});
});
that.setSpaceMode = function(spaceMode) {
emitJSONScriptEvent({
type: 'setSpaceMode',
spaceMode: spaceMode
});
};
that.clearEntityList = function() {
emitJSONScriptEvent({
type: 'clearEntityList'
});
};
that.removeEntities = function (deletedIDs, selectedIDs) {
emitJSONScriptEvent({
type: 'removeEntities',
deletedIDs: deletedIDs,
selectedIDs: selectedIDs
});
};
that.deleteEntities = function (deletedIDs) {
emitJSONScriptEvent({
type: "deleted",
ids: deletedIDs
});
};
function valueIfDefined(value) {
return value !== undefined ? value : "";
}
function entityIsBaked(properties) {
if (properties.type === "Model") {
var lowerModelURL = properties.modelURL.toLowerCase();
return lowerModelURL.endsWith(".baked.fbx") || lowerModelURL.endsWith(".baked.fst");
} else if (properties.type === "Zone") {
var lowerSkyboxURL = properties.skybox ? properties.skybox.url.toLowerCase() : "";
var lowerAmbientURL = properties.ambientLight ? properties.ambientLight.ambientURL.toLowerCase() : "";
return (lowerSkyboxURL === "" || lowerSkyboxURL.endsWith(".texmeta.json")) &&
(lowerAmbientURL === "" || lowerAmbientURL.endsWith(".texmeta.json"));
} else {
return false;
}
}
that.sendUpdate = function() {
PROFILE('Script-sendUpdate', function() {
var entities = [];
var ids;
PROFILE("findEntities", function() {
if (filterInView) {
ids = Entities.findEntitiesInFrustum(Camera.frustum);
} else {
ids = Entities.findEntities(MyAvatar.position, searchRadius);
}
});
var cameraPosition = Camera.position;
PROFILE("getMultipleProperties", function () {
var multipleProperties = Entities.getMultipleEntityProperties(ids, ['name', 'type', 'locked',
'visible', 'renderInfo', 'modelURL', 'materialURL', 'imageURL', 'script', 'certificateID',
'skybox.url', 'ambientLight.url']);
for (var i = 0; i < multipleProperties.length; i++) {
var properties = multipleProperties[i];
if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) {
var url = "";
if (properties.type === "Model") {
url = properties.modelURL;
} else if (properties.type === "Material") {
url = properties.materialURL;
} else if (properties.type === "Image") {
url = properties.imageURL;
}
entities.push({
id: ids[i],
name: properties.name,
type: properties.type,
url: url,
locked: properties.locked,
visible: properties.visible,
certificateID: properties.certificateID,
verticesCount: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.verticesCount) : ""),
texturesCount: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.texturesCount) : ""),
texturesSize: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.texturesSize) : ""),
hasTransparent: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.hasTransparent) : ""),
isBaked: entityIsBaked(properties),
drawCalls: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.drawCalls) : ""),
hasScript: properties.script !== ""
});
}
}
});
var selectedIDs = [];
for (var j = 0; j < selectionManager.selections.length; j++) {
selectedIDs.push(selectionManager.selections[j]);
}
emitJSONScriptEvent({
type: "update",
entities: entities,
selectedIDs: selectedIDs,
spaceMode: SelectionDisplay.getSpaceMode(),
});
});
};
function onFileSaveChanged(filename) {
Window.saveFileChanged.disconnect(onFileSaveChanged);
if (filename !== "") {
var success = Clipboard.exportEntities(filename, selectionManager.selections);
if (!success) {
Window.notifyEditError("Export failed.");
}
}
}
var onWebEventReceived = function(data) {
try {
data = JSON.parse(data);
} catch(e) {
print("entityList.js: Error parsing JSON");
return;
}
if (data.type === "selectionUpdate") {
var ids = data.entityIds;
var entityIDs = [];
for (var i = 0; i < ids.length; i++) {
entityIDs.push(ids[i]);
}
selectionManager.setSelections(entityIDs, that);
if (data.focus) {
cameraManager.enable();
cameraManager.focus(selectionManager.worldPosition,
selectionManager.worldDimensions,
Menu.isOptionChecked(MENU_EASE_ON_FOCUS));
}
} else if (data.type === "refresh") {
that.sendUpdate();
} else if (data.type === "teleport") {
if (selectionManager.hasSelection()) {
MyAvatar.position = selectionManager.worldPosition;
}
} else if (data.type === "export") {
if (!selectionManager.hasSelection()) {
Window.notifyEditError("No entities have been selected.");
} else {
Window.saveFileChanged.connect(onFileSaveChanged);
Window.saveAsync("Select Where to Save", "", "*.json");
}
} else if (data.type === "pal") {
var sessionIds = {}; // Collect the sessionsIds of all selected entities, w/o duplicates.
selectionManager.selections.forEach(function (id) {
var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy;
if (lastEditedBy) {
sessionIds[lastEditedBy] = true;
}
});
var dedupped = Object.keys(sessionIds);
if (!selectionManager.selections.length) {
Window.alert('No objects selected.');
} else if (!dedupped.length) {
Window.alert('There were no recent users of the ' + selectionManager.selections.length + ' selected objects.');
} else {
// No need to subscribe if we're just sending.
Messages.sendMessage('com.highfidelity.pal', JSON.stringify({method: 'select', params: [dedupped, true, false]}), 'local');
}
} else if (data.type === "delete") {
deleteSelectedEntities();
} else if (data.type === "toggleLocked") {
toggleSelectedEntitiesLocked();
} else if (data.type === "toggleVisible") {
toggleSelectedEntitiesVisible();
} else if (data.type === "filterInView") {
filterInView = data.filterInView === true;
} else if (data.type === "radius") {
searchRadius = data.radius;
} else if (data.type === "cut") {
SelectionManager.cutSelectedEntities();
} else if (data.type === "copy") {
SelectionManager.copySelectedEntities();
} else if (data.type === "paste") {
SelectionManager.pasteEntities();
} else if (data.type === "duplicate") {
SelectionManager.duplicateSelection();
that.sendUpdate();
} else if (data.type === "rename") {
Entities.editEntity(data.entityID, {name: data.name});
// make sure that the name also gets updated in the properties window
SelectionManager._update();
} else if (data.type === "toggleSpaceMode") {
SelectionDisplay.toggleSpaceMode();
} else if (data.type === 'keyUpEvent') {
keyUpEventFromUIWindow(data.keyUpEvent);
}
};
webView.webEventReceived.connect(onWebEventReceived);
entityListWindow.webEventReceived.addListener(onWebEventReceived);
that.interactiveWindowHidden = entityListWindow.interactiveWindowHidden;
return that;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,13 @@
//
// globals.js
// examples/libraries
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
isInterstitialOverlaysVisible = false;

View file

@ -0,0 +1,311 @@
/* global keyUpEventFromUIWindow */
var GRID_CONTROLS_HTML_URL = Script.resolvePath('../html/gridControls.html');
Grid = function() {
var that = {};
var gridColor = { red: 0, green: 0, blue: 0 };
var gridAlpha = 0.6;
var origin = { x: 0, y: +MyAvatar.getJointPosition('LeftToeBase').y.toFixed(1) + 0.1, z: 0 };
var scale = 500;
var minorGridEvery = 1.0;
var majorGridEvery = 5;
var halfSize = 40;
var worldSize = 16384;
var snapToGrid = false;
var gridOverlay = Overlays.addOverlay("grid", {
rotation: Quat.fromPitchYawRollDegrees(90, 0, 0),
dimensions: { x: scale, y: scale, z: scale },
position: origin,
visible: false,
drawInFront: false,
color: gridColor,
alpha: gridAlpha,
minorGridEvery: minorGridEvery,
majorGridEvery: majorGridEvery,
ignorePickIntersection: true
});
that.visible = false;
that.enabled = false;
that.getOrigin = function() {
return origin;
};
that.getMinorIncrement = function() {
return minorGridEvery;
};
that.setMinorIncrement = function(value) {
minorGridEvery = value;
updateGrid();
};
that.getMajorIncrement = function() {
return majorGridEvery;
};
that.setMajorIncrement = function(value) {
majorGridEvery = value;
updateGrid();
};
that.getColor = function() {
return gridColor;
};
that.setColor = function(value) {
gridColor = value;
updateGrid();
};
that.getSnapToGrid = function() {
return snapToGrid;
};
that.setSnapToGrid = function(value) {
snapToGrid = value;
that.emitUpdate();
};
that.setEnabled = function(enabled) {
that.enabled = enabled;
updateGrid();
};
that.getVisible = function() {
return that.visible;
};
that.setVisible = function(visible, noUpdate) {
that.visible = visible;
updateGrid();
if (!noUpdate) {
that.emitUpdate();
}
};
that.snapToSurface = function(position, dimensions, registration) {
if (!snapToGrid) {
return position;
}
if (dimensions === undefined) {
dimensions = { x: 0, y: 0, z: 0 };
}
if (registration === undefined) {
registration = { x: 0.5, y: 0.5, z: 0.5 };
}
return {
x: position.x,
y: origin.y + (registration.y * dimensions.y),
z: position.z
};
};
that.snapToGrid = function(position, majorOnly, dimensions, registration) {
if (!snapToGrid) {
return position;
}
if (dimensions === undefined) {
dimensions = { x: 0, y: 0, z: 0 };
}
if (registration === undefined) {
registration = { x: 0.5, y: 0.5, z: 0.5 };
}
var spacing = majorOnly ? majorGridEvery : minorGridEvery;
position = Vec3.subtract(position, origin);
position.x = Math.round(position.x / spacing) * spacing;
position.y = Math.round(position.y / spacing) * spacing;
position.z = Math.round(position.z / spacing) * spacing;
return Vec3.sum(Vec3.sum(position, Vec3.multiplyVbyV(registration, dimensions)), origin);
};
that.snapToSpacing = function(delta, majorOnly) {
if (!snapToGrid) {
return delta;
}
var spacing = majorOnly ? majorGridEvery : minorGridEvery;
var snappedDelta = {
x: Math.round(delta.x / spacing) * spacing,
y: Math.round(delta.y / spacing) * spacing,
z: Math.round(delta.z / spacing) * spacing
};
return snappedDelta;
};
that.setPosition = function(newPosition, noUpdate) {
origin = { x: 0, y: newPosition.y, z: 0 };
updateGrid();
if (!noUpdate) {
that.emitUpdate();
}
};
that.moveToSelection = function() {
var newPosition = SelectionManager.worldPosition;
newPosition = Vec3.subtract(newPosition, { x: 0, y: SelectionManager.worldDimensions.y * 0.5, z: 0 });
that.setPosition(newPosition);
};
that.emitUpdate = function() {
if (that.onUpdate) {
that.onUpdate({
origin: origin,
minorGridEvery: minorGridEvery,
majorGridEvery: majorGridEvery,
gridSize: halfSize,
visible: that.visible,
snapToGrid: snapToGrid
});
}
};
that.update = function(data) {
if (data.snapToGrid !== undefined) {
snapToGrid = data.snapToGrid;
}
if (data.origin) {
var pos = data.origin;
pos.x = pos.x === undefined ? origin.x : parseFloat(pos.x);
pos.y = pos.y === undefined ? origin.y : parseFloat(pos.y);
pos.z = pos.z === undefined ? origin.z : parseFloat(pos.z);
that.setPosition(pos, true);
}
if (data.minorGridEvery) {
minorGridEvery = data.minorGridEvery;
}
if (data.majorGridEvery) {
majorGridEvery = data.majorGridEvery;
}
if (data.gridColor) {
gridColor = data.gridColor;
}
if (data.gridSize) {
halfSize = data.gridSize;
}
if (data.visible !== undefined) {
that.setVisible(data.visible, true);
}
updateGrid(true);
};
function updateGrid(noUpdate) {
Overlays.editOverlay(gridOverlay, {
position: { x: 0, y: origin.y, z: 0 },
visible: that.visible && that.enabled,
minorGridEvery: minorGridEvery,
majorGridEvery: majorGridEvery,
color: gridColor,
alpha: gridAlpha
});
if (!noUpdate) {
that.emitUpdate();
}
}
function cleanup() {
Overlays.deleteOverlay(gridOverlay);
}
that.addListener = function(callback) {
that.onUpdate = callback;
};
Script.scriptEnding.connect(cleanup);
updateGrid();
that.onUpdate = null;
return that;
};
GridTool = function(opts) {
var that = {};
var horizontalGrid = opts.horizontalGrid;
var verticalGrid = opts.verticalGrid;
var createToolsWindow = opts.createToolsWindow;
var shouldUseEditTabletApp = opts.shouldUseEditTabletApp;
var listeners = [];
var webView = null;
webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
webView.setVisible = function(value) { };
horizontalGrid.addListener(function(data) {
var dataString = JSON.stringify(data);
webView.emitScriptEvent(dataString);
createToolsWindow.emitScriptEvent(dataString);
if (selectionDisplay) {
selectionDisplay.updateHandles();
}
});
var webEventReceived = function(data) {
try {
data = JSON.parse(data);
} catch (e) {
return;
}
if (data.type === "init") {
horizontalGrid.emitUpdate();
} else if (data.type === "update") {
horizontalGrid.update(data);
for (var i = 0; i < listeners.length; i++) {
listeners[i] && listeners[i](data);
}
} else if (data.type === "action") {
var action = data.action;
if (action === "moveToAvatar") {
var position = MyAvatar.getJointPosition("LeftFoot");
if (position.x === 0 && position.y === 0 && position.z === 0) {
position = MyAvatar.position;
}
horizontalGrid.setPosition(position);
} else if (action === "moveToSelection") {
horizontalGrid.moveToSelection();
}
} else if (data.type === 'keyUpEvent') {
keyUpEventFromUIWindow(data.keyUpEvent);
}
};
webView.webEventReceived.connect(webEventReceived);
createToolsWindow.webEventReceived.addListener(webEventReceived);
that.addListener = function(callback) {
listeners.push(callback);
};
that.setVisible = function(visible) {
webView.setVisible(shouldUseEditTabletApp() && visible);
};
return that;
};

View file

@ -0,0 +1,74 @@
//
// overlayUtils.js
// examples/libraries
//
// Copyright 2015 High Fidelity, Inc.
//
//
// DEPRECATION WARNING: Will be deprecated soon in favor of FloatingUIPanel.
//
// OverlayGroup provides a way to create composite overlays and control their
// position relative to a settable rootPosition and rootRotation.
//
OverlayGroup = function(opts) {
var that = {};
var overlays = {};
var rootPosition = opts.position || { x: 0, y: 0, z: 0 };
var rootRotation = opts.rotation || Quat.IDENTITY;
var visible = opts.visible == true;
function updateOverlays() {
for (overlayID in overlays) {
var overlay = overlays[overlayID];
var newPosition = Vec3.multiplyQbyV(rootRotation, overlay.position);
newPosition = Vec3.sum(rootPosition, newPosition);
Overlays.editOverlay(overlayID, {
visible: visible,
position: newPosition,
rotation: Quat.multiply(rootRotation, overlay.rotation),
});
};
}
that.createOverlay = function(type, properties) {
properties.position = properties.position || { x: 0, y: 0, z: 0 };
properties.rotation = properties.rotation || Quat.IDENTITY;
var overlay = Overlays.addOverlay(type, properties);
overlays[overlay] = {
position: properties.position,
rotation: properties.rotation,
};
updateOverlays();
return overlay;
}
that.setProperties = function(properties) {
if (properties.position !== undefined) {
rootPosition = properties.position;
}
if (properties.rotation !== undefined) {
rootRotation = properties.rotation;
}
if (properties.visible !== undefined) {
visible = properties.visible;
}
updateOverlays();
};
that.destroy = function() {
for (var overlay in overlays) {
Overlays.deleteOverlay(overlay);
}
overlays = {};
}
return that;
};

View file

@ -0,0 +1,204 @@
"use strict";
// pointerUtils.js
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* jslint bitwise: true */
/* global Script, Pointers,
DEFAULT_SEARCH_SPHERE_DISTANCE, COLORS_GRAB_SEARCHING_HALF_SQUEEZE, COLORS_GRAB_SEARCHING_FULL_SQUEEZE,
COLORS_GRAB_DISTANCE_HOLD, TRIGGER_ON_VALUE,
Pointer:true, PointerManager:true
*/
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Pointer = function(hudLayer, pickType, pointerData) {
this.SEARCH_SPHERE_SIZE = 0.0132;
this.dim = {x: this.SEARCH_SPHERE_SIZE, y: this.SEARCH_SPHERE_SIZE, z: this.SEARCH_SPHERE_SIZE};
this.halfPath = {
type: "line3d",
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
visible: true,
alpha: 1,
solid: true,
glow: 1.0,
ignoreRayIntersection: true, // always ignore this
drawInFront: !hudLayer, // Even when burried inside of something, show it.
drawHUDLayer: hudLayer,
};
this.halfEnd = {
type: "sphere",
dimensions: this.dim,
solid: true,
color: COLORS_GRAB_SEARCHING_HALF_SQUEEZE,
alpha: 0.9,
ignoreRayIntersection: true,
drawInFront: !hudLayer, // Even when burried inside of something, show it.
drawHUDLayer: hudLayer,
visible: true
};
this.fullPath = {
type: "line3d",
color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE,
visible: true,
alpha: 1,
solid: true,
glow: 1.0,
ignoreRayIntersection: true, // always ignore this
drawInFront: !hudLayer, // Even when burried inside of something, show it.
drawHUDLayer: hudLayer,
};
this.fullEnd = {
type: "sphere",
dimensions: this.dim,
solid: true,
color: COLORS_GRAB_SEARCHING_FULL_SQUEEZE,
alpha: 0.9,
ignoreRayIntersection: true,
drawInFront: !hudLayer, // Even when burried inside of something, show it.
drawHUDLayer: hudLayer,
visible: true
};
this.holdPath = {
type: "line3d",
color: COLORS_GRAB_DISTANCE_HOLD,
visible: true,
alpha: 1,
solid: true,
glow: 1.0,
ignoreRayIntersection: true, // always ignore this
drawInFront: !hudLayer, // Even when burried inside of something, show it.
drawHUDLayer: hudLayer,
};
this.renderStates = [
{name: "half", path: this.halfPath, end: this.halfEnd},
{name: "full", path: this.fullPath, end: this.fullEnd},
{name: "hold", path: this.holdPath}
];
this.defaultRenderStates = [
{name: "half", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: this.halfPath},
{name: "full", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: this.fullPath},
{name: "hold", distance: DEFAULT_SEARCH_SPHERE_DISTANCE, path: this.holdPath}
];
this.pointerID = null;
this.visible = false;
this.locked = false;
this.allwaysOn = false;
this.hand = pointerData.hand;
delete pointerData.hand;
function createPointer(pickType, pointerData) {
var pointerID = Pointers.createPointer(pickType, pointerData);
Pointers.setRenderState(pointerID, "");
Pointers.enablePointer(pointerID);
return pointerID;
}
this.enable = function() {
Pointers.enablePointer(this.pointerID);
};
this.disable = function() {
Pointers.disablePointer(this.pointerID);
};
this.removePointer = function() {
Pointers.removePointer(this.pointerID);
};
this.makeVisible = function() {
this.visible = true;
};
this.makeInvisible = function() {
this.visible = false;
};
this.lockEnd = function(lockData) {
if (lockData !== undefined) {
if (this.visible && !this.locked && lockData.targetID !== null) {
var targetID = lockData.targetID;
var targetIsOverlay = lockData.isOverlay;
if (lockData.offset === undefined) {
Pointers.setLockEndUUID(this.pointerID, targetID, targetIsOverlay);
} else {
Pointers.setLockEndUUID(this.pointerID, targetID, targetIsOverlay, lockData.offset);
}
this.locked = targetID;
}
} else if (this.locked) {
Pointers.setLockEndUUID(this.pointerID, null, false);
this.locked = false;
}
};
this.updateRenderState = function(triggerClicks, triggerValues) {
var mode = "";
if (this.visible) {
if (this.locked) {
mode = "hold";
} else if (triggerClicks[this.hand]) {
mode = "full";
} else if (triggerValues[this.hand] > TRIGGER_ON_VALUE || this.alwaysOn) {
mode = "half";
}
}
Pointers.setRenderState(this.pointerID, mode);
};
pointerData.renderStates = this.renderStates;
pointerData.defaultRenderStates = this.defaultRenderStates;
this.pointerID = createPointer(pickType, pointerData);
};
PointerManager = function() {
this.pointers = [];
this.createPointer = function(hudLayer, pickType, pointerData) {
var pointer = new Pointer(hudLayer, pickType, pointerData);
this.pointers.push(pointer);
return pointer.pointerID;
};
this.makePointerVisible = function(laserParams) {
var index = laserParams.hand;
if (index < this.pointers.length && index >= 0) {
this.pointers[index].makeVisible();
this.pointers[index].alwaysOn = laserParams.alwaysOn;
}
};
this.makePointerInvisible = function(laserParams) {
var index = laserParams.hand;
if (index < this.pointers.length && index >= 0) {
this.pointers[index].makeInvisible();
}
};
this.lockPointerEnd = function(laserParams, lockData) {
var index = laserParams.hand;
if (index < this.pointers.length && index >= 0) {
this.pointers[index].lockEnd(lockData);
}
};
this.updatePointersRenderState = function(triggerClicks, triggerValues) {
for (var index = 0; index < this.pointers.length; index++) {
this.pointers[index].updateRenderState(triggerClicks, triggerValues);
}
};
this.removePointers = function() {
for (var index = 0; index < this.pointers.length; index++) {
this.pointers[index].removePointer();
}
this.pointers = [];
};
};

View file

@ -0,0 +1,147 @@
//
// progressDialog.js
// examples/libraries
//
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var toolIconUrl = HIFI_PUBLIC_BUCKET + "images/tools/";
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,
backgroundAlpha: 0.9,
visible: false
});
cancelButton = Overlays.addOverlay("text", {
width: cancelWidth,
height: cancelHeight,
text: "Cancel",
textColor: textColor,
backgroundColor: textBackground,
alpha: 0.9,
backgroundAlpha: 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;
}());

View file

@ -0,0 +1,42 @@
/**
* An array for sounds, allows you to randomly play a sound
* taken from the removed editVoxels.js
*/
SoundArray = function(audioOptions, autoUpdateAudioPosition) {
this.audioOptions = audioOptions !== undefined ? audioOptions : {};
this.autoUpdateAudioPosition = autoUpdateAudioPosition !== undefined ? autoUpdateAudioPosition : false;
if (this.audioOptions.position === undefined) {
this.audioOptions.position = Vec3.sum(MyAvatar.position, { x: 0, y: 1, z: 0});
}
if (this.audioOptions.volume === undefined) {
this.audioOptions.volume = 1.0;
}
this.sounds = new Array();
this.addSound = function (soundURL) {
this.sounds[this.sounds.length] = SoundCache.getSound(soundURL);
};
this.play = function (index) {
if (0 <= index && index < this.sounds.length) {
if (this.autoUpdateAudioPosition) {
this.updateAudioPosition();
}
if (this.sounds[index].downloaded) {
Audio.playSound(this.sounds[index], this.audioOptions);
}
} else {
print("[ERROR] libraries/soundArray.js:play() : Index " + index + " out of range.");
}
};
this.playRandom = function () {
if (this.sounds.length > 0) {
this.play(Math.floor(Math.random() * this.sounds.length));
} else {
print("[ERROR] libraries/soundArray.js:playRandom() : Array is empty.");
}
};
this.updateAudioPosition = function() {
var position = MyAvatar.position;
var forwardVector = Quat.getForward(MyAvatar.orientation);
this.audioOptions.position = Vec3.sum(position, forwardVector);
};
};

View file

@ -0,0 +1,476 @@
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;
};
}
// Copyright Mathias Bynens <https://mathiasbynens.be/>
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/*! https://mths.be/includes v1.0.0 by @mathias */
if (!String.prototype.includes) {
(function() {
'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
var toString = {}.toString;
var defineProperty = (function() {
// IE 8 only supports `Object.defineProperty` on DOM elements
try {
var object = {};
var $defineProperty = Object.defineProperty;
var result = $defineProperty(object, object, object) && $defineProperty;
} catch (error) {}
return result;
}());
var indexOf = ''.indexOf;
var includes = function(search) {
if (this == null) {
throw TypeError();
}
var string = String(this);
if (search && toString.call(search) == '[object RegExp]') {
throw TypeError();
}
var stringLength = string.length;
var searchString = String(search);
var searchLength = searchString.length;
var position = arguments.length > 1 ? arguments[1] : undefined;
// `ToInteger`
var pos = position ? Number(position) : 0;
if (pos != pos) { // better `isNaN`
pos = 0;
}
var start = Math.min(Math.max(pos, 0), stringLength);
// Avoid the `indexOf` call if no match is possible
if (searchLength + start > stringLength) {
return false;
}
return indexOf.call(string, searchString, pos) != -1;
};
if (defineProperty) {
defineProperty(String.prototype, 'includes', {
'value': includes,
'configurable': true,
'writable': true
});
} else {
String.prototype.includes = includes;
}
}());
}
/*! https://mths.be/startswith v0.2.0 by @mathias */
if (!String.prototype.startsWith) {
(function() {
'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
var defineProperty = (function() {
// IE 8 only supports `Object.defineProperty` on DOM elements
try {
var object = {};
var $defineProperty = Object.defineProperty;
var result = $defineProperty(object, object, object) && $defineProperty;
} catch (error) {}
return result;
}());
var toString = {}.toString;
var startsWith = function(search) {
if (this == null) {
throw TypeError();
}
var string = String(this);
if (search && toString.call(search) == '[object RegExp]') {
throw TypeError();
}
var stringLength = string.length;
var searchString = String(search);
var searchLength = searchString.length;
var position = arguments.length > 1 ? arguments[1] : undefined;
// `ToInteger`
var pos = position ? Number(position) : 0;
if (pos != pos) { // better `isNaN`
pos = 0;
}
var start = Math.min(Math.max(pos, 0), stringLength);
// Avoid the `indexOf` call if no match is possible
if (searchLength + start > stringLength) {
return false;
}
var index = -1;
while (++index < searchLength) {
if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) {
return false;
}
}
return true;
};
if (defineProperty) {
defineProperty(String.prototype, 'startsWith', {
'value': startsWith,
'configurable': true,
'writable': true
});
} else {
String.prototype.startsWith = startsWith;
}
}());
}
if (!String.prototype.endsWith) {
(function() {
'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
var defineProperty = (function() {
// IE 8 only supports `Object.defineProperty` on DOM elements
try {
var object = {};
var $defineProperty = Object.defineProperty;
var result = $defineProperty(object, object, object) && $defineProperty;
} catch (error) {}
return result;
}());
var toString = {}.toString;
var endsWith = function(search) {
if (this == null) {
throw TypeError();
}
var string = String(this);
if (search && toString.call(search) == '[object RegExp]') {
throw TypeError();
}
var stringLength = string.length;
var searchString = String(search);
var searchLength = searchString.length;
var pos = stringLength;
if (arguments.length > 1) {
var position = arguments[1];
if (position !== undefined) {
// `ToInteger`
pos = position ? Number(position) : 0;
if (pos != pos) { // better `isNaN`
pos = 0;
}
}
}
var end = Math.min(Math.max(pos, 0), stringLength);
var start = end - searchLength;
if (start < 0) {
return false;
}
var index = -1;
while (++index < searchLength) {
if (string.charCodeAt(start + index) != searchString.charCodeAt(index)) {
return false;
}
}
return true;
};
if (defineProperty) {
defineProperty(String.prototype, 'endsWith', {
'value': endsWith,
'configurable': true,
'writable': true
});
} else {
String.prototype.endsWith = endsWith;
}
}());
}
/*! https://mths.be/repeat v0.2.0 by @mathias */
if (!String.prototype.repeat) {
(function() {
'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
var defineProperty = (function() {
// IE 8 only supports `Object.defineProperty` on DOM elements
try {
var object = {};
var $defineProperty = Object.defineProperty;
var result = $defineProperty(object, object, object) && $defineProperty;
} catch (error) {}
return result;
}());
var repeat = function(count) {
if (this == null) {
throw TypeError();
}
var string = String(this);
// `ToInteger`
var n = count ? Number(count) : 0;
if (n != n) { // better `isNaN`
n = 0;
}
// Account for out-of-bounds indices
if (n < 0 || n == Infinity) {
throw RangeError();
}
var result = '';
while (n) {
if (n % 2 == 1) {
result += string;
}
if (n > 1) {
string += string;
}
n >>= 1;
}
return result;
};
if (defineProperty) {
defineProperty(String.prototype, 'repeat', {
'value': repeat,
'configurable': true,
'writable': true
});
} else {
String.prototype.repeat = repeat;
}
}());
}
if (!String.prototype.at) {
(function() {
'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
var defineProperty = (function() {
// IE 8 only supports `Object.defineProperty` on DOM elements.
try {
var object = {};
var $defineProperty = Object.defineProperty;
var result = $defineProperty(object, object, object) && $defineProperty;
} catch (exception) {}
return result;
}());
var at = function(position) {
if (this == null) {
throw TypeError();
}
var string = String(this);
var size = string.length;
// `ToInteger`
var index = position ? Number(position) : 0;
if (index != index) { // better `isNaN`
index = 0;
}
// Account for out-of-bounds indices
// The odd lower bound is because the ToInteger operation is
// going to round `n` to `0` for `-1 < n <= 0`.
if (index <= -1 || index >= size) {
return '';
}
// Second half of `ToInteger`
index = index | 0;
// Get the first code unit and code unit value
var cuFirst = string.charCodeAt(index);
var cuSecond;
var nextIndex = index + 1;
var len = 1;
if ( // Check if its the start of a surrogate pair.
cuFirst >= 0xD800 && cuFirst <= 0xDBFF && // high surrogate
size > nextIndex // there is a next code unit
) {
cuSecond = string.charCodeAt(nextIndex);
if (cuSecond >= 0xDC00 && cuSecond <= 0xDFFF) { // low surrogate
len = 2;
}
}
return string.slice(index, index + len);
};
if (defineProperty) {
defineProperty(String.prototype, 'at', {
'value': at,
'configurable': true,
'writable': true
});
} else {
String.prototype.at = at;
}
}());
}
/*! https://mths.be/codepointat v0.2.0 by @mathias */
if (!String.prototype.codePointAt) {
(function() {
'use strict'; // needed to support `apply`/`call` with `undefined`/`null`
var defineProperty = (function() {
// IE 8 only supports `Object.defineProperty` on DOM elements
try {
var object = {};
var $defineProperty = Object.defineProperty;
var result = $defineProperty(object, object, object) && $defineProperty;
} catch (error) {}
return result;
}());
var codePointAt = function(position) {
if (this == null) {
throw TypeError();
}
var string = String(this);
var size = string.length;
// `ToInteger`
var index = position ? Number(position) : 0;
if (index != index) { // better `isNaN`
index = 0;
}
// Account for out-of-bounds indices:
if (index < 0 || index >= size) {
return undefined;
}
// Get the first code unit
var first = string.charCodeAt(index);
var second;
if ( // check if its the start of a surrogate pair
first >= 0xD800 && first <= 0xDBFF && // high surrogate
size > index + 1 // there is a next code unit
) {
second = string.charCodeAt(index + 1);
if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
}
}
return first;
};
if (defineProperty) {
defineProperty(String.prototype, 'codePointAt', {
'value': codePointAt,
'configurable': true,
'writable': true
});
} else {
String.prototype.codePointAt = codePointAt;
}
}());
}
/*! https://mths.be/fromcodepoint v0.2.1 by @mathias */
if (!String.fromCodePoint) {
(function() {
var defineProperty = (function() {
// IE 8 only supports `Object.defineProperty` on DOM elements
try {
var object = {};
var $defineProperty = Object.defineProperty;
var result = $defineProperty(object, object, object) && $defineProperty;
} catch (error) {}
return result;
}());
var stringFromCharCode = String.fromCharCode;
var floor = Math.floor;
var fromCodePoint = function(_) {
var MAX_SIZE = 0x4000;
var codeUnits = [];
var highSurrogate;
var lowSurrogate;
var index = -1;
var length = arguments.length;
if (!length) {
return '';
}
var result = '';
while (++index < length) {
var codePoint = Number(arguments[index]);
if (!isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity`
codePoint < 0 || // not a valid Unicode code point
codePoint > 0x10FFFF || // not a valid Unicode code point
floor(codePoint) != codePoint // not an integer
) {
throw RangeError('Invalid code point: ' + codePoint);
}
if (codePoint <= 0xFFFF) { // BMP code point
codeUnits.push(codePoint);
} else { // Astral code point; split in surrogate halves
// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
codePoint -= 0x10000;
highSurrogate = (codePoint >> 10) + 0xD800;
lowSurrogate = (codePoint % 0x400) + 0xDC00;
codeUnits.push(highSurrogate, lowSurrogate);
}
if (index + 1 == length || codeUnits.length > MAX_SIZE) {
result += stringFromCharCode.apply(null, codeUnits);
codeUnits.length = 0;
}
}
return result;
};
if (defineProperty) {
defineProperty(String, 'fromCodePoint', {
'value': fromCodePoint,
'configurable': true,
'writable': true
});
} else {
String.fromCodePoint = fromCodePoint;
}
}());
}

View file

@ -0,0 +1,493 @@
//
// toolBars.js
// examples
//
// Created by Clément Brisset on 5/7/14.
// Persistable drag position by HRS 6/11/15.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
Overlay2D = function(properties, overlay) { // overlay is an optional variable
if (!(typeof(properties) === 'undefined')) {
if(typeof(overlay) === 'undefined') {
overlay = Overlays.addOverlay("image", properties);
} else {
Overlays.editOverlay(overlay, properties);
}
}
this.overlay = function() {
return overlay;
}
this.x = function() {
return properties.x;
}
this.y = function() {
return properties.y;
}
this.width = function() {
return properties.width;
}
this.height = function() {
return properties.height;
}
this.alpha = function() {
return properties.alpha;
}
this.visible = function() {
return properties.visible;
}
this.move = function(x, y) {
properties.x = x;
properties.y = y;
Overlays.editOverlay(overlay, { x: x, y: y });
}
this.resize = function(width, height) {
properties.width = width;
properties.height = height;
Overlays.editOverlay(overlay, { width: width, height: height });
}
this.setAlpha = function(alpha) {
properties.alpha = alpha;
Overlays.editOverlay(overlay, { alpha: alpha });
}
this.setImageURL = function(imageURL) {
properties.imageURL = imageURL;
Overlays.editOverlay(overlay, { imageURL: imageURL });
}
this.show = function(doShow) {
properties.visible = doShow;
Overlays.editOverlay(overlay, { visible: doShow });
}
this.clicked = function(clickedOverlay) {
return overlay === clickedOverlay;
}
this.cleanup = function() {
Overlays.deleteOverlay(overlay);
}
}
Tool = function(properties, selectable, selected) { // selectable and selected are optional variables.
Overlay2D.call(this, properties);
if(typeof(selectable)==='undefined') {
selectable = false;
if(typeof(selected)==='undefined') {
selected = false;
}
}
this.selectable = function() {
return selectable;
}
this.selected = function() {
return selected;
}
this.select = function(doSelect) {
if (!selectable) {
return;
}
selected = doSelect;
properties.subImage.y = (selected ? 0 : 1) * properties.subImage.height;
Overlays.editOverlay(this.overlay(), { subImage: properties.subImage });
}
this.toggle = function() {
if (!selectable) {
return;
}
selected = !selected;
properties.subImage.y = (selected ? 0 : 1) * properties.subImage.height;
Overlays.editOverlay(this.overlay(), { subImage: properties.subImage });
return selected;
}
this.select(selected);
this.isButtonDown = false;
this.buttonDown = function (down) {
if (down !== this.isButtonDown) {
properties.subImage.y = (down ? 0 : 1) * properties.subImage.height;
Overlays.editOverlay(this.overlay(), { subImage: properties.subImage });
this.isButtonDown = down;
}
}
this.baseClicked = this.clicked;
this.clicked = function(clickedOverlay, update) {
if (this.baseClicked(clickedOverlay)) {
if (update) {
if (selectable) {
this.toggle();
}
}
return true;
}
return false;
}
}
Tool.prototype = new Overlay2D;
Tool.IMAGE_HEIGHT = 50;
Tool.IMAGE_WIDTH = 50;
ToolBar = function(x, y, direction, optionalPersistenceKey, optionalInitialPositionFunction, optionalOffset) {
this.tools = new Array();
this.x = x;
this.y = y;
this.offset = optionalOffset ? optionalOffset : { x: 0, y: 0 };
this.width = 0;
this.height = 0;
this.backAlpha = 1.0;
this.back = Overlays.addOverlay("rectangle", {
color: { red: 255, green: 255, blue: 255 },
x: this.x,
y: this.y,
radius: 4,
width: this.width,
height: this.height,
alpha: this.backAlpha,
visible: false
});
this.spacing = [];
this.onMove = null;
this.addTool = function(properties, selectable, selected) {
if (direction == ToolBar.HORIZONTAL) {
properties.x = this.x + this.width;
properties.y = this.y;
this.width += properties.width + ToolBar.SPACING;
this.height = Math.max(properties.height, this.height);
} else {
properties.x = this.x;
properties.y = this.y + this.height;
this.width = Math.max(properties.width, this.width);
this.height += properties.height + ToolBar.SPACING;
}
if (this.back != null) {
Overlays.editOverlay(this.back, {
width: this.width +
((direction == ToolBar.HORIZONTAL) ? 1 : 2) * ToolBar.SPACING,
height: this.height +
((direction == ToolBar.VERTICAL) ? 1 : 2) * ToolBar.SPACING,
});
}
this.tools.push(new Tool(properties, selectable, selected));
return ((this.tools.length) - 1);
}
this.addSpacing = function(size) {
if (direction == ToolBar.HORIZONTAL) {
this.width += size;
} else {
this.height += size;
}
this.spacing[this.tools.length] = size;
return (this.tools.length);
}
this.changeSpacing = function(size, id) {
if (this.spacing[id] === null) {
this.spacing[id] = 0;
}
var diff = size - this.spacing[id];
this.spacing[id] = size;
var dx = (direction == ToolBar.HORIZONTAL) ? diff : 0;
var dy = (direction == ToolBar.VERTICAL) ? diff : 0;
this.width += dx;
this.height += dy;
for(i = id; i < this.tools.length; i++) {
this.tools[i].move(this.tools[i].x() + dx,
this.tools[i].y() + dy);
}
if (this.back != null) {
Overlays.editOverlay(this.back, {
width: this.width +
((direction == ToolBar.HORIZONTAL) ? 1 : 2) * ToolBar.SPACING,
height: this.height +
((direction == ToolBar.VERTICAL) ? 1 : 2) * ToolBar.SPACING,
});
}
}
this.removeLastTool = function() {
this.tools.pop().cleanup();
if (direction == ToolBar.HORIZONTAL) {
this.width -= Tool.IMAGE_WIDTH + ToolBar.SPACING;
} else {
this.height -= Tool.IMAGE_HEIGHT + ToolBar.SPACING;
}
if (this.back != null) {
Overlays.editOverlay(this.back, {
width: this.width + 2 * ToolBar.SPACING,
height: this.height + 2 * ToolBar.SPACING
});
}
}
this.move = function (x, y) {
var dx = x - this.x;
var dy = y - this.y;
this.x = x;
this.y = y;
for(var tool in this.tools) {
this.tools[tool].move(this.tools[tool].x() + dx, this.tools[tool].y() + dy);
}
if (this.back != null) {
Overlays.editOverlay(this.back, {
x: x - ToolBar.SPACING,
y: y - ToolBar.SPACING
});
}
if (this.onMove !== null) {
this.onMove(x, y, dx, dy);
};
}
this.setAlpha = function(alpha, tool) {
if(typeof(tool) === 'undefined') {
for(var tool in this.tools) {
this.tools[tool].setAlpha(alpha);
}
if (this.back != null) {
this.backAlpha = alpha;
Overlays.editOverlay(this.back, { alpha: alpha });
}
} else {
this.tools[tool].setAlpha(alpha);
}
}
this.setImageURL = function(imageURL, tool) {
this.tools[tool].setImageURL(imageURL);
}
this.setBack = function(color, alpha) {
if (color == null) {
Overlays.editOverlay(this.back, { visible: false });
} else {
Overlays.editOverlay(this.back, {
width: this.width +
((direction == ToolBar.HORIZONTAL) ? 1 : 2) * ToolBar.SPACING,
height: this.height +
((direction == ToolBar.VERTICAL) ? 1 : 2) * ToolBar.SPACING,
visible: true,
color: color,
alpha: alpha
});
}
}
this.showTool = function(tool, doShow) {
this.tools[tool].show(doShow);
}
this.show = function(doShow) {
for(var tool in this.tools) {
this.tools[tool].show(doShow);
}
if (this.back != null) {
Overlays.editOverlay(this.back, { visible: doShow});
}
}
this.clicked = function(clickedOverlay, update) {
if(typeof(update) === 'undefined') {
update = true;
}
for(var tool in this.tools) {
if (this.tools[tool].visible() && this.tools[tool].clicked(clickedOverlay, update)) {
return parseInt(tool);
}
}
return -1;
}
this.numberOfTools = function() {
return this.tools.length;
}
this.selectTool = function (tool, select) {
this.tools[tool].select(select);
}
this.toolSelected = function (tool) {
return this.tools[tool].selected();
}
this.cleanup = function() {
for(var tool in this.tools) {
this.tools[tool].cleanup();
}
if (this.back != null) {
Overlays.deleteOverlay(this.back);
this.back = null;
}
this.tools = [];
this.x = x;
this.y = y;
this.width = 0;
this.height = 0;
}
var that = this;
this.contains = function (xOrPoint, optionalY) { // All four margins are draggable.
var x = (optionalY === undefined) ? xOrPoint.x : xOrPoint,
y = (optionalY === undefined) ? xOrPoint.y : optionalY;
return ((that.x - ToolBar.SPACING) <= x) && (x <= (that.x + that.width + ToolBar.SPACING)) &&
((that.y - ToolBar.SPACING) <= y) && (y <= (that.y + that.height));
}
that.hover = function (enable) { // Can be overridden or extended by clients.
that.isHovering = enable;
if (that.back) {
Overlays.editOverlay(this.back, {
visible: enable,
alpha: enable ? 0.5 : that.backAlpha
});
}
};
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
var recommendedRect = Controller.getRecommendedHUDRect();
var recommendedDimmensions = { x: recommendedRect.width, y: recommendedRect.height };
that.windowDimensions = recommendedDimmensions; // Controller.getViewportDimensions();
that.origin = { x: recommendedRect.x, y: recommendedRect.y };
// Maybe fixme: Keeping the same percent of the window size isn't always the right thing.
// For example, maybe we want "keep the same percentage to whatever two edges are closest to the edge of screen".
// If we change that, the places to do so are onResizeViewport, save (maybe), and the initial move based on Settings, below.
that.onResizeViewport = function (newSize) { // Can be overridden or extended by clients.
var recommendedRect = Controller.getRecommendedHUDRect();
var recommendedDimmensions = { x: recommendedRect.width, y: recommendedRect.height };
var originRelativeX = (that.x - that.origin.x - that.offset.x);
var originRelativeY = (that.y - that.origin.y - that.offset.y);
var fractionX = clamp(originRelativeX / that.windowDimensions.x, 0, 1);
var fractionY = clamp(originRelativeY / that.windowDimensions.y, 0, 1);
that.windowDimensions = newSize || recommendedDimmensions;
that.origin = { x: recommendedRect.x, y: recommendedRect.y };
var newX = (fractionX * that.windowDimensions.x) + recommendedRect.x + that.offset.x;
var newY = (fractionY * that.windowDimensions.y) + recommendedRect.y + that.offset.y;
that.move(newX, newY);
};
if (optionalPersistenceKey) {
this.fractionKey = optionalPersistenceKey + '.fraction';
// FIXME: New default position in RC8 is bottom center of screen instead of right. Can remove this key and associated
// code once the new toolbar position is well established with users.
this.isNewPositionKey = optionalPersistenceKey + '.isNewPosition';
this.save = function () {
var recommendedRect = Controller.getRecommendedHUDRect();
var screenSize = { x: recommendedRect.width, y: recommendedRect.height };
if (screenSize.x > 0 && screenSize.y > 0) {
// Guard against invalid screen size that can occur at shut-down.
var fraction = {x: (that.x - that.offset.x) / screenSize.x, y: (that.y - that.offset.y) / screenSize.y};
Settings.setValue(this.fractionKey, JSON.stringify(fraction));
}
}
} else {
this.save = function () { }; // Called on move. Can be overriden or extended by clients.
}
// These are currently only doing that which is necessary for toolbar hover and toolbar drag.
// They have not yet been extended to tool hover/click/release, etc.
this.mousePressEvent = function (event) {
if (Overlays.getOverlayAtPoint({ x: event.x, y: event.y }) == that.back) {
that.mightBeDragging = true;
that.dragOffsetX = that.x - event.x;
that.dragOffsetY = that.y - event.y;
} else {
that.mightBeDragging = false;
}
};
this.mouseReleaseEvent = function (event) {
for (var tool in that.tools) {
that.tools[tool].buttonDown(false);
}
if (that.mightBeDragging) {
that.save();
}
}
this.mouseMove = function (event) {
if (!that.mightBeDragging || !event.isLeftButton) {
that.mightBeDragging = false;
if (!that.contains(event)) {
if (that.isHovering) {
that.hover(false);
}
return;
}
if (!that.isHovering) {
that.hover(true);
}
return;
}
that.move(that.dragOffsetX + event.x, that.dragOffsetY + event.y);
};
that.checkResize = function () { // Can be overriden or extended, but usually not. See onResizeViewport.
var recommendedRect = Controller.getRecommendedHUDRect();
var currentWindowSize = { x: recommendedRect.width, y: recommendedRect.height };
if ((currentWindowSize.x !== that.windowDimensions.x) || (currentWindowSize.y !== that.windowDimensions.y)) {
that.onResizeViewport(currentWindowSize);
}
};
Controller.mousePressEvent.connect(this.mousePressEvent);
Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent);
Controller.mouseMoveEvent.connect(this.mouseMove);
Script.update.connect(that.checkResize);
// This compatability hack breaks the model, but makes converting existing scripts easier:
this.addOverlay = function (ignored, oldSchoolProperties) {
var properties = JSON.parse(JSON.stringify(oldSchoolProperties)); // a copy
if ((that.numberOfTools() === 0) && (properties.x != undefined) && (properties.y != undefined)) {
that.move(properties.x, properties.y);
}
delete properties.x;
delete properties.y;
var index = that.addTool(properties);
var id = that.tools[index].overlay();
return id;
}
if (this.fractionKey || optionalInitialPositionFunction) {
var isNewPosition = Settings.getValue(this.isNewPositionKey);
var savedFraction = isNewPosition ? JSON.parse(Settings.getValue(this.fractionKey) || "0") : 0;
Settings.setValue(this.isNewPositionKey, true);
var recommendedRect = Controller.getRecommendedHUDRect();
var screenSize = { x: recommendedRect.width, y: recommendedRect.height };
if (savedFraction) {
// If we have saved data, keep the toolbar at the same proportion of the screen width/height.
that.move(savedFraction.x * screenSize.x + that.offset.x, savedFraction.y * screenSize.y + that.offset.y);
} else if (!optionalInitialPositionFunction) {
print("No initPosition(screenSize, intializedToolbar) specified for ToolBar");
} else {
// Call the optionalInitialPositionFunctinon() AFTER the client has had a chance to set up.
var that = this;
Script.setTimeout(function () {
var position = optionalInitialPositionFunction(screenSize, that);
that.move(position.x + that.offset.x, position.y + that.offset.y);
}, 0);
}
}
}
ToolBar.SPACING = 6;
ToolBar.VERTICAL = 0;
ToolBar.HORIZONTAL = 1;

View file

@ -0,0 +1,250 @@
"use strict";
// touchEventUtils.js
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND,
enableDispatcherModule, disableDispatcherModule, makeRunningValues,
Messages, Quat, Vec3, getControllerWorldLocation, makeDispatcherModuleParameters, Overlays, controllerDispatcher.ZERO_VEC,
HMD, INCHES_TO_METERS, DEFAULT_REGISTRATION_POINT, Settings, getGrabPointSphereOffset
*/
var controllerDispatcher = Script.require("/~/system/libraries/controllerDispatcherUtils.js");
function touchTargetHasKeyboardFocus(touchTarget) {
if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL) {
return Entities.keyboardFocusEntity === touchTarget.entityID;
} else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) {
return Overlays.keyboardFocusOverlay === touchTarget.overlayID;
}
}
function setKeyboardFocusOnTouchTarget(touchTarget) {
if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL &&
Entities.wantsHandControllerPointerEvents(touchTarget.entityID)) {
Overlays.keyboardFocusOverlay = Uuid.NULL;
Entities.keyboardFocusEntity = touchTarget.entityID;
} else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) {
Overlays.keyboardFocusOverlay = touchTarget.overlayID;
Entities.keyboardFocusEntity = Uuid.NULL;
}
}
function sendHoverEnterEventToTouchTarget(hand, touchTarget) {
var pointerEvent = {
type: "Move",
id: hand + 1, // 0 is reserved for hardware mouse
pos2D: touchTarget.position2D,
pos3D: touchTarget.position,
normal: touchTarget.normal,
direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal),
button: "None"
};
if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL) {
Entities.sendHoverEnterEntity(touchTarget.entityID, pointerEvent);
} else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) {
Overlays.sendHoverEnterOverlay(touchTarget.overlayID, pointerEvent);
}
}
function sendHoverOverEventToTouchTarget(hand, touchTarget) {
var pointerEvent = {
type: "Move",
id: hand + 1, // 0 is reserved for hardware mouse
pos2D: touchTarget.position2D,
pos3D: touchTarget.position,
normal: touchTarget.normal,
direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal),
button: "None"
};
if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL) {
Entities.sendMouseMoveOnEntity(touchTarget.entityID, pointerEvent);
Entities.sendHoverOverEntity(touchTarget.entityID, pointerEvent);
} else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) {
Overlays.sendMouseMoveOnOverlay(touchTarget.overlayID, pointerEvent);
Overlays.sendHoverOverOverlay(touchTarget.overlayID, pointerEvent);
}
}
function sendTouchStartEventToTouchTarget(hand, touchTarget) {
var pointerEvent = {
type: "Press",
id: hand + 1, // 0 is reserved for hardware mouse
pos2D: touchTarget.position2D,
pos3D: touchTarget.position,
normal: touchTarget.normal,
direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal),
button: "Primary",
isPrimaryHeld: true
};
if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL) {
Entities.sendMousePressOnEntity(touchTarget.entityID, pointerEvent);
Entities.sendClickDownOnEntity(touchTarget.entityID, pointerEvent);
} else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) {
Overlays.sendMousePressOnOverlay(touchTarget.overlayID, pointerEvent);
}
}
function sendTouchEndEventToTouchTarget(hand, touchTarget) {
var pointerEvent = {
type: "Release",
id: hand + 1, // 0 is reserved for hardware mouse
pos2D: touchTarget.position2D,
pos3D: touchTarget.position,
normal: touchTarget.normal,
direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal),
button: "Primary"
};
if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL) {
Entities.sendMouseReleaseOnEntity(touchTarget.entityID, pointerEvent);
Entities.sendClickReleaseOnEntity(touchTarget.entityID, pointerEvent);
Entities.sendHoverLeaveEntity(touchTarget.entityID, pointerEvent);
} else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) {
Overlays.sendMouseReleaseOnOverlay(touchTarget.overlayID, pointerEvent);
}
}
function sendTouchMoveEventToTouchTarget(hand, touchTarget) {
var pointerEvent = {
type: "Move",
id: hand + 1, // 0 is reserved for hardware mouse
pos2D: touchTarget.position2D,
pos3D: touchTarget.position,
normal: touchTarget.normal,
direction: Vec3.subtract(controllerDispatcher.ZERO_VEC, touchTarget.normal),
button: "Primary",
isPrimaryHeld: true
};
if (touchTarget.entityID && touchTarget.entityID !== Uuid.NULL) {
Entities.sendMouseMoveOnEntity(touchTarget.entityID, pointerEvent);
Entities.sendHoldingClickOnEntity(touchTarget.entityID, pointerEvent);
} else if (touchTarget.overlayID && touchTarget.overlayID !== Uuid.NULL) {
Overlays.sendMouseMoveOnOverlay(touchTarget.overlayID, pointerEvent);
}
}
function composeTouchTargetFromIntersection(intersection) {
var isEntity = (intersection.type === Picks.INTERSECTED_ENTITY);
var objectID = intersection.objectID;
var worldPos = intersection.intersection;
var props = null;
if (isEntity) {
props = Entities.getProperties(intersection.objectID);
}
var position2D =(isEntity ? controllerDispatcher.projectOntoEntityXYPlane(objectID, worldPos, props) :
controllerDispatcher.projectOntoOverlayXYPlane(objectID, worldPos));
return {
entityID: isEntity ? objectID : null,
overlayID: isEntity ? null : objectID,
distance: intersection.distance,
position: worldPos,
position2D: position2D,
normal: intersection.surfaceNormal
};
}
// will return undefined if overlayID does not exist.
function calculateTouchTargetFromOverlay(touchTip, overlayID) {
var overlayPosition = Overlays.getProperty(overlayID, "position");
if (overlayPosition === undefined) {
return;
}
// project touchTip onto overlay plane.
var overlayRotation = Overlays.getProperty(overlayID, "rotation");
if (overlayRotation === undefined) {
return;
}
var normal = Vec3.multiplyQbyV(overlayRotation, {x: 0, y: 0, z: 1});
var distance = Vec3.dot(Vec3.subtract(touchTip.position, overlayPosition), normal);
var position = Vec3.subtract(touchTip.position, Vec3.multiply(normal, distance));
// calclulate normalized position
var invRot = Quat.inverse(overlayRotation);
var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition));
var dimensions = Overlays.getProperty(overlayID, "dimensions");
if (dimensions === undefined) {
return;
}
dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension
var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z };
var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT);
// 2D position on overlay plane in meters, relative to the bounding box upper-left hand corner.
var position2D = {
x: normalizedPosition.x * dimensions.x,
y: (1 - normalizedPosition.y) * dimensions.y // flip y-axis
};
return {
entityID: null,
overlayID: overlayID,
distance: distance,
position: position,
position2D: position2D,
normal: normal,
normalizedPosition: normalizedPosition,
dimensions: dimensions,
valid: true
};
}
// will return undefined if entity does not exist.
function calculateTouchTargetFromEntity(touchTip, props) {
if (props.rotation === undefined) {
// if rotation is missing from props object, then this entity has probably been deleted.
return;
}
// project touch tip onto entity plane.
var normal = Vec3.multiplyQbyV(props.rotation, {x: 0, y: 0, z: 1});
Vec3.multiplyQbyV(props.rotation, {x: 0, y: 1, z: 0});
var distance = Vec3.dot(Vec3.subtract(touchTip.position, props.position), normal);
var position = Vec3.subtract(touchTip.position, Vec3.multiply(normal, distance));
// generate normalized coordinates
var invRot = Quat.inverse(props.rotation);
var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, props.position));
var invDimensions = { x: 1 / props.dimensions.x, y: 1 / props.dimensions.y, z: 1 / props.dimensions.z };
var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), props.registrationPoint);
// 2D position on entity plane in meters, relative to the bounding box upper-left hand corner.
var position2D = {
x: normalizedPosition.x * props.dimensions.x,
y: (1 - normalizedPosition.y) * props.dimensions.y // flip y-axis
};
return {
entityID: props.id,
entityProps: props,
overlayID: null,
distance: distance,
position: position,
position2D: position2D,
normal: normal,
normalizedPosition: normalizedPosition,
dimensions: props.dimensions,
valid: true
};
}
module.exports = {
calculateTouchTargetFromEntity: calculateTouchTargetFromEntity,
calculateTouchTargetFromOverlay: calculateTouchTargetFromOverlay,
touchTargetHasKeyboardFocus: touchTargetHasKeyboardFocus,
setKeyboardFocusOnTouchTarget: setKeyboardFocusOnTouchTarget,
sendHoverEnterEventToTouchTarget: sendHoverEnterEventToTouchTarget,
sendHoverOverEventToTouchTarget: sendHoverOverEventToTouchTarget,
sendTouchStartEventToTouchTarget: sendTouchStartEventToTouchTarget,
sendTouchEndEventToTouchTarget: sendTouchEndEventToTouchTarget,
sendTouchMoveEventToTouchTarget: sendTouchMoveEventToTouchTarget,
composeTouchTargetFromIntersection: composeTouchTargetFromIntersection
};

View file

@ -0,0 +1,504 @@
//
// Created by Bradley Austin Davis on 2015/08/29
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// note: this constant is currently duplicated in edit.js and ambientSound.js
EDIT_SETTING = "io.highfidelity.isEditing";
isInEditMode = function isInEditMode() {
return Settings.getValue(EDIT_SETTING);
};
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
fBound.prototype = new fNOP();
return fBound;
};
}
vec3toStr = function(v, digits) {
if (!digits) { digits = 3; }
return "{ " + v.x.toFixed(digits) + ", " + v.y.toFixed(digits) + ", " + v.z.toFixed(digits)+ " }";
}
quatToStr = function(q, digits) {
if (!digits) { digits = 3; }
return "{ " + q.w.toFixed(digits) + ", " + q.x.toFixed(digits) + ", " +
q.y.toFixed(digits) + ", " + q.z.toFixed(digits)+ " }";
}
vec3equal = function(v0, v1) {
return (v0.x == v1.x) && (v0.y == v1.y) && (v0.z == v1.z);
}
colorMix = function(colorA, colorB, mix) {
var result = {};
for (var key in colorA) {
result[key] = (colorA[key] * (1 - mix)) + (colorB[key] * mix);
}
return result;
}
scaleLine = function (start, end, scale) {
var v = Vec3.subtract(end, start);
var length = Vec3.length(v);
v = Vec3.multiply(scale, v);
return Vec3.sum(start, v);
}
findAction = function(name) {
return Controller.findAction(name);
}
addLine = function(origin, vector, color) {
if (!color) {
color = COLORS.WHITE
}
return Entities.addEntity(mergeObjects(LINE_PROTOTYPE, {
position: origin,
linePoints: [
ZERO_VECTOR,
vector,
],
color: color
}));
}
// FIXME fetch from a subkey of user data to support non-destructive modifications
setEntityUserData = function(id, data) {
var json = JSON.stringify(data)
Entities.editEntity(id, { userData: json });
}
// FIXME do non-destructive modification of the existing user data
getEntityUserData = function(id) {
var results = null;
var properties = Entities.getEntityProperties(id, "userData");
if (properties.userData) {
try {
results = JSON.parse(properties.userData);
} catch(err) {
logDebug(err);
}
}
return results ? results : {};
}
// Non-destructively modify the user data of an entity.
setEntityCustomData = function(customKey, id, data) {
var userData = getEntityUserData(id);
if (data == null) {
delete userData[customKey];
} else {
userData[customKey] = data;
}
setEntityUserData(id, userData);
}
getEntityCustomData = function(customKey, id, defaultValue) {
var userData = getEntityUserData(id);
if (undefined != userData[customKey]) {
return userData[customKey];
} else {
return defaultValue;
}
}
mergeObjects = function(proto, custom) {
var result = {};
for (var attrname in proto) {
result[attrname] = proto[attrname];
}
for (var attrname in custom) {
result[attrname] = custom[attrname];
}
return result;
}
LOG_WARN = 1;
logWarn = function(str) {
if (LOG_WARN) {
print(str);
}
}
LOG_ERROR = 1;
logError = function(str) {
if (LOG_ERROR) {
print(str);
}
}
LOG_INFO = 1;
logInfo = function(str) {
if (LOG_INFO) {
print(str);
}
}
LOG_DEBUG = 0;
logDebug = function(str) {
if (LOG_DEBUG) {
print(str);
}
}
LOG_TRACE = 0;
logTrace = function(str) {
if (LOG_TRACE) {
print(str);
}
}
// Computes the penetration between a point and a sphere (centered at the origin)
// if point is inside sphere: returns true and stores the result in 'penetration'
// (the vector that would move the point outside the sphere)
// otherwise returns false
findSphereHit = function(point, sphereRadius) {
var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations
var vectorLength = Vec3.length(point);
if (vectorLength < EPSILON) {
return true;
}
var distance = vectorLength - sphereRadius;
if (distance < 0.0) {
return true;
}
return false;
}
findSpherePointHit = function(sphereCenter, sphereRadius, point) {
return findSphereHit(Vec3.subtract(point,sphereCenter), sphereRadius);
}
findSphereSphereHit = function(firstCenter, firstRadius, secondCenter, secondRadius) {
return findSpherePointHit(firstCenter, firstRadius + secondRadius, secondCenter);
}
// Given a vec3 v, return a vec3 that is the same vector relative to the avatars
// DEFAULT eye position, rotated into the avatars reference frame.
getEyeRelativePosition = function(v) {
return Vec3.sum(MyAvatar.getDefaultEyePosition(), Vec3.multiplyQbyV(MyAvatar.orientation, v));
}
getAvatarRelativeRotation = function(q) {
return Quat.multiply(MyAvatar.orientation, q);
}
pointInExtents = function(point, minPoint, maxPoint) {
return (point.x >= minPoint.x && point.x <= maxPoint.x) &&
(point.y >= minPoint.y && point.y <= maxPoint.y) &&
(point.z >= minPoint.z && point.z <= maxPoint.z);
}
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*
* @param Number h The hue
* @param Number s The saturation
* @param Number l The lightness
* @return Array The RGB representation
*/
hslToRgb = function(hsl) {
var r, g, b;
if (hsl.s == 0) {
r = g = b = hsl.l; // achromatic
} else {
var hue2rgb = function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
}
var q = hsl.l < 0.5 ? hsl.l * (1 + hsl.s) : hsl.l + hsl.s - hsl.l * hsl.s;
var p = 2 * hsl.l - q;
r = hue2rgb(p, q, hsl.h + 1 / 3);
g = hue2rgb(p, q, hsl.h);
b = hue2rgb(p, q, hsl.h - 1 / 3);
}
return {
red: Math.round(r * 255),
green: Math.round(g * 255),
blue: Math.round(b * 255)
};
}
map = function(value, min1, max1, min2, max2) {
return min2 + (max2 - min2) * ((value - min1) / (max1 - min1));
}
orientationOf = function(vector) {
var Y_AXIS = {
x: 0,
y: 1,
z: 0
};
var X_AXIS = {
x: 1,
y: 0,
z: 0
};
var theta = 0.0;
var RAD_TO_DEG = 180.0 / Math.PI;
var direction, yaw, pitch;
direction = Vec3.normalize(vector);
yaw = Quat.angleAxis(Math.atan2(direction.x, direction.z) * RAD_TO_DEG, Y_AXIS);
pitch = Quat.angleAxis(Math.asin(-direction.y) * RAD_TO_DEG, X_AXIS);
return Quat.multiply(yaw, pitch);
}
randFloat = function(low, high) {
return low + Math.random() * (high - low);
}
randInt = function(low, high) {
return Math.floor(randFloat(low, high));
}
randomColor = function() {
return {
red: randInt(0, 255),
green: randInt(0, 255),
blue: randInt(0, 255)
}
}
hexToRgb = function(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
red: parseInt(result[1], 16),
green: parseInt(result[2], 16),
blue: parseInt(result[3], 16)
} : null;
}
calculateHandSizeRatio = function() {
// Get the ratio of the current avatar's hand to Owen's hand
var standardCenterHandPoint = 0.11288;
var jointNames = MyAvatar.getJointNames();
//get distance from handJoint up to leftHandIndex3 as a proxy for center of hand
var wristToFingertipDistance = 0;;
for (var i = 0; i < jointNames.length; i++) {
var jointName = jointNames[i];
print(jointName)
if (jointName.indexOf("LeftHandIndex") !== -1) {
// translations are relative to parent joint, so simply add them together
// joints face down the y-axis
var translation = MyAvatar.getDefaultJointTranslation(i).y;
wristToFingertipDistance += translation;
}
}
// Right now units are in cm, so convert to meters
wristToFingertipDistance /= 100;
var centerHandPoint = wristToFingertipDistance/2;
// Compare against standard hand (Owen)
var handSizeRatio = centerHandPoint/standardCenterHandPoint;
return handSizeRatio;
}
clamp = function(val, min, max){
return Math.max(min, Math.min(max, val))
}
// flattens an array of arrays into a single array
// example: flatten([[1], [3, 4], []) => [1, 3, 4]
// NOTE: only does one level of flattening, it is not recursive.
flatten = function(array) {
return [].concat.apply([], array);
}
getTabletWidthFromSettings = function () {
var DEFAULT_TABLET_WIDTH = 0.4375;
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var toolbarMode = tablet.toolbarMode;
var DEFAULT_DESKTOP_TABLET_SCALE = 75;
var DEFAULT_HMD_TABLET_SCALE = 60;
var tabletScalePercentage = DEFAULT_HMD_TABLET_SCALE;
if (!toolbarMode) {
if (HMD.active) {
tabletScalePercentage = Settings.getValue("hmdTabletScale") || DEFAULT_HMD_TABLET_SCALE;
} else {
tabletScalePercentage = Settings.getValue("desktopTabletScale") || DEFAULT_DESKTOP_TABLET_SCALE;
}
}
return DEFAULT_TABLET_WIDTH * (tabletScalePercentage / 100);
};
resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) {
if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID || !HMD.homeButtonHighlightID) {
return;
}
var sensorScaleFactor = sensorToWorldScaleOverride || MyAvatar.sensorToWorldScale;
var sensorScaleOffsetOverride = 1;
var SENSOR_TO_ROOM_MATRIX = 65534;
var parentJointIndex = newParentJointIndex || Overlays.getProperty(HMD.tabletID, "parentJointIndex");
if (parentJointIndex === SENSOR_TO_ROOM_MATRIX) {
sensorScaleOffsetOverride = 1 / sensorScaleFactor;
}
// will need to be recaclulated if dimensions of fbx model change.
var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269};
var DEFAULT_DPI = 31;
var DEFAULT_WIDTH = 0.4375;
// scale factor of natural tablet dimensions.
var tabletWidth = (width || DEFAULT_WIDTH) * sensorScaleFactor;
var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x;
var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor;
var tabletDepth = TABLET_NATURAL_DIMENSIONS.z * tabletScaleFactor;
var tabletDpi = DEFAULT_DPI * (DEFAULT_WIDTH / tabletWidth);
// update tablet model dimensions
Entities.editEntity(HMD.tabletID, {
dimensions: { x: tabletWidth, y: tabletHeight, z: tabletDepth }
});
// update webOverlay
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.5) * sensorScaleOffsetOverride;
var WEB_ENTITY_Y_OFFSET = 1.25 * tabletScaleFactor * sensorScaleOffsetOverride;
var screenWidth = 0.9367 * tabletWidth;
var screenHeight = 0.9000 * tabletHeight;
var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape;
Entities.editEntity(HMD.tabletScreenID, {
localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET},
dimensions: {x: landscape ? screenHeight : screenWidth, y: landscape ? screenWidth : screenHeight, z: 1.0},
dpi: tabletDpi
});
// update homeButton
var homeButtonDim = 4.0 * tabletScaleFactor / 1.5;
var HOME_BUTTON_X_OFFSET = 0.00079 * sensorScaleOffsetOverride * sensorScaleFactor;
var HOME_BUTTON_Y_OFFSET = -1 * ((tabletHeight / 2) - (4.0 * tabletScaleFactor / 2)) * sensorScaleOffsetOverride;
var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleOffsetOverride;
Entities.editEntity(HMD.homeButtonID, {
localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET },
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }
});
Entities.editEntity(HMD.homeButtonHighlightID, {
localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET },
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }
});
};
reparentAndScaleTablet = function(width, reparentProps) {
if (!HMD.tabletID || !HMD.tabletScreenID || !HMD.homeButtonID || !HMD.homeButtonHighlightID) {
return;
}
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
var sensorScaleOffsetOverride = 1;
var SENSOR_TO_ROOM_MATRIX = 65534;
var parentJointIndex = reparentProps.parentJointIndex;
if (parentJointIndex === SENSOR_TO_ROOM_MATRIX) {
sensorScaleOffsetOverride = 1 / sensorScaleFactor;
}
// will need to be recaclulated if dimensions of fbx model change.
var TABLET_NATURAL_DIMENSIONS = {x: 32.083, y: 48.553, z: 2.269};
var DEFAULT_DPI = 31;
var DEFAULT_WIDTH = 0.4375;
// scale factor of natural tablet dimensions.
var tabletWidth = (width || DEFAULT_WIDTH) * sensorScaleFactor;
var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x;
var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor;
var tabletDepth = TABLET_NATURAL_DIMENSIONS.z * tabletScaleFactor;
var tabletDpi = DEFAULT_DPI * (DEFAULT_WIDTH / tabletWidth);
// update tablet model dimensions
Entities.editEntity(HMD.tabletID, {
parentID: reparentProps.parentID,
parentJointIndex: reparentProps.parentJointIndex,
dimensions: { x: tabletWidth, y: tabletHeight, z: tabletDepth}
});
// update webOverlay
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.5) * sensorScaleOffsetOverride;
var WEB_ENTITY_Y_OFFSET = 1.25 * tabletScaleFactor * sensorScaleOffsetOverride;
var screenWidth = 0.9367 * tabletWidth;
var screenHeight = 0.9000 * tabletHeight;
var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape;
Entities.editEntity(HMD.tabletScreenID, {
localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET},
dimensions: {x: landscape ? screenHeight : screenWidth, y: landscape ? screenWidth : screenHeight, z: 1.0},
dpi: tabletDpi
});
// update homeButton
var homeButtonDim = 4.0 * tabletScaleFactor / 1.5;
var HOME_BUTTON_X_OFFSET = 0.00079 * sensorScaleOffsetOverride * sensorScaleFactor;
var HOME_BUTTON_Y_OFFSET = -1 * ((tabletHeight / 2) - (4.0 * tabletScaleFactor / 2)) * sensorScaleOffsetOverride;
var HOME_BUTTON_Z_OFFSET = (tabletDepth / 1.9) * sensorScaleOffsetOverride;
Entities.editEntity(HMD.homeButtonID, {
localPosition: { x: HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET },
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }
});
Entities.editEntity(HMD.homeButtonHighlightID, {
localPosition: { x: -HOME_BUTTON_X_OFFSET, y: HOME_BUTTON_Y_OFFSET, z: -HOME_BUTTON_Z_OFFSET },
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }
});
}
getMainTabletIDs = function () {
var tabletIDs = [];
if (HMD.tabletID) {
tabletIDs.push(HMD.tabletID);
}
if (HMD.tabletScreenID) {
tabletIDs.push(HMD.tabletScreenID);
}
if (HMD.homeButtonID) {
tabletIDs.push(HMD.homeButtonID);
}
return tabletIDs;
};