1 | <!DOCTYPE html>
|
2 | <html lang="en">
|
3 | <head>
|
4 | <meta charset="utf-8">
|
5 | <title>FluxRenderer.js - Documentation</title>
|
6 |
|
7 | <script src="scripts/prettify/prettify.js"></script>
|
8 | <script src="scripts/prettify/lang-css.js"></script>
|
9 | |
10 |
|
11 |
|
12 | <link type="text/css" rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css">
|
13 | <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
|
14 | <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
|
15 | </head>
|
16 | <body>
|
17 |
|
18 | <input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
19 | <label for="nav-trigger" class="navicon-button x">
|
20 | <div class="navicon"></div>
|
21 | </label>
|
22 |
|
23 | <label for="nav-trigger" class="overlay"></label>
|
24 |
|
25 | <nav>
|
26 | <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="EdgesHelper.html">EdgesHelper</a><ul class='methods'><li data-type='method'><a href="EdgesHelper.html#.AddEdges">AddEdges</a></li></ul></li><li><a href="FluxCameras.html">FluxCameras</a><ul class='methods'><li data-type='method'><a href="FluxCameras.html#.cameraFromJSON">cameraFromJSON</a></li><li data-type='method'><a href="FluxCameras.html#.cameraToJSON">cameraToJSON</a></li><li data-type='method'><a href="FluxCameras.html#fromJSON">fromJSON</a></li><li data-type='method'><a href="FluxCameras.html#getCamera">getCamera</a></li><li data-type='method'><a href="FluxCameras.html#setView">setView</a></li><li data-type='method'><a href="FluxCameras.html#toJSON">toJSON</a></li><li data-type='method'><a href="FluxCameras.html#updateCamera">updateCamera</a></li></ul></li><li><a href="FluxRenderContext.html">FluxRenderContext</a><ul class='methods'><li data-type='method'><a href="FluxRenderContext.html#.getNextContext">getNextContext</a></li></ul></li><li><a href="FluxRenderer.html">FluxRenderer</a><ul class='methods'><li data-type='method'><a href="FluxRenderer.html#addControls">addControls</a></li><li data-type='method'><a href="FluxRenderer.html#anyValidPrims">anyValidPrims</a></li><li data-type='method'><a href="FluxRenderer.html#detach">detach</a></li><li data-type='method'><a href="FluxRenderer.html#doRender">doRender</a></li><li data-type='method'><a href="FluxRenderer.html#focus">focus</a></li><li data-type='method'><a href="FluxRenderer.html#fromJSON">fromJSON</a></li><li data-type='method'><a href="FluxRenderer.html#getGlCanvas">getGlCanvas</a></li><li data-type='method'><a href="FluxRenderer.html#getSelection">getSelection</a></li><li data-type='method'><a href="FluxRenderer.html#homeCamera">homeCamera</a></li><li data-type='method'><a href="FluxRenderer.html#setClearColor">setClearColor</a></li><li data-type='method'><a href="FluxRenderer.html#setEdgesMode">setEdgesMode</a></li><li data-type='method'><a href="FluxRenderer.html#setHelpersVisible">setHelpersVisible</a></li><li data-type='method'><a href="FluxRenderer.html#setLights">setLights</a></li><li data-type='method'><a href="FluxRenderer.html#setModel">setModel</a></li><li data-type='method'><a href="FluxRenderer.html#setSelection">setSelection</a></li><li data-type='method'><a href="FluxRenderer.html#setSelectionMaterial">setSelectionMaterial</a></li><li data-type='method'><a href="FluxRenderer.html#setSize">setSize</a></li><li data-type='method'><a href="FluxRenderer.html#setView">setView</a></li><li data-type='method'><a href="FluxRenderer.html#toJSON">toJSON</a></li></ul></li><li><a href="FluxViewport.html">FluxViewport</a><ul class='methods'><li data-type='method'><a href="FluxViewport.html#.getChangeEvent">getChangeEvent</a></li><li data-type='method'><a href="FluxViewport.html#.getEdgesModes">getEdgesModes</a></li><li data-type='method'><a href="FluxViewport.html#.getEvents">getEvents</a></li><li data-type='method'><a href="FluxViewport.html#.getSelectionModes">getSelectionModes</a></li><li data-type='method'><a href="FluxViewport.html#.getViews">getViews</a></li><li data-type='method'><a href="FluxViewport.html#.isKnownGeom">isKnownGeom</a></li><li data-type='method'><a href="FluxViewport.html#activateShadows">activateShadows</a></li><li data-type='method'><a href="FluxViewport.html#addControls">addControls</a></li><li data-type='method'><a href="FluxViewport.html#downloadState">downloadState</a></li><li data-type='method'><a href="FluxViewport.html#focus">focus</a></li><li data-type='method'><a href="FluxViewport.html#fromJSON">fromJSON</a></li><li data-type='method'><a href="FluxViewport.html#getAutoFocus">getAutoFocus</a></li><li data-type='method'><a href="FluxViewport.html#getGlCanvas">getGlCanvas</a></li><li data-type='method'><a href="FluxViewport.html#getJson">getJson</a></li><li data-type='method'><a href="FluxViewport.html#getObjectMap">getObjectMap</a></li><li data-type='method'><a href="FluxViewport.html#getSelection">getSelection</a></li><li data-type='method'><a href="FluxViewport.html#homeCamera">homeCamera</a></li><li data-type='method'><a href="FluxViewport.html#render">render</a></li><li data-type='method'><a href="FluxViewport.html#setAutoFocus">setAutoFocus</a></li><li data-type='method'><a href="FluxViewport.html#setClearColor">setClearColor</a></li><li data-type='method'><a href="FluxViewport.html#setEdgesMode">setEdgesMode</a></li><li data-type='method'><a href="FluxViewport.html#setFogDensity">setFogDensity</a></li><li data-type='method'><a href="FluxViewport.html#setGeometryEntity">setGeometryEntity</a></li><li data-type='method'><a href="FluxViewport.html#setGeometryJson">setGeometryJson</a></li><li data-type='method'><a href="FluxViewport.html#setHelpersVisible">setHelpersVisible</a></li><li data-type='method'><a href="FluxViewport.html#setSelection">setSelection</a></li><li data-type='method'><a href="FluxViewport.html#setSelectionMaterial">setSelectionMaterial</a></li><li data-type='method'><a href="FluxViewport.html#setSize">setSize</a></li><li data-type='method'><a href="FluxViewport.html#setTessUrl">setTessUrl</a></li><li data-type='method'><a href="FluxViewport.html#setupDefaultLighting">setupDefaultLighting</a></li><li data-type='method'><a href="FluxViewport.html#setView">setView</a></li><li data-type='method'><a href="FluxViewport.html#toJSON">toJSON</a></li></ul></li></ul><h3>Global</h3><ul><li><a href="global.html#_deleteFromScene">_deleteFromScene</a></li><li><a href="global.html#_removeGeometries">_removeGeometries</a></li></ul>
|
27 | </nav>
|
28 |
|
29 | <div id="main">
|
30 |
|
31 | <h1 class="page-title">FluxRenderer.js</h1>
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | <section>
|
40 | <article>
|
41 | <pre class="prettyprint source linenums"><code>'use strict';
|
42 |
|
43 | import * as THREE from 'three';
|
44 | import EdgesHelper from './EdgesHelper.js';
|
45 | import FluxCameras from './FluxCameras.js';
|
46 | import FluxHelpers from './helpers/FluxHelpers.js';
|
47 | import FluxRenderContext from './FluxRenderContext.js';
|
48 | import CameraControls from './controls/CameraControls.js';
|
49 | import SelectionControls from './controls/SelectionControls.js';
|
50 | import * as constants from './controls/constants.js';
|
51 |
|
52 | /**
|
53 | * Class wrapping the three.js renderer with more build in functionality.
|
54 | * Context swapping lets a single OpenGL context and canvas be used for multiple renderers.
|
55 | * @class FluxRenderer
|
56 | * @param {Element} domParent The div container for the canvas
|
57 | * @param {Number} width The width of the canvas
|
58 | * @param {Number} height The height of the canvas
|
59 | * @param {Enumeration} selection Whether to enable user selection
|
60 | */
|
61 | export default function FluxRenderer(domParent, width, height, selection) {
|
62 | this.id = FluxRenderer.nextId++;
|
63 |
|
64 | // Dom element that wraps the canvas
|
65 | this._domParent = domParent;
|
66 |
|
67 | // Current three.js geometry to render
|
68 | this._model = null;
|
69 |
|
70 | // The object containing the lights in the scene
|
71 | this._lights = null;
|
72 |
|
73 | // The context that contains the renderer and corresponds to a canvas
|
74 | // Create renderer for the first time.
|
75 | this._context = FluxRenderContext.getNewContext();
|
76 |
|
77 | this._width = width;
|
78 | this._height = height;
|
79 |
|
80 | this._createCacheCanvas(width, height);
|
81 |
|
82 | this.setClearColor(0xC5CDCC);
|
83 |
|
84 | this._cameras = new FluxCameras(width, height);
|
85 | this._helpers = new FluxHelpers();
|
86 | this._helpersScene = new THREE.Scene();
|
87 | this._helpersScene.add(this._helpers);
|
88 |
|
89 | // Scene containing geometry to be rendered in this viewport THREE.Scene
|
90 | this._scene = new THREE.Scene();
|
91 |
|
92 | this._controls = [];
|
93 | // Camera to be rendered with.Any instance of `THREE.Camera` can be set here.
|
94 | var _this = this;
|
95 | this._editorControls = this.addControls(CameraControls);
|
96 | this._editorControls.addEventListener(constants.Events.CHANGE, function(event) {
|
97 | _this._cameras.updateCamera(_this._width, _this._height);
|
98 | _this.dispatchEvent(event);
|
99 | });
|
100 | this._selectionControls = this.addControls(SelectionControls);
|
101 | this._selectionControls.setMode(selection);
|
102 |
|
103 | // Fog object for this viewport constructed from color and density
|
104 | this._fog = new THREE.FogExp2( this._clearColor, 0.0 );
|
105 | this._scene.fog = this._fog;
|
106 |
|
107 | // Scene containing edges geometry for hidden line rendering
|
108 | this._edgesScene = new THREE.Scene();
|
109 | this._edgesMode = EdgesHelper.EDGES_MODES.NONE;
|
110 | }
|
111 |
|
112 | FluxRenderer.prototype = Object.create( THREE.EventDispatcher.prototype );
|
113 | FluxRenderer.prototype.constructor = FluxRenderer;
|
114 |
|
115 | // Used for debugging issues with _setHost
|
116 | FluxRenderer.nextId = 0;
|
117 |
|
118 | /**
|
119 | * Set the lights used to illuminate the scene.
|
120 | * @param {THREE.Object3D} lights Object with lights as children
|
121 | */
|
122 | FluxRenderer.prototype.setLights = function(lights) {
|
123 | if (this._lights) {
|
124 | this._scene.remove(this._lights);
|
125 | }
|
126 | this._lights = lights;
|
127 | this._scene.add(this._lights);
|
128 | };
|
129 |
|
130 | /**
|
131 | * Remove the geometry objects from the THREE registry so they can be garbage collected
|
132 | * @param {THREE.Object3D} obj The object being removed
|
133 | */
|
134 | function _removeGeometries(obj) {
|
135 | if (obj.geometry) {
|
136 | obj.geometry.dispose();
|
137 | }
|
138 | }
|
139 |
|
140 | /**
|
141 | * Remove an object from the scene and clean up memory
|
142 | * @param {THREE.Scene} scene Scene containing the model
|
143 | * @param {THREE.Object3D} model The geometry to remove
|
144 | */
|
145 | function _deleteFromScene(scene, model) {
|
146 | if (!model || !scene) return;
|
147 | scene.remove(model);
|
148 | model.traverse(_removeGeometries);
|
149 | }
|
150 |
|
151 | /**
|
152 | * Add a new plugin for user interaction controls.
|
153 | * See ViewportControls.js for more information.
|
154 | * @param {ViewportControls} CustomControls A constructor that implements the controls interface.
|
155 | * @return {CustomControls} The new instance
|
156 | */
|
157 | FluxRenderer.prototype.addControls = function(CustomControls) {
|
158 | var customControls = new CustomControls(this._cameras.getCamera(), this._scene, this._domParent, this._width, this._height);
|
159 | var _this = this;
|
160 | customControls.addEventListener(constants.Events.CHANGE, function (event) {
|
161 | _this.dispatchEvent(event);
|
162 | });
|
163 | this._controls.push(customControls);
|
164 | return customControls;
|
165 | };
|
166 |
|
167 | /**
|
168 | * Define the material that is applied on selected objects
|
169 | * @param {Object} data Flux json description of a material
|
170 | */
|
171 | FluxRenderer.prototype.setSelectionMaterial = function(data) {
|
172 | this._selectionControls.setMaterial(data);
|
173 | };
|
174 |
|
175 | /**
|
176 | * Get the currently selected geometry
|
177 | * @return {THREE.Object3D} Current selection
|
178 | */
|
179 | FluxRenderer.prototype.getSelection = function() {
|
180 | return this._selectionControls.getSelection();
|
181 | };
|
182 |
|
183 | /**
|
184 | * set the currently selected geometry
|
185 | * @param {THREE.Object3D} object New selection
|
186 | */
|
187 | FluxRenderer.prototype.setSelection = function(object) {
|
188 | this._selectionControls.setSelection(object);
|
189 | };
|
190 |
|
191 | /**
|
192 | * Set the object to render
|
193 | * Replaces old render contents
|
194 | * @param {THREE.Object3D} model What to render
|
195 | */
|
196 | FluxRenderer.prototype.setModel = function(model) {
|
197 | if (this._model) {
|
198 | _deleteFromScene(this._scene, this._model);
|
199 | _deleteFromScene(this._edgesScene, this._model.edgesHelper);
|
200 | }
|
201 | this._model = model;
|
202 | if (this._model) {
|
203 | this._scene.add(this._model);
|
204 | if (EdgesHelper.AddEdges(this._model, this._edgesMode)) {
|
205 | this._edgesScene.add(this._model.edgesHelper);
|
206 | }
|
207 | }
|
208 | };
|
209 |
|
210 | /**
|
211 | * Set the edges rendering mode for hidden line rendering
|
212 | * @param {EdgesHelper.EDGES_MODES} mode Whether to render front, back, both or none
|
213 | */
|
214 | FluxRenderer.prototype.setEdgesMode = function(mode) {
|
215 | this._edgesMode = mode;
|
216 | };
|
217 |
|
218 | /**
|
219 | * Restore the camera to a default location
|
220 | */
|
221 | FluxRenderer.prototype.homeCamera = function() {
|
222 | this._editorControls.focus(this._helpers);
|
223 | };
|
224 |
|
225 | /**
|
226 | * Focus the controls' current camera on an object.
|
227 | * This function will focus on the union of object and all of it's visible children.
|
228 | * @param {THREE.Object3D} [obj] The scene object to focus on.
|
229 | */
|
230 | FluxRenderer.prototype.focus = function(obj) {
|
231 | if (!this._model && !obj) return;
|
232 | if (obj) {
|
233 | this._editorControls.focus(obj);
|
234 | } else {
|
235 | var selection = this._selectionControls.getSelectionSphere();
|
236 | if (selection) {
|
237 | this._editorControls.focus(selection);
|
238 | } else {
|
239 | this._editorControls.focus(this._model);
|
240 | }
|
241 | }
|
242 | // Changing the controls here triggers a render
|
243 | };
|
244 |
|
245 | /**
|
246 | * Set the clear color (background) for WebGL canvas
|
247 | * @param {String|Number} color Hexadecimal or a CSS-style string
|
248 | * @param {Number} alpha Opacity
|
249 | */
|
250 | FluxRenderer.prototype.setClearColor = function(color, alpha) {
|
251 | this._clearColor = new THREE.Color(color);
|
252 | this._clearAlpha = alpha;
|
253 | };
|
254 |
|
255 | /**
|
256 | * Whether to draw helpers (axis and grid)
|
257 | *
|
258 | * @param {Boolean} visible False to hide them
|
259 | */
|
260 | FluxRenderer.prototype.setHelpersVisible = function(visible) {
|
261 | this._helpersScene.visible = !!visible;
|
262 | };
|
263 |
|
264 | /**
|
265 | * Set up a new canvas used for storing a cached image.
|
266 | * The cache image is populated when this renderer loses it's context.
|
267 | * @private
|
268 | * @param {Number} width The width of the canvas
|
269 | * @param {Number} height The height of the canvas
|
270 | */
|
271 | FluxRenderer.prototype._createCacheCanvas = function(width, height) {
|
272 | if (this._cacheCanvas) return;
|
273 | // The canvas used to store a cached image of the previous render when all the WebGL contexts are in use with other renderers
|
274 | this._cacheCanvas = document.createElement('canvas');
|
275 | this._cacheCanvas.width = width;
|
276 | this._cacheCanvas.height = height;
|
277 | this._cacheCanvas.style.position = 'absolute';
|
278 | this._cacheCanvas.style['user-select'] = 'none';
|
279 | this._cacheCanvas.style['-webkit-user-select'] = 'none';
|
280 | this._domParent.appendChild(this._cacheCanvas);
|
281 |
|
282 | // Canvas2D used to store framebuffer pixels after renderer.domElement migration.
|
283 | this.ctx = this._cacheCanvas.getContext('2d');
|
284 | };
|
285 |
|
286 | /**
|
287 | * Destructor to prevent future rendering after being unloaded
|
288 | */
|
289 | FluxRenderer.prototype.detach = function() {
|
290 | if (this._context && this._context.currentHost === this) {
|
291 | this._context.currentHost = null;
|
292 | }
|
293 | };
|
294 |
|
295 | /**
|
296 | * Set which camera view to use (ex perspective, top etc.).
|
297 | * @param {FluxCameras.VIEWS} view The new view mode
|
298 | */
|
299 | FluxRenderer.prototype.setView = function(view) {
|
300 | this._cameras.setView(view);
|
301 | this._cameras.updateCamera(this._width, this._height);
|
302 | // Update the camera for each controls object
|
303 | for (var i=0;i<this._controls.length;i++) {
|
304 | this._controls[i].setCamera(this._cameras.getCamera());
|
305 | }
|
306 | this._helpers.setView(view);
|
307 | };
|
308 |
|
309 | /**
|
310 | * Creates depth, normal materials and depth, normal render targets.
|
311 | * @private
|
312 | */
|
313 | FluxRenderer.prototype._addRenderTargets = function() {
|
314 | // depth render target (uses THREE.js depth shader)
|
315 | this._depthTarget = new THREE.WebGLRenderTarget(
|
316 | window.innerWidth, //TODO(kyle) Why does this use window!?
|
317 | window.innerHeight,
|
318 | {
|
319 | minFilter: THREE.NearestFilter,
|
320 | magFilter: THREE.NearestFilter,
|
321 | format: THREE.RGBAFormat
|
322 | }
|
323 | );
|
324 |
|
325 | // normal render target
|
326 | this._normalTarget = this._depthTarget.clone();
|
327 | };
|
328 |
|
329 | /**
|
330 | * Render the scene with its geometry.
|
331 | */
|
332 | FluxRenderer.prototype.doRender = function () {
|
333 | this._setHost();
|
334 | this._update();
|
335 | this._context.renderer.clear();
|
336 | this._context.renderer.render(this._scene, this._cameras.getCamera());
|
337 | this._context.renderer.render(this._edgesScene, this._cameras.getCamera());
|
338 | this._context.renderer.render(this._helpersScene, this._cameras.getCamera());
|
339 | };
|
340 |
|
341 | /**
|
342 | * Say whether there are any objects to render in the model
|
343 | * @return {Boolean} True if there are objects to render
|
344 | */
|
345 | FluxRenderer.prototype.anyValidPrims = function() {
|
346 | return this._model ? this._model.children.length > 0 : false;
|
347 | };
|
348 |
|
349 | /**
|
350 | * Copy the image that is in the render canvas to this renderer's cache canvas.
|
351 | * This allows the rendered image to persist even when the renderer is not available.
|
352 | * This happens when the user moves the mouse away from this viewport to another one.
|
353 | * @private
|
354 | */
|
355 | FluxRenderer.prototype._cacheImageToCanvas = function () {
|
356 | this.doRender();
|
357 | this.ctx.drawImage(this._context.renderer.domElement, 0, 0, this._cacheCanvas.width, this._cacheCanvas.height);
|
358 | };
|
359 |
|
360 | /**
|
361 | * Get the canvas for use in QA scripts
|
362 | * @return {Canvas} WebGL canvas dom element
|
363 | */
|
364 | FluxRenderer.prototype.getGlCanvas = function() {
|
365 | return this._context.renderer.domElement;
|
366 | };
|
367 |
|
368 | /**
|
369 | * Migrate renderer.domElement to this host if necessary
|
370 | * and copy framebuffer into Canvas2D element of the previous host.
|
371 | * @private
|
372 | */
|
373 | FluxRenderer.prototype._setHost = function() {
|
374 | if (this === this._context.currentHost) return;
|
375 | if (this._context.currentHost) {
|
376 | // Copy the image from domElement (THREE's interactive canvas)
|
377 | // to the 2D context for this element's canvas
|
378 | // This image will remain up until the user interacts with the old viewport again
|
379 | this._context.currentHost._cacheImageToCanvas();
|
380 | }
|
381 | this._context.currentHost = this;
|
382 | this.setSize(this._width, this._height);
|
383 | // Move the THREE.WebGLRenderer's canvas under the new host
|
384 | this._domParent.appendChild(this._context.renderer.domElement);
|
385 | };
|
386 |
|
387 | /**
|
388 | * Set the WebGLRenderer parameters to match this renderer.
|
389 | * @private
|
390 | */
|
391 | FluxRenderer.prototype._update = function() {
|
392 | this._context.renderer.autoClearColor = false;
|
393 | this._context.renderer.autoClearDepth = false;
|
394 | this._context.renderer.setSize(this._width, this._height);
|
395 | this._context.renderer.setClearColor(this._clearColor, this._clearAlpha);
|
396 | };
|
397 |
|
398 | /**
|
399 | * Set the size of things that are per viewport.
|
400 | * @param {Number} width The canvas width in pixels
|
401 | * @param {Number} height The canvas height in pixels
|
402 | */
|
403 | FluxRenderer.prototype.setSize = function(width, height) {
|
404 | if (width <= 0 || height <= 0 || (width === this._width && height === this.height)) return;
|
405 | this._width = width;
|
406 | this._height = height;
|
407 |
|
408 | this._cameras.updateCamera(this._width, this._height);
|
409 |
|
410 | for (var i=0;i<this._controls.length;i++) {
|
411 | this._controls[i].setSize(this._width, this._height);
|
412 | }
|
413 |
|
414 | this._cacheCanvas.height = height;
|
415 | this._cacheCanvas.width = width;
|
416 | };
|
417 |
|
418 | /**
|
419 | * Make serializable by pruning all references and building an object property tree
|
420 | * @return {Object} Data to stringify
|
421 | */
|
422 | FluxRenderer.prototype.toJSON = function() {
|
423 | var serializableState = {
|
424 | cameras: this._cameras.toJSON(), // camera pos and view
|
425 | controls: this._editorControls.toJSON() // center point
|
426 | };
|
427 | //TODO(Kyle): Transition to serializing all controls objets
|
428 | return serializableState;
|
429 | };
|
430 |
|
431 | /**
|
432 | * Take a data object and use it to update the internal state
|
433 | * @param {Object} state The properties to set
|
434 | */
|
435 | FluxRenderer.prototype.fromJSON = function(state) {
|
436 | if (!state) return;
|
437 | if (state.cameras != null) {
|
438 | this.setView(state.cameras.view);
|
439 | this._cameras.fromJSON(state.cameras);
|
440 | }
|
441 | if (state.controls) {
|
442 | //TODO(Kyle): Transition to deserializing all controls objets
|
443 | this._editorControls.fromJSON(state.controls);
|
444 | }
|
445 | };
|
446 | </code></pre>
|
447 | </article>
|
448 | </section>
|
449 |
|
450 |
|
451 |
|
452 |
|
453 | </div>
|
454 |
|
455 | <br class="clear">
|
456 |
|
457 | <footer>
|
458 | Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Thu Jan 05 2017 16:26:56 GMT-0800 (PST) using the Minami theme.
|
459 | </footer>
|
460 |
|
461 | <script>prettyPrint();</script>
|
462 | <script src="scripts/linenumber.js"></script>
|
463 | </body>
|
464 | </html>
|