├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── data ├── fonts │ ├── DejaVuSans.ttf │ ├── Karla-Regular.ttf │ └── OFL.txt └── shaders │ ├── basic_ps.glsl │ ├── basic_vs.glsl │ ├── globals.glsl │ ├── gouraud_ps.glsl │ ├── gouraud_vs.glsl │ ├── lights.glsl │ ├── locals.glsl │ ├── pbr_ps.glsl │ ├── pbr_vs.glsl │ ├── phong_ps.glsl │ ├── phong_vs.glsl │ ├── quad_ps.glsl │ ├── quad_vs.glsl │ ├── shadow_ps.glsl │ ├── shadow_vs.glsl │ ├── skybox_ps.glsl │ ├── skybox_vs.glsl │ ├── sprite_ps.glsl │ └── sprite_vs.glsl ├── examples ├── anim.rs ├── aviator │ ├── README.md │ ├── main.rs │ ├── plane.rs │ ├── shot.png │ └── sky.rs ├── gltf-morph-targets.rs ├── gltf-node-animation.rs ├── gltf-pbr-shader.rs ├── gltf-vertex-skinning.rs ├── group.rs ├── lights.rs ├── materials.rs ├── mesh-update.rs ├── obj.rs ├── reload.rs ├── shapes.rs ├── sprite.rs ├── stl.rs ├── text.rs └── tutorial.rs ├── rustfmt.toml └── src ├── animation.rs ├── audio.rs ├── camera.rs ├── color.rs ├── controls ├── first_person.rs ├── mod.rs └── orbit.rs ├── custom.rs ├── data.rs ├── factory ├── load_gltf.rs └── mod.rs ├── geometry.rs ├── hub.rs ├── input ├── axis.rs ├── mod.rs └── timer.rs ├── lib.rs ├── light.rs ├── macros.rs ├── material.rs ├── mesh.rs ├── node.rs ├── object.rs ├── render ├── mod.rs ├── pso_data.rs └── source.rs ├── safe_float.rs ├── scene.rs ├── skeleton.rs ├── sprite.rs ├── template.rs ├── text.rs ├── texture.rs ├── util.rs └── window.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | 9 | # Configuration file for editors using the RLS. 10 | rls.toml 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test_data"] 2 | path = test_data 3 | url = https://github.com/three-rs/example-data 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: rust 3 | cache: cargo 4 | rust: 5 | - nightly 6 | - stable 7 | branches: 8 | except: 9 | - staging.tmp 10 | 11 | # Install ALSA development libraries before compiling on Linux. 12 | addons: 13 | apt: 14 | packages: 15 | - libasound2-dev 16 | 17 | notifications: 18 | webhooks: 19 | urls: 20 | - https://webhooks.gitter.im/e/fc87a993d7683ec8b279 21 | on_success: change 22 | on_failure: always 23 | on_start: never 24 | 25 | script: 26 | - cargo test --no-default-features --features opengl 27 | - cargo test --all-features 28 | - cargo doc 29 | # - if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then (cargo bench); fi 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Change Log 2 | 3 | ### v0.4 (11 Jan 2019) 4 | - glTF templates 5 | - sprite instancing 6 | 7 | ### v0.3 (24 Feb 2018) 8 | - parent objects now hold children alive, not the other way around 9 | - PBR materials, custom pipelines 10 | - glTF scene loader, including animation curves 11 | - animation framework with vertex skinning and morphing 12 | - instanced rendering 13 | - text objects 14 | - skybox and background texture support 15 | - richer input API 16 | - gltf examples: PBR shader, node animations, morpth targets, vertex skinning 17 | 18 | ### v0.2 (06 Jul 2017) 19 | - basic documentation 20 | - [Mint](https://github.com/kvark/mint)-flavored math API 21 | - revamped input API, orbital camera controller 22 | - blend shape animations 23 | - optimized scene graph and renderer 24 | - examples: anim, group (cube-ception) 25 | 26 | ### v0.1 (08 Jun 2017) 27 | - scene graph built with [Froggy](https://github.com/kvark/froggy) 28 | - renderer with [gfx-rs](https://github.com/gfx-rs/gfx/tree/pre-ll) 29 | - cameras: perspective, ortho 30 | - window and input handling 31 | - Wavefront OBJ loader 32 | - texture loader 33 | - examples: lights, materials, obj, shapes, sprite, basic aviator 34 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "three" 3 | version = "0.4.0" 4 | authors = ["Three-rs Developers"] 5 | license = "MIT/Apache-2.0" 6 | description = "Three.js inspired 3D engine in Rust" 7 | categories = ["rendering", "game-engines"] 8 | keywords = ["gamedev", "graphics", "engine", "3D"] 9 | homepage = "https://github.com/three-rs/three" 10 | repository = "https://github.com/three-rs/three" 11 | documentation = "https://docs.rs/three/" 12 | build = "build.rs" 13 | exclude = ["doc/*", "bors.toml", ".travis.yml", "test_data/*"] 14 | 15 | [lib] 16 | 17 | [features] 18 | default = ["opengl", "audio"] 19 | opengl = ["gfx_device_gl", "gfx_window_glutin", "glutin"] 20 | audio = ["rodio"] 21 | 22 | [build-dependencies] 23 | includedir_codegen = "0.5" 24 | 25 | [dependencies] 26 | arrayvec = "0.4" 27 | bitflags = "1" 28 | cgmath = { version = "0.16", features = ["mint"] } 29 | derivative = "1.0" 30 | froggy = "0.4.4" 31 | genmesh = "0.6" 32 | gfx = "0.18.1" 33 | gfx_glyph = "0.15.0" 34 | gltf = { features = ["names", "utils", "import"], optional = true, version = "0.15.2" } 35 | image = "0.23" 36 | includedir = "0.5" 37 | itertools = "0.8" 38 | log = "0.4" 39 | obj = { version = "0.10", features = ["genmesh"] } 40 | phf = "0.7.12" 41 | quick-error = "1.2" 42 | rodio = { version = "0.8", optional = true } 43 | mint = "0.5" 44 | vec_map = "0.8" 45 | stlv = "0.1.3" 46 | 47 | # OpenGL 48 | gfx_device_gl = { version = "0.16.2", optional = true } 49 | gfx_window_glutin = { version = "0.31.0", optional = true } 50 | glutin = { version = "0.21.1", optional = true } 51 | 52 | [dev-dependencies] 53 | env_logger = "0.6" 54 | notify = "4" 55 | rand = "0.6" 56 | 57 | [[example]] 58 | name = "lights" 59 | 60 | [[example]] 61 | name = "materials" 62 | 63 | [[example]] 64 | name = "mesh-update" 65 | 66 | [[example]] 67 | name = "obj" 68 | 69 | [[example]] 70 | name = "reload" 71 | 72 | [[example]] 73 | name = "shapes" 74 | 75 | [[example]] 76 | name = "sprite" 77 | 78 | [[example]] 79 | name = "tutorial" 80 | 81 | [[example]] 82 | name = "group" 83 | 84 | [[example]] 85 | name = "anim" 86 | 87 | [[example]] 88 | name = "text" 89 | 90 | [[example]] 91 | name = "stl" 92 | 93 | [[example]] 94 | name = "aviator" 95 | path = "examples/aviator/main.rs" 96 | 97 | [[example]] 98 | name = "gltf-pbr-shader" 99 | required-features = ["gltf"] 100 | 101 | [[example]] 102 | name = "gltf-node-animation" 103 | required-features = ["gltf"] 104 | 105 | [[example]] 106 | name = "gltf-vertex-skinning" 107 | required-features = ["gltf"] 108 | 109 | [[example]] 110 | name = "gltf-morph-targets" 111 | required-features = ["gltf"] 112 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Dzmitry Malyshau 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # three-rs 2 | [![Build Status](https://travis-ci.org/three-rs/three.svg)](https://travis-ci.org/three-rs/three) 3 | [![Docs](https://docs.rs/three/badge.svg)](https://docs.rs/three) 4 | [![Crates.io](https://img.shields.io/crates/v/three.svg?maxAge=2592000)](https://crates.io/crates/three) 5 | [![Gitter](https://badges.gitter.im/kvark/three-rs.svg)](https://gitter.im/three-rs/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 6 | 7 | NOTE: project is abandoned for being stuck with an old graphics stack (gfx-rs pre-ll). It needs to either be ported to wgpu (see https://github.com/three-rs/three/issues/222), or written anew, like https://github.com/kvark/baryon. 8 | 9 | Totally not inspired Rust 3D library! Ok, maybe, just a tiny bit... by [Three.JS](http://threejs.org). 10 | 11 | If you a looking for something simple to prototype 3D graphics with, you found it. 12 | 13 | ## Screenshots 14 | 15 | ![Aviator](examples/aviator/shot.png) 16 | ![CarObj](https://raw.githubusercontent.com/three-rs/example-data/5d821bc9647a8db26888f8dd286f318eaabd2a52/obj-car.png) 17 | ![glTF-skinning](https://user-images.githubusercontent.com/107301/36550245-d878073e-17c2-11e8-87ca-68299dfff775.png) 18 | 19 | ## Motivation and Goals 20 | 21 | Three-rs is focused at the ergonomics first and foremost. Being able to prototype quickly and code intuitively is more important than capturing all the 3D features. We already have a solid foundation with [gfx-rs](https://github.com/gfx-rs/gfx/tree/pre-ll), so let's make some use of it by providing a nice higher-level abstraction. 22 | 23 | We do leave the room for optimization opportunity here. Given the low-level declarative style of the API, the implementation can do a lot of tricks to ensure decent performance by default: 24 | - use of efficient low-level native graphics APIs 25 | - reordering the calls into passes, minimizing state changes 26 | - automatic instancing 27 | 28 | Interestingly, you can observe an new trend in rust-gamedev community to mimic existing libraries: [Amethyst](https://github.com/amethyst/amethyst) was created to be Rust version of Autodesk [Stingray](https://www.autodesk.com/products/stingray/overview) engine (formely Bitsquid), [ggez](https://github.com/ggez/ggez) is a re-interpretation of Love2D engine, and now `three-rs` attempting to capture _Three.JS_ usability. This is not to say that we failed to find [our own path](https://users.rust-lang.org/t/game-engine-design-lego-bricks/9151), I just see the current step inevitable. 29 | 30 | ## Examples 31 | 32 | ### Init submodule 33 | 34 | ``` 35 | git submodule update --init test_data 36 | ``` 37 | 38 | ### Build 39 | 40 | ``` 41 | cargo build --example 42 | ``` 43 | 44 | ### Run 45 | ``` 46 | cargo run --example 47 | ``` 48 | 49 | 50 | ## License 51 | 52 | Licensed under either of the following terms at your choice: 53 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 54 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 55 | 56 | ### Contribution 57 | 58 | Unless you explicitly state otherwise, any contribution intentionally submitted 59 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be 60 | dual licensed as above, without any additional terms or conditions. 61 | 62 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate includedir_codegen; 2 | 3 | const SHADERS: &str = "data/shaders"; 4 | 5 | fn main() { 6 | includedir_codegen::start("FILES") 7 | .dir(SHADERS, includedir_codegen::Compression::Gzip) 8 | .build("data.rs") 9 | .unwrap(); 10 | } 11 | -------------------------------------------------------------------------------- /data/fonts/DejaVuSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/three-rs/three/6403b70f23892db2d5b14c634e404fd2afb67889/data/fonts/DejaVuSans.ttf -------------------------------------------------------------------------------- /data/fonts/Karla-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/three-rs/three/6403b70f23892db2d5b14c634e404fd2afb67889/data/fonts/Karla-Regular.ttf -------------------------------------------------------------------------------- /data/fonts/OFL.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2012, Jonathan Pinhorn (jonpinhorn.typedesign@gmail.com), with Reserved Font Names 'Karla' 2 | 3 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 4 | This license is copied below, and is also available with a FAQ at: 5 | http://scripts.sil.org/OFL 6 | 7 | 8 | ----------------------------------------------------------- 9 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 10 | ----------------------------------------------------------- 11 | 12 | PREAMBLE 13 | The goals of the Open Font License (OFL) are to stimulate worldwide 14 | development of collaborative font projects, to support the font creation 15 | efforts of academic and linguistic communities, and to provide a free and 16 | open framework in which fonts may be shared and improved in partnership 17 | with others. 18 | 19 | The OFL allows the licensed fonts to be used, studied, modified and 20 | redistributed freely as long as they are not sold by themselves. The 21 | fonts, including any derivative works, can be bundled, embedded, 22 | redistributed and/or sold with any software provided that any reserved 23 | names are not used by derivative works. The fonts and derivatives, 24 | however, cannot be released under any other type of license. The 25 | requirement for fonts to remain under this license does not apply 26 | to any document created using the fonts or their derivatives. 27 | 28 | DEFINITIONS 29 | "Font Software" refers to the set of files released by the Copyright 30 | Holder(s) under this license and clearly marked as such. This may 31 | include source files, build scripts and documentation. 32 | 33 | "Reserved Font Name" refers to any names specified as such after the 34 | copyright statement(s). 35 | 36 | "Original Version" refers to the collection of Font Software components as 37 | distributed by the Copyright Holder(s). 38 | 39 | "Modified Version" refers to any derivative made by adding to, deleting, 40 | or substituting -- in part or in whole -- any of the components of the 41 | Original Version, by changing formats or by porting the Font Software to a 42 | new environment. 43 | 44 | "Author" refers to any designer, engineer, programmer, technical 45 | writer or other person who contributed to the Font Software. 46 | 47 | PERMISSION & CONDITIONS 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 50 | redistribute, and sell modified and unmodified copies of the Font 51 | Software, subject to the following conditions: 52 | 53 | 1) Neither the Font Software nor any of its individual components, 54 | in Original or Modified Versions, may be sold by itself. 55 | 56 | 2) Original or Modified Versions of the Font Software may be bundled, 57 | redistributed and/or sold with any software, provided that each copy 58 | contains the above copyright notice and this license. These can be 59 | included either as stand-alone text files, human-readable headers or 60 | in the appropriate machine-readable metadata fields within text or 61 | binary files as long as those fields can be easily viewed by the user. 62 | 63 | 3) No Modified Version of the Font Software may use the Reserved Font 64 | Name(s) unless explicit written permission is granted by the corresponding 65 | Copyright Holder. This restriction only applies to the primary font name as 66 | presented to the users. 67 | 68 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 69 | Software shall not be used to promote, endorse or advertise any 70 | Modified Version, except to acknowledge the contribution(s) of the 71 | Copyright Holder(s) and the Author(s) or with their explicit written 72 | permission. 73 | 74 | 5) The Font Software, modified or unmodified, in part or in whole, 75 | must be distributed entirely under this license, and must not be 76 | distributed under any other license. The requirement for fonts to 77 | remain under this license does not apply to any document created 78 | using the Font Software. 79 | 80 | TERMINATION 81 | This license becomes null and void if any of the above conditions are 82 | not met. 83 | 84 | DISCLAIMER 85 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 86 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 87 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 88 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 89 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 90 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 91 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 92 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 93 | OTHER DEALINGS IN THE FONT SOFTWARE. 94 | -------------------------------------------------------------------------------- /data/shaders/basic_ps.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | in vec2 v_TexCoord; 4 | in vec4 v_Color; 5 | out vec4 Target0; 6 | 7 | uniform sampler2D t_Map; 8 | 9 | void main() { 10 | Target0 = v_Color * texture(t_Map, v_TexCoord); 11 | } 12 | -------------------------------------------------------------------------------- /data/shaders/basic_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | #include 3 | 4 | in vec4 a_Position; 5 | in vec4 a_Normal; 6 | in vec2 a_TexCoord; 7 | out vec2 v_TexCoord; 8 | out vec4 v_Color; 9 | 10 | in vec4 i_World0; 11 | in vec4 i_World1; 12 | in vec4 i_World2; 13 | in vec4 i_Color; 14 | in vec4 i_UvRange; 15 | 16 | void main() { 17 | mat4 m_World = transpose(mat4(i_World0, i_World1, i_World2, vec4(0.0, 0.0, 0.0, 1.0))); 18 | v_TexCoord = mix(i_UvRange.xy, i_UvRange.zw, a_TexCoord); 19 | v_Color = i_Color; 20 | gl_Position = u_ViewProj * m_World * a_Position; 21 | } 22 | -------------------------------------------------------------------------------- /data/shaders/globals.glsl: -------------------------------------------------------------------------------- 1 | layout(std140) uniform b_Globals { 2 | mat4 u_ViewProj; 3 | mat4 u_InverseProj; 4 | mat4 u_View; 5 | uint u_NumLights; 6 | }; 7 | -------------------------------------------------------------------------------- /data/shaders/gouraud_ps.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | in vec4 v_ResultColor; 4 | flat in vec4 v_ResultColorFlat; 5 | flat in float v_Smooth; 6 | in vec4 v_LightEval[2]; 7 | flat in vec4 v_LightEvalFlat[2]; 8 | in vec4 v_ShadowCoord[2]; 9 | 10 | out vec4 Target0; 11 | 12 | uniform sampler2DShadow t_Shadow0; 13 | uniform sampler2DShadow t_Shadow1; 14 | 15 | void main() { 16 | Target0 = mix(v_ResultColorFlat, v_ResultColor, v_Smooth); 17 | if (v_ShadowCoord[0].w != 0.0) { 18 | vec3 coord = v_ShadowCoord[0].xyz / v_ShadowCoord[0].w; 19 | float shadow = texture(t_Shadow0, 0.5 * coord + 0.5); 20 | Target0 += shadow * mix(v_LightEvalFlat[0], v_LightEval[0], v_Smooth); 21 | } 22 | if (v_ShadowCoord[1].w != 0.0) { 23 | vec3 coord = v_ShadowCoord[1].xyz / v_ShadowCoord[1].w; 24 | float shadow = texture(t_Shadow1, 0.5 * coord + 0.5); 25 | Target0 += shadow * mix(v_LightEvalFlat[1], v_LightEval[1], v_Smooth); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /data/shaders/gouraud_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | #include 3 | #include 4 | 5 | #define MAX_SHADOWS 2 6 | 7 | in vec4 a_Position; 8 | in vec4 a_Normal; 9 | out vec4 v_ResultColor; 10 | flat out vec4 v_ResultColorFlat; 11 | flat out float v_Smooth; 12 | out vec4 v_LightEval[MAX_SHADOWS]; 13 | flat out vec4 v_LightEvalFlat[MAX_SHADOWS]; 14 | out vec4 v_ShadowCoord[MAX_SHADOWS]; 15 | 16 | in vec4 i_World0; 17 | in vec4 i_World1; 18 | in vec4 i_World2; 19 | in vec4 i_MatParams; 20 | in vec4 i_Color; 21 | in vec4 i_UvRange; 22 | 23 | void main() { 24 | mat4 m_World = transpose(mat4(i_World0, i_World1, i_World2, vec4(0.0, 0.0, 0.0, 1.0))); 25 | vec4 world = m_World * a_Position; 26 | vec3 normal = normalize(mat3(m_World) * a_Normal.xyz); 27 | for(int i=0; i 0.0) { 41 | irradiance = mix(light.color_back, light.color, dot_nl*0.5 + 0.5); 42 | dot_nl = 0.0; 43 | } 44 | v_ResultColor += light.intensity.x * i_Color * irradiance; //ambient 45 | vec4 color = light.intensity.y * max(0.0, dot_nl) * i_Color * light.color; 46 | // compute shadow coordinates 47 | int shadow_index = light.shadow_params[0]; 48 | if (0 <= shadow_index && shadow_index < MAX_SHADOWS) { 49 | v_ShadowCoord[shadow_index] = light.projection * world; 50 | v_LightEval[shadow_index] = color; 51 | v_LightEvalFlat[shadow_index] = color; 52 | } else { 53 | v_ResultColor += color; 54 | } 55 | } 56 | 57 | v_ResultColorFlat = v_ResultColor; 58 | gl_Position = u_ViewProj * world; 59 | } 60 | -------------------------------------------------------------------------------- /data/shaders/lights.glsl: -------------------------------------------------------------------------------- 1 | #define MAX_LIGHTS 4U 2 | 3 | struct Light { 4 | mat4 projection; 5 | vec4 pos; 6 | vec4 dir; 7 | vec4 focus; 8 | vec4 color; 9 | vec4 color_back; 10 | vec4 intensity; 11 | ivec4 shadow_params; 12 | }; 13 | 14 | layout(std140) uniform b_Lights { 15 | Light u_Lights[MAX_LIGHTS]; 16 | }; 17 | -------------------------------------------------------------------------------- /data/shaders/locals.glsl: -------------------------------------------------------------------------------- 1 | layout(std140) uniform b_Locals { 2 | vec4 u_Color; 3 | vec4 u_MatParams; 4 | vec4 u_UvRange; 5 | mat4 u_World; 6 | }; 7 | -------------------------------------------------------------------------------- /data/shaders/pbr_ps.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | #include 3 | #include 4 | 5 | const int BASE_COLOR_MAP = 1 << 0; 6 | const int NORMAL_MAP = 1 << 1; 7 | const int METALLIC_ROUGHNESS_MAP = 1 << 2; 8 | const int EMISSIVE_MAP = 1 << 3; 9 | const int OCCLUSION_MAP = 1 << 4; 10 | const int DISPLACEMENT_BUFFER = 1 << 5; 11 | 12 | uniform sampler2D u_BaseColorSampler; 13 | uniform sampler2D u_NormalSampler; 14 | uniform sampler2D u_EmissiveSampler; 15 | uniform sampler2D u_MetallicRoughnessSampler; 16 | uniform sampler2D u_OcclusionSampler; 17 | 18 | layout(std140) uniform b_PbrParams { 19 | vec4 u_BaseColorFactor; 20 | vec3 u_Camera; 21 | vec3 u_EmissiveFactor; 22 | vec2 u_MetallicRoughnessValues; 23 | float u_NormalScale; 24 | float u_OcclusionStrength; 25 | int u_PbrFlags; 26 | }; 27 | 28 | in vec3 v_Position; 29 | in vec2 v_TexCoord; 30 | in mat3 v_Tbn; 31 | 32 | out vec4 Target0; 33 | 34 | struct PbrInfo { 35 | float ndotl; 36 | float ndotv; 37 | float ndoth; 38 | float ldoth; 39 | float vdoth; 40 | float perceptual_roughness; 41 | float metalness; 42 | vec3 base_color; 43 | vec3 reflectance0; 44 | vec3 reflectance90; 45 | float alpha_roughness; 46 | }; 47 | 48 | const float PI = 3.141592653589793; 49 | const float MIN_ROUGHNESS = 0.04; 50 | 51 | float smith(float ndotv, float r) { 52 | float tan_sq = (1.0 - ndotv * ndotv) / max((ndotv * ndotv), 0.00001); 53 | return 2.0 / (1.0 + sqrt(1.0 + r * r * tan_sq)); 54 | } 55 | 56 | float geometric_occlusion_smith_ggx(PbrInfo pbr) { 57 | return smith(pbr.ndotl, pbr.alpha_roughness) * smith(pbr.ndotv, pbr.alpha_roughness); 58 | } 59 | 60 | // Basic Lambertian diffuse, implementation from Lambert's Photometria 61 | // https://archive.org/details/lambertsphotome00lambgoog 62 | vec3 lambertian_diffuse(PbrInfo pbr) { 63 | return pbr.base_color / PI; 64 | } 65 | 66 | // The following equations model the Fresnel reflectance term of the spec equation 67 | // (aka F()) implementation of fresnel from “An Inexpensive BRDF Model for Physically 68 | // based Rendering” by Christophe Schlick 69 | vec3 fresnel_schlick(PbrInfo pbr) { 70 | return pbr.reflectance0 + (pbr.reflectance90 - pbr.reflectance0) * pow(clamp(1.0 - pbr.vdoth, 0.0, 1.0), 5.0); 71 | } 72 | 73 | // The following equation(s) model the distribution of microfacet normals across 74 | // the area being drawn (aka D()) 75 | // Implementation from “Average Irregularity Representation of a Roughened Surface 76 | // for Ray Reflection” by T. S. Trowbridge, and K. P. Reitz 77 | float ggx(PbrInfo pbr) { 78 | float roughness_sq = pbr.alpha_roughness * pbr.alpha_roughness; 79 | float f = (pbr.ndoth * roughness_sq - pbr.ndoth) * pbr.ndoth + 1.0; 80 | return roughness_sq / (PI * f * f); 81 | } 82 | 83 | bool available(int flag) { 84 | return (u_PbrFlags & flag) == flag; 85 | } 86 | 87 | void main() { 88 | vec3 v = normalize(u_Camera - v_Position); 89 | 90 | vec3 n; 91 | if (available(NORMAL_MAP)) { 92 | n = texture(u_NormalSampler, v_TexCoord).rgb; 93 | n = normalize(v_Tbn * ((2.0 * n - 1.0) * vec3(u_NormalScale, u_NormalScale, 1.0))); 94 | } else { 95 | n = v_Tbn[2].xyz; 96 | } 97 | 98 | float perceptual_roughness = u_MetallicRoughnessValues.y; 99 | float metallic = u_MetallicRoughnessValues.x; 100 | 101 | if (available(METALLIC_ROUGHNESS_MAP)) { 102 | vec4 mr_sample = texture(u_MetallicRoughnessSampler, v_TexCoord); 103 | perceptual_roughness = mr_sample.g * perceptual_roughness; 104 | metallic = mr_sample.b * metallic; 105 | } 106 | 107 | perceptual_roughness = clamp(perceptual_roughness, MIN_ROUGHNESS, 1.0); 108 | metallic = clamp(metallic, 0.0, 1.0); 109 | 110 | vec4 base_color; 111 | if (available(BASE_COLOR_MAP)) { 112 | base_color = texture(u_BaseColorSampler, v_TexCoord) * u_BaseColorFactor; 113 | } else { 114 | base_color = u_BaseColorFactor; 115 | } 116 | 117 | vec3 f0 = vec3(0.04); 118 | vec3 diffuse_color = mix(base_color.rgb * (1.0 - f0), vec3(0.0, 0.0, 0.0), metallic); 119 | vec3 specular_color = mix(f0, base_color.rgb, metallic); 120 | float reflectance = max(max(specular_color.r, specular_color.g), specular_color.b); 121 | 122 | // For typical incident reflectance range (between 4% to 100%) set the grazing 123 | // reflectance to 100% for typical fresnel effect. 124 | // For very low reflectance range on highly diffuse objects (below 4%), 125 | // incrementally reduce grazing reflecance to 0%. 126 | float reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0); 127 | vec3 specular_environment_r0 = specular_color.rgb; 128 | vec3 specular_environment_r90 = vec3(1.0, 1.0, 1.0) * reflectance90; 129 | 130 | // Roughness is authored as perceptual roughness; as is convention, convert to 131 | // material roughness by squaring the perceptual roughness 132 | float alpha_roughness = perceptual_roughness * perceptual_roughness; 133 | 134 | vec3 color = vec3(0.0); 135 | for (uint i = 0U; i < min(MAX_LIGHTS, u_NumLights); ++i) { 136 | Light light = u_Lights[i]; 137 | vec3 l = normalize(light.dir.xyz); 138 | vec3 h = normalize(l + v); 139 | vec3 reflection = -normalize(reflect(v, n)); 140 | 141 | float ndotl = clamp(dot(n, l), 0.001, 1.0); 142 | float ndotv = abs(dot(n, v)) + 0.001; 143 | float ndoth = clamp(dot(n, h), 0.0, 1.0); 144 | float ldoth = clamp(dot(l, h), 0.0, 1.0); 145 | float vdoth = clamp(dot(v, h), 0.0, 1.0); 146 | PbrInfo pbr_inputs = PbrInfo( 147 | ndotl, 148 | ndotv, 149 | ndoth, 150 | ldoth, 151 | vdoth, 152 | perceptual_roughness, 153 | metallic, 154 | diffuse_color, 155 | specular_environment_r0, 156 | specular_environment_r90, 157 | alpha_roughness 158 | ); 159 | vec3 f = fresnel_schlick(pbr_inputs); 160 | float g = geometric_occlusion_smith_ggx(pbr_inputs); 161 | float d = ggx(pbr_inputs); 162 | vec3 diffuse_contrib = (1.0 - f) * lambertian_diffuse(pbr_inputs); 163 | vec3 spec_contrib = f * g * d / (4.0 * ndotl * ndotv); 164 | color += ndotl * light.intensity.y * light.color.rgb * (diffuse_contrib + spec_contrib); 165 | } 166 | 167 | if (available(OCCLUSION_MAP)) { 168 | float ao = texture(u_OcclusionSampler, v_TexCoord).r; 169 | color = mix(color, color * ao, u_OcclusionStrength); 170 | } 171 | 172 | if (available(EMISSIVE_MAP)) { 173 | vec3 emissive = texture(u_EmissiveSampler, v_TexCoord).rgb * u_EmissiveFactor; 174 | color += emissive; 175 | } 176 | 177 | Target0 = vec4(color, base_color.a); 178 | } 179 | -------------------------------------------------------------------------------- /data/shaders/pbr_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | #define MAX_TARGETS 8U 3 | #include 4 | 5 | const int DISPLACEMENT_BUFFER = 1 << 5; 6 | 7 | in vec4 a_Position; 8 | in vec2 a_TexCoord; 9 | in vec4 a_Normal; 10 | in vec4 a_Tangent; 11 | in ivec4 a_JointIndices; 12 | in vec4 a_JointWeights; 13 | 14 | out vec3 v_Position; 15 | out vec2 v_TexCoord; 16 | out mat3 v_Tbn; 17 | 18 | in vec4 i_World0; 19 | in vec4 i_World1; 20 | in vec4 i_World2; 21 | 22 | // Toggles displacement contributions to `a_Position/a_Normal/a_Tangent`. 23 | struct DisplacementContribution { 24 | // position: 1.0 if morph target weights should influence a_Position 25 | // normal: 1.0 if morph target weights should influence a_Normal 26 | // tangent: 1.0 if morph target weights should influence a_Tangent 27 | // weight: The weight to be applied. 28 | float position, normal, tangent, weight; 29 | }; 30 | 31 | layout(std140) uniform b_DisplacementContributions { 32 | DisplacementContribution u_DisplacementContributions[MAX_TARGETS]; 33 | }; 34 | 35 | layout(std140) uniform b_PbrParams { 36 | vec4 u_BaseColorFactor; 37 | vec3 u_Camera; 38 | vec3 u_EmissiveFactor; 39 | vec2 u_MetallicRoughnessValues; 40 | float u_NormalScale; 41 | float u_OcclusionStrength; 42 | int u_PbrFlags; 43 | }; 44 | 45 | uniform samplerBuffer b_JointTransforms; 46 | uniform sampler2D u_Displacements; 47 | 48 | //TODO: store each join transform in 3 vectors, similar to `i_WorldX` 49 | 50 | mat4 fetch_joint_transform(int i) { 51 | //Note: has to match `render::VECS_PER_BONE` 52 | vec4 row0 = texelFetch(b_JointTransforms, 3 * i + 0); 53 | vec4 row1 = texelFetch(b_JointTransforms, 3 * i + 1); 54 | vec4 row2 = texelFetch(b_JointTransforms, 3 * i + 2); 55 | 56 | return transpose(mat4(row0, row1, row2, vec4(0.0, 0.0, 0.0, 1.0))); 57 | } 58 | 59 | mat4 compute_skin_transform() { 60 | return 61 | a_JointWeights.x * fetch_joint_transform(a_JointIndices.x) + 62 | a_JointWeights.y * fetch_joint_transform(a_JointIndices.y) + 63 | a_JointWeights.z * fetch_joint_transform(a_JointIndices.z) + 64 | a_JointWeights.w * fetch_joint_transform(a_JointIndices.w); 65 | } 66 | 67 | bool available(int flag) { 68 | return (u_PbrFlags & flag) == flag; 69 | } 70 | 71 | void main() { 72 | vec3 local_position = a_Position.xyz; 73 | vec3 local_normal = a_Normal.xyz; 74 | vec3 local_tangent = a_Tangent.xyz; 75 | 76 | if (available(DISPLACEMENT_BUFFER)) { 77 | uint num_targets = uvec2(textureSize(u_Displacements, 0)).y / 3U; 78 | for (uint i = 0U; i < min(num_targets, MAX_TARGETS); ++i) { 79 | DisplacementContribution disp = u_DisplacementContributions[i]; 80 | if (disp.weight == 0.0) continue; 81 | local_position += disp.position * disp.weight * texelFetch(u_Displacements, ivec2(gl_VertexID, 3U*i+0U), 0).xyz; 82 | local_normal += disp.normal * disp.weight * texelFetch(u_Displacements, ivec2(gl_VertexID, 3U*i+1U), 0).xyz; 83 | local_tangent += disp.tangent * disp.weight * texelFetch(u_Displacements, ivec2(gl_VertexID, 3U*i+2U), 0).xyz; 84 | } 85 | } 86 | 87 | mat4 mx_world = transpose(mat4(i_World0, i_World1, i_World2, vec4(0.0, 0.0, 0.0, 1.0))); 88 | mat4 mx_mvp = u_ViewProj * mx_world; 89 | mat4 mx_skin = compute_skin_transform(); 90 | 91 | vec4 world_position = mx_world * vec4(local_position, a_Position.w); 92 | vec3 world_normal = mat3(mx_world) * normalize(local_normal); 93 | vec3 world_tangent = mat3(mx_world) * normalize(local_tangent); 94 | vec3 world_bitangent = cross(world_normal, world_tangent) * a_Tangent.w; 95 | 96 | v_Tbn = mat3(world_tangent, world_bitangent, world_normal); 97 | v_Position = world_position.xyz / world_position.w; 98 | v_TexCoord = a_TexCoord; 99 | 100 | gl_Position = mx_mvp * mx_skin * vec4(local_position, a_Position.w); 101 | } 102 | -------------------------------------------------------------------------------- /data/shaders/phong_ps.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | #include 3 | #include 4 | 5 | in vec3 v_World; 6 | in vec3 v_Normal; 7 | in vec3 v_Half[MAX_LIGHTS]; 8 | in vec4 v_ShadowCoord[MAX_LIGHTS]; 9 | 10 | in vec4 v_MatParams; 11 | in vec4 v_Color; 12 | 13 | out vec4 Target0; 14 | 15 | uniform sampler2DShadow t_Shadow0; 16 | uniform sampler2DShadow t_Shadow1; 17 | 18 | void main() { 19 | vec4 color = vec4(0.0); 20 | vec3 normal = normalize(v_Normal); 21 | float glossiness = v_MatParams.x; 22 | for(uint i=0U; i < min(MAX_LIGHTS, u_NumLights); ++i) { 23 | Light light = u_Lights[i]; 24 | vec4 lit_space = v_ShadowCoord[i]; 25 | float shadow = 1.0; 26 | if (light.shadow_params[0] == 0) { 27 | shadow = texture(t_Shadow0, 0.5 * lit_space.xyz / lit_space.w + 0.5); 28 | } 29 | if (light.shadow_params[0] == 1) { 30 | shadow = texture(t_Shadow1, 0.5 * lit_space.xyz / lit_space.w + 0.5); 31 | } 32 | if (shadow == 0.0) { 33 | continue; 34 | } 35 | vec3 dir = light.pos.xyz - light.pos.w * v_World.xyz; 36 | float dot_nl = dot(normal, normalize(dir)); 37 | // hemisphere light test 38 | if (dot(light.color_back, light.color_back) > 0.0) { 39 | vec4 irradiance = mix(light.color_back, light.color, dot_nl*0.5 + 0.5); 40 | color += shadow * light.intensity.x * v_Color * irradiance; 41 | } else { 42 | float kd = light.intensity.x + light.intensity.y * max(0.0, dot_nl); 43 | color += shadow * kd * v_Color * light.color; 44 | } 45 | if (dot_nl > 0.0 && glossiness > 0.0) { 46 | float ks = dot(normal, normalize(v_Half[i])); 47 | if (ks > 0.0) { 48 | color += shadow * pow(ks, glossiness) * light.color; 49 | } 50 | } 51 | } 52 | Target0 = color; 53 | } 54 | -------------------------------------------------------------------------------- /data/shaders/phong_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | #include 3 | #include 4 | 5 | in vec4 a_Position; 6 | in vec4 a_Normal; 7 | out vec3 v_World; 8 | out vec3 v_Normal; 9 | out vec3 v_Half[MAX_LIGHTS]; 10 | out vec4 v_ShadowCoord[MAX_LIGHTS]; 11 | out vec4 v_MatParams; 12 | out vec4 v_Color; 13 | 14 | in vec4 i_World0; 15 | in vec4 i_World1; 16 | in vec4 i_World2; 17 | in vec4 i_MatParams; 18 | in vec4 i_Color; 19 | 20 | void main() { 21 | mat4 m_World = transpose(mat4(i_World0, i_World1, i_World2, vec4(0.0, 0.0, 0.0, 1.0))); 22 | vec4 world = m_World * a_Position; 23 | v_World = world.xyz; 24 | v_Normal = normalize(mat3(m_World) * a_Normal.xyz); 25 | for(uint i=0U; i < min(MAX_LIGHTS, u_NumLights); ++i) { 26 | Light light = u_Lights[i]; 27 | vec3 dir = light.pos.xyz - light.pos.w * world.xyz; 28 | v_Half[i] = normalize(v_Normal + normalize(dir)); 29 | v_ShadowCoord[i] = light.projection * world; 30 | } 31 | v_Color = i_Color; 32 | v_MatParams = i_MatParams; 33 | gl_Position = u_ViewProj * world; 34 | } 35 | -------------------------------------------------------------------------------- /data/shaders/quad_ps.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | in vec2 v_TexCoord; 4 | out vec4 Target0; 5 | 6 | uniform sampler2D t_Input; 7 | 8 | void main() { 9 | Target0 = texture(t_Input, v_TexCoord); 10 | } 11 | -------------------------------------------------------------------------------- /data/shaders/quad_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | out vec2 v_TexCoord; 4 | 5 | layout(std140) uniform b_Params { 6 | vec4 u_Rect; 7 | float u_Depth; 8 | }; 9 | 10 | void main() { 11 | v_TexCoord = gl_VertexID==0 ? vec2(1.0, 0.0) : 12 | gl_VertexID==1 ? vec2(0.0, 0.0) : 13 | gl_VertexID==2 ? vec2(1.0, 1.0) : 14 | vec2(0.0, 1.0) ; 15 | vec2 pos = mix(u_Rect.xy, u_Rect.zw, v_TexCoord); 16 | gl_Position = vec4(pos, u_Depth, 1.0); 17 | } 18 | -------------------------------------------------------------------------------- /data/shaders/shadow_ps.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | void main() {} 3 | -------------------------------------------------------------------------------- /data/shaders/shadow_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | #include 3 | 4 | in vec4 a_Position; 5 | in vec4 i_World0; 6 | in vec4 i_World1; 7 | in vec4 i_World2; 8 | 9 | void main() { 10 | mat4 m_World = transpose(mat4(i_World0, i_World1, i_World2, vec4(0.0, 0.0, 0.0, 1.0))); 11 | gl_Position = u_ViewProj * m_World * a_Position; 12 | } 13 | -------------------------------------------------------------------------------- /data/shaders/skybox_ps.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | uniform samplerCube t_Input; 4 | 5 | in vec3 v_TexCoord; 6 | out vec4 Target0; 7 | 8 | void main() { 9 | Target0 = texture(t_Input, v_TexCoord); 10 | } 11 | -------------------------------------------------------------------------------- /data/shaders/skybox_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | #include 3 | 4 | out vec3 v_TexCoord; 5 | 6 | void main() { 7 | vec2 pos = gl_VertexID == 0 ? vec2(-1.0, -1.0) : 8 | gl_VertexID == 1 ? vec2(-1.0, 1.0) : 9 | gl_VertexID == 3 ? vec2( 1.0, 1.0) : 10 | vec2( 1.0, -1.0) ; 11 | 12 | vec4 a_Position = vec4(pos.xy, 1.0, 1.0); 13 | 14 | mat3 inverseView = transpose(mat3(u_View)); 15 | vec3 unprojected = (u_InverseProj * a_Position).xyz; 16 | 17 | v_TexCoord = inverseView * unprojected; 18 | 19 | gl_Position = a_Position; 20 | } 21 | -------------------------------------------------------------------------------- /data/shaders/sprite_ps.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | 3 | in vec2 v_TexCoord; 4 | out vec4 Target0; 5 | 6 | uniform sampler2D t_Map; 7 | 8 | void main() { 9 | Target0 = texture(t_Map, v_TexCoord); 10 | } 11 | -------------------------------------------------------------------------------- /data/shaders/sprite_vs.glsl: -------------------------------------------------------------------------------- 1 | #version 150 core 2 | #include 3 | 4 | in vec4 a_Position; 5 | in vec2 a_TexCoord; 6 | out vec2 v_TexCoord; 7 | in vec4 i_World0; 8 | in vec4 i_World1; 9 | in vec4 i_World2; 10 | in vec4 i_UvRange; 11 | 12 | void main() { 13 | mat4 m_World = transpose(mat4(i_World0, i_World1, i_World2, vec4(0.0, 0.0, 0.0, 1.0))); 14 | v_TexCoord = mix(i_UvRange.xy, i_UvRange.zw, a_TexCoord); 15 | gl_Position = u_ViewProj * m_World * a_Position; 16 | } 17 | -------------------------------------------------------------------------------- /examples/aviator/README.md: -------------------------------------------------------------------------------- 1 | ## Aviator demo 2 | 3 | This demo is a direct port of: 4 | https://tympanus.net/codrops/2016/04/26/the-aviator-animating-basic-3d-scene-threejs/ 5 | 6 | ## Screenshot 7 | ![Aviator](shot.png) 8 | -------------------------------------------------------------------------------- /examples/aviator/main.rs: -------------------------------------------------------------------------------- 1 | extern crate cgmath; 2 | extern crate env_logger; 3 | extern crate mint; 4 | extern crate rand; 5 | extern crate three; 6 | 7 | mod plane; 8 | mod sky; 9 | 10 | use cgmath::prelude::*; 11 | use three::Object; 12 | 13 | const COLOR_BACKGROUND: three::Color = 0xf0e0b6; 14 | const COLOR_RED: three::Color = 0xf25346; 15 | const COLOR_WHITE: three::Color = 0xd8d0d1; 16 | const COLOR_BROWN: three::Color = 0x59332e; 17 | //const COLOR_PINK: three::Color = 0xF5986E; 18 | const COLOR_BROWN_DARK: three::Color = 0x23190f; 19 | const COLOR_BLUE: three::Color = 0x68c3c0; 20 | 21 | fn main() { 22 | env_logger::init(); 23 | let mut rng = rand::thread_rng(); 24 | 25 | let mut win = three::Window::new("Three-rs Aviator demo"); 26 | win.scene.background = three::Background::Color(COLOR_BACKGROUND); 27 | 28 | let cam = win.factory.perspective_camera(60.0, 1.0 .. 1000.0); 29 | cam.set_position([0.0, 100.0, 200.0]); 30 | win.scene.add(&cam); 31 | 32 | //TODO: win.scene.fog = Some(three::Fog::new(...)); 33 | //TODO: Phong materials 34 | 35 | let hemi_light = win.factory.hemisphere_light(0xaaaaaa, 0x000000, 0.9); 36 | win.scene.add(&hemi_light); 37 | let mut dir_light = win.factory.directional_light(0xffffff, 0.9); 38 | dir_light.look_at([150.0, 350.0, 350.0], [0.0, 0.0, 0.0], None); 39 | let shadow_map = win.factory.shadow_map(2048, 2048); 40 | dir_light.set_shadow(shadow_map, 400.0, 1.0 .. 1000.0); 41 | win.scene.add(&dir_light); 42 | let ambient_light = win.factory.ambient_light(0xdc8874, 0.5); 43 | win.scene.add(&ambient_light); 44 | 45 | let sea = { 46 | let geo = three::Geometry::cylinder(600.0, 600.0, 800.0, 40); 47 | let material = three::material::Lambert { 48 | color: COLOR_BLUE, 49 | flat: true, 50 | }; 51 | win.factory.mesh(geo, material) 52 | }; 53 | let sea_base_q = cgmath::Quaternion::from_angle_x(-cgmath::Rad::turn_div_4()); 54 | sea.set_transform([0.0, -600.0, 0.0], sea_base_q, 1.0); 55 | win.scene.add(&sea); 56 | 57 | let sky = sky::Sky::new(&mut rng, &mut win.factory); 58 | sky.group.set_position([0.0, -600.0, 0.0]); 59 | win.scene.add(&sky.group); 60 | 61 | let mut airplane = plane::AirPlane::new(&mut win.factory); 62 | airplane 63 | .group 64 | .set_transform([0.0, 100.0, 0.0], [0.0, 0.0, 0.0, 1.0], 0.25); 65 | win.scene.add(&airplane.group); 66 | 67 | let timer = three::Timer::new(); 68 | while win.update() && !win.input.hit(three::KEY_ESCAPE) { 69 | use cgmath::{Quaternion, Rad}; 70 | // assume the original velocities are given for 60fps 71 | let time = 60.0 * timer.elapsed(); 72 | 73 | airplane.update(time, win.input.mouse_pos_ndc()); 74 | 75 | let sea_angle = Rad(0.005 * time); 76 | let sea_q = Quaternion::from_angle_z(sea_angle) * sea_base_q; 77 | sea.set_orientation(sea_q); 78 | let sky_angle = Rad(0.01 * time); 79 | let sky_q = Quaternion::from_angle_z(sky_angle); 80 | sky.group.set_orientation(sky_q); 81 | 82 | win.render(&cam); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /examples/aviator/plane.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{Quaternion, Rad, Rotation3}; 2 | use mint; 3 | use three::{self, Object}; 4 | 5 | use {COLOR_BROWN, COLOR_BROWN_DARK, COLOR_RED, COLOR_WHITE}; 6 | 7 | pub struct AirPlane { 8 | pub group: three::Group, 9 | _cockpit: three::Mesh, 10 | _engine: three::Mesh, 11 | _tail: three::Mesh, 12 | _wing: three::Mesh, 13 | propeller_group: three::Group, 14 | _propeller: three::Mesh, 15 | _blade: three::Mesh, 16 | } 17 | 18 | impl AirPlane { 19 | pub fn new(factory: &mut three::Factory) -> Self { 20 | let group = factory.group(); 21 | 22 | let cockpit = { 23 | let mut geo = three::Geometry::cuboid(80.0, 50.0, 50.0); 24 | for v in geo.base.vertices.iter_mut() { 25 | if v.x < 0.0 { 26 | v.z += if v.y > 0.0 { -20.0 } else { 20.0 }; 27 | v.y += if v.y > 0.0 { -10.0 } else { 30.0 }; 28 | } 29 | } 30 | factory.mesh( 31 | geo, 32 | three::material::Lambert { 33 | color: COLOR_RED, 34 | flat: false, 35 | }, 36 | ) 37 | }; 38 | group.add(&cockpit); 39 | 40 | let engine = factory.mesh( 41 | three::Geometry::cuboid(20.0, 50.0, 50.0), 42 | three::material::Lambert { 43 | color: COLOR_WHITE, 44 | flat: false, 45 | }, 46 | ); 47 | engine.set_position([40.0, 0.0, 0.0]); 48 | group.add(&engine); 49 | 50 | let tail = factory.mesh( 51 | three::Geometry::cuboid(15.0, 20.0, 5.0), 52 | three::material::Lambert { 53 | color: COLOR_RED, 54 | flat: false, 55 | }, 56 | ); 57 | tail.set_position([-35.0, 25.0, 0.0]); 58 | group.add(&tail); 59 | 60 | let wing = factory.mesh( 61 | three::Geometry::cuboid(40.0, 8.0, 150.0), 62 | three::material::Lambert { 63 | color: COLOR_RED, 64 | flat: false, 65 | }, 66 | ); 67 | group.add(&wing); 68 | 69 | let propeller_group = factory.group(); 70 | propeller_group.set_position([50.0, 0.0, 0.0]); 71 | group.add(&propeller_group); 72 | let propeller = factory.mesh( 73 | three::Geometry::cuboid(20.0, 10.0, 10.0), 74 | three::material::Lambert { 75 | color: COLOR_BROWN, 76 | flat: false, 77 | }, 78 | ); 79 | propeller_group.add(&propeller); 80 | let blade = factory.mesh( 81 | three::Geometry::cuboid(1.0, 100.0, 20.0), 82 | three::material::Lambert { 83 | color: COLOR_BROWN_DARK, 84 | flat: false, 85 | }, 86 | ); 87 | blade.set_position([8.0, 0.0, 0.0]); 88 | propeller_group.add(&blade); 89 | 90 | AirPlane { 91 | group, 92 | _cockpit: cockpit, 93 | _engine: engine, 94 | _tail: tail, 95 | _wing: wing, 96 | propeller_group, 97 | _propeller: propeller, 98 | _blade: blade, 99 | } 100 | } 101 | 102 | pub fn update( 103 | &mut self, 104 | time: f32, 105 | target: mint::Point2, 106 | ) { 107 | let q = Quaternion::from_angle_x(Rad(0.3 * time)); 108 | self.propeller_group.set_orientation(q); 109 | self.group 110 | .set_position([0.0 + target.x * 100.0, 100.0 + target.y * 75.0, 0.0]); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /examples/aviator/shot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/three-rs/three/6403b70f23892db2d5b14c634e404fd2afb67889/examples/aviator/shot.png -------------------------------------------------------------------------------- /examples/aviator/sky.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | 3 | use cgmath; 4 | use cgmath::prelude::*; 5 | use rand::Rng; 6 | use three::{self, Object}; 7 | 8 | use COLOR_WHITE; 9 | 10 | 11 | pub struct Sky { 12 | pub group: three::Group, 13 | } 14 | 15 | impl Sky { 16 | fn make_cloud( 17 | rng: &mut R, 18 | factory: &mut three::Factory, 19 | ) -> three::Group { 20 | let group = factory.group(); 21 | let geo = three::Geometry::cuboid(20.0, 20.0, 20.0); 22 | let material = three::material::Lambert { 23 | color: COLOR_WHITE, 24 | flat: true, 25 | }; 26 | let template = factory.mesh(geo, material.clone()); 27 | for i in 0i32 .. rng.gen_range(3, 6) { 28 | let m = factory.mesh_instance(&template); 29 | let rot = cgmath::Quaternion::::new(rng.gen(), rng.gen(), rng.gen(), rng.gen()); 30 | let q = rot.normalize(); 31 | m.set_transform( 32 | [ 33 | i as f32 * 15.0, 34 | rng.gen::() * 10.0, 35 | rng.gen::() * 10.0, 36 | ], 37 | q, 38 | rng.gen_range(0.1, 1.0), 39 | ); 40 | group.add(&m); 41 | } 42 | group 43 | } 44 | 45 | pub fn new( 46 | rng: &mut R, 47 | factory: &mut three::Factory, 48 | ) -> Self { 49 | let group = factory.group(); 50 | let num = 20i32; 51 | let step_angle = PI * 2.0 / num as f32; 52 | for i in 0 .. num { 53 | let cloud = Self::make_cloud(rng, factory); 54 | let angle = cgmath::Rad(i as f32 * step_angle); 55 | let dist = rng.gen_range(750.0, 950.0); 56 | let pos = [ 57 | angle.cos() * dist, 58 | angle.sin() * dist, 59 | rng.gen_range(-800.0, -400.0), 60 | ]; 61 | let q = cgmath::Quaternion::from_angle_z(angle + cgmath::Rad::turn_div_4()); 62 | cloud.set_transform(pos, q, rng.gen_range(1.0, 3.0)); 63 | group.add(&cloud); 64 | } 65 | Sky { group } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/gltf-morph-targets.rs: -------------------------------------------------------------------------------- 1 | extern crate three; 2 | 3 | use three::Object; 4 | 5 | fn main() { 6 | let mut window = three::Window::new("Three-rs glTF animation example"); 7 | let light = window.factory.directional_light(0xFFFFFF, 0.4); 8 | light.look_at([1.0, 1.0, 1.0], [0.0, 0.0, 0.0], None); 9 | window.scene.add(&light); 10 | window.scene.background = three::Background::Color(0xC6F0FF); 11 | 12 | let default = concat!(env!("CARGO_MANIFEST_DIR"), "/test_data/AnimatedMorphCube/AnimatedMorphCube.gltf"); 13 | let path = std::env::args().nth(1).unwrap_or(default.into()); 14 | 15 | // Load the contents of the glTF files. Scenes loaded from the file are returned as 16 | // `Template` objects, which can be used to instantiate the actual objects for rendering. 17 | let templates = window.factory.load_gltf(&path); 18 | 19 | // Instantiate the contents of the template, and then add it to the scene. 20 | let (instance, animations) = window.factory.instantiate_template(&templates[0]); 21 | window.scene.add(&instance); 22 | 23 | // Begin playing all the animations instantiated from the template. 24 | let mut mixer = three::animation::Mixer::new(); 25 | for animation in animations { 26 | mixer.action(animation); 27 | } 28 | 29 | // Create a camera with which to render the scene, and control it with the built-in 30 | // orbit controller, set to orbit the model. 31 | let camera = window.factory.perspective_camera(60.0, 0.1 .. 20.0); 32 | let mut controls = three::controls::Orbit::builder(&camera) 33 | .position([-3.0, 3.0, -3.0]) 34 | .up([0.0, 1.0, 0.0]) 35 | .build(); 36 | 37 | // Run the main loop, updating the camera controller, animations, and rendering the scene 38 | // every frame. 39 | while window.update() && !window.input.hit(three::KEY_ESCAPE) { 40 | mixer.update(window.input.delta_time()); 41 | controls.update(&window.input); 42 | window.render(&camera); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/gltf-node-animation.rs: -------------------------------------------------------------------------------- 1 | extern crate three; 2 | 3 | use three::Object; 4 | 5 | fn main() { 6 | let mut window = three::Window::new("Three-rs glTF animation example"); 7 | let light = window.factory.directional_light(0xFFFFFF, 0.4); 8 | light.look_at([1.0, -5.0, 10.0], [0.0, 0.0, 0.0], None); 9 | window.scene.add(&light); 10 | window.scene.background = three::Background::Color(0xC6F0FF); 11 | 12 | let default = concat!(env!("CARGO_MANIFEST_DIR"), "/test_data/BoxAnimated/BoxAnimated.gltf"); 13 | let path = std::env::args().nth(1).unwrap_or(default.into()); 14 | 15 | // Load the contents of the glTF files. Scenes loaded from the file are returned as 16 | // `Template` objects, which can be used to instantiate the actual objects for rendering. 17 | let templates = window.factory.load_gltf(&path); 18 | 19 | // Instantiate the contents of the template, and then add it to the scene. 20 | let (instance, animations) = window.factory.instantiate_template(&templates[0]); 21 | window.scene.add(&instance); 22 | 23 | // Start playing all the animations from the template. 24 | let mut mixer = three::animation::Mixer::new(); 25 | for animation in animations { 26 | mixer.action(animation); 27 | } 28 | 29 | // Create a camera with which to render the scene, and control it with the built-in 30 | // orbit controller, set to orbit the model. 31 | let camera = window.factory.perspective_camera(60.0, 0.1 .. 100.0); 32 | let mut controls = three::controls::Orbit::builder(&camera) 33 | .position([3.0, 3.0, 3.0]) 34 | .target([0.0, 1.0, 0.0]) 35 | .build(); 36 | 37 | // Run the main loop, updating the camera controller, animations, and rendering the scene 38 | // every frame. 39 | while window.update() && !window.input.hit(three::KEY_ESCAPE) { 40 | mixer.update(window.input.delta_time()); 41 | controls.update(&window.input); 42 | window.render(&camera); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/gltf-pbr-shader.rs: -------------------------------------------------------------------------------- 1 | extern crate three; 2 | 3 | use three::{ 4 | camera::Camera, 5 | Object, 6 | }; 7 | 8 | fn main() { 9 | let mut win = three::Window::new("Three-rs glTF example"); 10 | let light = win.factory.directional_light(0xFFFFFF, 7.0); 11 | light.look_at([1.0, 1.0, 1.0], [0.0, 0.0, 0.0], None); 12 | win.scene.add(&light); 13 | win.scene.background = three::Background::Color(0xC6F0FF); 14 | 15 | let default = concat!(env!("CARGO_MANIFEST_DIR"), "/test_data/Lantern/Lantern.gltf"); 16 | let path = std::env::args().nth(1).unwrap_or(default.into()); 17 | println!("Loading {:?} (this may take a while)", path); 18 | 19 | // Load the contents of the glTF files. Scenes loaded from the file are returned as 20 | // `Template` objects, which can be used to instantiate the actual objects for rendering. 21 | let templates = win.factory.load_gltf(&path); 22 | 23 | // Instantiate the contents of the template, and then add it to the scene. 24 | let (instance, _) = win.factory.instantiate_template(&templates[0]); 25 | win.scene.add(&instance); 26 | 27 | // Attempt to find a camera in the instantiated template to use as the perspective for 28 | // rendering. 29 | let cam = win 30 | .scene 31 | .sync_guard() 32 | .find_child_of_type::(&instance); 33 | 34 | // If we didn't find a camera in the glTF scene, create a default one to use. 35 | let cam = cam.unwrap_or_else(|| { 36 | let default = win.factory.perspective_camera(60.0, 0.001 .. 100.0); 37 | win.scene.add(&default); 38 | default 39 | }); 40 | 41 | // Create a skybox for the scene. 42 | let skybox_path = three::CubeMapPath { 43 | front: "test_data/skybox/posz.jpg", 44 | back: "test_data/skybox/negz.jpg", 45 | up: "test_data/skybox/posy.jpg", 46 | down: "test_data/skybox/negy.jpg", 47 | left: "test_data/skybox/negx.jpg", 48 | right: "test_data/skybox/posx.jpg", 49 | }; 50 | let skybox = win.factory.load_cubemap(&skybox_path); 51 | win.scene.background = three::Background::Skybox(skybox); 52 | 53 | // Determine the current position of the camera so that we can use it to initialize the 54 | // camera controller. 55 | let init = win.scene 56 | .sync_guard() 57 | .resolve_world(&cam) 58 | .transform; 59 | 60 | // Create a first person camera controller, starting at the camera's current position. 61 | let mut controls = three::controls::FirstPerson::builder(&cam) 62 | .position(init.position) 63 | .move_speed(4.0) 64 | .build(); 65 | 66 | // Run the main loop, updating the camera controller and rendering the scene every frame. 67 | while win.update() && !win.input.hit(three::KEY_ESCAPE) { 68 | controls.update(&win.input); 69 | win.render(&cam); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/gltf-vertex-skinning.rs: -------------------------------------------------------------------------------- 1 | extern crate three; 2 | 3 | use three::Object; 4 | 5 | fn main() { 6 | let mut window = three::Window::new("Three-rs glTF animation example"); 7 | let light = window.factory.directional_light(0xFFFFFF, 0.4); 8 | light.look_at([1.0, -5.0, 10.0], [0.0, 0.0, 0.0], None); 9 | window.scene.add(&light); 10 | window.scene.background = three::Background::Color(0xC6F0FF); 11 | 12 | let default = concat!(env!("CARGO_MANIFEST_DIR"), "/test_data/BrainStem/BrainStem.gltf"); 13 | let path = std::env::args().nth(1).unwrap_or(default.into()); 14 | 15 | // Load the contents of the glTF files. Scenes loaded from the file are returned as 16 | // `Template` objects, which can be used to instantiate the actual objects for rendering. 17 | let templates = window.factory.load_gltf(&path); 18 | 19 | // Instantiate the contents of the template, and then add it to the scene. 20 | let (instance, animations) = window.factory.instantiate_template(&templates[0]); 21 | window.scene.add(&instance); 22 | 23 | // Begin playing all the animations instantiated from the template. 24 | let mut mixer = three::animation::Mixer::new(); 25 | for animation in animations { 26 | mixer.action(animation); 27 | } 28 | 29 | // Create a camera with which to render the scene, and control it with the built-in 30 | // orbit controller, set to orbit the model. 31 | let camera = window.factory.perspective_camera(45.0, 0.1 .. 100.0); 32 | let mut controls = three::controls::Orbit::builder(&camera) 33 | .position([0.0, 3.0, -1.0]) 34 | .target([0.0, 0.0, -1.0]) 35 | .up([0.0, 0.0, -1.0]) 36 | .build(); 37 | 38 | // Run the main loop, updating the camera controller, animations, and rendering the scene 39 | // every frame. 40 | while window.update() && !window.input.hit(three::KEY_ESCAPE) { 41 | mixer.update(window.input.delta_time()); 42 | controls.update(&window.input); 43 | window.render(&camera); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/group.rs: -------------------------------------------------------------------------------- 1 | extern crate cgmath; 2 | extern crate mint; 3 | extern crate three; 4 | 5 | use cgmath::{Angle, Decomposed, One, Quaternion, Rad, Rotation3, Transform, Vector3}; 6 | use three::Object; 7 | 8 | struct Level { 9 | speed: f32, 10 | } 11 | 12 | struct Cube { 13 | group: three::Group, 14 | mesh: three::Mesh, 15 | level_id: usize, 16 | orientation: Quaternion, 17 | } 18 | 19 | fn create_cubes( 20 | factory: &mut three::Factory, 21 | materials: &[three::material::Lambert], 22 | levels: &[Level], 23 | ) -> Vec { 24 | let mut geometry = three::Geometry::cuboid(2.0, 2.0, 2.0); 25 | for v in geometry.base.vertices.iter_mut() { 26 | v.z += 1.0; 27 | } 28 | 29 | let root = { 30 | let group = factory.group(); 31 | let mesh = factory.mesh(geometry.clone(), materials[0].clone()); 32 | group.set_position([0.0, 0.0, 1.0]); 33 | group.set_scale(2.0); 34 | group.add(&mesh); 35 | Cube { 36 | group, 37 | mesh, 38 | level_id: 0, 39 | orientation: Quaternion::one(), 40 | } 41 | }; 42 | let mut list = vec![root]; 43 | 44 | struct Stack { 45 | parent_id: usize, 46 | mat_id: usize, 47 | lev_id: usize, 48 | } 49 | let mut stack = vec![ 50 | Stack { 51 | parent_id: 0, 52 | mat_id: 1, 53 | lev_id: 1, 54 | }, 55 | ]; 56 | 57 | let axis = [ 58 | Vector3::unit_z(), 59 | Vector3::unit_x(), 60 | -Vector3::unit_x(), 61 | Vector3::unit_y(), 62 | -Vector3::unit_y(), 63 | ]; 64 | let children: Vec<_> = axis.iter() 65 | .map(|&axe| { 66 | Decomposed { 67 | disp: Vector3::new(0.0, 0.0, 1.0), 68 | rot: Quaternion::from_axis_angle(axe, Rad::turn_div_4()), 69 | scale: 1.0, 70 | }.concat(&Decomposed { 71 | disp: Vector3::new(0.0, 0.0, 1.0), 72 | rot: Quaternion::one(), 73 | scale: 0.4, 74 | }) 75 | }) 76 | .collect(); 77 | 78 | while let Some(next) = stack.pop() { 79 | for child in &children { 80 | let mat = materials[next.mat_id].clone(); 81 | let cube = Cube { 82 | group: factory.group(), 83 | mesh: factory.mesh_instance_with_material(&list[0].mesh, mat), 84 | level_id: next.lev_id, 85 | orientation: child.rot, 86 | }; 87 | let p: mint::Vector3 = child.disp.into(); 88 | cube.group.set_transform(p, child.rot, child.scale); 89 | list[next.parent_id].group.add(&cube.group); 90 | cube.group.add(&cube.mesh); 91 | if next.mat_id + 1 < materials.len() && next.lev_id + 1 < levels.len() { 92 | stack.push(Stack { 93 | parent_id: list.len(), 94 | mat_id: next.mat_id + 1, 95 | lev_id: next.lev_id + 1, 96 | }); 97 | } 98 | list.push(cube); 99 | } 100 | } 101 | 102 | list 103 | } 104 | 105 | struct LevelDesc { 106 | color: three::Color, 107 | speed: f32, // in radians per second 108 | } 109 | const LEVELS: &[LevelDesc] = &[ 110 | LevelDesc { color: 0xffff80, speed: 0.7 }, 111 | LevelDesc { color: 0x8080ff, speed: -1.0 }, 112 | LevelDesc { color: 0x80ff80, speed: 1.3 }, 113 | LevelDesc { color: 0xff8080, speed: -1.6 }, 114 | LevelDesc { color: 0x80ffff, speed: 1.9 }, 115 | LevelDesc { color: 0xff80ff, speed: -2.2 }, 116 | //LevelDesc { color: 0x8080ff, speed: 2.5 }, 117 | ]; 118 | 119 | fn main() { 120 | let mut win = three::Window::new("Three-rs group example"); 121 | win.scene.background = three::Background::Color(0x204060); 122 | 123 | let cam = win.factory.perspective_camera(60.0, 1.0 .. 100.0); 124 | cam.look_at([-1.8, -8.0, 7.0], [0.0, 0.0, 3.5], None); 125 | 126 | let light = win.factory.point_light(0xffffff, 1.0); 127 | light.set_position([0.0, -10.0, 10.0]); 128 | win.scene.add(&light); 129 | 130 | let materials = LEVELS 131 | .iter() 132 | .map(|l| three::material::Lambert { color: l.color, flat: false }) 133 | .collect::>(); 134 | let levels = LEVELS 135 | .iter() 136 | .map(|l| Level { speed: l.speed }) 137 | .collect::>(); 138 | let mut cubes = create_cubes(&mut win.factory, &materials, &levels); 139 | win.scene.add(&cubes[0].group); 140 | 141 | let font = win.factory.load_font(format!( 142 | "{}/data/fonts/DejaVuSans.ttf", 143 | env!("CARGO_MANIFEST_DIR") 144 | )); 145 | let mut fps_counter = win.factory.ui_text(&font, "FPS: 00"); 146 | 147 | let timer = three::Timer::new(); 148 | println!("Total number of cubes: {}", cubes.len()); 149 | while win.update() && !win.input.hit(three::KEY_ESCAPE) { 150 | let time = timer.elapsed(); 151 | let delta_time = win.input.delta_time(); 152 | fps_counter.set_text(format!("FPS: {}", 1.0 / delta_time)); 153 | for cube in cubes.iter_mut() { 154 | let level = &levels[cube.level_id]; 155 | let angle = Rad(time * level.speed); 156 | let q = cube.orientation * cgmath::Quaternion::from_angle_z(angle); 157 | cube.group.set_orientation(q); 158 | } 159 | 160 | win.render(&cam); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /examples/lights.rs: -------------------------------------------------------------------------------- 1 | extern crate three; 2 | 3 | use three::Object; 4 | 5 | fn main() { 6 | let mut win = three::Window::new("Three-rs lights example"); 7 | let cam = win.factory.perspective_camera(45.0, 1.0 .. 50.0); 8 | cam.look_at([-4.0, 15.0, 10.0], [0.0, 0.0, 2.0], None); 9 | 10 | let hemisphere_light = win.factory.hemisphere_light(0xffffff, 0x8080ff, 0.5); 11 | let ambient_light = win.factory.ambient_light(0xffffffff, 0.5); 12 | let point_light = win.factory.point_light(0xffffff, 0.9); 13 | point_light.set_position([15.0, 35.0, 35.0]); 14 | 15 | let mut dir_light = win.factory.directional_light(0xffffff, 0.9); 16 | dir_light.look_at([15.0, 35.0, 35.0], [0.0, 0.0, 2.0], None); 17 | let shadow_map = win.factory.shadow_map(1024, 1024); 18 | let _debug_shadow = win.renderer 19 | .debug_shadow_quad(&shadow_map, 1, [10, 10], [256, 256]); 20 | dir_light.set_shadow(shadow_map, 40.0, 1.0 .. 200.0); 21 | 22 | let lights: [&three::object::Base; 4] = [ 23 | hemisphere_light.as_ref(), 24 | ambient_light.as_ref(), 25 | point_light.as_ref(), 26 | dir_light.as_ref(), 27 | ]; 28 | for l in &lights { 29 | l.set_visible(false); 30 | win.scene.add(l); 31 | } 32 | 33 | let sphere = { 34 | let geometry = three::Geometry::uv_sphere(3.0, 20, 20); 35 | let material = three::material::Phong { 36 | color: 0xffA0A0, 37 | glossiness: 80.0, 38 | }; 39 | win.factory.mesh(geometry, material) 40 | }; 41 | sphere.set_position([0.0, 0.0, 2.5]); 42 | win.scene.add(&sphere); 43 | 44 | let plane = { 45 | let geometry = three::Geometry::plane(100.0, 100.0); 46 | let material = three::material::Lambert { 47 | color: 0xA0ffA0, 48 | flat: false, 49 | }; 50 | win.factory.mesh(geometry, material) 51 | }; 52 | plane.set_position([0.0, -30.0, 0.0]); 53 | win.scene.add(&plane); 54 | 55 | let mut light_id = 0i8; 56 | lights[0].set_visible(true); 57 | while win.update() && !win.input.hit(three::KEY_ESCAPE) { 58 | if let Some(axis_hits) = win.input.delta(three::AXIS_LEFT_RIGHT) { 59 | lights[light_id as usize].set_visible(false); 60 | light_id += axis_hits; 61 | while light_id < 0 { 62 | light_id += lights.len() as i8; 63 | } 64 | light_id %= lights.len() as i8; 65 | lights[light_id as usize].set_visible(true); 66 | } 67 | 68 | win.render(&cam); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/materials.rs: -------------------------------------------------------------------------------- 1 | extern crate three; 2 | 3 | use three::Object; 4 | 5 | fn main() { 6 | let mut win = three::Window::new("Three-rs materials example"); 7 | let cam = win.factory.perspective_camera(75.0, 1.0 .. 50.0); 8 | cam.set_position([0.0, 0.0, 10.0]); 9 | 10 | let light = win.factory.point_light(0xffffff, 0.5); 11 | let mut pos = [0.0, 5.0, 5.0]; 12 | light.set_position(pos); 13 | win.scene.add(&light); 14 | 15 | let geometry = three::Geometry::cylinder(1.0, 2.0, 2.0, 5); 16 | let mut materials: Vec = vec![ 17 | three::material::Basic { 18 | color: 0xFFFFFF, 19 | map: None, 20 | }.into(), 21 | three::material::Lambert { 22 | color: 0xFFFFFF, 23 | flat: true, 24 | }.into(), 25 | three::material::Lambert { 26 | color: 0xFFFFFF, 27 | flat: false, 28 | }.into(), 29 | three::material::Phong { 30 | color: 0xFFFFFF, 31 | glossiness: 80.0, 32 | }.into(), 33 | three::material::Pbr { 34 | base_color_factor: 0xFFFFFF, 35 | base_color_alpha: 1.0, 36 | metallic_factor: 0.5, 37 | roughness_factor: 0.5, 38 | occlusion_strength: 0.2, 39 | emissive_factor: 0x000000, 40 | normal_scale: 1.0, 41 | base_color_map: None, 42 | normal_map: None, 43 | emissive_map: None, 44 | metallic_roughness_map: None, 45 | occlusion_map: None, 46 | }.into(), 47 | ]; 48 | let count = materials.len(); 49 | 50 | let _cubes: Vec<_> = materials 51 | .drain(..) 52 | .enumerate() 53 | .map(|(i, mat)| { 54 | let offset = 4.0 * (i as f32 + 0.5 - 0.5 * count as f32); 55 | let mesh = win.factory.mesh(geometry.clone(), mat); 56 | mesh.set_position([offset, 0.0, 0.0]); 57 | win.scene.add(&mesh); 58 | mesh 59 | }) 60 | .collect(); 61 | 62 | while win.update() && !win.input.hit(three::KEY_ESCAPE) { 63 | if let Some(diff) = win.input.timed(three::AXIS_LEFT_RIGHT) { 64 | pos[0] += 5.0 * diff; 65 | light.set_position(pos); 66 | } 67 | 68 | win.render(&cam); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /examples/mesh-update.rs: -------------------------------------------------------------------------------- 1 | extern crate cgmath; 2 | extern crate mint; 3 | extern crate three; 4 | 5 | use cgmath::prelude::*; 6 | use std::f32::consts::PI; 7 | 8 | 9 | fn make_tetrahedron_geometry() -> three::Geometry { 10 | let vertices = vec![ 11 | mint::Point3 { 12 | x: 0.0, 13 | y: 1.0, 14 | z: 0.0, 15 | }, 16 | mint::Point3 { 17 | x: 0.0, 18 | y: 0.0, 19 | z: 1.0, 20 | }, 21 | mint::Point3 { 22 | x: (2.0 * PI / 3.0).sin(), 23 | y: 0.0, 24 | z: (2.0 * PI / 3.0).cos(), 25 | }, 26 | mint::Point3 { 27 | x: (4.0 * PI / 3.0).sin(), 28 | y: 0.0, 29 | z: (4.0 * PI / 3.0).cos(), 30 | }, 31 | ]; 32 | let faces = vec![[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]]; 33 | three::Geometry { 34 | faces, 35 | base: three::Shape { 36 | vertices, 37 | ..three::Shape::default() 38 | }, 39 | ..three::Geometry::default() 40 | } 41 | } 42 | 43 | fn main() { 44 | let mut win = three::Window::new("Three-rs Mesh Update Example"); 45 | let cam = win.factory.perspective_camera(60.0, 1.0 .. 10.0); 46 | let mut controls = three::controls::Orbit::builder(&cam) 47 | .position([0.0, 2.0, -5.0]) 48 | .target([0.0, 0.0, 0.0]) 49 | .build(); 50 | 51 | let geometry = make_tetrahedron_geometry(); 52 | let material = three::material::Wireframe { color: 0xFFFF00 }; 53 | let mut mesh = win.factory.mesh_dynamic(geometry, material); 54 | let vertex_count = mesh.vertex_count(); 55 | win.scene.add(&mesh); 56 | 57 | let mut timer = three::Timer::new(); 58 | let mut vi = 0; 59 | while win.update() && !win.input.hit(three::KEY_ESCAPE) { 60 | let elapsed_time = timer.elapsed(); 61 | if elapsed_time > 1.0 { 62 | // Reset the timer. 63 | timer.reset(); 64 | // Update the vertex `vi`. 65 | let mut vmap = win.factory.map_vertices(&mut mesh); 66 | let dir = cgmath::Vector4::from(vmap[vi].pos).truncate(); 67 | let pos = cgmath::Point3::from_vec(1.2 * dir); 68 | vmap[vi].pos = [pos.x, pos.y, pos.z, 1.0]; 69 | // Increment vertex index. 70 | vi = (vi + 1) % vertex_count; 71 | } 72 | controls.update(&win.input); 73 | win.render(&cam); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/obj.rs: -------------------------------------------------------------------------------- 1 | extern crate three; 2 | 3 | use std::env; 4 | use three::Object; 5 | 6 | fn main() { 7 | let mut args = env::args(); 8 | let obj_path = concat!(env!("CARGO_MANIFEST_DIR"), "/test_data/car.obj"); 9 | let path = args.nth(1).unwrap_or(obj_path.into()); 10 | let mut win = three::Window::new("Three-rs obj loading example"); 11 | let cam = win.factory.perspective_camera(60.0, 1.0 .. 1000.0); 12 | let mut controls = three::controls::Orbit::builder(&cam) 13 | .position([0.0, 2.0, -5.0]) 14 | .target([0.0, 0.0, 0.0]) 15 | .build(); 16 | 17 | let dir_light = win.factory.directional_light(0xffffff, 0.9); 18 | dir_light.look_at([15.0, 35.0, 35.0], [0.0, 0.0, 2.0], None); 19 | win.scene.add(&dir_light); 20 | 21 | let root = win.factory.group(); 22 | win.scene.add(&root); 23 | let (mut group_map, _meshes) = win.factory.load_obj(&path); 24 | for g in group_map.values_mut() { 25 | root.add(g); 26 | } 27 | 28 | while win.update() && !win.input.hit(three::KEY_ESCAPE) { 29 | controls.update(&win.input); 30 | win.render(&cam); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/reload.rs: -------------------------------------------------------------------------------- 1 | extern crate notify; 2 | extern crate three; 3 | 4 | use std::{env, fs, io}; 5 | use std::sync::mpsc; 6 | 7 | use notify::Watcher; 8 | use std::path::{Path, PathBuf}; 9 | use std::time::Duration; 10 | use three::Object; 11 | 12 | const MANDELBROT_VERTEX_SHADER_CODE: &'static str = r#" 13 | #version 150 core 14 | #include 15 | #include 16 | 17 | in vec4 a_Position; 18 | in vec2 a_TexCoord; 19 | 20 | out vec2 v_TexCoord; 21 | 22 | void main() { 23 | v_TexCoord = mix(u_UvRange.xy, u_UvRange.zw, a_TexCoord); 24 | gl_Position = u_ViewProj * u_World * a_Position; 25 | } 26 | "#; 27 | 28 | const MANDELBROT_PIXEL_SHADER_CODE: &'static str = r#" 29 | #version 150 core 30 | 31 | in vec2 v_TexCoord; 32 | out vec4 Target0; 33 | 34 | uniform sampler2D t_Map; 35 | 36 | const float SCALE = 3.0; 37 | const vec2 CENTER = vec2(0.5, 0.0); 38 | const int ITER = 100; 39 | 40 | void main() { 41 | vec2 c; 42 | c.x = 1.3333 * (v_TexCoord.x - 0.5) * SCALE - CENTER.x; 43 | c.y = (v_TexCoord.y - 0.5) * SCALE - CENTER.y; 44 | 45 | int i; 46 | vec2 z = c; 47 | for (i = 0; i < ITER; ++i) { 48 | float x = (z.x * z.x - z.y * z.y) + c.x; 49 | float y = (z.y * z.x + z.x * z.y) + c.y; 50 | if ((x * x + y * y) > 4.0) { 51 | break; 52 | } 53 | z.x = x; 54 | z.y = y; 55 | } 56 | 57 | vec2 t = vec2(i == ITER ? 0.0 : float(i) / 100.0, 0.5); 58 | Target0 = texture(t_Map, t); 59 | } 60 | "#; 61 | 62 | fn main() { 63 | let dir = env::args() 64 | .nth(1) 65 | .map(PathBuf::from) 66 | .or(env::current_dir().ok()) 67 | .unwrap(); 68 | 69 | use io::Write; 70 | let _ = fs::create_dir_all(&dir); 71 | fs::File::create(dir.join("sprite_vs.glsl")) 72 | .unwrap() 73 | .write_all(MANDELBROT_VERTEX_SHADER_CODE.as_bytes()) 74 | .unwrap(); 75 | fs::File::create(dir.join("sprite_ps.glsl")) 76 | .unwrap() 77 | .write_all(MANDELBROT_PIXEL_SHADER_CODE.as_bytes()) 78 | .unwrap(); 79 | 80 | println!("Edit sprite_vs.glsl or sprite_ps.glsl and review."); 81 | 82 | let mut win = three::Window::new("Three-rs shader reloading example"); 83 | let cam = win.factory 84 | .orthographic_camera([0.0, 0.0], 1.0, -1.0 .. 1.0); 85 | 86 | let (tx, rx) = mpsc::channel(); 87 | let mut watcher = notify::watcher(tx, Duration::from_secs(1)).unwrap(); 88 | watcher 89 | .watch(&dir, notify::RecursiveMode::NonRecursive) 90 | .unwrap(); 91 | 92 | let map_path = concat!(env!("CARGO_MANIFEST_DIR"), "/test_data/texture.png"); 93 | let map = win.factory.load_texture(map_path); 94 | let material = three::material::Sprite { map }; 95 | let sprite = win.factory.sprite(material); 96 | sprite.set_scale(1.0); 97 | win.scene.add(&sprite); 98 | 99 | let mut reload = true; 100 | while win.update() && !win.input.hit(three::KEY_ESCAPE) { 101 | while let Ok(event) = rx.try_recv() { 102 | use notify::DebouncedEvent::{Create, Write}; 103 | match event { 104 | Create(_) | Write(_) => reload = true, 105 | _ => {} 106 | } 107 | } 108 | if reload { 109 | reload = false; 110 | let source_set = three::render::source::Set { 111 | sprite: three::render::source::Sprite::user(&dir).unwrap(), 112 | ..Default::default() 113 | }; 114 | match three::render::PipelineStates::new(&source_set, &mut win.factory) { 115 | Ok(pipeline_states) => win.renderer.reload(pipeline_states), 116 | Err(err) => println!("{:#?}", err), 117 | } 118 | } 119 | win.render(&cam); 120 | } 121 | } 122 | 123 | /// Reads the entire contents of a file into a `String`. 124 | pub fn read_file_to_string(path: &Path) -> io::Result { 125 | use self::io::Read; 126 | let file = fs::File::open(path)?; 127 | let len = file.metadata()?.len() as usize; 128 | let mut contents = String::with_capacity(len); 129 | let _ = io::BufReader::new(file).read_to_string(&mut contents)?; 130 | Ok(contents) 131 | } 132 | -------------------------------------------------------------------------------- /examples/shapes.rs: -------------------------------------------------------------------------------- 1 | extern crate cgmath; 2 | extern crate mint; 3 | extern crate three; 4 | 5 | use cgmath::prelude::*; 6 | use three::Object; 7 | 8 | fn main() { 9 | let mut win = three::Window::new("Three-rs shapes example"); 10 | let cam = win.factory.perspective_camera(75.0, 1.0 .. 50.0); 11 | cam.set_position([0.0, 0.0, 10.0]); 12 | 13 | let mbox = { 14 | let geometry = three::Geometry::cuboid(3.0, 2.0, 1.0); 15 | let material = three::material::Wireframe { color: 0x00FF00 }; 16 | win.factory.mesh(geometry, material) 17 | }; 18 | mbox.set_position([-3.0, -3.0, 0.0]); 19 | win.scene.add(&mbox); 20 | 21 | let mcyl = { 22 | let geometry = three::Geometry::cylinder(1.0, 2.0, 2.0, 5); 23 | let material = three::material::Wireframe { color: 0xFF0000 }; 24 | win.factory.mesh(geometry, material) 25 | }; 26 | mcyl.set_position([3.0, -3.0, 0.0]); 27 | win.scene.add(&mcyl); 28 | 29 | let msphere = { 30 | let geometry = three::Geometry::uv_sphere(2.0, 5, 5); 31 | let material = three::material::Wireframe { color: 0xFF0000 }; 32 | win.factory.mesh(geometry, material) 33 | }; 34 | msphere.set_position([-3.0, 3.0, 0.0]); 35 | win.scene.add(&msphere); 36 | 37 | // test removal from scene 38 | win.scene.remove(&mcyl); 39 | win.scene.remove(&mbox); 40 | win.scene.add(&mcyl); 41 | win.scene.add(&mbox); 42 | 43 | let mline = { 44 | let geometry = three::Geometry::with_vertices(vec![ 45 | [-2.0, -1.0, 0.0].into(), 46 | [0.0, 1.0, 0.0].into(), 47 | [2.0, -1.0, 0.0].into(), 48 | ]); 49 | let material = three::material::Line { color: 0x0000FF }; 50 | win.factory.mesh(geometry, material) 51 | }; 52 | mline.set_position([3.0, 3.0, 0.0]); 53 | win.scene.add(&mline); 54 | 55 | let mut angle = cgmath::Rad::zero(); 56 | while win.update() && !win.input.hit(three::KEY_ESCAPE) { 57 | if let Some(diff) = win.input.timed(three::AXIS_LEFT_RIGHT) { 58 | angle += cgmath::Rad(1.5 * diff); 59 | let q = cgmath::Quaternion::from_angle_y(angle); 60 | mbox.set_orientation(q); 61 | mcyl.set_orientation(q); 62 | msphere.set_orientation(q); 63 | mline.set_orientation(q); 64 | } 65 | win.render(&cam); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /examples/sprite.rs: -------------------------------------------------------------------------------- 1 | extern crate three; 2 | 3 | use three::Object; 4 | 5 | struct Animator { 6 | cell_size: [u16; 2], 7 | cell_counts: [u16; 2], 8 | duration: f32, 9 | repeat: bool, 10 | sprite: three::Sprite, 11 | current: [u16; 2], 12 | timer: three::Timer, 13 | } 14 | 15 | impl Animator { 16 | fn update_uv(&mut self) { 17 | let base = [ 18 | (self.current[0] * self.cell_size[0]) as i16, 19 | (self.current[1] * self.cell_size[1]) as i16, 20 | ]; 21 | self.sprite.set_texel_range(base, self.cell_size); 22 | } 23 | 24 | fn update( 25 | &mut self, 26 | switch_row: Option, 27 | ) { 28 | if let Some(row) = switch_row { 29 | self.timer.reset(); 30 | self.current = [0, row]; 31 | self.update_uv(); 32 | } else if self.timer.elapsed() >= self.duration && (self.repeat || self.current[0] < self.cell_counts[0]) { 33 | self.timer.reset(); 34 | self.current[0] += 1; 35 | if self.current[0] < self.cell_counts[0] { 36 | self.update_uv(); 37 | } else if self.repeat { 38 | self.current[0] = 0; 39 | self.update_uv(); 40 | } 41 | } 42 | } 43 | } 44 | 45 | fn main() { 46 | let mut win = three::Window::new("Three-rs sprite example"); 47 | let cam = win.factory 48 | .orthographic_camera([0.0, 0.0], 10.0, -10.0 .. 10.0); 49 | 50 | let pikachu_path: String = format!("{}/test_data/pikachu_anim.png", env!("CARGO_MANIFEST_DIR")); 51 | let pikachu_path_str: &str = pikachu_path.as_str(); 52 | let material = three::material::Sprite { 53 | map: win.factory.load_texture(pikachu_path_str), 54 | }; 55 | let sprite = win.factory.sprite(material); 56 | sprite.set_scale(8.0); 57 | win.scene.add(&sprite); 58 | 59 | let mut anim = Animator { 60 | cell_size: [96, 96], 61 | cell_counts: [5, 13], 62 | duration: 0.1, 63 | repeat: true, 64 | current: [0, 0], 65 | timer: three::Timer::new(), 66 | sprite, 67 | }; 68 | anim.update_uv(); 69 | 70 | // Specify background image. Remove `if` to enable. 71 | if false { 72 | let background = win.factory.load_texture("test_data/texture.png"); 73 | win.scene.background = three::Background::Texture(background); 74 | } 75 | 76 | while win.update() && !win.input.hit(three::KEY_ESCAPE) { 77 | let row = win.input.delta(three::AXIS_LEFT_RIGHT).map(|mut diff| { 78 | let total = anim.cell_counts[1] as i8; 79 | while diff < 0 { 80 | diff += total 81 | } 82 | (anim.current[1] + diff as u16) % total as u16 83 | }); 84 | anim.update(row); 85 | 86 | win.render(&cam); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /examples/stl.rs: -------------------------------------------------------------------------------- 1 | extern crate three; 2 | extern crate mint; 3 | extern crate froggy; 4 | 5 | use std::env; 6 | use three::{Geometry, Object}; 7 | use mint::Point3; 8 | use froggy::WeakPointer; 9 | 10 | fn main() { 11 | let mut args = env::args(); 12 | let path = args.nth(1).expect("Please provide STL file path"); 13 | let mut vertices = vec!(); 14 | 15 | match stlv::parser::load_file(path.as_str()) { 16 | Ok(model) => { 17 | for triangle in (*model).iter() { 18 | let stl_vertices = triangle.vertices(); 19 | vertices.push(Point3 { 20 | x: stl_vertices[0].get_x(), 21 | y: stl_vertices[0].get_y(), 22 | z: stl_vertices[0].get_z(), 23 | }); 24 | vertices.push(Point3 { 25 | x: stl_vertices[1].get_x(), 26 | y: stl_vertices[1].get_y(), 27 | z: stl_vertices[1].get_z(), 28 | }); 29 | vertices.push(Point3 { 30 | x: stl_vertices[2].get_x(), 31 | y: stl_vertices[2].get_y(), 32 | z: stl_vertices[2].get_z(), 33 | }); 34 | } 35 | } 36 | _ => panic!("Failed to parse the STL file {}", path), 37 | } 38 | 39 | let geometry = Geometry::with_vertices(vertices); 40 | 41 | // Upload the triangle data to the GPU. 42 | let mut window = three::Window::new("Loading STL..."); 43 | 44 | // Create multiple meshes with the same GPU data and material. 45 | let material = three::material::Wireframe{color: 0xff0000}; 46 | 47 | let mesh = window.factory.mesh(geometry, material); 48 | window.scene.add(&mesh); 49 | 50 | let cam = window.factory.perspective_camera(60.0, 1.0 .. 1000.0); 51 | let mut controls = three::controls::Orbit::builder(&cam) 52 | .position([0.0, 2.0, -5.0]) 53 | .target([0.0, 0.0, 0.0]) 54 | .build(); 55 | 56 | let dir_light = window.factory.directional_light(0xffffff, 0.9); 57 | dir_light.look_at([15.0, 35.0, 35.0], [0.0, 0.0, 2.0], None); 58 | window.scene.add(&dir_light); 59 | 60 | while window.update() && !window.input.hit(three::KEY_ESCAPE) { 61 | controls.update(&window.input); 62 | window.render(&cam); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /examples/text.rs: -------------------------------------------------------------------------------- 1 | extern crate three; 2 | 3 | fn main() { 4 | let mut window = three::Window::new("Three-rs text example"); 5 | 6 | window.scene.background = three::Background::Color(0x111111); 7 | 8 | let center = [0.0, 0.0]; 9 | let yextent = 1.0; 10 | let zrange = -1.0 .. 1.0; 11 | let camera = window.factory.orthographic_camera(center, yextent, zrange); 12 | 13 | let deja_vu = window.factory.load_font(format!( 14 | "{}/data/fonts/DejaVuSans.ttf", 15 | env!("CARGO_MANIFEST_DIR") 16 | )); 17 | let karla = window.factory.load_font_karla(); 18 | 19 | let mut counter_text = window.factory.ui_text(&deja_vu, ""); 20 | counter_text.set_font_size(20.0); 21 | window.scene.add(&counter_text); 22 | 23 | let mut greeting = window.factory.ui_text(&karla, "Hello World!"); 24 | greeting.set_font_size(80.0); 25 | greeting.set_pos([100.0, 100.0]); 26 | greeting.set_color(0xFF0000); 27 | window.scene.add(&greeting); 28 | 29 | let mut lenny = window.factory.ui_text(&deja_vu, "( ͡° ͜ʖ ͡°)"); 30 | lenny.set_font_size(60.0); 31 | lenny.set_color(0x2222FF); 32 | window.scene.add(&lenny); 33 | 34 | let mut counter = 0; 35 | while window.update() { 36 | counter_text.set_text(format!("Counter: {}", counter)); 37 | lenny.set_pos([(counter % 300) as f32, 200.0]); 38 | window.render(&camera); 39 | counter += 1; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/tutorial.rs: -------------------------------------------------------------------------------- 1 | extern crate three; 2 | 3 | fn main() { 4 | let mut window = three::Window::new("Getting started with three-rs"); 5 | 6 | let vertices = vec![ 7 | [-0.5, -0.5, -0.5].into(), 8 | [0.5, -0.5, -0.5].into(), 9 | [0.0, 0.5, -0.5].into(), 10 | ]; 11 | let geometry = three::Geometry::with_vertices(vertices); 12 | let material = three::material::Basic { 13 | color: 0xFFFF00, 14 | map: None, 15 | }; 16 | let mesh = window.factory.mesh(geometry, material); 17 | window.scene.add(&mesh); 18 | window.scene.background = three::Background::Color(0xC6F0FF); 19 | 20 | let center = [0.0, 0.0]; 21 | let yextent = 1.0; 22 | let zrange = -1.0 .. 1.0; 23 | let camera = window.factory.orthographic_camera(center, yextent, zrange); 24 | 25 | while window.update() { 26 | window.render(&camera); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | reorder_extern_crates = true 3 | reorder_extern_crates_in_group = true 4 | reorder_imported_names = true 5 | reorder_imports_in_group = true 6 | error_on_line_overflow = false 7 | max_width = 15000 8 | spaces_around_ranges = true 9 | fn_args_density = "Vertical" 10 | -------------------------------------------------------------------------------- /src/audio.rs: -------------------------------------------------------------------------------- 1 | //! Primitives for audio playback. 2 | 3 | use hub; 4 | use object::{Base, ObjectType}; 5 | use std::fmt; 6 | use std::io::Cursor; 7 | use std::rc::Rc; 8 | use std::time::Duration; 9 | 10 | use rodio as r; 11 | use rodio::Source as _Source; 12 | 13 | /// Audio segment with sound effects. 14 | /// 15 | /// Can be loaded from file using [`Factory::load_audio`](struct.Factory.html#method.load_audio). 16 | #[derive(Debug, Clone)] 17 | pub struct Clip { 18 | data: Rc>, 19 | repeat: bool, 20 | duration: Option, 21 | delay: Option, 22 | fade_in: Option, 23 | speed: f32, 24 | } 25 | 26 | impl Clip { 27 | pub(crate) fn new(data: Vec) -> Self { 28 | Clip { 29 | data: Rc::new(data), 30 | repeat: false, 31 | duration: None, 32 | delay: None, 33 | fade_in: None, 34 | speed: 1.0, 35 | } 36 | } 37 | 38 | /// Passing true enforces looping sound. Defaults to `false`. 39 | pub fn repeat( 40 | &mut self, 41 | enable: bool, 42 | ) { 43 | self.repeat = enable; 44 | } 45 | 46 | /// Clip the sound to the desired duration. 47 | pub fn take_duration( 48 | &mut self, 49 | duration: Duration, 50 | ) { 51 | self.duration = Some(duration); 52 | } 53 | 54 | /// Play sound after desired delay. 55 | pub fn delay( 56 | &mut self, 57 | delay: Duration, 58 | ) { 59 | self.delay = Some(delay); 60 | } 61 | 62 | /// Fade in sound in desired duration. 63 | pub fn fade_in( 64 | &mut self, 65 | duration: Duration, 66 | ) { 67 | self.fade_in = Some(duration); 68 | } 69 | 70 | /// Adjust the playback speed. Defaults to `1.0`. 71 | pub fn speed( 72 | &mut self, 73 | ratio: f32, 74 | ) { 75 | self.speed = ratio; 76 | } 77 | } 78 | 79 | #[derive(Debug, Clone)] 80 | pub(crate) enum Operation { 81 | Append(Clip), 82 | Resume, 83 | Pause, 84 | Stop, 85 | SetVolume(f32), 86 | } 87 | 88 | #[derive(Debug)] 89 | pub(crate) struct AudioData { 90 | pub(crate) source: SourceInternal, 91 | } 92 | 93 | impl AudioData { 94 | pub(crate) fn new() -> Self { 95 | // TODO: Change to `r::default_endpoint()` in next `rodio` release. 96 | #[allow(deprecated)] 97 | let endpoint = if let Some(endpoint) = r::default_output_device() { 98 | endpoint 99 | } else { 100 | // TODO: Better error handling 101 | panic!("Can't get default audio endpoint, can't play sound"); 102 | }; 103 | let sink = r::Sink::new(&endpoint); 104 | AudioData { 105 | source: SourceInternal::D2(sink), 106 | } 107 | } 108 | } 109 | 110 | /// Audio source. Can play only one sound at a time. 111 | /// 112 | /// You must add it to the scene to play sounds. 113 | /// You may create several `Source`s to play sounds simultaneously. 114 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 115 | pub struct Source { 116 | pub(crate) object: Base, 117 | } 118 | three_object!(Source::object); 119 | derive_DowncastObject!(Source => ObjectType::AudioSource); 120 | 121 | impl Source { 122 | pub(crate) fn with_object(object: Base) -> Self { 123 | Source { object } 124 | } 125 | 126 | /// Add clip to the queue. 127 | pub fn play( 128 | &self, 129 | clip: &Clip, 130 | ) { 131 | let msg = hub::Operation::SetAudio(Operation::Append(clip.clone())); 132 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 133 | } 134 | 135 | /// Pause current sound. 136 | /// 137 | /// You can [`resume`](struct.Source.html#method.resume) playback. 138 | pub fn pause(&self) { 139 | let msg = hub::Operation::SetAudio(Operation::Pause); 140 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 141 | } 142 | 143 | /// Resume playback after [`pausing`](struct.Source.html#method.pause). 144 | pub fn resume(&self) { 145 | let msg = hub::Operation::SetAudio(Operation::Resume); 146 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 147 | } 148 | 149 | /// Stop the playback by emptying the queue. 150 | pub fn stop(&self) { 151 | let msg = hub::Operation::SetAudio(Operation::Stop); 152 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 153 | } 154 | 155 | /// Adjust playback volume. 156 | /// 157 | /// Default value is `1.0`. 158 | pub fn set_volume( 159 | &self, 160 | volume: f32, 161 | ) { 162 | let msg = hub::Operation::SetAudio(Operation::SetVolume(volume)); 163 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 164 | } 165 | } 166 | 167 | //TODO: Remove dead_code lint 168 | #[allow(dead_code)] 169 | pub(crate) enum SourceInternal { 170 | D2(r::Sink), 171 | D3(r::SpatialSink), 172 | } 173 | 174 | impl fmt::Debug for SourceInternal { 175 | fn fmt( 176 | &self, 177 | f: &mut fmt::Formatter, 178 | ) -> fmt::Result { 179 | match *self { 180 | SourceInternal::D2(_) => write!(f, "SourceInternal::D2"), 181 | SourceInternal::D3(_) => write!(f, "SourceInternal::D3"), 182 | } 183 | } 184 | } 185 | 186 | impl SourceInternal { 187 | pub(crate) fn pause(&self) { 188 | match *self { 189 | SourceInternal::D2(ref sink) => sink.pause(), 190 | _ => unimplemented!(), 191 | } 192 | } 193 | 194 | pub(crate) fn resume(&self) { 195 | match *self { 196 | SourceInternal::D2(ref sink) => sink.play(), 197 | _ => unimplemented!(), 198 | } 199 | } 200 | 201 | pub(crate) fn stop(&self) { 202 | match *self { 203 | SourceInternal::D2(ref sink) => sink.stop(), 204 | _ => unimplemented!(), 205 | } 206 | } 207 | 208 | pub(crate) fn set_volume( 209 | &mut self, 210 | volume: f32, 211 | ) { 212 | match *self { 213 | SourceInternal::D2(ref mut sink) => sink.set_volume(volume), 214 | _ => unimplemented!(), 215 | } 216 | } 217 | 218 | pub(crate) fn append( 219 | &mut self, 220 | clip: Clip, 221 | ) { 222 | match *self { 223 | SourceInternal::D2(ref mut sink) => { 224 | let vec: Vec = (&*clip.data).clone(); 225 | let decoder = r::Decoder::new(Cursor::new(vec)); 226 | let mut boxed: Box + Send> = if let Ok(decoder) = decoder { 227 | Box::new(decoder) 228 | } else { 229 | eprintln!("Can't recognize audio clip format, can't play sound"); 230 | return; 231 | }; 232 | if clip.repeat { 233 | boxed = Box::new(boxed.repeat_infinite()); 234 | } 235 | if clip.speed != 1.0 { 236 | boxed = Box::new(boxed.speed(clip.speed)); 237 | } 238 | if let Some(duration) = clip.delay { 239 | boxed = Box::new(boxed.delay(duration)); 240 | } 241 | if let Some(duration) = clip.duration { 242 | boxed = Box::new(boxed.take_duration(duration)); 243 | } 244 | if let Some(duration) = clip.fade_in { 245 | boxed = Box::new(boxed.fade_in(duration)); 246 | } 247 | sink.append(boxed); 248 | } 249 | SourceInternal::D3(_) => unimplemented!(), 250 | } 251 | } 252 | } 253 | 254 | /* TODO: Implement 3d sound. 255 | pub struct Listener { 256 | pub(crate) object: object::Base, 257 | } 258 | */ 259 | -------------------------------------------------------------------------------- /src/camera.rs: -------------------------------------------------------------------------------- 1 | //! Cameras are used to view scenes from any point in the world. 2 | //! 3 | //! ## Projections 4 | //! 5 | //! ### Finite perspective 6 | //! 7 | //! Finite persepective projections are often used for 3D rendering. In a finite 8 | //! perspective projection, objects moving away from the camera appear smaller and 9 | //! are occluded by objects that are closer to the camera. 10 | //! 11 | //! Finite [`Perspective`] projections are created with the 12 | //! [`Factory::perspective_camera`] method with a bounded range. 13 | //! 14 | //! ```rust,no_run 15 | //! # let mut window = three::Window::new(""); 16 | //! # let _ = { 17 | //! window.factory.perspective_camera(60.0, 0.1 .. 1.0); 18 | //! # }; 19 | //! ``` 20 | //! 21 | //! ### Infinite perspective 22 | //! 23 | //! Infinite perspective projections are perspective projections with `zfar` planes 24 | //! at infinity. This means objects are never considered to be 'too far away' to be 25 | //! visible by the camera. 26 | //! 27 | //! Infinite [`Perspective`] projections are created with the 28 | //! [`Factory::perspective_camera`] method with an unbounded range. 29 | //! 30 | //! ```rust,no_run 31 | //! # let mut window = three::Window::new(""); 32 | //! # let _ = { 33 | //! window.factory.perspective_camera(60.0, 0.1 ..); 34 | //! # }; 35 | //! ``` 36 | //! 37 | //! ### Orthographic 38 | //! 39 | //! Orthographic projections are often used for 2D rendering. In an orthographic 40 | //! projection, objects moving away from the camera retain their size but are 41 | //! occluded by objects that are closer to the camera. 42 | //! 43 | //! [`Orthographic`] projections are created with the 44 | //! [`Factory::orthographic_camera`] method. 45 | //! 46 | //! ```rust,no_run 47 | //! # let mut window = three::Window::new(""); 48 | //! # let _ = { 49 | //! window.factory.orthographic_camera([0.0, 0.0], 1.0, -1.0 .. 1.0) 50 | //! # }; 51 | //! ``` 52 | //! 53 | //! [`Factory::orthographic_camera`]: ../factory/struct.Factory.html#method.orthographic_camera 54 | //! [`Factory::perspective_camera`]: ../factory/struct.Factory.html#method.perspective_camera 55 | //! [`object::Base`]: ../object/struct.Base.html 56 | //! [`Orthographic`]: struct.Orthographic.html 57 | //! [`Perspective`]: struct.Perspective.html 58 | 59 | use cgmath; 60 | use mint; 61 | 62 | use hub::{Hub, Operation, SubNode}; 63 | use object::{Base, DowncastObject, Object, ObjectType}; 64 | use scene::SyncGuard; 65 | 66 | use std::ops; 67 | 68 | /// The Z values of the near and far clipping planes of a camera's projection. 69 | #[derive(Clone, Debug, PartialEq)] 70 | pub enum ZRange { 71 | /// Z range for a finite projection. 72 | Finite(ops::Range), 73 | 74 | /// Z range for an infinite projection. 75 | Infinite(ops::RangeFrom), 76 | } 77 | 78 | impl From> for ZRange { 79 | fn from(range: ops::Range) -> ZRange { 80 | ZRange::Finite(range) 81 | } 82 | } 83 | 84 | impl From> for ZRange { 85 | fn from(range: ops::RangeFrom) -> ZRange { 86 | ZRange::Infinite(range) 87 | } 88 | } 89 | 90 | /// A camera's projection. 91 | #[derive(Clone, Debug, PartialEq)] 92 | pub enum Projection { 93 | /// An orthographic projection. 94 | Orthographic(Orthographic), 95 | /// A perspective projection. 96 | Perspective(Perspective), 97 | } 98 | 99 | /// Camera is used to render Scene with specific [`Projection`]. 100 | /// 101 | /// [`Projection`]: enum.Projection.html 102 | #[derive(Clone, Debug, PartialEq)] 103 | pub struct Camera { 104 | pub(crate) object: Base, 105 | } 106 | 107 | impl AsRef for Camera { 108 | fn as_ref(&self) -> &Base { &self.object } 109 | } 110 | 111 | impl Object for Camera { 112 | type Data = Projection; 113 | 114 | fn resolve_data(&self, sync_guard: &SyncGuard) -> Self::Data { 115 | match &sync_guard.hub[self].sub_node { 116 | SubNode::Camera(ref projection) => projection.clone(), 117 | sub_node @ _ => panic!("`Group` had a bad sub node type: {:?}", sub_node), 118 | } 119 | } 120 | } 121 | 122 | impl Camera { 123 | pub(crate) fn new(hub: &mut Hub, projection: Projection) -> Self { 124 | Camera { 125 | object: hub.spawn(SubNode::Camera(projection)), 126 | } 127 | } 128 | 129 | /// Sets the projection used by the camera. 130 | pub fn set_projection>(&self, projection: P) { 131 | self.as_ref().send(Operation::SetProjection(projection.into())); 132 | } 133 | } 134 | 135 | impl DowncastObject for Camera { 136 | fn downcast(object_type: ObjectType) -> Option { 137 | match object_type { 138 | ObjectType::Camera(camera) => Some(camera), 139 | _ => None, 140 | } 141 | } 142 | } 143 | 144 | impl Projection { 145 | /// Constructs an orthographic projection. 146 | pub fn orthographic

