Skip to content
Snippets Groups Projects
Commit bc257838 authored by netpro2k's avatar netpro2k
Browse files

Merge remote-tracking branch 'origin/master' into features/mode-switching

parents 7f31e405 53313ecb
No related branches found
No related tags found
No related merge requests found
File added
AFRAME.registerComponent("lifecycle-checker", {
schema: {
tick: { default: false }
},
init: function() {
console.log("init", this.el);
},
update: function() {
console.log("update", this.el);
},
tick: function() {
if (this.data.tick) {
console.log("tick", this.el);
}
},
remove: function() {
console.log("remove", this.el);
},
pause: function() {
console.log("pause", this.el);
},
play: function() {
console.log("play", this.el);
}
});
......@@ -7,29 +7,27 @@ AFRAME.registerComponent("ik-root", {
rightController: { type: "string", default: ".right-controller" }
},
update(oldData) {
let updated = false;
if (this.data.camera !== oldData.camera) {
this.camera = this.el.querySelector(this.data.camera);
updated = true;
}
if (this.data.leftController !== oldData.leftController) {
this.leftController = this.el.querySelector(this.data.leftController);
updated = true;
}
if (this.data.rightController !== oldData.rightController) {
this.rightController = this.el.querySelector(this.data.rightController);
updated = true;
}
if (updated) {
this.el.querySelector("[ik-controller]").components["ik-controller"].updateIkRoot(this);
}
}
});
function findIKRoot(entity) {
while (entity && !(entity.components && entity.components["ik-root"])) {
entity = entity.parentNode;
}
return entity && entity.components["ik-root"];
}
AFRAME.registerComponent("ik-controller", {
schema: {
leftEye: { type: "string", default: ".LeftEye" },
......@@ -65,6 +63,8 @@ AFRAME.registerComponent("ik-controller", {
this.rootToChest = new Matrix4();
this.invRootToChest = new Matrix4();
this.ikRoot = findIKRoot(this.el);
this.hands = {
left: {
lastVisible: true,
......@@ -124,10 +124,6 @@ AFRAME.registerComponent("ik-controller", {
.negate();
},
updateIkRoot(ikRoot) {
this.ikRoot = ikRoot;
},
tick(time, dt) {
if (!this.ikRoot) {
return;
......
AFRAME.registerComponent("player-info", {
schema: {
displayName: { type: "string" },
avatar: { type: "string" }
},
init() {
this.applyProperties = this.applyProperties.bind(this);
},
play() {
this.el.addEventListener("model-loaded", this.applyProperties);
},
pause() {
this.el.removeEventListener("model-loaded", this.applyProperties);
},
update(oldProps) {
this.applyProperties();
},
applyProperties() {
const nametagEl = this.el.querySelector(".nametag");
if (this.data.displayName && nametagEl) {
nametagEl.setAttribute("text", {
value: this.data.displayName
});
}
const modelEl = this.el.querySelector(".model");
if (this.data.avatar && modelEl) {
modelEl.setAttribute("src", this.data.avatar);
}
}
});
......@@ -20,7 +20,6 @@ AFRAME.registerComponent("super-cursor", {
this.direction = new THREE.Vector3();
this.point = new THREE.Vector3();
this.mousePos = new THREE.Vector2();
this.mouseDown = false;
this.data.cursor.setAttribute("material", { color: this.data.cursorColorUnhovered });
......@@ -60,7 +59,6 @@ AFRAME.registerComponent("super-cursor", {
}
this.isGrabbing = this.data.cursor.components["super-hands"].state.has("grab-start");
let isIntersecting = false;
const camera = this.data.camera.components.camera.camera;
const raycaster = this.el.components.raycaster.raycaster;
......@@ -69,15 +67,13 @@ AFRAME.registerComponent("super-cursor", {
this.direction = raycaster.ray.direction;
this.el.setAttribute("raycaster", { origin: this.origin, direction: this.direction });
let className = null;
let intersection = null;
if (!this.isGrabbing) {
const intersections = this.el.components.raycaster.intersections;
if (intersections.length > 0 && intersections[0].distance <= this.data.maxDistance) {
isIntersecting = true;
className = intersections[0].object.el.className;
this.point = intersections[0].point;
this.data.cursor.object3D.position.copy(this.point);
intersection = intersections[0];
this.data.cursor.object3D.position.copy(intersection.point);
this.currentDistance = intersections[0].distance;
this.currentDistanceMod = 0;
} else {
......@@ -85,7 +81,7 @@ AFRAME.registerComponent("super-cursor", {
}
}
if (this.isGrabbing || !isIntersecting) {
if (this.isGrabbing || !intersection) {
const distance = Math.min(
Math.max(this.data.minDistance, this.currentDistance - this.currentDistanceMod),
this.data.maxDistance
......@@ -96,7 +92,7 @@ AFRAME.registerComponent("super-cursor", {
this.data.cursor.object3D.position.copy(this.point);
}
this.isInteractable = isIntersecting && className === "interactable";
this.isInteractable = intersection && intersection.object.el.className === "interactable";
if ((this.isGrabbing || this.isInteractable) && !this.wasIntersecting) {
this.wasIntersecting = true;
......@@ -108,7 +104,6 @@ AFRAME.registerComponent("super-cursor", {
},
_handleMouseDown: function(e) {
this.mouseDown = true;
if (this.isInteractable) {
const lookControls = this.data.camera.components["look-controls"];
lookControls.pause();
......@@ -121,7 +116,6 @@ AFRAME.registerComponent("super-cursor", {
},
_handleMouseUp: function(e) {
this.mouseDown = false;
const lookControls = this.data.camera.components["look-controls"];
lookControls.play();
this.data.cursor.emit("action_release", {});
......
......@@ -22,19 +22,21 @@ AFRAME.AGLTFEntity = {
// From https://gist.github.com/cdata/f2d7a6ccdec071839bc1954c32595e87
// Tracking glTF cloning here: https://github.com/mrdoob/three.js/issues/11573
function cloneGltf(gltf) {
const clone = {
animations: gltf.animations,
scene: gltf.scene.clone(true)
};
const skinnedMeshes = {};
gltf.scene.traverse(node => {
if (!node.name) {
node.name = node.uuid;
}
if (node.isSkinnedMesh) {
skinnedMeshes[node.name] = node;
}
});
const clone = {
animations: gltf.animations,
scene: gltf.scene.clone(true)
};
const cloneBones = {};
const cloneSkinnedMeshes = {};
......@@ -68,18 +70,24 @@ function cloneGltf(gltf) {
return clone;
}
const inflateEntities = function(classPrefix, parentEl, node) {
const inflateEntities = function(parentEl, node) {
// setObject3D mutates the node's parent, so we have to copy
const children = node.children.slice(0);
const el = document.createElement("a-entity");
// Remove invalid CSS class name characters.
const className = node.name.replace(/[^\w-]/g, "");
el.classList.add(classPrefix + className);
const className = (node.name || node.uuid).replace(/[^\w-]/g, "");
el.classList.add(className);
parentEl.appendChild(el);
// Copy over transform to the THREE.Group and reset the actual transform of the Object3D
// AFRAME rotation component expects rotations in YXZ, convert it
if (node.rotation.order !== "YXZ") {
node.rotation.setFromQuaternion(node.quaternion, "YXZ");
}
// Copy over the object's transform to the THREE.Group and reset the actual transform of the Object3D
// all updates to the object should be done through the THREE.Group wrapper
el.setAttribute("position", {
x: node.position.x,
y: node.position.y,
......@@ -95,10 +103,8 @@ const inflateEntities = function(classPrefix, parentEl, node) {
y: node.scale.y,
z: node.scale.z
});
node.position.set(0, 0, 0);
node.rotation.set(0, 0, 0);
node.scale.set(1, 1, 1);
node.matrixAutoUpdate = false;
node.matrix.identity();
el.setObject3D(node.type.toLowerCase(), node);
......@@ -116,7 +122,6 @@ const inflateEntities = function(classPrefix, parentEl, node) {
}
const entityComponents = node.userData.components;
if (entityComponents) {
for (const prop in entityComponents) {
if (entityComponents.hasOwnProperty(prop)) {
......@@ -130,116 +135,184 @@ const inflateEntities = function(classPrefix, parentEl, node) {
}
children.forEach(childNode => {
inflateEntities(classPrefix, el, childNode);
inflateEntities(el, childNode);
});
};
function attachTemplate(templateEl) {
const selector = templateEl.getAttribute("data-selector");
const targetEls = templateEl.parentNode.querySelectorAll(selector);
const clone = document.importNode(templateEl.content, true);
const templateRoot = clone.firstElementChild;
return el;
};
function attachTemplate(root, { selector, templateRoot }) {
const targetEls = root.querySelectorAll(selector);
for (const el of targetEls) {
const root = templateRoot.cloneNode(true);
// Merge root element attributes with the target element
for (const { name, value } of templateRoot.attributes) {
for (const { name, value } of root.attributes) {
el.setAttribute(name, value);
}
// Append all child elements
for (const child of templateRoot.children) {
el.appendChild(document.importNode(child, true));
for (const child of root.children) {
el.appendChild(child);
}
}
}
function nextTick() {
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
function cachedLoadGLTF(src, onProgress) {
return new Promise((resolve, reject) => {
// Load the gltf model from the cache if it exists.
if (GLTFCache[src]) {
// Use a cloned copy of the cached model.
resolve(cloneGltf(GLTFCache[src]));
} else {
// Otherwise load the new gltf model.
new THREE.GLTFLoader().load(
src,
model => {
if (!GLTFCache[src]) {
// Store a cloned copy of the gltf model.
GLTFCache[src] = cloneGltf(model);
}
resolve(model);
},
onProgress,
reject
);
}
});
}
AFRAME.registerElement("a-gltf-entity", {
prototype: Object.create(AFRAME.AEntity.prototype, {
load: {
value() {
async value() {
if (this.hasLoaded || !this.parentEl) {
return;
}
// Get the src url.
let src = this.getAttribute("src");
// If the src attribute is a selector, get the url from the asset item.
if (src.charAt(0) === "#") {
const assetEl = document.getElementById(src.substring(1));
const fallbackSrc = assetEl.getAttribute("src");
const highSrc = assetEl.getAttribute("high-src");
const lowSrc = assetEl.getAttribute("low-src");
// The code above and below this are from AEntity.prototype.load, we need to monkeypatch in gltf loading mid function
this.loadTemplates();
await this.applySrc(this.getAttribute("src"));
//
if (highSrc && window.APP.quality === "high") {
src = highSrc;
} else if (lowSrc && window.APP.quality === "low") {
src = lowSrc;
} else {
src = fallbackSrc;
AFRAME.ANode.prototype.load.call(this, () => {
// Check if entity was detached while it was waiting to load.
if (!this.parentEl) {
return;
}
}
const onLoad = gltfModel => {
if (!GLTFCache[src]) {
// Store a cloned copy of the gltf model.
GLTFCache[src] = cloneGltf(gltfModel);
this.updateComponents();
if (this.isScene || this.parentEl.isPlaying) {
this.play();
}
});
}
},
this.model = gltfModel.scene || gltfModel.scenes[0];
this.model.animations = gltfModel.animations;
loadTemplates: {
value() {
this.templates = [];
this.querySelectorAll(":scope > template").forEach(templateEl =>
this.templates.push({
selector: templateEl.getAttribute("data-selector"),
templateRoot: document.importNode(templateEl.content.firstElementChild, true)
})
);
}
},
applySrc: {
async value(src) {
try {
// If the src attribute is a selector, get the url from the asset item.
if (src && src.charAt(0) === "#") {
const assetEl = document.getElementById(src.substring(1));
const fallbackSrc = assetEl.getAttribute("src");
const highSrc = assetEl.getAttribute("high-src");
const lowSrc = assetEl.getAttribute("low-src");
if (highSrc && window.APP.quality === "high") {
src = highSrc;
} else if (lowSrc && window.APP.quality === "low") {
src = lowSrc;
} else {
src = fallbackSrc;
}
}
this.setObject3D("mesh", this.model);
this.emit("model-loaded", { format: "gltf", model: this.model });
if (src === this.lastSrc) return;
this.lastSrc = src;
if (this.getAttribute("inflate")) {
inflate(this.model, finalizeLoad);
} else {
finalizeLoad();
if (!src) {
if (this.inflatedEl) {
console.warn("gltf-entity set to an empty source, unloading inflated model.");
this.removeInflatedEl();
}
return;
}
};
const inflate = (model, callback) => {
inflateEntities("", this, model);
this.querySelectorAll(":scope > template").forEach(attachTemplate);
const model = await cachedLoadGLTF(src);
// Wait one tick for the appended custom elements to be connected before calling finalizeLoad
setTimeout(callback, 0);
};
// If we started loading something else already
// TODO: there should be a way to cancel loading instead
if (src != this.lastSrc) return;
const finalizeLoad = () => {
AFRAME.ANode.prototype.load.call(this, () => {
// Check if entity was detached while it was waiting to load.
if (!this.parentEl) {
return;
}
// If we had inflated something already before, clean that up
this.removeInflatedEl();
this.updateComponents();
if (this.isScene || this.parentEl.isPlaying) {
this.play();
}
});
};
this.model = model.scene || model.scenes[0];
this.model.animations = model.animations;
this.setObject3D("mesh", this.model);
// Load the gltf model from the cache if it exists.
const gltf = GLTFCache[src];
if (this.getAttribute("inflate")) {
this.inflatedEl = inflateEntities(this, this.model);
// TODO: Still don't fully understand the lifecycle here and how it differs between browsers, we should dig in more
// Wait one tick for the appended custom elements to be connected before attaching templates
await nextTick();
if (src != this.lastSrc) return; // TODO: there must be a nicer pattern for this
this.templates.forEach(attachTemplate.bind(null, this));
}
if (gltf) {
// Use a cloned copy of the cached model.
const clonedGltf = cloneGltf(gltf);
onLoad(clonedGltf);
return;
this.emit("model-loaded", { format: "gltf", model: this.model });
} catch (e) {
const message = (e && e.message) || "Failed to load glTF model";
console.error(message);
this.emit("model-error", { format: "gltf", src });
}
}
},
removeInflatedEl: {
value() {
if (this.inflatedEl) {
this.inflatedEl.parentNode.removeChild(this.inflatedEl);
delete this.inflatedEl;
}
}
},
// Otherwise load the new gltf model.
new THREE.GLTFLoader().load(src, onLoad, undefined /* onProgress */, error => {
// On glTF load error
attributeChangedCallback: {
value(attr, oldVal, newVal) {
if (attr === "src") {
this.applySrc(newVal);
}
AFRAME.AEntity.prototype.attributeChangedCallback.call(this, attr, oldVal, newVal);
}
},
const message = error && error.message ? error.message : "Failed to load glTF model";
console.warn(message);
this.emit("model-error", { format: "gltf", src });
});
setAttribute: {
value(attr, arg1, arg2) {
if (attr === "src") {
this.applySrc(arg1);
}
AFRAME.AEntity.prototype.setAttribute.call(this, attr, arg1, arg2);
}
}
})
......
......@@ -5,6 +5,7 @@ function registerNetworkSchemas() {
"position",
"rotation",
"scale",
"player-info",
{
selector: ".camera",
component: "position"
......@@ -36,11 +37,6 @@ function registerNetworkSchemas() {
{
selector: ".right-controller",
component: "visible"
},
{
selector: ".nametag",
component: "text",
property: "value"
}
]
});
......
......@@ -35,7 +35,7 @@
high-src="./assets/avatars/BotDefault_Avatar.glb"
low-src="./assets/avatars/BotDefault_Avatar_Unlit.glb"
></a-progressive-asset>
<a-asset-item id="bot-dom-mesh" response-type="arraybuffer" src="./assets/avatars/BotDom_Avatar.glb"></a-asset-item>
<a-asset-item id="watch-model" response-type="arraybuffer" src="./assets/hud/watch.glb"></a-asset-item>
<a-asset-item id="meeting-space1-mesh" response-type="arraybuffer" src="./assets/environments/MeetingSpace1_mesh.glb"></a-asset-item>
......@@ -53,16 +53,16 @@
</template>
<template id="remote-avatar-template">
<a-entity ik-root>
<a-entity ik-root player-info>
<a-entity class="camera"></a-entity>
<a-entity class="left-controller"></a-entity>
<a-entity class="right-controller"></a-entity>
<a-gltf-entity src="#bot-skinned-mesh" inflate="true" ik-controller >
<a-gltf-entity class="model" inflate="true">
<template data-selector=".RootScene">
<a-entity animation-mixer ></a-entity>
<a-entity ik-controller animation-mixer></a-entity>
</template>
<template data-selector=".Neck">
......@@ -86,7 +86,7 @@
</a-entity>
</template>
<template selector=".LeftHand">
<template data-selector=".LeftHand">
<a-entity personal-space-invader ></a-entity>
</template>
......@@ -156,6 +156,7 @@
ik-root
app-mode-toggle-playing__character-controller="mode: hud; invert: true;"
app-mode-toggle-playing__wasd-to-analog2d="mode: hud; invert: true;"
player-info
>
<a-entity
......@@ -209,9 +210,13 @@
app-mode-toggle-attribute__line="mode: hud; property: visible;"
></a-entity>
<a-gltf-entity src="#bot-skinned-mesh" inflate="true" ik-controller >
<a-gltf-entity class="model" inflate="true">
<template data-selector=".RootScene">
<a-entity animation-mixer animated-robot-hands ></a-entity>
<a-entity
ik-controller
animated-robot-hands
animation-mixer
></a-entity>
</template>
<template data-selector=".Neck">
......
......@@ -37,6 +37,8 @@ import "./components/layers";
import "./components/spawn-controller";
import "./components/animated-robot-hands";
import "./components/hide-when-quality";
import "./components/player-info";
import "./components/debug";
import "./components/animation-mixer";
import "./components/loop-animation";
......@@ -133,16 +135,21 @@ async function exitScene() {
document.body.removeChild(scene);
}
function setNameTagFromStore() {
const myNametag = document.querySelector("#player-rig .nametag");
myNametag.setAttribute("text", "value", store.state.profile.display_name);
document.querySelector("a-scene").emit("username-changed", { username: store.state.profile.display_name });
function updatePlayerInfoFromStore() {
const qs = queryString.parse(location.search);
const playerRig = document.querySelector("#player-rig");
playerRig.setAttribute("player-info", {
displayName: store.state.profile.display_name,
avatar: qs.avatar || "#bot-skinned-mesh"
});
}
async function enterScene(mediaStream, enterInVR) {
const scene = document.querySelector("a-scene");
const playerRig = document.querySelector("#player-rig");
const qs = queryString.parse(location.search);
document.querySelector("a-scene canvas").classList.remove("blurred");
scene.setAttribute("networked-scene", "adapter: janus; audio: true; debug: true; connectOnLoad: false;");
registerNetworkSchemas();
if (enterInVR) {
......@@ -151,9 +158,11 @@ async function enterScene(mediaStream, enterInVR) {
AFRAME.registerInputActions(inGameActions, "default");
const qs = queryString.parse(location.search);
scene.setAttribute("networked-scene", {
adapter: "janus",
audio: true,
debug: true,
connectOnLoad: false,
room: qs.room && !isNaN(parseInt(qs.room)) ? parseInt(qs.room) : 1,
serverURL: process.env.JANUS_SERVER
});
......@@ -163,12 +172,11 @@ async function enterScene(mediaStream, enterInVR) {
}
if (isMobile || qs.mobile) {
const playerRig = document.querySelector("#player-rig");
playerRig.setAttribute("virtual-gamepad-controls", {});
}
setNameTagFromStore();
store.addEventListener("statechanged", setNameTagFromStore);
updatePlayerInfoFromStore();
store.addEventListener("statechanged", updatePlayerInfoFromStore);
const avatarScale = parseInt(qs.avatarScale, 10);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment