X Tutup
import { version, revision } from '../core/core.js'; import { now } from '../core/time.js'; import { path } from '../core/path.js'; import { EventHandler } from '../core/event-handler.js'; import { math } from '../math/math.js'; import { Color } from '../math/color.js'; import { Vec3 } from '../math/vec3.js'; import { Mat4 } from '../math/mat4.js'; import { Quat } from '../math/quat.js'; import { http } from '../net/http.js'; import { ADDRESS_CLAMP_TO_EDGE, CLEARFLAG_COLOR, CLEARFLAG_DEPTH, FILTER_NEAREST, PIXELFORMAT_DEPTHSTENCIL, PIXELFORMAT_R8_G8_B8_A8, PRIMITIVE_TRIANGLES, PRIMITIVE_TRIFAN, PRIMITIVE_TRISTRIP } from '../graphics/constants.js'; import { destroyPostEffectQuad } from '../graphics/simple-post-effect.js'; import { GraphicsDevice } from '../graphics/graphics-device.js'; import { RenderTarget } from '../graphics/render-target.js'; import { Texture } from '../graphics/texture.js'; import { LAYERID_DEPTH, LAYERID_IMMEDIATE, LAYERID_SKYBOX, LAYERID_UI, LAYERID_WORLD, LINEBATCH_OVERLAY, SHADER_DEPTH, SORTMODE_NONE, SORTMODE_MANUAL } from '../scene/constants.js'; import { BatchManager } from '../scene/batching/batch-manager.js'; import { ForwardRenderer } from '../scene/forward-renderer.js'; import { ImmediateData } from '../scene/immediate.js'; import { Layer } from '../scene/layer.js'; import { LayerComposition } from '../scene/layer-composition.js'; import { Lightmapper } from '../scene/lightmapper.js'; import { ParticleEmitter } from '../scene/particle-system/particle-emitter.js'; import { Scene } from '../scene/scene.js'; import { Material } from '../scene/materials/material.js'; import { WorldClusters } from '../scene/world-clusters.js'; import { SoundManager } from '../sound/manager.js'; import { AnimationHandler } from '../resources/animation.js'; import { AnimClipHandler } from '../resources/anim-clip.js'; import { AnimStateGraphHandler } from '../resources/anim-state-graph.js'; import { AudioHandler } from '../resources/audio.js'; import { BinaryHandler } from '../resources/binary.js'; import { BundleHandler } from '../resources/bundle.js'; import { ContainerHandler } from '../resources/container.js'; import { CssHandler } from '../resources/css.js'; import { CubemapHandler } from '../resources/cubemap.js'; import { FolderHandler } from '../resources/folder.js'; import { FontHandler } from '../resources/font.js'; import { HierarchyHandler } from '../resources/hierarchy.js'; import { HtmlHandler } from '../resources/html.js'; import { JsonHandler } from '../resources/json.js'; import { MaterialHandler } from '../resources/material.js'; import { ModelHandler } from '../resources/model.js'; import { RenderHandler } from '../resources/render.js'; import { ResourceLoader } from '../resources/loader.js'; import { SceneHandler } from '../resources/scene.js'; import { ScriptHandler } from '../resources/script.js'; import { ShaderHandler } from '../resources/shader.js'; import { SpriteHandler } from '../resources/sprite.js'; import { TemplateHandler } from '../resources/template.js'; import { TextHandler } from '../resources/text.js'; import { TextureAtlasHandler } from '../resources/texture-atlas.js'; import { TextureHandler } from '../resources/texture.js'; import { Asset } from '../asset/asset.js'; import { AssetRegistry } from '../asset/asset-registry.js'; import { BundleRegistry } from '../bundles/bundle-registry.js'; import { ScriptRegistry } from '../script/script-registry.js'; import { I18n } from '../i18n/i18n.js'; import { VrManager } from '../vr/vr-manager.js'; import { XrManager } from '../xr/xr-manager.js'; import { AnimationComponentSystem } from './components/animation/system.js'; import { AnimComponentSystem } from './components/anim/system.js'; import { AudioListenerComponentSystem } from './components/audio-listener/system.js'; import { AudioSourceComponentSystem } from './components/audio-source/system.js'; import { ButtonComponentSystem } from './components/button/system.js'; import { CameraComponentSystem } from './components/camera/system.js'; import { CollisionComponentSystem } from './components/collision/system.js'; import { ComponentSystemRegistry } from './components/registry.js'; import { ComponentSystem } from './components/system.js'; import { ElementComponentSystem } from './components/element/system.js'; import { JointComponentSystem } from './components/joint/system.js'; import { LayoutChildComponentSystem } from './components/layout-child/system.js'; import { LayoutGroupComponentSystem } from './components/layout-group/system.js'; import { LightComponentSystem } from './components/light/system.js'; import { ModelComponentSystem } from './components/model/system.js'; import { RenderComponentSystem } from './components/render/system.js'; import { ParticleSystemComponentSystem } from './components/particle-system/system.js'; import { RigidBodyComponentSystem } from './components/rigid-body/system.js'; import { ScreenComponentSystem } from './components/screen/system.js'; import { ScriptComponentSystem } from './components/script/system.js'; import { ScriptLegacyComponentSystem } from './components/script-legacy/system.js'; import { ScrollViewComponentSystem } from './components/scroll-view/system.js'; import { ScrollbarComponentSystem } from './components/scrollbar/system.js'; import { SoundComponentSystem } from './components/sound/system.js'; import { SpriteComponentSystem } from './components/sprite/system.js'; import { ZoneComponentSystem } from './components/zone/system.js'; import { script } from './script.js'; import { ApplicationStats } from './stats.js'; import { Entity } from './entity.js'; import { SceneRegistry } from './scene-registry.js'; import { FILLMODE_FILL_WINDOW, FILLMODE_KEEP_ASPECT, RESOLUTION_AUTO, RESOLUTION_FIXED } from './constants.js'; import { getApplication, setApplication } from './globals.js'; // Mini-object used to measure progress of loading sets class Progress { constructor(length) { this.length = length; this.count = 0; } inc() { this.count++; } done() { return (this.count === this.length); } } var _deprecationWarning = false; /** * @class * @name Application * @augments EventHandler * @classdesc An Application represents and manages your PlayCanvas application. * If you are developing using the PlayCanvas Editor, the Application is created * for you. You can access your Application instance in your scripts. Below is a * skeleton script which shows how you can access the application 'app' property inside * the initialize and update functions: * * ```javascript * // Editor example: accessing the pc.Application from a script * var MyScript = pc.createScript('myScript'); * * MyScript.prototype.initialize = function() { * // Every script instance has a property 'this.app' accessible in the initialize... * var app = this.app; * }; * * MyScript.prototype.update = function(dt) { * // ...and update functions. * var app = this.app; * }; * ``` * * If you are using the Engine without the Editor, you have to create the application * instance manually. * @description Create a new Application. * @param {Element} canvas - The canvas element. * @param {object} options * @param {ElementInput} [options.elementInput] - Input handler for {@link ElementComponent}s. * @param {Keyboard} [options.keyboard] - Keyboard handler for input. * @param {Mouse} [options.mouse] - Mouse handler for input. * @param {TouchDevice} [options.touch] - TouchDevice handler for input. * @param {GamePads} [options.gamepads] - Gamepad handler for input. * @param {string} [options.scriptPrefix] - Prefix to apply to script urls before loading. * @param {string} [options.assetPrefix] - Prefix to apply to asset urls before loading. * @param {object} [options.graphicsDeviceOptions] - Options object that is passed into the {@link GraphicsDevice} constructor. * @param {string[]} [options.scriptsOrder] - Scripts in order of loading first. * @example * // Engine-only example: create the application manually * var app = new pc.Application(canvas, options); * * // Start the application's main loop * app.start(); */ // PROPERTIES /** * @name Application#scene * @type {Scene} * @description The scene managed by the application. * @example * // Set the tone mapping property of the application's scene * this.app.scene.toneMapping = pc.TONEMAP_FILMIC; */ /** * @name Application#timeScale * @type {number} * @description Scales the global time delta. Defaults to 1. * @example * // Set the app to run at half speed * this.app.timeScale = 0.5; */ /** * @name Application#maxDeltaTime * @type {number} * @description Clamps per-frame delta time to an upper bound. Useful since returning from a tab * deactivation can generate huge values for dt, which can adversely affect game state. Defaults * to 0.1 (seconds). * @example * // Don't clamp inter-frame times of 200ms or less * this.app.maxDeltaTime = 0.2; */ /** * @name Application#scenes * @type {SceneRegistry} * @description The scene registry managed by the application. * @example * // Search the scene registry for a item with the name 'racetrack1' * var sceneItem = this.app.scenes.find('racetrack1'); * * // Load the scene using the item's url * this.app.scenes.loadScene(sceneItem.url); */ /** * @name Application#assets * @type {AssetRegistry} * @description The asset registry managed by the application. * @example * // Search the asset registry for all assets with the tag 'vehicle' * var vehicleAssets = this.app.assets.findByTag('vehicle'); */ /** * @name Application#graphicsDevice * @type {GraphicsDevice} * @description The graphics device used by the application. */ /** * @name Application#systems * @type {ComponentSystemRegistry} * @description The application's component system registry. The Application * constructor adds the following component systems to its component system registry: * * * anim ({@link AnimComponentSystem}) * * animation ({@link AnimationComponentSystem}) * * audiolistener ({@link AudioListenerComponentSystem}) * * button ({@link ButtonComponentSystem}) * * camera ({@link CameraComponentSystem}) * * collision ({@link CollisionComponentSystem}) * * element ({@link ElementComponentSystem}) * * layoutchild ({@link LayoutChildComponentSystem}) * * layoutgroup ({@link LayoutGroupComponentSystem}) * * light ({@link LightComponentSystem}) * * model ({@link ModelComponentSystem}) * * particlesystem ({@link ParticleSystemComponentSystem}) * * rigidbody ({@link RigidBodyComponentSystem}) * * render ({@link RenderComponentSystem}) * * screen ({@link ScreenComponentSystem}) * * script ({@link ScriptComponentSystem}) * * scrollbar ({@link ScrollbarComponentSystem}) * * scrollview ({@link ScrollViewComponentSystem}) * * sound ({@link SoundComponentSystem}) * * sprite ({@link SpriteComponentSystem}) * @example * // Set global gravity to zero * this.app.systems.rigidbody.gravity.set(0, 0, 0); * @example * // Set the global sound volume to 50% * this.app.systems.sound.volume = 0.5; */ /** * @name Application#xr * @type {XrManager} * @description The XR Manager that provides ability to start VR/AR sessions. * @example * // check if VR is available * if (app.xr.isAvailable(pc.XRTYPE_VR)) { * // VR is available * } */ /** * @name Application#lightmapper * @type {Lightmapper} * @description The run-time lightmapper. */ /** * @name Application#loader * @type {ResourceLoader} * @description The resource loader. */ /** * @name Application#root * @type {Entity} * @description The root entity of the application. * @example * // Return the first entity called 'Camera' in a depth-first search of the scene hierarchy * var camera = this.app.root.findByName('Camera'); */ /** * @name Application#keyboard * @type {Keyboard} * @description The keyboard device. */ /** * @name Application#mouse * @type {Mouse} * @description The mouse device. */ /** * @name Application#touch * @type {TouchDevice} * @description Used to get touch events input. */ /** * @name Application#gamepads * @type {GamePads} * @description Used to access GamePad input. */ /** * @name Application#elementInput * @type {ElementInput} * @description Used to handle input for {@link ElementComponent}s. */ /** * @name Application#scripts * @type {ScriptRegistry} * @description The application's script registry. */ /** * @name Application#batcher * @type {BatchManager} * @description The application's batch manager. The batch manager is used to * merge mesh instances in the scene, which reduces the overall number of draw * calls, thereby boosting performance. */ /** * @name Application#autoRender * @type {boolean} * @description When true, the application's render function is called every frame. * Setting autoRender to false is useful to applications where the rendered image * may often be unchanged over time. This can heavily reduce the application's * load on the CPU and GPU. Defaults to true. * @example * // Disable rendering every frame and only render on a keydown event * this.app.autoRender = false; * this.app.keyboard.on('keydown', function (event) { * this.app.renderNextFrame = true; * }, this); */ /** * @name Application#renderNextFrame * @type {boolean} * @description Set to true to render the scene on the next iteration of the main loop. * This only has an effect if {@link Application#autoRender} is set to false. The * value of renderNextFrame is set back to false again as soon as the scene has been * rendered. * @example * // Render the scene only while space key is pressed * if (this.app.keyboard.isPressed(pc.KEY_SPACE)) { * this.app.renderNextFrame = true; * } */ /** * @name Application#i18n * @type {I18n} * @description Handles localization. */ /** * @private * @static * @name app * @type {Application|undefined} * @description Gets the current application, if any. */ var app = null; class Application extends EventHandler { constructor(canvas, options = {}) { super(); console.log("Powered by PlayCanvas " + version + " " + revision); // Store application instance Application._applications[canvas.id] = this; setApplication(this); app = this; this._time = 0; this.timeScale = 1; this.maxDeltaTime = 0.1; // Maximum delta is 0.1s or 10 fps. this.frame = 0; // the total number of frames the application has updated since start() was called this.autoRender = true; this.renderNextFrame = false; // enable if you want entity type script attributes // to not be re-mapped when an entity is cloned this.useLegacyScriptAttributeCloning = script.legacy; this._librariesLoaded = false; this._fillMode = FILLMODE_KEEP_ASPECT; this._resolutionMode = RESOLUTION_FIXED; this._allowResize = true; // for compatibility this.context = this; if (! options.graphicsDeviceOptions) options.graphicsDeviceOptions = { }; options.graphicsDeviceOptions.xrCompatible = true; options.graphicsDeviceOptions.alpha = options.graphicsDeviceOptions.alpha || false; this.graphicsDevice = new GraphicsDevice(canvas, options.graphicsDeviceOptions); this.stats = new ApplicationStats(this.graphicsDevice); this._soundManager = new SoundManager(options); this.loader = new ResourceLoader(this); WorldClusters.init(this.graphicsDevice); // stores all entities that have been created // for this app by guid this._entityIndex = {}; this.scene = new Scene(); this.root = new Entity(this); this.root._enabledInHierarchy = true; this._enableList = []; this._enableList.size = 0; this.assets = new AssetRegistry(this.loader); if (options.assetPrefix) this.assets.prefix = options.assetPrefix; this.bundles = new BundleRegistry(this.assets); // set this to false if you want to run without using bundles // We set it to true only if TextDecoder is available because we currently // rely on it for untarring. this.enableBundles = (typeof TextDecoder !== 'undefined'); this.scriptsOrder = options.scriptsOrder || []; this.scripts = new ScriptRegistry(this); this.i18n = new I18n(this); this.scenes = new SceneRegistry(this); var self = this; this.defaultLayerWorld = new Layer({ name: "World", id: LAYERID_WORLD }); if (this.graphicsDevice.webgl2) { // WebGL 2 depth layer just copies existing depth this.defaultLayerDepth = new Layer({ enabled: false, name: "Depth", id: LAYERID_DEPTH, onEnable: function () { if (this.renderTarget) return; var depthBuffer = new Texture(self.graphicsDevice, { format: PIXELFORMAT_DEPTHSTENCIL, width: self.graphicsDevice.width, height: self.graphicsDevice.height, mipmaps: false }); depthBuffer.name = 'rt-depth2'; depthBuffer.minFilter = FILTER_NEAREST; depthBuffer.magFilter = FILTER_NEAREST; depthBuffer.addressU = ADDRESS_CLAMP_TO_EDGE; depthBuffer.addressV = ADDRESS_CLAMP_TO_EDGE; this.renderTarget = new RenderTarget({ colorBuffer: null, depthBuffer: depthBuffer, autoResolve: false }); self.graphicsDevice.scope.resolve("uDepthMap").setValue(depthBuffer); }, onDisable: function () { if (!this.renderTarget) return; this.renderTarget._depthBuffer.destroy(); this.renderTarget.destroy(); this.renderTarget = null; }, onPreRenderOpaque: function (cameraPass) { // resize depth map if needed var gl = self.graphicsDevice.gl; this.srcFbo = gl.getParameter(gl.FRAMEBUFFER_BINDING); if (!this.renderTarget || (this.renderTarget.width !== self.graphicsDevice.width || this.renderTarget.height !== self.graphicsDevice.height)) { this.onDisable(); this.onEnable(); } // disable clearing this.oldClear = this.cameras[cameraPass].camera._clearOptions; this.cameras[cameraPass].camera._clearOptions = this.depthClearOptions; }, onPostRenderOpaque: function (cameraPass) { // copy depth if (!this.renderTarget) return; this.cameras[cameraPass].camera._clearOptions = this.oldClear; var gl = self.graphicsDevice.gl; self.graphicsDevice.setRenderTarget(this.renderTarget); self.graphicsDevice.updateBegin(); gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.srcFbo); gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.renderTarget._glFrameBuffer); gl.blitFramebuffer(0, 0, this.renderTarget.width, this.renderTarget.height, 0, 0, this.renderTarget.width, this.renderTarget.height, gl.DEPTH_BUFFER_BIT, gl.NEAREST); } }); this.defaultLayerDepth.depthClearOptions = { flags: 0 }; } else { // WebGL 1 depth layer just renders same objects as in World, but with RGBA-encoded depth shader this.defaultLayerDepth = new Layer({ enabled: false, name: "Depth", id: LAYERID_DEPTH, shaderPass: SHADER_DEPTH, onEnable: function () { if (this.renderTarget) return; var colorBuffer = new Texture(self.graphicsDevice, { format: PIXELFORMAT_R8_G8_B8_A8, width: self.graphicsDevice.width, height: self.graphicsDevice.height, mipmaps: false }); colorBuffer.name = 'rt-depth1'; colorBuffer.minFilter = FILTER_NEAREST; colorBuffer.magFilter = FILTER_NEAREST; colorBuffer.addressU = ADDRESS_CLAMP_TO_EDGE; colorBuffer.addressV = ADDRESS_CLAMP_TO_EDGE; this.renderTarget = new RenderTarget(self.graphicsDevice, colorBuffer, { depth: true, stencil: self.graphicsDevice.supportsStencil }); self.graphicsDevice.scope.resolve("uDepthMap").setValue(colorBuffer); }, onDisable: function () { if (!this.renderTarget) return; this.renderTarget._colorBuffer.destroy(); this.renderTarget.destroy(); this.renderTarget = null; }, onPostCull: function (cameraPass) { // Collect all rendered mesh instances with the same render target as World has, depthWrite == true and prior to this layer to replicate blitFramebuffer on WebGL2 var visibleObjects = this.instances.visibleOpaque[cameraPass]; var visibleList = visibleObjects.list; var visibleLength = 0; var layers = self.scene.layers.layerList; var subLayerEnabled = self.scene.layers.subLayerEnabled; var isTransparent = self.scene.layers.subLayerList; // can't use self.defaultLayerWorld.renderTarget because projects that use the editor override default layers var rt = self.scene.layers.getLayerById(LAYERID_WORLD).renderTarget; var cam = this.cameras[cameraPass]; var layer; var j; var layerVisibleList, layerCamId, layerVisibleListLength, drawCall, transparent; for (var i = 0; i < layers.length; i++) { layer = layers[i]; if (layer === this) break; if (layer.renderTarget !== rt || !layer.enabled || !subLayerEnabled[i]) continue; layerCamId = layer.cameras.indexOf(cam); if (layerCamId < 0) continue; transparent = isTransparent[i]; layerVisibleList = transparent ? layer.instances.visibleTransparent[layerCamId] : layer.instances.visibleOpaque[layerCamId]; layerVisibleListLength = layerVisibleList.length; layerVisibleList = layerVisibleList.list; for (j = 0; j < layerVisibleListLength; j++) { drawCall = layerVisibleList[j]; if (drawCall.material && drawCall.material.depthWrite && !drawCall._noDepthDrawGl1) { visibleList[visibleLength] = drawCall; visibleLength++; } } } visibleObjects.length = visibleLength; }, onPreRenderOpaque: function (cameraPass) { // resize depth map if needed if (!this.renderTarget || (this.renderTarget.width !== self.graphicsDevice.width || this.renderTarget.height !== self.graphicsDevice.height)) { this.onDisable(); this.onEnable(); } this.oldClear = this.cameras[cameraPass].camera._clearOptions; this.cameras[cameraPass].camera._clearOptions = this.rgbaDepthClearOptions; }, onDrawCall: function () { self.graphicsDevice.setColorWrite(true, true, true, true); }, onPostRenderOpaque: function (cameraPass) { if (!this.renderTarget) return; this.cameras[cameraPass].camera._clearOptions = this.oldClear; } }); this.defaultLayerDepth.rgbaDepthClearOptions = { color: [254.0 / 255, 254.0 / 255, 254.0 / 255, 254.0 / 255], depth: 1.0, flags: CLEARFLAG_COLOR | CLEARFLAG_DEPTH }; } this.defaultLayerSkybox = new Layer({ enabled: false, name: "Skybox", id: LAYERID_SKYBOX, opaqueSortMode: SORTMODE_NONE }); this.defaultLayerUi = new Layer({ enabled: true, name: "UI", id: LAYERID_UI, transparentSortMode: SORTMODE_MANUAL, passThrough: false }); this.defaultLayerImmediate = new Layer({ enabled: true, name: "Immediate", id: LAYERID_IMMEDIATE, opaqueSortMode: SORTMODE_NONE, passThrough: true }); const defaultLayerComposition = new LayerComposition(this.graphicsDevice, "default"); defaultLayerComposition.pushOpaque(this.defaultLayerWorld); defaultLayerComposition.pushOpaque(this.defaultLayerDepth); defaultLayerComposition.pushOpaque(this.defaultLayerSkybox); defaultLayerComposition.pushTransparent(this.defaultLayerWorld); defaultLayerComposition.pushOpaque(this.defaultLayerImmediate); defaultLayerComposition.pushTransparent(this.defaultLayerImmediate); defaultLayerComposition.pushTransparent(this.defaultLayerUi); this.scene.layers = defaultLayerComposition; this._immediateLayer = this.defaultLayerImmediate; // Default layers patch this.scene.on('set:layers', function (oldComp, newComp) { var list = newComp.layerList; var layer; for (var i = 0; i < list.length; i++) { layer = list[i]; switch (layer.id) { case LAYERID_DEPTH: layer.onEnable = self.defaultLayerDepth.onEnable; layer.onDisable = self.defaultLayerDepth.onDisable; layer.onPreRenderOpaque = self.defaultLayerDepth.onPreRenderOpaque; layer.onPostRenderOpaque = self.defaultLayerDepth.onPostRenderOpaque; layer.depthClearOptions = self.defaultLayerDepth.depthClearOptions; layer.rgbaDepthClearOptions = self.defaultLayerDepth.rgbaDepthClearOptions; layer.shaderPass = self.defaultLayerDepth.shaderPass; layer.onPostCull = self.defaultLayerDepth.onPostCull; layer.onDrawCall = self.defaultLayerDepth.onDrawCall; break; case LAYERID_UI: layer.passThrough = self.defaultLayerUi.passThrough; break; case LAYERID_IMMEDIATE: layer.passThrough = self.defaultLayerImmediate.passThrough; break; } } }); this.renderer = new ForwardRenderer(this.graphicsDevice); this.renderer.scene = this.scene; this.lightmapper = new Lightmapper(this.graphicsDevice, this.root, this.scene, this.renderer, this.assets); this.once('prerender', this._firstBake, this); this.batcher = new BatchManager(this.graphicsDevice, this.root, this.scene); this.once('prerender', this._firstBatch, this); this.keyboard = options.keyboard || null; this.mouse = options.mouse || null; this.touch = options.touch || null; this.gamepads = options.gamepads || null; this.elementInput = options.elementInput || null; if (this.elementInput) this.elementInput.app = this; this.vr = null; this.xr = new XrManager(this); if (this.elementInput) this.elementInput.attachSelectEvents(); this._inTools = false; this._skyboxLast = 0; this._scriptPrefix = options.scriptPrefix || ''; if (this.enableBundles) { this.loader.addHandler("bundle", new BundleHandler(this.assets)); } this.loader.addHandler("animation", new AnimationHandler()); this.loader.addHandler("animclip", new AnimClipHandler()); this.loader.addHandler("animstategraph", new AnimStateGraphHandler()); this.loader.addHandler("model", new ModelHandler(this.graphicsDevice, this.scene.defaultMaterial)); this.loader.addHandler("render", new RenderHandler(this.assets)); this.loader.addHandler("material", new MaterialHandler(this)); this.loader.addHandler("texture", new TextureHandler(this.graphicsDevice, this.assets, this.loader)); this.loader.addHandler("text", new TextHandler()); this.loader.addHandler("json", new JsonHandler()); this.loader.addHandler("audio", new AudioHandler(this._soundManager)); this.loader.addHandler("script", new ScriptHandler(this)); this.loader.addHandler("scene", new SceneHandler(this)); this.loader.addHandler("cubemap", new CubemapHandler(this.graphicsDevice, this.assets, this.loader)); this.loader.addHandler("html", new HtmlHandler()); this.loader.addHandler("css", new CssHandler()); this.loader.addHandler("shader", new ShaderHandler()); this.loader.addHandler("hierarchy", new HierarchyHandler(this)); this.loader.addHandler("folder", new FolderHandler()); this.loader.addHandler("font", new FontHandler(this.loader)); this.loader.addHandler("binary", new BinaryHandler()); this.loader.addHandler("textureatlas", new TextureAtlasHandler(this.loader)); this.loader.addHandler("sprite", new SpriteHandler(this.assets, this.graphicsDevice)); this.loader.addHandler("template", new TemplateHandler(this)); this.loader.addHandler("container", new ContainerHandler(this.graphicsDevice, this.scene.defaultMaterial)); this.systems = new ComponentSystemRegistry(); this.systems.add(new RigidBodyComponentSystem(this)); this.systems.add(new CollisionComponentSystem(this)); this.systems.add(new JointComponentSystem(this)); this.systems.add(new AnimationComponentSystem(this)); this.systems.add(new AnimComponentSystem(this)); this.systems.add(new ModelComponentSystem(this)); this.systems.add(new RenderComponentSystem(this)); this.systems.add(new CameraComponentSystem(this)); this.systems.add(new LightComponentSystem(this)); if (script.legacy) { this.systems.add(new ScriptLegacyComponentSystem(this)); } else { this.systems.add(new ScriptComponentSystem(this)); } this.systems.add(new AudioSourceComponentSystem(this, this._soundManager)); this.systems.add(new SoundComponentSystem(this, this._soundManager)); this.systems.add(new AudioListenerComponentSystem(this, this._soundManager)); this.systems.add(new ParticleSystemComponentSystem(this)); this.systems.add(new ScreenComponentSystem(this)); this.systems.add(new ElementComponentSystem(this)); this.systems.add(new ButtonComponentSystem(this)); this.systems.add(new ScrollViewComponentSystem(this)); this.systems.add(new ScrollbarComponentSystem(this)); this.systems.add(new SpriteComponentSystem(this)); this.systems.add(new LayoutGroupComponentSystem(this)); this.systems.add(new LayoutChildComponentSystem(this)); this.systems.add(new ZoneComponentSystem(this)); this._visibilityChangeHandler = this.onVisibilityChange.bind(this); // Depending on browser add the correct visibiltychange event and store the name of the hidden attribute // in this._hiddenAttr. if (typeof document !== 'undefined') { if (document.hidden !== undefined) { this._hiddenAttr = 'hidden'; document.addEventListener('visibilitychange', this._visibilityChangeHandler, false); } else if (document.mozHidden !== undefined) { this._hiddenAttr = 'mozHidden'; document.addEventListener('mozvisibilitychange', this._visibilityChangeHandler, false); } else if (document.msHidden !== undefined) { this._hiddenAttr = 'msHidden'; document.addEventListener('msvisibilitychange', this._visibilityChangeHandler, false); } else if (document.webkitHidden !== undefined) { this._hiddenAttr = 'webkitHidden'; document.addEventListener('webkitvisibilitychange', this._visibilityChangeHandler, false); } } // bind tick function to current scope /* eslint-disable-next-line no-use-before-define */ this.tick = makeTick(this); // Circular linting issue as makeTick and Application reference each other } static _applications = {}; /** * @static * @function * @name Application.getApplication * @description Get the current application. In the case where there are multiple running * applications, the function can get an application based on a supplied canvas id. This * function is particularly useful when the current Application is not readily available. * For example, in the JavaScript console of the browser's developer tools. * @param {string} [id] - If defined, the returned application should use the canvas which has this id. Otherwise current application will be returned. * @returns {Application|undefined} The running application, if any. * @example * var app = pc.Application.getApplication(); */ static getApplication(id) { return id ? Application._applications[id] : getApplication(); } /** * @readonly * @name Application#fillMode * @type {string} * @description The current fill mode of the canvas. Can be: * * * {@link FILLMODE_NONE}: the canvas will always match the size provided. * * {@link FILLMODE_FILL_WINDOW}: the canvas will simply fill the window, changing aspect ratio. * * {@link FILLMODE_KEEP_ASPECT}: the canvas will grow to fill the window as best it can while maintaining the aspect ratio. */ get fillMode() { return this._fillMode; } /** * @readonly * @name Application#resolutionMode * @type {string} * @description The current resolution mode of the canvas, Can be: * * * {@link RESOLUTION_AUTO}: if width and height are not provided, canvas will be resized to match canvas client size. * * {@link RESOLUTION_FIXED}: resolution of canvas will be fixed. */ get resolutionMode() { return this._resolutionMode; } /** * @function * @name Application#configure * @description Load the application configuration file and apply application properties and fill the asset registry. * @param {string} url - The URL of the configuration file to load. * @param {callbacks.ConfigureApp} callback - The Function called when the configuration file is loaded and parsed (or an error occurs). */ configure(url, callback) { var self = this; http.get(url, function (err, response) { if (err) { callback(err); return; } var props = response.application_properties; var scenes = response.scenes; var assets = response.assets; self._parseApplicationProperties(props, function (err) { self._parseScenes(scenes); self._parseAssets(assets); if (!err) { callback(null); } else { callback(err); } }); }); } /** * @function * @name Application#preload * @description Load all assets in the asset registry that are marked as 'preload'. * @param {callbacks.PreloadApp} callback - Function called when all assets are loaded. */ preload(callback) { var self = this; var i, total; self.fire("preload:start"); // get list of assets to preload var assets = this.assets.list({ preload: true }); var _assets = new Progress(assets.length); var _done = false; // check if all loading is done var done = function () { // do not proceed if application destroyed if (!self.graphicsDevice) { return; } if (!_done && _assets.done()) { _done = true; self.fire("preload:end"); callback(); } }; // totals loading progress of assets total = assets.length; var count = function () { return _assets.count; }; if (_assets.length) { var onAssetLoad = function (asset) { _assets.inc(); self.fire('preload:progress', count() / total); if (_assets.done()) done(); }; var onAssetError = function (err, asset) { _assets.inc(); self.fire('preload:progress', count() / total); if (_assets.done()) done(); }; // for each asset for (i = 0; i < assets.length; i++) { if (!assets[i].loaded) { assets[i].once('load', onAssetLoad); assets[i].once('error', onAssetError); this.assets.load(assets[i]); } else { _assets.inc(); self.fire("preload:progress", count() / total); if (_assets.done()) done(); } } } else { done(); } } _preloadScripts(sceneData, callback) { if (!script.legacy) { callback(); return; } var self = this; self.systems.script.preloading = true; var scripts = this._getScriptReferences(sceneData); var i = 0, l = scripts.length; var progress = new Progress(l); var scriptUrl; var regex = /^http(s)?:\/\//; if (l) { var onLoad = function (err, ScriptType) { if (err) console.error(err); progress.inc(); if (progress.done()) { self.systems.script.preloading = false; callback(); } }; for (i = 0; i < l; i++) { scriptUrl = scripts[i]; // support absolute URLs (for now) if (!regex.test(scriptUrl.toLowerCase()) && self._scriptPrefix) scriptUrl = path.join(self._scriptPrefix, scripts[i]); this.loader.load(scriptUrl, 'script', onLoad); } } else { self.systems.script.preloading = false; callback(); } } // handle area light property _handleAreaLightDataProperty(prop) { var asset = this.assets.get(prop); if (asset) { this.setAreaLightLuts(asset); } else { this.assets.once('add:' + prop, this.setAreaLightLuts, this); } } // set application properties from data file _parseApplicationProperties(props, callback) { var i; var len; // configure retrying assets if (typeof props.maxAssetRetries === 'number' && props.maxAssetRetries > 0) { this.loader.enableRetry(props.maxAssetRetries); } // TODO: remove this temporary block after migrating properties if (!props.useDevicePixelRatio) props.useDevicePixelRatio = props.use_device_pixel_ratio; if (!props.resolutionMode) props.resolutionMode = props.resolution_mode; if (!props.fillMode) props.fillMode = props.fill_mode; this._width = props.width; this._height = props.height; if (props.useDevicePixelRatio) { this.graphicsDevice.maxPixelRatio = window.devicePixelRatio; } this.setCanvasResolution(props.resolutionMode, this._width, this._height); this.setCanvasFillMode(props.fillMode, this._width, this._height); // set up layers if (props.layers && props.layerOrder) { var composition = new LayerComposition(this.graphicsDevice, "application"); var layers = {}; for (var key in props.layers) { var data = props.layers[key]; data.id = parseInt(key, 10); // depth layer should only be enabled when needed // by incrementing its ref counter data.enabled = data.id !== LAYERID_DEPTH; layers[key] = new Layer(data); } for (i = 0, len = props.layerOrder.length; i < len; i++) { var sublayer = props.layerOrder[i]; var layer = layers[sublayer.layer]; if (!layer) continue; if (sublayer.transparent) { composition.pushTransparent(layer); } else { composition.pushOpaque(layer); } composition.subLayerEnabled[i] = sublayer.enabled; } this.scene.layers = composition; } // add batch groups if (props.batchGroups) { for (i = 0, len = props.batchGroups.length; i < len; i++) { var grp = props.batchGroups[i]; this.batcher.addGroup(grp.name, grp.dynamic, grp.maxAabbSize, grp.id, grp.layers); } } // set localization assets if (props.i18nAssets) { this.i18n.assets = props.i18nAssets; } if (props.areaLightDataAsset) { this._handleAreaLightDataProperty(props.areaLightDataAsset); } this._loadLibraries(props.libraries, callback); } _loadLibraries(urls, callback) { var len = urls.length; var count = len; var self = this; var regex = /^http(s)?:\/\//; if (len) { var onLoad = function (err, script) { count--; if (err) { callback(err); } else if (count === 0) { self.onLibrariesLoaded(); callback(null); } }; for (var i = 0; i < len; ++i) { var url = urls[i]; if (!regex.test(url.toLowerCase()) && self._scriptPrefix) url = path.join(self._scriptPrefix, url); this.loader.load(url, 'script', onLoad); } } else { self.onLibrariesLoaded(); callback(null); } } // insert scene name/urls into the registry _parseScenes(scenes) { if (!scenes) return; for (var i = 0; i < scenes.length; i++) { this.scenes.add(scenes[i].name, scenes[i].url); } } // insert assets into registry _parseAssets(assets) { var i, id; var list = []; var scriptsIndex = {}; var bundlesIndex = {}; if (!script.legacy) { // add scripts in order of loading first for (i = 0; i < this.scriptsOrder.length; i++) { id = this.scriptsOrder[i]; if (!assets[id]) continue; scriptsIndex[id] = true; list.push(assets[id]); } // then add bundles if (this.enableBundles) { for (id in assets) { if (assets[id].type === 'bundle') { bundlesIndex[id] = true; list.push(assets[id]); } } } // then add rest of assets for (id in assets) { if (scriptsIndex[id] || bundlesIndex[id]) continue; list.push(assets[id]); } } else { if (this.enableBundles) { // add bundles for (id in assets) { if (assets[id].type === 'bundle') { bundlesIndex[id] = true; list.push(assets[id]); } } } // then add rest of assets for (id in assets) { if (bundlesIndex[id]) continue; list.push(assets[id]); } } for (i = 0; i < list.length; i++) { var data = list[i]; var asset = new Asset(data.name, data.type, data.file, data.data); asset.id = parseInt(data.id, 10); asset.preload = data.preload ? data.preload : false; // if this is a script asset and has already been embedded in the page then // mark it as loaded asset.loaded = data.type === 'script' && data.data && data.data.loadingType > 0; // tags asset.tags.add(data.tags); // i18n if (data.i18n) { for (var locale in data.i18n) { asset.addLocalizedAssetId(locale, data.i18n[locale]); } } // registry this.assets.add(asset); } } _getScriptReferences(scene) { var i, key; var priorityScripts = []; if (scene.settings.priority_scripts) { priorityScripts = scene.settings.priority_scripts; } var _scripts = []; var _index = {}; // first add priority scripts for (i = 0; i < priorityScripts.length; i++) { _scripts.push(priorityScripts[i]); _index[priorityScripts[i]] = true; } // then iterate hierarchy to get referenced scripts var entities = scene.entities; for (key in entities) { if (!entities[key].components.script) { continue; } var scripts = entities[key].components.script.scripts; for (i = 0; i < scripts.length; i++) { if (_index[scripts[i].url]) continue; _scripts.push(scripts[i].url); _index[scripts[i].url] = true; } } return _scripts; } /** * @function * @name Application#start * @description Start the application. This function does the following: * 1. Fires an event on the application named 'start' * 2. Calls initialize for all components on entities in the hierarchy * 3. Fires an event on the application named 'initialize' * 4. Calls postInitialize for all components on entities in the hierarchy * 5. Fires an event on the application named 'postinitialize' * 6. Starts executing the main loop of the application * This function is called internally by PlayCanvas applications made in the Editor * but you will need to call start yourself if you are using the engine stand-alone. * @example * app.start(); */ start() { this.frame = 0; this.fire("start", { timestamp: now(), target: this }); if (!this._librariesLoaded) { this.onLibrariesLoaded(); } ComponentSystem.initialize(this.root); this.fire("initialize"); ComponentSystem.postInitialize(this.root); this.fire("postinitialize"); this.tick(); } /** * @function * @name Application#update * @description Update the application. This function will call the update * functions and then the postUpdate functions of all enabled components. It * will then update the current state of all connected input devices. * This function is called internally in the application's main loop and * does not need to be called explicitly. * @param {number} dt - The time delta since the last frame. */ update(dt) { this.frame++; this.graphicsDevice.updateClientRect(); if (this.vr) this.vr.poll(); // #if _PROFILER this.stats.frame.updateStart = now(); // #endif // Perform ComponentSystem update if (script.legacy) ComponentSystem.fixedUpdate(1.0 / 60.0, this._inTools); ComponentSystem.update(dt, this._inTools); ComponentSystem.animationUpdate(dt, this._inTools); ComponentSystem.postUpdate(dt, this._inTools); // fire update event this.fire("update", dt); if (this.controller) { this.controller.update(dt); } if (this.mouse) { this.mouse.update(dt); } if (this.keyboard) { this.keyboard.update(dt); } if (this.gamepads) { this.gamepads.update(dt); } // #if _PROFILER this.stats.frame.updateTime = now() - this.stats.frame.updateStart; // #endif } /** * @function * @name Application#render * @description Render the application's scene. More specifically, the scene's * {@link LayerComposition} is rendered by the application's {@link ForwardRenderer}. * This function is called internally in the application's main loop and * does not need to be called explicitly. */ render() { // #if _PROFILER this.stats.frame.renderStart = now(); // #endif this.fire("prerender"); this.root.syncHierarchy(); this.batcher.updateAll(); this.renderer.renderComposition(this.scene.layers); this.fire("postrender"); // #if _PROFILER this.stats.frame.renderTime = now() - this.stats.frame.renderStart; // #endif } _fillFrameStatsBasic(now, dt, ms) { // Timing stats var stats = this.stats.frame; stats.dt = dt; stats.ms = ms; if (now > stats._timeToCountFrames) { stats.fps = stats._fpsAccum; stats._fpsAccum = 0; stats._timeToCountFrames = now + 1000; } else { stats._fpsAccum++; } // total draw call this.stats.drawCalls.total = this.graphicsDevice._drawCallsPerFrame; this.graphicsDevice._drawCallsPerFrame = 0; } _fillFrameStats() { var stats = this.stats.frame; // Render stats stats.cameras = this.renderer._camerasRendered; stats.materials = this.renderer._materialSwitches; stats.shaders = this.graphicsDevice._shaderSwitchesPerFrame; stats.shadowMapUpdates = this.renderer._shadowMapUpdates; stats.shadowMapTime = this.renderer._shadowMapTime; stats.depthMapTime = this.renderer._depthMapTime; stats.forwardTime = this.renderer._forwardTime; var prims = this.graphicsDevice._primsPerFrame; stats.triangles = prims[PRIMITIVE_TRIANGLES] / 3 + Math.max(prims[PRIMITIVE_TRISTRIP] - 2, 0) + Math.max(prims[PRIMITIVE_TRIFAN] - 2, 0); stats.cullTime = this.renderer._cullTime; stats.sortTime = this.renderer._sortTime; stats.skinTime = this.renderer._skinTime; stats.morphTime = this.renderer._morphTime; stats.instancingTime = this.renderer._instancingTime; stats.lightClusters = this.renderer._lightClusters; stats.lightClustersTime = this.renderer._lightClustersTime; stats.otherPrimitives = 0; for (var i = 0; i < prims.length; i++) { if (i < PRIMITIVE_TRIANGLES) { stats.otherPrimitives += prims[i]; } prims[i] = 0; } this.renderer._camerasRendered = 0; this.renderer._materialSwitches = 0; this.renderer._shadowMapUpdates = 0; this.graphicsDevice._shaderSwitchesPerFrame = 0; this.renderer._cullTime = 0; this.renderer._layerCompositionUpdateTime = 0; this.renderer._lightClustersTime = 0; this.renderer._sortTime = 0; this.renderer._skinTime = 0; this.renderer._morphTime = 0; this.renderer._instancingTime = 0; this.renderer._shadowMapTime = 0; this.renderer._depthMapTime = 0; this.renderer._forwardTime = 0; // Draw call stats stats = this.stats.drawCalls; stats.forward = this.renderer._forwardDrawCalls; stats.culled = this.renderer._numDrawCallsCulled; stats.depth = 0; stats.shadow = this.renderer._shadowDrawCalls; stats.skinned = this.renderer._skinDrawCalls; stats.immediate = 0; stats.instanced = 0; stats.removedByInstancing = 0; stats.misc = stats.total - (stats.forward + stats.shadow); this.renderer._depthDrawCalls = 0; this.renderer._shadowDrawCalls = 0; this.renderer._forwardDrawCalls = 0; this.renderer._numDrawCallsCulled = 0; this.renderer._skinDrawCalls = 0; this.renderer._immediateRendered = 0; this.renderer._instancedDrawCalls = 0; this.renderer._removedByInstancing = 0; this.stats.misc.renderTargetCreationTime = this.graphicsDevice.renderTargetCreationTime; stats = this.stats.particles; stats.updatesPerFrame = stats._updatesPerFrame; stats.frameTime = stats._frameTime; stats._updatesPerFrame = 0; stats._frameTime = 0; } /** * @function * @name Application#setCanvasFillMode * @description Controls how the canvas fills the window and resizes when the window changes. * @param {string} mode - The mode to use when setting the size of the canvas. Can be: * * * {@link FILLMODE_NONE}: the canvas will always match the size provided. * * {@link FILLMODE_FILL_WINDOW}: the canvas will simply fill the window, changing aspect ratio. * * {@link FILLMODE_KEEP_ASPECT}: the canvas will grow to fill the window as best it can while maintaining the aspect ratio. * @param {number} [width] - The width of the canvas (only used when mode is {@link FILLMODE_NONE}). * @param {number} [height] - The height of the canvas (only used when mode is {@link FILLMODE_NONE}). */ setCanvasFillMode(mode, width, height) { this._fillMode = mode; this.resizeCanvas(width, height); } /** * @function * @name Application#setCanvasResolution * @description Change the resolution of the canvas, and set the way it behaves when the window is resized. * @param {string} mode - The mode to use when setting the resolution. Can be: * * * {@link RESOLUTION_AUTO}: if width and height are not provided, canvas will be resized to match canvas client size. * * {@link RESOLUTION_FIXED}: resolution of canvas will be fixed. * @param {number} [width] - The horizontal resolution, optional in AUTO mode, if not provided canvas clientWidth is used. * @param {number} [height] - The vertical resolution, optional in AUTO mode, if not provided canvas clientHeight is used. */ setCanvasResolution(mode, width, height) { this._resolutionMode = mode; // In AUTO mode the resolution is the same as the canvas size, unless specified if (mode === RESOLUTION_AUTO && (width === undefined)) { width = this.graphicsDevice.canvas.clientWidth; height = this.graphicsDevice.canvas.clientHeight; } this.graphicsDevice.resizeCanvas(width, height); } /** * @function * @name Application#isHidden * @description Queries the visibility of the window or tab in which the application is running. * @returns {boolean} True if the application is not visible and false otherwise. */ isHidden() { return document[this._hiddenAttr]; } /** * @private * @function * @name Application#onVisibilityChange * @description Called when the visibility state of the current tab/window changes. */ onVisibilityChange() { if (this.isHidden()) { this._soundManager.suspend(); } else { this._soundManager.resume(); } } /** * @function * @name Application#resizeCanvas * @description Resize the application's canvas element in line with the current fill mode. * In {@link FILLMODE_KEEP_ASPECT} mode, the canvas will grow to fill the window as best it can while maintaining the aspect ratio. * In {@link FILLMODE_FILL_WINDOW} mode, the canvas will simply fill the window, changing aspect ratio. * In {@link FILLMODE_NONE} mode, the canvas will always match the size provided. * @param {number} [width] - The width of the canvas. Only used if current fill mode is {@link FILLMODE_NONE}. * @param {number} [height] - The height of the canvas. Only used if current fill mode is {@link FILLMODE_NONE}. * @returns {object} A object containing the values calculated to use as width and height. */ resizeCanvas(width, height) { if (!this._allowResize) return; // prevent resizing (e.g. if presenting in VR HMD) // prevent resizing when in XR session if (this.xr && this.xr.session) return; var windowWidth = window.innerWidth; var windowHeight = window.innerHeight; if (this._fillMode === FILLMODE_KEEP_ASPECT) { var r = this.graphicsDevice.canvas.width / this.graphicsDevice.canvas.height; var winR = windowWidth / windowHeight; if (r > winR) { width = windowWidth; height = width / r; } else { height = windowHeight; width = height * r; } } else if (this._fillMode === FILLMODE_FILL_WINDOW) { width = windowWidth; height = windowHeight; } // OTHERWISE: FILLMODE_NONE use width and height that are provided this.graphicsDevice.canvas.style.width = width + 'px'; this.graphicsDevice.canvas.style.height = height + 'px'; // In AUTO mode the resolution is changed to match the canvas size if (this._resolutionMode === RESOLUTION_AUTO) { this.setCanvasResolution(RESOLUTION_AUTO); } // return the final values calculated for width and height return { width: width, height: height }; } /** * @private * @name Application#onLibrariesLoaded * @description Event handler called when all code libraries have been loaded. * Code libraries are passed into the constructor of the Application and the application won't start running or load packs until all libraries have * been loaded. */ onLibrariesLoaded() { this._librariesLoaded = true; this.systems.rigidbody.onLibraryLoaded(); } /** * @function * @name Application#applySceneSettings * @description Apply scene settings to the current scene. Useful when your scene settings are parsed or generated from a non-URL source. * @param {object} settings - The scene settings to be applied. * @param {object} settings.physics - The physics settings to be applied. * @param {number[]} settings.physics.gravity - The world space vector representing global gravity in the physics simulation. Must be a fixed size array with three number elements, corresponding to each axis [ X, Y, Z ]. * @param {object} settings.render - The rendering settings to be applied. * @param {number[]} settings.render.global_ambient - The color of the scene's ambient light. Must be a fixed size array with three number elements, corresponding to each color channel [ R, G, B ]. * @param {string} settings.render.fog - The type of fog used by the scene. Can be: * * * {@link FOG_NONE} * * {@link FOG_LINEAR} * * {@link FOG_EXP} * * {@link FOG_EXP2} * @param {number[]} settings.render.fog_color - The color of the fog (if enabled). Must be a fixed size array with three number elements, corresponding to each color channel [ R, G, B ]. * @param {number} settings.render.fog_density - The density of the fog (if enabled). This property is only valid if the fog property is set to {@link FOG_EXP} or {@link FOG_EXP2}. * @param {number} settings.render.fog_start - The distance from the viewpoint where linear fog begins. This property is only valid if the fog property is set to {@link FOG_LINEAR}. * @param {number} settings.render.fog_end - The distance from the viewpoint where linear fog reaches its maximum. This property is only valid if the fog property is set to {@link FOG_LINEAR}. * @param {number} settings.render.gamma_correction - The gamma correction to apply when rendering the scene. Can be: * * * {@link GAMMA_NONE} * * {@link GAMMA_SRGB} * @param {number} settings.render.tonemapping - The tonemapping transform to apply when writing fragments to the * frame buffer. Can be: * * * {@link TONEMAP_LINEAR} * * {@link TONEMAP_FILMIC} * * {@link TONEMAP_HEJL} * * {@link TONEMAP_ACES} * @param {number} settings.render.exposure - The exposure value tweaks the overall brightness of the scene. * @param {number|null} [settings.render.skybox] - The asset ID of the cube map texture to be used as the scene's skybox. Defaults to null. * @param {number} settings.render.skyboxIntensity - Multiplier for skybox intensity. * @param {number} settings.render.skyboxMip - The mip level of the skybox to be displayed. Only valid for prefiltered cubemap skyboxes. * @param {number[]} settings.render.skyboxRotation - Rotation of skybox. * @param {number} settings.render.lightmapSizeMultiplier - The lightmap resolution multiplier. * @param {number} settings.render.lightmapMaxResolution - The maximum lightmap resolution. * @param {number} settings.render.lightmapMode - The lightmap baking mode. Can be: * * * {@link BAKE_COLOR}: single color lightmap * * {@link BAKE_COLORDIR}: single color lightmap + dominant light direction (used for bump/specular) * * Only lights with bakeDir=true will be used for generating the dominant light direction. * @example * * var settings = { * physics: { * gravity: [0, -9.8, 0] * }, * render: { * fog_end: 1000, * tonemapping: 0, * skybox: null, * fog_density: 0.01, * gamma_correction: 1, * exposure: 1, * fog_start: 1, * global_ambient: [0, 0, 0], * skyboxIntensity: 1, * skyboxRotation: [0, 0, 0], * fog_color: [0, 0, 0], * lightmapMode: 1, * fog: 'none', * lightmapMaxResolution: 2048, * skyboxMip: 2, * lightmapSizeMultiplier: 16 * } * }; * app.applySceneSettings(settings); */ applySceneSettings(settings) { var asset; if (this.systems.rigidbody && typeof Ammo !== 'undefined') { var gravity = settings.physics.gravity; this.systems.rigidbody.gravity.set(gravity[0], gravity[1], gravity[2]); } this.scene.applySettings(settings); if (settings.render.hasOwnProperty('skybox')) { if (settings.render.skybox) { asset = this.assets.get(settings.render.skybox); if (asset) { this.setSkybox(asset); } else { this.assets.once('add:' + settings.render.skybox, this.setSkybox, this); } } else { this.setSkybox(null); } } } /** * @function * @name Application#setAreaLightLuts * @description Sets the area light LUT asset for this app. * @param {Asset} asset - LUT asset of type `binary` to be set. */ setAreaLightLuts(asset) { if (asset) { var renderer = this.renderer; asset.ready(function (asset) { renderer._uploadAreaLightLuts(asset.resource); }); this.assets.load(asset); } else { // #if _DEBUG console.warn("setAreaLightLuts: asset is not valid"); // #endif } } /** * @function * @name Application#setSkybox * @description Sets the skybox asset to current scene, and subscribes to asset load/change events. * @param {Asset} asset - Asset of type `skybox` to be set to, or null to remove skybox. */ setSkybox(asset) { if (asset) { if (this._skyboxLast === asset.id) { if (this.scene.skyboxMip === 0 && !asset.loadFaces) { this._skyboxLoad(asset); } else { this._onSkyboxChange(asset); } return; } if (this._skyboxLast) { this.assets.off('add:' + this._skyboxLast, this.setSkybox, this); this.assets.off('load:' + this._skyboxLast, this._onSkyboxChange, this); this.assets.off('remove:' + this._skyboxLast, this._skyboxRemove, this); } this._skyboxLast = asset.id; this.assets.on('load:' + asset.id, this._onSkyboxChange, this); this.assets.once('remove:' + asset.id, this._skyboxRemove, this); if (asset.resource) this.scene.setSkybox(asset.resources); this._skyboxLoad(asset); } else { if (!this._skyboxLast) return; this._skyboxRemove({ id: this._skyboxLast }); } } /** * @private * @deprecated * @function * @name Application#enableVr * @description Create and assign a {@link VrManager} object to allow this application render in VR. */ enableVr() { if (!this.vr) { this.vr = new VrManager(this); } } /** * @private * @deprecated * @function * @name Application#disableVr * @description Destroy the {@link VrManager}. */ disableVr() { if (this.vr) { this.vr.destroy(); this.vr = null; } } _onSkyboxChange(asset) { this.scene.setSkybox(asset.resources); } _skyboxLoad(asset) { if (this.scene.skyboxMip === 0) asset.loadFaces = true; this.assets.load(asset); this._onSkyboxChange(asset); } _skyboxRemove(asset) { if (!this._skyboxLast) return; this.assets.off('add:' + asset.id, this.setSkybox, this); this.assets.off('load:' + asset.id, this._onSkyboxChange, this); this.assets.off('remove:' + asset.id, this._skyboxRemove, this); this.scene.setSkybox(null); this._skyboxLast = null; } _firstBake() { this.lightmapper.bake(null, this.scene.lightmapMode); } _firstBatch() { if (this.scene._needsStaticPrepare) { this.renderer.prepareStaticMeshes(this.graphicsDevice, this.scene); this.scene._needsStaticPrepare = false; } this.batcher.generate(); } _processTimestamp(timestamp) { return timestamp; } // IMMEDIATE MODE API _preRenderImmediate() { this._immediateData.finalize(); } _postRenderImmediate() { this._immediateData.clear(); } _initImmediate() { // Init global line drawing data once if (!this._immediateData) { this._immediateData = new ImmediateData(this.graphicsDevice); this.on('prerender', this._preRenderImmediate, this); this.on('postrender', this._postRenderImmediate, this); } } _addLines(position, color, options) { var layer = (options && options.layer) ? options.layer : this.scene.layers.getLayerById(LAYERID_IMMEDIATE); var depthTest = (options && options.depthTest !== undefined) ? options.depthTest : true; var mask = (options && options.mask) ? options.mask : undefined; this._initImmediate(); const lineBatch = this._immediateData.prepareLineBatch(layer, depthTest, mask, position.length / 2); // Append lineBatch.addLines(position, color); } /** * @function * @name Application#renderLine * @description Renders a line. Line start and end coordinates are specified in * world-space. If a single color is supplied, the line will be flat-shaded with * that color. If two colors are supplied, the line will be smooth shaded between * those colors. It is also possible to control which scene layer the line is * rendered into. By default, lines are rendered into the immediate layer * {@link LAYERID_IMMEDIATE}. * @param {Vec3} start - The start world-space coordinate of the line. * @param {Vec3} end - The end world-space coordinate of the line. * @param {Color} color - The start color of the line. * @param {Color} [endColor] - The end color of the line. * @param {object} [options] - Options to set rendering properties. * @param {Layer} [options.layer] - The layer to render the line into. Defaults * to {@link LAYERID_IMMEDIATE}. * @example * // Render a 1-unit long white line * var start = new pc.Vec3(0, 0, 0); * var end = new pc.Vec3(1, 0, 0); * var color = new pc.Color(1, 1, 1); * app.renderLine(start, end, color); * @example * // Render a 1-unit long line that is smooth-shaded from white to red * var start = new pc.Vec3(0, 0, 0); * var end = new pc.Vec3(1, 0, 0); * var startColor = new pc.Color(1, 1, 1); * var endColor = new pc.Color(1, 0, 0); * app.renderLine(start, end, startColor, endColor); * @example * // Render a 1-unit long white line into the world layer * var start = new pc.Vec3(0, 0, 0); * var end = new pc.Vec3(1, 0, 0); * var color = new pc.Color(1, 1, 1); * var worldLayer = app.scene.layers.getLayerById(pc.LAYERID_WORLD); * app.renderLine(start, end, color, { * layer: worldLayer * }); * @example * // Render a 1-unit long line that is smooth-shaded from white to red into the world layer * var start = new pc.Vec3(0, 0, 0); * var end = new pc.Vec3(1, 0, 0); * var startColor = new pc.Color(1, 1, 1); * var endColor = new pc.Color(1, 0, 0); * var worldLayer = app.scene.layers.getLayerById(pc.LAYERID_WORLD); * app.renderLine(start, end, color, { * layer: worldLayer * }); */ renderLine(start, end, color) { var endColor = color; var options; var arg3 = arguments[3]; var arg4 = arguments[4]; if (arg3 instanceof Color) { // passed in end color endColor = arg3; if (typeof arg4 === 'number') { if (!_deprecationWarning) { console.warn("lineBatch argument is deprecated for renderLine. Use options.layer instead"); _deprecationWarning = true; } // compatibility: convert linebatch id into options if (arg4 === LINEBATCH_OVERLAY) { options = { layer: this.scene.layers.getLayerById(LAYERID_IMMEDIATE), depthTest: false }; } else { options = { layer: this.scene.layers.getLayerById(LAYERID_IMMEDIATE), depthTest: true }; } } else { // use passed in options options = arg4; } } else if (typeof arg3 === 'number') { if (!_deprecationWarning) { console.warn("lineBatch argument is deprecated for renderLine. Use options.layer instead"); _deprecationWarning = true; } endColor = color; // compatibility: convert linebatch id into options if (arg3 === LINEBATCH_OVERLAY) { options = { layer: this.scene.layers.getLayerById(LAYERID_IMMEDIATE), depthTest: false }; } else { options = { layer: this.scene.layers.getLayerById(LAYERID_IMMEDIATE), depthTest: true }; } } else if (arg3) { // options passed in options = arg3; } this._addLines([start, end], [color, endColor], options); } /** * @function * @name Application#renderLines * @description Renders an arbitrary number of discrete line segments. The lines * are not connected by each subsequent point in the array. Instead, they are * individual segments specified by two points. Therefore, the lengths of the * supplied position and color arrays must be the same and also must be a multiple * of 2. The colors of the ends of each line segment will be interpolated along * the length of each line. * @param {Vec3[]} position - An array of points to draw lines between. The * length of the array must be a multiple of 2. * @param {Color[]} color - An array of colors to color the lines. This * must be the same length as the position array. The length of the array must * also be a multiple of 2. * @param {object} [options] - Options to set rendering properties. * @param {Layer} [options.layer] - The layer to render the lines into. * @example * // Render 2 discrete line segments * var points = [ * // Line 1 * new pc.Vec3(0, 0, 0), * new pc.Vec3(1, 0, 0), * // Line 2 * new pc.Vec3(1, 1, 0), * new pc.Vec3(1, 1, 1) * ]; * var colors = [ * // Line 1 * pc.Color.RED, * pc.Color.YELLOW, * // Line 2 * pc.Color.CYAN, * pc.Color.BLUE * ]; * app.renderLines(points, colors); */ renderLines(position, color, options) { if (!options) { // default option options = { layer: this.scene.layers.getLayerById(LAYERID_IMMEDIATE), depthTest: true }; } else if (typeof options === 'number') { if (!_deprecationWarning) { console.warn("lineBatch argument is deprecated for renderLine. Use options.layer instead"); _deprecationWarning = true; } // backwards compatibility, LINEBATCH_OVERLAY lines have depthtest disabled if (options === LINEBATCH_OVERLAY) { options = { layer: this.scene.layers.getLayerById(LAYERID_IMMEDIATE), depthTest: false }; } else { options = { layer: this.scene.layers.getLayerById(LAYERID_IMMEDIATE), depthTest: true }; } } var multiColor = !!color.length; if (multiColor) { if (position.length !== color.length) { console.error("renderLines: position/color arrays have different lengths"); return; } } if (position.length % 2 !== 0) { console.error("renderLines: array length is not divisible by 2"); return; } this._addLines(position, color, options); } _getDefaultImmediateOptions(depthTest = false) { return { layer: this.scene.layers.getLayerById(LAYERID_IMMEDIATE), depthTest: depthTest }; } // Draw lines forming a transformed unit-sized cube at this frame renderWireCube(matrix, color, options = this._getDefaultImmediateOptions(true)) { this._initImmediate(); this._immediateData.renderWireCube(matrix, color, options); } // Draw lines forming sphere at this frame renderWireSphere(center, radius, color, options = this._getDefaultImmediateOptions(true)) { this._initImmediate(); this._immediateData.renderWireSphere(center, radius, color, options); } // Draw meshInstance at this frame renderMeshInstance(meshInstance, options = this._getDefaultImmediateOptions(true)) { this._initImmediate(); this._immediateData.renderMesh(null, null, null, meshInstance, options); } // Draw mesh at this frame renderMesh(mesh, material, matrix, options = this._getDefaultImmediateOptions(true)) { this._initImmediate(); this._immediateData.renderMesh(material, matrix, mesh, null, options); } // Draw quad of size [-0.5, 0.5] at this frame renderQuad(matrix, material, options = this._getDefaultImmediateOptions()) { this._initImmediate(); this._immediateData.renderMesh(material, matrix, this._immediateData.getQuadMesh(), null, options); } // draws a texture on [x,y] position on screen, with size [width, height]. // Coordinates / sizes are in projected space (-1 .. 1) renderTexture(x, y, width, height, texture, material, options) { this._initImmediate(); // TODO: if this is used for anything other than debug texture display, we should optimize this to avoid allocations const matrix = new Mat4(); matrix.setTRS(new Vec3(x, y, 0.0), Quat.IDENTITY, new Vec3(width, height, 0.0)); if (!material) { material = new Material(); material.setParameter("colorMap", texture); material.shader = this._immediateData.getTextureShader(); material.update(); } this.renderQuad(matrix, material, options); } // draws a depth texture on [x,y] position on screen, with size [width, height]. // Coordinates / sizes are in projected space (-1 .. 1) renderDepthTexture(x, y, width, height, options) { this._initImmediate(); const material = new Material(); material.shader = this._immediateData.getDepthTextureShader(); material.update(); this.renderTexture(x, y, width, height, null, material, options); } /** * @function * @name Application#destroy * @description Destroys application and removes all event listeners. * @example * this.app.destroy(); */ destroy() { var i, l; var canvasId = this.graphicsDevice.canvas.id; this.off('librariesloaded'); document.removeEventListener('visibilitychange', this._visibilityChangeHandler, false); document.removeEventListener('mozvisibilitychange', this._visibilityChangeHandler, false); document.removeEventListener('msvisibilitychange', this._visibilityChangeHandler, false); document.removeEventListener('webkitvisibilitychange', this._visibilityChangeHandler, false); this._visibilityChangeHandler = null; this.onVisibilityChange = null; this.root.destroy(); this.root = null; if (this.mouse) { this.mouse.off(); this.mouse.detach(); this.mouse = null; } if (this.keyboard) { this.keyboard.off(); this.keyboard.detach(); this.keyboard = null; } if (this.touch) { this.touch.off(); this.touch.detach(); this.touch = null; } if (this.elementInput) { this.elementInput.detach(); this.elementInput = null; } if (this.controller) { this.controller = null; } var systems = this.systems.list; for (i = 0, l = systems.length; i < l; i++) { systems[i].destroy(); } ComponentSystem.destroy(); // layer composition if (this.scene.layers) { this.scene.layers.destroy(); } // destroy all texture resources var assets = this.assets.list(); for (i = 0; i < assets.length; i++) { assets[i].unload(); assets[i].off(); } this.assets.off(); // destroy bundle registry this.bundles.destroy(); this.bundles = null; this.i18n.destroy(); this.i18n = null; for (var key in this.loader.getHandler('script')._cache) { var element = this.loader.getHandler('script')._cache[key]; var parent = element.parentNode; if (parent) parent.removeChild(element); } this.loader.getHandler('script')._cache = {}; this.loader.destroy(); this.loader = null; this.scene.destroy(); this.scene = null; this.systems = []; this.context = null; // script registry this.scripts.destroy(); this.scripts = null; this.scenes.destroy(); this.scenes = null; this.lightmapper.destroy(); this.lightmapper = null; this.batcher.destroyManager(); this.batcher = null; this._entityIndex = {}; this.defaultLayerDepth.onPreRenderOpaque = null; this.defaultLayerDepth.onPostRenderOpaque = null; this.defaultLayerDepth.onDisable = null; this.defaultLayerDepth.onEnable = null; this.defaultLayerDepth = null; this.defaultLayerWorld = null; destroyPostEffectQuad(); if (this.vr) { this.vr.destroy(); this.vr = null; } this.xr.end(); this.graphicsDevice.destroy(); this.graphicsDevice = null; this.renderer = null; this.tick = null; this.off(); // remove all events if (this._soundManager) { this._soundManager.destroy(); this._soundManager = null; } script.app = null; // remove default particle texture ParticleEmitter.DEFAULT_PARAM_TEXTURE = null; Application._applications[canvasId] = null; if (getApplication() === this) { setApplication(null); } } /** * @private * @function * @name Application#getEntityFromIndex * @description Get entity from the index by guid. * @param {string} guid - The GUID to search for. * @returns {Entity} The Entity with the GUID or null. */ getEntityFromIndex(guid) { return this._entityIndex[guid]; } } // static data var _frameEndData = {}; // create tick function to be wrapped in closure var makeTick = function (_app) { var application = _app; var frameRequest; return function (timestamp, frame) { if (!application.graphicsDevice) return; setApplication(application); if (frameRequest) { window.cancelAnimationFrame(frameRequest); frameRequest = null; } // have current application pointer in pc app = application; var currentTime = application._processTimestamp(timestamp) || now(); var ms = currentTime - (application._time || currentTime); var dt = ms / 1000.0; dt = math.clamp(dt, 0, application.maxDeltaTime); dt *= application.timeScale; application._time = currentTime; // Submit a request to queue up a new animation frame immediately if (application.vr && application.vr.display) { frameRequest = application.vr.display.requestAnimationFrame(application.tick); } else if (application.xr.session) { frameRequest = application.xr.session.requestAnimationFrame(application.tick); } else { frameRequest = window.requestAnimationFrame(application.tick); } if (application.graphicsDevice.contextLost) return; application._fillFrameStatsBasic(currentTime, dt, ms); // #if _PROFILER application._fillFrameStats(); // #endif application.fire("frameupdate", ms); if (frame) { application.xr.update(frame); application.graphicsDevice.defaultFramebuffer = frame.session.renderState.baseLayer.framebuffer; } else { application.graphicsDevice.defaultFramebuffer = null; } application.update(dt); application.fire("framerender"); if (application.autoRender || application.renderNextFrame) { application.render(); application.renderNextFrame = false; } // set event data _frameEndData.timestamp = now(); _frameEndData.target = application; application.fire("frameend", _frameEndData); application.fire("frameEnd", _frameEndData);// deprecated old event, remove when editor updated if (application.vr && application.vr.display && application.vr.display.presenting) { application.vr.display.submitFrame(); } }; }; export { app, Application };
X Tutup