( 147 | center: P, 148 | extent_y: f32, 149 | range: ops::Range, 150 | ) -> Self 151 | where 152 | P: Into>, 153 | { 154 | let center = center.into(); 155 | Projection::Orthographic(Orthographic { 156 | center, 157 | extent_y, 158 | range, 159 | }) 160 | } 161 | 162 | /// Constructs a perspective projection. 163 | pub fn perspective( 164 | fov_y: f32, 165 | range: R, 166 | ) -> Self 167 | where 168 | R: Into, 169 | { 170 | Projection::Perspective(Perspective { 171 | fov_y, 172 | zrange: range.into(), 173 | }) 174 | } 175 | 176 | /// Computes the projection matrix representing the camera's projection. 177 | pub fn matrix( 178 | &self, 179 | aspect_ratio: f32, 180 | ) -> mint::ColumnMatrix4 { 181 | match *self { 182 | Projection::Orthographic(ref x) => x.matrix(aspect_ratio), 183 | Projection::Perspective(ref x) => x.matrix(aspect_ratio), 184 | } 185 | } 186 | } 187 | 188 | /// Orthographic projection parameters. 189 | #[derive(Clone, Debug, PartialEq)] 190 | pub struct Orthographic { 191 | /// The center of the projection. 192 | pub center: mint::Point2, 193 | /// Vertical extent from the center point. The height is double the extent. 194 | /// The width is derived from the height based on the current aspect ratio. 195 | pub extent_y: f32, 196 | /// Distance to the clipping planes. 197 | pub range: ops::Range, 198 | } 199 | 200 | impl Orthographic { 201 | /// Computes the projection matrix representing the camera's projection. 202 | pub fn matrix( 203 | &self, 204 | aspect_ratio: f32, 205 | ) -> mint::ColumnMatrix4 { 206 | let extent_x = aspect_ratio * self.extent_y; 207 | cgmath::ortho( 208 | self.center.x - extent_x, 209 | self.center.x + extent_x, 210 | self.center.y - self.extent_y, 211 | self.center.y + self.extent_y, 212 | self.range.start, 213 | self.range.end, 214 | ).into() 215 | } 216 | } 217 | 218 | /// Perspective projection parameters. 219 | #[derive(Clone, Debug, PartialEq)] 220 | pub struct Perspective { 221 | /// Vertical field of view in degrees. 222 | /// Note: the horizontal FOV is computed based on the aspect. 223 | pub fov_y: f32, 224 | /// The distance to the clipping planes. 225 | pub zrange: ZRange, 226 | } 227 | 228 | impl Perspective { 229 | /// Computes the projection matrix representing the camera's projection. 230 | pub fn matrix( 231 | &self, 232 | aspect_ratio: f32, 233 | ) -> mint::ColumnMatrix4 { 234 | match self.zrange { 235 | ZRange::Finite(ref range) => cgmath::perspective( 236 | cgmath::Deg(self.fov_y), 237 | aspect_ratio, 238 | range.start, 239 | range.end, 240 | ).into(), 241 | ZRange::Infinite(ref range) => { 242 | let f = 1.0 / (0.5 * self.fov_y.to_radians()).tan(); 243 | 244 | let m00 = f / aspect_ratio; 245 | let m11 = f; 246 | let m22 = -1.0; 247 | let m23 = -1.0; 248 | let m32 = -2.0 * range.start; 249 | 250 | let m = [ 251 | [m00, 0.0, 0.0, 0.0], 252 | [0.0, m11, 0.0, 0.0], 253 | [0.0, 0.0, m22, m23], 254 | [0.0, 0.0, m32, 0.0], 255 | ]; 256 | 257 | m.into() 258 | } 259 | } 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | //! sRGB colors. 2 | 3 | /// sRGB color represented by a 4-byte hexadecimal number. 4 | /// 5 | /// ```rust 6 | /// let red = 0xFF0000; 7 | /// let green = 0x00FF00; 8 | /// let blue = 0x0000FF; 9 | /// ``` 10 | pub type Color = u32; 11 | 12 | /// Black. 13 | pub const BLACK: Color = 0x000000; 14 | 15 | /// Red. 16 | pub const RED: Color = 0xFF0000; 17 | 18 | /// Green. 19 | pub const GREEN: Color = 0x00FF00; 20 | 21 | /// Blue. 22 | pub const BLUE: Color = 0x0000FF; 23 | 24 | /// Yellow. 25 | pub const YELLOW: Color = RED | GREEN; 26 | 27 | /// Cyan. 28 | pub const CYAN: Color = GREEN | BLUE; 29 | 30 | /// Magenta. 31 | pub const MAGENTA: Color = RED | BLUE; 32 | 33 | /// White. 34 | pub const WHITE: Color = RED | BLUE | GREEN; 35 | 36 | /// sRGB to linear conversion. 37 | /// 38 | /// Implementation taken from https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_sRGB_decode.txt 39 | pub fn to_linear_rgb(c: Color) -> [f32; 3] { 40 | let f = |xu: u32| { 41 | let x = (xu & 0xFF) as f32 / 255.0; 42 | if x > 0.04045 { 43 | ((x + 0.055) / 1.055).powf(2.4) 44 | } else { 45 | x / 12.92 46 | } 47 | }; 48 | [f(c >> 16), f(c >> 8), f(c)] 49 | } 50 | 51 | /// Linear to sRGB conversion. 52 | /// 53 | /// Implementation taken from https://en.wikipedia.org/wiki/SRGB 54 | pub fn from_linear_rgb(c: [f32; 3]) -> Color { 55 | let f = |x: f32| -> u32 { 56 | let y = if x > 0.0031308 { 57 | let a = 0.055; 58 | (1.0 + a) * x.powf(-2.4) - a 59 | } else { 60 | 12.92 * x 61 | }; 62 | (y * 255.0).round() as u32 63 | }; 64 | f(c[0]) << 16 | f(c[1]) << 8 | f(c[2]) 65 | } 66 | -------------------------------------------------------------------------------- /src/controls/first_person.rs: -------------------------------------------------------------------------------- 1 | use cgmath; 2 | use mint; 3 | use object; 4 | use std::ops; 5 | 6 | use cgmath::Rotation3; 7 | use input::{axis, Input, Key}; 8 | use object::Object; 9 | use std::f32::consts::PI; 10 | 11 | #[derive(Clone, Debug, PartialEq)] 12 | struct Axes { 13 | pub forward: Option, 14 | pub strafing: Option, 15 | pub vertical: Option, 16 | } 17 | 18 | impl Default for Axes { 19 | fn default() -> Self { 20 | Axes { 21 | forward: Some(axis::Key { 22 | pos: Key::W, 23 | neg: Key::S, 24 | }), 25 | strafing: Some(axis::Key { 26 | pos: Key::D, 27 | neg: Key::A, 28 | }), 29 | vertical: None, 30 | } 31 | } 32 | } 33 | 34 | /// Controls for first person camera. 35 | #[derive(Clone, Debug, PartialEq)] 36 | pub struct FirstPerson { 37 | object: object::Base, 38 | position: mint::Point3, 39 | yaw: f32, 40 | pitch: f32, 41 | pitch_range: Option>, 42 | move_speed: f32, 43 | look_speed: f32, 44 | axes: Axes, 45 | vertical_move: bool, 46 | vertical_look: bool, 47 | } 48 | 49 | /// Constructs custom [`FirstPerson`](struct.FirstPerson.html) controls. 50 | #[derive(Clone, Debug, PartialEq)] 51 | pub struct Builder { 52 | object: object::Base, 53 | position: mint::Point3, 54 | pitch_range: Option>, 55 | yaw: f32, 56 | pitch: f32, 57 | move_speed: f32, 58 | look_speed: f32, 59 | axes: Axes, 60 | vertical_move: bool, 61 | vertical_look: bool, 62 | } 63 | 64 | impl Builder { 65 | /// Create new `Builder` with default parameters. 66 | pub fn new(object: &T) -> Self { 67 | Builder { 68 | object: object.upcast(), 69 | position: [0.0, 0.0, 0.0].into(), 70 | yaw: 0.0, 71 | pitch: 0.0, 72 | pitch_range: Some(-PI / 2.0 .. PI / 2.0), 73 | move_speed: 1.0, 74 | look_speed: 0.5, 75 | axes: Axes::default(), 76 | vertical_move: true, 77 | vertical_look: true, 78 | } 79 | } 80 | 81 | /// Set the initial yaw angle in radians. 82 | /// 83 | /// Default is 0.0. 84 | pub fn yaw( 85 | &mut self, 86 | yaw: f32, 87 | ) -> &mut Self { 88 | self.yaw = yaw; 89 | self 90 | } 91 | 92 | /// Set the initial pitch angle in radians. 93 | /// 94 | /// Defaults to 0.0. 95 | pub fn pitch( 96 | &mut self, 97 | pitch: f32, 98 | ) -> &mut Self { 99 | self.pitch = pitch; 100 | self 101 | } 102 | 103 | /// Set the initial pitch range in radians. 104 | /// 105 | /// Defaults to `Some(-PI / 2.0 .. PI / 2.0)`. 106 | pub fn pitch_range( 107 | &mut self, 108 | range: Option>, 109 | ) -> &mut Self { 110 | self.pitch_range = range; 111 | self 112 | } 113 | 114 | /// Set the initial position. 115 | /// 116 | /// Defaults to the world origin. 117 | pub fn position

