// // Created by Luis Cuenca on 10/27/18 // 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 // /* jslint bitwise: true */ /* global Script, Overlays, Controller, Entities, MyAvatar, Quat, Vec3, AvatarList, Tablet, Render */ (function() { var shown = false; // ["{0b3f7741-9d91-4313-a01c-e857f68d71a0}", "{90143a7c-c83f-4ab7-b208-df4826ed03b1}", "{c15b9c1b-2d7a-4003-a23d-e10c74e47dfc}","{139ca1f2-7f7c-4238-a5ac-8b2f97a20853}"]; var fieldID = "{dbb98a91-749b-4681-a465-1767776d0313}"; var fieldProps = Entities.getEntityProperties(fieldID, ["position", "dimensions"]); var fieldPosition = fieldProps.position; var fieldDimensions = fieldProps.dimensions; var monitorsIDs = Entities.findEntitiesByName("Monitor", fieldPosition, 1.5 * Math.max(fieldDimensions.x, fieldDimensions.z)); var ballIDs = Entities.findEntitiesByName("Soccer Ball", fieldPosition, 1.5 * Math.max(fieldDimensions.x, fieldDimensions.z)); var createMonitors = false; var spectatorCameraConfig = Render.getConfig("SecondaryCamera"); var camera; var cameraPosition; var cameraRotation; var CAMERA_FOLLOW_ENTITY = 0; var CAMERA_FOLLOW_AVATAR = 1; var CAMERA_CONSTRAINT_NONE = 0; var CAMERA_CONSTRAINT_POINT = 1; var CAMERA_CONSTRAINT_LINE = 2; var CAMERA_CONSTRAINT_PLANE = 3; var cameraRig; var CameraFollow = function(type, id) { this.type = type; this.id = id; } var CameraConstraint = function(type, vector){ var self = this; this.type = type; this.vector = Vec3.normalize(vector); this.uVector; this.vVector; this.calculatePlaneUV = function() { if (vector.x === 0 && vector.y === 1 && vector.z === 0) { self.uVector = {x: 1, y: 0, z: 0}; self.vVector = {x: 0, y: 0, z: 1}; } else { self.vVector = Vec3.normalize(Vec3.cross(self.vector, {x: 1, y: 0, z: 0})); self.uVector = Vec3.normalize(Vec3.cross(self.vVector, self.vector)); } } if (type === CAMERA_CONSTRAINT_PLANE) { self.calculatePlaneUV(); } } var CameraNode = function(position, orientation, constraint, follows) { var self = this; this.follows = follows; this.constraint = constraint; this.followPoint; this.position = position; this.orientation = orientation; this.distanceVector; this.velocityVector; this.computeFollowPoint = function() { self.followPoint = Vec3.Zero; for (var i = 0; i < self.follows.length; i++) { var follow = self.follows[i]; if (follow.type === CAMERA_FOLLOW_AVATAR) { self.followPoint = Vec3.sum(self.followPoint, AvatarList.getAvatar(follow.id).position); } else if (follow.type === CAMERA_FOLLOW_ENTITY) { self.followPoint = Vec3.sum(self.followPoint, Entities.getEntityProperties(follow.id, ["position"]).position); } self.followPoint = Vec3.multiply(self.followPoint, 1.0/self.follows.length); } } this.computePosition = function() { self.distanceVector = Vec3.subtract(self.followPoint, self.position); if (self.constraint.type != CAMERA_CONSTRAINT_POINT) { if (self.constraint.type === CAMERA_CONSTRAINT_NONE) { self.velocityVector = 0.2 * self.distanceVector; } else if (self.constraint.type === CAMERA_CONSTRAINT_LINE) { self.velocityVector = Vec3.multiply(self.constraint.vector, 0.01 * Vec3.dot(self.distanceVector, self.constraint.vector)); } else if (self.constraint.type === CAMERA_CONSTRAINT_PLANE) { var uvec = Vec3.multiply(self.constraint.uVector ,0.01 * Vec3.dot(self.distanceVector, self.constraint.uVector)); var vvec = Vec3.multiply(self.constraint.vVector ,0.01 * Vec3.dot(self.distanceVector, self.constraint.vVector)); self.velocityVector = Vec3.sum(uvec, vvec); } } self.position = Vec3.sum(self.position, self.velocityVector); } this.computeOrientation = function() { self.distanceVector = Vec3.subtract(self.followPoint, self.position); var lookAtVector = Vec3.normalize(self.distanceVector); self.orientation = Quat.lookAtSimple({x:0, y:0, z:0}, lookAtVector); } this.update = function() { self.computeFollowPoint(); self.computePosition(); self.computeOrientation(); } } var Monitor = function(entityID, position, rotation, dimensions) { var self = this; this.position = position; this.rotation = rotation; this.dimensions = dimensions; this.entityID = entityID; this.overlayID; this.created = false; this.remove = function() { if (self.entityID && self.created) { Entities.deleteEntity(self.entityID); } if (self.overlayID) { Overlays.deleteOverlay(self.overlayID); } } this.create = function() { self.remove(); if (!self.entityID) { self.entityID = Entities.addEntity({ name: "Monitor", type: "Box", dimensions: self.dimensions, position: self.position, rotation: self.rotation }) self.created = true; } else { var props = Entities.getEntityProperties(self.entityID, ["dimensions", "position", "rotation"]); self.dimensions = props.dimensions; self.position = props.position; self.rotation = props.rotation; } var overlayDimensions = { x: self.dimensions.x, y: self.dimensions.x, z: 0.0}; self.overlayID = Overlays.addOverlay("image3d", { url: "resource://spectatorCameraFrame", emissive: true, parentID: self.entityID, localRotation: { w: 0, x: 0, y: 0, z: 1.0 }, localPosition: { x: 0.0, y: 0.0, z: -0.6 * self.dimensions.z }, alpha: 1, dimensions: overlayDimensions }); } self.create(); } var CameraRig = function(length, width, monitorLength, yOffset) { var self = this; this.length = length; this.width = width; this.cameraRotation; this.cameraPosition; this.cameraNodes = []; this.monitors = []; this.emitIndex = 0; this.monitorDimensions = { x: monitorLength, y: 0.66 * monitorLength, z: 0.03 * monitorLength }; this.frontVector = {x: 1, y: 0, z: 0}; this.backVector = {x: -1, y: 0, z: 0}; this.rightVector = {x: 0, y: 0, z: 1}; this.leftVector = {x: 0, y: 0, z: -1}; this.offset = {x: 0, y: yOffset, z: 0}; /* this.frontVector = Quat.getFront(MyAvatar.orientation); this.backVector = Vec3.multiply(self.frontVector, -1); this.rightVector = Quat.getRight(MyAvatar.orientation); this.leftVector = Vec3.multiply(self.rightVector, -1); */ this.changeCount = 500; this.changeTimer = 0; this.createRig = function() { var follow = new CameraFollow(CAMERA_FOLLOW_ENTITY, ballIDs[0]); var constraint1 = new CameraConstraint(CAMERA_CONSTRAINT_LINE, self.rightVector); var position1 = Vec3.sum(Vec3.sum(self.center, Vec3.multiply(self.frontVector, 10.0)), {x: 0, y: 2.0, z: 0}); var orientation1 = Quat.lookAtSimple({x:0, y:0, z:0}, self.leftVector); var node1 = new CameraNode(position1, orientation1, constraint1, [follow]); var constraint2 = new CameraConstraint(CAMERA_CONSTRAINT_PLANE, Vec3.sum(self.frontVector, {x: 0, y: 5, z: 0})); var position2 = Vec3.sum(Vec3.sum(self.center, Vec3.multiply(self.frontVector, 10.0)), {x: 0, y: 5.0, z: 0}); var orientation2 = Quat.lookAtSimple({x:0, y:0, z:0}, self.backVector); var node2 = new CameraNode(position2, orientation2, constraint2, [follow]); var constraint3 = new CameraConstraint(CAMERA_CONSTRAINT_LINE, Vec3.sum(self.rightVector, self.frontVector)); var position3 = Vec3.sum(self.center, {x: 0, y: 0.5, z: 0}); var orientation3 = Quat.lookAtSimple({x:0, y:0, z:0}, self.leftVector); var node3 = new CameraNode(position3, orientation3, constraint3, [follow]); self.cameraNodes.push(node1); self.cameraNodes.push(node2); self.cameraNodes.push(node3); } this.createMonitors = function() { if (createMonitors) { var centerPosition = Vec3.sum(MyAvatar.position, self.offset); centerPosition.y += 0.5 * (self.monitorDimensions.y - MyAvatar.getHeight()); var pos1 = Vec3.sum(centerPosition, Vec3.multiply(self.frontVector, 0.5 * self.length)); var pos2 = Vec3.sum(centerPosition, Vec3.multiply(self.rightVector, 0.5 * self.width)); var pos3 = Vec3.sum(centerPosition, Vec3.multiply(self.backVector, 0.5 * self.length)); var pos4 = Vec3.sum(centerPosition, Vec3.multiply(self.leftVector, 0.5 * self.width)); var ori1 = Quat.lookAtSimple({x:0, y:0, z:0}, Vec3.multiply(self.frontVector, -1)); var ori2 = Quat.lookAtSimple({x:0, y:0, z:0}, Vec3.multiply(self.rightVector, -1)); var ori3 = Quat.lookAtSimple({x:0, y:0, z:0}, Vec3.multiply(self.backVector, -1)); var ori4 = Quat.lookAtSimple({x:0, y:0, z:0}, Vec3.multiply(self.leftVector, -1)); self.monitors.push(new Monitor(undefined, pos1, ori1, self.monitorDimensions)); self.monitors.push(new Monitor(undefined, pos2, ori2, self.monitorDimensions)); self.monitors.push(new Monitor(undefined, pos3, ori3, self.monitorDimensions)); self.monitors.push(new Monitor(undefined, pos4, ori4, self.monitorDimensions)); } else { for (var i = 0; i < monitorsIDs.length; i++) { self.monitors.push(new Monitor(monitorsIDs[i])); } } if (!fieldPosition) { self.center = {x: 0, y: 0, z: 0}; for (var i = 0; i < self.monitors.length; i++) { self.center = Vec3.sum(self.center, self.monitors[i].position); } self.center = Vec3.multiply(self.center, 1.0/self.monitors.length); } else { self.center = fieldPosition; } } this.create = function() { self.createMonitors(); self.createRig(); } this.cleanup = function() { for (var i = 0; i < self.monitors.length; i++) { self.monitors[i].remove(); } self.monitors = []; } this.emitFromCamera = function(index) { if (index < self.cameraNodes.length) { self.emitIndex = index; } } this.update = function() { self.changeTimer += 1; if (self.emitIndex < self.cameraNodes.length) { for (var i = 0; i < self.cameraNodes.length; i++) { self.cameraNodes[i].update(); } spectatorCameraConfig.setPosition(self.cameraNodes[self.emitIndex].position); spectatorCameraConfig.setOrientation(self.cameraNodes[self.emitIndex].orientation); } if (self.changeTimer > self.changeCount) { self.changeTimer = 0; self.emitIndex = self.emitIndex + 1 >= self.cameraNodes.length ? 0 : self.emitIndex + 1; ballIDs = Entities.findEntitiesByName("Soccer Ball", fieldPosition, 1.5 * Math.max(fieldDimensions.x, fieldDimensions.z)); } self.emitFromCamera(self.emitIndex); } self.create(); } function spectatorCameraOn() { spectatorCameraConfig.enableSecondaryCameraRenderConfigs(true); spectatorCameraConfig.resetSizeSpectatorCamera(Window.innerWidth, Window.innerHeight); cameraRig = new CameraRig(45, 90, 20.0, 3.0); } function spectatorCameraOff() { spectatorCameraConfig.attachedEntityId = false; spectatorCameraConfig.enableSecondaryCameraRenderConfigs(false); if (cameraRig) { cameraRig.cleanup(); } } function shutdownTabletApp() { spectatorCameraOff(); } function onDomainChanged() { spectatorCameraOff(); } spectatorCameraOn(); Script.scriptEnding.connect(shutdownTabletApp); Script.update.connect(function() { if (cameraRig) { cameraRig.update(); } }); Window.domainChanged.connect(onDomainChanged); })()