Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[portals] consolidated changes from smoother-portals branch #23

Merged
merged 1 commit into from
Jul 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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