├── .doclets.yml ├── .editorconfig ├── .eslintrc.json ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── benchmarks ├── .eslintrc.json ├── lib │ ├── bleacher.js │ └── bleacher.less ├── pixi │ ├── index.html │ ├── transform.bench.js │ └── update.bench.js └── self │ └── index.html ├── build ├── .eslintrc.json ├── EntryGeneratorPlugin.js ├── banner.txt ├── build-name.js ├── jsdoc-fix.js ├── jsdoc.conf.json ├── karma.conf.js └── webpack.config.js ├── docs ├── BuildingPlugins.md ├── ComposingEntities.md ├── CustomComponents.md ├── CustomSystems.md ├── GettingStarted.md └── README.md ├── examples ├── basic │ ├── index.html │ └── spade_A.png ├── batching │ ├── imgs │ │ ├── diamond_A.png │ │ ├── diamond_J.png │ │ ├── diamond_K.png │ │ ├── diamond_Q.png │ │ ├── spade_A.png │ │ ├── spade_J.png │ │ ├── spade_K.png │ │ └── spade_Q.png │ └── index.html ├── filters │ ├── index.html │ └── spade_A.png └── interaction │ └── index.html ├── package.json └── plugins ├── core ├── README.md ├── package.json ├── src │ ├── debug.js │ ├── ecs │ │ ├── Entity.js │ │ ├── SelfRenderComponent.js │ │ ├── SelfRenderSystem.js │ │ ├── System.js │ │ ├── VisibilityComponent.js │ │ └── index.js │ ├── gl │ │ ├── GLBuffer.js │ │ ├── GLContext.js │ │ ├── GLData.js │ │ ├── GLFramebuffer.js │ │ ├── GLProgramCache.js │ │ ├── GLQuad.js │ │ ├── GLShader.js │ │ ├── GLTexture.js │ │ ├── GLVertexArrayObject.js │ │ └── index.js │ ├── index.js │ ├── math │ │ ├── Matrix2d.js │ │ ├── Vector2d.js │ │ └── index.js │ ├── render │ │ ├── ObjectRenderer.js │ │ ├── RenderState.js │ │ ├── RenderTarget.js │ │ ├── Renderer.js │ │ ├── Shader.js │ │ └── index.js │ └── util │ │ ├── BlendMode.js │ │ ├── Buffer.js │ │ ├── Color.js │ │ ├── Flags.js │ │ └── index.js └── test │ ├── .eslintrc.json │ ├── gl │ └── GLQuad.test.js │ └── util │ ├── BlendMode.test.js │ ├── Buffer.test.js │ └── index.test.js ├── filters-pack ├── README.md ├── package.json └── src │ ├── blur │ ├── BlurFilter.js │ ├── BlurXFilter.js │ ├── BlurYFilter.js │ ├── blur.frag │ ├── blur.vert │ └── blurUtil.js │ ├── fxaa │ ├── FXAAFilter.js │ ├── fxaa.frag │ └── fxaa.vert │ ├── index.js │ └── noise │ ├── NoiseFilter.js │ └── noise.frag ├── filters ├── README.md ├── package.json ├── src │ ├── Filter.js │ ├── FilterComponent.js │ ├── FilterPrepareSystem.js │ ├── FilterRenderSystem.js │ ├── FilterUtils.js │ ├── default.vert │ └── index.js └── test │ └── .eslintrc.json ├── interaction ├── README.md ├── package.json └── src │ ├── InteractionComponent.js │ ├── InteractionSystem.js │ ├── Pointer.js │ └── index.js ├── shapes ├── README.md ├── package.json ├── src │ ├── BoundingBox.js │ ├── BoundsComponent.js │ ├── Polygon.js │ ├── Rectangle.js │ ├── SpriteBoundsComponent.js │ └── index.js └── test │ ├── .eslintrc.json │ ├── BoundingBox.test.js │ └── Rectangle.test.js ├── sprites ├── README.md ├── package.json └── src │ ├── Sprite.js │ ├── SpriteComponent.js │ ├── SpriteRenderSystem.js │ ├── SpriteRenderer.js │ ├── index.js │ └── shader │ ├── multi-texture.frag │ └── multi-texture.vert ├── text-bitmap ├── package.json ├── src │ ├── BitmapTextComponent.js │ └── index.js └── test │ └── .eslintrc.json ├── text-canvas ├── README.md ├── package.json ├── src │ ├── CanvasTextStyle.js │ ├── CanvasTextWriter.js │ └── index.js └── test │ └── .eslintrc.json ├── textures ├── README.md ├── package.json └── src │ ├── Texture.js │ ├── TextureComponent.js │ ├── TextureSource.js │ ├── TextureUVs.js │ └── index.js └── transform ├── README.md ├── package.json └── src ├── Transform.js ├── TransformComponent.js ├── TransformUpdateSystem.js └── index.js /.doclets.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - plugins/**/*.js 3 | 4 | branches: 5 | - master 6 | 7 | packageJson: package.json 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs. 2 | # More information at http://EditorConfig.org 3 | root = true 4 | 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | indent_style = space 10 | indent_size = 4 11 | 12 | [{package.json,bower.json,*.yml}] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@englercj/code-style/.eslintrc.json", 3 | "env": { 4 | "commonjs": true, 5 | "browser": true 6 | }, 7 | "globals": { 8 | }, 9 | "parserOptions": { 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "new-cap": [2, { "newIsCap": true, "capIsNew": false, "properties": false }], 14 | "curly": [2, "multi-line"], 15 | "brace-style": [2, "allman"], 16 | "no-restricted-syntax": [2, "DebuggerStatement", "EmptyStatement", "WithStatement"], 17 | "no-magic-numbers": 0, 18 | "no-labels": 0 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Fae 2 | 3 | ## Code of Conduct 4 | 5 | The Code of Conduct explains the *bare minimum* behavior 6 | expectations that are required of contributors. 7 | [Please read it before participating.](../CODE_OF_CONDUCT.md) 8 | 9 | ## Issue Contributions 10 | 11 | You can report issues on [GitHub](https://github.com/Fae/fae/issues). 12 | Please search existing issues before submitting a new one. 13 | 14 | ## Making Changes 15 | 16 | To make changes you will need to have [nodejs](http://nodejs.org) installed. 17 | Once you are ready you can contribute a change by following these steps: 18 | 19 | ### Step 1: Fork 20 | 21 | Fork the project [on Github](https://github.com/Fae/fae) and checkout 22 | your copy locally. 23 | 24 | ```text 25 | $ git clone git@github.com:Fae/fae.git 26 | $ cd fae 27 | $ git remote add upstream git://github.com/Fae/fae.git 28 | ``` 29 | 30 | #### Which branch? 31 | 32 | For developing new features and bug fixes, the `master` branch should be pulled 33 | and built upon. 34 | 35 | ### Step 2: Branch 36 | 37 | Create a feature/bug-fix branch and start hacking: 38 | 39 | ```text 40 | $ git checkout -b feature/new-feature -t origin/master # or bug/fix-something 41 | ``` 42 | 43 | ### Step 3: Commit 44 | 45 | Make sure git knows your name and email address: 46 | 47 | ```text 48 | $ git config --global user.name "J. Random User" 49 | $ git config --global user.email "j.random.user@example.com" 50 | ``` 51 | 52 | Writing good commit logs is important. A commit log should describe what 53 | changed and why. Follow these guidelines when writing one: 54 | 55 | 1. The first line should be 50 characters or less and contain a short 56 | description of the change. 57 | 2. Keep the second line blank. 58 | 3. Wrap all other lines at 72 columns. 59 | 60 | A good commit log can look something like this: 61 | 62 | ```txt 63 | explaining the commit in one line 64 | 65 | Body of commit message is a few lines of text, explaining things 66 | in more detail, possibly giving some background about the issue 67 | being fixed, etc. etc. 68 | 69 | The body of the commit message can be several paragraphs, and 70 | please do proper word-wrap and keep columns shorter than about 71 | 72 characters or so. That way `git log` will show things 72 | nicely even when it is indented. 73 | ``` 74 | 75 | The header line should be meaningful; it is what other people see when they 76 | run `git shortlog` or `git log --oneline`. 77 | 78 | Check the output of `git log --oneline files_that_you_changed` to find out 79 | what subsystem (or subsystems) your changes touch. 80 | 81 | If your patch fixes an open issue, you can add a reference to it at the end 82 | of the log. Use the `Fixes:` prefix and the full issue URL. For example: 83 | 84 | ```txt 85 | Fixes: https://github.com/Fae/fae/issues/1337 86 | ``` 87 | 88 | ### Step 4: Rebase 89 | 90 | Use `git rebase` (not `git merge`) to sync your work from time to time. 91 | 92 | ```text 93 | $ git fetch upstream 94 | $ git rebase upstream/master 95 | ``` 96 | ### Step 5: Test 97 | 98 | Bug fixes and features **should come with tests**. Add your tests in the 99 | `test/unit/` directory. Looking at other tests to see how they should be 100 | structured can help. 101 | 102 | To run the tests: 103 | 104 | ```text 105 | $ npm test 106 | ``` 107 | 108 | Make sure the linter is happy and that all tests pass. Please, do not submit 109 | patches that fail either check. 110 | 111 | If you want to run the linter without running tests, use `npm run lint`. 112 | 113 | ### Step 6: Push 114 | 115 | ```text 116 | $ git push origin feature/new-feature # or bug/fix-something 117 | ``` 118 | 119 | Go to https://github.com//fae and select your feature branch. 120 | Click the 'Pull Request' button and fill out the form. 121 | 122 | Pull requests are usually reviewed within a few days. If there are comments 123 | to address, apply your changes in a separate commit and push that to your 124 | feature branch. Post a comment in the pull request afterwards; GitHub does 125 | not send out notifications when you add commits. 126 | 127 | 128 | ## Developer's Certificate of Origin 1.1 129 | 130 | By making a contribution to this project, I certify that: 131 | 132 | * (a) The contribution was created in whole or in part by me and I 133 | have the right to submit it under the open source license 134 | indicated in the file; or 135 | 136 | * (b) The contribution is based upon previous work that, to the best 137 | of my knowledge, is covered under an appropriate open source 138 | license and I have the right under that license to submit that 139 | work with modifications, whether created in whole or in part 140 | by me, under the same open source license (unless I am 141 | permitted to submit under a different license), as indicated 142 | in the file; or 143 | 144 | * (c) The contribution was provided directly to me by some other 145 | person who certified (a), (b) or (c) and I have not modified 146 | it. 147 | 148 | * (d) I understand and agree that this project and the contribution 149 | are public and that a record of the contribution (including all 150 | personal information I submit with it, including my sign-off) is 151 | maintained indefinitely and may be redistributed consistent with 152 | this project or the open source license(s) involved. 153 | 154 | ## Attribution 155 | 156 | This contribution guide is adapted from the Contribution Guide of 157 | [Node.js](https://github.com/nodejs/node). 158 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 15 | 16 | - **Library Version**: _version_ 17 | - **Browser & Version**: _version_ 18 | - **OS & Version**: _version_ 19 | - **Running Example**: _url_ 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | ##### Description of change 13 | 14 | 15 | ##### Pre-Merge Checklist 16 | 17 | 18 | - [ ] Tests and/or benchmarks are included 19 | - [ ] Documentation is changed or added 20 | - [ ] Lint process passed (`npm run lint`) 21 | - [ ] Tests passed (`npm run test`) 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # sublime text files 2 | *.sublime* 3 | *.*~*.TMP 4 | 5 | # temp files 6 | .DS_Store 7 | Thumbs.db 8 | Desktop.ini 9 | npm-debug.log 10 | 11 | # project files 12 | .project 13 | .idea 14 | 15 | # vim swap files 16 | *.sw* 17 | 18 | # emacs temp files 19 | *~ 20 | \#*# 21 | 22 | # project ignores 23 | !.gitkeep 24 | *__temp 25 | *.sqlite 26 | .snyk 27 | .commit 28 | entry-*.js 29 | node_modules/ 30 | dist/ 31 | out/ 32 | api-docs/ 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - '6' 7 | 8 | branches: 9 | only: 10 | - master 11 | 12 | cache: 13 | directories: 14 | - node_modules 15 | 16 | before_script: 17 | - export DISPLAY=:99.0 18 | - sh -e /etc/init.d/xvfb start 19 | 20 | install: 21 | - npm config set spin false 22 | - npm config set loglevel http 23 | - npm install 24 | 25 | script: 26 | - npm test 27 | - npm run build 28 | 29 | addons: 30 | sauce_connect: true 31 | 32 | deploy: 33 | - provider: s3 34 | access_key_id: $AWS_ACCESS_KEY 35 | secret_access_key: $AWS_SECRET_KEY 36 | bucket: "fae-builds" 37 | skip_cleanup: true 38 | acl: public_read 39 | region: us-west-2 40 | local_dir: dist 41 | upload_dir: unstable/$TRAVIS_COMMIT 42 | storage_class: STANDARD_IA 43 | edge: true 44 | on: 45 | repo: Fae/fae 46 | - provider: s3 47 | access_key_id: $AWS_ACCESS_KEY 48 | secret_access_key: $AWS_SECRET_KEY 49 | bucket: "fae-builds" 50 | skip_cleanup: true 51 | acl: public_read 52 | region: us-west-2 53 | local_dir: dist 54 | upload_dir: unstable/$TRAVIS_BRANCH 55 | on: 56 | repo: Fae/fae 57 | - provider: s3 58 | access_key_id: $AWS_ACCESS_KEY 59 | secret_access_key: $AWS_SECRET_KEY 60 | bucket: "fae-builds" 61 | skip_cleanup: true 62 | acl: public_read 63 | region: us-west-2 64 | local_dir: dist 65 | upload_dir: release/$TRAVIS_TAG 66 | on: 67 | repo: Fae/fae 68 | tags: true 69 | 70 | env: 71 | global: 72 | - SAUCE_USERNAME=faejs 73 | - secure: "Zt8SsozLoD3Jss0r7bOb6a5/q1/oiJh/95NtPEjfHiMrxOsEgFrn8Vr8i7iVJnhOsc2UMzuC5j7zKBepkvTkle4uyCBMlcVl98C/aBoXKZ2rZ8VX+rVqncVSJIF7lMKd5gmgL5X3XJK73rSniqdZZy60b91qB7vtMjVAilG1aHk1DCbsGS8vtup12KUG4WpU0uX4K8s0mh7tlGdHqw8PMqjUs8LzP37V5auBBgbeH7w1mmm0Qv8KXep+5ErRzg9FjrBZnDl6lFrfXeKI44qgQWn2aO0x6iREaHM6ppnA7oeJB54EZiBs0C2JjMA0r37jFfXuy9Ofx4KfkxAnGQs9ohAECTinDu87Y3znY4YqmOmjn6Nn6ayYqvmwTC3CuzhUUtLS7nxSL65/F/7xWubj7dMSsfQFb83aYHhy3xue2NBRGOXsRdlMxz+nis0mO4XWcAAYlryu2/hoL8gkMna3vKWxcBLkpjaZjTbOy0zqpE2sodGSdXxv3AwVII/eHXE8sbNlcrGl4lPkWbLa3PUikPuBAETlwhM1fll0epK74qbwF3z7dn/ESqNBjiWCcBr0JaY1bYCENxnCR3uWIA7e8J5t1W6DWM719IzXDWLzBV4egxN4IgDwSEiCv9O/J2qdYjQ8YQSvVUC48hLjGYVTyTELxeIX8Jj3tkspy45fNSQ=" 74 | - secure: "aTgQWMjQvOO5ppXcdeUgve33l07dykPKXBNf8KLM4hJshUONsVGdeIa5jRet/w6qA1/Q1v3OLp96Hle85PQZcGcjUUt/oijKRUAjyd1HJ7uvGMW5O2w1nuwtRyIwEXlIFSNld79Fmz9WpRYr4x5h2+UnSJl4peSnJ/1Ex16aIQrczfDjoehgzV43GL5Fa1tZiUS3VQ1YbM8pZCCY515uyagwrlKvBDGTX18iTtel0I4ZdZXEhtriLcojmAG3Gcg1paxgNP2F1FMIMOXEDbmnEVi0SAhdgD9xWkVCWfspSyxyFuPJITg8F9Ua1EKdgDPc0bAT6+hKvlm7h0o5fHR5NV5cwE30xWqoLptytpakCOGy9cv/EK3/jj7oWbcFl5PPWXyfdFtMY2ZDAlZl08hhIPHX0JBC8uRy8u4OgbhosFixPDT/Ap1jvJFt7kC6pmD6BW9nCF62qmt0IdEQhj7XQS9UK2rEAKvcOts3S5cmiCA0xy1VPXfVl8EfGFhSI8ywu5W2GUxMy/qh9gjExZcGsCJnrmzquuHecoT7z2hZgh2QVat/oJcPfFWUMOjf/fHN+FsVdELjiZbyNm2cQD4W6DeT5RRQkyMu2fd6jYlz4/2HMC5V00nYCcqDVn8TmoqVLQCnqxcx7c/mgeLG5KeIuzZDR6aAGvbXZ3x/I6nyLdM=" 75 | - secure: "wxq07p4X9B4+eWwFObIcevZLh79PkwHvUS9oFJDKdCZZqssWpS+2gfhWmLC+Lpr/R8qjScxzBoiqJWrn/E+nyoqSkllc9k/LYFZc4Z0P+1NSjLl32RdBLltPucqC7Rgr5DlUAvRvBlJY1jvKFaYW5BRaQ1ItozEbmOE5s8xiz3gmm9b493zFGCr4sdtrgjaB8LatWrYtyQLxJvtnfSnZnXfdZkOQssRCoAffk2gXb43TEZIuxBHMTOPS+Ia/XQWDvJNuniZD1EtQMgOAzhLM5JYnQHq4jU2Cyq79hLAC/7AZ1w++TM5pljHFP0jfbOuKk9IMBBGGrFd/3Ep8A/O3ZDJpuXnG8ltTYId5OYfQ32Xl2yN2BH5w1PiQw7lefdNDuqIBNs9t8o8OVfq9BjgQcDtwMinct9Osm6+ZWsiaVPyaaPmaeK9T3S3aWeE+I6UwHaFsKPJW95QnrdGR2FgJ4slAKWza9B0PqMj9eip7uManectZ/dFmCmWWuJwinXROG65dKoZyRvy+XGzIMqXO0o8iO9nW+9GXR2fd3wOW/cIdaFCvJfQuFpKcF2CaI0mWPEUAcLIJlvn4mJpJHSm4wtjWBPhIG9858IwnAfJbYVr+77Sv5SyT1TwKNtkj/GTZrMlAU47ldDls+WSqfy2H9k//vYguk8yu2EPk0+IUrPg=" 76 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our 7 | project and our community a harassment-free experience for everyone, 8 | regardless of age, body size, disability, ethnicity, gender identity 9 | and expression, level of experience, nationality, personal appearance, 10 | race, religion, or sexual identity and orientation. 11 | 12 | ## Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention 26 | or advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of 37 | acceptable behavior and are expected to take appropriate and fair 38 | corrective action in response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, 41 | or reject comments, commits, code, wiki edits, issues, and other 42 | contributions that are not aligned to this Code of Conduct, or to ban 43 | temporarily or permanently any contributor for other behaviors that they 44 | deem inappropriate, threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public 49 | spaces when an individual is representing the project or its community. 50 | Examples of representing a project or community include using an 51 | official project e-mail address, posting via an official social media 52 | account, or acting as an appointed representative at an online or offline 53 | event. Representation of a project may be further defined and clarified 54 | by project maintainers. 55 | 56 | ## Attribution 57 | 58 | This Code of Conduct is adapted from the 59 | [Contributor Covenant](http://contributor-covenant.org/), version 1.4, 60 | available at http://contributor-covenant.org/version/1/4. 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Chad Engler (https://github.com/englercj) 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. 22 | -------------------------------------------------------------------------------- /benchmarks/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.eslintrc.json", 3 | "env": { 4 | "commonjs": false, 5 | "browser": true 6 | }, 7 | "globals": { 8 | "Fae": false, 9 | "PIXI": false, 10 | "Benchmark": false, 11 | "Bench": true 12 | }, 13 | "parserOptions": { 14 | "sourceType": "script" 15 | }, 16 | "rules": { 17 | "no-console": 0, 18 | "no-unused-vars": 0, 19 | "func-names": 0, 20 | "no-undef": 0, 21 | "strict": [2, "function"] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /benchmarks/lib/bleacher.js: -------------------------------------------------------------------------------- 1 | (function () 2 | { 3 | 'use strict'; 4 | 5 | const benchmarks = []; 6 | const rgxTemplate = /\{\{([^}]*)\}\}/g; 7 | let suites = null; 8 | 9 | const bleacherTemplate = ` 10 |

{{header}}

11 |

${navigator.userAgent}

12 |

