// // mirrorClient.js // // Created by Patrick Manalich // Edited by Rebecca Stankus on 8/30/17. // Edited by David Back on 11/17/17. // Edited by Anna Brewer on 7/12/19. // Copyright 2017 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 // // Attach `mirrorClient.js` to a box entity whose z dimension is very small, // and whose x and y dimensions are up to you. See comments in `mirrorReflection.js` // for more information about the mirror on/off zone. "use strict"; (function () { // BEGIN LOCAL SCOPE // VARIABLES /* globals utils, Render */ var _this = this; var MAX_MIRROR_RESOLUTION_SIDE_PX = 960; // The max pixel resolution of the long side of the mirror var ZERO_ROT = { w: 1, x: 0, y: 0, z: 0 }; // Constant quaternion for a rotation of 0 var FAR_CLIP_DISTANCE = 16; // The far clip distance for the spectator camera when the mirror is on var mirrorLocalEntityID = false; // The entity ID of the local entity that displays the mirror reflection var mirrorLocalEntityRunning; // True if mirror local entity is reflecting, false otherwise var mirrorLocalEntityOffset = 0.01; // The distance between the center of the mirror and the mirror local entity var spectatorCameraConfig = Render.getConfig("SecondaryCamera"); // Render configuration for the spectator camera var lastDimensions = { x: 0, y: 0 }; // The previous dimensions of the mirror var previousFarClipDistance; // Store the specator camera's previous far clip distance that we override for the mirror // LOCAL FUNCTIONS function isPositionInsideBox(position, boxProperties) { var localPosition = Vec3.multiplyQbyV(Quat.inverse(boxProperties.rotation), Vec3.subtract(MyAvatar.position, boxProperties.position)); var halfDimensions = Vec3.multiply(boxProperties.dimensions, 0.5); return -halfDimensions.x <= localPosition.x && halfDimensions.x >= localPosition.x && -halfDimensions.y <= localPosition.y && halfDimensions.y >= localPosition.y && -halfDimensions.z <= localPosition.z && halfDimensions.z >= localPosition.z; } // When x or y dimensions of the mirror change - reset the resolution of the // spectator camera and edit the mirror local entity to adjust for the new dimensions function updateMirrorDimensions(forceUpdate) { if (!mirrorLocalEntityID) { return; } if (mirrorLocalEntityRunning) { var newDimensions = Entities.getEntityProperties(_this.entityID, 'dimensions').dimensions; if (forceUpdate === true || (newDimensions.x != lastDimensions.x || newDimensions.y != lastDimensions.y)) { var mirrorResolution = _this.calculateMirrorResolution(newDimensions); spectatorCameraConfig.resetSizeSpectatorCamera(mirrorResolution.x, mirrorResolution.y); Entities.editEntity(mirrorLocalEntityID, { dimensions: { x: (Math.max(newDimensions.x, newDimensions.y)), y: (Math.max(newDimensions.x, newDimensions.y)), z: 0 } }); } lastDimensions = newDimensions; } } // Takes in an mirror scalar number which is used for the index of "halfDimSigns" that is needed to adjust the mirror // local entity's position. Deletes and re-adds the mirror local entity so the url and position are updated. function createMirrorLocalEntity() { if (mirrorLocalEntityID) { Entities.deleteEntity(mirrorLocalEntityID); mirrorLocalEntityID = false; } if (mirrorLocalEntityRunning) { mirrorLocalEntityID = Entities.addEntity({ type: "Image", name: "mirrorLocalEntity", imageURL: "resource://spectatorCameraFrame", emissive: true, parentID: _this.entityID, alpha: 1, localPosition: { x: 0, y: 0, z: mirrorLocalEntityOffset }, localRotation: Quat.fromPitchYawRollDegrees(0, 0, 180), isVisibleInSecondaryCamera: false }, "local"); updateMirrorDimensions(true); } } _this.calculateMirrorResolution = function(entityDimensions) { var mirrorResolutionX, mirrorResolutionY; if (entityDimensions.x > entityDimensions.y) { mirrorResolutionX = MAX_MIRROR_RESOLUTION_SIDE_PX; mirrorResolutionY = Math.round(mirrorResolutionX * entityDimensions.y / entityDimensions.x); } else { mirrorResolutionY = MAX_MIRROR_RESOLUTION_SIDE_PX; mirrorResolutionX = Math.round(mirrorResolutionY * entityDimensions.x / entityDimensions.y); } var resolution = { "x": mirrorResolutionX, "y": mirrorResolutionY }; return resolution; }; // Sets up spectator camera to render the mirror, calls 'createMirrorLocalEntity' once to set up // mirror local entity, then connects 'updateMirrorDimensions' to update dimension changes _this.mirrorLocalEntityOn = function(onPreload) { if (!mirrorLocalEntityRunning) { if (!spectatorCameraConfig.attachedEntityId) { mirrorLocalEntityRunning = true; spectatorCameraConfig.mirrorProjection = true; spectatorCameraConfig.attachedEntityId = _this.entityID; previousFarClipDistance = spectatorCameraConfig.farClipPlaneDistance; spectatorCameraConfig.farClipPlaneDistance = FAR_CLIP_DISTANCE; var entityProperties = Entities.getEntityProperties(_this.entityID, ['dimensions']); var mirrorEntityDimensions = entityProperties.dimensions; var initialResolution = _this.calculateMirrorResolution(mirrorEntityDimensions); spectatorCameraConfig.resetSizeSpectatorCamera(initialResolution.x, initialResolution.y); spectatorCameraConfig.enableSecondaryCameraRenderConfigs(true); createMirrorLocalEntity(); Script.update.connect(updateMirrorDimensions); } else { print("Cannot turn on mirror if spectator camera is already in use"); } } }; // Resets spectator camera, deletes the mirror local entity, and disconnects 'updateMirrorDimensions' _this.mirrorLocalEntityOff = function() { if (mirrorLocalEntityRunning) { spectatorCameraConfig.enableSecondaryCameraRenderConfigs(false); spectatorCameraConfig.mirrorProjection = false; spectatorCameraConfig.attachedEntityId = null; spectatorCameraConfig.farClipPlaneDistance = previousFarClipDistance; if (mirrorLocalEntityID) { Entities.deleteEntity(mirrorLocalEntityID); mirrorLocalEntityID = false; } Script.update.disconnect(updateMirrorDimensions); mirrorLocalEntityRunning = false; } }; // ENTITY FUNCTIONS _this.preload = function(entityID) { _this.entityID = entityID; mirrorlocalEntityRunning = false; // If avatar is already inside the mirror zone at the time preload is called then turn on the mirror var children = Entities.getChildrenIDs(_this.entityID); var childZero = Entities.getEntityProperties(children[0]); if (isPositionInsideBox(MyAvatar.position, { position: childZero.position, rotation: childZero.rotation, dimensions: childZero.dimensions })) { _this.mirrorLocalEntityOn(true); } }; // Turn off mirror on unload _this.unload = function(entityID) { _this.mirrorLocalEntityOff(); }; });