Needs a lot of cleanup. Data has been de-duplicated, and where identical copies existed, one of them has been replaced with a symlink. Some files have been excluded, such as binaries, installers and debug dumps. Some of that may still be present.
471 lines
16 KiB
JavaScript
471 lines
16 KiB
JavaScript
"use strict";
|
|
/*jslint vars:true, plusplus:true, forin:true*/
|
|
/*global Tablet, Script, */
|
|
/* eslint indent: ["error", 4, { "outerIIFEBody": 1 }] */
|
|
//
|
|
// portal.js
|
|
//
|
|
// Created by Zach Fox on 2018-11-07
|
|
// Copyright 2018 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 () { // BEGIN LOCAL_SCOPE
|
|
var AppUi = Script.require('appUi');
|
|
|
|
// CONSTS START
|
|
const PORTAL_Z_DIMENSION = 0.005;
|
|
const PORTAL_OVERLAY_OFFSET = PORTAL_Z_DIMENSION * 2 + 0.05; // In meters, the distance between the Portal entity and the Portal overlay
|
|
const PORTAL_OVERLAY_UPDATE_INTERVAL_MS = 50;
|
|
const PORTAL_ZONE_Z_DIMENSION = 0.2;
|
|
const PORTAL_ZONE_TP_OFFSET = PORTAL_ZONE_Z_DIMENSION + 0.05;
|
|
const SECONDARY_CAMERA_RESOLUTION = 1024; // width/height multiplier, in pixels
|
|
// CONSTS END
|
|
|
|
var spectatorCameraConfig = Render.getConfig("SecondaryCamera");
|
|
var previousPortalOverlayDimensions = { x: 0, y: 0 }; // The previous dimensions of the Portal Overlay
|
|
var currentPortalWithOverlay = false;
|
|
var portalOverlay = false;
|
|
var portalOverlayUpdateTimer = false;
|
|
function updatePortalOverlayDimensions(forceUpdate) {
|
|
if (!currentPortalWithOverlay) {
|
|
console.log("Portal is active, but Portal doesn't have an associated entity!");
|
|
return;
|
|
}
|
|
|
|
if (!currentPortalWithOverlay) {
|
|
console.log("Portal is active, but it doesn't have an associated overlay!");
|
|
return;
|
|
}
|
|
|
|
var newDimensions = Entities.getEntityProperties(currentPortalWithOverlay, 'dimensions').dimensions;
|
|
if (forceUpdate === true ||
|
|
(newDimensions.x !== previousPortalOverlayDimensions.x ||
|
|
newDimensions.y !== previousPortalOverlayDimensions.y)) {
|
|
if (portalCameraRunning) {
|
|
spectatorCameraConfig.resetSizeSpectatorCamera(newDimensions.x * SECONDARY_CAMERA_RESOLUTION,
|
|
newDimensions.y * SECONDARY_CAMERA_RESOLUTION);
|
|
}
|
|
Overlays.editOverlay(portalOverlay, {
|
|
dimensions: {
|
|
x: (newDimensions.y > newDimensions.x ? newDimensions.y : newDimensions.x),
|
|
y: -(newDimensions.y > newDimensions.x ? newDimensions.y : newDimensions.x),
|
|
z: 0
|
|
}
|
|
});
|
|
}
|
|
previousPortalOverlayDimensions = newDimensions;
|
|
}
|
|
|
|
// Function Name: inFrontOf()
|
|
//
|
|
// Description:
|
|
// -Returns the position in front of the given "position" argument, where the forward vector is based off
|
|
// the "orientation" argument and the amount in front is based off the "distance" argument.
|
|
function inFrontOf(distance, position, orientation) {
|
|
return Vec3.sum(position || MyAvatar.position,
|
|
Vec3.multiply(distance, Quat.getForward(orientation || MyAvatar.orientation)));
|
|
}
|
|
|
|
var portalAScript = (function () {
|
|
this.enterEntity = function (entityID) {
|
|
var portalA = Entities.getEntityProperties(entityID, ['parentID']).parentID;
|
|
var portalB = Entities.getEntityProperties(portalA, ['userData']).userData;
|
|
var portalBProperties = Entities.getEntityProperties(portalB, ['position', 'rotation']);
|
|
// Uses `PORTAL_ZONE_TP_OFFSET` - if you change that const, change this value.
|
|
MyAvatar.position = Vec3.sum(portalBProperties.position, Vec3.multiply(0.25, Quat.getForward(portalBProperties.rotation)));
|
|
MyAvatar.orientation = portalBProperties.rotation;
|
|
};
|
|
});
|
|
|
|
var portalBScript = (function () {
|
|
this.enterEntity = function (entityID) {
|
|
var portalB = Entities.getEntityProperties(entityID, ['parentID']).parentID;
|
|
var portalA = Entities.getEntityProperties(portalB, ['userData']).userData;
|
|
var portalAProperties = Entities.getEntityProperties(portalA, ['position', 'rotation']);
|
|
// Uses `PORTAL_ZONE_TP_OFFSET` - if you change that const, change this value.
|
|
MyAvatar.position = Vec3.sum(portalAProperties.position, Vec3.multiply(0.25, Quat.getForward(portalAProperties.rotation)));
|
|
MyAvatar.orientation = portalAProperties.rotation;
|
|
};
|
|
});
|
|
|
|
var portalAEntity = false;
|
|
var portalAZone = false;
|
|
function rezPortalA() {
|
|
setPortalCameraStatus(false);
|
|
|
|
if (portalAEntity) {
|
|
Entities.deleteEntity(portalAEntity);
|
|
}
|
|
|
|
portalAEntity = Entities.addEntity({
|
|
"collidesWith": "static,dynamic,kinematic,otherAvatar,",
|
|
"collisionMask": 23,
|
|
"collisionless": true,
|
|
"color": {
|
|
"blue": 239,
|
|
"green": 180,
|
|
"red": 0
|
|
},
|
|
"dimensions": {
|
|
"x": 2,
|
|
"y": 2,
|
|
"z": PORTAL_Z_DIMENSION
|
|
},
|
|
"grab": {
|
|
"grabbable": true
|
|
},
|
|
"ignoreForCollisions": true,
|
|
"shape": "Cube",
|
|
"type": "Box",
|
|
"position": inFrontOf(2, Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 })),
|
|
"rotation": Quat.multiply(MyAvatar.orientation, { w: 0, x: 0, y: 1, z: 0 }),
|
|
"userData": portalBEntity || ""
|
|
});
|
|
|
|
portalAZone = Entities.addEntity({
|
|
"dimensions": {
|
|
"x": 2,
|
|
"y": 2,
|
|
"z": PORTAL_ZONE_Z_DIMENSION
|
|
},
|
|
"grab": {
|
|
"grabbable": false
|
|
},
|
|
"shapeType": "box",
|
|
"type": "Zone",
|
|
"parentID": portalAEntity,
|
|
"script": "(" + portalAScript + ")"
|
|
});
|
|
|
|
if (portalBEntity) {
|
|
Entities.editEntity(portalBEntity, { "userData": portalAEntity });
|
|
}
|
|
|
|
currentPortalWithOverlay = portalAEntity;
|
|
|
|
if (portalAEntity && portalBEntity) {
|
|
setPortalCameraStatus(true);
|
|
rezPortalOverlay(currentPortalWithOverlay);
|
|
}
|
|
}
|
|
|
|
var portalBEntity = false;
|
|
var portalBZone = false;
|
|
function rezPortalB() {
|
|
setPortalCameraStatus(false);
|
|
|
|
if (portalBEntity) {
|
|
Entities.deleteEntity(portalBEntity);
|
|
portalBEntity = false;
|
|
}
|
|
|
|
portalBEntity = Entities.addEntity({
|
|
"collidesWith": "static,dynamic,kinematic,otherAvatar,",
|
|
"collisionMask": 23,
|
|
"collisionless": true,
|
|
"color": {
|
|
"blue": 0,
|
|
"green": 180,
|
|
"red": 239
|
|
},
|
|
"dimensions": {
|
|
"x": 2,
|
|
"y": 2,
|
|
"z": PORTAL_Z_DIMENSION
|
|
},
|
|
"grab": {
|
|
"grabbable": true
|
|
},
|
|
"ignoreForCollisions": true,
|
|
"shape": "Cube",
|
|
"type": "Box",
|
|
"position": inFrontOf(2, Vec3.sum(MyAvatar.position, { x: 0, y: 0.5, z: 0 })),
|
|
"rotation": Quat.multiply(MyAvatar.orientation, { w: 0, x: 0, y: 1, z: 0 }),
|
|
"userData": portalAEntity || ""
|
|
});
|
|
|
|
portalBZone = Entities.addEntity({
|
|
"dimensions": {
|
|
"x": 2,
|
|
"y": 2,
|
|
"z": PORTAL_ZONE_Z_DIMENSION
|
|
},
|
|
"grab": {
|
|
"grabbable": false
|
|
},
|
|
"shapeType": "box",
|
|
"type": "Zone",
|
|
"parentID": portalBEntity,
|
|
"script": "(" + portalBScript + ")"
|
|
});
|
|
|
|
if (portalAEntity) {
|
|
Entities.editEntity(portalAEntity, {"userData": portalBEntity});
|
|
}
|
|
|
|
currentPortalWithOverlay = portalBEntity;
|
|
|
|
if (portalAEntity && portalBEntity) {
|
|
setPortalCameraStatus(true);
|
|
rezPortalOverlay(currentPortalWithOverlay);
|
|
}
|
|
}
|
|
|
|
function rezPortalOverlay(parentPortalEntity) {
|
|
if (!parentPortalEntity) {
|
|
console.log("Tried to rez portal overlay, but no parent portal entity was defined!");
|
|
return;
|
|
}
|
|
|
|
maybeDestroyPortalOverlay();
|
|
|
|
portalOverlay = Overlays.addOverlay("image3d", {
|
|
name: "portalOverlay",
|
|
url: "resource://spectatorCameraFrame",
|
|
emissive: true,
|
|
parentID: parentPortalEntity,
|
|
alpha: 1,
|
|
localRotation: {
|
|
w: 0,
|
|
x: 0,
|
|
y: 1,
|
|
z: 0
|
|
},
|
|
localPosition: {
|
|
x: 0,
|
|
y: 0,
|
|
z: -PORTAL_OVERLAY_OFFSET
|
|
}
|
|
});
|
|
updatePortalOverlayDimensions(true);
|
|
|
|
if (portalOverlayUpdateTimer) {
|
|
Script.clearInterval(portalOverlayUpdateTimer);
|
|
}
|
|
portalOverlayUpdateTimer = Script.setInterval(updatePortalOverlayDimensions, PORTAL_OVERLAY_UPDATE_INTERVAL_MS);
|
|
|
|
if (portalDistanceInterval) {
|
|
Script.clearInterval(portalDistanceInterval);
|
|
}
|
|
portalDistanceInterval = Script.setInterval(portalDistanceCheck, PORTAL_DISTANCE_INTERVAL_MS);
|
|
}
|
|
|
|
function setPortalEntranceAndExitCameras() {
|
|
spectatorCameraConfig.portalEntranceEntityId = null;
|
|
spectatorCameraConfig.attachedEntityId = null;
|
|
if (currentPortalWithOverlay === portalAEntity) {
|
|
spectatorCameraConfig.portalEntranceEntityId = portalAEntity;
|
|
spectatorCameraConfig.attachedEntityId = portalBEntity;
|
|
} else {
|
|
spectatorCameraConfig.portalEntranceEntityId = portalBEntity;
|
|
spectatorCameraConfig.attachedEntityId = portalAEntity;
|
|
}
|
|
}
|
|
|
|
var portalCameraRunning = false;
|
|
function setPortalCameraStatus(enabled) {
|
|
if (enabled) {
|
|
if (!(portalAEntity && portalBEntity)) {
|
|
console.log("Can't enable Portal camera if either Portal A or Portal B are not rezzed.");
|
|
return;
|
|
}
|
|
|
|
if (portalCameraRunning) {
|
|
console.log("Portal camera already running.");
|
|
return;
|
|
}
|
|
|
|
portalCameraRunning = true;
|
|
spectatorCameraConfig.portalProjection = true;
|
|
|
|
setPortalEntranceAndExitCameras();
|
|
|
|
Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 0;
|
|
spectatorCameraConfig.enableSecondaryCameraRenderConfigs(true);
|
|
|
|
rezPortalOverlay(currentPortalWithOverlay);
|
|
} else {
|
|
if (!portalCameraRunning) {
|
|
console.log("User tried to disable the portal camera, but it was already off!");
|
|
return;
|
|
}
|
|
|
|
maybeDestroyPortalOverlay();
|
|
|
|
spectatorCameraConfig.enableSecondaryCameraRenderConfigs(false);
|
|
spectatorCameraConfig.portalProjection = false;
|
|
spectatorCameraConfig.portalEntranceEntityId = null;
|
|
spectatorCameraConfig.attachedEntityId = null;
|
|
Render.getConfig("SecondaryCameraJob.ToneMapping").curve = 1;
|
|
portalCameraRunning = false;
|
|
}
|
|
}
|
|
|
|
var portalDistanceInterval = false;
|
|
const PORTAL_DISTANCE_INTERVAL_MS = 200;
|
|
function portalDistanceCheck() {
|
|
if (!(portalAEntity && portalBEntity)) {
|
|
return;
|
|
}
|
|
|
|
var otherPortalWithOverlay;
|
|
if (currentPortalWithOverlay === portalAEntity) {
|
|
otherPortalWithOverlay = portalBEntity;
|
|
} else {
|
|
otherPortalWithOverlay = portalAEntity;
|
|
}
|
|
|
|
var entitiesInCameraFrustum = Entities.findEntitiesInFrustum(Camera.frustum);
|
|
// If neither portals are in view, we don't care
|
|
if (entitiesInCameraFrustum.indexOf(currentPortalWithOverlay) === -1 &&
|
|
entitiesInCameraFrustum.indexOf(otherPortalWithOverlay) === -1) {
|
|
return;
|
|
}
|
|
|
|
var currentPortalWithOverlayPosition = Entities.getEntityProperties(currentPortalWithOverlay, ['position']).position;
|
|
var otherPortalWithOverlayPosition = Entities.getEntityProperties(otherPortalWithOverlay, ['position']).position;
|
|
var cameraPosition = Camera.position;
|
|
|
|
var distanceToCurrentPortal = Vec3.distance(currentPortalWithOverlayPosition, cameraPosition);
|
|
var distanceToOtherPortal = Vec3.distance(otherPortalWithOverlayPosition, cameraPosition);
|
|
|
|
var newPortalWithOverlay = currentPortalWithOverlay;
|
|
|
|
if (distanceToOtherPortal < distanceToCurrentPortal ||
|
|
(entitiesInCameraFrustum.indexOf(currentPortalWithOverlay) === -1 &&
|
|
entitiesInCameraFrustum.indexOf(otherPortalWithOverlay) > -1)) {
|
|
newPortalWithOverlay = otherPortalWithOverlay;
|
|
}
|
|
|
|
// Make sure the new portal is visible
|
|
if (currentPortalWithOverlay !== newPortalWithOverlay &&
|
|
entitiesInCameraFrustum.indexOf(newPortalWithOverlay) > -1) {
|
|
currentPortalWithOverlay = newPortalWithOverlay;
|
|
rezPortalOverlay(currentPortalWithOverlay);
|
|
setPortalEntranceAndExitCameras();
|
|
}
|
|
}
|
|
|
|
function disablePortals() {
|
|
setPortalCameraStatus(false);
|
|
|
|
if (portalAZone) {
|
|
Entities.editEntity(portalAZone, { "script": "" });
|
|
}
|
|
|
|
if (portalBZone) {
|
|
Entities.editEntity(portalBZone, { "script": "" });
|
|
}
|
|
|
|
if (portalDistanceInterval) {
|
|
Script.clearInterval(portalDistanceInterval);
|
|
portalDistanceInterval = false;
|
|
}
|
|
}
|
|
|
|
function enablePortals() {
|
|
setPortalCameraStatus(true);
|
|
|
|
if (portalAZone) {
|
|
Entities.editEntity(portalAZone, { "script": "(" + portalAScript + ")" });
|
|
}
|
|
|
|
if (portalBZone) {
|
|
Entities.editEntity(portalBZone, { "script": "(" + portalBScript + ")" });
|
|
}
|
|
}
|
|
|
|
// Function Name: fromQml()
|
|
//
|
|
// Description:
|
|
// -Called when a message is received from Portal.qml. The "message" argument is what is sent from the Portal QML
|
|
// in the format "{method, params}", like json-rpc. See also sendToQml().
|
|
function fromQml(message) {
|
|
switch (message.method) {
|
|
case 'disablePortals':
|
|
disablePortals();
|
|
break;
|
|
case 'enablePortals':
|
|
enablePortals();
|
|
break;
|
|
case 'rezPortalA':
|
|
rezPortalA();
|
|
break;
|
|
case 'rezPortalB':
|
|
rezPortalB();
|
|
break;
|
|
default:
|
|
print('Unrecognized message from Portal.qml');
|
|
}
|
|
}
|
|
|
|
function initializeUI() {
|
|
ui.sendMessage({
|
|
method: 'initializeUI',
|
|
portalsEnabled: portalCameraRunning,
|
|
portalARezzed: !!portalAEntity,
|
|
portalBRezzed: !!portalBEntity
|
|
});
|
|
}
|
|
|
|
function maybeDestroyPortalOverlay() {
|
|
if (portalOverlayUpdateTimer) {
|
|
Script.clearInterval(portalOverlayUpdateTimer);
|
|
portalOverlayUpdateTimer = false;
|
|
}
|
|
|
|
if (portalOverlay) {
|
|
Overlays.deleteOverlay(portalOverlay);
|
|
portalOverlay = false;
|
|
}
|
|
}
|
|
|
|
function destroyPortals() {
|
|
disablePortals();
|
|
|
|
maybeDestroyPortalOverlay();
|
|
|
|
if (portalAEntity) {
|
|
Entities.deleteEntity(portalAEntity);
|
|
portalAEntity = false;
|
|
}
|
|
|
|
if (portalBEntity) {
|
|
Entities.deleteEntity(portalBEntity);
|
|
portalBEntity = false;
|
|
}
|
|
}
|
|
|
|
// Function Name: onDomainChanged()
|
|
//
|
|
// Description:
|
|
// -A small utility function used when the Window.domainChanged() signal is fired.
|
|
function onDomainChanged() {
|
|
destroyPortals();
|
|
}
|
|
|
|
var ui;
|
|
function startup() {
|
|
ui = new AppUi({
|
|
buttonName: "PORTAL",
|
|
home: Script.resolvePath("./Portal.qml"),
|
|
graphicsDirectory: Script.resolvePath("./"),
|
|
onMessage: fromQml,
|
|
onOpened: initializeUI
|
|
});
|
|
|
|
Window.domainChanged.connect(onDomainChanged);
|
|
}
|
|
|
|
function shutdown() {
|
|
destroyPortals();
|
|
|
|
Window.domainChanged.disconnect(onDomainChanged);
|
|
}
|
|
|
|
startup();
|
|
Script.scriptEnding.connect(shutdown);
|
|
}()); // END LOCAL_SCOPE
|