( 118 | &mut self, 119 | position: P, 120 | ) -> &mut Self 121 | where 122 | P: Into>, 123 | { 124 | self.position = position.into(); 125 | self 126 | } 127 | 128 | /// Setup the movement speed in world units per second. 129 | /// 130 | /// Defaults to 1.0 world units per second. 131 | pub fn move_speed( 132 | &mut self, 133 | speed: f32, 134 | ) -> &mut Self { 135 | self.move_speed = speed; 136 | self 137 | } 138 | 139 | /// Setup mouse sensitivity. 140 | /// 141 | /// Defaults to 0.5 142 | pub fn look_speed( 143 | &mut self, 144 | speed: f32, 145 | ) -> &mut Self { 146 | self.look_speed = speed; 147 | self 148 | } 149 | 150 | /// Setup whether controlled object should move along `y` axis when looking 151 | /// down or up. 152 | /// 153 | /// Defaults to true. 154 | pub fn vertical_movement( 155 | &mut self, 156 | value: bool, 157 | ) -> &mut Self { 158 | self.vertical_move = value; 159 | self 160 | } 161 | 162 | /// Setup whether controlled object can adjust pitch using mouse. 163 | /// 164 | /// Defaults to true. 165 | pub fn vertical_look( 166 | &mut self, 167 | value: bool, 168 | ) -> &mut Self { 169 | self.vertical_look = value; 170 | self 171 | } 172 | 173 | /// Setup key axis for moving forward/backward. 174 | /// 175 | /// Defaults to `W` and `S` keys. 176 | pub fn axis_forward( 177 | &mut self, 178 | axis: Option, 179 | ) -> &mut Self { 180 | self.axes.forward = axis; 181 | self 182 | } 183 | 184 | /// Setup button for "strafing" left/right. 185 | /// 186 | /// Defaults to `A` and `D` keys. 187 | pub fn axis_strafing( 188 | &mut self, 189 | axis: Option, 190 | ) -> &mut Self { 191 | self.axes.strafing = axis; 192 | self 193 | } 194 | 195 | /// Setup button for moving up/down. 196 | /// 197 | /// Defaults to `None`. 198 | pub fn axis_vertical( 199 | &mut self, 200 | axis: Option, 201 | ) -> &mut Self { 202 | self.axes.vertical = axis; 203 | self 204 | } 205 | 206 | /// Finalize builder and create new `FirstPerson` controls. 207 | pub fn build(&mut self) -> FirstPerson { 208 | FirstPerson { 209 | object: self.object.clone(), 210 | position: self.position, 211 | yaw: self.yaw, 212 | pitch: self.pitch, 213 | pitch_range: self.pitch_range.clone(), 214 | move_speed: self.move_speed, 215 | look_speed: self.look_speed, 216 | axes: self.axes.clone(), 217 | vertical_move: self.vertical_move, 218 | vertical_look: self.vertical_look, 219 | } 220 | } 221 | } 222 | 223 | impl FirstPerson { 224 | /// Create a `Builder`. 225 | pub fn builder(object: &T) -> Builder { 226 | Builder::new(object) 227 | } 228 | 229 | /// Create `FirstPerson` controls with default parameters. 230 | pub fn default(object: &T) -> Self { 231 | Self::builder(object).build() 232 | } 233 | 234 | /// Sets the yaw angle in radians. 235 | pub fn set_yaw( 236 | &mut self, 237 | yaw: f32, 238 | ) -> &mut Self { 239 | self.yaw = yaw; 240 | self 241 | } 242 | 243 | /// Sets the pitch angle in radians. 244 | pub fn set_pitch( 245 | &mut self, 246 | pitch: f32, 247 | ) -> &mut Self { 248 | self.pitch = pitch; 249 | self 250 | } 251 | 252 | /// Sets the pitch range in radians. 253 | pub fn pitch_range( 254 | &mut self, 255 | range: Option>, 256 | ) -> &mut Self { 257 | self.pitch_range = range; 258 | self 259 | } 260 | 261 | /// Sets the object position. 262 | pub fn set_position

