diff --git a/src/bit-components.js b/src/bit-components.js index 77c202cf5d..b9a58d0b9b 100644 --- a/src/bit-components.js +++ b/src/bit-components.js @@ -229,3 +229,4 @@ export const VideoTextureTarget = defineComponent({ flags: Types.ui8 }); export const SimpleWater = defineComponent(); +export const Mirror = defineComponent(); diff --git a/src/inflators/mirror.ts b/src/inflators/mirror.ts new file mode 100644 index 0000000000..353d5f9f07 --- /dev/null +++ b/src/inflators/mirror.ts @@ -0,0 +1,51 @@ +import { addComponent } from "bitecs"; +import { PlaneGeometry } from "three"; +import { Reflector } from "three/examples/jsm/objects/Reflector"; +import { addObject3DComponent } from "../utils/jsx-entity"; +import { Mirror } from "../bit-components"; +import { HubsWorld } from "../app"; +import { Layers } from "../camera-layers"; + +const DEFAULT_MIRROR_GEOMETRY = new PlaneGeometry(); +const DEFAULT_TEXTURE_WIDTH = window.innerWidth * window.devicePixelRatio; +const DEFAULT_TEXTURE_HEIGHT = window.innerHeight * window.devicePixelRatio; + +export type MirrorParams = { + color?: string; +}; + +const DEFAULTS = { + color: '#7f7f7f' +}; + +export function inflateMirror(world: HubsWorld, eid: number, params: MirrorParams) { + params = Object.assign({}, DEFAULTS, params); + const geometry = DEFAULT_MIRROR_GEOMETRY; + const reflector = new Reflector(geometry, { + color: params.color, + textureWidth: DEFAULT_TEXTURE_WIDTH, + textureHeight: DEFAULT_TEXTURE_HEIGHT + }); + + // HACK the first time we render this, add the appropriate camera layers + // to the virtual camera. There is no other way to easily access camera + // used for Reflector. We may remove with Three.js r149 or newer because + // Reflector will expose camera. + const originalOnBeforeRender = reflector.onBeforeRender; + reflector.onBeforeRender = function (renderer, scene, camera, geometry, material, group) { + const originalRender = renderer.render; + renderer.render = function (scene, camera) { + camera.layers.enable(Layers.CAMERA_LAYER_THIRD_PERSON_ONLY); + camera.layers.enable(Layers.CAMERA_LAYER_VIDEO_TEXTURE_TARGET); + reflector.onBeforeRender = originalOnBeforeRender; + originalRender.call(renderer, scene, camera); + }; + originalOnBeforeRender(renderer, scene, camera, geometry, material, group); + renderer.render = originalRender; + }; + + addObject3DComponent(world, eid, reflector); + addComponent(world, Mirror, eid); + return eid; +} + diff --git a/src/systems/remove-object3D-system.js b/src/systems/remove-object3D-system.js index 7d1ac03aad..388af4466a 100644 --- a/src/systems/remove-object3D-system.js +++ b/src/systems/remove-object3D-system.js @@ -8,6 +8,7 @@ import { MediaFrame, MediaImage, MediaVideo, + Mirror, Object3DTag, SimpleWater, Skybox, @@ -74,6 +75,7 @@ const cleanupSimpleWaters = cleanupObjOnExit(SimpleWater, obj => { obj.material.dispose(); } }); +const cleanupMirrors = cleanupObjOnExit(Mirror, obj => obj.dispose()); // TODO This feels messy and brittle // @@ -135,6 +137,7 @@ export function removeObject3DSystem(world) { cleanupAudioEmitters(world); cleanupSkyboxes(world); cleanupSimpleWaters(world); + cleanupMirrors(world); // Finally remove all the entities we just removed from the eid2obj map entities.forEach(removeObjFromMap); diff --git a/src/utils/jsx-entity.ts b/src/utils/jsx-entity.ts index 46fa39122d..fd80eaa0c4 100644 --- a/src/utils/jsx-entity.ts +++ b/src/utils/jsx-entity.ts @@ -37,7 +37,8 @@ import { NetworkedFloatyObject, Billboard, MaterialTag, - VideoTextureSource + VideoTextureSource, + Mirror } from "../bit-components"; import { inflateMediaLoader } from "../inflators/media-loader"; import { inflateMediaFrame } from "../inflators/media-frame"; @@ -73,6 +74,7 @@ import { inflateVideoTextureTarget, VideoTextureTargetParams } from "../inflator import { inflateUVScroll, UVScrollParams } from "../inflators/uv-scroll"; import { SimpleWaterParams, inflateSimpleWater } from "../inflators/simple-water"; import { inflatePDF, PDFParams } from "../inflators/pdf"; +import { MirrorParams, inflateMirror } from "../inflators/mirror"; preload( new Promise(resolve => { @@ -225,6 +227,7 @@ export interface ComponentData { grabbable?: GrabbableParams; billboard?: { onlyY: boolean }; simpleWater?: SimpleWaterParams; + mirror?: MirrorParams; } export interface JSXComponentData extends ComponentData { @@ -366,7 +369,8 @@ export const commonInflators: Required<{ [K in keyof ComponentData]: InflatorFn // inflators that create Object3Ds directionalLight: inflateDirectionalLight, - simpleWater: inflateSimpleWater + simpleWater: inflateSimpleWater, + mirror: inflateMirror }; const jsxInflators: Required<{ [K in keyof JSXComponentData]: InflatorFn }> = {