Karol SuprynowiczIncrease default vertical FOV69105e72 years agoHistory
637 lines · 24.6 KB · JavaScript
RawDownload
////  WebTablet.js////  Created by Anthony J. Thibault on August 8th, 2016//  Copyright 2016 High Fidelity, Inc.//  Copyright 2023, Overte e.V.////  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("utils.js"));Script.include(Script.resolvePath("controllers.js"));Script.include(Script.resolvePath("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 = 55; // degreesvar 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 = Script.getExternalPath(Script.ExternalPaths.HF_Content, "/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 controllerStandard = Controller.Standard;    var LEFT_HAND = controllerStandard.LeftHand;    var sensorToWorldScale = MyAvatar.sensorToWorldScale;    var headPos = (HMD.active && (Camera.mode === "first person" || Camera.mode === "first person look at")) ? HMD.position : Camera.position;    var headRot = Quat.cancelOutRollAndPitch((HMD.active && (Camera.mode === "first person" || Camera.mode === "first person look at")) ?        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) {        if (avatarEntityData[entityID].name === 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,        "grab": {             "grabbable": true         },        "dimensions": { "x": tabletWidth, "y": tabletHeight, "z": tabletDepth },        "parentID": MyAvatar.SELF_ID,        "visible": visible,        "groupCulled": 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 = Entities.addEntity(tabletProperties, "local");     if (this.webOverlayID) {        Entities.deleteEntity(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 = Entities.addEntity({        "type": "Web",        "name": "WebTablet Web",        "sourceUrl": 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,        "showKeyboardFocusHighlight": false,        "grab": {            "grabbable": false        },        "visible": visible    }, "local");     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 = Entities.addEntity({        "type": "Gizmo",        "gizmoType": "ring",        "name": "homeButton",        "localPosition": { "x": HOME_BUTTON_X_OFFSET, "y": HOME_BUTTON_Y_OFFSET, "z": -HOME_BUTTON_Z_OFFSET },        "localRotation": Quat.fromVec3Degrees({ "x": 90, "y": 0, "z": 0}),        "dimensions": { "x": homeButtonDim, "y": homeButtonDim, "z": homeButtonDim },        "primitiveMode": "solid",        "ring": {            "innerStartAlpha": 0.0,            "innerEndAlpha": 0.0,            "outerStartAlpha": 0.0,            "outerEndAlpha": 0.0,        },        "visible": visible,        "renderLayer": "world",        "grab": {             "grabbable": false        },        "parentID": this.tabletEntityID    }, "local");     this.homeButtonHighlightID = Entities.addEntity({        "type": "Gizmo",        "gizmoType": "ring",        "name": "homeButtonHighlight",        "localPosition": { "x": -HOME_BUTTON_X_OFFSET, "y": HOME_BUTTON_Y_OFFSET, "z": -HOME_BUTTON_Z_OFFSET },        "localRotation": Quat.fromVec3Degrees({ "x": 90, "y": 0, "z": 0}),        "dimensions": { "x": homeButtonDim, "y": homeButtonDim, "z": homeButtonDim },        "primitiveMode": "solid",        "ring": {            "innerStartColor": {"red": 255, "green": 255, "blue": 255},            "innerEndColor": {"red": 255, "green": 255, "blue": 255},            "outerStartColor": {"red": 255, "green": 255, "blue": 255},            "outerEndColor": {"red": 255, "green": 255, "blue": 255},            "innerStartAlpha": 0.0,            "innerEndAlpha": 0.0,            "outerStartAlpha": 0.0,            "outerEndAlpha": 0.0,            "innerRadius": 0.9,        },        "ignorePickIntersection": true,        "visible": visible,        "renderLayer": "world",        "grab": {             "grabbable": false        },                "parentID": this.tabletEntityID    }, "local");     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);    Entities.editEntity(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);    Entities.editEntity(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 = Entities.getEntityProperties(this.tabletEntityID, ["localPosition"]).localPosition;    var orientation = Entities.getEntityProperties(this.tabletEntityID, ["localRotation"]).localRotation;    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) {    Entities.editEntity(this.webOverlayID, { "sourceUrl": url });}; WebTablet.prototype.setScriptURL = function (scriptURL) {    Entities.editEntity(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 () {    Entities.deleteEntity(this.webOverlayID);    Entities.deleteEntity(this.tabletEntityID);    Entities.deleteEntity(this.homeButtonID);    Entities.deleteEntity(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);        Entities.editEntity(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 = hifiClamp(windowPos.x, X_CLAMP, Window.innerWidth - X_CLAMP);    windowPos.y = hifiClamp(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 tabletWebTablet.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);    Entities.editEntity(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);

Lines 1–500 of 637.