13 |
    14 | `; 15 | 16 | const benchTemplate = ` 17 |
  1. 18 | 19 | {{name}} 20 | 21 | 22 | (Run Suite) 23 | 24 |
      {{testsHtml}}
    25 |
  2. 26 | `; 27 | 28 | const testTemplate = ` 29 |
  3. 30 | {{name}}: 31 | Not run 32 | 33 |
  4. 34 | `; 35 | 36 | window.Bleacher = { 37 | init(id, header) 38 | { 39 | const elm = document.getElementById(id || 'bleacher'); 40 | 41 | if (!elm) throw new Error('Unable to find bleacher element'); 42 | 43 | elm.innerHTML = compile(bleacherTemplate, { header }); 44 | 45 | suites = document.getElementById('bleacher-suites'); 46 | }, 47 | add(obj) 48 | { 49 | obj.index = benchmarks.length; 50 | obj.testsHtml = ''; 51 | 52 | for (let i = 0; i < obj.tests.length; ++i) 53 | { 54 | const test = obj.tests[i]; 55 | 56 | test.index = obj.index; 57 | test.testIndex = i; 58 | obj.testsHtml += compile(testTemplate, test); 59 | } 60 | 61 | benchmarks.push(obj); 62 | suites.innerHTML += compile(benchTemplate, obj); 63 | }, 64 | expand(index) 65 | { 66 | const elm = document.getElementById(`bleacher-${index}-tests`); 67 | 68 | elm.classList.toggle('bleacher-collapsed'); 69 | }, 70 | run(index) 71 | { 72 | const bench = benchmarks[index]; 73 | 74 | if (!bench) 75 | { 76 | console.error('No benchmark found for index %d', index); 77 | 78 | return; 79 | } 80 | 81 | const suite = new Benchmark.Suite(); 82 | 83 | for (let i = 0; i < bench.tests.length; ++i) 84 | { 85 | suite.add(bench.tests[i]); 86 | } 87 | 88 | let testIndex = 0; 89 | 90 | suite 91 | .on('start', function onStart(event) 92 | { 93 | setTestStatus(index, testIndex, 'run'); 94 | 95 | for (let i = 1; i < bench.tests.length; ++i) 96 | { 97 | setTestStatus(index, i, 'wait'); 98 | } 99 | }) 100 | .on('cycle', function onCycle(event) 101 | { 102 | event.target._testIndex = testIndex; 103 | setTestStatus(index, testIndex, 'done', event.target); 104 | 105 | testIndex++; 106 | 107 | setTestStatus(index, testIndex, 'run'); 108 | }) 109 | .on('complete', function onComplete(event) 110 | { 111 | event.currentTarget.filter('fastest').forEach((b) => 112 | { 113 | const elm = document.getElementById(`bleacher-${index}-test-${b._testIndex}`); 114 | 115 | if (elm) 116 | { 117 | elm.classList.remove('fast', 'slow', 'neutral'); 118 | elm.classList.add('fast'); 119 | } 120 | }); 121 | 122 | event.currentTarget.filter('slowest').forEach((b) => 123 | { 124 | const elm = document.getElementById(`bleacher-${index}-test-${b._testIndex}`); 125 | 126 | if (elm) 127 | { 128 | elm.classList.remove('fast', 'slow', 'neutral'); 129 | elm.classList.add('slow'); 130 | } 131 | }); 132 | }) 133 | .run({ async: true }); 134 | }, 135 | }; 136 | 137 | function setTestStatus(index, testIndex, status, bench) 138 | { 139 | const li = document.getElementById(`bleacher-${index}-test-${testIndex}`); 140 | const msg = document.getElementById(`bleacher-${index}-test-${testIndex}-msg`); 141 | const res = document.getElementById(`bleacher-${index}-test-${testIndex}-result`); 142 | 143 | if (!li || !msg || !res) return; 144 | 145 | li.classList.remove('wait', 'run', 'done'); 146 | li.classList.add(status); 147 | 148 | switch (status) 149 | { 150 | case 'wait': 151 | msg.textContent = 'Waiting...'; 152 | res.textContent = ''; 153 | break; 154 | 155 | case 'run': 156 | msg.textContent = 'Running...'; 157 | res.textContent = ''; 158 | break; 159 | 160 | case 'done': 161 | msg.textContent = 'Done.'; 162 | res.textContent = bench.error ? String(bench.error) : String(bench); 163 | break; 164 | } 165 | } 166 | 167 | function compile(template, context) 168 | { 169 | return template.replace(rgxTemplate, (match, p1) => 170 | { 171 | const keys = (p1 || '').split('.'); 172 | 173 | if (!keys || !keys.length) return ''; 174 | 175 | let value = context; 176 | 177 | for (let i = 0; i < keys.length; ++i) 178 | { 179 | if (typeof value !== 'object') return ''; 180 | 181 | value = value[keys[i]]; 182 | } 183 | 184 | if (typeof value === 'undefined') return ''; 185 | 186 | return value; 187 | }); 188 | } 189 | })(); 190 | -------------------------------------------------------------------------------- /benchmarks/lib/bleacher.less: -------------------------------------------------------------------------------- 1 | // Colors 2 | @color-primary: #20BF55; 3 | @color-secondary: #D62828; 4 | @color-accent-light: #1075A0; 5 | @color-accent-dark: #0B4F6C; 6 | @color-light: #F2F2F7; 7 | @color-lighter: #FBFBFF; 8 | @color-dark: #939393; 9 | @color-darker: #494949; 10 | 11 | @color-text: @color-lighter; 12 | 13 | .bleacher-header, .bleacher-banner, 14 | .bleacher-useragent, #bleacher-suites { 15 | margin: 0; 16 | padding: 0; 17 | 18 | // font-family: Calibri, Candara, Segoe, Segoe UI, Optima, Arial, sans-serif; 19 | font-family: Tahoma, Verdana, Segoe, sans-serif; 20 | // font-family: Verdana, Geneva, sans-serif; 21 | color: @color-text; 22 | } 23 | 24 | #bleacher-toolbar, .bleacher-useragent, 25 | #bleacher-suites li { 26 | font-size: small; 27 | } 28 | 29 | .bleacher-header { 30 | padding: 0.5em 0 0 1em; 31 | background-color: @color-accent-dark; 32 | font-size: 1.5em; 33 | line-height: 1em; 34 | font-weight: 400; 35 | border-radius: 5px 5px 0 0; 36 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 37 | } 38 | 39 | .bleacher-banner { 40 | background-color: @color-primary; 41 | height: 5px; 42 | } 43 | 44 | 45 | .bleacher-useragent { 46 | padding: 0.5em 1em; 47 | background-color: @color-accent-dark; 48 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 49 | } 50 | 51 | #bleacher-suites { 52 | list-style-position: inside; 53 | 54 | &> li:last-child { 55 | border-radius: 0 0 5px 5px; 56 | } 57 | 58 | .suite { 59 | display: list-item; 60 | list-style-position: inside; 61 | padding: 0.4em 1em; 62 | border-bottom: 1px solid @color-accent-dark; 63 | 64 | background-color: @color-accent-light; 65 | 66 | strong { 67 | cursor: pointer; 68 | } 69 | 70 | a 71 | { 72 | color: @color-primary; 73 | } 74 | 75 | .counts { 76 | color: @color-text; 77 | } 78 | 79 | .tests { 80 | margin-top: 0.5em; 81 | padding: 0.5em; 82 | background-color: @color-light; 83 | border-radius: 5px; 84 | 85 | li { 86 | color: @color-darker; 87 | display: list-item; 88 | padding: 5px; 89 | background-color: @color-light; 90 | border-bottom: none; 91 | list-style-position: inside; 92 | 93 | &.fast { 94 | color: darken(@color-primary, 25%); 95 | border-left: 10px solid @color-primary; 96 | } 97 | 98 | &.slow { 99 | color: darken(@color-secondary, 25%); 100 | border-left: 10px solid @color-secondary; 101 | } 102 | } 103 | } 104 | } 105 | 106 | .bleacher-collapsed { 107 | display: none; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /benchmarks/pixi/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fae Benchmark Suite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /benchmarks/pixi/transform.bench.js: -------------------------------------------------------------------------------- 1 | (function () 2 | { 3 | 'use strict'; 4 | 5 | Bleacher.add({ 6 | name: 'Transform', 7 | tests: [ 8 | { 9 | name: 'fae', 10 | fn() 11 | { 12 | lt.update(pt); 13 | }, 14 | setup() 15 | { 16 | const pt = new Fae.transform.Transform(); 17 | const lt = new Fae.transform.Transform(); 18 | }, 19 | }, 20 | { 21 | name: 'pixi', 22 | fn() 23 | { 24 | lt.updateTransform(pt); 25 | }, 26 | setup() 27 | { 28 | const pt = new PIXI.TransformStatic(); 29 | const lt = new PIXI.TransformStatic(); 30 | }, 31 | }, 32 | ], 33 | }); 34 | })(); 35 | -------------------------------------------------------------------------------- /benchmarks/pixi/update.bench.js: -------------------------------------------------------------------------------- 1 | (function () 2 | { 3 | 'use strict'; 4 | 5 | Bleacher.add({ 6 | name: 'Update', 7 | tests: [ 8 | { 9 | name: 'fae', 10 | fn() 11 | { 12 | stage.update(); 13 | }, 14 | setup() 15 | { 16 | const stage = new Fae.scene.Container(); 17 | const falseParent = new Fae.scene.Container(); 18 | 19 | falseParent.addChild(stage); 20 | 21 | for (let i = 0; i < 100; ++i) 22 | { 23 | stage.addChild(new Fae.scene.Container()); 24 | } 25 | }, 26 | }, 27 | { 28 | name: 'pixi', 29 | fn() 30 | { 31 | stage.updateTransform(); 32 | }, 33 | setup() 34 | { 35 | const stage = new PIXI.Container(); 36 | const falseParent = new PIXI.Container(); 37 | 38 | falseParent.addChild(stage); 39 | 40 | for (let i = 0; i < 100; ++i) 41 | { 42 | stage.addChild(new PIXI.Container()); 43 | } 44 | }, 45 | }, 46 | ], 47 | }); 48 | })(); 49 | -------------------------------------------------------------------------------- /benchmarks/self/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Fae Benchmark Suite 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /build/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.eslintrc.json", 3 | "env": { 4 | "node": true, 5 | "browser": false 6 | }, 7 | "globals": { 8 | }, 9 | "parserOptions": { 10 | "sourceType": "script" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /build/EntryGeneratorPlugin.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const async = require('async'); 6 | const buildName = require('./build-name'); 7 | 8 | const SingleEntryDependency = require('webpack/lib/dependencies/SingleEntryDependency'); 9 | 10 | /** 11 | * @class 12 | */ 13 | class EntryGeneratorWebpackPlugin 14 | { 15 | /** 16 | * @param {string[]} plugins - The plugins to include in the build. 17 | */ 18 | constructor(plugins) 19 | { 20 | plugins = plugins || (process.env.FAE_PLUGINS ? process.env.FAE_PLUGINS.split(',') : []); 21 | 22 | this.outputFile = `entry-${buildName(plugins)}.js`; 23 | this.plugins = plugins; 24 | } 25 | 26 | /** 27 | * Called by webpack to apply the plugin. 28 | * 29 | * @param {object} compiler - The webpack compiler object. 30 | */ 31 | apply(compiler) 32 | { 33 | const onRunhandler = (a, b) => this.onRun(a, b); 34 | 35 | compiler.plugin('run', onRunhandler); 36 | compiler.plugin('watch-run', onRunhandler); 37 | 38 | compiler.plugin('compilation', (c, p) => this.onCompilation(c, p)); 39 | compiler.plugin('make', (c, f) => this.onMake(c, f)); 40 | } 41 | 42 | /** 43 | * Called by webpack when compilation object is created. 44 | * 45 | * @param {object} compilation - The webpack compilation object. 46 | * @param {object} params - Params to the compilation creation? 47 | */ 48 | onCompilation(compilation, params) 49 | { 50 | const normalModuleFactory = params.normalModuleFactory; 51 | 52 | compilation.dependencyFactories.set(SingleEntryDependency, normalModuleFactory); 53 | } 54 | 55 | /** 56 | * Called by webpack when compilation begins the make process. 57 | * 58 | * @param {object} compilation - The webpack compilation object. 59 | * @param {function} done - Callback to call when complete. 60 | */ 61 | onMake(compilation, done) 62 | { 63 | const dep = new SingleEntryDependency(this.outputFile); 64 | 65 | dep.loc = 'main'; 66 | 67 | compilation.addEntry(compilation.compiler.context, dep, 'main', done); 68 | } 69 | 70 | /** 71 | * Called by webpack when starting to run the compiler. 72 | * 73 | * @param {object} compiler - The webpack compiler object. 74 | * @param {function} done - Callback to call when complete. 75 | */ 76 | onRun(compiler, done) 77 | { 78 | const basePath = compiler.context || process.cwd(); 79 | 80 | this.outputFile = path.resolve(basePath, this.outputFile); 81 | 82 | fs.access(this.outputFile, fs.R_OK | fs.W_OK, (err) => 83 | { 84 | if (err && err.code !== 'ENOENT') return done(err); 85 | 86 | // At this point it either exists, and we can write to it, or 87 | // it doesn't exist at all. Either way, we can continue. 88 | 89 | // set entry string with export for core. 90 | let str = 'export * from \'@fae/core\';\n\n'; 91 | 92 | this.loadPluginData((err, pluginData) => 93 | { 94 | if (err) return done(err); 95 | 96 | // add exports for each plugin 97 | for (let i = 0; i < pluginData.length; ++i) 98 | { 99 | const data = pluginData[i]; 100 | 101 | if (data.name === 'core') continue; 102 | 103 | const pkg = data.pkg; 104 | const namespace = pkg.fae && pkg.fae.namespace ? pkg.fae.namespace : pkg.name.replace('@fae/', '').replace('-', '_'); // eslint-disable-line max-len 105 | 106 | str += `import * as ${namespace} from '${data.pkg.name}';\n`; 107 | str += `export { ${namespace} };\n\n`; 108 | } 109 | 110 | // write exports and update entry 111 | fs.writeFile(this.outputFile, str, done); 112 | 113 | return null; 114 | }); 115 | 116 | return null; 117 | }); 118 | } 119 | 120 | /** 121 | * Loads the package data about plugins. 122 | * 123 | * @param {function} cb - Callback to call when complete. 124 | */ 125 | loadPluginData(cb) 126 | { 127 | // load all the descriptors for all the plugins 128 | async.map(this.plugins, (name, next) => 129 | { 130 | const pluginPath = path.join(__dirname, '..', 'plugins', name); 131 | 132 | fs.readFile(path.join(pluginPath, 'package.json'), (err, data) => 133 | { 134 | // TODO: Pull plugin from NPM instead of just failing. 135 | if (err) return next(err); 136 | 137 | try 138 | { 139 | return next(null, { 140 | name, 141 | pkg: JSON.parse(data), 142 | path: pluginPath, 143 | local: true, 144 | }); 145 | } 146 | catch (e) 147 | { 148 | return next(e); 149 | } 150 | }); 151 | }, cb); 152 | } 153 | } 154 | 155 | module.exports = EntryGeneratorWebpackPlugin; 156 | -------------------------------------------------------------------------------- /build/banner.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * Fae.js v{{version}} 3 | * 4 | * {{compileDate}} - {{commitHash}} 5 | * 6 | * {{homepage}} 7 | * 8 | * Released under the {{license}} License. 9 | * 10 | */ 11 | -------------------------------------------------------------------------------- /build/build-name.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const crypto = require('crypto'); 4 | 5 | module.exports = function buildName(plugins) 6 | { 7 | if (Array.isArray(plugins)) 8 | { 9 | plugins = plugins.join(','); 10 | } 11 | 12 | const hash = crypto.createHash('sha256'); 13 | 14 | hash.update(plugins); 15 | 16 | return hash.digest('hex'); 17 | }; 18 | -------------------------------------------------------------------------------- /build/jsdoc-fix.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // JSDoc has issues getting the name of `export default class NAME {}` 4 | // this gross regex hacks around that issue until it is fixed. 5 | // See: https://github.com/jsdoc3/jsdoc/issues/1137#issuecomment-174829004 6 | // and: https://github.com/jsdoc3/jsdoc/issues/1252 7 | 8 | const rgxGross = /(\/\*{2}[\W\w]+?\*\/)\s*export\s+default\s+class\s+([^\s]*)/g; 9 | const grossReplace = 'export default $2;\n\n$1\nclass $2'; 10 | 11 | exports.handlers = { 12 | /** 13 | * Called before parsing a file, giving us a change to replace the source. 14 | * 15 | * @param {*} e - The `beforeParse` event data. 16 | * @param {string} e.filename - The name of the file being parsed. 17 | * @param {string} e.source - The source of the file being parsed. 18 | */ 19 | beforeParse(e) 20 | { 21 | e.source = e.source.replace(rgxGross, grossReplace); 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /build/jsdoc.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": { 3 | "allowUnknownTags": false 4 | }, 5 | "source": { 6 | "include": [ 7 | "./plugins" 8 | ], 9 | "exclude": [ 10 | "./plugins/**/test/**" 11 | ], 12 | "includePattern": ".+\\.js(doc)?$", 13 | "excludePattern": "(^|\\/|\\\\)_" 14 | }, 15 | "plugins": [ 16 | "./node_modules/jsdoc/plugins/markdown", 17 | "./build/jsdoc-fix" 18 | ], 19 | "templates": { 20 | "cleverLinks": false, 21 | "monospaceLinks": false, 22 | "default": { 23 | "outputSourceFiles": true 24 | }, 25 | "applicationName": "Fae", 26 | "copyright": "Fae Copyright © 2016 Chad Engler.", 27 | "linenums": true 28 | }, 29 | "markdown": { 30 | "parser": "gfm", 31 | "hardwrap": true 32 | }, 33 | "opts": { 34 | "encoding": "utf8", 35 | "recurse": true, 36 | "private": true, 37 | "lenient": true, 38 | "destination": "./api-docs", 39 | "template": "./node_modules/jaguarjs-jsdoc" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /build/karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function conf(config) 4 | { 5 | config.set({ 6 | basePath: '../', 7 | frameworks: ['mocha', 'sinon-chai'], 8 | autoWatch: true, 9 | logLevel: config.LOG_INFO, 10 | logColors: true, 11 | reporters: ['mocha'], 12 | browsers: ['Chrome'], 13 | browserDisconnectTimeout: 10000, 14 | browserDisconnectTolerance: 2, 15 | browserNoActivityTimeout: 30000, 16 | 17 | sauceLabs: { 18 | testName: 'Fae', 19 | startConnect: true, 20 | }, 21 | 22 | files: [ 23 | // our code 24 | 'dist/fae.js', 25 | 26 | // fixtures 27 | { 28 | pattern: 'plugins/*/test/fixtures/**/*.js', 29 | included: true, 30 | }, 31 | 32 | // tests 33 | { 34 | pattern: `plugins/*/test/**/*.test.js`, 35 | served: true, 36 | included: true, 37 | watched: true, 38 | }, 39 | ], 40 | 41 | plugins: [ 42 | 'karma-mocha', 43 | 'karma-sinon-chai', 44 | 'karma-mocha-reporter', 45 | 'karma-chrome-launcher', 46 | 'karma-firefox-launcher', 47 | 'karma-sauce-launcher', 48 | ], 49 | 50 | customLaunchers: { 51 | /* eslint-disable camelcase */ 52 | SL_Chrome: { 53 | base: 'SauceLabs', 54 | browserName: 'chrome', 55 | version: '35', 56 | }, 57 | SL_Firefox: { 58 | base: 'SauceLabs', 59 | browserName: 'firefox', 60 | version: '30', 61 | }, 62 | SL_Safari_7: { 63 | base: 'SauceLabs', 64 | browserName: 'safari', 65 | platform: 'OS X 10.9', 66 | version: '7.1', 67 | }, 68 | SL_Safari_8: { 69 | base: 'SauceLabs', 70 | browserName: 'safari', 71 | platform: 'OS X 10.10', 72 | version: '8', 73 | }, 74 | SL_Safari_9: { 75 | base: 'SauceLabs', 76 | browserName: 'safari', 77 | platform: 'OS X 10.11', 78 | version: '9', 79 | }, 80 | SL_IE_11: { 81 | base: 'SauceLabs', 82 | browserName: 'internet explorer', 83 | platform: 'Windows 10', 84 | version: '11', 85 | }, 86 | SL_Edge: { 87 | base: 'SauceLabs', 88 | browserName: 'edge', 89 | platform: 'Windows 10', 90 | version: '13', 91 | }, 92 | SL_iOS: { 93 | base: 'SauceLabs', 94 | browserName: 'iphone', 95 | platform: 'OS X 10.10', 96 | version: '8.1', 97 | }, 98 | /* eslint-enable camelcase */ 99 | }, 100 | }); 101 | 102 | if (process.env.TRAVIS) 103 | { 104 | config.logLevel = config.LOG_DEBUG; 105 | 106 | if (process.env.TRAVIS_PULL_REQUEST) 107 | { 108 | config.browsers = ['Firefox']; 109 | } 110 | else 111 | { 112 | config.reporters.push('saucelabs'); 113 | config.browsers = [ 114 | 'SL_Chrome', 115 | 'SL_Firefox', 116 | 'SL_IE_11', 117 | // 'SL_Safari_7', 118 | // 'SL_Safari_8', 119 | // 'SL_Safari_9', // Safari doesn't like Fae right now 120 | // 'SL_Edge', // Edge seems to be having issues on saucelabs right now 121 | // 'SL_iOS', // iOS doesn't like Fae right now 122 | ]; 123 | 124 | // Karma (with socket.io 1.x) buffers by 50 and 50 tests can take a long time on IEs;-) 125 | config.browserNoActivityTimeout = 120000; 126 | 127 | // config.browserStack.build = buildLabel; 128 | // config.browserStack.startTunnel = false; 129 | // config.browserStack.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER; 130 | 131 | config.sauceLabs.build = `TRAVIS #${process.env.TRAVIS_BUILD_NUMBER} (${process.env.TRAVIS_BUILD_ID})`; 132 | config.sauceLabs.startConnect = false; 133 | config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER; 134 | config.sauceLabs.recordScreenshots = true; 135 | 136 | // Allocating a browser can take pretty long (eg. if we are out of capacity and need to wait 137 | // for another build to finish) and so the `captureTimeout` typically kills 138 | // an in-queue-pending request, which makes no sense. 139 | config.captureTimeout = 0; 140 | } 141 | } 142 | }; 143 | -------------------------------------------------------------------------------- /build/webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const webpack = require('webpack'); 6 | const pkg = require('../package.json'); 7 | 8 | const EntryGeneratorPlugin = require('./EntryGeneratorPlugin'); 9 | 10 | const pluginbase = path.join(__dirname, '..', 'plugins'); 11 | const pluginList = process.env.FAE_PLUGINS ? process.env.FAE_PLUGINS.split(',') : fs.readdirSync(pluginbase); 12 | 13 | const config = { pkg }; 14 | 15 | // set debug if not in prod build mode 16 | if (process.env.NODE_ENV !== 'production') 17 | { 18 | config.DEBUG = true; 19 | } 20 | 21 | // core is always required 22 | if (pluginList.indexOf('core') === -1) 23 | { 24 | pluginList.push('core'); 25 | } 26 | 27 | // configure aliases for plugins 28 | const aliases = {}; 29 | 30 | for (let i = 0; i < pluginList.length; ++i) 31 | { 32 | // TODO: install a plugin if it doesn't exist in plugins folder (3rd-party plugin) 33 | aliases[`@fae/${pluginList[i]}`] = path.join(pluginbase, pluginList[i]); 34 | } 35 | 36 | // main config 37 | module.exports = { 38 | // entry: path.join(__dirname, '..', 'src', 'index.js'), 39 | output: { 40 | path: path.join(__dirname, '..', 'dist'), 41 | library: 'Fae', 42 | libraryTarget: 'umd', 43 | umdNamedDefine: true, 44 | }, 45 | module: { 46 | loaders: [ 47 | { 48 | test: /\.js$/, 49 | exclude: /node_modules/, 50 | loaders: [ 51 | 'babel?cacheDirectory=true&presets[]=es2015-loose', 52 | `preprocess?${JSON.stringify(config)}`, 53 | ], 54 | }, 55 | { 56 | test: /\.(glsl|frag|vert)$/, 57 | exclude: /node_modules/, 58 | loaders: ['raw', 'glslify'], 59 | }, 60 | ], 61 | }, 62 | resolve: { 63 | alias: aliases, 64 | }, 65 | plugins: [ 66 | // generate entry file 67 | new EntryGeneratorPlugin(pluginList), 68 | 69 | // don't emit output when there are errors 70 | new webpack.NoErrorsPlugin(), 71 | 72 | // Add a banner to output chunks 73 | new webpack.BannerPlugin(loadBannerText(), { raw: true, entry: true }), 74 | ], 75 | }; 76 | 77 | function loadBannerText() 78 | { 79 | let str = fs.readFileSync(path.join(__dirname, 'banner.txt'), 'utf8'); 80 | 81 | str = str.replace('{{version}}', pkg.version); 82 | str = str.replace('{{compileDate}}', (new Date()).toISOString()); 83 | str = str.replace('{{commitHash}}', fs.readFileSync('./.commit', 'utf8').trim()); 84 | str = str.replace('{{homepage}}', pkg.homepage); 85 | str = str.replace('{{license}}', pkg.license); 86 | 87 | return str; 88 | } 89 | -------------------------------------------------------------------------------- /docs/BuildingPlugins.md: -------------------------------------------------------------------------------- 1 | 2 | Coming Soon! 3 | -------------------------------------------------------------------------------- /docs/ComposingEntities.md: -------------------------------------------------------------------------------- 1 | 2 | Coming Soon! 3 | -------------------------------------------------------------------------------- /docs/CustomComponents.md: -------------------------------------------------------------------------------- 1 | 2 | Coming Soon! 3 | -------------------------------------------------------------------------------- /docs/CustomSystems.md: -------------------------------------------------------------------------------- 1 | 2 | Coming Soon! 3 | -------------------------------------------------------------------------------- /docs/GettingStarted.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | In this getting started guide we will be building a small application that will 4 | have a rocket that will fly around when we click it, and crash when out of fuel. 5 | 6 | WIP: Coming Soon... 7 | 8 | ## Contents 9 | 10 | - [Fae Basics](#basics) 11 | * [ECS Primer](#basics-ecs) 12 | * [Plugins](#basics-plugins) 13 | - [Setting Up](#setup) 14 | * [Download Dependencies](#setup-deps) 15 | * [Download Resources](#setup-res) 16 | * [Create Files](#setup-files) 17 | - [Rendering Entities](#render) 18 | * [Creating a Renderer](#render-create) 19 | * [Adding Entities](#render-add) 20 | * [Sorting and Grouping](#render-sort) 21 | - [Input & UI](#ui) 22 | * [Add a Fuel Bar](#ui-fuel) 23 | * [Handle Keydown](#ui-key) 24 | * [Fly the Ship](#ui-fly) 25 | - [Juice!](#juice) 26 | * [Add Flames](#juice-flames) 27 | * [Add Sparks](#juice-sparks) 28 | * [Add Screen Shake](#juice-shake) 29 | 30 | 31 | ## Fae Basics 32 | 33 | 34 | ### ECS Primer 35 | 36 | 37 | ### Plugins 38 | 39 | 40 | ## Setting Up 41 | 42 | 43 | ### Download Dependencies 44 | 45 | 46 | ### Download Resources 47 | 48 | 49 | ### Create Files 50 | 51 | 52 | ## Rendering Entities 53 | 54 | 55 | ### Creating a Renderer 56 | 57 | 58 | ### Adding Entities 59 | 60 | 61 | ### Sorting and Grouping 62 | 63 | 64 | ## Input & UI 65 | 66 | 67 | ### Add a Fuel Bar 68 | 69 | 70 | ### Handle Keydown 71 | 72 | 73 | ### Fly the Ship 74 | 75 | 76 | ## Juice! 77 | 78 | 79 | ### Add Flames 80 | 81 | 82 | ### Add Sparks 83 | 84 | 85 | ### Add Screen Shake 86 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Fae - A 2D JavaScript Engine 2 | 3 | ## Contents 4 | 5 | - [Overview](#overview) 6 | - [Philosophy](#philosophy) 7 | - [Getting Started](GettingStarted.md) 8 | - [Building Plugins](BuildingPlugins.md) 9 | * [Composing Entities](ComposingEntities.md) 10 | * [Custom Components](CustomComponents.md) 11 | * [Custom Systems](CustomSystems.md) 12 | 13 | 14 | 15 | ## Overview 16 | 17 | Welcome to the Fae ecosystem. Fae might be a bit different than what most people are used to 18 | in other libraries and frameworks, but the hope is to provide a solid foundation to build 19 | you applications and frameworks upon. 20 | 21 | At its core Fae is a collection of components, systems, and entities that empower users to 22 | build amazing 2D applications. Fae isn't just a library, or a framework, but instead an ecosystem 23 | of small modules that together can do almost anything. 24 | 25 | While different, if you give Fae's way of doing things a try I promise you will come to appreciate 26 | the elegance of the system. 27 | 28 | 29 | ## Philosophy 30 | 31 | The *Mission Statement* of Fae is: 32 | 33 | > Fae sets out to create a powerful ecosystem of *Modular*, *Composable*, and *Reusable* 34 | > packages that work together to power amazing 2D applications. 35 | 36 | Like it says in the *Mission Statement*, the basic philosophy of Fae is to build a collection 37 | *Modular*, *Composable*, *Reusable* parts. Each of these descriptors is considered a 38 | *Core Value* of the Fae ecosystem, and is explored in more detail below. When developing for 39 | Fae contributors and plugin authors should keep this section in mind. 40 | 41 | ### Modularity 42 | 43 | Fae takes the concept of [modularity][modularity] very seriously. By modularizing as much as 44 | is reasonable in order to provide the choice to the users of what they want in their builds. 45 | A good guide is the [Unix Philosophy][unix-phil] which emphasizes building "simple, short, 46 | clear, modular, and extensible code that can be easily maintained and repurposed by 47 | developers other than its creators." 48 | 49 | Most frameworks achieve something like this by allowing you to exclude or include parts of the 50 | library, which helps with file-size. Other ecosystems like Fae and jQuery UI take it a step 51 | further. Not only is file-size reduced but not a single CPU cycle is spent on the excluded 52 | feature. Not even to check if the feature is active. Everyone who uses Fae may have a slightly 53 | different build, and that is OK. 54 | 55 | ### Composability 56 | 57 | While modularity alone can be good, it can also introduce some challenges. One specific 58 | challenges that presents itself is that once you have a bunch of different small packages, how 59 | do you use them together in a meaningful way? 60 | 61 | Fae approaches this problem using a similar pattern to the [Entity Component System][ecs] 62 | pattern, with a couple of minor differences. I wrote a detailed article about Fae's flavor 63 | of ECS [on my blog][ecs-diff], if you are interested. Fae's flavor of ECS defines the terms 64 | as follows: 65 | 66 | - `Entites` are created from chains of inherited components. 67 | - `Components` are "subclass factories," functions that return a class inheriting from 68 | the parameter. 69 | - `Systems` are classes that perform actions on entities and their components. 70 | 71 | #### Components as Mixins 72 | 73 | Those familiar with ECS will probably be wondering why in our flavor of ECS are components 74 | mixins instead of data containers independent of the entity. For full details about why 75 | you can read [my blog article][ecs-diff] on the subject. The short version though is 76 | that people want to use an OOP interface when using libraries like this. So by implementing 77 | ECS with Entities as Assemblages only and Components as Mixins I feel that we get the best 78 | of both worlds. We get the ease of use of classes (for the end user) with the composability 79 | and modularization of ECS (for library developers). 80 | 81 | ### Reusability 82 | 83 | If you have read the description of Fae's flavor of ECS and read the 2 *Core Value* sections 84 | above, you may have an idea where this is going. Since we strive to build small plugins 85 | that do one thing and do it well, it is pretty likely that code in that plugin is applicable 86 | elsewhere. For example, components can be reused over and over (with their associated systems) 87 | in new Entities as much as is imaginable. You don't have to extend the `Sprite` class to 88 | have the `SpriteRenderer` render your texture. You just need the right components and system! 89 | 90 | 91 | 92 | [modularity]: https://en.wikipedia.org/wiki/Modularity 93 | [unix-phil]: https://en.wikipedia.org/wiki/Unix_philosophy 94 | [ecs]: https://en.wikipedia.org/wiki/Entity_component_system 95 | [ecs-diff]: https://englercj.github.io/2016/08/24/composition-ecs/ 96 | -------------------------------------------------------------------------------- /examples/basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/basic/spade_A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fae/fae/ecfa5a3c382cf47ad846dd6f8a6125295861ee5d/examples/basic/spade_A.png -------------------------------------------------------------------------------- /examples/batching/imgs/diamond_A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fae/fae/ecfa5a3c382cf47ad846dd6f8a6125295861ee5d/examples/batching/imgs/diamond_A.png -------------------------------------------------------------------------------- /examples/batching/imgs/diamond_J.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fae/fae/ecfa5a3c382cf47ad846dd6f8a6125295861ee5d/examples/batching/imgs/diamond_J.png -------------------------------------------------------------------------------- /examples/batching/imgs/diamond_K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fae/fae/ecfa5a3c382cf47ad846dd6f8a6125295861ee5d/examples/batching/imgs/diamond_K.png -------------------------------------------------------------------------------- /examples/batching/imgs/diamond_Q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fae/fae/ecfa5a3c382cf47ad846dd6f8a6125295861ee5d/examples/batching/imgs/diamond_Q.png -------------------------------------------------------------------------------- /examples/batching/imgs/spade_A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fae/fae/ecfa5a3c382cf47ad846dd6f8a6125295861ee5d/examples/batching/imgs/spade_A.png -------------------------------------------------------------------------------- /examples/batching/imgs/spade_J.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fae/fae/ecfa5a3c382cf47ad846dd6f8a6125295861ee5d/examples/batching/imgs/spade_J.png -------------------------------------------------------------------------------- /examples/batching/imgs/spade_K.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fae/fae/ecfa5a3c382cf47ad846dd6f8a6125295861ee5d/examples/batching/imgs/spade_K.png -------------------------------------------------------------------------------- /examples/batching/imgs/spade_Q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fae/fae/ecfa5a3c382cf47ad846dd6f8a6125295861ee5d/examples/batching/imgs/spade_Q.png -------------------------------------------------------------------------------- /examples/filters/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/filters/spade_A.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fae/fae/ecfa5a3c382cf47ad846dd6f8a6125295861ee5d/examples/filters/spade_A.png -------------------------------------------------------------------------------- /examples/interaction/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fae", 3 | "version": "0.0.1", 4 | "description": "A next-gen 2D renderer for the web.", 5 | "author": "Chad Engler ", 6 | "license": "MIT", 7 | "main": "./dist/fae.js", 8 | "homepage": "https://github.com/Fae/fae#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Fae/fae.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/Fae/fae/issues" 15 | }, 16 | "scripts": { 17 | "clean": "rm -rf dist/ entry-*", 18 | "hash": "git rev-parse HEAD > .commit", 19 | "build": "set NODE_ENV=production && npm run hash && webpack -p --config ./build/webpack.config.js --output-filename fae.min.js --progress", 20 | "dev": "npm run hash && webpack -d --config ./build/webpack.config.js --output-filename fae.js --progress --colors", 21 | "watch": "npm run dev -- --watch", 22 | "lint": "eslint plugins/ build/", 23 | "start": "npm run build", 24 | "test": "npm run lint && npm run dev && npm run test-dev -- --single-run", 25 | "test-ci": "npm run test-dev -- --single-run", 26 | "test-dev": "karma start build/karma.conf.js", 27 | "docs": "jsdoc -c build/jsdoc.conf.json -R README.md" 28 | }, 29 | "dependencies": { 30 | "@fae/ecs": "^1.0.2", 31 | "bit-twiddle": "^1.0.2", 32 | "core-js": "^2.4.1", 33 | "ismobilejs": "^0.4.0", 34 | "mini-signals": "^1.1.1" 35 | }, 36 | "devDependencies": { 37 | "@englercj/code-style": "^1.0.6", 38 | "async": "^2.0.1", 39 | "babel-core": "^6.13.2", 40 | "babel-loader": "^6.2.5", 41 | "babel-preset-es2015": "^6.13.2", 42 | "babel-preset-es2015-loose": "^7.0.0", 43 | "benchmark": "^2.1.1", 44 | "chai": "^3.5.0", 45 | "eslint": "^3.3.1", 46 | "glslify-loader": "^1.0.2", 47 | "jaguarjs-jsdoc": "^1.0.0", 48 | "jsdoc": "git+https://github.com/jsdoc3/jsdoc.git#master", 49 | "karma": "^1.2.0", 50 | "karma-chrome-launcher": "^2.0.0", 51 | "karma-firefox-launcher": "^1.0.0", 52 | "karma-mocha": "^1.1.1", 53 | "karma-mocha-reporter": "^2.1.0", 54 | "karma-sauce-launcher": "^1.0.0", 55 | "karma-sinon-chai": "^1.2.3", 56 | "mocha": "^3.0.2", 57 | "preprocess-loader": "^0.2.0", 58 | "raw-loader": "^0.5.1", 59 | "sinon": "^1.17.5", 60 | "sinon-chai": "^2.8.0", 61 | "webpack": "^1.13.2" 62 | }, 63 | "private": true 64 | } 65 | -------------------------------------------------------------------------------- /plugins/core/README.md: -------------------------------------------------------------------------------- 1 | # Core 2 | 3 | The core plugin contains a multitude of utilities useful throughout the entire Fae ecosystem. 4 | It exports the core `ecs` utilities, WebGL wrappers, Matrix/Vector math classes, the `Renderer` 5 | and a few other useful items. 6 | 7 | ## Usage 8 | 9 | Mostly the `core` plugin is just a set of utilities useful when building your own plugins. 10 | The few classes you may interact with directly as an end-user will likely be: 11 | 12 | - `render.Renderer` - The main renderer and system/entity manager for Fae 13 | - `ecs.Entity` - The base Entity class all entities in the Renderer extend 14 | - `ecs.System` - The base System class that all systems in the Renderer extend 15 | 16 | For example: 17 | 18 | ```js 19 | class MyEntity extends Fae.ecs.Entity.with(Fae.ecs.SelfRenderComponent) 20 | { 21 | render() 22 | { 23 | // draw! 24 | } 25 | } 26 | 27 | const renderer = new Fae.render.Renderer(document.getElementById('my-canvas')); 28 | 29 | renderer.addEntity(new MyEntity()); 30 | 31 | (function animate() { 32 | requestAnimationFrame(animate); 33 | renderer.render(); 34 | })(); 35 | ``` 36 | 37 | If you have specific options you want to pass into the context creation, you could pass a 38 | WebGLRenderingContext directly instead of a canvas. The core plugin exports a utility to 39 | help with context creation, you can use it like this: 40 | 41 | ```js 42 | const ctx = Fae.glutil.GLContext.create(document.getElementById('my-canvas'), { antialias: false }); 43 | const renderer = new Fae.render.Renderer(ctx); 44 | ``` 45 | -------------------------------------------------------------------------------- /plugins/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fae/core", 3 | "version": "0.0.1", 4 | "description": "Core GL Rendering utilities.", 5 | "author": "Chad Engler ", 6 | "license": "MIT", 7 | "main": "src/index.js", 8 | "homepage": "https://github.com/Fae/fae#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Fae/fae.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/Fae/fae/issues" 15 | }, 16 | "files": [ 17 | "src/" 18 | ], 19 | "dependencies": { 20 | "@fae/ecs": "^1.0.2", 21 | "@fae/shapes": "^1.0.0", 22 | "ismobilejs": "^0.4.0", 23 | "mini-signals": "^1.1.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /plugins/core/src/debug.js: -------------------------------------------------------------------------------- 1 | // @ifdef DEBUG 2 | /** 3 | * Note that the debug namespace is only exported in "debug" builds. 4 | * In production builds, this namespace is not included. 5 | * 6 | * @namespace debug 7 | */ 8 | 9 | /** 10 | * @memberof debug 11 | * @param {boolean} bool - The condition to ensure is true. 12 | * @param {string} message - The message to display if the first param is not true. 13 | */ 14 | export function ASSERT(bool, message) 15 | { 16 | if (!bool) throw new Error(`[Fae ASSERT] ${message}`); 17 | } 18 | // @endif 19 | -------------------------------------------------------------------------------- /plugins/core/src/ecs/Entity.js: -------------------------------------------------------------------------------- 1 | import ECS from '@fae/ecs'; 2 | 3 | /** 4 | * @class 5 | * @memberof ecs 6 | */ 7 | export default class Entity extends ECS.Entity { 8 | /** 9 | * 10 | */ 11 | constructor() 12 | { 13 | super(); 14 | 15 | /** 16 | * The render priority of this entity. A lower number will make it render 17 | * first. Think of this like a "z-index" the higher number will render on top. 18 | * 19 | * If you change this value at all after adding the entity to the renderer you will 20 | * need to call {@link Renderer#sortEntities} for the change to affect the sort. 21 | * 22 | * @member {number} 23 | * @default 0 24 | */ 25 | this.renderPriority = 0; 26 | 27 | /** 28 | * An value that hints to the renderer how to group this entity to try and 29 | * improve batching. If this is set the renderer can group entities with the 30 | * same `priority` and `renderGroupHint` together, which may improve batching. 31 | * 32 | * While you can assign this value to anything you want, a good idea is to set 33 | * this to the ObjectRenderer class you know that entity will use. That way that 34 | * renderer can operate on those objects in sequence and potentially batch them. 35 | * If you don't know what to assign this to, leaving it as `null` is fine. 36 | * 37 | * If you change this value at all after adding the entity to the renderer you will 38 | * need to call {@link Renderer#sortEntities} for the change to affect the sort. 39 | * 40 | * @example 41 | * 42 | * ```js 43 | * import { ecs } from '@fae/core'; 44 | * import { SpriteRenderer } from '@fae/sprites'; 45 | * 46 | * class MySprite extends ecs.Entity.with(...) 47 | * { 48 | * constructor() 49 | * { 50 | * super(); 51 | * 52 | * // When added to the renderer all `MySprite` instances with the same 53 | * // priority will now be grouped together, improving the SpriteRenderer's 54 | * // ability to batch them! 55 | * this.renderGroupHint = SpriteRenderer; 56 | * } 57 | * } 58 | * ``` 59 | * 60 | * @member {*} 61 | * @default null 62 | */ 63 | this.renderGroupHint = null; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /plugins/core/src/ecs/SelfRenderComponent.js: -------------------------------------------------------------------------------- 1 | export default function SelfRenderComponent(Base) 2 | { 3 | /** 4 | * @class SelfRenderComponent 5 | * @memberof ecs 6 | */ 7 | return class extends Base 8 | { 9 | /** 10 | * The method called for the object to render itself. 11 | * 12 | * @abstract 13 | * @param {Renderer} renderer - The renderer to use. 14 | */ 15 | render(/* renderer */) 16 | { 17 | /* empty */ 18 | } 19 | }; 20 | } 21 | -------------------------------------------------------------------------------- /plugins/core/src/ecs/SelfRenderSystem.js: -------------------------------------------------------------------------------- 1 | import System from './System'; 2 | import SelfRenderComponent from './SelfRenderComponent'; 3 | import Renderer from '../render/Renderer'; 4 | 5 | /** 6 | * @class 7 | * @memberof ecs 8 | */ 9 | export default class SelfRenderSystem extends System 10 | { 11 | /** 12 | * @param {Renderer} renderer - The renderer to use. 13 | * @param {number} priority - The priority of the system, higher means earlier. 14 | * @param {number} frequency - How often to run the update loop. `1` means every 15 | * time, `2` is every other time, etc. 16 | */ 17 | constructor(renderer, priority = SelfRenderSystem.defaultPriority, frequency = 1) 18 | { 19 | super(renderer, priority, frequency); 20 | } 21 | 22 | /** 23 | * Returns true if the entity is eligible to the system, false otherwise. 24 | * 25 | * @param {Entity} entity - The entity to test. 26 | * @return {boolean} True if entity should be included. 27 | */ 28 | test(entity) 29 | { 30 | return entity.hasComponent(SelfRenderComponent); 31 | } 32 | 33 | /** 34 | * Tells the entity to render itself by calling the `render()` method with 35 | * the renderer and elapsed time. 36 | * 37 | * @param {Entity} entity - The entity to update. 38 | * @param {number} elapsed - The time elapsed since last update call. 39 | */ 40 | update(entity, elapsed) 41 | { 42 | entity.render(this.renderer, elapsed); 43 | } 44 | } 45 | 46 | Renderer.addDefaultSystem(SelfRenderSystem); 47 | 48 | /** 49 | * @static 50 | * @constant 51 | * @member {number} 52 | * @default 1000 53 | */ 54 | SelfRenderSystem.defaultPriority = System.PRIORITY.RENDER; 55 | -------------------------------------------------------------------------------- /plugins/core/src/ecs/System.js: -------------------------------------------------------------------------------- 1 | import ECS from '@fae/ecs'; 2 | 3 | /** 4 | * @class 5 | * @memberof ecs 6 | */ 7 | export default class System extends ECS.System { 8 | /** 9 | * 10 | * @param {Renderer} renderer - The renderer this sytem belongs to. 11 | * @param {number} priority - The priority of the system, higher means earlier. 12 | * @param {number} frequency - How often to run the update loop. `1` means every 13 | * time, `2` is every other time, etc. 14 | */ 15 | constructor(renderer, priority = System.PRIORITY.USER, frequency = 1) 16 | { 17 | super(frequency); 18 | 19 | /** 20 | * The renderer to use. 21 | * 22 | * @member {Renderer} 23 | */ 24 | this.renderer = renderer; 25 | 26 | /** 27 | * The priority of the system. A lower number makes it run earlier. 28 | * 29 | * See {@link System.PRIORITY} for some common ranges. The default is {@link System.PRIORITY.USER}. 30 | * 31 | * If you change this value at all after adding the system to the renderer you will 32 | * need to call {@link Renderer#sortSystems} for the change to affect the sort. 33 | * 34 | * @member {number} 35 | */ 36 | this.priority = priority; 37 | } 38 | 39 | /** 40 | * Destroys the system. 41 | * 42 | */ 43 | destroy() 44 | { 45 | this.renderer = null; 46 | } 47 | } 48 | 49 | /** 50 | * Some common priority ranges. Lower priority numbers run first. 51 | * You can use any number you want for priority of your systems, these just serve as a guideline for 52 | * what the core and official plugins will try to follow. 53 | * 54 | * If you use these values directly it will run in the order: 55 | * 56 | * 1. USER 57 | * 2. PLUGIN 58 | * 3. RENDER 59 | * 60 | * @static 61 | * @constant 62 | * @property {number} USER - The start of the user range (0-2999) 63 | * @property {number} PLUGIN - The start of the plugin range (3000-5999) 64 | * @property {number} RENDER - The start of the render range (6000+) 65 | */ 66 | System.PRIORITY = { 67 | USER: 0, 68 | PLUGIN: 3000, 69 | RENDER: 6000, 70 | }; 71 | -------------------------------------------------------------------------------- /plugins/core/src/ecs/VisibilityComponent.js: -------------------------------------------------------------------------------- 1 | // TODO: World Alpha needs updates... 2 | 3 | export default function VisibilityComponent(Base) 4 | { 5 | /** 6 | * @class VisibilityComponent 7 | * @memberof ecs 8 | */ 9 | return class extends Base 10 | { 11 | /** 12 | * 13 | */ 14 | constructor() 15 | { 16 | super(...arguments); // eslint-disable-line prefer-rest-params 17 | 18 | /** 19 | * Controls the visibility of the object. If false, not rendered. 20 | * 21 | * @member {boolean} 22 | * @default true 23 | */ 24 | this.visible = true; 25 | 26 | /** 27 | * The alpha of the object when rendered. 28 | * 0 = transparent, 1 = opaque. 29 | * 30 | * @member {number} 31 | * @default 1 32 | */ 33 | this.alpha = 1; 34 | 35 | /** 36 | * The world alpha of the object (local alpha * parent alpha). 37 | * 0 = transparent, 1 = opaque. 38 | * 39 | * @member {number} 40 | * @default 1 41 | */ 42 | this.worldAlpha = 1; 43 | } 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /plugins/core/src/ecs/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace ecs */ 2 | 3 | export { default as SelfRenderComponent } from './SelfRenderComponent'; 4 | export { default as SelfRenderSystem } from './SelfRenderSystem'; 5 | export { default as VisibilityComponent } from './VisibilityComponent'; 6 | export { default as Entity } from './Entity'; 7 | export { default as System } from './System'; 8 | -------------------------------------------------------------------------------- /plugins/core/src/gl/GLBuffer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains code that was taken from, or heavily based upon, code 3 | * from the pixi.js project. Those sections are used under the terms of The 4 | * Pixi License, detailed below: 5 | * 6 | * The Pixi License 7 | * 8 | * Copyright (c) 2013-2016 Mathew Groves 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | */ 28 | const EMPTY_ARRAY_BUFFER = new ArrayBuffer(0); 29 | 30 | /** 31 | * Helper class to create a WebGL buffer. 32 | * 33 | * @class 34 | * @memberof glutil 35 | */ 36 | export default class GLBuffer 37 | { 38 | /** 39 | * Creates a new GLBuffer. 40 | * 41 | * @param {WebGLRenderingContext} gl - The current WebGL rendering context 42 | * @param {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} type - Array type 43 | * @param {ArrayBuffer|SharedArrayBuffer|ArrayBufferView} data - an array of data 44 | * @param {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} drawType - Type of draw 45 | */ 46 | constructor(gl, type = gl.ARRAY_BUFFER, data = EMPTY_ARRAY_BUFFER, drawType = gl.STATIC_DRAW) 47 | { 48 | /** 49 | * The current WebGL rendering context 50 | * 51 | * @member {WebGLRenderingContext} 52 | */ 53 | this.gl = gl; 54 | 55 | /** 56 | * The WebGL buffer, created upon instantiation 57 | * 58 | * @member {WebGLBuffer} 59 | */ 60 | this.buffer = gl.createBuffer(); 61 | 62 | /** 63 | * The type of the buffer 64 | * 65 | * @member {gl.ARRAY_BUFFER|gl.ELEMENT_ARRAY_BUFFER} 66 | */ 67 | this.type = type; 68 | 69 | /** 70 | * The draw type of the buffer 71 | * 72 | * @member {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} 73 | */ 74 | this.drawType = drawType; 75 | 76 | /** 77 | * The data in the buffer, as a typed array 78 | * 79 | * @member {ArrayBuffer|SharedArrayBuffer|ArrayBufferView} 80 | */ 81 | this.data = EMPTY_ARRAY_BUFFER; 82 | 83 | // uplaod data if there is some 84 | if (data && data !== EMPTY_ARRAY_BUFFER) 85 | { 86 | this.upload(data); 87 | } 88 | } 89 | 90 | /** 91 | * Creates a new GLBuffer, using `gl.ARRAY_BUFFER` as the type. 92 | * 93 | * @static 94 | * @param {WebGLRenderingContext} gl - The current WebGL rendering context 95 | * @param {ArrayBuffer|SharedArrayBuffer|ArrayBufferView} data - an array of data 96 | * @param {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} drawType - Type of draw 97 | * @return {GLBuffer} New buffer, using `gl.ARRAY_BUFFER` as the type 98 | */ 99 | static createVertexBuffer(gl, data, drawType) 100 | { 101 | return new GLBuffer(gl, gl.ARRAY_BUFFER, data, drawType); 102 | } 103 | 104 | /** 105 | * Creates a new GLBuffer, using `gl.ELEMENT_ARRAY_BUFFER` as the type. 106 | * 107 | * @static 108 | * @param {WebGLRenderingContext} gl - The current WebGL rendering context 109 | * @param {ArrayBuffer|SharedArrayBuffer|ArrayBufferView} data - an array of data 110 | * @param {gl.STATIC_DRAW|gl.DYNAMIC_DRAW|gl.STREAM_DRAW} drawType - Type of draw 111 | * @return {GLBuffer} New buffer, using `gl.ELEMENT_ARRAY_BUFFER` as the type 112 | */ 113 | static createIndexBuffer(gl, data, drawType) 114 | { 115 | return new GLBuffer(gl, gl.ELEMENT_ARRAY_BUFFER, data, drawType); 116 | } 117 | 118 | /** 119 | * Uploads the buffer to the GPU 120 | * 121 | * @param {ArrayBuffer|SharedArrayBuffer|ArrayBufferView} data - an array of data to upload 122 | * @param {number} offset - if only a subset of the data should be uploaded, this is the amount of data to subtract 123 | * @param {boolean} dontBind - whether to bind the buffer before uploading it 124 | */ 125 | upload(data = this.data, offset = 0, dontBind = false) 126 | { 127 | // todo - needed? 128 | if (!dontBind) this.bind(); 129 | 130 | const gl = this.gl; 131 | 132 | if (this.data.byteLength >= data.byteLength) 133 | { 134 | gl.bufferSubData(this.type, offset, data); 135 | } 136 | else 137 | { 138 | gl.bufferData(this.type, data, this.drawType); 139 | } 140 | 141 | this.data = data; 142 | } 143 | 144 | /** 145 | * Binds the buffer 146 | * 147 | */ 148 | bind() 149 | { 150 | this.gl.bindBuffer(this.type, this.buffer); 151 | } 152 | 153 | /** 154 | * Destroys the buffer 155 | * 156 | */ 157 | destroy() 158 | { 159 | if (this.gl.isBuffer(this.buffer)) 160 | { 161 | this.gl.deleteBuffer(this.buffer); 162 | } 163 | 164 | this.gl = null; 165 | this.buffer = null; 166 | this.data = null; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /plugins/core/src/gl/GLContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @namespace GLContext 3 | * @memberof glutil 4 | */ 5 | export default { 6 | defaultOptions: { 7 | alpah: true, 8 | antialias: false, 9 | premultipliedAlpha: true, 10 | }, 11 | /** 12 | * Helper function to create a webGL Context. 13 | * 14 | * @memberof glutil.GLContext 15 | * @param {HTMLCanvasElement} canvas - The canvas element that we will get the context from. 16 | * @param {object} options - An options object that gets passed in to the canvas element containing 17 | * the context attributes, see https://developer.mozilla.org/en/docs/Web/API/HTMLCanvasElement/getContext 18 | * for the options available 19 | * @return {WebGLRenderingContext} the WebGL context 20 | */ 21 | create(canvas, options = this.defaultOptions) 22 | { 23 | const gl = canvas.getContext('webgl2', options) 24 | || canvas.getContext('experimental-webgl2', options) 25 | || canvas.getContext('webgl', options) 26 | || canvas.getContext('experimental-webgl', options); 27 | 28 | if (!gl) 29 | { 30 | // fail, not able to get a context 31 | throw new Error('This browser does not support WebGL.'); 32 | } 33 | 34 | return gl; 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /plugins/core/src/gl/GLProgramCache.js: -------------------------------------------------------------------------------- 1 | let PROGRAM_CACHE = {}; 2 | 3 | /** 4 | * @namespace GLProgramCache 5 | * @memberof glutil 6 | */ 7 | export default { 8 | /** 9 | * Gets a program from the cache. 10 | * 11 | * @memberof glutil.GLProgramCache 12 | * @param {string} key - The key of the program to get. 13 | * @return {WebGLProgram} The cached program, or undefined if none found. 14 | */ 15 | get(key) 16 | { 17 | return PROGRAM_CACHE[key]; 18 | }, 19 | 20 | /** 21 | * Sets a program in the cache. 22 | * 23 | * @memberof glutil.GLProgramCache 24 | * @param {string} key - The key of the program to get. 25 | * @param {WebGLProgram} program - The program to put into the cache. 26 | */ 27 | set(key, program) 28 | { 29 | PROGRAM_CACHE[key] = program; 30 | }, 31 | 32 | /** 33 | * Generates a cache key for a vertex/fragment source pair. 34 | * 35 | * @memberof glutil.GLProgramCache 36 | * @param {string} vsrc - The vertex source of the program that will be stored. 37 | * @param {string} fsrc - The fragment source of the program that will be stored. 38 | * @return {string} The cache key. 39 | */ 40 | key(vsrc, fsrc) 41 | { 42 | return vsrc + fsrc; 43 | }, 44 | 45 | /** 46 | * Clears the GLProgramCache storage. 47 | * 48 | * @memberof glutil.GLProgramCache 49 | */ 50 | clear() 51 | { 52 | PROGRAM_CACHE = {}; 53 | }, 54 | }; 55 | -------------------------------------------------------------------------------- /plugins/core/src/gl/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace glutil */ 2 | 3 | export { default as GLBuffer } from './GLBuffer'; 4 | export { default as GLContext } from './GLContext'; 5 | export { default as GLFramebuffer } from './GLFramebuffer'; 6 | export { default as GLProgramCache } from './GLProgramCache'; 7 | export { default as GLQuad } from './GLQuad'; 8 | export { default as GLShader } from './GLShader'; 9 | export { default as GLTexture } from './GLTexture'; 10 | export { default as GLVertexArrayObject } from './GLVertexArrayObject'; 11 | 12 | import * as GLData from './GLData'; 13 | export { GLData }; 14 | -------------------------------------------------------------------------------- /plugins/core/src/index.js: -------------------------------------------------------------------------------- 1 | // export some core modules 2 | import * as ecs from './ecs'; 3 | import * as glutil from './gl'; 4 | import * as math from './math'; 5 | import * as render from './render'; 6 | import * as util from './util'; 7 | 8 | export { ecs, glutil, math, render, util }; 9 | 10 | // @ifdef DEBUG 11 | import * as debug from './debug'; 12 | export { debug }; 13 | // @endif 14 | -------------------------------------------------------------------------------- /plugins/core/src/math/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace math */ 2 | 3 | export { default as Matrix2d } from './Matrix2d'; 4 | export { default as Vector2d } from './Vector2d'; 5 | -------------------------------------------------------------------------------- /plugins/core/src/render/ObjectRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * An ObjectRenderer is any renderer that is capable of rendering a single entity, or 3 | * a batch of similar entities. 4 | * 5 | * @class 6 | * @abstract 7 | * @memberof render 8 | */ 9 | export default class ObjectRenderer 10 | { 11 | /** 12 | * @param {Renderer} renderer - The renderer this manager works for. 13 | */ 14 | constructor(renderer) 15 | { 16 | /** 17 | * The renderer this manager works for. 18 | * 19 | * @member {Renderer} 20 | */ 21 | this.renderer = renderer; 22 | } 23 | 24 | /** 25 | * Starts the renderer, called when this becomes the active object renderer. 26 | * 27 | */ 28 | start() 29 | { 30 | // no base implementation 31 | } 32 | 33 | /** 34 | * Stops the renderer, called when this is no longer the active object renderer. 35 | * 36 | */ 37 | stop() 38 | { 39 | // no base implementation 40 | } 41 | 42 | /** 43 | * Renders an object, usually called by a system in the update loop. 44 | * 45 | * @param {*} object - The object to render. 46 | */ 47 | render(object) // eslint-disable-line no-unused-vars 48 | { 49 | // no base implementation 50 | } 51 | 52 | /** 53 | * Destroys the object renderer instance. 54 | * 55 | */ 56 | destroy() 57 | { 58 | this.renderer = null; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /plugins/core/src/render/Shader.js: -------------------------------------------------------------------------------- 1 | import GLShader from '../gl/GLShader'; 2 | 3 | /** 4 | * Shader wrapper. 5 | * 6 | * @class 7 | * @memberof render 8 | */ 9 | export default class Shader extends GLShader 10 | { 11 | /** 12 | * Constructs a new Shader. 13 | * 14 | * @param {Renderer} renderer - The Renderer to use for this shader. 15 | * @param {string} vertexSrc - The vertex shader source as an array of strings. 16 | * @param {string} fragmentSrc - The fragment shader source as an array of strings. 17 | */ 18 | constructor(renderer, vertexSrc, fragmentSrc) 19 | { 20 | super(renderer.gl, checkPrecision(vertexSrc), checkPrecision(fragmentSrc)); 21 | 22 | /** 23 | * Parent Renderer instance. 24 | * 25 | * @member {Renderer} 26 | */ 27 | this.renderer = renderer; 28 | 29 | /** 30 | * Binding for when context is restored. 31 | * 32 | * @member {SignalBinding} 33 | */ 34 | this._onContextChangeBinding = renderer.onContextChange.add(this.recompile, this); 35 | } 36 | 37 | /** 38 | * 39 | */ 40 | destroy() 41 | { 42 | this._onContextChangeBinding.detachAll(); 43 | this._onContextChangeBinding = null; 44 | 45 | this.renderer = null; 46 | } 47 | } 48 | 49 | /** 50 | * Value that specifies float precision in shaders. 51 | * 52 | * @static 53 | * @constant 54 | * @type {object} 55 | * @property {string} DEFAULT=MEDIUM - The default precision to use. 56 | * @property {string} LOW - The low precision header. 57 | * @property {string} MEDIUM - The medium precision header. 58 | * @property {string} HIGH - The high precision header. 59 | */ 60 | Shader.PRECISION = { 61 | DEFAULT: 'highp', 62 | LOW: 'lowp', 63 | MEDIUM: 'mediump', 64 | HIGH: 'highp', 65 | }; 66 | 67 | /** 68 | * Ensures that the source of the program has precision specified. 69 | * 70 | * @ignore 71 | * @param {string} source - The source to check. 72 | * @return {string} The potentially modified source. 73 | */ 74 | function checkPrecision(source) 75 | { 76 | if (!source) return ''; 77 | 78 | const lines = source.split('\n'); 79 | 80 | let commentOpen = false; 81 | 82 | for (let i = 0; i < lines.length; ++i) 83 | { 84 | const line = lines[i].trim(); 85 | const firstChars = line.substring(0, 2); 86 | 87 | // line comment, ignore 88 | if (firstChars === '//') continue; 89 | 90 | // start of block comment, set flag 91 | if (firstChars === '/*') 92 | { 93 | commentOpen = true; 94 | } 95 | 96 | // if comment open, check if this line ends it. If not continue 97 | if (commentOpen) 98 | { 99 | if (line.indexOf('*/') !== -1) 100 | { 101 | commentOpen = false; 102 | } 103 | 104 | continue; 105 | } 106 | 107 | // not in a comment, check if precision is set 108 | if (line.substring(0, 9) !== 'precision') 109 | { 110 | return `precision ${Shader.PRECISION.DEFAULT} float;\n\n${source}`; 111 | } 112 | } 113 | 114 | return source; 115 | } 116 | -------------------------------------------------------------------------------- /plugins/core/src/render/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace render */ 2 | 3 | export { default as ObjectRenderer } from './ObjectRenderer'; 4 | export { default as Renderer } from './Renderer'; 5 | export { default as RenderState } from './RenderState'; 6 | export { default as RenderTarget } from './RenderTarget'; 7 | export { default as Shader } from './Shader'; 8 | -------------------------------------------------------------------------------- /plugins/core/src/util/BlendMode.js: -------------------------------------------------------------------------------- 1 | /* eslint max-params: [2, { max: 7 }] */ 2 | 3 | /** 4 | * @class 5 | * @memberof util 6 | */ 7 | export default class BlendMode 8 | { 9 | /** 10 | * @param {number} sfactor - The source factor (for glBlendFunc). 11 | * @param {number} dfactor - The destination factor (for glBlendFunc). 12 | * @param {number} equation - The Alpha blend function to use (for glBlendEquation). 13 | * @param {number} id - The preset blend mode this object represents. 14 | */ 15 | constructor(sfactor, dfactor, equation) 16 | { 17 | this.sfactor = sfactor; 18 | this.dfactor = dfactor; 19 | this.equation = equation; 20 | } 21 | 22 | /** 23 | * @param {WebGLRenderingContext} gl - The rendering context to set on. 24 | */ 25 | enable(gl) 26 | { 27 | gl.blendFunc(this.sfactor, this.dfactor); 28 | gl.blendEquation(this.equation); 29 | } 30 | 31 | /** 32 | * Checks for equality with another blend mode. 33 | * 34 | * @param {BlendMode} mode - The mode to check equality against. 35 | * @return {boolean} True if they are equal. 36 | */ 37 | equals(mode) 38 | { 39 | return !!mode 40 | && this.sfactor === mode.sfactor 41 | && this.dfactor === mode.dfactor 42 | && this.equation === mode.equation; 43 | } 44 | } 45 | 46 | // short name, I'm lazy. 47 | const c = WebGLRenderingContext; 48 | 49 | /* eslint-disable no-multi-spaces */ 50 | BlendMode.NORMAL = new BlendMode(c.ONE, c.ONE_MINUS_SRC_ALPHA, c.FUNC_ADD); 51 | BlendMode.ADD = new BlendMode(c.ONE, c.DST_ALPHA, c.FUNC_ADD); 52 | BlendMode.SUBTRACT = new BlendMode(c.ONE, c.DST_ALPHA, c.FUNC_SUBTRACT); 53 | BlendMode.MULTIPLY = new BlendMode(c.DST_COLOR, c.ONE_MINUS_SRC_ALPHA, c.FUNC_ADD); 54 | BlendMode.EXCLUSION = new BlendMode(c.ONE_MINUS_DST_COLOR, c.ONE_MINUS_SRC_COLOR, c.FUNC_ADD); 55 | /* eslint-enable no-multi-spaces */ 56 | -------------------------------------------------------------------------------- /plugins/core/src/util/Buffer.js: -------------------------------------------------------------------------------- 1 | // @ifdef DEBUG 2 | import { ASSERT } from '../debug'; 3 | // @endif 4 | 5 | /** 6 | * Simple ArrayBuffer wrapper that manages offsets and views into that buffer. 7 | * 8 | * @class 9 | * @memberof util 10 | */ 11 | export default class Buffer 12 | { 13 | /** 14 | * @param {Buffer|ArrayBuffer|SharedArrayBuffer|ArrayBufferView|number} parentBufferOrSize - A parent 15 | * buffer to have views into, or a size to create a new buffer. If a number is passed 16 | * as the first argument it is assumed to be size, and other arguments are ignored. 17 | * @param {number} offset - Offset in bytes this buffer starts at in the parent buffer. 18 | * @param {number} length - Length in bytes of this buffer. 19 | */ 20 | constructor(parentBufferOrSize, offset = 0, length = -1) 21 | { 22 | // @ifdef DEBUG 23 | ASSERT(offset >= 0, 'Invalid offset, must be negative.'); 24 | // @endif 25 | 26 | let _buffer = null; 27 | 28 | if (typeof parentBufferOrSize === 'number') 29 | { 30 | _buffer = new ArrayBuffer(parentBufferOrSize); 31 | } 32 | else if (parentBufferOrSize instanceof ArrayBuffer) 33 | { 34 | _buffer = parentBufferOrSize; 35 | } 36 | else 37 | { 38 | // @ifdef DEBUG 39 | ASSERT(parentBufferOrSize.buffer, 'No buffer in object passed as parent.'); 40 | // @endif 41 | _buffer = parentBufferOrSize.buffer; 42 | } 43 | 44 | const bytesLength = length !== -1 ? length : (_buffer.byteLength - offset); 45 | const view32Length = length !== -1 ? length / 4 : ((_buffer.byteLength / 4) - (offset / 4)); 46 | 47 | // @ifdef DEBUG 48 | ASSERT(offset + bytesLength <= _buffer.byteLength, 'Offset + length > size of memory buffer.'); 49 | // @endif 50 | 51 | /** 52 | * Raw underlying ArrayBuffer. This is all the memory of the buffer, 53 | * NOT just the view it has (based on offset/length). 54 | * 55 | * @member {ArrayBuffer} 56 | */ 57 | this.buffer = _buffer; 58 | 59 | /** 60 | * View on the data as a Uint8Array. 61 | * 62 | * @member {Uint8Array} 63 | */ 64 | this.bytes = new Uint8Array(_buffer, offset, bytesLength); 65 | 66 | /** 67 | * View on the data as a Float32Array. 68 | * 69 | * @member {Float32Array} 70 | */ 71 | this.float32View = new Float32Array(_buffer, offset, view32Length); 72 | 73 | /** 74 | * View on the data as a Uint32Array. 75 | * 76 | * @member {Uint32Array} 77 | */ 78 | this.uint32View = new Uint32Array(_buffer, offset, view32Length); 79 | 80 | // for duck-typing 81 | this.__isBuffer = true; 82 | } 83 | 84 | /** 85 | * Determines if the passed object is a buffer. 86 | * 87 | * @param {*} b - Object to check. 88 | * @return {boolean} True if the object is Buffer, false otherwise. 89 | */ 90 | static isBuffer(b) 91 | { 92 | return !!b.__isBuffer; 93 | } 94 | 95 | /** 96 | * Destroys the buffer. 97 | */ 98 | destroy() 99 | { 100 | this.buffer = null; 101 | this.bytes = null; 102 | this.float32View = null; 103 | this.uint32View = null; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /plugins/core/src/util/Flags.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A helper class for managing a bitset of flags. 3 | * 4 | * @class 5 | * @memberof util 6 | */ 7 | export default class Flags 8 | { 9 | /** 10 | * @param {number} initialValue - The starting value of the data. 11 | */ 12 | constructor(initialValue = 0) 13 | { 14 | /** 15 | * The value of the flags underlying number. 16 | * 17 | * @member {number} 18 | */ 19 | this.value = initialValue | 0; 20 | } 21 | 22 | /** 23 | * Sets the bits masked by `flag`. 24 | * 25 | * @param {number} flag - The bits to set. 26 | * @param {boolean} on - Whether to set or clear the bits. 27 | */ 28 | set(flag, on = true) 29 | { 30 | if (on) this.value |= flag; 31 | else this.clear(flag); 32 | } 33 | 34 | /** 35 | * Clears the bits masked by `flag`. 36 | * 37 | * @param {number} flag - The bits to clear. 38 | */ 39 | clear(flag) 40 | { 41 | this.value &= ~flag; 42 | } 43 | 44 | /** 45 | * Checks if the flag is set to the value. 46 | * 47 | * @param {number} flag - The bits to check. 48 | * @param {boolean} on - Whether to check on or off. 49 | * @return {boolean} True if it is the pased value, false otherwise. 50 | */ 51 | is(flag, on) 52 | { 53 | return on ? this.isSet(flag) : this.isUnset(flag); 54 | } 55 | 56 | /** 57 | * Checks if the flag is set. 58 | * 59 | * @param {number} flag - The bits to check. 60 | * @return {boolean} True if it is set, false otherwise. 61 | */ 62 | isSet(flag) 63 | { 64 | return (this.value & flag) !== 0; 65 | } 66 | 67 | /** 68 | * Checks if the flag is unset. 69 | * 70 | * @param {number} flag - The bits to check. 71 | * @return {boolean} True if it is unset, false otherwise. 72 | */ 73 | isUnset(flag) 74 | { 75 | return (this.value & flag) === 0; 76 | } 77 | 78 | /** 79 | * Copies the values from another Flags object into this one. 80 | * 81 | * @param {Flags} flags - The object to copy from. 82 | * @return {Flags} Returns itself. 83 | */ 84 | copy(flags) 85 | { 86 | this.value = flags.value; 87 | 88 | return this; 89 | } 90 | 91 | /** 92 | * Creates a new flags helper from a default value. 93 | * 94 | * @return {Flags} The new object. 95 | */ 96 | clone() 97 | { 98 | return new Flags(this.value); 99 | } 100 | } 101 | 102 | /** 103 | * Helper to create a bit-shifted flag. 104 | * 105 | * @static 106 | * @method 107 | * @memberof Flags 108 | * @param {number} n - The index of the flag. 109 | * @return {number} The flag value. 110 | */ 111 | Flags.F = (n) => (1 << n); 112 | -------------------------------------------------------------------------------- /plugins/core/src/util/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace util */ 2 | 3 | import GLContext from '../gl/GLContext'; 4 | 5 | // @ifdef DEBUG 6 | import { ASSERT } from '../debug'; 7 | // @endif 8 | 9 | let nextUid = 0; 10 | 11 | // reexport some utils 12 | export { default as Color } from './Color'; 13 | export { default as Buffer } from './Buffer'; 14 | export { default as BlendMode } from './BlendMode'; 15 | export { default as Flags } from './Flags'; 16 | 17 | /** 18 | * Logs an error to the console. 19 | * 20 | * @memberof util 21 | * @param {...*} things to pass into `console.error`. 22 | */ 23 | export function error(...args) 24 | { 25 | args[0] = `[Fae Error] ${args[0]}`; 26 | 27 | console.error(...args); // eslint-disable-line no-console 28 | } 29 | 30 | /** 31 | * Logs a message to the console. 32 | * 33 | * @memberof util 34 | * @param {...*} things to pass into `console.log`. 35 | */ 36 | export function log(...args) 37 | { 38 | args[0] = `[Fae] ${args[0]}`; 39 | 40 | console.log(...args); // eslint-disable-line no-console 41 | } 42 | 43 | /** 44 | * Gets the next unique id. 45 | * 46 | * @memberof util 47 | * @return {number} The nexst unique id. 48 | */ 49 | export function uid() 50 | { 51 | return nextUid++; 52 | } 53 | 54 | /** 55 | * Fast replacement for splice that quickly removes elements from an array. 56 | * 57 | * @memberof util 58 | * @param {Array<*>} array - The array to manipulate. 59 | * @param {number} startIdx - The starting index. 60 | * @param {number} removeCount - The number of elements to remove. 61 | */ 62 | export function removeElements(array, startIdx = 0, removeCount = 1) 63 | { 64 | const length = array.length; 65 | 66 | // @ifdef DEBUG 67 | ASSERT(startIdx < length, 'removeElements: index out of range.'); 68 | ASSERT(removeCount !== 0, 'removeElements: remove count must be non-zero.'); 69 | // @endif 70 | 71 | removeCount = startIdx + removeCount > length ? (length - startIdx) : removeCount; 72 | const newLength = length - removeCount; 73 | 74 | for (let i = startIdx; i < newLength; ++i) 75 | { 76 | array[i] = array[i + removeCount]; 77 | } 78 | 79 | array.length = newLength; 80 | } 81 | 82 | const ifCheckShaderTemplate = ` 83 | precision mediump float; 84 | void main(void) 85 | { 86 | float test = 0.1; 87 | {{if_statements}} 88 | gl_FragColor = vec4(0.0); 89 | } 90 | `; 91 | 92 | /** 93 | * Calculate the max number of if statements supported in a shader, up to 94 | * a maximum cap. 95 | * 96 | * @memberof util 97 | * @param {WebGLRenderingContext} gl - The rendering context 98 | * @param {number} maxIfs - cap for the if checks. 99 | * @return {number} The max ifs supported 100 | */ 101 | export function getMaxIfStatmentsInShader(gl, maxIfs) 102 | { 103 | const createTempContext = !gl; 104 | 105 | if (createTempContext) 106 | { 107 | const tinyCanvas = document.createElement('canvas'); 108 | 109 | tinyCanvas.width = 1; 110 | tinyCanvas.height = 1; 111 | 112 | gl = GLContext.create(tinyCanvas); 113 | } 114 | 115 | const shader = gl.createShader(gl.FRAGMENT_SHADER); 116 | let max = typeof maxIfs === 'number' ? maxIfs : 50; 117 | let guess = max; 118 | 119 | // binary search for max ifs, optimistically. 120 | // - assumes higher will work most of the time. 121 | // - best case is even numbers, which this should be most of the time. 122 | while (guess) 123 | { 124 | const result = testMaxIf(shader, gl, guess); 125 | 126 | // failed, lower expectations. 127 | if (!result) 128 | { 129 | max = guess; 130 | guess = max >> 1; 131 | } 132 | // succeeded, raise expectations. 133 | else 134 | { 135 | if (max - guess <= 1) break; 136 | 137 | guess += (max - guess) >> 1; 138 | } 139 | } 140 | 141 | if (createTempContext) 142 | { 143 | // get rid of context 144 | if (gl.getExtension('WEBGL_lose_context')) 145 | { 146 | gl.getExtension('WEBGL_lose_context').loseContext(); 147 | } 148 | } 149 | 150 | return guess; 151 | } 152 | 153 | /** 154 | * Builds and tried to compile a shader with the specified number of if-statements. 155 | * 156 | * @ignore 157 | * @param {WebGLShader} shader - The shader program to use. 158 | * @param {WebGLRenderingContext} gl - The rendering context. 159 | * @param {number} max - Number of ifs to generate. 160 | * @return {number} The compilation status. 161 | */ 162 | function testMaxIf(shader, gl, max) 163 | { 164 | const fragmentSrc = ifCheckShaderTemplate.replace(/\{\{if_statements\}\}/gi, generateIfTestSrc(max)); 165 | 166 | gl.shaderSource(shader, fragmentSrc); 167 | gl.compileShader(shader); 168 | 169 | return gl.getShaderParameter(shader, gl.COMPILE_STATUS); 170 | } 171 | 172 | /** 173 | * Generates a shader source with the specified number of if-statements. 174 | * 175 | * @ignore 176 | * @param {number} max - Number of ifs to generate. 177 | * @return {string} The shader source. 178 | */ 179 | function generateIfTestSrc(max) 180 | { 181 | let src = ''; 182 | 183 | for (let i = 0; i < max; ++i) 184 | { 185 | if (i > 0) 186 | { 187 | src += '\nelse '; 188 | } 189 | 190 | if (i < max - 1) 191 | { 192 | src += `if(test == ${i}.0) {}`; 193 | } 194 | } 195 | 196 | return src; 197 | } 198 | -------------------------------------------------------------------------------- /plugins/core/test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../.eslintrc.json", 3 | "env": { 4 | "commonjs": false, 5 | "mocha": true 6 | }, 7 | "globals": { 8 | "chai": false, 9 | "expect": false, 10 | "sinon": false, 11 | "Fae": false 12 | }, 13 | "parserOptions": { 14 | "sourceType": "script" 15 | }, 16 | "rules": { 17 | "strict": 0, 18 | "no-unused-expressions": 0, 19 | "func-names": 0, 20 | "no-var": 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugins/core/test/gl/GLQuad.test.js: -------------------------------------------------------------------------------- 1 | describe('core', function () 2 | { 3 | describe('gl', function () 4 | { 5 | describe('GLQuad', function () 6 | { 7 | describe('.createIndices', function () 8 | { 9 | it('creates the proper indicies for 1 quad', function () 10 | { 11 | var indicies = Fae.glutil.GLQuad.createIndices(1); 12 | 13 | expect(indicies).to.eql(new Uint16Array([ 14 | 0, 1, 2, 0, 2, 3, 15 | ])); 16 | }); 17 | 18 | it('creates the proper indicies for 2 quads', function () 19 | { 20 | var indicies = Fae.glutil.GLQuad.createIndices(2); 21 | 22 | expect(indicies).to.eql(new Uint16Array([ 23 | 0, 1, 2, 0, 2, 3, 24 | 4, 5, 6, 4, 6, 7, 25 | ])); 26 | }); 27 | }); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /plugins/core/test/util/BlendMode.test.js: -------------------------------------------------------------------------------- 1 | describe('core', function () 2 | { 3 | describe('util', function () 4 | { 5 | describe('BlendMode', function () 6 | { 7 | describe('#ctor', function () 8 | { 9 | it('is properly constructed', function () 10 | { 11 | var b = new Fae.util.BlendMode(1, 2, 3); 12 | 13 | expect(b.sfactor).to.equal(1); 14 | expect(b.dfactor).to.equal(2); 15 | expect(b.equation).to.equal(3); 16 | }); 17 | }); 18 | 19 | describe('#enable', function () 20 | { 21 | it('enables the blendFunc and equation', function () 22 | { 23 | var gl = { blendFunc: sinon.spy(), blendEquation: sinon.spy() }; 24 | var b = new Fae.util.BlendMode(1, 2, 3); 25 | 26 | b.enable(gl); 27 | 28 | expect(gl.blendFunc).to.have.been.calledOnce.and.calledWith(1, 2); 29 | expect(gl.blendEquation).to.have.been.calledOnce.and.calledWith(3); 30 | }); 31 | }); 32 | 33 | describe('#equals', function () 34 | { 35 | it('returns true if equal', function () 36 | { 37 | var b1 = new Fae.util.BlendMode(1, 2, 3); 38 | var b2 = new Fae.util.BlendMode(1, 2, 3); 39 | 40 | expect(b1.equals(b2)).to.equal(true); 41 | expect(b2.equals(b1)).to.equal(true); 42 | }); 43 | 44 | it('returns false if not equal', function () 45 | { 46 | var b1 = new Fae.util.BlendMode(1, 2, 3); 47 | var b2 = new Fae.util.BlendMode(1, 2, 4); 48 | 49 | expect(b1.equals(b2)).to.equal(false); 50 | expect(b2.equals(b1)).to.equal(false); 51 | 52 | expect(b1.equals()).to.equal(false); 53 | expect(b2.equals()).to.equal(false); 54 | }); 55 | }); 56 | }); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /plugins/core/test/util/Buffer.test.js: -------------------------------------------------------------------------------- 1 | describe('core', function () 2 | { 3 | describe('util', function () 4 | { 5 | describe('Buffer', function () 6 | { 7 | describe('#ctor', function () 8 | { 9 | it('Can construct from an ArrayBuffer', function () 10 | { 11 | var _b = new ArrayBuffer(0); 12 | var b = new Fae.util.Buffer(_b); 13 | 14 | confirmBuffer(b, _b); 15 | }); 16 | 17 | it('Can construct from a Uint8Array', function () 18 | { 19 | var _b = new Uint8Array(0); 20 | var b = new Fae.util.Buffer(_b); 21 | 22 | confirmBuffer(b, _b.buffer); 23 | }); 24 | 25 | it('Can construct from a Float32Array', function () 26 | { 27 | var _b = new Float32Array(0); 28 | var b = new Fae.util.Buffer(_b); 29 | 30 | confirmBuffer(b, _b.buffer); 31 | }); 32 | 33 | it('Can construct from a number', function () 34 | { 35 | var b = new Fae.util.Buffer(2); 36 | 37 | expect(b.buffer).to.be.an.instanceOf(ArrayBuffer); 38 | confirmBuffer(b, b.buffer); 39 | }); 40 | 41 | it('Can take an offset into an ArrayBuffer', function () 42 | { 43 | var _b = new Float32Array(2); 44 | var b = new Fae.util.Buffer(_b, 4); 45 | 46 | _b[1] = 5.0; 47 | 48 | expect(b.bytes.length).to.equal(4); 49 | expect(b.bytes[0]).to.equal(0); 50 | expect(b.bytes[1]).to.equal(0); 51 | expect(b.bytes[2]).to.equal(160); 52 | expect(b.bytes[3]).to.equal(64); 53 | 54 | expect(b.float32View.length).to.equal(1); 55 | expect(b.float32View[0]).to.equal(5.0); 56 | 57 | expect(b.uint32View.length).to.equal(1); 58 | expect(b.uint32View[0]).to.equal(1084227584); 59 | }); 60 | 61 | it('Can take an offset and length into an ArrayBuffer', function () 62 | { 63 | var _b = new Float32Array(6); 64 | var b = new Fae.util.Buffer(_b, 12, 8); 65 | 66 | _b[3] = 5.0; 67 | _b[4] = 6.5; 68 | 69 | expect(b.bytes.length).to.equal(8); 70 | expect(b.bytes[0]).to.equal(0); 71 | expect(b.bytes[1]).to.equal(0); 72 | expect(b.bytes[2]).to.equal(160); 73 | expect(b.bytes[3]).to.equal(64); 74 | expect(b.bytes[4]).to.equal(0); 75 | expect(b.bytes[5]).to.equal(0); 76 | expect(b.bytes[6]).to.equal(208); 77 | expect(b.bytes[7]).to.equal(64); 78 | 79 | expect(b.float32View.length).to.equal(2); 80 | expect(b.float32View[0]).to.equal(5.0); 81 | expect(b.float32View[1]).to.equal(6.5); 82 | 83 | expect(b.uint32View.length).to.equal(2); 84 | expect(b.uint32View[0]).to.equal(1084227584); 85 | expect(b.uint32View[1]).to.equal(1087373312); 86 | }); 87 | }); 88 | 89 | function confirmBuffer(b, _buff) 90 | { 91 | expect(b).to.have.property('__isBuffer', true); 92 | 93 | expect(b).to.have.property('buffer', _buff); 94 | expect(b).to.have.deep.property('bytes.buffer', _buff); 95 | expect(b).to.have.deep.property('float32View.buffer', _buff); 96 | expect(b).to.have.deep.property('uint32View.buffer', _buff); 97 | } 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /plugins/core/test/util/index.test.js: -------------------------------------------------------------------------------- 1 | describe('core', function () 2 | { 3 | describe('util', function () 4 | { 5 | describe('index', function () 6 | { 7 | describe('#removeElements', function () 8 | { 9 | it('Removes the specified elements', function () 10 | { 11 | var a = [1, 2, 3, 4, 5]; 12 | 13 | Fae.util.removeElements(a, 1, 2); 14 | 15 | expect(a).to.eql([1, 4, 5]); 16 | }); 17 | 18 | it('Uses the length when count is too large', function () 19 | { 20 | var a = [1, 2, 3, 4, 5]; 21 | 22 | Fae.util.removeElements(a, 1, 10); 23 | 24 | expect(a).to.eql([1]); 25 | }); 26 | }); 27 | 28 | describe('#getMaxIfStatmentsInShader', function () 29 | { 30 | it('works properly'); 31 | }); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /plugins/filters-pack/README.md: -------------------------------------------------------------------------------- 1 | # Filters Pack 2 | 3 | A pack of pre-made filters that might be useful in a variety of situations! 4 | 5 | ## Usage 6 | 7 | You can use any filter in the pack by accessing it under the `filter_pack` namespace. 8 | 9 | For example: 10 | 11 | ```js 12 | // create an assemblage of a sprite that is filterable 13 | class FilteredSprite extends Fae.sprites.Sprite.with( 14 | Fae.filters.FilterComponent, 15 | Fae.shapes.SpriteBoundsComponent // so I don't need to set .filterArea manually 16 | ) {} 17 | 18 | // create a filtered sprite 19 | const img = new Image(); img.src = 'spade_A.png'; 20 | const sprite = new FilteredSprite(new Fae.textures.Texture(img)); 21 | 22 | // add a filter from the filter pack 23 | sprite.filters.push(new Fae.filters_pack.NoiseFilter(renderer)); 24 | 25 | // render like normal. 26 | renderer.addEntity(sprite); 27 | renderer.render(); 28 | ``` 29 | -------------------------------------------------------------------------------- /plugins/filters-pack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fae/filters-pack", 3 | "version": "1.0.0", 4 | "description": "Pack of multiple pre-made filters.", 5 | "author": "Chad Engler ", 6 | "license": "MIT", 7 | "main": "src/index.js", 8 | "homepage": "https://github.com/Fae/fae#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Fae/fae.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/Fae/fae/issues" 15 | }, 16 | "files": [ 17 | "src/" 18 | ], 19 | "dependencies": { 20 | "@fae/filters": "^1.0.0", 21 | "glsl-fast-gaussian-blur": "^1.0.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plugins/filters-pack/src/blur/BlurFilter.js: -------------------------------------------------------------------------------- 1 | import { Filter, FilterUtils } from '@fae/filters'; 2 | import BlurXFilter from './BlurXFilter'; 3 | import BlurYFilter from './BlurYFilter'; 4 | 5 | /** 6 | * @class 7 | */ 8 | export default class BlurFilter extends Filter 9 | { 10 | /** 11 | * @param {Renderer} renderer - The renderer to use. 12 | */ 13 | constructor(renderer) 14 | { 15 | super(renderer); 16 | 17 | this.blurXFilter = new BlurXFilter(renderer); 18 | this.blurYFilter = new BlurYFilter(renderer); 19 | } 20 | 21 | /** 22 | * Runs the filter, performing the post-processing passes the filter defines. 23 | * 24 | * @param {FilterRenderSystem} system - The render system. 25 | * @param {RenderTarget} input - The render target to use as input. 26 | * @param {RenderTarget} output - The render target to use as output. 27 | * @param {boolean} clear - Should the output buffer be cleared before use? 28 | */ 29 | run(system, input, output /* , clear */) 30 | { 31 | const renderTarget = FilterUtils.getRenderTarget(this.renderer.gl); 32 | 33 | this.blurXFilter.run(system, input, renderTarget, true); 34 | this.blurYFilter.run(system, renderTarget, output, false); 35 | 36 | FilterUtils.freeRenderTarget(renderTarget); 37 | } 38 | 39 | /** 40 | * Number of passes for the blur. Higher number produices a higher 41 | * quality blur, but is less performant. 42 | * 43 | * @member {number} 44 | * @default 1 45 | */ 46 | get numPasses() 47 | { 48 | return this.blurXFilter.numPasses; 49 | } 50 | 51 | /** 52 | * Number of passes for the blur. Higher number produices a higher 53 | * quality blur, but is less performant. 54 | * 55 | * @param {number} v - The value to set to. 56 | */ 57 | set numPasses(v) 58 | { 59 | this.blurXFilter.numPasses = v; 60 | this.blurYFilter.numPasses = v; 61 | } 62 | 63 | /** 64 | * Number of passes for the blur. Higher number produices a higher 65 | * quality blur, but is less performant. 66 | * 67 | * @member {number} 68 | * @default 1 69 | */ 70 | get strength() 71 | { 72 | return this.blurXFilter.strength; 73 | } 74 | 75 | /** 76 | * Number of passes for the blur. Higher number produices a higher 77 | * quality blur, but is less performant. 78 | * 79 | * @param {number} v - The value to set to. 80 | */ 81 | set strength(v) 82 | { 83 | this.blurXFilter.strength = v; 84 | this.blurYFilter.strength = v; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /plugins/filters-pack/src/blur/BlurXFilter.js: -------------------------------------------------------------------------------- 1 | import { Filter, FilterUtils } from '@fae/filters'; 2 | import { GAUSSIAN_VALUES, getFragSource, getVertSource, getMaxKernelSize } from './blurUtil'; 3 | 4 | /** 5 | * @class 6 | */ 7 | export default class BlurXFilter extends Filter 8 | { 9 | /** 10 | * @param {Renderer} renderer - The renderer to use. 11 | */ 12 | constructor(renderer) 13 | { 14 | const kernelSize = getMaxKernelSize(renderer.gl); 15 | 16 | super(renderer, getFragSource(kernelSize), getVertSource(kernelSize, true)); 17 | 18 | // set default values 19 | this.values.uBlurValues = GAUSSIAN_VALUES[kernelSize]; 20 | 21 | /** 22 | * Number of passes for the blur. Higher number produices a higher 23 | * quality blur, but is less performant. 24 | * 25 | * @member {number} 26 | * @default 1 27 | */ 28 | this.numPasses = 1; 29 | 30 | /** 31 | * The strength of the blur. 32 | * 33 | * @member {number} 34 | * @default 1 35 | */ 36 | this.strength = 1; 37 | } 38 | 39 | /** 40 | * Runs the filter, performing the post-processing passes the filter defines. 41 | * 42 | * @param {FilterRenderSystem} system - The render system. 43 | * @param {RenderTarget} input - The render target to use as input. 44 | * @param {RenderTarget} output - The render target to use as output. 45 | * @param {boolean} clear - Should the output buffer be cleared before use? 46 | */ 47 | run(system, input, output, clear) 48 | { 49 | this.values.uStrength = (1 / output.size.x) * (output.size.x / input.size.y); 50 | this.values.uStrength *= Math.abs(this.strength); 51 | this.values.uStrength /= this.numPasses; 52 | 53 | if (this.numPasses === 1) 54 | { 55 | system.drawFilter(this, input, output, clear); 56 | } 57 | else 58 | { 59 | const renderTarget = FilterUtils.getRenderTarget(this.renderer.gl); 60 | let flip = input; 61 | let flop = renderTarget; 62 | 63 | for (let i = 0; i < this.numPasses - 1; ++i) 64 | { 65 | system.drawFilter(this, flip, flop, true); 66 | 67 | const t = flip; 68 | 69 | flip = flop; 70 | flop = t; 71 | } 72 | 73 | system.drawFilter(this, flip, output, clear); 74 | FilterUtils.freeRenderTarget(renderTarget); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /plugins/filters-pack/src/blur/BlurYFilter.js: -------------------------------------------------------------------------------- 1 | import { Filter, FilterUtils } from '@fae/filters'; 2 | import { GAUSSIAN_VALUES, getFragSource, getVertSource } from './blurUtil'; 3 | 4 | // @ifdef DEBUG 5 | import { debug } from '@fae/core'; 6 | // @endif 7 | 8 | /** 9 | * @class 10 | */ 11 | export default class BlurYFilter extends Filter 12 | { 13 | /** 14 | * @param {Renderer} renderer - The renderer to use. 15 | * @param {number} tapLevel - The guasian tap level to use. 5, 7, 9, 11, 13, or 15. 16 | */ 17 | constructor(renderer, tapLevel = 5) 18 | { 19 | // @ifdef DEBUG 20 | debug.ASSERT(GAUSSIAN_VALUES[tapLevel], 21 | `Unknown tap level: ${tapLevel}, looking for one of: ${Object.keys(GAUSSIAN_VALUES)}`); 22 | // @endif 23 | 24 | super(renderer, getFragSource(tapLevel), getVertSource(tapLevel, false)); 25 | 26 | // set default values 27 | this.values.uBlurValues = GAUSSIAN_VALUES[tapLevel]; 28 | 29 | /** 30 | * Number of passes for the blur. Higher number produices a higher 31 | * quality blur, but is less performant. 32 | * 33 | * @member {number} 34 | * @default 1 35 | */ 36 | this.numPasses = 1; 37 | 38 | /** 39 | * The strength of the blur. 40 | * 41 | * @member {number} 42 | * @default 1 43 | */ 44 | this.strength = 1; 45 | } 46 | 47 | /** 48 | * Runs the filter, performing the post-processing passes the filter defines. 49 | * 50 | * @param {FilterRenderSystem} system - The render system. 51 | * @param {RenderTarget} input - The render target to use as input. 52 | * @param {RenderTarget} output - The render target to use as output. 53 | * @param {boolean} clear - Should the output buffer be cleared before use? 54 | */ 55 | run(system, input, output, clear) 56 | { 57 | this.values.uStrength = (1 / output.size.y) * (output.size.y / input.size.y); 58 | this.values.uStrength *= Math.abs(this.strength); 59 | this.values.uStrength /= this.numPasses; 60 | 61 | if (this.numPasses === 1) 62 | { 63 | system.drawFilter(this, input, output, clear); 64 | } 65 | else 66 | { 67 | const renderTarget = FilterUtils.getRenderTarget(this.renderer.gl); 68 | let flip = input; 69 | let flop = renderTarget; 70 | 71 | for (let i = 0; i < this.numPasses - 1; ++i) 72 | { 73 | system.drawFilter(this, flip, flop, true); 74 | 75 | const t = flip; 76 | 77 | flip = flop; 78 | flop = t; 79 | } 80 | 81 | system.drawFilter(this, flip, output, clear); 82 | FilterUtils.freeRenderTarget(renderTarget); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /plugins/filters-pack/src/blur/blur.frag: -------------------------------------------------------------------------------- 1 | #define BLUR_KERNEL_SIZE {{size}} 2 | 3 | varying vec2 vBlurTexCoords[BLUR_KERNEL_SIZE]; 4 | 5 | uniform sampler2D uSampler; 6 | uniform float uBlurValues[BLUR_KERNEL_SIZE]; 7 | 8 | void main(void) 9 | { 10 | gl_FragColor = vec4(0.0); 11 | 12 | // this loop will get unrolled 13 | for (int i = 0; i < BLUR_KERNEL_SIZE; ++i) 14 | { 15 | gl_FragColor += texture2D(uSampler, vBlurTexCoords[i]) * uBlurValues[i]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /plugins/filters-pack/src/blur/blur.vert: -------------------------------------------------------------------------------- 1 | #define BLUR_KERNEL_SIZE {{size}} 2 | #define BLUR_KERNAL_HALF_LENGTH {{halfLength}}.0 3 | #define BLUR_HORIZONTAL {{horizontal}} 4 | 5 | #define SAMPLE_INDEX(i) (float(i) - (BLUR_KERNAL_HALF_LENGTH - 1.0)) 6 | 7 | attribute vec2 aVertexPosition; 8 | attribute vec2 aTextureCoord; 9 | 10 | uniform mat3 uProjectionMatrix; 11 | uniform float uStrength; 12 | 13 | varying vec2 vBlurTexCoords[BLUR_KERNEL_SIZE]; 14 | 15 | void main(void) 16 | { 17 | gl_Position = vec4((uProjectionMatrix * vec3((aVertexPosition), 1.0)).xy, 0.0, 1.0); 18 | 19 | // this loop will get unrolled 20 | for (int i = 0; i < BLUR_KERNEL_SIZE; ++i) 21 | { 22 | #if BLUR_HORIZONTAL == 1 23 | vBlurTexCoords[i] = aTextureCoord + vec2(SAMPLE_INDEX(i) * uStrength, 0.0); 24 | #else 25 | vBlurTexCoords[i] = aTextureCoord + vec2(0.0, SAMPLE_INDEX(i) * uStrength); 26 | #endif 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plugins/filters-pack/src/blur/blurUtil.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | export const GAUSSIAN_VALUES = { 3 | 5: [0.153388, 0.221461, 0.250301, 0.221461, 0.153388], 4 | 7: [0.071303, 0.131514, 0.189879, 0.214607, 0.189879, 0.131514, 0.071303], 5 | 9: [0.028532, 0.067234, 0.124009, 0.179044, 0.202360, 0.179044, 0.124009, 0.067234, 0.028532], 6 | 11: [0.009300, 0.028002, 0.065984, 0.121703, 0.175713, 0.198596, 0.175713, 0.121703, 0.065984, 0.028002, 0.0093], 7 | 13: [0.002406, 0.009255, 0.027867, 0.065666, 0.121117, 0.174868, 0.197641, 0.174868, 0.121117, 0.065666, 0.027867, 0.009255, 0.002406], 8 | 15: [0.000489, 0.002403, 0.009246, 0.027840, 0.065602, 0.120999, 0.174697, 0.197448, 0.174697, 0.120999, 0.065602, 0.027840, 0.009246, 0.002403, 0.000489], 9 | }; 10 | /* eslint-enable max-len */ 11 | 12 | const vertTemplate = require('./blur.vert'); 13 | const fragTemplate = require('./blur.frag'); 14 | 15 | export function getFragSource(kernelSize) 16 | { 17 | return fragTemplate 18 | .replace('{{size}}', kernelSize); 19 | } 20 | 21 | export function getVertSource(kernelSize, isHorizontal) 22 | { 23 | return vertTemplate 24 | .replace('{{size}}', kernelSize) 25 | .replace('{{halfLength}}', Math.ceil(kernelSize / 2)) 26 | .replace('{{horizontal}}', isHorizontal ? 1 : 0); 27 | } 28 | 29 | export function getMaxKernelSize(gl) 30 | { 31 | const maxVaryings = gl.getParameter(gl.MAX_VARYING_VECTORS); 32 | let kernelSize = 15; 33 | 34 | while (kernelSize > maxVaryings) 35 | { 36 | kernelSize -= 2; 37 | } 38 | 39 | return kernelSize; 40 | } 41 | -------------------------------------------------------------------------------- /plugins/filters-pack/src/fxaa/FXAAFilter.js: -------------------------------------------------------------------------------- 1 | import { Filter } from '@fae/filters'; 2 | 3 | const vertSrc = require('./fxaa.vert'); 4 | const fragSrc = require('./fxaa.frag'); 5 | 6 | /** 7 | * Basic FXAA implementation based on the code on geeks3d.com with the 8 | * modification that the texture2DLod stuff was removed since it's 9 | * unsupported by WebGL. 10 | * 11 | * @see https://github.com/mitsuhiko/webgl-meincraft 12 | * 13 | * @class 14 | */ 15 | export default class FXAAFilter extends Filter 16 | { 17 | /** 18 | * @param {Renderer} renderer - The renderer to use. 19 | */ 20 | constructor(renderer) 21 | { 22 | super(renderer, fragSrc, vertSrc); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /plugins/filters-pack/src/fxaa/fxaa.frag: -------------------------------------------------------------------------------- 1 | varying vec2 vRgbNW; 2 | varying vec2 vRgbNE; 3 | varying vec2 vRgbSW; 4 | varying vec2 vRgbSE; 5 | varying vec2 vRgbM; 6 | 7 | varying vec2 vTextureCoord; 8 | 9 | uniform sampler2D uSampler; 10 | uniform vec4 uFilterArea; 11 | 12 | /** 13 | Basic FXAA implementation based on the code on geeks3d.com with the 14 | modification that the texture2DLod stuff was removed since it's 15 | unsupported by WebGL. 16 | 17 | -- 18 | 19 | From: 20 | https://github.com/mitsuhiko/webgl-meincraft 21 | 22 | Copyright (c) 2011 by Armin Ronacher. 23 | 24 | Some rights reserved. 25 | 26 | Redistribution and use in source and binary forms, with or without 27 | modification, are permitted provided that the following conditions are 28 | met: 29 | 30 | * Redistributions of source code must retain the above copyright 31 | notice, this list of conditions and the following disclaimer. 32 | 33 | * Redistributions in binary form must reproduce the above 34 | copyright notice, this list of conditions and the following 35 | disclaimer in the documentation and/or other materials provided 36 | with the distribution. 37 | 38 | * The names of the contributors may not be used to endorse or 39 | promote products derived from this software without specific 40 | prior written permission. 41 | 42 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 43 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 44 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 45 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 46 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 47 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 48 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 49 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 50 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 51 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 52 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 53 | */ 54 | 55 | #ifndef FXAA_REDUCE_MIN 56 | #define FXAA_REDUCE_MIN (1.0/ 128.0) 57 | #endif 58 | #ifndef FXAA_REDUCE_MUL 59 | #define FXAA_REDUCE_MUL (1.0 / 8.0) 60 | #endif 61 | #ifndef FXAA_SPAN_MAX 62 | #define FXAA_SPAN_MAX 8.0 63 | #endif 64 | 65 | //optimized version for mobile, where dependent 66 | //texture reads can be a bottleneck 67 | vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 resolution, 68 | vec2 v_rgbNW, vec2 v_rgbNE, 69 | vec2 v_rgbSW, vec2 v_rgbSE, 70 | vec2 v_rgbM) { 71 | vec4 color; 72 | mediump vec2 inverseVP = vec2(1.0 / resolution.x, 1.0 / resolution.y); 73 | vec3 rgbNW = texture2D(tex, v_rgbNW).xyz; 74 | vec3 rgbNE = texture2D(tex, v_rgbNE).xyz; 75 | vec3 rgbSW = texture2D(tex, v_rgbSW).xyz; 76 | vec3 rgbSE = texture2D(tex, v_rgbSE).xyz; 77 | vec4 texColor = texture2D(tex, v_rgbM); 78 | vec3 rgbM = texColor.xyz; 79 | vec3 luma = vec3(0.299, 0.587, 0.114); 80 | float lumaNW = dot(rgbNW, luma); 81 | float lumaNE = dot(rgbNE, luma); 82 | float lumaSW = dot(rgbSW, luma); 83 | float lumaSE = dot(rgbSE, luma); 84 | float lumaM = dot(rgbM, luma); 85 | float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); 86 | float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); 87 | 88 | mediump vec2 dir; 89 | dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); 90 | dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); 91 | 92 | float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * 93 | (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); 94 | 95 | float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); 96 | dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), 97 | max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), 98 | dir * rcpDirMin)) * inverseVP; 99 | 100 | vec3 rgbA = 0.5 * ( 101 | texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + 102 | texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz); 103 | vec3 rgbB = rgbA * 0.5 + 0.25 * ( 104 | texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + 105 | texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz); 106 | 107 | float lumaB = dot(rgbB, luma); 108 | if ((lumaB < lumaMin) || (lumaB > lumaMax)) 109 | color = vec4(rgbA, texColor.a); 110 | else 111 | color = vec4(rgbB, texColor.a); 112 | return color; 113 | } 114 | 115 | void main() { 116 | 117 | vec2 fragCoord = vTextureCoord * uFilterArea.xy; 118 | 119 | vec4 color; 120 | 121 | color = fxaa(uSampler, fragCoord, uFilterArea.xy, vRgbNW, vRgbNE, vRgbSW, vRgbSE, vRgbM); 122 | 123 | gl_FragColor = color; 124 | } 125 | -------------------------------------------------------------------------------- /plugins/filters-pack/src/fxaa/fxaa.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 aVertexPosition; 2 | attribute vec2 aTextureCoord; 3 | 4 | varying vec2 vRgbNW; 5 | varying vec2 vRgbNE; 6 | varying vec2 vRgbSW; 7 | varying vec2 vRgbSE; 8 | varying vec2 vRgbM; 9 | 10 | varying vec2 vTextureCoord; 11 | 12 | uniform mat3 uProjectionMatrix; 13 | uniform vec4 uFilterArea; 14 | 15 | vec2 mapCoord(vec2 coord) 16 | { 17 | coord *= uFilterArea.xy; 18 | coord += uFilterArea.zw; 19 | 20 | return coord; 21 | } 22 | 23 | vec2 unmapCoord(vec2 coord) 24 | { 25 | coord -= uFilterArea.zw; 26 | coord /= uFilterArea.xy; 27 | 28 | return coord; 29 | } 30 | 31 | void texcoords(vec2 fragCoord, vec2 resolution, 32 | out vec2 vRgbNW, out vec2 vRgbNE, 33 | out vec2 vRgbSW, out vec2 vRgbSE, 34 | out vec2 vRgbM) { 35 | vec2 inverseVP = 1.0 / resolution.xy; 36 | vRgbNW = (fragCoord + vec2(-1.0, -1.0)) * inverseVP; 37 | vRgbNE = (fragCoord + vec2(1.0, -1.0)) * inverseVP; 38 | vRgbSW = (fragCoord + vec2(-1.0, 1.0)) * inverseVP; 39 | vRgbSE = (fragCoord + vec2(1.0, 1.0)) * inverseVP; 40 | vRgbM = vec2(fragCoord * inverseVP); 41 | } 42 | 43 | void main(void) { 44 | 45 | gl_Position = vec4((uProjectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); 46 | 47 | vTextureCoord = aTextureCoord; 48 | 49 | vec2 fragCoord = vTextureCoord * uFilterArea.xy; 50 | 51 | texcoords(fragCoord, uFilterArea.xy, vRgbNW, vRgbNE, vRgbSW, vRgbSE, vRgbM); 52 | } 53 | -------------------------------------------------------------------------------- /plugins/filters-pack/src/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace filters-pack */ 2 | 3 | export { default as NoiseFilter } from './noise/NoiseFilter'; 4 | export { default as BlurFilter } from './blur/BlurFilter'; 5 | export { default as BlurXFilter } from './blur/BlurXFilter'; 6 | export { default as BlurYFilter } from './blur/BlurYFilter'; 7 | export { default as FXAAFilter } from './fxaa/FXAAFilter'; 8 | -------------------------------------------------------------------------------- /plugins/filters-pack/src/noise/NoiseFilter.js: -------------------------------------------------------------------------------- 1 | import { Filter } from '@fae/filters'; 2 | 3 | const fragSrc = require('./noise.frag'); 4 | 5 | /** 6 | * A Noise effect filter. 7 | * 8 | * @class 9 | * @memberof filters-pack 10 | */ 11 | export default class NoiseFilter extends Filter 12 | { 13 | /** 14 | * @param {Renderer} renderer - The renderer this filter runs in. 15 | */ 16 | constructor(renderer) 17 | { 18 | super(renderer, fragSrc); 19 | 20 | this.values.uNoise = 0.5; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugins/filters-pack/src/noise/noise.frag: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | // 1 "Standard" fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); 4 | // 2 One-Dimension fract( mod( 12345678., 256. * p.x) ); 5 | #define RANDOM_TYPE 1 6 | 7 | varying vec2 vTextureCoord; 8 | 9 | uniform sampler2D uSampler; 10 | uniform float uNoise; 11 | 12 | // http://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl 13 | // http://stackoverflow.com/questions/12964279/whats-the-origin-of-this-glsl-rand-one-liner 14 | float rand(vec2 co) 15 | { 16 | return fract(sin(dot(co.xy, vec2(12.9898, 78.233))) * 43758.5453); 17 | } 18 | 19 | void main() 20 | { 21 | vec4 color = texture2D(uSampler, vTextureCoord); 22 | 23 | color *= rand(gl_FragCoord.xy * uNoise); 24 | 25 | gl_FragColor = color; 26 | } 27 | -------------------------------------------------------------------------------- /plugins/filters/README.md: -------------------------------------------------------------------------------- 1 | # Filters 2 | 3 | The filters plugin adds post-processing support for individual entities. 4 | 5 | ## Usage 6 | 7 | Filters are high-level constructs that represent shaders. A Filter is usually a post-processing 8 | effect that runs in the local space of an entity. 9 | 10 | To use them, you simply need to add the `FilterComponent` to your entity, then add a filter to 11 | the `.filters` array the component adds. 12 | 13 | For example: 14 | 15 | ```js 16 | // create an assemblage of a sprite that is filterable 17 | class FilteredSprite extends Fae.sprites.Sprite.with( 18 | Fae.filters.FilterComponent, 19 | Fae.shapes.SpriteBoundsComponent // so I don't need to set .filterArea manually 20 | ) {} 21 | 22 | // create a filtered sprite 23 | const img = new Image(); img.src = 'spade_A.png'; 24 | const sprite = new FilteredSprite(new Fae.textures.Texture(img)); 25 | 26 | // add a new filter, pretend there is a fragment source and renderer :P 27 | sprite.filters.push(new Fae.filters.Filter(renderer, fragmentSource)); 28 | 29 | // render like normal. 30 | renderer.addEntity(sprite); 31 | renderer.render(); 32 | ``` 33 | 34 | ## Writing Custom Filters 35 | 36 | When writting custom filters, there are a few ways you can go about applying your custom effect. 37 | The `Filter` class is ready to go and generates properties based off your shader source. So, 38 | if you only want one instance of a filter with a particular source, you can just create it with 39 | the built-in filter: 40 | 41 | ```js 42 | const filter = new Fae.filters.Filter(renderer, fragmentSource, optionalVertexSource); 43 | ``` 44 | 45 | However, if you want to make a reusable filter where you don't need to pass the source in every 46 | time, you can subclass `Filter` to make your own: 47 | 48 | ```js 49 | class MyFilter extends Fae.filters.Filter 50 | { 51 | constructor(renderer) 52 | { 53 | super(renderer, fragmentSource, optionalVertexSource); 54 | } 55 | } 56 | ``` 57 | 58 | ### Automatic Properties 59 | 60 | The base filter class automatically reads your shader source and generates properties for each 61 | of the uniforms in your filter on a properties called `.values` (excluding a few reserved names, 62 | talked about in sections below). 63 | 64 | For example, this shader: 65 | 66 | ```glsl 67 | varying vec2 vTextureCoord; 68 | 69 | uniform sampler2D uSampler; 70 | uniform float uMultiplier; 71 | 72 | void main() 73 | { 74 | vec4 color = texture2D(uSampler, vTextureCoord); 75 | 76 | color *= uMultiplier; 77 | 78 | gl_FragColor = color; 79 | } 80 | ``` 81 | 82 | Will create a `.values` object that looks like this: 83 | 84 | ```json 85 | { 86 | "uMultiplier": 0 87 | } 88 | ``` 89 | 90 | This is the property you should use to change your uniform values. **Do not use the `.uniforms` 91 | object, you will get unexpected results**. 92 | 93 | Here is an example of correct usage: 94 | 95 | ```js 96 | // where `multiplierFragSource` is the GLSL code above in a string: 97 | const multiplyFilter = new Fae.filters.Filter(renderer, multiplierFragSource); 98 | 99 | // `uMultiplier` property exists because it was auto detected from the source. 100 | multiplyFilter.values.uMultiplier = 0.5; 101 | ``` 102 | 103 | ### Fragment Shader 104 | 105 | When writing a custom fragment shader, there are a few assumptions the plugin makes. 106 | 107 | First, it expects a sampler uniform with name `uSampler`: 108 | 109 | ```glsl 110 | uniform sampler2D uSampler; 111 | ``` 112 | 113 | This sampler2D is set to the input texture. Additionally, there are couple other uniforms 114 | that *can* be set if they exist in the shader, though not including them is not an error. 115 | 116 | 1) `uniform vec4 uFilterArea;` 117 | - The first two components (`xy`) represent the width/height of the render target 118 | - The second two components (`zw`) represent the x/y coords of the bounds of the entity 119 | 2) `uniform vec4 uFilterClamp;` 120 | - The first two components (`xy`) represent the min texture coord for the filter 121 | - The second two components (`zw`) represent the max texture coord for the filter 122 | 123 | These uniforms, if they exist, will be set automatically for you. Setting their values 124 | manually will be ignored or may result in an error if you set the wrong type. Best to 125 | let the engine set these values if you use them. 126 | 127 | ### Vertex Shader 128 | 129 | Many filters will not require a custom vertex shader, but if you do write a custom vertex 130 | shader, you need to be aware that the plugin will be looking for these attributes and 131 | uniforms to exist: 132 | 133 | ```glsl 134 | attribute vec2 aVertexPosition; 135 | attribute vec2 aTextureCoord; 136 | 137 | uniform mat3 uProjectionMatrix; 138 | ``` 139 | 140 | The plugin expects these to exist and tries to set their values. If they are not in the 141 | shader, you will get an error. You can use the [default vertex shader](src/default.vert) 142 | as a guide. 143 | -------------------------------------------------------------------------------- /plugins/filters/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fae/filters", 3 | "version": "1.0.0", 4 | "description": "Add support for post-processing on individual objects", 5 | "author": "Chad Engler ", 6 | "license": "MIT", 7 | "main": "src/index.js", 8 | "homepage": "https://github.com/Fae/fae#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Fae/fae.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/Fae/fae/issues" 15 | }, 16 | "files": [ 17 | "src/" 18 | ], 19 | "dependencies": { 20 | "@fae/core": "^1.0.0", 21 | "mini-signals": "^1.1.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plugins/filters/src/Filter.js: -------------------------------------------------------------------------------- 1 | import { render, util } from '@fae/core'; 2 | 3 | const reservedUniforms = [ 4 | '__data', // reserved key from the parsing of uniforms in the GLShader 5 | 'uSampler', // reserved key as the input texture to a filter 6 | 'uProjectionMatrix', // reserved key as the precalculated projection matrix 7 | ]; 8 | 9 | /** 10 | * @class 11 | * @memberof filters 12 | */ 13 | export default class Filter extends render.Shader 14 | { 15 | /** 16 | * @param {Renderer} renderer - The renderer instance of the filter. 17 | * @param {string} fragmentSrc - The source of the fragment shader for this filter. 18 | * @param {string} vertexSrc - The source of the vertex shader for this filter. 19 | */ 20 | constructor(renderer, fragmentSrc, vertexSrc = Filter.defaultVertexSrc) 21 | { 22 | super(renderer, vertexSrc, fragmentSrc); 23 | 24 | /** 25 | * Whether or not this filter is currently enabled. This is useful 26 | * if you need to turn on/off a filter often and don't want to change 27 | * an Entity's `filters` array. 28 | * 29 | * @member {boolean} 30 | * @default true 31 | */ 32 | this.enable = true; 33 | 34 | /** 35 | * The blend mode to be applied to the filter pass. 36 | * 37 | * @member {BlendMode} 38 | * @default BlendMode.NORMAL 39 | */ 40 | this.blendMode = util.BlendMode.NORMAL; 41 | 42 | /** 43 | * The values for the uniforms of this filter. This object will contain a property 44 | * for each uniform, automatically detected from the shader source. 45 | * 46 | * Use this to set uniform values. *Do not* set values with 47 | * `filter.uniforms. = ` as that will cause unexpected results. 48 | * 49 | * @member {*} 50 | */ 51 | this.values = Filter.generateValueProperties(this.uniforms); 52 | } 53 | 54 | /** 55 | * Generates the values object with a key for each uniform. 56 | * 57 | * @param {*} uniforms - The uniforms to create a values object for. 58 | * @return {*} The values object. 59 | */ 60 | static generateValueProperties(uniforms) 61 | { 62 | // TODO: Structs.... 63 | 64 | const values = {}; 65 | 66 | for (const k in uniforms) 67 | { 68 | // skip reserved names 69 | if (reservedUniforms.indexOf(k) !== -1) continue; 70 | 71 | values[k] = uniforms[k]; 72 | } 73 | 74 | return values; 75 | } 76 | 77 | /** 78 | * Runs the filter, performing the post-processing passes the filter defines. 79 | * 80 | * @param {FilterRenderSystem} system - The render system. 81 | * @param {RenderTarget} input - The render target to use as input. 82 | * @param {RenderTarget} output - The render target to use as output. 83 | * @param {boolean} clear - Should the output buffer be cleared before use? 84 | */ 85 | run(system, input, output, clear) 86 | { 87 | system.drawFilter(this, input, output, clear); 88 | } 89 | 90 | /** 91 | * Should only be called after the filter has been set as the bound shader. 92 | * Since the FilterRenderSystem calls this for you, there should be almost 93 | * no situation where you should call this yourself. 94 | * 95 | */ 96 | syncUniforms() 97 | { 98 | // TODO: Structs.... 99 | 100 | // slot 0 is the main texture, additional textures start at 1 101 | let textureCount = 1; 102 | 103 | const values = this.values; 104 | const uniforms = this.uniforms; 105 | const uniformData = this.uniforms.__data; 106 | 107 | for (const k in values) 108 | { 109 | if (!uniformData[k]) continue; 110 | 111 | if (uniformData[k].type === 'sampler2D') 112 | { 113 | uniforms[k] = textureCount; 114 | 115 | // TextureSource object from the textures plugin 116 | // or anything that can give me a gl texture really. 117 | if (values[k].getGlTexture) 118 | { 119 | const tx = values[k].getGlTexture(this.renderer); 120 | 121 | if (tx) tx.bind(textureCount); 122 | } 123 | // Texture object from the textures plugin. 124 | else if (values[k].source && values[k].source.getGlTexture) 125 | { 126 | const tx = values[k].source.getGlTexture(this.renderer); 127 | 128 | if (tx) tx.bind(textureCount); 129 | } 130 | // RenderTarget, GLFramebuffer, or anything with a GLTexture property. 131 | else if (values[k].texture) 132 | { 133 | values[k].texture.bind(textureCount); 134 | } 135 | 136 | textureCount++; 137 | } 138 | else if (uniformData[k].type === 'mat3') 139 | { 140 | // check if its a matrix object 141 | if (typeof values[k].a !== 'undefined') 142 | { 143 | uniforms[k] = values[k].toArray(true); 144 | } 145 | else 146 | { 147 | uniforms[k] = values[k]; 148 | } 149 | } 150 | else if (uniformData[k].type === 'vec2') 151 | { 152 | // check if its a vector object 153 | if (typeof values[k].x !== 'undefined') 154 | { 155 | const val = uniforms[k] || new Float32Array(2); 156 | 157 | val[0] = values[k].x; 158 | val[1] = values[k].y; 159 | 160 | uniforms[k] = val; 161 | } 162 | else 163 | { 164 | uniforms[k] = values[k]; 165 | } 166 | } 167 | else 168 | { 169 | uniforms[k] = values[k]; 170 | } 171 | } 172 | } 173 | } 174 | 175 | /** 176 | * @static 177 | * @constant 178 | * @memberof Filter 179 | * @type {string} 180 | */ 181 | Filter.defaultVertexSrc = require('./default.vert'); 182 | -------------------------------------------------------------------------------- /plugins/filters/src/FilterComponent.js: -------------------------------------------------------------------------------- 1 | export default function FilterComponent(Base) 2 | { 3 | /** 4 | * @class FilterComponent 5 | * @memberof filters 6 | */ 7 | return class extends Base 8 | { 9 | /** 10 | * 11 | */ 12 | constructor() 13 | { 14 | super(...arguments); // eslint-disable-line prefer-rest-params 15 | 16 | /** 17 | * The filters to apply to this object. 18 | * 19 | * @member {Filter[]} 20 | */ 21 | this.filters = []; 22 | 23 | /** 24 | * The area to use to filter, relative to the object's frame. Either this 25 | * rectangle must exist, or the entity needs to have the bounds component. 26 | * 27 | * When rendering filters the system tries to get this value, and if it 28 | * doesn't exist calls `getBounds()` instead. 29 | * 30 | * @member {Rectangle} 31 | */ 32 | this.filterArea = null; 33 | } 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /plugins/filters/src/FilterPrepareSystem.js: -------------------------------------------------------------------------------- 1 | import { ecs, render } from '@fae/core'; 2 | import FilterComponent from './FilterComponent'; 3 | import FilterUtils from './FilterUtils'; 4 | 5 | /** 6 | * @class 7 | * @memberof filters 8 | */ 9 | export default class FilterPrepareSystem extends ecs.System 10 | { 11 | /** 12 | * @param {Renderer} renderer - The renderer to use. 13 | * @param {number} priority - The priority of the system, higher means earlier. 14 | * @param {number} frequency - How often to run the update loop. `1` means every 15 | * time, `2` is every other time, etc. 16 | */ 17 | constructor(renderer, priority = (ecs.System.PRIORITY.PLUGIN + 500), frequency = 1) 18 | { 19 | super(renderer, priority, frequency); 20 | } 21 | 22 | /** 23 | * Returns true if the entity is eligible to the system, false otherwise. 24 | * 25 | * @param {Entity} entity - The entity to test. 26 | * @return {boolean} True if entity should be included. 27 | */ 28 | test(entity) 29 | { 30 | return entity.hasComponent(FilterComponent); 31 | } 32 | 33 | /** 34 | * Prepare the entity for filtered rendering. 35 | * 36 | * @param {Entity} entity - The entity to update 37 | */ 38 | update(entity) 39 | { 40 | if (entity.filters.length === 0) return; 41 | 42 | // stop obj renderer 43 | this.renderer.activeObjectRenderer.stop(); 44 | 45 | // reassign target and setup filter state 46 | FilterUtils.setup(entity, this.renderer); 47 | 48 | // start obj renderer 49 | this.renderer.activeObjectRenderer.start(); 50 | } 51 | } 52 | 53 | render.Renderer.addDefaultSystem(FilterPrepareSystem); 54 | -------------------------------------------------------------------------------- /plugins/filters/src/FilterUtils.js: -------------------------------------------------------------------------------- 1 | import { render } from '@fae/core'; 2 | import { Rectangle } from '@fae/shapes'; 3 | import bitTwiddle from 'bit-twiddle'; 4 | 5 | /** 6 | * @namespace FilterUtils 7 | * @memberof filters 8 | */ 9 | export default { 10 | initialRenderTarget: null, 11 | activeRenderTarget: null, 12 | activeBounds: new Rectangle(), 13 | activeSize: new Rectangle(), 14 | _renderTargetPool: [], 15 | 16 | /** 17 | * Sets up the render targets and bounds for a filter render pass. 18 | * This is called by the FilterPrepareSystem when we encounter an 19 | * entity that has filters. 20 | * 21 | * @memberof filters.FilterUtils 22 | * @param {Entity} entity - The entity to setup for. 23 | * @param {Renderer} renderer - The renderer to setup for. 24 | */ 25 | setup(entity, renderer /* , resolution*/) 26 | { 27 | this.initialRenderTarget = renderer.state.target; 28 | 29 | if (this.activeRenderTarget) 30 | { 31 | this.freeRenderTarget(this.activeRenderTarget); 32 | } 33 | 34 | // prepare state 35 | const bounds = entity.filterArea || entity.getBounds(); 36 | const width = bitTwiddle.nextPow2(bounds.width); 37 | const height = bitTwiddle.nextPow2(bounds.height); 38 | 39 | this.activeBounds.copy(bounds); 40 | this.activeSize.width = bounds.width; 41 | this.activeSize.height = bounds.height; 42 | 43 | this.activeRenderTarget = this.getRenderTarget(renderer.gl, width, height); 44 | 45 | renderer.state.setRenderTarget(this.activeRenderTarget); 46 | }, 47 | 48 | /** 49 | * Gets a render target that a filter can use. 50 | * 51 | * @memberof filters.FilterUtils 52 | * @param {WebGLRenderingContext} gl - The context to create it for. 53 | * @param {number} width - The width of the render target to set. 54 | * @param {number} height - The height of the render target to set. 55 | * @return {RenderTarget} The render target. 56 | */ 57 | getRenderTarget(gl, width = this.activeBounds.width, height = this.activeBounds.height /* , resolution */) 58 | { 59 | width = bitTwiddle.nextPow2(width /* * resolution */); 60 | height = bitTwiddle.nextPow2(height /* * resolution */); 61 | 62 | const renderTarget = this._renderTargetPool.pop() || new render.RenderTarget(gl, width, height); 63 | 64 | renderTarget 65 | .resize(width, height) 66 | .setFrame(this.activeSize, this.activeBounds); 67 | 68 | return renderTarget; 69 | }, 70 | 71 | /** 72 | * Returns a render target to the pool for use later. 73 | * 74 | * @memberof filters.FilterUtils 75 | * @param {RenderTarget} renderTarget - The render target. 76 | */ 77 | freeRenderTarget(renderTarget) 78 | { 79 | this._renderTargetPool.push(renderTarget); 80 | }, 81 | }; 82 | -------------------------------------------------------------------------------- /plugins/filters/src/default.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 aVertexPosition; 2 | attribute vec2 aTextureCoord; 3 | 4 | uniform mat3 uProjectionMatrix; 5 | 6 | varying vec2 vTextureCoord; 7 | 8 | void main(void) { 9 | gl_Position = vec4((uProjectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); 10 | vTextureCoord = aTextureCoord; 11 | } 12 | -------------------------------------------------------------------------------- /plugins/filters/src/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace filters */ 2 | 3 | export { default as Filter } from './Filter'; 4 | export { default as FilterComponent } from './FilterComponent'; 5 | export { default as FilterPrepareSystem } from './FilterPrepareSystem'; 6 | export { default as FilterRenderSystem } from './FilterRenderSystem'; 7 | export { default as FilterUtils } from './FilterUtils'; 8 | -------------------------------------------------------------------------------- /plugins/filters/test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../.eslintrc.json", 3 | "env": { 4 | "commonjs": false, 5 | "mocha": true 6 | }, 7 | "globals": { 8 | "chai": false, 9 | "expect": false, 10 | "sinon": false, 11 | "Fae": false 12 | }, 13 | "parserOptions": { 14 | "sourceType": "script" 15 | }, 16 | "rules": { 17 | "strict": 0, 18 | "no-unused-expressions": 0, 19 | "func-names": 0, 20 | "no-var": 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugins/interaction/README.md: -------------------------------------------------------------------------------- 1 | # Interaction 2 | 3 | The interaction plugin adds support for managing mouse, touch, and pointer input and relates 4 | those events to entities in the scene. Specifically, any object with the `InteractionComponent` 5 | (which includes the `BoundsComponent`). 6 | 7 | ## Usage 8 | 9 | Using the interaction plugin is fairly simple. Just add an entity that has the `InteractionComponent` 10 | to the renderer. 11 | 12 | For example: 13 | 14 | ```js 15 | // create an assemblage of a sprite that is interactable 16 | class InteractableSprite extends Fae.sprites.Sprite.with( 17 | Fae.interaction.InteractionComponent, 18 | Fae.sprites.SpriteBoundsComponent // for proper bounds calculation of this sprite 19 | ) {} 20 | 21 | const sprite = new InteractableSprite(); 22 | 23 | sprite.onClick.add((pointer) => { 24 | console.log(pointer); 25 | }); 26 | 27 | renderer.addEntity(sprite); 28 | renderer.render(); 29 | ``` 30 | 31 | Since by default the `InteractionSystem` is automatically added to the renderer (if the plugin 32 | exists) the above sample just works as is. 33 | -------------------------------------------------------------------------------- /plugins/interaction/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fae/interaction", 3 | "version": "0.0.1", 4 | "description": "Adds support for handling interaction events.", 5 | "author": "Chad Engler ", 6 | "license": "MIT", 7 | "main": "src/index.js", 8 | "homepage": "https://github.com/Fae/fae#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Fae/fae.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/Fae/fae/issues" 15 | }, 16 | "files": [ 17 | "src/" 18 | ], 19 | "dependencies": { 20 | "@fae/core": "^1.0.0", 21 | "@fae/shapes": "^1.0.0", 22 | "mini-signals": "^1.1.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /plugins/interaction/src/InteractionComponent.js: -------------------------------------------------------------------------------- 1 | import Signal from 'mini-signals'; 2 | import { BoundsComponent } from '@fae/shapes'; 3 | 4 | export default function InteractionComponent(Base) 5 | { 6 | /** 7 | * @class InteractionComponent 8 | * @mixes BoundsComponent 9 | * @memberof interaction 10 | */ 11 | return class extends BoundsComponent(Base) 12 | { 13 | /** 14 | * 15 | */ 16 | constructor() 17 | { 18 | super(...arguments); // eslint-disable-line prefer-rest-params 19 | 20 | /** 21 | * Dispatched when a pointer starts an interaction (mousedown, pointerdown, touchstart). 22 | * 23 | * The callback looks like {@link InteractionComponent.OnInteractionCallback} 24 | * 25 | * @member {Signal} 26 | */ 27 | this.onDown = new Signal(); 28 | 29 | /** 30 | * Dispatched when a pointer ends an interaction (mouseup, pointerup, touchend). 31 | * 32 | * The callback looks like {@link InteractionComponent.OnInteractionCallback} 33 | * 34 | * @member {Signal} 35 | */ 36 | this.onUp = new Signal(); 37 | 38 | /** 39 | * Dispatched when a pointer ends an interaction (mouseup, pointerup, touchend) 40 | * but is outside of the current target. 41 | * 42 | * The callback looks like {@link InteractionComponent.OnInteractionCallback} 43 | * 44 | * @member {Signal} 45 | */ 46 | this.onUpOutside = new Signal(); 47 | 48 | /** 49 | * Dispatched when a pointer moves (mousemove, pointermove, touchmove). 50 | * 51 | * The callback looks like {@link InteractionComponent.OnInteractionCallback} 52 | * 53 | * @member {Signal} 54 | */ 55 | this.onMove = new Signal(); 56 | 57 | /** 58 | * Dispatched when a pointer cancels interaction (mouseout, pointerout, touchcancel). 59 | * 60 | * The callback looks like {@link InteractionComponent.OnInteractionCallback} 61 | * 62 | * @member {Signal} 63 | */ 64 | this.onCancel = new Signal(); 65 | 66 | /** 67 | * Dispatched when a pointer has a scroll interaction (wheel). 68 | * 69 | * The callback looks like {@link InteractionComponent.OnInteractionCallback} 70 | * 71 | * @member {Signal} 72 | */ 73 | this.onScroll = new Signal(); 74 | 75 | /** 76 | * Dispatched when a click occurs on an object. 77 | * 78 | * The callback looks like {@link InteractionComponent.OnInteractionCallback} 79 | * 80 | * @member {Signal} 81 | */ 82 | this.onClick = new Signal(); 83 | 84 | /** 85 | * Dispatched when a hover begins on an object. 86 | * 87 | * The callback looks like {@link InteractionComponent.OnInteractionCallback} 88 | * 89 | * @member {Signal} 90 | */ 91 | this.onHoverStart = new Signal(); 92 | 93 | /** 94 | * Dispatched when a hover begins on an object. 95 | * 96 | * The callback looks like {@link InteractionComponent.OnInteractionCallback} 97 | * 98 | * @member {Signal} 99 | */ 100 | this.onHoverEnd = new Signal(); 101 | 102 | /** 103 | * When an interaction occurs the interaction object is passed to the callback. 104 | * 105 | * @memberof InteractionComponent 106 | * @callback OnInteractionCallback 107 | * @param {Pointer} pointer - The pointer the interaction happened on. 108 | * @param {InteractableObject} target - The target of the interaction. 109 | */ 110 | } 111 | /** 112 | * Called to test if this object contains the passed in point. 113 | * 114 | * @param {number} x - The x coord to check. 115 | * @param {number} y - The y coord to check. 116 | * @return {SceneObject} The SceneObject that was hit, or null if nothing was. 117 | */ 118 | hitTest(x, y) 119 | { 120 | if (this.getBounds().contains(x, y)) 121 | { 122 | return this; 123 | } 124 | 125 | return null; 126 | } 127 | }; 128 | } 129 | -------------------------------------------------------------------------------- /plugins/interaction/src/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace interaction */ 2 | 3 | export { default as InteractionComponent } from './InteractionComponent'; 4 | export { default as InteractionSystem } from './InteractionSystem'; 5 | export { default as Pointer } from './Pointer'; 6 | -------------------------------------------------------------------------------- /plugins/shapes/README.md: -------------------------------------------------------------------------------- 1 | # Shapes 2 | 3 | The shapes plugin has some interesting utilities that are helpful when dealing with shapes. 4 | Most notably, it contains the `BoundingBox` class which can calculate the bounding box of 5 | an entity based on various pieces of information. It also has the `BoundsComponent` which 6 | adds a bounding box tracker and methods to get/update it. 7 | -------------------------------------------------------------------------------- /plugins/shapes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fae/shapes", 3 | "version": "0.0.1", 4 | "description": "Adds support for shapes.", 5 | "author": "Chad Engler ", 6 | "license": "MIT", 7 | "main": "src/index.js", 8 | "homepage": "https://github.com/Fae/fae#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Fae/fae.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/Fae/fae/issues" 15 | }, 16 | "files": [ 17 | "src/" 18 | ], 19 | "dependencies": { 20 | "@fae/core": "^1.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugins/shapes/src/BoundingBox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains code that was taken from, or heavily based upon, code 3 | * from the pixi.js project. Those sections are used under the terms of The 4 | * Pixi License, detailed below: 5 | * 6 | * The Pixi License 7 | * 8 | * Copyright (c) 2013-2016 Mathew Groves 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | */ 28 | import Rectangle from './Rectangle'; 29 | 30 | /** 31 | * BoundingBox is an axis-aligned bounding box for an owning object. 32 | * 33 | * @class 34 | * @memberof shapes 35 | */ 36 | export default class BoundingBox extends Rectangle 37 | { 38 | /** 39 | * 40 | */ 41 | constructor() 42 | { 43 | super(); 44 | 45 | /** 46 | * Tracks the "empty" bounds state so we always have a valid rectangle. 47 | * 48 | * @member {boolean} 49 | */ 50 | this._empty = true; 51 | } 52 | 53 | /** 54 | * Resets the bounding box to a default empty box. 55 | * 56 | */ 57 | clear() 58 | { 59 | this.x = this.y = this.width = this.height = 0; 60 | this._empty = true; 61 | } 62 | 63 | /** 64 | * Adds the child bounds object to the size of this bounding box. 65 | * 66 | * @param {BoundingBox} bounds - The child bounds to include. 67 | * @return {BoundingBox} Returns itself. 68 | */ 69 | addChild(bounds) 70 | { 71 | if (bounds._empty) return this; 72 | 73 | if (this._empty) 74 | { 75 | this.copy(bounds); 76 | this._empty = false; 77 | } 78 | else 79 | { 80 | this.union(bounds); 81 | } 82 | 83 | return this; 84 | } 85 | 86 | /** 87 | * Adds the vertices of a quad to the size of this bounding box. This 88 | * has the effect of extending the bounding box to include this quad. 89 | * 90 | * @param {Float32Array|number[]} vertices - The vertices of the quad. 91 | * @return {BoundingBox} Returns itself. 92 | */ 93 | addQuad(vertices) 94 | { 95 | let minX = this._empty ? Infinity : this.x; 96 | let minY = this._empty ? Infinity : this.y; 97 | let maxX = this._empty ? -Infinity : this.right; 98 | let maxY = this._empty ? -Infinity : this.bottom; 99 | 100 | let x = 0; 101 | let y = 0; 102 | 103 | x = vertices[0]; 104 | y = vertices[1]; 105 | minX = x < minX ? x : minX; 106 | minY = y < minY ? y : minY; 107 | maxX = x > maxX ? x : maxX; 108 | maxY = y > maxY ? y : maxY; 109 | 110 | x = vertices[2]; 111 | y = vertices[3]; 112 | minX = x < minX ? x : minX; 113 | minY = y < minY ? y : minY; 114 | maxX = x > maxX ? x : maxX; 115 | maxY = y > maxY ? y : maxY; 116 | 117 | x = vertices[4]; 118 | y = vertices[5]; 119 | minX = x < minX ? x : minX; 120 | minY = y < minY ? y : minY; 121 | maxX = x > maxX ? x : maxX; 122 | maxY = y > maxY ? y : maxY; 123 | 124 | x = vertices[6]; 125 | y = vertices[7]; 126 | minX = x < minX ? x : minX; 127 | minY = y < minY ? y : minY; 128 | maxX = x > maxX ? x : maxX; 129 | maxY = y > maxY ? y : maxY; 130 | 131 | this.x = minX; 132 | this.y = minY; 133 | this.width = maxX - this.x; 134 | this.height = maxY - this.y; 135 | 136 | this._empty = false; 137 | 138 | return this; 139 | } 140 | 141 | /** 142 | * Adds an arbitrary array of vertices to the size of the bounding box. 143 | * 144 | * @param {Transform} transform - The transform to consider. 145 | * @param {Float32Array|number[]} vertices - The vertices to consider. 146 | * @param {number} offset - offset into the vertices array to start at. 147 | * @param {number} endOffset - The end of the vertices at which to stop. 148 | * @return {BoundingBox} Returns itself. 149 | */ 150 | addVertices(transform, vertices, offset = 0, endOffset = vertices.length) 151 | { 152 | const matrix = transform.worldTransform; 153 | 154 | const a = matrix.a; 155 | const b = matrix.b; 156 | const c = matrix.c; 157 | const d = matrix.d; 158 | const tx = matrix.tx; 159 | const ty = matrix.ty; 160 | 161 | let minX = this._empty ? Infinity : this.x; 162 | let minY = this._empty ? Infinity : this.y; 163 | let maxX = this._empty ? -Infinity : this.right; 164 | let maxY = this._empty ? -Infinity : this.bottom; 165 | 166 | for (let i = offset; i < endOffset; i += 2) 167 | { 168 | const rawX = vertices[i]; 169 | const rawY = vertices[i + 1]; 170 | 171 | const x = (a * rawX) + (c * rawY) + tx; 172 | const y = (d * rawY) + (b * rawX) + ty; 173 | 174 | minX = Math.min(x, minX); 175 | minY = Math.min(y, minY); 176 | maxX = Math.max(x, maxX); 177 | maxY = Math.max(y, maxY); 178 | } 179 | 180 | this.x = minX; 181 | this.y = minY; 182 | this.wight = maxX - this.x; 183 | this.height = maxY - this.y; 184 | 185 | this._empty = false; 186 | 187 | return this; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /plugins/shapes/src/BoundsComponent.js: -------------------------------------------------------------------------------- 1 | import BoundingBox from './BoundingBox'; 2 | 3 | export default function BoundsComponent(Base) 4 | { 5 | /** 6 | * @class BoundsComponent 7 | * @memberof shapes 8 | */ 9 | return class extends Base 10 | { 11 | /** 12 | * 13 | */ 14 | constructor() 15 | { 16 | super(...arguments); // eslint-disable-line prefer-rest-params 17 | 18 | /** 19 | * The bounding box of this scene object. 20 | * 21 | * @private 22 | * @member {BoundingBox} 23 | */ 24 | this._bounds = new BoundingBox(); 25 | } 26 | 27 | /** 28 | * Returns the bounding box of this scene object. 29 | * 30 | * @return {BoundingBox} The object's bounding box. 31 | */ 32 | getBounds() 33 | { 34 | this._updateBounds(); 35 | 36 | return this._bounds; 37 | } 38 | 39 | /** 40 | * Updates the bounds of this object. 41 | * 42 | * @protected 43 | */ 44 | _updateBounds() 45 | { 46 | this._bounds.clear(); 47 | } 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /plugins/shapes/src/Polygon.js: -------------------------------------------------------------------------------- 1 | import { math } from '@fae/core'; 2 | 3 | /** 4 | * A simple class representing a polygon. 5 | * 6 | * @class 7 | * @memberof shapes 8 | */ 9 | export default class Polygon 10 | { 11 | /** 12 | * Constructs a polygon. 13 | * 14 | * @param {Vector2d[]|number[]} points - This can be an array of `Vector2d`s that 15 | * form the polygon, a flat array of numbers that will be interpreted as [x,y, x,y, ...], or the arguments 16 | * passed can be all the points of the polygon e.g. `new Polygon(new Vector2d(), new Vector2d(), ...)`, 17 | * or the arguments passed can be flat x,y values e.g. `new Polygon(x,y, x,y, x,y, ...)` where `x` 18 | * and `y` are Numbers. 19 | */ 20 | constructor(...points) 21 | { 22 | // if the first param is an array, ignore the rest. 23 | if (Array.isArray(points[0])) points = points[0]; 24 | 25 | // if this is an array of Vector2d, convert it to a flat array of numbers 26 | if (points[0] instanceof math.Vector2d) 27 | { 28 | const p = []; 29 | 30 | for (let i = 0; i < points.length; ++i) 31 | { 32 | p.push(points[i].x, points[i].y); 33 | } 34 | 35 | points = p; 36 | } 37 | 38 | /** 39 | * Whether or not this polygon is a "closed" polygon. 40 | * 41 | * @member {boolean} 42 | */ 43 | this.closed = true; 44 | 45 | /** 46 | * An array of the points of this polygon 47 | * 48 | * @member {number[]} 49 | */ 50 | this.points = points; 51 | } 52 | 53 | /** 54 | * Creates a clone of this polygon 55 | * 56 | * @return {Polygon} a copy of the polygon 57 | */ 58 | clone() 59 | { 60 | return new Polygon(this.points.slice()); 61 | } 62 | 63 | /** 64 | * Closes the polygon, adding points if necessary. 65 | */ 66 | close() 67 | { 68 | const points = this.points; 69 | 70 | // close the poly if the value is true! 71 | if (points[0] !== points[points.length - 2] || points[1] !== points[points.length - 1]) 72 | { 73 | points.push(points[0], points[1]); 74 | } 75 | } 76 | 77 | /** 78 | * Checks whether the x and y coordinates passed to this function are contained within this polygon 79 | * 80 | * @param {number} x - The X coordinate of the point to test 81 | * @param {number} y - The Y coordinate of the point to test 82 | * @return {boolean} Whether the x/y coordinates are within this polygon 83 | */ 84 | contains(x, y) 85 | { 86 | let inside = false; 87 | 88 | // use some raycasting to test hits 89 | // https://github.com/substack/point-in-polygon/blob/master/index.js 90 | const length = this.points.length / 2; 91 | 92 | for (let i = 0, j = length - 1; i < length; j = i++) 93 | { 94 | const xi = this.points[i * 2]; 95 | const yi = this.points[(i * 2) + 1]; 96 | const xj = this.points[j * 2]; 97 | const yj = this.points[(j * 2) + 1]; 98 | 99 | const intersect = ((yi > y) !== (yj > y)) 100 | && (x < (((xj - xi) * (y - yi)) / (yj - yi)) + xi); 101 | 102 | if (intersect) 103 | { 104 | inside = !inside; 105 | } 106 | } 107 | 108 | return inside; 109 | } 110 | 111 | /** 112 | * Checks if the passed polygon is equal to this one (has the same points). 113 | * 114 | * @param {Polygon} polygon - The polygon to check for equality. 115 | * @return {boolean} Whether polygons are equal. 116 | */ 117 | equals(polygon) 118 | { 119 | if (!polygon || this.points.length !== polygon.points.length) 120 | { 121 | return false; 122 | } 123 | 124 | for (let i = 0; i < this.points.length; ++i) 125 | { 126 | if (this.points[i] !== polygon.points[i]) 127 | { 128 | return false; 129 | } 130 | } 131 | 132 | return true; 133 | } 134 | } 135 | 136 | -------------------------------------------------------------------------------- /plugins/shapes/src/SpriteBoundsComponent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains code that was taken from, or heavily based upon, code 3 | * from the pixi.js project. Those sections are used under the terms of The 4 | * Pixi License, detailed below: 5 | * 6 | * The Pixi License 7 | * 8 | * Copyright (c) 2013-2016 Mathew Groves 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | */ 28 | import BoundsComponent from './BoundsComponent'; 29 | 30 | export default function SpriteBoundsComponent(Base) 31 | { 32 | /** 33 | * Component that adds sprite bounds calculations for sprite assemblages that also 34 | * want to include the BoundsComponent. 35 | * 36 | * @class SpriteBoundsComponent 37 | * @memberof sprites 38 | */ 39 | return class extends BoundsComponent(Base) 40 | { 41 | /** 42 | * Updates the bounds of this sprite. 43 | * 44 | * @private 45 | */ 46 | _updateBounds() 47 | { 48 | this._bounds.clear(); 49 | 50 | if (!this.visible || !this._texture || !this._texture.ready) return; 51 | 52 | const trim = this._texture.trim; 53 | const orig = this._texture.orig; 54 | 55 | if (!trim || (trim.width === orig.width && trim.height === orig.height)) 56 | { 57 | this._bounds.addQuad(this.vertexData); 58 | } 59 | else 60 | { 61 | const wt = this.transform.worldTransform; 62 | const a = wt.a; 63 | const b = wt.b; 64 | const c = wt.c; 65 | const d = wt.d; 66 | const tx = wt.tx; 67 | const ty = wt.ty; 68 | 69 | const w0 = (orig.width) * (1 - this._anchorX); 70 | const w1 = (orig.width) * -this._anchorX; 71 | 72 | const h0 = orig.height * (1 - this._anchorY); 73 | const h1 = orig.height * -this._anchorY; 74 | 75 | this._bounds.addQuad([ 76 | (a * w1) + (c * h1) + tx, 77 | (d * h1) + (b * w1) + ty, 78 | 79 | (a * w0) + (c * h1) + tx, 80 | (d * h1) + (b * w0) + ty, 81 | 82 | (a * w0) + (c * h0) + tx, 83 | (d * h0) + (b * w0) + ty, 84 | 85 | (a * w1) + (c * h0) + tx, 86 | (d * h0) + (b * w1) + ty, 87 | ]); 88 | } 89 | } 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /plugins/shapes/src/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace shapes */ 2 | 3 | export { default as BoundingBox } from './BoundingBox'; 4 | export { default as BoundsComponent } from './BoundsComponent'; 5 | export { default as Polygon } from './Polygon'; 6 | export { default as Rectangle } from './Rectangle'; 7 | export { default as SpriteBoundsComponent } from './SpriteBoundsComponent'; 8 | -------------------------------------------------------------------------------- /plugins/shapes/test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../.eslintrc.json", 3 | "env": { 4 | "commonjs": false, 5 | "mocha": true 6 | }, 7 | "globals": { 8 | "chai": false, 9 | "expect": false, 10 | "sinon": false, 11 | "Fae": false 12 | }, 13 | "parserOptions": { 14 | "sourceType": "script" 15 | }, 16 | "rules": { 17 | "strict": 0, 18 | "no-unused-expressions": 0, 19 | "func-names": 0, 20 | "no-var": 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugins/shapes/test/BoundingBox.test.js: -------------------------------------------------------------------------------- 1 | describe('shapes', function () 2 | { 3 | describe('BoundingBox', function () 4 | { 5 | describe('#ctor', function () 6 | { 7 | it('Constructs'); 8 | }); 9 | 10 | describe('#clear', function () 11 | { 12 | it('Clears'); 13 | }); 14 | 15 | describe('#addChild', function () 16 | { 17 | it('Adds a child'); 18 | }); 19 | 20 | describe('#addQuad', function () 21 | { 22 | it('Add a quad'); 23 | }); 24 | 25 | describe('#addVertices', function () 26 | { 27 | it('Add a vertex'); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /plugins/shapes/test/Rectangle.test.js: -------------------------------------------------------------------------------- 1 | describe('shapes', function () 2 | { 3 | describe('Rectangle', function () 4 | { 5 | describe('#ctor', function () 6 | { 7 | it('Constructs a default rectangle', function () 8 | { 9 | var r = new Fae.shapes.Rectangle(); 10 | 11 | expect(r).to.have.property('x', 0); 12 | expect(r).to.have.property('y', 0); 13 | expect(r).to.have.property('width', 0); 14 | expect(r).to.have.property('height', 0); 15 | 16 | expect(r).to.have.property('left', 0); 17 | expect(r).to.have.property('right', 0); 18 | expect(r).to.have.property('top', 0); 19 | expect(r).to.have.property('bottom', 0); 20 | }); 21 | 22 | it('Constructs a rectangle based on params', function () 23 | { 24 | var r = new Fae.shapes.Rectangle(1, 2, 3, 4); 25 | 26 | expect(r).to.have.property('x', 1); 27 | expect(r).to.have.property('y', 2); 28 | expect(r).to.have.property('width', 3); 29 | expect(r).to.have.property('height', 4); 30 | 31 | expect(r).to.have.property('left', 1); 32 | expect(r).to.have.property('right', 4); 33 | expect(r).to.have.property('top', 2); 34 | expect(r).to.have.property('bottom', 6); 35 | }); 36 | }); 37 | 38 | describe('#isEmpty', function () 39 | { 40 | it('Returns true when empty', function () 41 | { 42 | var r = new Fae.shapes.Rectangle(); 43 | 44 | expect(r.isEmpty()).to.equal(true); 45 | }); 46 | 47 | it('Returns true when empty but offset', function () 48 | { 49 | var r = new Fae.shapes.Rectangle(5, 5); 50 | 51 | expect(r.isEmpty()).to.equal(true); 52 | }); 53 | 54 | it('Returns false when not empty', function () 55 | { 56 | var r = new Fae.shapes.Rectangle(5, 5, 1, 0); 57 | 58 | expect(r.isEmpty()).to.equal(false); 59 | }); 60 | 61 | it('Returns false when not empty', function () 62 | { 63 | var r = new Fae.shapes.Rectangle(5, 5, 1, 1); 64 | 65 | expect(r.isEmpty()).to.equal(false); 66 | }); 67 | }); 68 | 69 | describe('#clone', function () 70 | { 71 | it('Clones'); 72 | }); 73 | 74 | describe('#copy', function () 75 | { 76 | it('Copies'); 77 | }); 78 | 79 | describe('#contains', function () 80 | { 81 | it('Contains'); 82 | }); 83 | 84 | describe('#union', function () 85 | { 86 | it('Unions'); 87 | }); 88 | 89 | describe('#intersection', function () 90 | { 91 | it('intersects'); 92 | }); 93 | 94 | describe('#inflate', function () 95 | { 96 | it('Inflates'); 97 | }); 98 | 99 | describe('#fit', function () 100 | { 101 | it('Fits'); 102 | }); 103 | 104 | describe('#equals', function () 105 | { 106 | it('Equals'); 107 | }); 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /plugins/sprites/README.md: -------------------------------------------------------------------------------- 1 | # Sprites 2 | 3 | The `sprites` plugin exposes an object that renders a `Texture` object from the `textures` 4 | plugin. The `SpriteRenderSystem` renders sprites in batches when it can by combining sprites 5 | that share a texture/blendMode into a single batch. 6 | 7 | ## Usage 8 | 9 | The sprite plugin provides a pre-made entity for rendering. It is a combonation of the 10 | following components: 11 | 12 | - `Fae.ecs.VisiblityComponent` - Defines whether or not to render 13 | - `Fae.transform.TransformComponent` - Defines where to render 14 | - `Fae.textures.TextureComponent` - Defines what to render 15 | - `Fae.sprites.SpriteComponent` - Defines how to render 16 | 17 | Here is an example usage of the pre-made sprite assemblage: 18 | 19 | ```js 20 | const img = new Image(); 21 | const texture = new Fae.textures.Texture(img); 22 | const sprite = new Fae.sprites.Sprite(texture); 23 | 24 | renderer.addEntity(sprite); 25 | renderer.render(); 26 | ``` 27 | 28 | It's that easy! 29 | -------------------------------------------------------------------------------- /plugins/sprites/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fae/sprites", 3 | "version": "0.0.1", 4 | "description": "Adds support for sprites.", 5 | "author": "Chad Engler ", 6 | "license": "MIT", 7 | "main": "src/index.js", 8 | "homepage": "https://github.com/Fae/fae#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Fae/fae.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/Fae/fae/issues" 15 | }, 16 | "files": [ 17 | "src/" 18 | ], 19 | "dependencies": { 20 | "@fae/core": "^1.0.0", 21 | "@fae/textures": "^1.0.0", 22 | "@fae/transform": "^1.0.0", 23 | "ismobilejs": "^0.4.0", 24 | "bit-twiddle": "^1.0.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plugins/sprites/src/Sprite.js: -------------------------------------------------------------------------------- 1 | import { ecs } from '@fae/core'; 2 | import { TransformComponent } from '@fae/transform'; 3 | import { Texture, TextureComponent } from '@fae/textures'; 4 | import SpriteComponent from './SpriteComponent'; 5 | import SpriteRenderer from './SpriteRenderer'; 6 | 7 | /** 8 | * A Sprite is a textured entity. It is implemented as a quad 9 | * with a texture drawn on it. 10 | * 11 | * @class 12 | * @mixes VisibilityComponent 13 | * @mixes TransformComponent 14 | * @mixes SpriteComponent 15 | * @memberof sprites 16 | */ 17 | export default class Sprite extends ecs.Entity.with( 18 | ecs.VisibilityComponent, // whether or not to render 19 | TransformComponent, // where to render 20 | TextureComponent, // what to render 21 | SpriteComponent // how to render 22 | ) 23 | { 24 | /** 25 | * 26 | * @param {Texture} texture - The texture to use. 27 | */ 28 | constructor(texture = Texture.EMPTY) 29 | { 30 | super(); 31 | 32 | this.renderGroupHint = SpriteRenderer; 33 | 34 | this._onTextureUpdateBinding = null; 35 | 36 | // run texture component setter 37 | this.texture = texture; 38 | } 39 | 40 | /** 41 | * Destroys the sprite. 42 | * 43 | * @param {object|boolean} options - A boolean value will act as if all options are set to that value. 44 | * @param {boolean} options.texture=false - If true the texture is also destroyed. 45 | * @param {boolean} options.baseTexture=false - If true the texture's base texture is also destroyed. 46 | */ 47 | destroy(options = false) 48 | { 49 | super.destroy(options); 50 | 51 | const destroyTexture = typeof options === 'boolean' ? options : options && options.texture; 52 | 53 | if (destroyTexture) 54 | { 55 | this.texture.destroy(options); 56 | } 57 | 58 | this.tint = null; 59 | this.blendMode = null; 60 | this.shader = null; 61 | this.vertexData = null; 62 | 63 | this._texture = null; 64 | 65 | if (this._onTextureUpdateBinding) 66 | { 67 | this._onTextureUpdateBinding.detach(); 68 | this._onTextureUpdateBinding = null; 69 | } 70 | } 71 | 72 | /** 73 | * Called by the texture component when the texture changes. 74 | * 75 | * @protected 76 | */ 77 | _onTextureChange() 78 | { 79 | if (this._onTextureUpdateBinding) 80 | { 81 | this._onTextureUpdateBinding.detach(); 82 | this._onTextureUpdateBinding = null; 83 | } 84 | 85 | this._vertsDirty = true; 86 | 87 | if (this._texture) 88 | { 89 | this._onTextureUpdateBinding = this._texture.onUpdate.add(this._onTextureUpdate, this); 90 | 91 | if (this._texture.ready) 92 | { 93 | this._onTextureUpdate(); 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * Called when the underlying texture updates. 100 | * 101 | * @private 102 | */ 103 | _onTextureUpdate() 104 | { 105 | this._vertsDirty = true; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /plugins/sprites/src/SpriteComponent.js: -------------------------------------------------------------------------------- 1 | import { util } from '@fae/core'; 2 | 3 | export default function SpriteComponent(Base) 4 | { 5 | /** 6 | * @class SpriteComponent 7 | * @memberof sprites 8 | */ 9 | return class extends Base 10 | { 11 | /** 12 | * 13 | */ 14 | constructor() 15 | { 16 | super(...arguments); // eslint-disable-line prefer-rest-params 17 | 18 | /** 19 | * The tint to apply to the sprite. A value of white will render without 20 | * any tinting. This works because this color value is multiplied against 21 | * the actual color value in the texture (per pixel). 22 | * 23 | * @member {Color} 24 | */ 25 | this.tint = util.Color.WHITE.clone(); 26 | 27 | /** 28 | * The blend mode to be applied to the sprite. 29 | * 30 | * @member {BlendMode} 31 | * @default BlendMode.NORMAL 32 | */ 33 | this.blendMode = util.BlendMode.NORMAL; 34 | 35 | /** 36 | * The vertex data used to draw the sprite. 37 | * 38 | * @readonly 39 | * @member {Float32Array} 40 | */ 41 | this.vertexData = new Float32Array(8); 42 | 43 | /** 44 | * Custom shader to use for drawing instead of the built-in batched shader. 45 | * Warning: Setting this property will break the batch of sprites as it 46 | * will need to be drawn in isolation. 47 | * 48 | * @member {Shader|GLShader} 49 | */ 50 | this.shader = null; 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /plugins/sprites/src/SpriteRenderSystem.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains code that was taken from, or heavily based upon, code 3 | * from the pixi.js project. Those sections are used under the terms of The 4 | * Pixi License, detailed below: 5 | * 6 | * The Pixi License 7 | * 8 | * Copyright (c) 2013-2016 Mathew Groves 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | */ 28 | import { ecs, render } from '@fae/core'; 29 | import { TransformComponent } from '@fae/transform'; 30 | import { TextureComponent } from '@fae/textures'; 31 | import SpriteRenderer from './SpriteRenderer'; 32 | import SpriteComponent from './SpriteComponent'; 33 | 34 | /** 35 | * @class 36 | * @memberof sprites 37 | */ 38 | export default class SpriteRenderSystem extends ecs.System 39 | { 40 | /** 41 | * @param {Renderer} renderer - The renderer to use. 42 | * @param {number} priority - The priority of the system, higher means earlier. 43 | * @param {number} frequency - How often to run the update loop. `1` means every 44 | * time, `2` is every other time, etc. 45 | */ 46 | constructor(renderer, priority = SpriteRenderSystem.defaultPriority, frequency = 1) 47 | { 48 | super(renderer, priority, frequency); 49 | 50 | /** 51 | * The sprite renderer instance to use to draw/batch sprites. 52 | * 53 | * @member {SpriteRenderer} 54 | */ 55 | this.spriteRenderer = new SpriteRenderer(renderer); 56 | } 57 | 58 | /** 59 | * Returns true if the entity is eligible to the system, false otherwise. 60 | * 61 | * @param {Entity} entity - The entity to test. 62 | * @return {boolean} True if entity should be included. 63 | */ 64 | test(entity) 65 | { 66 | return entity.hasComponents( 67 | ecs.VisibilityComponent, // whether or not to render 68 | TransformComponent, // where to render 69 | TextureComponent, // what to render 70 | SpriteComponent // how to render 71 | ); 72 | } 73 | 74 | /** 75 | * Render a sprite using the batching SpriteRenderer. 76 | * 77 | * @param {Entity} sprite - The entity to render. 78 | */ 79 | update(sprite) 80 | { 81 | if (!sprite.visible) return; 82 | 83 | this.renderer.setActiveObjectRenderer(this.spriteRenderer); 84 | 85 | const clean = !sprite._anchorDirty && sprite._cachedTransformUpdateId === sprite.transform._worldUpdateId; 86 | 87 | if (sprite._texture.ready && !clean) 88 | { 89 | calculateVertices(sprite); 90 | sprite._cachedTransformUpdateId = sprite.transform._worldUpdateId; 91 | sprite._anchorDirty = false; 92 | } 93 | 94 | this.spriteRenderer.render(sprite); 95 | } 96 | } 97 | 98 | render.Renderer.addDefaultSystem(SpriteRenderSystem); 99 | 100 | /** 101 | * Updates the vertices of the entity. 102 | * 103 | * @ignore 104 | * @param {Entity} sprite - The sprite to update. 105 | */ 106 | function calculateVertices(sprite) 107 | { 108 | // set the vertex data 109 | const wt = sprite.transform.worldTransform; 110 | const a = wt.a; 111 | const b = wt.b; 112 | const c = wt.c; 113 | const d = wt.d; 114 | const tx = wt.tx; 115 | const ty = wt.ty; 116 | const vertexData = sprite.vertexData; 117 | const trim = sprite._texture.trim; 118 | const orig = sprite._texture.orig; 119 | 120 | let w0; 121 | let w1; 122 | let h0; 123 | let h1; 124 | 125 | if (trim) 126 | { 127 | // if the sprite is trimmed then we need to add the extra space 128 | // before transforming the sprite coords. 129 | w1 = trim.x - (sprite._anchorX * orig.width); 130 | w0 = w1 + trim.width; 131 | 132 | h1 = trim.y - (sprite._anchorY * orig.height); 133 | h0 = h1 + trim.height; 134 | } 135 | else 136 | { 137 | w0 = (orig.width) * (1 - sprite._anchorX); 138 | w1 = (orig.width) * -sprite._anchorX; 139 | 140 | h0 = orig.height * (1 - sprite._anchorY); 141 | h1 = orig.height * -sprite._anchorY; 142 | } 143 | 144 | // xy 145 | vertexData[0] = (a * w1) + (c * h1) + tx; 146 | vertexData[1] = (d * h1) + (b * w1) + ty; 147 | 148 | // xy 149 | vertexData[2] = (a * w0) + (c * h1) + tx; 150 | vertexData[3] = (d * h1) + (b * w0) + ty; 151 | 152 | // xy 153 | vertexData[4] = (a * w0) + (c * h0) + tx; 154 | vertexData[5] = (d * h0) + (b * w0) + ty; 155 | 156 | // xy 157 | vertexData[6] = (a * w1) + (c * h0) + tx; 158 | vertexData[7] = (d * h0) + (b * w1) + ty; 159 | } 160 | 161 | /** 162 | * @static 163 | * @constant 164 | * @member {number} 165 | * @default 1000 166 | */ 167 | SpriteRenderSystem.defaultPriority = ecs.System.PRIORITY.RENDER; 168 | -------------------------------------------------------------------------------- /plugins/sprites/src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0 */ 2 | /** @namespace sprites */ 3 | 4 | export { default as Sprite } from './Sprite'; 5 | export { default as SpriteRenderer } from './SpriteRenderer'; 6 | export { default as SpriteComponent } from './SpriteComponent'; 7 | export { default as SpriteRenderSystem } from './SpriteRenderSystem'; 8 | 9 | export const shaders = { 10 | texture: { 11 | vert: require('./shader/multi-texture.vert'), 12 | frag: require('./shader/multi-texture.frag'), 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /plugins/sprites/src/shader/multi-texture.frag: -------------------------------------------------------------------------------- 1 | #define TEXTURE_COUNT {{count}} 2 | 3 | varying vec2 vTextureCoord; 4 | varying vec4 vColor; 5 | varying float vTextureId; 6 | 7 | uniform sampler2D uSamplers[TEXTURE_COUNT]; 8 | 9 | void main(void) 10 | { 11 | vec4 color; 12 | float textureId = floor(vTextureId + 0.5); 13 | 14 | {{texture_choice}} 15 | 16 | gl_FragColor = color * vColor; 17 | } 18 | -------------------------------------------------------------------------------- /plugins/sprites/src/shader/multi-texture.vert: -------------------------------------------------------------------------------- 1 | attribute vec2 aVertexPosition; 2 | attribute vec2 aTextureCoord; 3 | attribute vec4 aColor; 4 | attribute float aTextureId; 5 | 6 | uniform mat3 uProjectionMatrix; 7 | 8 | varying vec2 vTextureCoord; 9 | varying vec4 vColor; 10 | varying float vTextureId; 11 | 12 | void main(void){ 13 | gl_Position = vec4((uProjectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0); 14 | 15 | vTextureCoord = aTextureCoord; 16 | vTextureId = aTextureId; 17 | vColor = vec4(aColor.rgb * aColor.a, aColor.a); 18 | } 19 | -------------------------------------------------------------------------------- /plugins/text-bitmap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fae/text-bitmap", 3 | "version": "1.0.0", 4 | "description": "Adds support for bitmap text via the sprites plugin", 5 | "author": "Chad Engler ", 6 | "license": "MIT", 7 | "main": "src/index.js", 8 | "homepage": "https://github.com/Fae/fae#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Fae/fae.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/Fae/fae/issues" 15 | }, 16 | "files": [ 17 | "src/" 18 | ], 19 | "dependencies": { 20 | "@fae/core": "^1.0.0", 21 | "@fae/sprites": "^1.0.0", 22 | "mini-signals": "^1.1.1" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /plugins/text-bitmap/src/BitmapTextComponent.js: -------------------------------------------------------------------------------- 1 | export default function BitmapTextComponent(Base) 2 | { 3 | /** 4 | * @class BitmapTextComponent 5 | * @memberof text-bitmap 6 | */ 7 | return class extends Base 8 | { 9 | /** 10 | * 11 | */ 12 | constructor() 13 | { 14 | super(...arguments); // eslint-disable-line prefer-rest-params 15 | 16 | /** 17 | * Private tracker for the letter sprite pool. 18 | * 19 | * @private 20 | * @member {Array<*>} 21 | */ 22 | this._glyphs = []; 23 | 24 | /** 25 | * Private tracker for the current text. 26 | * 27 | * @private 28 | * @member {string} 29 | */ 30 | this._text = ''; 31 | 32 | /** 33 | * The max width of this bitmap text in pixels. If the text provided is longer than 34 | * the value provided, line breaks will be automatically inserted in the last whitespace. 35 | * Disable by setting value to 0. 36 | * 37 | * @member {number} 38 | */ 39 | this.maxWidth = 0; 40 | 41 | /** 42 | * The max line height. This is useful when trying to use the total height of the Text, 43 | * ie: when trying to vertically align. 44 | * 45 | * @member {number} 46 | */ 47 | this.maxLineHeight = 0; 48 | } 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /plugins/text-bitmap/src/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace text-bitmap */ 2 | 3 | export { default as BitmapTextComponent } from './BitmapTextComponent'; 4 | -------------------------------------------------------------------------------- /plugins/text-bitmap/test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../.eslintrc.json", 3 | "env": { 4 | "commonjs": false, 5 | "mocha": true 6 | }, 7 | "globals": { 8 | "chai": false, 9 | "expect": false, 10 | "sinon": false, 11 | "Fae": false 12 | }, 13 | "parserOptions": { 14 | "sourceType": "script" 15 | }, 16 | "rules": { 17 | "strict": 0, 18 | "no-unused-expressions": 0, 19 | "func-names": 0, 20 | "no-var": 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugins/text-canvas/README.md: -------------------------------------------------------------------------------- 1 | # text-canvas 2 | 3 | The canvas text plugin contains helpers to draw text to a canvas element. It handles spacing, word 4 | wrapping, text shadowing, and other useful functions. 5 | 6 | ## Usage 7 | 8 | In the most basic of usage you just create a writer and write some text: 9 | 10 | ```js 11 | const writer = new Fae.text_canvas.CanvasTextWriter(); 12 | 13 | // draws "Hello World!" to the canvas at `writer.canvas` 14 | writer.write('Hello World!'); 15 | 16 | ``` 17 | 18 | You can also pass your own canvas in if you want to draw to a specific canvas element. 19 | 20 | ```js 21 | const writer = new Fae.text_canvas.CanvasTextWriter(document.getElementById('my-canvas')); 22 | 23 | // draws "Hello World!" to the canvas at `writer.canvas`, which is also the canvas 24 | // element that was passed as the constructor argument. 25 | writer.write('Hello World!'); 26 | ``` 27 | 28 | You can also create custom styles with the `CanvasTextStyle` class: 29 | 30 | ```js 31 | const style = new Fae.text_canvas.CanvasTextStyle(); 32 | const writer = new Fae.text_canvas.CanvasTextWriter(); 33 | 34 | style.fillStyle = 'red'; 35 | 36 | // draws "Hello World!" to the canvas at `writer.canvas`, in red this time. 37 | writer.write('Hello World!', style); 38 | ``` 39 | 40 | ## Rendering in the Scene 41 | 42 | Rendering the text you draw to a canvas in the Fae renderer is trivial when using the `sprites` and 43 | `textures` plugins, since a `Texture` can take a canvas as the source: 44 | 45 | ```js 46 | const writer = new Fae.text_canvas.CanvasTextWriter(); 47 | const texture = new Fae.textures.Texture(writer.canvas); 48 | const sprite = new Fae.sprites.Sprite(texture); 49 | 50 | // draws "Hello World!" to the canvas at `writer.canvas` 51 | writer.write('Hello World!'); 52 | texture.update(); // updates the texture on the gpu 53 | 54 | // render the scene 55 | renderer.addEntity(sprite); 56 | renderer.render(); 57 | ``` 58 | -------------------------------------------------------------------------------- /plugins/text-canvas/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fae/text-canvas", 3 | "version": "1.0.0", 4 | "description": "Adds support for text drawn via the Canvas2d API", 5 | "author": "Chad Engler ", 6 | "license": "MIT", 7 | "main": "src/index.js", 8 | "homepage": "https://github.com/Fae/fae#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Fae/fae.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/Fae/fae/issues" 15 | }, 16 | "files": [ 17 | "src/" 18 | ], 19 | "dependencies": { 20 | "@fae/core": "^1.0.0", 21 | "mini-signals": "^1.1.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /plugins/text-canvas/src/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace text-canvas */ 2 | 3 | export { default as CanvasTextWriter } from './CanvasTextWriter'; 4 | export { default as CanvasTextStyle } from './CanvasTextStyle'; 5 | -------------------------------------------------------------------------------- /plugins/text-canvas/test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../.eslintrc.json", 3 | "env": { 4 | "commonjs": false, 5 | "mocha": true 6 | }, 7 | "globals": { 8 | "chai": false, 9 | "expect": false, 10 | "sinon": false, 11 | "Fae": false 12 | }, 13 | "parserOptions": { 14 | "sourceType": "script" 15 | }, 16 | "rules": { 17 | "strict": 0, 18 | "no-unused-expressions": 0, 19 | "func-names": 0, 20 | "no-var": 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugins/textures/README.md: -------------------------------------------------------------------------------- 1 | # Textures 2 | 3 | The `textures` plugin adds utilities that make it easy to manage WebGL textures. This plugin 4 | exports two main classes; the `Texture` class and the `TextureSource` class. 5 | 6 | A `TextureSource` manages a drawing source. It supports any [CanvasImageSource][cis] 7 | (images, video, canvas, etc) or a `RenderTarget`. The job of the `TextureSource` is to manage 8 | the loaded state of the source and the underlying `GLTexture` that gets uploaded to the GPU. 9 | 10 | A `Texture` represents a rectangular frame of a given `TextureSource`. Its job is to 11 | manage the UVs of the texture so after a texture is uploaded, we know what portion of it to draw. 12 | 13 | Additionally this plugin contains a `TextureComponent` which adds a texture property to 14 | an entity along with anchor properties that describe the attachment of the texture to the entity. 15 | This plugin **does not** provide any methods to directly render textures. To render a 16 | texture object you will need to either use the `sprites` plugin or build your own rendering 17 | mechanism for `Texture`s. 18 | 19 | ## Usage 20 | 21 | If you pass a [CanvasImageSource][cis] or `RenderTarget` to the constructor of a `Texture` it 22 | will automatically create an underlying `TextureSource` for you. 23 | 24 | For example: 25 | 26 | ```js 27 | const img = new Image(); 28 | const texture = new Fae.textures.Texture(img); 29 | ``` 30 | 31 | This is nice for ease-of-use, but sometimes you need many textures for a single source. This is 32 | often the case with spritesheets. You have one source (an image) but many frames (textures). 33 | For these cases you can create the `TextureSource` yourself and reuse it for each `Texture` frame. 34 | 35 | For example: 36 | 37 | ```js 38 | const spritesheet = new Image(); 39 | 40 | const source = new Fae.textures.TextureSource(spritesheet); 41 | 42 | const frame1 = new Fae.textures.Texture(source, new Fae.shapes.Rectangle(0, 0, 10, 10)); 43 | const frame2 = new Fae.textures.Texture(source, new Fae.shapes.Rectangle(10, 0, 10, 10)); 44 | const frame3 = new Fae.textures.Texture(source, new Fae.shapes.Rectangle(20, 0, 10, 10)); 45 | ``` 46 | 47 | 48 | [cis]: (https://developer.mozilla.org/en-US/docs/Web/API/CanvasImageSource) 49 | -------------------------------------------------------------------------------- /plugins/textures/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fae/textures", 3 | "version": "0.0.1", 4 | "description": "Adds support for high-level texture management.", 5 | "author": "Chad Engler ", 6 | "license": "MIT", 7 | "main": "src/index.js", 8 | "homepage": "https://github.com/Fae/fae#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Fae/fae.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/Fae/fae/issues" 15 | }, 16 | "files": [ 17 | "src/" 18 | ], 19 | "dependencies": { 20 | "@fae/core": "^1.0.0", 21 | "@fae/shapes": "^1.0.0", 22 | "mini-signals": "^1.1.1", 23 | "bit-twiddle": "^1.0.2" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /plugins/textures/src/TextureComponent.js: -------------------------------------------------------------------------------- 1 | export default function TextureComponent(Base) 2 | { 3 | /** 4 | * @class TextureComponent 5 | * @memberof textures 6 | */ 7 | return class extends Base 8 | { 9 | /** 10 | * @memberof TextureComponent 11 | */ 12 | constructor() 13 | { 14 | super(...arguments); // eslint-disable-line prefer-rest-params 15 | 16 | /** 17 | * The texture object. 18 | * 19 | * @private 20 | * @member {Texture} 21 | */ 22 | this._texture = null; 23 | 24 | /** 25 | * The X coord of the attachment point of the texture. 26 | * 27 | * @private 28 | * @member {number} 29 | */ 30 | this._anchorX = 0; 31 | 32 | /** 33 | * The Y coord of the attachment point of the texture. 34 | * 35 | * @private 36 | * @member {number} 37 | */ 38 | this._anchorY = 0; 39 | 40 | /** 41 | * Tracker for if the vertices are dirty. 42 | * 43 | * @private 44 | * @member {number} 45 | */ 46 | this._anchorDirty = true; 47 | } 48 | 49 | /** 50 | * The texture to use. 51 | * 52 | * @member {Texture} 53 | */ 54 | get texture() 55 | { 56 | return this._texture; 57 | } 58 | 59 | /** 60 | * @param {Texture} v - The new texture. 61 | */ 62 | set texture(v) 63 | { 64 | if (this._texture === v) return; 65 | 66 | this._texture = v; 67 | 68 | this._onTextureChange(); 69 | } 70 | 71 | /** 72 | * The X coord of the attachment point of the texture. 73 | * 74 | * @member {number} 75 | */ 76 | get anchorX() 77 | { 78 | return this._anchorX; 79 | } 80 | 81 | /** 82 | * @param {number} v - The new anchor. 83 | */ 84 | set anchorX(v) 85 | { 86 | if (this._anchorX === v) return; 87 | 88 | this._anchorX = v; 89 | this._anchorDirty = true; 90 | } 91 | 92 | /** 93 | * The Y coord of the attachment point of the texture. 94 | * 95 | * @member {number} 96 | */ 97 | get anchorY() 98 | { 99 | return this._anchorY; 100 | } 101 | 102 | /** 103 | * @param {number} v - The new anchor. 104 | */ 105 | set anchorY(v) 106 | { 107 | if (this._anchorY === v) return; 108 | 109 | this._anchorY = v; 110 | this._anchorDirty = true; 111 | } 112 | 113 | /** 114 | * Called when the underlying texture changes. 115 | * 116 | * @protected 117 | * @abstract 118 | */ 119 | _onTextureChange() 120 | { 121 | /* empty */ 122 | } 123 | }; 124 | } 125 | -------------------------------------------------------------------------------- /plugins/textures/src/TextureUVs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains code that was taken from, or heavily based upon, code 3 | * from the pixi.js project. Those sections are used under the terms of The 4 | * Pixi License, detailed below: 5 | * 6 | * The Pixi License 7 | * 8 | * Copyright (c) 2013-2016 Mathew Groves 9 | * 10 | * Permission is hereby granted, free of charge, to any person obtaining a copy 11 | * of this software and associated documentation files (the "Software"), to deal 12 | * in the Software without restriction, including without limitation the rights 13 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | * copies of the Software, and to permit persons to whom the Software is 15 | * furnished to do so, subject to the following conditions: 16 | * 17 | * The above copyright notice and this permission notice shall be included in 18 | * all copies or substantial portions of the Software. 19 | * 20 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 26 | * THE SOFTWARE. 27 | */ 28 | 29 | /** 30 | * Helper to store and calculate the UVs of a texture. 31 | * 32 | * @class 33 | * @memberof textures 34 | */ 35 | export default class TextureUVs 36 | { 37 | /** 38 | * 39 | */ 40 | constructor() 41 | { 42 | this.x0 = 0; 43 | this.y0 = 0; 44 | 45 | this.x1 = 1; 46 | this.y1 = 0; 47 | 48 | this.x2 = 1; 49 | this.y2 = 1; 50 | 51 | this.x3 = 0; 52 | this.y3 = 1; 53 | 54 | this.uvsUint32 = new Uint32Array(4); 55 | } 56 | 57 | /** 58 | * Calculates the UVs based on the given frames. 59 | * 60 | * @param {Rectangle} frame - The frame of the region in the texture. 61 | * @param {Rectangle} baseFrame - The frame of the full texture. 62 | * @param {number} rotation - Rotation of frame, in radians. 63 | * @private 64 | */ 65 | set(frame, baseFrame, rotation) 66 | { 67 | const tw = baseFrame.width; 68 | const th = baseFrame.height; 69 | 70 | this.x0 = frame.x / tw; 71 | this.y0 = frame.y / th; 72 | 73 | this.x1 = (frame.x + frame.width) / tw; 74 | this.y1 = frame.y / th; 75 | 76 | this.x2 = (frame.x + frame.width) / tw; 77 | this.y2 = (frame.y + frame.height) / th; 78 | 79 | this.x3 = frame.x / tw; 80 | this.y3 = (frame.y + frame.height) / th; 81 | 82 | if (rotation) 83 | { 84 | // coordinates of center 85 | const cx = (frame.x / tw) + (frame.width / 2 / tw); 86 | const cy = (frame.y / th) + (frame.height / 2 / th); 87 | 88 | // rotation values 89 | const sr = Math.sin(rotation); 90 | const cr = Math.cos(rotation); 91 | 92 | this.x0 = cx + (((this.x0 - cx) * cr) - ((this.y0 - cy) * sr)); 93 | this.y0 = cy + (((this.x0 - cx) * sr) + ((this.y0 - cy) * cr)); 94 | 95 | this.x1 = cx + (((this.x1 - cx) * cr) - ((this.y1 - cy) * sr)); 96 | this.y1 = cy + (((this.x1 - cx) * sr) + ((this.y1 - cy) * cr)); 97 | 98 | this.x2 = cx + (((this.x2 - cx) * cr) - ((this.y2 - cy) * sr)); 99 | this.y2 = cy + (((this.x2 - cx) * sr) + ((this.y2 - cy) * cr)); 100 | 101 | this.x3 = cx + (((this.x3 - cx) * cr) - ((this.y3 - cy) * sr)); 102 | this.y3 = cy + (((this.x3 - cx) * sr) + ((this.y3 - cy) * cr)); 103 | } 104 | 105 | this.uvsUint32[0] = (((this.y0 * 65535) & 0xFFFF) << 16) | ((this.x0 * 65535) & 0xFFFF); 106 | this.uvsUint32[1] = (((this.y1 * 65535) & 0xFFFF) << 16) | ((this.x1 * 65535) & 0xFFFF); 107 | this.uvsUint32[2] = (((this.y2 * 65535) & 0xFFFF) << 16) | ((this.x2 * 65535) & 0xFFFF); 108 | this.uvsUint32[3] = (((this.y3 * 65535) & 0xFFFF) << 16) | ((this.x3 * 65535) & 0xFFFF); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /plugins/textures/src/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace textures */ 2 | 3 | export { default as Texture } from './Texture'; 4 | export { default as TextureComponent } from './TextureComponent'; 5 | export { default as TextureSource } from './TextureSource'; 6 | export { default as TextureUVs } from './TextureUVs'; 7 | -------------------------------------------------------------------------------- /plugins/transform/README.md: -------------------------------------------------------------------------------- 1 | # Transform 2 | 3 | The `transform` plugin provides utilities for dealing with matrix transforms. This includes 4 | a component that adds a transform property to an entity and a system that updates the transform 5 | matrix of each entity each frame. 6 | 7 | ## Usage 8 | 9 | You can simply add the transform component to an entity and you will have access to familiar 10 | positional APIs like position, scale, rotation, and skew. 11 | 12 | For example: 13 | 14 | ```js 15 | class TransformableEntity extends Fae.ecs.Entity.with( 16 | Fae.transform.TransformComponent 17 | ) { } 18 | 19 | const ent = new TransformableEntity(); 20 | 21 | ent.transform.x = 0; // x position 22 | ent.transform.y = 0; // y position 23 | 24 | ent.transform.scaleX = 1; // x scale 25 | ent.transform.scaleY = 1; // y scale 26 | 27 | ent.transform.skewX = 0; // x skew 28 | ent.transform.skewY = 0; // y skew 29 | 30 | ent.transform.rotation = 0; // rotation angle (radians) 31 | 32 | // updates the underlying transform matrix based on the above properties. 33 | // normally you would just let the TransformUpdateSystem do this for you. 34 | ent.transform.update(); 35 | ``` 36 | -------------------------------------------------------------------------------- /plugins/transform/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@fae/transform", 3 | "version": "0.0.1", 4 | "description": "Adds support for object transform matrices.", 5 | "author": "Chad Engler ", 6 | "license": "MIT", 7 | "main": "src/index.js", 8 | "homepage": "https://github.com/Fae/fae#readme", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Fae/fae.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/Fae/fae/issues" 15 | }, 16 | "files": [ 17 | "src/" 18 | ], 19 | "dependencies": { 20 | "@fae/core": "^1.0.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /plugins/transform/src/TransformComponent.js: -------------------------------------------------------------------------------- 1 | import Transform from './Transform'; 2 | 3 | export default function TransformComponent(Base) 4 | { 5 | /** 6 | * @class TransformComponent 7 | * @memberof transform 8 | */ 9 | return class extends Base 10 | { 11 | /** 12 | * 13 | */ 14 | constructor() 15 | { 16 | super(...arguments); // eslint-disable-line prefer-rest-params 17 | 18 | /** 19 | * The transformation of the object. 20 | * 21 | * @member {Transform} 22 | */ 23 | this.transform = new Transform(); 24 | 25 | /** 26 | * The last transform update ID that this object has seen. 27 | * 28 | * @private 29 | * @member {number} 30 | */ 31 | this._cachedTransformUpdateId = -1; 32 | } 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /plugins/transform/src/TransformUpdateSystem.js: -------------------------------------------------------------------------------- 1 | import { ecs, render } from '@fae/core'; 2 | import TransformComponent from './TransformComponent'; 3 | 4 | // TODO: Transforms can have parents, but it is possible here to have 5 | // a child's update() called first before the parent updates. 6 | // Need to sort by parents in some efficient way. 7 | 8 | /** 9 | * @class 10 | * @memberof transform 11 | */ 12 | export default class TransformUpdateSystem extends ecs.System 13 | { 14 | /** 15 | * @param {Renderer} renderer - The renderer to use. 16 | * @param {number} priority - The priority of the system, higher means earlier. 17 | * @param {number} frequency - How often to run the update loop. `1` means every 18 | * time, `2` is every other time, etc. 19 | */ 20 | constructor(renderer, priority = ecs.System.PRIORITY.PLUGIN, frequency = 1) 21 | { 22 | super(renderer, priority, frequency); 23 | } 24 | 25 | /** 26 | * Returns true if the entity is eligible to the system, false otherwise. 27 | * 28 | * @param {Entity} entity - The entity to test. 29 | * @return {boolean} True if entity should be included. 30 | */ 31 | test(entity) 32 | { 33 | return entity.hasComponent(TransformComponent); 34 | } 35 | 36 | /** 37 | * Update the entity's transform matrix. 38 | * 39 | * @param {Entity} entity - The entity to update 40 | */ 41 | update(entity) 42 | { 43 | entity.transform.update(); 44 | } 45 | } 46 | 47 | render.Renderer.addDefaultSystem(TransformUpdateSystem); 48 | -------------------------------------------------------------------------------- /plugins/transform/src/index.js: -------------------------------------------------------------------------------- 1 | /** @namespace transform */ 2 | 3 | export { default as Transform } from './Transform'; 4 | export { default as TransformComponent } from './TransformComponent'; 5 | export { default as TransformUpdateSystem } from './TransformUpdateSystem'; 6 | --------------------------------------------------------------------------------