├── LICENSE
├── Path_Traced_Pong.html
├── README.md
├── css
└── default.css
├── js
├── InitCommon.js
├── MobileJoystickControls.js
├── PathTracingCommon.js
├── Path_Traced_Pong.js
├── lil-gui.module.min.js
├── stats.module.js
├── three.core.min.js
└── three.module.min.js
├── shaders
├── Path_Traced_Pong_Fragment.glsl
├── ScreenCopy_Fragment.glsl
├── ScreenOutput_Fragment.glsl
└── common_PathTracing_Vertex.glsl
├── sounds
├── ballMiss.mp3
├── highPing.mp3
├── lowPing.mp3
├── synthHit.mp3
└── winner.mp3
└── textures
└── BlueNoise_R_128.png
/LICENSE:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | CC0 1.0 Universal
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12 | HEREUNDER.
13 |
14 | Statement of Purpose
15 |
16 | The laws of most jurisdictions throughout the world automatically confer
17 | exclusive Copyright and Related Rights (defined below) upon the creator
18 | and subsequent owner(s) (each and all, an "owner") of an original work of
19 | authorship and/or a database (each, a "Work").
20 |
21 | Certain owners wish to permanently relinquish those rights to a Work for
22 | the purpose of contributing to a commons of creative, cultural and
23 | scientific works ("Commons") that the public can reliably and without fear
24 | of later claims of infringement build upon, modify, incorporate in other
25 | works, reuse and redistribute as freely as possible in any form whatsoever
26 | and for any purposes, including without limitation commercial purposes.
27 | These owners may contribute to the Commons to promote the ideal of a free
28 | culture and the further production of creative, cultural and scientific
29 | works, or to gain reputation or greater distribution for their Work in
30 | part through the use and efforts of others.
31 |
32 | For these and/or other purposes and motivations, and without any
33 | expectation of additional consideration or compensation, the person
34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35 | is an owner of Copyright and Related Rights in the Work, voluntarily
36 | elects to apply CC0 to the Work and publicly distribute the Work under its
37 | terms, with knowledge of his or her Copyright and Related Rights in the
38 | Work and the meaning and intended legal effect of CC0 on those rights.
39 |
40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
41 | protected by copyright and related or neighboring rights ("Copyright and
42 | Related Rights"). Copyright and Related Rights include, but are not
43 | limited to, the following:
44 |
45 | i. the right to reproduce, adapt, distribute, perform, display,
46 | communicate, and translate a Work;
47 | ii. moral rights retained by the original author(s) and/or performer(s);
48 | iii. publicity and privacy rights pertaining to a person's image or
49 | likeness depicted in a Work;
50 | iv. rights protecting against unfair competition in regards to a Work,
51 | subject to the limitations in paragraph 4(a), below;
52 | v. rights protecting the extraction, dissemination, use and reuse of data
53 | in a Work;
54 | vi. database rights (such as those arising under Directive 96/9/EC of the
55 | European Parliament and of the Council of 11 March 1996 on the legal
56 | protection of databases, and under any national implementation
57 | thereof, including any amended or successor version of such
58 | directive); and
59 | vii. other similar, equivalent or corresponding rights throughout the
60 | world based on applicable law or treaty, and any national
61 | implementations thereof.
62 |
63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
65 | irrevocably and unconditionally waives, abandons, and surrenders all of
66 | Affirmer's Copyright and Related Rights and associated claims and causes
67 | of action, whether now known or unknown (including existing as well as
68 | future claims and causes of action), in the Work (i) in all territories
69 | worldwide, (ii) for the maximum duration provided by applicable law or
70 | treaty (including future time extensions), (iii) in any current or future
71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
72 | including without limitation commercial, advertising or promotional
73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74 | member of the public at large and to the detriment of Affirmer's heirs and
75 | successors, fully intending that such Waiver shall not be subject to
76 | revocation, rescission, cancellation, termination, or any other legal or
77 | equitable action to disrupt the quiet enjoyment of the Work by the public
78 | as contemplated by Affirmer's express Statement of Purpose.
79 |
80 | 3. Public License Fallback. Should any part of the Waiver for any reason
81 | be judged legally invalid or ineffective under applicable law, then the
82 | Waiver shall be preserved to the maximum extent permitted taking into
83 | account Affirmer's express Statement of Purpose. In addition, to the
84 | extent the Waiver is so judged Affirmer hereby grants to each affected
85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
88 | maximum duration provided by applicable law or treaty (including future
89 | time extensions), (iii) in any current or future medium and for any number
90 | of copies, and (iv) for any purpose whatsoever, including without
91 | limitation commercial, advertising or promotional purposes (the
92 | "License"). The License shall be deemed effective as of the date CC0 was
93 | applied by Affirmer to the Work. Should any part of the License for any
94 | reason be judged legally invalid or ineffective under applicable law, such
95 | partial invalidity or ineffectiveness shall not invalidate the remainder
96 | of the License, and in such case Affirmer hereby affirms that he or she
97 | will not (i) exercise any of his or her remaining Copyright and Related
98 | Rights in the Work or (ii) assert any associated claims and causes of
99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 |
102 | 4. Limitations and Disclaimers.
103 |
104 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 | surrendered, licensed or otherwise affected by this document.
106 | b. Affirmer offers the Work as-is and makes no representations or
107 | warranties of any kind concerning the Work, express, implied,
108 | statutory or otherwise, including without limitation warranties of
109 | title, merchantability, fitness for a particular purpose, non
110 | infringement, or the absence of latent or other defects, accuracy, or
111 | the present or absence of errors, whether or not discoverable, all to
112 | the greatest extent permissible under applicable law.
113 | c. Affirmer disclaims responsibility for clearing rights of other persons
114 | that may apply to the Work or any use thereof, including without
115 | limitation any person's Copyright and Related Rights in the Work.
116 | Further, Affirmer disclaims responsibility for obtaining any necessary
117 | consents, permissions or other rights required for any use of the
118 | Work.
119 | d. Affirmer understands and acknowledges that Creative Commons is not a
120 | party to this document and has no duty or obligation with respect to
121 | this CC0 or use of the Work.
122 |
--------------------------------------------------------------------------------
/Path_Traced_Pong.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Path Traced Pong (a fully path traced game)
5 |
6 |
7 |
8 |
9 |
16 |
17 |
25 |
26 |
27 |
28 |
29 |
30 | Play
31 |
32 |
33 |
34 |
35 | Path Traced Pong (a fully path traced game)
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PathTracedPong
2 | A real-time path traced game for desktop and mobile using WebGL.
3 | Click to Play --> https://erichlof.github.io/PathTracedPong/Path_Traced_Pong.html
4 |
5 |
6 | Desktop Controls
7 |
8 | * Click anywhere to capture mouse
9 | * move Mouse to control paddle
10 | * Mousewheel to dolly camera in or out
11 | * Click the 'Gravity' button to simulate gravity!
12 | * Click the 'Difficulty' button to cycle through the 3 difficulty levels
13 |
14 |
15 | Mobile Controls
16 |
17 | * Swipe to control paddle
18 | * Pinch to dolly camera in or out
19 | * Tap the 'Gravity' button to simulate gravity!
20 | * Tap the 'Difficulty' button to cycle through the 3 difficulty levels
21 |
22 | TODO
23 |
24 | * Possibly add a small moving obstacle (mirror sphere or box) that randomly slides on the floor, changing the ball direction if it gets hit. ;-)
25 | * Network gameplay for 2 players using WebSockets would be a great addition (if I can ever find the time!) ;)
26 |
27 | ABOUT
28 |
29 | * Following my AntiGravity Pool [game](https://github.com/erichlof/AntiGravity-Pool), this is the second in a series of real-time fully path traced games for all devices with a browser, including mobile. The technology behind this simple game is a combination of my three.js path tracing [project](https://github.com/erichlof/THREE.js-PathTracing-Renderer) and the WebAudio API for sound effects. The goal of this project and others like it is enabling path traced real-time games for all players, regardless of their system specs and GPU power.
30 |
--------------------------------------------------------------------------------
/css/default.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | width: 100%;
3 | height: 100%;
4 | font-family: Monospace;
5 | background-color: #000;
6 | color: #000;
7 | margin: 0px;
8 | overflow: hidden;
9 | touch-action: none;
10 | cursor: default;
11 | user-select: none;
12 | -webkit-user-select: none;
13 | }
14 |
15 | #info {
16 | position: fixed;
17 | top: 5px;
18 | width: 100%;
19 | text-align: center;
20 | color: #ffffff;
21 | cursor: default;
22 | user-select: none;
23 | }
24 |
25 | #cameraInfo {
26 | position: fixed;
27 | left: 3%;
28 | bottom: 2%;
29 | font-family: Arial;
30 | color: #ffffff;
31 | cursor: default;
32 | user-select: none;
33 | }
34 |
35 | #instructions {
36 | position: fixed;
37 | left: 3%;
38 | bottom: 1%;
39 | font-family: Arial;
40 | color: #ffffff;
41 | cursor: default;
42 | user-select: none;
43 | }
44 |
45 | .toggleButton {
46 | position:fixed;
47 | background-color: gray;
48 | border-color: darkgray;
49 | color: white;
50 | top: 5px;
51 | right: 5px;
52 | padding: 2px 5px;
53 | text-align: center;
54 | text-decoration: none;
55 | font-size: 14px;
56 | margin: 4px 2px;
57 | cursor: pointer;
58 | user-select: none;
59 | z-index: 11;
60 | }
61 | #timePauseButton {
62 | top: 50px;
63 | }
64 | #gravityButton {
65 | top: 40px;
66 | }
67 |
68 | /* begin Web Audio user-interaction requirement for MacOS & iOS */
69 | #overlay {
70 | position: absolute;
71 | font-size: 16px;
72 | z-index: 2;
73 | top: 0;
74 | left: 0;
75 | width: 100%;
76 | height: 100%;
77 | display: flex;
78 | align-items: center;
79 | justify-content: center;
80 | flex-direction: column;
81 | background: rgba(0,0,0,0.7);
82 | }
83 |
84 | #overlay button {
85 | background: transparent;
86 | border: 0;
87 | border: 1px solid rgb(255, 255, 255);
88 | border-radius: 4px;
89 | color: #ffffff;
90 | padding: 12px 18px;
91 | text-transform: uppercase;
92 | cursor: pointer;
93 | }
94 | /* end Web Audio user-interaction requirement for MacOS & iOS */
95 |
--------------------------------------------------------------------------------
/js/InitCommon.js:
--------------------------------------------------------------------------------
1 | let SCREEN_WIDTH;
2 | let SCREEN_HEIGHT;
3 | let canvas, context;
4 | let container, stats;
5 | let controls;
6 | let pathTracingScene, screenCopyScene, screenOutputScene;
7 | let pathTracingUniforms = {};
8 | let pathTracingUniformsGroups = [];
9 | let screenCopyUniforms, screenOutputUniforms;
10 | let pathTracingDefines;
11 | let pathTracingVertexShader, pathTracingFragmentShader;
12 | let demoFragmentShaderFileName;
13 | let screenCopyVertexShader, screenCopyFragmentShader;
14 | let screenOutputVertexShader, screenOutputFragmentShader;
15 | let triangleGeometry = new THREE.BufferGeometry();
16 | let trianglePositions = [];
17 | let pathTracingMaterial, pathTracingMesh;
18 | let screenCopyMaterial, screenCopyMesh;
19 | let screenOutputMaterial, screenOutputMesh;
20 | let pathTracingRenderTarget, screenCopyRenderTarget;
21 | let orthoCamera, worldCamera;
22 | let renderer, clock;
23 | let frameTime, elapsedTime;
24 | let sceneIsDynamic = false;
25 | let cameraFlightSpeed = 60;
26 | let cameraRotationSpeed = 1;
27 | let fovScale;
28 | let storedFOV = 0;
29 | let increaseFOV = false;
30 | let decreaseFOV = false;
31 | let dollyCameraIn = false;
32 | let dollyCameraOut = false;
33 | let apertureSize = 0.0;
34 | let increaseAperture = false;
35 | let decreaseAperture = false;
36 | let apertureChangeSpeed = 1;
37 | let focusDistance = 132.0;
38 | let increaseFocusDist = false;
39 | let decreaseFocusDist = false;
40 | let pixelRatio = 1.0;
41 | let windowIsBeingResized = false;
42 | let TWO_PI = Math.PI * 2;
43 | let sampleCounter = 0.0; // will get increased by 1 in animation loop before rendering
44 | let frameCounter = 1.0; // 1 instead of 0 because it is used as a rng() seed in pathtracing shader
45 | let cameraIsMoving = false;
46 | let cameraRecentlyMoving = false;
47 | let isPaused = true;
48 | let inputMovementHorizontal = 0;
49 | let inputMovementVertical = 0;
50 | let oldYawRotation, oldPitchRotation;
51 | let mobileJoystickControls = null;
52 | let mobileShowButtons = true;
53 | let mobileUseDarkButtons = false;
54 | let oldDeltaX = 0;
55 | let oldDeltaY = 0;
56 | let newDeltaX = 0;
57 | let newDeltaY = 0;
58 | let mobileControlsMoveX = 0;
59 | let mobileControlsMoveY = 0;
60 | let oldPinchWidthX = 0;
61 | let oldPinchWidthY = 0;
62 | let pinchDeltaX = 0;
63 | let pinchDeltaY = 0;
64 | let useGenericInput = true;
65 | let EPS_intersect = 0.01; // default precision
66 | let textureLoader = new THREE.TextureLoader();
67 | let blueNoiseTexture;
68 | let useToneMapping = true;
69 | let canPress_O = true;
70 | let canPress_P = true;
71 | let allowOrthographicCamera = true;
72 | let changeToOrthographicCamera = false;
73 | let changeToPerspectiveCamera = false;
74 | let pixelEdgeSharpness = 0.75;
75 | let edgeSharpenSpeed = 0.05;
76 | //let filterDecaySpeed = 0.0001;
77 |
78 | let gui;
79 | let ableToEngagePointerLock = true;
80 | let pixel_ResolutionController, pixel_ResolutionObject;
81 | let needChangePixelResolution = false;
82 | let orthographicCamera_ToggleController, orthographicCamera_ToggleObject;
83 | let currentlyUsingOrthographicCamera = false;
84 |
85 | // the following variables will be used to calculate rotations and directions from the camera
86 | let cameraDirectionVector = new THREE.Vector3(); //for moving where the camera is looking
87 | let cameraRightVector = new THREE.Vector3(); //for strafing the camera right and left
88 | let cameraUpVector = new THREE.Vector3(); //for moving camera up and down
89 | let cameraControlsObject; //for positioning and moving the camera itself
90 | let cameraControlsYawObject; //allows access to control camera's left/right movements through mobile input
91 | let cameraControlsPitchObject; //allows access to control camera's up/down movements through mobile input
92 | let PI_2 = Math.PI / 2; //used by controls below
93 | let inputRotationHorizontal = 0;
94 | let inputRotationVertical = 0;
95 |
96 | let infoElement = document.getElementById('info');
97 | infoElement.style.cursor = "default";
98 | infoElement.style.userSelect = "none";
99 | infoElement.style.MozUserSelect = "none";
100 |
101 | let cameraInfoElement = document.getElementById('cameraInfo');
102 | cameraInfoElement.style.cursor = "default";
103 | cameraInfoElement.style.userSelect = "none";
104 | cameraInfoElement.style.MozUserSelect = "none";
105 |
106 | let mouseControl = true;
107 | let pointerlockChange;
108 | let fileLoader = new THREE.FileLoader();
109 |
110 | // The following list of keys is not exhaustive, but it should be more than enough to build interactive demos and games
111 | let KeyboardState = {
112 | KeyA: false, KeyB: false, KeyC: false, KeyD: false, KeyE: false, KeyF: false, KeyG: false, KeyH: false, KeyI: false, KeyJ: false, KeyK: false, KeyL: false, KeyM: false,
113 | KeyN: false, KeyO: false, KeyP: false, KeyQ: false, KeyR: false, KeyS: false, KeyT: false, KeyU: false, KeyV: false, KeyW: false, KeyX: false, KeyY: false, KeyZ: false,
114 | ArrowLeft: false, ArrowUp: false, ArrowRight: false, ArrowDown: false, Space: false, Enter: false, PageUp: false, PageDown: false, Tab: false,
115 | Minus: false, Equal: false, BracketLeft: false, BracketRight: false, Semicolon: false, Quote: false, Backquote: false,
116 | Comma: false, Period: false, ShiftLeft: false, ShiftRight: false, Slash: false, Backslash: false, Backspace: false,
117 | Digit1: false, Digit2: false, Digit3: false, Digit4: false, Digit5: false, Digit6: false, Digit7: false, Digit8: false, Digit9: false, Digit0: false
118 | }
119 |
120 | function onKeyDown(event)
121 | {
122 | event.preventDefault();
123 |
124 | KeyboardState[event.code] = true;
125 | }
126 |
127 | function onKeyUp(event)
128 | {
129 | event.preventDefault();
130 |
131 | KeyboardState[event.code] = false;
132 | }
133 |
134 | function keyPressed(keyName)
135 | {
136 | if (!mouseControl)
137 | return;
138 |
139 | return KeyboardState[keyName];
140 | }
141 |
142 |
143 | function onMouseWheel(event)
144 | {
145 | if (isPaused)
146 | return;
147 |
148 | // use the following instead, because event.preventDefault() gives errors in console
149 | event.stopPropagation();
150 |
151 | if (event.deltaY > 0)
152 | {
153 | increaseFOV = true;
154 | dollyCameraOut = true;
155 | }
156 | else if (event.deltaY < 0)
157 | {
158 | decreaseFOV = true;
159 | dollyCameraIn = true;
160 | }
161 | }
162 |
163 | /**
164 | * originally from https://github.com/mrdoob/three.js/blob/dev/examples/js/controls/PointerLockControls.js
165 | * @author mrdoob / http://mrdoob.com/
166 | *
167 | * edited by Erich Loftis (erichlof on GitHub)
168 | * https://github.com/erichlof
169 | * Btw, this is the most concise and elegant way to implement first person camera rotation/movement that I've ever seen -
170 | * look at how short it is, without spaces/braces it would be around 30 lines! Way to go, mrdoob!
171 | */
172 |
173 | function FirstPersonCameraControls(camera)
174 | {
175 | camera.rotation.set(0, 0, 0);
176 |
177 | let pitchObject = new THREE.Object3D();
178 | pitchObject.add(camera);
179 |
180 | let yawObject = new THREE.Object3D();
181 | yawObject.add(pitchObject);
182 |
183 | function onMouseMove(event)
184 | {
185 | if (isPaused)
186 | return;
187 | inputMovementHorizontal = event.movementX || event.mozMovementX || 0;
188 | inputMovementVertical = event.movementY || event.mozMovementY || 0;
189 |
190 | inputMovementHorizontal = -inputMovementHorizontal * 0.0012 * cameraRotationSpeed;
191 | inputMovementVertical = -inputMovementVertical * 0.001 * cameraRotationSpeed;
192 |
193 | if (inputMovementHorizontal) // prevent NaNs due to invalid mousemove data from browser
194 | inputRotationHorizontal += inputMovementHorizontal;
195 | if (inputMovementVertical) // prevent NaNs due to invalid mousemove data from browser
196 | inputRotationVertical += inputMovementVertical;
197 | // clamp the camera's vertical movement (around the x-axis) to the scene's 'ceiling' and 'floor'
198 | inputRotationVertical = Math.max(- PI_2, Math.min(PI_2, inputRotationVertical));
199 | }
200 |
201 | document.addEventListener('mousemove', onMouseMove, false);
202 |
203 |
204 | this.getObject = function()
205 | {
206 | return yawObject;
207 | };
208 |
209 | this.getYawObject = function()
210 | {
211 | return yawObject;
212 | };
213 |
214 | this.getPitchObject = function()
215 | {
216 | return pitchObject;
217 | };
218 |
219 | this.getDirection = function()
220 | {
221 | const te = pitchObject.matrixWorld.elements;
222 |
223 | return function(v)
224 | {
225 | v.set(te[8], te[9], te[10]).negate();
226 | return v;
227 | };
228 | }();
229 |
230 | this.getUpVector = function()
231 | {
232 | const te = pitchObject.matrixWorld.elements;
233 |
234 | return function(v)
235 | {
236 | v.set(te[4], te[5], te[6]);
237 | return v;
238 | };
239 | }();
240 |
241 | this.getRightVector = function()
242 | {
243 | const te = pitchObject.matrixWorld.elements;
244 |
245 | return function(v)
246 | {
247 | v.set(te[0], te[1], te[2]);
248 | return v;
249 | };
250 | }();
251 |
252 | } // end function FirstPersonCameraControls(camera)
253 |
254 |
255 | function onWindowResize(event)
256 | {
257 |
258 | windowIsBeingResized = true;
259 |
260 | // the following change to document.body.clientWidth and Height works better for mobile, especially iOS
261 | // suggestion from Github user q750831855 - Thank you!
262 | SCREEN_WIDTH = document.body.clientWidth; //window.innerWidth;
263 | SCREEN_HEIGHT = document.body.clientHeight; //window.innerHeight;
264 |
265 | renderer.setPixelRatio(pixelRatio);
266 | renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
267 |
268 | pathTracingUniforms.uResolution.value.x = context.drawingBufferWidth;
269 | pathTracingUniforms.uResolution.value.y = context.drawingBufferHeight;
270 |
271 | pathTracingRenderTarget.setSize(context.drawingBufferWidth, context.drawingBufferHeight);
272 | screenCopyRenderTarget.setSize(context.drawingBufferWidth, context.drawingBufferHeight);
273 |
274 | worldCamera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
275 | // the following is normally used with traditional rasterized rendering, but it is not needed for our fragment shader raytraced rendering
276 | ///worldCamera.updateProjectionMatrix();
277 |
278 | // the following scales all scene objects by the worldCamera's field of view,
279 | // taking into account the screen aspect ratio and multiplying the uniform uULen,
280 | // the x-coordinate, by this ratio
281 | fovScale = worldCamera.fov * 0.5 * (Math.PI / 180.0);
282 | pathTracingUniforms.uVLen.value = Math.tan(fovScale);
283 | pathTracingUniforms.uULen.value = pathTracingUniforms.uVLen.value * worldCamera.aspect;
284 |
285 | if (!mouseControl && mobileShowButtons)
286 | {
287 | button1Element.style.display = "";
288 | button2Element.style.display = "";
289 | button3Element.style.display = "";
290 | button4Element.style.display = "";
291 | button5Element.style.display = "";
292 | button6Element.style.display = "";
293 | // check if mobile device is in portrait or landscape mode and position buttons accordingly
294 | if (SCREEN_WIDTH < SCREEN_HEIGHT)
295 | {
296 | button1Element.style.right = 36 + "%";
297 | button2Element.style.right = 2 + "%";
298 | button3Element.style.right = 16 + "%";
299 | button4Element.style.right = 16 + "%";
300 | button5Element.style.right = 3 + "%";
301 | button6Element.style.right = 3 + "%";
302 |
303 | button1Element.style.bottom = 5 + "%";
304 | button2Element.style.bottom = 5 + "%";
305 | button3Element.style.bottom = 13 + "%";
306 | button4Element.style.bottom = 2 + "%";
307 | button5Element.style.bottom = 25 + "%";
308 | button6Element.style.bottom = 18 + "%";
309 | }
310 | else
311 | {
312 | button1Element.style.right = 22 + "%";
313 | button2Element.style.right = 3 + "%";
314 | button3Element.style.right = 11 + "%";
315 | button4Element.style.right = 11 + "%";
316 | button5Element.style.right = 3 + "%";
317 | button6Element.style.right = 3 + "%";
318 |
319 | button1Element.style.bottom = 10 + "%";
320 | button2Element.style.bottom = 10 + "%";
321 | button3Element.style.bottom = 26 + "%";
322 | button4Element.style.bottom = 4 + "%";
323 | button5Element.style.bottom = 48 + "%";
324 | button6Element.style.bottom = 34 + "%";
325 | }
326 | } // end if ( !mouseControl ) {
327 |
328 | } // end function onWindowResize( event )
329 |
330 |
331 |
332 | function init()
333 | {
334 |
335 | window.addEventListener('resize', onWindowResize, false);
336 | window.addEventListener('orientationchange', onWindowResize, false);
337 |
338 | if ('ontouchstart' in window)
339 | {
340 | mouseControl = false;
341 | // if on mobile device, unpause the app because there is no ESC key and no mouse capture to do
342 | isPaused = false;
343 |
344 | ableToEngagePointerLock = true;
345 | }
346 |
347 | // default GUI elements for all demos
348 |
349 | pixel_ResolutionObject = {
350 | pixel_Resolution: 0.5 // will be set by each demo's js file
351 | }
352 | orthographicCamera_ToggleObject = {
353 | Orthographic_Camera: false
354 | }
355 |
356 | function handlePixelResolutionChange()
357 | {
358 | needChangePixelResolution = true;
359 | }
360 | function handleCameraProjectionChange()
361 | {
362 | if (!currentlyUsingOrthographicCamera)
363 | changeToOrthographicCamera = true;
364 | else if (currentlyUsingOrthographicCamera)
365 | changeToPerspectiveCamera = true;
366 | // toggle boolean flag
367 | currentlyUsingOrthographicCamera = !currentlyUsingOrthographicCamera;
368 | }
369 |
370 |
371 | gui = new GUI();
372 |
373 | gui.domElement.style.userSelect = "none";
374 | gui.domElement.style.MozUserSelect = "none";
375 |
376 |
377 | if (mouseControl) // on desktop
378 | {
379 | pixel_ResolutionController = gui.add(pixel_ResolutionObject, 'pixel_Resolution', 0.5, 2.0, 0.1).onChange(handlePixelResolutionChange);
380 |
381 | gui.domElement.addEventListener("mouseenter", function (event)
382 | {
383 | ableToEngagePointerLock = false;
384 | }, false);
385 | gui.domElement.addEventListener("mouseleave", function (event)
386 | {
387 | ableToEngagePointerLock = true;
388 | }, false);
389 |
390 | window.addEventListener('wheel', onMouseWheel, false);
391 |
392 | // window.addEventListener("click", function(event)
393 | // {
394 | // event.preventDefault();
395 | // }, false);
396 | window.addEventListener("dblclick", function (event)
397 | {
398 | event.preventDefault();
399 | }, false);
400 |
401 | document.body.addEventListener("click", function (event)
402 | {
403 | if (!ableToEngagePointerLock)
404 | return;
405 | this.requestPointerLock = this.requestPointerLock || this.mozRequestPointerLock;
406 | this.requestPointerLock();
407 | }, false);
408 |
409 |
410 | pointerlockChange = function (event)
411 | {
412 | if (document.pointerLockElement === document.body ||
413 | document.mozPointerLockElement === document.body || document.webkitPointerLockElement === document.body)
414 | {
415 | document.addEventListener('keydown', onKeyDown, false);
416 | document.addEventListener('keyup', onKeyUp, false);
417 | isPaused = false;
418 | }
419 | else
420 | {
421 | document.removeEventListener('keydown', onKeyDown, false);
422 | document.removeEventListener('keyup', onKeyUp, false);
423 | isPaused = true;
424 | }
425 | };
426 |
427 | // Hook pointer lock state change events
428 | document.addEventListener('pointerlockchange', pointerlockChange, false);
429 | document.addEventListener('mozpointerlockchange', pointerlockChange, false);
430 | document.addEventListener('webkitpointerlockchange', pointerlockChange, false);
431 |
432 | } // end if (mouseControl)
433 |
434 | if (!mouseControl) // on mobile
435 | {
436 | pixel_ResolutionController = gui.add(pixel_ResolutionObject, 'pixel_Resolution', 0.5, 1.0, 0.05).onChange(handlePixelResolutionChange);
437 | orthographicCamera_ToggleController = gui.add(orthographicCamera_ToggleObject, 'Orthographic_Camera', false).onChange(handleCameraProjectionChange);
438 | }
439 |
440 |
441 | /* // Fullscreen API (optional)
442 | document.addEventListener("click", function()
443 | {
444 | if ( !document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement )
445 | {
446 | if (document.documentElement.requestFullscreen)
447 | document.documentElement.requestFullscreen();
448 | else if (document.documentElement.mozRequestFullScreen)
449 | document.documentElement.mozRequestFullScreen();
450 | else if (document.documentElement.webkitRequestFullscreen)
451 | document.documentElement.webkitRequestFullscreen();
452 | }
453 | }); */
454 |
455 | // load a resource
456 | blueNoiseTexture = textureLoader.load(
457 | // resource URL
458 | 'textures/BlueNoise_R_128.png',
459 |
460 | // onLoad callback
461 | function (texture)
462 | {
463 | texture.wrapS = THREE.RepeatWrapping;
464 | texture.wrapT = THREE.RepeatWrapping;
465 | texture.flipY = false;
466 | texture.minFilter = THREE.NearestFilter;
467 | texture.magFilter = THREE.NearestFilter;
468 | texture.generateMipmaps = false;
469 | //console.log("blue noise texture loaded");
470 |
471 | initTHREEjs(); // boilerplate: init necessary three.js items and scene/demo-specific objects
472 | }
473 | );
474 |
475 |
476 | } // end function init()
477 |
478 |
479 |
480 | function initTHREEjs()
481 | {
482 |
483 | canvas = document.createElement('canvas');
484 |
485 | renderer = new THREE.WebGLRenderer({ canvas: canvas, context: canvas.getContext('webgl2') });
486 | //suggestion: set to false for production
487 | renderer.debug.checkShaderErrors = true;
488 |
489 | renderer.autoClear = false;
490 |
491 | renderer.toneMapping = THREE.ReinhardToneMapping;
492 |
493 | //required by WebGL 2.0 for rendering to FLOAT textures
494 | context = renderer.getContext();
495 | context.getExtension('EXT_color_buffer_float');
496 |
497 | container = document.getElementById('container');
498 | container.appendChild(renderer.domElement);
499 |
500 | stats = new Stats();
501 | stats.domElement.style.position = 'absolute';
502 | stats.domElement.style.top = '0px';
503 | stats.domElement.style.cursor = "default";
504 | stats.domElement.style.userSelect = "none";
505 | stats.domElement.style.MozUserSelect = "none";
506 | container.appendChild(stats.domElement);
507 |
508 |
509 | clock = new THREE.Clock();
510 |
511 | pathTracingScene = new THREE.Scene();
512 | screenCopyScene = new THREE.Scene();
513 | screenOutputScene = new THREE.Scene();
514 |
515 | // orthoCamera is the camera to help render the oversized full-screen triangle, which is stretched across the
516 | // screen (and a little outside the viewport). orthoCamera is an orthographic camera that sits facing the view plane,
517 | // which serves as the window into our 3d world. This camera will not move or rotate for the duration of the app.
518 | orthoCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
519 | screenCopyScene.add(orthoCamera);
520 | screenOutputScene.add(orthoCamera);
521 |
522 | // worldCamera is the dynamic camera 3d object that will be positioned, oriented and constantly updated inside
523 | // the 3d scene. Its view will ultimately get passed back to the stationary orthoCamera that renders
524 | // the scene to a full-screen triangle, which is stretched across the viewport.
525 | worldCamera = new THREE.PerspectiveCamera(60, document.body.clientWidth / document.body.clientHeight, 1, 1000);
526 | storedFOV = worldCamera.fov;
527 | pathTracingScene.add(worldCamera);
528 |
529 | controls = new FirstPersonCameraControls(worldCamera);
530 |
531 | cameraControlsObject = controls.getObject();
532 | cameraControlsYawObject = controls.getYawObject();
533 | cameraControlsPitchObject = controls.getPitchObject();
534 |
535 | pathTracingScene.add(cameraControlsObject);
536 |
537 |
538 | // setup render targets...
539 | pathTracingRenderTarget = new THREE.WebGLRenderTarget(context.drawingBufferWidth, context.drawingBufferHeight, {
540 | minFilter: THREE.NearestFilter,
541 | magFilter: THREE.NearestFilter,
542 | format: THREE.RGBAFormat,
543 | type: THREE.FloatType,
544 | depthBuffer: false,
545 | stencilBuffer: false
546 | });
547 | pathTracingRenderTarget.texture.generateMipmaps = false;
548 |
549 | screenCopyRenderTarget = new THREE.WebGLRenderTarget(context.drawingBufferWidth, context.drawingBufferHeight, {
550 | minFilter: THREE.NearestFilter,
551 | magFilter: THREE.NearestFilter,
552 | format: THREE.RGBAFormat,
553 | type: THREE.FloatType,
554 | depthBuffer: false,
555 | stencilBuffer: false
556 | });
557 | screenCopyRenderTarget.texture.generateMipmaps = false;
558 |
559 |
560 |
561 | // setup scene/demo-specific objects, variables, GUI elements, and data
562 | initSceneData();
563 |
564 |
565 | if ( !mouseControl )
566 | {
567 | mobileJoystickControls = new MobileJoystickControls({
568 | //showJoystick: true,
569 | showButtons: mobileShowButtons,
570 | useDarkButtons: mobileUseDarkButtons
571 | });
572 | }
573 |
574 | pixel_ResolutionController.setValue(pixelRatio);
575 | if (!allowOrthographicCamera && !mouseControl)
576 | {
577 | orthographicCamera_ToggleController.domElement.hidden = true;
578 | orthographicCamera_ToggleController.domElement.remove();
579 | }
580 |
581 |
582 | // setup oversized full-screen triangle geometry and shaders....
583 |
584 | // this full-screen single triangle mesh will perform the path tracing operations, producing a screen-sized image
585 |
586 | trianglePositions.push(-1,-1, 0 ); // start in lower left corner of viewport
587 | trianglePositions.push( 3,-1, 0 ); // go beyond right side of viewport, in order to have full-screen coverage
588 | trianglePositions.push(-1, 3, 0 ); // go beyond top of viewport, in order to have full-screen coverage
589 | triangleGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( trianglePositions, 3 ));
590 |
591 |
592 | pathTracingUniforms.tPreviousTexture = { type: "t", value: screenCopyRenderTarget.texture };
593 | pathTracingUniforms.tBlueNoiseTexture = { type: "t", value: blueNoiseTexture };
594 |
595 | pathTracingUniforms.uCameraMatrix = { type: "m4", value: new THREE.Matrix4() };
596 |
597 | pathTracingUniforms.uResolution = { type: "v2", value: new THREE.Vector2() };
598 | pathTracingUniforms.uRandomVec2 = { type: "v2", value: new THREE.Vector2() };
599 |
600 | pathTracingUniforms.uEPS_intersect = { type: "f", value: EPS_intersect };
601 | pathTracingUniforms.uTime = { type: "f", value: 0.0 };
602 | pathTracingUniforms.uSampleCounter = { type: "f", value: 0.0 }; //0.0
603 | pathTracingUniforms.uPreviousSampleCount = { type: "f", value: 1.0 };
604 | pathTracingUniforms.uFrameCounter = { type: "f", value: 1.0 }; //1.0
605 | pathTracingUniforms.uULen = { type: "f", value: 1.0 };
606 | pathTracingUniforms.uVLen = { type: "f", value: 1.0 };
607 | pathTracingUniforms.uApertureSize = { type: "f", value: apertureSize };
608 | pathTracingUniforms.uFocusDistance = { type: "f", value: focusDistance };
609 |
610 | pathTracingUniforms.uCameraIsMoving = { type: "b1", value: false };
611 | pathTracingUniforms.uUseOrthographicCamera = { type: "b1", value: false };
612 |
613 | pathTracingDefines = {
614 | //NUMBER_OF_TRIANGLES: total_number_of_triangles
615 | };
616 |
617 | // load vertex and fragment shader files that are used in the pathTracing material, mesh and scene
618 | fileLoader.load('shaders/common_PathTracing_Vertex.glsl', function (vertexShaderText)
619 | {
620 | pathTracingVertexShader = vertexShaderText;
621 |
622 | fileLoader.load('shaders/' + demoFragmentShaderFileName, function (fragmentShaderText)
623 | {
624 |
625 | pathTracingFragmentShader = fragmentShaderText;
626 |
627 | pathTracingMaterial = new THREE.ShaderMaterial({
628 | uniforms: pathTracingUniforms,
629 | uniformsGroups: pathTracingUniformsGroups,
630 | defines: pathTracingDefines,
631 | vertexShader: pathTracingVertexShader,
632 | fragmentShader: pathTracingFragmentShader,
633 | depthTest: false,
634 | depthWrite: false
635 | });
636 |
637 | pathTracingMesh = new THREE.Mesh(triangleGeometry, pathTracingMaterial);
638 | pathTracingScene.add(pathTracingMesh);
639 |
640 | // the following keeps the oversized full-screen triangle right in front
641 | // of the camera at all times. This is necessary because without it, the full-screen
642 | // triangle will fall out of view and get clipped when the camera rotates past 180 degrees.
643 | worldCamera.add(pathTracingMesh);
644 |
645 | });
646 | });
647 |
648 |
649 | // this oversized full-screen triangle mesh copies the image output of the pathtracing shader and feeds it back in to that shader as a 'previousTexture'
650 |
651 | screenCopyUniforms = {
652 | tPathTracedImageTexture: { type: "t", value: pathTracingRenderTarget.texture }
653 | };
654 |
655 | fileLoader.load('shaders/ScreenCopy_Fragment.glsl', function (shaderText)
656 | {
657 |
658 | screenCopyFragmentShader = shaderText;
659 |
660 | screenCopyMaterial = new THREE.ShaderMaterial({
661 | uniforms: screenCopyUniforms,
662 | vertexShader: pathTracingVertexShader,
663 | fragmentShader: screenCopyFragmentShader,
664 | depthWrite: false,
665 | depthTest: false
666 | });
667 |
668 | screenCopyMesh = new THREE.Mesh(triangleGeometry, screenCopyMaterial);
669 | screenCopyScene.add(screenCopyMesh);
670 | });
671 |
672 |
673 | // this oversized full-screen triangle mesh takes the image output of the path tracing shader (which is a continuous blend of the previous frame and current frame),
674 | // and applies gamma correction (which brightens the entire image), and then displays the final accumulated rendering to the screen
675 |
676 | screenOutputUniforms = {
677 | tPathTracedImageTexture: { type: "t", value: pathTracingRenderTarget.texture },
678 | uSampleCounter: { type: "f", value: 0.0 },
679 | uOneOverSampleCounter: { type: "f", value: 0.0 },
680 | uPixelEdgeSharpness: { type: "f", value: pixelEdgeSharpness },
681 | uEdgeSharpenSpeed: { type: "f", value: edgeSharpenSpeed },
682 | //uFilterDecaySpeed: { type: "f", value: filterDecaySpeed },
683 | uCameraIsMoving: { type: "b1", value: false },
684 | uSceneIsDynamic: { type: "b1", value: sceneIsDynamic },
685 | uUseToneMapping: { type: "b1", value: useToneMapping }
686 | };
687 |
688 | fileLoader.load('shaders/ScreenOutput_Fragment.glsl', function (shaderText)
689 | {
690 |
691 | screenOutputFragmentShader = shaderText;
692 |
693 | screenOutputMaterial = new THREE.ShaderMaterial({
694 | uniforms: screenOutputUniforms,
695 | vertexShader: pathTracingVertexShader,
696 | fragmentShader: screenOutputFragmentShader,
697 | depthWrite: false,
698 | depthTest: false
699 | });
700 |
701 | screenOutputMesh = new THREE.Mesh(triangleGeometry, screenOutputMaterial);
702 | screenOutputScene.add(screenOutputMesh);
703 | });
704 |
705 |
706 | // this 'jumpstarts' the initial dimensions and parameters for the window and renderer
707 | onWindowResize();
708 |
709 | // everything is set up, now we can start animating
710 | animate();
711 |
712 | } // end function initTHREEjs()
713 |
714 |
715 |
716 |
717 | function animate()
718 | {
719 |
720 | frameTime = clock.getDelta();
721 |
722 | elapsedTime = clock.getElapsedTime() % 1000;
723 |
724 | // reset flags
725 | cameraIsMoving = false;
726 |
727 | // if GUI has been used, update
728 | if (needChangePixelResolution)
729 | {
730 | pixelRatio = pixel_ResolutionController.getValue();
731 | onWindowResize();
732 | needChangePixelResolution = false;
733 | }
734 |
735 | if (windowIsBeingResized)
736 | {
737 | cameraIsMoving = true;
738 | windowIsBeingResized = false;
739 | }
740 |
741 | // check user controls
742 | if (mouseControl)
743 | {
744 | // movement detected
745 | if (oldYawRotation != inputRotationHorizontal ||
746 | oldPitchRotation != inputRotationVertical)
747 | {
748 | cameraIsMoving = true;
749 | }
750 |
751 | // save state for next frame
752 | oldYawRotation = inputRotationHorizontal;
753 | oldPitchRotation = inputRotationVertical;
754 |
755 | } // end if (mouseControl)
756 |
757 | // if on mobile device, get input from the mobileJoystickControls
758 | if (!mouseControl)
759 | {
760 | newDeltaX = joystickDeltaX * cameraRotationSpeed;
761 |
762 | if (newDeltaX)
763 | {
764 | cameraIsMoving = true;
765 | // the ' || 0 ' prevents NaNs from creeping into inputRotationHorizontal calc below
766 | inputMovementHorizontal = ((oldDeltaX - newDeltaX) * 0.01) || 0;
767 | // mobileJoystick X movement (left and right) affects camera rotation around the Y axis
768 | inputRotationHorizontal += inputMovementHorizontal;
769 | }
770 |
771 | newDeltaY = joystickDeltaY * cameraRotationSpeed;
772 |
773 | if (newDeltaY)
774 | {
775 | cameraIsMoving = true;
776 | // the ' || 0 ' prevents NaNs from creeping into inputRotationVertical calc below
777 | inputMovementVertical = ((oldDeltaY - newDeltaY) * 0.01) || 0;
778 | // mobileJoystick Y movement (up and down) affects camera rotation around the X axis
779 | inputRotationVertical += inputMovementVertical;
780 | }
781 |
782 | // clamp the camera's vertical movement (around the x-axis) to the scene's 'ceiling' and 'floor',
783 | // so you can't accidentally flip the camera upside down
784 | inputRotationVertical = Math.max(-PI_2, Math.min(PI_2, inputRotationVertical));
785 |
786 | // save state for next frame
787 | oldDeltaX = newDeltaX;
788 | oldDeltaY = newDeltaY;
789 |
790 | newPinchWidthX = pinchWidthX;
791 | newPinchWidthY = pinchWidthY;
792 | pinchDeltaX = newPinchWidthX - oldPinchWidthX;
793 | pinchDeltaY = newPinchWidthY - oldPinchWidthY;
794 |
795 | if (Math.abs(pinchDeltaX) > Math.abs(pinchDeltaY))
796 | {
797 | if (pinchDeltaX < -1)
798 | {
799 | increaseFOV = true;
800 | dollyCameraOut = true;
801 | }
802 | if (pinchDeltaX > 1)
803 | {
804 | decreaseFOV = true;
805 | dollyCameraIn = true;
806 | }
807 | }
808 |
809 | if (Math.abs(pinchDeltaY) >= Math.abs(pinchDeltaX))
810 | {
811 | if (pinchDeltaY > 1)
812 | {
813 | increaseAperture = true;
814 | }
815 | if (pinchDeltaY < -1)
816 | {
817 | decreaseAperture = true;
818 | }
819 | }
820 |
821 | // save state for next frame
822 | oldPinchWidthX = newPinchWidthX;
823 | oldPinchWidthY = newPinchWidthY;
824 |
825 | } // end if ( !mouseControl )
826 |
827 |
828 | cameraControlsYawObject.rotateY(inputMovementHorizontal);
829 | cameraControlsPitchObject.rotateX(inputMovementVertical);
830 |
831 | // this gives us a vector in the direction that the camera is pointing,
832 | // which will be useful for moving the camera 'forward' and shooting projectiles in that direction
833 | controls.getDirection(cameraDirectionVector);
834 | cameraDirectionVector.normalize();
835 | controls.getUpVector(cameraUpVector);
836 | cameraUpVector.normalize();
837 | controls.getRightVector(cameraRightVector);
838 | cameraRightVector.normalize();
839 |
840 |
841 | if (useGenericInput)
842 | {
843 | if (!isPaused)
844 | {
845 | if ((keyPressed('KeyW') || button3Pressed) && !(keyPressed('KeyS') || button4Pressed))
846 | {
847 | cameraControlsObject.position.add(cameraDirectionVector.multiplyScalar(cameraFlightSpeed * frameTime));
848 | cameraIsMoving = true;
849 | }
850 | if ((keyPressed('KeyS') || button4Pressed) && !(keyPressed('KeyW') || button3Pressed))
851 | {
852 | cameraControlsObject.position.sub(cameraDirectionVector.multiplyScalar(cameraFlightSpeed * frameTime));
853 | cameraIsMoving = true;
854 | }
855 | if ((keyPressed('KeyA') || button1Pressed) && !(keyPressed('KeyD') || button2Pressed))
856 | {
857 | cameraControlsObject.position.sub(cameraRightVector.multiplyScalar(cameraFlightSpeed * frameTime));
858 | cameraIsMoving = true;
859 | }
860 | if ((keyPressed('KeyD') || button2Pressed) && !(keyPressed('KeyA') || button1Pressed))
861 | {
862 | cameraControlsObject.position.add(cameraRightVector.multiplyScalar(cameraFlightSpeed * frameTime));
863 | cameraIsMoving = true;
864 | }
865 | if (keyPressed('KeyQ') && !keyPressed('KeyZ'))
866 | {
867 | cameraControlsObject.position.add(cameraUpVector.multiplyScalar(cameraFlightSpeed * frameTime));
868 | cameraIsMoving = true;
869 | }
870 | if (keyPressed('KeyZ') && !keyPressed('KeyQ'))
871 | {
872 | cameraControlsObject.position.sub(cameraUpVector.multiplyScalar(cameraFlightSpeed * frameTime));
873 | cameraIsMoving = true;
874 | }
875 | if ((keyPressed('ArrowUp') || button5Pressed) && !(keyPressed('ArrowDown') || button6Pressed))
876 | {
877 | increaseFocusDist = true;
878 | }
879 | if ((keyPressed('ArrowDown') || button6Pressed) && !(keyPressed('ArrowUp') || button5Pressed))
880 | {
881 | decreaseFocusDist = true;
882 | }
883 | if (keyPressed('ArrowRight') && !keyPressed('ArrowLeft'))
884 | {
885 | increaseAperture = true;
886 | }
887 | if (keyPressed('ArrowLeft') && !keyPressed('ArrowRight'))
888 | {
889 | decreaseAperture = true;
890 | }
891 | if (keyPressed('KeyO') && canPress_O)
892 | {
893 | changeToOrthographicCamera = true;
894 | canPress_O = false;
895 | }
896 | if (!keyPressed('KeyO'))
897 | canPress_O = true;
898 |
899 | if (keyPressed('KeyP') && canPress_P)
900 | {
901 | changeToPerspectiveCamera = true;
902 | canPress_P = false;
903 | }
904 | if (!keyPressed('KeyP'))
905 | canPress_P = true;
906 | } // end if (!isPaused)
907 |
908 | } // end if (useGenericInput)
909 |
910 |
911 |
912 | // update scene/demo-specific input(if custom), variables and uniforms every animation frame
913 | updateVariablesAndUniforms();
914 |
915 | //reset controls movement
916 | inputMovementHorizontal = inputMovementVertical = 0;
917 |
918 |
919 | if (increaseFOV)
920 | {
921 | worldCamera.fov++;
922 | if (worldCamera.fov > 179)
923 | worldCamera.fov = 179;
924 | fovScale = worldCamera.fov * 0.5 * (Math.PI / 180.0);
925 | pathTracingUniforms.uVLen.value = Math.tan(fovScale);
926 | pathTracingUniforms.uULen.value = pathTracingUniforms.uVLen.value * worldCamera.aspect;
927 |
928 | cameraIsMoving = true;
929 | increaseFOV = false;
930 | }
931 | if (decreaseFOV)
932 | {
933 | worldCamera.fov--;
934 | if (worldCamera.fov < 1)
935 | worldCamera.fov = 1;
936 | fovScale = worldCamera.fov * 0.5 * (Math.PI / 180.0);
937 | pathTracingUniforms.uVLen.value = Math.tan(fovScale);
938 | pathTracingUniforms.uULen.value = pathTracingUniforms.uVLen.value * worldCamera.aspect;
939 |
940 | cameraIsMoving = true;
941 | decreaseFOV = false;
942 | }
943 |
944 | if (increaseFocusDist)
945 | {
946 | focusDistance += 1;
947 | pathTracingUniforms.uFocusDistance.value = focusDistance;
948 | cameraIsMoving = true;
949 | increaseFocusDist = false;
950 | }
951 | if (decreaseFocusDist)
952 | {
953 | focusDistance -= 1;
954 | if (focusDistance < 1)
955 | focusDistance = 1;
956 | pathTracingUniforms.uFocusDistance.value = focusDistance;
957 | cameraIsMoving = true;
958 | decreaseFocusDist = false;
959 | }
960 |
961 | if (increaseAperture)
962 | {
963 | apertureSize += (0.1 * apertureChangeSpeed);
964 | if (apertureSize > 10000.0)
965 | apertureSize = 10000.0;
966 |
967 | cameraIsMoving = true;
968 | increaseAperture = false;
969 | }
970 | if (decreaseAperture)
971 | {
972 | apertureSize -= (0.1 * apertureChangeSpeed);
973 | if (apertureSize < 0.0)
974 | apertureSize = 0.0;
975 |
976 | cameraIsMoving = true;
977 | decreaseAperture = false;
978 | }
979 | if (allowOrthographicCamera && changeToOrthographicCamera)
980 | {
981 | storedFOV = worldCamera.fov; // save current perspective camera's FOV
982 |
983 | worldCamera.fov = 90; // good default for Ortho camera - lets user see most of the scene
984 | fovScale = worldCamera.fov * 0.5 * (Math.PI / 180.0);
985 | pathTracingUniforms.uVLen.value = Math.tan(fovScale);
986 | pathTracingUniforms.uULen.value = pathTracingUniforms.uVLen.value * worldCamera.aspect;
987 |
988 | pathTracingUniforms.uUseOrthographicCamera.value = true;
989 | cameraIsMoving = true;
990 | changeToOrthographicCamera = false;
991 | }
992 | if (allowOrthographicCamera && changeToPerspectiveCamera)
993 | {
994 | worldCamera.fov = storedFOV; // return to prior perspective camera's FOV
995 | fovScale = worldCamera.fov * 0.5 * (Math.PI / 180.0);
996 | pathTracingUniforms.uVLen.value = Math.tan(fovScale);
997 | pathTracingUniforms.uULen.value = pathTracingUniforms.uVLen.value * worldCamera.aspect;
998 |
999 | pathTracingUniforms.uUseOrthographicCamera.value = false;
1000 | cameraIsMoving = true;
1001 | changeToPerspectiveCamera = false;
1002 | }
1003 |
1004 | // now update uniforms that are common to all scenes
1005 | if (!cameraIsMoving)
1006 | {
1007 | if (sceneIsDynamic)
1008 | sampleCounter = 1.0; // reset for continuous updating of image
1009 | else sampleCounter += 1.0; // for progressive refinement of image
1010 |
1011 | frameCounter += 1.0;
1012 |
1013 | cameraRecentlyMoving = false;
1014 | }
1015 |
1016 | if (cameraIsMoving)
1017 | {
1018 | frameCounter += 1.0;
1019 |
1020 | if (!cameraRecentlyMoving)
1021 | {
1022 | // record current sampleCounter before it gets set to 1.0 below
1023 | pathTracingUniforms.uPreviousSampleCount.value = sampleCounter;
1024 | frameCounter = 1.0;
1025 | cameraRecentlyMoving = true;
1026 | }
1027 |
1028 | sampleCounter = 1.0;
1029 | }
1030 |
1031 | pathTracingUniforms.uTime.value = elapsedTime;
1032 | pathTracingUniforms.uCameraIsMoving.value = cameraIsMoving;
1033 | pathTracingUniforms.uSampleCounter.value = sampleCounter;
1034 | pathTracingUniforms.uFrameCounter.value = frameCounter;
1035 | pathTracingUniforms.uRandomVec2.value.set(Math.random(), Math.random());
1036 |
1037 | // CAMERA
1038 |
1039 | cameraControlsObject.updateMatrixWorld(true);
1040 | worldCamera.updateMatrixWorld(true);
1041 | pathTracingUniforms.uCameraMatrix.value.copy(worldCamera.matrixWorld);
1042 | pathTracingUniforms.uApertureSize.value = apertureSize;
1043 |
1044 | screenOutputUniforms.uCameraIsMoving.value = cameraIsMoving;
1045 | screenOutputUniforms.uSampleCounter.value = sampleCounter;
1046 | // PROGRESSIVE SAMPLE WEIGHT (reduces intensity of each successive animation frame's image)
1047 | screenOutputUniforms.uOneOverSampleCounter.value = 1.0 / sampleCounter;
1048 |
1049 |
1050 | // RENDERING in 3 steps
1051 |
1052 | // STEP 1
1053 | // Perform PathTracing and Render(save) into pathTracingRenderTarget, a full-screen texture (on the oversized triangle).
1054 | // Read previous screenCopyRenderTarget(via texelFetch inside fragment shader) to use as a new starting point to blend with
1055 | renderer.setRenderTarget(pathTracingRenderTarget);
1056 | renderer.render(pathTracingScene, worldCamera);
1057 |
1058 | // STEP 2
1059 | // Render(copy) the pathTracingScene output(pathTracingRenderTarget above) into screenCopyRenderTarget.
1060 | // This will be used as a new starting point for Step 1 above (essentially creating ping-pong buffers)
1061 | renderer.setRenderTarget(screenCopyRenderTarget);
1062 | renderer.render(screenCopyScene, orthoCamera);
1063 |
1064 | // STEP 3
1065 | // Render to the oversized full-screen triangle with generated pathTracingRenderTarget in STEP 1 above.
1066 | // After applying tonemapping and gamma-correction to the image, it will be shown on the screen as the final accumulated output
1067 | renderer.setRenderTarget(null);
1068 | renderer.render(screenOutputScene, orthoCamera);
1069 |
1070 | stats.update();
1071 |
1072 | requestAnimationFrame(animate);
1073 |
1074 | } // end function animate()
1075 |
--------------------------------------------------------------------------------
/js/MobileJoystickControls.js:
--------------------------------------------------------------------------------
1 | // exposed global variables/elements that your program can access
2 | let joystickDeltaX = 0;
3 | let joystickDeltaY = 0;
4 | let pinchWidthX = 0;
5 | let pinchWidthY = 0;
6 | let button1Pressed = false;
7 | let button2Pressed = false;
8 | let button3Pressed = false;
9 | let button4Pressed = false;
10 | let button5Pressed = false;
11 | let button6Pressed = false;
12 |
13 | let stickElement = null;
14 | let baseElement = null;
15 | let button1Element = null;
16 | let button2Element = null;
17 | let button3Element = null;
18 | let button4Element = null;
19 | let button5Element = null;
20 | let button6Element = null;
21 |
22 | // the following variables marked with an underscore ( _ ) are for internal use
23 | let _touches = [];
24 | let _eventTarget;
25 | let _stickDistance;
26 | let _stickNormalizedX;
27 | let _stickNormalizedY;
28 | let _buttonCanvasWidth = 70;
29 | let _buttonCanvasReducedWidth = 50;
30 | let _buttonCanvasHalfWidth = _buttonCanvasWidth * 0.5;
31 | let _smallButtonCanvasWidth = 40;
32 | let _smallButtonCanvasReducedWidth = 28;
33 | let _smallButtonCanvasHalfWidth = _smallButtonCanvasWidth * 0.5;
34 | let _showJoystick;
35 | let _showButtons;
36 | let _useDarkButtons;
37 | let _limitStickTravel;
38 | let _stickRadius;
39 | let _baseX;
40 | let _baseY;
41 | let _stickX;
42 | let _stickY;
43 | let _container;
44 | let _pinchWasActive = false;
45 |
46 |
47 |
48 | let MobileJoystickControls = function(opts)
49 | {
50 | opts = opts || {};
51 | // grab the options passed into this constructor function
52 | _showJoystick = opts.showJoystick || false;
53 | _showButtons = opts.showButtons || true;
54 | _useDarkButtons = opts.useDarkButtons || false;
55 |
56 | _baseX = _stickX = opts.baseX || 100;
57 | _baseY = _stickY = opts.baseY || 200;
58 |
59 | _limitStickTravel = opts.limitStickTravel || false;
60 | if (_limitStickTravel) _showJoystick = true;
61 | _stickRadius = opts.stickRadius || 50;
62 | if (_stickRadius > 100) _stickRadius = 100;
63 |
64 |
65 | _container = document.body;
66 |
67 |
68 | //create joystick Base
69 | baseElement = document.createElement('canvas');
70 | baseElement.width = 126;
71 | baseElement.height = 126;
72 | _container.appendChild(baseElement);
73 | baseElement.style.position = "absolute";
74 | baseElement.style.display = "none";
75 |
76 | _Base_ctx = baseElement.getContext('2d');
77 | _Base_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
78 | _Base_ctx.lineWidth = 2;
79 | _Base_ctx.beginPath();
80 | _Base_ctx.arc(baseElement.width / 2, baseElement.width / 2, 40, 0, Math.PI * 2, true);
81 | _Base_ctx.stroke();
82 |
83 | //create joystick Stick
84 | stickElement = document.createElement('canvas');
85 | stickElement.width = 86;
86 | stickElement.height = 86;
87 | _container.appendChild(stickElement);
88 | stickElement.style.position = "absolute";
89 | stickElement.style.display = "none";
90 |
91 | _Stick_ctx = stickElement.getContext('2d');
92 | _Stick_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
93 | _Stick_ctx.lineWidth = 3;
94 | _Stick_ctx.beginPath();
95 | _Stick_ctx.arc(stickElement.width / 2, stickElement.width / 2, 30, 0, Math.PI * 2, true);
96 | _Stick_ctx.stroke();
97 |
98 |
99 | //create button1
100 | button1Element = document.createElement('canvas');
101 | button1Element.width = _buttonCanvasReducedWidth; // for Triangle Button
102 | //button1Element.width = _buttonCanvasWidth; // for Circle Button
103 | button1Element.height = _buttonCanvasWidth;
104 |
105 | _container.appendChild(button1Element);
106 | button1Element.style.position = "absolute";
107 | button1Element.style.display = "none";
108 | button1Element.style.zIndex = "10";
109 | button1Pressed = false;
110 |
111 | _Button1_ctx = button1Element.getContext('2d');
112 | _Button1_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
113 | _Button1_ctx.lineWidth = 3;
114 |
115 | // Triangle Button
116 | _Button1_ctx.beginPath();
117 | _Button1_ctx.moveTo(0, _buttonCanvasHalfWidth);
118 | _Button1_ctx.lineTo(_buttonCanvasReducedWidth, _buttonCanvasWidth);
119 | _Button1_ctx.lineTo(_buttonCanvasReducedWidth, 0);
120 | _Button1_ctx.closePath();
121 | _Button1_ctx.stroke();
122 |
123 | /*
124 | // Circle Button
125 | _Button1_ctx.beginPath();
126 | _Button1_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 8, 0, Math.PI * 2);
127 | _Button1_ctx.stroke();
128 | _Button1_ctx.lineWidth = 1;
129 | _Button1_ctx.beginPath();
130 | _Button1_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 1, 0, Math.PI * 2);
131 | _Button1_ctx.stroke();
132 | */
133 |
134 | //create button2
135 | button2Element = document.createElement('canvas');
136 | button2Element.width = _buttonCanvasReducedWidth; // for Triangle Button
137 | //button2Element.width = _buttonCanvasWidth; // for Circle Button
138 | button2Element.height = _buttonCanvasWidth;
139 |
140 | _container.appendChild(button2Element);
141 | button2Element.style.position = "absolute";
142 | button2Element.style.display = "none";
143 | button2Element.style.zIndex = "10";
144 | button2Pressed = false;
145 |
146 | _Button2_ctx = button2Element.getContext('2d');
147 | _Button2_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
148 | _Button2_ctx.lineWidth = 3;
149 |
150 | // Triangle Button
151 | _Button2_ctx.beginPath();
152 | _Button2_ctx.moveTo(_buttonCanvasReducedWidth, _buttonCanvasHalfWidth);
153 | _Button2_ctx.lineTo(0, 0);
154 | _Button2_ctx.lineTo(0, _buttonCanvasWidth);
155 | _Button2_ctx.closePath();
156 | _Button2_ctx.stroke();
157 |
158 | /*
159 | // Circle Button
160 | _Button2_ctx.beginPath();
161 | _Button2_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 8, 0, Math.PI * 2);
162 | _Button2_ctx.stroke();
163 | _Button2_ctx.lineWidth = 1;
164 | _Button2_ctx.beginPath();
165 | _Button2_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 1, 0, Math.PI * 2);
166 | _Button2_ctx.stroke();
167 | */
168 |
169 | //create button3
170 | button3Element = document.createElement('canvas');
171 | button3Element.width = _buttonCanvasWidth;
172 | button3Element.height = _buttonCanvasReducedWidth; // for Triangle Button
173 | //button3Element.height = _buttonCanvasWidth; // for Circle Button
174 |
175 | _container.appendChild(button3Element);
176 | button3Element.style.position = "absolute";
177 | button3Element.style.display = "none";
178 | button3Element.style.zIndex = "10";
179 | button3Pressed = false;
180 |
181 | _Button3_ctx = button3Element.getContext('2d');
182 | _Button3_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
183 | _Button3_ctx.lineWidth = 3;
184 |
185 | // Triangle Button
186 | _Button3_ctx.beginPath();
187 | _Button3_ctx.moveTo(_buttonCanvasHalfWidth, 0);
188 | _Button3_ctx.lineTo(0, _buttonCanvasReducedWidth);
189 | _Button3_ctx.lineTo(_buttonCanvasWidth, _buttonCanvasReducedWidth);
190 | _Button3_ctx.closePath();
191 | _Button3_ctx.stroke();
192 |
193 | /*
194 | // Circle Button
195 | _Button3_ctx.beginPath();
196 | _Button3_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 8, 0, Math.PI * 2);
197 | _Button3_ctx.stroke();
198 | _Button3_ctx.lineWidth = 1;
199 | _Button3_ctx.beginPath();
200 | _Button3_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 1, 0, Math.PI * 2);
201 | _Button3_ctx.stroke();
202 | */
203 |
204 | //create button4
205 | button4Element = document.createElement('canvas');
206 | button4Element.width = _buttonCanvasWidth;
207 | button4Element.height = _buttonCanvasReducedWidth; // for Triangle Button
208 | //button4Element.height = _buttonCanvasWidth; // for Circle Button
209 |
210 | _container.appendChild(button4Element);
211 | button4Element.style.position = "absolute";
212 | button4Element.style.display = "none";
213 | button4Element.style.zIndex = "10";
214 | button4Pressed = false;
215 |
216 | _Button4_ctx = button4Element.getContext('2d');
217 | _Button4_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
218 | _Button4_ctx.lineWidth = 3;
219 |
220 | // Triangle Button
221 | _Button4_ctx.beginPath();
222 | _Button4_ctx.moveTo(_buttonCanvasHalfWidth, _buttonCanvasReducedWidth);
223 | _Button4_ctx.lineTo(_buttonCanvasWidth, 0);
224 | _Button4_ctx.lineTo(0, 0);
225 | _Button4_ctx.closePath();
226 | _Button4_ctx.stroke();
227 |
228 | /*
229 | // Circle Button
230 | _Button4_ctx.beginPath();
231 | _Button4_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 8, 0, Math.PI * 2);
232 | _Button4_ctx.stroke();
233 | _Button4_ctx.lineWidth = 1;
234 | _Button4_ctx.beginPath();
235 | _Button4_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 1, 0, Math.PI * 2);
236 | _Button4_ctx.stroke();
237 | */
238 |
239 | //create button5
240 | button5Element = document.createElement('canvas');
241 | button5Element.width = _smallButtonCanvasWidth;
242 | button5Element.height = _smallButtonCanvasReducedWidth; // for Triangle Button
243 | //button5Element.height = _smallButtonCanvasWidth; // for Circle Button
244 |
245 | _container.appendChild(button5Element);
246 | button5Element.style.position = "absolute";
247 | button5Element.style.display = "none";
248 | button5Element.style.zIndex = "10";
249 | button5Pressed = false;
250 |
251 | _Button5_ctx = button5Element.getContext('2d');
252 | _Button5_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
253 | _Button5_ctx.lineWidth = 3;
254 |
255 | // Triangle Button
256 | _Button5_ctx.beginPath();
257 | _Button5_ctx.moveTo(_smallButtonCanvasHalfWidth, 0);
258 | _Button5_ctx.lineTo(0, _smallButtonCanvasReducedWidth);
259 | _Button5_ctx.lineTo(_smallButtonCanvasWidth, _smallButtonCanvasReducedWidth);
260 | _Button5_ctx.closePath();
261 | _Button5_ctx.stroke();
262 |
263 | /*
264 | // Circle Button
265 | _Button5_ctx.beginPath();
266 | _Button5_ctx.arc(_smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth - 8, 0, Math.PI * 2);
267 | _Button5_ctx.stroke();
268 | _Button5_ctx.lineWidth = 1;
269 | _Button5_ctx.beginPath();
270 | _Button5_ctx.arc(_smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth - 1, 0, Math.PI * 2);
271 | _Button5_ctx.stroke();
272 | */
273 |
274 | //create button6
275 | button6Element = document.createElement('canvas');
276 | button6Element.width = _smallButtonCanvasWidth;
277 | button6Element.height = _smallButtonCanvasReducedWidth; // for Triangle Button
278 | //button6Element.height = _buttonCanvasWidth; // for Circle Button
279 |
280 | _container.appendChild(button6Element);
281 | button6Element.style.position = "absolute";
282 | button6Element.style.display = "none";
283 | button6Element.style.zIndex = "10";
284 | button6Pressed = false;
285 |
286 | _Button6_ctx = button6Element.getContext('2d');
287 | _Button6_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
288 | _Button6_ctx.lineWidth = 3;
289 |
290 | // Triangle Button
291 | _Button6_ctx.beginPath();
292 | _Button6_ctx.moveTo(_smallButtonCanvasHalfWidth, _smallButtonCanvasReducedWidth);
293 | _Button6_ctx.lineTo(_smallButtonCanvasWidth, 0);
294 | _Button6_ctx.lineTo(0, 0);
295 | _Button6_ctx.closePath();
296 | _Button6_ctx.stroke();
297 |
298 | /*
299 | // Circle Button
300 | _Button6_ctx.beginPath();
301 | _Button6_ctx.arc(_smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth - 8, 0, Math.PI * 2);
302 | _Button6_ctx.stroke();
303 | _Button6_ctx.lineWidth = 1;
304 | _Button6_ctx.beginPath();
305 | _Button6_ctx.arc(_smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth - 1, 0, Math.PI * 2);
306 | _Button6_ctx.stroke();
307 | */
308 |
309 |
310 | // the following listeners are for 1-finger touch detection to emulate mouse-click and mouse-drag operations
311 | _container.addEventListener('pointerdown', _onPointerDown, false);
312 | _container.addEventListener('pointermove', _onPointerMove, false);
313 | _container.addEventListener('pointerup', _onPointerUp, false);
314 | // the following listener is for 2-finger pinch gesture detection
315 | _container.addEventListener('touchmove', _onTouchMove, false);
316 |
317 | }; // end let MobileJoystickControls = function (opts)
318 |
319 |
320 | function _move(style, x, y)
321 | {
322 | style.left = x + 'px';
323 | style.top = y + 'px';
324 | }
325 |
326 | function _onButton1Down()
327 | {
328 | button1Pressed = true;
329 |
330 | _Button1_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.5)' : 'rgba(255,255,255,0.5)';
331 | _Button1_ctx.lineWidth = 3;
332 |
333 | // Triangle Button
334 | _Button1_ctx.beginPath();
335 | _Button1_ctx.moveTo(0, _buttonCanvasHalfWidth);
336 | _Button1_ctx.lineTo(_buttonCanvasReducedWidth, _buttonCanvasWidth);
337 | _Button1_ctx.lineTo(_buttonCanvasReducedWidth, 0);
338 | _Button1_ctx.closePath();
339 | _Button1_ctx.stroke();
340 |
341 | /*
342 | // Circle Button
343 | _Button1_ctx.beginPath();
344 | _Button1_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 8, 0, Math.PI * 2);
345 | _Button1_ctx.stroke();
346 | _Button1_ctx.strokeStyle = 'rgba(255,255,255,0.2)';
347 | _Button1_ctx.lineWidth = 1;
348 | _Button1_ctx.beginPath();
349 | _Button1_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 1, 0, Math.PI * 2);
350 | _Button1_ctx.stroke();
351 | */
352 | }
353 |
354 | function _onButton1Up()
355 | {
356 | button1Pressed = false;
357 |
358 | _Button1_ctx.clearRect(0, 0, _buttonCanvasWidth, _buttonCanvasWidth);
359 | _Button1_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
360 | _Button1_ctx.lineWidth = 3;
361 |
362 | // Triangle Button
363 | _Button1_ctx.beginPath();
364 | _Button1_ctx.moveTo(0, _buttonCanvasHalfWidth);
365 | _Button1_ctx.lineTo(_buttonCanvasReducedWidth, _buttonCanvasWidth);
366 | _Button1_ctx.lineTo(_buttonCanvasReducedWidth, 0);
367 | _Button1_ctx.closePath();
368 | _Button1_ctx.stroke();
369 |
370 | /*
371 | // Circle Button
372 | _Button1_ctx.beginPath();
373 | _Button1_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 8, 0, Math.PI * 2);
374 | _Button1_ctx.stroke();
375 | _Button1_ctx.lineWidth = 1;
376 | _Button1_ctx.beginPath();
377 | _Button1_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 1, 0, Math.PI * 2);
378 | _Button1_ctx.stroke();
379 | */
380 | }
381 |
382 | function _onButton2Down()
383 | {
384 | button2Pressed = true;
385 |
386 | _Button2_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.5)' : 'rgba(255,255,255,0.5)';
387 | _Button2_ctx.lineWidth = 3;
388 |
389 | // Triangle Button
390 | _Button2_ctx.beginPath();
391 | _Button2_ctx.moveTo(_buttonCanvasReducedWidth, _buttonCanvasHalfWidth);
392 | _Button2_ctx.lineTo(0, 0);
393 | _Button2_ctx.lineTo(0, _buttonCanvasWidth);
394 | _Button2_ctx.closePath();
395 | _Button2_ctx.stroke();
396 |
397 | /*
398 | // Circle Button
399 | _Button2_ctx.beginPath();
400 | _Button2_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 8, 0, Math.PI * 2);
401 | _Button2_ctx.stroke();
402 | _Button2_ctx.strokeStyle = 'rgba(255,255,255,0.2)';
403 | _Button2_ctx.lineWidth = 1;
404 | _Button2_ctx.beginPath();
405 | _Button2_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 1, 0, Math.PI * 2);
406 | _Button2_ctx.stroke();
407 | */
408 | }
409 |
410 | function _onButton2Up()
411 | {
412 | button2Pressed = false;
413 |
414 | _Button2_ctx.clearRect(0, 0, _buttonCanvasWidth, _buttonCanvasWidth);
415 | _Button2_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
416 | _Button2_ctx.lineWidth = 3;
417 |
418 | // Triangle Button
419 | _Button2_ctx.beginPath();
420 | _Button2_ctx.moveTo(_buttonCanvasReducedWidth, _buttonCanvasHalfWidth);
421 | _Button2_ctx.lineTo(0, 0);
422 | _Button2_ctx.lineTo(0, _buttonCanvasWidth);
423 | _Button2_ctx.closePath();
424 | _Button2_ctx.stroke();
425 |
426 | /*
427 | // Circle Button
428 | _Button2_ctx.beginPath();
429 | _Button2_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 8, 0, Math.PI * 2);
430 | _Button2_ctx.stroke();
431 | _Button2_ctx.lineWidth = 1;
432 | _Button2_ctx.beginPath();
433 | _Button2_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 1, 0, Math.PI * 2);
434 | _Button2_ctx.stroke();
435 | */
436 | }
437 |
438 | function _onButton3Down()
439 | {
440 | button3Pressed = true;
441 |
442 | _Button3_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.5)' : 'rgba(255,255,255,0.5)';
443 | _Button3_ctx.lineWidth = 3;
444 |
445 | // Triangle Button
446 | _Button3_ctx.beginPath();
447 | _Button3_ctx.moveTo(_buttonCanvasHalfWidth, 0);
448 | _Button3_ctx.lineTo(0, _buttonCanvasReducedWidth);
449 | _Button3_ctx.lineTo(_buttonCanvasWidth, _buttonCanvasReducedWidth);
450 | _Button3_ctx.closePath();
451 | _Button3_ctx.stroke();
452 |
453 | /*
454 | // Circle Button
455 | _Button3_ctx.beginPath();
456 | _Button3_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 8, 0, Math.PI * 2);
457 | _Button3_ctx.stroke();
458 | _Button3_ctx.strokeStyle = 'rgba(255,255,255,0.2)';
459 | _Button3_ctx.lineWidth = 1;
460 | _Button3_ctx.beginPath();
461 | _Button3_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 1, 0, Math.PI * 2);
462 | _Button3_ctx.stroke();
463 | */
464 | }
465 |
466 | function _onButton3Up()
467 | {
468 | button3Pressed = false;
469 |
470 | _Button3_ctx.clearRect(0, 0, _buttonCanvasWidth, _buttonCanvasWidth);
471 | _Button3_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
472 | _Button3_ctx.lineWidth = 3;
473 |
474 | // Triangle Button
475 | _Button3_ctx.beginPath();
476 | _Button3_ctx.moveTo(_buttonCanvasHalfWidth, 0);
477 | _Button3_ctx.lineTo(0, _buttonCanvasReducedWidth);
478 | _Button3_ctx.lineTo(_buttonCanvasWidth, _buttonCanvasReducedWidth);
479 | _Button3_ctx.closePath();
480 | _Button3_ctx.stroke();
481 |
482 | /*
483 | // Circle Button
484 | _Button3_ctx.beginPath();
485 | _Button3_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 8, 0, Math.PI * 2);
486 | _Button3_ctx.stroke();
487 | _Button3_ctx.lineWidth = 1;
488 | _Button3_ctx.beginPath();
489 | _Button3_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 1, 0, Math.PI * 2);
490 | _Button3_ctx.stroke();
491 | */
492 | }
493 |
494 | function _onButton4Down()
495 | {
496 | button4Pressed = true;
497 |
498 | _Button4_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.5)' : 'rgba(255,255,255,0.5)';
499 | _Button4_ctx.lineWidth = 3;
500 |
501 | // Triangle Button
502 | _Button4_ctx.beginPath();
503 | _Button4_ctx.moveTo(_buttonCanvasHalfWidth, _buttonCanvasReducedWidth);
504 | _Button4_ctx.lineTo(_buttonCanvasWidth, 0);
505 | _Button4_ctx.lineTo(0, 0);
506 | _Button4_ctx.closePath();
507 | _Button4_ctx.stroke();
508 |
509 | /*
510 | // Circle Button
511 | _Button4_ctx.beginPath();
512 | _Button4_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 8, 0, Math.PI * 2);
513 | _Button4_ctx.stroke();
514 | _Button4_ctx.strokeStyle = 'rgba(255,255,255,0.2)';
515 | _Button4_ctx.lineWidth = 1;
516 | _Button4_ctx.beginPath();
517 | _Button4_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 1, 0, Math.PI * 2);
518 | _Button4_ctx.stroke();
519 | */
520 | }
521 |
522 | function _onButton4Up()
523 | {
524 | button4Pressed = false;
525 |
526 | _Button4_ctx.clearRect(0, 0, _buttonCanvasWidth, _buttonCanvasWidth);
527 | _Button4_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
528 | _Button4_ctx.lineWidth = 3;
529 |
530 | // Triangle Button
531 | _Button4_ctx.beginPath();
532 | _Button4_ctx.moveTo(_buttonCanvasHalfWidth, _buttonCanvasReducedWidth);
533 | _Button4_ctx.lineTo(_buttonCanvasWidth, 0);
534 | _Button4_ctx.lineTo(0, 0);
535 | _Button4_ctx.closePath();
536 | _Button4_ctx.stroke();
537 |
538 | /*
539 | // Circle Button
540 | _Button4_ctx.beginPath();
541 | _Button4_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 8, 0, Math.PI * 2);
542 | _Button4_ctx.stroke();
543 | _Button4_ctx.lineWidth = 1;
544 | _Button4_ctx.beginPath();
545 | _Button4_ctx.arc(_buttonCanvasHalfWidth, _buttonCanvasHalfWidth, _buttonCanvasHalfWidth - 1, 0, Math.PI * 2);
546 | _Button4_ctx.stroke();
547 | */
548 | }
549 |
550 | function _onButton5Down()
551 | {
552 | button5Pressed = true;
553 |
554 | _Button5_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.5)' : 'rgba(255,255,255,0.5)';
555 | _Button5_ctx.lineWidth = 3;
556 |
557 | // Triangle Button
558 | _Button5_ctx.beginPath();
559 | _Button5_ctx.moveTo(_smallButtonCanvasHalfWidth, 0);
560 | _Button5_ctx.lineTo(0, _smallButtonCanvasReducedWidth);
561 | _Button5_ctx.lineTo(_smallButtonCanvasWidth, _smallButtonCanvasReducedWidth);
562 | _Button5_ctx.closePath();
563 | _Button5_ctx.stroke();
564 |
565 | /*
566 | // Circle Button
567 | _Button5_ctx.beginPath();
568 | _Button5_ctx.arc(_smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth - 8, 0, Math.PI * 2);
569 | _Button5_ctx.stroke();
570 | _Button5_ctx.lineWidth = 1;
571 | _Button5_ctx.beginPath();
572 | _Button5_ctx.arc(_smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth - 1, 0, Math.PI * 2);
573 | _Button5_ctx.stroke();
574 | */
575 | }
576 |
577 | function _onButton5Up()
578 | {
579 | button5Pressed = false;
580 |
581 | _Button5_ctx.clearRect(0, 0, _smallButtonCanvasWidth, _smallButtonCanvasWidth);
582 | _Button5_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
583 | _Button5_ctx.lineWidth = 3;
584 |
585 | // Triangle Button
586 | _Button5_ctx.beginPath();
587 | _Button5_ctx.moveTo(_smallButtonCanvasHalfWidth, 0);
588 | _Button5_ctx.lineTo(0, _smallButtonCanvasReducedWidth);
589 | _Button5_ctx.lineTo(_smallButtonCanvasWidth, _smallButtonCanvasReducedWidth);
590 | _Button5_ctx.closePath();
591 | _Button5_ctx.stroke();
592 |
593 | /*
594 | // Circle Button
595 | _Button5_ctx.beginPath();
596 | _Button5_ctx.arc(_smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth - 8, 0, Math.PI * 2);
597 | _Button5_ctx.stroke();
598 | _Button5_ctx.lineWidth = 1;
599 | _Button5_ctx.beginPath();
600 | _Button5_ctx.arc(_smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth - 1, 0, Math.PI * 2);
601 | _Button5_ctx.stroke();
602 | */
603 | }
604 |
605 | function _onButton6Down()
606 | {
607 | button6Pressed = true;
608 |
609 | _Button6_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.5)' : 'rgba(255,255,255,0.5)';
610 | _Button6_ctx.lineWidth = 3;
611 |
612 | // Triangle Button
613 | _Button6_ctx.beginPath();
614 | _Button6_ctx.moveTo(_smallButtonCanvasHalfWidth, _smallButtonCanvasReducedWidth);
615 | _Button6_ctx.lineTo(_smallButtonCanvasWidth, 0);
616 | _Button6_ctx.lineTo(0, 0);
617 | _Button6_ctx.closePath();
618 | _Button6_ctx.stroke();
619 |
620 | /*
621 | // Circle Button
622 | _Button6_ctx.beginPath();
623 | _Button6_ctx.arc(_smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth - 8, 0, Math.PI * 2);
624 | _Button6_ctx.stroke();
625 | _Button6_ctx.lineWidth = 1;
626 | _Button6_ctx.beginPath();
627 | _Button6_ctx.arc(_smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth - 1, 0, Math.PI * 2);
628 | _Button6_ctx.stroke();
629 | */
630 | }
631 |
632 | function _onButton6Up()
633 | {
634 | button6Pressed = false;
635 |
636 | _Button6_ctx.clearRect(0, 0, _smallButtonCanvasWidth, _smallButtonCanvasWidth);
637 | _Button6_ctx.strokeStyle = _useDarkButtons ? 'rgba(55,55,55,0.2)' : 'rgba(255,255,255,0.2)';
638 | _Button6_ctx.lineWidth = 3;
639 |
640 | // Triangle Button
641 | _Button6_ctx.beginPath();
642 | _Button6_ctx.moveTo(_smallButtonCanvasHalfWidth, _smallButtonCanvasReducedWidth);
643 | _Button6_ctx.lineTo(_smallButtonCanvasWidth, 0);
644 | _Button6_ctx.lineTo(0, 0);
645 | _Button6_ctx.closePath();
646 | _Button6_ctx.stroke();
647 |
648 | /*
649 | // Circle Button
650 | _Button6_ctx.beginPath();
651 | _Button6_ctx.arc(_smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth - 8, 0, Math.PI * 2);
652 | _Button6_ctx.stroke();
653 | _Button6_ctx.lineWidth = 1;
654 | _Button6_ctx.beginPath();
655 | _Button6_ctx.arc(_smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth, _smallButtonCanvasHalfWidth - 1, 0, Math.PI * 2);
656 | _Button6_ctx.stroke();
657 | */
658 | }
659 |
660 |
661 | function _onPointerDown(event)
662 | {
663 |
664 | _eventTarget = event.target;
665 |
666 | if (_showButtons)
667 | {
668 | if (_eventTarget == button1Element)
669 | return _onButton1Down();
670 | if (_eventTarget == button2Element)
671 | return _onButton2Down();
672 | if (_eventTarget == button3Element)
673 | return _onButton3Down();
674 | if (_eventTarget == button4Element)
675 | return _onButton4Down();
676 | if (_eventTarget == button5Element)
677 | return _onButton5Down();
678 | if (_eventTarget == button6Element)
679 | return _onButton6Down();
680 | }
681 |
682 | if (_eventTarget != renderer.domElement) // target was the GUI menu
683 | return;
684 |
685 | // else target is the joystick area
686 | _stickX = event.clientX;
687 | _stickY = event.clientY;
688 |
689 | _baseX = _stickX;
690 | _baseY = _stickY;
691 |
692 | joystickDeltaX = joystickDeltaY = 0;
693 |
694 | } // end function _onPointerDown(event)
695 |
696 |
697 | function _onPointerMove(event)
698 | {
699 |
700 | _eventTarget = event.target;
701 |
702 | if (_eventTarget != renderer.domElement) // target was the GUI menu or Buttons
703 | return;
704 |
705 | _stickX = event.clientX;
706 | _stickY = event.clientY;
707 |
708 | joystickDeltaX = _stickX - _baseX;
709 | joystickDeltaY = _stickY - _baseY;
710 |
711 | if (_limitStickTravel)
712 | {
713 | _stickDistance = Math.sqrt((joystickDeltaX * joystickDeltaX) + (joystickDeltaY * joystickDeltaY));
714 |
715 | if (_stickDistance > _stickRadius)
716 | {
717 | _stickNormalizedX = joystickDeltaX / _stickDistance;
718 | _stickNormalizedY = joystickDeltaY / _stickDistance;
719 |
720 | _stickX = _stickNormalizedX * _stickRadius + _baseX;
721 | _stickY = _stickNormalizedY * _stickRadius + _baseY;
722 |
723 | joystickDeltaX = _stickX - _baseX;
724 | joystickDeltaY = _stickY - _baseY;
725 | }
726 | }
727 |
728 | if (_pinchWasActive)
729 | {
730 | _pinchWasActive = false;
731 |
732 | _baseX = event.clientX;
733 | _baseY = event.clientY;
734 |
735 | _stickX = _baseX;
736 | _stickY = _baseY;
737 |
738 | joystickDeltaX = joystickDeltaY = 0;
739 | }
740 |
741 | if (_showJoystick)
742 | {
743 | stickElement.style.display = "";
744 | _move(baseElement.style, (_baseX - baseElement.width / 2), (_baseY - baseElement.height / 2));
745 |
746 | baseElement.style.display = "";
747 | _move(stickElement.style, (_stickX - stickElement.width / 2), (_stickY - stickElement.height / 2));
748 | }
749 |
750 | } // end function _onPointerMove(event)
751 |
752 |
753 | function _onPointerUp(event)
754 | {
755 |
756 | _eventTarget = event.target;
757 |
758 | if (_showButtons)
759 | {
760 | if (_eventTarget == button1Element)
761 | return _onButton1Up();
762 | if (_eventTarget == button2Element)
763 | return _onButton2Up();
764 | if (_eventTarget == button3Element)
765 | return _onButton3Up();
766 | if (_eventTarget == button4Element)
767 | return _onButton4Up();
768 | if (_eventTarget == button5Element)
769 | return _onButton5Up();
770 | if (_eventTarget == button6Element)
771 | return _onButton6Up();
772 | }
773 |
774 | if (_eventTarget != renderer.domElement) // target was the GUI menu
775 | return;
776 |
777 | joystickDeltaX = joystickDeltaY = 0;
778 |
779 | baseElement.style.display = "none";
780 | stickElement.style.display = "none";
781 |
782 | } // end function _onPointerUp(event)
783 |
784 |
785 | function _onTouchMove(event)
786 | {
787 | // we only want to deal with a 2-finger pinch
788 | if (event.touches.length != 2)
789 | return;
790 |
791 | _touches = event.touches;
792 |
793 | if ( (!_showButtons) || // if no show buttons, there's no need to do the following checks:
794 | (_touches[0].target != button1Element && _touches[0].target != button2Element &&
795 | _touches[0].target != button3Element && _touches[0].target != button4Element &&
796 | _touches[0].target != button5Element && _touches[0].target != button6Element &&
797 | _touches[1].target != button1Element && _touches[1].target != button2Element &&
798 | _touches[1].target != button3Element && _touches[1].target != button4Element &&
799 | _touches[1].target != button5Element && _touches[1].target != button6Element) )
800 | {
801 | pinchWidthX = Math.abs(_touches[1].pageX - _touches[0].pageX);
802 | pinchWidthY = Math.abs(_touches[1].pageY - _touches[0].pageY);
803 |
804 | _stickX = _baseX;
805 | _stickY = _baseY;
806 |
807 | joystickDeltaX = joystickDeltaY = 0;
808 |
809 | _pinchWasActive = true;
810 |
811 | baseElement.style.display = "none";
812 | stickElement.style.display = "none";
813 | }
814 |
815 | } // end function _onTouchMove(event)
--------------------------------------------------------------------------------
/js/Path_Traced_Pong.js:
--------------------------------------------------------------------------------
1 | // scene/demo-specific variables go here
2 | let ballRad = 5; // 5
3 | let paddleRadX = 25; // 25
4 | let paddleRadY = 15; // 15
5 | let halfPaddleRadX = paddleRadX * 0.5;
6 | let halfPaddleRadY = paddleRadY * 0.5;
7 | let oldRotY = 0;
8 | let newRotY = 0;
9 | let oldRotX = 0;
10 | let newRotX = 0;
11 | let ballPos = new THREE.Vector3();
12 | let ballDir = new THREE.Vector3();
13 | let ballVel = new THREE.Vector3();
14 | let computerPos2D = new THREE.Vector2();
15 | let ballPos2D = new THREE.Vector2();
16 | let targetDir2D = new THREE.Vector2();
17 | let computerSpeed = 0;
18 | let maxComputerSpeed = 0;
19 | let initialBallSpeed = 0;
20 | let ballSpeed = 0;
21 | let halfRoomDimensions = new THREE.Vector3(100, 50, 200); // (100, 50, 200)
22 | let paddle_Z = halfRoomDimensions.z - 100;
23 | let playerPos = new THREE.Vector3(0, 0, paddle_Z);
24 | let computerPos = new THREE.Vector3(0, 0, -paddle_Z);
25 | let newGameFlag = true;
26 | let newVolleyFlag = false;
27 | let playerMissed = false;
28 | let computerMissed = false;
29 | let gravityOn = true;
30 | let playerScore = 0;
31 | let computerScore = 0;
32 | let numberOfPointsToWinGame = 5;
33 | let playerWins = false;
34 | let computerWins = false;
35 | let missTimer = 0;
36 | let cutSceneTimer = 0;
37 | let cutSceneLengthInSeconds = 5;
38 | let difficulty = 'Novice';
39 | let saved_Z_Position = paddle_Z + 80;
40 | let cutSceneIsPlaying = false;
41 |
42 | let gravity_ToggleController, gravity_ToggleObject;
43 | let needChangeGravityToggle = false;
44 | let difficulty_SettingController, difficulty_SettingObject;
45 | let needChangeDifficultySetting = false;
46 |
47 | // WebAudio variables
48 | let audioLoader;
49 | let listener;
50 | let highPingSound1;
51 | let highPingSound2;
52 | let lowPingSound1;
53 | let lowPingSound2;
54 | let ballMissedSound;
55 | let winnerSound;
56 | let losingSound;
57 | let ballObj = new THREE.Object3D();
58 | let playerObj = new THREE.Object3D();
59 | let computerObj = new THREE.Object3D();
60 |
61 |
62 | let playerScoreElement = document.getElementById('playerScore');
63 | playerScoreElement.style.cursor = "default";
64 | playerScoreElement.style.userSelect = "none";
65 | playerScoreElement.style.MozUserSelect = "none";
66 | let computerScoreElement = document.getElementById('computerScore');
67 | computerScoreElement.style.cursor = "default";
68 | computerScoreElement.style.userSelect = "none";
69 | computerScoreElement.style.MozUserSelect = "none";
70 | let infoBannerElement = document.getElementById('infoBanner');
71 | infoBannerElement.style.cursor = "default";
72 | infoBannerElement.style.userSelect = "none";
73 | infoBannerElement.style.MozUserSelect = "none";
74 |
75 | // called automatically from within initTHREEjs() function (located in InitCommon.js file)
76 | function initSceneData()
77 | {
78 | demoFragmentShaderFileName = 'Path_Traced_Pong_Fragment.glsl';
79 |
80 | // scene/demo-specific three.js objects setup goes here
81 | allowOrthographicCamera = false;
82 |
83 | sceneIsDynamic = true;
84 |
85 | pixelEdgeSharpness = 0.5;
86 |
87 | cameraFlightSpeed = 100;
88 |
89 | // pixelRatio is resolution - range: 0.5(half resolution) to 1.0(full resolution)
90 | pixelRatio = mouseControl ? 1.0 : 0.75;
91 |
92 | EPS_intersect = 0.001;
93 |
94 | useGenericInput = false;
95 |
96 | // the usual directional buttons for mobile are not needed on this game
97 | mobileShowButtons = false;
98 |
99 | // set camera's field of view
100 | worldCamera.fov = 50; // 50
101 |
102 | audioLoader = new THREE.AudioLoader();
103 | listener = new THREE.AudioListener();
104 | worldCamera.add(listener);
105 |
106 | audioLoader.load('sounds/highPing.mp3', function (buffer)
107 | {
108 | highPingSound1 = new THREE.PositionalAudio(listener);
109 | highPingSound1.setBuffer(buffer);
110 | highPingSound1.setVolume(2);
111 | ballObj.add(highPingSound1);
112 | highPingSound2 = new THREE.PositionalAudio(listener);
113 | highPingSound2.setBuffer(buffer);
114 | highPingSound2.setVolume(2);
115 | ballObj.add(highPingSound2);
116 | });
117 |
118 | audioLoader.load('sounds/lowPing.mp3', function (buffer)
119 | {
120 | lowPingSound1 = new THREE.PositionalAudio(listener);
121 | lowPingSound1.setBuffer(buffer);
122 | lowPingSound1.setVolume(3);
123 | playerObj.add(lowPingSound1);
124 | lowPingSound2 = new THREE.PositionalAudio(listener);
125 | lowPingSound2.setBuffer(buffer);
126 | lowPingSound2.setVolume(3);
127 | computerObj.add(lowPingSound2);
128 | });
129 |
130 | audioLoader.load('sounds/ballMiss.mp3', function (buffer)
131 | {
132 | ballMissedSound = new THREE.PositionalAudio(listener);
133 | ballMissedSound.setBuffer(buffer);
134 | ballMissedSound.setVolume(0.2);
135 | worldCamera.add(ballMissedSound);
136 | });
137 |
138 | audioLoader.load('sounds/winner.mp3', function (buffer)
139 | {
140 | winnerSound = new THREE.PositionalAudio(listener);
141 | winnerSound.setBuffer(buffer);
142 | winnerSound.setVolume(0.05);
143 | worldCamera.add(winnerSound);
144 | });
145 |
146 | audioLoader.load('sounds/synthHit.mp3', function (buffer)
147 | {
148 | losingSound = new THREE.PositionalAudio(listener);
149 | losingSound.setBuffer(buffer);
150 | losingSound.setVolume(0.04);
151 | worldCamera.add(losingSound);
152 | });
153 |
154 |
155 | gravity_ToggleObject = {
156 | Gravity: false
157 | };
158 |
159 | difficulty_SettingObject = {
160 | Difficulty: 'Novice'
161 | };
162 |
163 | function handleGravityToggleChange()
164 | {
165 | needChangeGravityToggle = true;
166 | }
167 |
168 | function handleDifficultySettingChange()
169 | {
170 | needChangeDifficultySetting = true;
171 | }
172 |
173 | gravity_ToggleController = gui.add(gravity_ToggleObject, 'Gravity', false).onChange(handleGravityToggleChange);
174 | difficulty_SettingController = gui.add(difficulty_SettingObject, 'Difficulty', ['Novice', 'Advanced', 'PONG LORD']).onChange(handleDifficultySettingChange);
175 |
176 | // jumpstart the game settings and GUI menu
177 | handleGravityToggleChange();
178 | handleDifficultySettingChange();
179 |
180 | // scene/demo-specific uniforms go here
181 | pathTracingUniforms.uHalfRoomDimensions = { value: halfRoomDimensions };
182 | pathTracingUniforms.uBallPos = { value: ballPos };
183 | pathTracingUniforms.uPlayerPos = { value: playerPos };
184 | pathTracingUniforms.uComputerPos = { value: computerPos };
185 | pathTracingUniforms.uPaddleRadX = { value: paddleRadX };
186 | pathTracingUniforms.uPaddleRadY = { value: paddleRadY };
187 | pathTracingUniforms.uCutSceneIsPlaying = { value: cutSceneIsPlaying };
188 |
189 | } // end function initSceneData()
190 |
191 |
192 |
193 |
194 | function startNewGame()
195 | {
196 | missTimer = 0;
197 | cutSceneTimer = 0;
198 | playerScore = 0;
199 | computerScore = 0;
200 | playerScoreElement.innerHTML = "Player: " + playerScore;
201 | computerScoreElement.innerHTML = "Computer: " + computerScore;
202 | cameraInfoElement.innerHTML = "";
203 | infoBannerElement.innerHTML = "";
204 |
205 | // position and orient camera
206 | cameraControlsObject.position.set(0, 0, saved_Z_Position);
207 |
208 | cameraControlsYawObject.rotation.set(0,0,0);
209 | cameraControlsPitchObject.rotation.set(0,0,0);
210 |
211 | pathTracingUniforms.uCutSceneIsPlaying.value = false;
212 |
213 | newVolleyFlag = true;
214 | newGameFlag = false;
215 | playerWins = false;
216 | computerWins = false;
217 | } // end function startNewGame()
218 |
219 |
220 | function startNewVolley()
221 | {
222 | missTimer = 0;
223 | ballSpeed = initialBallSpeed;
224 | ballPos.set(0, 0, 0);
225 | ballDir.set(Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, -1).normalize();
226 |
227 | if (playerMissed)
228 | {
229 | computerScore += 1;
230 | computerScoreElement.innerHTML = "Computer: " + computerScore;
231 | }
232 | if (computerMissed)
233 | {
234 | playerScore += 1;
235 | playerScoreElement.innerHTML = "Player: " + playerScore;
236 | }
237 |
238 | if (playerScore >= numberOfPointsToWinGame)
239 | {
240 | playerWins = true;
241 | winnerSound.play();
242 | infoBannerElement.style.color = "rgb(26,179,255)";
243 | infoBannerElement.innerHTML = "Player WINS!"
244 | pathTracingUniforms.uCutSceneIsPlaying.value = true;
245 | }
246 | if (computerScore >= numberOfPointsToWinGame)
247 | {
248 | computerWins = true;
249 | losingSound.play();
250 | infoBannerElement.style.color = "rgb(179,26,255)";
251 | infoBannerElement.innerHTML = "Computer WINS!"
252 | pathTracingUniforms.uCutSceneIsPlaying.value = true;
253 | }
254 |
255 | playerMissed = false;
256 | computerMissed = false;
257 | newVolleyFlag = false;
258 | } // end function startNewVolley()
259 |
260 |
261 | function updateComputerAI()
262 | {
263 | computerPos2D.set(computerPos.x, computerPos.y);
264 | ballPos2D.set(ballPos.x, ballPos.y);
265 | targetDir2D.subVectors(ballPos2D, computerPos2D);
266 | computerSpeed = Math.min(targetDir2D.length(), maxComputerSpeed);
267 |
268 | computerPos.x += targetDir2D.x * computerSpeed * frameTime;
269 | computerPos.y += targetDir2D.y * computerSpeed * frameTime;
270 | computerPos.z = -paddle_Z; // clamp computer's Z position
271 |
272 | // clamp computer's position against room walls
273 | if (computerPos.x + paddleRadX > halfRoomDimensions.x)
274 | computerPos.x = halfRoomDimensions.x - paddleRadX;
275 | if (computerPos.x - paddleRadX < -halfRoomDimensions.x)
276 | computerPos.x = -halfRoomDimensions.x + paddleRadX;
277 | if (computerPos.y + paddleRadY > halfRoomDimensions.y)
278 | computerPos.y = halfRoomDimensions.y - paddleRadY;
279 | if (computerPos.y - paddleRadY < -halfRoomDimensions.y)
280 | computerPos.y = -halfRoomDimensions.y + paddleRadY;
281 | } // end function updateComputerAI()
282 |
283 |
284 | function updateGameState()
285 | {
286 | // first check if there is a winner - if so, do a simple winner cutscene animation
287 | if (playerWins)
288 | {
289 | cutSceneTimer += 1.0 * frameTime;
290 | if (cutSceneTimer >= cutSceneLengthInSeconds)
291 | {
292 | newGameFlag = true;
293 | }
294 |
295 | cameraIsMoving = true;
296 |
297 | cameraControlsObject.position.x = playerPos.x + (Math.cos(cutSceneTimer) * (cutSceneLengthInSeconds - cutSceneTimer + 1) * 50); // 80
298 | cameraControlsObject.position.y = playerPos.y + 20;
299 | cameraControlsObject.position.z = playerPos.z - (Math.sin(cutSceneTimer) * (cutSceneLengthInSeconds - cutSceneTimer + 1) * 50); // 80
300 |
301 | worldCamera.lookAt(playerPos);
302 | }
303 |
304 | if (computerWins)
305 | {
306 | cutSceneTimer += 1.0 * frameTime;
307 | if (cutSceneTimer >= cutSceneLengthInSeconds)
308 | {
309 | newGameFlag = true;
310 | }
311 |
312 | cameraIsMoving = true;
313 |
314 | cameraControlsObject.position.x = computerPos.x + (Math.cos(cutSceneTimer) * (cutSceneLengthInSeconds - cutSceneTimer + 1) * 50); // 80
315 | cameraControlsObject.position.y = computerPos.y + 20;
316 | cameraControlsObject.position.z = computerPos.z - (Math.sin(cutSceneTimer) * (cutSceneLengthInSeconds - cutSceneTimer + 1) * 50); // 80
317 |
318 | worldCamera.lookAt(computerPos);
319 | }
320 |
321 | if (newGameFlag)
322 | startNewGame();
323 | if (newVolleyFlag)
324 | startNewVolley();
325 |
326 | // move ball
327 | if (cutSceneTimer == 0)
328 | {
329 | if (gravityOn)
330 | ballDir.y -= 0.5 * frameTime; // simulate constant acceleration due to gravity
331 | ballVel.copy(ballDir);
332 | ballVel.multiplyScalar(ballSpeed * frameTime);
333 | ballPos.add(ballVel);
334 | }
335 |
336 |
337 | // check player-ball collision
338 | if (!playerMissed && ballPos.z + ballRad > playerPos.z)
339 | {
340 | if ( ballPos.x - ballRad < playerPos.x + paddleRadX && ballPos.x + ballRad > playerPos.x - paddleRadX &&
341 | ballPos.y - ballRad < playerPos.y + paddleRadY && ballPos.y + ballRad > playerPos.y - paddleRadY )
342 | {
343 | if (!lowPingSound1.isPlaying)
344 | lowPingSound1.play();
345 | ballPos.z = playerPos.z - ballRad;
346 | ballDir.z *= -1;
347 |
348 | halfPaddleRadX = playerPos.x + (paddleRadX * 0.5);
349 | if (ballPos.x > playerPos.x)
350 | {
351 | ballDir.x += (ballPos.x - playerPos.x) / paddleRadX * 0.5;
352 | ballDir.x = Math.min(ballDir.x, 0.6);
353 | }
354 | halfPaddleRadX = playerPos.x - (paddleRadX * 0.5);
355 | if (ballPos.x < playerPos.x)
356 | {
357 | ballDir.x += (ballPos.x - playerPos.x) / paddleRadX * 0.5;
358 | ballDir.x = Math.max(ballDir.x, -0.6);
359 | }
360 | halfPaddleRadY = playerPos.y + (paddleRadY * 0.5);
361 | if (ballPos.y > playerPos.y)
362 | {
363 | ballDir.y += (ballPos.y - playerPos.y) / paddleRadY * 0.5;
364 | ballDir.y = Math.min(ballDir.y, 0.6);
365 | }
366 | halfPaddleRadY = playerPos.y - (paddleRadY * 0.5);
367 | if (ballPos.y < playerPos.y)
368 | {
369 | ballDir.y += (ballPos.y - playerPos.y) / paddleRadY * 0.5;
370 | ballDir.y = Math.max(ballDir.y, -0.6);
371 | }
372 |
373 | ballDir.normalize();
374 | ballSpeed *= 1.015; // slightly increase ball speed
375 | }
376 | else
377 | {
378 | ballMissedSound.play();
379 | playerMissed = true;
380 | }
381 |
382 | }
383 |
384 | // check A.I.-ball collision
385 | if (!computerMissed && ballPos.z - ballRad < computerPos.z)
386 | {
387 | if (ballPos.x - ballRad < computerPos.x + paddleRadX && ballPos.x + ballRad > computerPos.x - paddleRadX &&
388 | ballPos.y - ballRad < computerPos.y + paddleRadY && ballPos.y + ballRad > computerPos.y - paddleRadY)
389 | {
390 | if (!lowPingSound2.isPlaying)
391 | lowPingSound2.play();
392 | ballPos.z = computerPos.z + ballRad;
393 | ballDir.z *= -1;
394 | ballSpeed *= 1.015; // slightly increase ball speed
395 | }
396 | else
397 | {
398 | ballMissedSound.play();
399 | computerMissed = true;
400 | }
401 |
402 | }
403 |
404 | // check ball-room walls collision
405 | if (ballPos.x + ballRad > halfRoomDimensions.x)
406 | {
407 | ballPos.x = halfRoomDimensions.x - ballRad;
408 | ballDir.x *= -1;
409 | if (!playerMissed && !computerMissed)
410 | {
411 | if (!highPingSound1.isPlaying)
412 | highPingSound1.play();
413 | else highPingSound2.play();
414 | }
415 | }
416 | if (ballPos.x - ballRad < -halfRoomDimensions.x)
417 | {
418 | ballPos.x = -halfRoomDimensions.x + ballRad;
419 | ballDir.x *= -1;
420 | if (!playerMissed && !computerMissed)
421 | {
422 | if (!highPingSound1.isPlaying)
423 | highPingSound1.play();
424 | else highPingSound2.play();
425 | }
426 | }
427 | if (ballPos.y + ballRad > halfRoomDimensions.y)
428 | {
429 | ballPos.y = halfRoomDimensions.y - ballRad;
430 | ballDir.y *= -1;
431 | if (!playerMissed && !computerMissed)
432 | {
433 | if (!highPingSound1.isPlaying)
434 | highPingSound1.play();
435 | else highPingSound2.play();
436 | }
437 | }
438 | if (ballPos.y - ballRad < -halfRoomDimensions.y)
439 | {
440 | ballPos.y = -halfRoomDimensions.y + ballRad;
441 | ballDir.y *= -1;
442 | if (!playerMissed && !computerMissed)
443 | {
444 | if (!highPingSound1.isPlaying)
445 | highPingSound1.play();
446 | else highPingSound2.play();
447 | }
448 | }
449 | if (ballPos.z > halfRoomDimensions.z)
450 | {
451 | missTimer += 1.1 * frameTime;
452 | if (missTimer >= 3)
453 | newVolleyFlag = true;
454 | }
455 | if (ballPos.z < -halfRoomDimensions.z)
456 | {
457 | missTimer += 1.1 * frameTime;
458 | if (missTimer >= 3)
459 | newVolleyFlag = true;
460 | }
461 |
462 | } // end function updateGameState()
463 |
464 |
465 | function updateInputAndCamera()
466 | {
467 | // disable horizontal pinch FOV changing on mobile
468 | increaseFOV = decreaseFOV = false;
469 | // disable vertical pinch aperture size changing on mobile
470 | increaseAperture = decreaseAperture = false;
471 |
472 | // slightly add to the player movement speed on mobile
473 | if (!mouseControl)
474 | {
475 | if (newDeltaX)
476 | cameraControlsYawObject.rotation.y += (mobileControlsMoveX) * 0.001;
477 | if (newDeltaY)
478 | cameraControlsPitchObject.rotation.x += (mobileControlsMoveY) * 0.001;
479 | }
480 |
481 | newRotY = -100 * cameraControlsYawObject.rotation.y;
482 | newRotX = 100 * cameraControlsPitchObject.rotation.x;
483 |
484 | if (cutSceneTimer == 0)
485 | playerPos.set(newRotY, newRotX, paddle_Z);
486 |
487 | if (playerPos.x + paddleRadX > halfRoomDimensions.x)
488 | {
489 | playerPos.x = halfRoomDimensions.x - paddleRadX;
490 | cameraControlsYawObject.rotation.y = oldRotY;
491 | }
492 | if (playerPos.x - paddleRadX < -halfRoomDimensions.x)
493 | {
494 | playerPos.x = -halfRoomDimensions.x + paddleRadX;
495 | cameraControlsYawObject.rotation.y = oldRotY;
496 | }
497 | if (playerPos.y + paddleRadY > halfRoomDimensions.y)
498 | {
499 | playerPos.y = halfRoomDimensions.y - paddleRadY;
500 | cameraControlsPitchObject.rotation.x = oldRotX;
501 | }
502 | if (playerPos.y - paddleRadY < -halfRoomDimensions.y)
503 | {
504 | playerPos.y = -halfRoomDimensions.y + paddleRadY;
505 | cameraControlsPitchObject.rotation.x = oldRotX;
506 | }
507 | // save oldRot for next frame
508 | oldRotY = cameraControlsYawObject.rotation.y;
509 | oldRotX = cameraControlsPitchObject.rotation.x;
510 |
511 | worldCamera.lookAt(playerPos);
512 |
513 | if (dollyCameraOut)
514 | {
515 | cameraControlsObject.position.z += 2;
516 | if (cameraControlsObject.position.z > paddle_Z + 120)
517 | cameraControlsObject.position.z = paddle_Z + 120;
518 |
519 | // save z position for next game
520 | saved_Z_Position = cameraControlsObject.position.z;
521 |
522 | cameraIsMoving = true;
523 | dollyCameraOut = false;
524 | }
525 | if (dollyCameraIn)
526 | {
527 | cameraControlsObject.position.z -= 2;
528 | if (cameraControlsObject.position.z < paddle_Z + 50)
529 | cameraControlsObject.position.z = paddle_Z + 50;
530 |
531 | // save z position for next game
532 | saved_Z_Position = cameraControlsObject.position.z;
533 |
534 | cameraIsMoving = true;
535 | dollyCameraIn = false;
536 | }
537 | } // end function updateInputAndCamera()
538 |
539 |
540 | // called automatically from within the animate() function (located in InitCommon.js file)
541 | function updateVariablesAndUniforms()
542 | {
543 | if (needChangeGravityToggle)
544 | {
545 | gravityOn = gravity_ToggleController.getValue();
546 |
547 | if (gravityOn)
548 | {
549 | if (difficulty == 'Novice')
550 | maxComputerSpeed = 4;
551 | else if (difficulty == 'Advanced')
552 | maxComputerSpeed = 6;
553 | else if (difficulty == 'PONG LORD')
554 | maxComputerSpeed = 7.5;
555 | }
556 | else if (!gravityOn)
557 | {
558 | if (difficulty == 'Novice')
559 | maxComputerSpeed = 3.5;
560 | else if (difficulty == 'Advanced')
561 | maxComputerSpeed = 5.5;
562 | else if (difficulty == 'PONG LORD')
563 | maxComputerSpeed = 7;
564 | }
565 |
566 | needChangeGravityToggle = false;
567 | }
568 |
569 | if (needChangeDifficultySetting)
570 | {
571 | difficulty = difficulty_SettingController.getValue();
572 |
573 | if (difficulty == 'Novice')
574 | {
575 | maxComputerSpeed = gravityOn ? 4 : 3.5;
576 | initialBallSpeed = 110;
577 | ballSpeed = initialBallSpeed;
578 | }
579 | else if (difficulty == 'Advanced')
580 | {
581 | maxComputerSpeed = gravityOn ? 6 : 5.5;
582 | initialBallSpeed = 150;
583 | ballSpeed = initialBallSpeed;
584 | }
585 | else if (difficulty == 'PONG LORD')
586 | {
587 | maxComputerSpeed = gravityOn ? 7.5 : 7;
588 | initialBallSpeed = 200;
589 | ballSpeed = initialBallSpeed;
590 | }
591 |
592 | needChangeDifficultySetting = false;
593 | }
594 |
595 | // INPUT and CAMERA
596 | if (cutSceneTimer == 0) // only if not during a 'winner' cutscene
597 | updateInputAndCamera();
598 |
599 | // GAME STATE
600 | updateGameState();
601 |
602 | if (cutSceneTimer == 0) // only if not during a 'winner' cutscene
603 | {
604 | // if ball is still in play
605 | // do simple A.I. and update computer's position
606 | if (ballPos.z < halfRoomDimensions.z && ballPos.z > -halfRoomDimensions.z)
607 | updateComputerAI();
608 |
609 | // BALL
610 | pathTracingUniforms.uBallPos.value.copy(ballPos);
611 |
612 | // PLAYER
613 | pathTracingUniforms.uPlayerPos.value.copy(playerPos);
614 |
615 | // A.I.
616 | pathTracingUniforms.uComputerPos.value.copy(computerPos);
617 | }
618 |
619 | // update Positional Sound sources
620 | ballObj.position.copy(ballPos);
621 | playerObj.position.copy(playerPos);
622 | computerObj.position.copy(computerPos);
623 |
624 | // DEBUG INFO
625 | //cameraInfoElement.innerHTML = "maxComputerSpeed: " + maxComputerSpeed.toFixed(1);
626 |
627 | } // end function updateVariablesAndUniforms()
628 |
629 |
630 | // begin Web Audio user-interaction requirement for MacOS & iOS
631 | const startButton = document.getElementById('startButton');
632 | startButton.addEventListener('click', beginInit);
633 |
634 | function beginInit()
635 | {
636 | const overlay = document.getElementById('overlay');
637 | overlay.remove();
638 | init(); // init app and start animating
639 | }
640 | // end Web Audio user-interaction requirement for MacOS & iOS
641 |
--------------------------------------------------------------------------------
/js/lil-gui.module.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * lil-gui
3 | * https://lil-gui.georgealways.com
4 | * @version 0.17.0
5 | * @author George Michael Brower
6 | * @license MIT
7 | */
8 | class t{constructor(i,e,s,n,l="div"){this.parent=i,this.object=e,this.property=s,this._disabled=!1,this._hidden=!1,this.initialValue=this.getValue(),this.domElement=document.createElement("div"),this.domElement.classList.add("controller"),this.domElement.classList.add(n),this.$name=document.createElement("div"),this.$name.classList.add("name"),t.nextNameID=t.nextNameID||0,this.$name.id="lil-gui-name-"+ ++t.nextNameID,this.$widget=document.createElement(l),this.$widget.classList.add("widget"),this.$disable=this.$widget,this.domElement.appendChild(this.$name),this.domElement.appendChild(this.$widget),this.parent.children.push(this),this.parent.controllers.push(this),this.parent.$children.appendChild(this.domElement),this._listenCallback=this._listenCallback.bind(this),this.name(s)}name(t){return this._name=t,this.$name.innerHTML=t,this}onChange(t){return this._onChange=t,this}_callOnChange(){this.parent._callOnChange(this),void 0!==this._onChange&&this._onChange.call(this,this.getValue()),this._changed=!0}onFinishChange(t){return this._onFinishChange=t,this}_callOnFinishChange(){this._changed&&(this.parent._callOnFinishChange(this),void 0!==this._onFinishChange&&this._onFinishChange.call(this,this.getValue())),this._changed=!1}reset(){return this.setValue(this.initialValue),this._callOnFinishChange(),this}enable(t=!0){return this.disable(!t)}disable(t=!0){return t===this._disabled||(this._disabled=t,this.domElement.classList.toggle("disabled",t),this.$disable.toggleAttribute("disabled",t)),this}show(t=!0){return this._hidden=!t,this.domElement.style.display=this._hidden?"none":"",this}hide(){return this.show(!1)}options(t){const i=this.parent.add(this.object,this.property,t);return i.name(this._name),this.destroy(),i}min(t){return this}max(t){return this}step(t){return this}decimals(t){return this}listen(t=!0){return this._listening=t,void 0!==this._listenCallbackID&&(cancelAnimationFrame(this._listenCallbackID),this._listenCallbackID=void 0),this._listening&&this._listenCallback(),this}_listenCallback(){this._listenCallbackID=requestAnimationFrame(this._listenCallback);const t=this.save();t!==this._listenPrevValue&&this.updateDisplay(),this._listenPrevValue=t}getValue(){return this.object[this.property]}setValue(t){return this.object[this.property]=t,this._callOnChange(),this.updateDisplay(),this}updateDisplay(){return this}load(t){return this.setValue(t),this._callOnFinishChange(),this}save(){return this.getValue()}destroy(){this.listen(!1),this.parent.children.splice(this.parent.children.indexOf(this),1),this.parent.controllers.splice(this.parent.controllers.indexOf(this),1),this.parent.$children.removeChild(this.domElement)}}class i extends t{constructor(t,i,e){super(t,i,e,"boolean","label"),this.$input=document.createElement("input"),this.$input.setAttribute("type","checkbox"),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$widget.appendChild(this.$input),this.$input.addEventListener("change",()=>{this.setValue(this.$input.checked),this._callOnFinishChange()}),this.$disable=this.$input,this.updateDisplay()}updateDisplay(){return this.$input.checked=this.getValue(),this}}function e(t){let i,e;return(i=t.match(/(#|0x)?([a-f0-9]{6})/i))?e=i[2]:(i=t.match(/rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\)/))?e=parseInt(i[1]).toString(16).padStart(2,0)+parseInt(i[2]).toString(16).padStart(2,0)+parseInt(i[3]).toString(16).padStart(2,0):(i=t.match(/^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i))&&(e=i[1]+i[1]+i[2]+i[2]+i[3]+i[3]),!!e&&"#"+e}const s={isPrimitive:!0,match:t=>"string"==typeof t,fromHexString:e,toHexString:e},n={isPrimitive:!0,match:t=>"number"==typeof t,fromHexString:t=>parseInt(t.substring(1),16),toHexString:t=>"#"+t.toString(16).padStart(6,0)},l={isPrimitive:!1,match:Array.isArray,fromHexString(t,i,e=1){const s=n.fromHexString(t);i[0]=(s>>16&255)/255*e,i[1]=(s>>8&255)/255*e,i[2]=(255&s)/255*e},toHexString:([t,i,e],s=1)=>n.toHexString(t*(s=255/s)<<16^i*s<<8^e*s<<0)},r={isPrimitive:!1,match:t=>Object(t)===t,fromHexString(t,i,e=1){const s=n.fromHexString(t);i.r=(s>>16&255)/255*e,i.g=(s>>8&255)/255*e,i.b=(255&s)/255*e},toHexString:({r:t,g:i,b:e},s=1)=>n.toHexString(t*(s=255/s)<<16^i*s<<8^e*s<<0)},o=[s,n,l,r];class a extends t{constructor(t,i,s,n){var l;super(t,i,s,"color"),this.$input=document.createElement("input"),this.$input.setAttribute("type","color"),this.$input.setAttribute("tabindex",-1),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$text=document.createElement("input"),this.$text.setAttribute("type","text"),this.$text.setAttribute("spellcheck","false"),this.$text.setAttribute("aria-labelledby",this.$name.id),this.$display=document.createElement("div"),this.$display.classList.add("display"),this.$display.appendChild(this.$input),this.$widget.appendChild(this.$display),this.$widget.appendChild(this.$text),this._format=(l=this.initialValue,o.find(t=>t.match(l))),this._rgbScale=n,this._initialValueHexString=this.save(),this._textFocused=!1,this.$input.addEventListener("input",()=>{this._setValueFromHexString(this.$input.value)}),this.$input.addEventListener("blur",()=>{this._callOnFinishChange()}),this.$text.addEventListener("input",()=>{const t=e(this.$text.value);t&&this._setValueFromHexString(t)}),this.$text.addEventListener("focus",()=>{this._textFocused=!0,this.$text.select()}),this.$text.addEventListener("blur",()=>{this._textFocused=!1,this.updateDisplay(),this._callOnFinishChange()}),this.$disable=this.$text,this.updateDisplay()}reset(){return this._setValueFromHexString(this._initialValueHexString),this}_setValueFromHexString(t){if(this._format.isPrimitive){const i=this._format.fromHexString(t);this.setValue(i)}else this._format.fromHexString(t,this.getValue(),this._rgbScale),this._callOnChange(),this.updateDisplay()}save(){return this._format.toHexString(this.getValue(),this._rgbScale)}load(t){return this._setValueFromHexString(t),this._callOnFinishChange(),this}updateDisplay(){return this.$input.value=this._format.toHexString(this.getValue(),this._rgbScale),this._textFocused||(this.$text.value=this.$input.value.substring(1)),this.$display.style.backgroundColor=this.$input.value,this}}class h extends t{constructor(t,i,e){super(t,i,e,"function"),this.$button=document.createElement("button"),this.$button.appendChild(this.$name),this.$widget.appendChild(this.$button),this.$button.addEventListener("click",t=>{t.preventDefault(),this.getValue().call(this.object)}),this.$button.addEventListener("touchstart",()=>{},{passive:!0}),this.$disable=this.$button}}class d extends t{constructor(t,i,e,s,n,l){super(t,i,e,"number"),this._initInput(),this.min(s),this.max(n);const r=void 0!==l;this.step(r?l:this._getImplicitStep(),r),this.updateDisplay()}decimals(t){return this._decimals=t,this.updateDisplay(),this}min(t){return this._min=t,this._onUpdateMinMax(),this}max(t){return this._max=t,this._onUpdateMinMax(),this}step(t,i=!0){return this._step=t,this._stepExplicit=i,this}updateDisplay(){const t=this.getValue();if(this._hasSlider){let i=(t-this._min)/(this._max-this._min);i=Math.max(0,Math.min(i,1)),this.$fill.style.width=100*i+"%"}return this._inputFocused||(this.$input.value=void 0===this._decimals?t:t.toFixed(this._decimals)),this}_initInput(){this.$input=document.createElement("input"),this.$input.setAttribute("type","number"),this.$input.setAttribute("step","any"),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$widget.appendChild(this.$input),this.$disable=this.$input;const t=t=>{const i=parseFloat(this.$input.value);isNaN(i)||(this._snapClampSetValue(i+t),this.$input.value=this.getValue())};let i,e,s,n,l,r=!1;const o=t=>{if(r){const s=t.clientX-i,n=t.clientY-e;Math.abs(n)>5?(t.preventDefault(),this.$input.blur(),r=!1,this._setDraggingStyle(!0,"vertical")):Math.abs(s)>5&&a()}if(!r){const i=t.clientY-s;l-=i*this._step*this._arrowKeyMultiplier(t),n+l>this._max?l=this._max-n:n+l{this._setDraggingStyle(!1,"vertical"),this._callOnFinishChange(),window.removeEventListener("mousemove",o),window.removeEventListener("mouseup",a)};this.$input.addEventListener("input",()=>{let t=parseFloat(this.$input.value);isNaN(t)||(this._stepExplicit&&(t=this._snap(t)),this.setValue(this._clamp(t)))}),this.$input.addEventListener("keydown",i=>{"Enter"===i.code&&this.$input.blur(),"ArrowUp"===i.code&&(i.preventDefault(),t(this._step*this._arrowKeyMultiplier(i))),"ArrowDown"===i.code&&(i.preventDefault(),t(this._step*this._arrowKeyMultiplier(i)*-1))}),this.$input.addEventListener("wheel",i=>{this._inputFocused&&(i.preventDefault(),t(this._step*this._normalizeMouseWheel(i)))},{passive:!1}),this.$input.addEventListener("mousedown",t=>{i=t.clientX,e=s=t.clientY,r=!0,n=this.getValue(),l=0,window.addEventListener("mousemove",o),window.addEventListener("mouseup",a)}),this.$input.addEventListener("focus",()=>{this._inputFocused=!0}),this.$input.addEventListener("blur",()=>{this._inputFocused=!1,this.updateDisplay(),this._callOnFinishChange()})}_initSlider(){this._hasSlider=!0,this.$slider=document.createElement("div"),this.$slider.classList.add("slider"),this.$fill=document.createElement("div"),this.$fill.classList.add("fill"),this.$slider.appendChild(this.$fill),this.$widget.insertBefore(this.$slider,this.$input),this.domElement.classList.add("hasSlider");const t=t=>{const i=this.$slider.getBoundingClientRect();let e=(s=t,n=i.left,l=i.right,r=this._min,o=this._max,(s-n)/(l-n)*(o-r)+r);var s,n,l,r,o;this._snapClampSetValue(e)},i=i=>{t(i.clientX)},e=()=>{this._callOnFinishChange(),this._setDraggingStyle(!1),window.removeEventListener("mousemove",i),window.removeEventListener("mouseup",e)};let s,n,l=!1;const r=i=>{i.preventDefault(),this._setDraggingStyle(!0),t(i.touches[0].clientX),l=!1},o=i=>{if(l){const t=i.touches[0].clientX-s,e=i.touches[0].clientY-n;Math.abs(t)>Math.abs(e)?r(i):(window.removeEventListener("touchmove",o),window.removeEventListener("touchend",a))}else i.preventDefault(),t(i.touches[0].clientX)},a=()=>{this._callOnFinishChange(),this._setDraggingStyle(!1),window.removeEventListener("touchmove",o),window.removeEventListener("touchend",a)},h=this._callOnFinishChange.bind(this);let d;this.$slider.addEventListener("mousedown",s=>{this._setDraggingStyle(!0),t(s.clientX),window.addEventListener("mousemove",i),window.addEventListener("mouseup",e)}),this.$slider.addEventListener("touchstart",t=>{t.touches.length>1||(this._hasScrollBar?(s=t.touches[0].clientX,n=t.touches[0].clientY,l=!0):r(t),window.addEventListener("touchmove",o,{passive:!1}),window.addEventListener("touchend",a))},{passive:!1}),this.$slider.addEventListener("wheel",t=>{if(Math.abs(t.deltaX)this._max&&(t=this._max),t}_snapClampSetValue(t){this.setValue(this._clamp(this._snap(t)))}get _hasScrollBar(){const t=this.parent.root.$children;return t.scrollHeight>t.clientHeight}get _hasMin(){return void 0!==this._min}get _hasMax(){return void 0!==this._max}}class c extends t{constructor(t,i,e,s){super(t,i,e,"option"),this.$select=document.createElement("select"),this.$select.setAttribute("aria-labelledby",this.$name.id),this.$display=document.createElement("div"),this.$display.classList.add("display"),this._values=Array.isArray(s)?s:Object.values(s),this._names=Array.isArray(s)?s:Object.keys(s),this._names.forEach(t=>{const i=document.createElement("option");i.innerHTML=t,this.$select.appendChild(i)}),this.$select.addEventListener("change",()=>{this.setValue(this._values[this.$select.selectedIndex]),this._callOnFinishChange()}),this.$select.addEventListener("focus",()=>{this.$display.classList.add("focus")}),this.$select.addEventListener("blur",()=>{this.$display.classList.remove("focus")}),this.$widget.appendChild(this.$select),this.$widget.appendChild(this.$display),this.$disable=this.$select,this.updateDisplay()}updateDisplay(){const t=this.getValue(),i=this._values.indexOf(t);return this.$select.selectedIndex=i,this.$display.innerHTML=-1===i?t:this._names[i],this}}class u extends t{constructor(t,i,e){super(t,i,e,"string"),this.$input=document.createElement("input"),this.$input.setAttribute("type","text"),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$input.addEventListener("input",()=>{this.setValue(this.$input.value)}),this.$input.addEventListener("keydown",t=>{"Enter"===t.code&&this.$input.blur()}),this.$input.addEventListener("blur",()=>{this._callOnFinishChange()}),this.$widget.appendChild(this.$input),this.$disable=this.$input,this.updateDisplay()}updateDisplay(){return this.$input.value=this.getValue(),this}}let p=!1;class g{constructor({parent:t,autoPlace:i=void 0===t,container:e,width:s,title:n="Controls",injectStyles:l=!0,touchStyles:r=!0}={}){if(this.parent=t,this.root=t?t.root:this,this.children=[],this.controllers=[],this.folders=[],this._closed=!1,this._hidden=!1,this.domElement=document.createElement("div"),this.domElement.classList.add("lil-gui"),this.$title=document.createElement("div"),this.$title.classList.add("title"),this.$title.setAttribute("role","button"),this.$title.setAttribute("aria-expanded",!0),this.$title.setAttribute("tabindex",0),this.$title.addEventListener("click",()=>this.openAnimated(this._closed)),this.$title.addEventListener("keydown",t=>{"Enter"!==t.code&&"Space"!==t.code||(t.preventDefault(),this.$title.click())}),this.$title.addEventListener("touchstart",()=>{},{passive:!0}),this.$children=document.createElement("div"),this.$children.classList.add("children"),this.domElement.appendChild(this.$title),this.domElement.appendChild(this.$children),this.title(n),r&&this.domElement.classList.add("allow-touch-styles"),this.parent)return this.parent.children.push(this),this.parent.folders.push(this),void this.parent.$children.appendChild(this.domElement);this.domElement.classList.add("root"),!p&&l&&(!function(t){const i=document.createElement("style");i.innerHTML=t;const e=document.querySelector("head link[rel=stylesheet], head style");e?document.head.insertBefore(i,e):document.head.appendChild(i)}('.lil-gui{--background-color:#1f1f1f;--text-color:#ebebeb;--title-background-color:#111;--title-text-color:#ebebeb;--widget-color:#424242;--hover-color:#4f4f4f;--focus-color:#595959;--number-color:#2cc9ff;--string-color:#a2db3c;--font-size:11px;--input-font-size:11px;--font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;--font-family-mono:Menlo,Monaco,Consolas,"Droid Sans Mono",monospace;--padding:4px;--spacing:4px;--widget-height:20px;--name-width:45%;--slider-knob-width:2px;--slider-input-width:27%;--color-input-width:27%;--slider-input-min-width:45px;--color-input-min-width:45px;--folder-indent:7px;--widget-padding:0 0 0 3px;--widget-border-radius:2px;--checkbox-size:calc(var(--widget-height)*0.75);--scrollbar-width:5px;background-color:var(--background-color);color:var(--text-color);font-family:var(--font-family);font-size:var(--font-size);font-style:normal;font-weight:400;line-height:1;text-align:left;touch-action:manipulation;user-select:none;-webkit-user-select:none}.lil-gui,.lil-gui *{box-sizing:border-box;margin:0;padding:0}.lil-gui.root{display:flex;flex-direction:column;width:var(--width,245px)}.lil-gui.root>.title{background:var(--title-background-color);color:var(--title-text-color)}.lil-gui.root>.children{overflow-x:hidden;overflow-y:auto}.lil-gui.root>.children::-webkit-scrollbar{background:var(--background-color);height:var(--scrollbar-width);width:var(--scrollbar-width)}.lil-gui.root>.children::-webkit-scrollbar-thumb{background:var(--focus-color);border-radius:var(--scrollbar-width)}.lil-gui.force-touch-styles{--widget-height:28px;--padding:6px;--spacing:6px;--font-size:13px;--input-font-size:16px;--folder-indent:10px;--scrollbar-width:7px;--slider-input-min-width:50px;--color-input-min-width:65px}.lil-gui.autoPlace{max-height:100%;position:fixed;right:15px;top:0;z-index:1001}.lil-gui .controller{align-items:center;display:flex;margin:var(--spacing) 0;padding:0 var(--padding)}.lil-gui .controller.disabled{opacity:.5}.lil-gui .controller.disabled,.lil-gui .controller.disabled *{pointer-events:none!important}.lil-gui .controller>.name{flex-shrink:0;line-height:var(--widget-height);min-width:var(--name-width);padding-right:var(--spacing);white-space:pre}.lil-gui .controller .widget{align-items:center;display:flex;min-height:var(--widget-height);position:relative;width:100%}.lil-gui .controller.string input{color:var(--string-color)}.lil-gui .controller.boolean .widget{cursor:pointer}.lil-gui .controller.color .display{border-radius:var(--widget-border-radius);height:var(--widget-height);position:relative;width:100%}.lil-gui .controller.color input[type=color]{cursor:pointer;height:100%;opacity:0;width:100%}.lil-gui .controller.color input[type=text]{flex-shrink:0;font-family:var(--font-family-mono);margin-left:var(--spacing);min-width:var(--color-input-min-width);width:var(--color-input-width)}.lil-gui .controller.option select{max-width:100%;opacity:0;position:absolute;width:100%}.lil-gui .controller.option .display{background:var(--widget-color);border-radius:var(--widget-border-radius);height:var(--widget-height);line-height:var(--widget-height);max-width:100%;overflow:hidden;padding-left:.55em;padding-right:1.75em;pointer-events:none;position:relative;word-break:break-all}.lil-gui .controller.option .display.active{background:var(--focus-color)}.lil-gui .controller.option .display:after{bottom:0;content:"↕";font-family:lil-gui;padding-right:.375em;position:absolute;right:0;top:0}.lil-gui .controller.option .widget,.lil-gui .controller.option select{cursor:pointer}.lil-gui .controller.number input{color:var(--number-color)}.lil-gui .controller.number.hasSlider input{flex-shrink:0;margin-left:var(--spacing);min-width:var(--slider-input-min-width);width:var(--slider-input-width)}.lil-gui .controller.number .slider{background-color:var(--widget-color);border-radius:var(--widget-border-radius);cursor:ew-resize;height:var(--widget-height);overflow:hidden;padding-right:var(--slider-knob-width);touch-action:pan-y;width:100%}.lil-gui .controller.number .slider.active{background-color:var(--focus-color)}.lil-gui .controller.number .slider.active .fill{opacity:.95}.lil-gui .controller.number .fill{border-right:var(--slider-knob-width) solid var(--number-color);box-sizing:content-box;height:100%}.lil-gui-dragging .lil-gui{--hover-color:var(--widget-color)}.lil-gui-dragging *{cursor:ew-resize!important}.lil-gui-dragging.lil-gui-vertical *{cursor:ns-resize!important}.lil-gui .title{--title-height:calc(var(--widget-height) + var(--spacing)*1.25);-webkit-tap-highlight-color:transparent;text-decoration-skip:objects;cursor:pointer;font-weight:600;height:var(--title-height);line-height:calc(var(--title-height) - 4px);outline:none;padding:0 var(--padding)}.lil-gui .title:before{content:"▾";display:inline-block;font-family:lil-gui;padding-right:2px}.lil-gui .title:active{background:var(--title-background-color);opacity:.75}.lil-gui.root>.title:focus{text-decoration:none!important}.lil-gui.closed>.title:before{content:"▸"}.lil-gui.closed>.children{opacity:0;transform:translateY(-7px)}.lil-gui.closed:not(.transition)>.children{display:none}.lil-gui.transition>.children{overflow:hidden;pointer-events:none;transition-duration:.3s;transition-property:height,opacity,transform;transition-timing-function:cubic-bezier(.2,.6,.35,1)}.lil-gui .children:empty:before{content:"Empty";display:block;font-style:italic;height:var(--widget-height);line-height:var(--widget-height);margin:var(--spacing) 0;opacity:.5;padding:0 var(--padding)}.lil-gui.root>.children>.lil-gui>.title{border-width:0;border-bottom:1px solid var(--widget-color);border-left:0 solid var(--widget-color);border-right:0 solid var(--widget-color);border-top:1px solid var(--widget-color);transition:border-color .3s}.lil-gui.root>.children>.lil-gui.closed>.title{border-bottom-color:transparent}.lil-gui+.controller{border-top:1px solid var(--widget-color);margin-top:0;padding-top:var(--spacing)}.lil-gui .lil-gui .lil-gui>.title{border:none}.lil-gui .lil-gui .lil-gui>.children{border:none;border-left:2px solid var(--widget-color);margin-left:var(--folder-indent)}.lil-gui .lil-gui .controller{border:none}.lil-gui input{-webkit-tap-highlight-color:transparent;background:var(--widget-color);border:0;border-radius:var(--widget-border-radius);color:var(--text-color);font-family:var(--font-family);font-size:var(--input-font-size);height:var(--widget-height);outline:none;width:100%}.lil-gui input:disabled{opacity:1}.lil-gui input[type=number],.lil-gui input[type=text]{padding:var(--widget-padding)}.lil-gui input[type=number]:focus,.lil-gui input[type=text]:focus{background:var(--focus-color)}.lil-gui input::-webkit-inner-spin-button,.lil-gui input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.lil-gui input[type=number]{-moz-appearance:textfield}.lil-gui input[type=checkbox]{appearance:none;-webkit-appearance:none;border-radius:var(--widget-border-radius);cursor:pointer;height:var(--checkbox-size);text-align:center;width:var(--checkbox-size)}.lil-gui input[type=checkbox]:checked:before{content:"✓";font-family:lil-gui;font-size:var(--checkbox-size);line-height:var(--checkbox-size)}.lil-gui button{-webkit-tap-highlight-color:transparent;background:var(--widget-color);border:1px solid var(--widget-color);border-radius:var(--widget-border-radius);color:var(--text-color);cursor:pointer;font-family:var(--font-family);font-size:var(--font-size);height:var(--widget-height);line-height:calc(var(--widget-height) - 4px);outline:none;text-align:center;text-transform:none;width:100%}.lil-gui button:active{background:var(--focus-color)}@font-face{font-family:lil-gui;src:url("data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAUsAAsAAAAACJwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAAH4AAADAImwmYE9TLzIAAAGIAAAAPwAAAGBKqH5SY21hcAAAAcgAAAD0AAACrukyyJBnbHlmAAACvAAAAF8AAACEIZpWH2hlYWQAAAMcAAAAJwAAADZfcj2zaGhlYQAAA0QAAAAYAAAAJAC5AHhobXR4AAADXAAAABAAAABMAZAAAGxvY2EAAANsAAAAFAAAACgCEgIybWF4cAAAA4AAAAAeAAAAIAEfABJuYW1lAAADoAAAASIAAAIK9SUU/XBvc3QAAATEAAAAZgAAAJCTcMc2eJxVjbEOgjAURU+hFRBK1dGRL+ALnAiToyMLEzFpnPz/eAshwSa97517c/MwwJmeB9kwPl+0cf5+uGPZXsqPu4nvZabcSZldZ6kfyWnomFY/eScKqZNWupKJO6kXN3K9uCVoL7iInPr1X5baXs3tjuMqCtzEuagm/AAlzQgPAAB4nGNgYRBlnMDAysDAYM/gBiT5oLQBAwuDJAMDEwMrMwNWEJDmmsJwgCFeXZghBcjlZMgFCzOiKOIFAB71Bb8AeJy1kjFuwkAQRZ+DwRAwBtNQRUGKQ8OdKCAWUhAgKLhIuAsVSpWz5Bbkj3dEgYiUIszqWdpZe+Z7/wB1oCYmIoboiwiLT2WjKl/jscrHfGg/pKdMkyklC5Zs2LEfHYpjcRoPzme9MWWmk3dWbK9ObkWkikOetJ554fWyoEsmdSlt+uR0pCJR34b6t/TVg1SY3sYvdf8vuiKrpyaDXDISiegp17p7579Gp3p++y7HPAiY9pmTibljrr85qSidtlg4+l25GLCaS8e6rRxNBmsnERunKbaOObRz7N72ju5vdAjYpBXHgJylOAVsMseDAPEP8LYoUHicY2BiAAEfhiAGJgZWBgZ7RnFRdnVJELCQlBSRlATJMoLV2DK4glSYs6ubq5vbKrJLSbGrgEmovDuDJVhe3VzcXFwNLCOILB/C4IuQ1xTn5FPilBTj5FPmBAB4WwoqAHicY2BkYGAA4sk1sR/j+W2+MnAzpDBgAyEMQUCSg4EJxAEAwUgFHgB4nGNgZGBgSGFggJMhDIwMqEAYAByHATJ4nGNgAIIUNEwmAABl3AGReJxjYAACIQYlBiMGJ3wQAEcQBEV4nGNgZGBgEGZgY2BiAAEQyQWEDAz/wXwGAAsPATIAAHicXdBNSsNAHAXwl35iA0UQXYnMShfS9GPZA7T7LgIu03SSpkwzYTIt1BN4Ak/gKTyAeCxfw39jZkjymzcvAwmAW/wgwHUEGDb36+jQQ3GXGot79L24jxCP4gHzF/EIr4jEIe7wxhOC3g2TMYy4Q7+Lu/SHuEd/ivt4wJd4wPxbPEKMX3GI5+DJFGaSn4qNzk8mcbKSR6xdXdhSzaOZJGtdapd4vVPbi6rP+cL7TGXOHtXKll4bY1Xl7EGnPtp7Xy2n00zyKLVHfkHBa4IcJ2oD3cgggWvt/V/FbDrUlEUJhTn/0azVWbNTNr0Ens8de1tceK9xZmfB1CPjOmPH4kitmvOubcNpmVTN3oFJyjzCvnmrwhJTzqzVj9jiSX911FjeAAB4nG3HMRKCMBBA0f0giiKi4DU8k0V2GWbIZDOh4PoWWvq6J5V8If9NVNQcaDhyouXMhY4rPTcG7jwYmXhKq8Wz+p762aNaeYXom2n3m2dLTVgsrCgFJ7OTmIkYbwIbC6vIB7WmFfAAAA==") format("woff")}@media (pointer:coarse){.lil-gui.allow-touch-styles{--widget-height:28px;--padding:6px;--spacing:6px;--font-size:13px;--input-font-size:16px;--folder-indent:10px;--scrollbar-width:7px;--slider-input-min-width:50px;--color-input-min-width:65px}}@media (hover:hover){.lil-gui .controller.color .display:hover:before{border:1px solid #fff9;border-radius:var(--widget-border-radius);bottom:0;content:" ";display:block;left:0;position:absolute;right:0;top:0}.lil-gui .controller.option .display.focus{background:var(--focus-color)}.lil-gui .controller.option .widget:hover .display{background:var(--hover-color)}.lil-gui .controller.number .slider:hover{background-color:var(--hover-color)}body:not(.lil-gui-dragging) .lil-gui .title:hover{background:var(--title-background-color);opacity:.85}.lil-gui .title:focus{text-decoration:underline var(--focus-color)}.lil-gui input:hover{background:var(--hover-color)}.lil-gui input:active{background:var(--focus-color)}.lil-gui input[type=checkbox]:focus{box-shadow:inset 0 0 0 1px var(--focus-color)}.lil-gui button:hover{background:var(--hover-color);border-color:var(--hover-color)}.lil-gui button:focus{border-color:var(--focus-color)}}'),p=!0),e?e.appendChild(this.domElement):i&&(this.domElement.classList.add("autoPlace"),document.body.appendChild(this.domElement)),s&&this.domElement.style.setProperty("--width",s+"px"),this.domElement.addEventListener("keydown",t=>t.stopPropagation()),this.domElement.addEventListener("keyup",t=>t.stopPropagation())}add(t,e,s,n,l){if(Object(s)===s)return new c(this,t,e,s);const r=t[e];switch(typeof r){case"number":return new d(this,t,e,s,n,l);case"boolean":return new i(this,t,e);case"string":return new u(this,t,e);case"function":return new h(this,t,e)}console.error("gui.add failed\n\tproperty:",e,"\n\tobject:",t,"\n\tvalue:",r)}addColor(t,i,e=1){return new a(this,t,i,e)}addFolder(t){return new g({parent:this,title:t})}load(t,i=!0){return t.controllers&&this.controllers.forEach(i=>{i instanceof h||i._name in t.controllers&&i.load(t.controllers[i._name])}),i&&t.folders&&this.folders.forEach(i=>{i._title in t.folders&&i.load(t.folders[i._title])}),this}save(t=!0){const i={controllers:{},folders:{}};return this.controllers.forEach(t=>{if(!(t instanceof h)){if(t._name in i.controllers)throw new Error(`Cannot save GUI with duplicate property "${t._name}"`);i.controllers[t._name]=t.save()}}),t&&this.folders.forEach(t=>{if(t._title in i.folders)throw new Error(`Cannot save GUI with duplicate folder "${t._title}"`);i.folders[t._title]=t.save()}),i}open(t=!0){return this._closed=!t,this.$title.setAttribute("aria-expanded",!this._closed),this.domElement.classList.toggle("closed",this._closed),this}close(){return this.open(!1)}show(t=!0){return this._hidden=!t,this.domElement.style.display=this._hidden?"none":"",this}hide(){return this.show(!1)}openAnimated(t=!0){return this._closed=!t,this.$title.setAttribute("aria-expanded",!this._closed),requestAnimationFrame(()=>{const i=this.$children.clientHeight;this.$children.style.height=i+"px",this.domElement.classList.add("transition");const e=t=>{t.target===this.$children&&(this.$children.style.height="",this.domElement.classList.remove("transition"),this.$children.removeEventListener("transitionend",e))};this.$children.addEventListener("transitionend",e);const s=t?this.$children.scrollHeight:0;this.domElement.classList.toggle("closed",!t),requestAnimationFrame(()=>{this.$children.style.height=s+"px"})}),this}title(t){return this._title=t,this.$title.innerHTML=t,this}reset(t=!0){return(t?this.controllersRecursive():this.controllers).forEach(t=>t.reset()),this}onChange(t){return this._onChange=t,this}_callOnChange(t){this.parent&&this.parent._callOnChange(t),void 0!==this._onChange&&this._onChange.call(this,{object:t.object,property:t.property,value:t.getValue(),controller:t})}onFinishChange(t){return this._onFinishChange=t,this}_callOnFinishChange(t){this.parent&&this.parent._callOnFinishChange(t),void 0!==this._onFinishChange&&this._onFinishChange.call(this,{object:t.object,property:t.property,value:t.getValue(),controller:t})}destroy(){this.parent&&(this.parent.children.splice(this.parent.children.indexOf(this),1),this.parent.folders.splice(this.parent.folders.indexOf(this),1)),this.domElement.parentElement&&this.domElement.parentElement.removeChild(this.domElement),Array.from(this.children).forEach(t=>t.destroy())}controllersRecursive(){let t=Array.from(this.controllers);return this.folders.forEach(i=>{t=t.concat(i.controllersRecursive())}),t}foldersRecursive(){let t=Array.from(this.folders);return this.folders.forEach(i=>{t=t.concat(i.foldersRecursive())}),t}}export default g;export{i as BooleanController,a as ColorController,t as Controller,h as FunctionController,g as GUI,d as NumberController,c as OptionController,u as StringController};
9 |
--------------------------------------------------------------------------------
/js/stats.module.js:
--------------------------------------------------------------------------------
1 | var Stats = function () {
2 |
3 | var mode = 0;
4 |
5 | var container = document.createElement( 'div' );
6 | container.style.cssText = 'position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000';
7 | container.addEventListener( 'click', function ( event ) {
8 |
9 | event.preventDefault();
10 | showPanel( ++ mode % container.children.length );
11 |
12 | }, false );
13 |
14 | //
15 |
16 | function addPanel( panel ) {
17 |
18 | container.appendChild( panel.dom );
19 | return panel;
20 |
21 | }
22 |
23 | function showPanel( id ) {
24 |
25 | for ( var i = 0; i < container.children.length; i ++ ) {
26 |
27 | container.children[ i ].style.display = i === id ? 'block' : 'none';
28 |
29 | }
30 |
31 | mode = id;
32 |
33 | }
34 |
35 | //
36 |
37 | var beginTime = ( performance || Date ).now(), prevTime = beginTime, frames = 0;
38 |
39 | var fpsPanel = addPanel( new Stats.Panel( 'FPS', '#0ff', '#002' ) );
40 | var msPanel = addPanel( new Stats.Panel( 'MS', '#0f0', '#020' ) );
41 |
42 | if ( self.performance && self.performance.memory ) {
43 |
44 | var memPanel = addPanel( new Stats.Panel( 'MB', '#f08', '#201' ) );
45 |
46 | }
47 |
48 | showPanel( 0 );
49 |
50 | return {
51 |
52 | REVISION: 16,
53 |
54 | dom: container,
55 |
56 | addPanel: addPanel,
57 | showPanel: showPanel,
58 |
59 | begin: function () {
60 |
61 | beginTime = ( performance || Date ).now();
62 |
63 | },
64 |
65 | end: function () {
66 |
67 | frames ++;
68 |
69 | var time = ( performance || Date ).now();
70 |
71 | msPanel.update( time - beginTime, 200 );
72 |
73 | if ( time >= prevTime + 1000 ) {
74 |
75 | fpsPanel.update( ( frames * 1000 ) / ( time - prevTime ), 100 );
76 |
77 | prevTime = time;
78 | frames = 0;
79 |
80 | if ( memPanel ) {
81 |
82 | var memory = performance.memory;
83 | memPanel.update( memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576 );
84 |
85 | }
86 |
87 | }
88 |
89 | return time;
90 |
91 | },
92 |
93 | update: function () {
94 |
95 | beginTime = this.end();
96 |
97 | },
98 |
99 | // Backwards Compatibility
100 |
101 | domElement: container,
102 | setMode: showPanel
103 |
104 | };
105 |
106 | };
107 |
108 | Stats.Panel = function ( name, fg, bg ) {
109 |
110 | var min = Infinity, max = 0, round = Math.round;
111 | var PR = round( window.devicePixelRatio || 1 );
112 |
113 | var WIDTH = 80 * PR, HEIGHT = 48 * PR,
114 | TEXT_X = 3 * PR, TEXT_Y = 2 * PR,
115 | GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR,
116 | GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR;
117 |
118 | var canvas = document.createElement( 'canvas' );
119 | canvas.width = WIDTH;
120 | canvas.height = HEIGHT;
121 | canvas.style.cssText = 'width:80px;height:48px';
122 |
123 | var context = canvas.getContext( '2d' );
124 | context.font = 'bold ' + ( 9 * PR ) + 'px Helvetica,Arial,sans-serif';
125 | context.textBaseline = 'top';
126 |
127 | context.fillStyle = bg;
128 | context.fillRect( 0, 0, WIDTH, HEIGHT );
129 |
130 | context.fillStyle = fg;
131 | context.fillText( name, TEXT_X, TEXT_Y );
132 | context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );
133 |
134 | context.fillStyle = bg;
135 | context.globalAlpha = 0.9;
136 | context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT );
137 |
138 | return {
139 |
140 | dom: canvas,
141 |
142 | update: function ( value, maxValue ) {
143 |
144 | min = Math.min( min, value );
145 | max = Math.max( max, value );
146 |
147 | context.fillStyle = bg;
148 | context.globalAlpha = 1;
149 | context.fillRect( 0, 0, WIDTH, GRAPH_Y );
150 | context.fillStyle = fg;
151 | context.fillText( round( value ) + ' ' + name + ' (' + round( min ) + '-' + round( max ) + ')', TEXT_X, TEXT_Y );
152 |
153 | context.drawImage( canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT );
154 |
155 | context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT );
156 |
157 | context.fillStyle = bg;
158 | context.globalAlpha = 0.9;
159 | context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round( ( 1 - ( value / maxValue ) ) * GRAPH_HEIGHT ) );
160 |
161 | }
162 |
163 | };
164 |
165 | };
166 |
167 | export default Stats;
168 |
--------------------------------------------------------------------------------
/shaders/Path_Traced_Pong_Fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | precision highp int;
3 | precision highp sampler2D;
4 |
5 | #include
6 | uniform vec3 uBallPos;
7 | uniform vec3 uPlayerPos;
8 | uniform vec3 uComputerPos;
9 | uniform vec3 uHalfRoomDimensions;
10 | uniform float uPaddleRadX;
11 | uniform float uPaddleRadY;
12 | uniform bool uCutSceneIsPlaying;
13 |
14 | #define N_SPHERES 1
15 | #define N_BOXES 7
16 | #define N_QUADS 1
17 |
18 |
19 | //-----------------------------------------------------------------------
20 |
21 | vec3 rayOrigin, rayDirection;
22 | // recorded intersection data:
23 | vec3 hitNormal, hitEmission, hitColor;
24 | vec2 hitUV;
25 | float hitObjectID = -INFINITY;
26 | int hitType = -100;
27 |
28 | struct Sphere { float radius; vec3 position; vec3 emission; vec3 color; int type; };
29 | struct Quad { vec3 normal; vec3 v0; vec3 v1; vec3 v2; vec3 v3; vec3 emission; vec3 color; int type; };
30 | struct Box { vec3 minCorner; vec3 maxCorner; vec3 emission; vec3 color; int type; };
31 |
32 | Sphere spheres[N_SPHERES];
33 | Box boxes[N_BOXES];
34 | Quad quads[N_QUADS];
35 |
36 |
37 | #include
38 |
39 | #include
40 |
41 | #include
42 |
43 | #include
44 |
45 | #include
46 |
47 | #include
48 |
49 | #include
50 |
51 | #include
52 |
53 |
54 |
55 | //---------------------
56 | float SceneIntersect( )
57 | //---------------------
58 | {
59 | vec3 n;
60 | float d = INFINITY;
61 | float t = INFINITY;
62 | int objectCount = 0;
63 | int isRayExiting = FALSE;
64 |
65 |
66 | d = SphereIntersect( spheres[0].radius, spheres[0].position, rayOrigin, rayDirection );
67 | if (d < t)
68 | {
69 | t = d;
70 | hitNormal = (rayOrigin + rayDirection * t) - spheres[0].position;
71 | hitEmission = spheres[0].emission;
72 | hitColor = spheres[0].color;
73 | hitType = spheres[0].type;
74 | hitObjectID = float(objectCount);
75 | }
76 | objectCount++;
77 |
78 | d = QuadIntersect( quads[0].v0, quads[0].v1, quads[0].v2, quads[0].v3, rayOrigin, rayDirection, FALSE);
79 | if (d < t)
80 | {
81 | t = d;
82 | hitNormal = quads[0].normal;
83 | hitEmission = quads[0].emission;
84 | hitColor = quads[0].color;
85 | hitType = quads[0].type;
86 | hitObjectID = float(objectCount);
87 | }
88 | objectCount++;
89 |
90 | d = BoxInteriorIntersect( boxes[6].minCorner, boxes[6].maxCorner, rayOrigin, rayDirection, n );
91 | if (d < t && n != vec3(0,0,-1))
92 | {
93 | t = d;
94 | hitNormal = n;
95 | hitEmission = boxes[6].emission;
96 |
97 | if (n == vec3(0,0,1)) // back mirror wall
98 | {
99 | hitColor = vec3(0.1);
100 | hitType = SPEC;
101 | }
102 | else if (n == vec3(0,-1,0)) // ceiling
103 | {
104 | hitColor = vec3(0.1);
105 | hitType = DIFF;
106 | }
107 | else if (n == vec3(0,1,0)) // floor
108 | {
109 | hitColor = vec3(0.9);
110 | hitType = COAT;
111 | }
112 | else if (n == vec3(1,0,0)) // left red wall
113 | {
114 | hitColor = vec3(1, 0, 0);
115 | hitType = COAT;
116 | }
117 | else //if (n == vec3(-1,0,0)) // right green wall
118 | {
119 | hitColor = vec3(0, 0.7, 0);
120 | hitType = COAT;
121 | }
122 |
123 | hitObjectID = float(objectCount);
124 | }
125 | objectCount++;
126 |
127 |
128 | d = BoxIntersect( boxes[0].minCorner, boxes[0].maxCorner, rayOrigin, rayDirection, n, isRayExiting );
129 | if (d < t)
130 | {
131 | t = d;
132 | hitNormal = n;
133 | hitEmission = boxes[0].emission;
134 | hitColor = boxes[0].color;
135 | hitType = boxes[0].type;
136 | //finalIsRayExiting = isRayExiting;
137 | hitObjectID = float(objectCount);
138 | }
139 | objectCount++;
140 |
141 | d = BoxIntersect( boxes[1].minCorner, boxes[1].maxCorner, rayOrigin, rayDirection, n, isRayExiting );
142 | if (d < t)
143 | {
144 | t = d;
145 | hitNormal = n;
146 | hitEmission = boxes[1].emission;
147 | hitColor = boxes[1].color;
148 | hitType = boxes[1].type;
149 | //finalIsRayExiting = isRayExiting;
150 | hitObjectID = float(objectCount);
151 | }
152 | objectCount++;
153 |
154 | d = BoxIntersect( boxes[2].minCorner, boxes[2].maxCorner, rayOrigin, rayDirection, n, isRayExiting );
155 | if (d < t)
156 | {
157 | t = d;
158 | hitNormal = n;
159 | hitEmission = boxes[2].emission;
160 | hitColor = boxes[2].color;
161 | hitType = boxes[2].type;
162 | //finalIsRayExiting = isRayExiting;
163 | hitObjectID = float(objectCount);
164 | }
165 | objectCount++;
166 |
167 | d = BoxIntersect( boxes[3].minCorner, boxes[3].maxCorner, rayOrigin, rayDirection, n, isRayExiting );
168 | if (d < t)
169 | {
170 | t = d;
171 | hitNormal = n;
172 | hitEmission = boxes[3].emission;
173 | hitColor = boxes[3].color;
174 | hitType = boxes[3].type;
175 | //finalIsRayExiting = isRayExiting;
176 | hitObjectID = float(objectCount);
177 | }
178 | objectCount++;
179 |
180 | d = BoxIntersect( boxes[4].minCorner, boxes[4].maxCorner, rayOrigin, rayDirection, n, isRayExiting );
181 | if (d < t)
182 | {
183 | t = d;
184 | hitNormal = n;
185 | hitEmission = boxes[4].emission;
186 | hitColor = boxes[4].color;
187 | hitType = boxes[4].type;
188 | //finalIsRayExiting = isRayExiting;
189 | hitObjectID = float(objectCount);
190 | }
191 | objectCount++;
192 |
193 | d = BoxIntersect( boxes[5].minCorner, boxes[5].maxCorner, rayOrigin, rayDirection, n, isRayExiting );
194 | if (d < t)
195 | {
196 | t = d;
197 | hitNormal = n;
198 | hitEmission = boxes[5].emission;
199 | hitColor = boxes[5].color;
200 | hitType = boxes[5].type;
201 | //finalIsRayExiting = isRayExiting;
202 | hitObjectID = float(objectCount);
203 | }
204 | objectCount++;
205 |
206 | return t;
207 | } // end float SceneIntersect( )
208 |
209 |
210 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
211 | vec3 CalculateRadiance( out vec3 objectNormal, out vec3 objectColor, out float objectID, out float pixelSharpness, out float dynamicSurface )
212 | //------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
213 | {
214 | vec3 accumCol = vec3(0);
215 | vec3 mask = vec3(1);
216 | vec3 reflectionMask = vec3(1);
217 | vec3 reflectionRayOrigin = vec3(0);
218 | vec3 reflectionRayDirection = vec3(0);
219 | vec3 diffuseBounceMask = vec3(1);
220 | vec3 diffuseBounceRayOrigin = vec3(0);
221 | vec3 diffuseBounceRayDirection = vec3(0);
222 | vec3 x, n, nl;
223 |
224 | float t;
225 | float nc, nt, ratioIoR, Re, Tr;
226 | float weight;
227 | float previousObjectID;
228 |
229 | int reflectionBounces = -1;
230 | int diffuseCount = 0;
231 | int previousIntersecType = -100;
232 | hitType = -100;
233 |
234 | int bounceIsSpecular = TRUE;
235 | int sampleLight = FALSE;
236 | int lastHitWasSpec = FALSE;
237 | int willNeedReflectionRay = FALSE;
238 | int isReflectionTime = FALSE;
239 | int reflectionNeedsToBeSharp = FALSE;
240 | int willNeedDiffuseBounceRay = FALSE;
241 | int isDiffuseBounceTime = FALSE;
242 |
243 | dynamicSurface = 0.0;
244 |
245 |
246 | for (int bounces = 0; bounces < 10; bounces++)
247 | {
248 | // if (isReflectionTime == TRUE)
249 | // reflectionBounces++;
250 |
251 | previousIntersecType = hitType;
252 | previousObjectID = hitObjectID;
253 |
254 | t = SceneIntersect();
255 |
256 |
257 | if (t == INFINITY)
258 | {
259 | if (willNeedDiffuseBounceRay == TRUE)
260 | {
261 | mask = diffuseBounceMask;
262 | rayOrigin = diffuseBounceRayOrigin;
263 | rayDirection = diffuseBounceRayDirection;
264 |
265 | willNeedDiffuseBounceRay = FALSE;
266 | bounceIsSpecular = FALSE;
267 | sampleLight = FALSE;
268 | isDiffuseBounceTime = TRUE;
269 | isReflectionTime = FALSE;
270 | diffuseCount = 1;
271 | continue;
272 | }
273 |
274 | if (willNeedReflectionRay == TRUE)
275 | {
276 | mask = reflectionMask;
277 | rayOrigin = reflectionRayOrigin;
278 | rayDirection = reflectionRayDirection;
279 |
280 | willNeedReflectionRay = FALSE;
281 | bounceIsSpecular = TRUE;
282 | sampleLight = FALSE;
283 | isReflectionTime = TRUE;
284 | isDiffuseBounceTime = FALSE;
285 | diffuseCount = 0;
286 | continue;
287 | }
288 |
289 | break;
290 | }
291 |
292 | // useful data
293 | n = normalize(hitNormal);
294 | nl = dot(n, rayDirection) < 0.0 ? n : -n;
295 | x = rayOrigin + rayDirection * t;
296 |
297 |
298 | if (bounces == 0)
299 | {
300 | objectID = hitObjectID;
301 | }
302 | if (isReflectionTime == FALSE && diffuseCount == 0 && hitObjectID != previousObjectID)
303 | {
304 | objectNormal += n;
305 | objectColor += hitColor;
306 | }
307 |
308 |
309 | if (hitType == LIGHT)
310 | {
311 | if (diffuseCount == 0 && hitEmission == vec3(10))
312 | dynamicSurface = 1.0;
313 |
314 | if (diffuseCount == 0 && isReflectionTime == FALSE)
315 | pixelSharpness = 1.0;
316 |
317 | if (isReflectionTime == TRUE && bounceIsSpecular == TRUE)
318 | {
319 | objectNormal += nl;
320 | //objectColor = hitColor;
321 | objectID += hitObjectID;
322 | }
323 |
324 | if (sampleLight == TRUE)
325 | accumCol += mask * hitEmission;
326 | else if (bounceIsSpecular == TRUE)
327 | {
328 | accumCol += mask * clamp(hitEmission, 0.0, 1.0);
329 | }
330 |
331 | if (willNeedDiffuseBounceRay == TRUE)
332 | {
333 | mask = diffuseBounceMask;
334 | rayOrigin = diffuseBounceRayOrigin;
335 | rayDirection = diffuseBounceRayDirection;
336 |
337 | willNeedDiffuseBounceRay = FALSE;
338 | bounceIsSpecular = FALSE;
339 | sampleLight = FALSE;
340 | isDiffuseBounceTime = TRUE;
341 | isReflectionTime = FALSE;
342 | diffuseCount = 1;
343 | continue;
344 | }
345 |
346 | if (willNeedReflectionRay == TRUE)
347 | {
348 | mask = reflectionMask;
349 | rayOrigin = reflectionRayOrigin;
350 | rayDirection = reflectionRayDirection;
351 |
352 | willNeedReflectionRay = FALSE;
353 | bounceIsSpecular = TRUE;
354 | sampleLight = FALSE;
355 | isReflectionTime = TRUE;
356 | isDiffuseBounceTime = FALSE;
357 | diffuseCount = 0;
358 | continue;
359 | }
360 | // reached a light, so we can exit
361 | break;
362 | }
363 |
364 |
365 |
366 |
367 |
368 |
369 | // make player's background mirror reflection a solid plastic surface
370 | if (lastHitWasSpec == TRUE && hitType == REFR)
371 | hitType = COAT;
372 |
373 |
374 | if (hitType == DIFF) // Ideal DIFFUSE reflection
375 | {
376 | diffuseCount++;
377 |
378 | mask *= hitColor;
379 |
380 | bounceIsSpecular = FALSE;
381 |
382 | rayOrigin = x + nl * uEPS_intersect;
383 |
384 | if (diffuseCount == 1)
385 | {
386 | diffuseBounceMask = mask;
387 | diffuseBounceRayOrigin = rayOrigin;
388 | diffuseBounceRayDirection = randomCosWeightedDirectionInHemisphere(nl);
389 | willNeedDiffuseBounceRay = TRUE;
390 | }
391 |
392 | // if (diffuseCount == 1)
393 | // {
394 | // rayDirection = sampleSphereLight(x, nl, spheres[0], weight);
395 | // mask *= 2.0;
396 | // }
397 | // else
398 | // rayDirection = sampleQuadLight(x, nl, quads[0], weight);
399 | if (distance(x, uBallPos) < rng() * 100.0 && rng() < dot(nl, normalize(uBallPos - x)))
400 | rayDirection = sampleSphereLight(x, nl, spheres[0], weight);
401 | else
402 | rayDirection = sampleQuadLight(x, nl, quads[0], weight);
403 |
404 | mask *= weight;
405 | sampleLight = TRUE;
406 | continue;
407 |
408 | } // end if (hitType == DIFF)
409 |
410 |
411 | if (hitType == SPEC) // Ideal SPECULAR reflection
412 | {
413 | lastHitWasSpec = TRUE;
414 |
415 | mask *= hitColor;
416 |
417 | rayDirection = reflect(rayDirection, nl);
418 | rayOrigin = x + nl * uEPS_intersect;
419 |
420 | continue;
421 | }
422 |
423 |
424 |
425 | if (hitType == REFR) // Ideal dielectric REFRACTION
426 | {
427 | nc = 1.0; // IOR of Air
428 | nt = 1.5; // IOR of common Glass
429 | Re = calcFresnelReflectance(rayDirection, n, nc, nt, ratioIoR);
430 | Tr = 1.0 - Re;
431 |
432 | if (Re == 1.0)
433 | {
434 | rayDirection = reflect(rayDirection, nl);
435 | rayOrigin = x + nl * uEPS_intersect;
436 | continue;
437 | }
438 |
439 | if (bounces == 0 || (bounces == 1 && hitObjectID != objectID && bounceIsSpecular == TRUE))
440 | {
441 | reflectionMask = mask * Re;
442 | reflectionRayDirection = reflect(rayDirection, nl); // reflect ray from surface
443 | reflectionRayOrigin = x + nl * uEPS_intersect;
444 | willNeedReflectionRay = TRUE;
445 | }
446 |
447 | // transmit ray through surface
448 | mask *= hitColor;
449 | mask *= Tr;
450 |
451 | //rayDirection = refract(rayDirection, nl, ratioIoR);
452 | rayDirection = rayDirection;
453 | rayOrigin = x - nl * uEPS_intersect;
454 |
455 | // if (diffuseCount == 1)
456 | // bounceIsSpecular = TRUE; // turn on refracting caustics
457 |
458 | continue;
459 |
460 | } // end if (hitType == REFR)
461 |
462 |
463 | if (hitType == COAT) // Diffuse object underneath with ClearCoat on top
464 | {
465 | nc = 1.0; // IOR of Air
466 | nt = 1.4; // IOR of Clear Coat
467 | Re = calcFresnelReflectance(rayDirection, nl, nc, nt, ratioIoR);
468 | Tr = 1.0 - Re;
469 |
470 | if (hitColor == vec3(0.7, 0.1, 1.0) && (bounces == 0 || previousIntersecType == REFR))
471 | {
472 | dynamicSurface = 1.0;
473 | }
474 |
475 | if (bounces == 0 || (bounces == 1 && hitObjectID != objectID && bounceIsSpecular == TRUE))
476 | {
477 | reflectionMask = mask * Re;
478 | reflectionRayDirection = reflect(rayDirection, nl); // reflect ray from surface
479 | reflectionRayOrigin = x + nl * uEPS_intersect;
480 | willNeedReflectionRay = TRUE;
481 | }
482 |
483 | diffuseCount++;
484 |
485 | mask *= Tr;
486 | mask *= hitColor;
487 |
488 | bounceIsSpecular = FALSE;
489 |
490 | rayOrigin = x + nl * uEPS_intersect;
491 |
492 | // if (diffuseCount == 1)
493 | // {
494 | // diffuseBounceMask = mask;
495 | // diffuseBounceRayOrigin = rayOrigin;
496 | // diffuseBounceRayDirection = randomCosWeightedDirectionInHemisphere(nl);
497 | // willNeedDiffuseBounceRay = TRUE;
498 | // }
499 |
500 | if (distance(x, uBallPos) < rng() * 100.0 && rand() < dot(nl, normalize(uBallPos - x)))
501 | rayDirection = sampleSphereLight(x, nl, spheres[0], weight);
502 | else
503 | rayDirection = sampleQuadLight(x, nl, quads[0], weight);
504 |
505 | mask *= weight;
506 | sampleLight = TRUE;
507 | continue;
508 |
509 | } //end if (hitType == COAT)
510 |
511 |
512 | } // end for (int bounces = 0; bounces < 10; bounces++)
513 |
514 |
515 | return max(vec3(0), accumCol);
516 |
517 | } // end vec3 CalculateRadiance(Ray r)
518 |
519 |
520 |
521 | //-----------------------------------------------------------------------
522 | void SetupScene(void)
523 | //-----------------------------------------------------------------------
524 | {
525 | vec3 z = vec3(0);// No color value, Black
526 | vec3 L1 = vec3(1.0, 0.7, 0.4) * 30.0;// Bright light
527 | float lX = 10.0;
528 | float lY = uHalfRoomDimensions.y - 3.0;
529 | float lZ = 10.0;
530 |
531 | spheres[0] = Sphere( 5.0, uBallPos, vec3(10), z, LIGHT);// Game Ball
532 |
533 | boxes[0] = Box( vec3(-uHalfRoomDimensions.x + 1.0, uBallPos.y - 5.0, uBallPos.z - 10.0), vec3(-uHalfRoomDimensions.x + 2.0, uBallPos.y + 5.0, uBallPos.z + 10.0), z, vec3(1.0, 0.765557, 0.336057), SPEC);// Gold Metal Box left
534 | boxes[1] = Box( vec3( uHalfRoomDimensions.x - 2.0, uBallPos.y - 5.0, uBallPos.z - 10.0), vec3( uHalfRoomDimensions.x - 1.0, uBallPos.y + 5.0, uBallPos.z + 10.0), z, vec3(1.0), SPEC);// Aluminum Metal Box right
535 | boxes[2] = Box( vec3(uBallPos.x - 5.0, uHalfRoomDimensions.y - 2.0, uBallPos.z - 10.0), vec3(uBallPos.x + 5.0, uHalfRoomDimensions.y - 1.0, uBallPos.z + 10.0), z, vec3(0.955008, 0.637427, 0.538163), SPEC);// Copper Metal Box ceiling
536 | boxes[3] = Box( vec3(uBallPos.x - 5.0, -uHalfRoomDimensions.y + 1.0, uBallPos.z - 10.0), vec3(uBallPos.x + 5.0, -uHalfRoomDimensions.y + 2.0, uBallPos.z + 10.0), z, vec3(0.955008, 0.637427, 0.538163), SPEC);// Copper Metal Box floor
537 | boxes[4] = Box( vec3(uPlayerPos.x-uPaddleRadX, uPlayerPos.y-uPaddleRadY, uPlayerPos.z), vec3(uPlayerPos.x+uPaddleRadX, uPlayerPos.y+uPaddleRadY, uPlayerPos.z+3.0), z, vec3(0.1, 0.7, 1.0), uCutSceneIsPlaying ? COAT : REFR);// Player paddle (0.1,0.7,1.0)
538 | boxes[5] = Box( vec3(uComputerPos.x-uPaddleRadX, uComputerPos.y-uPaddleRadY, uComputerPos.z), vec3(uComputerPos.x+uPaddleRadX, uComputerPos.y+uPaddleRadY, uComputerPos.z+3.0), z, vec3(0.7, 0.1, 1.0), COAT);// Computer A.I. paddle (0.7, 0.1, 1.0)
539 | boxes[6] = Box( -uHalfRoomDimensions, uHalfRoomDimensions, z, vec3(1), COAT);
540 |
541 | quads[0] = Quad( vec3(0,-1, 0), vec3(-lX,lY,-lZ), vec3(lX,lY,-lZ), vec3(lX,lY,lZ), vec3(-lX,lY,lZ), L1, z, LIGHT);// rectangular Area Light in ceiling
542 | }
543 |
544 |
545 |
546 | // tentFilter from Peter Shirley's 'Realistic Ray Tracing (2nd Edition)' book, pg. 60
547 | float tentFilter(float x)
548 | {
549 | return (x < 0.5) ? sqrt(2.0 * x) - 1.0 : 1.0 - sqrt(2.0 - (2.0 * x));
550 | }
551 |
552 |
553 | void main( void )
554 | {
555 | vec3 camRight = vec3( uCameraMatrix[0][0], uCameraMatrix[0][1], uCameraMatrix[0][2]);
556 | vec3 camUp = vec3( uCameraMatrix[1][0], uCameraMatrix[1][1], uCameraMatrix[1][2]);
557 | vec3 camForward = vec3(-uCameraMatrix[2][0], -uCameraMatrix[2][1], -uCameraMatrix[2][2]);
558 | // the following is not needed - three.js has a built-in uniform named cameraPosition
559 | //vec3 camPos = vec3( uCameraMatrix[3][0], uCameraMatrix[3][1], uCameraMatrix[3][2]);
560 |
561 | // calculate unique seed for rng() function
562 | seed = uvec2(uFrameCounter, uFrameCounter + 1.0) * uvec2(gl_FragCoord);
563 | // initialize rand() variables
564 | randNumber = 0.0; // the final randomly-generated number (range: 0.0 to 1.0)
565 | blueNoise = texelFetch(tBlueNoiseTexture, ivec2(mod(floor(gl_FragCoord.xy), 128.0)), 0).r;
566 |
567 | vec2 pixelOffset = vec2( tentFilter(rand()), tentFilter(rand()) );
568 | //pixelOffset *= 0.5;//uCameraIsMoving ? 0.5 : 1.0;
569 |
570 | // we must map pixelPos into the range -1.0 to +1.0
571 | vec2 pixelPos = ((gl_FragCoord.xy + vec2(0.5) + pixelOffset) / uResolution) * 2.0 - 1.0;
572 |
573 | vec3 rayDir = normalize( pixelPos.x * camRight * uULen + pixelPos.y * camUp * uVLen + camForward );
574 |
575 | /* // depth of field
576 | vec3 focalPoint = uFocusDistance * rayDir;
577 | float randomAngle = rng() * TWO_PI; // pick random point on aperture
578 | float randomRadius = rng() * uApertureSize;
579 | vec3 randomAperturePos = ( cos(randomAngle) * camRight + sin(randomAngle) * camUp ) * sqrt(randomRadius);
580 | // point on aperture to focal point
581 | vec3 finalRayDir = normalize(focalPoint - randomAperturePos); */
582 |
583 | rayOrigin = cameraPosition;
584 | rayDirection = rayDir;
585 |
586 | SetupScene();
587 |
588 | // Edge Detection - don't want to blur edges where either surface normals change abruptly (i.e. room wall corners), objects overlap each other (i.e. edge of a foreground sphere in front of another sphere right behind it),
589 | // or an abrupt color variation on the same smooth surface, even if it has similar surface normals (i.e. checkerboard pattern). Want to keep all of these cases as sharp as possible - no blur filter will be applied.
590 | vec3 objectNormal, objectColor;
591 | float objectID = -INFINITY;
592 | float pixelSharpness = 0.0;
593 | float dynamicSurface = 0.0;
594 |
595 | // perform path tracing and get resulting pixel color
596 | vec4 currentPixel = vec4( vec3(CalculateRadiance(objectNormal, objectColor, objectID, pixelSharpness, dynamicSurface)), 0.0 );
597 |
598 | // if difference between normals of neighboring pixels is less than the first edge0 threshold, the white edge line effect is considered off (0.0)
599 | float edge0 = 0.2; // edge0 is the minimum difference required between normals of neighboring pixels to start becoming a white edge line
600 | // any difference between normals of neighboring pixels that is between edge0 and edge1 smoothly ramps up the white edge line brightness (smoothstep 0.0-1.0)
601 | float edge1 = 0.6; // once the difference between normals of neighboring pixels is >= this edge1 threshold, the white edge line is considered fully bright (1.0)
602 | float difference_Nx = fwidth(objectNormal.x);
603 | float difference_Ny = fwidth(objectNormal.y);
604 | float difference_Nz = fwidth(objectNormal.z);
605 | float normalDifference = smoothstep(edge0, edge1, difference_Nx) + smoothstep(edge0, edge1, difference_Ny) + smoothstep(edge0, edge1, difference_Nz);
606 |
607 | float objectDifference = min(fwidth(objectID), 1.0);
608 |
609 | float colorDifference = (fwidth(objectColor.r) + fwidth(objectColor.g) + fwidth(objectColor.b)) > 0.0 ? 1.0 : 0.0;
610 | // white-line debug visualization for normal difference
611 | //currentPixel.rgb += (rng() * 1.5) * vec3(normalDifference);
612 | // white-line debug visualization for object difference
613 | //currentPixel.rgb += (rng() * 1.5) * vec3(objectDifference);
614 | // white-line debug visualization for color difference
615 | //currentPixel.rgb += (rng() * 1.5) * vec3(colorDifference);
616 | // white-line debug visualization for all 3 differences
617 | //currentPixel.rgb += (rng() * 1.5) * vec3( clamp(max(normalDifference, max(objectDifference, colorDifference)), 0.0, 1.0) );
618 |
619 | vec4 previousPixel = texelFetch(tPreviousTexture, ivec2(gl_FragCoord.xy), 0);
620 |
621 | if (dynamicSurface > 0.0 || previousPixel.a == 0.99)
622 | {
623 | previousPixel = vec4(0); // motion-blur trail amount (old image)
624 | }
625 | else if (uCameraIsMoving) // camera is currently moving
626 | {
627 | previousPixel.rgb *= 0.6; // motion-blur trail amount (old image)
628 | currentPixel.rgb *= 0.4; // brightness of new image (noisy)
629 |
630 | previousPixel.a = 0.0;
631 | }
632 | else
633 | {
634 | previousPixel.rgb *= 0.9; // motion-blur trail amount (old image)
635 | currentPixel.rgb *= 0.1; // brightness of new image (noisy)
636 | }
637 |
638 | currentPixel.a = pixelSharpness;
639 |
640 | // check for all edges that are not light sources
641 | if (pixelSharpness < 1.01 && (colorDifference >= 1.0 || normalDifference >= 0.9 || objectDifference >= 1.0)) // all other edges
642 | currentPixel.a = pixelSharpness = 1.0;
643 |
644 | // makes light source edges (shape boundaries) more stable
645 | // if (previousPixel.a == 1.01)
646 | // currentPixel.a = 1.01;
647 |
648 | // makes sharp edges more stable
649 | if (previousPixel.a == 1.0)
650 | currentPixel.a = 1.0;
651 |
652 | // for dynamic scenes (to clear out old, dark, sharp pixel trails left behind from moving objects)
653 | if (previousPixel.a == 1.0 && rng() < 0.05)
654 | currentPixel.a = 0.0;
655 |
656 |
657 | pc_fragColor = vec4(previousPixel.rgb + currentPixel.rgb, dynamicSurface > 0.0 ? 0.99 : currentPixel.a);
658 | }
659 |
--------------------------------------------------------------------------------
/shaders/ScreenCopy_Fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | precision highp int;
3 | precision highp sampler2D;
4 |
5 | uniform sampler2D tPathTracedImageTexture;
6 |
7 | void main()
8 | {
9 | pc_fragColor = texelFetch(tPathTracedImageTexture, ivec2(gl_FragCoord.xy), 0);
10 | }
--------------------------------------------------------------------------------
/shaders/ScreenOutput_Fragment.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | precision highp int;
3 | precision highp sampler2D;
4 |
5 | uniform sampler2D tPathTracedImageTexture;
6 | uniform float uSampleCounter;
7 | uniform float uOneOverSampleCounter;
8 | uniform float uPixelEdgeSharpness;
9 | uniform float uEdgeSharpenSpeed;
10 | //uniform float uFilterDecaySpeed;
11 | uniform bool uCameraIsMoving;
12 | uniform bool uSceneIsDynamic;
13 | uniform bool uUseToneMapping;
14 |
15 | #define TRUE 1
16 | #define FALSE 0
17 |
18 | void main()
19 | {
20 | // First, start with a large blur kernel, which will be used on all diffuse
21 | // surfaces. It will blur out the noise, giving a smoother, more uniform color.
22 | // Starting at the current pixel (centerPixel), the algorithm performs an outward search/walk
23 | // moving to the immediate neighbor pixels around the center pixel, and then out farther to
24 | // more distant neighbors. If the outward walk doesn't encounter any 'edge' pixels, it will continue
25 | // until it reaches the maximum extents of the large kernel (a little less than 7x7 pixels, minus the 4
26 | // corners to give a more rounded kernel filter shape). However, while walking/searching outward from
27 | // the center pixel, if the walk encounters an 'edge' boundary pixel, it will not blend (average in) with
28 | // that pixel, and will stop the search/walk from going any further in that direction. This keeps the edge
29 | // boundary pixels non-blurred, and these edges remain sharp in the final image.
30 |
31 | vec4 m37[37];
32 |
33 | vec2 glFragCoord_xy = gl_FragCoord.xy;
34 |
35 |
36 | m37[ 0] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-1, 3)), 0);
37 | m37[ 1] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 0, 3)), 0);
38 | m37[ 2] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 1, 3)), 0);
39 | m37[ 3] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-2, 2)), 0);
40 | m37[ 4] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-1, 2)), 0);
41 | m37[ 5] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 0, 2)), 0);
42 | m37[ 6] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 1, 2)), 0);
43 | m37[ 7] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 2, 2)), 0);
44 | m37[ 8] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-3, 1)), 0);
45 | m37[ 9] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-2, 1)), 0);
46 | m37[10] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-1, 1)), 0);
47 | m37[11] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 0, 1)), 0);
48 | m37[12] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 1, 1)), 0);
49 | m37[13] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 2, 1)), 0);
50 | m37[14] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 3, 1)), 0);
51 | m37[15] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-3, 0)), 0);
52 | m37[16] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-2, 0)), 0);
53 | m37[17] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-1, 0)), 0);
54 | m37[18] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 0, 0)), 0); // center pixel
55 | m37[19] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 1, 0)), 0);
56 | m37[20] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 2, 0)), 0);
57 | m37[21] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 3, 0)), 0);
58 | m37[22] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-3,-1)), 0);
59 | m37[23] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-2,-1)), 0);
60 | m37[24] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-1,-1)), 0);
61 | m37[25] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 0,-1)), 0);
62 | m37[26] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 1,-1)), 0);
63 | m37[27] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 2,-1)), 0);
64 | m37[28] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 3,-1)), 0);
65 | m37[29] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-2,-2)), 0);
66 | m37[30] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-1,-2)), 0);
67 | m37[31] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 0,-2)), 0);
68 | m37[32] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 1,-2)), 0);
69 | m37[33] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 2,-2)), 0);
70 | m37[34] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2(-1,-3)), 0);
71 | m37[35] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 0,-3)), 0);
72 | m37[36] = texelFetch(tPathTracedImageTexture, ivec2(glFragCoord_xy + vec2( 1,-3)), 0);
73 |
74 |
75 | vec4 centerPixel = m37[18];
76 | vec3 filteredPixelColor, edgePixelColor;
77 | float threshold = 1.0;
78 | int count = 1;
79 | int nextToAnEdgePixel = FALSE;
80 |
81 | // start with center pixel rgb color
82 | filteredPixelColor = centerPixel.rgb;
83 |
84 | // search above
85 | if (m37[11].a < threshold)
86 | {
87 | filteredPixelColor += m37[11].rgb;
88 | count++;
89 | if (m37[5].a < threshold)
90 | {
91 | filteredPixelColor += m37[5].rgb;
92 | count++;
93 | if (m37[1].a < threshold)
94 | {
95 | filteredPixelColor += m37[1].rgb;
96 | count++;
97 | if (m37[0].a < threshold)
98 | {
99 | filteredPixelColor += m37[0].rgb;
100 | count++;
101 | }
102 | if (m37[2].a < threshold)
103 | {
104 | filteredPixelColor += m37[2].rgb;
105 | count++;
106 | }
107 | }
108 | }
109 | }
110 | else
111 | {
112 | nextToAnEdgePixel = TRUE;
113 | }
114 |
115 |
116 |
117 | // search left
118 | if (m37[17].a < threshold)
119 | {
120 | filteredPixelColor += m37[17].rgb;
121 | count++;
122 | if (m37[16].a < threshold)
123 | {
124 | filteredPixelColor += m37[16].rgb;
125 | count++;
126 | if (m37[15].a < threshold)
127 | {
128 | filteredPixelColor += m37[15].rgb;
129 | count++;
130 | if (m37[8].a < threshold)
131 | {
132 | filteredPixelColor += m37[8].rgb;
133 | count++;
134 | }
135 | if (m37[22].a < threshold)
136 | {
137 | filteredPixelColor += m37[22].rgb;
138 | count++;
139 | }
140 | }
141 | }
142 | }
143 | else
144 | {
145 | nextToAnEdgePixel = TRUE;
146 | }
147 |
148 | // search right
149 | if (m37[19].a < threshold)
150 | {
151 | filteredPixelColor += m37[19].rgb;
152 | count++;
153 | if (m37[20].a < threshold)
154 | {
155 | filteredPixelColor += m37[20].rgb;
156 | count++;
157 | if (m37[21].a < threshold)
158 | {
159 | filteredPixelColor += m37[21].rgb;
160 | count++;
161 | if (m37[14].a < threshold)
162 | {
163 | filteredPixelColor += m37[14].rgb;
164 | count++;
165 | }
166 | if (m37[28].a < threshold)
167 | {
168 | filteredPixelColor += m37[28].rgb;
169 | count++;
170 | }
171 | }
172 | }
173 | }
174 | else
175 | {
176 | nextToAnEdgePixel = TRUE;
177 | }
178 |
179 | // search below
180 | if (m37[25].a < threshold)
181 | {
182 | filteredPixelColor += m37[25].rgb;
183 | count++;
184 | if (m37[31].a < threshold)
185 | {
186 | filteredPixelColor += m37[31].rgb;
187 | count++;
188 | if (m37[35].a < threshold)
189 | {
190 | filteredPixelColor += m37[35].rgb;
191 | count++;
192 | if (m37[34].a < threshold)
193 | {
194 | filteredPixelColor += m37[34].rgb;
195 | count++;
196 | }
197 | if (m37[36].a < threshold)
198 | {
199 | filteredPixelColor += m37[36].rgb;
200 | count++;
201 | }
202 | }
203 | }
204 | }
205 | else
206 | {
207 | nextToAnEdgePixel = TRUE;
208 | }
209 |
210 | // search upper-left diagonal
211 | if (m37[10].a < threshold)
212 | {
213 | filteredPixelColor += m37[10].rgb;
214 | count++;
215 | if (m37[3].a < threshold)
216 | {
217 | filteredPixelColor += m37[3].rgb;
218 | count++;
219 | }
220 | if (m37[4].a < threshold)
221 | {
222 | filteredPixelColor += m37[4].rgb;
223 | count++;
224 | }
225 | if (m37[9].a < threshold)
226 | {
227 | filteredPixelColor += m37[9].rgb;
228 | count++;
229 | }
230 | }
231 |
232 | // search upper-right diagonal
233 | if (m37[12].a < threshold)
234 | {
235 | filteredPixelColor += m37[12].rgb;
236 | count++;
237 | if (m37[6].a < threshold)
238 | {
239 | filteredPixelColor += m37[6].rgb;
240 | count++;
241 | }
242 | if (m37[7].a < threshold)
243 | {
244 | filteredPixelColor += m37[7].rgb;
245 | count++;
246 | }
247 | if (m37[13].a < threshold)
248 | {
249 | filteredPixelColor += m37[13].rgb;
250 | count++;
251 | }
252 | }
253 |
254 | // search lower-left diagonal
255 | if (m37[24].a < threshold)
256 | {
257 | filteredPixelColor += m37[24].rgb;
258 | count++;
259 | if (m37[23].a < threshold)
260 | {
261 | filteredPixelColor += m37[23].rgb;
262 | count++;
263 | }
264 | if (m37[29].a < threshold)
265 | {
266 | filteredPixelColor += m37[29].rgb;
267 | count++;
268 | }
269 | if (m37[30].a < threshold)
270 | {
271 | filteredPixelColor += m37[30].rgb;
272 | count++;
273 | }
274 | }
275 |
276 | // search lower-right diagonal
277 | if (m37[26].a < threshold)
278 | {
279 | filteredPixelColor += m37[26].rgb;
280 | count++;
281 | if (m37[27].a < threshold)
282 | {
283 | filteredPixelColor += m37[27].rgb;
284 | count++;
285 | }
286 | if (m37[32].a < threshold)
287 | {
288 | filteredPixelColor += m37[32].rgb;
289 | count++;
290 | }
291 | if (m37[33].a < threshold)
292 | {
293 | filteredPixelColor += m37[33].rgb;
294 | count++;
295 | }
296 | }
297 |
298 |
299 | // divide by total count to get the average
300 | filteredPixelColor *= (1.0 / float(count));
301 |
302 |
303 |
304 | // next, use a smaller blur kernel (13 pixels in roughly circular shape), to help blend the noisy, sharp edge pixels
305 |
306 | // m37[18] is the center pixel
307 | edgePixelColor = m37[ 5].rgb +
308 | m37[10].rgb + m37[11].rgb + m37[12].rgb +
309 | m37[16].rgb + m37[17].rgb + m37[18].rgb + m37[19].rgb + m37[20].rgb +
310 | m37[24].rgb + m37[25].rgb + m37[26].rgb +
311 | m37[31].rgb;
312 |
313 | // if not averaged, the above additions produce white outlines along edges
314 | edgePixelColor *= 0.0769230769; // same as dividing by 13 pixels (1 / 13), to get the average
315 |
316 | if (uSceneIsDynamic) // dynamic scene with moving objects and camera (i.e. a game)
317 | {
318 | if (uCameraIsMoving)
319 | {
320 | if (nextToAnEdgePixel == TRUE)
321 | filteredPixelColor = mix(edgePixelColor, centerPixel.rgb, 0.25);
322 | }
323 | else if (centerPixel.a == 1.0 || nextToAnEdgePixel == TRUE)
324 | filteredPixelColor = mix(edgePixelColor, centerPixel.rgb, 0.5);
325 |
326 | }
327 | if (!uSceneIsDynamic) // static scene (only camera can move)
328 | {
329 | if (uCameraIsMoving)
330 | {
331 | if (nextToAnEdgePixel == TRUE)
332 | filteredPixelColor = mix(edgePixelColor, centerPixel.rgb, 0.25);
333 | }
334 | else if (centerPixel.a == 1.0)
335 | filteredPixelColor = mix(filteredPixelColor, centerPixel.rgb, clamp(uSampleCounter * uEdgeSharpenSpeed, 0.0, 1.0));
336 | // the following statement helps smooth out jagged stairstepping where the blurred filteredPixelColor pixels meet the sharp edges
337 | else if (uSampleCounter > 500.0 && nextToAnEdgePixel == TRUE)
338 | filteredPixelColor = centerPixel.rgb;
339 |
340 | }
341 |
342 | // if the .a value comes into this shader as 1.01, this is an outdoor raymarching demo, and no denoising/blended is needed
343 | if (centerPixel.a == 1.01)
344 | filteredPixelColor = centerPixel.rgb; // no blending, maximum sharpness
345 |
346 |
347 | // final filteredPixelColor processing ////////////////////////////////////
348 |
349 | // average accumulation buffer
350 | filteredPixelColor *= uOneOverSampleCounter;
351 |
352 | // apply tone mapping (brings pixel into 0.0-1.0 rgb color range)
353 | filteredPixelColor = uUseToneMapping ? ReinhardToneMapping(filteredPixelColor) : filteredPixelColor;
354 |
355 | // lastly, apply gamma correction (gives more intensity/brightness range where it's needed)
356 | pc_fragColor = vec4(sqrt(filteredPixelColor), 1.0);
357 | }
358 |
--------------------------------------------------------------------------------
/shaders/common_PathTracing_Vertex.glsl:
--------------------------------------------------------------------------------
1 | precision highp float;
2 | precision highp int;
3 |
4 | void main()
5 | {
6 | gl_Position = vec4( position, 1.0 );
7 | }
--------------------------------------------------------------------------------
/sounds/ballMiss.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erichlof/PathTracedPong/7cbfec9503419eb0175fc2e911324d46db7f2c91/sounds/ballMiss.mp3
--------------------------------------------------------------------------------
/sounds/highPing.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erichlof/PathTracedPong/7cbfec9503419eb0175fc2e911324d46db7f2c91/sounds/highPing.mp3
--------------------------------------------------------------------------------
/sounds/lowPing.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erichlof/PathTracedPong/7cbfec9503419eb0175fc2e911324d46db7f2c91/sounds/lowPing.mp3
--------------------------------------------------------------------------------
/sounds/synthHit.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erichlof/PathTracedPong/7cbfec9503419eb0175fc2e911324d46db7f2c91/sounds/synthHit.mp3
--------------------------------------------------------------------------------
/sounds/winner.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erichlof/PathTracedPong/7cbfec9503419eb0175fc2e911324d46db7f2c91/sounds/winner.mp3
--------------------------------------------------------------------------------
/textures/BlueNoise_R_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erichlof/PathTracedPong/7cbfec9503419eb0175fc2e911324d46db7f2c91/textures/BlueNoise_R_128.png
--------------------------------------------------------------------------------