├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── config └── game.json ├── etc ├── TODO.md ├── blender │ ├── io_kri │ │ ├── __init__.py │ │ ├── action.py │ │ └── common.py │ ├── io_kri_mat │ │ ├── __init__.py │ │ └── mat.py │ ├── io_kri_mesh │ │ ├── __init__.py │ │ └── mesh.py │ └── io_kri_scene │ │ ├── __init__.py │ │ └── scene.py └── screens │ ├── 0-cube.jpg │ ├── 1-model-raw.jpg │ ├── 2-model-textured.jpg │ ├── 3-model-scene.jpg │ ├── 4-grid.jpg │ ├── 5-model-nodes.jpg │ ├── 6-tile-generator.jpg │ └── 7-forest.jpg ├── examples ├── forest │ ├── bin.rs │ ├── config.json │ ├── generate.rs │ └── reflect.rs └── viewer │ ├── README.md │ ├── bin.rs │ └── control.rs └── src ├── game ├── Cargo.toml ├── field.rs ├── lib.rs └── reflect.rs ├── load ├── Cargo.toml ├── aux.rs ├── chunk.rs ├── lib.rs ├── mat.rs ├── mesh.rs ├── program.rs ├── reflect.rs └── scene.rs ├── main.rs └── scene ├── Cargo.toml ├── lib.rs └── space.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Data 2 | data/ 3 | 4 | # Executables 5 | target/ 6 | 7 | # Caches 8 | __pycache__ 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - nightly 4 | - 1.0.0-beta.4 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "claymore" 3 | version = "0.2.0" 4 | dependencies = [ 5 | "cgmath 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "claymore-game 0.0.2", 7 | "claymore-load 0.1.1", 8 | "claymore-scene 0.1.1", 9 | "clock_ticks 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)", 10 | "env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 11 | "gfx 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "gfx_debug_draw 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 13 | "gfx_phase 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 14 | "gfx_pipeline 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 15 | "gfx_text 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 16 | "gfx_window_glutin 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 17 | "glutin 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 18 | "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 19 | "rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 20 | "rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 21 | ] 22 | 23 | [[package]] 24 | name = "android_glue" 25 | version = "0.0.2" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | 28 | [[package]] 29 | name = "bitflags" 30 | version = "0.1.1" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | 33 | [[package]] 34 | name = "bitflags" 35 | version = "0.2.1" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | 38 | [[package]] 39 | name = "byteorder" 40 | version = "0.3.10" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | 43 | [[package]] 44 | name = "cgmath" 45 | version = "0.2.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | dependencies = [ 48 | "num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", 49 | "rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 50 | "rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 51 | ] 52 | 53 | [[package]] 54 | name = "claymore-game" 55 | version = "0.0.2" 56 | dependencies = [ 57 | "cgmath 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "claymore-load 0.1.1", 59 | "claymore-scene 0.1.1", 60 | "gfx 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 61 | "gfx_pipeline 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 62 | "grid 0.0.1 (git+https://github.com/kvark/grid-rs)", 63 | "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 65 | ] 66 | 67 | [[package]] 68 | name = "claymore-load" 69 | version = "0.1.1" 70 | dependencies = [ 71 | "cgmath 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 72 | "claymore-scene 0.1.1", 73 | "gfx 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 74 | "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "piston-gfx_texture 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 77 | ] 78 | 79 | [[package]] 80 | name = "claymore-scene" 81 | version = "0.1.1" 82 | dependencies = [ 83 | "cgmath 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "gfx 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "gfx_pipeline 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "gfx_scene 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 87 | "id 0.0.1 (git+https://github.com/kvark/simplecs)", 88 | "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 89 | ] 90 | 91 | [[package]] 92 | name = "clock_ticks" 93 | version = "0.0.6" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | dependencies = [ 96 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 97 | ] 98 | 99 | [[package]] 100 | name = "draw_queue" 101 | version = "0.1.1" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | 104 | [[package]] 105 | name = "draw_state" 106 | version = "0.1.1" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | dependencies = [ 109 | "bitflags 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 110 | ] 111 | 112 | [[package]] 113 | name = "dylib" 114 | version = "0.0.1" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | dependencies = [ 117 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 118 | ] 119 | 120 | [[package]] 121 | name = "enum_primitive" 122 | version = "0.0.1" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | dependencies = [ 125 | "num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", 126 | ] 127 | 128 | [[package]] 129 | name = "env_logger" 130 | version = "0.3.1" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | dependencies = [ 133 | "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 134 | "regex 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)", 135 | ] 136 | 137 | [[package]] 138 | name = "freetype-rs" 139 | version = "0.0.10" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | dependencies = [ 142 | "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 143 | "freetype-sys 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 144 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 145 | ] 146 | 147 | [[package]] 148 | name = "freetype-sys" 149 | version = "0.0.5" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | dependencies = [ 152 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "libz-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 154 | "pkg-config 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 155 | ] 156 | 157 | [[package]] 158 | name = "gdi32-sys" 159 | version = "0.1.0" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | dependencies = [ 162 | "winapi 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 163 | ] 164 | 165 | [[package]] 166 | name = "gfx" 167 | version = "0.6.2" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | dependencies = [ 170 | "bitflags 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 171 | "draw_state 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 172 | "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 173 | "num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", 174 | ] 175 | 176 | [[package]] 177 | name = "gfx_debug_draw" 178 | version = "0.3.0" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | dependencies = [ 181 | "gfx 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 182 | "gfx_text 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 183 | "vecmath 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 184 | ] 185 | 186 | [[package]] 187 | name = "gfx_device_gl" 188 | version = "0.4.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | dependencies = [ 191 | "gfx 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 192 | "gfx_gl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 193 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 194 | "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 195 | ] 196 | 197 | [[package]] 198 | name = "gfx_gl" 199 | version = "0.1.4" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | dependencies = [ 202 | "gl_common 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 203 | "gl_generator 0.0.26 (registry+https://github.com/rust-lang/crates.io-index)", 204 | "khronos_api 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 205 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 206 | ] 207 | 208 | [[package]] 209 | name = "gfx_phase" 210 | version = "0.2.1" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | dependencies = [ 213 | "draw_queue 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 214 | "gfx 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 215 | "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 216 | ] 217 | 218 | [[package]] 219 | name = "gfx_pipeline" 220 | version = "0.2.1" 221 | source = "registry+https://github.com/rust-lang/crates.io-index" 222 | dependencies = [ 223 | "cgmath 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 224 | "gfx 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 225 | "gfx_phase 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 226 | "gfx_scene 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 227 | ] 228 | 229 | [[package]] 230 | name = "gfx_scene" 231 | version = "0.3.1" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | dependencies = [ 234 | "cgmath 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 235 | "gfx 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 236 | "gfx_phase 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 237 | ] 238 | 239 | [[package]] 240 | name = "gfx_text" 241 | version = "0.2.0" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | dependencies = [ 244 | "freetype-rs 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)", 245 | "gfx 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 246 | ] 247 | 248 | [[package]] 249 | name = "gfx_window_glutin" 250 | version = "0.2.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | dependencies = [ 253 | "gfx 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 254 | "gfx_device_gl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 255 | "glutin 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 256 | ] 257 | 258 | [[package]] 259 | name = "gl_common" 260 | version = "0.0.4" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | dependencies = [ 263 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 264 | ] 265 | 266 | [[package]] 267 | name = "gl_generator" 268 | version = "0.0.26" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | dependencies = [ 271 | "khronos_api 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 272 | "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 273 | "xml-rs 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", 274 | ] 275 | 276 | [[package]] 277 | name = "glutin" 278 | version = "0.1.6" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | dependencies = [ 281 | "android_glue 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 282 | "gdi32-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 283 | "gl_common 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 284 | "gl_generator 0.0.26 (registry+https://github.com/rust-lang/crates.io-index)", 285 | "glutin_cocoa 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 286 | "glutin_core_foundation 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 287 | "glutin_core_graphics 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 288 | "kernel32-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 289 | "khronos_api 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 290 | "lazy_static 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 291 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 292 | "objc 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 293 | "osmesa-sys 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 294 | "shared_library 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 295 | "user32-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 296 | "wayland-client 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 297 | "wayland-kbd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 298 | "winapi 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 299 | "x11-dl 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 300 | ] 301 | 302 | [[package]] 303 | name = "glutin_cocoa" 304 | version = "0.1.6" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | dependencies = [ 307 | "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 308 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 309 | "objc 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 310 | ] 311 | 312 | [[package]] 313 | name = "glutin_core_foundation" 314 | version = "0.1.2" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | dependencies = [ 317 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 318 | ] 319 | 320 | [[package]] 321 | name = "glutin_core_graphics" 322 | version = "0.1.4" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | dependencies = [ 325 | "glutin_core_foundation 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 326 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 327 | ] 328 | 329 | [[package]] 330 | name = "grid" 331 | version = "0.0.1" 332 | source = "git+https://github.com/kvark/grid-rs#48ffff94e162e6b2c12ab95fbad007358621c5f3" 333 | 334 | [[package]] 335 | name = "id" 336 | version = "0.0.1" 337 | source = "git+https://github.com/kvark/simplecs#d2c42d18bea1f7b3761374d793d25091635bfe24" 338 | 339 | [[package]] 340 | name = "image" 341 | version = "0.3.10" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | dependencies = [ 344 | "byteorder 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)", 345 | "enum_primitive 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 346 | "num 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", 347 | ] 348 | 349 | [[package]] 350 | name = "kernel32-sys" 351 | version = "0.1.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | dependencies = [ 354 | "winapi 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 355 | ] 356 | 357 | [[package]] 358 | name = "khronos_api" 359 | version = "0.0.5" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | 362 | [[package]] 363 | name = "lazy_static" 364 | version = "0.1.11" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | 367 | [[package]] 368 | name = "libc" 369 | version = "0.1.8" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | 372 | [[package]] 373 | name = "libz-sys" 374 | version = "0.1.3" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | dependencies = [ 377 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 378 | "pkg-config 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 379 | ] 380 | 381 | [[package]] 382 | name = "log" 383 | version = "0.3.1" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | dependencies = [ 386 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 387 | ] 388 | 389 | [[package]] 390 | name = "malloc_buf" 391 | version = "0.0.5" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | dependencies = [ 394 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 395 | ] 396 | 397 | [[package]] 398 | name = "mmap" 399 | version = "0.1.1" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | dependencies = [ 402 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 403 | "tempdir 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 404 | ] 405 | 406 | [[package]] 407 | name = "num" 408 | version = "0.1.24" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | dependencies = [ 411 | "rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 412 | "rustc-serialize 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 413 | ] 414 | 415 | [[package]] 416 | name = "objc" 417 | version = "0.1.5" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | dependencies = [ 420 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 421 | "malloc_buf 0.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 422 | ] 423 | 424 | [[package]] 425 | name = "osmesa-sys" 426 | version = "0.0.5" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | dependencies = [ 429 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 430 | "shared_library 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 431 | ] 432 | 433 | [[package]] 434 | name = "piston-float" 435 | version = "0.1.0" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | 438 | [[package]] 439 | name = "piston-gfx_texture" 440 | version = "0.1.0" 441 | source = "registry+https://github.com/rust-lang/crates.io-index" 442 | dependencies = [ 443 | "gfx 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 444 | "image 0.3.10 (registry+https://github.com/rust-lang/crates.io-index)", 445 | "piston-texture 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 446 | ] 447 | 448 | [[package]] 449 | name = "piston-texture" 450 | version = "0.1.0" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | 453 | [[package]] 454 | name = "pkg-config" 455 | version = "0.3.4" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | 458 | [[package]] 459 | name = "rand" 460 | version = "0.3.8" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | dependencies = [ 463 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 464 | ] 465 | 466 | [[package]] 467 | name = "regex" 468 | version = "0.1.33" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | dependencies = [ 471 | "regex-syntax 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 472 | ] 473 | 474 | [[package]] 475 | name = "regex-syntax" 476 | version = "0.1.1" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | 479 | [[package]] 480 | name = "rustc-serialize" 481 | version = "0.3.14" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | 484 | [[package]] 485 | name = "shared_library" 486 | version = "0.1.0" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | dependencies = [ 489 | "lazy_static 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 490 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 491 | ] 492 | 493 | [[package]] 494 | name = "tempdir" 495 | version = "0.3.4" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | dependencies = [ 498 | "rand 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 499 | ] 500 | 501 | [[package]] 502 | name = "user32-sys" 503 | version = "0.1.1" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | dependencies = [ 506 | "winapi 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", 507 | "winapi-build 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 508 | ] 509 | 510 | [[package]] 511 | name = "vecmath" 512 | version = "0.1.0" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | dependencies = [ 515 | "piston-float 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 516 | ] 517 | 518 | [[package]] 519 | name = "wayland-client" 520 | version = "0.1.8" 521 | source = "registry+https://github.com/rust-lang/crates.io-index" 522 | dependencies = [ 523 | "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 524 | "lazy_static 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 525 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 526 | ] 527 | 528 | [[package]] 529 | name = "wayland-kbd" 530 | version = "0.1.1" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | dependencies = [ 533 | "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 534 | "lazy_static 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 535 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 536 | "mmap 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 537 | "wayland-client 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 538 | ] 539 | 540 | [[package]] 541 | name = "winapi" 542 | version = "0.1.20" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | dependencies = [ 545 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 546 | ] 547 | 548 | [[package]] 549 | name = "winapi-build" 550 | version = "0.1.0" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | 553 | [[package]] 554 | name = "x11-dl" 555 | version = "1.0.1" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | dependencies = [ 558 | "dylib 0.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 559 | "libc 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", 560 | ] 561 | 562 | [[package]] 563 | name = "xml-rs" 564 | version = "0.1.25" 565 | source = "registry+https://github.com/rust-lang/crates.io-index" 566 | dependencies = [ 567 | "bitflags 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 568 | ] 569 | 570 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "claymore" 3 | version = "0.2.0" 4 | authors = [ 5 | "Dzmitry Malyshau ", 6 | ] 7 | 8 | [[bin]] 9 | name = "claymore" 10 | path = "src/main.rs" 11 | 12 | # Dependencies 13 | 14 | [dependencies] 15 | env_logger = "*" 16 | gfx = "0.6" 17 | glutin = "*" 18 | gfx_window_glutin = "0.2" 19 | 20 | [dependencies.claymore-scene] 21 | path = "src/scene/" 22 | 23 | [dependencies.claymore-load] 24 | path = "src/load/" 25 | 26 | [dependencies.claymore-game] 27 | path = "src/game/" 28 | 29 | # Examples 30 | 31 | [[example]] 32 | name = "viewer" 33 | path = "examples/viewer/bin.rs" 34 | 35 | [[example]] 36 | name = "forest" 37 | path = "examples/forest/bin.rs" 38 | 39 | [dev_dependencies] 40 | clock_ticks = "*" 41 | log = "*" 42 | rand = "*" 43 | rustc-serialize = "*" 44 | cgmath = "*" 45 | gfx_phase = "0.2" 46 | gfx_pipeline = "0.2" 47 | gfx_text = "0.2" 48 | gfx_debug_draw = "0.3" 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/kvark/claymore.png?branch=master)](https://travis-ci.org/kvark/claymore) 2 | Claymore 3 | ============ 4 | 5 | This is an experimental game project in Rust. It provides Blender-based asset pipeline and scene rendering as separate library sub-crates. It is based on and extends GFX rendering ecosystem. 6 | 7 | ## Technology 8 | - [rust](https://github.com/rust-lang/rust) 9 | - [simplecs](https://github.com/kvark/simplecs) 10 | - [gfx-rs](https://github.com/gfx-rs/gfx-rs) 11 | - [gfx_scene](https://github.com/kvark/gfx_scene) 12 | - [gfx_pipeline](https://github.com/kvark/gfx_pipeline) 13 | - [blender](blender.org) 14 | 15 | ## Controls 16 | - left mouse click: move character 17 | - `Q`/`E`: rotate camera 18 | - `Esc`: exit game 19 | 20 | ## Latest screen 21 | ![forest](etc/screens/7-forest.jpg "Forest demo") 22 | -------------------------------------------------------------------------------- /config/game.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Claymore", 3 | "characters": { 4 | "vika": { 5 | "scene": "data/vika", 6 | "alpha_test": 20, 7 | "direction": "North", 8 | "health": 30 9 | }, 10 | "valefor": { 11 | "scene": "data/valefor", 12 | "alpha_test": 20, 13 | "direction": "West", 14 | "health": 100 15 | } 16 | }, 17 | "level": { 18 | "scene": "data/level", 19 | "grid": { 20 | "center": [0, 0, 0], 21 | "size": 0.5, 22 | "area": 7.0, 23 | "color": [0.2, 0.4, 0.2, 1.0] 24 | }, 25 | "characters": { 26 | "vika": { 27 | "team": 0, 28 | "cell": [2, -4, "East"], 29 | "scale": 0.5 30 | }, 31 | "valefor": { 32 | "team": 1, 33 | "cell": [1, 3, "West"], 34 | "scale": 0.3 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /etc/TODO.md: -------------------------------------------------------------------------------- 1 | TODO: 2 | - [x] Spatial hierarchy 3 | - [ ] Build asset pipeline 4 | - [x] meshes 5 | - [x] scenes 6 | - [ ] skeletons 7 | - [ ] materials 8 | - [ ] animations 9 | - [ ] Graphics pipeline 10 | - [ ] entities 11 | - [ ] materials 12 | - [ ] techniques 13 | - [ ] render queues 14 | - [ ] Basic rendering with shader compositing 15 | - evaluate alternatives to compositing 16 | - [ ] Grid 17 | - [ ] Turn-based action (move, attack, wait) 18 | 19 | Models: 20 | - [Fire Emblem characters](http://www.models-resource.com/wii/fireemblempathofradiance/) 21 | - [Final Fantasy X characters](http://www.models-resource.com/playstation_2/finalfantasyx/) 22 | - [Snake woman monster](http://artist-3d.com/free_3d_models/dnm/model_disp.php?uid=2440&ad=&op=op) 23 | - [Leo man](http://www.models-resource.com/pc_computer/digimonmasters/model/7258/) 24 | -------------------------------------------------------------------------------- /etc/blender/io_kri/__init__.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | 'name': 'KRI common routines', 3 | 'author': 'Dzmitry Malyshau', 4 | 'version': (0, 1, 0), 5 | 'blender': (2, 6, 2), 6 | 'warning': '', 7 | 'category': 'Import-Export' 8 | } 9 | -------------------------------------------------------------------------------- /etc/blender/io_kri/action.py: -------------------------------------------------------------------------------- 1 | __author__ = ['Dzmitry Malyshau'] 2 | __bpydoc__ = 'Action module of KRI exporter.' 3 | 4 | import bpy 5 | from io_kri.common import * 6 | 7 | 8 | ### ANIMATION CURVES ### 9 | 10 | def gather_anim(ob,log): 11 | ad = ob.animation_data 12 | if not ad: return [] 13 | all = [ns.action for nt in ad.nla_tracks for ns in nt.strips] 14 | if ad.action not in ([None]+all): 15 | log.log(1,'w','current action (%s) is not finalized' % (ad.action.name)) 16 | all.append( ad.action ) 17 | return all 18 | 19 | def save_action(out,act,prefix,log): 20 | import re 21 | offset,nf = act.frame_range 22 | rnas = {} # [attrib_name][sub_id] 23 | indexator,n_empty = None,0 24 | # gather all 25 | for f in act.fcurves: 26 | attrib = f.data_path 27 | if not len(attrib): 28 | n_empty += 1 29 | continue 30 | pos_dot = attrib.find('.') 31 | pos_array = attrib.find('[') 32 | domain = None 33 | if pos_dot>=0 and pos_dot 0.01: 96 | log.log(1,'w', 'non-uniform scale: (%.1f,%.1f,%.1f)' % sca.to_tuple(1)) 97 | out.pack('8f', 98 | pos.x, pos.y, pos.z, scale, 99 | rot.x, rot.y, rot.z, rot.w ) 100 | -------------------------------------------------------------------------------- /etc/blender/io_kri_mat/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | 3 | bl_info = { 4 | 'name': 'KRI Material Library', 5 | 'author': 'Dzmitry Malyshau', 6 | 'version': (0, 1, 0), 7 | 'blender': (2, 6, 2), 8 | 'api': 36079, 9 | 'location': 'File > Export > Kri Material (.xml)', 10 | 'description': 'Export all materials into Kri XML scene file, compose GLSL shaders.', 11 | 'warning': '', 12 | 'wiki_url': 'http://code.google.com/p/kri/wiki/Exporter', 13 | 'tracker_url': '', 14 | 'category': 'Import-Export'} 15 | 16 | extension = '.xml' 17 | 18 | # To support reload properly, try to access a package var, if it's there, reload everything 19 | if 'bpy' in locals(): 20 | import imp 21 | if 'export_kri_mat' in locals(): 22 | imp.reload(export_kri_mat) 23 | 24 | 25 | import bpy 26 | from bpy.props import * 27 | from bpy_extras.io_utils import ImportHelper, ExportHelper 28 | from io_kri.common import Settings 29 | from io_kri_mat.mat import save_mat 30 | 31 | 32 | class ExportMat( bpy.types.Operator, ExportHelper ): 33 | '''Export materials to Kri XML library format''' 34 | bl_idname = 'export_mat.kri_mat' 35 | bl_label = '-= KRI Material Library=- (%s)' % extension 36 | filename_ext = extension 37 | 38 | filepath = StringProperty( name='File Path', 39 | description='Filepath used for exporting Kri materials', 40 | maxlen=1024, default='') 41 | show_info = BoolProperty( name='Show infos', 42 | description='Print information messages (i)', 43 | default=Settings.showInfo ) 44 | show_warn = BoolProperty( name='Show warnings', 45 | description='Print warning messages (w)', 46 | default=Settings.showWarning ) 47 | break_err = BoolProperty( name='Break on error', 48 | description='Stop the process on first error', 49 | default=Settings.breakError ) 50 | 51 | def execute(self, context): 52 | Settings.showInfo = self.properties.show_info 53 | Settings.showWarning = self.properties.show_warn 54 | Settings.breakError = self.properties.break_err 55 | save_mat(self.properties.filepath, context) 56 | return {'FINISHED'} 57 | 58 | 59 | # Add to a menu 60 | def menu_func(self, context): 61 | self.layout.operator( ExportMat.bl_idname, text= ExportMat.bl_label ) 62 | 63 | def register(): 64 | bpy.utils.register_module(__name__) 65 | bpy.types.INFO_MT_file_export.append(menu_func) 66 | 67 | def unregister(): 68 | bpy.utils.unregister_module(__name__) 69 | bpy.types.INFO_MT_file_export.remove(menu_func) 70 | 71 | if __name__ == '__main__': 72 | register() 73 | -------------------------------------------------------------------------------- /etc/blender/io_kri_mat/mat.py: -------------------------------------------------------------------------------- 1 | __author__ = ['Dzmitry Malyshau'] 2 | __bpydoc__ = 'Material Library module of KRI exporter.' 3 | 4 | import mathutils 5 | from xml.dom.minidom import Document 6 | from io_kri.action import * 7 | from io_kri.common import * 8 | 9 | 10 | def save_mat(filename,context): 11 | # ready... 12 | # steady... 13 | print('Exporting Material Library.') 14 | doc = Document() 15 | mat = doc.createElement('kri:Material') 16 | mat.attributes['xmlns:kri'] = 'world' 17 | doc.appendChild(mat) 18 | meta = doc.createElement('kri:Meta') 19 | mat.appendChild(meta) 20 | meta.appendChild( doc.createTextNode('') ) 21 | data = doc.createElement('kri:Data') 22 | mat.appendChild(data) 23 | # go! 24 | text = doc.toprettyxml(indent="\t") 25 | file = open(filename,'w') 26 | file.write(text) 27 | file.close() 28 | # animations 29 | # done 30 | print('Done.') 31 | -------------------------------------------------------------------------------- /etc/blender/io_kri_mesh/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | 3 | bl_info = { 4 | 'name': 'KRI Mesh format ', 5 | 'author': 'Dzmitry Malyshau', 6 | 'version': (0, 1, 0), 7 | 'blender': (2, 6, 2), 8 | 'api': 36079, 9 | 'location': 'File > Export > Kri Mesh (.k3mesh)', 10 | 'description': 'Export selected mesh into KRI.', 11 | 'warning': '', 12 | 'wiki_url': 'http://code.google.com/p/kri/wiki/Exporter', 13 | 'tracker_url': '', 14 | 'category': 'Import-Export'} 15 | 16 | extension = '.k3mesh' 17 | 18 | # To support reload properly, try to access a package var, if it's there, reload everything 19 | if 'bpy' in locals(): 20 | import imp 21 | if 'export_kri_mesh' in locals(): 22 | imp.reload(export_kri_mesh) 23 | 24 | 25 | import bpy 26 | from bpy.props import * 27 | from bpy_extras.io_utils import ImportHelper, ExportHelper 28 | from io_kri.common import * 29 | from io_kri_mesh.mesh import save_mesh 30 | 31 | 32 | class ExportMesh( bpy.types.Operator, ExportHelper ): 33 | '''Export mesh to KRI format''' 34 | bl_idname = 'export_mesh.kri_mesh' 35 | bl_label = '-= KRI Mesh=- (%s)' % extension 36 | filename_ext = extension 37 | 38 | filepath = StringProperty( name='File Path', 39 | description='Filepath used for exporting the KRI mesh', 40 | maxlen=1024, default='') 41 | show_info = BoolProperty( name='Show infos', 42 | description='Print information messages (i)', 43 | default=Settings.showInfo ) 44 | show_warn = BoolProperty( name='Show warnings', 45 | description='Print warning messages (w)', 46 | default=Settings.showWarning ) 47 | break_err = BoolProperty( name='Break on error', 48 | description='Stop the process on first error', 49 | default=Settings.breakError ) 50 | put_normal = BoolProperty( name='Put normals', 51 | description='Export vertex normals', 52 | default=Settings.putNormal ) 53 | put_tangent = BoolProperty( name='Put tangents', 54 | description='Export vertex tangents', 55 | default=Settings.putTangent ) 56 | put_quat = BoolProperty( name='Put quaternions', 57 | description='Export vertex quaternions', 58 | default=Settings.putQuat ) 59 | put_uv = BoolProperty( name='Put UV layers', 60 | description='Export vertex UVs', 61 | default=Settings.putUv ) 62 | put_color = BoolProperty( name='Put color layers', 63 | description='Export vertex colors', 64 | default=Settings.putColor ) 65 | compress_uv = BoolProperty( name='Compress UV', 66 | description='Use compact representation of texture coordinates, if possible', 67 | default=Settings.compressUv ) 68 | quat_fake = EnumProperty( name='Fake quaternions', 69 | description='Derive quaternions from normals only', 70 | items=( 71 | ('Never','Never','Dont fake anything'), 72 | ('Auto','Auto','Fake if no UV is given'), 73 | ('Force','Force','Always fake') 74 | ), default='Auto' ) 75 | quat_int = BoolProperty( name='Process quaternions', 76 | description='Prepare mesh quaternions for interpolation', 77 | default=Settings.doQuatInt ) 78 | 79 | def execute(self, context): 80 | Settings.showInfo = self.properties.show_info 81 | Settings.showWarning = self.properties.show_warn 82 | Settings.breakError = self.properties.break_err 83 | Settings.putNormal = self.properties.put_normal 84 | Settings.putTangent = self.properties.put_tangent 85 | Settings.putQuat = self.properties.put_quat 86 | Settings.putUv = self.properties.put_uv 87 | Settings.putColor = self.properties.put_color 88 | Settings.compressUv = self.properties.compress_uv 89 | Settings.doQuatInt = self.properties.quat_int 90 | Settings.fakeQuat = self.properties.quat_fake 91 | obj = None 92 | for ob in context.scene.objects: 93 | if (ob.type == 'MESH' and ob.select): 94 | obj = ob 95 | break 96 | if obj != None: 97 | fp = self.properties.filepath 98 | log = Logger(fp+'.log') 99 | out = Writer(fp) 100 | save_mesh(out, obj, log) 101 | out.close() 102 | log.conclude() 103 | return {'FINISHED'} 104 | 105 | 106 | # Add to a menu 107 | def menu_func(self, context): 108 | self.layout.operator( ExportMesh.bl_idname, text= ExportMesh.bl_label ) 109 | 110 | def register(): 111 | bpy.utils.register_module(__name__) 112 | bpy.types.INFO_MT_file_export.append(menu_func) 113 | 114 | def unregister(): 115 | bpy.utils.unregister_module(__name__) 116 | bpy.types.INFO_MT_file_export.remove(menu_func) 117 | 118 | if __name__ == '__main__': 119 | register() 120 | -------------------------------------------------------------------------------- /etc/blender/io_kri_mesh/mesh.py: -------------------------------------------------------------------------------- 1 | __author__ = ['Dzmitry Malyshau'] 2 | __bpydoc__ = 'Mesh module of KRI exporter.' 3 | 4 | import mathutils 5 | from io_kri.common import * 6 | 7 | 8 | def calc_TBN(verts, uvs): 9 | va = verts[1].co - verts[0].co 10 | vb = verts[2].co - verts[0].co 11 | n0 = n1 = va.cross(vb) 12 | tan,bit,hand = None,None,1.0 13 | if uvs!=None and n1.length_squared>0.0: 14 | ta = uvs[1] - uvs[0] 15 | tb = uvs[2] - uvs[0] 16 | tan = va*tb.y - vb*ta.y 17 | if tan.length_squared>0.0: 18 | bit = vb*ta.x - va*tb.x 19 | n0 = tan.cross(bit) 20 | tan.normalize() 21 | hand = (-1.0,1.0)[n0.dot(n1) > 0.0] 22 | else: tan = None 23 | return (tan, bit, n0, hand, n1) 24 | 25 | def calc_quat(normal): 26 | import math 27 | #note: constructor is Quaternion(w,x,y,z) 28 | if normal.z > 0.0: 29 | d = math.sqrt(2.0 + 2.0*normal.z) 30 | return mathutils.Quaternion(( 0.5*d, -normal.y/d, normal.x/d, 0.0 )) 31 | else: 32 | d = math.sqrt(2.0 - 2.0*normal.z) 33 | return mathutils.Quaternion(( normal.x/d, 0.0, 0.5*d, normal.y/d )) 34 | 35 | def fix_convert(type, valist): 36 | typeScale = { 37 | 'B': 0xFF, 38 | 'H': 0xFFFF, 39 | 'I': 0xFFFFFFFF 40 | } 41 | fun = None 42 | if type.islower(): 43 | scale = typeScale[type.upper()] + 1 44 | fun = lambda x: int(min(1,max(-1,x))*0.5*scale-0.5) 45 | else: 46 | scale = typeScale[type] 47 | fun = lambda x: int(min(1,max(0,x))*scale) 48 | return [fun(x) for x in valist] 49 | 50 | 51 | 52 | class Vertex: 53 | __slots__= 'face', 'vert','vert2', 'coord', 'tex', 'color', 'normal', 'tangent', 'quat', 'dual' 54 | def __init__(self, v): 55 | self.face = None 56 | self.vert = v 57 | self.vert2 = None 58 | self.coord = v.co 59 | self.tex = None 60 | self.color = None 61 | self.normal = v.normal 62 | self.tangent = None 63 | self.quat = None 64 | self.dual = -1 65 | 66 | 67 | class Face: 68 | __slots__ = 'v', 'vi', 'no', 'uv', 'color', 'mat', 'ta', 'hand', 'normal', 'wes' 69 | def __init__(self, face, m, ind = None, uves = None, colors = None): 70 | if not ind: # clone KRI face 71 | self.vi = list(face.vi) 72 | self.hand = face.hand 73 | self.mat = face.mat 74 | return 75 | # this section requires optimization! 76 | # hint: try 'Recalculate Outside' if getting lighting problems 77 | self.mat = face.material_index 78 | self.vi = [ face.vertices[i] for i in ind ] 79 | self.v = tuple( m.vertices[x] for x in self.vi ) 80 | self.no = tuple( x.normal for x in self.v ) 81 | self.normal = ( face.normal, mathutils.Vector((0,0,0)) )[face.use_smooth] 82 | xuv = tuple(tuple( layer[i] for i in ind ) for layer in uves) 83 | color = tuple(tuple( layer[i] for i in ind ) for layer in colors) 84 | uv_base = None 85 | if Settings.fakeQuat != 'Force' and len(xuv)>0: 86 | uv_base = xuv[0] 87 | t,b,n,hand,nv = calc_TBN(self.v, uv_base) 88 | self.wes = tuple( 3 * [0.1+nv.length_squared] ) 89 | self.ta = t 90 | self.hand = hand 91 | self.uv = ([],xuv) [Settings.putUv] 92 | self.color = ([],color) [Settings.putColor] 93 | 94 | 95 | ### MESH ### 96 | 97 | class Attribute: 98 | # fixed enumerant: 99 | # 0: not fixed, leave as is (e.g. positions) 100 | # 1: fixed, already converted (e.g. bone weights) 101 | # 2: fixed, needs conversion (e.g. normals) 102 | __slots__ = 'name', 'type', 'fixed', 'data', 'interpolate' 103 | def __init__(self, name, type, fixed=0): 104 | self.name = name 105 | self.type = type 106 | self.fixed = fixed 107 | self.data = [] 108 | self.interpolate = True 109 | 110 | class Mesh: 111 | __slots__ = 'nv', 'ni', 'attribs', 'index' 112 | def __init__(self): 113 | self.nv = self.ni = 0 114 | self.attribs = [] 115 | self.index = None 116 | 117 | 118 | def save_mesh(out, ob, log): # -> (Mesh, bounds, faces_per_mat) 119 | # ready... 120 | print("\t", 'Exporting Mesh', ob.data.name) 121 | log.logu(0, 'Mesh %s' % (ob.data.name)) 122 | arm = None 123 | if ob.parent and ob.parent.type == 'ARMATURE': 124 | arm = ob.parent.data 125 | # steady... 126 | (km, bounds, face_num) = collect_attributes(ob.data, arm, ob.vertex_groups, False, log) 127 | if km == None: 128 | return (None, None, []) 129 | # go! 130 | out.begin('mesh') 131 | totalFm = ''.join(a.type for a in km.attribs) 132 | assert len(totalFm) == 2*len(km.attribs) 133 | stride = out.size_of(totalFm) 134 | log.logu(1, 'Format: %s, Stride: %d' % (totalFm,stride)) 135 | out.text(ob.data.name); 136 | out.pack('L', km.nv) 137 | out.text('3') # topology 138 | # vertices 139 | out.begin('buffer') 140 | out.pack('B', stride) 141 | out.text(totalFm) 142 | seqStart = out.tell() 143 | for vats in zip(*(a.data for a in km.attribs)): 144 | for a,d in zip( km.attribs, vats ): 145 | tip = a.type[1] 146 | size = out.size_of(a.type) 147 | if (size&3) != 0: 148 | log.log(2, 'w', 'Attrib %d has has non-aligned type: %d' % (a.name,tip)) 149 | d2 = (d if a.fixed<=1 else fix_convert(tip,d)) 150 | out.array(tip, d2) 151 | assert out.tell() == seqStart + km.nv*stride 152 | # extra info per attribute 153 | for a in km.attribs: 154 | out.text(a.name) 155 | flag = 0 156 | if a.fixed: flag |= 1 157 | if a.interpolate: flag |= 2 158 | out.pack('B', flag) 159 | out.end() 160 | # indices 161 | if km.index: 162 | out.begin('index') 163 | stype = km.index.type[0]; 164 | stride = out.size_of(stype) 165 | out.pack('Lc', km.ni, bytes(stype, 'utf-8')) 166 | seqStart = out.tell() 167 | for d in km.index.data: 168 | out.array(stype, d) 169 | assert out.tell() == seqStart + km.ni*stride 170 | out.end() 171 | # done 172 | out.end() #mesh 173 | return (km, bounds, face_num) 174 | 175 | def compute_bounds_2d(vectors): 176 | x, y = zip(*vectors) 177 | vmin = (min(x), min(y)) 178 | vmax = (max(x), max(y)) 179 | return (vmin, vmax) 180 | 181 | def compute_bounds_3d(vectors): 182 | x, y, z = zip(*vectors) 183 | vmin = (min(x), min(y), min(z)) 184 | vmax = (max(x), max(y), max(z)) 185 | return (vmin, vmax) 186 | 187 | def collect_attributes(mesh, armature, groups, no_output,log): 188 | # 0: compute bounds 189 | bounds = compute_bounds_3d(tuple(v.co) for v in mesh.vertices) 190 | 191 | # 1: convert Mesh to Triangle Mesh 192 | for layer in mesh.uv_textures: 193 | if not len(layer.data): 194 | log.log(1,'e','UV layer is locked by the user') 195 | return (None, bounds, []) 196 | hasUv = len(mesh.uv_textures)>0 197 | hasTangent = Settings.putTangent and hasUv 198 | hasQuatUv = Settings.putQuat and hasUv 199 | hasQuat = Settings.putQuat and (Settings.fakeQuat != 'Never' or hasUv) 200 | ar_face = [] 201 | for i, face in enumerate(mesh.polygons): 202 | uves, colors = [], [] 203 | loop_end = face.loop_start + face.loop_total 204 | for layer in mesh.uv_layers: 205 | storage = layer.data[face.loop_start : loop_end] 206 | cur = tuple(mathutils.Vector(x.uv) for x in storage) 207 | uves.append(cur) 208 | for layer in mesh.vertex_colors: 209 | storage = layer.data[face.loop_start : loop_end] 210 | cur = tuple(mathutils.Vector(x.color) for x in storage) 211 | colors.append(cur) 212 | for i in range(2, face.loop_total): 213 | ar_face.append(Face(face, mesh, (0,i-1,i), uves, colors)) 214 | #else: log.logu(1,'converted to tri-mesh') 215 | if not 'ClearNonUV': 216 | n_bad_face = len(ar_face) 217 | ar_face = list(filter( lambda f: f.ta!=None, ar_face )) 218 | n_bad_face -= len(ar_face) 219 | if n_bad_face: 220 | log.log(1, 'w', '%d pure faces detected' % (n_bad_face)) 221 | if not len(ar_face): 222 | log.log(1, 'e', 'object has no faces') 223 | return (None, bounds, []) 224 | 225 | # 1.5: face indices 226 | ar_face.sort(key = lambda x: x.mat) 227 | face_num = (len(mesh.materials)+1) * [0] 228 | for face in ar_face: 229 | face_num[face.mat] += 1 230 | 231 | if no_output: 232 | return (None, bounds, face_num) 233 | 234 | # 2: fill sparsed vertex array 235 | avg,set_vert,set_surf = 0.0,{},{} 236 | for face in ar_face: 237 | avg += face.hand 238 | nor = face.normal 239 | for i in range(3): 240 | v = Vertex( face.v[i] ) 241 | v.normal = (face.no[i],nor)[nor.length_squared>0.1] 242 | v.tex = [layer[i] for layer in face.uv] 243 | v.color = [layer[i] for layer in face.color] 244 | v.face = face 245 | vs = str((v.coord,v.tex,v.color,v.normal,face.hand)) 246 | if not vs in set_vert: 247 | set_vert[vs] = [] 248 | set_vert[vs].append(v) 249 | vs = str((v.coord,v.normal,face.hand)) 250 | if not vs in set_surf: 251 | set_surf[vs] = [] 252 | set_surf[vs].append(v) 253 | log.log(1,'i', '%.2f avg handness' % (avg / len(ar_face))) 254 | 255 | # 3: compute tangents 256 | avg,bad_vert = 0.0,0 257 | for vgrup in set_surf.values(): 258 | tan,lensum = mathutils.Vector((0,0,0)),0.0 259 | for v in vgrup: 260 | assert v.quat == None 261 | f = v.face 262 | if f.ta: 263 | lensum += f.ta.length 264 | tan += f.ta 265 | quat = vgrup[0].quat 266 | no = vgrup[0].normal.normalized() 267 | if Settings.fakeQuat=='Force' or (Settings.fakeQuat=='Auto' and not hasQuatUv): 268 | quat = calc_quat(no) 269 | if lensum>0.0: 270 | avg += tan.length / lensum 271 | tan.normalize() # mean tangent 272 | if hasQuatUv and quat==None: 273 | bit = no.cross(tan) * vgrup[0].face.hand # using handness 274 | tan = bit.cross(no) # handness will be applied in shader 275 | tbn = mathutils.Matrix((tan,bit,no)) # tbn is orthonormal, right-handed 276 | quat = tbn.to_quaternion().normalized() 277 | if None in (quat,tan): 278 | bad_vert += 1 279 | quat = mathutils.Quaternion((0,0,0,1)) 280 | tan = mathutils.Vector((1,0,0)) 281 | for v in vgrup: 282 | v.quat = quat 283 | v.tangent = tan 284 | if bad_vert: 285 | log.log(1,'w','%d pure vertices detected' % (bad_vert)) 286 | if hasQuatUv and avg!=0.0: 287 | log.log(1,'i','%.2f avg tangent accuracy' % (avg / len(set_vert))) 288 | del set_surf 289 | del bad_vert 290 | del avg 291 | 292 | # 4: update triangle indexes 293 | ar_vert = [] 294 | for i,vgrup in enumerate(set_vert.values()): 295 | v = vgrup[0] 296 | for v2 in vgrup: 297 | f = v2.face 298 | ind = f.v.index(v2.vert) 299 | f.vi[ind] = i 300 | ar_vert.append(v) 301 | del set_vert 302 | 303 | # 5: unlock quaternions to make all the faces QI-friendly 304 | def qi_check(f): # check Quaternion Interpolation friendliness 305 | qx = tuple( ar_vert[x].quat for x in f.vi ) 306 | assert qx[0].dot(qx[1]) >= 0 and qx[0].dot(qx[2]) >= 0 307 | def mark_used(ind): # mark quaternion as used 308 | v = ar_vert[ind] 309 | if v.dual < 0: v.dual = ind 310 | n_dup,ex_face = 0,[] 311 | for f in ([],ar_face)[hasQuat and Settings.doQuatInt]: 312 | vx,cs,pos,n_neg = (1,2,0),[0,0,0],0,0 313 | def isGood(j): 314 | ind = f.vi[j] 315 | vi = ar_vert[ind] 316 | if vi.dual == ind: return False # used, no clone 317 | if vi.dual < 0: vi.quat.negate() # not used 318 | else: f.vi[j] = vi.dual # clone exists 319 | return True 320 | def duplicate(): 321 | src = ar_vert[ f.vi[pos] ] 322 | dst = Vertex(src.vert) 323 | dst.face = f 324 | dst.tex = tuple( layer.copy() for layer in src.tex ) 325 | dst.quat = src.quat.copy() 326 | dst.quat.negate() 327 | dst.dual = f.vi[pos] 328 | f.vi[pos] = src.dual = len(ar_vert) 329 | ar_vert.append(dst) 330 | return 1 331 | for j in range(3): 332 | qx = tuple( ar_vert[f.vi[x]].quat for x in (vx[j],vx[vx[j]]) ) 333 | cs[j] = qx[0].dot(qx[1]) 334 | if cs[j] > cs[pos]: pos = j 335 | if(cs[j] < 0): n_neg += 1 336 | #print ("\t %d: %.1f, %.1f, %.1f" % (pos,cs[0],cs[1],cs[2])) 337 | if n_neg == 2 and not isGood(pos): # classic duplication case 338 | n_dup += duplicate() 339 | if n_neg == 3: # extremely rare case 340 | pos = next((j for j in range(3) if isGood(j)), -1) 341 | if pos < 0: 342 | pos = 1 343 | n_dup += duplicate() 344 | cs[vx[pos]] *= -1 345 | cs[vx[vx[pos]]] *= -1 346 | n_neg -= 2 347 | if n_neg == 1: # that's a bad case 348 | pos = min((x,j) for j,x in enumerate(cs))[1] 349 | # prepare 350 | ia,ib = vx[pos],vx[vx[pos]] 351 | va = ar_vert[ f.vi[ia] ] 352 | vb = ar_vert[ f.vi[ib] ] 353 | vc = ar_vert[ f.vi[pos] ] 354 | # create mean vertex 355 | v = Vertex( vc.vert ) 356 | v.vert = va.vert 357 | v.vert2 = vb.vert 358 | n_dup += 1 359 | v.face = f 360 | v.coord = 0.5 * (va.coord + vb.coord) 361 | v.quat = (va.quat + vb.quat).normalized() 362 | if va.tex and vb.tex: 363 | v.tex = tuple( 0.5*(a[0]+a[1]) for a in zip(va.tex,vb.tex) ) 364 | # create additional face 365 | f2 = Face( f, mesh ) 366 | mark_used( f.vi[ia] ) # caution: easy to miss case 367 | v.dual = f.vi[ia] = f2.vi[ib] = len(ar_vert) 368 | # it's mathematically proven that both faces are QI friendly now! 369 | ar_vert.append(v) 370 | ex_face.append(f2) 371 | # mark as used 372 | for ind in f.vi: mark_used(ind) 373 | 374 | if Settings.doQuatInt and hasQuat: 375 | log.log(1,'i', 'extra %d vertices, %d faces' % (n_dup,len(ex_face))) 376 | ar_face += ex_face 377 | # run a check 378 | for f in ar_face: qi_check(f) 379 | del ex_face 380 | 381 | # 6: stats and output 382 | log.logu(1, 'total: %d vertices, %d faces' % (len(ar_vert),len(ar_face))) 383 | avg_vu = 3.0 * len(ar_face) / len(ar_vert) 384 | log.log(1,'i', '%.2f avg vertex usage' % (avg_vu)) 385 | 386 | km = Mesh() 387 | 388 | if 'putVertex': 389 | vat = Attribute('Position', '3f', 0) 390 | km.attribs.append(vat) 391 | for v in ar_vert: 392 | vat.data.append( v.coord.to_3d() ) 393 | 394 | if Settings.putNormal: 395 | #vat = Attribute('Normal', '3f', 0) 396 | vat = Attribute('Normal', '4h', 2) 397 | # WebGL only accept multiples of 4 for the attribute size 398 | km.attribs.append(vat) 399 | for v in ar_vert: 400 | vat.data.append( v.normal.to_4d() ) 401 | 402 | if hasTangent: 403 | vat = Attribute('Tangent', '4b', 2) 404 | km.attribs.append(vat) 405 | for v in ar_vert: 406 | vat.data.append( [v.tangent.x, v.tangent.y, v.tangent.z, v.face.hand] ) 407 | 408 | if hasQuat: 409 | vat2 = Attribute('Handedness', '1f') # has to be aligned 410 | vat1 = Attribute('Quaternion', '4h', 2) 411 | vat1.interpolate = vat2.interpolate = Settings.doQuatInt 412 | km.attribs.extend([ vat2,vat1 ]) 413 | for v in ar_vert: 414 | vat1.data.append([ v.quat.x, v.quat.y, v.quat.z, v.quat.w ]) 415 | vat2.data.append([ int(v.face.hand) ]) 416 | 417 | if Settings.putUv: 418 | all = mesh.uv_textures 419 | log.log(1,'i', 'UV layers: %d' % (len(all))) 420 | for i, layer in enumerate(all): 421 | name = 'Tex%d' % i 422 | vat = Attribute(name, '2f', 0) 423 | if Settings.compressUv: 424 | vmin, vmax = compute_bounds_2d(v.tex[i] for v in ar_vert) 425 | lo, hi, threshold = min(vmin), max(vmax), 0.01 426 | if lo > -threshold and hi < 1.0 + threshold: 427 | vat = Attribute(name, '2H', 2) 428 | elif lo > -1-threshold and hi < 1.0 + threshold: 429 | vat = Attribute(name, '2h', 2) 430 | log.log(2,'i', 'UV[%d] bounds: [%.1f,%.1f], format: %s' % (i, lo, hi, vat.type)) 431 | km.attribs.append(vat) 432 | for v in ar_vert: 433 | assert i=0 and weight<256 494 | r_ids.append(bid) 495 | r_weights.append(weight) 496 | vat1.data.append(r_ids) 497 | vat2.data.append(r_weights) 498 | avg /= len(ar_vert) 499 | log.logu(1, 'bone weights: %d empty, %.1f avg' % (nempty,avg)) 500 | 501 | # 9: the end! 502 | km.nv = len(ar_vert) 503 | return (km, bounds, face_num) 504 | -------------------------------------------------------------------------------- /etc/blender/io_kri_scene/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | 3 | bl_info = { 4 | 'name': 'KRI scene', 5 | 'author': 'Dzmitry Malyshau', 6 | 'version': (0, 1, 0), 7 | 'blender': (2, 6, 2), 8 | 'api': 36079, 9 | 'location': 'File > Export > Blade Scene', 10 | 'description': 'Export the scene to Blade engine (.json,.k3arm,.k3mesh)', 11 | 'warning': '', 12 | 'tracker_url': '', 13 | 'category': 'Import-Export'} 14 | 15 | # To support reload properly, try to access a package var, if it's there, reload everything 16 | if 'bpy' in locals(): 17 | import imp 18 | if 'export_kri_scene' in locals(): 19 | imp.reload(export_kri_scene) 20 | 21 | 22 | import bpy 23 | from bpy.props import * 24 | from bpy_extras.io_utils import ImportHelper, ExportHelper 25 | from io_kri.common import Settings 26 | from io_kri_scene.scene import save_scene 27 | 28 | 29 | class ExportScene( bpy.types.Operator, ExportHelper ): 30 | '''Export the scene to Blade engine''' 31 | bl_idname = 'export_scene.kri_scene' 32 | bl_label = '-= Blade scene =-' 33 | filename_ext = '' 34 | 35 | # scene settings 36 | export_meshes = BoolProperty( name='Export meshes', 37 | description='Dump mesh binary collection', 38 | default=True ) 39 | export_actions = BoolProperty( name='Export actions', 40 | description='Dump action binary collections', 41 | default=True ) 42 | num_precision = IntProperty( name='Numeric precision', 43 | description='Number of digit past the dot for numerics', 44 | default=2, min=0, max=10) 45 | # general settings 46 | filepath = StringProperty( name='File Path', 47 | description='Filepath used for exporting the scene', 48 | maxlen=1024, default='') 49 | show_info = BoolProperty( name='Show infos', 50 | description='Print information messages (i)', 51 | default=Settings.showInfo ) 52 | show_warn = BoolProperty( name='Show warnings', 53 | description='Print warning messages (w)', 54 | default=Settings.showWarning ) 55 | break_err = BoolProperty( name='Break on error', 56 | description='Stop the process on first error', 57 | default=Settings.breakError ) 58 | # mesh settings 59 | put_normal = BoolProperty( name='Put normals', 60 | description='Export vertex normals', 61 | default=Settings.putNormal ) 62 | put_tangent = BoolProperty( name='Put tangents', 63 | description='Export vertex tangents', 64 | default=Settings.putTangent ) 65 | put_quat = BoolProperty( name='Put quaternions', 66 | description='Export vertex quaternions', 67 | default=Settings.putQuat ) 68 | put_uv = BoolProperty( name='Put UV layers', 69 | description='Export vertex UVs', 70 | default=Settings.putUv ) 71 | put_color = BoolProperty( name='Put color layers', 72 | description='Export vertex colors', 73 | default=Settings.putColor ) 74 | compress_uv = BoolProperty( name='Compress UV', 75 | description='Use compact representation of texture coordinates, if possible', 76 | default=Settings.compressUv ) 77 | quat_fake = EnumProperty( name='Fake quaternions', 78 | description='Derive quaternions from normals only', 79 | items=( 80 | ('Never','Never','Dont fake anything'), 81 | ('Auto','Auto','Fake if no UV is given'), 82 | ('Force','Force','Always fake') 83 | ), default='Auto' ) 84 | quat_int = BoolProperty( name='Process quaternions', 85 | description='Prepare mesh quaternions for interpolation', 86 | default=Settings.doQuatInt ) 87 | 88 | def execute(self, context): 89 | Settings.showInfo = self.properties.show_info 90 | Settings.showWarning= self.properties.show_warn 91 | Settings.breakError = self.properties.break_err 92 | Settings.putNormal = self.properties.put_normal 93 | Settings.putTangent = self.properties.put_tangent 94 | Settings.putQuat = self.properties.put_quat 95 | Settings.putUv = self.properties.put_uv 96 | Settings.putColor = self.properties.put_color 97 | Settings.compressUv = self.properties.compress_uv 98 | Settings.doQuatInt = self.properties.quat_int 99 | Settings.fakeQuat = self.properties.quat_fake 100 | save_scene( self.properties.filepath, context, 101 | self.properties.export_meshes, 102 | self.properties.export_actions, 103 | self.properties.num_precision ) 104 | return {'FINISHED'} 105 | 106 | 107 | # Add to a menu 108 | def menu_func(self, context): 109 | self.layout.operator( ExportScene.bl_idname, text=ExportScene.bl_label ) 110 | 111 | def register(): 112 | bpy.utils.register_module(__name__) 113 | bpy.types.INFO_MT_file_export.append(menu_func) 114 | 115 | def unregister(): 116 | bpy.utils.unregister_module(__name__) 117 | bpy.types.INFO_MT_file_export.remove(menu_func) 118 | 119 | if __name__ == '__main__': 120 | register() 121 | -------------------------------------------------------------------------------- /etc/blender/io_kri_scene/scene.py: -------------------------------------------------------------------------------- 1 | __author__ = ['Dzmitry Malyshau'] 2 | __bpydoc__ = 'Scene module of KRI exporter.' 3 | 4 | import mathutils 5 | import math 6 | from io_kri.common import * 7 | from io_kri.action import * 8 | from io_kri_mesh.mesh import * 9 | 10 | 11 | def cook_mat(mat,log): 12 | textures = [] 13 | for mt in mat.texture_slots: 14 | if mt == None: continue 15 | it = mt.texture 16 | if it == None: continue 17 | if it.type != 'IMAGE': 18 | log.log(2, 'w','Texture "%s": type is not IMAGE' % (it.name)) 19 | continue 20 | if it.image == None: 21 | log.log(2, 'w','Texture "%s": image is not assigned' % (it.name)) 22 | continue 23 | wrap_x = ((0, 1)[it.repeat_x != 0], -1)[it.use_mirror_x] 24 | wrap_y = ((0, 1)[it.repeat_y != 0], -1)[it.use_mirror_y] 25 | textures.append({ 26 | 'name' : mt.name, 27 | 'image' : { 28 | 'path': it.image.filepath, 29 | 'space': it.image.colorspace_settings.name, 30 | 'mapping': it.image.mapping, 31 | }, 32 | 'filter': (1, (2, 3)[it.use_mipmap])[it.use_interpolation], 33 | 'wrap' : (wrap_x, wrap_y, 0), 34 | 'scale' : list(mt.scale), 35 | 'offset': list(mt.offset), 36 | }) 37 | kind = 'phong' 38 | if mat.use_shadeless: 39 | kind = 'flat' 40 | elif mat.use_tangent_shading: 41 | kind = 'anisotropic' 42 | diff_params = [mat.diffuse_intensity, float(mat.emit), 0.0, mat.alpha] 43 | spec_params = [mat.specular_intensity, float(mat.specular_hardness), 0.0, mat.specular_alpha] 44 | return { 45 | 'name' : mat.name, 46 | 'shader' : kind, 47 | 'transparent': mat.use_transparency, 48 | 'data' : { 49 | 'Ambient' : ('scalar', (mat.ambient,)), 50 | 'DiffuseColor' : ('color', list(mat.diffuse_color)), 51 | 'DiffuseParams' : ('vector', diff_params), 52 | 'SpecularColor' : ('color', list(mat.specular_color)), 53 | 'SpecularParams': ('vector', spec_params), 54 | }, 55 | 'textures' : textures, 56 | } 57 | 58 | 59 | def cook_space(matrix, name, log): 60 | pos, rot, sca = matrix.decompose() 61 | scale = (sca.x + sca.y + sca.z)/3.0 62 | if sca.x*sca.x+sca.y*sca.y+sca.z*sca.z > 0.01 + sca.x*sca.y+sca.y*sca.z+sca.z*sca.x: 63 | log.log(1,'w', 'Non-uniform scale (%.1f,%.1f,%.1f) on %s' % (sca.x, sca.y, sca.z, name)) 64 | return { 65 | 'pos' : list(pos), 66 | 'rot' : [rot.x, rot.y, rot.z, rot.w], 67 | 'scale' : scale, 68 | } 69 | 70 | def cook_node(ob, log): 71 | return { 72 | 'name' : ob.name, 73 | 'space' : cook_space(ob.matrix_local, ob.name, log), 74 | 'children' : [], 75 | 'actions' : [], 76 | } 77 | 78 | def cook_camera(cam, log): 79 | return { #todo: ortho 80 | 'name' : cam.name, 81 | 'angle' : [cam.angle_x, cam.angle_y], 82 | 'range' : [cam.clip_start, cam.clip_end], 83 | 'actions' : [], 84 | } 85 | 86 | def cook_lamp(lamp, log): 87 | attenu = [0.0, 0.0] 88 | sphere = False 89 | params = [] 90 | kind = lamp.type 91 | if kind not in ('SUN', 'HEMI'): 92 | attenu = [lamp.linear_attenuation, lamp.quadratic_attenuation] 93 | if kind in ('SPOT', 'POINT'): 94 | sphere = lamp.use_sphere 95 | if kind == 'SPOT': 96 | params = [lamp.spot_size, lamp.spot_blend, 0.1] 97 | elif kind == 'AREA': 98 | params = [lamp.size, lamp.size_y, 0.1] 99 | return { 100 | 'name' : lamp.name, 101 | 'kind' : kind, 102 | 'parameters' : params, 103 | 'color' : list(lamp.color), 104 | 'energy' : lamp.energy, 105 | 'attenuation' : attenu, 106 | 'distance' : lamp.distance, 107 | 'spherical' : sphere, 108 | 'actions' : [], 109 | } 110 | 111 | def cook_armature(arm, log): 112 | root = { 'children': [] } 113 | bones = { '':root } 114 | for b in arm.bones: 115 | par = bones[''] 116 | mx = b.matrix_local 117 | if b.parent: 118 | par = bones[b.parent.name] 119 | mx = b.parent.matrix_local.copy().inverted() * mx 120 | ob = { 121 | 'name' : b.name, 122 | 'space' : cook_space(mx, b.name, log), 123 | 'children' : [], 124 | } 125 | par['children'].append(ob) 126 | bones[b.name] = ob 127 | return { 128 | 'name' : arm.name, 129 | 'dual_quat' : False, 130 | 'bones' : root['children'], 131 | 'actions' : [], 132 | } 133 | 134 | 135 | def export_value(elem, ofile, num_format, level): 136 | import collections 137 | #print('Exporting:',str(elem)) 138 | new_line = '\n%s' % (level * '\t') 139 | tip = type(elem) 140 | if tip is tuple: 141 | last = elem[len(elem)-1] 142 | if type(last) is dict: # object 143 | assert len(elem) <= 3 144 | name = elem[0] 145 | if len(elem) == 3: 146 | name = elem[1] 147 | ofile.write(elem[0] + '(') 148 | ofile.write(name) 149 | if len(last): 150 | ofile.write('{') 151 | for key,val in last.items(): 152 | ofile.write('%s\t%s\t: ' % (new_line, key)) 153 | export_value(val, ofile, num_format, level+1) 154 | ofile.write(',' ) 155 | ofile.write(new_line + '}') 156 | if len(elem) == 3: 157 | ofile.write(')') 158 | else: 159 | if type(elem[0]) is str: # enum element 160 | ofile.write(elem[0]) 161 | if len(elem) > 1: 162 | ofile.write('(\t') 163 | for sub in elem[1:]: 164 | export_value(sub, ofile, num_format, level+1) 165 | if not (sub is last): 166 | ofile.write(',\t') 167 | ofile.write(')') 168 | #else: 169 | #raise Exception( 'Composite element %s is unknown' % (str(elem))) 170 | elif tip is bool: 171 | ofile.write(('false', 'true')[elem]) 172 | elif tip is int: 173 | ofile.write(str(elem)) 174 | elif tip is float: 175 | ofile.write(num_format % (elem)) 176 | elif tip is str: 177 | ofile.write('~"%s"' % (elem)) 178 | elif tip is list: 179 | if len(elem): 180 | subtip = type(elem[0]) 181 | is_class = subtip in (tuple, dict, list, str) 182 | ofile.write(('[', '~[')[is_class]) 183 | for i,sub in enumerate(elem): 184 | assert type(sub) == subtip 185 | if is_class: 186 | ofile.write(new_line + '\t') 187 | export_value(sub, ofile, num_format, level+1) 188 | if i+1 != len(elem): 189 | ofile.write((', ', ',')[is_class]) 190 | if is_class: 191 | ofile.write(new_line) 192 | ofile.write(']') 193 | else: 194 | ofile.write('~[]') 195 | else: 196 | ofile.write('0') 197 | raise Exception('Element %s is unknown' % (str(type(elem)))) 198 | 199 | 200 | def export_doc(document,filepath,num_format): 201 | ofile = open(filepath+'.rs','w') 202 | ofile.write('use common::*;\n') 203 | ofile.write('pub fn load()-> Scene {') 204 | export_value(document, ofile, num_format, 1) 205 | ofile.write('}\n') 206 | ofile.close() 207 | 208 | 209 | def export_json(document, filepath, num_format): 210 | import json 211 | class KriEncoder(json.JSONEncoder): 212 | def default(self, obj): 213 | if isinstance(obj, float): 214 | return num_format % obj 215 | return json.JSONEncoder.default(self, obj) 216 | json.encoder.FLOAT_REPR = lambda o: num_format % (o) 217 | text = json.dumps(document, indent="\t", allow_nan=False, cls=KriEncoder) 218 | file = open(filepath+'.json', 'w') 219 | file.write(text) 220 | file.close() 221 | 222 | 223 | def save_scene(filepath, context, export_meshes, export_actions, precision): 224 | glob = {} 225 | materials = [] 226 | nodes = [] 227 | cameras = [] 228 | lights = [] 229 | entities = [] 230 | # ready... 231 | import os 232 | if not os.path.exists(filepath): 233 | os.makedirs(filepath) 234 | log = Logger(filepath+'.log') 235 | out_mesh, out_act_node, out_act_arm = None, None, None 236 | collection_mesh, collection_node_anim = 'all', 'nodes' 237 | if export_meshes: 238 | out_mesh = Writer('%s/%s.k3mesh' % (filepath, collection_mesh)) 239 | if export_actions: 240 | out_act_node= Writer('%s/%s.k3act' % (filepath, collection_node_anim)) 241 | sc = context.scene 242 | print('Exporting Scene...') 243 | log.logu(0, 'Scene %s' % (filepath)) 244 | # -globals 245 | bDegrees = (sc.unit_settings.system_rotation == 'DEGREES') 246 | if not bDegrees: 247 | #it's easier to convert on loading than here 248 | log.log(1, 'w', 'Radians are not supported') 249 | if sc.use_gravity: 250 | gv = sc.gravity 251 | log.log(1, 'i', 'gravity: (%.1f,%.1f,%.1f)' % (gv.x, gv.y, gv.z)) 252 | glob['gravity'] = list(sc.gravity) 253 | else: 254 | glob['gravity'] = [0,0,0] 255 | # -materials 256 | for mat in context.blend_data.materials: 257 | if log.stop: break 258 | materials.append(cook_mat(mat, log)) 259 | #save_actions( mat, 'm','t' ) 260 | # -nodes 261 | node_tree = {} 262 | for ob in sc.objects: 263 | node_tree[ob.name] = n = cook_node(ob,log) 264 | if ob.parent == None: 265 | nodes.append(n) 266 | else: 267 | node_tree[ob.parent.name]['children'].append(n) 268 | del node_tree 269 | # steady... 270 | for ob in sc.objects: 271 | if log.stop: break 272 | # parse node 273 | if len(ob.modifiers): 274 | log.log(1, 'w', 'Unapplied modifiers detected on object %s' % (ob.name)) 275 | current = { 276 | 'actions' : [], 277 | } 278 | if ob.type == 'MESH': 279 | if out_mesh != None: 280 | (_, bounds, face_num) = save_mesh(out_mesh, ob, log) 281 | else: 282 | (_, bounds, face_num) = collect_attributes(ob.data, None, ob.vertex_groups, True, log) 283 | if ob.data.materials == []: 284 | log.log(1, 'w', 'No materials detected') 285 | has_arm = ob.parent and ob.parent.type == 'ARMATURE' 286 | current = { 287 | 'node' : ob.name, 288 | 'mesh' : '%s@%s' % (ob.data.name, collection_mesh), 289 | 'armature' : ob.parent.data.name if has_arm else '', 290 | 'bounds' : bounds, 291 | 'fragments' : [], 292 | 'actions' : [], 293 | } 294 | entities.append(current) 295 | offset = 0 296 | for fn, m in zip(face_num, ob.data.materials): 297 | if not fn: break 298 | s = (m.name if m else '') 299 | log.logu(1, '+entity: %d faces, [%s]' % (fn,s)) 300 | current['fragments'].append({ 301 | 'material' : s, 302 | 'slice' : [3*offset, 3*(offset+fn)], 303 | }) 304 | offset += fn 305 | elif ob.type == 'ARMATURE': 306 | arm = cook_armature(ob.data, log) 307 | current['node'] = ob.name 308 | name = ob.data.name 309 | ani_path = (None, '%s/%s' % (filepath,name))[export_actions] 310 | anims = save_actions_ext(ani_path, ob, 'pose', log) 311 | for ani in anims: 312 | current['actions'].append('%s@%s' % (ani,name)) 313 | elif ob.type == 'CAMERA': 314 | current = cook_camera(ob.data, log) 315 | current['node'] = ob.name 316 | cameras.append(current) 317 | elif ob.type == 'LAMP': 318 | current = cook_lamp(ob.data, log) 319 | current['node'] = ob.name 320 | lights.append(current) 321 | # animations 322 | anims = save_actions_int(out_act_node, ob, None, log) 323 | for ani in anims: 324 | current['actions'].append('%s@%s' % (ani, collection_node_anim)) 325 | if out_mesh != None: 326 | out_mesh.close() 327 | if out_act_node != None: 328 | out_act_node.close() 329 | # go! 330 | document = { 331 | 'global' : glob, 332 | 'materials' : materials, 333 | 'nodes' : nodes, 334 | 'cameras' : cameras, 335 | 'lights' : lights, 336 | 'entities' : entities, 337 | } 338 | num_format = '%' + ('.%df' % precision) 339 | export_json(document, filepath, num_format) 340 | # finish 341 | print('Done.') 342 | log.conclude() 343 | -------------------------------------------------------------------------------- /etc/screens/0-cube.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvark/claymore/708c820ed0841c091c53245df3853d2b4a2839de/etc/screens/0-cube.jpg -------------------------------------------------------------------------------- /etc/screens/1-model-raw.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvark/claymore/708c820ed0841c091c53245df3853d2b4a2839de/etc/screens/1-model-raw.jpg -------------------------------------------------------------------------------- /etc/screens/2-model-textured.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvark/claymore/708c820ed0841c091c53245df3853d2b4a2839de/etc/screens/2-model-textured.jpg -------------------------------------------------------------------------------- /etc/screens/3-model-scene.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvark/claymore/708c820ed0841c091c53245df3853d2b4a2839de/etc/screens/3-model-scene.jpg -------------------------------------------------------------------------------- /etc/screens/4-grid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvark/claymore/708c820ed0841c091c53245df3853d2b4a2839de/etc/screens/4-grid.jpg -------------------------------------------------------------------------------- /etc/screens/5-model-nodes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvark/claymore/708c820ed0841c091c53245df3853d2b4a2839de/etc/screens/5-model-nodes.jpg -------------------------------------------------------------------------------- /etc/screens/6-tile-generator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvark/claymore/708c820ed0841c091c53245df3853d2b4a2839de/etc/screens/6-tile-generator.jpg -------------------------------------------------------------------------------- /etc/screens/7-forest.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kvark/claymore/708c820ed0841c091c53245df3853d2b4a2839de/etc/screens/7-forest.jpg -------------------------------------------------------------------------------- /examples/forest/bin.rs: -------------------------------------------------------------------------------- 1 | extern crate clock_ticks; 2 | #[macro_use] 3 | extern crate log; 4 | extern crate env_logger; 5 | extern crate rustc_serialize; 6 | extern crate rand; 7 | extern crate cgmath; 8 | extern crate glutin; 9 | extern crate gfx; 10 | extern crate gfx_phase; 11 | extern crate gfx_pipeline; 12 | extern crate gfx_window_glutin; 13 | extern crate gfx_text; 14 | extern crate gfx_debug_draw; 15 | extern crate claymore_load; 16 | extern crate claymore_scene; 17 | 18 | mod generate; 19 | mod reflect; 20 | 21 | 22 | #[derive(Debug)] 23 | enum Order { 24 | Default, 25 | FrontToBack, 26 | BackToFront, 27 | Material, 28 | Mesh, 29 | Unordered, 30 | } 31 | 32 | impl Order { 33 | pub fn next(&mut self) -> Option> { 34 | let (fun, order): (Option>, Order) = match *self { 35 | Order::Default => (Some(gfx_phase::sort::front_to_back), Order::FrontToBack), 36 | Order::FrontToBack => (Some(gfx_phase::sort::back_to_front), Order::BackToFront), 37 | Order::BackToFront => (Some(gfx_phase::sort::program), Order::Material), 38 | Order::Material => (Some(gfx_phase::sort::mesh), Order::Mesh), 39 | Order::Mesh => (None, Order::Unordered), 40 | Order::Unordered => (Some(gfx_pipeline::forward::order), Order::Default), 41 | }; 42 | *self = order; 43 | fun 44 | } 45 | } 46 | 47 | fn move_camera( 48 | camera: &claymore_scene::Camera, 49 | vec: &cgmath::Vector3, 50 | world: &mut claymore_scene::World 51 | ){ 52 | use cgmath::{EuclideanVector, Transform, Vector}; 53 | let node = world.mut_node(camera.node); 54 | let mut cam_offset = node.local.transform_vector(vec); 55 | let len = cam_offset.length(); 56 | if vec.z != cgmath::zero() { 57 | cam_offset.x = cgmath::zero(); 58 | cam_offset.y = cgmath::zero(); 59 | }else { 60 | cam_offset.z = cgmath::zero(); 61 | }; 62 | let rescale = len / cam_offset.length(); 63 | node.local.disp.add_self_v(&cam_offset.mul_s(rescale)); 64 | } 65 | 66 | fn rotate_camera( 67 | camera: &claymore_scene::Camera, 68 | amount: S, 69 | world: &mut claymore_scene::World 70 | ){ 71 | use cgmath::{Transform, Vector}; 72 | let node = world.mut_node(camera.node); 73 | let zvec = node.local.transform_vector(&cgmath::Vector3::unit_z()); 74 | let t = -node.local.disp.z / zvec.z; 75 | let anchor = node.local.disp.add_v(&zvec.mul_s(t)); 76 | let t_rotation = cgmath::Decomposed { 77 | scale: cgmath::one(), 78 | rot: cgmath::Rotation3::from_axis_angle( 79 | &cgmath::Vector3::unit_z(), cgmath::rad(amount)), 80 | disp: cgmath::zero(), 81 | }; 82 | let t_offset_inv = cgmath::Decomposed { 83 | scale: cgmath::one(), 84 | rot: cgmath::Rotation::identity(), 85 | disp: -anchor, 86 | }; 87 | let t_offset = cgmath::Decomposed { 88 | scale: cgmath::one(), 89 | rot: cgmath::Rotation::identity(), 90 | disp: anchor, 91 | }; 92 | let relative = t_offset.concat(&t_rotation.concat(&t_offset_inv)); 93 | node.local = relative.concat(&node.local); 94 | } 95 | 96 | 97 | fn main() { 98 | use std::env; 99 | use gfx::traits::*; 100 | use gfx_pipeline::Pipeline; 101 | 102 | env_logger::init().unwrap(); 103 | let root = env::var("CARGO_MANIFEST_DIR").unwrap_or(".".to_string()); 104 | 105 | println!("Creating the window..."); 106 | let window = glutin::WindowBuilder::new() 107 | .with_title("Forest generator".to_string()) 108 | .with_vsync() 109 | .with_gl(glutin::GL_CORE) 110 | .build().unwrap(); 111 | let (mut stream, mut device, mut factory) = gfx_window_glutin::init(window); 112 | 113 | let mut debug = gfx_debug_draw::DebugRenderer::new( 114 | factory.clone(), 115 | gfx_text::new(factory.clone()).unwrap(), 116 | 64).unwrap(); 117 | 118 | println!("Reading configuration..."); 119 | let config: reflect::Demo = { 120 | use std::fs::File; 121 | use std::io::Read; 122 | use rustc_serialize::json; 123 | let mut file = File::open(&format!("{}/examples/forest/config.json", root)) 124 | .unwrap(); 125 | let mut s = String::new(); 126 | file.read_to_string(&mut s).unwrap(); 127 | json::decode(&s).unwrap() 128 | }; 129 | 130 | println!("Loading asset palette..."); 131 | let mut scene = claymore_load::Context::new(&mut factory, root) 132 | .load_scene(&config.palette.scene) 133 | .unwrap(); 134 | scene.world.update(); 135 | 136 | if config.generate { 137 | let gen = generate::Gen::new(&config.palette, &scene); 138 | gen.populate(&config.palette.model, &mut scene); 139 | } 140 | 141 | println!("Initializing the graphics..."); 142 | let mut pipeline = gfx_pipeline::forward::Pipeline::new(&mut factory) 143 | .unwrap(); 144 | pipeline.background = Some([0.2, 0.3, 0.4, 1.0]); 145 | 146 | let mut camera = match scene.cameras.first() { 147 | Some(cam) => cam.clone(), 148 | None => { 149 | println!("No cameras found!"); 150 | return; 151 | } 152 | }; 153 | 154 | let mut last_moment = clock_ticks::precise_time_ns(); 155 | let mut avg_time = 0; 156 | let mut order = Order::Default; 157 | 158 | println!("Rendering..."); 159 | 'main: loop { 160 | let delta = clock_ticks::precise_time_ns() - last_moment; 161 | avg_time = (avg_time * config.debug.time_factor + delta) / 162 | (config.debug.time_factor + 1); 163 | last_moment += delta; 164 | 165 | let seconds = (delta/1000000) as f32 / 1000.0; 166 | let move_delta = config.control.move_speed * seconds; 167 | let rotate_delta = config.control.rotate_speed * seconds; 168 | 169 | for event in stream.out.window.poll_events() { 170 | // TODO: use the scroll 171 | use glutin::{Event, VirtualKeyCode}; 172 | use glutin::ElementState::Pressed; 173 | match event { 174 | Event::Closed => break 'main, 175 | Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::Escape)) => 176 | break 'main, 177 | Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::A)) => 178 | move_camera(&camera, &cgmath::vec3(-move_delta, 0.0, 0.0), &mut scene.world), 179 | Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::D)) => 180 | move_camera(&camera, &cgmath::vec3(move_delta, 0.0, 0.0), &mut scene.world), 181 | Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::S)) => 182 | move_camera(&camera, &cgmath::vec3(0.0, -move_delta, 0.0), &mut scene.world), 183 | Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::W)) => 184 | move_camera(&camera, &cgmath::vec3(0.0, move_delta, 0.0), &mut scene.world), 185 | Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::X)) => 186 | move_camera(&camera, &cgmath::vec3(0.0, 0.0, -move_delta), &mut scene.world), 187 | Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::Z)) => 188 | move_camera(&camera, &cgmath::vec3(0.0, 0.0, move_delta), &mut scene.world), 189 | Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::Q)) => 190 | rotate_camera(&camera, -rotate_delta, &mut scene.world), 191 | Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::E)) => 192 | rotate_camera(&camera, rotate_delta, &mut scene.world), 193 | Event::KeyboardInput(Pressed, _, Some(VirtualKeyCode::Tab)) => 194 | pipeline.phase.sort = order.next(), 195 | _ => {}, 196 | } 197 | } 198 | 199 | scene.world.update(); 200 | 201 | camera.projection.aspect = stream.get_aspect_ratio(); 202 | let report = pipeline.render(&scene, &camera, &mut stream).unwrap(); 203 | 204 | { 205 | let win_size = stream.out.get_size(); 206 | let color = config.debug.color; 207 | let offset = config.debug.offset; 208 | let mut offset = [ 209 | if offset.0 < 0 {win_size.0 as i32 + offset.0} else {offset.0}, 210 | if offset.1 < 0 {win_size.1 as i32 + offset.1} else {offset.1}, 211 | ]; 212 | let color = [color.0, color.1, color.2, color.3]; 213 | let strings = [ 214 | format!("frame time = {} ms", avg_time / 1000000), 215 | format!("primitives = {}", report.primitives_rendered), 216 | format!("order = {:?}", order), 217 | //format!("ratio = {}", report.get_ratio()), 218 | //format!("invisible = {}", report.calls_invisible), 219 | format!("calls culled = {}", report.calls_culled), 220 | //format!("rejected = {}", report.calls_rejected), 221 | //format!("failed = {}", report.calls_failed), 222 | format!("calls passed = {}", report.calls_passed), 223 | ]; 224 | for s in strings.iter() { 225 | debug.draw_text_on_screen(s, offset, color); 226 | offset[1] += config.debug.line_jump; 227 | } 228 | } 229 | debug.render(&mut stream, [[0.0; 4]; 4]).unwrap(); 230 | 231 | stream.present(&mut device); 232 | } 233 | println!("Done."); 234 | } 235 | -------------------------------------------------------------------------------- /examples/forest/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Forest Generator", 3 | "generate": true, 4 | "control": { 5 | "move_speed": 1.0, 6 | "rotate_speed": 1.0 7 | }, 8 | "debug": { 9 | "offset": [-150, 10], 10 | "line_jump": 20, 11 | "color": [1.0, 0.1, 0.4, 0.7], 12 | "time_factor": 5 13 | }, 14 | "palette": { 15 | "scene": "data/nature", 16 | "size": "3", 17 | "model": { 18 | "grid_size": [20, 20], 19 | "water_plant_chance": 0.2, 20 | "plant_chance": 0.3, 21 | "max_grass_plants": 5, 22 | "max_river_plants": 2, 23 | "tent_chance": 0.05, 24 | "water_height": 0.15, 25 | "ground_height": 0.25 26 | }, 27 | "tiles": { 28 | "Mesh1 Plate_Grass Model": "", 29 | "Mesh1 Plate_Grass_Dirt Model": "", 30 | "Mesh1 Plate_River Model": "ns", 31 | "Mesh1 Plate_River_Corner Model": "nw", 32 | "Mesh1 Plate_River_Corner_Dirt Model": "nw", 33 | "Mesh1 Plate_River_Dirt Model": "ns" 34 | }, 35 | "water_plants": [ 36 | "Mesh1 Water_lily Model" 37 | ], 38 | "plants": [ 39 | "Mesh1 Plant_1 Model", 40 | "Mesh1 Plant_2 Model", 41 | "Mesh1 Plant_3 Model", 42 | "Mesh1 Oak_Dark Model", 43 | "Mesh1 Oak_Fall Model", 44 | "Mesh1 Oak_Green Model", 45 | "Mesh1 Large_Oak_Dark Model", 46 | "Mesh1 Large_Oak_Fall Model", 47 | "Mesh1 Large_Oak_Green Model", 48 | "Mesh1 Rock_1 Model", 49 | "Mesh1 Rock_2 Model", 50 | "Mesh1 Rock_3 Model", 51 | "Mesh1 Rock_4 Model", 52 | "Mesh1 Rock_5 Model", 53 | "Mesh1 Rock_6 Model" 54 | ], 55 | "tents": [ 56 | "Mesh1 Tent Model", 57 | "Mesh1 Tent_Poles Model" 58 | ], 59 | "camp_fires": [ 60 | "Mesh1 Campfire Model" 61 | ] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/forest/generate.rs: -------------------------------------------------------------------------------- 1 | use std::ops; 2 | use rand; 3 | use cgmath; 4 | use gfx; 5 | use claymore_scene; 6 | use reflect; 7 | 8 | 9 | #[derive(Clone, Copy, Debug, PartialEq)] 10 | #[repr(u8)] 11 | enum Direction { 12 | North, 13 | East, 14 | South, 15 | West, 16 | } 17 | 18 | const ALL_DIRECTIONS: &'static [Direction] = &[ 19 | Direction::North, Direction::East, 20 | Direction::South, Direction::West 21 | ]; 22 | 23 | impl Direction { 24 | pub fn from_char(c: char) -> Result { 25 | Ok(match c { 26 | 'n' => Direction::North, 27 | 'e' => Direction::East, 28 | 's' => Direction::South, 29 | 'w' => Direction::West, 30 | _ => return Err(c) 31 | }) 32 | } 33 | 34 | pub fn to_vector(self) -> [i32; 2] { 35 | [[0, 1], [1, 0], [0,-1], [-1, 0]][self as usize] 36 | } 37 | 38 | pub fn to_degrees(self) -> f32 { 39 | (self as u8) as f32 * 90.0 40 | } 41 | 42 | pub fn aligned_as(self, das: Direction, dto: Direction) -> Direction { 43 | match ((self as u8) + 4 + (das as u8) - (dto as u8)) & 3 { 44 | 0 => Direction::North, 45 | 1 => Direction::East, 46 | 2 => Direction::South, 47 | 3 => Direction::West, 48 | _ => panic!("bad direction") 49 | } 50 | } 51 | } 52 | 53 | #[derive(Clone, Copy, Debug, PartialEq)] 54 | struct DirectionSet(u8); 55 | 56 | const SET_EMPTY : DirectionSet = DirectionSet(0); 57 | const SET_VERTICAL : DirectionSet = DirectionSet(5); 58 | const SET_HORISONTAL: DirectionSet = DirectionSet(12); 59 | 60 | impl DirectionSet { 61 | pub fn from_str(s: &str) -> Result { 62 | let mut set = SET_EMPTY; 63 | for c in s.chars() { 64 | set = set | try!(Direction::from_char(c)); 65 | } 66 | Ok(set) 67 | } 68 | 69 | pub fn has(&self, d: Direction) -> bool { 70 | self.0 & (1 << (d as u8)) != 0 71 | } 72 | } 73 | 74 | impl ops::Shr for DirectionSet { 75 | type Output = DirectionSet; 76 | fn shr(self, rhs: Direction) -> DirectionSet { 77 | let d = rhs as u8; 78 | let m = self.0; 79 | DirectionSet(((m << d) & 0xF) | (m >> (4 - d))) 80 | } 81 | } 82 | 83 | impl ops::BitOr for DirectionSet { 84 | type Output = DirectionSet; 85 | fn bitor(self, rhs: Direction) -> DirectionSet { 86 | DirectionSet(self.0 | (1 << (rhs as u8))) 87 | } 88 | } 89 | 90 | impl ops::BitAnd for DirectionSet { 91 | type Output = DirectionSet; 92 | fn bitand(self, rhs: DirectionSet) -> DirectionSet { 93 | DirectionSet(self.0 & rhs.0) 94 | } 95 | } 96 | 97 | 98 | struct Drawable { 99 | node: claymore_scene::NodeId, 100 | mesh: gfx::Mesh, 101 | bound: cgmath::Aabb3, 102 | fragments: Vec>, 103 | } 104 | 105 | impl Drawable { 106 | pub fn new(ent: &claymore_scene::Entity) -> Drawable { 107 | Drawable { 108 | node: ent.node.clone(), 109 | mesh: ent.mesh.clone(), 110 | bound: ent.bound.clone(), 111 | fragments: ent.fragments.clone(), 112 | } 113 | } 114 | } 115 | 116 | 117 | struct TileProto { 118 | drawable: Drawable, 119 | river_mask: DirectionSet, 120 | } 121 | 122 | impl TileProto { 123 | pub fn fit_orientation(&self, dir: Direction, neighbors: DirectionSet, 124 | rivers: DirectionSet) -> bool { 125 | (self.river_mask >> dir) & neighbors == rivers 126 | } 127 | } 128 | 129 | struct Tile { 130 | proto_id: usize, 131 | orientation: Direction, 132 | node: claymore_scene::NodeId, 133 | } 134 | 135 | 136 | pub struct Gen { 137 | proto_tiles: Vec>, 138 | water_plants: Vec>, 139 | plants: Vec>, 140 | tents: Vec>, 141 | camp_fires: Vec>, 142 | tile_size: f32, 143 | } 144 | 145 | impl Gen { 146 | pub fn new(config: &reflect::Palette, scene: &claymore_scene::Scene) -> Gen { 147 | println!("Processing data..."); 148 | let protos: Vec<_> = config.tiles.iter().map(|(name, river)| { 149 | let ent = scene.entities.iter() 150 | .find(|ent| &ent.name == name) 151 | .expect(&format!("Unable to find entity {}", name)); 152 | info!("Found tile {} with river mask {}", name, river); 153 | TileProto { 154 | drawable: Drawable::new(ent), 155 | river_mask: DirectionSet::from_str(river).unwrap(), 156 | } 157 | }).collect(); 158 | let water_plants = config.water_plants.iter().map(|name| Drawable::new( 159 | scene.entities.iter() 160 | .find(|ent| &ent.name == name) 161 | .expect(&format!("Unable to find water plant {}", name)) 162 | )).collect(); 163 | let plants = config.plants.iter().map(|name| Drawable::new( 164 | scene.entities.iter() 165 | .find(|ent| &ent.name == name) 166 | .expect(&format!("Unable to find plant {}", name)) 167 | )).collect(); 168 | let tents = config.tents.iter().map(|name| Drawable::new( 169 | scene.entities.iter() 170 | .find(|ent| &ent.name == name) 171 | .expect(&format!("Unable to find tent {}", name)) 172 | )).collect(); 173 | let camp_fires = config.tents.iter().map(|name| Drawable::new( 174 | scene.entities.iter() 175 | .find(|ent| &ent.name == name) 176 | .expect(&format!("Unable to find camp fire {}", name)) 177 | )).collect(); 178 | Gen { 179 | proto_tiles: protos, 180 | water_plants: water_plants, 181 | plants: plants, 182 | tents: tents, 183 | camp_fires: camp_fires, 184 | tile_size: config.size, 185 | } 186 | } 187 | 188 | fn get_water_spots(&self, river_mask: DirectionSet) -> Vec<(f32, f32)> { 189 | let mut spots = Vec::new(); 190 | if river_mask == SET_VERTICAL || river_mask == SET_HORISONTAL { 191 | spots.push((0.5, 0.5)) 192 | } 193 | if river_mask.has(Direction::North) { 194 | spots.push((0.5, 0.8)); 195 | } 196 | if river_mask.has(Direction::East) { 197 | spots.push((0.8, 0.5)); 198 | } 199 | if river_mask.has(Direction::South) { 200 | spots.push((0.5, 0.2)); 201 | } 202 | if river_mask.has(Direction::West) { 203 | spots.push((0.2, 0.5)); 204 | } 205 | spots 206 | } 207 | 208 | fn get_grass_spots(&self, river_mask: DirectionSet, has_tent: bool) 209 | -> Vec<(f32, f32)> { 210 | if has_tent { 211 | return Vec::new() 212 | } 213 | let low = 0.15; 214 | let mid = 0.5; 215 | let hai = 0.85; 216 | let mut spots = vec![ 217 | (low, low), 218 | (hai, low), 219 | (low, hai), 220 | (hai, hai), 221 | ]; 222 | if river_mask == SET_EMPTY { 223 | spots.push((mid, mid)); 224 | } 225 | if !river_mask.has(Direction::North) { 226 | spots.push((mid, hai)); 227 | } 228 | if !river_mask.has(Direction::East) { 229 | spots.push((hai, mid)); 230 | } 231 | if !river_mask.has(Direction::South) { 232 | spots.push((mid, low)); 233 | } 234 | if !river_mask.has(Direction::West) { 235 | spots.push((low, mid)); 236 | } 237 | spots 238 | } 239 | 240 | fn make_tile(&self, x: i32, y: i32, proto_id: usize, orientation: Direction, 241 | world: &mut claymore_scene::World) 242 | -> claymore_scene::Entity { 243 | let drawable = &self.proto_tiles[proto_id].drawable; 244 | debug!("\tUsing orientation {:?} and proto id {}", orientation, proto_id); 245 | let rotation = { 246 | use cgmath::Rotation; 247 | let relative: cgmath::Quaternion<_> = cgmath::Rotation3::from_axis_angle( 248 | &cgmath::Vector3::new(0.0, 0.0, -1.0), 249 | cgmath::deg(orientation.to_degrees()).into(), 250 | ); 251 | relative.concat(&world.get_node(drawable.node).world.rot) 252 | }; 253 | let (rot_x, rot_y) = match orientation { 254 | Direction::North => (0, 0), 255 | Direction::East => (0, 1), 256 | Direction::South => (1, 1), 257 | Direction::West => (1, 0), 258 | }; 259 | let node = world.add_node( 260 | format!("Tile ({}, {})", x, y), 261 | claymore_scene::space::Parent::None, 262 | cgmath::Decomposed { 263 | scale: 1.0, 264 | rot: rotation, 265 | disp: cgmath::Vector3::new( 266 | (x + rot_x) as f32 * self.tile_size, 267 | (y + rot_y) as f32 * self.tile_size, 268 | 0.0, 269 | ), 270 | }); 271 | claymore_scene::Entity { 272 | name: String::new(), 273 | visible: true, 274 | mesh: drawable.mesh.clone(), 275 | node: node, 276 | skeleton: None, 277 | bound: drawable.bound.clone(), 278 | fragments: drawable.fragments.clone(), 279 | } 280 | } 281 | 282 | fn make_prop(&self, base_node: claymore_scene::NodeId, 283 | drawable: &Drawable, position: (f32, f32), z: f32, 284 | world: &mut claymore_scene::World) 285 | -> claymore_scene::Entity { 286 | use cgmath::{Aabb, Point, Rotation, Transform}; 287 | let rotation = world.get_node(drawable.node) 288 | .local.rot.clone(); 289 | let mut bound_center = rotation.rotate_point(&drawable.bound.center()); 290 | bound_center.z = 0.0; 291 | let offset = cgmath::Point3::new( 292 | position.0 * self.tile_size, 293 | z, 294 | -position.1 * self.tile_size, 295 | ); 296 | let translation = world.get_node(base_node) 297 | .local.transform_point(&offset); 298 | debug!("Found spot {:?}, ended up at pos {:?}", position, translation); 299 | let node = world.add_node( 300 | String::new(), 301 | claymore_scene::space::Parent::None, 302 | cgmath::Decomposed { 303 | scale: 1.0, 304 | rot: rotation, 305 | disp: translation.sub_p(&bound_center), 306 | }); 307 | claymore_scene::Entity { 308 | name: String::new(), 309 | visible: true, 310 | mesh: drawable.mesh.clone(), 311 | node: node, 312 | skeleton: None, 313 | bound: drawable.bound.clone(), 314 | fragments: drawable.fragments.clone(), 315 | } 316 | } 317 | 318 | pub fn populate(&self, model: &reflect::Model, 319 | scene: &mut claymore_scene::Scene) { 320 | use std::collections::HashMap; 321 | type Position = (i32, i32); 322 | scene.entities.clear(); 323 | println!("Generating content..."); 324 | let mut rng = rand::thread_rng(); 325 | let mut tile_map: HashMap = HashMap::new(); 326 | for y in -model.grid_size.0 ..model.grid_size.0 { 327 | for x in -model.grid_size.1 ..model.grid_size.1 { 328 | use rand::Rng; 329 | if tile_map.contains_key(&(x,y)) { 330 | continue 331 | } 332 | debug!("Generating tile {:?}", (x,y)); 333 | // figure out what neighbour edges are rivers 334 | let mut river_mask = SET_EMPTY; 335 | let mut neighbour_mask = SET_EMPTY; 336 | for dir in ALL_DIRECTIONS { 337 | let offset = dir.to_vector(); 338 | let pos = (x + offset[0], y + offset[1]); 339 | if let Some(tile) = tile_map.get(&pos) { 340 | neighbour_mask = neighbour_mask | *dir; 341 | let river_bit = dir.aligned_as(Direction::South, tile.orientation); 342 | debug!("\tChecking for river dir {:?} of neighbor dir {:?}", river_bit, dir); 343 | let proto = &self.proto_tiles[tile.proto_id]; 344 | if proto.river_mask.has(river_bit) { 345 | river_mask = river_mask | *dir; 346 | } 347 | } 348 | } 349 | debug!("\tLooking for river mask {:?} of neighbors {:?}", river_mask, neighbour_mask); 350 | // find a matching prototype 351 | let mut matched = 0; 352 | for proto in self.proto_tiles.iter() { 353 | for dir in ALL_DIRECTIONS { 354 | if proto.fit_orientation(*dir, neighbour_mask, river_mask) { 355 | matched += 1; 356 | } 357 | } 358 | } 359 | if matched == 0 { 360 | error!("Couldn't find a tile match for {:?}, where neighbors = {:?}, rivers = {:?}", 361 | (x, y), neighbour_mask, river_mask); 362 | continue 363 | } 364 | let chosen = rng.gen_range(0, matched); 365 | debug!("\tChosen match {} of total {}", chosen, matched); 366 | matched = 0; 367 | 'proto: for (id, proto) in self.proto_tiles.iter().enumerate() { 368 | for dir in ALL_DIRECTIONS { 369 | if !proto.fit_orientation(*dir, neighbour_mask, river_mask) { 370 | continue; 371 | } 372 | if matched < chosen { 373 | matched += 1; 374 | continue 375 | } 376 | let entity = self.make_tile(x, y, id, *dir, &mut scene.world); 377 | tile_map.insert((x, y), Tile { 378 | proto_id: id, 379 | orientation: *dir, 380 | node: entity.node.clone(), 381 | }); 382 | scene.entities.push(entity); 383 | break 'proto; 384 | } 385 | } 386 | } 387 | } 388 | // place props 389 | for (&(x, y), tile) in tile_map.iter() { 390 | use rand::Rng; 391 | let river_mask = self.proto_tiles[tile.proto_id].river_mask; 392 | // water plants 393 | if river_mask != SET_EMPTY && rng.next_f32() < model.water_plant_chance { 394 | let plant_type = rng.gen_range(0, self.water_plants.len()); 395 | debug!("Generating water plant type {} on tile ({}, {}) with mask {:?}", 396 | plant_type, x, y, river_mask); 397 | let spots = self.get_water_spots(river_mask); 398 | let position = spots[rng.gen_range(0, spots.len())]; 399 | let entity = self.make_prop(tile.node, &self.water_plants[plant_type], 400 | position, model.water_height, &mut scene.world); 401 | scene.entities.push(entity); 402 | } 403 | // tents 404 | let mut has_tent = false; 405 | if river_mask == SET_EMPTY && rng.next_f32() < model.tent_chance { 406 | let tent_type = rng.gen_range(0, self.tents.len()); 407 | let tent_entity = self.make_prop(tile.node, &self.tents[tent_type], 408 | (0.5, 0.5), model.ground_height, &mut scene.world); 409 | scene.entities.push(tent_entity); 410 | let fire_type = rng.gen_range(0, self.camp_fires.len()); 411 | let fire_entity = self.make_prop(tile.node, &self.camp_fires[fire_type], 412 | (0.5, 1.1), model.ground_height, &mut scene.world); 413 | scene.entities.push(fire_entity); 414 | debug!("Generated tent type {} with fire type {} on tile ({}, {})", 415 | tent_type, fire_type, x, y); 416 | has_tent = true; 417 | } 418 | // plants 419 | let mut spots = self.get_grass_spots(river_mask, has_tent); 420 | let max_plants = if river_mask != SET_EMPTY || has_tent { 421 | model.max_river_plants 422 | } else { 423 | model.max_grass_plants 424 | }; 425 | for _ in 0.. max_plants { 426 | if spots.is_empty() || rng.next_f32() >= model.plant_chance { 427 | continue 428 | } 429 | let plant_type = rng.gen_range(0, self.plants.len()); 430 | debug!("Generating plant type {} on tile ({}, {}) with mask {:?}", 431 | plant_type, x, y, river_mask); 432 | let spot_id = rng.gen_range(0, spots.len()); 433 | let position = spots.swap_remove(spot_id); 434 | let entity = self.make_prop(tile.node, &self.plants[plant_type], 435 | position, model.ground_height, &mut scene.world); 436 | scene.entities.push(entity); 437 | } 438 | } 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /examples/forest/reflect.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(RustcDecodable)] 4 | pub struct Demo { 5 | pub name: String, 6 | pub generate: bool, 7 | pub control: Control, 8 | pub debug: Debug, 9 | pub palette: Palette, 10 | } 11 | 12 | #[derive(RustcDecodable)] 13 | pub struct Control { 14 | pub move_speed: f32, 15 | pub rotate_speed: f32, 16 | } 17 | 18 | #[derive(RustcDecodable)] 19 | pub struct Debug { 20 | pub offset: (i32, i32), 21 | pub line_jump: i32, 22 | pub color: (f32, f32, f32, f32), 23 | pub time_factor: u64, 24 | } 25 | 26 | #[derive(RustcDecodable)] 27 | pub struct Palette { 28 | pub scene: String, 29 | pub size: f32, 30 | pub model: Model, 31 | pub tiles: HashMap, 32 | pub water_plants: Vec, 33 | pub plants: Vec, 34 | pub tents: Vec, 35 | pub camp_fires: Vec, 36 | } 37 | 38 | #[derive(RustcDecodable)] 39 | pub struct Model { 40 | pub grid_size: (i32, i32), 41 | pub water_plant_chance: f32, 42 | pub plant_chance: f32, 43 | pub max_grass_plants: u8, 44 | pub max_river_plants: u8, 45 | pub tent_chance: f32, 46 | pub water_height: f32, 47 | pub ground_height: f32, 48 | } 49 | -------------------------------------------------------------------------------- /examples/viewer/README.md: -------------------------------------------------------------------------------- 1 | Viewer application allows you to load freshly exported scenes and wonder around. 2 | 3 | Controls: 4 | - left mouse hold and move: rotate camera 5 | - middle mouse hold and move: move camera 6 | - scroll: zoom camera 7 | - `Esc`: exit game 8 | -------------------------------------------------------------------------------- /examples/viewer/bin.rs: -------------------------------------------------------------------------------- 1 | extern crate env_logger; 2 | extern crate cgmath; 3 | extern crate glutin; 4 | extern crate gfx; 5 | extern crate gfx_pipeline; 6 | extern crate gfx_window_glutin; 7 | extern crate gfx_text; 8 | extern crate gfx_debug_draw; 9 | extern crate claymore_load; 10 | extern crate claymore_scene; 11 | 12 | mod control; 13 | 14 | fn main() { 15 | use std::env; 16 | use cgmath::{vec3, FixedArray, Matrix, Transform}; 17 | use gfx::traits::*; 18 | use gfx_pipeline::Pipeline; 19 | 20 | env_logger::init().unwrap(); 21 | println!("Creating the window..."); 22 | 23 | let window = glutin::WindowBuilder::new() 24 | .with_title("Scene viewer".to_string()) 25 | .with_vsync() 26 | .with_gl(glutin::GL_CORE) 27 | .build().unwrap(); 28 | let (mut stream, mut device, mut factory) = gfx_window_glutin::init(window); 29 | 30 | let text_renderer = gfx_text::new(factory.clone()).unwrap(); 31 | let mut debug_renderer = gfx_debug_draw::DebugRenderer::new( 32 | factory.clone(), text_renderer, 64).unwrap(); 33 | 34 | let mut scene = claymore_scene::Scene::new(); 35 | { 36 | let mut context = claymore_load::Context::new(&mut factory, 37 | env::var("CARGO_MANIFEST_DIR").unwrap_or(".".to_string())); 38 | context.alpha_test = Some(20); 39 | context.forgive = true; 40 | for path in env::args().skip(1) { 41 | println!("Loading scene: {}", path); 42 | context.extend_scene(&mut scene, &path).unwrap(); 43 | } 44 | } 45 | 46 | println!("Initializing the graphics..."); 47 | let mut pipeline = gfx_pipeline::forward::Pipeline::new(&mut factory) 48 | .unwrap(); 49 | pipeline.background = Some([0.2, 0.3, 0.4, 1.0]); 50 | 51 | let mut camera = match scene.cameras.first() { 52 | Some(cam) => cam.clone(), 53 | None => { 54 | println!("No cameras found in any of the scenes. Usage:"); 55 | println!("viewer ..."); 56 | return; 57 | } 58 | }; 59 | let mut control = { 60 | let target_node = scene.entities[0].node; 61 | control::Control::new(0.005, 0.01, 0.5, 62 | scene.world.get_node(target_node).world.clone()) 63 | }; 64 | 65 | println!("Rendering..."); 66 | 'main: loop { 67 | for event in stream.out.window.poll_events() { 68 | use glutin::{Event, ElementState, MouseButton, VirtualKeyCode}; 69 | match event { 70 | Event::KeyboardInput(_, _, Some(VirtualKeyCode::Escape)) => 71 | break 'main, 72 | Event::Closed => break 'main, 73 | Event::MouseInput(ElementState::Pressed, MouseButton::Left) => 74 | control.rot_capture(&scene.world.get_node(camera.node).local), 75 | Event::MouseInput(ElementState::Released, MouseButton::Left) => 76 | control.rot_release(), 77 | Event::MouseInput(ElementState::Pressed, MouseButton::Middle) => 78 | control.move_capture(&scene.world.get_node(camera.node).local), 79 | Event::MouseInput(ElementState::Released, MouseButton::Middle) => 80 | control.move_release(), 81 | Event::MouseMoved(coords) => 82 | control.position(coords, &mut scene.world.mut_node(camera.node).local), 83 | Event::MouseWheel(_, shift) => 84 | control.wheel(shift, &mut scene.world.mut_node(camera.node).local), 85 | _ => {}, 86 | } 87 | } 88 | 89 | scene.world.update(); 90 | let len = 0.1f32; 91 | 92 | for node in scene.world.iter_nodes() { 93 | let r = node.world.transform_as_point(&vec3(0.0, 0.0, 0.0)).into_fixed(); 94 | let x = node.world.transform_as_point(&vec3(len, 0.0, 0.0)).into_fixed(); 95 | let y = node.world.transform_as_point(&vec3(0.0, len, 0.0)).into_fixed(); 96 | let z = node.world.transform_as_point(&vec3(0.0, 0.0, len)).into_fixed(); 97 | debug_renderer.draw_line(r, x, [1.0, 0.0, 0.0, 0.5]); 98 | debug_renderer.draw_line(r, y, [0.0, 1.0, 0.0, 0.5]); 99 | debug_renderer.draw_line(r, z, [0.0, 0.0, 1.0, 0.5]); 100 | } 101 | 102 | camera.projection.aspect = stream.get_aspect_ratio(); 103 | pipeline.render(&scene, &camera, &mut stream).unwrap(); 104 | 105 | { 106 | use cgmath::FixedArray; 107 | //use claymore_scene::base::Camera; 108 | //let proj_mx = camera.get_view_projection(&scene.world).into_fixed(); //TODO 109 | let cam_inv = scene.world.get_node(camera.node).world.invert().unwrap(); 110 | let temp: cgmath::Matrix4 = camera.projection.clone().into(); 111 | let proj_mx = temp.mul_m(&cam_inv.into()).into_fixed(); 112 | debug_renderer.render(&mut stream, proj_mx).unwrap(); 113 | } 114 | 115 | stream.present(&mut device); 116 | } 117 | println!("Done."); 118 | } 119 | -------------------------------------------------------------------------------- /examples/viewer/control.rs: -------------------------------------------------------------------------------- 1 | use cgmath; 2 | use claymore_scene::Transform; 3 | 4 | 5 | pub type MousePos = (i32, i32); 6 | 7 | pub struct Control { 8 | rotate_speed: f32, 9 | move_speed: f32, 10 | zoom_speed: f32, 11 | rotate_base: Option<(MousePos, Transform)>, 12 | move_base: Option<(MousePos, cgmath::Vector3)>, 13 | last_pos: MousePos, 14 | space: Transform, 15 | } 16 | 17 | impl Control { 18 | pub fn new(rot_speed: f32, move_speed: f32, zoom_speed: f32, 19 | space: Transform) -> Control { 20 | Control { 21 | rotate_speed: rot_speed, 22 | move_speed: move_speed, 23 | zoom_speed: zoom_speed, 24 | rotate_base: None, 25 | move_base: None, 26 | last_pos: (0, 0), 27 | space: space, 28 | } 29 | } 30 | 31 | pub fn rot_capture(&mut self, transform: &Transform) { 32 | self.rotate_base = Some((self.last_pos, transform.clone())); 33 | } 34 | 35 | pub fn rot_release(&mut self) { 36 | self.rotate_base = None; 37 | } 38 | 39 | pub fn move_capture(&mut self, transform: &Transform) { 40 | self.move_base = Some((self.last_pos, transform.disp)); 41 | } 42 | 43 | pub fn move_release(&mut self) { 44 | self.move_base = None; 45 | } 46 | 47 | pub fn position(&mut self, coords: MousePos, 48 | transform: &mut Transform) { 49 | self.last_pos = coords; 50 | match self.rotate_base { 51 | Some((ref base_pos, ref base_transform)) => { 52 | use cgmath::Transform; 53 | // p' = Mp * Tc^ * (Tr * Rz * Tr^) * p 54 | // Tx = (Tr * Rz^ * Tr^) * Tc 55 | let path = (coords.0 - base_pos.0) as f32 * -self.rotate_speed; 56 | let rotation = cgmath::Decomposed { 57 | scale: 1.0, 58 | rot: cgmath::Rotation3::from_axis_angle( 59 | &cgmath::vec3(0.0, 0.0, 1.0), cgmath::rad(path)), 60 | disp: cgmath::zero(), 61 | }; 62 | let space_inv = self.space.invert().unwrap(); 63 | let relative = self.space.concat(&rotation.concat(&space_inv)); 64 | *transform = relative.concat(base_transform); 65 | }, 66 | None => (), 67 | } 68 | match self.move_base { 69 | Some((base_pos, ref base_disp)) => { 70 | use cgmath::{Vector, Rotation}; 71 | let local_vector = cgmath::vec3( 72 | -(coords.0 - base_pos.0) as f32, 73 | (coords.1 - base_pos.1) as f32, 74 | 0.0).mul_s(self.move_speed); 75 | let cam_vector = transform.rot.rotate_vector(&local_vector); 76 | transform.disp = base_disp.add_v(&cam_vector); 77 | }, 78 | None => (), 79 | } 80 | } 81 | 82 | pub fn wheel(&mut self, shift: f64, transform: &mut Transform) { 83 | use cgmath::{Vector, Transform}; 84 | let vector = transform.transform_vector(&cgmath::vec3(0.0, 0.0, 1.0)); 85 | transform.disp.add_self_v(&vector.mul_s(shift as f32 * -self.zoom_speed)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/game/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "claymore-game" 3 | version = "0.0.2" 4 | authors = [ 5 | "Dzmitry Malyshau ", 6 | ] 7 | 8 | [lib] 9 | name = "claymore_game" 10 | path = "lib.rs" 11 | 12 | [dependencies] 13 | log = "*" 14 | rustc-serialize = "*" 15 | cgmath = "*" 16 | gfx = "0.6" 17 | gfx_pipeline = "0.2" 18 | 19 | [dependencies.claymore-scene] 20 | path = "../scene" 21 | 22 | [dependencies.claymore-load] 23 | path = "../load" 24 | 25 | [dependencies.grid] 26 | git = "https://github.com/kvark/grid-rs" 27 | -------------------------------------------------------------------------------- /src/game/field.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use cgmath; 3 | use cgmath::FixedArray; 4 | use gfx; 5 | use grid; 6 | use grid::Grid; 7 | use scene; 8 | 9 | gfx_parameters!( Param { 10 | u_Transform@ mvp: [[f32; 4]; 4], 11 | u_Color@ color: [f32; 4], 12 | }); 13 | 14 | gfx_vertex!( Vertex { 15 | a_Position@ position: [f32; 2], 16 | }); 17 | 18 | impl Vertex { 19 | pub fn new(x: f32, y: f32) -> Vertex { 20 | Vertex { 21 | position: [x, y], 22 | } 23 | } 24 | } 25 | 26 | static VERTEX_SRC: &'static [u8] = b" 27 | #version 150 core 28 | 29 | uniform mat4 u_Transform; 30 | in vec2 a_Position; 31 | 32 | void main() { 33 | gl_Position = u_Transform * vec4(a_Position, 0.0, 1.0); 34 | } 35 | "; 36 | 37 | static FRAGMENT_SRC: &'static [u8] = b" 38 | #version 150 core 39 | 40 | uniform vec4 u_Color; 41 | out vec4 o_Color; 42 | 43 | void main() { 44 | o_Color = u_Color; 45 | } 46 | "; 47 | 48 | pub use grid::quad::{Coordinate, Direction}; 49 | 50 | pub struct Field { 51 | pub node: scene::NodeId, 52 | pub grid: grid::quad::Grid, 53 | batch: gfx::batch::Full>, 54 | } 55 | 56 | impl Field { 57 | pub fn new>(factory: &mut F, 58 | node: scene::NodeId, size: f32, area: f32, 59 | color: (f32, f32, f32, f32)) 60 | -> Field { 61 | use gfx::traits::FactoryExt; 62 | let grid = grid::quad::Grid::new(size); 63 | let area = [[-area, -area, 0.0], [area, area, 0.0]]; 64 | let vertices = grid.fold_edges_in_area(&area, Vec::new(), |mut u, a, b, _, _| { 65 | u.push(Vertex::new(a[0], a[1])); 66 | u.push(Vertex::new(b[0], b[1])); 67 | u 68 | }); 69 | let mesh = factory.create_mesh(&vertices); 70 | let program = factory.link_program(VERTEX_SRC, FRAGMENT_SRC).unwrap(); 71 | let mut batch = gfx::batch::Full::new(mesh, program, Param { 72 | mvp: [[0.0; 4]; 4], 73 | color: [color.0, color.1, color.2, color.3], 74 | _r: PhantomData, 75 | }).unwrap(); 76 | batch.state = batch.state.depth(gfx::state::Comparison::LessEqual, false); 77 | batch.slice.prim_type = gfx::PrimitiveType::Line; 78 | Field { 79 | node: node, 80 | grid: grid, 81 | batch: batch, 82 | } 83 | } 84 | 85 | pub fn get_center(&self, coord: Coordinate) -> cgmath::Point3 { 86 | let fixed = self.grid.get_cell_center(coord); 87 | cgmath::Point3::new(fixed[0], fixed[1], fixed[2]) 88 | } 89 | 90 | pub fn get_cell(&self, position: &cgmath::Point3) -> Coordinate { 91 | self.grid.get_coordinate(position.as_fixed()) 92 | } 93 | 94 | pub fn cast_ray(&self, ray: &cgmath::Ray3) -> Coordinate { 95 | use cgmath::{Point, Vector}; 96 | let t = -ray.origin.z / ray.direction.z; 97 | let p = ray.origin.add_v(&ray.direction.mul_s(t)); 98 | self.get_cell(&p) 99 | } 100 | 101 | pub fn update_params(&mut self, camera: &scene::Camera, world: &scene::World) { 102 | use cgmath::{Matrix, Transform}; 103 | let mx_proj: cgmath::Matrix4 = camera.projection.clone().into(); 104 | let model_view = world.get_node(camera.node).world.invert().unwrap() 105 | .concat(&world.get_node(self.node).world); 106 | self.batch.params.mvp = mx_proj.mul_m(&model_view.into()).into_fixed(); 107 | } 108 | 109 | pub fn draw>(&self, stream: &mut S) { 110 | stream.draw(&self.batch).unwrap(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/game/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate rustc_serialize; 2 | #[macro_use] 3 | extern crate log; 4 | extern crate grid; 5 | extern crate cgmath; 6 | #[macro_use] 7 | extern crate gfx; 8 | extern crate gfx_pipeline; 9 | extern crate claymore_scene as scene; 10 | extern crate claymore_load as load; 11 | 12 | use std::fs::File; 13 | use rustc_serialize::json; 14 | use gfx_pipeline::forward::Pipeline; 15 | 16 | mod field; 17 | mod reflect; 18 | 19 | 20 | fn convert_dir(d: reflect::Direction) -> field::Direction { 21 | use reflect::Direction as A; 22 | use field::Direction as B; 23 | match d { 24 | A::North => B::North, 25 | A::East => B::East, 26 | A::South => B::South, 27 | A::West => B::West, 28 | } 29 | } 30 | 31 | struct Character { 32 | _name: String, 33 | team: u8, 34 | cell: field::Coordinate, 35 | node: scene::NodeId, 36 | } 37 | 38 | pub struct App { 39 | scene: scene::Scene, 40 | camera: scene::Camera, 41 | pipeline: Pipeline, 42 | field: field::Field, 43 | characters: Vec, 44 | } 45 | 46 | impl App { 47 | pub fn new>(factory: &mut F) -> App { 48 | use std::env; 49 | use std::io::Read; 50 | let root = env::var("CARGO_MANIFEST_DIR").unwrap_or(".".to_string()); 51 | let mut scene = scene::Scene::new(); 52 | // load the config 53 | let config: reflect::Game = { 54 | let mut file = File::open(&format!("{}/config/game.json", root)).unwrap(); 55 | let mut s = String::new(); 56 | file.read_to_string(&mut s).unwrap(); 57 | json::decode(&s).unwrap() 58 | }; 59 | // create the grid 60 | let field_node = scene.world.add_node( 61 | "Field".to_string(), 62 | scene::space::Parent::None, 63 | cgmath::Transform::identity() 64 | ); 65 | let field = field::Field::new(factory, field_node, 66 | config.level.grid.size, 67 | config.level.grid.area, 68 | config.level.grid.color); 69 | // load the scene 70 | let mut characters = Vec::new(); 71 | { 72 | let mut context = load::Context::new(factory, root); 73 | context.extend_scene(&mut scene, &config.level.scene).unwrap(); 74 | for (name, ch) in config.level.characters.iter() { 75 | let coord = [ch.cell.0 as i32, ch.cell.1 as i32]; 76 | let cur_dir = convert_dir(ch.cell.2); 77 | match config.characters.get(name) { 78 | Some(desc) => { 79 | use grid::Grid2; 80 | use cgmath::Point; 81 | context.alpha_test = Some(desc.alpha_test); 82 | let nid = context.extend_scene(&mut scene, &desc.scene).unwrap(); 83 | let node = scene.world.mut_node(nid); 84 | let angle = field.grid.get_angle(convert_dir(desc.direction), cur_dir); 85 | node.parent = scene::space::Parent::Domestic(field.node); 86 | node.local = cgmath::Decomposed { 87 | rot: cgmath::Rotation3::from_axis_angle( 88 | &cgmath::Vector3::new(0.0, 0.0, -1.0), 89 | cgmath::deg(angle * 180.0).into() 90 | ), 91 | scale: ch.scale, 92 | disp: field.get_center(coord).to_vec(), 93 | }; 94 | characters.push(Character { 95 | _name: name.clone(), 96 | team: ch.team, 97 | cell: coord, 98 | node: nid, 99 | }); 100 | }, 101 | None => { 102 | error!("Unable to find character: {}", name); 103 | }, 104 | } 105 | } 106 | }; 107 | // create the pipeline 108 | let mut pipeline = Pipeline::new(factory).unwrap(); 109 | pipeline.background = Some([0.2, 0.3, 0.4, 1.0]); 110 | // done 111 | let camera = scene.cameras[0].clone(); 112 | App { 113 | scene: scene, 114 | camera: camera, 115 | pipeline: pipeline, 116 | field: field, 117 | characters: characters, 118 | } 119 | } 120 | 121 | fn mouse_cast(&self, x: f32, y: f32) -> field::Coordinate { 122 | use cgmath::{EuclideanVector, Matrix, Point, Transform}; 123 | let end_proj = cgmath::Point3::new(x*2.0 - 1.0, 1.0 - y*2.0, 0.0); 124 | let mx_proj: cgmath::Matrix4 = self.camera.projection.clone().into(); 125 | let inv_proj = mx_proj.invert().unwrap(); 126 | let end_cam = cgmath::Point3::from_homogeneous( 127 | &inv_proj.mul_v(&end_proj.to_homogeneous()) 128 | ); 129 | let ray = cgmath::Ray3::new(cgmath::Point3::new(0.0, 0.0, 0.0), 130 | end_cam.to_vec().normalize()); 131 | let transform = self.scene.world.get_node(self.field.node).world 132 | .invert().unwrap() 133 | .concat(&self.scene.world.get_node(self.camera.node).world); 134 | let ray_grid = transform.transform_ray(&ray); 135 | self.field.cast_ray(&ray_grid) 136 | } 137 | 138 | pub fn mouse_click(&mut self, x: f32, y: f32) { 139 | use std::collections::HashMap; 140 | let cell = self.mouse_cast(x, y); 141 | info!("[click] on {:?}", cell); 142 | let mut cell_map = HashMap::new(); 143 | for ch in self.characters.iter() { 144 | cell_map.insert(ch.cell, ch.team); 145 | } 146 | let player = match self.characters.iter_mut().find(|c| c.team == 0) { 147 | Some(p) => p, 148 | None => { 149 | info!("[click] no playable character"); 150 | return 151 | }, 152 | }; 153 | match cell_map.get(&cell) { 154 | Some(team) if *team == player.team => { 155 | info!("[click] aid ally"); 156 | }, 157 | Some(_team) => { 158 | info!("[click] attack"); 159 | }, 160 | None => { //move 161 | use cgmath::Point; 162 | info!("[click] move"); 163 | player.cell = cell; 164 | let node = self.scene.world.mut_node(player.node); 165 | node.local.disp = self.field.get_center(cell).to_vec(); 166 | }, 167 | } 168 | } 169 | 170 | pub fn rotate_camera(&mut self, degrees: f32) { 171 | use cgmath::Transform; 172 | let rotation = cgmath::Decomposed { 173 | scale: 1.0, 174 | rot: cgmath::Rotation3::from_axis_angle( 175 | &cgmath::vec3(0.0, 0.0, 1.0), 176 | cgmath::deg(degrees).into() 177 | ), 178 | disp: cgmath::zero(), 179 | }; 180 | let transform = &mut self.scene.world.mut_node(self.camera.node).local; 181 | *transform = rotation.concat(transform); 182 | } 183 | 184 | pub fn render>(&mut self, stream: &mut S) { 185 | use gfx_pipeline::Pipeline; 186 | self.scene.world.update(); 187 | self.camera.projection.aspect = stream.get_aspect_ratio(); 188 | self.pipeline.render(&self.scene, &self.camera, stream).unwrap(); 189 | self.field.update_params(&self.camera, &self.scene.world); 190 | self.field.draw(stream); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/game/reflect.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | #[derive(Clone, Copy, Debug, RustcDecodable)] 4 | pub enum Direction { 5 | North, 6 | East, 7 | South, 8 | West, 9 | } 10 | 11 | pub type WorldVector = (f32, f32, f32); 12 | pub type CellInfo = (i8, i8, Direction); 13 | pub type Color = (f32, f32, f32, f32); 14 | 15 | #[derive(RustcDecodable)] 16 | pub struct Game { 17 | pub name: String, 18 | pub characters: HashMap, 19 | pub level: Level, 20 | } 21 | 22 | #[derive(RustcDecodable)] 23 | pub struct GameChar { 24 | pub scene: String, 25 | pub alpha_test: u8, 26 | pub direction: Direction, 27 | pub health: u32, 28 | } 29 | 30 | #[derive(RustcDecodable)] 31 | pub struct Level { 32 | pub scene: String, 33 | pub grid: Grid, 34 | pub characters: HashMap, 35 | } 36 | 37 | #[derive(RustcDecodable)] 38 | pub struct Grid { 39 | pub center: WorldVector, 40 | pub size: f32, 41 | pub area: f32, 42 | pub color: Color, 43 | } 44 | 45 | #[derive(RustcDecodable)] 46 | pub struct LevelChar { 47 | pub team: u8, 48 | pub cell: CellInfo, 49 | pub scale: f32, 50 | } 51 | -------------------------------------------------------------------------------- /src/load/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "claymore-load" 3 | version = "0.1.1" 4 | authors = [ 5 | "Dzmitry Malyshau ", 6 | ] 7 | 8 | [lib] 9 | name = "claymore_load" 10 | path = "lib.rs" 11 | 12 | [dependencies] 13 | log = "*" 14 | rustc-serialize = "*" 15 | cgmath = "*" 16 | gfx = "0.6" 17 | 18 | [dependencies.piston-gfx_texture] 19 | gfx_texture = "0.1" 20 | 21 | [dependencies.claymore-scene] 22 | path = "../scene" 23 | -------------------------------------------------------------------------------- /src/load/aux.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | pub trait ReadExt { 4 | fn read_u8(&mut self) -> Result; 5 | fn read_u32(&mut self) -> Result; 6 | } 7 | 8 | impl ReadExt for R { 9 | fn read_u8(&mut self) -> Result { 10 | let mut res = [0u8; 1]; 11 | self.read(&mut res).map(|_| res[0]) 12 | } 13 | 14 | fn read_u32(&mut self) -> Result { 15 | let mut buf = [0u8; 4]; 16 | self.read(&mut buf).map(|_| { 17 | ((buf[0] as u32) << 0) | ((buf[1] as u32) << 8) | 18 | ((buf[2] as u32) << 16) | ((buf[3] as u32) << 24) 19 | }) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/load/chunk.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::fmt; 3 | use std::ops::{Deref, DerefMut}; 4 | use ::aux::ReadExt; 5 | 6 | static NAME_LENGTH: u32 = 8; 7 | 8 | pub struct Root { 9 | pub name: String, 10 | input: R, 11 | buffer: Vec, 12 | position: u32, 13 | } 14 | 15 | impl Root { 16 | pub fn tell(&mut self) -> u32 { 17 | self.input.seek(io::SeekFrom::Current(0)).unwrap() as u32 18 | } 19 | } 20 | 21 | impl Root { 22 | pub fn new(name: String, input: R) -> Root { 23 | Root { 24 | name: name, 25 | input: input, 26 | buffer: Vec::new(), 27 | position: 0, 28 | } 29 | } 30 | 31 | pub fn get_pos(&self) -> u32 { 32 | self.position 33 | } 34 | 35 | fn skip(&mut self, num: u32) { 36 | self.read_bytes(num); 37 | } 38 | 39 | pub fn read_bytes(&mut self, num: u32) -> &[u8] { 40 | self.position += num; 41 | self.buffer.clear(); 42 | for _ in (0.. num) { 43 | let b = self.input.read_u8().unwrap(); 44 | self.buffer.push(b); 45 | } 46 | &self.buffer 47 | } 48 | 49 | pub fn read_u8(&mut self) -> u8 { 50 | self.position += 1; 51 | self.input.read_u8().unwrap() 52 | } 53 | 54 | pub fn read_u32(&mut self) -> u32 { 55 | self.position += 4; 56 | self.input.read_u32().unwrap() 57 | } 58 | 59 | pub fn read_bool(&mut self) -> bool { 60 | self.position += 1; 61 | self.input.read_u8().unwrap() != 0 62 | } 63 | 64 | pub fn read_str(&mut self) -> &str { 65 | use std::str::from_utf8; 66 | let size = self.input.read_u8().unwrap() as u32; 67 | self.position += 1; 68 | let buf = self.read_bytes(size); 69 | from_utf8(buf).unwrap() 70 | } 71 | 72 | pub fn enter<'b>(&'b mut self) -> Chunk<'b, R> { 73 | let name = { 74 | let raw = self.read_bytes(NAME_LENGTH); 75 | let buf = match raw.iter().position(|b| *b == 0) { 76 | Some(p) => &raw[..p], 77 | None => raw, 78 | }; 79 | String::from_utf8_lossy(buf) 80 | .into_owned() 81 | }; 82 | debug!("Entering chunk {}", name); 83 | let size = self.read_u32(); 84 | Chunk { 85 | name: name, 86 | size: size, 87 | end_pos: self.position + size, 88 | root: self, 89 | } 90 | } 91 | } 92 | 93 | pub struct Chunk<'a, R: io::Read + 'a> { 94 | name: String, 95 | size: u32, 96 | end_pos: u32, 97 | root: &'a mut Root, 98 | } 99 | 100 | impl<'a, R: io::Read> fmt::Display for Chunk<'a, R> { 101 | fn fmt(&self, fm: &mut fmt::Formatter) -> Result<(), fmt::Error> { 102 | write!(fm, "Chunk({}, {} left)", self.name, self.size) 103 | } 104 | } 105 | 106 | impl<'a, R: io::Read> Chunk<'a, R> { 107 | pub fn get_name(&self) -> &str { 108 | &self.name 109 | } 110 | 111 | pub fn has_more(&self)-> bool { 112 | self.root.get_pos() < self.end_pos 113 | } 114 | 115 | pub fn ignore(self) { 116 | let left = self.end_pos - self.root.get_pos(); 117 | self.root.skip(left) 118 | } 119 | } 120 | 121 | impl<'a, R: io::Read> Drop for Chunk<'a, R> { 122 | fn drop(&mut self) { 123 | debug!("Leaving chunk"); 124 | assert!(!self.has_more()) 125 | } 126 | } 127 | 128 | impl<'a, R: io::Read> Deref for Chunk<'a, R> { 129 | type Target = Root; 130 | fn deref(&self) -> &Root { 131 | self.root 132 | } 133 | } 134 | 135 | impl<'a, R: io::Read> DerefMut for Chunk<'a, R> { 136 | fn deref_mut(&mut self) -> &mut Root { 137 | self.root 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/load/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | extern crate rustc_serialize; 4 | extern crate cgmath; 5 | extern crate gfx; 6 | extern crate gfx_texture; 7 | extern crate claymore_scene; 8 | 9 | mod aux; 10 | pub mod chunk; 11 | mod mesh; 12 | mod mat; 13 | mod program; 14 | mod reflect; 15 | mod scene; 16 | 17 | use std::collections::hash_map::{HashMap, Entry}; 18 | use std::io; 19 | use std::fs::File; 20 | use rustc_serialize::json; 21 | use claymore_scene as cs; 22 | 23 | pub use self::scene::Scalar; 24 | 25 | 26 | pub static PREFIX_ATTRIB : &'static str = "a_"; 27 | pub static PREFIX_UNIFORM: &'static str = "u_"; 28 | pub static PREFIX_TEXTURE: &'static str = "t_"; 29 | 30 | pub type TextureError = String; 31 | 32 | pub struct Cache { 33 | meshes: HashMap>, 34 | textures: HashMap, TextureError>>, 35 | programs: HashMap, program::Error>>, 36 | } 37 | 38 | impl Cache { 39 | pub fn new() -> Cache { 40 | Cache { 41 | meshes: HashMap::new(), 42 | textures: HashMap::new(), 43 | programs: HashMap::new(), 44 | } 45 | } 46 | } 47 | 48 | pub struct Context<'a, R: 'a + gfx::Resources, F: 'a + gfx::Factory> { 49 | pub cache: Cache, 50 | pub factory: &'a mut F, 51 | pub base_path: String, 52 | prefix: String, 53 | pub alpha_test: Option, 54 | pub flip_textures: bool, 55 | pub forgive: bool, 56 | } 57 | 58 | impl<'a, R: gfx::Resources, F: gfx::Factory> Context<'a, R, F> { 59 | pub fn new(factory: &'a mut F, path: String) -> Context<'a, R, F> { 60 | Context { 61 | cache: Cache::new(), 62 | factory: factory, 63 | base_path: path, 64 | prefix: String::new(), 65 | alpha_test: None, 66 | flip_textures: true, // following Blender 67 | forgive: false, // panic out 68 | } 69 | } 70 | 71 | fn read_mesh_collection(&mut self, path_str: &str) -> Result<(), mesh::Error> { 72 | info!("Loading mesh collection from {}", path_str); 73 | let path = format!("{}/{}.k3mesh", self.prefix, path_str); 74 | match File::open(path) { 75 | Ok(file) => { 76 | let size = file.metadata().unwrap().len() as u32; 77 | let mut reader = chunk::Root::new(path_str.to_string(), file); 78 | while reader.get_pos() < size { 79 | debug_assert_eq!(reader.tell(), reader.get_pos()); 80 | debug!("Current position {}/{}", reader.get_pos(), size); 81 | let (name, success) = try!(mesh::load(&mut reader, self.factory)); 82 | let full_name = format!("{}@{}", name, path_str); 83 | self.cache.meshes.insert(full_name, success); 84 | } 85 | Ok(()) 86 | }, 87 | Err(e) => Err(mesh::Error::Path(e)), 88 | } 89 | } 90 | 91 | pub fn request_mesh(&mut self, path: &str) 92 | -> Result, mesh::Error> { 93 | match self.cache.meshes.get(path) { 94 | Some(m) => return Ok(m.clone()), 95 | None => (), 96 | } 97 | let mut split = path.split('@'); 98 | let _name = split.next().unwrap(); 99 | match split.next() { 100 | Some(container) => { 101 | try!(self.read_mesh_collection(container)); 102 | match self.cache.meshes.get(path) { 103 | Some(m) => Ok(m.clone()), 104 | None => Err(mesh::Error::NameNotInCollection), 105 | } 106 | }, 107 | None => Err(mesh::Error::Other), 108 | } 109 | } 110 | 111 | pub fn request_texture(&mut self, path_str: &str, srgb: bool) 112 | -> Result, TextureError> { 113 | match self.cache.textures.entry(path_str.to_string()) { 114 | Entry::Occupied(v) => v.get().clone(), 115 | Entry::Vacant(v) => { 116 | info!("Loading texture from {}", path_str); 117 | let path = format!("{}{}", self.prefix, path_str); 118 | let mut settings = gfx_texture::Settings::new(); 119 | settings.flip_vertical = true; 120 | settings.convert_gamma = srgb; 121 | settings.generate_mipmap = true; 122 | let tex_result = gfx_texture::Texture::from_path( 123 | self.factory, path, &settings); 124 | let tex = match tex_result { 125 | Ok(t) => Ok(t.handle()), 126 | Err(e) => { 127 | if self.forgive { 128 | error!("Texture failed to load: {:?}", e); 129 | } 130 | Err(e) 131 | }, 132 | }; 133 | v.insert(tex).clone() 134 | }, 135 | } 136 | } 137 | 138 | pub fn request_program(&mut self, name: &str) 139 | -> Result, program::Error> { 140 | match self.cache.programs.entry(name.to_string()) { 141 | Entry::Occupied(v) => v.get().clone(), 142 | Entry::Vacant(v) => { 143 | info!("Loading program {}", name); 144 | let prog_maybe = program::load(name, self.factory); 145 | v.insert(prog_maybe).clone() 146 | }, 147 | } 148 | } 149 | } 150 | 151 | #[derive(Debug)] 152 | pub enum SceneError { 153 | Open(io::Error), 154 | Read(io::Error), 155 | Decode(json::DecoderError), 156 | Parse(scene::Error), 157 | } 158 | 159 | impl<'a, R: gfx::Resources, F: gfx::Factory> Context<'a, R, F> { 160 | pub fn load_scene_into(&mut self, scene: &mut cs::Scene, 161 | global_parent: cs::Parent, 162 | path_str: &str) -> Result<(), SceneError> 163 | { 164 | use std::io::Read; 165 | info!("Loading scene from {}", path_str); 166 | self.prefix = format!("{}/{}", self.base_path, path_str); 167 | let path = format!("{}.json", self.prefix); 168 | match File::open(&path) { 169 | Ok(mut file) => { 170 | let mut s = String::new(); 171 | match file.read_to_string(&mut s) { 172 | Ok(_) => match json::decode(&s) { 173 | Ok(raw) => match scene::load_into(scene, global_parent, raw, self) { 174 | Ok(s) => Ok(s), 175 | Err(e) => Err(SceneError::Parse(e)), 176 | }, 177 | Err(e) => Err(SceneError::Decode(e)), 178 | }, 179 | Err(e) => Err(SceneError::Read(e)), 180 | } 181 | }, 182 | Err(e) => Err(SceneError::Open(e)), 183 | } 184 | } 185 | 186 | pub fn load_scene(&mut self, path_str: &str) 187 | -> Result, SceneError> 188 | { 189 | let mut scene = cs::Scene::new(); 190 | match self.load_scene_into(&mut scene, cs::space::Parent::None, path_str) { 191 | Ok(()) => Ok(scene), 192 | Err(e) => Err(e), 193 | } 194 | } 195 | 196 | pub fn extend_scene(&mut self, scene: &mut cs::Scene, path_str: &str) 197 | -> Result, SceneError> 198 | { 199 | let nid = scene.world.add_node( 200 | path_str.to_string(), 201 | cs::space::Parent::None, 202 | cgmath::Transform::identity() 203 | ); 204 | self.load_scene_into(scene, cs::space::Parent::Domestic(nid), path_str) 205 | .map(|_| nid) 206 | } 207 | } 208 | 209 | pub fn load_mesh<'a, R: gfx::Resources, F: gfx::Factory>(path_str: &str, factory: &mut F) 210 | -> Result<(String, mesh::Success), mesh::Error> { 211 | info!("Loading mesh from {}", path_str); 212 | let path = format!("{}.k3mesh", path_str); 213 | match File::open(&path) { 214 | Ok(file) => { 215 | let mut reader = chunk::Root::new(path, file); 216 | mesh::load(&mut reader, factory) 217 | }, 218 | Err(e) => Err(mesh::Error::Path(e)), 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/load/mat.rs: -------------------------------------------------------------------------------- 1 | use gfx; 2 | use claymore_scene::{Material, Transparency}; 3 | use super::reflect; 4 | 5 | #[derive(Debug)] 6 | pub enum Error { 7 | NotFound, 8 | //Program(String), 9 | Texture(String, super::TextureError), 10 | SamplerFilter(String, u8), 11 | SamplerWrap(i8), 12 | } 13 | 14 | pub fn load>(mat: &reflect::Material, 15 | context: &mut super::Context) -> Result, Error> { 16 | let mut out = Material { 17 | color: [1.0, 1.0, 1.0, 1.0], 18 | texture: None, 19 | transparency: match (mat.transparent, context.alpha_test) { 20 | (true, Some(v)) => Transparency::Cutout(v), 21 | (true, None) => Transparency::Blend(gfx::BlendPreset::Alpha), 22 | (false, _) => Transparency::Opaque, 23 | }, 24 | }; 25 | if let Some(ref rt) = mat.textures.first() { 26 | let space = match rt.image.space.as_ref() { 27 | "Linear" => false, 28 | "sRGB" => true, 29 | other => { 30 | warn!("Unknown color space: {}", other); 31 | false 32 | } 33 | }; 34 | match context.request_texture(&rt.image.path, space) { 35 | Ok(t) => { 36 | fn unwrap(mode: i8) -> Result { 37 | match mode { 38 | -1 => Ok(gfx::tex::WrapMode::Mirror), 39 | 0 => Ok(gfx::tex::WrapMode::Clamp), 40 | 1 => Ok(gfx::tex::WrapMode::Tile), 41 | _ => Err(Error::SamplerWrap(mode)), 42 | } 43 | } 44 | let (wx, wy, wz) = ( 45 | try!(unwrap(rt.wrap.0)), 46 | try!(unwrap(rt.wrap.1)), 47 | try!(unwrap(rt.wrap.2)), 48 | ); 49 | let filter = match rt.filter { 50 | 1 => gfx::tex::FilterMethod::Scale, 51 | 2 => gfx::tex::FilterMethod::Bilinear, 52 | 3 => gfx::tex::FilterMethod::Trilinear, 53 | other => return Err(Error::SamplerFilter(rt.name.clone(), other)), 54 | }; 55 | let mut sinfo = gfx::tex::SamplerInfo::new(filter, wx); 56 | sinfo.wrap_mode.1 = wy; 57 | sinfo.wrap_mode.2 = wz; 58 | let sampler = context.factory.create_sampler(sinfo); 59 | out.texture = Some((t, Some(sampler))); 60 | }, 61 | Err(_) if context.forgive => (), //already errored in request_texture() 62 | Err(e) => return Err(Error::Texture(rt.image.path.clone(), e)), 63 | } 64 | }; 65 | if let Some(&(_, ref vec)) = mat.data.get("DiffuseColor") { 66 | out.color = [vec[0], vec[1], vec[2], 1.0]; 67 | } 68 | Ok(out) 69 | } 70 | -------------------------------------------------------------------------------- /src/load/mesh.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use gfx; 3 | use ::aux::ReadExt; 4 | use ::chunk::Root; 5 | 6 | pub type Success = (gfx::Mesh, gfx::Slice); 7 | 8 | /// Parse type character to gfx attribute type 9 | /// using Python packing notation: 10 | /// https://docs.python.org/2/library/struct.html#format-characters 11 | fn parse_type(type_: char, normalized: u8) -> Result { 12 | use gfx::attrib::Type::*; 13 | use gfx::attrib::IntSubType::*; 14 | use gfx::attrib::IntSize::*; 15 | use gfx::attrib::SignFlag::*; 16 | use gfx::attrib::FloatSubType::*; 17 | use gfx::attrib::FloatSize::*; 18 | Ok(match (type_, normalized) { 19 | ('b', 0) => Int(Raw, U8, Signed), 20 | ('B', 0) => Int(Raw, U8, Unsigned), 21 | ('b', 1) => Int(Normalized, U8, Signed), 22 | ('B', 1) => Int(Normalized, U8, Unsigned), 23 | ('h', 0) => Int(Raw, U16, Signed), 24 | ('H', 0) => Int(Raw, U16, Unsigned), 25 | ('h', 1) => Int(Normalized, U16, Signed), 26 | ('H', 1) => Int(Normalized, U16, Unsigned), 27 | ('l', 0) => Int(Raw, U32, Signed), 28 | ('L', 0) => Int(Raw, U32, Unsigned), 29 | ('l', 1) => Int(Normalized, U32, Signed), 30 | ('L', 1) => Int(Normalized, U32, Unsigned), 31 | ('f', 0) => Float(Default, F32), 32 | ('d', 0) => Float(Precision, F64), 33 | _ => return Err(()), 34 | }) 35 | } 36 | 37 | #[derive(Debug)] 38 | pub enum Error { 39 | Path(io::Error), 40 | NameNotInCollection, 41 | Chunk(String), 42 | Signature(String), 43 | Topology(String), 44 | DoubleIndex, 45 | AttribType(char, u8), 46 | IndexType(char), 47 | Stride(u8), 48 | Other, 49 | } 50 | 51 | pub fn load>( 52 | reader: &mut Root, factory: &mut F) 53 | -> Result<(String, Success), Error> { 54 | use gfx::PrimitiveType; 55 | let mut cmesh = reader.enter(); 56 | if cmesh.get_name() != "mesh" { 57 | return Err(Error::Signature(cmesh.get_name().to_string())) 58 | } 59 | let mesh_name = cmesh.read_str().to_string(); 60 | let n_vert = cmesh.read_u32(); 61 | info!("\tname: {}, vertices: {}", mesh_name, n_vert); 62 | let mut slice = gfx::Slice { 63 | start: 0, 64 | end: n_vert, 65 | prim_type: match cmesh.read_str() { 66 | "1" => PrimitiveType::Point, 67 | "2" => PrimitiveType::Line, 68 | "2s"=> PrimitiveType::LineStrip, 69 | "3" => PrimitiveType::TriangleList, 70 | "3s"=> PrimitiveType::TriangleStrip, 71 | "3f"=> PrimitiveType::TriangleFan, 72 | top => return Err(Error::Topology(top.to_string())), 73 | }, 74 | kind: gfx::SliceKind::Vertex, 75 | }; 76 | let mut mesh = gfx::Mesh::new(n_vert); 77 | while cmesh.has_more() { 78 | let mut cbuf = cmesh.enter(); 79 | match &slice.kind { 80 | _ if cbuf.get_name() == "buffer" => { 81 | let stride = cbuf.read_u8(); 82 | let format_str = cbuf.read_str().to_string(); 83 | debug!("\tBuffer stride: {}, format: {}", stride, format_str); 84 | let buffer = { 85 | let data = cbuf.read_bytes(n_vert * (stride as u32)); 86 | factory.create_buffer_static_raw(data, gfx::BufferRole::Vertex) 87 | }; 88 | let mut offset = 0; 89 | for sub in format_str.as_bytes().chunks(2) { 90 | let el_count = sub[0] - ('0' as u8); 91 | let type_ = sub[1] as char; 92 | let name = cbuf.read_str().to_string(); 93 | let flags = cbuf.read_u8(); 94 | debug!("\t\tname: {}, count: {}, type: {}, flags: {}", 95 | name, el_count, type_, flags); 96 | let normalized = flags & 1; 97 | let el_type = match parse_type(type_, normalized) { 98 | Ok(t) => t, 99 | Err(_) => return Err(Error::AttribType(type_, flags)), 100 | }; 101 | mesh.attributes.push(gfx::Attribute { 102 | name: format!("{}{}", super::PREFIX_ATTRIB, name), 103 | buffer: buffer.clone(), 104 | format: gfx::attrib::Format { 105 | elem_count: el_count, 106 | elem_type: el_type, 107 | offset: offset as gfx::attrib::Offset, 108 | stride: stride as gfx::attrib::Stride, 109 | instance_rate: 0, 110 | }, 111 | }); 112 | offset += el_count * el_type.get_size(); 113 | } 114 | if offset != stride { 115 | return Err(Error::Stride(offset)); 116 | } 117 | }, 118 | &gfx::SliceKind::Vertex if cbuf.get_name() == "index" => { 119 | let n_ind = cbuf.read_u32(); 120 | let format = cbuf.read_u8() as char; 121 | debug!("\tIndex format: {}, count: {}", format, n_ind); 122 | slice.kind = match format { 123 | 'B' => { 124 | let data = cbuf.read_bytes(n_ind * 1); 125 | let buf = factory.create_buffer_static(data, gfx::BufferRole::Index); 126 | gfx::SliceKind::Index8(buf, 0) 127 | }, 128 | 'H' => { 129 | let data = cbuf.read_bytes(n_ind * 2); 130 | let buf = factory.create_buffer_static(data, gfx::BufferRole::Index); 131 | gfx::SliceKind::Index16(buf.cast(), 0) 132 | }, 133 | 'L' => { 134 | let data = cbuf.read_bytes(n_ind * 4); 135 | let buf = factory.create_buffer_static(data, gfx::BufferRole::Index); 136 | gfx::SliceKind::Index32(buf.cast(), 0) 137 | }, 138 | _ => return Err(Error::IndexType(format)), 139 | }; 140 | }, 141 | _ if cbuf.get_name() == "index" => return Err(Error::DoubleIndex), 142 | _ => return Err(Error::Chunk(cbuf.get_name().to_string())), 143 | } 144 | } 145 | Ok((mesh_name, (mesh, slice))) 146 | } 147 | -------------------------------------------------------------------------------- /src/load/program.rs: -------------------------------------------------------------------------------- 1 | //use std::io; 2 | use std::fs::File; 3 | use gfx; 4 | use gfx::traits::*; 5 | 6 | #[derive(Clone, Debug)] 7 | // https://github.com/rust-lang/rust/issues/24135 8 | pub enum Error { 9 | Open(String, String), 10 | Read(String), 11 | Create(gfx::ProgramError), 12 | } 13 | 14 | pub fn load>(name: &str, factory: &mut F) 15 | -> Result, Error> { 16 | use std::io::Read; 17 | // vertex 18 | let mut src_vert = Vec::new(); 19 | let path = format!("shader/{}.glslv", name); 20 | match File::open(&path) { 21 | Ok(mut file) => match file.read_to_end(&mut src_vert) { 22 | Ok(_) => (), 23 | Err(e) => return Err(Error::Read(e.to_string())), 24 | }, 25 | Err(e) => return Err(Error::Open(path, e.to_string())), 26 | } 27 | // fragment 28 | let mut src_frag = Vec::new(); 29 | let path = format!("shader/{}.glslf", name); 30 | match File::open(&path) { 31 | Ok(mut file) => match file.read_to_end(&mut src_frag) { 32 | Ok(_) => (), 33 | Err(e) => return Err(Error::Read(e.to_string())), 34 | }, 35 | Err(e) => return Err(Error::Open(path, e.to_string())), 36 | } 37 | // program 38 | factory.link_program(&src_vert, &src_frag) 39 | .map_err(|e| Error::Create(e)) 40 | } 41 | -------------------------------------------------------------------------------- /src/load/reflect.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | pub type Scalar = f32; 4 | pub type Vector3 = (Scalar, Scalar, Scalar); 5 | 6 | #[derive(RustcDecodable)] 7 | pub struct Scene { 8 | pub global: Global, 9 | pub nodes: Vec, 10 | pub materials: Vec, 11 | pub entities: Vec, 12 | pub cameras: Vec, 13 | pub lights: Vec, 14 | } 15 | 16 | #[derive(RustcDecodable)] 17 | pub struct Global { 18 | pub gravity: (Scalar, Scalar, Scalar), 19 | } 20 | 21 | #[derive(RustcDecodable)] 22 | pub struct Node { 23 | pub name: String, 24 | pub space: Space, 25 | pub children: Vec, 26 | pub actions: Vec, 27 | } 28 | 29 | #[derive(RustcDecodable)] 30 | pub struct Space { 31 | pub pos: (S, S, S), 32 | pub rot: (S, S, S, S), 33 | pub scale: S, 34 | } 35 | 36 | #[derive(RustcDecodable)] 37 | pub struct Entity { 38 | pub node: String, 39 | pub mesh: String, 40 | pub armature: String, 41 | pub bounds: (Vector3, Vector3), 42 | pub fragments: Vec, 43 | pub actions: Vec, 44 | } 45 | 46 | #[derive(RustcDecodable)] 47 | pub struct Fragment { 48 | pub material: String, 49 | pub slice: (u32, u32), 50 | } 51 | 52 | #[derive(RustcDecodable)] 53 | pub struct Light { 54 | pub name: String, 55 | pub node: String, 56 | pub kind: String, 57 | pub color: (Scalar, Scalar, Scalar), 58 | pub energy: Scalar, 59 | pub distance: Scalar, 60 | pub attenuation: (Scalar, Scalar), 61 | pub spherical: bool, 62 | pub parameters: Vec, 63 | pub actions: Vec, 64 | } 65 | 66 | #[derive(RustcDecodable)] 67 | pub struct Camera { 68 | pub name: String, 69 | pub node: String, 70 | pub angle: (Scalar, Scalar), 71 | pub range: (Scalar, Scalar), 72 | pub actions: Vec, 73 | } 74 | 75 | #[derive(RustcDecodable)] 76 | pub struct Material { 77 | pub name: String, 78 | pub shader: String, 79 | pub transparent: bool, 80 | pub data: HashMap, 81 | pub textures: Vec, 82 | } 83 | 84 | pub type Data = (String, Vec); 85 | 86 | #[derive(RustcDecodable)] 87 | pub struct Texture { 88 | pub name: String, 89 | pub image: Image, 90 | pub filter: u8, 91 | pub wrap: (i8, i8, i8), 92 | pub offset: Vector3, 93 | pub scale: Vector3, 94 | } 95 | 96 | #[derive(RustcDecodable)] 97 | pub struct Image { 98 | pub path: String, 99 | pub space: String, 100 | pub mapping: String, 101 | } 102 | 103 | pub type Action = (); //TODO 104 | -------------------------------------------------------------------------------- /src/load/scene.rs: -------------------------------------------------------------------------------- 1 | use cgmath; 2 | use gfx; 3 | use claymore_scene as cs; 4 | use super::reflect as json; 5 | 6 | pub type Scalar = f32; 7 | 8 | #[derive(Debug)] 9 | pub enum Error { 10 | NoCamera, 11 | MissingNode(String), 12 | Mesh(String, super::mesh::Error), 13 | Material(String, super::mat::Error), 14 | } 15 | 16 | pub fn load_into<'a, R: 'a + gfx::Resources, F: 'a + gfx::Factory>( 17 | this: &mut cs::Scene, global_parent: cs::Parent, 18 | raw: json::Scene, context: &mut super::Context<'a, R, F>) 19 | -> Result<(), Error> 20 | { 21 | use std::collections::hash_map::{HashMap, Entry}; 22 | 23 | fn read_space(space: &json::Space) 24 | -> cs::Transform { 25 | cgmath::Decomposed { 26 | scale: space.scale, 27 | rot: { 28 | let (x, y, z, w) = space.rot; 29 | cgmath::Quaternion::new(w, x, y, z).normalize() 30 | }, 31 | disp: { 32 | let (x, y, z) = space.pos; 33 | cgmath::Vector3::new(x, y, z) 34 | }, 35 | } 36 | } 37 | 38 | fn populate_world(world: &mut cs::World, 39 | raw_nodes: &[json::Node], 40 | parent: cs::space::Parent>) { 41 | for n in raw_nodes.iter() { 42 | let space = read_space(&n.space); 43 | let nid = world.add_node(n.name.clone(), parent, space); 44 | populate_world(world, &n.children, cs::space::Parent::Domestic(nid)); 45 | } 46 | } 47 | 48 | // create world 49 | populate_world(&mut this.world, &raw.nodes, global_parent); 50 | // read camera 51 | let camera = { 52 | let cam = match raw.cameras.first() { 53 | Some(c) => c, 54 | None => return Err(Error::NoCamera), 55 | }; 56 | let node = match this.world.find_node(&cam.node) { 57 | Some(n) => n, 58 | None => return Err(Error::MissingNode(cam.node.to_string())), 59 | }; 60 | let (fovx, fovy) = cam.angle; 61 | let (near, far) = cam.range; 62 | let proj = cgmath::PerspectiveFov { 63 | fovy: cgmath::rad(fovy), 64 | aspect: fovx.tan() / fovy.tan(), 65 | near: near, 66 | far: far, 67 | }; 68 | cs::Camera { 69 | name: cam.name.clone(), 70 | node: node, 71 | projection: proj, 72 | } 73 | }; 74 | this.cameras.push(camera); 75 | 76 | // read entities and materials 77 | let mut material_map: HashMap> = HashMap::new(); 78 | for ent in raw.entities.into_iter() { 79 | let node = match this.world.find_node(&ent.node) { 80 | Some(n) => n, 81 | None => return Err(Error::MissingNode(ent.node.clone())), 82 | }; 83 | let (mesh, mut slice) = match context.request_mesh(&ent.mesh) { 84 | Ok(success) => success, 85 | Err(e) => return Err(Error::Mesh(ent.mesh.clone(), e)), 86 | }; 87 | let (vmin, vmax) = ent.bounds; 88 | let mut entity = cs::Entity { 89 | name: ent.node.clone(), 90 | visible: true, 91 | mesh: mesh, 92 | node: node, 93 | skeleton: None, //TODO 94 | bound: cgmath::Aabb3::new( 95 | cgmath::Point3::new(vmin.0, vmin.1, vmin.2), 96 | cgmath::Point3::new(vmax.0, vmax.1, vmax.2), 97 | ), 98 | fragments: Vec::new(), 99 | }; 100 | for frag in ent.fragments.into_iter() { 101 | slice.start = frag.slice.0 as gfx::VertexCount; 102 | slice.end = frag.slice.1 as gfx::VertexCount; 103 | let material = match material_map.entry(frag.material.clone()) { 104 | Entry::Occupied(m) => m.get().clone(), 105 | Entry::Vacant(v) => match raw.materials.iter().find(|r| r.name == frag.material) { 106 | Some(raw_mat) => match super::mat::load(&raw_mat, context) { 107 | Ok(m) => v.insert(m).clone(), 108 | Err(e) => return Err(Error::Material(frag.material, e)), 109 | }, 110 | None => return Err(Error::Material( 111 | frag.material, super::mat::Error::NotFound)), 112 | }, 113 | }; 114 | entity.add_fragment(material, slice.clone()); 115 | } 116 | this.entities.push(entity); 117 | } 118 | // done 119 | Ok(()) 120 | } 121 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate env_logger; 2 | extern crate glutin; 3 | extern crate gfx; 4 | extern crate gfx_window_glutin; 5 | extern crate claymore_game as game; 6 | 7 | pub fn main() { 8 | use gfx::traits::*; 9 | 10 | env_logger::init().unwrap(); 11 | println!("Initializing the window..."); 12 | 13 | let window = glutin::WindowBuilder::new() 14 | .with_title("Claymore".to_string()) 15 | .with_vsync() 16 | .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 2))) 17 | .with_srgb(Some(true)) 18 | .build().unwrap(); 19 | let (mut stream, mut device, mut factory) = gfx_window_glutin::init(window); 20 | let _ = stream.out.set_gamma(gfx::Gamma::Convert); 21 | 22 | println!("Loading the game..."); 23 | let mut app = game::App::new(&mut factory); 24 | 25 | println!("Rendering..."); 26 | let (mut mouse_x, mut mouse_y) = (0, 0); 27 | 'main: loop { 28 | // quit when Esc is pressed. 29 | for event in stream.out.window.poll_events() { 30 | use glutin::{ElementState, Event, MouseButton, VirtualKeyCode}; 31 | match event { 32 | Event::Closed => break 'main, 33 | Event::KeyboardInput(ElementState::Pressed, _, Some(VirtualKeyCode::Escape)) => break 'main, 34 | Event::KeyboardInput(ElementState::Pressed, _, Some(VirtualKeyCode::W)) => 35 | app.rotate_camera(-90.0), 36 | Event::KeyboardInput(ElementState::Pressed, _, Some(VirtualKeyCode::Q)) => 37 | app.rotate_camera(90.0), 38 | Event::MouseMoved((x, y)) => { mouse_x = x; mouse_y = y; }, 39 | Event::MouseInput(ElementState::Pressed, MouseButton::Left) => { 40 | let (sx, sy) = stream.out.get_size(); 41 | app.mouse_click(mouse_x as f32 / sx as f32, mouse_y as f32 / sy as f32); 42 | }, 43 | _ => (), 44 | } 45 | } 46 | 47 | app.render(&mut stream); 48 | stream.present(&mut device); 49 | } 50 | println!("Done."); 51 | } 52 | -------------------------------------------------------------------------------- /src/scene/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "claymore-scene" 3 | version = "0.1.1" 4 | authors = [ 5 | "Dzmitry Malyshau ", 6 | ] 7 | 8 | [lib] 9 | name = "claymore_scene" 10 | path = "lib.rs" 11 | 12 | [dependencies] 13 | log = "*" 14 | cgmath = "*" 15 | gfx = "0.6" 16 | gfx_scene = "0.3" 17 | gfx_pipeline = "0.2" 18 | 19 | [dependencies.id] 20 | git = "https://github.com/kvark/simplecs" 21 | -------------------------------------------------------------------------------- /src/scene/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | extern crate id; 4 | extern crate cgmath; 5 | extern crate gfx; 6 | extern crate gfx_scene; 7 | extern crate gfx_pipeline; 8 | 9 | pub mod space; 10 | pub use gfx_pipeline::{Material, Transparency, ViewInfo}; 11 | pub use gfx_scene as base; 12 | 13 | 14 | pub type Transform = cgmath::Decomposed< 15 | S, 16 | cgmath::Vector3, 17 | cgmath::Quaternion 18 | >; 19 | 20 | pub type Parent = space::Parent>; 21 | pub type World = space::World>; 22 | pub type Node = space::Node>; 23 | pub type NodeId = id::Id>>; 24 | pub type Skeleton = space::Skeleton>; 25 | pub type Projection = cgmath::PerspectiveFov>; 26 | pub type Fragment = gfx_scene::Fragment>; 27 | 28 | pub struct Pair(A, B); 29 | 30 | /// A simple camera with generic projection and spatial relation. 31 | #[derive(Clone)] 32 | pub struct Camera { 33 | /// Name of the camera. 34 | pub name: String, 35 | /// Generic projection. 36 | pub projection: Projection, 37 | /// Generic spatial node. 38 | pub node: NodeId, 39 | } 40 | 41 | impl<'a, S: cgmath::BaseFloat> gfx_scene::Node for Pair<&'a Camera, &'a World> { 42 | type Transform = Transform; 43 | fn get_transform(&self) -> Transform { 44 | self.1.get_node(self.0.node).world.clone() 45 | } 46 | } 47 | 48 | impl<'a, S: cgmath::BaseFloat> gfx_scene::Camera for Pair<&'a Camera, &'a World> { 49 | type Projection = Projection; 50 | fn get_projection(&self) -> Projection { self.0.projection.clone() } 51 | } 52 | 53 | #[derive(Clone)] 54 | pub struct Entity { 55 | pub name: String, 56 | pub visible: bool, 57 | pub mesh: gfx::Mesh, 58 | pub node: NodeId, 59 | pub skeleton: Option>>, 60 | pub bound: cgmath::Aabb3, 61 | pub fragments: Vec>, 62 | } 63 | 64 | impl Entity { 65 | /// Create a minimal new `Entity`. 66 | pub fn new(mesh: gfx::Mesh, node: NodeId, bound: cgmath::Aabb3) 67 | -> Entity 68 | { 69 | Entity { 70 | name: String::new(), 71 | visible: true, 72 | mesh: mesh, 73 | node: node, 74 | skeleton: None, 75 | bound: bound, 76 | fragments: Vec::new(), 77 | } 78 | } 79 | 80 | /// Add another fragment to the list 81 | pub fn add_fragment(&mut self, mat: Material, slice: gfx::Slice) { 82 | self.fragments.push(gfx_scene::Fragment { 83 | material: mat, 84 | slice: slice, 85 | }); 86 | } 87 | } 88 | 89 | impl<'a, 90 | R: gfx::Resources, 91 | S: cgmath::BaseFloat, 92 | > gfx_scene::Node for Pair<&'a Entity, &'a World> { 93 | type Transform = Transform; 94 | fn get_transform(&self) -> Transform { 95 | self.1.get_node(self.0.node).world.clone() 96 | } 97 | } 98 | 99 | impl<'a, 100 | R: gfx::Resources, 101 | S: cgmath::BaseFloat, 102 | > gfx_scene::Entity> for Pair<&'a Entity, &'a World> { 103 | type Bound = cgmath::Aabb3; 104 | fn is_visible(&self) -> bool { self.0.visible } 105 | fn get_bound(&self) -> cgmath::Aabb3 { self.0.bound.clone() } 106 | fn get_mesh(&self) -> &gfx::Mesh { &self.0.mesh } 107 | fn get_fragments(&self) -> &[Fragment] { &self.0.fragments } 108 | } 109 | 110 | /// An example scene type. 111 | pub struct Scene { 112 | pub entities: Vec>, 113 | pub cameras: Vec>, 114 | pub world: World, 115 | } 116 | 117 | impl< 118 | R: gfx::Resources, 119 | S: cgmath::BaseFloat, 120 | > Scene { 121 | /// Create a new empty scene 122 | pub fn new() -> Scene { 123 | Scene { 124 | entities: Vec::new(), 125 | cameras: Vec::new(), 126 | world: space::World::new(), 127 | } 128 | } 129 | } 130 | 131 | impl< 132 | R: gfx::Resources, 133 | S: cgmath::BaseFloat, 134 | > gfx_scene::AbstractScene for Scene { 135 | type ViewInfo = ViewInfo; 136 | type Material = Material; 137 | type Camera = Camera; 138 | type Status = gfx_scene::Report; 139 | 140 | fn draw(&self, phase: &mut H, camera: &Camera, 141 | stream: &mut X) -> Result where 142 | H: gfx_scene::AbstractPhase, ViewInfo>, 143 | X: gfx::Stream, 144 | { 145 | let mut culler = gfx_scene::Frustum::new(); 146 | let entities: Vec<_> = self.entities.iter() //TODO: avoid allocation 147 | .map(|e| Pair(e, &self.world)) 148 | .collect(); 149 | gfx_scene::Context::new(&mut culler, &Pair(camera, &self.world)) 150 | .draw(entities.iter(), phase, stream) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/scene/space.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | use std::slice; 3 | use cgmath::{BaseFloat, Transform, Transform3}; 4 | use id::{Array, Id, Storage}; 5 | 6 | #[derive(Clone, Copy, Debug)] 7 | pub enum Parent { 8 | None, 9 | Domestic(Id>), 10 | Foreign(Id>, Id>), 11 | } 12 | 13 | #[derive(Debug)] 14 | pub struct Node { 15 | pub name : String, 16 | pub parent: Parent, 17 | pub local: T, 18 | pub world: T, 19 | } 20 | 21 | #[derive(Debug)] 22 | pub struct Bone { 23 | pub name : String, 24 | parent: Option>>, 25 | pub local: T, 26 | pub world: T, 27 | bind_pose: T, 28 | bind_pose_root_inverse: T, 29 | } 30 | 31 | #[derive(Debug)] 32 | pub struct Skeleton { 33 | pub name: String, 34 | node: Id>, 35 | bones: Array>, 36 | } 37 | 38 | #[derive(Debug)] 39 | pub struct World { 40 | nodes: Array>, 41 | skeletons: Array>, 42 | phantom: PhantomData, 43 | } 44 | 45 | impl + Clone> World { 46 | pub fn new() -> World { 47 | World { 48 | nodes: Array::new(), 49 | skeletons: Array::new(), 50 | phantom: PhantomData, 51 | } 52 | } 53 | 54 | pub fn get_node(&self, id: Id>) -> &Node { 55 | self.nodes.get(id) 56 | } 57 | 58 | pub fn mut_node(&mut self, id: Id>) -> &mut Node { 59 | self.nodes.get_mut(id) 60 | } 61 | 62 | pub fn find_node(&self, name: &str) -> Option>> { 63 | self.nodes.find_id(|n| n.name == name) 64 | } 65 | 66 | pub fn add_node(&mut self, name: String, parent: Parent, local: T) 67 | -> Id> { 68 | //TODO: check that parent is valid 69 | self.nodes.add(Node { 70 | name: name, 71 | parent: parent, 72 | local: local, 73 | world: Transform::identity(), 74 | }) 75 | } 76 | 77 | pub fn iter_nodes<'a>(&'a self) -> slice::Iter<'a, Node> { 78 | self.nodes.iter() 79 | } 80 | 81 | pub fn update(&mut self) { 82 | let skeletons = &mut self.skeletons; 83 | self.nodes.walk_looking_back(|left, n| { 84 | n.world = match n.parent { 85 | Parent::None => n.local.clone(), 86 | Parent::Domestic(pid) => 87 | left.get(pid) 88 | .world.concat(&n.local), 89 | Parent::Foreign(sid, bid) => 90 | skeletons.get(sid) 91 | .bones.get(bid) 92 | .world.concat(&n.local), 93 | }; 94 | }); 95 | 96 | for s in skeletons.iter_mut() { 97 | let world = &self.nodes.get(s.node).world; 98 | s.bones.walk_looking_back(|left, b| { 99 | let base = match b.parent { 100 | Some(bid) => &left.get(bid).world, 101 | None => world, 102 | }; 103 | b.world = base.concat(&b.local); 104 | }) 105 | } 106 | } 107 | } 108 | --------------------------------------------------------------------------------