Skip to content

Commit

Permalink
Merge pull request #23 from croquet/portals-tmp
Browse files Browse the repository at this point in the history
[portals] consolidated changes from smoother-portals branch
  • Loading branch information
aranlunzer committed Jul 22, 2022
2 parents 0aebb7e + 84875ca commit 4c195d1
Show file tree
Hide file tree
Showing 11 changed files with 484 additions and 261 deletions.
2 changes: 1 addition & 1 deletion behaviors/campus/openPortal.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class OpenRefineryPortalActor {
this.createCard({
translation: [104.07644536824432, 5.026017508210918, -142.5899873815636],
rotation: [0, 0.9275515022151297, 0, 0.37369534481774874],
layers: ["pointer", "portal"],
layers: ["pointer"],
className: "PortalActor",
color: 16737996,
cornerRadius: 0.05,
Expand Down
2 changes: 1 addition & 1 deletion behaviors/default/openPortal.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class OpenRefineryPortalActor {
this.createCard({
translation: [-12, -0.4, -10.2],
rotation: [0, -1.5707963267948966, 0],
layers: ["pointer", "portal"],
layers: ["pointer"],
className: "PortalActor",
color: 16737996,
cornerRadius: 0.05,
Expand Down
2 changes: 1 addition & 1 deletion behaviors/factory/openPortal.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class OpenArtGalleryPortalActor {
this.createCard({
translation: [0, 4.1, 39],
rotation: [0, 0, 0],
layers: ["pointer", "portal"],
layers: ["pointer"],
className: "PortalActor",
color: 0xFF66CC,
cornerRadius: 0.05,
Expand Down
231 changes: 155 additions & 76 deletions shell.js

Large diffs are not rendered by default.

358 changes: 232 additions & 126 deletions src/avatar.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion src/frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ addShellListener((command, data) => {
if (command === "frame-type") {
const primary = data.frameType === "primary";
if (isPrimaryFrame !== primary) {
console.log(frameName(), "frame-type", data.frameType);
isPrimaryFrame = primary;
document.body.style.background = "transparent";
document.getElementById("hud").classList.toggle("primary-frame", isPrimaryFrame);
Expand Down
78 changes: 52 additions & 26 deletions src/microverse.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
FontModelManager, FontViewManager } from "./text/text.js";
import { CardActor, VideoManager, MicroverseAppManager } from "./card.js";
import { AvatarActor, } from "./avatar.js";
import { frameName } from "./frame.js";

import { BehaviorModelManager, BehaviorViewManager, CodeLibrary } from "./code.js";
import { TextFieldActor } from "./text/text.js";
Expand Down Expand Up @@ -166,10 +167,16 @@ class MyPlayerManager extends PlayerManager {
// (as it is) it'd fall back to use the short string as a stem of the model file name.
// if it is an object, we use it as the card spec.

// when an avatar is created to hold the through-portal camera in a secondary
// world, it is initialised according to the next entry in the rota of default
// names/shapes (but remains invisible). if the user comes through into this
// world, at that point the avatar is updated to the name and shape that the
// user had in the previous world (see AvatarPawn.frameTypeChanged).

let index = this.avatarCount % Constants.AvatarNames.length;
this.avatarCount++;
let avatarSpec = Constants.AvatarNames[index];
console.log("MyPlayerManager", this.avatarCount);
console.log(frameName(), "MyPlayerManager", this.avatarCount);
let options = {...playerOptions};
options.noSave = true;
options.type = "3d";
Expand All @@ -188,7 +195,7 @@ class MyPlayerManager extends PlayerManager {
let behaviorManager = this.service("BehaviorModelManager");

if (behaviorManager && behaviorManager.modules.get("AvatarEventHandler")) {
let modules;
// let modules;
if (!options.behaviorModules) {
options.behaviorModules = ["AvatarEventHandler"];
} else {
Expand Down Expand Up @@ -216,24 +223,37 @@ class MyPlayerManager extends PlayerManager {
return [...this.players.values()].filter((player) => player.inWorld);
}

startPresentation(playerId, presenterToken=null) {
if (this.presentationMode && this.presentationMode !== playerId) {
return; // somebody is already presenting
}
startPresentation(playerId, presenterToken = null) {
// sent by AvatarActor.comeToMe or this.continuePresenting (triggered by a
// presenter arriving from another world). in either case it may turn out
// that some other presenter has beaten them to it. %%in this world someone else is already presenting. in
// that case, the arriving presenter and their followers will be left to their
// own devices.
if (this.presentationMode && this.presentationMode !== playerId) return;

this.presentationMode = playerId;

// if we have a token, we came through a portal and teleport only previous followers to the presenter
// otherwise someone started a presentation, and grab everyone who is in the world currently
// examining the current inWorld players, decide who will join this
// presentation. if a token was provided, only those players carrying the same
// token are signed up (which will include the presenter, and any follower that
// showed up before the presenter started presenting). only the presenter needs
// to keep that token, to catch potential late followers.
// if no token, grab everyone (and delete any token they might have, while we're
// about it).
for (const player of this.playersInWorld()) {
if (presenterToken && player.presenterToken !== presenterToken) continue;
if (!presenterToken || player.playerId !== playerId) delete player.presenterToken;
this.followers.add(player.playerId);
}

this.publish("playerManager", "presentationStarted", playerId);
this.publish("playerManager", "presentationStarted");
this.publish("playerManager", "playerCountChanged");
}

addFollower(playerId) {
this.followers.add(playerId);
}

stopPresentation() {
this.presentationMode = null;
this.publish("playerManager", "presentationStopped");
Expand All @@ -248,38 +268,44 @@ class MyPlayerManager extends PlayerManager {
}

continuePresenting(presenter, presenterToken) {
// a presenter came into this world through a portal carrying a token
// we need to make them the presenter
// and make all others carrying the same token follow them
// it's a bit tricky because the presenter may enter before or after the others
console.log(this.sessionId, "continuePresenting", presenter.id, presenterToken);
presenter.presenterToken = presenterToken;
this.startPresentation(presenter.playerId, presenterToken);
// a presenter came into this world through a portal carrying a token. if there
// is not already a presentation in progress we make them the presenter, and make
// all followers carrying the same token follow them. note that followers may
// enter before or after the presenter.
if (!this.presentationMode) {
console.log(frameName(), "continuePresenting", presenter.id, presenterToken);
presenter.presenterToken = presenterToken; // we keep this for as long as we're presenting
this.startPresentation(presenter.playerId, presenterToken);
} else {
console.log(frameName(), "continuePresenting rejected due to presentation in progress");
}
}

continueFollowing(follower, presenterToken) {
// a follower came into this world through a portal carrying a token
// we need to make them follow the presenter with the same token
// it's a bit tricky because the follower may enter before or after the presenter
follower.presenterToken = presenterToken;
// a follower came into this world through a portal carrying a token, hoping
// to follow the presenter with the same token. the follower may be entering
// before or after the presenter. if the expected presenter isn't presenting,
// the follower will just wait; even if someone else is presenting now, it's
// conceivable - albeit unlikely - that the current presentation will end in
// time for the expected presenter to take over.
if (this.presentationMode && this.presenter.presenterToken === presenterToken) {
console.log(this.sessionId, "continueFollowing", this.presenter.id, presenterToken);
console.log(frameName(), "continueFollowing", this.presenter.id, presenterToken);
this.followers.add(follower.playerId);
follower.presentationStarted(this.presentationMode);
follower.presentationStarted();
this.publish("playerManager", "playerCountChanged");
} else {
// the presenter is not here yet, so we'll wait for them to continuePresenting
console.log(this.sessionId, "continueFollowing but presenter not here yet", presenterToken);
follower.presenterToken = presenterToken;
console.log(frameName(), "continueFollowing: expected presenter not presenting", presenterToken);
}
}

playerEnteredWorld(player) {
console.log(this.sessionId, "playerEnteredWorld", player);
console.log(frameName(), "playerEnteredWorld", player);
this.publish("playerManager", "playerCountChanged");
}

playerLeftWorld(player) {
console.log(this.sessionId, "playerLeftWorld", player);
console.log(frameName(), "playerLeftWorld", player);
if (player.playerId === this.presentationMode) {
this.stopPresentation();
}
Expand Down
61 changes: 37 additions & 24 deletions src/portal.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export class PortalActor extends CardActor {
init(options) {
super.init(options);
this._isOpen = true;
this.addLayer("portal");
this._portalTime = this.now();
this.listen("isOpenSet", this.setIsOpen);
}

get isPortal() { return true; }
Expand All @@ -26,6 +28,11 @@ export class PortalActor extends CardActor {
get sparkle() { return this._cardData.sparkle; }

get pawn() { return PortalPawn; }

setIsOpen() {
if (this.isOpen) this.addLayer("portal");
else this.removeLayer("portal");
}
}
PortalActor.register("PortalActor");

Expand All @@ -38,7 +45,7 @@ export class PortalPawn extends CardPawn {
this.portalId = undefined;
this.targetMatrix = new THREE.Matrix4();
this.targetMatrixBefore = new THREE.Matrix4();
this.openPortal();
if (this.actor.isOpen) this.openPortal();

this.setGhostWorld({ v: this.actor._ghostWorld });
this.listen("ghostWorldSet", this.setGhostWorld);
Expand All @@ -52,6 +59,8 @@ export class PortalPawn extends CardPawn {

this.shellListener = (command, data) => this.receiveFromShell(command, data);
addShellListener(this.shellListener);

this.subscribe("avatar", { event: "gatherPortalSpecs", handling: "immediate" }, this.updatePortal);
}

destroy() {
Expand Down Expand Up @@ -99,7 +108,8 @@ export class PortalPawn extends CardPawn {
float angle = atan(vUv.y, vUv.x);
float v = sin(time * 30.0 - r * 1.0 + 5.0 * angle) + r - time * 2.0 + 2.0;
float alpha = clamp(v, 0.0, 1.0);
gl_FragColor = vec4(0, 0, 0, alpha); // we only care about alpha
// gl_FragColor = vec4(0, 0, 0, alpha); // if we only care about alpha
gl_FragColor = vec4(alpha*0.3, alpha*0.3, alpha*0.3, alpha); // add some grey
}
`,
clipping: true,
Expand Down Expand Up @@ -194,39 +204,45 @@ export class PortalPawn extends CardPawn {
}

update(t) {
super.update();
this.updatePortalCamera();
super.update(t);
this.updatePortalMaterial();
this.updateParticles();
}

updatePortalCamera() {
updatePortal({ callback, force }) {
// invoked synchronously for message scope avatar:gatherPortalSpecs
this.updatePortalCamera(callback, force);
}

updatePortalCamera(callback, force) {
// if the portal's position with respect to the camera has changed, tell the
// embedded world to re-render itself from the suitably adjusted camera angle.
// while these changes continue, the shell will take over the scheduling of
// the worlds' rendering with the goal of ensuring that the embedded world has
// always finished its rendering by the time the outer world (and the portal)
// is rendered.
if (!this.portalId) return;

const { targetMatrix, targetMatrixBefore, portalId } = this;
const renderMgr = this.service("ThreeRenderManager");
const { camera } = renderMgr;
camera.updateMatrixWorld(true); // evidently not guaranteed to have been handled since last time, perhaps because the "synced" rendering is decoupled from render-objects' update() invocations
this.renderObject.updateMatrixWorld(true); // ditto
const frustum = new THREE.Frustum()
const matrix = new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
frustum.setFromProjectionMatrix(matrix);
// if the portal isn't on view, tell the shell there's no need to do synchronised
// rendering right now (even though the portal is moving)
if (!frustum.intersectsObject(this.renderObject.children[0])) {
sendToShell("portal-update", { portalId, cameraMatrix: null });
return;
}
const { camera } = this.service("ThreeRenderManager");
// objects' matrices are not guaranteed to have been updated, because this is
// decoupled from THREE rendering.
camera.updateMatrixWorld(true);
this.renderObject.updateMatrixWorld(true);
targetMatrix.copy(this.renderObject.matrixWorld);
targetMatrix.invert();
targetMatrix.multiply(camera.matrixWorld);
if (!targetMatrixBefore.equals(targetMatrix)) {
sendToShell("portal-update", { portalId, cameraMatrix: targetMatrix.elements, updateTime: Date.now() });
if (force || !targetMatrixBefore.equals(targetMatrix)) {
const frustum = new THREE.Frustum();
const matrix = new THREE.Matrix4().multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
frustum.setFromProjectionMatrix(matrix);
// if the portal isn't on view (though this is very approximate, based on
// its bounding sphere), tell the shell there's no need to do synchronised
// rendering right now - even though the portal is moving
const remoteMatrix = frustum.intersectsObject(this.renderObject.children[0])
? targetMatrix.elements
: null;
callback({ portalId, cameraMatrix: remoteMatrix });
targetMatrixBefore.copy(targetMatrix);
}
}
Expand Down Expand Up @@ -268,8 +284,7 @@ export class PortalPawn extends CardPawn {
}

onPointerDown() {
// replay opening animation to remind user that this is a portal
// and they can't interact with the inner world yet
// currently toggles open/closed
this.say("_set", {
isOpen: !this.actor.isOpen,
portalTime: this.now(),
Expand Down Expand Up @@ -353,10 +368,8 @@ export class PortalPawn extends CardPawn {
switch (command) {
case "portal-opened":
this.portalId = portalId;
this.updatePortalCamera();
break;
case "frame-type":
this.updatePortalCamera();
this.setGhostWorld({v: this.actor._ghostWorld});
break;
}
Expand Down
6 changes: 3 additions & 3 deletions worlds/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function init(Constants) {
textureType: "image",
textureLocation: "./assets/images/CroquetLogo_RGB.jpg",
cardURL: "https://croquet.io",
cardHilite: 0xffffaa,
cardHilite: 0xffffaa,
behaviorModules: ["URLLink"],
fullBright: true,
frameColor: 0xcccccc,
Expand Down Expand Up @@ -112,7 +112,7 @@ export function init(Constants) {
card: {
translation: [-12, -0.4, -10.2],
rotation: [0, -Math.PI / 2, 0],
layers: ["pointer", "portal"],
layers: ["pointer"],
className: "PortalActor",
color: 16737996,
cornerRadius: 0.05,
Expand Down Expand Up @@ -234,7 +234,7 @@ export function init(Constants) {
runs: [{text: `
translation: [-12, -0.4, -10.2],
rotation: [0, -1.5707963267948966, 0],
layers: ["pointer", "portal"],
layers: ["pointer"],
className: "PortalActor",
color: 16737996,
cornerRadius: 0.05,
Expand Down
2 changes: 1 addition & 1 deletion worlds/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,7 @@ export function init(Constants) {
card: {
name: "start point",
type: "object",
translation: [0, 4.4, 34],
translation: [0, 4.4, 38],
spawn: "default"
}
}
Expand Down
2 changes: 1 addition & 1 deletion worlds/refinery.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export function init(Constants) {
translation: [-4, -0.4, -29],
rotation: [0, -Math.PI / 2, 0],
type: "2d",
layers: ["pointer", "portal"],
layers: ["pointer"],
color: 0xFF66CC,
frameColor: frameColor,
width: 1.8,
Expand Down

0 comments on commit 4c195d1

Please sign in to comment.