|
| 1 | +// @config HIDDEN |
| 2 | +import { data } from 'examples/observer'; |
| 3 | +import { deviceType, rootPath, fileImport } from 'examples/utils'; |
| 4 | +import * as pc from 'playcanvas'; |
| 5 | +const { createGoochMaterial } = await fileImport(`${rootPath}/static/assets/scripts/misc/gooch-material.mjs`); |
| 6 | + |
| 7 | +const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); |
| 8 | +window.focus(); |
| 9 | + |
| 10 | +const assets = { |
| 11 | + script: new pc.Asset('script', 'script', { url: `${rootPath}/static/scripts/camera/orbit-camera.js` }), |
| 12 | + terrain: new pc.Asset('terrain', 'container', { url: `${rootPath}/static/assets/models/terrain.glb` }), |
| 13 | + biker: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/biker.ply` }), |
| 14 | + helipad: new pc.Asset( |
| 15 | + 'helipad-env-atlas', |
| 16 | + 'texture', |
| 17 | + { url: `${rootPath}/static/assets/cubemaps/table-mountain-env-atlas.png` }, |
| 18 | + { type: pc.TEXTURETYPE_RGBP, mipmaps: false } |
| 19 | + ) |
| 20 | +}; |
| 21 | + |
| 22 | +const gfxOptions = { |
| 23 | + deviceTypes: [deviceType], |
| 24 | + glslangUrl: `${rootPath}/static/lib/glslang/glslang.js`, |
| 25 | + twgslUrl: `${rootPath}/static/lib/twgsl/twgsl.js` |
| 26 | +}; |
| 27 | + |
| 28 | +const device = await pc.createGraphicsDevice(canvas, gfxOptions); |
| 29 | +device.maxPixelRatio = Math.min(window.devicePixelRatio, 2); |
| 30 | + |
| 31 | +const createOptions = new pc.AppOptions(); |
| 32 | +createOptions.graphicsDevice = device; |
| 33 | +createOptions.mouse = new pc.Mouse(document.body); |
| 34 | +createOptions.touch = new pc.TouchDevice(document.body); |
| 35 | + |
| 36 | +createOptions.componentSystems = [ |
| 37 | + pc.RenderComponentSystem, |
| 38 | + pc.CameraComponentSystem, |
| 39 | + pc.LightComponentSystem, |
| 40 | + pc.ScriptComponentSystem, |
| 41 | + pc.GSplatComponentSystem, |
| 42 | + pc.ParticleSystemComponentSystem |
| 43 | +]; |
| 44 | +createOptions.resourceHandlers = [ |
| 45 | + pc.TextureHandler, |
| 46 | + pc.ContainerHandler, |
| 47 | + pc.ScriptHandler, |
| 48 | + pc.GSplatHandler |
| 49 | +]; |
| 50 | + |
| 51 | +const app = new pc.AppBase(canvas); |
| 52 | +app.init(createOptions); |
| 53 | + |
| 54 | +const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets); |
| 55 | +assetListLoader.load(() => { |
| 56 | + app.start(); |
| 57 | + |
| 58 | + // Set the canvas to fill the window and automatically change resolution to be the same as the canvas size |
| 59 | + app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW); |
| 60 | + app.setCanvasResolution(pc.RESOLUTION_AUTO); |
| 61 | + |
| 62 | + // Ensure canvas is resized when window changes size |
| 63 | + const resize = () => app.resizeCanvas(); |
| 64 | + window.addEventListener('resize', resize); |
| 65 | + app.on('destroy', () => { |
| 66 | + window.removeEventListener('resize', resize); |
| 67 | + }); |
| 68 | + |
| 69 | + // setup skydome |
| 70 | + app.scene.skyboxMip = 0; |
| 71 | + app.scene.envAtlas = assets.helipad.resource; |
| 72 | + app.scene.skyboxRotation = new pc.Quat().setFromEulerAngles(0, -70, 0); |
| 73 | + |
| 74 | + // for params |
| 75 | + app.scene.fog.color = new pc.Color(0.8, 0.8, 0.8); |
| 76 | + app.scene.fog.start = 400; |
| 77 | + app.scene.fog.end = 800; |
| 78 | + app.scene.fog.density = 0.001; |
| 79 | + |
| 80 | + // STANDARD MATERIAL ---------- |
| 81 | + |
| 82 | + /** @type {pc.Entity} */ |
| 83 | + const terrain = assets.terrain.resource.instantiateRenderEntity(); |
| 84 | + terrain.setLocalScale(30, 30, 30); |
| 85 | + app.root.addChild(terrain); |
| 86 | + |
| 87 | + // GSPLAT MATERIAL ---------- |
| 88 | + |
| 89 | + const biker = new pc.Entity(); |
| 90 | + biker.addComponent('gsplat', { |
| 91 | + asset: assets.biker |
| 92 | + }); |
| 93 | + biker.setLocalPosition(0, 0, 150); |
| 94 | + biker.setLocalEulerAngles(180, 90, 0); |
| 95 | + biker.setLocalScale(20, 20, 20); |
| 96 | + app.root.addChild(biker); |
| 97 | + |
| 98 | + // SHADER MATERIAL ---------- |
| 99 | + |
| 100 | + const box = new pc.Entity('ShaderMaterial'); |
| 101 | + const boxMaterial = createGoochMaterial(null, [0.13, 0.55, 0.13]); |
| 102 | + box.addComponent('render', { |
| 103 | + type: 'box', |
| 104 | + material: boxMaterial |
| 105 | + }); |
| 106 | + box.setLocalScale(30, 30, 30); |
| 107 | + box.setLocalPosition(-70, 30, 130); |
| 108 | + app.root.addChild(box); |
| 109 | + |
| 110 | + // LIT MATERIAL ---------- |
| 111 | + |
| 112 | + const material = new pc.LitMaterial(); |
| 113 | + material.setParameter('texture_envAtlas', assets.helipad.resource); |
| 114 | + material.setParameter('material_reflectivity', 1.0); |
| 115 | + material.useSkybox = true; |
| 116 | + material.hasSpecular = true; |
| 117 | + material.hasSpecularityFactor = true; |
| 118 | + material.hasNormals = true; |
| 119 | + material.hasMetalness = true; |
| 120 | + material.occludeSpecular = pc.SPECOCC_AO; |
| 121 | + |
| 122 | + const argumentsChunk = ` |
| 123 | + void evaluateFrontend() { |
| 124 | + litArgs_emission = vec3(0.7, 0.4, 0); |
| 125 | + litArgs_metalness = 0.5; |
| 126 | + litArgs_specularity = vec3(0.5, 0.5, 0.5); |
| 127 | + litArgs_specularityFactor = 1.0; |
| 128 | + litArgs_gloss = 0.5; |
| 129 | + litArgs_ior = 0.1; |
| 130 | + litArgs_ao = 0.0; |
| 131 | + litArgs_opacity = 1.0; |
| 132 | + }`; |
| 133 | + material.shaderChunk = argumentsChunk; |
| 134 | + material.update(); |
| 135 | + |
| 136 | + // create primitive |
| 137 | + const primitive = new pc.Entity(); |
| 138 | + primitive.addComponent('render', { |
| 139 | + type: 'sphere', |
| 140 | + material: material |
| 141 | + }); |
| 142 | + |
| 143 | + primitive.setLocalScale(30, 30, 30); |
| 144 | + primitive.setLocalPosition(-170, 30, 130); |
| 145 | + app.root.addChild(primitive); |
| 146 | + |
| 147 | + // PARTICLE SYSTEM ---------- |
| 148 | + |
| 149 | + const localVelocityCurve = new pc.CurveSet([ |
| 150 | + [0, 0, 0.5, 30], |
| 151 | + [0, 0, 0.5, 30], |
| 152 | + [0, 0, 0.5, 30] |
| 153 | + ]); |
| 154 | + const localVelocityCurve2 = new pc.CurveSet([ |
| 155 | + [0, 0, 0.5, -30], |
| 156 | + [0, 0, 0.5, -30], |
| 157 | + [0, 0, 0.5, -30] |
| 158 | + ]); |
| 159 | + const worldVelocityCurve = new pc.CurveSet([ |
| 160 | + [0, 0], |
| 161 | + [0, 0, 0.2, 6, 1, 300], |
| 162 | + [0, 0] |
| 163 | + ]); |
| 164 | + |
| 165 | + // Create entity for particle system |
| 166 | + const entity = new pc.Entity('ParticleSystem'); |
| 167 | + app.root.addChild(entity); |
| 168 | + entity.setLocalPosition(0, 20, 0); |
| 169 | + |
| 170 | + // add particlesystem component to entity |
| 171 | + entity.addComponent('particlesystem', { |
| 172 | + numParticles: 200, |
| 173 | + lifetime: 1, |
| 174 | + rate: 0.01, |
| 175 | + scaleGraph: new pc.Curve([0, 10]), |
| 176 | + velocityGraph: worldVelocityCurve, |
| 177 | + localVelocityGraph: localVelocityCurve, |
| 178 | + localVelocityGraph2: localVelocityCurve2, |
| 179 | + colorGraph: new pc.CurveSet([ |
| 180 | + [0, 1, 0.25, 1], |
| 181 | + [0, 0, 0.25, 0.3], |
| 182 | + [0, 0, 1, 0] |
| 183 | + ]) |
| 184 | + }); |
| 185 | + |
| 186 | + // -------- |
| 187 | + |
| 188 | + // create an Entity with a camera component |
| 189 | + const camera = new pc.Entity(); |
| 190 | + camera.addComponent('camera', { |
| 191 | + clearColor: new pc.Color(0.9, 0.9, 0.9), |
| 192 | + farClip: 1000, |
| 193 | + toneMapping: pc.TONEMAP_ACES |
| 194 | + }); |
| 195 | + |
| 196 | + // and position it in the world |
| 197 | + camera.setLocalPosition(-500, 60, 300); |
| 198 | + |
| 199 | + // add orbit camera script with a mouse and a touch support |
| 200 | + camera.addComponent('script'); |
| 201 | + camera.script.create('orbitCamera', { |
| 202 | + attributes: { |
| 203 | + inertiaFactor: 0.2, |
| 204 | + distanceMax: 500 |
| 205 | + } |
| 206 | + }); |
| 207 | + camera.script.create('orbitCameraInputMouse'); |
| 208 | + camera.script.create('orbitCameraInputTouch'); |
| 209 | + app.root.addChild(camera); |
| 210 | + |
| 211 | + // Create a directional light casting soft shadows |
| 212 | + const dirLight = new pc.Entity('Cascaded Light'); |
| 213 | + dirLight.addComponent('light', { |
| 214 | + type: 'directional', |
| 215 | + color: pc.Color.WHITE, |
| 216 | + shadowBias: 0.3, |
| 217 | + normalOffsetBias: 0.2, |
| 218 | + intensity: 1.0, |
| 219 | + |
| 220 | + // enable shadow casting |
| 221 | + castShadows: true, |
| 222 | + shadowType: pc.SHADOW_PCF3_32F, |
| 223 | + shadowDistance: 1000, |
| 224 | + shadowResolution: 2048 |
| 225 | + }); |
| 226 | + app.root.addChild(dirLight); |
| 227 | + dirLight.setLocalEulerAngles(75, 120, 20); |
| 228 | + |
| 229 | + // handle HUD changes |
| 230 | + data.on('*:set', (path, value) => { |
| 231 | + const propertyName = path.split('.')[1]; |
| 232 | + if (propertyName === 'tonemapping') { |
| 233 | + // set up selected tone-mapping |
| 234 | + camera.camera.toneMapping = value; |
| 235 | + } |
| 236 | + if (propertyName === 'fog') { |
| 237 | + app.scene.fog.type = value; |
| 238 | + } |
| 239 | + if (propertyName === 'gamma') { |
| 240 | + camera.camera.gammaCorrection = value ? pc.GAMMA_SRGB : pc.GAMMA_NONE; |
| 241 | + } |
| 242 | + }); |
| 243 | + |
| 244 | + // initial values |
| 245 | + data.set('data', { |
| 246 | + tonemapping: pc.TONEMAP_ACES, |
| 247 | + fog: pc.FOG_LINEAR, |
| 248 | + gamma: true |
| 249 | + }); |
| 250 | +}); |
| 251 | + |
| 252 | +export { app }; |
0 commit comments