diff --git a/scripts/default.env b/scripts/default.env index f26d4c84db07c28644e7c9d479842e6c771b1596..3d6556c0fa826b118d94dc8bc88a9f2555c5a520 100644 --- a/scripts/default.env +++ b/scripts/default.env @@ -4,3 +4,4 @@ ORIGIN_TRIAL_TOKEN="AgN/JtqSF6qpD3OZk8KgM5/UYqUUrwc166cOQSRCqvU+TIpHWdiwBUWH5V1K ORIGIN_TRIAL_EXPIRES="2018-05-15" JANUS_SERVER="wss://prod-janus.reticulum.io" DEV_RETICULUM_SERVER="dev.reticulum.io" +ASSET_BUNDLE_SERVER="https://asset-bundles-prod.reticulum.io" diff --git a/src/assets/environments/environments.js b/src/assets/environments/environments.js new file mode 100644 index 0000000000000000000000000000000000000000..7c20ce61da763de912493c4e8d35385d09347b5a --- /dev/null +++ b/src/assets/environments/environments.js @@ -0,0 +1,7 @@ +export const ENVIRONMENT_URLS = [ + process.env.ASSET_BUNDLE_SERVER + "/rooms/meetingroom/MeetingRoom.bundle.json", + process.env.ASSET_BUNDLE_SERVER + "/rooms/atrium/Atrium.bundle.json", + process.env.ASSET_BUNDLE_SERVER + "/rooms/MedievalFantasyBook/MedievalFantasyBook.bundle.json" +]; + +export const DEFAULT_ENVIRONMENT_URL = ENVIRONMENT_URLS[0]; diff --git a/src/components/css-class.js b/src/components/css-class.js new file mode 100644 index 0000000000000000000000000000000000000000..1528ed4d450aea61f63b7426524229ed63609044 --- /dev/null +++ b/src/components/css-class.js @@ -0,0 +1,17 @@ +AFRAME.registerComponent("css-class", { + schema: { + type: "string" + }, + init() { + this.el.classList.add(this.data); + }, + update(oldData) { + if (this.data !== oldData) { + this.el.classList.remove(oldData); + this.el.classList.add(this.data); + } + }, + remove() { + this.el.classList.remove(this.data); + } +}); diff --git a/src/components/scene-shadow.js b/src/components/scene-shadow.js new file mode 100644 index 0000000000000000000000000000000000000000..72d77cf36b36c80279b0683531f4682f1f5b7a47 --- /dev/null +++ b/src/components/scene-shadow.js @@ -0,0 +1,30 @@ +// For use in environment gltf bundles to set scene shadow properties. +AFRAME.registerComponent("scene-shadow", { + schema: { + autoUpdate: { + type: "boolean", + default: true + }, + type: { + type: "string", + default: "pcf" + }, + renderReverseSided: { + type: "boolean", + default: true + }, + renderSingleSided: { + type: "boolean", + default: true + } + }, + init() { + this.originalShadowProperties = this.el.sceneEl.getAttribute("shadow"); + }, + update() { + this.el.sceneEl.setAttribute("shadow", this.data); + }, + remove() { + this.el.sceneEl.setAttribute("shadow", this.originalShadowProperties); + } +}); diff --git a/src/components/spawn-controller.js b/src/components/spawn-controller.js index eab8314c8664965cbdd2b19acf99fa74609052c5..1daf01b756e4308f1f9d50ceffd362aeb4b8e399 100644 --- a/src/components/spawn-controller.js +++ b/src/components/spawn-controller.js @@ -1,31 +1,26 @@ AFRAME.registerComponent("spawn-controller", { schema: { - radius: { type: "number", default: 1 } + target: { type: "selector" }, + loadedEvent: { type: "string" } }, - init() { - const el = this.el; - const center = el.getAttribute("position"); + this.onLoad = this.onLoad.bind(this); + this.data.target.addEventListener(this.data.loadedEvent, this.onLoad); + }, + onLoad() { + const spawnPoints = document.querySelectorAll("[spawn-point]"); - const angleRad = Math.random() * 2 * Math.PI; - const circlePoint = this.getPointOnCircle(this.data.radius, angleRad); - const worldPoint = { - x: circlePoint.x + center.x, - y: center.y, - z: circlePoint.z + center.z - }; - el.setAttribute("position", worldPoint); + if (spawnPoints.length === 0) { + // Keep default position + return; + } - const angleDeg = angleRad * THREE.Math.RAD2DEG; - const angleToCenter = -1 * angleDeg + 90; - el.setAttribute("rotation", { x: 0, y: angleToCenter, z: 0 }); + const spawnPointIndex = Math.round((spawnPoints.length - 1) * Math.random()); + const spawnPoint = spawnPoints[spawnPointIndex]; - el.object3D.updateMatrix(); - }, - - getPointOnCircle(radius, angleRad) { - const x = Math.cos(angleRad) * radius; - const z = Math.sin(angleRad) * radius; - return { x: x, z: z }; + spawnPoint.object3D.getWorldPosition(this.el.object3D.position); + this.el.object3D.rotation.copy(spawnPoint.object3D.rotation); } }); + +AFRAME.registerComponent("spawn-point", {}); diff --git a/src/components/water.js b/src/components/water.js index b7f176131ea97f6f47fbbb31b27c59e80350b78b..cfa33e4b166eea99f5af30d5c5fa36d0075c7378 100644 --- a/src/components/water.js +++ b/src/components/water.js @@ -148,10 +148,14 @@ AFRAME.registerComponent("water", { distance: { type: "number", default: 1 }, speed: { type: "number", default: 0.1 }, forceMobile: { type: "boolean", default: false }, - normalMap: { type: "asset" } + normalMap: { type: "asset", default: "#water-normal-map" } }, init() { - const waterGeometry = new THREE.PlaneBufferGeometry(800, 800); + const waterMesh = this.el.getObject3D("mesh"); + const waterGeometry = waterMesh.geometry; + + // Render THREE.Water shader instead of THREE.Mesh + waterMesh.visible = false; const waterNormals = new THREE.Texture(this.data.normalMap); waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping; @@ -223,5 +227,7 @@ AFRAME.registerComponent("water", { remove() { this.el.removeObject3D("water"); + const waterMesh = this.el.getObject3D("mesh"); + waterMesh.visible = true; } }); diff --git a/src/gltf-component-mappings.js b/src/gltf-component-mappings.js index e2177fb25a1025ad56e9b135626b3abca64b1d97..99b9b0bf6c2dae246cfd33bc654bd21260d98eef 100644 --- a/src/gltf-component-mappings.js +++ b/src/gltf-component-mappings.js @@ -1,10 +1,27 @@ import "./components/gltf-model-plus"; import { resolveURL } from "./utils/resolveURL"; +AFRAME.GLTFModelPlus.registerComponent("quack", "quack"); +AFRAME.GLTFModelPlus.registerComponent("sound", "sound"); +AFRAME.GLTFModelPlus.registerComponent("collision-filter", "collision-filter"); +AFRAME.GLTFModelPlus.registerComponent("css-class", "css-class"); +AFRAME.GLTFModelPlus.registerComponent("scene-shadow", "scene-shadow"); +AFRAME.GLTFModelPlus.registerComponent("super-spawner", "super-spawner"); +AFRAME.GLTFModelPlus.registerComponent("gltf-model-plus", "gltf-model-plus"); +AFRAME.GLTFModelPlus.registerComponent("body", "body"); +AFRAME.GLTFModelPlus.registerComponent("hide-when-quality", "hide-when-quality"); +AFRAME.GLTFModelPlus.registerComponent("light", "light"); +AFRAME.GLTFModelPlus.registerComponent("skybox", "skybox"); +AFRAME.GLTFModelPlus.registerComponent("layers", "layers"); +AFRAME.GLTFModelPlus.registerComponent("shadow", "shadow"); +AFRAME.GLTFModelPlus.registerComponent("xr", "xr"); +AFRAME.GLTFModelPlus.registerComponent("water", "water"); AFRAME.GLTFModelPlus.registerComponent("scale-audio-feedback", "scale-audio-feedback"); +AFRAME.GLTFModelPlus.registerComponent("animation-mixer", "animation-mixer"); AFRAME.GLTFModelPlus.registerComponent("loop-animation", "loop-animation"); AFRAME.GLTFModelPlus.registerComponent("shape", "shape"); AFRAME.GLTFModelPlus.registerComponent("visible", "visible"); +AFRAME.GLTFModelPlus.registerComponent("spawn-point", "spawn-point"); AFRAME.GLTFModelPlus.registerComponent("nav-mesh", "nav-mesh", (el, componentName, componentData, gltfPath) => { if (componentData.src) { componentData.src = resolveURL(componentData.src, gltfPath); diff --git a/src/hub.html b/src/hub.html index aa7bab1cd25efafbacdb25e7ef00e6d91671d16d..af1bb3ef2bf5ff1ccd6839793ac70b7395af89f0 100644 --- a/src/hub.html +++ b/src/hub.html @@ -190,19 +190,6 @@ <!-- Interactables --> <a-entity id="counter" networked-counter="max: 3; ttl: 120"></a-entity> - <a-entity - gltf-model-plus="src: #interactable-duck" - scale="2 2 2" - class="interactable" - super-spawner="template: #interactable-template;" - position="2.9 1.2 0" - body="mass: 0; type: static; shape: box;" - collision-filter="collisionForces: false;" - quack - sound__quack="src: #quack; on: quack; poolSize: 2;" - sound__specialquack="src: #specialquack; on: specialquack;" - ></a-entity> - <a-entity id="cursor-controller" cursor-controller=" @@ -236,7 +223,7 @@ <a-entity id="player-rig" networked="template: #remote-avatar-template; attachTemplateToLocal: false;" - spawn-controller="radius: 4;" + spawn-controller="loadedEvent: bundleloaded; target: #environment-root" wasd-to-analog2d character-controller="pivot: #player-camera" ik-root @@ -357,37 +344,12 @@ </a-entity> </a-entity> - <!-- Lights --> - <a-entity - hide-when-quality="low" - light="type: directional; color: #F9FFCE; intensity: 0.6" - position="0.002 5.231 -15.3" - ></a-entity> - <!-- Environment --> <a-entity id="environment-root" nav-mesh-helper static-body="shape: none;" ></a-entity> - - <a-entity - id="skybox" - scale="8000 8000 8000" - skybox="azimuth:0.280; inclination:0.440" - light="type: ambient; color: #FFF" - layers="reflection:true" - xr="ar: false" - ></a-entity> - - <a-entity - id="water" - water="forceMobile: true; normalMap:#water-normal-map" - rotation="-90 0 0" - position="0 -88.358 -332.424" - xr="ar: false" - ></a-entity> - </a-scene> <div id="ui-root"></div> diff --git a/src/hub.js b/src/hub.js index 0a9775d92cf159ea203e9bf12db646a08a3931fc..554d1f3a7a449f3fd22571b7a251c6690ee74b77 100644 --- a/src/hub.js +++ b/src/hub.js @@ -57,6 +57,8 @@ import "./components/block-button"; import "./components/visible-while-frozen"; import "./components/stats-plus"; import "./components/networked-avatar"; +import "./components/css-class"; +import "./components/scene-shadow"; import ReactDOM from "react-dom"; import React from "react"; @@ -68,6 +70,7 @@ import "./systems/app-mode"; import "./systems/exit-on-blur"; import "./gltf-component-mappings"; +import { DEFAULT_ENVIRONMENT_URL } from "./assets/environments/environments"; import { App } from "./App"; @@ -347,10 +350,7 @@ const onReady = async () => { // If ?room is set, this is `yarn start`, so just use a default environment and query string room. remountUI({ janusRoomId: qs.room && !isNaN(parseInt(qs.room)) ? parseInt(qs.room) : 1 }); initialEnvironmentEl.setAttribute("gltf-bundle", { - src: "https://asset-bundles-prod.reticulum.io/rooms/meetingroom/MeetingRoom.bundle.json" - // src: "https://asset-bundles-prod.reticulum.io/rooms/theater/TheaterMeshes.bundle.json" - // src: "https://asset-bundles-prod.reticulum.io/rooms/atrium/AtriumMeshes.bundle.json" - // src: "https://asset-bundles-prod.reticulum.io/rooms/courtyard/CourtyardMeshes.bundle.json" + src: DEFAULT_ENVIRONMENT_URL }); return; } diff --git a/src/react-components/home-root.js b/src/react-components/home-root.js index 6e780474b5ecf98164f6e34cc47e6dcc9a0551ac..401270832f397dc03039f67e84487173a461dd80 100644 --- a/src/react-components/home-root.js +++ b/src/react-components/home-root.js @@ -4,6 +4,7 @@ import { IntlProvider, FormattedMessage, addLocaleData } from "react-intl"; import en from "react-intl/locale-data/en"; import homeVideo from "../assets/video/home.webm"; import classNames from "classnames"; +import { ENVIRONMENT_URLS } from "../assets/environments/environments"; import HubCreatePanel from "./hub-create-panel.js"; import InfoDialog from "./info-dialog.js"; @@ -17,14 +18,6 @@ addLocaleData([...en]); const messages = localeData[lang] || localeData.en; -const ENVIRONMENT_URLS = [ - "https://asset-bundles-prod.reticulum.io/rooms/meetingroom/MeetingRoom.bundle.json", - "https://asset-bundles-prod.reticulum.io/rooms/theater/Theater.bundle.json", - "https://asset-bundles-prod.reticulum.io/rooms/atrium/Atrium.bundle.json", - "https://asset-bundles-prod.reticulum.io/rooms/courtyard/Courtyard.bundle.json", - "https://asset-bundles-prod.reticulum.io/rooms/MedievalFantasyBook/MedievalFantasyBook.bundle.json" -]; - class HomeRoot extends Component { static propTypes = { intl: PropTypes.object, diff --git a/src/react-components/hub-create-panel.js b/src/react-components/hub-create-panel.js index 0b74d55b2698d51f6688e23e02e871177ecea436..f925d6ae55d82e7019f68658d3f7c03a986a038e 100644 --- a/src/react-components/hub-create-panel.js +++ b/src/react-components/hub-create-panel.js @@ -6,6 +6,7 @@ import classNames from "classnames"; import faAngleLeft from "@fortawesome/fontawesome-free-solid/faAngleLeft"; import faAngleRight from "@fortawesome/fontawesome-free-solid/faAngleRight"; import FontAwesomeIcon from "@fortawesome/react-fontawesome"; +import { resolveURL, extractUrlBase } from "../utils/resolveURL"; import default_scene_preview_thumbnail from "../assets/images/default_thumbnail.png"; @@ -42,11 +43,24 @@ class HubCreatePanel extends Component { _getEnvironmentThumbnail = environmentIndex => { const environment = this.props.environments[environmentIndex]; const meta = environment.meta || {}; - return ( - (meta.images || []).find(i => i.type === "preview-thumbnail") || { - srcset: default_scene_preview_thumbnail + + let environmentThumbnail = { + srcset: default_scene_preview_thumbnail + }; + + if (meta.images) { + const thumbnailImage = meta.images.find(i => i.type === "preview-thumbnail"); + + if (thumbnailImage) { + const baseURL = new URL(extractUrlBase(environment.bundle_url), window.location.href); + + environmentThumbnail = { + srcset: resolveURL(thumbnailImage.srcset, baseURL) + }; } - ); + } + + return environmentThumbnail; }; createHub = async e => { diff --git a/src/utils/resolveURL.js b/src/utils/resolveURL.js index ddc6c86803b9adc7ea028b2b127ae0783270f521..35ccc3150278558e4026240164116785620ecfc3 100644 --- a/src/utils/resolveURL.js +++ b/src/utils/resolveURL.js @@ -15,3 +15,11 @@ export function resolveURL(url, path) { // Relative URL return path + url; } + +export function extractUrlBase(url) { + const index = url.lastIndexOf("/"); + + if (index === -1) return "./"; + + return url.substr(0, index + 1); +} diff --git a/webpack.config.js b/webpack.config.js index bd561cbdaeab520fb6bd47dcf66f159c4a0b01bc..4fc219ef830791bd6dbc6d201143ba82af6b961c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -234,7 +234,8 @@ const config = { "process.env": JSON.stringify({ NODE_ENV: process.env.NODE_ENV, JANUS_SERVER: process.env.JANUS_SERVER, - DEV_RETICULUM_SERVER: process.env.DEV_RETICULUM_SERVER + DEV_RETICULUM_SERVER: process.env.DEV_RETICULUM_SERVER, + ASSET_BUNDLE_SERVER: process.env.ASSET_BUNDLE_SERVER }) }) ]