content/hifi-public/sam/temp/holo/Portal/portal.js
Dale Glass 0d14e5a379 Initial data.
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.
2022-02-13 18:59:11 +01:00

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