@google/model-viewer
Advanced tools
Comparing version 0.0.2 to 0.0.3
@@ -16,10 +16,12 @@ /* | ||
import { IS_AR_CANDIDATE, IS_IOS } from '../constants.js'; | ||
import { $renderer, $scene } from '../model-viewer-element-base.js'; | ||
import { $renderer, $scene } from '../model-viewer-base.js'; | ||
import { openIOSARQuickLook } from '../utils.js'; | ||
import { deserializeUrl } from '../utils.js'; | ||
const $enterARElement = Symbol('enterARElement'); | ||
const $enterARWithQuickLook = Symbol('enterARWithQuickLook'); | ||
const $enterARWithWebXR = Symbol('enterARWithWebXR'); | ||
export const ARMixin = (ModelViewerElement) => { | ||
return class extends ModelViewerElement { | ||
static get properties() { | ||
return Object.assign({}, super.properties, { unstableWebXR: { type: Boolean, attribute: 'unstable-webxr' }, iosSrc: { type: deserializeUrl, attribute: 'ios-src' } }); | ||
return Object.assign({}, super.properties, { unstableWebxr: { type: Boolean, attribute: 'unstable-webxr' }, iosSrc: { type: deserializeUrl, attribute: 'ios-src' } }); | ||
} | ||
@@ -59,12 +61,12 @@ get canActivateAR() { | ||
if (IS_IOS) { | ||
this.enterARWithQuickLook(); | ||
this[$enterARWithQuickLook](); | ||
} | ||
else if (IS_AR_CANDIDATE) { | ||
this.enterARWithWebXR(); | ||
this[$enterARWithWebXR](); | ||
} | ||
} | ||
async enterARWithQuickLook() { | ||
async [$enterARWithQuickLook]() { | ||
openIOSARQuickLook(this.iosSrc); | ||
} | ||
async enterARWithWebXR() { | ||
async [$enterARWithWebXR]() { | ||
const renderer = this[$renderer]; | ||
@@ -96,6 +98,6 @@ console.log('Attempting to enter fullscreen and present in AR...'); | ||
super.update(changedProperties); | ||
if (!changedProperties.has('unstableWebXR') && !changedProperties.has('iosSrc')) { | ||
if (!changedProperties.has('unstableWebxr') && !changedProperties.has('iosSrc')) { | ||
return; | ||
} | ||
const canShowButton = this.unstableWebXR && IS_AR_CANDIDATE; | ||
const canShowButton = this.unstableWebxr && IS_AR_CANDIDATE; | ||
const iosCandidate = IS_IOS && this.iosSrc != null; | ||
@@ -102,0 +104,0 @@ const renderer = this[$renderer]; |
@@ -15,3 +15,3 @@ /* | ||
*/ | ||
import { $needsRender, $scene, $tick } from '../model-viewer-element-base.js'; | ||
import { $needsRender, $scene, $tick } from '../model-viewer-base.js'; | ||
// How much the model should rotate per | ||
@@ -18,0 +18,0 @@ // second in radians. |
@@ -15,3 +15,3 @@ /* | ||
*/ | ||
import { $needsRender, $onModelLoad, $onResize, $scene } from '../model-viewer-element-base.js'; | ||
import { $needsRender, $onModelLoad, $onResize, $scene } from '../model-viewer-base.js'; | ||
import OrbitControls from '../third_party/three/OrbitControls.js'; | ||
@@ -18,0 +18,0 @@ import { FRAMED_HEIGHT } from '../three-components/ModelScene.js'; |
@@ -16,7 +16,4 @@ /* | ||
import { Color } from 'three'; | ||
import { $needsRender, $onModelLoad, $renderer, $scene, $tick } from '../model-viewer-element-base.js'; | ||
import EnvMapGenerator from '../three-components/EnvMapGenerator.js'; | ||
import { toCubemapAndEquirect } from '../three-components/TextureUtils.js'; | ||
import { $needsRender, $onModelLoad, $renderer, $scene, $tick } from '../model-viewer-base.js'; | ||
const DEFAULT_BACKGROUND_COLOR = '#ffffff'; | ||
const DEFAULT_ENVMAP_SIZE = 512; | ||
const GAMMA_TO_LINEAR = 2.2; | ||
@@ -26,5 +23,5 @@ const $currentCubemap = Symbol('currentCubemap'); | ||
const $setEnvironmentColor = Symbol('setEnvironmentColor'); | ||
const $envMapGenerator = Symbol('envMapGenerator'); | ||
const $hasBackgroundImage = Symbol('hasBackgroundImage'); | ||
const $hasBackgroundColor = Symbol('hasBackgroundColor'); | ||
const $deallocateTextures = Symbol('deallocateTextures'); | ||
export const EnvironmentMixin = (ModelViewerElement) => { | ||
@@ -35,6 +32,2 @@ return class extends ModelViewerElement { | ||
} | ||
constructor() { | ||
super(); | ||
this[$envMapGenerator] = new EnvMapGenerator(this[$renderer].renderer); | ||
} | ||
get [$hasBackgroundImage]() { | ||
@@ -63,17 +56,4 @@ // @TODO #76 | ||
} | ||
let backgroundImage = this.backgroundImage; | ||
if (this[$hasBackgroundImage]) { | ||
let textures = await toCubemapAndEquirect(this[$renderer].renderer, backgroundImage); | ||
// If the background image has changed | ||
// while fetching textures, abort and defer to that | ||
// invocation of this function. | ||
if (backgroundImage !== this.backgroundImage) { | ||
return; | ||
} | ||
if (textures) { | ||
textures.cubemap.name = backgroundImage; | ||
textures.equirect.name = backgroundImage; | ||
this[$setEnvironmentImage](textures.equirect, textures.cubemap); | ||
return; | ||
} | ||
this[$setEnvironmentImage](this.backgroundImage); | ||
} | ||
@@ -97,6 +77,23 @@ else if (this[$hasBackgroundColor]) { | ||
/** | ||
* @param {THREE.Texture} equirect | ||
* @param {THREE.Texture} cubemap | ||
* @param {string} url | ||
*/ | ||
[$setEnvironmentImage](equirect, cubemap) { | ||
async [$setEnvironmentImage](url) { | ||
const textureUtils = this[$renderer].textureUtils; | ||
const textures = await textureUtils.toCubemapAndEquirect(url); | ||
// If the background image has changed | ||
// while fetching textures, abort and defer to that | ||
// invocation of this function. | ||
if (url !== this.backgroundImage) { | ||
return; | ||
} | ||
this[$deallocateTextures](); | ||
// If could not load textures (probably an invalid URL), then abort | ||
// after deallocating textures. | ||
if (!textures) { | ||
this[$scene].model.applyEnvironmentMap(null); | ||
return; | ||
} | ||
const { cubemap, equirect } = textures; | ||
cubemap.name = this.backgroundImage; | ||
equirect.name = this.backgroundImage; | ||
this[$scene].skysphere.material.color = new Color(0xffffff); | ||
@@ -113,2 +110,4 @@ this[$scene].skysphere.material.map = equirect; | ||
[$setEnvironmentColor](color) { | ||
const textureUtils = this[$renderer].textureUtils; | ||
this[$deallocateTextures](); | ||
this[$scene].skysphere.material.color = new Color(color); | ||
@@ -119,3 +118,3 @@ this[$scene].skysphere.material.color.convertGammaToLinear(GAMMA_TO_LINEAR); | ||
// TODO can cache this per renderer and color | ||
const cubemap = this[$envMapGenerator].generate(DEFAULT_ENVMAP_SIZE); | ||
const cubemap = textureUtils.generateDefaultEnvMap(); | ||
this[$currentCubemap] = cubemap; | ||
@@ -125,4 +124,14 @@ this[$scene].model.applyEnvironmentMap(this[$currentCubemap]); | ||
} | ||
[$deallocateTextures]() { | ||
if (this[$scene].skysphere.material.map) { | ||
this[$scene].skysphere.material.map.dispose(); | ||
this[$scene].skysphere.material.map = null; | ||
} | ||
if (this[$currentCubemap]) { | ||
this[$currentCubemap].dispose(); | ||
this[$currentCubemap] = null; | ||
} | ||
} | ||
}; | ||
}; | ||
//# sourceMappingURL=environment.js.map |
@@ -15,3 +15,3 @@ /* | ||
*/ | ||
import { $updateSource } from '../model-viewer-element-base.js'; | ||
import { $updateSource } from '../model-viewer-base.js'; | ||
import { CachingGLTFLoader } from '../three-components/CachingGLTFLoader.js'; | ||
@@ -18,0 +18,0 @@ import { deserializeUrl } from '../utils.js'; |
@@ -15,3 +15,3 @@ /* | ||
*/ | ||
import { $container, $scene } from '../model-viewer-element-base.js'; | ||
import { $container, $scene } from '../model-viewer-base.js'; | ||
const $showMlModel = Symbol('showMlModel'); | ||
@@ -18,0 +18,0 @@ const $hideMlModel = Symbol('hideMlModel'); |
@@ -17,3 +17,3 @@ /* | ||
import { ARMixin } from '../../features/ar.js'; | ||
import ModelViewerElementBase from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase from '../../model-viewer-base.js'; | ||
import { timePasses, waitForEvent } from '../helpers.js'; | ||
@@ -48,3 +48,3 @@ const expect = chai.expect; | ||
document.body.appendChild(element); | ||
element.unstableWebXR = true; | ||
element.unstableWebxr = true; | ||
element.src = './examples/assets/Astronaut.glb'; | ||
@@ -51,0 +51,0 @@ await waitForEvent(element, 'load'); |
@@ -16,3 +16,3 @@ /* | ||
import { $controls, ControlsMixin } from '../../features/controls.js'; | ||
import ModelViewerElementBase, { $scene } from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, { $scene } from '../../model-viewer-base.js'; | ||
import { timePasses, waitForEvent } from '../helpers.js'; | ||
@@ -19,0 +19,0 @@ const expect = chai.expect; |
@@ -16,3 +16,3 @@ /* | ||
import { EnvironmentMixin } from '../../features/environment.js'; | ||
import ModelViewerElementBase, { $scene } from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, { $scene } from '../../model-viewer-base.js'; | ||
import { timePasses, waitForEvent } from '../helpers.js'; | ||
@@ -19,0 +19,0 @@ const expect = chai.expect; |
@@ -16,3 +16,3 @@ /* | ||
import { LoadingMixin } from '../../features/loading.js'; | ||
import ModelViewerElementBase, { $canvas } from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, { $canvas } from '../../model-viewer-base.js'; | ||
import { pickShadowDescendant, timePasses, waitForEvent } from '../helpers.js'; | ||
@@ -19,0 +19,0 @@ const expect = chai.expect; |
@@ -16,3 +16,3 @@ /* | ||
import { MagicLeapMixin } from '../../features/magic-leap.js'; | ||
import ModelViewerElementBase from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase from '../../model-viewer-base.js'; | ||
import { pickShadowDescendant, timePasses } from '../helpers.js'; | ||
@@ -19,0 +19,0 @@ const expect = chai.expect; |
@@ -16,3 +16,3 @@ /* | ||
import './utils-spec.js'; | ||
import './model-viewer-element-base-spec.js'; | ||
import './model-viewer-base-spec.js'; | ||
import './three-components/ModelScene-spec.js'; | ||
@@ -19,0 +19,0 @@ import './three-components/Renderer-spec.js'; |
@@ -16,3 +16,3 @@ /* | ||
import { IS_AR_CANDIDATE } from '../../constants.js'; | ||
import ModelViewerElementBase, { $renderer, $scene } from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, { $renderer, $scene } from '../../model-viewer-base.js'; | ||
import { ARRenderer } from '../../three-components/ARRenderer.js'; | ||
@@ -19,0 +19,0 @@ import { waitForEvent } from '../helpers.js'; |
@@ -16,3 +16,3 @@ /* | ||
import { Matrix4, Mesh, Object3D, SphereBufferGeometry, Vector3 } from 'three'; | ||
import ModelViewerElementBase, { $canvas } from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, { $canvas } from '../../model-viewer-base.js'; | ||
import ModelScene, { FRAMED_HEIGHT, ROOM_PADDING_SCALE } from '../../three-components/ModelScene.js'; | ||
@@ -19,0 +19,0 @@ import Renderer from '../../three-components/Renderer.js'; |
@@ -15,3 +15,3 @@ /* | ||
*/ | ||
import ModelViewerElementBase, { $canvas } from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, { $canvas } from '../../model-viewer-base.js'; | ||
import ModelScene from '../../three-components/ModelScene.js'; | ||
@@ -18,0 +18,0 @@ import Renderer from '../../three-components/Renderer.js'; |
@@ -15,23 +15,78 @@ /* | ||
*/ | ||
import { TextureLoader } from 'three'; | ||
import Renderer from '../../three-components/Renderer.js'; | ||
import { equirectangularToCubemap } from '../../three-components/TextureUtils.js'; | ||
import { WebGLRenderer } from 'three'; | ||
import TextureUtils from '../../three-components/TextureUtils.js'; | ||
const expect = chai.expect; | ||
// Reuse the same canvas as to not stress the WebGL | ||
// context limit | ||
const canvas = document.createElement('canvas'); | ||
const EQUI_URL = './examples/assets/equirectangular.png'; | ||
suite('TextureUtils', () => { | ||
let textureUtils; | ||
setup(() => { | ||
const renderer = new WebGLRenderer({ canvas }); | ||
textureUtils = new TextureUtils(renderer); | ||
}); | ||
teardown(() => textureUtils.dispose()); | ||
suite('load', () => { | ||
test('loads a valid texture from URL', async () => { | ||
let texture = await textureUtils.load(EQUI_URL); | ||
texture.dispose(); | ||
expect(texture.isTexture).to.be.ok; | ||
}); | ||
test('throws on invalid URL', async () => { | ||
try { | ||
await textureUtils.load(null); | ||
expect(false).to.be.ok; | ||
} | ||
catch (e) { | ||
expect(true).to.be.ok; | ||
} | ||
}); | ||
test('throws if texture not found', async () => { | ||
try { | ||
await textureUtils.load('./nope.png'); | ||
expect(false).to.be.ok; | ||
} | ||
catch (e) { | ||
expect(true).to.be.ok; | ||
} | ||
}); | ||
}); | ||
suite('equirectangularToCubemap', () => { | ||
suite('with a valid renderer and texture', () => { | ||
let texture; | ||
let webGlRenderer; | ||
setup((done) => { | ||
const renderer = new Renderer(); | ||
texture = new TextureLoader().load('./examples/assets/equirectangular.png', () => done()); | ||
webGlRenderer = renderer.renderer; | ||
}); | ||
test('creates a cubemap', () => { | ||
const cubemap = equirectangularToCubemap(webGlRenderer, texture); | ||
expect(cubemap).to.be.ok; | ||
}); | ||
test('creates a cubemap from texture', async () => { | ||
const texture = await textureUtils.load(EQUI_URL); | ||
const cubemap = textureUtils.equirectangularToCubemap(texture); | ||
texture.dispose(); | ||
cubemap.dispose(); | ||
expect(cubemap.isTexture).to.be.ok; | ||
}); | ||
test('throws on invalid texture', async () => { | ||
try { | ||
await textureUtils.equirectangularToCubemap({}); | ||
expect(false).to.be.ok; | ||
} | ||
catch (e) { | ||
expect(true).to.be.ok; | ||
} | ||
}); | ||
}); | ||
suite('toCubemapAndEquirect', () => { | ||
test('returns a cubemap and texture from url', async () => { | ||
const textures = await textureUtils.toCubemapAndEquirect(EQUI_URL); | ||
textures.equirect.dispose(); | ||
textures.cubemap.dispose(); | ||
expect(textures.equirect.isTexture).to.be.ok; | ||
expect(textures.cubemap.isTexture).to.be.ok; | ||
}); | ||
test('throws if given an invalid url', async () => { | ||
try { | ||
await textureUtils.toCubemapAndEquirect({}); | ||
expect(false).to.be.ok; | ||
} | ||
catch (e) { | ||
expect(true).to.be.ok; | ||
} | ||
}); | ||
}); | ||
}); | ||
//# sourceMappingURL=TextureUtils-spec.js.map |
@@ -15,7 +15,8 @@ /* | ||
*/ | ||
import { CubeCamera, Scene, Vector3 } from 'three'; | ||
import { CubeCamera, EventDispatcher, Scene, Vector3 } from 'three'; | ||
import Sky from '../third_party/three/Sky.js'; | ||
const SKYSPHERE_SIZE = 10000; | ||
export default class EnvMapGenerator { | ||
export default class EnvMapGenerator extends EventDispatcher { | ||
constructor(renderer) { | ||
super(); | ||
this.renderer = renderer; | ||
@@ -40,2 +41,3 @@ this.scene = new Scene(); | ||
this.scene.add(this.sky); | ||
this.camera = new CubeCamera(1, SKYSPHERE_SIZE * 3, this.maxMapSize); | ||
} | ||
@@ -49,3 +51,3 @@ /** | ||
mapSize = Math.min(mapSize, this.maxMapSize); | ||
this.camera = new CubeCamera(1, SKYSPHERE_SIZE * 3, mapSize); | ||
this.camera.renderTarget.setSize(mapSize, mapSize); | ||
this.camera.clear(this.renderer); | ||
@@ -52,0 +54,0 @@ this.camera.update(this.renderer, this.scene); |
@@ -17,3 +17,4 @@ /* | ||
import { IS_AR_CANDIDATE } from '../constants.js'; | ||
import { $tick } from '../model-viewer-element-base.js'; | ||
import { $tick } from '../model-viewer-base.js'; | ||
import TextureUtils from './TextureUtils.js'; | ||
import { ARRenderer } from './ARRenderer.js'; | ||
@@ -52,2 +53,3 @@ const GAMMA_FACTOR = 2.2; | ||
this[$arRenderer] = ARRenderer.fromInlineRenderer(this); | ||
this.textureUtils = new TextureUtils(this.renderer); | ||
this.scenes = new Set(); | ||
@@ -130,3 +132,8 @@ this.scenesRendered = 0; | ||
} | ||
dispose() { | ||
super.dispose(); | ||
this.textureUtils.dispose(); | ||
this.textureUtils = null; | ||
} | ||
} | ||
//# sourceMappingURL=Renderer.js.map |
@@ -15,42 +15,86 @@ /* | ||
*/ | ||
import { TextureLoader } from 'three'; | ||
import { EventDispatcher, TextureLoader } from 'three'; | ||
import EquirectangularToCubemap from '../third_party/three.equirectangular-to-cubemap/EquirectangularToCubemap.js'; | ||
const CUBE_MAP_SIZE = 1024; | ||
import EnvMapGenerator from './EnvMapGenerator.js'; | ||
const loader = new TextureLoader(); | ||
export const loadTexture = (url) => new Promise((res, rej) => loader.load(url, res, undefined, rej)); | ||
/** | ||
* The texture returned here is from a WebGLRenderCubeTarget, | ||
* which is not the same as a THREE.CubeTexture, and just what | ||
* the current THREE.CubeCamera uses, and has the same effect | ||
* when being used as an environment map. | ||
* | ||
* @param {THREE.Renderer} renderer | ||
* @param {THREE.Texture} texture | ||
* @return {THREE.Texture} | ||
*/ | ||
export const equirectangularToCubemap = async function (renderer, texture) { | ||
const equiToCube = new EquirectangularToCubemap(renderer); | ||
const cubemap = equiToCube.convert(texture, CUBE_MAP_SIZE); | ||
return cubemap; | ||
const defaultConfig = { | ||
cubemapSize: 1024, | ||
synthesizedEnvmapSize: 512, | ||
}; | ||
/** | ||
* Returns a { equirect, cubemap } object with the textures | ||
* accordingly, or null if cannot generate a texture from | ||
* the URL. | ||
* | ||
* @see equirectangularToCubemap with regard to the THREE types. | ||
* @param {THREE.Renderer} renderer | ||
* @param {string} url | ||
* @return {object} | ||
*/ | ||
export const toCubemapAndEquirect = async (renderer, url) => { | ||
try { | ||
const equirect = await loadTexture(url); | ||
const cubemap = await equirectangularToCubemap(renderer, equirect); | ||
return { equirect, cubemap }; | ||
export default class TextureManager extends EventDispatcher { | ||
/** | ||
* @param {THREE.WebGLRenderer} renderer | ||
* @param {?number} config.cubemapSize [1024] | ||
* @param {?number} config.synthesizedEnvmapSize [512] | ||
*/ | ||
constructor(renderer, config = {}) { | ||
super(); | ||
this.config = Object.assign({}, defaultConfig, config); | ||
this.renderer = renderer; | ||
this.cubemapGenerator = new EquirectangularToCubemap(this.renderer); | ||
this.envMapGenerator = new EnvMapGenerator(this.renderer); | ||
} | ||
catch (e) { | ||
return null; | ||
/** | ||
* @param {string} url | ||
* @return {Promise<THREE.Texture>} | ||
*/ | ||
load(url) { | ||
return new Promise((resolve, reject) => loader.load(url, resolve, undefined, reject)); | ||
} | ||
}; | ||
/** | ||
* @param {?number} size | ||
* @return {THREE.Texture} | ||
*/ | ||
generateDefaultEnvMap(size) { | ||
const mapSize = size || this.config.synthesizedEnvmapSize; | ||
return this.envMapGenerator.generate(mapSize); | ||
} | ||
/** | ||
* The texture returned here is from a WebGLRenderCubeTarget, | ||
* which is not the same as a THREE.CubeTexture, and just what | ||
* the current THREE.CubeCamera uses, and has the same effect | ||
* when being used as an environment map. | ||
* | ||
* @param {THREE.Texture} texture | ||
* @param {?number} size | ||
* @return {THREE.Texture} | ||
*/ | ||
equirectangularToCubemap(texture, size) { | ||
const mapSize = size || this.config.cubemapSize; | ||
const cubemap = this.cubemapGenerator.convert(texture, mapSize); | ||
return cubemap; | ||
} | ||
/** | ||
* Returns a { equirect, cubemap } object with the textures | ||
* accordingly, or null if cannot generate a texture from | ||
* the URL. | ||
* | ||
* @see equirectangularToCubemap with regard to the THREE types. | ||
* @param {string} url | ||
* @return {Promise<Object|null>} | ||
*/ | ||
async toCubemapAndEquirect(url) { | ||
let equirect, cubemap; | ||
try { | ||
equirect = await this.load(url); | ||
cubemap = await this.equirectangularToCubemap(equirect); | ||
return { equirect, cubemap }; | ||
} | ||
catch (e) { | ||
if (equirect) { | ||
equirect.dispose(); | ||
} | ||
if (cubemap) { | ||
cubemap.dispose(); | ||
} | ||
return null; | ||
} | ||
} | ||
dispose() { | ||
this.cubemapGenerator.camera.renderTarget.dispose(); | ||
this.envMapGenerator.camera.renderTarget.dispose(); | ||
this.cubemapGenerator = null; | ||
this.envMapGenerator = null; | ||
} | ||
} | ||
//# sourceMappingURL=TextureUtils.js.map |
{ | ||
"name": "@google/model-viewer", | ||
"version": "0.0.2", | ||
"version": "0.0.3", | ||
"description": "Easily display interactive 3D models on the web and in AR!", | ||
@@ -17,8 +17,8 @@ "repository": "https://github.com/GoogleWebComponents/model-viewer", | ||
], | ||
"main": "./lib/model-viewer-element.js", | ||
"module": "./lib/model-viewer-element.js", | ||
"main": "./lib/model-viewer.js", | ||
"module": "./lib/model-viewer.js", | ||
"files": [ | ||
"src", | ||
"lib", | ||
"dist/model-viewer-element.js", | ||
"dist/model-viewer.js", | ||
"dist/unit-tests.js", | ||
@@ -25,0 +25,0 @@ "test.html", |
@@ -5,4 +5,7 @@ > ## 🚨 Status: Experimental | ||
# `<model-viewer>` | ||
# `<model-viewer>` | ||
[](https://travis-ci.org/GoogleWebComponents/model-viewer) | ||
[](https://www.npmjs.com/package/@google/model-viewer) | ||
`<model-viewer>` is a web component that makes rendering interactive 3D | ||
@@ -16,9 +19,13 @@ models - optionally in AR - easy to do, without writing any code, on as many | ||
See: [API](#api), [Demo](https://model-viewer.glitch.me), [Examples](https://github.com/GoogleWebComponents/model-viewer/tree/master/examples) | ||
## Installing | ||
You can load a _bundled build_ via | ||
[unpkg.com](https://unpkg.com/@google/model-viewer/dist/model-viewer-element.js): | ||
[unpkg.com](https://unpkg.com/@google/model-viewer/dist/model-viewer.js): | ||
```html | ||
<script src="https://unpkg.com/@google/model-viewer/dist/model-viewer-element.js"></script> | ||
<script src="https://unpkg.com/@google/model-viewer/dist/model-viewer.js"></script> | ||
``` | ||
@@ -47,3 +54,3 @@ | ||
```html | ||
<script src="path/to/bundled/model-viewer-element.js"></script> | ||
<script src="path/to/bundled/model-viewer.js"></script> | ||
``` | ||
@@ -67,3 +74,3 @@ | ||
<title>3D Test</title> | ||
<script src="path/to/bundled/model-viewer-element.js"></script> | ||
<script src="path/to/bundled/model-viewer.js"></script> | ||
</head> | ||
@@ -118,7 +125,13 @@ <body> | ||
Fullscreen API | 🚧 | ✅ | 🚧 | 🚧 | 🚧 | 🚧 | 🚧 | ||
Web XR Device API | 🚫 | 🎌 | 🚫 | 🚫 | 🚫 | 🚫 | 🚫 | ||
Web XR HitTest API | 🚫 | 🎌 | 🚫 | 🚫 | 🚫 | 🚫 | 🚫 | ||
WebXR Device API | 🚫 | 🎌 | 🚫 | 🚫 | 🚫 | 🚫 | 🚫 | ||
WebXR HitTest API | 🚫 | 🎌 | 🚫 | 🚫 | 🚫 | 🚫 | 🚫 | ||
## API | ||
### Styles | ||
Currently no custom CSS variables are supported, but the model viewer's containing box | ||
can be sized via traditional `width` and `height` properties, and positioned with | ||
the typical properties (`display`, `position`, etc.). | ||
### Attributes | ||
@@ -129,16 +142,20 @@ | ||
* *`src`*: The URL to the 3D model. This parameter is required for | ||
`<model-viewer`> to display. **Note:** only [glTF][glTF]/[GLB][GLB] files | ||
are supported. For more information, see the Supported Formats section. | ||
`<model-viewer`> to display. Only [glTF][glTF]/[GLB][GLB] models | ||
are supported, see [Supported Formats](#supported-formats). | ||
Optional parameters (not required for display): | ||
* *`ar`*: Enables the option to enter AR and place the 3D model in the real | ||
world if the platform supports it. On iOS, this requires that `ios-src` has | ||
also been configured. | ||
* *`auto-rotate`*: Enables the auto rotation of the model. | ||
* *`background-color`*: Sets the background color of the flat view. Takes any | ||
* *`background-color`*: Sets the background color of the scene when viewed inline. Takes any | ||
valid CSS color string. | ||
* *`background-image`*: Sets the background image of the scene when viewed inline. Takes a | ||
URL to an [equirectangular projection image](https://en.wikipedia.org/wiki/Equirectangular_projection) that's used for the skybox, as well as applied as an environment map on the model. Currently only supports traditional image formats (png, jpg), and does not yet support HDR (#65). Setting `background-image` supercedes `background-color`. | ||
* *`controls`*: Enables controls via mouse/touch when in flat view. | ||
* *`ios-src`*: The url to a [USDZ][USDZ] model which will be used in iOS | ||
Safari to launch Quick Look for AR. | ||
* *`ios-src`*: The url to a [USDZ][USDZ] model which will be used on | ||
[supported iOS 12+ devices](https://www.apple.com/ios/augmented-reality/) via | ||
[AR Quick Look](https://developer.apple.com/videos/play/wwdc2018/603/) on Safari. | ||
See [Augmented Reality](#augmented-reality). | ||
* *`magic-leap`*: Enables the ability to view models in AR when viewing content on | ||
[Magic Leap's Helio](https://magicleaphelio.com/) browser, requires that `src` is | ||
a GLB model, and requires the inclusion of the [@magicleap/prismatic](https://www.npmjs.com/package/@magicleap/prismatic) library. | ||
* *`poster`*: Displays an image instead of the model. See [On | ||
@@ -152,2 +169,5 @@ Loading](#on-loading) for more information. | ||
Loading](#on-loading) for more information. | ||
* *`unstable-webxr`*: Enables the ability to view the model in AR via the experimental | ||
[WebXR Device API], currently [implemented only in Chrome Canary](https://developers.google.com/web/updates/2018/06/ar-for-the-web). | ||
See [Augmented Reality](#augmented-reality). | ||
@@ -165,10 +185,10 @@ All attributes have a corresponding property in camel-case format. For example, | ||
## Supported Formats | ||
### Supported Formats | ||
A `<model-viewer>`'s attributes allows developers to specify multiple file types to | ||
work across different platforms. For WebGL and Web XR purposes, both | ||
work across different platforms. For WebGL and WebXR purposes, both | ||
[glTF][glTF] and [GLB][GLB] are supported out of the box. Additionally, | ||
developers can specify a [USDZ][USDZ] file (using the `ios-src` attribute) that | ||
will be used to launch Quick Look on iOS Safari as an interim solution until | ||
Safari has support for something like the Web XR Device and Hit Test APIs. | ||
Safari has support for something like the WebXR Device and Hit Test APIs. | ||
@@ -194,2 +214,4 @@ ### On Loading | ||
See the [loading examples](https://github.com/googlewebcomponents/model-viewer/blob/master/examples/loading.html). | ||
### Important note on data usage | ||
@@ -202,2 +224,18 @@ | ||
### Augmented Reality | ||
There are currently multiple options for viewing content in augmented reality. | ||
Different platforms enable slightly different experiences, but generally finds | ||
a real-world surface and allows the user to place the model, to be viewed through | ||
a camera. | ||
The attributes `ios-src`, `magic-leap` and `unstable-webxr` enable AR features | ||
on certain platforms -- read the [API attributes](#attributes) for each to | ||
understand the support and caveats. | ||
When in augmented reality, all current platforms assume that the models unit size | ||
be in meters, such that a 1.5 unit tall model will be 1.5 meters when in AR. | ||
See the [augmented reality examples](https://github.com/googlewebcomponents/model-viewer/blob/master/examples/augmented-reality.html). | ||
## Development | ||
@@ -230,1 +268,2 @@ | ||
[GLB]: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#glb-file-format-specification | ||
[WebXR Device API]: https://github.com/immersive-web/webxr |
@@ -17,8 +17,9 @@ /* | ||
import {IS_AR_CANDIDATE, IS_IOS} from '../constants.js'; | ||
import {$renderer, $scene} from '../model-viewer-element-base.js'; | ||
import {$renderer, $scene} from '../model-viewer-base.js'; | ||
import {openIOSARQuickLook} from '../utils.js'; | ||
import {deserializeUrl} from '../utils.js'; | ||
const $enterARElement = Symbol('enterARElement'); | ||
const $enterARWithQuickLook = Symbol('enterARWithQuickLook'); | ||
const $enterARWithWebXR = Symbol('enterARWithWebXR'); | ||
@@ -30,3 +31,3 @@ export const ARMixin = (ModelViewerElement) => { | ||
...super.properties, | ||
unstableWebXR: {type: Boolean, attribute: 'unstable-webxr' }, | ||
unstableWebxr: {type: Boolean, attribute: 'unstable-webxr' }, | ||
iosSrc: {type: deserializeUrl, attribute: 'ios-src'} | ||
@@ -74,13 +75,13 @@ }; | ||
if (IS_IOS) { | ||
this.enterARWithQuickLook(); | ||
this[$enterARWithQuickLook](); | ||
} else if (IS_AR_CANDIDATE) { | ||
this.enterARWithWebXR(); | ||
this[$enterARWithWebXR](); | ||
} | ||
} | ||
async enterARWithQuickLook() { | ||
async [$enterARWithQuickLook]() { | ||
openIOSARQuickLook(this.iosSrc); | ||
} | ||
async enterARWithWebXR() { | ||
async [$enterARWithWebXR]() { | ||
const renderer = this[$renderer]; | ||
@@ -115,7 +116,7 @@ | ||
if (!changedProperties.has('unstableWebXR') && !changedProperties.has('iosSrc')) { | ||
if (!changedProperties.has('unstableWebxr') && !changedProperties.has('iosSrc')) { | ||
return; | ||
} | ||
const canShowButton = this.unstableWebXR && IS_AR_CANDIDATE; | ||
const canShowButton = this.unstableWebxr && IS_AR_CANDIDATE; | ||
const iosCandidate = IS_IOS && this.iosSrc != null; | ||
@@ -122,0 +123,0 @@ const renderer = this[$renderer]; |
@@ -16,3 +16,3 @@ /* | ||
import {$needsRender, $scene, $tick} from '../model-viewer-element-base.js'; | ||
import {$needsRender, $scene, $tick} from '../model-viewer-base.js'; | ||
@@ -19,0 +19,0 @@ // How much the model should rotate per |
@@ -18,3 +18,3 @@ /* | ||
import {$needsRender, $onModelLoad, $onResize, $scene} from '../model-viewer-element-base.js'; | ||
import {$needsRender, $onModelLoad, $onResize, $scene} from '../model-viewer-base.js'; | ||
import OrbitControls from '../third_party/three/OrbitControls.js'; | ||
@@ -21,0 +21,0 @@ import {FRAMED_HEIGHT} from '../three-components/ModelScene.js'; |
@@ -18,8 +18,4 @@ /* | ||
import {$needsRender, $onModelLoad, $renderer, $scene, $tick} from '../model-viewer-element-base.js'; | ||
import EnvMapGenerator from '../three-components/EnvMapGenerator.js'; | ||
import {toCubemapAndEquirect} from '../three-components/TextureUtils.js'; | ||
import {$needsRender, $onModelLoad, $renderer, $scene, $tick} from '../model-viewer-base.js'; | ||
const DEFAULT_BACKGROUND_COLOR = '#ffffff'; | ||
const DEFAULT_ENVMAP_SIZE = 512; | ||
const GAMMA_TO_LINEAR = 2.2; | ||
@@ -30,5 +26,5 @@ | ||
const $setEnvironmentColor = Symbol('setEnvironmentColor'); | ||
const $envMapGenerator = Symbol('envMapGenerator'); | ||
const $hasBackgroundImage = Symbol('hasBackgroundImage'); | ||
const $hasBackgroundColor = Symbol('hasBackgroundColor'); | ||
const $deallocateTextures = Symbol('deallocateTextures'); | ||
@@ -45,7 +41,2 @@ export const EnvironmentMixin = (ModelViewerElement) => { | ||
constructor() { | ||
super(); | ||
this[$envMapGenerator] = new EnvMapGenerator(this[$renderer].renderer); | ||
} | ||
get [$hasBackgroundImage]() { | ||
@@ -80,21 +71,4 @@ // @TODO #76 | ||
let backgroundImage = this.backgroundImage; | ||
if (this[$hasBackgroundImage]) { | ||
let textures = await toCubemapAndEquirect( | ||
this[$renderer].renderer, backgroundImage); | ||
// If the background image has changed | ||
// while fetching textures, abort and defer to that | ||
// invocation of this function. | ||
if (backgroundImage !== this.backgroundImage) { | ||
return; | ||
} | ||
if (textures) { | ||
textures.cubemap.name = backgroundImage; | ||
textures.equirect.name = backgroundImage; | ||
this[$setEnvironmentImage](textures.equirect, textures.cubemap); | ||
return; | ||
} | ||
this[$setEnvironmentImage](this.backgroundImage); | ||
} else if (this[$hasBackgroundColor]) { | ||
@@ -121,6 +95,28 @@ this[$setEnvironmentColor](this.backgroundColor); | ||
/** | ||
* @param {THREE.Texture} equirect | ||
* @param {THREE.Texture} cubemap | ||
* @param {string} url | ||
*/ | ||
[$setEnvironmentImage](equirect, cubemap) { | ||
async [$setEnvironmentImage](url) { | ||
const textureUtils = this[$renderer].textureUtils; | ||
const textures = await textureUtils.toCubemapAndEquirect(url); | ||
// If the background image has changed | ||
// while fetching textures, abort and defer to that | ||
// invocation of this function. | ||
if (url !== this.backgroundImage) { | ||
return; | ||
} | ||
this[$deallocateTextures](); | ||
// If could not load textures (probably an invalid URL), then abort | ||
// after deallocating textures. | ||
if (!textures) { | ||
this[$scene].model.applyEnvironmentMap(null); | ||
return; | ||
} | ||
const { cubemap, equirect } = textures; | ||
cubemap.name = this.backgroundImage; | ||
equirect.name = this.backgroundImage; | ||
this[$scene].skysphere.material.color = new Color(0xffffff); | ||
@@ -139,2 +135,6 @@ this[$scene].skysphere.material.map = equirect; | ||
[$setEnvironmentColor](color) { | ||
const textureUtils = this[$renderer].textureUtils; | ||
this[$deallocateTextures](); | ||
this[$scene].skysphere.material.color = new Color(color); | ||
@@ -147,3 +147,3 @@ this[$scene].skysphere.material.color.convertGammaToLinear( | ||
// TODO can cache this per renderer and color | ||
const cubemap = this[$envMapGenerator].generate(DEFAULT_ENVMAP_SIZE); | ||
const cubemap = textureUtils.generateDefaultEnvMap(); | ||
this[$currentCubemap] = cubemap; | ||
@@ -154,3 +154,14 @@ this[$scene].model.applyEnvironmentMap(this[$currentCubemap]); | ||
} | ||
[$deallocateTextures]() { | ||
if (this[$scene].skysphere.material.map) { | ||
this[$scene].skysphere.material.map.dispose(); | ||
this[$scene].skysphere.material.map = null; | ||
} | ||
if (this[$currentCubemap]) { | ||
this[$currentCubemap].dispose(); | ||
this[$currentCubemap] = null; | ||
} | ||
} | ||
} | ||
}; |
@@ -16,3 +16,3 @@ /* | ||
import {$updateSource} from '../model-viewer-element-base.js'; | ||
import {$updateSource} from '../model-viewer-base.js'; | ||
import {CachingGLTFLoader} from '../three-components/CachingGLTFLoader.js'; | ||
@@ -19,0 +19,0 @@ import {deserializeUrl} from '../utils.js'; |
@@ -16,3 +16,3 @@ /* | ||
import {$container, $scene} from '../model-viewer-element-base.js'; | ||
import {$container, $scene} from '../model-viewer-base.js'; | ||
@@ -19,0 +19,0 @@ const $showMlModel = Symbol('showMlModel'); |
@@ -18,3 +18,3 @@ /* | ||
import {$enterARElement, ARMixin} from '../../features/ar.js'; | ||
import ModelViewerElementBase, {$canvas} from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, {$canvas} from '../../model-viewer-base.js'; | ||
import {pickShadowDescendant, timePasses, waitForEvent} from '../helpers.js'; | ||
@@ -58,3 +58,3 @@ | ||
element.unstableWebXR = true; | ||
element.unstableWebxr = true; | ||
element.src = './examples/assets/Astronaut.glb'; | ||
@@ -61,0 +61,0 @@ |
@@ -17,3 +17,3 @@ /* | ||
import {$controls, ControlsMixin} from '../../features/controls.js'; | ||
import ModelViewerElementBase, {$scene} from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, {$scene} from '../../model-viewer-base.js'; | ||
import {timePasses, waitForEvent} from '../helpers.js'; | ||
@@ -20,0 +20,0 @@ |
@@ -17,3 +17,3 @@ /* | ||
import {EnvironmentMixin} from '../../features/environment.js'; | ||
import ModelViewerElementBase, {$scene} from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, {$scene} from '../../model-viewer-base.js'; | ||
import {timePasses, waitForEvent} from '../helpers.js'; | ||
@@ -20,0 +20,0 @@ |
@@ -17,3 +17,3 @@ /* | ||
import {LoadingMixin} from '../../features/loading.js'; | ||
import ModelViewerElementBase, {$canvas} from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, {$canvas} from '../../model-viewer-base.js'; | ||
import {pickShadowDescendant, timePasses, waitForEvent} from '../helpers.js'; | ||
@@ -20,0 +20,0 @@ |
@@ -17,3 +17,3 @@ /* | ||
import {MagicLeapMixin} from '../../features/magic-leap.js'; | ||
import ModelViewerElementBase from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase from '../../model-viewer-base.js'; | ||
import {pickShadowDescendant, timePasses} from '../helpers.js'; | ||
@@ -20,0 +20,0 @@ |
@@ -17,3 +17,3 @@ /* | ||
import './utils-spec.js'; | ||
import './model-viewer-element-base-spec.js'; | ||
import './model-viewer-base-spec.js'; | ||
import './three-components/ModelScene-spec.js'; | ||
@@ -20,0 +20,0 @@ import './three-components/Renderer-spec.js'; |
@@ -17,3 +17,3 @@ /* | ||
import {IS_AR_CANDIDATE} from '../../constants.js'; | ||
import ModelViewerElementBase, {$renderer, $scene} from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, {$renderer, $scene} from '../../model-viewer-base.js'; | ||
import {ARRenderer} from '../../three-components/ARRenderer.js'; | ||
@@ -20,0 +20,0 @@ import ModelScene from '../../three-components/ModelScene.js'; |
@@ -18,3 +18,3 @@ /* | ||
import ModelViewerElementBase, {$canvas} from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, {$canvas} from '../../model-viewer-base.js'; | ||
import ModelScene, {FRAMED_HEIGHT, ROOM_PADDING_SCALE} from '../../three-components/ModelScene.js'; | ||
@@ -21,0 +21,0 @@ import Renderer from '../../three-components/Renderer.js'; |
@@ -16,3 +16,3 @@ /* | ||
import ModelViewerElementBase, {$canvas} from '../../model-viewer-element-base.js'; | ||
import ModelViewerElementBase, {$canvas} from '../../model-viewer-base.js'; | ||
import ModelScene from '../../three-components/ModelScene.js'; | ||
@@ -19,0 +19,0 @@ import Renderer from '../../three-components/Renderer.js'; |
@@ -16,29 +16,81 @@ /* | ||
import {TextureLoader} from 'three'; | ||
import {TextureLoader, WebGLRenderer} from 'three'; | ||
import Renderer from '../../three-components/Renderer.js'; | ||
import {equirectangularToCubemap} from '../../three-components/TextureUtils.js'; | ||
import {waitForEvent} from '../helpers.js'; | ||
import TextureUtils from '../../three-components/TextureUtils.js'; | ||
const expect = chai.expect; | ||
// Reuse the same canvas as to not stress the WebGL | ||
// context limit | ||
const canvas = document.createElement('canvas'); | ||
const EQUI_URL = './examples/assets/equirectangular.png'; | ||
suite('TextureUtils', () => { | ||
let textureUtils; | ||
setup(() => { | ||
const renderer = new WebGLRenderer({canvas}); | ||
textureUtils = new TextureUtils(renderer); | ||
}); | ||
teardown(() => textureUtils.dispose()); | ||
suite('load', () => { | ||
test('loads a valid texture from URL', async () => { | ||
let texture = await textureUtils.load(EQUI_URL); | ||
texture.dispose(); | ||
expect(texture.isTexture).to.be.ok; | ||
}); | ||
test('throws on invalid URL', async () => { | ||
try { | ||
await textureUtils.load(null); | ||
expect(false).to.be.ok; | ||
} catch(e) { | ||
expect(true).to.be.ok; | ||
} | ||
}); | ||
test('throws if texture not found', async () => { | ||
try { | ||
await textureUtils.load('./nope.png'); | ||
expect(false).to.be.ok; | ||
} catch(e) { | ||
expect(true).to.be.ok; | ||
} | ||
}); | ||
}); | ||
suite('equirectangularToCubemap', () => { | ||
suite('with a valid renderer and texture', () => { | ||
let texture; | ||
let webGlRenderer; | ||
test('creates a cubemap from texture', async () => { | ||
const texture = await textureUtils.load(EQUI_URL); | ||
const cubemap = textureUtils.equirectangularToCubemap(texture); | ||
texture.dispose(); | ||
cubemap.dispose(); | ||
expect(cubemap.isTexture).to.be.ok; | ||
}); | ||
test('throws on invalid texture', async () => { | ||
try { | ||
await textureUtils.equirectangularToCubemap({}); | ||
expect(false).to.be.ok; | ||
} catch(e) { | ||
expect(true).to.be.ok; | ||
} | ||
}); | ||
}); | ||
setup((done) => { | ||
const renderer = new Renderer(); | ||
texture = new TextureLoader().load( | ||
'./examples/assets/equirectangular.png', () => done()); | ||
webGlRenderer = renderer.renderer; | ||
}); | ||
suite('toCubemapAndEquirect', () => { | ||
test('returns a cubemap and texture from url', async () => { | ||
const textures = await textureUtils.toCubemapAndEquirect(EQUI_URL); | ||
textures.equirect.dispose(); | ||
textures.cubemap.dispose(); | ||
expect(textures.equirect.isTexture).to.be.ok; | ||
expect(textures.cubemap.isTexture).to.be.ok; | ||
}); | ||
test('creates a cubemap', () => { | ||
const cubemap = equirectangularToCubemap(webGlRenderer, texture); | ||
expect(cubemap).to.be.ok; | ||
}); | ||
test('throws if given an invalid url', async () => { | ||
try { | ||
await textureUtils.toCubemapAndEquirect({}); | ||
expect(false).to.be.ok; | ||
} catch(e) { | ||
expect(true).to.be.ok; | ||
} | ||
}); | ||
}); | ||
}); |
@@ -16,3 +16,3 @@ /* | ||
import {BackSide, CubeCamera, Mesh, MeshBasicMaterial, PlaneBufferGeometry, PointLight, Scene, Vector3} from 'three'; | ||
import {BackSide, CubeCamera, EventDispatcher, Mesh, MeshBasicMaterial, PlaneBufferGeometry, PointLight, Scene, Vector3} from 'three'; | ||
@@ -23,4 +23,5 @@ import Sky from '../third_party/three/Sky.js'; | ||
export default class EnvMapGenerator { | ||
export default class EnvMapGenerator extends EventDispatcher { | ||
constructor(renderer) { | ||
super(); | ||
this.renderer = renderer; | ||
@@ -51,2 +52,4 @@ this.scene = new Scene(); | ||
this.scene.add(this.sky); | ||
this.camera = new CubeCamera(1, SKYSPHERE_SIZE * 3, this.maxMapSize); | ||
} | ||
@@ -61,3 +64,3 @@ | ||
mapSize = Math.min(mapSize, this.maxMapSize); | ||
this.camera = new CubeCamera(1, SKYSPHERE_SIZE * 3, mapSize); | ||
this.camera.renderTarget.setSize(mapSize, mapSize); | ||
this.camera.clear(this.renderer); | ||
@@ -64,0 +67,0 @@ this.camera.update(this.renderer, this.scene); |
@@ -19,4 +19,5 @@ /* | ||
import {IS_AR_CANDIDATE} from '../constants.js'; | ||
import {$tick} from '../model-viewer-element-base.js'; | ||
import {$tick} from '../model-viewer-base.js'; | ||
import TextureUtils from './TextureUtils.js'; | ||
import {ARRenderer} from './ARRenderer.js'; | ||
@@ -62,2 +63,3 @@ | ||
this[$arRenderer] = ARRenderer.fromInlineRenderer(this); | ||
this.textureUtils = new TextureUtils(this.renderer); | ||
@@ -166,2 +168,8 @@ this.scenes = new Set(); | ||
} | ||
dispose() { | ||
super.dispose(); | ||
this.textureUtils.dispose(); | ||
this.textureUtils = null; | ||
} | ||
} |
@@ -16,46 +16,94 @@ /* | ||
import {TextureLoader} from 'three'; | ||
import {EventDispatcher, TextureLoader} from 'three'; | ||
import EquirectangularToCubemap from '../third_party/three.equirectangular-to-cubemap/EquirectangularToCubemap.js'; | ||
const CUBE_MAP_SIZE = 1024; | ||
import EnvMapGenerator from './EnvMapGenerator.js'; | ||
const loader = new TextureLoader(); | ||
const defaultConfig = { | ||
cubemapSize: 1024, | ||
synthesizedEnvmapSize: 512, | ||
}; | ||
export const loadTexture = (url) => | ||
new Promise((res, rej) => loader.load(url, res, undefined, rej)); | ||
export default class TextureManager extends EventDispatcher { | ||
/** | ||
* @param {THREE.WebGLRenderer} renderer | ||
* @param {?number} config.cubemapSize [1024] | ||
* @param {?number} config.synthesizedEnvmapSize [512] | ||
*/ | ||
constructor(renderer, config = {}) { | ||
super(); | ||
this.config = {...defaultConfig, ...config}; | ||
this.renderer = renderer; | ||
this.cubemapGenerator = new EquirectangularToCubemap(this.renderer); | ||
this.envMapGenerator = new EnvMapGenerator(this.renderer); | ||
} | ||
/** | ||
* The texture returned here is from a WebGLRenderCubeTarget, | ||
* which is not the same as a THREE.CubeTexture, and just what | ||
* the current THREE.CubeCamera uses, and has the same effect | ||
* when being used as an environment map. | ||
* | ||
* @param {THREE.Renderer} renderer | ||
* @param {THREE.Texture} texture | ||
* @return {THREE.Texture} | ||
*/ | ||
export const equirectangularToCubemap = | ||
async function(renderer, texture) { | ||
const equiToCube = new EquirectangularToCubemap(renderer); | ||
const cubemap = equiToCube.convert(texture, CUBE_MAP_SIZE); | ||
return cubemap; | ||
} | ||
/** | ||
* @param {string} url | ||
* @return {Promise<THREE.Texture>} | ||
*/ | ||
load(url) { | ||
return new Promise( | ||
(resolve, reject) => loader.load(url, resolve, undefined, reject)); | ||
} | ||
/** | ||
* Returns a { equirect, cubemap } object with the textures | ||
* accordingly, or null if cannot generate a texture from | ||
* the URL. | ||
* | ||
* @see equirectangularToCubemap with regard to the THREE types. | ||
* @param {THREE.Renderer} renderer | ||
* @param {string} url | ||
* @return {object} | ||
*/ | ||
export const toCubemapAndEquirect = async (renderer, url) => { | ||
try { | ||
const equirect = await loadTexture(url); | ||
const cubemap = await equirectangularToCubemap(renderer, equirect); | ||
return {equirect, cubemap}; | ||
} catch (e) { | ||
return null; | ||
/** | ||
* @param {?number} size | ||
* @return {THREE.Texture} | ||
*/ | ||
generateDefaultEnvMap(size) { | ||
const mapSize = size || this.config.synthesizedEnvmapSize; | ||
return this.envMapGenerator.generate(mapSize); | ||
} | ||
/** | ||
* The texture returned here is from a WebGLRenderCubeTarget, | ||
* which is not the same as a THREE.CubeTexture, and just what | ||
* the current THREE.CubeCamera uses, and has the same effect | ||
* when being used as an environment map. | ||
* | ||
* @param {THREE.Texture} texture | ||
* @param {?number} size | ||
* @return {THREE.Texture} | ||
*/ | ||
equirectangularToCubemap(texture, size) { | ||
const mapSize = size || this.config.cubemapSize; | ||
const cubemap = this.cubemapGenerator.convert(texture, mapSize); | ||
return cubemap; | ||
} | ||
/** | ||
* Returns a { equirect, cubemap } object with the textures | ||
* accordingly, or null if cannot generate a texture from | ||
* the URL. | ||
* | ||
* @see equirectangularToCubemap with regard to the THREE types. | ||
* @param {string} url | ||
* @return {Promise<Object|null>} | ||
*/ | ||
async toCubemapAndEquirect(url) { | ||
let equirect, cubemap; | ||
try { | ||
equirect = await this.load(url); | ||
cubemap = await this.equirectangularToCubemap(equirect); | ||
return {equirect, cubemap}; | ||
} catch (e) { | ||
if (equirect) { | ||
equirect.dispose(); | ||
} | ||
if (cubemap) { | ||
cubemap.dispose(); | ||
} | ||
return null; | ||
} | ||
} | ||
dispose() { | ||
this.cubemapGenerator.camera.renderTarget.dispose(); | ||
this.envMapGenerator.camera.renderTarget.dispose(); | ||
this.cubemapGenerator = null; | ||
this.envMapGenerator = null; | ||
} | ||
} |
@@ -7,2 +7,5 @@ { | ||
], | ||
"mochaOptions": { | ||
"timeout": 20000 | ||
}, | ||
"plugins": { | ||
@@ -9,0 +12,0 @@ "sauce": { |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 3 instances in 1 package
3713731
8
77153
259