( 263 | &mut self, 264 | position: P, 265 | ) -> &mut Self 266 | where 267 | P: Into>, 268 | { 269 | self.position = position.into(); 270 | self 271 | } 272 | 273 | /// Sets the movement speed in world units per second. 274 | pub fn set_move_speed( 275 | &mut self, 276 | speed: f32, 277 | ) -> &mut Self { 278 | self.move_speed = speed; 279 | self 280 | } 281 | 282 | /// Sets the mouse sensitivity. 283 | pub fn set_look_speed( 284 | &mut self, 285 | speed: f32, 286 | ) -> &mut Self { 287 | self.look_speed = speed; 288 | self 289 | } 290 | 291 | /// Specifies whether controlled object should move along `y` axis when looking 292 | /// down or up. 293 | pub fn set_vertical_movement( 294 | &mut self, 295 | value: bool, 296 | ) -> &mut Self { 297 | self.vertical_move = value; 298 | self 299 | } 300 | 301 | /// Specifies whether controlled object can adjust pitch using mouse. 302 | pub fn set_vertical_look( 303 | &mut self, 304 | value: bool, 305 | ) -> &mut Self { 306 | self.vertical_look = value; 307 | self 308 | } 309 | 310 | /// Sets the key axis for moving forward/backward. 311 | pub fn set_axis_forward( 312 | &mut self, 313 | axis: Option, 314 | ) -> &mut Self { 315 | self.axes.forward = axis; 316 | self 317 | } 318 | 319 | /// Sets the button for "strafing" left/right. 320 | pub fn set_axis_strafing( 321 | &mut self, 322 | axis: Option, 323 | ) -> &mut Self { 324 | self.axes.strafing = axis; 325 | self 326 | } 327 | 328 | /// Sets button for moving up/down. 329 | pub fn set_axis_vertical( 330 | &mut self, 331 | axis: Option, 332 | ) -> &mut Self { 333 | self.axes.vertical = axis; 334 | self 335 | } 336 | 337 | /// Updates the position, yaw, and pitch of the controlled object according to 338 | /// the last frame input. 339 | pub fn update( 340 | &mut self, 341 | input: &Input, 342 | ) { 343 | let dlook = input.delta_time() * self.look_speed; 344 | let mouse = input.mouse_delta_raw(); 345 | 346 | self.yaw += dlook * mouse.x; 347 | if self.vertical_look { 348 | self.pitch += dlook * mouse.y; 349 | if let Some(range) = self.pitch_range.as_ref() { 350 | if self.pitch < range.start { 351 | self.pitch = range.start; 352 | } 353 | if self.pitch > range.end { 354 | self.pitch = range.end; 355 | } 356 | } 357 | } 358 | 359 | self.axes.vertical.map(|a| { 360 | if let Some(diff) = input.timed(a) { 361 | self.position.y += self.move_speed * diff; 362 | } 363 | }); 364 | 365 | self.axes.forward.map(|a| { 366 | if let Some(diff) = input.timed(a) { 367 | self.position.x += self.move_speed * diff * self.yaw.sin(); 368 | self.position.z -= self.move_speed * diff * self.yaw.cos(); 369 | if self.vertical_move { 370 | self.position.y -= self.move_speed * diff * self.pitch.sin(); 371 | } 372 | } 373 | }); 374 | self.axes.strafing.map(|a| { 375 | if let Some(diff) = input.timed(a) { 376 | self.position.x += self.move_speed * diff * self.yaw.cos(); 377 | self.position.z += self.move_speed * diff * self.yaw.sin(); 378 | } 379 | }); 380 | 381 | let yrot = cgmath::Quaternion::from_angle_y(cgmath::Rad(-self.yaw)); 382 | let xrot = cgmath::Quaternion::from_angle_x(cgmath::Rad(-self.pitch)); 383 | self.object.set_transform(self.position, yrot * xrot, 1.0); 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /src/controls/mod.rs: -------------------------------------------------------------------------------- 1 | //! High-level input handling. 2 | //! 3 | //! ## Controllers 4 | //! 5 | //! Controllers are used to orient objects in the scene using input devices. 6 | //! Any [`Object`] can be the target of a controller, including cameras. 7 | //! 8 | //! ### Orbital 9 | //! 10 | //! * Uses mouse movement to rotate the object around its target. 11 | //! * Uses the mouse scroll wheel to move the object closer to or further 12 | //! from its target. 13 | //! 14 | //! ### First-person 15 | //! 16 | //! * Uses the W and S keys to move forward or backward. 17 | //! * Uses the A and D keys to strafe left or right. 18 | //! * Uses mouse movement to rotate the object when the right mouse button 19 | //! is held down. 20 | //! 21 | //! [`Object`]: ../object/trait.Object.html 22 | 23 | /// First person controls. 24 | pub mod first_person; 25 | 26 | /// Mouse orbit controls. 27 | pub mod orbit; 28 | 29 | #[doc(inline)] 30 | pub use self::first_person::FirstPerson; 31 | 32 | #[doc(inline)] 33 | pub use self::orbit::Orbit; 34 | 35 | pub use input::{axis, 36 | Button, Delta, Hit, HitCount, Key, Input, Timer, MouseButton, 37 | AXIS_DOWN_UP, AXIS_LEFT_RIGHT, KEY_ESCAPE, KEY_SPACE, MOUSE_LEFT, MOUSE_RIGHT, 38 | }; 39 | -------------------------------------------------------------------------------- /src/controls/orbit.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{Decomposed, Point3, Quaternion, Rad, Vector3}; 2 | use cgmath::{EuclideanSpace, InnerSpace, Rotation, Rotation3, Transform as Transform_}; 3 | use mint; 4 | use object; 5 | 6 | use input::{Button, Input, MOUSE_LEFT}; 7 | use node::TransformInternal; 8 | use object::Object; 9 | 10 | /// Simple controls for Orbital Camera. 11 | /// 12 | /// Camera is rotating around the fixed point without any restrictions. 13 | /// By default, it uses left mouse button as control button (hold it to rotate) and mouse wheel 14 | /// to adjust distance to the central point. 15 | #[derive(Clone, Debug)] 16 | pub struct Orbit { 17 | object: object::Base, 18 | transform: TransformInternal, 19 | initial_transform: TransformInternal, 20 | target: Point3, 21 | button: Button, 22 | speed: f32, 23 | } 24 | 25 | /// Helper struct to construct [`Orbit`](struct.Orbit.html) with desired settings. 26 | #[derive(Clone, Debug)] 27 | pub struct Builder { 28 | object: object::Base, 29 | position: mint::Point3, 30 | up: mint::Vector3, 31 | target: mint::Point3, 32 | button: Button, 33 | speed: f32, 34 | } 35 | 36 | impl Builder { 37 | /// Create new `Builder` with default values. 38 | pub fn new(object: &T) -> Self { 39 | Builder { 40 | object: object.upcast(), 41 | position: [0.0, 0.0, 0.0].into(), 42 | up: [0.0, 0.0, 1.0].into(), 43 | target: [0.0, 0.0, 0.0].into(), 44 | button: MOUSE_LEFT, 45 | speed: 1.0, 46 | } 47 | } 48 | 49 | /// Set the initial position. 50 | /// 51 | /// Defaults to the world origin. 52 | pub fn position

