├── .editorconfig ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── demo ├── .eslintrc.js ├── animation-test.html ├── clean-slate.html ├── entity-viewer.html ├── globe.html ├── link-test.html ├── portal-test.html ├── sandbox.html ├── scripts │ └── capture.js └── snapshots.html ├── dist └── ingress-model-viewer.js ├── docma.config.json ├── docs ├── content │ └── readme.html ├── css │ ├── docma.css │ └── styles.css ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── glyphicons-halflings-regular.woff2 │ ├── icomoon.svg │ ├── icomoon.ttf │ └── icomoon.woff ├── index.html └── js │ ├── app.min.js │ ├── docma-web.js │ └── highlight.pack.js ├── manifest ├── abaddon1.json ├── abaddon2.json ├── amar.json ├── assets.json ├── helios.json ├── jarvis.json ├── lightman.json ├── shard2017.json └── shonin.json ├── package-lock.json ├── package.json ├── snapshot.json ├── src ├── OculusRiftEffect.js ├── animation │ ├── animation.js │ ├── animator.js │ └── easing.js ├── asset-loader.js ├── asset-manager.js ├── camera.js ├── constants.js ├── drawable.js ├── drawable │ ├── atmosphere.js │ ├── bicolored.js │ ├── glowramp.js │ ├── inventory.js │ ├── link.js │ ├── ornament.js │ ├── particle-portal.js │ ├── particle.js │ ├── portal-link.js │ ├── resonator-link.js │ ├── resource.js │ ├── shield-effect.js │ ├── spherical-portal-link.js │ ├── textured-sphere.js │ ├── textured.js │ ├── world.js │ └── xm.js ├── engine.js ├── entity.js ├── entity │ ├── inventory.js │ └── portal.js ├── geometry │ ├── field.js │ ├── parametric.js │ └── particle-portal.js ├── gl-bound.js ├── gl │ ├── gl-attribute.js │ ├── gl-buffer.js │ └── gl-index.js ├── ingress-model-viewer.js ├── mesh.js ├── mesh │ ├── file.js │ ├── particle-portal.js │ ├── plane.js │ ├── portal-link.js │ ├── resonator-link.js │ ├── sphere.js │ └── spherical-portal-link.js ├── orbit-controls.js ├── polyfill.js ├── program.js ├── program │ ├── glowramp.js │ └── opaque.js ├── renderer.js ├── renderer │ ├── object.js │ └── portal.js ├── texture.js ├── utils.js └── vertex-attribute.js ├── static ├── atmosphere.glsl.frag ├── atmosphere.glsl.vert ├── link3d.glsl.frag ├── link3d.glsl.vert └── world.jpg ├── test ├── all.html ├── all.js └── lib │ ├── qunit.css │ └── qunit.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components/ 2 | node_modules/ 3 | temp/ 4 | tmp/ 5 | logs/ 6 | log/ 7 | results/ 8 | assets 9 | !docs/assets 10 | 11 | *.seed 12 | *.log 13 | *.csv 14 | *.dat 15 | *.out 16 | *.pid 17 | *.gz 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Daniel Benton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ingress-model-viewer 2 | 3 | Rendering engine for Ingress game models 4 | 5 | ## About 6 | 7 | Capable of rendering Ingress game models in the browser, with no conversion required. However, this does require a copy of the `assets` directory from some version of the Ingress apk. Not all models are found in all versions of Ingress; you may have to look in older versions to find some assets. Shards, in particular, have been added and removed over time. 8 | 9 | A JavaScript library by Daniel Benton. 10 | 11 | ## Installation 12 | 13 | Grab the [compiled library](https://github.com/DeviateFish/ingress-model-viewer/blob/master/dist/ingress-model-viewer.js). Alternatively, build the library yourself, from source (see below). 14 | 15 | ## Usage 16 | 17 | Before starting, be sure to install dependencies with `npm install`. 18 | 19 | ### Starting development server 20 | Use `npm run serve` to start a development server. This will watch for changes and rebuild as necessary. 21 | 22 | ### Running ESLint 23 | Use `npm run lint` to run ESLint on all source files 24 | 25 | ### Adding assets 26 | By default, all demos will look for a directory named `assets` in the root directory. This can be the assets directory straight from a version of the ingress apk, extracted with your tool of choice. This library is capable of reading and parsing assets from the ingress apk without any conversion required. 27 | 28 | ### Demos 29 | Note that as of this writing, not all demos are functional or self-explanatory. They are mostly sandboxes for development at this time, but some are nifty demos of custom shaders/models (e.g. [`demos/globe.html`](https://github.com/DeviateFish/ingress-model-viewer/blob/master/demo/globe.html)). These sometimes will attempt to include external resources, such as [`FileSaver`](https://rawgit.com/eligrey/FileSaver.js/). 30 | 31 | ### Building documentation 32 | Use `npm run docs` to rebuild the documentation pages. 33 | 34 | ## Documentation 35 | 36 | See the [documentation site](https://deviatefish.github.io/ingress-model-viewer/) 37 | 38 | ## License 39 | 40 | MIT. See [`LICENSE`](https://github.com/DeviateFish/ingress-model-viewer/blob/master/LICENSE) 41 | -------------------------------------------------------------------------------- /demo/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": false 5 | }, 6 | "globals": { 7 | "IMV": true, 8 | "vec3": true, 9 | "vec4": true 10 | }, 11 | "rules": { 12 | "no-console": false 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /demo/animation-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ingress Model Sandbox 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /demo/clean-slate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ingress Model Sandbox 5 | 6 | 7 | 15 | 16 | 17 | 18 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /demo/entity-viewer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ingress Model Sandbox - Inventory viewer 6 | 7 | 8 | 9 | 10 | 34 | 35 | 36 | 37 |
38 |
39 | Inventory 40 | 43 |
44 |
45 | Camera 46 | 47 | 48 |
49 | 50 | 51 |
52 |
53 |
54 | 55 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /demo/globe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ingress Model Sandbox 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /demo/link-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ingress Model Sandbox 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /demo/portal-test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ingress Model Sandbox 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /demo/sandbox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ingress Model Sandbox 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 |
21 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /demo/snapshots.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ingress Model Sandbox 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /docma.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "src": { 3 | "engine": [ 4 | "./src/engine.js", 5 | "./src/camera.js", 6 | "./src/asset-manager.js" 7 | ], 8 | "assets": "./src/asset-loader.js", 9 | "utilities": [ 10 | "./src/utils.js", 11 | "./src/animation/animation.js", 12 | "./src/animation/easing.js" 13 | ], 14 | "drawables": [ 15 | "./src/drawable/*.js", 16 | "./src/drawable.js" 17 | ], 18 | "constants": "./src/constants.js", 19 | "internals": "./src/**/*.js", 20 | "readme": "./README.md" 21 | }, 22 | "dest": "./docs", 23 | "app": { 24 | "title": "Ingress Model Viewer", 25 | "routing": "query", 26 | "entrance": "content:readme", 27 | "server": "github", 28 | "base": "/ingress-model-viewer" 29 | }, 30 | "jsdoc": { 31 | "module": false, 32 | "sort": "grouped" 33 | }, 34 | "template": { 35 | "options": { 36 | "outline": "tree", 37 | "navItems": [ 38 | { 39 | "label": "Documentation", 40 | "iconClass": "ico-book", 41 | "items": [ 42 | { 43 | "label": "Readme", 44 | "href": "?content=readme" 45 | }, 46 | { 47 | "label": "Core", 48 | "href": "?api=engine" 49 | }, 50 | { 51 | "label": "AssetLoader", 52 | "href": "?api=assets" 53 | }, 54 | { 55 | "label": "Utilities", 56 | "href": "?api=utilities" 57 | }, 58 | { 59 | "label": "Drawables", 60 | "href": "?api=drawables" 61 | }, 62 | { 63 | "label": "Constants", 64 | "href": "?api=constants" 65 | }, 66 | { 67 | "label": "Internals", 68 | "href": "?api=internals" 69 | } 70 | ] 71 | }, 72 | { 73 | "label": "Github", 74 | "href": "https://github.com/DeviateFish/ingress-model-viewer/", 75 | "iconClass": "ico-github" 76 | } 77 | ] 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /docs/content/readme.html: -------------------------------------------------------------------------------- 1 | 2 |

ingress-model-viewer

3 |
4 |

Rendering engine for Ingress game models

5 |

About

6 |

Capable of rendering Ingress game models in the browser, with no conversion required. However, this does require a copy of the assets directory from some version of the Ingress apk. Not all models are found in all versions of Ingress; you may have to look in older versions to find some assets. Shards, in particular, have been added and removed over time.

7 |

A JavaScript library by Daniel Benton.

8 |

Installation

9 |

Grab the compiled library. Alternatively, build the library yourself, from source (see below).

10 |

Usage

11 |

Before starting, be sure to install dependencies with npm install.

12 |

Starting development server

13 |

Use npm run serve to start a development server. This will watch for changes and rebuild as necessary.

14 |

Running ESLint

15 |

Use npm run lint to run ESLint on all source files

16 |

Adding assets

17 |

By default, all demos will look for a directory named assets in the root directory. This can be the assets directory straight from a version of the ingress apk, extracted with your tool of choice. This library is capable of reading and parsing assets from the ingress apk without any conversion required.

18 |

Demos

19 |

Note that as of this writing, not all demos are functional or self-explanatory. They are mostly sandboxes for development at this time, but some are nifty demos of custom shaders/models (e.g. demos/globe.html). These sometimes will attempt to include external resources, such as FileSaver.

20 |

Building documentation

21 |

Use npm run docs to rebuild the documentation pages.

22 |

Documentation

23 |

See the documentation site

24 |

License

25 |

MIT. See LICENSE

26 | -------------------------------------------------------------------------------- /docs/css/docma.css: -------------------------------------------------------------------------------- 1 | img.docma{display:inline-block;border:0}img.docma.emoji,img.docma.emoji-1x,img.docma.emoji-sm{height:1em;width:1em;margin:0 .05em 0 .1em;vertical-align:-.1em}img.docma.emoji-md{height:1.33em;width:1.33em;margin:0 .0665em 0 .133em;vertical-align:-.133em}img.docma.emoji-lg{height:1.66em;width:1.66em;margin:0 .083em 0 .166em;vertical-align:-.166em}img.docma .emoji-2x{height:2em;width:2em;margin:0 .1em 0 .2em;vertical-align:-.2em}img.docma .emoji-3x{height:3em;width:3em;margin:0 .15em 0 .3em;vertical-align:-.3em}img.docma .emoji-4x{height:4em;width:4em;margin:0 .2em 0 .4em;vertical-align:-.4em}img.docma .emoji-5x{height:5em;width:5em;margin:0 .25em 0 .5em;vertical-align:-.5em}ul.docma.task-list,ul.docma.task-list>li.docma.task-item{padding-left:0;margin-left:0}ul.docma.task-list{list-style:none} -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeviateFish/ingress-model-viewer/acd83dca9ad9b152fb62a9b04efb5a068775cabc/docs/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeviateFish/ingress-model-viewer/acd83dca9ad9b152fb62a9b04efb5a068775cabc/docs/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeviateFish/ingress-model-viewer/acd83dca9ad9b152fb62a9b04efb5a068775cabc/docs/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /docs/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeviateFish/ingress-model-viewer/acd83dca9ad9b152fb62a9b04efb5a068775cabc/docs/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /docs/fonts/icomoon.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeviateFish/ingress-model-viewer/acd83dca9ad9b152fb62a9b04efb5a068775cabc/docs/fonts/icomoon.ttf -------------------------------------------------------------------------------- /docs/fonts/icomoon.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeviateFish/ingress-model-viewer/acd83dca9ad9b152fb62a9b04efb5a068775cabc/docs/fonts/icomoon.woff -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Ingress Model Viewer 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/js/app.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";function a(a){function e(a,e,t){return'
'+t+'
'}return a?docma.utils.isClass(a)?e("diamond bg-green","Class","C"):docma.utils.isNamespace(a)?e("diamond bg-red","Namespace","N"):docma.utils.isModule(a)?e("diamond bg-pink","Module","M"):docma.utils.isEnum(a)?e("square bg-purple","Enum","E"):docma.utils.isGlobal(a)?docma.utils.isMethod(a)?e("diamond bg-accent","Global Function","G"):e("diamond bg-red","Global Object","G"):docma.utils.isInner(a)?docma.utils.isMethod(a)?e("circle bg-gray-dark","Inner Method","M"):e("circle bg-gray-dark","Inner","I"):docma.utils.isStaticProperty(a)?e("square bg-orange","Static Property","P"):docma.utils.isInstanceProperty(a)?e("circle bg-yellow","Instance Property","P"):docma.utils.isStaticMethod(a)?e("square bg-accent","Static Method","M"):docma.utils.isInstanceMethod(a)?e("circle bg-cyan","Instance Method","M"):"":""}function e(a){return a.replace(/[.#~:]/g,'$&')}function t(a,t,i,s){var r=/[.#~:]/,n=i.split(r),o=n[n.length-1],d=20*(n.length-1);return o=i.slice(-(o.length+1)),''+t+''+e(o)+""}function i(){var a=o.val().trim().toLowerCase();if(""===a)return r.show(),void n.hide();n.show();var e;r.each(function(){e=$(this).attr("data-keywords"),e.indexOf(a)<0?$(this).hide():$(this).show()})}function s(a){for(var e=l;a.width()>215&&e>=d;)e--,a.css("font-size",e+"px")}docma.addFilter("$color_ops",function(a){return e(a)}).addFilter("$dot_prop",function(a){var t=/(.*)([.#~:]\w+)/g,i=t.exec(a);return i?''+e(i[1])+""+e(i[2]):""+a+""}).addFilter("$author",function(a){return(Array.isArray(a)?a:a.author||[]).join(", ")}).addFilter("$type",function(a){if(docma.utils.isConstructor(a))return"";if("function"===a.kind){var e=docma.utils.getReturnTypes(a);return e||""}var t=docma.utils.getTypes(a);return t||""}).addFilter("$type_sep",function(a){return docma.utils.isConstructor(a)?"":"function"===a.kind?"⇒":"class"===a.kind?":":a.type||a.returns?":":""}).addFilter("$param_desc",function(a){var e=a.optional?"":'Required ';return e+=a.description,docma.utils.parse(e)}).addFilter("$longname",function(a){return"string"==typeof a?a:(docma.utils.isConstructor(a)?"new ":"")+a.$longname}).addFilter("$longname_params",function(a){var t=docma.utils.isConstructor(a),i=e(a.$longname);if("function"===a.kind||t){var s,r="",n=t?"new ":"",o=n+i+"(";if(Array.isArray(a.params)){o+=a.params.reduce(function(a,e){return-1===e.name.indexOf(".")&&(s=e.hasOwnProperty("defaultvalue")?String(e.defaultvalue):"undefined",r=e.optional?'='+s+"":"",a.push(e.name+r)),a},[]).join(", ")}return o+")"}return i}).addFilter("$extends",function(a){var e=Array.isArray(a)?a:a.augments;return docma.utils.listType(e)}).addFilter("$returns",function(a){var e=Array.isArray(a)?a:a.returns;return docma.utils.listTypeDesc(e)}).addFilter("$exceptions",function(a){var e=Array.isArray(a)?a:a.exceptions;return docma.utils.listTypeDesc(e)}).addFilter("$tags",function(a){var e='',t="",i=[];docma.utils.isDeprecated(a)&&i.push('deprecated'+t),docma.utils.isGlobal(a)&&!docma.utils.isConstructor(a)&&i.push(e+"global"+t),docma.utils.isStatic(a)&&i.push('static'+t),!1===docma.utils.isPublic(a)&&i.push(''+a.access+t),docma.utils.isNamespace(a)&&i.push(e+"namespace"+t),docma.utils.isReadOnly(a)&&i.push('readonly'+t);var s=Array.isArray(a)?a:a.tags||[],r=s.map(function(a){return''+a.originalTitle+t});return i=i.concat(r),i.length?"  "+i.join(" "):""}).addFilter("$menuitem",function(e){var i=docma.documentation,s=docma.utils.getSymbolByName(i,e);if(!s)return e;var r=dust.filters.$id(s),n=docma.utils.getKeywords(s),o=docma.template.options.badges?a(s):"• ";return"tree"===docma.template.options.outline?t(r,o,e,n):''+o+''+dust.filters.$dot_prop(e)+""}),hljs.configure({tabReplace:" ",useBR:!1});var r,n,o,d=10,l=14;docma.template.options.title||(docma.template.options.title=docma.app.title||"Documentation"),docma.on("render",function(a){$('[data-toggle="tooltip"]').tooltip({container:"body",placement:"bottom"}),docma.template.options.navbar||($("body, html").css("padding",0),$("#sidebar-wrapper").css("margin-top",0),$(".symbol-container").css({"padding-top":0,"margin-top":0})),docma.currentRoute&&"api"===docma.currentRoute.type&&(docma.template.options.search?(r=$("ul.sidebar-nav .sidebar-item"),n=$(".sidebar-search-clean"),o=$("#txt-search"),n.hide(),o.on("keyup",i),o.on("change",i),n.on("click",function(){o.val("").focus(),r.show(),n.hide()})):$(".sidebar-nav").css("top","65px"),$(".sidebar-nav .item-label").each(function(){s($(this))}));var e=$("#page-content-wrapper").find(".row").first();docma.template.options.sidebar?(docma.template.options.collapsed&&$("#wrapper").addClass("toggled"),$(".sidebar-toggle").click(function(a){if(a.preventDefault(),$("#wrapper").toggleClass("toggled"),!docma.template.options.navbar){var t=$("#wrapper").hasClass("toggled")?"+=30px":"-=30px";e.animate({"margin-left":t},300)}})):$("#wrapper").addClass("toggled");var t=50,d=15;docma.currentRoute&&"api"===docma.currentRoute.type||($("table").addClass("table table-striped table-bordered"),t=0,d=0),$(".navbar-brand").css({"margin-left":t+"px","padding-left":d+"px","padding-right":d+"px"}),$("#docma-main pre > code").each(function(a,e){hljs.highlightBlock(e)})})}(); -------------------------------------------------------------------------------- /manifest/amar.json: -------------------------------------------------------------------------------- 1 | { 2 | "texture": { 3 | "ArtifactAmarTexture": { "path": "scanner/artifacts/artifact_amar.png", "minFilter": "Linear", "magFilter": "Linear", "wrapS": "ClampToEdge", "wrapT": "ClampToEdge" } 4 | }, 5 | "mesh": { 6 | "Amar1": { "path": "scanner/artifacts/artifact_amar_fragment_1.obj" }, 7 | "AmarFrozen1": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_1.obj" }, 8 | "Amar2": { "path": "scanner/artifacts/artifact_amar_fragment_2.obj" }, 9 | "AmarFrozen2": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_2.obj" }, 10 | "Amar3": { "path": "scanner/artifacts/artifact_amar_fragment_3.obj" }, 11 | "AmarFrozen3": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_3.obj" }, 12 | "Amar4": { "path": "scanner/artifacts/artifact_amar_fragment_4.obj" }, 13 | "AmarFrozen4": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_4.obj" }, 14 | "Amar5": { "path": "scanner/artifacts/artifact_amar_fragment_5.obj" }, 15 | "AmarFrozen5": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_5.obj" }, 16 | "Amar6": { "path": "scanner/artifacts/artifact_amar_fragment_6.obj" }, 17 | "AmarFrozen6": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_6.obj" }, 18 | "Amar7": { "path": "scanner/artifacts/artifact_amar_fragment_7.obj" }, 19 | "AmarFrozen7": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_7.obj" }, 20 | "Amar8": { "path": "scanner/artifacts/artifact_amar_fragment_8.obj" }, 21 | "AmarFrozen8": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_8.obj" }, 22 | "Amar9": { "path": "scanner/artifacts/artifact_amar_fragment_9.obj" }, 23 | "AmarFrozen9": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_9.obj" }, 24 | "Amar10": { "path": "scanner/artifacts/artifact_amar_fragment_10.obj" }, 25 | "AmarFrozen10": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_10.obj" }, 26 | "Amar11": { "path": "scanner/artifacts/artifact_amar_fragment_11.obj" }, 27 | "AmarFrozen11": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_11.obj" }, 28 | "Amar12": { "path": "scanner/artifacts/artifact_amar_fragment_12.obj" }, 29 | "AmarFrozen12": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_12.obj" }, 30 | "Amar13": { "path": "scanner/artifacts/artifact_amar_fragment_13.obj" }, 31 | "AmarFrozen13": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_13.obj" }, 32 | "Amar14": { "path": "scanner/artifacts/artifact_amar_fragment_14.obj" }, 33 | "AmarFrozen14": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_14.obj" }, 34 | "Amar15": { "path": "scanner/artifacts/artifact_amar_fragment_15.obj" }, 35 | "AmarFrozen15": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_15.obj" }, 36 | "Amar16": { "path": "scanner/artifacts/artifact_amar_fragment_16.obj" }, 37 | "AmarFrozen16": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_16.obj" }, 38 | "Amar17": { "path": "scanner/artifacts/artifact_amar_fragment_17.obj" }, 39 | "AmarFrozen17": { "path": "scanner/artifacts/artifact_frozen_amar_fragment_17.obj" } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /manifest/helios.json: -------------------------------------------------------------------------------- 1 | { 2 | "texture": { 3 | "ArtifactHeliosTexture": { "path": "scanner/artifacts/artifact_helios.png", "minFilter": "Linear", "magFilter": "Linear", "wrapS": "ClampToEdge", "wrapT": "ClampToEdge" } 4 | }, 5 | "mesh": { 6 | "Helios1": { "path": "scanner/artifacts/artifact_helios_fragment_1.obj" }, 7 | "HeliosFrozen1": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_1.obj" }, 8 | "Helios2": { "path": "scanner/artifacts/artifact_helios_fragment_2.obj" }, 9 | "HeliosFrozen2": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_2.obj" }, 10 | "Helios3": { "path": "scanner/artifacts/artifact_helios_fragment_3.obj" }, 11 | "HeliosFrozen3": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_3.obj" }, 12 | "Helios4": { "path": "scanner/artifacts/artifact_helios_fragment_4.obj" }, 13 | "HeliosFrozen4": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_4.obj" }, 14 | "Helios5": { "path": "scanner/artifacts/artifact_helios_fragment_5.obj" }, 15 | "HeliosFrozen5": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_5.obj" }, 16 | "Helios6": { "path": "scanner/artifacts/artifact_helios_fragment_6.obj" }, 17 | "HeliosFrozen6": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_6.obj" }, 18 | "Helios7": { "path": "scanner/artifacts/artifact_helios_fragment_7.obj" }, 19 | "HeliosFrozen7": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_7.obj" }, 20 | "Helios8": { "path": "scanner/artifacts/artifact_helios_fragment_8.obj" }, 21 | "HeliosFrozen8": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_8.obj" }, 22 | "Helios9": { "path": "scanner/artifacts/artifact_helios_fragment_9.obj" }, 23 | "HeliosFrozen9": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_9.obj" }, 24 | "Helios10": { "path": "scanner/artifacts/artifact_helios_fragment_10.obj" }, 25 | "HeliosFrozen10": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_10.obj" }, 26 | "Helios11": { "path": "scanner/artifacts/artifact_helios_fragment_11.obj" }, 27 | "HeliosFrozen11": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_11.obj" }, 28 | "Helios12": { "path": "scanner/artifacts/artifact_helios_fragment_12.obj" }, 29 | "HeliosFrozen12": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_12.obj" }, 30 | "Helios13": { "path": "scanner/artifacts/artifact_helios_fragment_13.obj" }, 31 | "HeliosFrozen13": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_13.obj" }, 32 | "Helios14": { "path": "scanner/artifacts/artifact_helios_fragment_14.obj" }, 33 | "HeliosFrozen14": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_14.obj" }, 34 | "Helios15": { "path": "scanner/artifacts/artifact_helios_fragment_15.obj" }, 35 | "HeliosFrozen15": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_15.obj" }, 36 | "Helios16": { "path": "scanner/artifacts/artifact_helios_fragment_16.obj" }, 37 | "HeliosFrozen16": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_16.obj" }, 38 | "Helios17": { "path": "scanner/artifacts/artifact_helios_fragment_17.obj" }, 39 | "HeliosFrozen17": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_17.obj" }, 40 | "Helios18": { "path": "scanner/artifacts/artifact_helios_fragment_18.obj" }, 41 | "HeliosFrozen18": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_18.obj" }, 42 | "Helios19": { "path": "scanner/artifacts/artifact_helios_fragment_19.obj" }, 43 | "HeliosFrozen19": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_19.obj" }, 44 | "Helios20": { "path": "scanner/artifacts/artifact_helios_fragment_20.obj" }, 45 | "HeliosFrozen20": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_20.obj" }, 46 | "Helios21": { "path": "scanner/artifacts/artifact_helios_fragment_21.obj" }, 47 | "HeliosFrozen21": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_21.obj" }, 48 | "Helios22": { "path": "scanner/artifacts/artifact_helios_fragment_22.obj" }, 49 | "HeliosFrozen22": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_22.obj" }, 50 | "Helios23": { "path": "scanner/artifacts/artifact_helios_fragment_23.obj" }, 51 | "HeliosFrozen23": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_23.obj" }, 52 | "Helios24": { "path": "scanner/artifacts/artifact_helios_fragment_24.obj" }, 53 | "HeliosFrozen24": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_24.obj" }, 54 | "Helios25": { "path": "scanner/artifacts/artifact_helios_fragment_25.obj" }, 55 | "HeliosFrozen25": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_25.obj" }, 56 | "Helios26": { "path": "scanner/artifacts/artifact_helios_fragment_26.obj" }, 57 | "HeliosFrozen26": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_26.obj" }, 58 | "Helios27": { "path": "scanner/artifacts/artifact_helios_fragment_27.obj" }, 59 | "HeliosFrozen27": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_27.obj" }, 60 | "Helios28": { "path": "scanner/artifacts/artifact_helios_fragment_28.obj" }, 61 | "HeliosFrozen28": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_28.obj" }, 62 | "Helios29": { "path": "scanner/artifacts/artifact_helios_fragment_29.obj" }, 63 | "HeliosFrozen29": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_29.obj" }, 64 | "Helios30": { "path": "scanner/artifacts/artifact_helios_fragment_30.obj" }, 65 | "HeliosFrozen30": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_30.obj" }, 66 | "Helios31": { "path": "scanner/artifacts/artifact_helios_fragment_31.obj" }, 67 | "HeliosFrozen31": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_31.obj" }, 68 | "Helios32": { "path": "scanner/artifacts/artifact_helios_fragment_32.obj" }, 69 | "HeliosFrozen32": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_32.obj" }, 70 | "Helios33": { "path": "scanner/artifacts/artifact_helios_fragment_33.obj" }, 71 | "HeliosFrozen33": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_33.obj" }, 72 | "Helios34": { "path": "scanner/artifacts/artifact_helios_fragment_34.obj" }, 73 | "HeliosFrozen34": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_34.obj" }, 74 | "Helios35": { "path": "scanner/artifacts/artifact_helios_fragment_35.obj" }, 75 | "HeliosFrozen35": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_35.obj" }, 76 | "Helios36": { "path": "scanner/artifacts/artifact_helios_fragment_36.obj" }, 77 | "HeliosFrozen36": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_36.obj" }, 78 | "Helios37": { "path": "scanner/artifacts/artifact_helios_fragment_37.obj" }, 79 | "HeliosFrozen37": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_37.obj" }, 80 | "Helios38": { "path": "scanner/artifacts/artifact_helios_fragment_38.obj" }, 81 | "HeliosFrozen38": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_38.obj" }, 82 | "Helios39": { "path": "scanner/artifacts/artifact_helios_fragment_39.obj" }, 83 | "HeliosFrozen39": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_39.obj" }, 84 | "Helios40": { "path": "scanner/artifacts/artifact_helios_fragment_40.obj" }, 85 | "HeliosFrozen40": { "path": "scanner/artifacts/artifact_frozen_helios_fragment_40.obj" } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /manifest/jarvis.json: -------------------------------------------------------------------------------- 1 | { 2 | "texture": { 3 | "ArtifactJarvisTexture": { "path": "scanner/artifacts/artifact_jarvis.png", "minFilter": "Linear", "magFilter": "Linear", "wrapS": "ClampToEdge", "wrapT": "ClampToEdge" } 4 | }, 5 | "mesh": { 6 | "Jarvis1": { "path": "scanner/artifacts/artifact_jarvis_fragment_1.obj" }, 7 | "Jarvis2": { "path": "scanner/artifacts/artifact_jarvis_fragment_2.obj" }, 8 | "Jarvis3": { "path": "scanner/artifacts/artifact_jarvis_fragment_3.obj" }, 9 | "Jarvis4": { "path": "scanner/artifacts/artifact_jarvis_fragment_4.obj" }, 10 | "Jarvis5": { "path": "scanner/artifacts/artifact_jarvis_fragment_5.obj" }, 11 | "Jarvis6": { "path": "scanner/artifacts/artifact_jarvis_fragment_6.obj" }, 12 | "Jarvis7": { "path": "scanner/artifacts/artifact_jarvis_fragment_7.obj" }, 13 | "Jarvis8": { "path": "scanner/artifacts/artifact_jarvis_fragment_8.obj" }, 14 | "Jarvis9": { "path": "scanner/artifacts/artifact_jarvis_fragment_9.obj" }, 15 | "Jarvis10": { "path": "scanner/artifacts/artifact_jarvis_fragment_10.obj" }, 16 | "Jarvis11": { "path": "scanner/artifacts/artifact_jarvis_fragment_11.obj" }, 17 | "Jarvis12": { "path": "scanner/artifacts/artifact_jarvis_fragment_12.obj" }, 18 | "Jarvis13": { "path": "scanner/artifacts/artifact_jarvis_fragment_13.obj" } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /manifest/lightman.json: -------------------------------------------------------------------------------- 1 | { 2 | "texture": { 3 | "ArtifactLightmanTexture": { "path": "scanner/artifacts/artifact_lightman.png", "minFilter": "Linear", "magFilter": "Linear", "wrapS": "ClampToEdge", "wrapT": "ClampToEdge" } 4 | }, 5 | "mesh": { 6 | "Lightman1": { "path": "scanner/artifacts/artifact_lightman_fragment_1.obj" }, 7 | "LightmanFrozen1": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_1.obj" }, 8 | "Lightman2": { "path": "scanner/artifacts/artifact_lightman_fragment_2.obj" }, 9 | "LightmanFrozen2": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_2.obj" }, 10 | "Lightman3": { "path": "scanner/artifacts/artifact_lightman_fragment_3.obj" }, 11 | "LightmanFrozen3": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_3.obj" }, 12 | "Lightman4": { "path": "scanner/artifacts/artifact_lightman_fragment_4.obj" }, 13 | "LightmanFrozen4": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_4.obj" }, 14 | "Lightman5": { "path": "scanner/artifacts/artifact_lightman_fragment_5.obj" }, 15 | "LightmanFrozen5": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_5.obj" }, 16 | "Lightman6": { "path": "scanner/artifacts/artifact_lightman_fragment_6.obj" }, 17 | "LightmanFrozen6": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_6.obj" }, 18 | "Lightman7": { "path": "scanner/artifacts/artifact_lightman_fragment_7.obj" }, 19 | "LightmanFrozen7": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_7.obj" }, 20 | "Lightman8": { "path": "scanner/artifacts/artifact_lightman_fragment_8.obj" }, 21 | "LightmanFrozen8": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_8.obj" }, 22 | "Lightman9": { "path": "scanner/artifacts/artifact_lightman_fragment_9.obj" }, 23 | "LightmanFrozen9": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_9.obj" }, 24 | "Lightman10": { "path": "scanner/artifacts/artifact_lightman_fragment_10.obj" }, 25 | "LightmanFrozen10": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_10.obj" }, 26 | "Lightman11": { "path": "scanner/artifacts/artifact_lightman_fragment_11.obj" }, 27 | "LightmanFrozen11": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_11.obj" }, 28 | "Lightman12": { "path": "scanner/artifacts/artifact_lightman_fragment_12.obj" }, 29 | "LightmanFrozen12": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_12.obj" }, 30 | "Lightman13": { "path": "scanner/artifacts/artifact_lightman_fragment_13.obj" }, 31 | "LightmanFrozen13": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_13.obj" }, 32 | "Lightman14": { "path": "scanner/artifacts/artifact_lightman_fragment_14.obj" }, 33 | "LightmanFrozen14": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_14.obj" }, 34 | "Lightman15": { "path": "scanner/artifacts/artifact_lightman_fragment_15.obj" }, 35 | "LightmanFrozen15": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_15.obj" }, 36 | "Lightman16": { "path": "scanner/artifacts/artifact_lightman_fragment_16.obj" }, 37 | "LightmanFrozen16": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_16.obj" }, 38 | "Lightman17": { "path": "scanner/artifacts/artifact_lightman_fragment_17.obj" }, 39 | "LightmanFrozen17": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_17.obj" }, 40 | "Lightman18": { "path": "scanner/artifacts/artifact_lightman_fragment_18.obj" }, 41 | "LightmanFrozen18": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_18.obj" }, 42 | "Lightman19": { "path": "scanner/artifacts/artifact_lightman_fragment_19.obj" }, 43 | "LightmanFrozen19": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_19.obj" }, 44 | "Lightman20": { "path": "scanner/artifacts/artifact_lightman_fragment_20.obj" }, 45 | "LightmanFrozen20": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_20.obj" }, 46 | "Lightman21": { "path": "scanner/artifacts/artifact_lightman_fragment_21.obj" }, 47 | "LightmanFrozen21": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_21.obj" }, 48 | "Lightman22": { "path": "scanner/artifacts/artifact_lightman_fragment_22.obj" }, 49 | "LightmanFrozen22": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_22.obj" }, 50 | "Lightman23": { "path": "scanner/artifacts/artifact_lightman_fragment_23.obj" }, 51 | "LightmanFrozen23": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_23.obj" }, 52 | "Lightman24": { "path": "scanner/artifacts/artifact_lightman_fragment_24.obj" }, 53 | "LightmanFrozen24": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_24.obj" }, 54 | "Lightman25": { "path": "scanner/artifacts/artifact_lightman_fragment_25.obj" }, 55 | "LightmanFrozen25": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_25.obj" }, 56 | "Lightman26": { "path": "scanner/artifacts/artifact_lightman_fragment_26.obj" }, 57 | "LightmanFrozen26": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_26.obj" }, 58 | "Lightman27": { "path": "scanner/artifacts/artifact_lightman_fragment_27.obj" }, 59 | "LightmanFrozen27": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_27.obj" }, 60 | "Lightman28": { "path": "scanner/artifacts/artifact_lightman_fragment_28.obj" }, 61 | "LightmanFrozen28": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_28.obj" }, 62 | "Lightman29": { "path": "scanner/artifacts/artifact_lightman_fragment_29.obj" }, 63 | "LightmanFrozen29": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_29.obj" }, 64 | "Lightman30": { "path": "scanner/artifacts/artifact_lightman_fragment_30.obj" }, 65 | "LightmanFrozen30": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_30.obj" }, 66 | "Lightman31": { "path": "scanner/artifacts/artifact_lightman_fragment_31.obj" }, 67 | "LightmanFrozen31": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_31.obj" }, 68 | "Lightman32": { "path": "scanner/artifacts/artifact_lightman_fragment_32.obj" }, 69 | "LightmanFrozen32": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_32.obj" }, 70 | "Lightman33": { "path": "scanner/artifacts/artifact_lightman_fragment_33.obj" }, 71 | "LightmanFrozen33": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_33.obj" }, 72 | "Lightman34": { "path": "scanner/artifacts/artifact_lightman_fragment_34.obj" }, 73 | "LightmanFrozen34": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_34.obj" }, 74 | "Lightman35": { "path": "scanner/artifacts/artifact_lightman_fragment_35.obj" }, 75 | "LightmanFrozen35": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_35.obj" }, 76 | "Lightman36": { "path": "scanner/artifacts/artifact_lightman_fragment_36.obj" }, 77 | "LightmanFrozen36": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_36.obj" }, 78 | "Lightman37": { "path": "scanner/artifacts/artifact_lightman_fragment_37.obj" }, 79 | "LightmanFrozen37": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_37.obj" }, 80 | "Lightman38": { "path": "scanner/artifacts/artifact_lightman_fragment_38.obj" }, 81 | "LightmanFrozen38": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_38.obj" }, 82 | "Lightman39": { "path": "scanner/artifacts/artifact_lightman_fragment_39.obj" }, 83 | "LightmanFrozen39": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_39.obj" }, 84 | "Lightman40": { "path": "scanner/artifacts/artifact_lightman_fragment_40.obj" }, 85 | "LightmanFrozen40": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_40.obj" }, 86 | "Lightman41": { "path": "scanner/artifacts/artifact_lightman_fragment_41.obj" }, 87 | "LightmanFrozen41": { "path": "scanner/artifacts/artifact_frozen_lightman_fragment_41.obj" } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /manifest/shonin.json: -------------------------------------------------------------------------------- 1 | { 2 | "texture": { 3 | "ArtifactShoninTexture": { "path": "scanner/artifacts/artifact_shonin.png", "minFilter": "Linear", "magFilter": "Linear", "wrapS": "ClampToEdge", "wrapT": "ClampToEdge" } 4 | }, 5 | "mesh": { 6 | "Shonin1": { "path": "scanner/artifacts/artifact_shonin_fragment_1.obj" }, 7 | "ShoninFrozen1": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_1.obj" }, 8 | "Shonin2": { "path": "scanner/artifacts/artifact_shonin_fragment_2.obj" }, 9 | "ShoninFrozen2": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_2.obj" }, 10 | "Shonin3": { "path": "scanner/artifacts/artifact_shonin_fragment_3.obj" }, 11 | "ShoninFrozen3": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_3.obj" }, 12 | "Shonin4": { "path": "scanner/artifacts/artifact_shonin_fragment_4.obj" }, 13 | "ShoninFrozen4": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_4.obj" }, 14 | "Shonin5": { "path": "scanner/artifacts/artifact_shonin_fragment_5.obj" }, 15 | "ShoninFrozen5": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_5.obj" }, 16 | "Shonin6": { "path": "scanner/artifacts/artifact_shonin_fragment_6.obj" }, 17 | "ShoninFrozen6": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_6.obj" }, 18 | "Shonin7": { "path": "scanner/artifacts/artifact_shonin_fragment_7.obj" }, 19 | "ShoninFrozen7": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_7.obj" }, 20 | "Shonin8": { "path": "scanner/artifacts/artifact_shonin_fragment_8.obj" }, 21 | "ShoninFrozen8": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_8.obj" }, 22 | "Shonin9": { "path": "scanner/artifacts/artifact_shonin_fragment_9.obj" }, 23 | "ShoninFrozen9": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_9.obj" }, 24 | "Shonin10": { "path": "scanner/artifacts/artifact_shonin_fragment_10.obj" }, 25 | "ShoninFrozen10": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_10.obj" }, 26 | "Shonin11": { "path": "scanner/artifacts/artifact_shonin_fragment_11.obj" }, 27 | "ShoninFrozen11": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_11.obj" }, 28 | "Shonin12": { "path": "scanner/artifacts/artifact_shonin_fragment_12.obj" }, 29 | "ShoninFrozen12": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_12.obj" }, 30 | "Shonin13": { "path": "scanner/artifacts/artifact_shonin_fragment_13.obj" }, 31 | "ShoninFrozen13": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_13.obj" }, 32 | "Shonin14": { "path": "scanner/artifacts/artifact_shonin_fragment_14.obj" }, 33 | "ShoninFrozen14": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_14.obj" }, 34 | "Shonin15": { "path": "scanner/artifacts/artifact_shonin_fragment_15.obj" }, 35 | "ShoninFrozen15": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_15.obj" }, 36 | "Shonin16": { "path": "scanner/artifacts/artifact_shonin_fragment_16.obj" }, 37 | "ShoninFrozen16": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_16.obj" }, 38 | "Shonin17": { "path": "scanner/artifacts/artifact_shonin_fragment_17.obj" }, 39 | "ShoninFrozen17": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_17.obj" }, 40 | "Shonin18": { "path": "scanner/artifacts/artifact_shonin_fragment_18.obj" }, 41 | "ShoninFrozen18": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_18.obj" }, 42 | "Shonin19": { "path": "scanner/artifacts/artifact_shonin_fragment_19.obj" }, 43 | "ShoninFrozen19": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_19.obj" }, 44 | "Shonin20": { "path": "scanner/artifacts/artifact_shonin_fragment_20.obj" }, 45 | "ShoninFrozen20": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_20.obj" }, 46 | "Shonin21": { "path": "scanner/artifacts/artifact_shonin_fragment_21.obj" }, 47 | "ShoninFrozen21": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_21.obj" }, 48 | "Shonin22": { "path": "scanner/artifacts/artifact_shonin_fragment_22.obj" }, 49 | "ShoninFrozen22": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_22.obj" }, 50 | "Shonin23": { "path": "scanner/artifacts/artifact_shonin_fragment_23.obj" }, 51 | "ShoninFrozen23": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_23.obj" }, 52 | "Shonin24": { "path": "scanner/artifacts/artifact_shonin_fragment_24.obj" }, 53 | "ShoninFrozen24": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_24.obj" }, 54 | "Shonin25": { "path": "scanner/artifacts/artifact_shonin_fragment_25.obj" }, 55 | "ShoninFrozen25": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_25.obj" }, 56 | "Shonin26": { "path": "scanner/artifacts/artifact_shonin_fragment_26.obj" }, 57 | "ShoninFrozen26": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_26.obj" }, 58 | "Shonin27": { "path": "scanner/artifacts/artifact_shonin_fragment_27.obj" }, 59 | "ShoninFrozen27": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_27.obj" }, 60 | "Shonin28": { "path": "scanner/artifacts/artifact_shonin_fragment_28.obj" }, 61 | "ShoninFrozen28": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_28.obj" }, 62 | "Shonin29": { "path": "scanner/artifacts/artifact_shonin_fragment_29.obj" }, 63 | "ShoninFrozen29": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_29.obj" }, 64 | "Shonin30": { "path": "scanner/artifacts/artifact_shonin_fragment_30.obj" }, 65 | "ShoninFrozen30": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_30.obj" }, 66 | "Shonin31": { "path": "scanner/artifacts/artifact_shonin_fragment_31.obj" }, 67 | "ShoninFrozen31": { "path": "scanner/artifacts/artifact_frozen_shonin_fragment_31.obj" } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ingress-model-viewer", 3 | "version": "0.22.2", 4 | "main": "src/ingress-model-viewer.js", 5 | "description": "Rendering engine for Ingress game models", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/DeviateFish/ingress-model-viewer.git" 9 | }, 10 | "scripts": { 11 | "build": "webpack", 12 | "lint": "eslint ./src/ --ext .js -c .eslintrc.js", 13 | "serve": "webpack-dev-server --open", 14 | "docs": "docma -c docma.config.json" 15 | }, 16 | "keywords": ["ingress", "renderer", "model viewer"], 17 | "author": "DeviateFish", 18 | "license": "MIT", 19 | "readmeFilename": "README.md", 20 | "dependencies": { 21 | "es6-promises": "1.0.10", 22 | "gl-matrix": "2.3.2", 23 | "java-deserializer": "0.3.0", 24 | "libtga": "0.4.0" 25 | }, 26 | "devDependencies": { 27 | "babel-core": "^6.22.1", 28 | "babel-loader": "^6.2.10", 29 | "babel-preset-es2015": "^6.22.0", 30 | "docma": "^1.5.1", 31 | "eslint": "^3.19.0", 32 | "eslint-loader": "^1.7.1", 33 | "webpack": "^2.2.1", 34 | "webpack-dev-server": "^2.3.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/animation/animation.js: -------------------------------------------------------------------------------- 1 | import Ease from './easing'; 2 | /** 3 | * Simple class for hooking up animations to drawables. 4 | * 5 | * Animations refers specifically to things like moving objects/cameras around. 6 | * Animations handled by the existing shaders should be implemented that way, instead. 7 | */ 8 | 9 | class Animation { 10 | 11 | /** 12 | * Create an animation for a drawable 13 | * 14 | * @chainable 15 | * @param {Number} duration Duration of one cycle of the animation 16 | * @param {Function} transform Animation callback 17 | * Parameter: Number t 18 | * Parameter: Drawable drawable 19 | * @param {Function} timing Timing function (i.e. easing) Defaults. to Ease.linear 20 | * @param {Boolean} loop Whether or not to loop the animation 21 | * @return {this} The animation 22 | */ 23 | constructor(duration, transform, timing, loop) { 24 | this.elapsed = 0; 25 | this.duration = duration; 26 | this.transform = transform; 27 | this.timing = timing || Ease.linear; 28 | this.loop = loop; 29 | this.running = false; 30 | this.next = []; 31 | return this; 32 | } 33 | 34 | /** 35 | * Starts the animation 36 | * 37 | * @chainable 38 | * @return {this} Returns `this` 39 | */ 40 | start() { 41 | if(!this.running) { 42 | this.running = true; 43 | } 44 | return this; 45 | } 46 | 47 | /** 48 | * Stops the animation, and resets the elasped time to 0 49 | * 50 | * @chainable 51 | * @return {this} Returns `this` 52 | */ 53 | stop() { 54 | this.elapsed = 0; 55 | return this.pause(); 56 | } 57 | 58 | /** 59 | * Pauses the running animation 60 | * 61 | * @chainable 62 | * @return {this} Returns `this` 63 | */ 64 | pause() { 65 | if(this.running) { 66 | this.running = false; 67 | } 68 | return this; 69 | } 70 | 71 | /** 72 | * Perform a step of the animation 73 | * @param {Number} delta Time elasped since last frame 74 | * @param {Drawable} drawable The drawable to operate on 75 | * @return {Boolean} Return true to signal the end of the animation 76 | */ 77 | step(delta, drawable) { 78 | if(!this.running) { 79 | return false; 80 | } 81 | this.elapsed += delta; 82 | // if we're done with the animation 83 | if (this.elapsed > this.duration && !this.loop) { 84 | let t = this.timing(1); 85 | this.transform(t, drawable); 86 | this.stop(); 87 | return true; 88 | } 89 | let t = this.timing((this.elapsed / this.duration) % 1); 90 | this.transform(t, drawable); 91 | return false; 92 | } 93 | 94 | /** 95 | * Allows for chaining of animations 96 | * 97 | * @chainable 98 | * @param {Animation} animation The animation to queue after this one 99 | * completes. Note that this isn't really 100 | * valid for looping animations 101 | * @return {this} Returns `this` 102 | */ 103 | chain(animation) { 104 | if (!(animation instanceof Animation)) { 105 | throw new Error('New animation should be an instance of an Animation'); 106 | } 107 | this.next.push(animation); 108 | return this; 109 | } 110 | } 111 | 112 | export default Animation; 113 | -------------------------------------------------------------------------------- /src/animation/animator.js: -------------------------------------------------------------------------------- 1 | import Animation from './animation'; 2 | 3 | /** 4 | * This class handles running animations on animatable objects. 5 | * 6 | * This is generally composed into a class (e.g. Camera or Drawable) 7 | */ 8 | class Animator { 9 | constructor() { 10 | this._animations = []; 11 | } 12 | 13 | /** 14 | * Adds an animation. 15 | * 16 | * Note that this does not start the animation. 17 | * 18 | * @chainable 19 | * @param {Animation} animation The animation to be run. 20 | * This will need to be started independently, or 21 | * prior to being added. 22 | * @return {this} Returns `this` 23 | */ 24 | addAnimation(animation) { 25 | if (!(animation instanceof Animation)) { 26 | throw new Error('New animation should be an instance of an Animation'); 27 | } 28 | this._animations.unshift(animation); 29 | return this; 30 | } 31 | 32 | /** 33 | * @param {Number} delta Time since last update 34 | * @param {Object} subject Object to animate 35 | * @return {void} 36 | */ 37 | runAnimations(delta, subject) { 38 | let i = this._animations.length - 1; 39 | for(; i >= 0; i--) { 40 | let animation = this._animations[i]; 41 | if(animation.running && animation.step(delta, subject)) { 42 | this._animations.splice.apply( 43 | this._animations, 44 | [i, 1].concat(animation.next) 45 | ); 46 | } 47 | } 48 | } 49 | } 50 | 51 | export default Animator; 52 | -------------------------------------------------------------------------------- /src/asset-loader.js: -------------------------------------------------------------------------------- 1 | import libtga from 'libtga'; 2 | import Promise from 'es6-promises'; 3 | 4 | /** 5 | * An AssetLoader manages loading one or more assets. It handles debouncing of 6 | * of multiple requests for the same asset, etc. 7 | * 8 | * @class 9 | */ 10 | class AssetLoader { 11 | 12 | /** 13 | * Noop. 14 | */ 15 | constructor() { 16 | this._callbacks = {}; 17 | this._assets = {}; 18 | } 19 | 20 | /** 21 | * Loads a single asset. 22 | * 23 | * @param {String} url The url of the asset to load. 24 | * @param {String} type The type of asset being requested 25 | * 26 | * @returns { Promise } Returns a promise. Resolves immediately 27 | * if the asset it already loaded. 28 | * @see AssetLoader.loadResource 29 | */ 30 | loadAsset(url, type) { 31 | var name = '_' + encodeURIComponent(url); 32 | if(this._assets[name]) 33 | { 34 | return Promise.resolve(this._assets[name]); 35 | } 36 | else 37 | { 38 | return new Promise((resolve, reject) => { 39 | this._callbacks[name] = this._callbacks[name] || []; 40 | this._callbacks[name].push({resolve, reject}); 41 | if(!this._assets.hasOwnProperty(name)) 42 | { 43 | this._assets[name] = false; 44 | AssetLoader.loadResource(url, type).then((value) => { 45 | this._assets[name] = value; 46 | var cb; 47 | while((cb = this._callbacks[name].shift())) 48 | { 49 | cb.resolve(value); 50 | } 51 | }).catch((err) => { 52 | var cb; 53 | while((cb = this._callbacks[name].shift())) 54 | { 55 | cb.reject(err); 56 | } 57 | }); 58 | } 59 | }); 60 | } 61 | } 62 | 63 | /** 64 | * Load a set of assets in parallel 65 | * @param {Array} urls Array of urls of resources 66 | * @param {Array} types Array of types of resources 67 | * @return {Promise} A Promise that resolves when all assets are loaded, 68 | * or rejects when any fail. 69 | * @see AssetLoader.loadResource 70 | */ 71 | loadAssetGroup(urls, types) { 72 | if(urls.length !== types.length) 73 | { 74 | throw 'Incompatible types: types.length = ' + types.length + '; urls.length = ' + urls.length; 75 | } 76 | return Promise.all( 77 | urls.map((url, i) => { 78 | return this.loadAsset(url, types[i]); 79 | }) 80 | ); 81 | } 82 | 83 | /** 84 | * Directly retrieve an asset from the cache 85 | * @param {String} name The cache key 86 | * @return {mixed} The cached asset, if it exists. 87 | */ 88 | getAsset(name) { 89 | return this._assets[name]; 90 | } 91 | 92 | /** 93 | * Loads a resource via xhr or Image 94 | * 95 | * @static 96 | * @param {String} url href of the resource to fetch 97 | * @param {String} type One of XHMLHttpRequest's supported responseType 98 | * values (arraybuffer, blob, document, json, text) 99 | * or 'image' or 'image.co' (for a cross-origin image) 100 | * @return {Promise} Returns a promise that resolves on success, or rejects 101 | * on failure. 102 | */ 103 | static loadResource(url, type) { 104 | return new Promise(function(resolve, reject) { 105 | if(type === 'image' || type === 'image.co') 106 | { 107 | if(/\.tga$/.test(url)) 108 | { 109 | libtga.loadFile(url, function(err, tga) { 110 | if(err) 111 | { 112 | reject(err); 113 | return; 114 | } 115 | var canvas = document.createElement('canvas'); 116 | var context = canvas.getContext('2d'); 117 | var imageData = context.createImageData(tga.width, tga.height); 118 | imageData.data.set(tga.imageData); 119 | canvas.height = tga.height; 120 | canvas.width = tga.width; 121 | context.putImageData(imageData, 0, 0); 122 | var image = new Image(); 123 | image.onload = function() { 124 | resolve(this); 125 | }; 126 | image.onerror = function(e) { 127 | reject(e); 128 | }; 129 | image.src = canvas.toDataURL(); 130 | }); 131 | } 132 | else 133 | { 134 | var i = new Image(); 135 | // cross-origin image: 136 | if(type === 'image.co') 137 | { 138 | i.crossOrigin = 'anoymous'; 139 | } 140 | i.onload = function() 141 | { 142 | resolve(this); 143 | }; 144 | i.onerror = function(e) 145 | { 146 | reject(e); 147 | }; 148 | i.src = url; 149 | } 150 | } 151 | else 152 | { 153 | var xhr = new XMLHttpRequest(); 154 | xhr.open('GET', url); 155 | xhr.responseType = type; 156 | xhr.onload = function() { 157 | resolve(this.response); 158 | }; 159 | xhr.onerror = function(e) { 160 | reject(e); 161 | }; 162 | 163 | xhr.send(); 164 | } 165 | }); 166 | } 167 | } 168 | 169 | export default AssetLoader; 170 | -------------------------------------------------------------------------------- /src/camera.js: -------------------------------------------------------------------------------- 1 | import { mat4, vec3 } from 'gl-matrix'; 2 | import Animator from './animation/animator'; 3 | 4 | /** 5 | * A Camera is a class to manage view of the scene. 6 | * 7 | * @class 8 | * @chainable 9 | * @param {Number} width The width of the viewport 10 | * @param {Number} height The height of the viewport 11 | * @return {this} The new Camera 12 | */ 13 | class Camera { 14 | 15 | constructor(width, height) { 16 | this.position = vec3.create(); 17 | this.view = mat4.create(); 18 | this.project = mat4.create(); 19 | this.viewProject = mat4.create(); 20 | this.hFoV = Math.PI / 4; 21 | this.near = 0.1; 22 | this.far = 100; 23 | this.width = width; 24 | this.height = height; 25 | this.focus = vec3.create(); 26 | this.up = vec3.fromValues(0, 1, 0); 27 | this.animator = new Animator(); 28 | return this._updateProjection()._updateView(); 29 | } 30 | 31 | /** 32 | * Generates a view matrix, as if the camera is looking at the specified point. 33 | * 34 | * @chainable 35 | * @param {vec3} point The point to look at 36 | * @return {this} Returns `this` 37 | */ 38 | lookAt(point) { 39 | vec3.copy(this.focus, point); 40 | return this._updateView(); 41 | } 42 | 43 | /** 44 | * Moves the camera's position in some direction 45 | * 46 | * Maintains the camera's current focus. 47 | * 48 | * @chainable 49 | * @param {vec3} vec The vector to translate by 50 | * @return {this} Returns `this` 51 | */ 52 | translate(vec) { 53 | vec3.translate(this.position, this.position, vec); 54 | return this._updateView(); 55 | } 56 | 57 | /** 58 | * Sets the camera's position 59 | * 60 | * @chainable 61 | * @param {vec3} position Camera position 62 | * @return {this} Returns `this` 63 | */ 64 | setPosition(position) { 65 | vec3.copy(this.position, position); 66 | return this._updateView(); 67 | } 68 | 69 | /** 70 | * Set the viewport dimensions and update the projection matrix 71 | * 72 | * @chainable 73 | * @param {Number} width Viewport width 74 | * @param {Number} height Viewport height 75 | * @return {this} Returns `this` 76 | */ 77 | setDimensions(width, height) { 78 | this.width = width; 79 | this.height = height; 80 | return this._updateProjection(); 81 | } 82 | 83 | /** 84 | * Set the horizontal field of view 85 | * 86 | * @chainable 87 | * @param {Number} fov Field of view, in radians 88 | * @return {this} Returns `this` 89 | */ 90 | setFieldOfView(fov) { 91 | this.hFoV = fov; 92 | return this._updateProjection(); 93 | } 94 | 95 | /** 96 | * Sets the far clip distance 97 | * 98 | * @chainable 99 | * @param {Number} far Max viewable distance 100 | * @return {this} Returns `this` 101 | */ 102 | setFar(far) { 103 | this.far = far; 104 | return this._updateProjection(); 105 | } 106 | 107 | /** 108 | * Adds an animation 109 | * 110 | * @chainable 111 | * @param {Animation} animation The animation to be run. 112 | * This will need to be started independently, or prior to being added. 113 | * @return {this} Returns `this` 114 | */ 115 | addAnimation(animation) { 116 | this.animator.addAnimation(animation); 117 | return this; 118 | } 119 | 120 | /** 121 | * @param {Number} delta The time elapsed since the last draw 122 | * @return {this} Returns `this` 123 | */ 124 | updateTime(delta) { 125 | this.animator.runAnimations(delta, this); 126 | return this; 127 | } 128 | 129 | /** 130 | * Updates the camera's view matrix from all parameters. 131 | * 132 | * @chainable 133 | * @private 134 | * @return {this} Returns `this` 135 | */ 136 | _updateView() { 137 | mat4.lookAt(this.view, this.position, this.focus, this.up); 138 | return this; 139 | } 140 | 141 | /** 142 | * Update the camera's projection matrix 143 | * 144 | * @chainable 145 | * @private 146 | * @return {this} Returns `this` 147 | */ 148 | _updateProjection() { 149 | mat4.perspective(this.project, this.hFoV, this.width / this.height, this.near, this.far); 150 | return this; 151 | } 152 | } 153 | 154 | export default Camera; 155 | -------------------------------------------------------------------------------- /src/drawable/atmosphere.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import Drawable from '../drawable'; 3 | import SphereMesh from '../mesh/sphere'; 4 | import { mat3, mat4 } from 'gl-matrix'; 5 | 6 | const PROGRAM = Constants.Program.Atmosphere; 7 | 8 | /** 9 | * Creates an "atmosphere" effect. 10 | * 11 | * This is a modified version of the atmosphere program from: 12 | * https://github.com/dataarts/webgl-globe/blob/master/globe/globe.js 13 | * @param {Number} radius Radius of the world. 14 | * This should match the radius of the world mesh the 15 | * atmosphere is being rendered over. 16 | * @param {Number} vSlices Number of vertical slices for the sphere mesh 17 | * @param {Number} hSlices Number of horizontal slices for the sphere mesh 18 | * @param {Number} scaleFactor The percent to scale the mesh 19 | * @return {void} 20 | */ 21 | class AtmosphereDrawable extends Drawable { 22 | constructor(radius, vSlices, hSlices, scaleFactor) { 23 | super(PROGRAM, null); 24 | this.radius = radius; 25 | this.vSlices = vSlices; 26 | this.hSlices = hSlices; 27 | this.uniforms.u_normalMatrix = mat3.create(); 28 | this.scaleFactor = scaleFactor || 1.1; 29 | this.setScalarScale(this.scaleFactor); 30 | } 31 | 32 | /** 33 | * Updates the view matrices of the model 34 | * 35 | * @chainable 36 | * @see src/drawable/model.js#updateView 37 | * @param {mat4} viewProject combined projection matrix multiplied by view matrix. 38 | * @return {this} Returns `this` 39 | */ 40 | updateView(viewProject) { 41 | super.updateView(viewProject); 42 | var invert = mat4.invert(mat4.create(), viewProject), 43 | transpose = mat4.transpose(mat4.create(), invert); 44 | this.uniforms.u_normalMatrix = mat3.fromMat4(mat3.create(), transpose); 45 | return this; 46 | } 47 | 48 | /** 49 | * Initializes the drawable 50 | * 51 | * @see src/drawable.js 52 | * @param {AssetManager} manager The AssetManager containing the required assets. 53 | * @return {Promise} A Promise that resolves when the asset is initialized 54 | */ 55 | init(manager) { 56 | this.mesh = new SphereMesh( 57 | manager._gl, 58 | this.radius, 59 | this.vSlices, 60 | this.hSlices 61 | ); 62 | return super.init(manager); 63 | } 64 | } 65 | 66 | export default AtmosphereDrawable; 67 | -------------------------------------------------------------------------------- /src/drawable/bicolored.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import TexturedDrawable from './textured'; 3 | import { vec4 } from 'gl-matrix'; 4 | 5 | const PROGRAM = Constants.Program.Bicolored; 6 | 7 | /** 8 | * Default quality color. 9 | * 10 | * @private 11 | * @type {vec4} 12 | */ 13 | const defaultColor0 = vec4.clone(Constants.qualityColors.VERY_RARE); 14 | 15 | /** 16 | * Default glow color 17 | * 18 | * @private 19 | * @type {vec4} 20 | */ 21 | const defaultColor1 = vec4.clone(Constants.xmColors.coreGlow); 22 | 23 | /** 24 | * This is used for items and other renderables that have two visible colors 25 | * 26 | * The specifics of it are basically: if the texture has an opacity less than 0.5, 27 | * the texture color is blended with u_color0 28 | * Otherwise, it's the texture color blended with u_color1 29 | * 30 | * Or something like that. 31 | * @param {String} meshName Internal name of the mesh for this drawable 32 | * @param {String} textureName Internal name of the texture for this drawble 33 | */ 34 | class BicoloredDrawable extends TexturedDrawable { 35 | 36 | constructor(meshName, textureName) { 37 | super(PROGRAM, meshName, textureName); 38 | this.uniforms.u_color0 = vec4.clone(defaultColor0); 39 | this.uniforms.u_color1 = vec4.clone(defaultColor1); 40 | } 41 | } 42 | 43 | export default BicoloredDrawable; 44 | -------------------------------------------------------------------------------- /src/drawable/glowramp.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import TexturedDrawable from './textured'; 3 | import { vec4 } from 'gl-matrix'; 4 | 5 | const PROGRAM = Constants.Program.Glowramp; 6 | 7 | /** 8 | * Default base color for the glowramp drawable 9 | * 10 | * @private 11 | * @type {vec4} 12 | */ 13 | const defaultBaseColor = vec4.clone(Constants.teamColors.NEUTRAL); 14 | 15 | /** 16 | * A "glowramp" refers to the usage of the red, green, and blue channels to create 17 | * a "glowing" texture. 18 | * 19 | * @param {String} meshName Internal name of the mesh 20 | * @param {String} textureName Internal name of the texture 21 | */ 22 | class GlowrampDrawable extends TexturedDrawable { 23 | 24 | constructor(meshName, textureName) { 25 | super(PROGRAM, meshName, textureName); 26 | this.uniforms.u_baseColor = vec4.clone(defaultBaseColor); 27 | this.uniforms.u_rotation = 0; 28 | this.uniforms.u_rampTarget = 0; 29 | this.uniforms.u_alpha = 0.6; 30 | } 31 | 32 | /** 33 | * Updates default glowramp variables (rotation, ramp target, elapsed time 34 | * and alpha) 35 | * @param {Number} tick Time delta since last tick 36 | * @return {Boolean} @see src/drawable.js#updateTime 37 | */ 38 | updateTime(tick) { 39 | var ret = super.updateTime(tick); 40 | var inc = this.elapsed / 5000; 41 | this.uniforms.u_rotation = inc; 42 | this.uniforms.u_rampTarget = Math.sin(Math.PI / 2 * (inc - Math.floor(inc))); 43 | this.uniforms.u_alpha = Math.sin(inc) * 0.05 + 0.75; 44 | return ret; 45 | } 46 | } 47 | 48 | export default GlowrampDrawable; 49 | -------------------------------------------------------------------------------- /src/drawable/inventory.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import BicoloredDrawable from './bicolored'; 3 | import XmDrawable from './xm'; 4 | import TexturedDrawable from './textured'; 5 | 6 | /** 7 | * Contains drawable primitives for many of the inventory items. 8 | */ 9 | var Inventory = {}; 10 | 11 | 12 | var meshes = Constants.Mesh.Inventory; 13 | var textures = Constants.Texture; 14 | 15 | /** 16 | * Creates the outer "shell" for an xm item. 17 | * 18 | * @private 19 | * @param {String} name Internal name of the mesh 20 | * @return {itembase} A BicoloredDrawable with the specified mesh name 21 | * and the flipcard texture 22 | */ 23 | function createShell(name) { 24 | class itembase extends BicoloredDrawable { 25 | constructor() { 26 | super(meshes[name], textures.FlipCard); 27 | } 28 | } 29 | 30 | return itembase; 31 | } 32 | 33 | /** 34 | * Creates the xm "core" of an item 35 | * 36 | * @private 37 | * @param {String} name Internal name of the xm mesh 38 | * @return {xmbase} An XmDrawable with the specified mesh name 39 | * and the Xm texture. 40 | */ 41 | function createCore(name) { 42 | class xmbase extends XmDrawable { 43 | constructor() { 44 | super(meshes[name], textures.Xm); 45 | } 46 | } 47 | 48 | return xmbase; 49 | } 50 | 51 | /** 52 | * Creates a media item 53 | * 54 | * @private 55 | * @param {String} name Media mesh internal name 56 | * @return {media} A TexturedDrawable with the Textured program, 57 | * the specified mesh, and the flipcard texture. 58 | */ 59 | function createMedia(name) { 60 | class media extends TexturedDrawable { 61 | constructor() { 62 | super( 63 | Constants.Program.Textured, 64 | meshes[name], 65 | Constants.Texture.FlipCard 66 | ); 67 | } 68 | } 69 | 70 | return media; 71 | } 72 | 73 | for(var i in meshes) { 74 | if(/^Media/.test(i)) { 75 | if(i === 'MediaPlane') { 76 | continue; 77 | } 78 | Inventory[i] = createMedia(i); 79 | } 80 | else { 81 | if(/Xm$/.test(i)) { 82 | Inventory[i] = createCore(i); 83 | } 84 | else { 85 | Inventory[i] = createShell(i); 86 | } 87 | } 88 | } 89 | 90 | export default Inventory; 91 | -------------------------------------------------------------------------------- /src/drawable/link.js: -------------------------------------------------------------------------------- 1 | import TexturedDrawable from './textured'; 2 | import { vec3, mat3, quat } from 'gl-matrix'; 3 | 4 | /** 5 | * The LinkDrawable represents the base class for link-type drawables. 6 | * 7 | * @param {String} programName Internal name of the program to use 8 | * @param {String} textureName Internal name of the texture to use 9 | */ 10 | class LinkDrawable extends TexturedDrawable { 11 | 12 | constructor(programName, textureName) { 13 | super(programName, null, textureName); 14 | this.uniforms.u_cameraFwd = vec3.fromValues(0, 0, -1); 15 | this.uniforms.u_elapsedTime = 0; 16 | } 17 | 18 | /** 19 | * Updates the camera transforms for the link drawables 20 | * @param {mat4} viewProject Combined view and project matrix 21 | * @param {Camera} camera The camera 22 | * @return {void} 23 | */ 24 | updateView(viewProject, camera) { 25 | super.updateView(viewProject, camera); 26 | if(camera) { 27 | var rot = mat3.fromMat4(mat3.create(), camera.view); 28 | var q = quat.fromMat3(quat.create(), rot); 29 | var fwd = vec3.transformQuat(vec3.create(), vec3.fromValues(0, 0, -1), q); 30 | vec3.normalize(fwd, fwd); 31 | this.uniforms.u_cameraFwd = fwd; 32 | } 33 | } 34 | 35 | /** 36 | * Updates default periodic uniforms for links 37 | * @param {Number} delta Time delta since last draw 38 | * @return {Boolean} @see src/drawable.js#updateTime 39 | */ 40 | updateTime(delta) { 41 | var ret = super.updateTime(delta); 42 | this.uniforms.u_elapsedTime = ((this.elapsed / 1000) % 300.0) * 0.1; 43 | return ret; 44 | } 45 | } 46 | 47 | export default LinkDrawable; 48 | -------------------------------------------------------------------------------- /src/drawable/ornament.js: -------------------------------------------------------------------------------- 1 | import TexturedDrawable from './textured'; 2 | import Constants from '../constants'; 3 | import { vec2, vec4 } from 'gl-matrix'; 4 | 5 | const PROGRAM = Constants.Program.RegionTextured; 6 | 7 | /** 8 | * An OrnamentDrawable is a TextuedDrawable that draws an ornament on 9 | * a unit plane. 10 | * @param {String} meshName Internal name of the ornament mesh 11 | * @param {String} textureName Internal name of the texture 12 | */ 13 | class OrnamentDrawable extends TexturedDrawable { 14 | constructor(meshName, textureName) { 15 | super(PROGRAM, meshName, textureName); 16 | this.uniforms.u_texCoordBase = vec2.fromValues(0, 0); 17 | this.uniforms.u_texCoordExtent = vec2.fromValues(1, 1); 18 | this.uniforms.u_color = vec4.clone(Constants.teamColors.LOKI); 19 | } 20 | } 21 | 22 | export default OrnamentDrawable; 23 | -------------------------------------------------------------------------------- /src/drawable/particle-portal.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import ParticleDrawable from './particle'; 3 | import ParticlePortalMesh from '../mesh/particle-portal'; 4 | import { vec3, vec4 } from 'gl-matrix'; 5 | 6 | const PROGRAM = Constants.Program.ParticlePortal; 7 | const MAX_SYSTEMS = 40; 8 | 9 | /** 10 | * A drawable representing a system of particles emanating from a portal 11 | * 12 | * @class 13 | * @extends {ParticleDrawable} 14 | * @param {vec4} color The particle color 15 | * @param {Number} height The height to propagate 16 | * @param {Number} count The number of particles 17 | * @param {Number} spread The spread between particles 18 | * @param {Number} distance The distance 19 | */ 20 | class ParticlePortalDrawable extends ParticleDrawable { 21 | 22 | constructor(color, height, count, spread, distance) { 23 | super(PROGRAM); 24 | let modColor = vec4.clone(color); 25 | modColor[3] = count; 26 | // uniforms should be flattened arrays. 27 | // Since they're expected to contain up to 40 systems, we'll need to create 28 | // arrays of 40 * 4 elements each. 29 | this.uniforms.u_color = new Float32Array(MAX_SYSTEMS * 4); 30 | this.uniforms.u_position = new Float32Array(MAX_SYSTEMS * 4); 31 | this.uniforms.u_params = new Float32Array(MAX_SYSTEMS * 4); 32 | // fill in the first 4 slots. 33 | vec4.copy(this.uniforms.u_color, modColor); 34 | vec4.copy(this.uniforms.u_position, vec4.fromValues(0, 0, 0, height)); 35 | vec4.copy(this.uniforms.u_params, vec4.fromValues(0, distance, spread, 1)); 36 | } 37 | 38 | /** 39 | * Update the view, and uniforms pertaining to the view 40 | * @param {mat4} viewProject Camera's combine view and projection matrix 41 | * @param {Camera} camera The camera 42 | * @return {void} 43 | */ 44 | updateView(viewProject, camera) { 45 | super.updateView(viewProject, camera); 46 | if(camera) { 47 | let dist = vec3.length(camera.position); 48 | let scale = Math.pow(dist, 0.2); 49 | this.uniforms.u_params[3] = scale; 50 | } 51 | } 52 | 53 | /** 54 | * Update the time for the system 55 | * @param {Number} delta Time since last tick 56 | * @return {Boolean} Results of onUpdate 57 | */ 58 | updateTime(delta) { 59 | let ret = super.updateTime(delta); 60 | this.uniforms.u_params[0] = (this.elapsed / 100000) * this.uniforms.u_params[1]; 61 | return ret; 62 | } 63 | 64 | /** 65 | * Initialize the portal particle mesh 66 | * @param {AssetManager} manager AssetManager containing the remaining assets 67 | * @return {Boolean} Success/failure 68 | */ 69 | init(manager) { 70 | this.mesh = new ParticlePortalMesh(manager._gl); 71 | return super.init(manager); 72 | } 73 | } 74 | 75 | export default ParticlePortalDrawable; 76 | -------------------------------------------------------------------------------- /src/drawable/particle.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import TexturedDrawable from './textured'; 3 | import { vec3 } from 'gl-matrix'; 4 | 5 | const TEXTURE = Constants.Texture.Particle; 6 | 7 | /** 8 | * A ParticleDrawable represents the base class for particles 9 | * 10 | * @extends {TexturedDrawable} 11 | */ 12 | class ParticleDrawable extends TexturedDrawable { 13 | 14 | constructor(programName) { 15 | super(programName, null, TEXTURE); 16 | this.uniforms.u_cameraPos = vec3.fromValues(0, 0, 0); 17 | } 18 | 19 | updateView(viewProject, camera) { 20 | super.updateView(viewProject, camera); 21 | if(camera) { 22 | vec3.copy(this.uniforms.u_cameraPos, camera.position); 23 | } 24 | } 25 | } 26 | 27 | export default ParticleDrawable; 28 | -------------------------------------------------------------------------------- /src/drawable/portal-link.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import LinkDrawable from './link'; 3 | import PortalLinkMesh from '../mesh/portal-link'; 4 | 5 | /** 6 | * A LinkDrawable that represents a link from one portal to another 7 | * @extends {LinkDrawable} 8 | * @param {vec2} start X, Z of origin portal 9 | * @param {vec2} end X, Z of destination portal 10 | * @param {vec4} color Color of link 11 | * @param {Number} startPercent Percent health of the origin portal 12 | * @param {Number} endPercent Percent health of the destination portal 13 | */ 14 | class PortalLinkDrawable extends LinkDrawable { 15 | 16 | constructor(start, end, color, startPercent, endPercent) { 17 | super(Constants.Program.Link, Constants.Texture.PortalLink); 18 | this.start = start; 19 | this.end = end; 20 | this.color = color; 21 | this.startPercent = startPercent; 22 | this.endPercent = endPercent; 23 | } 24 | 25 | /** 26 | * Construct the PortalLinkMesh for this link 27 | * @param {AssetManager} manager AssetManager to look up the program and texture 28 | * @return {Boolean} Success/failure 29 | */ 30 | init(manager) { 31 | this.mesh = new PortalLinkMesh(manager._gl, this.start, this.end, this.color, this.startPercent, this.endPercent); 32 | return super.init(manager); 33 | } 34 | } 35 | 36 | export default PortalLinkDrawable; 37 | -------------------------------------------------------------------------------- /src/drawable/resonator-link.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import LinkDrawable from './link'; 3 | import ResonatorLinkMesh from '../mesh/resonator-link'; 4 | 5 | 6 | /** 7 | * A ResonatorLinkDrawable is a LinkDrawable that represents a link 8 | * between a portal and a resonator 9 | * @param {vec2} portalPosition X,Z of the portal (usually 0,0) 10 | * @param {Number} slot Slot (0-7) 11 | * @param {Number} distance Usually 0-40 12 | * @param {vec4} color Color of the resonator link (TODO: make this disco) 13 | * @param {Number} resonatorPercent Percent health of the resonator 14 | */ 15 | class ResonatorLinkDrawable extends LinkDrawable { 16 | 17 | constructor(portalPosition, slot, distance, color, resonatorPercent) { 18 | super(Constants.Program.Link, Constants.Texture.ResonatorLink); 19 | this.portalPosition = portalPosition; 20 | this.slot = slot; 21 | this.distance = distance; 22 | this.color = color; 23 | this.resonatorPercent = resonatorPercent; 24 | } 25 | 26 | /** 27 | * Creates a ResonatorLinkMesh with the given params, and initializes the 28 | * texture/program 29 | * @param {AssetManager} manager AssetManager containing the required program/texture 30 | * @return {Boolean} Success/failure 31 | */ 32 | init(manager) { 33 | this.mesh = new ResonatorLinkMesh( 34 | manager._gl, 35 | this.portalPosition, 36 | this.slot, 37 | this.distance, 38 | this.color, 39 | this.resonatorPercent 40 | ); 41 | return super.init(manager); 42 | } 43 | } 44 | 45 | export default ResonatorLinkDrawable; 46 | -------------------------------------------------------------------------------- /src/drawable/resource.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import BicoloredDrawable from './bicolored'; 3 | 4 | var Resource = {}; 5 | var meshes = Constants.Mesh.Resource; 6 | 7 | /** 8 | * Creates a resource drawable 9 | * 10 | * @private 11 | * @param {String} name InternalName 12 | * @return {itembase} A BicoloredDrawable representing this resource item 13 | */ 14 | function createResource(name) { 15 | class itembase extends BicoloredDrawable { 16 | constructor() { 17 | super(meshes[name], Constants.Texture.FlipCard); 18 | } 19 | } 20 | 21 | return itembase; 22 | } 23 | 24 | for(var i in meshes) { 25 | Resource[name] = createResource(i); 26 | } 27 | 28 | export default Resource; 29 | -------------------------------------------------------------------------------- /src/drawable/shield-effect.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import TexturedDrawable from './textured'; 3 | import { vec2, vec3, vec4 } from 'gl-matrix'; 4 | 5 | const PROGRAM = Constants.Program.ShieldEffect; 6 | 7 | // these defaults are whack. Need to find the real 8 | // functions used to update these, too 9 | // As of 1.62.0, that was in ...ingress.common.scanner.b.a.d 10 | // The baksmali is a little jacked up, though. 11 | var defaultColor = vec4.clone(Constants.teamColors.NEUTRAL); 12 | var defaultRampTargetInv = vec2.fromValues(0.5, 1.3); 13 | var defaultContributions = vec3.fromValues(0.5, 0.5, 0.5); 14 | 15 | /** 16 | * Represents the shield idle effect 17 | * 18 | * Note: This probably should actually be generalized differently... 19 | * Apparently all three shield effects use the same texture and mesh, but have 20 | * different programs and variables. 21 | * 22 | * So, perhaps a better way would be to have the base class hardcode the texture 23 | * and mesh internal names, and then the derived classes pick a program and handle 24 | * the variables. 25 | * 26 | * @param {String} meshName Mesh internal name 27 | * @param {String} textureName Texture internal name 28 | */ 29 | class ShieldEffectDrawable extends TexturedDrawable { 30 | 31 | constructor(meshName, textureName) { 32 | super(PROGRAM, meshName, textureName); 33 | this.uniforms.u_color = vec4.clone(defaultColor); 34 | this.uniforms.u_rampTargetInvWidth = vec2.clone(defaultRampTargetInv); 35 | this.uniforms.u_contributionsAndAlpha = vec3.clone(defaultContributions); 36 | } 37 | 38 | /** 39 | * Updates the default uniforms 40 | * 41 | * Note: these are nothing like what's in the apk, just some functions that 42 | * happen to look kinda sorta nice 43 | * @param {Number} delta Time since last frame 44 | * @return {Boolean} Returns true to continue the animation. 45 | */ 46 | updateTime(delta) { 47 | var ret = super.updateTime(delta); 48 | var inc = this.elapsed / 10000; 49 | // this is so shitty, but again, this java decompiler really doesn't like the file. 50 | // This is nothing close to what's 'supposed' to happen in these uniforms, just a hack 51 | // that's kinda sorta like the actual thing. 52 | this.uniforms.u_rampTargetInvWidth[0] = -(inc - Math.floor(inc)); 53 | this.uniforms.u_rampTargetInvWidth[1] = Math.sin((inc - Math.floor(inc)) * Math.PI / 2); 54 | // u_contributionsAndAlpha? 55 | return ret; 56 | } 57 | } 58 | 59 | export default ShieldEffectDrawable; 60 | -------------------------------------------------------------------------------- /src/drawable/spherical-portal-link.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import LinkDrawable from './link'; 3 | import SphericalPortalLinkMesh from '../mesh/spherical-portal-link'; 4 | 5 | 6 | /** 7 | * Represents a portal link that follows the surface of a sphere. 8 | * 9 | * Hooray for custom shaders, etc! 10 | * 11 | * @param {Number} sphereRadius Radius of the sphere 12 | * @param {vec2} start Lat,lng of the origin portal 13 | * @param {vec2} end Lat,lng of the destination portal 14 | * @param {vec4} color Color of the link 15 | * @param {Number} startPercent Percent health of the origin portal 16 | * @param {Number} endPercent Percent health of the destination portal 17 | */ 18 | class SphericalPortalLinkDrawable extends LinkDrawable { 19 | 20 | constructor(sphereRadius, start, end, color, startPercent, endPercent) { 21 | super(Constants.Program.SphericalLink, Constants.Texture.PortalLink); 22 | this.radius = sphereRadius; 23 | this.start = start; 24 | this.end = end; 25 | this.color = color; 26 | this.startPercent = startPercent; 27 | this.endPercent = endPercent; 28 | this.uniforms.u_model = this._model; 29 | } 30 | 31 | /** 32 | * Constructs a mesh for the link, then initializes the remaining assets. 33 | * @param {AssetManager} manager AssetManager containing the program/texture 34 | * @return {Boolean} Success/failure 35 | */ 36 | init(manager) { 37 | this.mesh = new SphericalPortalLinkMesh( 38 | manager._gl, 39 | this.radius, 40 | this.start, 41 | this.end, 42 | this.color, 43 | this.startPercent, 44 | this.endPercent 45 | ); 46 | return super.init(manager); 47 | } 48 | 49 | updateView(viewProject, camera) { 50 | super.updateView(viewProject, camera); 51 | this.uniforms.u_model = this._model; 52 | } 53 | } 54 | 55 | export default SphericalPortalLinkDrawable; 56 | -------------------------------------------------------------------------------- /src/drawable/textured-sphere.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import TexturedDrawable from './textured'; 3 | import SphereMesh from '../mesh/sphere'; 4 | 5 | const PROGRAM = Constants.Program.Textured; 6 | 7 | /** 8 | * A sphere with a texture mapped to it 9 | * 10 | * @param {String} textureName Internal name of the texture to use 11 | * @param {Number} radius Radius of the sphere 12 | * @param {Number} vSlices Number of vertical slices 13 | * @param {Number} hSlices Number of horizontal slices 14 | */ 15 | class TexturedSphereDrawable extends TexturedDrawable { 16 | constructor(textureName, radius, vSlices, hSlices) { 17 | super(PROGRAM, null, textureName); 18 | this.radius = radius; 19 | this.vSlices = vSlices; 20 | this.hSlices = hSlices; 21 | } 22 | 23 | /** 24 | * Create a sphere mesh and initialize the other resources 25 | * @param {AssetManager} manager AssetManager containing the texture/program 26 | * @return {Boolean} Success/failure 27 | */ 28 | init(manager) { 29 | this.mesh = new SphereMesh( 30 | manager._gl, 31 | this.radius, 32 | this.vSlices, 33 | this.hSlices 34 | ); 35 | return super.init(manager); 36 | } 37 | } 38 | 39 | export default TexturedSphereDrawable; 40 | -------------------------------------------------------------------------------- /src/drawable/textured.js: -------------------------------------------------------------------------------- 1 | import Drawable from '../drawable'; 2 | 3 | /** 4 | * A TexturedDrawable is a Drawable with a specific texture 5 | * 6 | * @param {String} programName Program internal name 7 | * @param {String} meshName Mesh internal name 8 | * @param {String} textureName Texture internal name 9 | */ 10 | class TexturedDrawable extends Drawable { 11 | constructor(programName, meshName, textureName) { 12 | super(programName, meshName); 13 | this.textureName = textureName; 14 | this.texture = null; 15 | } 16 | 17 | /** 18 | * Draw the textured object 19 | * 20 | * @return {void} 21 | */ 22 | draw() { 23 | if(this.ready) { 24 | this.texture.use(0); 25 | this.uniforms.u_texture = 0; 26 | super.draw(); 27 | } 28 | } 29 | 30 | _loadAssets(manager) { 31 | let promises = super._loadAssets(manager); 32 | promises.push( 33 | manager.loadTexture(this.textureName).then((texture) => { 34 | this.texture = texture; 35 | }).catch((err) => { 36 | console.warn('missing texture ' + this.textureName); // eslint-disable-line no-console 37 | return Promise.reject(err); 38 | }) 39 | ); 40 | return promises; 41 | } 42 | } 43 | 44 | export default TexturedDrawable; 45 | -------------------------------------------------------------------------------- /src/drawable/world.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import GlowrampDrawable from './glowramp'; 3 | import BicoloredDrawable from './bicolored'; 4 | import ShieldEffectDrawable from './shield-effect'; 5 | import OrnamentDrawable from './ornament'; 6 | 7 | /** 8 | * Various world drawables 9 | * 10 | * Includes Portal, ShieldEffect, waypoints, resonators, and artifact glows 11 | * @type {Object} 12 | */ 13 | var World = {}; 14 | 15 | var meshes = Constants.Mesh.World; 16 | var textures = Constants.Texture; 17 | 18 | function makeGlowramp(mesh, texture) { 19 | class glowrampbase extends GlowrampDrawable { 20 | constructor() { 21 | super(mesh, texture); 22 | } 23 | } 24 | 25 | return glowrampbase; 26 | } 27 | 28 | function makeBicolored(mesh, texture) { 29 | class bicoloredbase extends BicoloredDrawable { 30 | constructor() { 31 | super(mesh, texture); 32 | } 33 | } 34 | 35 | return bicoloredbase; 36 | } 37 | 38 | function makeShieldEffect(mesh, texture) { 39 | class shieldeffectbase extends ShieldEffectDrawable { 40 | constructor() { 41 | super(mesh, texture); 42 | } 43 | } 44 | 45 | return shieldeffectbase; 46 | } 47 | 48 | function makeOrnament(mesh, texture) { 49 | class ornamentbase extends OrnamentDrawable { 50 | constructor() { 51 | super(mesh, texture); 52 | } 53 | } 54 | 55 | return ornamentbase; 56 | } 57 | 58 | World.Portal = makeGlowramp(meshes.Portal, textures.Glowramp); 59 | World.Waypoint = makeGlowramp(meshes.Waypoint, textures.Waypoint); 60 | World.ArtifactsRedGlow = makeGlowramp(meshes.ArtifactsRedGlow, textures.ColorGlow); 61 | World.ArtifactsGreenGlow = makeGlowramp(meshes.ArtifactsGreenGlow, textures.ColorGlow); 62 | World.ArtifactsPurpleGlow = makeGlowramp(meshes.ArtifactsPurpleGlow, textures.ColorGlow); 63 | World.ArtifactsTargetGlow = makeGlowramp(meshes.ArtifactsTargetGlow, textures.TargetGlow); 64 | 65 | World.Shield = makeShieldEffect(meshes.Shield, textures.ShieldEffect); 66 | World.Resonator = makeBicolored(meshes.Resonator, textures.FlipCard); 67 | 68 | World.OrnamentMeetupPoint = makeOrnament(meshes.OrnamentMeetupPoint, textures.OrnamentMeetupPoint); 69 | World.OrnamentFinishPoint = makeOrnament(meshes.OrnamentFinishPoint, textures.OrnamentFinishPoint); 70 | World.OrnamentCluster = makeOrnament(meshes.OrnamentCluster, textures.OrnamentCluster); 71 | World.OrnamentVolatile = makeOrnament(meshes.OrnamentVolatile, textures.OrnamentVolatile); 72 | 73 | export default World; 74 | -------------------------------------------------------------------------------- /src/drawable/xm.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import TexturedDrawable from './textured'; 3 | import { vec4 } from 'gl-matrix'; 4 | 5 | 6 | const PROGRAM = Constants.Program.Xm; 7 | const defaultTeamColor = vec4.clone(Constants.xmColors.coreGlow); 8 | const defaultAltColor = vec4.clone(Constants.xmColors.coreGlowAlt); 9 | 10 | /** 11 | * An XmDrawable is a drawable representing the animate "xm core" of inventory items 12 | * 13 | * @param {String} meshName Mesh internal name 14 | * @param {String} textureName Texture internal name 15 | * @param {vec4} teamColor Color of the xm glow. 16 | * @return {void} 17 | */ 18 | class XmDrawable extends TexturedDrawable { 19 | 20 | constructor(meshName, textureName, teamColor) { 21 | super(PROGRAM, meshName, textureName); 22 | this.uniforms.u_elapsedTime = 0; 23 | this.uniforms.u_teamColor = vec4.clone(teamColor || defaultTeamColor); 24 | this.uniforms.u_altColor = vec4.clone(defaultAltColor); 25 | } 26 | 27 | /** 28 | * Animates the xm core 29 | * @param {Number} delta Time since last frame 30 | * @return {Boolean} Returns true to continue the animation. 31 | */ 32 | updateTime(delta) { 33 | var ret = super.updateTime(delta); 34 | this.uniforms.u_elapsedTime = ((this.elapsed / 1000) % 300.0) * 0.1; 35 | return ret; 36 | } 37 | } 38 | 39 | export default XmDrawable; 40 | -------------------------------------------------------------------------------- /src/engine.js: -------------------------------------------------------------------------------- 1 | import AssetManager from './asset-manager'; 2 | import ObjectRenderer from './renderer/object'; 3 | import { resetGL } from './utils'; 4 | import World from './drawable/world'; 5 | import Resource from './drawable/resource'; 6 | import Inventory from './drawable/inventory'; 7 | import InventoryItems from './entity/inventory'; 8 | import PortalEntity from './entity/portal'; 9 | import Camera from './camera'; 10 | import { vec3, mat4 } from 'gl-matrix'; 11 | 12 | /** 13 | * The Engine provides nearly all the mechanics for actually drawing things to a canvas. 14 | * 15 | * Also includes a few simple functions for demoing various entities/drawables. This 16 | * will probably go away in a future release. 17 | * 18 | * @param {HTMLCanvas} canvas A Canvas element 19 | * @param {Object} assets A manifest to pass to the internal AssetManager 20 | * @see AssetManager 21 | * @param {Boolean} enableSnapshots If set to true, the canvas will preserve its drawing 22 | * buffer, to allow for accurate .toDataURL calls. 23 | * This will have a performance impact. 24 | */ 25 | class Engine { 26 | 27 | constructor(canvas, assets, enableSnapshots) { 28 | this.canvas = canvas; 29 | var opt = {}; 30 | if(enableSnapshots) { 31 | opt.preserveDrawingBuffer = true; 32 | } 33 | this.canScreenshot = enableSnapshots && !!canvas.toBlob; 34 | var gl = canvas.getContext('webgl', opt) || canvas.getContext('experimental-webgl', opt); 35 | if(!gl) 36 | { 37 | throw 'Could not initialize webgl'; 38 | } 39 | gl.clearColor(0.0, 0.0, 0.0, 1.0); 40 | this.gl = gl; 41 | this.camera = new Camera(canvas.width, canvas.height); 42 | this.camera.setPosition( 43 | vec3.fromValues(0.0, 20.0, 25.0) 44 | ).lookAt( 45 | vec3.fromValues(0.0, 10.0, 0.0) 46 | ); 47 | 48 | this.assetManager = new AssetManager(this.gl, assets); 49 | this.objectRenderer = new ObjectRenderer(this.gl, this.assetManager); 50 | this._start = this._last = null; 51 | this._frame = null; 52 | this.scale = 1; 53 | this.resize(); 54 | } 55 | 56 | /** 57 | * Resize the canvas and viewport to new dimensions. 58 | * Uses the canvas' clientWidth and clientHeight to determine viewport size, 59 | * if not provided. 60 | * 61 | * @chainable 62 | * @param {Number} width (optional) width 63 | * @param {Number} height (optional) height 64 | * @return {this} Returns `this` 65 | */ 66 | resize(width, height) { 67 | let devicePixels = window.devicePixelRatio; 68 | if(!width) { 69 | width = this.canvas.clientWidth; 70 | } 71 | if (!height) { 72 | height = this.canvas.clientHeight; 73 | } 74 | let targetWidth = Math.floor(width * this.scale * devicePixels); 75 | let targetHeight = Math.floor(height * this.scale * devicePixels); 76 | this.canvas.width = targetWidth; 77 | this.canvas.height = targetHeight; 78 | this.camera.setDimensions(targetWidth, targetHeight); 79 | this.gl.viewport(0, 0, targetWidth, targetHeight); 80 | return this.updateView(); 81 | } 82 | 83 | /** 84 | * Sets the scaling factor for the canvas. 85 | * 86 | * @chainable 87 | * @param {Number} factor The scale factor 88 | * @return {this} Returns `this` 89 | */ 90 | rescale(factor) { 91 | this.scale = factor; 92 | return this.resize(); 93 | } 94 | 95 | /** 96 | * Updates the current drawing viewport to the canvas' current dimensions 97 | * 98 | * @chainable 99 | * @return {this} Returns `this` 100 | */ 101 | updateView() { 102 | this.objectRenderer.updateView(this.camera); 103 | return this; 104 | } 105 | 106 | /** 107 | * Stops the render loop, if it's running. 108 | * 109 | * @chainable 110 | * @return {this} Returns `this` 111 | */ 112 | stop() { 113 | this._last = this._start = null; 114 | if(this._frame) { 115 | window.cancelAnimationFrame(this._frame); 116 | } 117 | return this; 118 | } 119 | 120 | /** 121 | * Adds one of each inventory item, and a portal, to the scene 122 | * @return {void} 123 | */ 124 | demoEntities() { 125 | var x = -5, y = 0, z = 4; 126 | var i, item; 127 | for(i in InventoryItems) { 128 | item = new InventoryItems[i](this); 129 | if(item) { 130 | item.translate(vec3.fromValues(x, y, z)); 131 | x++; 132 | if(x > 5) { 133 | x = -5; 134 | z--; 135 | } 136 | } 137 | } 138 | var portal = new PortalEntity(this); 139 | portal.translate(vec3.fromValues(x, y, z)); 140 | } 141 | 142 | /** 143 | * Adds one of each drawable to the scene 144 | * @return {void} 145 | */ 146 | demo() { 147 | var x = -5, y = 0, z = 4; 148 | var i, item; 149 | for(i in Inventory) { 150 | item = new Inventory[i](); 151 | if(item) { 152 | mat4.translate(item.world, item.world, vec3.fromValues(x, y, z)); 153 | x++; 154 | if(x > 5) { 155 | x = -5; 156 | z--; 157 | } 158 | this.objectRenderer.addDrawable(item); 159 | } 160 | } 161 | 162 | for(i in Resource) { 163 | item = new Resource[i](); 164 | if(item) { 165 | mat4.translate(item.world, item.world, vec3.fromValues(x, y, z)); 166 | x++; 167 | if(x > 5) { 168 | x = -5; 169 | z--; 170 | } 171 | this.objectRenderer.addDrawable(item); 172 | } 173 | } 174 | 175 | for(i in World) { 176 | item = new World[i](); 177 | if(item) { 178 | mat4.translate(item.world, item.world, vec3.fromValues(x, y, z)); 179 | x++; 180 | if(x > 5) { 181 | x = -5; 182 | z--; 183 | } 184 | this.objectRenderer.addDrawable(item); 185 | } 186 | } 187 | } 188 | 189 | /** 190 | * Draw a single frame, with a specified time since last draw 191 | * @param {Number} delta Time since last render 192 | * @return {void} 193 | */ 194 | draw(delta) { 195 | var gl = this.gl; 196 | // default setup stuff: 197 | gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); 198 | resetGL(gl); 199 | //gl.enable(gl.BLEND); 200 | //gl.depthMask(false); 201 | 202 | // render passes: 203 | this.objectRenderer.render(); 204 | 205 | // run animations 206 | this.objectRenderer.updateTime(delta); 207 | this.camera.updateTime(delta); 208 | } 209 | 210 | /** 211 | * Start the render loop. 212 | * @param {Number} tick Time since last tick (optional) 213 | * @return {void} 214 | */ 215 | render(tick) { 216 | var delta = 0; 217 | if(!this._start) { 218 | this._start = tick; 219 | this._last = tick; 220 | } 221 | else if (tick) { 222 | delta = tick - this._last; 223 | this._last = tick; 224 | } 225 | this.draw(delta); 226 | // queue up next frame: 227 | this._frame = window.requestAnimationFrame(this.render.bind(this)); 228 | } 229 | 230 | /** 231 | * Preloads all assets 232 | * @param {Function} callback Callback to invoke on completion 233 | * @return {void} 234 | */ 235 | preload() { 236 | return this.assetManager.loadAll(); 237 | } 238 | 239 | /** 240 | * Captures a screenshot, if enabled 241 | * 242 | * @param {String} mimeType The mime type of the image 243 | * @param {Number} quality Quality, if applicable (applies to image/jpeg) 244 | * @return {Promise} A promise that resolves when the screenshot is complete 245 | */ 246 | capture(mimeType, quality) { 247 | if (this.canScreenshot) { 248 | this.stop(); 249 | let promise = new Promise((resolve, reject) => { 250 | try { 251 | this.canvas.toBlob((blob) => { 252 | resolve(blob); 253 | }, mimeType, quality); 254 | } catch (e) { 255 | reject(e); 256 | } 257 | }); 258 | // promise.then(() => { 259 | // this.render(); 260 | // }, () => { 261 | // this.render(); 262 | // }); 263 | return promise; 264 | } else { 265 | return Promise.reject(new Error('Screenshots not enabled. Initialize engine with `enableSnapshots` and ensure `canvas.toBlob` is supported by your browser.')); 266 | } 267 | } 268 | } 269 | 270 | export default Engine; 271 | -------------------------------------------------------------------------------- /src/entity.js: -------------------------------------------------------------------------------- 1 | import { mat4 } from 'gl-matrix'; 2 | 3 | // TODO: Deprecate 4 | class Entity { 5 | constructor(engine) { 6 | this.drawables = {}; 7 | this.transform = mat4.create(); 8 | this.engine = engine; 9 | } 10 | 11 | addDrawable(name, drawable) { 12 | // add dispose if this already exists. 13 | this.removeDrawable(name); 14 | this.drawables[name] = drawable; 15 | this.engine.objectRenderer.addDrawable(drawable); 16 | } 17 | 18 | removeDrawable(name, destroy) { 19 | // dispose stuffs. 20 | if(this.drawables[name]) { 21 | this.engine.objectRenderer.removeDrawable(this.drawables[name], destroy); 22 | } 23 | } 24 | 25 | applyTransform() { 26 | for(var i in this.drawables) 27 | { 28 | this.drawables[i].updateWorld(this.transform); 29 | } 30 | } 31 | 32 | translate(vec) { 33 | mat4.translate(this.transform, this.transform, vec); 34 | this.applyTransform(); 35 | } 36 | 37 | rotate(quat) { 38 | var rotate = mat4.create(); 39 | mat4.fromQuat(rotate, quat); 40 | mat4.multiply(this.transform, this.transform, rotate); 41 | this.applyTransform(); 42 | } 43 | 44 | setAnimation(animate) { 45 | for(var i in this.drawables) 46 | { 47 | this.drawables[i].onUpdate = animate; 48 | } 49 | } 50 | } 51 | 52 | export default Entity; 53 | -------------------------------------------------------------------------------- /src/entity/inventory.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import Entity from '../entity'; 3 | import Inventory from '../drawable/inventory'; 4 | import { vec4 } from 'gl-matrix'; 5 | 6 | // TODO: Deprecate in favor of a proper scene graph 7 | var InventoryItems = {}; 8 | 9 | var simple = { 10 | Xmp: 'L8', 11 | Ultrastrike: 'L8', 12 | ResShield: 'VERY_RARE', 13 | PowerCube: 'L8', 14 | LinkAmp: 'EXTREMELY_RARE', 15 | HeatSink: 'VERY_RARE', 16 | MultiHack: 'VERY_RARE', 17 | ForceAmp: 'RARE', 18 | Turret: 'RARE', 19 | Resonator: 'L8', 20 | Capsule: 'RARE' 21 | }; 22 | 23 | export function createItemEntity(name, color) { 24 | 25 | class entitybase extends Entity { 26 | constructor(engine) { 27 | super(engine); 28 | this.addDrawable(name, new Inventory[name]()); 29 | this.addDrawable(name + 'Xm', new Inventory[name + 'Xm']()); 30 | this.drawables[name].uniforms.u_color0 = vec4.clone(color); 31 | } 32 | } 33 | 34 | return entitybase; 35 | } 36 | 37 | for(var i in simple) { 38 | InventoryItems[i] = createItemEntity(i, Constants.qualityColors[simple[i]]); 39 | } 40 | 41 | class FlipCardAda extends Entity { 42 | constructor(engine) { 43 | super(engine); 44 | this.addDrawable('FlipCardAda', new Inventory.FlipCardAda()); 45 | this.addDrawable('FlipCardXm', new Inventory.FlipCardXm()); 46 | this.drawables.FlipCardXm.uniforms.u_teamColor = vec4.clone(Constants.teamColors.RESISTANCE); 47 | this.drawables.FlipCardAda.uniforms.u_color1 = vec4.clone(Constants.teamColors.RESISTANCE); 48 | this.drawables.FlipCardAda.uniforms.u_color0 = vec4.clone(Constants.qualityColors.VERY_RARE); 49 | } 50 | } 51 | 52 | InventoryItems.FlipCardAda = FlipCardAda; 53 | 54 | class FlipCardJarvis extends Entity { 55 | constructor(engine) { 56 | super(engine); 57 | this.addDrawable('FlipCardJarvis', new Inventory.FlipCardJarvis()); 58 | this.addDrawable('FlipCardXm', new Inventory.FlipCardXm()); 59 | this.drawables.FlipCardXm.uniforms.u_teamColor = vec4.clone(Constants.teamColors.ENLIGHTENED); 60 | this.drawables.FlipCardJarvis.uniforms.u_color1 = vec4.clone(Constants.teamColors.ENLIGHTENED); 61 | this.drawables.FlipCardJarvis.uniforms.u_color0 = vec4.clone(Constants.qualityColors.VERY_RARE); 62 | } 63 | } 64 | 65 | InventoryItems.FlipCardJarvis = FlipCardJarvis; 66 | 67 | class ExtraShield extends Entity { 68 | constructor(engine) { 69 | super(engine); 70 | this.addDrawable('ExtraShield', new Inventory.ExtraShield()); 71 | this.addDrawable('ResShieldXm', new Inventory.ResShieldXm()); 72 | this.drawables.ExtraShield.uniforms.u_color0 = vec4.clone(Constants.qualityColors.VERY_RARE); 73 | } 74 | } 75 | 76 | InventoryItems.ExtraShield = ExtraShield; 77 | 78 | class InterestCapsule extends Entity { 79 | constructor(engine) { 80 | super(engine); 81 | this.addDrawable('InterestCapsule', new Inventory.InterestCapsule()); 82 | this.addDrawable('CapsuleXm', new Inventory.CapsuleXm()); 83 | this.drawables.InterestCapsule.uniforms.u_color0 = vec4.clone(Constants.qualityColors.VERY_RARE); 84 | } 85 | } 86 | 87 | InventoryItems.InterestCapsule = InterestCapsule; 88 | 89 | class PortalKeyResourceUnit extends Entity { 90 | constructor(engine){ 91 | super(engine); 92 | this.addDrawable('PortalKey', new Inventory.PortalKeyResourceUnit()); 93 | } 94 | } 95 | 96 | InventoryItems.PortalKeyResourceUnit = PortalKeyResourceUnit; 97 | 98 | class KeyCapsule extends Entity { 99 | constructor(engine) { 100 | super(engine); 101 | this.addDrawable('KeyCapsule', new Inventory.KeyCapsule()); 102 | this.addDrawable('KeyCapsuleXm', new Inventory.KeyCapsuleXm()); 103 | this.drawables.KeyCapsule.uniforms.u_color0 = vec4.clone(Constants.keyCapsuleColors.blue[0]); 104 | this.drawables.KeyCapsule.uniforms.u_color1 = vec4.clone(Constants.keyCapsuleColors.blue[1]); 105 | this.drawables.KeyCapsuleXm.uniforms.u_teamColor = vec4.clone(Constants.xmColors.coreGlowChaotic); 106 | this.drawables.KeyCapsuleXm.uniforms.u_altColor = vec4.clone(Constants.xmColors.coreGlowChaoticAlt); 107 | } 108 | } 109 | 110 | InventoryItems.KeyCapsule = KeyCapsule; 111 | 112 | export default InventoryItems; 113 | -------------------------------------------------------------------------------- /src/entity/portal.js: -------------------------------------------------------------------------------- 1 | import Constants from '../constants'; 2 | import Entity from '../entity'; 3 | import World from '../drawable/world'; 4 | import ResonatorLink from '../drawable/resonator-link'; 5 | import { vec3, vec4, mat4 } from 'gl-matrix'; 6 | 7 | 8 | // TODO: Deprecate in favor of a proper scene graph 9 | class PortalEntity extends Entity { 10 | constructor(engine) { 11 | super(engine); 12 | this.addDrawable('Portal', new World.Portal()); 13 | // why 6? I dunno, ask Niantic 14 | mat4.scale(this.drawables.Portal.local, this.drawables.Portal.local, vec3.fromValues(6, 6, 6)); 15 | this.setColor(vec4.clone(Constants.teamColors.LOKI)); 16 | } 17 | 18 | setColor(color) { 19 | this.color = vec4.clone(color); 20 | this.drawables.Portal.uniforms.u_baseColor = this.color; 21 | if(this.drawables.Shield) { 22 | this.drawables.Shield.uniforms.u_color = this.color; 23 | } 24 | if(this.drawables.ArtifactsGreenGlow) { 25 | this.drawables.ArtifactsGreenGlow.u_baseColor = this.color; 26 | } 27 | /*for(var i = 0; i < 8; i++) { 28 | this._redrawLink(i);sd 29 | }*/ 30 | } 31 | 32 | addResonator(level, slot, range, percent) { 33 | if(percent === undefined) { 34 | percent = 1.0; 35 | } 36 | if(+slot < 0 || +slot > 8) { 37 | throw new Error('slot out of bounds for resonator'); 38 | } 39 | if(!(level in Constants.qualityColors)) { 40 | throw new Error('level must be one of ' + Object.keys(Constants.qualityColors).join(' ')); 41 | } 42 | range = range === undefined ? 40 : range; 43 | var resonatorName = 'Resonator' + (+slot); 44 | var linkName = 'Link' + (+slot); 45 | var theta = slot / 8 * 2 * Math.PI; 46 | var resonator = new World.Resonator(); 47 | var x = range * Math.cos(theta); 48 | var y = range * Math.sin(theta); 49 | var link = new ResonatorLink( 50 | [0,0], 51 | slot, 52 | range, 53 | vec4.clone(this.color), 54 | 1.0 55 | ); 56 | resonator.uniforms.u_color0 = vec4.clone(Constants.qualityColors[level]); 57 | resonator.world = mat4.clone(this.drawables.Portal.local); 58 | //link.local = mat4.clone(this.drawables.Portal.local); 59 | mat4.translate( 60 | resonator.world, 61 | resonator.world, 62 | vec3.fromValues(x / 6, 0, y / 6) 63 | ); 64 | resonator.updateMatrix(); 65 | link.updateMatrix(); 66 | // keep the portal sorted last (this is a terrible way of doing this.) 67 | this.addDrawable(linkName, link); 68 | this.addDrawable(resonatorName, resonator); 69 | this.addDrawable('Portal', this.drawables.Portal); 70 | } 71 | 72 | removeResonator(slot) { 73 | if(+slot < 0 || +slot > 8) { 74 | throw new Error('slot out of bounds for resonator'); 75 | } 76 | var name = 'Resonator' + (+slot); 77 | var resonator = this.drawables[name] || null; 78 | if(resonator) { 79 | this.removeDrawable(name); 80 | this._removeResonatorLink(slot); 81 | this.addDrawable('Portal', this.drawables.Portal); 82 | } 83 | } 84 | 85 | addShield() { 86 | if(!('Shield' in this.drawables)) { 87 | this.addDrawable('Shield', new World.Shield()); 88 | // why 12? I don't know. 89 | mat4.scale(this.drawables.Shield.local, this.drawables.Shield.local, vec3.fromValues(12, 12, 12)); 90 | this.drawables.Shield.updateMatrix(); 91 | } 92 | this.drawables.Shield.uniforms.u_color = this.color; 93 | this.applyTransform(); 94 | } 95 | 96 | addArtifact(artifact, name) { 97 | var rotate = function(delta/*, elapsed*/) { 98 | mat4.rotateY(this.model, this.model, delta / 1000); 99 | this.updateMatrix(); 100 | return true; 101 | }; 102 | if(!(name in this.drawables)) { 103 | this.addDrawable(name, artifact); 104 | } 105 | this.drawables[name].onUpdate = rotate; 106 | this.applyTransform(); 107 | } 108 | 109 | addGlowMarker(name, color) { 110 | var n = 'Artifacts' + name + 'Glow'; 111 | if(!(n in this.drawables)) { 112 | this.addDrawable(n, new World[n]()); 113 | } 114 | this.drawables[n].uniforms.u_baseColor = vec4.clone(color); 115 | } 116 | } 117 | 118 | export default PortalEntity; 119 | -------------------------------------------------------------------------------- /src/geometry/field.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var FieldGeometry = (function(){ 3 | 4 | // 5 sets of 4 points, breaking the link into 4 pieces, each providing 4 faces 5 | var fillChunk = function(vert, pos, col, index, v1, f1, f2, v2) 6 | { 7 | var off = index * 4; 8 | vert[index * 3 + 0] = v1.x; 9 | vert[index * 3 + 1] = 0; 10 | vert[index * 3 + 2] = v1.y; 11 | pos[off + 0] = v1.x; 12 | pos[off + 1] = v1.y; 13 | pos[off + 2] = f1; 14 | pos[off + 3] = f2; 15 | col[off + 0] = v2.x; 16 | col[off + 1] = v2.y; 17 | col[off + 2] = v2.z; 18 | col[off + 3] = v2.w; 19 | return index + 1; 20 | }; 21 | 22 | var fillVectors = function(vert, pos, col, index, v1, f1, v2, f2, v3, f3, v4) 23 | { 24 | return fillChunk(vert, pos, col, fillChunk(vert, pos, col, fillChunk(vert, pos, col, index, v1, 0, f1, v4), v2, 0, f2, v4), v3, ((v2.x - v1.x) * (v1.y - v3.y) - (v2.y - v1.y) * (v1.x - v3.x)) / v1.distanceTo(v2), f3, v4); 25 | }; 26 | 27 | var fieldgeometry = function(options) 28 | { 29 | options = options || {}; 30 | options.transparent = true; 31 | Geometry.call(this, options); 32 | this.position = new THREE.BufferAttribute(new Float32Array(), 3); 33 | this.index = new THREE.BufferAttribute(new Uint16Array(), 1); 34 | this.a_position = new THREE.BufferAttribute(new Float32Array(), 4); 35 | this.a_color = new THREE.BufferAttribute(new Float32Array(), 4); 36 | this.geometry.addAttribute('position', this.position); 37 | this.geometry.addAttribute('index', this.index); 38 | this.geometry.addAttribute('a_position', this.a_position); 39 | this.geometry.addAttribute('a_color', this.a_color); 40 | this.attributes = { 41 | "a_position": { type: "v4", values: null }, 42 | "a_color": { type: "v4", values: null } 43 | }; 44 | this.fieldCount = 0; 45 | this.lastOffset = 0; 46 | }; 47 | inherits(fieldgeometry, Geometry); 48 | 49 | fieldgeometry.prototype.extendBuffer = function(attribute, count) 50 | { 51 | var buf = new Float32Array(attribute.length + (count * attribute.itemSize)); 52 | buf.set(attribute.array); 53 | attribute.array = buf; 54 | return buf; 55 | }; 56 | 57 | fieldgeometry.prototype.addField = function(A, B, C, color, duration /* ? */) 58 | { 59 | duration = 1.0; 60 | var f1 = 1.1, 61 | f2 = A.percent, 62 | f3 = B.percent, 63 | f4 = C.percent; 64 | var vert = this.extendBuffer(this.position, 9), 65 | pos = this.extendBuffer(this.a_position, 9), 66 | col = this.extendBuffer(this.a_color, 9); 67 | if(duration < 1.0) 68 | { 69 | var f6 = Math.max(0.0, duration); 70 | f1 = 1.1 * f6; 71 | var f7 = f6 * f6; 72 | f2 *= f7; 73 | f3 *= f7; 74 | f4 *= f7; 75 | } 76 | var aVec = new THREE.Vector2(A.x, A.y), 77 | bVec = new THREE.Vector2(B.x, B.y), 78 | cVec = new THREE.Vector2(C.x, C.y); 79 | var center = aVec.clone().add(bVec).add(cVec).multiplyScalar(0.333333333333); 80 | var start = this.lastOffset; 81 | this.lastOffset = fillVectors(vert, pos, col, this.lastOffset, aVec, f2, bVec, f3, center, f1, color); 82 | this.lastOffset = fillVectors(vert, pos, col, this.lastOffset, bVec, f3, cVec, f4, center, f1, color); 83 | this.lastOffset = fillVectors(vert, pos, col, this.lastOffset, cVec, f4, aVec, f2, center, f1, color); 84 | var ind = new Uint16Array((this.fieldCount + 1) * 9); 85 | ind.set(this.index.array); 86 | for(var i = 0; i < 9; i++) 87 | { 88 | ind[start + i] = start + i; 89 | } 90 | this.index.array = ind; 91 | this.index.needsUpdate = true; 92 | this.position.needsUpdate = true; 93 | this.a_position.needsUpdate = true; 94 | this.a_color.needsUpdate = true; 95 | this.geometry.needsUpdate = true; 96 | this.fieldCount++; 97 | return this.fieldCount; 98 | }; 99 | 100 | return fieldgeometry; 101 | }()); 102 | 103 | imv.Geometries = imv.Geometries || {}; 104 | imv.Geometries.Field = FieldGeometry; 105 | -------------------------------------------------------------------------------- /src/geometry/parametric.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var ParametricGeometry = (function(){ 3 | 4 | // basic template for our parametric function. 5 | // takes u, v; 6 | // gives array of proper length (options.paramSize) 7 | var linear = function(u, v) { 8 | // x, y, z 9 | return [u, 0, v]; 10 | }; 11 | 12 | var parametric = function(func, options) 13 | { 14 | Geometry.call(this, options); 15 | func = func || linear; 16 | options = options || {}; 17 | var params = { 18 | slices: 1, 19 | paramSize: 3 20 | }; 21 | this.options = setParams(params, options); 22 | var t = func(0, 0); 23 | if(!t.length || t.length != this.options.paramSize) 24 | { 25 | throw 'Parametric function returned invalid results, must be array of length ' + this.options.paramSize; 26 | } 27 | this.attributes = { 28 | 'a_position': { type: 'v' + this.options.paramSize, values: null }, 29 | 'a_texCoord0': { type: 'v2', values: null } 30 | }; 31 | var n = this.options.slices, l = this.options.paramSize; 32 | // (n + 1)^2 points to define n x n squares in u,v space 33 | var len = (n + 1) * (n + 1); 34 | var position = new Float32Array(len * 3); 35 | var a_position = new Float32Array(len * l); 36 | var a_texCoord0 = new Float32Array(len * 2); 37 | // number of square subdivisions is n^2, 3 indices per face, 2 faces per square 38 | var faces = new Uint16Array(n * n * 3 * 2); 39 | var f = 0; 40 | for(var i = 0; i <= n; i++) 41 | { 42 | for(var j = 0; j <= n; j++) 43 | { 44 | var u = i / n, v = j / n; 45 | var slice = func(u, v); 46 | var idx = i * (n + 1) + j; 47 | a_texCoord0[idx * 2] = u; 48 | a_texCoord0[idx * 2 + 1] = v; 49 | for(var k = 0; k < l; k++) 50 | { 51 | a_position[(idx * l) + k] = slice[k]; 52 | if(k < 3) 53 | { 54 | position[(idx * l) + k] = slice[k]; 55 | } 56 | } 57 | if(l < 3) 58 | { 59 | position[(idx * 3) + 2] = 0.0; 60 | } 61 | if(i < n && j < n) 62 | { 63 | faces[f++] = idx; 64 | faces[f++] = idx + 1; 65 | faces[f++] = idx + n + 1; 66 | faces[f++] = idx + n + 1; 67 | faces[f++] = idx + 1; 68 | faces[f++] = idx + n + 2; 69 | } 70 | } 71 | } 72 | this.geometry.addAttribute('a_position', new THREE.BufferAttribute(a_position, l)); 73 | this.geometry.addAttribute('a_texCoord0', new THREE.BufferAttribute(a_texCoord0, 2)); 74 | this.geometry.addAttribute('position', new THREE.BufferAttribute(position, 3)); 75 | this.geometry.addAttribute('index', new THREE.BufferAttribute(faces, 1)); 76 | }; 77 | inherits(parametric, Geometry); 78 | 79 | return parametric; 80 | }()); 81 | 82 | imv.Geometries = imv.Geometries || {}; 83 | imv.Geometries.Parametric = ParametricGeometry; 84 | -------------------------------------------------------------------------------- /src/geometry/particle-portal.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var ParticlePortalsGeometry = (function(){ 3 | 4 | var MAX_SYSTEMS = 40, 5 | NUM_PARTICLES = 96, 6 | NUM_INDICES_PER_PARTICLE = 4, 7 | NUM_INDICES_PER_FACE = 6; 8 | 9 | var U = [0.0, 0.0, 1.0, 1.0]; 10 | var V = [1.0, 0.0, 1.0, 0.0]; 11 | 12 | var extendBuffer = function(attribute, count) 13 | { 14 | var buf = new Float32Array(attribute.length + (count * attribute.itemSize)); 15 | buf.set(attribute.array); 16 | attribute.array = buf; 17 | return buf; 18 | }; 19 | 20 | var particlePortalsGeometry = function() { 21 | Geometry.call(this, {transparent: true}); 22 | this.count = 0; 23 | this.attributes = { 24 | 'a_position': { type: 'v3', values: null }, 25 | 'a_texCoord0': { type: 'v2', values: null }, 26 | 'a_scale': { type: "f", values: null }, 27 | 'a_speed': { type: "f", values: null }, 28 | 'a_portalIndex': { type: "f", values: null }, 29 | 'a_index': { type: "f", values: null} 30 | }; 31 | this.position = new THREE.BufferAttribute(new Float32Array(), 3); 32 | this.index = new THREE.BufferAttribute(new Uint16Array(), 1); 33 | this.a_position = new THREE.BufferAttribute(new Float32Array(), 3); 34 | this.a_texCoord0 = new THREE.BufferAttribute(new Float32Array(), 2); 35 | this.a_scale = new THREE.BufferAttribute(new Float32Array(), 1); 36 | this.a_speed = new THREE.BufferAttribute(new Float32Array(), 1); 37 | this.a_portalIndex = new THREE.BufferAttribute(new Float32Array(), 1); 38 | this.a_index = new THREE.BufferAttribute(new Float32Array(), 1); 39 | this.seeds = []; 40 | for(var i = 0; i < NUM_PARTICLES; i++) 41 | { 42 | this.seeds.push({ 43 | x: Math.random() - 0.5, 44 | y: 0.4 * Math.random() - 0.2, 45 | z: Math.random() - 0.5, 46 | a_scale: 10.0 * (0.1 + 0.9 * Math.random()), 47 | a_speed: 6.0 * (0.5 + 0.5 * Math.random()) 48 | }); 49 | } 50 | this.geometry.addAttribute('a_position', this.a_position); 51 | this.geometry.addAttribute('a_texCoord0', this.a_texCoord0); 52 | this.geometry.addAttribute('a_scale', this.a_scale); 53 | this.geometry.addAttribute('a_speed', this.a_speed); 54 | this.geometry.addAttribute('a_portalIndex', this.a_portalIndex); 55 | this.geometry.addAttribute('a_index', this.a_index); 56 | this.geometry.addAttribute('position', this.position); 57 | this.geometry.addAttribute('index', this.index); 58 | }; 59 | inherits(particlePortalsGeometry, Geometry); 60 | 61 | particlePortalsGeometry.prototype.addSystem = function() { 62 | if(this.count + 1 >= MAX_SYSTEMS) 63 | { 64 | throw 'This system is full'; 65 | } 66 | var n = NUM_PARTICLES * NUM_INDICES_PER_PARTICLE; 67 | var a_position = extendBuffer(this.a_position, n); 68 | var a_texCoord0 = extendBuffer(this.a_texCoord0, n); 69 | var a_scale = extendBuffer(this.a_scale, n); 70 | var a_speed = extendBuffer(this.a_speed, n); 71 | var a_portalIndex = extendBuffer(this.a_portalIndex, n); 72 | var a_index = extendBuffer(this.a_index, n); 73 | var position = extendBuffer(this.position, n); 74 | var c = this.count++; 75 | var idx = c * n, seed, i, j; 76 | for(i = 0; i < NUM_PARTICLES; i++) 77 | { 78 | seed = this.seeds[i]; 79 | for(j = 0; j < NUM_INDICES_PER_PARTICLE; j++) 80 | { 81 | position[idx * 3 + 0] = 0;//seed.x; 82 | position[idx * 3 + 1] = 0;//seed.y; 83 | position[idx * 3 + 2] = 0;//seed.z; 84 | a_position[idx * 3 + 0] = seed.x; 85 | a_position[idx * 3 + 1] = seed.y; 86 | a_position[idx * 3 + 1] = seed.z; 87 | a_texCoord0[idx * 2 + 0] = U[j]; 88 | a_texCoord0[idx * 2 + 1] = V[j]; 89 | a_scale[idx] = seed.a_scale; 90 | a_speed[idx] = seed.a_speed; 91 | a_portalIndex[idx] = c; 92 | a_index[idx] = i; 93 | idx++; 94 | } 95 | } 96 | 97 | var index = new Uint16Array((c + 1) * NUM_PARTICLES * NUM_INDICES_PER_FACE); 98 | index.set(this.index.array); 99 | var indices = [0, 1, 2, 1, 3, 2]; 100 | idx = c * n; 101 | var f = c * NUM_PARTICLES * NUM_INDICES_PER_FACE; 102 | for(i = 0; i < NUM_PARTICLES; i++) 103 | { 104 | for(j = 0; j < NUM_INDICES_PER_FACE; j++) 105 | { 106 | index[f + j] = idx + indices[j]; 107 | } 108 | f += 6; 109 | idx += 4; 110 | } 111 | this.index.array = index; 112 | this.position.needsUpdate = true; 113 | this.index.needsUpdate = true; 114 | this.a_position.needsUpdate = true; 115 | this.a_texCoord0.needsUpdate = true; 116 | this.a_scale.needsUpdate = true; 117 | this.a_speed.needsUpdate = true; 118 | this.a_portalIndex.needsUpdate = true; 119 | this.a_index.needsUpdate = true; 120 | this.geometry.needsUpdate = true; 121 | }; 122 | 123 | return particlePortalsGeometry; 124 | }()); 125 | 126 | imv.Geometries = imv.Geometries || {}; 127 | imv.Geometries.ParticlePortals = ParticlePortalsGeometry; 128 | -------------------------------------------------------------------------------- /src/gl-bound.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base class for all things bound to a gl context. 3 | * 4 | * @param {context} gl A webgl context 5 | */ 6 | class GLBound { 7 | constructor(gl) { 8 | this._gl = gl; 9 | } 10 | } 11 | 12 | export default GLBound; 13 | -------------------------------------------------------------------------------- /src/gl/gl-attribute.js: -------------------------------------------------------------------------------- 1 | import GLBuffer from './gl-buffer'; 2 | 3 | /** 4 | * A GLAttribute is a GLBuffer that represents vertex attributes 5 | * 6 | * @private 7 | * @extends {GLBuffer} 8 | * @chainable 9 | * @param {context} gl WebGLContext 10 | * @param {Array} attributes An array of VertexAttributes 11 | * @param {ArrayBuffer} values Values to fill the buffer with 12 | * @param {enum} usage Usage @see https://www.khronos.org/registry/webgl/specs/1.0/#5.14.5 13 | * @return {this} The new GLAttribute 14 | */ 15 | class GLAttribute extends GLBuffer { 16 | constructor(gl, attributes, values, usage) { 17 | usage = usage || gl.STATIC_DRAW; 18 | super(gl, gl.ARRAY_BUFFER, usage); 19 | this.attributes = attributes; 20 | this.values = values; 21 | this.size = this.count = null; 22 | this._validate = false; 23 | this.size = 0; 24 | this.width = 0; 25 | for(var i = 0, a; i < this.attributes.length; i++) 26 | { 27 | a = this.attributes[i]; 28 | this.size += 4 * a.size; // 4 because float is 4 bytes. 29 | this.width += a.size; 30 | } 31 | return this; 32 | } 33 | 34 | /** 35 | * Confirms that the underlying buffer's length is an even multiple 36 | * of total size of the attributes for the buffer 37 | * 38 | * Issues a warning if not. 39 | * 40 | * @return {void} 41 | */ 42 | validate() { 43 | if(this._validate) { 44 | if(this.values.length % this.width !== 0) 45 | { 46 | console.warn('values array length is not an even multiple of the total size of the attributes'); // eslint-disable-line no-console 47 | } 48 | } 49 | } 50 | 51 | /** 52 | * Update the values in the buffer and pushes the buffer to the gpu 53 | * 54 | * @chainable 55 | * @param {ArrayBuffer} values New values to write to the buffer 56 | * 57 | * @return {this} Returns `this` 58 | */ 59 | updateValues(values) { 60 | this.values = values; 61 | this.validate(); 62 | return this.update(); 63 | } 64 | 65 | /** 66 | * Given a set of program locations, set up the attribute pointers 67 | * 68 | * @chainable 69 | * @param {Object} locations Map of attribute names to program locations 70 | * 71 | * @return {this} Returns `this` 72 | */ 73 | draw(locations) { 74 | var gl = this._gl; 75 | var a, s = 0; 76 | if(!this.glBuf) { 77 | this.update(); 78 | } else { 79 | this.bindBuffer(); 80 | } 81 | for(var i = 0; i < this.attributes.length; i++) 82 | { 83 | a = this.attributes[i]; 84 | if(a.name in locations) 85 | { 86 | gl.enableVertexAttribArray(locations[a.name]); 87 | gl.vertexAttribPointer(locations[a.name], a.size, gl.FLOAT, false, this.size, s); 88 | } 89 | // I don't know if I should suppress this, but if I 90 | // don't, it generates one warning per frame. 91 | //console.warn('Program is missing attribute ' + a.name); 92 | s += 4 * a.size; 93 | } 94 | return this; //.unbindBuffer(); // maybe? 95 | } 96 | 97 | /** 98 | * Perform some operation on each set of values for some attribute 99 | * 100 | * @chainable 101 | * @param {Number} attributeIndex Index of the attribute to select 102 | * @param {Function} callback Callback 103 | * 104 | * @return {this} Returns `this` 105 | */ 106 | eachAttribute(attributeIndex, callback) { 107 | var offset = 0, size, i; 108 | if(attributeIndex >= 0 && attributeIndex < this.attributes.length) { 109 | for(i = 0; i < attributeIndex; i++) { 110 | offset += this.attributes[i].size; 111 | } 112 | size = this.attributes[attributeIndex].size; 113 | for(i = offset; i < this.values.length; i += this.width) { 114 | callback(this.values.subarray(i, i + size)); 115 | } 116 | } 117 | return this; 118 | } 119 | } 120 | 121 | export default GLAttribute; 122 | -------------------------------------------------------------------------------- /src/gl/gl-buffer.js: -------------------------------------------------------------------------------- 1 | import GLBound from '../gl-bound'; 2 | 3 | /** 4 | * A GLBuffer is a buffer of some sort that will be passed to the gpu 5 | * 6 | * @private 7 | * @extends {GLBound} 8 | * @chainable 9 | * @param {context} gl WebGL context 10 | * @param {enum} target gl target @see https://www.khronos.org/registry/webgl/specs/1.0/#5.14.5 11 | * @param {enum} usage gl usage @see https://www.khronos.org/registry/webgl/specs/1.0/#5.14.5 12 | * @return {this} the GLBuffer 13 | */ 14 | class GLBuffer extends GLBound { 15 | 16 | constructor(gl, target, usage) { 17 | super(gl); 18 | this.target = target || gl.ARRAY_BUFFER; // probably shouldn't default this. 19 | this.usage = usage || gl.STATIC_DRAW; 20 | this.glBuf = null; 21 | this.values = null; 22 | return this; 23 | } 24 | 25 | /** 26 | * Binds the buffer to the gpu 27 | * 28 | * @chainable 29 | * @return {this} Returns `this` 30 | */ 31 | bindBuffer() { 32 | if(!this.values) { 33 | throw new Error('trying to update a buffer with no values.'); 34 | } 35 | if(!this.glBuf) { 36 | this.glBuf = this._gl.createBuffer(); 37 | } 38 | this._gl.bindBuffer(this.target, this.glBuf); 39 | return this; 40 | } 41 | 42 | /** 43 | * Unbinds the buffer (NPI) 44 | * 45 | * @chainable 46 | * @return {this} Returns `this` 47 | */ 48 | unbindBuffer() { 49 | // this._gl.bindBuffer(this.target, 0); // apparently this makes webgl cranky 50 | return this; 51 | } 52 | 53 | /** 54 | * Update the buffer data on the gpu 55 | * 56 | * @chainable 57 | * @return {this} Returns `this` 58 | */ 59 | update() { 60 | this.bindBuffer(); 61 | // if I do it this way, does it break? 62 | // if it works, will updating the underlying buffer 63 | // update the buffer without needing to call gl.bufferData again?? 64 | this._gl.bufferData(this.target, this.values, this.usage); 65 | return this; // .unbindBuffer(); // apparently this makes webgl angry. 66 | } 67 | 68 | /** 69 | * Sets the buffer contents 70 | * 71 | * @chainable 72 | * @param {ArrayBuffer} values Values to store in the buffer 73 | * @param {Number} offset Offset to write the values 74 | * @return {this} Returns `this` 75 | */ 76 | setValues(values, offset) { 77 | if(!this.values) { 78 | this.values = values; 79 | } else { 80 | this.values.set(values, offset); 81 | } 82 | this.update(); 83 | return this; 84 | } 85 | 86 | /** 87 | * Deletes a chunk of a buffer 88 | * 89 | * @chainable 90 | * @param {Number} start Start of deletion 91 | * @param {Number} end End of deletion 92 | * @return {this} Returns `this` 93 | */ 94 | deleteWithin(start, end) { 95 | if(!this.values) { 96 | throw new Error('Trying to splice a buffer that has no values.'); 97 | } 98 | var nValues = end - start; 99 | var empty = new this.values.constructor(nValues); 100 | this.values.set(this.values.subarray(end), start); 101 | this.values.set(empty, this.values.length - nValues); 102 | this.update(); 103 | return this; 104 | } 105 | 106 | /** 107 | * Do something with each elemnt of the buffer 108 | * 109 | * @chainable 110 | * @param {Function} callback The callback (values returned will overwrite 111 | * the contents of the buffer at that offset) 112 | * @param {Number} start Offset to start 113 | * @param {Number} end Offset to end 114 | * @return {this} Returns `this` 115 | */ 116 | map(callback, start, end) { 117 | start = start === undefined ? 0 : start; 118 | end = end === undefined ? this.values.length : end; 119 | for(var i = start; i < end; i++) { 120 | this.values[i] = callback(this.values[i], i); 121 | } 122 | return this; 123 | } 124 | 125 | /** 126 | * Update a buffer's values, and also update the buffer on the gpu 127 | * 128 | * @chainable 129 | * @param {ArrayBuffer} values New values to fill the buffer with 130 | * @return {this} Returns `this` 131 | */ 132 | updateBuffer(values) { 133 | this.values = values; 134 | return this.update(); 135 | } 136 | } 137 | 138 | export default GLBuffer; 139 | -------------------------------------------------------------------------------- /src/gl/gl-index.js: -------------------------------------------------------------------------------- 1 | import GLBuffer from './gl-buffer'; 2 | 3 | /** 4 | * A GLIndex is a GLBuffer representing an index buffer of some kind 5 | * 6 | * @private 7 | * @extends {GLBuffer} 8 | * @chainable 9 | * @param {context} gl WebGL context 10 | * @param {ArrayBuffer} values Values to initialize the buffer with 11 | * @param {enum} drawMode Draw mode @see https://www.khronos.org/registry/webgl/specs/1.0/#5.14.11 12 | * @param {enum} usage Usage @see https://www.khronos.org/registry/webgl/specs/1.0/#5.14.5 13 | * @return {this} The new GLIndex 14 | */ 15 | class GLIndex extends GLBuffer { 16 | 17 | constructor(gl, values, drawMode, usage) { 18 | usage = usage || gl.STATIC_DRAW; 19 | super(gl, gl.ELEMENT_ARRAY_BUFFER, usage); 20 | this.mode = drawMode; 21 | this.values = values; 22 | this.count = null; 23 | return this; 24 | } 25 | 26 | /** 27 | * Perform a draw call using this index buffer. 28 | * 29 | * @chainable 30 | * @return {this} Returns `this` 31 | */ 32 | draw() { 33 | var gl = this._gl; 34 | if(!this.glBuf) { 35 | this.update(); 36 | } else { 37 | this.bindBuffer(); 38 | } 39 | gl.drawElements(this.mode, this.values.length, gl.UNSIGNED_SHORT, 0); 40 | return this; 41 | } 42 | } 43 | 44 | export default GLIndex; 45 | -------------------------------------------------------------------------------- /src/ingress-model-viewer.js: -------------------------------------------------------------------------------- 1 | import Constants from './constants'; 2 | import Engine from './engine'; 3 | import { default as AssetLoader } from './asset-loader'; 4 | import Drawable from './drawable'; 5 | import Inventory from './drawable/inventory'; 6 | import World from './drawable/world'; 7 | import PortalLink from './drawable/portal-link'; 8 | import ResonatorLink from './drawable/resonator-link'; 9 | import SphericalPortalLink from './drawable/spherical-portal-link'; 10 | import Atmosphere from './drawable/atmosphere'; 11 | import TexturedSphere from './drawable/textured-sphere'; 12 | import ParticlePortal from './drawable/particle-portal'; 13 | 14 | import InventoryItems from './entity/inventory'; 15 | import PortalEntity from './entity/portal'; 16 | 17 | import OrbitControls from './orbit-controls'; 18 | 19 | import { resetGL, setParams, disco, generateArtifacts, makeArtifact } from './utils'; 20 | import Ease from './animation/easing'; 21 | import Animation from './animation/animation'; 22 | import GLMatrix from 'gl-matrix'; 23 | import Promise from 'es6-promises'; 24 | 25 | const IMV = { 26 | Constants, 27 | Engine, 28 | AssetLoader, 29 | Utilities: { 30 | resetGL, 31 | setParams, 32 | disco, 33 | generateArtifacts, 34 | makeArtifact, 35 | Ease, 36 | Animation, 37 | GLMatrix, 38 | Promise, 39 | }, 40 | Drawables: { 41 | Inventory, 42 | World, 43 | ResonatorLink, 44 | PortalLink, 45 | SphericalPortalLink, 46 | Atmosphere, 47 | TexturedSphere, 48 | ParticlePortal, 49 | Drawable 50 | }, 51 | Entities: { 52 | World: { 53 | Portal: PortalEntity 54 | }, 55 | Inventory: InventoryItems 56 | }, 57 | Controls: { 58 | Orbit: OrbitControls 59 | }, 60 | VERSION: '0.22.2' 61 | }; 62 | 63 | export default IMV; 64 | 65 | module.exports = IMV; // eslint-disable-line no-undef 66 | -------------------------------------------------------------------------------- /src/mesh.js: -------------------------------------------------------------------------------- 1 | import GLBound from './gl-bound'; 2 | 3 | const MODE_TRIANGLES = 'triangles'; 4 | const MODE_LINES = 'lines'; 5 | 6 | /** 7 | * Base class for all meshes 8 | * 9 | * @extends {GLBound} 10 | * @param {context} gl A webgl context 11 | * @param {Float32Array} attributes A typed array of vertex attributes 12 | * @param {Uint16Array} faces A typed array of face indices 13 | * @param {Uint16Array} lines A typed array of line indices 14 | */ 15 | class Mesh extends GLBound { 16 | 17 | constructor(gl, attributes, faces, lines) { 18 | super(gl); 19 | this.attributes = attributes; 20 | this.faces = faces; 21 | this.lines = lines; 22 | this.bounds = null; 23 | this.center = null; 24 | } 25 | 26 | /** 27 | * Given a set of locations from the currently-active shader, draw this mesh 28 | * @param {Object} locations A hash of locations by name 29 | * @param {String} mode (optional) The draw mode 30 | * Either MODE_TRIANGLES or MODE_LINES 31 | * @return {void} 32 | */ 33 | draw(locations, mode) { 34 | mode = mode || MODE_TRIANGLES; 35 | this.attributes.draw(locations); 36 | if(mode === MODE_TRIANGLES) { 37 | this.faces.draw(); 38 | } else if (mode === MODE_LINES) { 39 | this.lines.draw(); 40 | } 41 | } 42 | 43 | /** 44 | * Calculate the bounding box of the mesh 45 | * @param {Number} coordAttribute Index of the attribute representing vertex position 46 | * @return {Object} An object consisting of two arrays of the same length 47 | * as the coordinate attribute, representing min and max 48 | * coordinates. 49 | */ 50 | boundingBox(coordAttribute) { 51 | if(!this.bounds) { 52 | coordAttribute = coordAttribute === undefined ? 0 : coordAttribute; 53 | var bounds = { 54 | max: null, 55 | min: null 56 | }; 57 | this.attributes.eachAttribute(coordAttribute, function(arr) { 58 | if(bounds.max) { 59 | bounds.max = bounds.max.map(function(e, i) { 60 | return Math.max(e, arr[i]); 61 | }); 62 | } else { 63 | bounds.max = Array.prototype.slice.call(arr); 64 | } 65 | if(bounds.min) { 66 | bounds.min = bounds.min.map(function(e, i) { 67 | return Math.min(e, arr[i]); 68 | }); 69 | } else { 70 | bounds.min = Array.prototype.slice.call(arr); 71 | } 72 | }); 73 | this.bounds = bounds; 74 | } 75 | return this.bounds; 76 | } 77 | 78 | centerOfMass(coordAttribute) { 79 | if(!this.center) { 80 | coordAttribute = coordAttribute === undefined ? 0 : coordAttribute; 81 | var sum = null, 82 | count = 0; 83 | this.attributes.eachAttribute(coordAttribute, function(arr) { 84 | count++; 85 | if(sum) { 86 | sum = sum.map(function(e, i) { 87 | return e + arr[i]; 88 | }); 89 | } else { 90 | sum = Array.prototype.slice.call(arr); 91 | } 92 | }); 93 | this.center = sum.map(function(e) { 94 | return e / count; 95 | }); 96 | } 97 | return this.center; 98 | } 99 | 100 | /** 101 | * Calculate the center of the bounding box. 102 | * @param {Number} coordAttribute Index of the attribute represention vertex position. 103 | * @return {mixed} A vector of the same size as the position attribute, 104 | * representing the center of the bounding box. 105 | */ 106 | boundingBoxCenter(coordAttribute) { 107 | if(!this.bounds) { 108 | this.boundingBox(coordAttribute); 109 | } 110 | return this.bounds.max.map(function(e, i) { 111 | return (e - this.bounds.min[i]) / 2; 112 | }.bind(this)); 113 | } 114 | } 115 | 116 | /** 117 | * Specifies drawing in `lines` mode 118 | * @type {String} 119 | */ 120 | Mesh.MODE_LINES = MODE_LINES; 121 | 122 | /** 123 | * Specifies drawing in `triangles` mode 124 | * @type {String} 125 | */ 126 | Mesh.MODE_TRIANGLES = MODE_TRIANGLES; 127 | 128 | export default Mesh; 129 | -------------------------------------------------------------------------------- /src/mesh/file.js: -------------------------------------------------------------------------------- 1 | import Mesh from '../mesh'; 2 | import VertexAttribute from '../vertex-attribute'; 3 | import GLIndex from '../gl/gl-index'; 4 | import GLAttribute from '../gl/gl-attribute'; 5 | import JavaDeserializer from 'java-deserializer'; 6 | 7 | function parseAttributes(buf) 8 | { 9 | var v = new DataView(buf.buffer, buf.byteOffset, buf.byteLength), c = 0; 10 | var n = v.getUint32(c), type, size, len, j, name; 11 | c += 4; 12 | var attributes = []; 13 | for(var i = 0; i < n; i++) 14 | { 15 | type = v.getUint32(c); 16 | c += 4; 17 | size = v.getUint32(c); 18 | c += 4; 19 | len = v.getUint16(c); 20 | c += 2; 21 | name = ''; 22 | for(j = 0; j < len; j++) 23 | { 24 | name += String.fromCharCode(v.getUint8(c+j)); 25 | } 26 | c += len; 27 | attributes.push(new VertexAttribute(name, size, type)); 28 | } 29 | return attributes; 30 | } 31 | 32 | /** 33 | * A FileMesh is a Mesh that is loaded from a serialzied Java object, 34 | * as found in the apk. 35 | * 36 | * @extends {Mesh} 37 | */ 38 | class FileMesh extends Mesh { 39 | 40 | /** 41 | * Construct the Mesh from the given file 42 | * @param {context} gl WebGL context 43 | * @param {ArrayBuffer} arraybuf ArrayBuffer representing the entire .obj file 44 | */ 45 | constructor(gl, arraybuf) { 46 | var jd = new JavaDeserializer(arraybuf); 47 | var blocks = jd.getContents(); 48 | 49 | // should be Float32Array 50 | var values = blocks[0].elements; 51 | 52 | // should be ArrayBuffer 53 | var attributeData = blocks[3]; 54 | 55 | // array of VertexAttributes 56 | var spec = parseAttributes(attributeData); 57 | 58 | // should be Uint16Array 59 | var faces = new GLIndex(gl, blocks[1].elements, gl.TRIANGLES); 60 | var attributes = new GLAttribute(gl, spec, values); 61 | 62 | // should be Uint16Array 63 | var lines = new GLIndex(gl, blocks[2].elements, gl.LINES); 64 | 65 | super(gl, attributes, faces, lines); 66 | } 67 | } 68 | 69 | export default FileMesh; 70 | -------------------------------------------------------------------------------- /src/mesh/particle-portal.js: -------------------------------------------------------------------------------- 1 | import Mesh from '../mesh'; 2 | import VertexAttribute from '../vertex-attribute'; 3 | import GLIndex from '../gl/gl-index'; 4 | import GLAttribute from '../gl/gl-attribute'; 5 | 6 | // const MAX_SYSTEMS = 40; 7 | const NUM_PARTICLES_PER_SYSTEM = 96; 8 | const NUM_VERTICES_PER_PARTICLE = 4; 9 | const NUM_INDICES_PER_FACE = 6; 10 | const TOTAL_VERTEX_SIZE = 3 + 2 + 1 + 1 + 1 + 1; 11 | const U = [0.0, 0.0, 1.0, 1.0]; 12 | const V = [1.0, 0.0, 1.0, 0.0]; 13 | 14 | var seeds = []; 15 | for(var i = 0; i < NUM_PARTICLES_PER_SYSTEM; i++) 16 | { 17 | seeds.push({ 18 | x: Math.random() - 0.5, 19 | y: 0.4 * Math.random() - 0.2, 20 | z: Math.random() - 0.5, 21 | a_scale: 10.0 * (0.1 + 0.9 * Math.random()), 22 | a_speed: 6.0 * (0.5 + 0.5 * Math.random()) 23 | }); 24 | } 25 | 26 | /** 27 | * A ParticlePortalMesh is a Mesh that represents a single system or portal particles. 28 | * 29 | * @extends {Mesh} 30 | */ 31 | class ParticlePortalMesh extends Mesh { 32 | 33 | /** 34 | * Construct a system of portal particles 35 | * @param {context} gl WebGL context 36 | */ 37 | constructor(gl) { 38 | var attributes = []; 39 | attributes.push(new VertexAttribute('a_position', 3)); 40 | attributes.push(new VertexAttribute('a_texCoord0', 2)); 41 | attributes.push(new VertexAttribute('a_scale', 1)); 42 | attributes.push(new VertexAttribute('a_speed', 1)); 43 | attributes.push(new VertexAttribute('a_portalIndex', 1)); 44 | attributes.push(new VertexAttribute('a_index', 1)); 45 | var values = new Float32Array(NUM_PARTICLES_PER_SYSTEM * NUM_VERTICES_PER_PARTICLE * TOTAL_VERTEX_SIZE); 46 | var seed, i, j, idx = 0; 47 | for(i = 0; i < NUM_PARTICLES_PER_SYSTEM; i++) 48 | { 49 | seed = seeds[i]; 50 | for(j = 0; j < NUM_VERTICES_PER_PARTICLE; j++) 51 | { 52 | values[idx * TOTAL_VERTEX_SIZE + 0] = seed.x; 53 | values[idx * TOTAL_VERTEX_SIZE + 1] = seed.y; 54 | values[idx * TOTAL_VERTEX_SIZE + 2] = seed.z; 55 | values[idx * TOTAL_VERTEX_SIZE + 3] = U[j]; 56 | values[idx * TOTAL_VERTEX_SIZE + 4] = V[j]; 57 | values[idx * TOTAL_VERTEX_SIZE + 5] = seed.a_scale; 58 | values[idx * TOTAL_VERTEX_SIZE + 6] = seed.a_speed; 59 | values[idx * TOTAL_VERTEX_SIZE + 7] = 0; 60 | values[idx * TOTAL_VERTEX_SIZE + 8] = i; 61 | idx++; 62 | } 63 | } 64 | 65 | var faces = new Uint16Array(NUM_PARTICLES_PER_SYSTEM * NUM_INDICES_PER_FACE); 66 | var indices = [0, 1, 2, 1, 3, 2]; 67 | idx = 0; 68 | var f = 0; 69 | for(i = 0; i < NUM_PARTICLES_PER_SYSTEM; i++) 70 | { 71 | for(j = 0; j < NUM_INDICES_PER_FACE; j++) 72 | { 73 | faces[f + j] = idx + indices[j]; 74 | } 75 | f += 6; 76 | idx += 4; 77 | } 78 | super( 79 | gl, 80 | new GLAttribute(gl, attributes, values), 81 | new GLIndex(gl, faces, gl.TRIANGLES) 82 | ); 83 | } 84 | } 85 | 86 | export default ParticlePortalMesh; 87 | -------------------------------------------------------------------------------- /src/mesh/plane.js: -------------------------------------------------------------------------------- 1 | import Mesh from '../mesh'; 2 | import VertexAttribute from '../vertex-attribute'; 3 | import GLIndex from '../gl/gl-index'; 4 | import GLAttribute from '../gl/gl-attribute'; 5 | 6 | /** 7 | * A PlaneMesh is a Mesh that represents a unit square plane, centered on 8 | * 0,0. Consists of a single quad. 9 | * 10 | * @extends {Mesh} 11 | */ 12 | class PlaneMesh extends Mesh { 13 | 14 | /** 15 | * Construct a sphere 16 | * @param {context} gl WebGL context 17 | */ 18 | constructor(gl) { 19 | var attributes = []; 20 | attributes.push(new VertexAttribute('a_position', 3)); 21 | attributes.push(new VertexAttribute('a_texCoord0', 2)); 22 | var values = new Float32Array(4 * 5); // 4 vertices, 5 bytes each. 23 | var faces = new Uint16Array(2 * 3); // two triangles 24 | 25 | // Upper left: 26 | values[0] = -0.5; 27 | values[1] = 0; 28 | values[2] = 0.5; 29 | values[3] = 0; 30 | values[4] = 0; 31 | // Upper right: 32 | values[5] = 0.5; 33 | values[6] = 0; 34 | values[7] = 0.5; 35 | values[8] = 1; 36 | values[9] = 0; 37 | // Lower left: 38 | values[10] = -0.5; 39 | values[11] = 0; 40 | values[12] = -0.5; 41 | values[13] = 0; 42 | values[14] = 1; 43 | // Lower right: 44 | values[15] = 0.5; 45 | values[16] = 0; 46 | values[17] = -0.5; 47 | values[18] = 1; 48 | values[19] = 1; 49 | 50 | // Faces: 51 | faces[0] = 0; 52 | faces[1] = 1; 53 | faces[2] = 2; 54 | faces[3] = 1; 55 | faces[4] = 3; 56 | faces[5] = 2; 57 | super( 58 | gl, 59 | new GLAttribute(gl, attributes, values), 60 | new GLIndex(gl, faces, gl.TRIANGLES) 61 | ); 62 | } 63 | } 64 | 65 | export default PlaneMesh; 66 | -------------------------------------------------------------------------------- /src/mesh/portal-link.js: -------------------------------------------------------------------------------- 1 | import Mesh from '../mesh'; 2 | import VertexAttribute from '../vertex-attribute'; 3 | import GLIndex from '../gl/gl-index'; 4 | import GLAttribute from '../gl/gl-attribute'; 5 | import { vec3, vec4 } from 'gl-matrix'; 6 | 7 | // TODO: Parameterize this concept a little better 8 | // this has potential to be a really flexible and powerful way of 9 | // making, essentially, extruded geometry. 10 | 11 | // 9 sets of 6 points, breaking the link into 8 pieces, each providing 6 faces, something like that? 12 | var _len = 9, _size = _len * 6, _chunkSize = 12; 13 | var c = new Array(_len), 14 | d = new Array(_len), 15 | e = new Array(_len); 16 | 17 | var baseColor = vec4.fromValues(0.46, 0.18, 0.18, 1.0); 18 | var baseOffset = vec4.create(); 19 | 20 | function clampedSin(f) 21 | { 22 | return Math.sin(Math.PI * Math.max(Math.min(1.0, f), 0) / 2); 23 | } 24 | 25 | for(var i = 0; i < _len; i++) 26 | { 27 | var f = i / 8.0; 28 | c[i] = f; 29 | e[i] = (3.0 + (-1.5 * Math.pow(clampedSin(2.0 * Math.abs(f - 0.5)), 4))); 30 | d[i] = clampedSin(1.0 - 2.0 * Math.abs(f - 0.5)); 31 | } 32 | 33 | function fillChunk(buf, index, x, y, z, u, v, normal, f6, color) 34 | { 35 | var off = index * _chunkSize; 36 | buf[off + 0] = x; 37 | buf[off + 1] = y; 38 | buf[off + 2] = z; 39 | buf[off + 3] = f6; 40 | buf[off + 4] = u; 41 | buf[off + 5] = v; 42 | buf[off + 6] = normal[0]; 43 | buf[off + 7] = normal[2]; 44 | buf[off + 8] = color[0]; 45 | buf[off + 9] = color[1]; 46 | buf[off + 10] = color[2]; 47 | buf[off + 11] = color[3]; 48 | } 49 | 50 | function _generateLinkAttributes(start, end, color, startPercent, endPercent) { 51 | startPercent = startPercent === undefined ? 1 : Math.max(Math.min(startPercent, 1), 0); 52 | endPercent = endPercent === undefined ? 1 : Math.max(Math.min(endPercent, 1), 0); 53 | var values = new Float32Array(_size * _chunkSize); 54 | var length = Math.sqrt((end[0] - start[0]) * (end[0] - start[0]) + (end[1] - start[1]) * (end[1] - start[1])); 55 | var yMin = baseOffset[1], 56 | yMax = yMin + Math.min(30.0, 0.08 * length), 57 | avgPercent = (startPercent + endPercent) / 2.0, 58 | f6 = 0.01 * length, 59 | f7 = 0.1 + avgPercent * 0.3; 60 | var vec = vec3.fromValues(end[0], 0, end[1]); 61 | vec3.subtract(vec, vec, vec3.fromValues(start[0], 0, start[1])); 62 | var up = vec3.fromValues(0, 1, 0); 63 | var right = vec3.cross(vec3.create(), vec, up); 64 | vec3.normalize(right, right); 65 | var step = _len * 2; 66 | for(var i = 0; i < _len; i++) 67 | { 68 | var f8 = c[i], 69 | f9 = startPercent + f8 * (endPercent - startPercent), 70 | f10 = 0.6 + 0.35 * f9, 71 | f12 = f8 * f6, 72 | f13 = start[0] + f8 * vec[0], 73 | f14 = start[1] + f8 * vec[2], 74 | f15 = yMin + d[i] * (yMax - yMin), 75 | f16 = e[i]; 76 | var cl = vec4.lerp(vec4.create(), baseColor, color, 0.25 + f9 * 0.75); 77 | cl[3] = f10; 78 | fillChunk(values, (i * 2), f13 + f16 * right[0], f15, f14 + f16 * right[2], 0, f12, up, f7, cl); 79 | fillChunk(values, (i * 2) + 1, f13 - f16 * right[0], f15, f14 - f16 * right[2], 0.5, f12, up, f7, cl); 80 | fillChunk(values, step + (i * 2), f13, f15 + f16, f14, 0, f12, right, f7, cl); 81 | fillChunk(values, step + (i * 2) + 1, f13, f15 - f16, f14, 0.5, f12, right, f7, cl); 82 | fillChunk(values, 2 * step + (i * 2), f13, f15 - f16, f14, 0.5, f12, right, f7, cl); 83 | fillChunk(values, 2 * step + (i * 2) + 1, f13, 0, f14, 1.0, f12, right, f7, cl); 84 | } 85 | return values; 86 | } 87 | 88 | function _generateFaces(vertexOffset) { 89 | var ind = new Uint16Array(144), 90 | iOff = 0; 91 | for(var i = 0; i < 3; i++) { 92 | 93 | for(var j = 0; j < _len - 1; j++) { 94 | 95 | ind[iOff + 0] = vertexOffset + 1; 96 | ind[iOff + 1] = vertexOffset + 0; 97 | ind[iOff + 2] = vertexOffset + 2; 98 | ind[iOff + 3] = vertexOffset + 1; 99 | ind[iOff + 4] = vertexOffset + 2; 100 | ind[iOff + 5] = vertexOffset + 3; 101 | vertexOffset += 2; 102 | iOff += 6; 103 | } 104 | vertexOffset += 2; 105 | } 106 | 107 | return ind; 108 | } 109 | 110 | /** 111 | * A PortalLinkMesh represents the mesh for a single portal link. 112 | * 113 | * @extends {Mesh} 114 | */ 115 | class PortalLinkMesh extends Mesh { 116 | 117 | /** 118 | * Programatically constructs the mesh for a link between two points 119 | * @param {context} gl WebGL context 120 | * @param {vec2} start X,Z of the origin point 121 | * @param {vec2} end X,Z of the destination point 122 | * @param {vec4} color Color of the link 123 | * @param {Number} startPercent Origin point percentage 124 | * @param {Number} endPercent Destination point percentage 125 | */ 126 | constructor(gl, start, end, color, startPercent, endPercent) { 127 | var buf = _generateLinkAttributes(start, end, color, startPercent, endPercent); 128 | var ind = _generateFaces(0); 129 | var attributes = []; 130 | attributes.push(new VertexAttribute('a_position', 4)); 131 | attributes.push(new VertexAttribute('a_texCoord0', 4)); 132 | attributes.push(new VertexAttribute('a_color', 4)); 133 | var attribute = new GLAttribute(gl, attributes, buf, gl.DYNAMIC_DRAW); 134 | var faces = new GLIndex(gl, ind, gl.TRIANGLES); 135 | super(gl, attribute, faces); 136 | } 137 | } 138 | 139 | export default PortalLinkMesh; 140 | -------------------------------------------------------------------------------- /src/mesh/resonator-link.js: -------------------------------------------------------------------------------- 1 | import Mesh from '../mesh'; 2 | import VertexAttribute from '../vertex-attribute'; 3 | import GLIndex from '../gl/gl-index'; 4 | import GLAttribute from '../gl/gl-attribute'; 5 | import { vec2, vec3, vec4 } from 'gl-matrix'; 6 | 7 | // TODO: Parameterize this concept a little better 8 | // this has potential to be a really flexible and powerful way of 9 | // making, essentially, extruded geometry. 10 | 11 | // 5 sets of 4 points, breaking the link into 4 pieces, each providing 4 faces 12 | // chunksize is size of each element in the packed vertex array, in bytes 13 | var _len = 5, _size = _len * 4, _chunkSize = 12; 14 | var j = new Array(_len), 15 | k = new Array(_len), 16 | l = new Array(_len); 17 | 18 | function clampedSin(f) 19 | { 20 | return Math.sin(Math.PI * Math.max(Math.min(1.0, f), 0) / 2); 21 | } 22 | 23 | for(var i = 0; i < _len; i++) 24 | { 25 | var f = i / 4.0; 26 | j[i] = f; 27 | l[i] = 3.5 * Math.max(1.0 - Math.pow(clampedSin(2.0 * Math.abs(f - 0.5)), 4.0), 0.2); 28 | k[i] = clampedSin(1.0 - 2.0 * Math.abs(f - 0.5)); 29 | } 30 | 31 | var baseColor = vec4.fromValues(0.78, 0.31, 0.31, 1.0); 32 | var resonatorMidOffset = 0; 33 | var portalBaseOffset = 0; 34 | var up = vec3.fromValues(0, 1, 0); 35 | 36 | function fillChunk(buf, index, x, y, z, u, v, normal, f6, color) 37 | { 38 | var off = index * _chunkSize; 39 | buf[off + 0] = x; 40 | buf[off + 1] = y; 41 | buf[off + 2] = z; 42 | buf[off + 3] = f6; 43 | buf[off + 4] = u; 44 | buf[off + 5] = v; 45 | buf[off + 6] = normal[0]; 46 | buf[off + 7] = normal[2]; 47 | buf[off + 8] = color[0]; 48 | buf[off + 9] = color[1]; 49 | buf[off + 10] = color[2]; 50 | buf[off + 11] = color[3]; 51 | } 52 | 53 | function _generateLinkAttributes(portal, resonator, color, resonatorPercent) { 54 | resonatorPercent = resonatorPercent === undefined ? 1 : Math.max(Math.min(resonatorPercent, 1), 0); 55 | var values = new Float32Array(_size * _chunkSize); 56 | var dist = Math.sqrt( 57 | (resonator[0] - portal[0]) * (resonator[0] - portal[0]) + 58 | (resonator[1] - portal[1]) * (resonator[1] - portal[1]) 59 | ); 60 | var f4 = (2 / 30) * dist, 61 | f5 = 0.9 + 0.1 * resonatorPercent, 62 | f6 = 0.65 + 0.35 * resonatorPercent, 63 | f8 = 0.1 + 0.3 * resonatorPercent; 64 | var cl = vec4.lerp(vec4.create(), baseColor, color, 0.1 + resonatorPercent * 0.85); 65 | cl[3] = 0.75 + 0.25 * resonatorPercent * cl[3]; 66 | var vec = vec3.fromValues(resonator[0], 0, resonator[1]); 67 | vec3.subtract(vec, vec, vec3.fromValues(portal[0], 0, portal[1])); 68 | var right = vec3.cross(vec3.create(), vec, up); 69 | vec3.normalize(right, right); 70 | var step = _len * 2; 71 | var f10 = 5.0 * ((portal[0] + portal[1]) - Math.floor(portal[0] + portal[1])); 72 | for(var i = 0; i < _len; i++) 73 | { 74 | var f11 = j[i], 75 | f12 = portal[0] + f11 * vec[0], 76 | f13 = portal[1] + f11 * vec[2], 77 | f14 = portalBaseOffset + f11 * (resonatorMidOffset - portalBaseOffset) + f5 * k[i], 78 | f15 = f6 * l[i], 79 | f16 = f11 * f4; 80 | fillChunk(values, (i * 2) + 0, f12 + f15 * right[0], f14, f13 + f15 * right[2], 0.0, f16 + f10, up, f8, cl); 81 | fillChunk(values, (i * 2) + 1, f12 - f15 * right[0], f14, f13 - f15 * right[2], 1.0, f16 + f10, up, f8, cl); 82 | fillChunk(values, step + (i * 2) + 0, f12, f14 + f15, f13, 0.0, f16 + f10, right, f8, cl); 83 | fillChunk(values, step + (i * 2) + 1, f12, f14 - f15, f13, 1.0, f16 + f10, right, f8, cl); 84 | } 85 | return values; 86 | } 87 | 88 | function _generateFaces(vertexOffset) { 89 | var ind = new Uint16Array(48), 90 | iOff = 0; 91 | 92 | for(i = 0; i < 2; i++) 93 | { 94 | for(var i2 = 0; i2 < _len - 1; i2++) 95 | { 96 | ind[iOff + 0] = vertexOffset + 1; 97 | ind[iOff + 1] = vertexOffset + 0; 98 | ind[iOff + 2] = vertexOffset + 2; 99 | ind[iOff + 3] = vertexOffset + 1; 100 | ind[iOff + 4] = vertexOffset + 2; 101 | ind[iOff + 5] = vertexOffset + 3; 102 | vertexOffset += 2; 103 | iOff += 6; 104 | } 105 | vertexOffset += 2; 106 | } 107 | 108 | return ind; 109 | } 110 | 111 | /** 112 | * A ResonatorLinkMesh is a Mesh that represents a single link between a portal and a resonator 113 | * 114 | * TODO: Make disco 115 | * 116 | * @extends {Mesh} 117 | */ 118 | class ResonatorLinkMesh extends Mesh { 119 | 120 | /** 121 | * Construct a resonator link mesh 122 | * @param {context} gl WebGL context 123 | * @param {vec2} portalPosition X,Z of the portal 124 | * @param {Number} slot Resonator slot (0-7) 125 | * @param {Number} distance Distance from the portal 126 | * @param {vec4} color Color of the resonator link 127 | * @param {Number} resonatorPercent Percent health of the resonator 128 | */ 129 | constructor(gl, portalPosition, slot, distance, color, resonatorPercent) { 130 | var theta = slot / 8 * 2 * Math.PI; 131 | var end = vec2.create(); 132 | var relative = vec2.fromValues(distance * Math.cos(theta), distance * Math.sin(theta)); 133 | vec2.add(end, portalPosition, relative); 134 | var buf = _generateLinkAttributes(portalPosition, end, color, resonatorPercent); 135 | var ind = _generateFaces(0); 136 | var attributes = []; 137 | attributes.push(new VertexAttribute('a_position', 4)); 138 | attributes.push(new VertexAttribute('a_texCoord0', 4)); 139 | attributes.push(new VertexAttribute('a_color', 4)); 140 | var attribute = new GLAttribute(gl, attributes, buf, gl.DYNAMIC_DRAW); 141 | var faces = new GLIndex(gl, ind, gl.TRIANGLES); 142 | super(gl, attribute, faces); 143 | } 144 | } 145 | 146 | export default ResonatorLinkMesh; 147 | -------------------------------------------------------------------------------- /src/mesh/sphere.js: -------------------------------------------------------------------------------- 1 | import Mesh from '../mesh'; 2 | import VertexAttribute from '../vertex-attribute'; 3 | import GLIndex from '../gl/gl-index'; 4 | import GLAttribute from '../gl/gl-attribute'; 5 | import { vec3 } from 'gl-matrix'; 6 | 7 | // part of doing away with the THREE.js dependency 8 | // means giving up a lot of helper code for doing things 9 | // like this. 10 | // 11 | // Needless to say, this borrows heavily from THREE.SphereGeometry 12 | // https://github.com/mrdoob/three.js/blob/master/src/extras/geometries/SphereGeometry.js 13 | function createSphere(radius, phiSlices, thetaSlices) { 14 | var i, j, u, v, vec, v1, v2, v3, v4, 15 | verticesRow, faces, 16 | phi = Math.PI * 2, 17 | theta = Math.PI, 18 | // size is 8 for vec3 a_position + vec2 a_texCoord + vec3 a_normal 19 | values = new Float32Array((phiSlices + 1) * (thetaSlices + 1) * 8), 20 | faceArray = [], 21 | vertices = [], 22 | aIdx = 0, 23 | attributes = []; 24 | phiSlices = Math.max(3, phiSlices || 8); 25 | thetaSlices = Math.max(2, thetaSlices || 6); 26 | 27 | for(i = 0; i <= phiSlices; i++) { 28 | verticesRow = []; 29 | for(j = 0; j <= thetaSlices; j++) 30 | { 31 | u = j / phiSlices; 32 | v = i / thetaSlices; 33 | vec = vec3.fromValues( 34 | -radius * Math.cos(u * phi) * Math.sin(v * theta), 35 | radius * Math.cos(v * theta), 36 | radius * Math.sin(u * phi) * Math.sin(v * theta) 37 | ); 38 | 39 | values[aIdx * 8 + 0] = vec[0]; 40 | values[aIdx * 8 + 1] = vec[1]; 41 | values[aIdx * 8 + 2] = vec[2]; 42 | values[aIdx * 8 + 3] = u; 43 | values[aIdx * 8 + 4] = v; 44 | // normalized: 45 | vec3.normalize(vec, vec); 46 | values[aIdx * 8 + 5] = vec[0]; 47 | values[aIdx * 8 + 6] = vec[1]; 48 | values[aIdx * 8 + 7] = vec[2]; 49 | 50 | verticesRow.push(aIdx++); 51 | } 52 | vertices.push(verticesRow); 53 | } 54 | 55 | for(i = 0; i < phiSlices; i++) { 56 | for(j = 0; j < thetaSlices; j++) { 57 | v1 = vertices[i][j + 1]; 58 | v2 = vertices[i][j]; 59 | v3 = vertices[i + 1][j]; 60 | v4 = vertices[i + 1][j + 1]; 61 | 62 | if(Math.abs(values[v1 * 8 + 1]) === radius) { 63 | faceArray.push.apply(faceArray, [v1, v3, v4]); 64 | values[v1 * 8 + 3] = (values[v1 * 8 + 3] + values[v2 * 8 + 3]) / 2; 65 | } 66 | else if(Math.abs(values[v3 * 8 + 1]) === radius) { 67 | faceArray.push.apply(faceArray, [v1, v2, v3]); 68 | values[v3 * 8 + 3] = (values[v3 * 8 + 3] + values[v4 * 8 + 3]) / 2; 69 | } 70 | else { 71 | faceArray.push.apply(faceArray, [v1, v2, v4]); 72 | faceArray.push.apply(faceArray, [v2, v3, v4]); 73 | } 74 | } 75 | } 76 | 77 | faces = new Uint16Array(faceArray.length); 78 | faceArray.forEach(function(v, i) { 79 | faces[i] = v; 80 | }); 81 | attributes.push(new VertexAttribute('a_position', 3)); 82 | attributes.push(new VertexAttribute('a_texCoord0', 2)); 83 | attributes.push(new VertexAttribute('a_normal', 3)); 84 | return { 85 | values: values, 86 | faces: faces, 87 | attributes: attributes 88 | }; 89 | } 90 | 91 | /** 92 | * A SphereMesh is a Mesh that is a sphere, made of a number of quads determined 93 | * by the number of horizontal and vertical slices involved in its construction 94 | * 95 | * @extends {Mesh} 96 | */ 97 | class SphereMesh extends Mesh { 98 | 99 | /** 100 | * Construct a sphere 101 | * @param {context} gl WebGL context 102 | * @param {Number} radius Radius of the sphere 103 | * @param {Number} vSlices Number of vertical slices 104 | * @param {Number} hSlices Number of horizontal slices 105 | */ 106 | constructor(gl, radius, vSlices, hSlices) { 107 | var parsed = createSphere(radius, vSlices, hSlices); 108 | var attributes = new GLAttribute(gl, parsed.attributes, parsed.values); 109 | var faces = new GLIndex(gl, parsed.faces, gl.TRIANGLES); 110 | super(gl, attributes, faces); 111 | } 112 | } 113 | 114 | export default SphereMesh; 115 | -------------------------------------------------------------------------------- /src/polyfill.js: -------------------------------------------------------------------------------- 1 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 2 | // http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating 3 | 4 | // requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel 5 | 6 | // MIT license 7 | 8 | function polyfillRequestAnimationFrame() { 9 | var lastTime = 0; 10 | var vendors = ['ms', 'moz', 'webkit', 'o']; 11 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 12 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 13 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; 14 | } 15 | 16 | if (!window.requestAnimationFrame) { 17 | window.requestAnimationFrame = function(callback) { 18 | var currTime = new Date().getTime(); 19 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 20 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 21 | timeToCall); 22 | lastTime = currTime + timeToCall; 23 | return id; 24 | }; 25 | } 26 | 27 | if (!window.cancelAnimationFrame){ 28 | window.cancelAnimationFrame = function(id) { 29 | clearTimeout(id); 30 | }; 31 | } 32 | } 33 | 34 | export default polyfillRequestAnimationFrame; 35 | -------------------------------------------------------------------------------- /src/program.js: -------------------------------------------------------------------------------- 1 | import GLBound from './gl-bound'; 2 | 3 | // Taken from PhiloGL's program class: 4 | //Returns a Magic Uniform Setter 5 | function getUniformSetter(gl, program, info, isArray) { 6 | var name = info.name, 7 | loc = gl.getUniformLocation(program, name), 8 | type = info.type, 9 | matrix = false, 10 | vector = true, 11 | glFunction, typedArray; 12 | 13 | if (info.size > 1 && isArray) { 14 | switch(type) { 15 | case gl.FLOAT: 16 | glFunction = gl.uniform1fv; 17 | typedArray = Float32Array; 18 | vector = false; 19 | break; 20 | case gl.INT: case gl.BOOL: case gl.SAMPLER_2D: case gl.SAMPLER_CUBE: 21 | glFunction = gl.uniform1iv; 22 | typedArray = Uint16Array; 23 | vector = false; 24 | break; 25 | } 26 | } 27 | 28 | if (vector) { 29 | switch (type) { 30 | case gl.FLOAT: 31 | glFunction = gl.uniform1f; 32 | break; 33 | case gl.FLOAT_VEC2: 34 | glFunction = gl.uniform2fv; 35 | typedArray = isArray ? Float32Array : new Float32Array(2); 36 | break; 37 | case gl.FLOAT_VEC3: 38 | glFunction = gl.uniform3fv; 39 | typedArray = isArray ? Float32Array : new Float32Array(3); 40 | break; 41 | case gl.FLOAT_VEC4: 42 | glFunction = gl.uniform4fv; 43 | typedArray = isArray ? Float32Array : new Float32Array(4); 44 | break; 45 | case gl.INT: case gl.BOOL: case gl.SAMPLER_2D: case gl.SAMPLER_CUBE: 46 | glFunction = gl.uniform1i; 47 | break; 48 | case gl.INT_VEC2: case gl.BOOL_VEC2: 49 | glFunction = gl.uniform2iv; 50 | typedArray = isArray ? Uint16Array : new Uint16Array(2); 51 | break; 52 | case gl.INT_VEC3: case gl.BOOL_VEC3: 53 | glFunction = gl.uniform3iv; 54 | typedArray = isArray ? Uint16Array : new Uint16Array(3); 55 | break; 56 | case gl.INT_VEC4: case gl.BOOL_VEC4: 57 | glFunction = gl.uniform4iv; 58 | typedArray = isArray ? Uint16Array : new Uint16Array(4); 59 | break; 60 | case gl.FLOAT_MAT2: 61 | matrix = true; 62 | glFunction = gl.uniformMatrix2fv; 63 | break; 64 | case gl.FLOAT_MAT3: 65 | matrix = true; 66 | glFunction = gl.uniformMatrix3fv; 67 | break; 68 | case gl.FLOAT_MAT4: 69 | matrix = true; 70 | glFunction = gl.uniformMatrix4fv; 71 | break; 72 | } 73 | } 74 | 75 | //TODO(nico): Safari 5.1 doesn't have Function.prototype.bind. 76 | //remove this check when they implement it. 77 | if (glFunction.bind) { 78 | glFunction = glFunction.bind(gl); 79 | } else { 80 | var target = glFunction; 81 | glFunction = function() { target.apply(gl, arguments); }; 82 | } 83 | 84 | //Set a uniform array 85 | if (isArray && typedArray) { 86 | return function(val) { 87 | glFunction(loc, new typedArray(val)); // jshint ignore:line 88 | }; 89 | 90 | //Set a matrix uniform 91 | } else if (matrix) { 92 | return function(val) { 93 | glFunction(loc, false, val); 94 | }; 95 | 96 | //Set a vector/typed array uniform 97 | } else if (typedArray) { 98 | return function(val) { 99 | typedArray.set(val.toFloat32Array ? val.toFloat32Array() : val); 100 | glFunction(loc, typedArray); 101 | }; 102 | 103 | //Set a primitive-valued uniform 104 | } else { 105 | return function(val) { 106 | glFunction(loc, val); 107 | }; 108 | } 109 | } 110 | 111 | /** 112 | * Represents a shader program consisting of a vertex shader and a fragment 113 | * shader. 114 | * 115 | * Manages the shader's attributes and uniforms. 116 | * 117 | * @class 118 | * @extends {GLBound} 119 | * @param {context} gl Webgl context 120 | * @param {String} vertex Vertex shader 121 | * @param {String} fragment Fragment shader 122 | */ 123 | class Program extends GLBound { 124 | 125 | constructor(gl, vertex, fragment) { 126 | super(gl); 127 | this.program = null; 128 | this.vertexSource = Program.fixPrecision(vertex); 129 | this.fragmentSource = fragment; 130 | this.attributes = {}; 131 | this.uniforms = {}; 132 | } 133 | 134 | /** 135 | * Initialize the shader 136 | * 137 | * Parses out shader parameters, compiles the shader, and binds it to 138 | * the context. 139 | * 140 | * @return {void} 141 | */ 142 | init() { 143 | var gl = this._gl, vertex, fragment; 144 | vertex = gl.createShader(gl.VERTEX_SHADER); 145 | gl.shaderSource(vertex, this.vertexSource); 146 | gl.compileShader(vertex); 147 | if(!gl.getShaderParameter(vertex, gl.COMPILE_STATUS)) 148 | { 149 | console.warn(gl.getShaderInfoLog(vertex)); // eslint-disable-line no-console 150 | console.error('could not compile vertex shader: ' + this.vertexSource); // eslint-disable-line no-console 151 | throw 'Vertex shader compile error!'; 152 | } 153 | fragment = gl.createShader(gl.FRAGMENT_SHADER); 154 | gl.shaderSource(fragment, this.fragmentSource); 155 | gl.compileShader(fragment); 156 | if(!gl.getShaderParameter(fragment, gl.COMPILE_STATUS)) 157 | { 158 | console.warn(gl.getShaderInfoLog(fragment)); // eslint-disable-line no-console 159 | console.error('could not compile fragment shader: ' + this.fragmentSource); // eslint-disable-line no-console 160 | throw 'Fragment shader compile error!'; 161 | } 162 | 163 | this.program = gl.createProgram(); 164 | gl.attachShader(this.program, vertex); 165 | gl.attachShader(this.program, fragment); 166 | 167 | gl.linkProgram(this.program); 168 | 169 | if(!gl.getProgramParameter(this.program, gl.LINK_STATUS)) 170 | { 171 | // TODO: verbose like above 172 | throw 'Could not link program'; 173 | } 174 | gl.useProgram(this.program); 175 | 176 | this._setupLocations(); 177 | } 178 | 179 | /** 180 | * Use the program with the given draw function 181 | * @param {Function} fn Function to handle the actual drawing. 182 | * The programs attributes and uniforms will 183 | * be passed to the draw function for use. 184 | * @return {void} 185 | */ 186 | use(fn) { 187 | var gl = this._gl; 188 | if(!this.program) 189 | { 190 | this.init(); 191 | } 192 | else 193 | { 194 | gl.useProgram(this.program); 195 | } 196 | fn(this.attributes, this.uniforms); 197 | //gl.useProgram(0); 198 | } 199 | 200 | _setupLocations() { 201 | var gl = this._gl, program = this.program; 202 | // this is taken partly from PhiloGL's Program class. 203 | //fill attribute locations 204 | var len = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES), info, name; 205 | for (var i = 0; i < len; i++) { 206 | info = gl.getActiveAttrib(program, i); 207 | this.attributes[info.name] = gl.getAttribLocation(program, info.name); 208 | } 209 | 210 | //create uniform setters 211 | len = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); 212 | for (i = 0; i < len; i++) { 213 | info = gl.getActiveUniform(program, i); 214 | name = info.name; 215 | //if array name then clean the array brackets 216 | name = name[name.length -1] == ']' ? name.substr(0, name.length -3) : name; 217 | this.uniforms[name] = getUniformSetter(gl, program, info, info.name != name); 218 | } 219 | } 220 | 221 | /** 222 | * Fixes an issue with shaders where the shader doesn't set a precision, 223 | * leading it to have a mismatch with its counterpart 224 | * 225 | * I.e. the vertex shader might set a precision, but the fragment shader 226 | * does not, leading to precision mismatch errors. 227 | * @static 228 | * @param {String} shader The shader to check/fix 229 | * @return {String} The fixed shader, or the original if it needed 230 | * no patching. 231 | */ 232 | static fixPrecision(shader) 233 | { 234 | if(/precision mediump float/g.test(shader)) 235 | { 236 | return shader; 237 | } 238 | else 239 | { 240 | var lines = shader.split("\n"); 241 | lines.splice(1, 0, "#ifdef GL_ES", "precision mediump float;", "#endif"); 242 | return lines.join("\n"); 243 | } 244 | } 245 | } 246 | 247 | export default Program; 248 | -------------------------------------------------------------------------------- /src/program/glowramp.js: -------------------------------------------------------------------------------- 1 | import Program from '../program'; 2 | import { resetGL } from '../utils'; 3 | 4 | /** 5 | * A GlowrampProgram is a program meant for drawing 6 | * transparent glowramp drawables 7 | * 8 | * @extends {Program} 9 | * @param {context} gl WebGL context 10 | * @param {String} vertex Vertex shader source 11 | * @param {String} fragment Fragment shader source 12 | */ 13 | class GlowrampProgram extends Program { 14 | 15 | constructor(gl, vertex, fragment) { 16 | super(gl, vertex, fragment); 17 | } 18 | 19 | /** 20 | * Use this program to draw 21 | * 22 | * Sets up the proper blending modes, etc 23 | * @param {Function} fn The draw function 24 | * @return {void} 25 | */ 26 | use(fn) { 27 | if(!this.program) 28 | { 29 | this.init(); 30 | } 31 | var gl = this._gl; 32 | gl.useProgram(this.program); 33 | // init stuffs. 34 | gl.disable(gl.CULL_FACE); 35 | gl.enable(gl.BLEND); 36 | gl.depthMask(false); 37 | gl.blendEquation(gl.FUNC_ADD); 38 | //gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); 39 | gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); 40 | 41 | fn(this.attributes, this.uniforms); 42 | 43 | resetGL(gl); 44 | //gl.useProgram(0); 45 | } 46 | } 47 | 48 | export default GlowrampProgram; 49 | -------------------------------------------------------------------------------- /src/program/opaque.js: -------------------------------------------------------------------------------- 1 | import Program from '../program'; 2 | import { resetGL } from '../utils'; 3 | 4 | /** 5 | * And OpaqueProgram is a Program used to draw opaque drawables 6 | * 7 | * @extends {Program} 8 | * @param {context} gl WebGL context 9 | * @param {String} vertex Vertex shader source 10 | * @param {String} fragment Fragment shader source 11 | */ 12 | class OpaqueProgram extends Program { 13 | 14 | constructor(gl, vertex, fragment) { 15 | super(gl, vertex, fragment); 16 | } 17 | 18 | /** 19 | * Use this program to draw. 20 | * 21 | * Sets up the proper culling for drawing opaque objects 22 | * 23 | * @param {Function} fn The draw function 24 | * @return {void} 25 | */ 26 | use(fn) { 27 | if(!this.program) 28 | { 29 | this.init(); 30 | } 31 | var gl = this._gl; 32 | gl.useProgram(this.program); 33 | // init stuffs. 34 | gl.enable(gl.DEPTH_TEST); 35 | gl.enable(gl.CULL_FACE); 36 | gl.frontFace(gl.CCW); 37 | gl.cullFace(gl.BACK); 38 | gl.depthMask(true); 39 | 40 | fn(this.attributes, this.uniforms); 41 | 42 | resetGL(gl); 43 | //gl.useProgram(0); 44 | } 45 | } 46 | 47 | export default OpaqueProgram; 48 | -------------------------------------------------------------------------------- /src/renderer.js: -------------------------------------------------------------------------------- 1 | import GLBound from './gl-bound'; 2 | import { mat4 } from 'gl-matrix'; 3 | 4 | /** 5 | * ... In retrospect, I'm not sure exactly the purpose this class serves 6 | * It seems that ObjectRenderer inherits from this class, but it's also 7 | * the only renderer that's currently used. 8 | * TODO: Revisit this 9 | * 10 | * @class 11 | * @extends {GLBound} 12 | * @param {context} gl A WebGL context 13 | * @param {AssetManager} manager An AssetManager to manage GL-bound 14 | */ 15 | class Renderer extends GLBound { 16 | 17 | constructor(gl, manager) { 18 | super(gl); 19 | this.manager = manager; 20 | this.viewProject = mat4.create(); 21 | this.view = mat4.create(); 22 | this.project = mat4.create(); 23 | this.elapsed = 0; 24 | } 25 | 26 | /** 27 | * Update the internal view and projection matrices 28 | * 29 | * @param {Camera} camera The camera 30 | * @return {void} 31 | */ 32 | updateView(camera) { 33 | this.view = camera.view; 34 | this.project = camera.project; 35 | mat4.multiply(this.viewProject, this.project, this.view); 36 | } 37 | 38 | /** 39 | * Actually controls the render loop? 40 | * 41 | * @abstract 42 | * @return {void} 43 | */ 44 | render() { 45 | throw new Error('render() must be implemented'); 46 | } 47 | 48 | /** 49 | * Updates the internal counter of elapsed time. 50 | * 51 | * @param {Number} delta Time elapsed since last render call 52 | * @return {void} 53 | */ 54 | updateTime(delta) { 55 | this.elapsed += delta; 56 | } 57 | } 58 | 59 | export default Renderer; 60 | -------------------------------------------------------------------------------- /src/renderer/object.js: -------------------------------------------------------------------------------- 1 | import Renderer from '../renderer'; 2 | import Drawable from '../drawable'; 3 | 4 | // TODO rework this. 5 | class ObjectRenderer extends Renderer { 6 | constructor(gl, manager) { 7 | super(gl, manager); 8 | this.drawables = []; 9 | } 10 | 11 | addDrawable(drawable, excludeChildren) { 12 | if(!(drawable instanceof Drawable)) 13 | { 14 | return Promise.reject(new Error('Drawables must always inherit from the base Drawable')); 15 | } 16 | var promise = drawable.init(this.manager).catch((err) => { 17 | console.warn('could not initialize drawable: ', drawable); // eslint-disable-line no-console 18 | return Promise.reject(err); 19 | }); 20 | if(drawable.updateView) 21 | { 22 | drawable.updateView(this.viewProject, null); 23 | } 24 | this.drawables.push(drawable); 25 | if(!excludeChildren) { 26 | drawable.children.forEach((c) => { 27 | this.addDrawable(c); 28 | }); 29 | } 30 | return promise; 31 | } 32 | 33 | removeDrawable(drawable, destroy) { 34 | for(var i = 0; i < this.drawables.length; i++) 35 | { 36 | if(this.drawables[i] === drawable) 37 | { 38 | this.drawables.splice(i, 1); 39 | if(destroy) { 40 | drawable.dispose(); 41 | return true; 42 | } else { 43 | return drawable; 44 | } 45 | } 46 | } 47 | return false; 48 | } 49 | 50 | addEntity(entity) { 51 | for(var i in entity.drawables) { 52 | this.addDrawable(entity.drawables[i]); 53 | } 54 | } 55 | 56 | updateView(camera) { 57 | super.updateView(camera); 58 | var i, len = this.drawables.length; 59 | for(i = 0; i < len; i++) 60 | { 61 | if(this.drawables[i].updateView) { 62 | this.drawables[i].updateView(this.viewProject, camera); 63 | } 64 | } 65 | } 66 | 67 | render() { 68 | var i, len = this.drawables.length; 69 | for(i = 0; i < len; i++) 70 | { 71 | this.drawables[i].draw(); 72 | } 73 | } 74 | 75 | updateTime(delta) { 76 | super.updateTime(delta); 77 | var i, len = this.drawables.length; 78 | for(i = 0; i < len; i++) 79 | { 80 | // if these return false, remove them from the render loop: 81 | if(!this.drawables[i].updateTime(delta)) 82 | { 83 | this.drawables.splice(i, 1); 84 | i--; 85 | len--; 86 | } 87 | } 88 | } 89 | } 90 | 91 | export default ObjectRenderer; 92 | -------------------------------------------------------------------------------- /src/renderer/portal.js: -------------------------------------------------------------------------------- 1 | import Renderer from '../renderer'; 2 | 3 | // TODO: rework this. 4 | class PortalRenderer extends Renderer { 5 | constructor(gl, manager) { 6 | super(gl, manager); 7 | this.portals = []; 8 | this.links = null; 9 | this.particles = null; 10 | } 11 | 12 | updateView(camera) { 13 | super.updateView(camera); 14 | var i, len = this.portals.length; 15 | for(i = 0; i < len; i++) 16 | { 17 | this.portals[i].updateView(this.viewProject, camera); 18 | } 19 | } 20 | 21 | render() { 22 | var i, len = this.portals.length; 23 | for(i = 0; i < len; i++) 24 | { 25 | this.portals[i].draw(); 26 | } 27 | } 28 | 29 | updateTime(delta) { 30 | super.updateTime(delta); 31 | var i, len = this.portals.length; 32 | for(i = 0; i < len; i++) 33 | { 34 | // if these return false, remove them from the render loop: 35 | if(!this.portals[i].updateTime(delta)) 36 | { 37 | this.portals.splice(i, 1); 38 | i--; 39 | len--; 40 | } 41 | } 42 | } 43 | } 44 | 45 | export default PortalRenderer; 46 | -------------------------------------------------------------------------------- /src/texture.js: -------------------------------------------------------------------------------- 1 | import GLBound from './gl-bound'; 2 | 3 | /** 4 | * A gl-bound texture 5 | * Supports most (all?) of the texture binding options. 6 | * Also generates mipmaps if the texture requires it. 7 | * 8 | * @class 9 | * @param {context} gl A WebGL context 10 | * @param {Object} info Texture parameters 11 | * @param {Images} image An image to use as the texture 12 | */ 13 | class Texture extends GLBound { 14 | 15 | constructor(gl, info, image) { 16 | super(gl); 17 | this.info = info; 18 | var map = { 19 | 'MipMapLinearLinear': gl.LINEAR_MIPMAP_LINEAR, 20 | 'Linear': gl.LINEAR, 21 | 'MipMapLinearNearest': gl.LINEAR_MIPMAP_NEAREST, 22 | 'MipMapNearestLinear': gl.NEAREST_MIPMAP_LINEAR, 23 | 'Repeat': gl.REPEAT, 24 | 'ClampToEdge': gl.CLAMP_TO_EDGE 25 | }; 26 | var texture = gl.createTexture(); 27 | gl.bindTexture(gl.TEXTURE_2D, texture); 28 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, map[info.minFilter]); 29 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, map[info.magFilter]); 30 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, map[info.wrapS]); 31 | gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, map[info.wrapT]); 32 | gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); 33 | if(/MipMap/.test(info.minFilter)) 34 | { 35 | gl.generateMipmap(gl.TEXTURE_2D); 36 | } 37 | 38 | gl.bindTexture(gl.TEXTURE_2D, null); 39 | 40 | this.texture = texture; 41 | } 42 | 43 | /** 44 | * Bind the texture to a particular texture index 45 | * 46 | * @param {Number} index Texture index to bind to 47 | * @return {void} 48 | */ 49 | use(index) { 50 | var gl = this._gl; 51 | index = index || 0; 52 | gl.bindTexture(gl.TEXTURE_2D, this.texture); 53 | gl.activeTexture(gl.TEXTURE0 + index); 54 | } 55 | 56 | /** 57 | * NYI: TODO 58 | * 59 | * @return {void} 60 | */ 61 | dispose() { 62 | // TODO: Figure out when this should be called. 63 | // noop; 64 | } 65 | } 66 | 67 | export default Texture; 68 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | import Constants from './constants'; 2 | import TexturedDrawable from './drawable/textured'; 3 | 4 | /** 5 | * Reset the GL state to some base state 6 | * @param {context} gl A WebGL context 7 | * @return {void} 8 | */ 9 | export function resetGL(gl) { 10 | gl.lineWidth(1.0); 11 | gl.enable(gl.CULL_FACE); 12 | gl.frontFace(gl.CCW); 13 | gl.cullFace(gl.BACK); 14 | gl.enable(gl.DEPTH_TEST); 15 | gl.blendEquation(gl.FUNC_ADD); 16 | //gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); 17 | gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); 18 | gl.disable(gl.BLEND); 19 | gl.depthMask(true); 20 | } 21 | 22 | /** 23 | * Set parameters base on some base set of defaults 24 | * @param {Object} base Parameter definition with defaults 25 | * @param {Object} opts Options (overrides) 26 | * @param {Boolean} deep Do deep copying on objects. 27 | * @return {Object} The base object 28 | */ 29 | export function setParams(base, opts, deep) { 30 | for(var i in base) 31 | { 32 | if(base.hasOwnProperty(i) && opts.hasOwnProperty(i)) 33 | { 34 | if(deep && typeof(base[i]) == 'object' && typeof(opts[i]) == 'object') 35 | { 36 | base[i] = setParams(base[i], opts[i], deep); 37 | } 38 | else 39 | { 40 | base[i] = opts[i]; 41 | } 42 | } 43 | } 44 | return base; 45 | } 46 | 47 | /** 48 | * Disco portal animation 49 | * @param {Number} delta Time since last frame 50 | * @param {Number} elapsed Total time elapsed 51 | * @return {Boolean} Returns true to continue animation 52 | */ 53 | export function disco(delta, elapsed) { 54 | var inc = elapsed / 1000; 55 | this.uniforms.u_baseColor[0] = Math.sin(inc); 56 | this.uniforms.u_baseColor[1] = Math.sin(inc + (2 * Math.PI / 3)); 57 | this.uniforms.u_baseColor[2] = Math.sin(inc + (4 * Math.PI / 3)); 58 | return true; 59 | } 60 | 61 | /** 62 | * Makes an artifact drawable class 63 | * @param {String} meshName Name of the mesh to use 64 | * @param {String} textureName Name of the texture to use 65 | * @return {ArtifactDrawable} A new drawable class for this artifact 66 | */ 67 | export function makeArtifact(meshName, textureName) { 68 | class artifact extends TexturedDrawable { 69 | constructor() { 70 | super(Constants.Program.Textured, meshName, textureName); 71 | } 72 | } 73 | 74 | return artifact; 75 | } 76 | 77 | /** 78 | * Generate a set of artifacts 79 | * 80 | * @private 81 | * @param {String} series Series name 82 | * Should match the internal name of the resources 83 | * @param {Number} num Number of artifacts in the series 84 | * @param {Boolean} hasFrozen Whether or not the series also includes frozen 85 | * variants 86 | * @return {Object} Object containing artifact drawable classes 87 | * for each artifact. 88 | */ 89 | export function generateArtifacts(series, num, hasFrozen) { 90 | var i, meshName, textureName = 'Artifact' + series + 'Texture'; 91 | 92 | var artifacts = {}; 93 | 94 | for(i = 1; i <= num; i++) { 95 | meshName = series + i; 96 | artifacts['' + i] = makeArtifact(meshName, textureName); 97 | } 98 | if(hasFrozen) { 99 | for(i = 1; i <= num; i++) { 100 | meshName = series + 'Frozen' + i; 101 | artifacts['Frozen' + i] = makeArtifact(meshName, textureName); 102 | } 103 | } 104 | 105 | return artifacts; 106 | } 107 | -------------------------------------------------------------------------------- /src/vertex-attribute.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A vertex attribute 3 | * 4 | * @param {String} name Name of the attribute 5 | * @param {Number} size Size of the attribute (in bytes) 6 | * @param {Number} type The type of vertex attribute 7 | */ 8 | class VertexAttribute { 9 | constructor(name, size, type) { 10 | this.name = name; 11 | this.size = size; 12 | this.type = type; 13 | } 14 | } 15 | 16 | export default VertexAttribute; 17 | -------------------------------------------------------------------------------- /static/atmosphere.glsl.frag: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump float; 3 | #endif 4 | varying vec3 vNormal; 5 | void main() { 6 | float intensity = pow( 0.8 - dot( vNormal, vec3( 0, 1.0, 0 ) ), 12.0 ); 7 | gl_FragColor = vec4( 1.0, 1.0, 1.0, 1.0 ) * intensity; 8 | } -------------------------------------------------------------------------------- /static/atmosphere.glsl.vert: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump float; 3 | #endif 4 | attribute vec3 a_position; 5 | attribute vec3 a_normal; 6 | uniform mat3 u_normalMatrix; 7 | uniform mat4 u_modelViewProject; 8 | varying vec3 vNormal; 9 | void main() { 10 | vNormal = normalize( u_normalMatrix * a_normal ); 11 | gl_Position = u_modelViewProject * vec4( a_position, 1.0 ); 12 | } 13 | -------------------------------------------------------------------------------- /static/link3d.glsl.frag: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump float; 3 | #endif 4 | uniform sampler2D u_texture; 5 | varying vec4 v_color; 6 | varying vec4 v_texCoord0And1; 7 | // this is unchanged from the original 8 | void main() { 9 | vec4 base = texture2D(u_texture, v_texCoord0And1.xy); 10 | vec4 scrolled = texture2D(u_texture, v_texCoord0And1.zw); 11 | float dots = scrolled.g; 12 | float brighten = mix(1.0, 2.0, base.b + (dots / 4.0)); 13 | gl_FragColor.rgb = v_color.rgb * mix(0.35, brighten, dots); 14 | gl_FragColor.a = v_color.a * base.r; 15 | } 16 | -------------------------------------------------------------------------------- /static/link3d.glsl.vert: -------------------------------------------------------------------------------- 1 | #ifdef GL_ES 2 | precision mediump float; 3 | #endif 4 | uniform mat4 u_modelViewProject; 5 | uniform vec3 u_cameraFwd; 6 | uniform float u_elapsedTime; 7 | uniform mat4 u_model; 8 | attribute vec4 a_position; 9 | attribute vec2 a_texCoord0; 10 | attribute vec3 a_normal; 11 | attribute vec4 a_color; 12 | varying vec4 v_texCoord0And1; 13 | varying vec4 v_color; 14 | void main() { 15 | v_texCoord0And1.xy = (a_texCoord0 + vec2(0, u_elapsedTime * a_position.w * 0.6)); 16 | v_texCoord0And1.zw = a_texCoord0 + vec2(0, u_elapsedTime * a_position.w); 17 | v_color = a_color; 18 | vec4 normal = u_model * vec4(a_normal.xyz, 0.0); 19 | float alpha = abs(dot(normalize(normal.xyz), u_cameraFwd)); 20 | v_color.a *= (3.0 * alpha * alpha) - (2.0 * alpha * alpha * alpha); 21 | gl_Position = u_modelViewProject * vec4(a_position.xyz, 1.0); 22 | } 23 | -------------------------------------------------------------------------------- /static/world.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DeviateFish/ingress-model-viewer/acd83dca9ad9b152fb62a9b04efb5a068775cabc/static/world.jpg -------------------------------------------------------------------------------- /test/all.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | QUnit Example 6 | 7 | 8 | 9 |
10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/all.js: -------------------------------------------------------------------------------- 1 | test("the base function exists", function() { 2 | ok(IMV); 3 | }); 4 | -------------------------------------------------------------------------------- /test/lib/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.12.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | #qunit-tests li .runtime { 115 | float: right; 116 | font-size: smaller; 117 | } 118 | 119 | .qunit-assert-list { 120 | margin-top: 0.5em; 121 | padding: 0.5em; 122 | 123 | background-color: #fff; 124 | 125 | border-radius: 5px; 126 | -moz-border-radius: 5px; 127 | -webkit-border-radius: 5px; 128 | } 129 | 130 | .qunit-collapsed { 131 | display: none; 132 | } 133 | 134 | #qunit-tests table { 135 | border-collapse: collapse; 136 | margin-top: .2em; 137 | } 138 | 139 | #qunit-tests th { 140 | text-align: right; 141 | vertical-align: top; 142 | padding: 0 .5em 0 0; 143 | } 144 | 145 | #qunit-tests td { 146 | vertical-align: top; 147 | } 148 | 149 | #qunit-tests pre { 150 | margin: 0; 151 | white-space: pre-wrap; 152 | word-wrap: break-word; 153 | } 154 | 155 | #qunit-tests del { 156 | background-color: #e0f2be; 157 | color: #374e0c; 158 | text-decoration: none; 159 | } 160 | 161 | #qunit-tests ins { 162 | background-color: #ffcaca; 163 | color: #500; 164 | text-decoration: none; 165 | } 166 | 167 | /*** Test Counts */ 168 | 169 | #qunit-tests b.counts { color: black; } 170 | #qunit-tests b.passed { color: #5E740B; } 171 | #qunit-tests b.failed { color: #710909; } 172 | 173 | #qunit-tests li li { 174 | padding: 5px; 175 | background-color: #fff; 176 | border-bottom: none; 177 | list-style-position: inside; 178 | } 179 | 180 | /*** Passing Styles */ 181 | 182 | #qunit-tests li li.pass { 183 | color: #3c510c; 184 | background-color: #fff; 185 | border-left: 10px solid #C6E746; 186 | } 187 | 188 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 189 | #qunit-tests .pass .test-name { color: #366097; } 190 | 191 | #qunit-tests .pass .test-actual, 192 | #qunit-tests .pass .test-expected { color: #999999; } 193 | 194 | #qunit-banner.qunit-pass { background-color: #C6E746; } 195 | 196 | /*** Failing Styles */ 197 | 198 | #qunit-tests li li.fail { 199 | color: #710909; 200 | background-color: #fff; 201 | border-left: 10px solid #EE5757; 202 | white-space: pre; 203 | } 204 | 205 | #qunit-tests > li:last-child { 206 | border-radius: 0 0 5px 5px; 207 | -moz-border-radius: 0 0 5px 5px; 208 | -webkit-border-bottom-right-radius: 5px; 209 | -webkit-border-bottom-left-radius: 5px; 210 | } 211 | 212 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 213 | #qunit-tests .fail .test-name, 214 | #qunit-tests .fail .module-name { color: #000000; } 215 | 216 | #qunit-tests .fail .test-actual { color: #EE5757; } 217 | #qunit-tests .fail .test-expected { color: green; } 218 | 219 | #qunit-banner.qunit-fail { background-color: #EE5757; } 220 | 221 | 222 | /** Result */ 223 | 224 | #qunit-testresult { 225 | padding: 0.5em 0.5em 0.5em 2.5em; 226 | 227 | color: #2b81af; 228 | background-color: #D2E0E6; 229 | 230 | border-bottom: 1px solid white; 231 | } 232 | #qunit-testresult .module-name { 233 | font-weight: bold; 234 | } 235 | 236 | /** Fixture */ 237 | 238 | #qunit-fixture { 239 | position: absolute; 240 | top: -10000px; 241 | left: -10000px; 242 | width: 1000px; 243 | height: 1000px; 244 | } 245 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | entry: path.resolve(__dirname, 'src', 'ingress-model-viewer.js'), 6 | output: { 7 | path: path.resolve(__dirname, 'dist'), 8 | filename: 'ingress-model-viewer.js', 9 | libraryTarget: 'var', 10 | library: "IMV" 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | exclude: /(node_modules|bower_components)/, 17 | loader: 'babel-loader', 18 | query: { 19 | presets: ['es2015'] 20 | } 21 | }, 22 | { 23 | test: /\.js$/, 24 | exclude: /(node_modules|bower_components)/, 25 | loader: "eslint-loader", 26 | options: {} 27 | }, 28 | ] 29 | }, 30 | devServer: { 31 | contentBase: path.join(__dirname), 32 | port: 8080, 33 | publicPath: '/dist/' 34 | } 35 | }; 36 | --------------------------------------------------------------------------------