( 53 | &mut self, 54 | position: P, 55 | ) -> &mut Self 56 | where 57 | P: Into>, 58 | { 59 | self.position = position.into(); 60 | self 61 | } 62 | 63 | /// Sets the initial up direction. 64 | /// 65 | /// Defaults to the unit z axis. 66 | pub fn up

( 67 | &mut self, 68 | up: P, 69 | ) -> &mut Self 70 | where 71 | P: Into> 72 | { 73 | self.up = up.into(); 74 | self 75 | } 76 | 77 | /// Set the target position. 78 | /// 79 | /// Defaults to the world origin. 80 | pub fn target

( 81 | &mut self, 82 | target: P, 83 | ) -> &mut Self 84 | where 85 | P: Into>, 86 | { 87 | self.target = target.into(); 88 | self 89 | } 90 | 91 | /// Setup the speed of the movements. Default value is 1.0 92 | pub fn speed( 93 | &mut self, 94 | speed: f32, 95 | ) -> &mut Self { 96 | self.speed = speed; 97 | self 98 | } 99 | 100 | /// Setup control button. Default is left mouse button (`MOUSE_LEFT`). 101 | pub fn button( 102 | &mut self, 103 | button: Button, 104 | ) -> &mut Self { 105 | self.button = button; 106 | self 107 | } 108 | 109 | /// Finalize builder and create new `OrbitControls`. 110 | pub fn build(&mut self) -> Orbit { 111 | let dir = (Point3::from(self.position) - Point3::from(self.target)).normalize(); 112 | let up = self.up; 113 | let q = Quaternion::look_at(dir, up.into()).invert(); 114 | let object = self.object.clone(); 115 | object.set_transform(self.position, q, 1.0); 116 | let transform = Decomposed { 117 | disp: mint::Vector3::from(self.position).into(), 118 | rot: q, 119 | scale: 1.0, 120 | }; 121 | 122 | Orbit { 123 | object, 124 | transform, 125 | initial_transform: transform, 126 | target: self.target.into(), 127 | button: self.button, 128 | speed: self.speed, 129 | } 130 | } 131 | } 132 | 133 | impl Orbit { 134 | /// Create new `Builder` with default values. 135 | pub fn builder(object: &T) -> Builder { 136 | Builder::new(object) 137 | } 138 | 139 | /// Update current position and rotation of the controlled object according to the last frame input. 140 | pub fn update( 141 | &mut self, 142 | input: &Input, 143 | ) { 144 | let mouse_delta = if input.hit(self.button) { 145 | input.mouse_delta_ndc() 146 | } else { 147 | [0.0, 0.0].into() 148 | }; 149 | let pre = Decomposed { 150 | disp: -self.target.to_vec(), 151 | ..Decomposed::one() 152 | }; 153 | let q_ver = Quaternion::from_angle_y(Rad(self.speed * (mouse_delta.x))); 154 | let axis = self.transform.rot * Vector3::unit_x(); 155 | let q_hor = Quaternion::from_axis_angle(axis, Rad(self.speed * (mouse_delta.y))); 156 | let post = Decomposed { 157 | scale: 1.0 + input.mouse_wheel() / 1000.0, 158 | rot: q_hor * q_ver, 159 | disp: self.target.to_vec(), 160 | }; 161 | self.transform = post.concat(&pre.concat(&self.transform)); 162 | let pf: mint::Vector3 = self.transform.disp.into(); 163 | self.object.set_transform(pf, self.transform.rot, 1.0); 164 | } 165 | 166 | /// Reset the current position and orientation of the controlled object to their initial values. 167 | pub fn reset(&mut self) { 168 | self.transform = self.initial_transform; 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/custom.rs: -------------------------------------------------------------------------------- 1 | //! Contains re-exports for custom pipeline state. 2 | 3 | pub use gfx::Primitive; 4 | pub use gfx::preset; 5 | pub use gfx::state; 6 | -------------------------------------------------------------------------------- /src/data.rs: -------------------------------------------------------------------------------- 1 | //! The contents of this module is automatically generated. 2 | //! 3 | //! See `build.rs` for the code generator. 4 | 5 | include!(concat!(env!("OUT_DIR"), "/data.rs")); 6 | -------------------------------------------------------------------------------- /src/geometry.rs: -------------------------------------------------------------------------------- 1 | //! Structures for creating and storing geometric primitives. 2 | 3 | use genmesh::{EmitTriangles, Triangulate, Vertex as GenVertex}; 4 | use genmesh::generators::{self, IndexedPolygon, SharedVertex}; 5 | use mint; 6 | 7 | /// A collection of vertices, their normals, and faces that defines the 8 | /// shape of a polyhedral object. 9 | /// 10 | /// # Examples 11 | /// 12 | /// Tetrahedron of unit height and base radius. 13 | /// 14 | /// ```rust 15 | /// # extern crate three; 16 | /// # fn make_tetrahedron() -> three::Geometry { 17 | /// use std::f32::consts::PI; 18 | /// 19 | /// let vertices = vec![ 20 | /// [0.0, 1.0, 0.0].into(), 21 | /// [0.0, 0.0, 1.0].into(), 22 | /// [(2.0 * PI / 3.0).sin(), 0.0, (2.0 * PI / 3.0).cos()].into(), 23 | /// [(4.0 * PI / 3.0).sin(), 0.0, (4.0 * PI / 3.0).cos()].into(), 24 | /// ]; 25 | /// 26 | /// let faces = vec![ 27 | /// [0, 1, 2], 28 | /// [0, 2, 3], 29 | /// [0, 3, 1], 30 | /// [1, 3, 2], 31 | /// ]; 32 | /// 33 | /// three::Geometry { 34 | /// faces, 35 | /// base: three::Shape { 36 | /// vertices, 37 | /// .. three::Shape::default() 38 | /// }, 39 | /// .. three::Geometry::default() 40 | /// } 41 | /// # } 42 | /// # fn main() { let _ = make_tetrahedron(); } 43 | /// ``` 44 | /// # Notes 45 | /// 46 | /// * If any vertex normals, tangents, or texture co-ordinates are provided, 47 | /// the number of entries in each array must match the number of entries 48 | /// in `vertices`. 49 | /// * If joints are provided, the number of entries in `joints.indices` must 50 | /// match the number of entries in `joints.weights`. 51 | #[derive(Clone, Debug, Default)] 52 | pub struct Geometry { 53 | /// Idle shape of the geometry. 54 | pub base: Shape, 55 | /// Texture co-ordinates. 56 | pub tex_coords: Vec>, 57 | /// Face indices. 58 | /// 59 | /// When omitted, the vertex order `[[0, 1, 2], [3, 4, 5], ...]` is 60 | /// assumed. 61 | pub faces: Vec<[u32; 3]>, 62 | /// Properties for vertex skinning. 63 | pub joints: Joints, 64 | /// A list of blend shapes. 65 | pub shapes: Vec, 66 | } 67 | 68 | /// A geometry shape. 69 | #[derive(Clone, Debug, Default)] 70 | pub struct Shape { 71 | /// Vertices. 72 | pub vertices: Vec>, 73 | /// Normals. 74 | pub normals: Vec>, 75 | /// Tangents. 76 | pub tangents: Vec>, 77 | } 78 | 79 | /// Properties for vertex skinning. 80 | #[derive(Clone, Debug, Default)] 81 | pub struct Joints { 82 | /// Joint indices, encoded as floats. 83 | pub indices: Vec<[i32; 4]>, 84 | /// Joint weights. 85 | pub weights: Vec<[f32; 4]>, 86 | } 87 | 88 | impl Geometry { 89 | /// Create `Geometry` from vector of vertices. 90 | /// 91 | /// # Examples 92 | /// 93 | /// Triangle in the XY plane. 94 | /// 95 | /// ```rust 96 | /// let vertices = vec![ 97 | /// [-0.5, -0.5, 0.0].into(), 98 | /// [ 0.5, -0.5, 0.0].into(), 99 | /// [ 0.5, -0.5, 0.0].into(), 100 | /// ]; 101 | /// let geometry = three::Geometry::with_vertices(vertices); 102 | /// ``` 103 | pub fn with_vertices(vertices: Vec>) -> Self { 104 | Geometry { 105 | base: Shape { 106 | vertices, 107 | .. Shape::default() 108 | }, 109 | .. Geometry::default() 110 | } 111 | } 112 | 113 | fn generate( 114 | gen: G, 115 | fpos: Fpos, 116 | fnor: Fnor, 117 | ) -> Self 118 | where 119 | P: EmitTriangles, 120 | G: IndexedPolygon

+ SharedVertex, 121 | Fpos: Fn(GenVertex) -> mint::Point3, 122 | Fnor: Fn(GenVertex) -> mint::Vector3, 123 | { 124 | Geometry { 125 | base: Shape { 126 | vertices: gen.shared_vertex_iter().map(fpos).collect(), 127 | normals: gen.shared_vertex_iter().map(fnor).collect(), 128 | .. Shape::default() 129 | }, 130 | // TODO: Add similar functions for tangents and texture coords 131 | faces: gen.indexed_polygon_iter() 132 | .triangulate() 133 | .map(|t| [t.x as u32, t.y as u32, t.z as u32]) 134 | .collect(), 135 | .. Geometry::default() 136 | } 137 | } 138 | 139 | /// Creates planar geometry in the XY plane. 140 | /// 141 | /// The `width` and `height` parameters specify the total length of the 142 | /// geometry along the X and Y axes respectively. 143 | /// 144 | /// # Examples 145 | /// 146 | /// Unit square in the XY plane, centered at the origin. 147 | /// 148 | /// ```rust 149 | /// # extern crate three; 150 | /// fn make_square() -> three::Geometry { 151 | /// three::Geometry::plane(1.0, 1.0) 152 | /// } 153 | /// # fn main() { let _ = make_square(); } 154 | /// ``` 155 | pub fn plane( 156 | width: f32, 157 | height: f32, 158 | ) -> Self { 159 | Self::generate( 160 | generators::Plane::new(), 161 | |GenVertex { pos, .. }| [pos.x * 0.5 * width, pos.y * 0.5 * height, 0.0].into(), 162 | |v| v.normal.into(), 163 | ) 164 | } 165 | 166 | /// Creates cuboidal geometry. 167 | /// 168 | /// The `width`, `height`, and `depth` parameters specify the total length of 169 | /// the geometry along the X, Y, and Z axes respectively. 170 | /// 171 | /// # Examples 172 | /// 173 | /// Unit cube, centered at the origin. 174 | /// 175 | /// ```rust 176 | /// # extern crate three; 177 | /// fn make_cube() -> three::Geometry { 178 | /// three::Geometry::cuboid(1.0, 1.0, 1.0) 179 | /// } 180 | /// # fn main() { let _ = make_cube(); } 181 | /// ``` 182 | pub fn cuboid( 183 | width: f32, 184 | height: f32, 185 | depth: f32, 186 | ) -> Self { 187 | Self::generate( 188 | generators::Cube::new(), 189 | |GenVertex { pos, .. }| { 190 | [ 191 | pos.x * 0.5 * width, 192 | pos.y * 0.5 * height, 193 | pos.z * 0.5 * depth, 194 | ].into() 195 | }, 196 | |v| v.normal.into(), 197 | ) 198 | } 199 | 200 | /// Creates cylindrial geometry. 201 | /// 202 | /// # Examples 203 | /// 204 | /// Cylinder of unit height and radius, using 12 segments at each end. 205 | /// 206 | /// ```rust 207 | /// # extern crate three; 208 | /// fn make_cylinder() -> three::Geometry { 209 | /// three::Geometry::cylinder(1.0, 1.0, 1.0, 12) 210 | /// } 211 | /// # fn main() { let _ = make_cylinder(); } 212 | /// ``` 213 | /// 214 | /// Cone of unit height and unit radius at the bottom. 215 | /// 216 | /// ```rust 217 | /// # extern crate three; 218 | /// fn make_cone() -> three::Geometry { 219 | /// three::Geometry::cylinder(0.0, 1.0, 1.0, 12) 220 | /// } 221 | /// # fn main() { let _ = make_cone(); } 222 | /// ``` 223 | pub fn cylinder( 224 | radius_top: f32, 225 | radius_bottom: f32, 226 | height: f32, 227 | radius_segments: usize, 228 | ) -> Self { 229 | Self::generate( 230 | generators::Cylinder::new(radius_segments), 231 | //Three.js has height along the Y axis for some reason 232 | |GenVertex { pos, .. }| { 233 | let scale = (pos.z + 1.0) * 0.5 * radius_top + (1.0 - pos.z) * 0.5 * radius_bottom; 234 | [pos.y * scale, pos.z * 0.5 * height, pos.x * scale].into() 235 | }, 236 | |GenVertex { normal, .. }| [normal.y, normal.z, normal.x].into(), 237 | ) 238 | } 239 | 240 | /// Creates geometry for a sphere, using the UV method. 241 | /// 242 | /// * `equatorial_divisions` specifies the number of segments about 243 | /// the sphere equator that lies in the XZ plane. 244 | /// * `meridional_segments` specifies the number of segments around 245 | /// the sphere meridian that lies in the YZ plane. 246 | /// 247 | /// ```rust 248 | /// # extern crate three; 249 | /// fn make_sphere() -> three::Geometry { 250 | /// three::Geometry::uv_sphere(1.0, 12, 12) 251 | /// } 252 | /// # fn main() { let _ = make_sphere(); } 253 | /// ``` 254 | pub fn uv_sphere( 255 | radius: f32, 256 | equatorial_segments: usize, 257 | meridional_segments: usize, 258 | ) -> Self { 259 | Self::generate( 260 | generators::SphereUv::new(equatorial_segments, meridional_segments), 261 | |GenVertex { pos, .. }| [pos.x * radius, pos.y * radius, pos.z * radius].into(), 262 | |v| v.normal.into(), 263 | ) 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/input/axis.rs: -------------------------------------------------------------------------------- 1 | //! Axes for handling input. 2 | 3 | use glutin::VirtualKeyCode as KeyCode; 4 | 5 | /// Two buttons responsible for opposite directions along specific axis. 6 | #[derive(Clone, Copy, Debug, PartialEq, Hash)] 7 | pub struct Key { 8 | /// Key for "negative" direction 9 | pub neg: KeyCode, 10 | /// Key for "positive" direction 11 | pub pos: KeyCode, 12 | } 13 | 14 | /// Raw axis. 15 | /// 16 | /// Usually you can get input from mouse using three axes: 17 | /// - `id = 0` for moves along `X` axis. 18 | /// - `id = 1` for moves along `Y` axis. 19 | /// - `id = 2` for mouse wheel moves. 20 | /// 21 | /// However, these `id`s depend on hardware and may vary on different machines. 22 | #[derive(Clone, Copy, Debug, PartialEq, Hash)] 23 | pub struct Raw { 24 | /// Axis id. 25 | pub id: u8, 26 | } 27 | 28 | /// Axis for left and right arrow keys. 29 | pub const AXIS_LEFT_RIGHT: Key = Key { 30 | neg: KeyCode::Left, 31 | pos: KeyCode::Right, 32 | }; 33 | /// Axis for up and down arrow keys. 34 | pub const AXIS_DOWN_UP: Key = Key { 35 | neg: KeyCode::Down, 36 | pos: KeyCode::Up, 37 | }; 38 | -------------------------------------------------------------------------------- /src/input/timer.rs: -------------------------------------------------------------------------------- 1 | use input::TimerDuration; 2 | use std::time; 3 | 4 | /// Timer can be used to find the time difference between the moment of timer creation and the 5 | /// moment of calling [`elapsed`](struct.Timer.html#method.get). 6 | #[derive(Clone, Copy, Debug, PartialEq)] 7 | pub struct Timer { 8 | pub(crate) start: time::Instant, 9 | } 10 | 11 | impl Timer { 12 | /// Create new timer based on current system time. 13 | pub fn new() -> Self { 14 | Self { 15 | start: time::Instant::now(), 16 | } 17 | } 18 | 19 | /// Reset time of creation to current time. 20 | pub fn reset(&mut self) { 21 | self.start = time::Instant::now(); 22 | } 23 | 24 | /// Get period of time since timer creation in seconds. 25 | pub fn elapsed( 26 | &self, 27 | ) -> TimerDuration { 28 | let dt = self.start.elapsed(); 29 | dt.as_secs() as f32 + 1e-9 * dt.subsec_nanos() as f32 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(missing_docs)] 2 | //! Three.js inspired 3D engine. 3 | //! 4 | //! # Getting Started 5 | //! 6 | //! ## Creating a window 7 | //! 8 | //! Every `three` application begins with a [`Window`]. We create it as follows. 9 | //! 10 | //! ```rust,no_run 11 | //! # extern crate three; 12 | //! # fn main() { 13 | //! let title = "Getting started with three-rs"; 14 | //! let mut window = three::Window::new(title); 15 | //! # } 16 | //! ``` 17 | //! 18 | //! ## The four key structs 19 | //! 20 | //! Every [`Window`] comes equipped with four structures, namely [`Factory`], 21 | //! [`Renderer`], [`Input`], and [`Scene`]. 22 | //! 23 | //! * The [`Factory`] instantiates game objects such as [`Mesh`] and [`Camera`]. 24 | //! * The [`Input`] handles window events at a high-level. 25 | //! * The [`Scene`] is the root node of the component-graph system. 26 | //! * The [`Renderer`] renders the [`Scene`] from the view of a [`Camera`] object. 27 | //! 28 | //! ## Creating a basic mesh 29 | //! 30 | //! Renderable 3D objects are represented by the [`Mesh`] struct. A mesh is a 31 | //! combination of [`Geometry`], describing the shape of the object, paired with a 32 | //! [`Material`], describing the appearance of the object. 33 | //! 34 | //! ```rust,no_run 35 | //! # extern crate three; 36 | //! # fn main() { 37 | //! # let title = "Getting started with three-rs"; 38 | //! # let mut window = three::Window::new(title); 39 | //! let geometry = three::Geometry::with_vertices(vec![ 40 | //! [-0.5, -0.5, -0.5].into(), 41 | //! [ 0.5, -0.5, -0.5].into(), 42 | //! [ 0.0, 0.5, -0.5].into(), 43 | //! ]); 44 | //! let material = three::material::Basic { 45 | //! color: 0xFFFF00, 46 | //! .. Default::default() 47 | //! }; 48 | //! let mut mesh = window.factory.mesh(geometry, material); 49 | //! # } 50 | //! ``` 51 | //! 52 | //! ## Managing the scene 53 | //! 54 | //! In order to be rendered by the [`Renderer`], meshes must be placed in the 55 | //! [`Scene`] within the viewable region. Any marked with the [`Object`] trait 56 | //! may be placed into the scene heirarchy, including user-defined structs. 57 | //! 58 | //! ```rust,no_run 59 | //! # extern crate three; 60 | //! # fn main() { 61 | //! # let title = "Getting started with three-rs"; 62 | //! # let mut window = three::Window::new(title); 63 | //! # let vertices = vec![ 64 | //! # [-0.5, -0.5, -0.5].into(), 65 | //! # [ 0.5, -0.5, -0.5].into(), 66 | //! # [ 0.0, 0.5, -0.5].into(), 67 | //! # ]; 68 | //! # let geometry = three::Geometry::with_vertices(vertices); 69 | //! # let material = three::material::Basic { 70 | //! # color: 0xFFFF00, 71 | //! # .. Default::default() 72 | //! # }; 73 | //! # let mesh = window.factory.mesh(geometry, material); 74 | //! window.scene.add(&mesh); 75 | //! # } 76 | //! ``` 77 | //! 78 | //! We can set the initial scene background using the `Scene::background` 79 | //! field. The default background is solid black. Let's set the background to a 80 | //! sky blue color instead. 81 | //! 82 | //! ```rust,no_run 83 | //! # extern crate three; 84 | //! # fn main() { 85 | //! # let title = "Getting started with three-rs"; 86 | //! # let mut window = three::Window::new(title); 87 | //! window.scene.background = three::Background::Color(0xC6F0FF); 88 | //! # } 89 | //! ``` 90 | //! 91 | //! ## Creating the game loop 92 | //! 93 | //! All is left to do to render our triangle is to create a camera and to 94 | //! write the main game loop. 95 | //! 96 | //! ```rust,no_run 97 | //! # extern crate three; 98 | //! # fn main() { 99 | //! # let title = "Getting started with three-rs"; 100 | //! # let mut window = three::Window::new(title); 101 | //! # let vertices = vec![ 102 | //! # [-0.5, -0.5, -0.5].into(), 103 | //! # [ 0.5, -0.5, -0.5].into(), 104 | //! # [ 0.0, 0.5, -0.5].into(), 105 | //! # ]; 106 | //! # let geometry = three::Geometry::with_vertices(vertices); 107 | //! # let material = three::material::Basic { 108 | //! # color: 0xFFFF00, 109 | //! # .. Default::default() 110 | //! # }; 111 | //! # let mut mesh = window.factory.mesh(geometry, material); 112 | //! # window.scene.add(&mesh); 113 | //! let center = [0.0, 0.0]; 114 | //! let yextent = 1.0; 115 | //! let zrange = -1.0 .. 1.0; 116 | //! let camera = window.factory.orthographic_camera(center, yextent, zrange); 117 | //! while window.update() { 118 | //! window.render(&camera); 119 | //! } 120 | //! # } 121 | //! ``` 122 | //! 123 | //! ## Putting it all together 124 | //! 125 | //! You should have the following code which renders a single yellow triangle 126 | //! upon a sky blue background. 127 | //! 128 | //! ```rust,no_run 129 | //! extern crate three; 130 | //! 131 | //! use three::Object; 132 | //! 133 | //! fn main() { 134 | //! let title = "Getting started with three-rs"; 135 | //! let mut window = three::Window::new(title); 136 | //! 137 | //! let vertices = vec![ 138 | //! [-0.5, -0.5, -0.5].into(), 139 | //! [ 0.5, -0.5, -0.5].into(), 140 | //! [ 0.0, 0.5, -0.5].into(), 141 | //! ]; 142 | //! let geometry = three::Geometry::with_vertices(vertices); 143 | //! let material = three::material::Basic { 144 | //! color: 0xFFFF00, 145 | //! .. Default::default() 146 | //! }; 147 | //! let mesh = window.factory.mesh(geometry, material); 148 | //! window.scene.add(&mesh); 149 | //! 150 | //! let center = [0.0, 0.0]; 151 | //! let yextent = 1.0; 152 | //! let zrange = -1.0 .. 1.0; 153 | //! let camera = window.factory.orthographic_camera(center, yextent, zrange); 154 | //! 155 | //! while window.update() { 156 | //! window.render(&camera); 157 | //! } 158 | //! } 159 | //! ``` 160 | //! 161 | //! # Highlighted features 162 | //! 163 | //! ## Scene management 164 | //! 165 | //! We use [`froggy`] to manage the scene heirarchy. `three` takes a slightly 166 | //! different approach to regular scene graphs whereby child objects keep their 167 | //! parents alive, opposed to parents keeping their children alive. 168 | //! 169 | //! At the heart of the scene heirarchy is the [`object::Base`] type, which 170 | //! is a member of all `three` objects that are placeable in the scene. 171 | //! 172 | //! All three objects are marked by the [`Object`] trait which provides the 173 | //! library with the [`object::Base`] type. Users may implement this trait in 174 | //! order to add their own structs into the scene heirarchy. Three-rs provides a helper 175 | //! macro [`three_object`] which provides a convenient way to implement [`Object`] for your 176 | //! types: 177 | //! 178 | //! ```rust,no_run 179 | //! #[macro_use] 180 | //! extern crate three; 181 | //! 182 | //! use three::Object; 183 | //! 184 | //! struct MyObject { 185 | //! group: three::Group, 186 | //! } 187 | //! 188 | //! three_object!(MyObject::group); 189 | //! 190 | //! fn main() { 191 | //! # let mut window = three::Window::new(""); 192 | //! // Initialization code omitted. 193 | //! let my_object = MyObject { group: window.factory.group() }; 194 | //! window.scene.add(&my_object); 195 | //! } 196 | //! ``` 197 | //! 198 | //! ## Multiple graphics pipelines 199 | //! 200 | //! Graphics pipeline management is handled implicitly by `three`. The pipeline used 201 | //! to render a [`Mesh`] is chosen by its [`Material`] properties and the available 202 | //! vertex attributes from its [`Geometry`]. 203 | //! 204 | //! The range of graphics pipelines available range from simple sprite rendering to 205 | //! physically based rendering. 206 | //! 207 | //! ## 3D format loading 208 | //! 209 | //! ### glTF 2.0 210 | //! 211 | //! `three` comes equipped with support for rendering and animating glTF scenes. 212 | //! 213 | //! See [`Factory::load_gltf`] to get started. 214 | //! 215 | //! ### Wavefront OBJ 216 | //! 217 | //! For less complex meshes, `three` supports loading models in OBJ format. 218 | //! 219 | //! See [`Factory::load_obj`] for more information. 220 | //! 221 | //! ## Procedurally generated geometry 222 | //! 223 | //! The [`Geometry`] struct leverages the [`genmesh`] crate to provide procedurally 224 | //! generated primtives such as cuboids, spheres, and cylinders. See the 225 | //! documentation on the [`Geometry`] struct for more information. 226 | //! 227 | //! [`froggy`]: https://crates.io/crates/froggy 228 | //! [`genmesh`]: https://crates.io/crates/genmesh 229 | //! 230 | //! [`Camera`]: camera/struct.Camera.html 231 | //! [`Factory`]: factory/struct.Factory.html 232 | //! [`Factory::load_gltf`]: factory/struct.Factory.html#method.load_gltf 233 | //! [`Factory::load_obj`]: factory/struct.Factory.html#method.load_obj 234 | //! [`Geometry`]: geometry/struct.Geometry.html 235 | //! [`Input`]: input/struct.Input.html 236 | //! [`Material`]: material/enum.Material.html 237 | //! [`Mesh`]: mesh/struct.Mesh.html 238 | //! [`object::Base`]: object/struct.Base.html 239 | //! [`Object`]: object/trait.Object.html 240 | //! [`Renderer`]: struct.Renderer.html 241 | //! [`Scene`]: scene/struct.Scene.html 242 | //! [`Window`]: window/struct.Window.html 243 | //! [`three_object`]: macro.three_object.html 244 | 245 | extern crate arrayvec; 246 | #[macro_use] 247 | extern crate bitflags; 248 | extern crate cgmath; 249 | #[macro_use] 250 | extern crate derivative; 251 | extern crate froggy; 252 | extern crate genmesh; 253 | #[macro_use] 254 | extern crate gfx; 255 | extern crate gfx_glyph; 256 | #[cfg(feature = "gltf")] 257 | extern crate gltf; 258 | extern crate image; 259 | extern crate includedir; 260 | #[macro_use] 261 | extern crate itertools; 262 | #[macro_use] 263 | extern crate log; 264 | extern crate mint; 265 | extern crate obj; 266 | extern crate phf; 267 | #[macro_use] 268 | extern crate quick_error; 269 | #[cfg(feature = "audio")] 270 | extern crate rodio; 271 | extern crate vec_map; 272 | 273 | #[cfg(feature = "opengl")] 274 | extern crate gfx_device_gl; 275 | #[cfg(feature = "opengl")] 276 | extern crate gfx_window_glutin; 277 | #[cfg(feature = "opengl")] 278 | extern crate glutin; 279 | 280 | #[macro_use] 281 | mod macros; 282 | 283 | #[cfg(feature = "audio")] 284 | pub mod audio; 285 | 286 | pub mod animation; 287 | pub mod camera; 288 | pub mod color; 289 | pub mod controls; 290 | pub mod custom; 291 | mod data; 292 | mod factory; 293 | mod geometry; 294 | mod hub; 295 | mod input; 296 | pub mod light; 297 | pub mod material; 298 | mod mesh; 299 | mod node; 300 | pub mod object; 301 | pub mod render; 302 | pub mod scene; 303 | pub mod skeleton; 304 | mod sprite; 305 | pub mod template; 306 | mod text; 307 | mod texture; 308 | mod util; 309 | 310 | #[cfg(feature = "opengl")] 311 | pub mod window; 312 | 313 | #[doc(inline)] 314 | pub use color::Color; 315 | 316 | #[doc(inline)] 317 | pub use controls::{AXIS_DOWN_UP, AXIS_LEFT_RIGHT, KEY_ESCAPE, KEY_SPACE, MOUSE_LEFT, MOUSE_RIGHT}; 318 | 319 | #[doc(inline)] 320 | pub use controls::{Button, MouseButton, Input, Timer}; 321 | 322 | #[doc(inline)] 323 | pub use factory::Factory; 324 | 325 | #[doc(inline)] 326 | pub use geometry::{Geometry, Joints, Shape}; 327 | 328 | #[cfg(feature = "opengl")] 329 | #[doc(inline)] 330 | pub use glutin::VirtualKeyCode as Key; 331 | 332 | //#[doc(inline)] 333 | //pub use group::Group; 334 | 335 | #[doc(inline)] 336 | pub use material::Material; 337 | 338 | #[doc(inline)] 339 | pub use mesh::{DynamicMesh, Mesh}; 340 | 341 | #[doc(inline)] 342 | pub use node::{Node, Transform, Local, World}; 343 | 344 | #[doc(inline)] 345 | pub use object::{Group, Object}; 346 | 347 | #[doc(inline)] 348 | pub use render::Renderer; 349 | 350 | #[doc(inline)] 351 | pub use scene::{Background, Scene}; 352 | 353 | #[doc(inline)] 354 | pub use sprite::Sprite; 355 | 356 | #[doc(inline)] 357 | pub use text::{Align, Font, Layout, Text}; 358 | 359 | #[doc(inline)] 360 | pub use texture::{CubeMap, CubeMapPath, FilterMethod, Sampler, Texture, WrapMode}; 361 | 362 | #[cfg(feature = "opengl")] 363 | #[doc(inline)] 364 | pub use window::Window; 365 | -------------------------------------------------------------------------------- /src/light.rs: -------------------------------------------------------------------------------- 1 | //! Contains different types of light sources. 2 | 3 | use gfx; 4 | use object::{Base, Object, ObjectType}; 5 | use std::ops; 6 | 7 | use camera::Orthographic; 8 | use color::Color; 9 | use hub::{self, Operation, SubLight, SubNode}; 10 | use render::{BackendResources, ShadowFormat}; 11 | use scene::SyncGuard; 12 | 13 | #[derive(Debug)] 14 | pub(crate) enum LightOperation { 15 | Color(Color), 16 | Intensity(f32), 17 | } 18 | 19 | /// Marks light sources and implements their common methods. 20 | pub trait Light: Object { 21 | /// Change light color. 22 | fn set_color( 23 | &self, 24 | color: Color, 25 | ) { 26 | let msg = Operation::SetLight(LightOperation::Color(color)); 27 | let _ = self.as_ref().tx.send((self.as_ref().node.downgrade(), msg)); 28 | } 29 | 30 | /// Change light intensity. 31 | fn set_intensity( 32 | &self, 33 | intensity: f32, 34 | ) { 35 | let msg = Operation::SetLight(LightOperation::Intensity(intensity)); 36 | let _ = self.as_ref().tx.send((self.as_ref().node.downgrade(), msg)); 37 | } 38 | } 39 | 40 | impl Light for Ambient {} 41 | impl Light for Directional {} 42 | impl Light for Hemisphere {} 43 | impl Light for Point {} 44 | 45 | /// `ShadowMap` is used to render shadows from [`PointLight`](struct.PointLight.html) 46 | /// and [`DirectionalLight`](struct.DirectionalLight.html). 47 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 48 | pub struct ShadowMap { 49 | pub(crate) resource: gfx::handle::ShaderResourceView, 50 | pub(crate) target: gfx::handle::DepthStencilView, 51 | } 52 | 53 | #[derive(Clone, Debug, PartialEq)] 54 | pub(crate) enum ShadowProjection { 55 | Orthographic(Orthographic), 56 | } 57 | 58 | impl ShadowMap { 59 | pub(crate) fn to_target(&self) -> gfx::handle::DepthStencilView { 60 | self.target.clone() 61 | } 62 | 63 | pub(crate) fn to_resource(&self) -> gfx::handle::ShaderResourceView { 64 | self.resource.clone() 65 | } 66 | } 67 | 68 | /// Omni-directional, fixed-intensity and fixed-color light source that affects 69 | /// all objects in the scene equally. 70 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 71 | pub struct Ambient { 72 | pub(crate) object: Base, 73 | } 74 | 75 | impl Ambient { 76 | pub(crate) fn new(object: Base) -> Self { 77 | Ambient { object } 78 | } 79 | } 80 | 81 | impl AsRef for Ambient { 82 | fn as_ref(&self) -> &Base { &self.object } 83 | } 84 | 85 | impl Object for Ambient { 86 | type Data = LightData; 87 | 88 | fn resolve_data(&self, sync_guard: &SyncGuard) -> Self::Data { 89 | match &sync_guard.hub[self].sub_node { 90 | SubNode::Light(ref light_data) => light_data.into(), 91 | sub_node @ _ => panic!("`Ambient` had a bad sub node type: {:?}", sub_node), 92 | } 93 | } 94 | } 95 | 96 | derive_DowncastObject!(Ambient => ObjectType::AmbientLight); 97 | 98 | /// The light source that illuminates all objects equally from a given direction, 99 | /// like an area light of infinite size and infinite distance from the scene; 100 | /// there is shading, but cannot be any distance falloff. 101 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 102 | pub struct Directional { 103 | pub(crate) object: Base, 104 | } 105 | 106 | impl Directional { 107 | pub(crate) fn new(object: Base) -> Self { 108 | Directional { 109 | object, 110 | } 111 | } 112 | 113 | /// Adds or updates the shadow map for this light source. 114 | pub fn set_shadow( 115 | &mut self, 116 | map: ShadowMap, 117 | extent_y: f32, 118 | range: ops::Range, 119 | ) { 120 | let sp = ShadowProjection::Orthographic(Orthographic { 121 | center: [0.0; 2].into(), 122 | extent_y, 123 | range, 124 | }); 125 | let msg = Operation::SetShadow(map, sp); 126 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 127 | } 128 | } 129 | 130 | impl AsRef for Directional { 131 | fn as_ref(&self) -> &Base { &self.object } 132 | } 133 | 134 | impl Object for Directional { 135 | type Data = LightData; 136 | 137 | fn resolve_data(&self, sync_guard: &SyncGuard) -> Self::Data { 138 | match &sync_guard.hub[self].sub_node { 139 | SubNode::Light(ref light_data) => light_data.into(), 140 | sub_node @ _ => panic!("`Directional` had a bad sub node type: {:?}", sub_node), 141 | } 142 | } 143 | } 144 | 145 | derive_DowncastObject!(Directional => ObjectType::DirectionalLight); 146 | 147 | /// `HemisphereLight` uses two different colors in opposite to 148 | /// [`Ambient`](struct.Ambient.html). 149 | /// 150 | /// The color of each fragment is determined by direction of normal. If the 151 | /// normal points in the direction of the upper hemisphere, the fragment has 152 | /// color of the "sky". If the direction of the normal is opposite, then fragment 153 | /// takes color of the "ground". In other cases, color is determined as 154 | /// interpolation between colors of upper and lower hemispheres, depending on 155 | /// how much the normal is oriented to the upper and the lower hemisphere. 156 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 157 | pub struct Hemisphere { 158 | pub(crate) object: Base, 159 | } 160 | 161 | impl Hemisphere { 162 | pub(crate) fn new(object: Base) -> Self { 163 | Hemisphere { object } 164 | } 165 | } 166 | 167 | impl AsRef for Hemisphere { 168 | fn as_ref(&self) -> &Base { &self.object } 169 | } 170 | 171 | impl Object for Hemisphere { 172 | type Data = HemisphereLightData; 173 | 174 | fn resolve_data(&self, sync_guard: &SyncGuard) -> Self::Data { 175 | match &sync_guard.hub[self].sub_node { 176 | SubNode::Light(ref light_data) => light_data.into(), 177 | sub_node @ _ => panic!("`Hemisphere` had a bad sub node type: {:?}", sub_node), 178 | } 179 | } 180 | } 181 | 182 | derive_DowncastObject!(Hemisphere => ObjectType::HemisphereLight); 183 | 184 | /// Light originates from a single point, and spreads outward in all directions. 185 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 186 | pub struct Point { 187 | pub(crate) object: Base, 188 | } 189 | 190 | impl Point { 191 | pub(crate) fn new(object: Base) -> Self { 192 | Point { object } 193 | } 194 | } 195 | 196 | impl AsRef for Point { 197 | fn as_ref(&self) -> &Base { &self.object } 198 | } 199 | 200 | impl Object for Point { 201 | type Data = LightData; 202 | 203 | fn resolve_data(&self, sync_guard: &SyncGuard) -> Self::Data { 204 | match &sync_guard.hub[self].sub_node { 205 | SubNode::Light(ref light_data) => light_data.into(), 206 | sub_node @ _ => panic!("`Point` had a bad sub node type: {:?}", sub_node), 207 | } 208 | } 209 | } 210 | 211 | derive_DowncastObject!(Point => ObjectType::PointLight); 212 | 213 | /// Internal data for [`Ambient`], [`Directional`], and [`Point`] lights. 214 | /// 215 | /// [`Ambient`]: ./struct.Ambient.html 216 | /// [`Directional`]: ./struct.Directional.html 217 | /// [`Point`]: ./struct.Point.html 218 | #[derive(Clone, Copy, Debug, PartialEq)] 219 | pub struct LightData { 220 | /// The color of the light. 221 | pub color: Color, 222 | 223 | /// The intensity of the light. 224 | pub intensity: f32, 225 | } 226 | 227 | impl<'a> From<&'a hub::LightData> for LightData { 228 | fn from(from: &'a hub::LightData) -> Self { 229 | LightData { 230 | color: from.color, 231 | intensity: from.intensity, 232 | } 233 | } 234 | } 235 | 236 | /// Internal data for [`Hemisphere`] lights. 237 | /// 238 | /// [`Hemisphere`]: ./struct.Hemisphere.html 239 | #[derive(Clone, Copy, Debug, PartialEq)] 240 | pub struct HemisphereLightData { 241 | /// The ground color of the light. 242 | pub ground_color: Color, 243 | 244 | /// The sky color of the light. 245 | pub sky_color: Color, 246 | 247 | /// The intensity of the light. 248 | pub intensity: f32, 249 | } 250 | 251 | impl<'a> From<&'a hub::LightData> for HemisphereLightData { 252 | fn from(from: &'a hub::LightData) -> Self { 253 | let ground_color = match from.sub_light { 254 | SubLight::Hemisphere { ground } => ground, 255 | _ => panic!("Bad sub-light for `Hemisphere`: {:?}", from.sub_light), 256 | }; 257 | HemisphereLightData { 258 | sky_color: from.color, 259 | ground_color, 260 | intensity: from.intensity, 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Implements the following traits: 2 | /// 3 | /// * `AsRef` 4 | /// * `AsMut` 5 | /// * `Object` 6 | /// 7 | /// # Examples 8 | /// 9 | /// Creating a wrapper around a named field. 10 | /// 11 | /// ```rust 12 | /// #[macro_use] 13 | /// extern crate three; 14 | /// 15 | /// three_object!(MyStruct::mesh); 16 | /// struct MyStruct { 17 | /// mesh: three::Mesh, 18 | /// } 19 | /// # fn main() {} 20 | /// ``` 21 | /// 22 | /// If the field parameter is omitted then the field name defaults to `object`. 23 | /// 24 | /// ```rust 25 | /// #[macro_use] 26 | /// extern crate three; 27 | /// 28 | /// // Equivalent to `three_object!(MyStruct::object);` 29 | /// three_object!(MyStruct); 30 | /// struct MyStruct { 31 | /// object: three::Mesh, 32 | /// } 33 | /// # fn main() {} 34 | /// ``` 35 | /// 36 | /// [`object::Base`]: object/struct.Base.html 37 | #[macro_export] 38 | macro_rules! three_object { 39 | ($name:ident::$field:ident) => { 40 | impl AsRef<$crate::object::Base> for $name { 41 | fn as_ref(&self) -> &$crate::object::Base { 42 | &self.$field.as_ref() 43 | } 44 | } 45 | 46 | impl $crate::Object for $name { 47 | type Data = (); 48 | 49 | fn resolve_data(&self, _: & $crate::scene::SyncGuard) -> Self::Data {} 50 | } 51 | }; 52 | 53 | ($name:ident) => { 54 | three_object!($name ::object); 55 | } 56 | } 57 | 58 | macro_rules! derive_DowncastObject { 59 | ($type:ident => $pattern:path) => { 60 | impl ::object::DowncastObject for $type { 61 | fn downcast(object: ::object::ObjectType) -> Option { 62 | match object { 63 | $pattern (inner) => Some(inner), 64 | _ => None, 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/material.rs: -------------------------------------------------------------------------------- 1 | //! Material parameters for mesh rendering. 2 | 3 | use color; 4 | 5 | use color::Color; 6 | use render::BasicPipelineState; 7 | use texture::Texture; 8 | use util; 9 | 10 | #[doc(inline)] 11 | pub use self::basic::Basic; 12 | 13 | /// Basic material API. 14 | pub mod basic { 15 | use super::*; 16 | 17 | /// Parameters for a basic solid mesh material. 18 | /// 19 | /// Renders triangle meshes with a solid color or texture. 20 | #[derive(Clone, Hash, Debug, PartialEq, Eq)] 21 | pub struct Basic { 22 | /// Solid color applied in the absence of `map`. 23 | /// 24 | /// Default: `WHITE`. 25 | pub color: Color, 26 | 27 | /// Texture applied using the mesh texture co-ordinates. 28 | /// 29 | /// Default: `None`. 30 | pub map: Option>, 31 | } 32 | 33 | impl Default for Basic { 34 | fn default() -> Self { 35 | Self { 36 | color: color::WHITE, 37 | map: None, 38 | } 39 | } 40 | } 41 | 42 | /// Parameters for a basic solid mesh material with a custom pipeline. 43 | /// 44 | /// Renders triangle meshes with a custom pipeline with a basic material as 45 | /// its input. 46 | #[derive(Clone, Debug, PartialEq, Hash)] 47 | pub struct Custom { 48 | /// Solid color applied in the absense of `map`. 49 | pub color: Color, 50 | 51 | /// Texture applied using the mesh texture co-ordinates. 52 | pub map: Option>, 53 | 54 | /// The custom pipeline state object to be applied to the mesh. 55 | pub pipeline: BasicPipelineState, 56 | } 57 | 58 | impl Eq for Custom {} 59 | } 60 | 61 | /// Parameters for a Lamberian diffusion reflection model. 62 | /// 63 | /// Renders triangle meshes with the Gouraud illumination model. 64 | #[derive(Clone, Hash, Debug, PartialEq, Eq)] 65 | pub struct Lambert { 66 | /// Solid color applied in the absense of `map`. 67 | /// 68 | /// Default: `WHITE`. 69 | pub color: Color, 70 | 71 | /// Specifies whether lighting should be constant over faces. 72 | /// 73 | /// Default: `false` (lighting is interpolated across faces). 74 | pub flat: bool, 75 | } 76 | 77 | impl Default for Lambert { 78 | fn default() -> Self { 79 | Self { 80 | color: color::WHITE, 81 | flat: false, 82 | } 83 | } 84 | } 85 | 86 | /// Parameters for a line material. 87 | /// 88 | /// Renders line strip meshes with a solid color and unit width. 89 | #[derive(Clone, Hash, Debug, PartialEq, Eq)] 90 | pub struct Line { 91 | /// Solid line color. 92 | /// 93 | /// Default: `0xFFFFFF` (white). 94 | pub color: Color, 95 | } 96 | 97 | impl Default for Line { 98 | fn default() -> Self { 99 | Self { 100 | color: color::WHITE, 101 | } 102 | } 103 | } 104 | 105 | /// Parameters for a PBR (physically based rendering) lighting model. 106 | /// 107 | /// Renders triangle meshes with a PBR (physically-based rendering) 108 | /// illumination model 109 | #[derive(Derivative)] 110 | #[derivative(Clone, Debug, PartialEq, Hash, Eq)] 111 | pub struct Pbr { 112 | /// Solid base color applied in the absense of `base_color_map`. 113 | /// 114 | /// Default: `WHITE`. 115 | pub base_color_factor: Color, 116 | 117 | /// Base color alpha factor applied in the absense of `base_color_map`. 118 | /// 119 | /// Default: `1.0` (opaque). 120 | #[derivative(Hash(hash_with = "util::hash_f32"))] 121 | pub base_color_alpha: f32, 122 | 123 | /// Metallic factor in the range [0.0, 1.0]. 124 | /// 125 | /// Default: `1.0`. 126 | #[derivative(Hash(hash_with = "util::hash_f32"))] 127 | pub metallic_factor: f32, 128 | 129 | /// Roughness factor in the range [0.0, 1.0]. 130 | /// 131 | /// * A value of 1.0 means the material is completely rough. 132 | /// * A value of 0.0 means the material is completely smooth. 133 | /// 134 | /// Default: `1.0`. 135 | #[derivative(Hash(hash_with = "util::hash_f32"))] 136 | pub roughness_factor: f32, 137 | 138 | /// Scalar multiplier in the range [0.0, 1.0] that controls the amount of 139 | /// occlusion applied in the presense of `occlusion_map`. 140 | /// 141 | /// Default: `1.0`. 142 | #[derivative(Hash(hash_with = "util::hash_f32"))] 143 | pub occlusion_strength: f32, 144 | 145 | /// Solid emissive color applied in the absense of `emissive_map`. 146 | /// 147 | /// Default: `BLACK`. 148 | pub emissive_factor: Color, 149 | 150 | /// Scalar multiplier applied to each normal vector of the `normal_map`. 151 | /// 152 | /// This value is ignored in the absense of `normal_map`. 153 | /// 154 | /// Default: `1.0`. 155 | #[derivative(Hash(hash_with = "util::hash_f32"))] 156 | pub normal_scale: f32, 157 | 158 | /// Base color texture. 159 | /// 160 | /// Default: `None`. 161 | pub base_color_map: Option>, 162 | 163 | /// Normal texture. 164 | /// 165 | /// Default: `None`. 166 | pub normal_map: Option>, 167 | 168 | /// Emissive texture. 169 | /// 170 | /// Default: `None`. 171 | pub emissive_map: Option>, 172 | 173 | /// Metallic-roughness texture. 174 | /// 175 | /// Default: `None`. 176 | pub metallic_roughness_map: Option>, 177 | 178 | /// Occlusion texture. 179 | /// 180 | /// Default: `None`. 181 | pub occlusion_map: Option>, 182 | } 183 | 184 | impl Default for Pbr { 185 | fn default() -> Self { 186 | Self { 187 | base_color_factor: color::WHITE, 188 | base_color_alpha: 1.0, 189 | metallic_factor: 1.0, 190 | roughness_factor: 1.0, 191 | occlusion_strength: 1.0, 192 | emissive_factor: color::BLACK, 193 | normal_scale: 1.0, 194 | base_color_map: None, 195 | normal_map: None, 196 | emissive_map: None, 197 | metallic_roughness_map: None, 198 | occlusion_map: None, 199 | } 200 | } 201 | } 202 | 203 | /// Parameters for a Phong reflection model. 204 | /// 205 | /// Renders triangle meshes with the Phong illumination model. 206 | #[derive(Derivative)] 207 | #[derivative(Clone, Debug, PartialEq, Hash, Eq)] 208 | pub struct Phong { 209 | /// Solid color applied in the absense of `map`. 210 | /// 211 | /// Default: `WHITE`. 212 | pub color: Color, 213 | 214 | /// Determines the sharpness of specular highlights. 215 | /// 216 | /// Higher values result in sharper highlights to produce a glossy effect. 217 | /// 218 | /// Default: `30.0`. 219 | #[derivative(Hash(hash_with = "util::hash_f32"))] 220 | pub glossiness: f32, 221 | } 222 | 223 | impl Default for Phong { 224 | fn default() -> Self { 225 | Self { 226 | color: color::WHITE, 227 | glossiness: 30.0, 228 | } 229 | } 230 | } 231 | 232 | /// Texture for a 2D sprite. 233 | /// 234 | /// Renders [`Sprite`] objects with the given texture. 235 | /// 236 | /// [`Sprite`]: ../sprite/struct.Sprite.html 237 | #[derive(Clone, Hash, Debug, PartialEq, Eq)] 238 | pub struct Sprite { 239 | /// The texture the apply to the sprite. 240 | pub map: Texture<[f32; 4]>, 241 | } 242 | 243 | /// Parameters for mesh wireframe rasterization. 244 | /// 245 | /// Renders the edges of a triangle mesh with a solid color. 246 | #[derive(Clone, Hash, Debug, PartialEq, Eq)] 247 | pub struct Wireframe { 248 | /// Solid color applied to each wireframe edge. 249 | /// 250 | /// Default: `WHITE`. 251 | pub color: Color, 252 | } 253 | 254 | /// Specifies the appearance of a [`Mesh`](struct.Mesh.html). 255 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] 256 | pub enum Material { 257 | /// Renders triangle meshes with a solid color or texture. 258 | Basic(Basic), 259 | 260 | /// Renders triangle meshes with a custom pipeline with a basic material as 261 | /// its input. 262 | CustomBasic(basic::Custom), 263 | 264 | /// Renders line strip meshes with a solid color and unit width. 265 | Line(Line), 266 | 267 | /// Renders triangle meshes with the Gouraud illumination model. 268 | Lambert(Lambert), 269 | 270 | /// Renders triangle meshes with the Phong illumination model. 271 | Phong(Phong), 272 | 273 | /// Renders triangle meshes with a PBR (physically-based rendering) 274 | /// illumination model 275 | Pbr(Pbr), 276 | 277 | /// Renders [`Sprite`] objects with the given texture. 278 | /// 279 | /// [`Sprite`]: ../sprite/struct.Sprite.html 280 | Sprite(Sprite), 281 | 282 | /// Renders the edges of a triangle mesh with a solid color. 283 | Wireframe(Wireframe), 284 | } 285 | 286 | impl From for Material { 287 | fn from(params: Basic) -> Self { 288 | Material::Basic(params) 289 | } 290 | } 291 | 292 | impl From for Material { 293 | fn from(params: basic::Custom) -> Self { 294 | Material::CustomBasic(params) 295 | } 296 | } 297 | 298 | impl From for Material { 299 | fn from(params: Lambert) -> Self { 300 | Material::Lambert(params) 301 | } 302 | } 303 | 304 | impl From for Material { 305 | fn from(params: Line) -> Self { 306 | Material::Line(params) 307 | } 308 | } 309 | 310 | impl From for Material { 311 | fn from(params: Phong) -> Self { 312 | Material::Phong(params) 313 | } 314 | } 315 | 316 | impl From for Material { 317 | fn from(params: Pbr) -> Self { 318 | Material::Pbr(params) 319 | } 320 | } 321 | 322 | impl From for Material { 323 | fn from(params: Sprite) -> Self { 324 | Material::Sprite(params) 325 | } 326 | } 327 | 328 | impl From for Material { 329 | fn from(params: Wireframe) -> Self { 330 | Material::Wireframe(params) 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/mesh.rs: -------------------------------------------------------------------------------- 1 | use geometry::Geometry; 2 | use hub::Operation; 3 | use material::Material; 4 | use object::{self, DowncastObject, ObjectType}; 5 | use render::DynamicData; 6 | use skeleton::Skeleton; 7 | 8 | use std::hash::{Hash, Hasher}; 9 | 10 | /// [`Geometry`](struct.Geometry.html) with some [`Material`](struct.Material.html). 11 | /// 12 | /// # Examples 13 | /// 14 | /// Creating a solid red triangle. 15 | /// 16 | /// ```rust,no_run 17 | /// # let mut win = three::Window::new("Example"); 18 | /// # let factory = &mut win.factory; 19 | /// let vertices = vec![ 20 | /// [-0.5, -0.5, 0.0].into(), 21 | /// [ 0.5, -0.5, 0.0].into(), 22 | /// [ 0.5, -0.5, 0.0].into(), 23 | /// ]; 24 | /// let geometry = three::Geometry::with_vertices(vertices); 25 | /// let red_material = three::material::Basic { color: three::color::RED, map: None }; 26 | /// let mesh = factory.mesh(geometry, red_material); 27 | /// # let _ = mesh; 28 | /// ``` 29 | /// 30 | /// Duplicating a mesh. 31 | /// 32 | /// ```rust,no_run 33 | /// # let mut win = three::Window::new("Example"); 34 | /// # let factory = &mut win.factory; 35 | /// # let vertices = vec![ 36 | /// # [-0.5, -0.5, 0.0].into(), 37 | /// # [ 0.5, -0.5, 0.0].into(), 38 | /// # [ 0.5, -0.5, 0.0].into(), 39 | /// # ]; 40 | /// # let geometry = three::Geometry::with_vertices(vertices); 41 | /// # let red_material = three::material::Basic { color: three::color::RED, map: None }; 42 | /// # let mesh = factory.mesh(geometry, red_material); 43 | /// use three::Object; 44 | /// let mut duplicate = factory.mesh_instance(&mesh); 45 | /// // Duplicated meshes share their geometry but may be transformed individually. 46 | /// duplicate.set_position([1.2, 3.4, 5.6]); 47 | /// ``` 48 | /// 49 | /// Duplicating a mesh with a different material. 50 | /// 51 | /// ```rust,no_run 52 | /// # let mut win = three::Window::new("Example"); 53 | /// # let factory = &mut win.factory; 54 | /// # let vertices = vec![ 55 | /// # [-0.5, -0.5, 0.0].into(), 56 | /// # [ 0.5, -0.5, 0.0].into(), 57 | /// # [ 0.5, -0.5, 0.0].into(), 58 | /// # ]; 59 | /// # let geometry = three::Geometry::with_vertices(vertices); 60 | /// # let red_material = three::material::Basic { color: three::color::RED, map: None }; 61 | /// # let mesh = factory.mesh(geometry, red_material); 62 | /// let yellow_material = three::material::Wireframe { color: three::color::YELLOW }; 63 | /// # use three::Object; 64 | /// let mut duplicate = factory.mesh_instance_with_material(&mesh, yellow_material); 65 | /// duplicate.set_position([1.2, 3.4, 5.6]); 66 | /// ``` 67 | /// 68 | /// # Notes 69 | /// 70 | /// * Meshes are removed from the scene when dropped. 71 | /// * Hence, meshes must be kept in scope in order to be displayed. 72 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 73 | pub struct Mesh { 74 | pub(crate) object: object::Base, 75 | } 76 | three_object!(Mesh::object); 77 | 78 | impl DowncastObject for Mesh { 79 | fn downcast(object_type: ObjectType) -> Option { 80 | match object_type { 81 | ObjectType::Mesh(mesh) => Some(mesh), 82 | _ => None, 83 | } 84 | } 85 | } 86 | 87 | /// A dynamic version of a mesh allows changing the geometry on CPU side 88 | /// in order to animate the mesh. 89 | #[derive(Clone, Debug)] 90 | pub struct DynamicMesh { 91 | pub(crate) object: object::Base, 92 | pub(crate) geometry: Geometry, 93 | pub(crate) dynamic: DynamicData, 94 | } 95 | three_object!(DynamicMesh::object); 96 | 97 | impl PartialEq for DynamicMesh { 98 | fn eq( 99 | &self, 100 | other: &DynamicMesh, 101 | ) -> bool { 102 | self.object == other.object 103 | } 104 | } 105 | 106 | impl Eq for DynamicMesh {} 107 | 108 | impl Hash for DynamicMesh { 109 | fn hash( 110 | &self, 111 | state: &mut H, 112 | ) { 113 | self.object.hash(state); 114 | } 115 | } 116 | 117 | impl Mesh { 118 | /// Set mesh material. 119 | pub fn set_material>( 120 | &self, 121 | material: M, 122 | ) { 123 | self.as_ref().send(Operation::SetMaterial(material.into())); 124 | } 125 | 126 | /// Bind a skeleton to the mesh. 127 | pub fn set_skeleton( 128 | &self, 129 | skeleton: Skeleton, 130 | ) { 131 | self.as_ref().send(Operation::SetSkeleton(skeleton)); 132 | } 133 | } 134 | 135 | impl DynamicMesh { 136 | /// Returns the number of vertices of the geometry base shape. 137 | pub fn vertex_count(&self) -> usize { 138 | self.geometry.base.vertices.len() 139 | } 140 | 141 | /// Set mesh material. 142 | pub fn set_material>( 143 | &mut self, 144 | material: M, 145 | ) { 146 | self.as_ref().send(Operation::SetMaterial(material.into())); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/node.rs: -------------------------------------------------------------------------------- 1 | use cgmath; 2 | use froggy; 3 | use mint; 4 | 5 | use hub::SubNode; 6 | use material::Material; 7 | 8 | use std::marker::PhantomData; 9 | 10 | /// Pointer to a Node 11 | pub(crate) type NodePointer = froggy::Pointer; 12 | pub(crate) type TransformInternal = cgmath::Decomposed, cgmath::Quaternion>; 13 | 14 | // Fat node of the scene graph. 15 | // 16 | // `NodeInternal` is used by `three-rs` to represent an object in our scene graph, 17 | // shaped as a node tree. Client code uses [`object::Base`](struct.Base.html) instead. 18 | #[derive(Debug)] 19 | pub(crate) struct NodeInternal { 20 | /// `true` if this node (and its children) are visible to cameras. 21 | pub(crate) visible: bool, 22 | 23 | /// A user-defined name for the node. 24 | /// 25 | /// Not used internally to implement functionality. This is used by users to identify nodes 26 | /// programatically, and to act as a utility when debugging. 27 | pub(crate) name: Option, 28 | 29 | /// The transform relative to the node's parent. 30 | pub(crate) transform: TransformInternal, 31 | 32 | /// The transform relative to the scene root. 33 | pub(crate) world_transform: TransformInternal, 34 | 35 | /// Pointer to the next sibling. 36 | pub(crate) next_sibling: Option, 37 | 38 | /// Context specific-data, for example, `UiText`, `Visual` or `Light`. 39 | pub(crate) sub_node: SubNode, 40 | } 41 | 42 | impl NodeInternal { 43 | pub(crate) fn to_node(&self) -> Node { 44 | Node { 45 | transform: self.transform.into(), 46 | visible: self.visible, 47 | name: self.name.clone(), 48 | material: match self.sub_node { 49 | SubNode::Visual(ref mat, _, _) => Some(mat.clone()), 50 | _ => None, 51 | }, 52 | _space: PhantomData, 53 | } 54 | } 55 | } 56 | 57 | impl From for NodeInternal { 58 | fn from(sub: SubNode) -> Self { 59 | NodeInternal { 60 | visible: true, 61 | name: None, 62 | transform: cgmath::Transform::one(), 63 | world_transform: cgmath::Transform::one(), 64 | next_sibling: None, 65 | sub_node: sub, 66 | } 67 | } 68 | } 69 | 70 | /// Position, rotation, and scale of the scene node. 71 | #[derive(Clone, Debug, PartialEq)] 72 | pub struct Transform { 73 | /// Position. 74 | pub position: mint::Point3, 75 | /// Orientation. 76 | pub orientation: mint::Quaternion, 77 | /// Scale. 78 | pub scale: f32, 79 | } 80 | 81 | impl Transform { 82 | /// Creates a new `Transform` with default position, orientation, and scale. 83 | /// 84 | /// * The default position is `(0, 0, 0)`, meaning the global origin when in world space, or 85 | /// meaning no translation relative to a parent in local space. 86 | /// * The default orientation has no rotation, meaning the coordinate axes will match the 87 | /// global axes when the transform is in world space, or will match the axis of the parent 88 | /// when in local space. 89 | /// * The default scale is 1, meaning no change from the object's natural dimensions, or 90 | /// no change relative to the parent's dimensions. 91 | pub fn new() -> Transform { 92 | Default::default() 93 | } 94 | } 95 | 96 | impl Default for Transform { 97 | fn default() -> Self { 98 | Transform { 99 | position: [0.0, 0.0, 0.0].into(), 100 | orientation: [0.0, 0.0, 0.0, 1.0].into(), 101 | scale: 1.0, 102 | } 103 | } 104 | } 105 | 106 | impl From for Transform { 107 | fn from(tf: TransformInternal) -> Self { 108 | let pos: mint::Vector3 = tf.disp.into(); 109 | Transform { 110 | position: pos.into(), 111 | orientation: tf.rot.into(), 112 | scale: tf.scale, 113 | } 114 | } 115 | } 116 | 117 | /// Local space, defined relative to the parent node. 118 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 119 | pub enum Local {} 120 | 121 | /// World space, defined relative to the scene root. 122 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 123 | pub enum World {} 124 | 125 | /// General information about an object in a scene. 126 | #[derive(Clone, Debug, PartialEq)] 127 | pub struct Node { 128 | /// Is `Node` visible by cameras or not? 129 | pub visible: bool, 130 | 131 | /// The name of the node, if any. 132 | pub name: Option, 133 | 134 | /// Transformation in `Space`. 135 | // NOTE: this really begs for `euclid`-style parametrized math types. 136 | pub transform: Transform, 137 | 138 | /// Material in case this `Node` has it. 139 | pub material: Option, 140 | 141 | /// 142 | pub(crate) _space: PhantomData, 143 | } 144 | -------------------------------------------------------------------------------- /src/render/pso_data.rs: -------------------------------------------------------------------------------- 1 | use color; 2 | use gfx::handle as h; 3 | use material::Material; 4 | use render::{BackendResources, PbrParams}; 5 | use std::mem; 6 | use texture::Texture; 7 | 8 | type MapParam = ( 9 | h::ShaderResourceView, 10 | h::Sampler, 11 | ); 12 | 13 | bitflags! { 14 | pub struct PbrFlags: i32 { 15 | const BASE_COLOR_MAP = 1 << 0; 16 | const NORMAL_MAP = 1 << 1; 17 | const METALLIC_ROUGHNESS_MAP = 1 << 2; 18 | const EMISSIVE_MAP = 1 << 3; 19 | const OCCLUSION_MAP = 1 << 4; 20 | const DISPLACEMENT_BUFFER = 1 << 5; 21 | } 22 | } 23 | 24 | #[derive(Clone, Debug)] 25 | pub(crate) struct PbrMaps { 26 | base_color: Option>, 27 | normal: Option>, 28 | emissive: Option>, 29 | metallic_roughness: Option>, 30 | occlusion: Option>, 31 | } 32 | 33 | #[derive(Clone, Debug)] 34 | pub(crate) struct PbrMapParams { 35 | pub(crate) base_color: MapParam, 36 | pub(crate) normal: MapParam, 37 | pub(crate) emissive: MapParam, 38 | pub(crate) metallic_roughness: MapParam, 39 | pub(crate) occlusion: MapParam, 40 | } 41 | 42 | impl PbrMaps { 43 | pub(crate) fn into_params( 44 | self, 45 | map_default: &Texture<[f32; 4]>, 46 | ) -> PbrMapParams { 47 | PbrMapParams { 48 | base_color: self.base_color.as_ref().unwrap_or(map_default).to_param(), 49 | normal: self.normal.as_ref().unwrap_or(map_default).to_param(), 50 | emissive: self.emissive.as_ref().unwrap_or(map_default).to_param(), 51 | metallic_roughness: self.metallic_roughness 52 | .as_ref() 53 | .unwrap_or(map_default) 54 | .to_param(), 55 | occlusion: self.occlusion.as_ref().unwrap_or(map_default).to_param(), 56 | } 57 | } 58 | } 59 | 60 | #[derive(Clone, Debug)] 61 | pub(crate) enum PsoData { 62 | Pbr { 63 | params: PbrParams, 64 | maps: PbrMaps, 65 | }, 66 | Basic { 67 | color: u32, 68 | param0: f32, 69 | map: Option>, 70 | }, 71 | } 72 | 73 | impl Material { 74 | pub(crate) fn to_pso_data(&self) -> PsoData { 75 | match *self { 76 | Material::Pbr(ref material) => { 77 | let mut pbr_flags = PbrFlags::empty(); 78 | if material.base_color_map.is_some() { 79 | pbr_flags.insert(PbrFlags::BASE_COLOR_MAP); 80 | } 81 | if material.normal_map.is_some() { 82 | pbr_flags.insert(PbrFlags::NORMAL_MAP); 83 | } 84 | if material.metallic_roughness_map.is_some() { 85 | pbr_flags.insert(PbrFlags::METALLIC_ROUGHNESS_MAP); 86 | } 87 | if material.emissive_map.is_some() { 88 | pbr_flags.insert(PbrFlags::EMISSIVE_MAP); 89 | } 90 | if material.occlusion_map.is_some() { 91 | pbr_flags.insert(PbrFlags::OCCLUSION_MAP); 92 | } 93 | let bcf = color::to_linear_rgb(material.base_color_factor); 94 | let emf = color::to_linear_rgb(material.emissive_factor); 95 | let pbr_params = PbrParams { 96 | base_color_factor: [bcf[0], bcf[1], bcf[2], material.base_color_alpha], 97 | camera: [0.0, 0.0, 1.0], 98 | emissive_factor: [emf[0], emf[1], emf[2]], 99 | metallic_roughness: [material.metallic_factor, material.roughness_factor], 100 | normal_scale: material.normal_scale, 101 | occlusion_strength: material.occlusion_strength, 102 | pbr_flags: pbr_flags.bits(), 103 | _padding0: unsafe { mem::uninitialized() }, 104 | _padding1: unsafe { mem::uninitialized() }, 105 | }; 106 | PsoData::Pbr { 107 | maps: PbrMaps { 108 | base_color: material.base_color_map.clone(), 109 | normal: material.normal_map.clone(), 110 | emissive: material.emissive_map.clone(), 111 | metallic_roughness: material.metallic_roughness_map.clone(), 112 | occlusion: material.occlusion_map.clone(), 113 | }, 114 | params: pbr_params, 115 | } 116 | } 117 | Material::Basic(ref params) => PsoData::Basic { 118 | color: params.color, 119 | map: params.map.clone(), 120 | param0: 0.0, 121 | }, 122 | Material::CustomBasic(ref params) => PsoData::Basic { 123 | color: params.color, 124 | map: params.map.clone(), 125 | param0: 0.0, 126 | }, 127 | Material::Line(ref params) => PsoData::Basic { 128 | color: params.color, 129 | map: None, 130 | param0: 0.0, 131 | }, 132 | Material::Wireframe(ref params) => PsoData::Basic { 133 | color: params.color, 134 | map: None, 135 | param0: 0.0, 136 | }, 137 | Material::Lambert(ref params) => PsoData::Basic { 138 | color: params.color, 139 | map: None, 140 | param0: if params.flat { 0.0 } else { 1.0 }, 141 | }, 142 | Material::Phong(ref params) => PsoData::Basic { 143 | color: params.color, 144 | map: None, 145 | param0: params.glossiness, 146 | }, 147 | Material::Sprite(ref params) => PsoData::Basic { 148 | color: !0, 149 | map: Some(params.map.clone()), 150 | param0: 0.0, 151 | }, 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/render/source.rs: -------------------------------------------------------------------------------- 1 | //! Source for for GLSL shaders used by the renderer. 2 | 3 | use data; 4 | use util; 5 | 6 | use std::{io, ops, str}; 7 | use std::borrow::Borrow; 8 | use std::path::Path; 9 | 10 | /// Source code for a single GLSL shader. 11 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 12 | pub struct Source(pub(crate) String); 13 | 14 | impl ops::Deref for Source { 15 | type Target = [u8]; 16 | fn deref(&self) -> &Self::Target { 17 | self.0.as_bytes() 18 | } 19 | } 20 | 21 | impl Source { 22 | fn preprocess>( 23 | root: P, 24 | code: &str, 25 | ) -> io::Result { 26 | let root = root.as_ref(); 27 | let mut new_code = String::new(); 28 | for line in code.lines() { 29 | if line.starts_with("#include") { 30 | if let Some(arg) = line.split_whitespace().skip(1).next() { 31 | if arg.starts_with('<') { 32 | if let Some(pos) = arg[1 ..].find('>') { 33 | let name = &arg[1 .. (pos + 1)]; 34 | let path = format!("data/shaders/{}.glsl", name); 35 | let content = &data::FILES.get(&path).unwrap(); 36 | new_code += str::from_utf8(content.borrow()).unwrap(); 37 | } 38 | } else if arg.starts_with('"') { 39 | if let Some(pos) = arg[1 ..].find('"') { 40 | let relative_path = &arg[1 .. (pos + 1)]; 41 | let path = root.join(relative_path); 42 | let content = util::read_file_to_string(&path)?; 43 | let include = Self::preprocess(root, &content)?; 44 | new_code += &include; 45 | } 46 | } 47 | } 48 | } else { 49 | new_code.push_str(&line); 50 | new_code.push('\n'); 51 | } 52 | } 53 | Ok(new_code) 54 | } 55 | 56 | /// Load the named shader from the default set of shaders. 57 | pub fn default( 58 | name: &str, 59 | suffix: &str, 60 | ) -> io::Result { 61 | let path = format!("data/shaders/{}_{}.glsl", name, suffix); 62 | let unprocessed = data::FILES.get(&path).unwrap(); 63 | let processed = Self::preprocess("", str::from_utf8(unprocessed.borrow()).unwrap())?; 64 | Ok(Source(processed)) 65 | } 66 | 67 | /// Load the named shader from the given directory path. 68 | pub fn user>( 69 | root: P, 70 | name: &str, 71 | suffix: &str, 72 | ) -> io::Result { 73 | let base_name = format!("{}_{}.glsl", name, suffix); 74 | let path = root.as_ref().join(&base_name); 75 | let unprocessed = util::read_file_to_string(Path::new(&path))?; 76 | let processed = Self::preprocess(root, &unprocessed)?; 77 | Ok(Source(processed)) 78 | } 79 | } 80 | 81 | macro_rules! decl_shaders { 82 | { $(($pso:ident, $doc:ident, $ty:ident),)* } => { 83 | $( decl_shaders!($pso, $doc, $ty); )* 84 | 85 | /// The set of shaders needed by the `three` renderer. 86 | #[derive(Clone, Debug, Default)] 87 | pub struct Set { 88 | $( 89 | #[allow(missing_docs)] 90 | pub $pso: $ty, 91 | )* 92 | } 93 | }; 94 | 95 | ($pso:ident, $doc:ident, $ty:ident) => { 96 | #[allow(missing_docs)] 97 | #[derive(Clone, Debug)] 98 | pub struct $ty { 99 | /// Vertex shader code. 100 | pub(crate) vs: Source, 101 | 102 | /// Pixel/fragment shader code. 103 | pub(crate) ps: Source, 104 | } 105 | 106 | impl $ty { 107 | /// Loads user shader code. 108 | pub fn user>(root: P) -> io::Result { 109 | Ok(Self { 110 | vs: Source::user(&root, stringify!($pso), "vs")?, 111 | ps: Source::user(&root, stringify!($pso), "ps")?, 112 | }) 113 | } 114 | } 115 | 116 | impl Default for $ty { 117 | fn default() -> Self { 118 | Self { 119 | vs: Source::default(stringify!($pso), "vs").unwrap(), 120 | ps: Source::default(stringify!($pso), "ps").unwrap(), 121 | } 122 | } 123 | } 124 | }; 125 | } 126 | 127 | decl_shaders! { 128 | (basic, basic, Basic), 129 | (gouraud, Gouraud, Gouraud), 130 | (pbr, PBR, Pbr), 131 | (phong, Phong, Phong), 132 | (quad, quad, Quad), 133 | (shadow, shadow, Shadow), 134 | (skybox, skybox, Skybox), 135 | (sprite, sprite, Sprite), 136 | } 137 | -------------------------------------------------------------------------------- /src/safe_float.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/three-rs/three/6403b70f23892db2d5b14c634e404fd2afb67889/src/safe_float.rs -------------------------------------------------------------------------------- /src/skeleton.rs: -------------------------------------------------------------------------------- 1 | //! Mesh skinning. 2 | 3 | use mint; 4 | use object::{self, ObjectType}; 5 | 6 | /// Contains array of bones. 7 | #[derive(Clone, Debug)] 8 | pub struct Skeleton { 9 | pub(crate) object: object::Base, 10 | } 11 | three_object!(Skeleton::object); 12 | derive_DowncastObject!(Skeleton => ObjectType::Skeleton); 13 | 14 | /// A single bone that forms one component of a [`Skeleton`]. 15 | /// 16 | /// [`Skeleton`]: struct.Skeleton.html 17 | #[derive(Clone, Debug)] 18 | pub struct Bone { 19 | pub(crate) object: object::Base, 20 | } 21 | three_object!(Bone::object); 22 | derive_DowncastObject!(Bone => ObjectType::Bone); 23 | 24 | /// A matrix defining how bind mesh nodes to a bone. 25 | pub type InverseBindMatrix = mint::ColumnMatrix4; 26 | -------------------------------------------------------------------------------- /src/sprite.rs: -------------------------------------------------------------------------------- 1 | use hub::Operation; 2 | use mint; 3 | use object; 4 | 5 | /// Two-dimensional bitmap that is integrated into a larger scene. 6 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 7 | pub struct Sprite { 8 | pub(crate) object: object::Base, 9 | } 10 | three_object!(Sprite::object); 11 | derive_DowncastObject!(Sprite => object::ObjectType::Sprite); 12 | 13 | impl Sprite { 14 | pub(crate) fn new(object: object::Base) -> Self { 15 | Sprite { object } 16 | } 17 | 18 | /// Set area of the texture to render. It can be used in sequential animations. 19 | pub fn set_texel_range( 20 | &mut self, 21 | base: P, 22 | size: S, 23 | ) where 24 | P: Into>, 25 | S: Into>, 26 | { 27 | let msg = Operation::SetTexelRange(base.into(), size.into()); 28 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/text.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::fmt; 3 | use std::rc::Rc; 4 | 5 | use gfx::Encoder; 6 | use gfx::handle::{DepthStencilView, RenderTargetView}; 7 | use gfx_glyph as g; 8 | use mint; 9 | use object; 10 | 11 | use color::Color; 12 | use hub::Operation as HubOperation; 13 | use render::{BackendCommandBuffer, BackendFactory, BackendResources, ColorFormat, DepthFormat}; 14 | 15 | #[derive(Debug)] 16 | pub(crate) enum Operation { 17 | Text(String), 18 | Font(Font), 19 | Scale(f32), 20 | Pos(mint::Point2), 21 | Size(mint::Vector2), 22 | Color(Color), 23 | Opacity(f32), 24 | Layout(Layout), 25 | } 26 | 27 | /// Describes the horizontal alignment preference for positioning & bounds. 28 | /// See [`gfx_glyph::HorizontalAlign`](https://docs.rs/gfx_glyph/0.13.0/gfx_glyph/enum.HorizontalAlign.html) 29 | /// for more. 30 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 31 | pub enum Align { 32 | /// Leftmost character is immediately to the right of the render position. 33 | /// Bounds start from the render position and advance rightwards. 34 | Left, 35 | /// Leftmost & rightmost characters are equidistant to the render position. 36 | /// Bounds start from the render position and advance equally left & right. 37 | Center, 38 | /// Rightmost character is immediately to the left of the render position. 39 | /// Bounds start from the render position and advance leftwards. 40 | Right, 41 | } 42 | 43 | /// Describes text alignment & wrapping. 44 | /// See [`gfx_glyph::Layout`](https://docs.rs/gfx_glyph/0.13.0/gfx_glyph/enum.Layout.html). 45 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 46 | pub enum Layout { 47 | /// Renders a single line from left-to-right according to the inner alignment. 48 | SingleLine(Align), 49 | /// Renders multiple lines from left-to-right according to the inner alignment. 50 | Wrap(Align), 51 | } 52 | 53 | impl Default for Layout { 54 | fn default() -> Self { 55 | Layout::SingleLine(Align::Left) 56 | } 57 | } 58 | 59 | impl From for g::HorizontalAlign { 60 | fn from(align: Align) -> g::HorizontalAlign { 61 | match align { 62 | Align::Left => g::HorizontalAlign::Left, 63 | Align::Center => g::HorizontalAlign::Center, 64 | Align::Right => g::HorizontalAlign::Right, 65 | } 66 | } 67 | } 68 | 69 | impl From for g::Layout { 70 | fn from(layout: Layout) -> g::Layout { 71 | match layout { 72 | Layout::Wrap(a) => g::Layout::Wrap { 73 | line_breaker: g::BuiltInLineBreaker::UnicodeLineBreaker, 74 | h_align: a.into(), 75 | v_align: g::VerticalAlign::Top, 76 | }, 77 | Layout::SingleLine(a) => g::Layout::SingleLine { 78 | line_breaker: g::BuiltInLineBreaker::UnicodeLineBreaker, 79 | h_align: a.into(), 80 | v_align: g::VerticalAlign::Top, 81 | }, 82 | } 83 | } 84 | } 85 | 86 | /// Smart pointer containing a font to draw text. 87 | #[derive(Clone)] 88 | pub struct Font { 89 | brush: Rc>>, 90 | pub(crate) id: String, 91 | } 92 | 93 | impl Font { 94 | pub(crate) fn new>>( 95 | buf: T, 96 | id: String, 97 | factory: BackendFactory, 98 | ) -> Font { 99 | Font { 100 | brush: Rc::new(RefCell::new( 101 | g::GlyphBrushBuilder::using_font_bytes(buf).build(factory), 102 | )), 103 | id: id, 104 | } 105 | } 106 | 107 | pub(crate) fn queue( 108 | &self, 109 | section: &g::OwnedVariedSection, 110 | ) { 111 | let mut brush = self.brush.borrow_mut(); 112 | brush.queue(section); 113 | } 114 | 115 | pub(crate) fn draw( 116 | &self, 117 | encoder: &mut Encoder, 118 | out: &RenderTargetView, 119 | depth: &DepthStencilView, 120 | ) { 121 | let mut brush = self.brush.borrow_mut(); 122 | brush 123 | .draw_queued(encoder, out, depth) 124 | .expect("Error while drawing text"); 125 | } 126 | } 127 | 128 | impl fmt::Debug for Font { 129 | fn fmt( 130 | &self, 131 | f: &mut fmt::Formatter, 132 | ) -> fmt::Result { 133 | write!(f, "Font {{ {} }}", self.id) 134 | } 135 | } 136 | 137 | #[derive(Debug, Clone)] 138 | pub(crate) struct TextData { 139 | pub(crate) section: g::OwnedVariedSection, 140 | pub(crate) font: Font, 141 | } 142 | 143 | impl TextData { 144 | pub(crate) fn new>( 145 | font: &Font, 146 | text: S, 147 | ) -> Self { 148 | TextData { 149 | section: g::OwnedVariedSection { 150 | text: vec![ 151 | g::OwnedSectionText { 152 | color: [1.0, 1.0, 1.0, 1.0], 153 | text: text.into(), 154 | ..g::OwnedSectionText::default() 155 | }, 156 | ], 157 | ..Default::default() 158 | }, 159 | font: font.clone(), 160 | } 161 | } 162 | } 163 | 164 | /// UI (on-screen) text. 165 | /// To use, create the new one using [`Factory::ui_text`](struct.Factory.html#method.ui_text) 166 | /// and add it to the scene using [`Scene::add`](struct.Scene.html#method.add). 167 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 168 | pub struct Text { 169 | pub(crate) object: object::Base, 170 | } 171 | three_object!(Text::object); 172 | derive_DowncastObject!(Text => object::ObjectType::Text); 173 | 174 | impl Text { 175 | pub(crate) fn with_object(object: object::Base) -> Self { 176 | Text { object } 177 | } 178 | 179 | /// Change text. 180 | pub fn set_text>( 181 | &mut self, 182 | text: S, 183 | ) { 184 | let msg = HubOperation::SetText(Operation::Text(text.into())); 185 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 186 | } 187 | 188 | /// Change font. 189 | pub fn set_font( 190 | &mut self, 191 | font: &Font, 192 | ) { 193 | let msg = HubOperation::SetText(Operation::Font(font.clone())); 194 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 195 | } 196 | 197 | /// Change text position. 198 | /// Coordinates in pixels from top-left. 199 | /// Defaults to (0, 0). 200 | pub fn set_pos>>( 201 | &mut self, 202 | point: P, 203 | ) { 204 | let msg = HubOperation::SetText(Operation::Pos(point.into())); 205 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 206 | } 207 | 208 | /// Change maximum bounds size, in pixels from top-left. 209 | /// Defaults to unbound. 210 | pub fn set_size>>( 211 | &mut self, 212 | dimensions: V, 213 | ) { 214 | let msg = HubOperation::SetText(Operation::Size(dimensions.into())); 215 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 216 | } 217 | 218 | /// Change text color. 219 | /// Defaults to white (`0xFFFFFF`). 220 | pub fn set_color( 221 | &mut self, 222 | color: Color, 223 | ) { 224 | let msg = HubOperation::SetText(Operation::Color(color)); 225 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 226 | } 227 | 228 | /// Change text opacity. 229 | /// From `0.0` to `1.0`. 230 | /// Defaults to `1.0`. 231 | pub fn set_opacity( 232 | &mut self, 233 | opacity: f32, 234 | ) { 235 | let msg = HubOperation::SetText(Operation::Opacity(opacity)); 236 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 237 | } 238 | 239 | /// Change font size (scale). 240 | /// Defaults to 16. 241 | pub fn set_font_size( 242 | &mut self, 243 | size: f32, 244 | ) { 245 | let msg = HubOperation::SetText(Operation::Scale(size)); 246 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 247 | } 248 | 249 | /// Change text layout. 250 | /// Defaults to `Layout::SingleLine(Align::Left)`. 251 | pub fn set_layout( 252 | &mut self, 253 | layout: Layout, 254 | ) { 255 | let msg = HubOperation::SetText(Operation::Layout(layout)); 256 | let _ = self.object.tx.send((self.object.node.downgrade(), msg)); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/texture.rs: -------------------------------------------------------------------------------- 1 | use std::path::Path; 2 | 3 | use gfx::handle as h; 4 | use mint; 5 | 6 | use render::BackendResources; 7 | use util; 8 | 9 | pub use gfx::texture::{FilterMethod, WrapMode}; 10 | 11 | /// The sampling properties for a `Texture`. 12 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 13 | pub struct Sampler(pub h::Sampler); 14 | 15 | /// An image applied (mapped) to the surface of a shape or polygon. 16 | #[derive(Derivative)] 17 | #[derivative(Clone, Debug, PartialEq, Eq(bound = "T: PartialEq"), Hash(bound = ""))] 18 | pub struct Texture { 19 | view: h::ShaderResourceView, 20 | sampler: h::Sampler, 21 | total_size: [u32; 2], 22 | #[derivative(Hash(hash_with = "util::hash_f32_slice"))] tex0: [f32; 2], 23 | #[derivative(Hash(hash_with = "util::hash_f32_slice"))] tex1: [f32; 2], 24 | } 25 | 26 | impl Texture { 27 | pub(crate) fn new( 28 | view: h::ShaderResourceView, 29 | sampler: h::Sampler, 30 | total_size: [u32; 2], 31 | ) -> Self { 32 | Texture { 33 | view, 34 | sampler, 35 | total_size, 36 | tex0: [0.0; 2], 37 | tex1: [total_size[0] as f32, total_size[1] as f32], 38 | } 39 | } 40 | 41 | pub(crate) fn to_param( 42 | &self, 43 | ) -> ( 44 | h::ShaderResourceView, 45 | h::Sampler, 46 | ) { 47 | (self.view.clone(), self.sampler.clone()) 48 | } 49 | 50 | /// See [`Sprite::set_texel_range`](struct.Sprite.html#method.set_texel_range). 51 | pub fn set_texel_range( 52 | &mut self, 53 | base: mint::Point2, 54 | size: mint::Vector2, 55 | ) { 56 | self.tex0 = [ 57 | base.x as f32, 58 | self.total_size[1] as f32 - base.y as f32 - size.y as f32, 59 | ]; 60 | self.tex1 = [ 61 | base.x as f32 + size.x as f32, 62 | self.total_size[1] as f32 - base.y as f32, 63 | ]; 64 | } 65 | 66 | /// Returns normalized UV rectangle (x0, y0, x1, y1) of the current texel range. 67 | pub fn uv_range(&self) -> [f32; 4] { 68 | [ 69 | self.tex0[0] / self.total_size[0] as f32, 70 | self.tex0[1] / self.total_size[1] as f32, 71 | self.tex1[0] / self.total_size[0] as f32, 72 | self.tex1[1] / self.total_size[1] as f32, 73 | ] 74 | } 75 | } 76 | 77 | /// Represents paths to cube map texture, useful for loading 78 | /// [`CubeMap`](struct.CubeMap.html). 79 | #[derive(Clone, Debug)] 80 | pub struct CubeMapPath> { 81 | /// "Front" image. `Z+`. 82 | pub front: P, 83 | /// "Back" image. `Z-`. 84 | pub back: P, 85 | /// "Left" image. `X-`. 86 | pub left: P, 87 | /// "Right" image. `X+`. 88 | pub right: P, 89 | /// "Up" image. `Y+`. 90 | pub up: P, 91 | /// "Down" image. `Y-`. 92 | pub down: P, 93 | } 94 | 95 | impl> CubeMapPath

{ 96 | pub(crate) fn as_array(&self) -> [&P; 6] { 97 | [ 98 | &self.right, 99 | &self.left, 100 | &self.up, 101 | &self.down, 102 | &self.front, 103 | &self.back, 104 | ] 105 | } 106 | } 107 | 108 | /// Cubemap is six textures useful for 109 | /// [`Cubemapping`](https://en.wikipedia.org/wiki/Cube_mapping). 110 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 111 | pub struct CubeMap { 112 | view: h::ShaderResourceView, 113 | sampler: h::Sampler, 114 | } 115 | 116 | impl CubeMap { 117 | pub(crate) fn new( 118 | view: h::ShaderResourceView, 119 | sampler: h::Sampler, 120 | ) -> Self { 121 | CubeMap { view, sampler } 122 | } 123 | 124 | pub(crate) fn to_param( 125 | &self, 126 | ) -> ( 127 | h::ShaderResourceView, 128 | h::Sampler, 129 | ) { 130 | (self.view.clone(), self.sampler.clone()) 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | //! Internal utility functions. 2 | 3 | use std::{fs, io, path}; 4 | use std::hash::{Hash, Hasher}; 5 | 6 | /// Reads the entire contents of a file into a `String`. 7 | pub fn read_file_to_string>(path: P) -> io::Result { 8 | use self::io::Read; 9 | let file = fs::File::open(path)?; 10 | let len = file.metadata()?.len() as usize; 11 | let mut contents = String::with_capacity(len); 12 | let _ = io::BufReader::new(file).read_to_string(&mut contents)?; 13 | Ok(contents) 14 | } 15 | 16 | /// Hash f32 value using its bit interpretation. 17 | pub fn hash_f32( 18 | value: &f32, 19 | state: &mut H, 20 | ) { 21 | value.to_bits().hash(state); 22 | } 23 | 24 | /// Hash slice of floats using its bit interpretation. 25 | pub fn hash_f32_slice( 26 | value: &[f32], 27 | state: &mut H, 28 | ) { 29 | for element in value { 30 | element.to_bits().hash(state); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/window.rs: -------------------------------------------------------------------------------- 1 | //! Primitives for creating and controlling [`Window`](struct.Window.html). 2 | 3 | use glutin; 4 | use mint; 5 | use render; 6 | 7 | use camera::Camera; 8 | use factory::Factory; 9 | use input::Input; 10 | use render::Renderer; 11 | use scene::Scene; 12 | use std::path::PathBuf; 13 | use glutin::{GlRequest, GlProfile, PossiblyCurrent}; 14 | 15 | /// `Window` is the core entity of every `three-rs` application. 16 | /// 17 | /// It provides [user input](struct.Window.html#method.update), 18 | /// [`Factory`](struct.Factory.html) and [`Renderer`](struct.Renderer.html). 19 | pub struct Window { 20 | event_loop: glutin::EventsLoop, 21 | windowedContext: glutin::WindowedContext, 22 | dpi: f64, 23 | /// See [`Input`](struct.Input.html). 24 | pub input: Input, 25 | /// See [`Renderer`](struct.Renderer.html). 26 | pub renderer: Renderer, 27 | /// See [`Factory`](struct.Factory.html). 28 | pub factory: Factory, 29 | /// See [`Scene`](struct.Scene.html). 30 | pub scene: Scene, 31 | /// Reset input on each frame? See [`Input::reset`](struct.Input.html#method.reset). 32 | /// 33 | /// Defaults to `true`. 34 | pub reset_input: bool, 35 | is_fullscreen: bool, 36 | } 37 | 38 | /// Builder for creating new [`Window`](struct.Window.html) with desired parameters. 39 | #[derive(Debug, Clone)] 40 | pub struct Builder { 41 | dimensions: glutin::dpi::LogicalSize, 42 | fullscreen: bool, 43 | multisampling: u16, 44 | shader_directory: Option, 45 | title: String, 46 | vsync: bool, 47 | } 48 | 49 | impl Builder { 50 | /// Set the size of the viewport (the resolution) in logical pixels. 51 | /// That is the dpi setting affects the amount of pixels used but the window will 52 | /// take up the same amount of space regardless of dpi. Defaults to 1024x768. 53 | pub fn dimensions( 54 | &mut self, 55 | width: f64, 56 | height: f64, 57 | ) -> &mut Self { 58 | self.dimensions = glutin::dpi::LogicalSize::new(width, height); 59 | self 60 | } 61 | 62 | /// Whether enable fullscreen mode or not. Defauls to `false`. 63 | pub fn fullscreen( 64 | &mut self, 65 | option: bool, 66 | ) -> &mut Self { 67 | self.fullscreen = option; 68 | self 69 | } 70 | 71 | /// Sets the multisampling level to request. A value of `0` indicates that multisampling must 72 | /// not be enabled. Must be the power of 2. Defaults to `0`. 73 | pub fn multisampling( 74 | &mut self, 75 | option: u16, 76 | ) -> &mut Self { 77 | self.multisampling = option; 78 | self 79 | } 80 | 81 | /// Specifies the user shader directory. 82 | pub fn shader_directory>( 83 | &mut self, 84 | option: P, 85 | ) -> &mut Self { 86 | self.shader_directory = Some(option.into()); 87 | self 88 | } 89 | 90 | /// Whether to enable vertical synchronization or not. Defaults to `true`. 91 | pub fn vsync( 92 | &mut self, 93 | option: bool, 94 | ) -> &mut Self { 95 | self.vsync = option; 96 | self 97 | } 98 | 99 | /// Create new `Window` with desired parameters. 100 | pub fn build(&mut self) -> Window { 101 | let event_loop = glutin::EventsLoop::new(); 102 | let monitor_id = if self.fullscreen { 103 | Some(event_loop.get_primary_monitor()) 104 | } else { 105 | None 106 | }; 107 | let is_fullscreen = self.fullscreen; 108 | 109 | let builder = glutin::WindowBuilder::new() 110 | .with_fullscreen(monitor_id) 111 | .with_dimensions(self.dimensions) 112 | .with_title(self.title.clone()); 113 | 114 | let context = glutin::ContextBuilder::new() 115 | .with_gl_profile(GlProfile::Core) 116 | .with_gl(GlRequest::Latest) 117 | .with_vsync(self.vsync) 118 | .with_multisampling(self.multisampling); 119 | 120 | let mut source_set = render::source::Set::default(); 121 | if let Some(path) = self.shader_directory.as_ref() { 122 | let path = path.to_str().unwrap(); 123 | macro_rules! try_override { 124 | ($name:ident) => { 125 | match render::Source::user(path, stringify!($name), "vs") { 126 | Ok(src) => { 127 | info!("Overriding {}_vs.glsl", stringify!($name)); 128 | source_set.$name.vs = src; 129 | } 130 | Err(err) => { 131 | error!("{:#?}", err); 132 | info!("Using default {}_vs.glsl", stringify!($name)); 133 | } 134 | } 135 | match render::Source::user(path, stringify!($name), "ps") { 136 | Ok(src) => { 137 | info!("Overriding {}_ps.glsl", stringify!($name)); 138 | source_set.$name.ps = src; 139 | } 140 | Err(err) => { 141 | error!("{:#?}", err); 142 | info!("Using default {}_ps.glsl", stringify!($name)); 143 | } 144 | } 145 | }; 146 | ( $($name:ident,)* ) => { 147 | $( try_override!($name); )* 148 | }; 149 | } 150 | try_override!(basic, gouraud, pbr, phong, quad, shadow, skybox, sprite,); 151 | } 152 | 153 | let (renderer, windowedContext, mut factory) = Renderer::new(builder, context, &event_loop, &source_set); 154 | let dpi = windowedContext.window().get_hidpi_factor(); 155 | let scene = factory.scene(); 156 | Window { 157 | event_loop, 158 | windowedContext, 159 | dpi, 160 | input: Input::new(), 161 | renderer, 162 | factory, 163 | scene, 164 | reset_input: true, 165 | is_fullscreen, 166 | } 167 | } 168 | } 169 | 170 | impl Window { 171 | /// Create a new window with default parameters. 172 | pub fn new>(title: T) -> Self { 173 | Self::builder(title).build() 174 | } 175 | 176 | /// Create new `Builder` with standard parameters. 177 | pub fn builder>(title: T) -> Builder { 178 | Builder { 179 | dimensions: glutin::dpi::LogicalSize::new(1024.0, 768.0), 180 | fullscreen: false, 181 | multisampling: 0, 182 | shader_directory: None, 183 | title: title.into(), 184 | vsync: true, 185 | } 186 | } 187 | 188 | /// `update` method returns `false` if the window was closed. 189 | pub fn update(&mut self) -> bool { 190 | let mut running = true; 191 | let renderer = &mut self.renderer; 192 | let input = &mut self.input; 193 | if self.reset_input { 194 | input.reset(); 195 | } 196 | 197 | let wc = &self.windowedContext; 198 | self.windowedContext.swap_buffers().unwrap(); 199 | let dpi = self.dpi; 200 | 201 | self.event_loop.poll_events(|event| { 202 | use glutin::WindowEvent; 203 | match event { 204 | glutin::Event::WindowEvent { event, .. } => match event { 205 | WindowEvent::Resized(size) => renderer.resize(wc, size), 206 | WindowEvent::HiDpiFactorChanged(dpi) => renderer.dpi_change(wc, dpi), 207 | WindowEvent::Focused(state) => input.window_focus(state), 208 | WindowEvent::CloseRequested | WindowEvent::Destroyed => running = false, 209 | WindowEvent::KeyboardInput { 210 | input: glutin::KeyboardInput { 211 | state, 212 | virtual_keycode: Some(keycode), 213 | .. 214 | }, 215 | .. 216 | } => input.keyboard_input(state, keycode), 217 | WindowEvent::MouseInput { state, button, .. } => input.mouse_input(state, button), 218 | WindowEvent::CursorMoved { position, .. } => { 219 | let pos = position.to_physical(dpi); 220 | input.mouse_moved([pos.x as f32, pos.y as f32].into(), renderer.map_to_ndc([pos.x as f32, pos.y as f32])); 221 | } 222 | WindowEvent::MouseWheel { delta, .. } => input.mouse_wheel_input(delta), 223 | _ => {} 224 | }, 225 | glutin::Event::DeviceEvent { event, .. } => match event { 226 | glutin::DeviceEvent::Motion { axis, value } => { 227 | input.axis_moved_raw(axis as u8, value as f32); 228 | } 229 | _ => {} 230 | }, 231 | _ => {} 232 | } 233 | }); 234 | 235 | running 236 | } 237 | 238 | /// Render the current scene with specific [`Camera`](struct.Camera.html). 239 | pub fn render( 240 | &mut self, 241 | camera: &Camera, 242 | ) { 243 | self.renderer.render(&self.scene, camera); 244 | } 245 | 246 | /// Get current window size in pixels. 247 | pub fn size(&self) -> mint::Vector2 { 248 | let size = self.windowedContext 249 | .window() 250 | .get_inner_size() 251 | .expect("Can't get window size") 252 | .to_physical(self.dpi); 253 | [size.width as f32, size.height as f32].into() 254 | } 255 | 256 | /// Returns underlaying `glutin::WindowedContext`. 257 | #[cfg(feature = "opengl")] 258 | pub fn glutin_window(&self) -> &glutin::WindowedContext { 259 | &self.windowedContext 260 | } 261 | 262 | /// Returns the current full screen mode. 263 | pub fn is_fullscreen(&self) -> bool { 264 | self.is_fullscreen 265 | } 266 | 267 | /// Sets the full screen mode. 268 | /// If the window is already in full screen mode, does nothing. 269 | pub fn set_fullscreen(&mut self, fullscreen: bool) { 270 | if self.is_fullscreen == fullscreen { 271 | return; 272 | } 273 | self.is_fullscreen = fullscreen; 274 | let monitor = if fullscreen { 275 | Some(self.event_loop.get_primary_monitor()) 276 | } else { 277 | None 278 | }; 279 | self.windowedContext.window().set_fullscreen(monitor); 280 | } 281 | 282 | /// Toggles the full screen mode. 283 | /// Returns the new actual mode. 284 | pub fn toggle_fullscreen(&mut self) -> bool { 285 | let fullscreen = !self.is_fullscreen; 286 | self.set_fullscreen(fullscreen); 287 | fullscreen 288 | } 289 | } 290 | --------------------------------------------------------------------------------