From 7410a2ea852945b21ae944357d8daf6df9e45971 Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Thu, 28 Feb 2019 14:12:15 +0800 Subject: [PATCH] Make this repository a submodule of a superbuild repository. And countless changes and updates to the game, including converting gameplay code to ECS-based --- .gitignore | 10 + .gitmodules | 12 - CMakeLists.txt | 46 + COPYING | 1348 ++-- README.md | 82 +- bin/.gitignore | 3 - build/linux32/.gitignore | 3 - build/linux64/.gitignore | 3 - build/win32/.gitignore | 3 - build/win64/.gitignore | 3 - cmake/project.cmake | 1 + data | 1 - dist/.gitignore | 3 - lib/SDL2 | 1 - lib/dirent | 1 - lib/emergent | 1 - src/application.hpp | 429 -- src/configuration.hpp.in | 74 +- src/controls.hpp | 134 - src/debug.cpp | 80 - src/dr_libs/dr_wav.cpp | 2 + src/dr_libs/dr_wav.h | 4377 +++++++++++ src/entity/component-manager.cpp | 81 + src/entity/component-manager.hpp | 116 + .../component-observer.cpp} | 19 +- src/entity/component-observer.hpp | 69 + src/entity/component.hpp | 77 + .../components/animation-component.hpp} | 32 +- src/entity/components/ant-hill-component.cpp | 27 + src/entity/components/ant-hill-component.hpp | 36 + src/entity/components/behavior-component.cpp | 29 + src/entity/components/behavior-component.hpp | 41 + src/entity/components/collision-component.cpp | 30 + .../components/collision-component.hpp} | 30 +- src/entity/components/component-type.hpp | 38 + .../legged-locomotion-component.cpp | 34 + .../legged-locomotion-component.hpp | 45 + src/entity/components/model-component.cpp | 29 + src/entity/components/model-component.hpp | 37 + .../components/sound-source-component.cpp | 28 + .../components/sound-source-component.hpp | 39 + src/entity/components/steering-component.cpp | 27 + src/entity/components/steering-component.hpp | 56 + src/entity/components/tool-component.cpp | 29 + .../components/tool-component.hpp} | 24 +- src/entity/components/transform-component.cpp | 28 + src/entity/components/transform-component.hpp | 38 + src/entity/entity-group-member.hpp | 43 + src/entity/entity-group-observer.hpp | 52 + src/entity/entity-group.cpp | 59 + src/entity/entity-group.hpp | 268 + src/entity/entity-id-pool.cpp | 68 + src/entity/entity-id-pool.hpp | 87 + src/entity/entity-id.hpp | 28 + src/entity/entity-manager.cpp | 69 + src/entity/entity-manager.hpp | 97 + src/entity/entity-template.cpp | 54 + src/entity/entity-template.hpp | 60 + src/entity/system-manager.cpp | 45 + src/entity/system-manager.hpp | 68 + .../system.cpp} | 10 +- .../system.hpp} | 48 +- src/entity/systems/animation-system.cpp | 48 + src/entity/systems/animation-system.hpp | 46 + src/entity/systems/behavior-system.cpp | 181 + src/entity/systems/behavior-system.hpp | 61 + src/entity/systems/collision-system.cpp | 65 + src/entity/systems/collision-system.hpp | 46 + src/entity/systems/locomotion-system.cpp | 107 + src/entity/systems/locomotion-system.hpp | 52 + src/entity/systems/particle-system.cpp | 161 + src/entity/systems/particle-system.hpp | 86 + src/entity/systems/render-system.cpp | 56 + src/entity/systems/render-system.hpp | 55 + src/entity/systems/sound-system.cpp | 119 + src/entity/systems/sound-system.hpp | 56 + src/entity/systems/steering-system.cpp | 96 + src/entity/systems/steering-system.hpp | 53 + src/entity/systems/tool-system.cpp | 96 + src/entity/systems/tool-system.hpp | 66 + src/game.cpp | 1719 +++++ src/game.hpp | 404 + src/game/agent.cpp | 222 - src/game/agent.hpp | 216 - src/game/ant.cpp | 293 - src/game/ant.hpp | 155 - src/game/biome.hpp | 42 - src/game/brush.cpp | 168 + src/game/brush.hpp | 67 + src/{ => game}/camera-rig.cpp | 4 +- src/{ => game}/camera-rig.hpp | 4 +- src/game/colony.cpp | 106 - src/game/colony.hpp | 146 - src/game/curl-noise.cpp | 40 + src/game/curl-noise.hpp | 28 + src/game/forceps.cpp | 396 + src/game/forceps.hpp | 95 + src/game/lens.cpp | 176 + src/game/lens.hpp | 96 + src/game/level.cpp | 149 - src/game/level.hpp | 83 - src/game/navmesh.cpp | 692 -- src/game/navmesh.hpp | 289 - src/game/nest.cpp | 217 - src/game/nest.hpp | 182 - src/game/pheromone-matrix.cpp | 176 - src/game/pheromone-matrix.hpp | 83 - src/game/terrain.cpp | 949 --- src/game/terrain.hpp | 161 - src/game/tool.cpp | 670 +- src/game/tool.hpp | 224 +- src/graphics/clear-render-pass.cpp | 86 + .../clear-render-pass.hpp} | 55 +- src/graphics/final-render-pass.cpp | 142 + src/graphics/final-render-pass.hpp | 52 + src/graphics/lighting-render-pass.cpp | 393 + src/graphics/lighting-render-pass.hpp | 91 + src/graphics/shadow-map-render-pass.cpp | 278 + src/graphics/shadow-map-render-pass.hpp | 74 + src/graphics/silhouette-render-pass.cpp | 154 + src/graphics/silhouette-render-pass.hpp | 57 + src/graphics/sky-render-pass.cpp | 163 + src/graphics/sky-render-pass.hpp | 54 + src/graphics/ui-render-pass.cpp | 140 + src/graphics/ui-render-pass.hpp | 52 + src/graphics/vertex-format.hpp | 33 + src/input.hpp | 333 - src/main.cpp | 60 +- src/material-loader.hpp | 62 - src/mesh.cpp | 402 - src/mesh.hpp | 59 - src/model-loader.hpp | 112 - src/paths.cpp | 95 + src/paths.hpp | 38 + src/render-passes.hpp | 323 - src/resources/csv-table-loader.cpp | 113 + src/resources/csv-table.hpp | 30 + src/resources/entity-template-loader.cpp | 183 + src/resources/image-loader.cpp | 78 + src/resources/image.cpp | 89 + src/resources/image.hpp | 111 + src/{ => resources}/material-loader.cpp | 275 +- src/{ => resources}/model-loader.cpp | 282 +- src/resources/resource-handle.cpp | 25 + src/resources/resource-handle.hpp | 70 + src/resources/resource-loader.hpp | 41 + src/resources/resource-manager.cpp | 57 + src/resources/resource-manager.hpp | 147 + src/resources/shader-loader.cpp | 103 + src/resources/text-file-loader.cpp | 42 + src/resources/text-file.hpp | 29 + src/resources/texture-2d-loader.cpp | 108 + src/resources/texture-cube-loader.cpp | 377 + src/resources/triangle-mesh-loader.cpp | 91 + src/resources/typeface-loader.cpp | 30 + src/settings.hpp | 117 - src/states/game-state.cpp | 485 +- src/states/game-state.hpp | 45 +- src/states/sandbox-state.cpp | 434 ++ src/states/sandbox-state.hpp | 58 + src/states/splash-state.cpp | 161 +- src/states/splash-state.hpp | 28 +- src/states/title-state.cpp | 213 - src/stb/stb_image.cpp | 3 + src/stb/stb_image.h | 6755 +++++++++++++++++ src/stb/stb_image_write.cpp | 3 + src/stb/stb_image_write.h | 1625 ++++ src/triangle-mesh-operations.cpp | 148 + src/triangle-mesh-operations.hpp | 69 + src/ui/menu.cpp | 354 - src/ui/menu.hpp | 198 - src/ui/pie-menu.cpp | 218 - src/ui/pie-menu.hpp | 65 - src/ui/toolbar.cpp | 146 - src/ui/toolbar.hpp | 61 - src/ui/tween.cpp | 390 - src/ui/tween.hpp | 379 - src/ui/ui.cpp | 117 +- src/ui/ui.hpp | 126 +- src/windows/antkeeper.manifest | 14 +- 180 files changed, 25375 insertions(+), 10840 deletions(-) mode change 100644 => 100755 .gitignore delete mode 100644 .gitmodules mode change 100644 => 100755 COPYING delete mode 100644 bin/.gitignore delete mode 100644 build/linux32/.gitignore delete mode 100644 build/linux64/.gitignore delete mode 100644 build/win32/.gitignore delete mode 100644 build/win64/.gitignore create mode 100644 cmake/project.cmake delete mode 160000 data delete mode 100644 dist/.gitignore delete mode 160000 lib/SDL2 delete mode 160000 lib/dirent delete mode 160000 lib/emergent delete mode 100644 src/application.hpp delete mode 100644 src/controls.hpp delete mode 100644 src/debug.cpp create mode 100644 src/dr_libs/dr_wav.cpp create mode 100644 src/dr_libs/dr_wav.h create mode 100644 src/entity/component-manager.cpp create mode 100644 src/entity/component-manager.hpp rename src/{game/habitat.cpp => entity/component-observer.cpp} (64%) create mode 100644 src/entity/component-observer.hpp create mode 100644 src/entity/component.hpp rename src/{game/habitat.hpp => entity/components/animation-component.hpp} (61%) create mode 100644 src/entity/components/ant-hill-component.cpp create mode 100644 src/entity/components/ant-hill-component.hpp create mode 100644 src/entity/components/behavior-component.cpp create mode 100644 src/entity/components/behavior-component.hpp create mode 100644 src/entity/components/collision-component.cpp rename src/{states/title-state.hpp => entity/components/collision-component.hpp} (62%) create mode 100644 src/entity/components/component-type.hpp create mode 100644 src/entity/components/legged-locomotion-component.cpp create mode 100644 src/entity/components/legged-locomotion-component.hpp create mode 100644 src/entity/components/model-component.cpp create mode 100644 src/entity/components/model-component.hpp create mode 100644 src/entity/components/sound-source-component.cpp create mode 100644 src/entity/components/sound-source-component.hpp create mode 100644 src/entity/components/steering-component.cpp create mode 100644 src/entity/components/steering-component.hpp create mode 100644 src/entity/components/tool-component.cpp rename src/{states/loading-state.hpp => entity/components/tool-component.hpp} (66%) create mode 100644 src/entity/components/transform-component.cpp create mode 100644 src/entity/components/transform-component.hpp create mode 100644 src/entity/entity-group-member.hpp create mode 100644 src/entity/entity-group-observer.hpp create mode 100644 src/entity/entity-group.cpp create mode 100644 src/entity/entity-group.hpp create mode 100644 src/entity/entity-id-pool.cpp create mode 100644 src/entity/entity-id-pool.hpp create mode 100644 src/entity/entity-id.hpp create mode 100644 src/entity/entity-manager.cpp create mode 100644 src/entity/entity-manager.hpp create mode 100644 src/entity/entity-template.cpp create mode 100644 src/entity/entity-template.hpp create mode 100644 src/entity/system-manager.cpp create mode 100644 src/entity/system-manager.hpp rename src/{application-state.cpp => entity/system.cpp} (78%) rename src/{application-state.hpp => entity/system.hpp} (54%) create mode 100644 src/entity/systems/animation-system.cpp create mode 100644 src/entity/systems/animation-system.hpp create mode 100644 src/entity/systems/behavior-system.cpp create mode 100644 src/entity/systems/behavior-system.hpp create mode 100644 src/entity/systems/collision-system.cpp create mode 100644 src/entity/systems/collision-system.hpp create mode 100644 src/entity/systems/locomotion-system.cpp create mode 100644 src/entity/systems/locomotion-system.hpp create mode 100755 src/entity/systems/particle-system.cpp create mode 100755 src/entity/systems/particle-system.hpp create mode 100644 src/entity/systems/render-system.cpp create mode 100644 src/entity/systems/render-system.hpp create mode 100644 src/entity/systems/sound-system.cpp create mode 100644 src/entity/systems/sound-system.hpp create mode 100644 src/entity/systems/steering-system.cpp create mode 100644 src/entity/systems/steering-system.hpp create mode 100644 src/entity/systems/tool-system.cpp create mode 100644 src/entity/systems/tool-system.hpp create mode 100644 src/game.cpp create mode 100644 src/game.hpp delete mode 100644 src/game/agent.cpp delete mode 100644 src/game/agent.hpp delete mode 100644 src/game/ant.cpp delete mode 100644 src/game/ant.hpp delete mode 100644 src/game/biome.hpp create mode 100755 src/game/brush.cpp create mode 100755 src/game/brush.hpp rename src/{ => game}/camera-rig.cpp (97%) mode change 100644 => 100755 rename src/{ => game}/camera-rig.hpp (98%) mode change 100644 => 100755 delete mode 100644 src/game/colony.cpp delete mode 100644 src/game/colony.hpp create mode 100755 src/game/curl-noise.cpp create mode 100755 src/game/curl-noise.hpp create mode 100755 src/game/forceps.cpp create mode 100755 src/game/forceps.hpp create mode 100755 src/game/lens.cpp create mode 100755 src/game/lens.hpp delete mode 100644 src/game/level.cpp delete mode 100644 src/game/level.hpp delete mode 100644 src/game/navmesh.cpp delete mode 100644 src/game/navmesh.hpp delete mode 100644 src/game/nest.cpp delete mode 100644 src/game/nest.hpp delete mode 100644 src/game/pheromone-matrix.cpp delete mode 100644 src/game/pheromone-matrix.hpp delete mode 100644 src/game/terrain.cpp delete mode 100644 src/game/terrain.hpp mode change 100644 => 100755 src/game/tool.cpp mode change 100644 => 100755 src/game/tool.hpp create mode 100755 src/graphics/clear-render-pass.cpp rename src/{debug.hpp => graphics/clear-render-pass.hpp} (54%) mode change 100644 => 100755 create mode 100755 src/graphics/final-render-pass.cpp create mode 100755 src/graphics/final-render-pass.hpp create mode 100755 src/graphics/lighting-render-pass.cpp create mode 100755 src/graphics/lighting-render-pass.hpp create mode 100755 src/graphics/shadow-map-render-pass.cpp create mode 100755 src/graphics/shadow-map-render-pass.hpp create mode 100755 src/graphics/silhouette-render-pass.cpp create mode 100755 src/graphics/silhouette-render-pass.hpp create mode 100755 src/graphics/sky-render-pass.cpp create mode 100755 src/graphics/sky-render-pass.hpp create mode 100755 src/graphics/ui-render-pass.cpp create mode 100755 src/graphics/ui-render-pass.hpp create mode 100644 src/graphics/vertex-format.hpp delete mode 100644 src/input.hpp mode change 100644 => 100755 src/main.cpp delete mode 100644 src/material-loader.hpp delete mode 100644 src/mesh.cpp delete mode 100644 src/mesh.hpp delete mode 100644 src/model-loader.hpp create mode 100644 src/paths.cpp create mode 100644 src/paths.hpp delete mode 100644 src/render-passes.hpp create mode 100644 src/resources/csv-table-loader.cpp create mode 100644 src/resources/csv-table.hpp create mode 100644 src/resources/entity-template-loader.cpp create mode 100644 src/resources/image-loader.cpp create mode 100644 src/resources/image.cpp create mode 100644 src/resources/image.hpp rename src/{ => resources}/material-loader.cpp (67%) mode change 100644 => 100755 rename src/{ => resources}/model-loader.cpp (64%) mode change 100644 => 100755 create mode 100644 src/resources/resource-handle.cpp create mode 100644 src/resources/resource-handle.hpp create mode 100644 src/resources/resource-loader.hpp create mode 100644 src/resources/resource-manager.cpp create mode 100644 src/resources/resource-manager.hpp create mode 100644 src/resources/shader-loader.cpp create mode 100644 src/resources/text-file-loader.cpp create mode 100644 src/resources/text-file.hpp create mode 100644 src/resources/texture-2d-loader.cpp create mode 100644 src/resources/texture-cube-loader.cpp create mode 100644 src/resources/triangle-mesh-loader.cpp create mode 100644 src/resources/typeface-loader.cpp delete mode 100644 src/settings.hpp mode change 100644 => 100755 src/states/game-state.cpp mode change 100644 => 100755 src/states/game-state.hpp create mode 100755 src/states/sandbox-state.cpp create mode 100755 src/states/sandbox-state.hpp mode change 100644 => 100755 src/states/splash-state.cpp mode change 100644 => 100755 src/states/splash-state.hpp delete mode 100644 src/states/title-state.cpp create mode 100644 src/stb/stb_image.cpp create mode 100644 src/stb/stb_image.h create mode 100644 src/stb/stb_image_write.cpp create mode 100644 src/stb/stb_image_write.h create mode 100644 src/triangle-mesh-operations.cpp create mode 100644 src/triangle-mesh-operations.hpp delete mode 100644 src/ui/menu.cpp delete mode 100644 src/ui/menu.hpp delete mode 100644 src/ui/pie-menu.cpp delete mode 100644 src/ui/pie-menu.hpp delete mode 100644 src/ui/toolbar.cpp delete mode 100644 src/ui/toolbar.hpp delete mode 100644 src/ui/tween.cpp delete mode 100644 src/ui/tween.hpp mode change 100644 => 100755 src/ui/ui.cpp mode change 100644 => 100755 src/ui/ui.hpp mode change 100644 => 100755 src/windows/antkeeper.manifest diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 63cc3b0..ad0e957 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,12 @@ +<<<<<<< HEAD +CMakeFiles +cmake_install.cmake +CMakeCache.txt +Makefile +bin +dist +tags +======= CMakeFiles CMakeCache.txt cmake_install.cmake @@ -6,3 +15,4 @@ src/configuration.hpp .DS_Store *.swo *.swp +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036 diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 59b7e65..0000000 --- a/.gitmodules +++ /dev/null @@ -1,12 +0,0 @@ -[submodule "emergent"] - path = lib/emergent - url = https://github.com/cjhoward/emergent.git -[submodule "lib/SDL2"] - path = lib/SDL2 - url = https://github.com/cjhoward/SDL2.git -[submodule "data"] - path = data - url = https://gitlab.com/cjhoward/antkeeper-data.git -[submodule "lib/dirent"] - path = lib/dirent - url = https://github.com/cjhoward/dirent.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e3f1a9..1702dd7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,3 +1,48 @@ +<<<<<<< HEAD +cmake_minimum_required(VERSION 3.7) + +# Set compiler flags +set(CMAKE_CXX_FLAGS "-Wall -Wextra") +set(CMAKE_CXX_FLAGS_DEBUG "-g -O3 -DDEBUG") +set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") + +# Include project macro +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/project.cmake) + +# Find dependency packages +find_package(emergent REQUIRED CONFIG) +find_package(OpenAL REQUIRED CONFIG) + +# Determine dependencies +set(STATIC_LIBS + emergent + OpenAL::OpenAL) + +# Generate configuration header file +configure_file(${PROJECT_SOURCE_DIR}/src/configuration.hpp.in + ${PROJECT_BINARY_DIR}/src/configuration.hpp) + +# Collect source files +file(GLOB_RECURSE SOURCE_FILES + ${PROJECT_SOURCE_DIR}/src/*.cpp) + +# Add executable target +set(EXECUTABLE_TARGET ${PROJECT_NAME}-executable) +add_executable(${EXECUTABLE_TARGET} ${SOURCE_FILES}) +set_target_properties(${EXECUTABLE_TARGET} PROPERTIES OUTPUT_NAME ${PROJECT_NAME}) + +# Set include directories +target_include_directories(${EXECUTABLE_TARGET} + PUBLIC + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_BINARY_DIR}/src) + +# Link to dependencies +target_link_libraries(${EXECUTABLE_TARGET} ${STATIC_LIBS}) + +# Install executable +install(TARGETS ${EXECUTABLE_TARGET} DESTINATION bin) +======= cmake_minimum_required(VERSION 3.7) project(antkeeper VERSION "0.0.0" @@ -372,3 +417,4 @@ elseif(${PLATFORM} STREQUAL "linux32" OR ${PLATFORM} STREQUAL "linux64") endif() include(CPack) +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036 diff --git a/COPYING b/COPYING old mode 100644 new mode 100755 index 94a9ed0..818433e --- a/COPYING +++ b/COPYING @@ -1,674 +1,674 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index 3f56dd1..301db6f 100644 --- a/README.md +++ b/README.md @@ -1,64 +1,18 @@ -# Antkeeper - -Antkeeper is an ant colony simulation game. This repository contains all of the source code to Antkeeper. The game data, however, is proprietary and resides in a closed-source Git submodule. - -## Download - -Use Git to download the `antkeeper` repository and its submodules: - - git clone --recursive https://github.com/cjhoward/antkeeper.git antkeeper - -## Configuration & Building - -CMake is required to configure and build the application. Depending on the target build platform, CMake should be invoked from one of the following directories: - - build/linux32 // 32-bit GNU/Linux application - build/linux64 // 64-bit GNU/Linux application - build/win32 // 32-bit Windows application - build/win64 // 64-bit Windows application - -The following arguments may be passed to CMake during configuration: - - -DCMAKE_BUILD_TYPE // [Debug, Release] - -DLANGUAGE // [en-us, zh-cn] - -DSTANDALONE // [OFF, ON] - -### GNU/Linux - -Building on GNU/Linux requires CMake, GCC, G++, and GNU Make. Open a terminal in the project root directory and run the following commands: - - cd build/linux64 - cmake ../.. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=... - cmake --build . - -### Windows - -Building on Windows requires CMake and Visual Studio 2017. Additionally, [NSIS](http://nsis.sourceforge.net/) is required if you want to build a distributable installer program. In order to correctly build for your target architecture, you must use the `x86 Native Tools Command Prompt` or the `x64 Native Tools Command Prompt` for 32-bit and 64-bit applications, respectively. Then navigate to the project root directory and run the following commands: - - cd build\win64 - cmake ..\.. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=... - cmake --build . - -## Testing - -After building, a standalone version of the application will be located somewhere in the `bin` directory according to the build type, build platform, and version string. This application can be executed with the following command: - - cmake --build . --target run - -## Distribution - -The built application can be packaged into a distributable format with the following command: - - cmake --build . --target package - -The resulting package will be located in the `dist` directory. - -## Contributing - -If any changes have been made to the submodules, commit those first. Each submodule can then be updated to their latest commits with the following command: - - git submodule update --recursive --remote - -## License - -The source code for Antkeeper is licensed under the GNU General Public License, version 3. See [COPYING](./COPYING) for details. +# Antkeeper Source · [![GitHub license](https://img.shields.io/github/license/cjhoward/antkeeper.svg)](https://github.com/cjhoward/antkeeper/blob/master/COPYING) [![GitHub release](https://img.shields.io/github/release/cjhoward/antkeeper.svg)](https://github.com/cjhoward/antkeeper/releases/) + +Antkeeper is an ant colony simulation game for Windows, Mac OS X, and GNU/Linux. This repository contains all of the source code to Antkeeper. The game data, however, is proprietary. You can access the game data by purchasing a copy of Antkeeper at [https://antkeeper.com/](). + +## License + +The source code for Antkeeper is licensed under the GNU General Public License, version 3. See [`COPYING`](./COPYING) for details. + +### 3rd-Party Software + +| Name | Author(s) | License | Files | +| :--------------- | :----------- | :-------------------------- | :---- | +| dr_wav | David Reid | Public Domain | [`dr_wav.h`](./src/dr_libs/dr_wav.h) | +| Emergent | C. J. Howard | GNU GPL v3.0 | | +| OpenAL soft | | GNU GPL v2.0 | | +| stb_image | Sean Barrett | Public Domain / MIT License | [`stb_image.h`](./src/stb/stb_image.h) | +| stb_image_writer | Sean Barrett | Public Domain / MIT License | [`stb_image_writer.h`](./src/stb/stb_image_writer.h) | + diff --git a/bin/.gitignore b/bin/.gitignore deleted file mode 100644 index bf99ac3..0000000 --- a/bin/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -*/* -!.gitignore \ No newline at end of file diff --git a/build/linux32/.gitignore b/build/linux32/.gitignore deleted file mode 100644 index bf99ac3..0000000 --- a/build/linux32/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -*/* -!.gitignore \ No newline at end of file diff --git a/build/linux64/.gitignore b/build/linux64/.gitignore deleted file mode 100644 index bf99ac3..0000000 --- a/build/linux64/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -*/* -!.gitignore \ No newline at end of file diff --git a/build/win32/.gitignore b/build/win32/.gitignore deleted file mode 100644 index bf99ac3..0000000 --- a/build/win32/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -*/* -!.gitignore \ No newline at end of file diff --git a/build/win64/.gitignore b/build/win64/.gitignore deleted file mode 100644 index bf99ac3..0000000 --- a/build/win64/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -*/* -!.gitignore \ No newline at end of file diff --git a/cmake/project.cmake b/cmake/project.cmake new file mode 100644 index 0000000..a6632b2 --- /dev/null +++ b/cmake/project.cmake @@ -0,0 +1 @@ +project(antkeeper VERSION "0.0.0") diff --git a/data b/data deleted file mode 160000 index 0ed0baf..0000000 --- a/data +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0ed0baf703fdc72c524d8c779c3ae2d92402d041 diff --git a/dist/.gitignore b/dist/.gitignore deleted file mode 100644 index bf99ac3..0000000 --- a/dist/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -* -*/* -!.gitignore \ No newline at end of file diff --git a/lib/SDL2 b/lib/SDL2 deleted file mode 160000 index 6f6d0ab..0000000 --- a/lib/SDL2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6f6d0abad18a8a1bb8131d2676a4ed6e606b1ccf diff --git a/lib/dirent b/lib/dirent deleted file mode 160000 index c652395..0000000 --- a/lib/dirent +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c652395f771a76796d2308a938cf6ce371c10d4a diff --git a/lib/emergent b/lib/emergent deleted file mode 160000 index 16405a4..0000000 --- a/lib/emergent +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 16405a4181a39cff79b57b4c9c2389c31a136794 diff --git a/src/application.hpp b/src/application.hpp deleted file mode 100644 index cbf4594..0000000 --- a/src/application.hpp +++ /dev/null @@ -1,429 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef APPLICATION_HPP -#define APPLICATION_HPP - -#include -using namespace Emergent; - -#include "mesh.hpp" -#include "game/terrain.hpp" -#include "game/level.hpp" -#include "game/biome.hpp" -#include "game/terrain.hpp" -#include "input.hpp" -#include "controls.hpp" -#include "settings.hpp" -#include "render-passes.hpp" -#include "ui/ui.hpp" -#include "ui/tween.hpp" - -class Menu; -class MenuItem; -class ApplicationState; -class Colony; -class LoadingState; -class SplashState; -class TitleState; -class GameState; -class CameraRig; -class OrbitCam; -class FreeCam; -class LineBatcher; -class ModelLoader; -class MaterialLoader; -class Toolbar; -class PieMenu; -class Tool; -class Forceps; -class Lens; -class Brush; - -/** - * Encapsulates the state of the application. - */ -class Application -{ -public: - Application(int argc, char* argv[]); - ~Application(); - - // Executes the application and returns a status code - int execute(); - - // Changes the application state - void changeState(ApplicationState* state); - - // Sets the termination code to be returned when the application finishes - void setTerminationCode(int code); - - // Closes the application - void close(int terminationCode); - - void changeFullscreen(); - void changeVerticalSync(); - void saveUserSettings(); - - bool loadScene(); - bool loadUI(); - bool loadModels(); - bool loadControls(); - bool loadGame(); - - - void resizeUI(); - void restringUI(); - - void openMenu(Menu* menu); - void closeMenu(); - void selectMenuItem(std::size_t index); - void activateMenuItem(); - void incrementMenuItem(); - void decrementMenuItem(); - - void loadWorld(std::size_t index); - void loadLevel(std::size_t index); - void continueGame(); - void newGame(); - - void deselectTool(Tool* tool); - void selectTool(Tool* tool); - - - - void pauseSimulation(); - void unpauseSimulation(); - - void openPauseMenu(); - void closePauseMenu(); - - void setDisplayDebugInfo(bool display); - - std::u32string getLevelName(std::size_t world, std::size_t level) const; - - // Options menu functions - void selectWindowedResolution(std::size_t index); - void selectFullscreenResolution(std::size_t index); - void selectFullscreenMode(std::size_t index); - void selectVSyncMode(std::size_t index); - void selectLanguage(std::size_t index); - - void bindControl(Control* control); - -private: - ApplicationState* state; - ApplicationState* nextState; - int terminationCode; - -public: - // SDL - SDL_Window* window; - SDL_GLContext context; - - // Paths - std::string appDataPath; - std::string userDataPath; - std::string defaultSettingsFilename; - std::string userSettingsFilename; - - // Settings - ParameterDict settings; - - // State machine - LoadingState* loadingState; - SplashState* splashState; - TitleState* titleState; - GameState* gameState; - - // Scene - Scene scene; - SceneLayer* defaultLayer; - SceneLayer* uiLayer; - Camera camera; - Camera sunlightCamera; - Camera uiCamera; - DirectionalLight sunlight; - ModelInstance forcepsModelInstance; - ModelInstance navigatorObject; - ModelInstance antModelInstance; - ModelInstance antHillModelInstance; - ModelInstance nestModelInstance; - ModelInstance sidewalkPanelInstance; - ModelInstance sidewalkPanelInstance1; - ModelInstance sidewalkPanelInstance2; - ModelInstance sidewalkPanelInstance3; - ModelInstance sidewalkPanelInstance4; - ModelInstance soilInstance; - - // Graphics - Renderer renderer; - RenderTarget defaultRenderTarget; - - int shadowMapResolution; - GLuint shadowMapDepthTextureID; - GLuint shadowMapFramebuffer; - RenderTarget shadowMapRenderTarget; - ShadowMapRenderPass shadowMapPass; - Compositor shadowMapCompositor; - Texture2D shadowMapDepthTexture; - - GLuint framebufferAColorTextureID; - GLuint framebufferADepthTextureID; - GLuint framebufferA; - RenderTarget framebufferARenderTarget; - Texture2D framebufferAColorTexture; - - GLuint framebufferBColorTextureID; - GLuint framebufferBDepthTextureID; - GLuint framebufferB; - RenderTarget framebufferBRenderTarget; - Texture2D framebufferBColorTexture; - - GLuint pheromonePBO; - GLuint pheromoneTextureID; - Texture2D pheromoneTexture; - - ClearRenderPass clearDepthPass; - LightingRenderPass lightingPass; - DebugRenderPass debugPass; - Compositor defaultCompositor; - BillboardBatch* uiBatch; - UIBatcher* uiBatcher; - UIRenderPass uiPass; - Compositor uiCompositor; - SkyboxRenderPass skyboxPass; - TextureLoader* textureLoader; - MaterialLoader* materialLoader; - ModelLoader* modelLoader; - BlurRenderPass horizontalBlurPass; - BlurRenderPass verticalBlurPass; - BlurRenderPass horizontalBlurPass2; - BlurRenderPass verticalBlurPass2; - - // Controls - Control* bindingControl; - InputManager* inputManager; - Keyboard* keyboard; - Mouse* mouse; - ControlProfile* menuControlProfile; - Control menuLeft; - Control menuRight; - Control menuUp; - Control menuDown; - Control menuSelect; - Control menuCancel; - Control toggleFullscreen; - Control toggleDebugDisplay; - Control escape; - ControlProfile* gameControlProfile; - Control cameraMoveForward; - Control cameraMoveBack; - Control cameraMoveLeft; - Control cameraMoveRight; - Control cameraRotateCW; - Control cameraRotateCCW; - Control cameraZoomIn; - Control cameraZoomOut; - Control cameraToggleOverheadView; - Control cameraToggleNestView; - Control walkForward; - Control walkBack; - Control turnLeft; - Control turnRight; - Control togglePause; - Control togglePauseMenu; - Control fastForward; - Control switchRig; - Arcball arcball; - - // Misc - Timer frameTimer; - float t; - float dt; - - // UI text - ParameterDict strings; - float dpi; - float fontSizePT; - float fontSizePX; - Font* menuFont; - Font* copyrightFont; - Font* levelNameFont; - - // UI textures - Texture2D* splashTexture; - Texture2D* titleTexture; - Texture2D* rectangularPaletteTexture; - Texture2D* foodIndicatorTexture; - Texture2D* toolBrushTexture; - Texture2D* toolLensTexture; - Texture2D* toolForcepsTexture; - Texture2D* toolTrowelTexture; - - Texture2D* toolbarTopTexture; - Texture2D* toolbarBottomTexture; - Texture2D* toolbarMiddleTexture; - Texture2D* toolbarButtonRaisedTexture; - Texture2D* toolbarButtonDepressedTexture; - - Texture2D* arcNorthTexture; - Texture2D* arcEastTexture; - Texture2D* arcSouthTexture; - Texture2D* arcWestTexture; - Texture2D* mouseLeftTexture; - Texture2D* mouseRightTexture; - - Texture2D* depthTexture; - - // UI elements - Vector4 selectedColor; - Vector4 deselectedColor; - UIContainer* uiRootElement; - UIImage* blackoutImage; - UIImage* splashBackgroundImage; - UIImage* splashImage; - UIImage* titleImage; - UIImage* darkenImage; - UILabel* frameTimeLabel; - UILabel* anyKeyLabel; - UILabel* copyrightLabel; - UIImage* rectangularPaletteImage; - UIImage* foodIndicatorImage; - UIImage* contextButtonImage0; - UIImage* contextButtonImage1; - UIImage* depthTextureImage; - UILabel* levelNameLabel; - Toolbar* toolbar; - PieMenu* pieMenu; - - // Animation - Tweener* tweener; - Tween* fadeInTween; - Tween* fadeOutTween; - Tween* darkenFadeInTween; - Tween* darkenFadeOutTween; - Tween* blurFadeInTween; - Tween* blurFadeOutTween; - Tween* splashFadeInTween; - Tween* splashFadeOutTween; - Tween* splashHangTween; - Tween* titleFadeInTween; - Tween* titleFadeOutTween; - Tween* anyKeyFadeInTween; - Tween* anyKeyFadeOutTween; - Tween* menuFadeInTween; - Tween* menuFadeOutTween; - Tween* menuActivateTween; - Tween* cameraTranslationTween; - Tween* forcepsSwoopTween; - - - // Menus - Menu* activeMenu; - Menu* previousActiveMenu; - - Menu* mainMenu; - MenuItem* mainMenuContinueItem; - MenuItem* mainMenuLevelsItem; - MenuItem* mainMenuNewGameItem; - MenuItem* mainMenuSandboxItem; - MenuItem* mainMenuOptionsItem; - MenuItem* mainMenuExitItem; - - Menu* levelsMenu; - MenuItem* levelsMenuBackItem; - - Menu* optionsMenu; - MenuItem* optionsMenuWindowedResolutionItem; - MenuItem* optionsMenuFullscreenResolutionItem; - MenuItem* optionsMenuFullscreenItem; - MenuItem* optionsMenuVSyncItem; - MenuItem* optionsMenuLanguageItem; - MenuItem* optionsMenuControlsItem; - MenuItem* optionsMenuBackItem; - - Menu* controlsMenu; - MenuItem* controlsMenuResetToDefaultItem; - MenuItem* controlsMenuMoveForwardItem; - MenuItem* controlsMenuMoveBackItem; - MenuItem* controlsMenuMoveLeftItem; - MenuItem* controlsMenuMoveRightItem; - MenuItem* controlsMenuBackItem; - - Menu* graphicsMenu; - Menu* audioMenu; - Menu* gameplayMenu; - - Menu* pauseMenu; - MenuItem* pauseMenuResumeItem; - MenuItem* pauseMenuLevelsItem; - MenuItem* pauseMenuOptionsItem; - MenuItem* pauseMenuMainMenuItem; - MenuItem* pauseMenuExitItem; - - // Models - Model* antModel; - Model* antHillModel; - Model* nestModel; - Model* forcepsModel; - Model* lensModel; - Model* brushModel; - Model* sidewalkPanelModel; - Model* soilModel; - - // Game variables - Biosphere biosphere; - Campaign campaign; - - int currentWorldIndex; - int currentLevelIndex; - Level* currentLevel; - - Colony* colony; - OrbitCam* orbitCam; - FreeCam* freeCam; - CameraRig* activeRig; - bool cameraOverheadView; - bool cameraNestView; - int toolIndex; - Tool* currentTool; - Forceps* forceps; - Lens* lens; - Brush* brush; - bool simulationPaused; - - // Debug - LineBatcher* lineBatcher; - bool displayDebugInfo; - - // Options menu values - bool fullscreen; - int swapInterval; - Vector2 resolution; - std::vector resolutions; - std::size_t windowedResolutionIndex; - std::size_t fullscreenResolutionIndex; - int* fullscreenModes; - int* vsyncModes; - std::vector languages; - std::size_t languageIndex; -}; - -#endif // APPLICATION_HPP \ No newline at end of file diff --git a/src/configuration.hpp.in b/src/configuration.hpp.in index 7ec0d41..74185d7 100644 --- a/src/configuration.hpp.in +++ b/src/configuration.hpp.in @@ -1,65 +1,9 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef CONFIGURATION_HPP -#define CONFIGURATION_HPP - -#include -using namespace Emergent; - -#define ANTKEEPER_VERSION_MAJOR @ANTKEEPER_VERSION_MAJOR@ -#define ANTKEEPER_VERSION_MINOR @ANTKEEPER_VERSION_MINOR@ -#define ANTKEEPER_VERSION_PATCH @ANTKEEPER_VERSION_PATCH@ -#define ANTKEEPER_VERSION_STRING "@ANTKEEPER_VERSION@" -#cmakedefine ANTKEEPER_DEBUG - -// Terrain dimensions -const float ANTKEEPER_TERRAIN_WIDTH = 100.0f; -const float ANTKEEPER_TERRAIN_BASE_HEIGHT = 35.7f; -const float ANTKEEPER_TERRAIN_DEPTH = ANTKEEPER_TERRAIN_WIDTH; -const float ANTKEEPER_OCTREE_PADDING = 5.0f; - -// UI -const int ANTKEEPER_UI_LAYER_LOADING = 40; -const int ANTKEEPER_UI_LAYER_BLACKOUT = 30; -const int ANTKEEPER_UI_LAYER_MENU = 20; -const int ANTKEEPER_UI_LAYER_DARKEN = 10; -const int ANTKEEPER_UI_LAYER_HUD = 0; - -const float WORLD_WIDTH = 100.0f; // cm -const float WORLD_HEIGHT = 100.0f; // cm -const float FRAMES_PER_SECOND = 60.0f; -const float TIMESTEP = 1.0f / FRAMES_PER_SECOND; -const float PHEROMONE_MATRIX_RESOLUTION = 5.12f; // pheromone cells per cm -const int PHEROMONE_MATRIX_COLUMNS = (int)(WORLD_WIDTH * PHEROMONE_MATRIX_RESOLUTION); -const int PHEROMONE_MATRIX_ROWS = (int)(WORLD_HEIGHT * PHEROMONE_MATRIX_RESOLUTION); -const Vector2 WORLD_BOUNDS_MIN = Vector2(-WORLD_WIDTH * 0.5f, -WORLD_HEIGHT * 0.5f); -const Vector2 WORLD_BOUNDS_MAX = Vector2(WORLD_WIDTH * 0.5f, WORLD_HEIGHT * 0.5f); -const float BRUSH_RADIUS = 0.5f; -const float EVAPORATION_FACTOR = 0.99925f; -const float DIFFUSIONS_PER_SECOND = 4.0f; -const int DIFFUSION_FRAME = static_cast(FRAMES_PER_SECOND / DIFFUSIONS_PER_SECOND); -const float HOMING_PHEROMONE_COLOR[] = {0.55f, 0.55f, 0.00f, 0.0f}; // CMYK -const float RECRUITMENT_PHEROMONE_COLOR[] = {0.00f, 0.55f, 0.55f, 0.0f}; // CMYK -const float ALARM_PHEROMONE_COLOR[] = {0.00f, 0.00f, 1.00f, 0.0f}; // CMYK - -const std::uint64_t MATERIAL_FLAG_TRANSLUCENT = 0x0000000001; -const std::uint64_t MATERIAL_FLAG_DISABLE_SHADOW_CASTING = 0x0000000002; - -#endif // CONFIGURATION_HPP +#ifndef CONFIGURATION_HPP +#define CONFIGURATION_HPP + +#define VERSION_MAJOR @PROJECT_VERSION_MAJOR@ +#define VERSION_MINOR @PROJECT_VERSION_MINOR@ +#define VERSION_PATCH @PROJECT_VERSION_PATCH@ +#define VERSION_STRING "@PROJECT_VERSION@" + +#endif // CONFIGURATION_HPP diff --git a/src/controls.hpp b/src/controls.hpp deleted file mode 100644 index 077db4d..0000000 --- a/src/controls.hpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef CONTROLS_HPP -#define CONTROLS_HPP - -#include "input.hpp" -#include -#include -#include -#include - -enum class MouseWheelAxis -{ - POSITIVE_X, - NEGATIVE_X, - POSITIVE_Y, - NEGATIVE_Y -}; - -class Control: - public KeyObserver, - public MouseButtonObserver, - public MouseWheelObserver, - public GamepadButtonObserver, - public GamepadAxisObserver -{ -public: - Control(); - virtual ~Control(); - void setDeadzone(float value); - void update(); - - float getDeadzone() const; - float getCurrentValue() const; - float getPreviousValue() const; - bool isTriggered() const; - bool wasTriggered() const; - bool isUnbound() const; - - void bindKey(Keyboard* keyboard, int scancode); - void bindMouseButton(Mouse* mouse, int button); - void bindMouseWheelAxis(Mouse* mouse, MouseWheelAxis axis); - void bindGamepadButton(Gamepad* gamepad, int button); - void bindGamepadAxis(Gamepad* gamepad, int axis, bool negative); - void bind(const InputEvent& event); - - void unbind(); - - virtual void keyPressed(int scancode); - virtual void keyReleased(int scancode); - virtual void mouseButtonPressed(int button, int x, int y); - virtual void mouseButtonReleased(int button, int x, int y); - virtual void mouseWheelScrolled(int x, int y); - virtual void gamepadButtonPressed(int button); - virtual void gamepadButtonReleased(int button); - virtual void gamepadAxisMoved(int axis, bool negative, float value); - - const std::list>* getBoundKeys() const; - const std::list>* getBoundMouseButtons() const; - const std::list>* getBoundMouseWheelAxes() const; - const std::list>* getBoundGamepadButtons() const; - const std::list>* getBoundGamepadAxes() const; - -private: - float deadzone; - float currentValue; - float previousValue; - - std::list> boundKeys; - std::list> boundMouseButtons; - std::list> boundMouseWheelAxes; - std::list> boundGamepadButtons; - std::list> boundGamepadAxes; -}; - -inline float Control::getDeadzone() const -{ - return deadzone; -} - -inline float Control::getCurrentValue() const -{ - return currentValue; -} - -inline float Control::getPreviousValue() const -{ - return previousValue; -} - -class ControlProfile -{ -public: - ControlProfile(InputManager* inputManager); - - void registerControl(const std::string& name, Control* control); - - bool save(const std::string& filename); - bool load(const std::string& filename); - - // Calls Control::update() on each control registered with this profile - void update(); - - const std::map* getControlMap() const; - -private: - InputManager* inputManager; - std::map controls; -}; - -inline const std::map* ControlProfile::getControlMap() const -{ - return &controls; -} - -#endif - diff --git a/src/debug.cpp b/src/debug.cpp deleted file mode 100644 index d3f18e8..0000000 --- a/src/debug.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#include "debug.hpp" -#include - -LineBatcher::LineBatcher(std::size_t lineCount): - lineCount(lineCount), - currentLine(0), - width(1.0f), - color(1.0f) -{ - batch.resize(lineCount); - range = batch.addRange(); - range->material = nullptr;//&material; - - //material.albedo = Vector3(1.0f); -} - -void LineBatcher::begin() -{ - currentLine = 0; - range->start = 0; - range->length = 0; -} - -void LineBatcher::end() -{ - range->length = currentLine; - batch.update(); -} - -void LineBatcher::draw(const Vector3& start, const Vector3& end) -{ - if (currentLine >= batch.getBillboardCount()) - { - std::cout << "LineBatcher::draw(): maximum line count exceeded" << std::endl; - return; - } - - Vector3 center = (start + end) * 0.5f; - float length = glm::length(end - start); - - Vector3 forward = glm::normalize(end - start); - glm::quat rotation = glm::normalize(glm::rotation(Vector3(1, 0, 0), forward)); - - Billboard* billboard = batch.getBillboard(currentLine); - billboard->setTranslation(center); - billboard->setDimensions(Vector2(length, width)); - billboard->setRotation(rotation); - billboard->setTintColor(color); - - ++currentLine; -} - -void LineBatcher::setWidth(float width) -{ - this->width = width; -} - -void LineBatcher::setColor(const Vector4& color) -{ - this->color = color; -} diff --git a/src/dr_libs/dr_wav.cpp b/src/dr_libs/dr_wav.cpp new file mode 100644 index 0000000..8f4dcd8 --- /dev/null +++ b/src/dr_libs/dr_wav.cpp @@ -0,0 +1,2 @@ +#define DR_WAV_IMPLEMENTATION +#include "dr_wav.h" diff --git a/src/dr_libs/dr_wav.h b/src/dr_libs/dr_wav.h new file mode 100644 index 0000000..ac4f6b5 --- /dev/null +++ b/src/dr_libs/dr_wav.h @@ -0,0 +1,4377 @@ +// WAV audio loader and writer. Public domain. See "unlicense" statement at the end of this file. +// dr_wav - v0.9.0 - 2018-12-16 +// +// David Reid - mackron@gmail.com + +/* +DEPRECATED APIS +=============== +Version 0.9.0 deprecated the per-sample reading and seeking APIs and replaced them with versions that work on the resolution +of a PCM frame instead. For example, given a stereo WAV file, previously you would pass 2 to drwav_read_f32() to read one +PCM frame, whereas now you would pass in 1 to drwav_read_pcm_frames_f32(). The old APIs would return the number of samples +read, whereas now it will return the number of PCM frames. Below is a list of APIs that have been deprecated and their +replacements. + + drwav_read() -> drwav_read_pcm_frames() + drwav_read_s16() -> drwav_read_pcm_frames_s16() + drwav_read_f32() -> drwav_read_pcm_frames_f32() + drwav_read_s32() -> drwav_read_pcm_frames_s32() + drwav_seek_to_sample() -> drwav_seek_to_pcm_frame() + drwav_write() -> drwav_write_pcm_frames() + drwav_open_and_read_s16() -> drwav_open_and_read_pcm_frames_s16() + drwav_open_and_read_f32() -> drwav_open_and_read_pcm_frames_f32() + drwav_open_and_read_s32() -> drwav_open_and_read_pcm_frames_s32() + drwav_open_file_and_read_s16() -> drwav_open_file_and_read_pcm_frames_s16() + drwav_open_file_and_read_f32() -> drwav_open_file_and_read_pcm_frames_f32() + drwav_open_file_and_read_s32() -> drwav_open_file_and_read_pcm_frames_s32() + drwav_open_memory_and_read_s16() -> drwav_open_memory_and_read_pcm_frames_s16() + drwav_open_memory_and_read_f32() -> drwav_open_memory_and_read_pcm_frames_f32() + drwav_open_memory_and_read_s32() -> drwav_open_memory_and_read_pcm_frames_s32() + drwav::totalSampleCount -> drwav::totalPCMFrameCount + +Rationale: + 1) Most programs will want to read in multiples of the channel count which demands a per-frame reading API. Per-sample + reading just adds complexity and maintenance costs for no practical benefit. + 2) This is consistent with my other decoders - dr_flac and dr_mp3. + +These APIs will be removed completely in version 0.10.0. You can continue to use drwav_read_raw() if you need per-sample +reading. +*/ + +// USAGE +// +// This is a single-file library. To use it, do something like the following in one .c file. +// #define DR_WAV_IMPLEMENTATION +// #include "dr_wav.h" +// +// You can then #include this file in other parts of the program as you would with any other header file. Do something +// like the following to read audio data: +// +// drwav wav; +// if (!drwav_init_file(&wav, "my_song.wav")) { +// // Error opening WAV file. +// } +// +// drwav_int32* pDecodedInterleavedSamples = malloc(wav.totalPCMFrameCount * wav.channels * sizeof(drwav_int32)); +// size_t numberOfSamplesActuallyDecoded = drwav_read_pcm_frames_s32(&wav, wav.totalPCMFrameCount, pDecodedInterleavedSamples); +// +// ... +// +// drwav_uninit(&wav); +// +// You can also use drwav_open() to allocate and initialize the loader for you: +// +// drwav* pWav = drwav_open_file("my_song.wav"); +// if (pWav == NULL) { +// // Error opening WAV file. +// } +// +// ... +// +// drwav_close(pWav); +// +// If you just want to quickly open and read the audio data in a single operation you can do something like this: +// +// unsigned int channels; +// unsigned int sampleRate; +// drwav_uint64 totalPCMFrameCount; +// float* pSampleData = drwav_open_file_and_read_pcm_frames_f32("my_song.wav", &channels, &sampleRate, &totalPCMFrameCount); +// if (pSampleData == NULL) { +// // Error opening and reading WAV file. +// } +// +// ... +// +// drwav_free(pSampleData); +// +// The examples above use versions of the API that convert the audio data to a consistent format (32-bit signed PCM, in +// this case), but you can still output the audio data in its internal format (see notes below for supported formats): +// +// size_t samplesRead = drwav_read_pcm_frames(&wav, wav.totalPCMFrameCount, pDecodedInterleavedSamples); +// +// You can also read the raw bytes of audio data, which could be useful if dr_wav does not have native support for +// a particular data format: +// +// size_t bytesRead = drwav_read_raw(&wav, bytesToRead, pRawDataBuffer); +// +// +// dr_wav can also be used to output WAV files. This does not currently support compressed formats. To use this, look at +// drwav_open_write(), drwav_open_file_write(), etc. Use drwav_write_pcm_frames() to write samples, or drwav_write_raw() +// to write raw data in the "data" chunk. +// +// drwav_data_format format; +// format.container = drwav_container_riff; // <-- drwav_container_riff = normal WAV files, drwav_container_w64 = Sony Wave64. +// format.format = DR_WAVE_FORMAT_PCM; // <-- Any of the DR_WAVE_FORMAT_* codes. +// format.channels = 2; +// format.sampleRate = 44100; +// format.bitsPerSample = 16; +// drwav* pWav = drwav_open_file_write("data/recording.wav", &format); +// +// ... +// +// drwav_uint64 samplesWritten = drwav_write_pcm_frames(pWav, frameCount, pSamples); +// +// +// dr_wav has seamless support the Sony Wave64 format. The decoder will automatically detect it and it should Just Work +// without any manual intervention. +// +// +// OPTIONS +// #define these options before including this file. +// +// #define DR_WAV_NO_CONVERSION_API +// Disables conversion APIs such as drwav_read_pcm_frames_f32() and drwav_s16_to_f32(). +// +// #define DR_WAV_NO_STDIO +// Disables drwav_open_file(), drwav_open_file_write(), etc. +// +// +// +// QUICK NOTES +// - Samples are always interleaved. +// - The default read function does not do any data conversion. Use drwav_read_pcm_frames_f32(), drwav_read_pcm_frames_s32() +// and drwav_read_pcm_frames_s16() to read and convert audio data to 32-bit floating point, signed 32-bit integer and +// signed 16-bit integer samples respectively. Tested and supported internal formats include the following: +// - Unsigned 8-bit PCM +// - Signed 12-bit PCM +// - Signed 16-bit PCM +// - Signed 24-bit PCM +// - Signed 32-bit PCM +// - IEEE 32-bit floating point +// - IEEE 64-bit floating point +// - A-law and u-law +// - Microsoft ADPCM +// - IMA ADPCM (DVI, format code 0x11) +// - dr_wav will try to read the WAV file as best it can, even if it's not strictly conformant to the WAV format. + + +#ifndef dr_wav_h +#define dr_wav_h + +#include + +#if defined(_MSC_VER) && _MSC_VER < 1600 +typedef signed char drwav_int8; +typedef unsigned char drwav_uint8; +typedef signed short drwav_int16; +typedef unsigned short drwav_uint16; +typedef signed int drwav_int32; +typedef unsigned int drwav_uint32; +typedef signed __int64 drwav_int64; +typedef unsigned __int64 drwav_uint64; +#else +#include +typedef int8_t drwav_int8; +typedef uint8_t drwav_uint8; +typedef int16_t drwav_int16; +typedef uint16_t drwav_uint16; +typedef int32_t drwav_int32; +typedef uint32_t drwav_uint32; +typedef int64_t drwav_int64; +typedef uint64_t drwav_uint64; +#endif +typedef drwav_uint8 drwav_bool8; +typedef drwav_uint32 drwav_bool32; +#define DRWAV_TRUE 1 +#define DRWAV_FALSE 0 + +#ifdef __cplusplus +extern "C" { +#endif + +// Common data formats. +#define DR_WAVE_FORMAT_PCM 0x1 +#define DR_WAVE_FORMAT_ADPCM 0x2 +#define DR_WAVE_FORMAT_IEEE_FLOAT 0x3 +#define DR_WAVE_FORMAT_ALAW 0x6 +#define DR_WAVE_FORMAT_MULAW 0x7 +#define DR_WAVE_FORMAT_DVI_ADPCM 0x11 +#define DR_WAVE_FORMAT_EXTENSIBLE 0xFFFE + +// Constants. +#ifndef DRWAV_MAX_SMPL_LOOPS +#define DRWAV_MAX_SMPL_LOOPS 1 +#endif + +// Flags to pass into drwav_init_ex(), etc. +#define DRWAV_SEQUENTIAL 0x00000001 + +typedef enum +{ + drwav_seek_origin_start, + drwav_seek_origin_current +} drwav_seek_origin; + +typedef enum +{ + drwav_container_riff, + drwav_container_w64 +} drwav_container; + +typedef struct +{ + union + { + drwav_uint8 fourcc[4]; + drwav_uint8 guid[16]; + } id; + + // The size in bytes of the chunk. + drwav_uint64 sizeInBytes; + + // RIFF = 2 byte alignment. + // W64 = 8 byte alignment. + unsigned int paddingSize; +} drwav_chunk_header; + +// Callback for when data is read. Return value is the number of bytes actually read. +// +// pUserData [in] The user data that was passed to drwav_init(), drwav_open() and family. +// pBufferOut [out] The output buffer. +// bytesToRead [in] The number of bytes to read. +// +// Returns the number of bytes actually read. +// +// A return value of less than bytesToRead indicates the end of the stream. Do _not_ return from this callback until +// either the entire bytesToRead is filled or you have reached the end of the stream. +typedef size_t (* drwav_read_proc)(void* pUserData, void* pBufferOut, size_t bytesToRead); + +// Callback for when data is written. Returns value is the number of bytes actually written. +// +// pUserData [in] The user data that was passed to drwav_init_write(), drwav_open_write() and family. +// pData [out] A pointer to the data to write. +// bytesToWrite [in] The number of bytes to write. +// +// Returns the number of bytes actually written. +// +// If the return value differs from bytesToWrite, it indicates an error. +typedef size_t (* drwav_write_proc)(void* pUserData, const void* pData, size_t bytesToWrite); + +// Callback for when data needs to be seeked. +// +// pUserData [in] The user data that was passed to drwav_init(), drwav_open() and family. +// offset [in] The number of bytes to move, relative to the origin. Will never be negative. +// origin [in] The origin of the seek - the current position or the start of the stream. +// +// Returns whether or not the seek was successful. +// +// Whether or not it is relative to the beginning or current position is determined by the "origin" parameter which +// will be either drwav_seek_origin_start or drwav_seek_origin_current. +typedef drwav_bool32 (* drwav_seek_proc)(void* pUserData, int offset, drwav_seek_origin origin); + +// Callback for when drwav_init_ex/drwav_open_ex finds a chunk. +// +// pChunkUserData [in] The user data that was passed to the pChunkUserData parameter of drwav_init_ex(), drwav_open_ex() and family. +// onRead [in] A pointer to the function to call when reading. +// onSeek [in] A pointer to the function to call when seeking. +// pReadSeekUserData [in] The user data that was passed to the pReadSeekUserData parameter of drwav_init_ex(), drwav_open_ex() and family. +// pChunkHeader [in] A pointer to an object containing basic header information about the chunk. Use this to identify the chunk. +// +// Returns the number of bytes read + seeked. +// +// To read data from the chunk, call onRead(), passing in pReadSeekUserData as the first parameter. Do the same +// for seeking with onSeek(). The return value must be the total number of bytes you have read _plus_ seeked. +// +// You must not attempt to read beyond the boundary of the chunk. +typedef drwav_uint64 (* drwav_chunk_proc)(void* pChunkUserData, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pReadSeekUserData, const drwav_chunk_header* pChunkHeader); + +// Structure for internal use. Only used for loaders opened with drwav_open_memory(). +typedef struct +{ + const drwav_uint8* data; + size_t dataSize; + size_t currentReadPos; +} drwav__memory_stream; + +// Structure for internal use. Only used for writers opened with drwav_open_memory_write(). +typedef struct +{ + void** ppData; + size_t* pDataSize; + size_t dataSize; + size_t dataCapacity; + size_t currentWritePos; +} drwav__memory_stream_write; + +typedef struct +{ + drwav_container container; // RIFF, W64. + drwav_uint32 format; // DR_WAVE_FORMAT_* + drwav_uint32 channels; + drwav_uint32 sampleRate; + drwav_uint32 bitsPerSample; +} drwav_data_format; + +typedef struct +{ + // The format tag exactly as specified in the wave file's "fmt" chunk. This can be used by applications + // that require support for data formats not natively supported by dr_wav. + drwav_uint16 formatTag; + + // The number of channels making up the audio data. When this is set to 1 it is mono, 2 is stereo, etc. + drwav_uint16 channels; + + // The sample rate. Usually set to something like 44100. + drwav_uint32 sampleRate; + + // Average bytes per second. You probably don't need this, but it's left here for informational purposes. + drwav_uint32 avgBytesPerSec; + + // Block align. This is equal to the number of channels * bytes per sample. + drwav_uint16 blockAlign; + + // Bits per sample. + drwav_uint16 bitsPerSample; + + // The size of the extended data. Only used internally for validation, but left here for informational purposes. + drwav_uint16 extendedSize; + + // The number of valid bits per sample. When is equal to WAVE_FORMAT_EXTENSIBLE, + // is always rounded up to the nearest multiple of 8. This variable contains information about exactly how + // many bits a valid per sample. Mainly used for informational purposes. + drwav_uint16 validBitsPerSample; + + // The channel mask. Not used at the moment. + drwav_uint32 channelMask; + + // The sub-format, exactly as specified by the wave file. + drwav_uint8 subFormat[16]; +} drwav_fmt; + +typedef struct +{ + drwav_uint32 cuePointId; + drwav_uint32 type; + drwav_uint32 start; + drwav_uint32 end; + drwav_uint32 fraction; + drwav_uint32 playCount; +} drwav_smpl_loop; + + typedef struct +{ + drwav_uint32 manufacturer; + drwav_uint32 product; + drwav_uint32 samplePeriod; + drwav_uint32 midiUnityNotes; + drwav_uint32 midiPitchFraction; + drwav_uint32 smpteFormat; + drwav_uint32 smpteOffset; + drwav_uint32 numSampleLoops; + drwav_uint32 samplerData; + drwav_smpl_loop loops[DRWAV_MAX_SMPL_LOOPS]; +} drwav_smpl; + +typedef struct +{ + // A pointer to the function to call when more data is needed. + drwav_read_proc onRead; + + // A pointer to the function to call when data needs to be written. Only used when the drwav object is opened in write mode. + drwav_write_proc onWrite; + + // A pointer to the function to call when the wav file needs to be seeked. + drwav_seek_proc onSeek; + + // The user data to pass to callbacks. + void* pUserData; + + + // Whether or not the WAV file is formatted as a standard RIFF file or W64. + drwav_container container; + + + // Structure containing format information exactly as specified by the wav file. + drwav_fmt fmt; + + // The sample rate. Will be set to something like 44100. + drwav_uint32 sampleRate; + + // The number of channels. This will be set to 1 for monaural streams, 2 for stereo, etc. + drwav_uint16 channels; + + // The bits per sample. Will be set to something like 16, 24, etc. + drwav_uint16 bitsPerSample; + + // Equal to fmt.formatTag, or the value specified by fmt.subFormat if fmt.formatTag is equal to 65534 (WAVE_FORMAT_EXTENSIBLE). + drwav_uint16 translatedFormatTag; + + // The total number of PCM frames making up the audio data. + drwav_uint64 totalPCMFrameCount; + + + // The size in bytes of the data chunk. + drwav_uint64 dataChunkDataSize; + + // The position in the stream of the first byte of the data chunk. This is used for seeking. + drwav_uint64 dataChunkDataPos; + + // The number of bytes remaining in the data chunk. + drwav_uint64 bytesRemaining; + + + // Only used in sequential write mode. Keeps track of the desired size of the "data" chunk at the point of initialization time. Always + // set to 0 for non-sequential writes and when the drwav object is opened in read mode. Used for validation. + drwav_uint64 dataChunkDataSizeTargetWrite; + + // Keeps track of whether or not the wav writer was initialized in sequential mode. + drwav_bool32 isSequentialWrite; + + + // smpl chunk. + drwav_smpl smpl; + + + // A hack to avoid a DRWAV_MALLOC() when opening a decoder with drwav_open_memory(). + drwav__memory_stream memoryStream; + drwav__memory_stream_write memoryStreamWrite; + + // Generic data for compressed formats. This data is shared across all block-compressed formats. + struct + { + drwav_uint64 iCurrentSample; // The index of the next sample that will be read by drwav_read_*(). This is used with "totalSampleCount" to ensure we don't read excess samples at the end of the last block. + } compressed; + + // Microsoft ADPCM specific data. + struct + { + drwav_uint32 bytesRemainingInBlock; + drwav_uint16 predictor[2]; + drwav_int32 delta[2]; + drwav_int32 cachedSamples[4]; // Samples are stored in this cache during decoding. + drwav_uint32 cachedSampleCount; + drwav_int32 prevSamples[2][2]; // The previous 2 samples for each channel (2 channels at most). + } msadpcm; + + // IMA ADPCM specific data. + struct + { + drwav_uint32 bytesRemainingInBlock; + drwav_int32 predictor[2]; + drwav_int32 stepIndex[2]; + drwav_int32 cachedSamples[16]; // Samples are stored in this cache during decoding. + drwav_uint32 cachedSampleCount; + } ima; + + + drwav_uint64 totalSampleCount; // <-- DEPRECATED. Will be removed in a future version. +} drwav; + + +// Initializes a pre-allocated drwav object. +// +// pWav [out] A pointer to the drwav object being initialized. +// onRead [in] The function to call when data needs to be read from the client. +// onSeek [in] The function to call when the read position of the client data needs to move. +// onChunk [in, optional] The function to call when a chunk is enumerated at initialized time. +// pUserData, pReadSeekUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. +// pChunkUserData [in, optional] A pointer to application defined data that will be passed to onChunk. +// flags [in, optional] A set of flags for controlling how things are loaded. +// +// Returns true if successful; false otherwise. +// +// Close the loader with drwav_uninit(). +// +// This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() +// to open the stream from a file or from a block of memory respectively. +// +// If you want dr_wav to manage the memory allocation for you, consider using drwav_open() instead. This will allocate +// a drwav object on the heap and return a pointer to it. +// +// Possible values for flags: +// DRWAV_SEQUENTIAL: Never perform a backwards seek while loading. This disables the chunk callback and will cause this function +// to return as soon as the data chunk is found. Any chunks after the data chunk will be ignored. +// +// drwav_init() is equivalent to "drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0);". +// +// The onChunk callback is not called for the WAVE or FMT chunks. The contents of the FMT chunk can be read from pWav->fmt +// after the function returns. +// +// See also: drwav_init_file(), drwav_init_memory(), drwav_uninit() +drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData); +drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags); + +// Initializes a pre-allocated drwav object for writing. +// +// onWrite [in] The function to call when data needs to be written. +// onSeek [in] The function to call when the write position needs to move. +// pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. +// +// Returns true if successful; false otherwise. +// +// Close the writer with drwav_uninit(). +// +// This is the lowest level function for initializing a WAV file. You can also use drwav_init_file() and drwav_init_memory() +// to open the stream from a file or from a block of memory respectively. +// +// If the total sample count is known, you can use drwav_init_write_sequential(). This avoids the need for dr_wav to perform +// a post-processing step for storing the total sample count and the size of the data chunk which requires a backwards seek. +// +// If you want dr_wav to manage the memory allocation for you, consider using drwav_open() instead. This will allocate +// a drwav object on the heap and return a pointer to it. +// +// See also: drwav_init_file_write(), drwav_init_memory_write(), drwav_uninit() +drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData); +drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData); + +// Uninitializes the given drwav object. +// +// Use this only for objects initialized with drwav_init(). +void drwav_uninit(drwav* pWav); + + +// Opens a wav file using the given callbacks. +// +// onRead [in] The function to call when data needs to be read from the client. +// onSeek [in] The function to call when the read position of the client data needs to move. +// pUserData [in, optional] A pointer to application defined data that will be passed to onRead and onSeek. +// +// Returns null on error. +// +// Close the loader with drwav_close(). +// +// You can also use drwav_open_file() and drwav_open_memory() to open the stream from a file or from a block of +// memory respectively. +// +// This is different from drwav_init() in that it will allocate the drwav object for you via DRWAV_MALLOC() before +// initializing it. +// +// See also: drwav_init(), drwav_open_file(), drwav_open_memory(), drwav_close() +drwav* drwav_open(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData); +drwav* drwav_open_ex(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags); + +// Opens a wav file for writing using the given callbacks. +// +// onWrite [in] The function to call when data needs to be written. +// onSeek [in] The function to call when the write position needs to move. +// pUserData [in, optional] A pointer to application defined data that will be passed to onWrite and onSeek. +// +// Returns null on error. +// +// Close the loader with drwav_close(). +// +// You can also use drwav_open_file_write() and drwav_open_memory_write() to open the stream from a file or from a block +// of memory respectively. +// +// This is different from drwav_init_write() in that it will allocate the drwav object for you via DRWAV_MALLOC() before +// initializing it. +// +// See also: drwav_open_file_write(), drwav_open_memory_write(), drwav_close() +drwav* drwav_open_write(const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData); +drwav* drwav_open_write_sequential(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData); + +// Uninitializes and deletes the the given drwav object. +// +// Use this only for objects created with drwav_open(). +void drwav_close(drwav* pWav); + + +// Reads raw audio data. +// +// This is the lowest level function for reading audio data. It simply reads the given number of +// bytes of the raw internal sample data. +// +// Consider using drwav_read_pcm_frames_s16(), drwav_read_pcm_frames_s32() or drwav_read_pcm_frames_f32() for +// reading sample data in a consistent format. +// +// Returns the number of bytes actually read. +size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut); + +// Reads a chunk of audio data in the native internal format. +// +// This is typically the most efficient way to retrieve audio data, but it does not do any format +// conversions which means you'll need to convert the data manually if required. +// +// If the return value is less than it means the end of the file has been reached or +// you have requested more samples than can possibly fit in the output buffer. +// +// This function will only work when sample data is of a fixed size and uncompressed. If you are +// using a compressed format consider using drwav_read_raw() or drwav_read_pcm_frames_s16/s32/f32/etc(). +drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut); + +// Seeks to the given PCM frame. +// +// Returns true if successful; false otherwise. +drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex); + + +// Writes raw audio data. +// +// Returns the number of bytes actually written. If this differs from bytesToWrite, it indicates an error. +size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData); + +// Writes PCM frames. +// +// Returns the number of PCM frames written. +drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData); + + +//// Conversion Utilities //// +#ifndef DR_WAV_NO_CONVERSION_API + +// Reads a chunk of audio data and converts it to signed 16-bit PCM samples. +// +// Returns the number of PCM frames actually read. +// +// If the return value is less than it means the end of the file has been reached. +drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut); + +// Low-level function for converting unsigned 8-bit PCM samples to signed 16-bit PCM samples. +void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting signed 24-bit PCM samples to signed 16-bit PCM samples. +void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting signed 32-bit PCM samples to signed 16-bit PCM samples. +void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount); + +// Low-level function for converting IEEE 32-bit floating point samples to signed 16-bit PCM samples. +void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount); + +// Low-level function for converting IEEE 64-bit floating point samples to signed 16-bit PCM samples. +void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount); + +// Low-level function for converting A-law samples to signed 16-bit PCM samples. +void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting u-law samples to signed 16-bit PCM samples. +void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount); + + + +// Reads a chunk of audio data and converts it to IEEE 32-bit floating point samples. +// +// Returns the number of PCM frames actually read. +// +// If the return value is less than it means the end of the file has been reached. +drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut); + +// Low-level function for converting unsigned 8-bit PCM samples to IEEE 32-bit floating point samples. +void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting signed 16-bit PCM samples to IEEE 32-bit floating point samples. +void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount); + +// Low-level function for converting signed 24-bit PCM samples to IEEE 32-bit floating point samples. +void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting signed 32-bit PCM samples to IEEE 32-bit floating point samples. +void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount); + +// Low-level function for converting IEEE 64-bit floating point samples to IEEE 32-bit floating point samples. +void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount); + +// Low-level function for converting A-law samples to IEEE 32-bit floating point samples. +void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting u-law samples to IEEE 32-bit floating point samples. +void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount); + + + +// Reads a chunk of audio data and converts it to signed 32-bit PCM samples. +// +// Returns the number of PCM frames actually read. +// +// If the return value is less than it means the end of the file has been reached. +drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut); + +// Low-level function for converting unsigned 8-bit PCM samples to signed 32-bit PCM samples. +void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting signed 16-bit PCM samples to signed 32-bit PCM samples. +void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount); + +// Low-level function for converting signed 24-bit PCM samples to signed 32-bit PCM samples. +void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting IEEE 32-bit floating point samples to signed 32-bit PCM samples. +void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount); + +// Low-level function for converting IEEE 64-bit floating point samples to signed 32-bit PCM samples. +void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount); + +// Low-level function for converting A-law samples to signed 32-bit PCM samples. +void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +// Low-level function for converting u-law samples to signed 32-bit PCM samples. +void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount); + +#endif //DR_WAV_NO_CONVERSION_API + + +//// High-Level Convenience Helpers //// + +#ifndef DR_WAV_NO_STDIO + +// Helper for initializing a wave file using stdio. +// +// This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav +// objects because the operating system may restrict the number of file handles an application can have open at +// any given time. +drwav_bool32 drwav_init_file(drwav* pWav, const char* filename); +drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags); + +// Helper for initializing a wave file for writing using stdio. +// +// This holds the internal FILE object until drwav_uninit() is called. Keep this in mind if you're caching drwav +// objects because the operating system may restrict the number of file handles an application can have open at +// any given time. +drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat); +drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); + +// Helper for opening a wave file using stdio. +// +// This holds the internal FILE object until drwav_close() is called. Keep this in mind if you're caching drwav +// objects because the operating system may restrict the number of file handles an application can have open at +// any given time. +drwav* drwav_open_file(const char* filename); +drwav* drwav_open_file_ex(const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags); + +// Helper for opening a wave file for writing using stdio. +// +// This holds the internal FILE object until drwav_close() is called. Keep this in mind if you're caching drwav +// objects because the operating system may restrict the number of file handles an application can have open at +// any given time. +drwav* drwav_open_file_write(const char* filename, const drwav_data_format* pFormat); +drwav* drwav_open_file_write_sequential(const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); + +#endif //DR_WAV_NO_STDIO + +// Helper for initializing a loader from a pre-allocated memory buffer. +// +// This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for +// the lifetime of the drwav object. +// +// The buffer should contain the contents of the entire wave file, not just the sample data. +drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize); +drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags); + +// Helper for initializing a writer which outputs data to a memory buffer. +// +// dr_wav will manage the memory allocations, however it is up to the caller to free the data with drwav_free(). +// +// The buffer will remain allocated even after drwav_uninit() is called. Indeed, the buffer should not be +// considered valid until after drwav_uninit() has been called anyway. +drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat); +drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); + +// Helper for opening a loader from a pre-allocated memory buffer. +// +// This does not create a copy of the data. It is up to the application to ensure the buffer remains valid for +// the lifetime of the drwav object. +// +// The buffer should contain the contents of the entire wave file, not just the sample data. +drwav* drwav_open_memory(const void* data, size_t dataSize); +drwav* drwav_open_memory_ex(const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags); + +// Helper for opening a writer which outputs data to a memory buffer. +// +// dr_wav will manage the memory allocations, however it is up to the caller to free the data with drwav_free(). +// +// The buffer will remain allocated even after drwav_close() is called. Indeed, the buffer should not be +// considered valid until after drwav_close() has been called anyway. +drwav* drwav_open_memory_write(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat); +drwav* drwav_open_memory_write_sequential(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount); + + +#ifndef DR_WAV_NO_CONVERSION_API +// Opens and reads a wav file in a single operation. +drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +#ifndef DR_WAV_NO_STDIO +// Opens and decodes a wav file in a single operation. +drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +#endif + +// Opens and decodes a wav file from a block of memory in a single operation. +drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalFrameCount); +#endif + +// Frees data that was allocated internally by dr_wav. +void drwav_free(void* pDataReturnedByOpenAndRead); + + +// DEPRECATED APIS +// =============== +drwav_uint64 drwav_read(drwav* pWav, drwav_uint64 samplesToRead, void* pBufferOut); +drwav_uint64 drwav_read_s16(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +drwav_uint64 drwav_read_f32(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut); +drwav_uint64 drwav_read_s32(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut); +drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample); +drwav_uint64 drwav_write(drwav* pWav, drwav_uint64 samplesToWrite, const void* pData); +#ifndef DR_WAV_NO_CONVERSION_API +drwav_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +float* drwav_open_and_read_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +drwav_int32* drwav_open_and_read_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +#ifndef DR_WAV_NO_STDIO +drwav_int16* drwav_open_memory_and_read_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +float* drwav_open_file_and_read_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +drwav_int32* drwav_open_file_and_read_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +#endif +drwav_int16* drwav_open_memory_and_read_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +float* drwav_open_memory_and_read_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +drwav_int32* drwav_open_memory_and_read_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount); +#endif + + +#ifdef __cplusplus +} +#endif +#endif // dr_wav_h + + +///////////////////////////////////////////////////// +// +// IMPLEMENTATION +// +///////////////////////////////////////////////////// + +#ifdef DR_WAV_IMPLEMENTATION +#include +#include // For memcpy(), memset() +#include // For INT_MAX + +#ifndef DR_WAV_NO_STDIO +#include +#endif + +// Standard library stuff. +#ifndef DRWAV_ASSERT +#include +#define DRWAV_ASSERT(expression) assert(expression) +#endif +#ifndef DRWAV_MALLOC +#define DRWAV_MALLOC(sz) malloc((sz)) +#endif +#ifndef DRWAV_REALLOC +#define DRWAV_REALLOC(p, sz) realloc((p), (sz)) +#endif +#ifndef DRWAV_FREE +#define DRWAV_FREE(p) free((p)) +#endif +#ifndef DRWAV_COPY_MEMORY +#define DRWAV_COPY_MEMORY(dst, src, sz) memcpy((dst), (src), (sz)) +#endif +#ifndef DRWAV_ZERO_MEMORY +#define DRWAV_ZERO_MEMORY(p, sz) memset((p), 0, (sz)) +#endif + +#define drwav_countof(x) (sizeof(x) / sizeof(x[0])) +#define drwav_align(x, a) ((((x) + (a) - 1) / (a)) * (a)) +#define drwav_min(a, b) (((a) < (b)) ? (a) : (b)) +#define drwav_max(a, b) (((a) > (b)) ? (a) : (b)) +#define drwav_clamp(x, lo, hi) (drwav_max((lo), drwav_min((hi), (x)))) + +#define drwav_assert DRWAV_ASSERT +#define drwav_copy_memory DRWAV_COPY_MEMORY +#define drwav_zero_memory DRWAV_ZERO_MEMORY + +typedef drwav_int32 drwav_result; +#define DRWAV_SUCCESS 0 +#define DRWAV_ERROR -1 +#define DRWAV_INVALID_ARGS -2 +#define DRWAV_INVALID_OPERATION -3 +#define DRWAV_INVALID_FILE -100 +#define DRWAV_EOF -101 + +#define DRWAV_MAX_SIMD_VECTOR_SIZE 64 // 64 for AVX-512 in the future. + +#ifdef _MSC_VER +#define DRWAV_INLINE __forceinline +#else +#ifdef __GNUC__ +#define DRWAV_INLINE inline __attribute__((always_inline)) +#else +#define DRWAV_INLINE inline +#endif +#endif + +#if defined(SIZE_MAX) + #define DRWAV_SIZE_MAX SIZE_MAX +#else + #if defined(_WIN64) || defined(_LP64) || defined(__LP64__) + #define DRWAV_SIZE_MAX ((drwav_uint64)0xFFFFFFFFFFFFFFFF) + #else + #define DRWAV_SIZE_MAX 0xFFFFFFFF + #endif +#endif + +static const drwav_uint8 drwavGUID_W64_RIFF[16] = {0x72,0x69,0x66,0x66, 0x2E,0x91, 0xCF,0x11, 0xA5,0xD6, 0x28,0xDB,0x04,0xC1,0x00,0x00}; // 66666972-912E-11CF-A5D6-28DB04C10000 +static const drwav_uint8 drwavGUID_W64_WAVE[16] = {0x77,0x61,0x76,0x65, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 65766177-ACF3-11D3-8CD1-00C04F8EDB8A +static const drwav_uint8 drwavGUID_W64_JUNK[16] = {0x6A,0x75,0x6E,0x6B, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 6B6E756A-ACF3-11D3-8CD1-00C04F8EDB8A +static const drwav_uint8 drwavGUID_W64_FMT [16] = {0x66,0x6D,0x74,0x20, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 20746D66-ACF3-11D3-8CD1-00C04F8EDB8A +static const drwav_uint8 drwavGUID_W64_FACT[16] = {0x66,0x61,0x63,0x74, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 74636166-ACF3-11D3-8CD1-00C04F8EDB8A +static const drwav_uint8 drwavGUID_W64_DATA[16] = {0x64,0x61,0x74,0x61, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 61746164-ACF3-11D3-8CD1-00C04F8EDB8A +static const drwav_uint8 drwavGUID_W64_SMPL[16] = {0x73,0x6D,0x70,0x6C, 0xF3,0xAC, 0xD3,0x11, 0x8C,0xD1, 0x00,0xC0,0x4F,0x8E,0xDB,0x8A}; // 6C706D73-ACF3-11D3-8CD1-00C04F8EDB8A + +static DRWAV_INLINE drwav_bool32 drwav__guid_equal(const drwav_uint8 a[16], const drwav_uint8 b[16]) +{ + const drwav_uint32* a32 = (const drwav_uint32*)a; + const drwav_uint32* b32 = (const drwav_uint32*)b; + + return + a32[0] == b32[0] && + a32[1] == b32[1] && + a32[2] == b32[2] && + a32[3] == b32[3]; +} + +static DRWAV_INLINE drwav_bool32 drwav__fourcc_equal(const unsigned char* a, const char* b) +{ + return + a[0] == b[0] && + a[1] == b[1] && + a[2] == b[2] && + a[3] == b[3]; +} + + + +static DRWAV_INLINE int drwav__is_little_endian() +{ + int n = 1; + return (*(char*)&n) == 1; +} + +static DRWAV_INLINE unsigned short drwav__bytes_to_u16(const unsigned char* data) +{ + return (data[0] << 0) | (data[1] << 8); +} + +static DRWAV_INLINE short drwav__bytes_to_s16(const unsigned char* data) +{ + return (short)drwav__bytes_to_u16(data); +} + +static DRWAV_INLINE unsigned int drwav__bytes_to_u32(const unsigned char* data) +{ + return (data[0] << 0) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); +} + +static DRWAV_INLINE drwav_uint64 drwav__bytes_to_u64(const unsigned char* data) +{ + return + ((drwav_uint64)data[0] << 0) | ((drwav_uint64)data[1] << 8) | ((drwav_uint64)data[2] << 16) | ((drwav_uint64)data[3] << 24) | + ((drwav_uint64)data[4] << 32) | ((drwav_uint64)data[5] << 40) | ((drwav_uint64)data[6] << 48) | ((drwav_uint64)data[7] << 56); +} + +static DRWAV_INLINE void drwav__bytes_to_guid(const unsigned char* data, drwav_uint8* guid) +{ + for (int i = 0; i < 16; ++i) { + guid[i] = data[i]; + } +} + + +static DRWAV_INLINE drwav_bool32 drwav__is_compressed_format_tag(drwav_uint16 formatTag) +{ + return + formatTag == DR_WAVE_FORMAT_ADPCM || + formatTag == DR_WAVE_FORMAT_DVI_ADPCM; +} + +drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +drwav_uint64 drwav_read_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut); +drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData); +drwav* drwav_open_write__internal(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData); + +static drwav_result drwav__read_chunk_header(drwav_read_proc onRead, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_chunk_header* pHeaderOut) +{ + if (container == drwav_container_riff) { + if (onRead(pUserData, pHeaderOut->id.fourcc, 4) != 4) { + return DRWAV_EOF; + } + + unsigned char sizeInBytes[4]; + if (onRead(pUserData, sizeInBytes, 4) != 4) { + return DRWAV_INVALID_FILE; + } + + pHeaderOut->sizeInBytes = drwav__bytes_to_u32(sizeInBytes); + pHeaderOut->paddingSize = (unsigned int)(pHeaderOut->sizeInBytes % 2); + *pRunningBytesReadOut += 8; + } else { + if (onRead(pUserData, pHeaderOut->id.guid, 16) != 16) { + return DRWAV_EOF; + } + + unsigned char sizeInBytes[8]; + if (onRead(pUserData, sizeInBytes, 8) != 8) { + return DRWAV_INVALID_FILE; + } + + pHeaderOut->sizeInBytes = drwav__bytes_to_u64(sizeInBytes) - 24; // <-- Subtract 24 because w64 includes the size of the header. + pHeaderOut->paddingSize = (unsigned int)(pHeaderOut->sizeInBytes % 8); + *pRunningBytesReadOut += 24; + } + + return DRWAV_SUCCESS; +} + +static drwav_bool32 drwav__seek_forward(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +{ + drwav_uint64 bytesRemainingToSeek = offset; + while (bytesRemainingToSeek > 0) { + if (bytesRemainingToSeek > 0x7FFFFFFF) { + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + bytesRemainingToSeek -= 0x7FFFFFFF; + } else { + if (!onSeek(pUserData, (int)bytesRemainingToSeek, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + bytesRemainingToSeek = 0; + } + } + + return DRWAV_TRUE; +} + +static drwav_bool32 drwav__seek_from_start(drwav_seek_proc onSeek, drwav_uint64 offset, void* pUserData) +{ + if (offset <= 0x7FFFFFFF) { + return onSeek(pUserData, (int)offset, drwav_seek_origin_start); + } + + // Larger than 32-bit seek. + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_start)) { + return DRWAV_FALSE; + } + offset -= 0x7FFFFFFF; + + for (;;) { + if (offset <= 0x7FFFFFFF) { + return onSeek(pUserData, (int)offset, drwav_seek_origin_current); + } + + if (!onSeek(pUserData, 0x7FFFFFFF, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + offset -= 0x7FFFFFFF; + } + + // Should never get here. + //return DRWAV_TRUE; +} + + +static drwav_bool32 drwav__read_fmt(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, drwav_container container, drwav_uint64* pRunningBytesReadOut, drwav_fmt* fmtOut) +{ + drwav_chunk_header header; + if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + + + // Skip non-fmt chunks. + while ((container == drwav_container_riff && !drwav__fourcc_equal(header.id.fourcc, "fmt ")) || (container == drwav_container_w64 && !drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT))) { + if (!drwav__seek_forward(onSeek, header.sizeInBytes + header.paddingSize, pUserData)) { + return DRWAV_FALSE; + } + *pRunningBytesReadOut += header.sizeInBytes + header.paddingSize; + + // Try the next header. + if (drwav__read_chunk_header(onRead, pUserData, container, pRunningBytesReadOut, &header) != DRWAV_SUCCESS) { + return DRWAV_FALSE; + } + } + + + // Validation. + if (container == drwav_container_riff) { + if (!drwav__fourcc_equal(header.id.fourcc, "fmt ")) { + return DRWAV_FALSE; + } + } else { + if (!drwav__guid_equal(header.id.guid, drwavGUID_W64_FMT)) { + return DRWAV_FALSE; + } + } + + + unsigned char fmt[16]; + if (onRead(pUserData, fmt, sizeof(fmt)) != sizeof(fmt)) { + return DRWAV_FALSE; + } + *pRunningBytesReadOut += sizeof(fmt); + + fmtOut->formatTag = drwav__bytes_to_u16(fmt + 0); + fmtOut->channels = drwav__bytes_to_u16(fmt + 2); + fmtOut->sampleRate = drwav__bytes_to_u32(fmt + 4); + fmtOut->avgBytesPerSec = drwav__bytes_to_u32(fmt + 8); + fmtOut->blockAlign = drwav__bytes_to_u16(fmt + 12); + fmtOut->bitsPerSample = drwav__bytes_to_u16(fmt + 14); + + fmtOut->extendedSize = 0; + fmtOut->validBitsPerSample = 0; + fmtOut->channelMask = 0; + memset(fmtOut->subFormat, 0, sizeof(fmtOut->subFormat)); + + if (header.sizeInBytes > 16) { + unsigned char fmt_cbSize[2]; + if (onRead(pUserData, fmt_cbSize, sizeof(fmt_cbSize)) != sizeof(fmt_cbSize)) { + return DRWAV_FALSE; // Expecting more data. + } + *pRunningBytesReadOut += sizeof(fmt_cbSize); + + int bytesReadSoFar = 18; + + fmtOut->extendedSize = drwav__bytes_to_u16(fmt_cbSize); + if (fmtOut->extendedSize > 0) { + // Simple validation. + if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + if (fmtOut->extendedSize != 22) { + return DRWAV_FALSE; + } + } + + if (fmtOut->formatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + unsigned char fmtext[22]; + if (onRead(pUserData, fmtext, fmtOut->extendedSize) != fmtOut->extendedSize) { + return DRWAV_FALSE; // Expecting more data. + } + + fmtOut->validBitsPerSample = drwav__bytes_to_u16(fmtext + 0); + fmtOut->channelMask = drwav__bytes_to_u32(fmtext + 2); + drwav__bytes_to_guid(fmtext + 6, fmtOut->subFormat); + } else { + if (!onSeek(pUserData, fmtOut->extendedSize, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + } + *pRunningBytesReadOut += fmtOut->extendedSize; + + bytesReadSoFar += fmtOut->extendedSize; + } + + // Seek past any leftover bytes. For w64 the leftover will be defined based on the chunk size. + if (!onSeek(pUserData, (int)(header.sizeInBytes - bytesReadSoFar), drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + *pRunningBytesReadOut += (header.sizeInBytes - bytesReadSoFar); + } + + if (header.paddingSize > 0) { + if (!onSeek(pUserData, header.paddingSize, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + *pRunningBytesReadOut += header.paddingSize; + } + + return DRWAV_TRUE; +} + + +#ifndef DR_WAV_NO_STDIO +FILE* drwav_fopen(const char* filePath, const char* openMode) +{ + FILE* pFile; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (fopen_s(&pFile, filePath, openMode) != 0) { + return DRWAV_FALSE; + } +#else + pFile = fopen(filePath, openMode); + if (pFile == NULL) { + return DRWAV_FALSE; + } +#endif + + return pFile; +} + +static size_t drwav__on_read_stdio(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + return fread(pBufferOut, 1, bytesToRead, (FILE*)pUserData); +} + +static size_t drwav__on_write_stdio(void* pUserData, const void* pData, size_t bytesToWrite) +{ + return fwrite(pData, 1, bytesToWrite, (FILE*)pUserData); +} + +static drwav_bool32 drwav__on_seek_stdio(void* pUserData, int offset, drwav_seek_origin origin) +{ + return fseek((FILE*)pUserData, offset, (origin == drwav_seek_origin_current) ? SEEK_CUR : SEEK_SET) == 0; +} + +drwav_bool32 drwav_init_file(drwav* pWav, const char* filename) +{ + return drwav_init_file_ex(pWav, filename, NULL, NULL, 0); +} + +drwav_bool32 drwav_init_file_ex(drwav* pWav, const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + FILE* pFile = drwav_fopen(filename, "rb"); + if (pFile == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_ex(pWav, drwav__on_read_stdio, drwav__on_seek_stdio, onChunk, (void*)pFile, pChunkUserData, flags); +} + + +drwav_bool32 drwav_init_file_write__internal(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential) +{ + FILE* pFile = drwav_fopen(filename, "wb"); + if (pFile == NULL) { + return DRWAV_FALSE; + } + + return drwav_init_write__internal(pWav, pFormat, totalSampleCount, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile); +} + +drwav_bool32 drwav_init_file_write(drwav* pWav, const char* filename, const drwav_data_format* pFormat) +{ + return drwav_init_file_write__internal(pWav, filename, pFormat, 0, DRWAV_FALSE); +} + +drwav_bool32 drwav_init_file_write_sequential(drwav* pWav, const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +{ + return drwav_init_file_write__internal(pWav, filename, pFormat, totalSampleCount, DRWAV_TRUE); +} + +drwav* drwav_open_file(const char* filename) +{ + return drwav_open_file_ex(filename, NULL, NULL, 0); +} + +drwav* drwav_open_file_ex(const char* filename, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + FILE* pFile = drwav_fopen(filename, "rb"); + if (pFile == NULL) { + return DRWAV_FALSE; + } + + drwav* pWav = drwav_open_ex(drwav__on_read_stdio, drwav__on_seek_stdio, onChunk, (void*)pFile, pChunkUserData, flags); + if (pWav == NULL) { + fclose(pFile); + return NULL; + } + + return pWav; +} + + +drwav* drwav_open_file_write__internal(const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential) +{ + FILE* pFile = drwav_fopen(filename, "wb"); + if (pFile == NULL) { + return DRWAV_FALSE; + } + + drwav* pWav = drwav_open_write__internal(pFormat, totalSampleCount, isSequential, drwav__on_write_stdio, drwav__on_seek_stdio, (void*)pFile); + if (pWav == NULL) { + fclose(pFile); + return NULL; + } + + return pWav; +} + +drwav* drwav_open_file_write(const char* filename, const drwav_data_format* pFormat) +{ + return drwav_open_file_write__internal(filename, pFormat, 0, DRWAV_FALSE); +} + +drwav* drwav_open_file_write_sequential(const char* filename, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +{ + return drwav_open_file_write__internal(filename, pFormat, totalSampleCount, DRWAV_TRUE); +} +#endif //DR_WAV_NO_STDIO + + +static size_t drwav__on_read_memory(void* pUserData, void* pBufferOut, size_t bytesToRead) +{ + drwav__memory_stream* memory = (drwav__memory_stream*)pUserData; + drwav_assert(memory != NULL); + drwav_assert(memory->dataSize >= memory->currentReadPos); + + size_t bytesRemaining = memory->dataSize - memory->currentReadPos; + if (bytesToRead > bytesRemaining) { + bytesToRead = bytesRemaining; + } + + if (bytesToRead > 0) { + DRWAV_COPY_MEMORY(pBufferOut, memory->data + memory->currentReadPos, bytesToRead); + memory->currentReadPos += bytesToRead; + } + + return bytesToRead; +} + +static drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, drwav_seek_origin origin) +{ + drwav__memory_stream* memory = (drwav__memory_stream*)pUserData; + drwav_assert(memory != NULL); + + if (origin == drwav_seek_origin_current) { + if (offset > 0) { + if (memory->currentReadPos + offset > memory->dataSize) { + return DRWAV_FALSE; // Trying to seek too far forward. + } + } else { + if (memory->currentReadPos < (size_t)-offset) { + return DRWAV_FALSE; // Trying to seek too far backwards. + } + } + + // This will never underflow thanks to the clamps above. + memory->currentReadPos += offset; + } else { + if ((drwav_uint32)offset <= memory->dataSize) { + memory->currentReadPos = offset; + } else { + return DRWAV_FALSE; // Trying to seek too far forward. + } + } + + return DRWAV_TRUE; +} + +static size_t drwav__on_write_memory(void* pUserData, const void* pDataIn, size_t bytesToWrite) +{ + drwav__memory_stream_write* memory = (drwav__memory_stream_write*)pUserData; + drwav_assert(memory != NULL); + drwav_assert(memory->dataCapacity >= memory->currentWritePos); + + size_t bytesRemaining = memory->dataCapacity - memory->currentWritePos; + if (bytesRemaining < bytesToWrite) { + // Need to reallocate. + size_t newDataCapacity = (memory->dataCapacity == 0) ? 256 : memory->dataCapacity * 2; + + // If doubling wasn't enough, just make it the minimum required size to write the data. + if ((newDataCapacity - memory->currentWritePos) < bytesToWrite) { + newDataCapacity = memory->currentWritePos + bytesToWrite; + } + + void* pNewData = DRWAV_REALLOC(*memory->ppData, newDataCapacity); + if (pNewData == NULL) { + return 0; + } + + *memory->ppData = pNewData; + memory->dataCapacity = newDataCapacity; + } + + drwav_uint8* pDataOut = (drwav_uint8*)(*memory->ppData); + DRWAV_COPY_MEMORY(pDataOut + memory->currentWritePos, pDataIn, bytesToWrite); + + memory->currentWritePos += bytesToWrite; + if (memory->dataSize < memory->currentWritePos) { + memory->dataSize = memory->currentWritePos; + } + + *memory->pDataSize = memory->dataSize; + + return bytesToWrite; +} + +static drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offset, drwav_seek_origin origin) +{ + drwav__memory_stream_write* memory = (drwav__memory_stream_write*)pUserData; + drwav_assert(memory != NULL); + + if (origin == drwav_seek_origin_current) { + if (offset > 0) { + if (memory->currentWritePos + offset > memory->dataSize) { + offset = (int)(memory->dataSize - memory->currentWritePos); // Trying to seek too far forward. + } + } else { + if (memory->currentWritePos < (size_t)-offset) { + offset = -(int)memory->currentWritePos; // Trying to seek too far backwards. + } + } + + // This will never underflow thanks to the clamps above. + memory->currentWritePos += offset; + } else { + if ((drwav_uint32)offset <= memory->dataSize) { + memory->currentWritePos = offset; + } else { + memory->currentWritePos = memory->dataSize; // Trying to seek too far forward. + } + } + + return DRWAV_TRUE; +} + +drwav_bool32 drwav_init_memory(drwav* pWav, const void* data, size_t dataSize) +{ + return drwav_init_memory_ex(pWav, data, dataSize, NULL, NULL, 0); +} + +drwav_bool32 drwav_init_memory_ex(drwav* pWav, const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + if (data == NULL || dataSize == 0) { + return DRWAV_FALSE; + } + + drwav__memory_stream memoryStream; + drwav_zero_memory(&memoryStream, sizeof(memoryStream)); + memoryStream.data = (const unsigned char*)data; + memoryStream.dataSize = dataSize; + memoryStream.currentReadPos = 0; + + if (!drwav_init_ex(pWav, drwav__on_read_memory, drwav__on_seek_memory, onChunk, (void*)&memoryStream, pChunkUserData, flags)) { + return DRWAV_FALSE; + } + + pWav->memoryStream = memoryStream; + pWav->pUserData = &pWav->memoryStream; + return DRWAV_TRUE; +} + + +drwav_bool32 drwav_init_memory_write__internal(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential) +{ + if (ppData == NULL) { + return DRWAV_FALSE; + } + + *ppData = NULL; // Important because we're using realloc()! + *pDataSize = 0; + + drwav__memory_stream_write memoryStreamWrite; + drwav_zero_memory(&memoryStreamWrite, sizeof(memoryStreamWrite)); + memoryStreamWrite.ppData = ppData; + memoryStreamWrite.pDataSize = pDataSize; + memoryStreamWrite.dataSize = 0; + memoryStreamWrite.dataCapacity = 0; + memoryStreamWrite.currentWritePos = 0; + + if (!drwav_init_write__internal(pWav, pFormat, totalSampleCount, isSequential, drwav__on_write_memory, drwav__on_seek_memory_write, (void*)&memoryStreamWrite)) { + return DRWAV_FALSE; + } + + pWav->memoryStreamWrite = memoryStreamWrite; + pWav->pUserData = &pWav->memoryStreamWrite; + return DRWAV_TRUE; +} + +drwav_bool32 drwav_init_memory_write(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat) +{ + return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, 0, DRWAV_FALSE); +} + +drwav_bool32 drwav_init_memory_write_sequential(drwav* pWav, void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +{ + return drwav_init_memory_write__internal(pWav, ppData, pDataSize, pFormat, totalSampleCount, DRWAV_TRUE); +} + + +drwav* drwav_open_memory(const void* data, size_t dataSize) +{ + return drwav_open_memory_ex(data, dataSize, NULL, NULL, 0); +} + +drwav* drwav_open_memory_ex(const void* data, size_t dataSize, drwav_chunk_proc onChunk, void* pChunkUserData, drwav_uint32 flags) +{ + if (data == NULL || dataSize == 0) { + return NULL; + } + + drwav__memory_stream memoryStream; + drwav_zero_memory(&memoryStream, sizeof(memoryStream)); + memoryStream.data = (const unsigned char*)data; + memoryStream.dataSize = dataSize; + memoryStream.currentReadPos = 0; + + drwav* pWav = drwav_open_ex(drwav__on_read_memory, drwav__on_seek_memory, onChunk, (void*)&memoryStream, pChunkUserData, flags); + if (pWav == NULL) { + return NULL; + } + + pWav->memoryStream = memoryStream; + pWav->pUserData = &pWav->memoryStream; + return pWav; +} + + +drwav* drwav_open_memory_write__internal(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential) +{ + if (ppData == NULL) { + return NULL; + } + + *ppData = NULL; // Important because we're using realloc()! + *pDataSize = 0; + + drwav__memory_stream_write memoryStreamWrite; + drwav_zero_memory(&memoryStreamWrite, sizeof(memoryStreamWrite)); + memoryStreamWrite.ppData = ppData; + memoryStreamWrite.pDataSize = pDataSize; + memoryStreamWrite.dataSize = 0; + memoryStreamWrite.dataCapacity = 0; + memoryStreamWrite.currentWritePos = 0; + + drwav* pWav = drwav_open_write__internal(pFormat, totalSampleCount, isSequential, drwav__on_write_memory, drwav__on_seek_memory_write, (void*)&memoryStreamWrite); + if (pWav == NULL) { + return NULL; + } + + pWav->memoryStreamWrite = memoryStreamWrite; + pWav->pUserData = &pWav->memoryStreamWrite; + return pWav; +} + +drwav* drwav_open_memory_write(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat) +{ + return drwav_open_memory_write__internal(ppData, pDataSize, pFormat, 0, DRWAV_FALSE); +} + +drwav* drwav_open_memory_write_sequential(void** ppData, size_t* pDataSize, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount) +{ + return drwav_open_memory_write__internal(ppData, pDataSize, pFormat, totalSampleCount, DRWAV_TRUE); +} + + +size_t drwav__on_read(drwav_read_proc onRead, void* pUserData, void* pBufferOut, size_t bytesToRead, drwav_uint64* pCursor) +{ + drwav_assert(onRead != NULL); + drwav_assert(pCursor != NULL); + + size_t bytesRead = onRead(pUserData, pBufferOut, bytesToRead); + *pCursor += bytesRead; + return bytesRead; +} + +drwav_bool32 drwav__on_seek(drwav_seek_proc onSeek, void* pUserData, int offset, drwav_seek_origin origin, drwav_uint64* pCursor) +{ + drwav_assert(onSeek != NULL); + drwav_assert(pCursor != NULL); + + if (!onSeek(pUserData, offset, origin)) { + return DRWAV_FALSE; + } + + if (origin == drwav_seek_origin_start) { + *pCursor = offset; + } else { + *pCursor += offset; + } + + return DRWAV_TRUE; +} + + +static drwav_uint32 drwav_get_bytes_per_sample(drwav* pWav) +{ + // The number of bytes per sample is based on the bits per sample or the block align. We prioritize floor(bitsPerSample/8), but if + // this is zero or the bits per sample is not a multiple of 8 we need to fall back to the block align. + drwav_uint32 bytesPerSample = pWav->bitsPerSample >> 3; + if (bytesPerSample == 0 || (pWav->bitsPerSample & 0x7) != 0) { + bytesPerSample = pWav->fmt.blockAlign/pWav->fmt.channels; + } + + return bytesPerSample; +} + +static drwav_uint32 drwav_get_bytes_per_pcm_frame(drwav* pWav) +{ + // The number of bytes per frame is based on the bits per sample or the block align. We prioritize floor(bitsPerSample*channels/8), but if + // this is zero or the bits per frame is not a multiple of 8 we need to fall back to the block align. + drwav_uint32 bitsPerFrame = pWav->bitsPerSample * pWav->fmt.channels; + drwav_uint32 bytesPerFrame = bitsPerFrame >> 3; + if (bytesPerFrame == 0 || (bitsPerFrame & 0x7) != 0) { + bytesPerFrame = pWav->fmt.blockAlign; + } + + return bytesPerFrame; +} + + +drwav_bool32 drwav_init(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData) +{ + return drwav_init_ex(pWav, onRead, onSeek, NULL, pUserData, NULL, 0); +} + +drwav_bool32 drwav_init_ex(drwav* pWav, drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags) +{ + if (onRead == NULL || onSeek == NULL) { + return DRWAV_FALSE; + } + + drwav_uint64 cursor = 0; // <-- Keeps track of the byte position so we can seek to specific locations. + drwav_bool32 sequential = (flags & DRWAV_SEQUENTIAL) != 0; + + drwav_zero_memory(pWav, sizeof(*pWav)); + pWav->onRead = onRead; + pWav->onSeek = onSeek; + pWav->pUserData = pReadSeekUserData; + + // The first 4 bytes should be the RIFF identifier. + unsigned char riff[4]; + if (drwav__on_read(onRead, pReadSeekUserData, riff, sizeof(riff), &cursor) != sizeof(riff)) { + return DRWAV_FALSE; + } + + // The first 4 bytes can be used to identify the container. For RIFF files it will start with "RIFF" and for + // w64 it will start with "riff". + if (drwav__fourcc_equal(riff, "RIFF")) { + pWav->container = drwav_container_riff; + } else if (drwav__fourcc_equal(riff, "riff")) { + pWav->container = drwav_container_w64; + + // Check the rest of the GUID for validity. + drwav_uint8 riff2[12]; + if (drwav__on_read(onRead, pReadSeekUserData, riff2, sizeof(riff2), &cursor) != sizeof(riff2)) { + return DRWAV_FALSE; + } + + for (int i = 0; i < 12; ++i) { + if (riff2[i] != drwavGUID_W64_RIFF[i+4]) { + return DRWAV_FALSE; + } + } + } else { + return DRWAV_FALSE; // Unknown or unsupported container. + } + + + if (pWav->container == drwav_container_riff) { + // RIFF/WAVE + unsigned char chunkSizeBytes[4]; + if (drwav__on_read(onRead, pReadSeekUserData, chunkSizeBytes, sizeof(chunkSizeBytes), &cursor) != sizeof(chunkSizeBytes)) { + return DRWAV_FALSE; + } + + unsigned int chunkSize = drwav__bytes_to_u32(chunkSizeBytes); + if (chunkSize < 36) { + return DRWAV_FALSE; // Chunk size should always be at least 36 bytes. + } + + unsigned char wave[4]; + if (drwav__on_read(onRead, pReadSeekUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { + return DRWAV_FALSE; + } + + if (!drwav__fourcc_equal(wave, "WAVE")) { + return DRWAV_FALSE; // Expecting "WAVE". + } + } else { + // W64 + unsigned char chunkSize[8]; + if (drwav__on_read(onRead, pReadSeekUserData, chunkSize, sizeof(chunkSize), &cursor) != sizeof(chunkSize)) { + return DRWAV_FALSE; + } + + if (drwav__bytes_to_u64(chunkSize) < 80) { + return DRWAV_FALSE; + } + + drwav_uint8 wave[16]; + if (drwav__on_read(onRead, pReadSeekUserData, wave, sizeof(wave), &cursor) != sizeof(wave)) { + return DRWAV_FALSE; + } + + if (!drwav__guid_equal(wave, drwavGUID_W64_WAVE)) { + return DRWAV_FALSE; + } + } + + + // The next bytes should be the "fmt " chunk. + drwav_fmt fmt; + if (!drwav__read_fmt(onRead, onSeek, pReadSeekUserData, pWav->container, &cursor, &fmt)) { + return DRWAV_FALSE; // Failed to read the "fmt " chunk. + } + + // Basic validation. + if (fmt.sampleRate == 0 || fmt.channels == 0 || fmt.bitsPerSample == 0 || fmt.blockAlign == 0) { + return DRWAV_FALSE; // Invalid channel count. Probably an invalid WAV file. + } + + + // Translate the internal format. + unsigned short translatedFormatTag = fmt.formatTag; + if (translatedFormatTag == DR_WAVE_FORMAT_EXTENSIBLE) { + translatedFormatTag = drwav__bytes_to_u16(fmt.subFormat + 0); + } + + + + drwav_uint64 sampleCountFromFactChunk = 0; + + // We need to enumerate over each chunk for two reasons: + // 1) The "data" chunk may not be the next one + // 2) We may want to report each chunk back to the client + // + // In order to correctly report each chunk back to the client we will need to keep looping until the end of the file. + drwav_bool32 foundDataChunk = DRWAV_FALSE; + drwav_uint64 dataChunkSize = 0; + + // The next chunk we care about is the "data" chunk. This is not necessarily the next chunk so we'll need to loop. + drwav_uint64 chunkSize = 0; + for (;;) + { + drwav_chunk_header header; + drwav_result result = drwav__read_chunk_header(onRead, pReadSeekUserData, pWav->container, &cursor, &header); + if (result != DRWAV_SUCCESS) { + if (!foundDataChunk) { + return DRWAV_FALSE; + } else { + break; // Probably at the end of the file. Get out of the loop. + } + } + + // Tell the client about this chunk. + if (!sequential && onChunk != NULL) { + drwav_uint64 callbackBytesRead = onChunk(pChunkUserData, onRead, onSeek, pReadSeekUserData, &header); + + // dr_wav may need to read the contents of the chunk, so we now need to seek back to the position before + // we called the callback. + if (callbackBytesRead > 0) { + if (!drwav__seek_from_start(onSeek, cursor, pReadSeekUserData)) { + return DRWAV_FALSE; + } + } + } + + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + + chunkSize = header.sizeInBytes; + if (pWav->container == drwav_container_riff) { + if (drwav__fourcc_equal(header.id.fourcc, "data")) { + foundDataChunk = DRWAV_TRUE; + dataChunkSize = chunkSize; + } + } else { + if (drwav__guid_equal(header.id.guid, drwavGUID_W64_DATA)) { + foundDataChunk = DRWAV_TRUE; + dataChunkSize = chunkSize; + } + } + + // If at this point we have found the data chunk and we're running in sequential mode, we need to break out of this loop. The reason for + // this is that we would otherwise require a backwards seek which sequential mode forbids. + if (foundDataChunk && sequential) { + break; + } + + // Optional. Get the total sample count from the FACT chunk. This is useful for compressed formats. + if (pWav->container == drwav_container_riff) { + if (drwav__fourcc_equal(header.id.fourcc, "fact")) { + drwav_uint32 sampleCount; + if (drwav__on_read(onRead, pReadSeekUserData, &sampleCount, 4, &cursor) != 4) { + return DRWAV_FALSE; + } + chunkSize -= 4; + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + + // The sample count in the "fact" chunk is either unreliable, or I'm not understanding it properly. For now I am only enabling this + // for Microsoft ADPCM formats. + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + sampleCountFromFactChunk = sampleCount; + } else { + sampleCountFromFactChunk = 0; + } + } + } else { + if (drwav__guid_equal(header.id.guid, drwavGUID_W64_FACT)) { + if (drwav__on_read(onRead, pReadSeekUserData, &sampleCountFromFactChunk, 8, &cursor) != 8) { + return DRWAV_FALSE; + } + chunkSize -= 8; + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + } + } + + // "smpl" chunk. + if (pWav->container == drwav_container_riff) { + if (drwav__fourcc_equal(header.id.fourcc, "smpl")) { + unsigned char smplHeaderData[36]; // 36 = size of the smpl header section, not including the loop data. + if (chunkSize >= sizeof(smplHeaderData)) { + drwav_uint64 bytesJustRead = drwav__on_read(onRead, pReadSeekUserData, smplHeaderData, sizeof(smplHeaderData), &cursor); + chunkSize -= bytesJustRead; + + if (bytesJustRead == sizeof(smplHeaderData)) { + pWav->smpl.manufacturer = drwav__bytes_to_u32(smplHeaderData+0); + pWav->smpl.product = drwav__bytes_to_u32(smplHeaderData+4); + pWav->smpl.samplePeriod = drwav__bytes_to_u32(smplHeaderData+8); + pWav->smpl.midiUnityNotes = drwav__bytes_to_u32(smplHeaderData+12); + pWav->smpl.midiPitchFraction = drwav__bytes_to_u32(smplHeaderData+16); + pWav->smpl.smpteFormat = drwav__bytes_to_u32(smplHeaderData+20); + pWav->smpl.smpteOffset = drwav__bytes_to_u32(smplHeaderData+24); + pWav->smpl.numSampleLoops = drwav__bytes_to_u32(smplHeaderData+28); + pWav->smpl.samplerData = drwav__bytes_to_u32(smplHeaderData+32); + + for (drwav_uint32 iLoop = 0; iLoop < pWav->smpl.numSampleLoops && iLoop < drwav_countof(pWav->smpl.loops); ++iLoop) { + unsigned char smplLoopData[24]; // 24 = size of a loop section in the smpl chunk. + bytesJustRead = drwav__on_read(onRead, pReadSeekUserData, smplLoopData, sizeof(smplLoopData), &cursor); + chunkSize -= bytesJustRead; + + if (bytesJustRead == sizeof(smplLoopData)) { + pWav->smpl.loops[iLoop].cuePointId = drwav__bytes_to_u32(smplLoopData+0); + pWav->smpl.loops[iLoop].type = drwav__bytes_to_u32(smplLoopData+4); + pWav->smpl.loops[iLoop].start = drwav__bytes_to_u32(smplLoopData+8); + pWav->smpl.loops[iLoop].end = drwav__bytes_to_u32(smplLoopData+12); + pWav->smpl.loops[iLoop].fraction = drwav__bytes_to_u32(smplLoopData+16); + pWav->smpl.loops[iLoop].playCount = drwav__bytes_to_u32(smplLoopData+20); + } else { + break; // Break from the smpl loop for loop. + } + } + } + } else { + // Looks like invalid data. Ignore the chunk. + } + } + } else { + if (drwav__guid_equal(header.id.guid, drwavGUID_W64_SMPL)) { + // This path will be hit when a W64 WAV file contains a smpl chunk. I don't have a sample file to test this path, so a contribution + // is welcome to add support for this. + } + } + + // Make sure we seek past the padding. + chunkSize += header.paddingSize; + if (!drwav__seek_forward(onSeek, chunkSize, pReadSeekUserData)) { + break; + } + cursor += chunkSize; + + if (!foundDataChunk) { + pWav->dataChunkDataPos = cursor; + } + } + + // If we haven't found a data chunk, return an error. + if (!foundDataChunk) { + return DRWAV_FALSE; + } + + // We may have moved passed the data chunk. If so we need to move back. If running in sequential mode we can assume we are already sitting on the data chunk. + if (!sequential) { + if (!drwav__seek_from_start(onSeek, pWav->dataChunkDataPos, pReadSeekUserData)) { + return DRWAV_FALSE; + } + cursor = pWav->dataChunkDataPos; + } + + + // At this point we should be sitting on the first byte of the raw audio data. + + pWav->fmt = fmt; + pWav->sampleRate = fmt.sampleRate; + pWav->channels = fmt.channels; + pWav->bitsPerSample = fmt.bitsPerSample; + pWav->bytesRemaining = dataChunkSize; + pWav->translatedFormatTag = translatedFormatTag; + pWav->dataChunkDataSize = dataChunkSize; + + if (sampleCountFromFactChunk != 0) { + pWav->totalPCMFrameCount = sampleCountFromFactChunk; + } else { + pWav->totalPCMFrameCount = dataChunkSize / drwav_get_bytes_per_pcm_frame(pWav); + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2)) / fmt.channels; // x2 because two samples per byte. + } + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels)) / fmt.channels; + } + } + + // Some formats only support a certain number of channels. + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM || pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + if (pWav->channels > 2) { + return DRWAV_FALSE; + } + } + +#ifdef DR_WAV_LIBSNDFILE_COMPAT + // I use libsndfile as a benchmark for testing, however in the version I'm using (from the Windows installer on the libsndfile website), + // it appears the total sample count libsndfile uses for MS-ADPCM is incorrect. It would seem they are computing the total sample count + // from the number of blocks, however this results in the inclusion of extra silent samples at the end of the last block. The correct + // way to know the total sample count is to inspect the "fact" chunk, which should always be present for compressed formats, and should + // always include the sample count. This little block of code below is only used to emulate the libsndfile logic so I can properly run my + // correctness tests against libsndfile, and is disabled by default. + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (6*pWav->channels))) * 2)) / fmt.channels; // x2 because two samples per byte. + } + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + drwav_uint64 blockCount = dataChunkSize / fmt.blockAlign; + pWav->totalPCMFrameCount = (((blockCount * (fmt.blockAlign - (4*pWav->channels))) * 2) + (blockCount * pWav->channels)) / fmt.channels; + } +#endif + + pWav->totalSampleCount = pWav->totalPCMFrameCount * pWav->channels; + + return DRWAV_TRUE; +} + + +drwav_uint32 drwav_riff_chunk_size_riff(drwav_uint64 dataChunkSize) +{ + if (dataChunkSize <= (0xFFFFFFFF - 36)) { + return 36 + (drwav_uint32)dataChunkSize; + } else { + return 0xFFFFFFFF; + } +} + +drwav_uint32 drwav_data_chunk_size_riff(drwav_uint64 dataChunkSize) +{ + if (dataChunkSize <= 0xFFFFFFFF) { + return (drwav_uint32)dataChunkSize; + } else { + return 0xFFFFFFFF; + } +} + +drwav_uint64 drwav_riff_chunk_size_w64(drwav_uint64 dataChunkSize) +{ + return 80 + 24 + dataChunkSize; // +24 because W64 includes the size of the GUID and size fields. +} + +drwav_uint64 drwav_data_chunk_size_w64(drwav_uint64 dataChunkSize) +{ + return 24 + dataChunkSize; // +24 because W64 includes the size of the GUID and size fields. +} + + +drwav_bool32 drwav_init_write__internal(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData) +{ + if (pWav == NULL) { + return DRWAV_FALSE; + } + + if (onWrite == NULL) { + return DRWAV_FALSE; + } + + if (!isSequential && onSeek == NULL) { + return DRWAV_FALSE; // <-- onSeek is required when in non-sequential mode. + } + + + // Not currently supporting compressed formats. Will need to add support for the "fact" chunk before we enable this. + if (pFormat->format == DR_WAVE_FORMAT_EXTENSIBLE) { + return DRWAV_FALSE; + } + if (pFormat->format == DR_WAVE_FORMAT_ADPCM || pFormat->format == DR_WAVE_FORMAT_DVI_ADPCM) { + return DRWAV_FALSE; + } + + + drwav_zero_memory(pWav, sizeof(*pWav)); + pWav->onWrite = onWrite; + pWav->onSeek = onSeek; + pWav->pUserData = pUserData; + pWav->fmt.formatTag = (drwav_uint16)pFormat->format; + pWav->fmt.channels = (drwav_uint16)pFormat->channels; + pWav->fmt.sampleRate = pFormat->sampleRate; + pWav->fmt.avgBytesPerSec = (drwav_uint32)((pFormat->bitsPerSample * pFormat->sampleRate * pFormat->channels) / 8); + pWav->fmt.blockAlign = (drwav_uint16)((pFormat->channels * pFormat->bitsPerSample) / 8); + pWav->fmt.bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; + pWav->fmt.extendedSize = 0; + pWav->isSequentialWrite = isSequential; + + + size_t runningPos = 0; + + // The initial values for the "RIFF" and "data" chunks depends on whether or not we are initializing in sequential mode or not. In + // sequential mode we set this to its final values straight away since they can be calculated from the total sample count. In non- + // sequential mode we initialize it all to zero and fill it out in drwav_uninit() using a backwards seek. + drwav_uint64 initialDataChunkSize = 0; + if (isSequential) { + initialDataChunkSize = (totalSampleCount * pWav->fmt.bitsPerSample) / 8; + + // The RIFF container has a limit on the number of samples. drwav is not allowing this. There's no practical limits for Wave64 + // so for the sake of simplicity I'm not doing any validation for that. + if (pFormat->container == drwav_container_riff) { + if (initialDataChunkSize > (0xFFFFFFFF - 36)) { + return DRWAV_FALSE; // Not enough room to store every sample. + } + } + } + + pWav->dataChunkDataSizeTargetWrite = initialDataChunkSize; + + + // "RIFF" chunk. + if (pFormat->container == drwav_container_riff) { + drwav_uint32 chunkSizeRIFF = 36 + (drwav_uint32)initialDataChunkSize; // +36 = "RIFF"+[RIFF Chunk Size]+"WAVE" + [sizeof "fmt " chunk] + runningPos += pWav->onWrite(pUserData, "RIFF", 4); + runningPos += pWav->onWrite(pUserData, &chunkSizeRIFF, 4); + runningPos += pWav->onWrite(pUserData, "WAVE", 4); + } else { + drwav_uint64 chunkSizeRIFF = 80 + 24 + initialDataChunkSize; // +24 because W64 includes the size of the GUID and size fields. + runningPos += pWav->onWrite(pUserData, drwavGUID_W64_RIFF, 16); + runningPos += pWav->onWrite(pUserData, &chunkSizeRIFF, 8); + runningPos += pWav->onWrite(pUserData, drwavGUID_W64_WAVE, 16); + } + + // "fmt " chunk. + drwav_uint64 chunkSizeFMT; + if (pFormat->container == drwav_container_riff) { + chunkSizeFMT = 16; + runningPos += pWav->onWrite(pUserData, "fmt ", 4); + runningPos += pWav->onWrite(pUserData, &chunkSizeFMT, 4); + } else { + chunkSizeFMT = 40; + runningPos += pWav->onWrite(pUserData, drwavGUID_W64_FMT, 16); + runningPos += pWav->onWrite(pUserData, &chunkSizeFMT, 8); + } + + runningPos += pWav->onWrite(pUserData, &pWav->fmt.formatTag, 2); + runningPos += pWav->onWrite(pUserData, &pWav->fmt.channels, 2); + runningPos += pWav->onWrite(pUserData, &pWav->fmt.sampleRate, 4); + runningPos += pWav->onWrite(pUserData, &pWav->fmt.avgBytesPerSec, 4); + runningPos += pWav->onWrite(pUserData, &pWav->fmt.blockAlign, 2); + runningPos += pWav->onWrite(pUserData, &pWav->fmt.bitsPerSample, 2); + + pWav->dataChunkDataPos = runningPos; + + // "data" chunk. + if (pFormat->container == drwav_container_riff) { + drwav_uint32 chunkSizeDATA = (drwav_uint32)initialDataChunkSize; + runningPos += pWav->onWrite(pUserData, "data", 4); + runningPos += pWav->onWrite(pUserData, &chunkSizeDATA, 4); + } else { + drwav_uint64 chunkSizeDATA = 24 + initialDataChunkSize; // +24 because W64 includes the size of the GUID and size fields. + runningPos += pWav->onWrite(pUserData, drwavGUID_W64_DATA, 16); + runningPos += pWav->onWrite(pUserData, &chunkSizeDATA, 8); + } + + + // Simple validation. + if (pFormat->container == drwav_container_riff) { + if (runningPos != 20 + chunkSizeFMT + 8) { + return DRWAV_FALSE; + } + } else { + if (runningPos != 40 + chunkSizeFMT + 24) { + return DRWAV_FALSE; + } + } + + + + // Set some properties for the client's convenience. + pWav->container = pFormat->container; + pWav->channels = (drwav_uint16)pFormat->channels; + pWav->sampleRate = pFormat->sampleRate; + pWav->bitsPerSample = (drwav_uint16)pFormat->bitsPerSample; + pWav->translatedFormatTag = (drwav_uint16)pFormat->format; + + return DRWAV_TRUE; +} + + +drwav_bool32 drwav_init_write(drwav* pWav, const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData) +{ + return drwav_init_write__internal(pWav, pFormat, 0, DRWAV_FALSE, onWrite, onSeek, pUserData); // DRWAV_FALSE = Not Sequential +} + +drwav_bool32 drwav_init_write_sequential(drwav* pWav, const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData) +{ + return drwav_init_write__internal(pWav, pFormat, totalSampleCount, DRWAV_TRUE, onWrite, NULL, pUserData); // DRWAV_TRUE = Sequential +} + +void drwav_uninit(drwav* pWav) +{ + if (pWav == NULL) { + return; + } + + // If the drwav object was opened in write mode we'll need to finalize a few things: + // - Make sure the "data" chunk is aligned to 16-bits for RIFF containers, or 64 bits for W64 containers. + // - Set the size of the "data" chunk. + if (pWav->onWrite != NULL) { + // Validation for sequential mode. + if (pWav->isSequentialWrite) { + drwav_assert(pWav->dataChunkDataSize == pWav->dataChunkDataSizeTargetWrite); + } + + // Padding. Do not adjust pWav->dataChunkDataSize - this should not include the padding. + drwav_uint32 paddingSize = 0; + if (pWav->container == drwav_container_riff) { + paddingSize = (drwav_uint32)(pWav->dataChunkDataSize % 2); + } else { + paddingSize = (drwav_uint32)(pWav->dataChunkDataSize % 8); + } + + if (paddingSize > 0) { + drwav_uint64 paddingData = 0; + pWav->onWrite(pWav->pUserData, &paddingData, paddingSize); + } + + + // Chunk sizes. When using sequential mode, these will have been filled in at initialization time. We only need + // to do this when using non-sequential mode. + if (pWav->onSeek && !pWav->isSequentialWrite) { + if (pWav->container == drwav_container_riff) { + // The "RIFF" chunk size. + if (pWav->onSeek(pWav->pUserData, 4, drwav_seek_origin_start)) { + drwav_uint32 riffChunkSize = drwav_riff_chunk_size_riff(pWav->dataChunkDataSize); + pWav->onWrite(pWav->pUserData, &riffChunkSize, 4); + } + + // the "data" chunk size. + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 4, drwav_seek_origin_start)) { + drwav_uint32 dataChunkSize = drwav_data_chunk_size_riff(pWav->dataChunkDataSize); + pWav->onWrite(pWav->pUserData, &dataChunkSize, 4); + } + } else { + // The "RIFF" chunk size. + if (pWav->onSeek(pWav->pUserData, 16, drwav_seek_origin_start)) { + drwav_uint64 riffChunkSize = drwav_riff_chunk_size_w64(pWav->dataChunkDataSize); + pWav->onWrite(pWav->pUserData, &riffChunkSize, 8); + } + + // The "data" chunk size. + if (pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos + 16, drwav_seek_origin_start)) { + drwav_uint64 dataChunkSize = drwav_data_chunk_size_w64(pWav->dataChunkDataSize); + pWav->onWrite(pWav->pUserData, &dataChunkSize, 8); + } + } + } + } + +#ifndef DR_WAV_NO_STDIO + // If we opened the file with drwav_open_file() we will want to close the file handle. We can know whether or not drwav_open_file() + // was used by looking at the onRead and onSeek callbacks. + if (pWav->onRead == drwav__on_read_stdio || pWav->onWrite == drwav__on_write_stdio) { + fclose((FILE*)pWav->pUserData); + } +#endif +} + + +drwav* drwav_open(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData) +{ + return drwav_open_ex(onRead, onSeek, NULL, pUserData, NULL, 0); +} + +drwav* drwav_open_ex(drwav_read_proc onRead, drwav_seek_proc onSeek, drwav_chunk_proc onChunk, void* pReadSeekUserData, void* pChunkUserData, drwav_uint32 flags) +{ + drwav* pWav = (drwav*)DRWAV_MALLOC(sizeof(*pWav)); + if (pWav == NULL) { + return NULL; + } + + if (!drwav_init_ex(pWav, onRead, onSeek, onChunk, pReadSeekUserData, pChunkUserData, flags)) { + DRWAV_FREE(pWav); + return NULL; + } + + return pWav; +} + + +drwav* drwav_open_write__internal(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_bool32 isSequential, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData) +{ + drwav* pWav = (drwav*)DRWAV_MALLOC(sizeof(*pWav)); + if (pWav == NULL) { + return NULL; + } + + if (!drwav_init_write__internal(pWav, pFormat, totalSampleCount, isSequential, onWrite, onSeek, pUserData)) { + DRWAV_FREE(pWav); + return NULL; + } + + return pWav; +} + +drwav* drwav_open_write(const drwav_data_format* pFormat, drwav_write_proc onWrite, drwav_seek_proc onSeek, void* pUserData) +{ + return drwav_open_write__internal(pFormat, 0, DRWAV_FALSE, onWrite, onSeek, pUserData); +} + +drwav* drwav_open_write_sequential(const drwav_data_format* pFormat, drwav_uint64 totalSampleCount, drwav_write_proc onWrite, void* pUserData) +{ + return drwav_open_write__internal(pFormat, totalSampleCount, DRWAV_TRUE, onWrite, NULL, pUserData); +} + +void drwav_close(drwav* pWav) +{ + drwav_uninit(pWav); + DRWAV_FREE(pWav); +} + + +size_t drwav_read_raw(drwav* pWav, size_t bytesToRead, void* pBufferOut) +{ + if (pWav == NULL || bytesToRead == 0 || pBufferOut == NULL) { + return 0; + } + + if (bytesToRead > pWav->bytesRemaining) { + bytesToRead = (size_t)pWav->bytesRemaining; + } + + size_t bytesRead = pWav->onRead(pWav->pUserData, pBufferOut, bytesToRead); + + pWav->bytesRemaining -= bytesRead; + return bytesRead; +} + +drwav_uint64 drwav_read(drwav* pWav, drwav_uint64 samplesToRead, void* pBufferOut) +{ + if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) { + return 0; + } + + // Cannot use this function for compressed formats. + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + return 0; + } + + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + // Don't try to read more samples than can potentially fit in the output buffer. + if (samplesToRead * bytesPerSample > DRWAV_SIZE_MAX) { + samplesToRead = DRWAV_SIZE_MAX / bytesPerSample; + } + + size_t bytesRead = drwav_read_raw(pWav, (size_t)(samplesToRead * bytesPerSample), pBufferOut); + return bytesRead / bytesPerSample; +} + +drwav_uint64 drwav_read_pcm_frames(drwav* pWav, drwav_uint64 framesToRead, void* pBufferOut) +{ + if (pWav == NULL || framesToRead == 0 || pBufferOut == NULL) { + return 0; + } + + // Cannot use this function for compressed formats. + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + return 0; + } + + drwav_uint32 bytesPerFrame = drwav_get_bytes_per_pcm_frame(pWav); + if (bytesPerFrame == 0) { + return 0; + } + + // Don't try to read more samples than can potentially fit in the output buffer. + if (framesToRead * bytesPerFrame > DRWAV_SIZE_MAX) { + framesToRead = DRWAV_SIZE_MAX / bytesPerFrame; + } + + size_t bytesRead = drwav_read_raw(pWav, (size_t)(framesToRead * bytesPerFrame), pBufferOut); + return bytesRead / bytesPerFrame; +} + +drwav_bool32 drwav_seek_to_first_pcm_frame(drwav* pWav) +{ + if (pWav->onWrite != NULL) { + return DRWAV_FALSE; // No seeking in write mode. + } + + if (!pWav->onSeek(pWav->pUserData, (int)pWav->dataChunkDataPos, drwav_seek_origin_start)) { + return DRWAV_FALSE; + } + + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + pWav->compressed.iCurrentSample = 0; + } + + pWav->bytesRemaining = pWav->dataChunkDataSize; + return DRWAV_TRUE; +} + +drwav_bool32 drwav_seek_to_sample(drwav* pWav, drwav_uint64 sample) +{ + // Seeking should be compatible with wave files > 2GB. + + if (pWav->onWrite != NULL) { + return DRWAV_FALSE; // No seeking in write mode. + } + + if (pWav == NULL || pWav->onSeek == NULL) { + return DRWAV_FALSE; + } + + // If there are no samples, just return DRWAV_TRUE without doing anything. + if (pWav->totalSampleCount == 0) { + return DRWAV_TRUE; + } + + // Make sure the sample is clamped. + if (sample >= pWav->totalSampleCount) { + sample = pWav->totalSampleCount - 1; + } + + + // For compressed formats we just use a slow generic seek. If we are seeking forward we just seek forward. If we are going backwards we need + // to seek back to the start. + if (drwav__is_compressed_format_tag(pWav->translatedFormatTag)) { + // TODO: This can be optimized. + + // If we're seeking forward it's simple - just keep reading samples until we hit the sample we're requesting. If we're seeking backwards, + // we first need to seek back to the start and then just do the same thing as a forward seek. + if (sample < pWav->compressed.iCurrentSample) { + if (!drwav_seek_to_first_pcm_frame(pWav)) { + return DRWAV_FALSE; + } + } + + if (sample > pWav->compressed.iCurrentSample) { + drwav_uint64 offset = sample - pWav->compressed.iCurrentSample; + + drwav_int16 devnull[2048]; + while (offset > 0) { + drwav_uint64 samplesToRead = offset; + if (samplesToRead > 2048) { + samplesToRead = 2048; + } + + drwav_uint64 samplesRead = 0; + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + samplesRead = drwav_read_s16__msadpcm(pWav, samplesToRead, devnull); + } else if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + samplesRead = drwav_read_s16__ima(pWav, samplesToRead, devnull); + } else { + assert(DRWAV_FALSE); // If this assertion is triggered it means I've implemented a new compressed format but forgot to add a branch for it here. + } + + if (samplesRead != samplesToRead) { + return DRWAV_FALSE; + } + + offset -= samplesRead; + } + } + } else { + drwav_uint64 totalSizeInBytes = pWav->totalPCMFrameCount * drwav_get_bytes_per_pcm_frame(pWav); + drwav_assert(totalSizeInBytes >= pWav->bytesRemaining); + + drwav_uint64 currentBytePos = totalSizeInBytes - pWav->bytesRemaining; + drwav_uint64 targetBytePos = sample * drwav_get_bytes_per_sample(pWav); + + drwav_uint64 offset; + if (currentBytePos < targetBytePos) { + // Offset forwards. + offset = (targetBytePos - currentBytePos); + } else { + // Offset backwards. + if (!drwav_seek_to_first_pcm_frame(pWav)) { + return DRWAV_FALSE; + } + offset = targetBytePos; + } + + while (offset > 0) { + int offset32 = ((offset > INT_MAX) ? INT_MAX : (int)offset); + if (!pWav->onSeek(pWav->pUserData, offset32, drwav_seek_origin_current)) { + return DRWAV_FALSE; + } + + pWav->bytesRemaining -= offset32; + offset -= offset32; + } + } + + return DRWAV_TRUE; +} + +drwav_bool32 drwav_seek_to_pcm_frame(drwav* pWav, drwav_uint64 targetFrameIndex) +{ + return drwav_seek_to_sample(pWav, targetFrameIndex * pWav->channels); +} + + +size_t drwav_write_raw(drwav* pWav, size_t bytesToWrite, const void* pData) +{ + if (pWav == NULL || bytesToWrite == 0 || pData == NULL) { + return 0; + } + + size_t bytesWritten = pWav->onWrite(pWav->pUserData, pData, bytesToWrite); + pWav->dataChunkDataSize += bytesWritten; + + return bytesWritten; +} + +drwav_uint64 drwav_write(drwav* pWav, drwav_uint64 samplesToWrite, const void* pData) +{ + if (pWav == NULL || samplesToWrite == 0 || pData == NULL) { + return 0; + } + + drwav_uint64 bytesToWrite = ((samplesToWrite * pWav->bitsPerSample) / 8); + if (bytesToWrite > DRWAV_SIZE_MAX) { + return 0; + } + + drwav_uint64 bytesWritten = 0; + const drwav_uint8* pRunningData = (const drwav_uint8*)pData; + while (bytesToWrite > 0) { + drwav_uint64 bytesToWriteThisIteration = bytesToWrite; + if (bytesToWriteThisIteration > DRWAV_SIZE_MAX) { + bytesToWriteThisIteration = DRWAV_SIZE_MAX; + } + + size_t bytesJustWritten = drwav_write_raw(pWav, (size_t)bytesToWriteThisIteration, pRunningData); + if (bytesJustWritten == 0) { + break; + } + + bytesToWrite -= bytesJustWritten; + bytesWritten += bytesJustWritten; + pRunningData += bytesJustWritten; + } + + return (bytesWritten * 8) / pWav->bitsPerSample; +} + +drwav_uint64 drwav_write_pcm_frames(drwav* pWav, drwav_uint64 framesToWrite, const void* pData) +{ + return drwav_write(pWav, framesToWrite * pWav->channels, pData) / pWav->channels; +} + + + +drwav_uint64 drwav_read_s16__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + drwav_assert(pWav != NULL); + drwav_assert(samplesToRead > 0); + drwav_assert(pBufferOut != NULL); + + // TODO: Lots of room for optimization here. + + drwav_uint64 totalSamplesRead = 0; + + while (samplesToRead > 0 && pWav->compressed.iCurrentSample < pWav->totalSampleCount) { + // If there are no cached samples we need to load a new block. + if (pWav->msadpcm.cachedSampleCount == 0 && pWav->msadpcm.bytesRemainingInBlock == 0) { + if (pWav->channels == 1) { + // Mono. + drwav_uint8 header[7]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalSamplesRead; + } + pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->msadpcm.predictor[0] = header[0]; + pWav->msadpcm.delta[0] = drwav__bytes_to_s16(header + 1); + pWav->msadpcm.prevSamples[0][1] = (drwav_int32)drwav__bytes_to_s16(header + 3); + pWav->msadpcm.prevSamples[0][0] = (drwav_int32)drwav__bytes_to_s16(header + 5); + pWav->msadpcm.cachedSamples[2] = pWav->msadpcm.prevSamples[0][0]; + pWav->msadpcm.cachedSamples[3] = pWav->msadpcm.prevSamples[0][1]; + pWav->msadpcm.cachedSampleCount = 2; + } else { + // Stereo. + drwav_uint8 header[14]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalSamplesRead; + } + pWav->msadpcm.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->msadpcm.predictor[0] = header[0]; + pWav->msadpcm.predictor[1] = header[1]; + pWav->msadpcm.delta[0] = drwav__bytes_to_s16(header + 2); + pWav->msadpcm.delta[1] = drwav__bytes_to_s16(header + 4); + pWav->msadpcm.prevSamples[0][1] = (drwav_int32)drwav__bytes_to_s16(header + 6); + pWav->msadpcm.prevSamples[1][1] = (drwav_int32)drwav__bytes_to_s16(header + 8); + pWav->msadpcm.prevSamples[0][0] = (drwav_int32)drwav__bytes_to_s16(header + 10); + pWav->msadpcm.prevSamples[1][0] = (drwav_int32)drwav__bytes_to_s16(header + 12); + + pWav->msadpcm.cachedSamples[0] = pWav->msadpcm.prevSamples[0][0]; + pWav->msadpcm.cachedSamples[1] = pWav->msadpcm.prevSamples[1][0]; + pWav->msadpcm.cachedSamples[2] = pWav->msadpcm.prevSamples[0][1]; + pWav->msadpcm.cachedSamples[3] = pWav->msadpcm.prevSamples[1][1]; + pWav->msadpcm.cachedSampleCount = 4; + } + } + + // Output anything that's cached. + while (samplesToRead > 0 && pWav->msadpcm.cachedSampleCount > 0 && pWav->compressed.iCurrentSample < pWav->totalSampleCount) { + pBufferOut[0] = (drwav_int16)pWav->msadpcm.cachedSamples[drwav_countof(pWav->msadpcm.cachedSamples) - pWav->msadpcm.cachedSampleCount]; + pWav->msadpcm.cachedSampleCount -= 1; + + pBufferOut += 1; + samplesToRead -= 1; + totalSamplesRead += 1; + pWav->compressed.iCurrentSample += 1; + } + + if (samplesToRead == 0) { + return totalSamplesRead; + } + + + // If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next + // loop iteration which will trigger the loading of a new block. + if (pWav->msadpcm.cachedSampleCount == 0) { + if (pWav->msadpcm.bytesRemainingInBlock == 0) { + continue; + } else { + drwav_uint8 nibbles; + if (pWav->onRead(pWav->pUserData, &nibbles, 1) != 1) { + return totalSamplesRead; + } + pWav->msadpcm.bytesRemainingInBlock -= 1; + + // TODO: Optimize away these if statements. + drwav_int32 nibble0 = ((nibbles & 0xF0) >> 4); if ((nibbles & 0x80)) { nibble0 |= 0xFFFFFFF0UL; } + drwav_int32 nibble1 = ((nibbles & 0x0F) >> 0); if ((nibbles & 0x08)) { nibble1 |= 0xFFFFFFF0UL; } + + static drwav_int32 adaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 + }; + static drwav_int32 coeff1Table[] = { 256, 512, 0, 192, 240, 460, 392 }; + static drwav_int32 coeff2Table[] = { 0, -256, 0, 64, 0, -208, -232 }; + + if (pWav->channels == 1) { + // Mono. + drwav_int32 newSample0; + newSample0 = ((pWav->msadpcm.prevSamples[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevSamples[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample0 += nibble0 * pWav->msadpcm.delta[0]; + newSample0 = drwav_clamp(newSample0, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevSamples[0][0] = pWav->msadpcm.prevSamples[0][1]; + pWav->msadpcm.prevSamples[0][1] = newSample0; + + + drwav_int32 newSample1; + newSample1 = ((pWav->msadpcm.prevSamples[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevSamples[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample1 += nibble1 * pWav->msadpcm.delta[0]; + newSample1 = drwav_clamp(newSample1, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevSamples[0][0] = pWav->msadpcm.prevSamples[0][1]; + pWav->msadpcm.prevSamples[0][1] = newSample1; + + + pWav->msadpcm.cachedSamples[2] = newSample0; + pWav->msadpcm.cachedSamples[3] = newSample1; + pWav->msadpcm.cachedSampleCount = 2; + } else { + // Stereo. + + // Left. + drwav_int32 newSample0; + newSample0 = ((pWav->msadpcm.prevSamples[0][1] * coeff1Table[pWav->msadpcm.predictor[0]]) + (pWav->msadpcm.prevSamples[0][0] * coeff2Table[pWav->msadpcm.predictor[0]])) >> 8; + newSample0 += nibble0 * pWav->msadpcm.delta[0]; + newSample0 = drwav_clamp(newSample0, -32768, 32767); + + pWav->msadpcm.delta[0] = (adaptationTable[((nibbles & 0xF0) >> 4)] * pWav->msadpcm.delta[0]) >> 8; + if (pWav->msadpcm.delta[0] < 16) { + pWav->msadpcm.delta[0] = 16; + } + + pWav->msadpcm.prevSamples[0][0] = pWav->msadpcm.prevSamples[0][1]; + pWav->msadpcm.prevSamples[0][1] = newSample0; + + + // Right. + drwav_int32 newSample1; + newSample1 = ((pWav->msadpcm.prevSamples[1][1] * coeff1Table[pWav->msadpcm.predictor[1]]) + (pWav->msadpcm.prevSamples[1][0] * coeff2Table[pWav->msadpcm.predictor[1]])) >> 8; + newSample1 += nibble1 * pWav->msadpcm.delta[1]; + newSample1 = drwav_clamp(newSample1, -32768, 32767); + + pWav->msadpcm.delta[1] = (adaptationTable[((nibbles & 0x0F) >> 0)] * pWav->msadpcm.delta[1]) >> 8; + if (pWav->msadpcm.delta[1] < 16) { + pWav->msadpcm.delta[1] = 16; + } + + pWav->msadpcm.prevSamples[1][0] = pWav->msadpcm.prevSamples[1][1]; + pWav->msadpcm.prevSamples[1][1] = newSample1; + + pWav->msadpcm.cachedSamples[2] = newSample0; + pWav->msadpcm.cachedSamples[3] = newSample1; + pWav->msadpcm.cachedSampleCount = 2; + } + } + } + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s16__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + drwav_assert(pWav != NULL); + drwav_assert(samplesToRead > 0); + drwav_assert(pBufferOut != NULL); + + // TODO: Lots of room for optimization here. + + drwav_uint64 totalSamplesRead = 0; + + while (samplesToRead > 0 && pWav->compressed.iCurrentSample < pWav->totalSampleCount) { + // If there are no cached samples we need to load a new block. + if (pWav->ima.cachedSampleCount == 0 && pWav->ima.bytesRemainingInBlock == 0) { + if (pWav->channels == 1) { + // Mono. + drwav_uint8 header[4]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalSamplesRead; + } + pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->ima.predictor[0] = drwav__bytes_to_s16(header + 0); + pWav->ima.stepIndex[0] = header[2]; + pWav->ima.cachedSamples[drwav_countof(pWav->ima.cachedSamples) - 1] = pWav->ima.predictor[0]; + pWav->ima.cachedSampleCount = 1; + } else { + // Stereo. + drwav_uint8 header[8]; + if (pWav->onRead(pWav->pUserData, header, sizeof(header)) != sizeof(header)) { + return totalSamplesRead; + } + pWav->ima.bytesRemainingInBlock = pWav->fmt.blockAlign - sizeof(header); + + pWav->ima.predictor[0] = drwav__bytes_to_s16(header + 0); + pWav->ima.stepIndex[0] = header[2]; + pWav->ima.predictor[1] = drwav__bytes_to_s16(header + 4); + pWav->ima.stepIndex[1] = header[6]; + + pWav->ima.cachedSamples[drwav_countof(pWav->ima.cachedSamples) - 2] = pWav->ima.predictor[0]; + pWav->ima.cachedSamples[drwav_countof(pWav->ima.cachedSamples) - 1] = pWav->ima.predictor[1]; + pWav->ima.cachedSampleCount = 2; + } + } + + // Output anything that's cached. + while (samplesToRead > 0 && pWav->ima.cachedSampleCount > 0 && pWav->compressed.iCurrentSample < pWav->totalSampleCount) { + pBufferOut[0] = (drwav_int16)pWav->ima.cachedSamples[drwav_countof(pWav->ima.cachedSamples) - pWav->ima.cachedSampleCount]; + pWav->ima.cachedSampleCount -= 1; + + pBufferOut += 1; + samplesToRead -= 1; + totalSamplesRead += 1; + pWav->compressed.iCurrentSample += 1; + } + + if (samplesToRead == 0) { + return totalSamplesRead; + } + + // If there's nothing left in the cache, just go ahead and load more. If there's nothing left to load in the current block we just continue to the next + // loop iteration which will trigger the loading of a new block. + if (pWav->ima.cachedSampleCount == 0) { + if (pWav->ima.bytesRemainingInBlock == 0) { + continue; + } else { + static drwav_int32 indexTable[16] = { + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 + }; + + static drwav_int32 stepTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, + 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, + 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, + 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, + 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, + 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 + }; + + // From what I can tell with stereo streams, it looks like every 4 bytes (8 samples) is for one channel. So it goes 4 bytes for the + // left channel, 4 bytes for the right channel. + pWav->ima.cachedSampleCount = 8 * pWav->channels; + for (drwav_uint32 iChannel = 0; iChannel < pWav->channels; ++iChannel) { + drwav_uint8 nibbles[4]; + if (pWav->onRead(pWav->pUserData, &nibbles, 4) != 4) { + return totalSamplesRead; + } + pWav->ima.bytesRemainingInBlock -= 4; + + for (drwav_uint32 iByte = 0; iByte < 4; ++iByte) { + drwav_uint8 nibble0 = ((nibbles[iByte] & 0x0F) >> 0); + drwav_uint8 nibble1 = ((nibbles[iByte] & 0xF0) >> 4); + + drwav_int32 step = stepTable[pWav->ima.stepIndex[iChannel]]; + drwav_int32 predictor = pWav->ima.predictor[iChannel]; + + drwav_int32 diff = step >> 3; + if (nibble0 & 1) diff += step >> 2; + if (nibble0 & 2) diff += step >> 1; + if (nibble0 & 4) diff += step; + if (nibble0 & 8) diff = -diff; + + predictor = drwav_clamp(predictor + diff, -32768, 32767); + pWav->ima.predictor[iChannel] = predictor; + pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble0], 0, (drwav_int32)drwav_countof(stepTable)-1); + pWav->ima.cachedSamples[(drwav_countof(pWav->ima.cachedSamples) - pWav->ima.cachedSampleCount) + (iByte*2+0)*pWav->channels + iChannel] = predictor; + + + step = stepTable[pWav->ima.stepIndex[iChannel]]; + predictor = pWav->ima.predictor[iChannel]; + + diff = step >> 3; + if (nibble1 & 1) diff += step >> 2; + if (nibble1 & 2) diff += step >> 1; + if (nibble1 & 4) diff += step; + if (nibble1 & 8) diff = -diff; + + predictor = drwav_clamp(predictor + diff, -32768, 32767); + pWav->ima.predictor[iChannel] = predictor; + pWav->ima.stepIndex[iChannel] = drwav_clamp(pWav->ima.stepIndex[iChannel] + indexTable[nibble1], 0, (drwav_int32)drwav_countof(stepTable)-1); + pWav->ima.cachedSamples[(drwav_countof(pWav->ima.cachedSamples) - pWav->ima.cachedSampleCount) + (iByte*2+1)*pWav->channels + iChannel] = predictor; + } + } + } + } + } + + return totalSamplesRead; +} + + +#ifndef DR_WAV_NO_CONVERSION_API +static unsigned short g_drwavAlawTable[256] = { + 0xEA80, 0xEB80, 0xE880, 0xE980, 0xEE80, 0xEF80, 0xEC80, 0xED80, 0xE280, 0xE380, 0xE080, 0xE180, 0xE680, 0xE780, 0xE480, 0xE580, + 0xF540, 0xF5C0, 0xF440, 0xF4C0, 0xF740, 0xF7C0, 0xF640, 0xF6C0, 0xF140, 0xF1C0, 0xF040, 0xF0C0, 0xF340, 0xF3C0, 0xF240, 0xF2C0, + 0xAA00, 0xAE00, 0xA200, 0xA600, 0xBA00, 0xBE00, 0xB200, 0xB600, 0x8A00, 0x8E00, 0x8200, 0x8600, 0x9A00, 0x9E00, 0x9200, 0x9600, + 0xD500, 0xD700, 0xD100, 0xD300, 0xDD00, 0xDF00, 0xD900, 0xDB00, 0xC500, 0xC700, 0xC100, 0xC300, 0xCD00, 0xCF00, 0xC900, 0xCB00, + 0xFEA8, 0xFEB8, 0xFE88, 0xFE98, 0xFEE8, 0xFEF8, 0xFEC8, 0xFED8, 0xFE28, 0xFE38, 0xFE08, 0xFE18, 0xFE68, 0xFE78, 0xFE48, 0xFE58, + 0xFFA8, 0xFFB8, 0xFF88, 0xFF98, 0xFFE8, 0xFFF8, 0xFFC8, 0xFFD8, 0xFF28, 0xFF38, 0xFF08, 0xFF18, 0xFF68, 0xFF78, 0xFF48, 0xFF58, + 0xFAA0, 0xFAE0, 0xFA20, 0xFA60, 0xFBA0, 0xFBE0, 0xFB20, 0xFB60, 0xF8A0, 0xF8E0, 0xF820, 0xF860, 0xF9A0, 0xF9E0, 0xF920, 0xF960, + 0xFD50, 0xFD70, 0xFD10, 0xFD30, 0xFDD0, 0xFDF0, 0xFD90, 0xFDB0, 0xFC50, 0xFC70, 0xFC10, 0xFC30, 0xFCD0, 0xFCF0, 0xFC90, 0xFCB0, + 0x1580, 0x1480, 0x1780, 0x1680, 0x1180, 0x1080, 0x1380, 0x1280, 0x1D80, 0x1C80, 0x1F80, 0x1E80, 0x1980, 0x1880, 0x1B80, 0x1A80, + 0x0AC0, 0x0A40, 0x0BC0, 0x0B40, 0x08C0, 0x0840, 0x09C0, 0x0940, 0x0EC0, 0x0E40, 0x0FC0, 0x0F40, 0x0CC0, 0x0C40, 0x0DC0, 0x0D40, + 0x5600, 0x5200, 0x5E00, 0x5A00, 0x4600, 0x4200, 0x4E00, 0x4A00, 0x7600, 0x7200, 0x7E00, 0x7A00, 0x6600, 0x6200, 0x6E00, 0x6A00, + 0x2B00, 0x2900, 0x2F00, 0x2D00, 0x2300, 0x2100, 0x2700, 0x2500, 0x3B00, 0x3900, 0x3F00, 0x3D00, 0x3300, 0x3100, 0x3700, 0x3500, + 0x0158, 0x0148, 0x0178, 0x0168, 0x0118, 0x0108, 0x0138, 0x0128, 0x01D8, 0x01C8, 0x01F8, 0x01E8, 0x0198, 0x0188, 0x01B8, 0x01A8, + 0x0058, 0x0048, 0x0078, 0x0068, 0x0018, 0x0008, 0x0038, 0x0028, 0x00D8, 0x00C8, 0x00F8, 0x00E8, 0x0098, 0x0088, 0x00B8, 0x00A8, + 0x0560, 0x0520, 0x05E0, 0x05A0, 0x0460, 0x0420, 0x04E0, 0x04A0, 0x0760, 0x0720, 0x07E0, 0x07A0, 0x0660, 0x0620, 0x06E0, 0x06A0, + 0x02B0, 0x0290, 0x02F0, 0x02D0, 0x0230, 0x0210, 0x0270, 0x0250, 0x03B0, 0x0390, 0x03F0, 0x03D0, 0x0330, 0x0310, 0x0370, 0x0350 +}; + +static unsigned short g_drwavMulawTable[256] = { + 0x8284, 0x8684, 0x8A84, 0x8E84, 0x9284, 0x9684, 0x9A84, 0x9E84, 0xA284, 0xA684, 0xAA84, 0xAE84, 0xB284, 0xB684, 0xBA84, 0xBE84, + 0xC184, 0xC384, 0xC584, 0xC784, 0xC984, 0xCB84, 0xCD84, 0xCF84, 0xD184, 0xD384, 0xD584, 0xD784, 0xD984, 0xDB84, 0xDD84, 0xDF84, + 0xE104, 0xE204, 0xE304, 0xE404, 0xE504, 0xE604, 0xE704, 0xE804, 0xE904, 0xEA04, 0xEB04, 0xEC04, 0xED04, 0xEE04, 0xEF04, 0xF004, + 0xF0C4, 0xF144, 0xF1C4, 0xF244, 0xF2C4, 0xF344, 0xF3C4, 0xF444, 0xF4C4, 0xF544, 0xF5C4, 0xF644, 0xF6C4, 0xF744, 0xF7C4, 0xF844, + 0xF8A4, 0xF8E4, 0xF924, 0xF964, 0xF9A4, 0xF9E4, 0xFA24, 0xFA64, 0xFAA4, 0xFAE4, 0xFB24, 0xFB64, 0xFBA4, 0xFBE4, 0xFC24, 0xFC64, + 0xFC94, 0xFCB4, 0xFCD4, 0xFCF4, 0xFD14, 0xFD34, 0xFD54, 0xFD74, 0xFD94, 0xFDB4, 0xFDD4, 0xFDF4, 0xFE14, 0xFE34, 0xFE54, 0xFE74, + 0xFE8C, 0xFE9C, 0xFEAC, 0xFEBC, 0xFECC, 0xFEDC, 0xFEEC, 0xFEFC, 0xFF0C, 0xFF1C, 0xFF2C, 0xFF3C, 0xFF4C, 0xFF5C, 0xFF6C, 0xFF7C, + 0xFF88, 0xFF90, 0xFF98, 0xFFA0, 0xFFA8, 0xFFB0, 0xFFB8, 0xFFC0, 0xFFC8, 0xFFD0, 0xFFD8, 0xFFE0, 0xFFE8, 0xFFF0, 0xFFF8, 0x0000, + 0x7D7C, 0x797C, 0x757C, 0x717C, 0x6D7C, 0x697C, 0x657C, 0x617C, 0x5D7C, 0x597C, 0x557C, 0x517C, 0x4D7C, 0x497C, 0x457C, 0x417C, + 0x3E7C, 0x3C7C, 0x3A7C, 0x387C, 0x367C, 0x347C, 0x327C, 0x307C, 0x2E7C, 0x2C7C, 0x2A7C, 0x287C, 0x267C, 0x247C, 0x227C, 0x207C, + 0x1EFC, 0x1DFC, 0x1CFC, 0x1BFC, 0x1AFC, 0x19FC, 0x18FC, 0x17FC, 0x16FC, 0x15FC, 0x14FC, 0x13FC, 0x12FC, 0x11FC, 0x10FC, 0x0FFC, + 0x0F3C, 0x0EBC, 0x0E3C, 0x0DBC, 0x0D3C, 0x0CBC, 0x0C3C, 0x0BBC, 0x0B3C, 0x0ABC, 0x0A3C, 0x09BC, 0x093C, 0x08BC, 0x083C, 0x07BC, + 0x075C, 0x071C, 0x06DC, 0x069C, 0x065C, 0x061C, 0x05DC, 0x059C, 0x055C, 0x051C, 0x04DC, 0x049C, 0x045C, 0x041C, 0x03DC, 0x039C, + 0x036C, 0x034C, 0x032C, 0x030C, 0x02EC, 0x02CC, 0x02AC, 0x028C, 0x026C, 0x024C, 0x022C, 0x020C, 0x01EC, 0x01CC, 0x01AC, 0x018C, + 0x0174, 0x0164, 0x0154, 0x0144, 0x0134, 0x0124, 0x0114, 0x0104, 0x00F4, 0x00E4, 0x00D4, 0x00C4, 0x00B4, 0x00A4, 0x0094, 0x0084, + 0x0078, 0x0070, 0x0068, 0x0060, 0x0058, 0x0050, 0x0048, 0x0040, 0x0038, 0x0030, 0x0028, 0x0020, 0x0018, 0x0010, 0x0008, 0x0000 +}; + +static DRWAV_INLINE drwav_int16 drwav__alaw_to_s16(drwav_uint8 sampleIn) +{ + return (short)g_drwavAlawTable[sampleIn]; +} + +static DRWAV_INLINE drwav_int16 drwav__mulaw_to_s16(drwav_uint8 sampleIn) +{ + return (short)g_drwavMulawTable[sampleIn]; +} + + + +static void drwav__pcm_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + // Special case for 8-bit sample data because it's treated as unsigned. + if (bytesPerSample == 1) { + drwav_u8_to_s16(pOut, pIn, totalSampleCount); + return; + } + + + // Slightly more optimal implementation for common formats. + if (bytesPerSample == 2) { + for (unsigned int i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((const drwav_int16*)pIn)[i]; + } + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_s16(pOut, pIn, totalSampleCount); + return; + } + if (bytesPerSample == 4) { + drwav_s32_to_s16(pOut, (const drwav_int32*)pIn, totalSampleCount); + return; + } + + + // Anything more than 64 bits per sample is not supported. + if (bytesPerSample > 8) { + drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); + return; + } + + + // Generic, slow converter. + for (unsigned int i = 0; i < totalSampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample && j < 8; j += 1) { + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (drwav_int16)((drwav_int64)sample >> 48); + } +} + +static void drwav__ieee_to_s16(drwav_int16* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + if (bytesPerSample == 4) { + drwav_f32_to_s16(pOut, (const float*)pIn, totalSampleCount); + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_s16(pOut, (const double*)pIn, totalSampleCount); + return; + } else { + // Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. + drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); + return; + } +} + +drwav_uint64 drwav_read_s16__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + drwav_uint32 bytesPerSample; + + // Fast path. + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 16) { + return drwav_read(pWav, samplesToRead, pBufferOut); + } + + bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__pcm_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s16__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__ieee_to_s16(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s16__alaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav_alaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s16__mulaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav_mulaw_to_s16(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s16(drwav* pWav, drwav_uint64 samplesToRead, drwav_int16* pBufferOut) +{ + if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) { + return 0; + } + + // Don't try to read more samples than can potentially fit in the output buffer. + if (samplesToRead * sizeof(drwav_int16) > DRWAV_SIZE_MAX) { + samplesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int16); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_s16__pcm(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + return drwav_read_s16__msadpcm(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_s16__ieee(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_s16__alaw(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_s16__mulaw(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_s16__ima(pWav, samplesToRead, pBufferOut); + } + + return 0; +} + +drwav_uint64 drwav_read_pcm_frames_s16(drwav* pWav, drwav_uint64 framesToRead, drwav_int16* pBufferOut) +{ + return drwav_read_s16(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels; +} + +void drwav_u8_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + int r; + for (size_t i = 0; i < sampleCount; ++i) { + int x = pIn[i]; + r = x - 128; + r = r << 8; + pOut[i] = (short)r; + } +} + +void drwav_s24_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + int r; + for (size_t i = 0; i < sampleCount; ++i) { + int x = ((int)(((unsigned int)(((const unsigned char*)pIn)[i*3+0]) << 8) | ((unsigned int)(((const unsigned char*)pIn)[i*3+1]) << 16) | ((unsigned int)(((const unsigned char*)pIn)[i*3+2])) << 24)) >> 8; + r = x >> 8; + pOut[i] = (short)r; + } +} + +void drwav_s32_to_s16(drwav_int16* pOut, const drwav_int32* pIn, size_t sampleCount) +{ + int r; + for (size_t i = 0; i < sampleCount; ++i) { + int x = pIn[i]; + r = x >> 16; + pOut[i] = (short)r; + } +} + +void drwav_f32_to_s16(drwav_int16* pOut, const float* pIn, size_t sampleCount) +{ + int r; + for (size_t i = 0; i < sampleCount; ++i) { + float x = pIn[i]; + float c; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + c = c + 1; + r = (int)(c * 32767.5f); + r = r - 32768; + pOut[i] = (short)r; + } +} + +void drwav_f64_to_s16(drwav_int16* pOut, const double* pIn, size_t sampleCount) +{ + int r; + for (size_t i = 0; i < sampleCount; ++i) { + double x = pIn[i]; + double c; + c = ((x < -1) ? -1 : ((x > 1) ? 1 : x)); + c = c + 1; + r = (int)(c * 32767.5); + r = r - 32768; + pOut[i] = (short)r; + } +} + +void drwav_alaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + for (size_t i = 0; i < sampleCount; ++i) { + pOut[i] = drwav__alaw_to_s16(pIn[i]); + } +} + +void drwav_mulaw_to_s16(drwav_int16* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + for (size_t i = 0; i < sampleCount; ++i) { + pOut[i] = drwav__mulaw_to_s16(pIn[i]); + } +} + + + +static void drwav__pcm_to_f32(float* pOut, const unsigned char* pIn, size_t sampleCount, unsigned int bytesPerSample) +{ + // Special case for 8-bit sample data because it's treated as unsigned. + if (bytesPerSample == 1) { + drwav_u8_to_f32(pOut, pIn, sampleCount); + return; + } + + // Slightly more optimal implementation for common formats. + if (bytesPerSample == 2) { + drwav_s16_to_f32(pOut, (const drwav_int16*)pIn, sampleCount); + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_f32(pOut, pIn, sampleCount); + return; + } + if (bytesPerSample == 4) { + drwav_s32_to_f32(pOut, (const drwav_int32*)pIn, sampleCount); + return; + } + + + // Anything more than 64 bits per sample is not supported. + if (bytesPerSample > 8) { + drwav_zero_memory(pOut, sampleCount * sizeof(*pOut)); + return; + } + + + // Generic, slow converter. + for (unsigned int i = 0; i < sampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample && j < 8; j += 1) { + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (float)((drwav_int64)sample / 9223372036854775807.0); + } +} + +static void drwav__ieee_to_f32(float* pOut, const unsigned char* pIn, size_t sampleCount, unsigned int bytesPerSample) +{ + if (bytesPerSample == 4) { + for (unsigned int i = 0; i < sampleCount; ++i) { + *pOut++ = ((const float*)pIn)[i]; + } + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_f32(pOut, (const double*)pIn, sampleCount); + return; + } else { + // Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. + drwav_zero_memory(pOut, sampleCount * sizeof(*pOut)); + return; + } +} + + +drwav_uint64 drwav_read_f32__pcm(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__pcm_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + pBufferOut += samplesRead; + + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_f32__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + // We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't + // want to duplicate that code. + drwav_uint64 totalSamplesRead = 0; + drwav_int16 samples16[2048]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read_s16(pWav, drwav_min(samplesToRead, 2048), samples16); + if (samplesRead == 0) { + break; + } + + drwav_s16_to_f32(pBufferOut, samples16, (size_t)samplesRead); // <-- Safe cast because we're clamping to 2048. + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_f32__ima(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + // We're just going to borrow the implementation from the drwav_read_s16() since IMA-ADPCM is a little bit more complicated than other formats and I don't + // want to duplicate that code. + drwav_uint64 totalSamplesRead = 0; + drwav_int16 samples16[2048]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read_s16(pWav, drwav_min(samplesToRead, 2048), samples16); + if (samplesRead == 0) { + break; + } + + drwav_s16_to_f32(pBufferOut, samples16, (size_t)samplesRead); // <-- Safe cast because we're clamping to 2048. + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_f32__ieee(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + drwav_uint32 bytesPerSample; + + // Fast path. + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT && pWav->bitsPerSample == 32) { + return drwav_read(pWav, samplesToRead, pBufferOut); + } + + bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__ieee_to_f32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_f32__alaw(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav_alaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_f32__mulaw(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav_mulaw_to_f32(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_f32(drwav* pWav, drwav_uint64 samplesToRead, float* pBufferOut) +{ + if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) { + return 0; + } + + // Don't try to read more samples than can potentially fit in the output buffer. + if (samplesToRead * sizeof(float) > DRWAV_SIZE_MAX) { + samplesToRead = DRWAV_SIZE_MAX / sizeof(float); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_f32__pcm(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + return drwav_read_f32__msadpcm(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_f32__ieee(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_f32__alaw(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_f32__mulaw(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_f32__ima(pWav, samplesToRead, pBufferOut); + } + + return 0; +} + +drwav_uint64 drwav_read_pcm_frames_f32(drwav* pWav, drwav_uint64 framesToRead, float* pBufferOut) +{ + return drwav_read_f32(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels; +} + +void drwav_u8_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + +#ifdef DR_WAV_LIBSNDFILE_COMPAT + // It appears libsndfile uses slightly different logic for the u8 -> f32 conversion to dr_wav, which in my opinion is incorrect. It appears + // libsndfile performs the conversion something like "f32 = (u8 / 256) * 2 - 1", however I think it should be "f32 = (u8 / 255) * 2 - 1" (note + // the divisor of 256 vs 255). I use libsndfile as a benchmark for testing, so I'm therefore leaving this block here just for my automated + // correctness testing. This is disabled by default. + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = (pIn[i] / 256.0f) * 2 - 1; + } +#else + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = (pIn[i] / 255.0f) * 2 - 1; + } +#endif +} + +void drwav_s16_to_f32(float* pOut, const drwav_int16* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = pIn[i] / 32768.0f; + } +} + +void drwav_s24_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + unsigned int s0 = pIn[i*3 + 0]; + unsigned int s1 = pIn[i*3 + 1]; + unsigned int s2 = pIn[i*3 + 2]; + + int sample32 = (int)((s0 << 8) | (s1 << 16) | (s2 << 24)); + *pOut++ = (float)(sample32 / 2147483648.0); + } +} + +void drwav_s32_to_f32(float* pOut, const drwav_int32* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = (float)(pIn[i] / 2147483648.0); + } +} + +void drwav_f64_to_f32(float* pOut, const double* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = (float)pIn[i]; + } +} + +void drwav_alaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = drwav__alaw_to_s16(pIn[i]) / 32768.0f; + } +} + +void drwav_mulaw_to_f32(float* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = drwav__mulaw_to_s16(pIn[i]) / 32768.0f; + } +} + + + +static void drwav__pcm_to_s32(drwav_int32* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + // Special case for 8-bit sample data because it's treated as unsigned. + if (bytesPerSample == 1) { + drwav_u8_to_s32(pOut, pIn, totalSampleCount); + return; + } + + // Slightly more optimal implementation for common formats. + if (bytesPerSample == 2) { + drwav_s16_to_s32(pOut, (const drwav_int16*)pIn, totalSampleCount); + return; + } + if (bytesPerSample == 3) { + drwav_s24_to_s32(pOut, pIn, totalSampleCount); + return; + } + if (bytesPerSample == 4) { + for (unsigned int i = 0; i < totalSampleCount; ++i) { + *pOut++ = ((const drwav_int32*)pIn)[i]; + } + return; + } + + + // Anything more than 64 bits per sample is not supported. + if (bytesPerSample > 8) { + drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); + return; + } + + + // Generic, slow converter. + for (unsigned int i = 0; i < totalSampleCount; ++i) { + drwav_uint64 sample = 0; + unsigned int shift = (8 - bytesPerSample) * 8; + + unsigned int j; + for (j = 0; j < bytesPerSample && j < 8; j += 1) { + sample |= (drwav_uint64)(pIn[j]) << shift; + shift += 8; + } + + pIn += j; + *pOut++ = (drwav_int32)((drwav_int64)sample >> 32); + } +} + +static void drwav__ieee_to_s32(drwav_int32* pOut, const unsigned char* pIn, size_t totalSampleCount, unsigned int bytesPerSample) +{ + if (bytesPerSample == 4) { + drwav_f32_to_s32(pOut, (const float*)pIn, totalSampleCount); + return; + } else if (bytesPerSample == 8) { + drwav_f64_to_s32(pOut, (const double*)pIn, totalSampleCount); + return; + } else { + // Only supporting 32- and 64-bit float. Output silence in all other cases. Contributions welcome for 16-bit float. + drwav_zero_memory(pOut, totalSampleCount * sizeof(*pOut)); + return; + } +} + + +drwav_uint64 drwav_read_s32__pcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + drwav_uint32 bytesPerSample; + + // Fast path. + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM && pWav->bitsPerSample == 32) { + return drwav_read(pWav, samplesToRead, pBufferOut); + } + + bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__pcm_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s32__msadpcm(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + // We're just going to borrow the implementation from the drwav_read_s16() since ADPCM is a little bit more complicated than other formats and I don't + // want to duplicate that code. + drwav_uint64 totalSamplesRead = 0; + drwav_int16 samples16[2048]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read_s16(pWav, drwav_min(samplesToRead, 2048), samples16); + if (samplesRead == 0) { + break; + } + + drwav_s16_to_s32(pBufferOut, samples16, (size_t)samplesRead); // <-- Safe cast because we're clamping to 2048. + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s32__ima(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + // We're just going to borrow the implementation from the drwav_read_s16() since IMA-ADPCM is a little bit more complicated than other formats and I don't + // want to duplicate that code. + drwav_uint64 totalSamplesRead = 0; + drwav_int16 samples16[2048]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read_s16(pWav, drwav_min(samplesToRead, 2048), samples16); + if (samplesRead == 0) { + break; + } + + drwav_s16_to_s32(pBufferOut, samples16, (size_t)samplesRead); // <-- Safe cast because we're clamping to 2048. + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s32__ieee(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav__ieee_to_s32(pBufferOut, sampleData, (size_t)samplesRead, bytesPerSample); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s32__alaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav_alaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s32__mulaw(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + drwav_uint32 bytesPerSample = drwav_get_bytes_per_sample(pWav); + if (bytesPerSample == 0) { + return 0; + } + + drwav_uint64 totalSamplesRead = 0; + unsigned char sampleData[4096]; + while (samplesToRead > 0) { + drwav_uint64 samplesRead = drwav_read(pWav, drwav_min(samplesToRead, sizeof(sampleData)/bytesPerSample), sampleData); + if (samplesRead == 0) { + break; + } + + drwav_mulaw_to_s32(pBufferOut, sampleData, (size_t)samplesRead); + + pBufferOut += samplesRead; + samplesToRead -= samplesRead; + totalSamplesRead += samplesRead; + } + + return totalSamplesRead; +} + +drwav_uint64 drwav_read_s32(drwav* pWav, drwav_uint64 samplesToRead, drwav_int32* pBufferOut) +{ + if (pWav == NULL || samplesToRead == 0 || pBufferOut == NULL) { + return 0; + } + + // Don't try to read more samples than can potentially fit in the output buffer. + if (samplesToRead * sizeof(drwav_int32) > DRWAV_SIZE_MAX) { + samplesToRead = DRWAV_SIZE_MAX / sizeof(drwav_int32); + } + + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_PCM) { + return drwav_read_s32__pcm(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ADPCM) { + return drwav_read_s32__msadpcm(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_IEEE_FLOAT) { + return drwav_read_s32__ieee(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_ALAW) { + return drwav_read_s32__alaw(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_MULAW) { + return drwav_read_s32__mulaw(pWav, samplesToRead, pBufferOut); + } + + if (pWav->translatedFormatTag == DR_WAVE_FORMAT_DVI_ADPCM) { + return drwav_read_s32__ima(pWav, samplesToRead, pBufferOut); + } + + return 0; +} + +drwav_uint64 drwav_read_pcm_frames_s32(drwav* pWav, drwav_uint64 framesToRead, drwav_int32* pBufferOut) +{ + return drwav_read_s32(pWav, framesToRead * pWav->channels, pBufferOut) / pWav->channels; +} + +void drwav_u8_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = ((int)pIn[i] - 128) << 24; + } +} + +void drwav_s16_to_s32(drwav_int32* pOut, const drwav_int16* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = pIn[i] << 16; + } +} + +void drwav_s24_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + unsigned int s0 = pIn[i*3 + 0]; + unsigned int s1 = pIn[i*3 + 1]; + unsigned int s2 = pIn[i*3 + 2]; + + drwav_int32 sample32 = (drwav_int32)((s0 << 8) | (s1 << 16) | (s2 << 24)); + *pOut++ = sample32; + } +} + +void drwav_f32_to_s32(drwav_int32* pOut, const float* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); + } +} + +void drwav_f64_to_s32(drwav_int32* pOut, const double* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = (drwav_int32)(2147483648.0 * pIn[i]); + } +} + +void drwav_alaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i = 0; i < sampleCount; ++i) { + *pOut++ = ((drwav_int32)drwav__alaw_to_s16(pIn[i])) << 16; + } +} + +void drwav_mulaw_to_s32(drwav_int32* pOut, const drwav_uint8* pIn, size_t sampleCount) +{ + if (pOut == NULL || pIn == NULL) { + return; + } + + for (size_t i= 0; i < sampleCount; ++i) { + *pOut++ = ((drwav_int32)drwav__mulaw_to_s16(pIn[i])) << 16; + } +} + + + +drwav_int16* drwav__read_and_close_s16(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + drwav_assert(pWav != NULL); + + drwav_uint64 sampleDataSize = pWav->totalSampleCount * sizeof(drwav_int16); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; // File's too big. + } + + drwav_int16* pSampleData = (drwav_int16*)DRWAV_MALLOC((size_t)sampleDataSize); // <-- Safe cast due to the check above. + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; // Failed to allocate memory. + } + + drwav_uint64 samplesRead = drwav_read_s16(pWav, (size_t)pWav->totalSampleCount, pSampleData); + if (samplesRead != pWav->totalSampleCount) { + DRWAV_FREE(pSampleData); + drwav_uninit(pWav); + return NULL; // There was an error reading the samples. + } + + drwav_uninit(pWav); + + if (sampleRate) *sampleRate = pWav->sampleRate; + if (channels) *channels = pWav->channels; + if (totalSampleCount) *totalSampleCount = pWav->totalSampleCount; + return pSampleData; +} + +float* drwav__read_and_close_f32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + drwav_assert(pWav != NULL); + + drwav_uint64 sampleDataSize = pWav->totalSampleCount * sizeof(float); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; // File's too big. + } + + float* pSampleData = (float*)DRWAV_MALLOC((size_t)sampleDataSize); // <-- Safe cast due to the check above. + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; // Failed to allocate memory. + } + + drwav_uint64 samplesRead = drwav_read_f32(pWav, (size_t)pWav->totalSampleCount, pSampleData); + if (samplesRead != pWav->totalSampleCount) { + DRWAV_FREE(pSampleData); + drwav_uninit(pWav); + return NULL; // There was an error reading the samples. + } + + drwav_uninit(pWav); + + if (sampleRate) *sampleRate = pWav->sampleRate; + if (channels) *channels = pWav->channels; + if (totalSampleCount) *totalSampleCount = pWav->totalSampleCount; + return pSampleData; +} + +drwav_int32* drwav__read_and_close_s32(drwav* pWav, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + drwav_assert(pWav != NULL); + + drwav_uint64 sampleDataSize = pWav->totalSampleCount * sizeof(drwav_int32); + if (sampleDataSize > DRWAV_SIZE_MAX) { + drwav_uninit(pWav); + return NULL; // File's too big. + } + + drwav_int32* pSampleData = (drwav_int32*)DRWAV_MALLOC((size_t)sampleDataSize); // <-- Safe cast due to the check above. + if (pSampleData == NULL) { + drwav_uninit(pWav); + return NULL; // Failed to allocate memory. + } + + drwav_uint64 samplesRead = drwav_read_s32(pWav, (size_t)pWav->totalSampleCount, pSampleData); + if (samplesRead != pWav->totalSampleCount) { + DRWAV_FREE(pSampleData); + drwav_uninit(pWav); + return NULL; // There was an error reading the samples. + } + + drwav_uninit(pWav); + + if (sampleRate) *sampleRate = pWav->sampleRate; + if (channels) *channels = pWav->channels; + if (totalSampleCount) *totalSampleCount = pWav->totalSampleCount; + return pSampleData; +} + + +drwav_int16* drwav_open_and_read_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (channels) *channels = 0; + if (sampleRate) *sampleRate = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init(&wav, onRead, onSeek, pUserData)) { + return NULL; + } + + return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount); +} + +drwav_int16* drwav_open_and_read_pcm_frames_s16(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int16* result = drwav_open_and_read_s16(onRead, onSeek, pUserData, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +float* drwav_open_and_read_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init(&wav, onRead, onSeek, pUserData)) { + return NULL; + } + + return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount); +} + +float* drwav_open_and_read_pcm_frames_f32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + float* result = drwav_open_and_read_f32(onRead, onSeek, pUserData, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +drwav_int32* drwav_open_and_read_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init(&wav, onRead, onSeek, pUserData)) { + return NULL; + } + + return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount); +} + +drwav_int32* drwav_open_and_read_pcm_frames_s32(drwav_read_proc onRead, drwav_seek_proc onSeek, void* pUserData, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int32* result = drwav_open_and_read_s32(onRead, onSeek, pUserData, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +#ifndef DR_WAV_NO_STDIO +drwav_int16* drwav_open_file_and_read_s16(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init_file(&wav, filename)) { + return NULL; + } + + return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount); +} + +drwav_int16* drwav_open_file_and_read_pcm_frames_s16(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int16* result = drwav_open_file_and_read_s16(filename, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +float* drwav_open_file_and_read_f32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init_file(&wav, filename)) { + return NULL; + } + + return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount); +} + +float* drwav_open_file_and_read_pcm_frames_f32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + float* result = drwav_open_file_and_read_f32(filename, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +drwav_int32* drwav_open_file_and_read_s32(const char* filename, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init_file(&wav, filename)) { + return NULL; + } + + return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount); +} + +drwav_int32* drwav_open_file_and_read_pcm_frames_s32(const char* filename, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int32* result = drwav_open_file_and_read_s32(filename, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} +#endif + +drwav_int16* drwav_open_memory_and_read_s16(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init_memory(&wav, data, dataSize)) { + return NULL; + } + + return drwav__read_and_close_s16(&wav, channels, sampleRate, totalSampleCount); +} + +drwav_int16* drwav_open_memory_and_read_pcm_frames_s16(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int16* result = drwav_open_memory_and_read_s16(data, dataSize, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +float* drwav_open_memory_and_read_f32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init_memory(&wav, data, dataSize)) { + return NULL; + } + + return drwav__read_and_close_f32(&wav, channels, sampleRate, totalSampleCount); +} + +float* drwav_open_memory_and_read_pcm_frames_f32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + float* result = drwav_open_memory_and_read_f32(data, dataSize, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} + +drwav_int32* drwav_open_memory_and_read_s32(const void* data, size_t dataSize, unsigned int* channels, unsigned int* sampleRate, drwav_uint64* totalSampleCount) +{ + if (sampleRate) *sampleRate = 0; + if (channels) *channels = 0; + if (totalSampleCount) *totalSampleCount = 0; + + drwav wav; + if (!drwav_init_memory(&wav, data, dataSize)) { + return NULL; + } + + return drwav__read_and_close_s32(&wav, channels, sampleRate, totalSampleCount); +} + +drwav_int32* drwav_open_memory_and_read_pcm_frames_s32(const void* data, size_t dataSize, unsigned int* channelsOut, unsigned int* sampleRateOut, drwav_uint64* totalFrameCountOut) +{ + if (channelsOut) *channelsOut = 0; + if (sampleRateOut) *sampleRateOut = 0; + if (totalFrameCountOut) *totalFrameCountOut = 0; + + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 totalSampleCount; + drwav_int32* result = drwav_open_memory_and_read_s32(data, dataSize, &channels, &sampleRate, &totalSampleCount); + if (result == NULL) { + return NULL; + } + + if (channelsOut) *channelsOut = channels; + if (sampleRateOut) *sampleRateOut = sampleRate; + if (totalFrameCountOut) *totalFrameCountOut = totalSampleCount / channels; + + return result; +} +#endif //DR_WAV_NO_CONVERSION_API + + +void drwav_free(void* pDataReturnedByOpenAndRead) +{ + DRWAV_FREE(pDataReturnedByOpenAndRead); +} + +#endif //DR_WAV_IMPLEMENTATION + + +// REVISION HISTORY +// +// v0.9.0 - 2018-12-16 +// - API CHANGE: Add new reading APIs for reading by PCM frames instead of samples. Old APIs have been deprecated and +// will be removed in v0.10.0. Deprecated APIs and their replacements: +// drwav_read() -> drwav_read_pcm_frames() +// drwav_read_s16() -> drwav_read_pcm_frames_s16() +// drwav_read_f32() -> drwav_read_pcm_frames_f32() +// drwav_read_s32() -> drwav_read_pcm_frames_s32() +// drwav_seek_to_sample() -> drwav_seek_to_pcm_frame() +// drwav_write() -> drwav_write_pcm_frames() +// drwav_open_and_read_s16() -> drwav_open_and_read_pcm_frames_s16() +// drwav_open_and_read_f32() -> drwav_open_and_read_pcm_frames_f32() +// drwav_open_and_read_s32() -> drwav_open_and_read_pcm_frames_s32() +// drwav_open_file_and_read_s16() -> drwav_open_file_and_read_pcm_frames_s16() +// drwav_open_file_and_read_f32() -> drwav_open_file_and_read_pcm_frames_f32() +// drwav_open_file_and_read_s32() -> drwav_open_file_and_read_pcm_frames_s32() +// drwav_open_memory_and_read_s16() -> drwav_open_memory_and_read_pcm_frames_s16() +// drwav_open_memory_and_read_f32() -> drwav_open_memory_and_read_pcm_frames_f32() +// drwav_open_memory_and_read_s32() -> drwav_open_memory_and_read_pcm_frames_s32() +// drwav::totalSampleCount -> drwav::totalPCMFrameCount +// - API CHANGE: Rename drwav_open_and_read_file_*() to drwav_open_file_and_read_*(). +// - API CHANGE: Rename drwav_open_and_read_memory_*() to drwav_open_memory_and_read_*(). +// - Add built-in support for smpl chunks. +// - Add support for firing a callback for each chunk in the file at initialization time. +// - This is enabled through the drwav_init_ex(), etc. family of APIs. +// - Handle invalid FMT chunks more robustly. +// +// v0.8.5 - 2018-09-11 +// - Const correctness. +// - Fix a potential stack overflow. +// +// v0.8.4 - 2018-08-07 +// - Improve 64-bit detection. +// +// v0.8.3 - 2018-08-05 +// - Fix C++ build on older versions of GCC. +// +// v0.8.2 - 2018-08-02 +// - Fix some big-endian bugs. +// +// v0.8.1 - 2018-06-29 +// - Add support for sequential writing APIs. +// - Disable seeking in write mode. +// - Fix bugs with Wave64. +// - Fix typos. +// +// v0.8 - 2018-04-27 +// - Bug fix. +// - Start using major.minor.revision versioning. +// +// v0.7f - 2018-02-05 +// - Restrict ADPCM formats to a maximum of 2 channels. +// +// v0.7e - 2018-02-02 +// - Fix a crash. +// +// v0.7d - 2018-02-01 +// - Fix a crash. +// +// v0.7c - 2018-02-01 +// - Set drwav.bytesPerSample to 0 for all compressed formats. +// - Fix a crash when reading 16-bit floating point WAV files. In this case dr_wav will output silence for +// all format conversion reading APIs (*_s16, *_s32, *_f32 APIs). +// - Fix some divide-by-zero errors. +// +// v0.7b - 2018-01-22 +// - Fix errors with seeking of compressed formats. +// - Fix compilation error when DR_WAV_NO_CONVERSION_API +// +// v0.7a - 2017-11-17 +// - Fix some GCC warnings. +// +// v0.7 - 2017-11-04 +// - Add writing APIs. +// +// v0.6 - 2017-08-16 +// - API CHANGE: Rename dr_* types to drwav_*. +// - Add support for custom implementations of malloc(), realloc(), etc. +// - Add support for Microsoft ADPCM. +// - Add support for IMA ADPCM (DVI, format code 0x11). +// - Optimizations to drwav_read_s16(). +// - Bug fixes. +// +// v0.5g - 2017-07-16 +// - Change underlying type for booleans to unsigned. +// +// v0.5f - 2017-04-04 +// - Fix a minor bug with drwav_open_and_read_s16() and family. +// +// v0.5e - 2016-12-29 +// - Added support for reading samples as signed 16-bit integers. Use the _s16() family of APIs for this. +// - Minor fixes to documentation. +// +// v0.5d - 2016-12-28 +// - Use drwav_int*/drwav_uint* sized types to improve compiler support. +// +// v0.5c - 2016-11-11 +// - Properly handle JUNK chunks that come before the FMT chunk. +// +// v0.5b - 2016-10-23 +// - A minor change to drwav_bool8 and drwav_bool32 types. +// +// v0.5a - 2016-10-11 +// - Fixed a bug with drwav_open_and_read() and family due to incorrect argument ordering. +// - Improve A-law and mu-law efficiency. +// +// v0.5 - 2016-09-29 +// - API CHANGE. Swap the order of "channels" and "sampleRate" parameters in drwav_open_and_read*(). Rationale for this is to +// keep it consistent with dr_audio and dr_flac. +// +// v0.4b - 2016-09-18 +// - Fixed a typo in documentation. +// +// v0.4a - 2016-09-18 +// - Fixed a typo. +// - Change date format to ISO 8601 (YYYY-MM-DD) +// +// v0.4 - 2016-07-13 +// - API CHANGE. Make onSeek consistent with dr_flac. +// - API CHANGE. Rename drwav_seek() to drwav_seek_to_sample() for clarity and consistency with dr_flac. +// - Added support for Sony Wave64. +// +// v0.3a - 2016-05-28 +// - API CHANGE. Return drwav_bool32 instead of int in onSeek callback. +// - Fixed a memory leak. +// +// v0.3 - 2016-05-22 +// - Lots of API changes for consistency. +// +// v0.2a - 2016-05-16 +// - Fixed Linux/GCC build. +// +// v0.2 - 2016-05-11 +// - Added support for reading data as signed 32-bit PCM for consistency with dr_flac. +// +// v0.1a - 2016-05-07 +// - Fixed a bug in drwav_open_file() where the file handle would not be closed if the loader failed to initialize. +// +// v0.1 - 2016-05-04 +// - Initial versioned release. + + +/* +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to +*/ diff --git a/src/entity/component-manager.cpp b/src/entity/component-manager.cpp new file mode 100644 index 0000000..f39a7b5 --- /dev/null +++ b/src/entity/component-manager.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "component-manager.hpp" +#include "component-observer.hpp" + +void ComponentManager::addComponentObserver(ComponentObserver* observer) +{ + componentObservers.push_back(observer); +} + +void ComponentManager::removeComponentObserver(ComponentObserver* observer) +{ + componentObservers.remove(observer); +} + +void ComponentManager::addComponent(EntityID entity, ComponentBase* component) +{ + ComponentType componentType = component->getComponentType(); + + entityMap[entity][componentType] = component; + + // Notify observers + for (auto observer: componentObservers) + { + observer->componentAdded(entity, component); + } +} + +ComponentBase* ComponentManager::removeComponent(EntityID entity, ComponentType type) +{ + ComponentMap& componentMap = entityMap[entity]; + + auto it = componentMap.find(type); + if (it == componentMap.end()) + { + return nullptr; + } + + ComponentBase* component = it->second; + + // Notify observers + for (auto observer: componentObservers) + { + observer->componentRemoved(entity, component); + } + + componentMap.erase(it); + + return component; +} + +ComponentBase* ComponentManager::getComponent(EntityID entity, ComponentType type) +{ + ComponentMap& componentMap = entityMap[entity]; + + auto it = componentMap.find(type); + if (it == componentMap.end()) + { + return nullptr; + } + + return it->second; +} + diff --git a/src/entity/component-manager.hpp b/src/entity/component-manager.hpp new file mode 100644 index 0000000..69a913d --- /dev/null +++ b/src/entity/component-manager.hpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef COMPONENT_MANAGER_HPP +#define COMPONENT_MANAGER_HPP + +#include "entity-id.hpp" +#include "component.hpp" +#include +#include + +class ComponentObserver; + +/// Maps component types to components. +typedef std::map ComponentMap; + +/// Maps entity IDs to a component map. +typedef std::map EntityComponentMap; + +/** + * Manages the aggregation of components which make up entities. + */ +class ComponentManager +{ +public: + /** + * Creates an instance of ComponentManager. + */ + ComponentManager() = default; + + /** + * Destroys an instance of ComponentManager. + */ + ~ComponentManager() = default; + + /** + * Adds a ComponentObserver. + * + * @param observer Pointer to the observer to add. + */ + void addComponentObserver(ComponentObserver* observer); + + /** + * Removes a ComponentObserver. + * + * @param observer Pointer to the observer to remove. + */ + void removeComponentObserver(ComponentObserver* observer); + + /** + * Adds a component to the specified entity. + * + * @param entity Specifies an entity. + * @param component Specifies the component to add. + */ + void addComponent(EntityID entity, ComponentBase* component); + + /** + * Removes a component from the specified entity. + * + * @param entity Specifies an entity. + * @param type Specifies the type of component. + * + * @return Pointer to the removed component. + */ + ComponentBase* removeComponent(EntityID entity, ComponentType type); + + /** + * Returns the specified component of an entity. + * + * @param entity Specifies an entity. + * @param type Specifies the type of component. + * + * @return Pointer to the component, or `nullptr` if the specified component was not found. + */ + ComponentBase* getComponent(EntityID entity, ComponentType type); + + /** + * Returns the component map of the specified entity. + * + * @param entity Specifies an entity. + * + * @return Pointer to the component map. + */ + ComponentMap* getComponents(EntityID entity); + +private: + ComponentManager(const ComponentManager&) = delete; + ComponentManager& operator=(const ComponentManager&) = delete; + + EntityComponentMap entityMap; + std::list componentObservers; +}; + +inline ComponentMap* ComponentManager::getComponents(EntityID entity) +{ + return &entityMap[entity]; +} + +#endif // COMPONENT_MANAGER_HPP diff --git a/src/game/habitat.cpp b/src/entity/component-observer.cpp similarity index 64% rename from src/game/habitat.cpp rename to src/entity/component-observer.cpp index af2d294..2beca1e 100644 --- a/src/game/habitat.cpp +++ b/src/entity/component-observer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -17,18 +17,17 @@ * along with Antkeeper Source Code. If not, see . */ -#include "habitat.hpp" +#include "component-observer.hpp" +#include "component-manager.hpp" -Habitat::Habitat(const AABB& bounds, int maxOctreeDepth) +ComponentObserver::ComponentObserver(ComponentManager* componentManager): + componentManager(componentManager) { - obstacleOctree = new Octree(maxOctreeDepth, bounds); - pheromoneOctree = new Octree(maxOctreeDepth, bounds); - agentOctree = new Octree(maxOctreeDepth, bounds); + componentManager->addComponentObserver(this); } -Habitat::~Habitat() +ComponentObserver::~ComponentObserver() { - delete obstacleOctree; - delete pheromoneOctree; - delete agentOctree; + componentManager->removeComponentObserver(this); } + diff --git a/src/entity/component-observer.hpp b/src/entity/component-observer.hpp new file mode 100644 index 0000000..2049ac0 --- /dev/null +++ b/src/entity/component-observer.hpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef COMPONENT_OBSERVER_HPP +#define COMPONENT_OBSERVER_HPP + +#include "entity-id.hpp" + +class ComponentBase; +class ComponentManager; + +/** + * Abstract base class for component observers. + */ +class ComponentObserver +{ +public: + /** + * Creates a component observer. + * + * @param componentManager Specifies the component manager with which to associate this component observer. + */ + ComponentObserver(ComponentManager* componentManager); + + /** + * Destroys a component observer. + */ + virtual ~ComponentObserver(); + +protected: + ComponentManager* componentManager; + +private: + friend class ComponentManager; + + /** + * Called after a component is added to an entity. + * + * @param entity Specifies the entity with which the component is associated. + * @param component Specifies the component added. + */ + virtual void componentAdded(EntityID entity, ComponentBase* component) = 0; + + /** + * Called after a component is removed from an entity. + * + * @param entity Specifies the entity with which the component is associated. + * @param component Specifies the component removed. + */ + virtual void componentRemoved(EntityID entity, ComponentBase* component) = 0; +}; + +#endif // COMPONENT_OBSERVER_HPP diff --git a/src/entity/component.hpp b/src/entity/component.hpp new file mode 100644 index 0000000..5884420 --- /dev/null +++ b/src/entity/component.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef COMPONENT_HPP +#define COMPONENT_HPP + +enum class ComponentType; + +/** + * Abstract base class for entity components. + */ +class ComponentBase +{ +public: + /** + * Destroys a component. + */ + virtual ~ComponentBase() = default; + + /** + * Clones the component. + */ + virtual ComponentBase* clone() const = 0; + + /** + * Returns the component type. + */ + virtual ComponentType getComponentType() const = 0; +}; + +/** + * Abstract templated class for entity components. + */ +template +class Component: public ComponentBase +{ +public: + static const ComponentType TYPE; + + /** + * Destroys a component. + */ + virtual ~Component() = default; + + /** + * Returns the component type. + */ + virtual ComponentType getComponentType() const final; +}; + +template +const ComponentType Component::TYPE = type; + +template +inline ComponentType Component::getComponentType() const +{ + return type; +} + +#endif // COMPONENT_HPP + diff --git a/src/game/habitat.hpp b/src/entity/components/animation-component.hpp similarity index 61% rename from src/game/habitat.hpp rename to src/entity/components/animation-component.hpp index 7cbe6f4..3032683 100644 --- a/src/game/habitat.hpp +++ b/src/entity/components/animation-component.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -17,32 +17,26 @@ * along with Antkeeper Source Code. If not, see . */ -#ifndef HABITAT_HPP -#define HABITAT_HPP +#ifndef ANIMATION_COMPONENT_HPP +#define ANIMATION_COMPONENT_HPP -#include +#include "../component.hpp" +#include "component-type.hpp" #include using namespace Emergent; -class Navmesh; -class Pheromone; -class Agent; +#include +#include -class Habitat +class AnimationComponent: public Component { public: - Habitat(const AABB& bounds, int maxOctreeDepth); - ~Habitat(); + virtual ComponentBase* clone() const; - const Octree* getObstacleOctree() const; - const Octree* getPheromoneOctree() const; - const Octree* getAgentOctree() const; - -private: - Octree* obstacleOctree; - Octree* pheromoneOctree; - Octree* agentOctree; + // List of animation-blend weight pairs + std::list, float>> animations; }; -#endif // HABITAT_HPP \ No newline at end of file +#endif // ANIMATION_COMPONENT_HPP + diff --git a/src/entity/components/ant-hill-component.cpp b/src/entity/components/ant-hill-component.cpp new file mode 100644 index 0000000..3a2ec98 --- /dev/null +++ b/src/entity/components/ant-hill-component.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "ant-hill-component.hpp" + +ComponentBase* AntHillComponent::clone() const +{ + AntHillComponent* component = new AntHillComponent(); + return component; +} + diff --git a/src/entity/components/ant-hill-component.hpp b/src/entity/components/ant-hill-component.hpp new file mode 100644 index 0000000..0118389 --- /dev/null +++ b/src/entity/components/ant-hill-component.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef ANT_HILL_COMPONENT_HPP +#define ANT_HILL_COMPONENT_HPP + +#include "../component.hpp" +#include "component-type.hpp" + +#include +using namespace Emergent; + +class AntHillComponent: public Component +{ +public: + virtual ComponentBase* clone() const; +}; + +#endif // ANT_HILL_COMPONENT_HPP + diff --git a/src/entity/components/behavior-component.cpp b/src/entity/components/behavior-component.cpp new file mode 100644 index 0000000..40cf0fd --- /dev/null +++ b/src/entity/components/behavior-component.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "behavior-component.hpp" + +ComponentBase* BehaviorComponent::clone() const +{ + BehaviorComponent* component = new BehaviorComponent(); + component->wanderDirection = wanderDirection; + component->wanderTriangle = wanderTriangle; + return component; +} + diff --git a/src/entity/components/behavior-component.hpp b/src/entity/components/behavior-component.hpp new file mode 100644 index 0000000..777fc18 --- /dev/null +++ b/src/entity/components/behavior-component.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef BEHAVIOR_COMPONENT_HPP +#define BEHAVIOR_COMPONENT_HPP + +#include "../component.hpp" +#include "component-type.hpp" + +#include +using namespace Emergent; + +class BehaviorComponent: public Component +{ +public: + virtual ComponentBase* clone() const; + float wanderCircleDistance; // cm + float wanderCircleRadius; // cm + float wanderRate; // radians/s + Vector3 wanderDirection; + TriangleMesh::Triangle* wanderTriangle; +}; + +#endif // BEHAVIOR_COMPONENT_HPP + diff --git a/src/entity/components/collision-component.cpp b/src/entity/components/collision-component.cpp new file mode 100644 index 0000000..67d690a --- /dev/null +++ b/src/entity/components/collision-component.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "collision-component.hpp" + +ComponentBase* CollisionComponent::clone() const +{ + CollisionComponent* component = new CollisionComponent(); + component->radius = radius; + component->collisions = collisions; + component->mesh = mesh; + return component; +} + diff --git a/src/states/title-state.hpp b/src/entity/components/collision-component.hpp similarity index 62% rename from src/states/title-state.hpp rename to src/entity/components/collision-component.hpp index bedf0f7..4e9d142 100644 --- a/src/states/title-state.hpp +++ b/src/entity/components/collision-component.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -17,30 +17,26 @@ * along with Antkeeper Source Code. If not, see . */ -#ifndef TITLE_STATE_HPP -#define TITLE_STATE_HPP +#ifndef COLLISION_COMPONENT_HPP +#define COLLISION_COMPONENT_HPP -#include "../application-state.hpp" -#include "../ui/ui.hpp" +#include "../component.hpp" +#include "../entity-id.hpp" +#include "component-type.hpp" #include using namespace Emergent; -/** - * Displays the title screen. - */ -class TitleState: public ApplicationState, public WindowObserver +class CollisionComponent: public Component { public: - TitleState(Application* application); - virtual ~TitleState(); + virtual ComponentBase* clone() const; - virtual void enter(); - virtual void execute(); - virtual void exit(); + float radius; + std::list collisions; - virtual void windowClosed(); - virtual void windowResized(int width, int height); + TriangleMesh* mesh; }; -#endif // TITLE_STATE_HPP \ No newline at end of file +#endif // COLLISION_COMPONENT_HPP + diff --git a/src/entity/components/component-type.hpp b/src/entity/components/component-type.hpp new file mode 100644 index 0000000..9c03646 --- /dev/null +++ b/src/entity/components/component-type.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef COMPONENT__TYPE_HPP +#define COMPONENT__TYPE_HPP + +enum class ComponentType +{ + ANIMATION, + ANT_HILL, + BEHAVIOR, + COLLISION, + LEGGED_LOCOMOTION, + MODEL, + STEERING, + SOUND_SOURCE, + TOOL, + TRANSFORM +}; + +#endif // COMPONENT_TYPE_HPP + diff --git a/src/entity/components/legged-locomotion-component.cpp b/src/entity/components/legged-locomotion-component.cpp new file mode 100644 index 0000000..25d3701 --- /dev/null +++ b/src/entity/components/legged-locomotion-component.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "legged-locomotion-component.hpp" + +ComponentBase* LeggedLocomotionComponent::clone() const +{ + LeggedLocomotionComponent* component = new LeggedLocomotionComponent(); + component->legCount = legCount; + component->crawlingSpeed = crawlingSpeed; + component->walkingSpeed = walkingSpeed; + component->runningSpeed = runningSpeed; + component->turningSpeed = turningSpeed; + component->speed = walkingSpeed; + component->surface = nullptr; + return component; +} + diff --git a/src/entity/components/legged-locomotion-component.hpp b/src/entity/components/legged-locomotion-component.hpp new file mode 100644 index 0000000..a213ee0 --- /dev/null +++ b/src/entity/components/legged-locomotion-component.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef LEGGED_LOCOMOTION_COMPONENT_HPP +#define LEGGED_LOCOMOTION_COMPONENT_HPP + +#include "../component.hpp" +#include "component-type.hpp" + +#include +using namespace Emergent; + +class LeggedLocomotionComponent: public Component +{ +public: + virtual ComponentBase* clone() const; + + unsigned char legCount; + float crawlingSpeed; // cm/s + float walkingSpeed; // cm/s + float runningSpeed; // cm/s + float turningSpeed; // radians/s + float speed; + TriangleMesh::Triangle* surface; + Vector3 barycentricPosition; +}; + +#endif // LEGGED_LOCOMOTION_COMPONENT_HPP + diff --git a/src/entity/components/model-component.cpp b/src/entity/components/model-component.cpp new file mode 100644 index 0000000..36af59f --- /dev/null +++ b/src/entity/components/model-component.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "model-component.hpp" + +ComponentBase* ModelComponent::clone() const +{ + ModelComponent* component = new ModelComponent(); + component->model.setModel(model.getModel()); + component->model.setPose(nullptr); + return component; +} + diff --git a/src/entity/components/model-component.hpp b/src/entity/components/model-component.hpp new file mode 100644 index 0000000..a97c536 --- /dev/null +++ b/src/entity/components/model-component.hpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef MODEL_COMPONENT_HPP +#define MODEL_COMPONENT_HPP + +#include "../component.hpp" +#include "component-type.hpp" + +#include +using namespace Emergent; + +class ModelComponent: public Component +{ +public: + virtual ComponentBase* clone() const; + ModelInstance model; +}; + +#endif // MODEL_COMPONENT_HPP + diff --git a/src/entity/components/sound-source-component.cpp b/src/entity/components/sound-source-component.cpp new file mode 100644 index 0000000..a52bc60 --- /dev/null +++ b/src/entity/components/sound-source-component.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "sound-source-component.hpp" + +ComponentBase* SoundSourceComponent::clone() const +{ + SoundSourceComponent* component = new SoundSourceComponent(); + component->playing = playing; + return component; +} + diff --git a/src/entity/components/sound-source-component.hpp b/src/entity/components/sound-source-component.hpp new file mode 100644 index 0000000..b179c44 --- /dev/null +++ b/src/entity/components/sound-source-component.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef SOUND_SOURCE_COMPONENT_HPP +#define SOUND_SOURCE_COMPONENT_HPP + +#include "../component.hpp" +#include "../entity-id.hpp" +#include "component-type.hpp" + +#include +using namespace Emergent; + +class SoundSourceComponent: public Component +{ +public: + virtual ComponentBase* clone() const; + + bool playing; +}; + +#endif // SOUND_SOURCE_COMPONENT_HPP + diff --git a/src/entity/components/steering-component.cpp b/src/entity/components/steering-component.cpp new file mode 100644 index 0000000..a1113c9 --- /dev/null +++ b/src/entity/components/steering-component.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "steering-component.hpp" + +ComponentBase* SteeringComponent::clone() const +{ + SteeringComponent* component = new SteeringComponent(); + return component; +} + diff --git a/src/entity/components/steering-component.hpp b/src/entity/components/steering-component.hpp new file mode 100644 index 0000000..c1b6e5a --- /dev/null +++ b/src/entity/components/steering-component.hpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef STEERING_COMPONENT_HPP +#define STEERING_COMPONENT_HPP + +#include "../component.hpp" +#include "component-type.hpp" + +#include +using namespace Emergent; + +#define MAX_STEERING_BEHAVIORS 8 + +struct SteeringBehavior +{ + /// Function object which calculates steering force + std::function function; + + /// Priority value which determines in what order the behaviors will be evaluated + float priority; + + /// Weight factor by which the calculated steering force should be multiplied + float weight; +}; + +class SteeringComponent: public Component +{ +public: + virtual ComponentBase* clone() const; + + SteeringBehavior behaviors[MAX_STEERING_BEHAVIORS]; + std::size_t behaviorCount; + Vector3 force; + float speed; + float maxSpeed; +}; + +#endif // STEERING_COMPONENT_HPP + diff --git a/src/entity/components/tool-component.cpp b/src/entity/components/tool-component.cpp new file mode 100644 index 0000000..abd949e --- /dev/null +++ b/src/entity/components/tool-component.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "tool-component.hpp" + +ComponentBase* ToolComponent::clone() const +{ + ToolComponent* component = new ToolComponent(); + component->active = active; + + return component; +} + diff --git a/src/states/loading-state.hpp b/src/entity/components/tool-component.hpp similarity index 66% rename from src/states/loading-state.hpp rename to src/entity/components/tool-component.hpp index a80075b..52d7d9b 100644 --- a/src/states/loading-state.hpp +++ b/src/entity/components/tool-component.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -17,23 +17,19 @@ * along with Antkeeper Source Code. If not, see . */ -#ifndef LOADING_STATE_HPP -#define LOADING_STATE_HPP +#ifndef TOOL_COMPONENT_HPP +#define TOOL_COMPONENT_HPP -#include "../application-state.hpp" +#include "../component.hpp" +#include "component-type.hpp" -/** - * Loads the application - */ -class LoadingState: public ApplicationState +class ToolComponent: public Component { public: - LoadingState(Application* application); - virtual ~LoadingState(); + virtual ComponentBase* clone() const; - virtual void enter(); - virtual void execute(); - virtual void exit(); + bool active; }; -#endif // LOADING_STATE_HPP +#endif // TOOL_COMPONENT_HPP + diff --git a/src/entity/components/transform-component.cpp b/src/entity/components/transform-component.cpp new file mode 100644 index 0000000..8533213 --- /dev/null +++ b/src/entity/components/transform-component.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "transform-component.hpp" + +ComponentBase* TransformComponent::clone() const +{ + TransformComponent* component = new TransformComponent(); + component->transform = transform; + return component; +} + diff --git a/src/entity/components/transform-component.hpp b/src/entity/components/transform-component.hpp new file mode 100644 index 0000000..a2c1b2f --- /dev/null +++ b/src/entity/components/transform-component.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef TRANSFORM_COMPONENT_HPP +#define TRANSFORM_COMPONENT_HPP + +#include "../component.hpp" +#include "component-type.hpp" + +#include +using namespace Emergent; + +class TransformComponent: public Component +{ +public: + virtual ComponentBase* clone() const; + + Transform transform; +}; + +#endif // TRANSFORM_COMPONENT_HPP + diff --git a/src/entity/entity-group-member.hpp b/src/entity/entity-group-member.hpp new file mode 100644 index 0000000..5c04b1b --- /dev/null +++ b/src/entity/entity-group-member.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef ENTITY_GROUP_MEMBER_HPP +#define ENTITY_GROUP_MEMBER_HPP + +#include "entity-id.hpp" +#include +#include + +/** + * A group of entities which share a set of specified component types. + * + * @tparam T Set of components which are required for group membership. + */ +template +struct EntityGroupMember +{ + /// Entity ID of the group member. + EntityID entity; + + /// A tuple containing pointers to the member's group-related components, in the order specified by the order of the template parameters. + std::tuple components; +}; + +#endif // ENTITY_GROUP_MEMBER_HPP + diff --git a/src/entity/entity-group-observer.hpp b/src/entity/entity-group-observer.hpp new file mode 100644 index 0000000..940cd61 --- /dev/null +++ b/src/entity/entity-group-observer.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef ENTITY_GROUP_OBSERVER_HPP +#define ENTITY_GROUP_OBSERVER_HPP + +#include "entity-group-member.hpp" + +/** + * Abstract base class for entity group observers, which are notified each time a member is registered or unregistered from the group. + * + * @tparam T Set of components which are required for group membership. + */ +template +class EntityGroupObserver +{ +public: + typedef EntityGroupMember Member; + + /** + * Called each time an entity joins the entity group by obtaining the necessary component types. + * + * @param entity Entity ID of the new member. + */ + virtual void memberRegistered(const Member* member) = 0; + + /** + * Called each time an entity leaves an the entity group by no longer possessing the necessary component types. + * + * @param entity Entity ID of the former member. + */ + virtual void memberUnregistered(const Member* member) = 0; +}; + +#endif // ENTITY_GROUP_OBSERVER_HPP + diff --git a/src/entity/entity-group.cpp b/src/entity/entity-group.cpp new file mode 100644 index 0000000..22bfd26 --- /dev/null +++ b/src/entity/entity-group.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "component.hpp" +#include "component-manager.hpp" +#include "entity-group.hpp" + +EntityGroupBase::EntityGroupBase(ComponentManager* componentManager, const ComponentFilter& componentFilter): + ComponentObserver(componentManager), + componentFilter(componentFilter) +{} + +void EntityGroupBase::componentAdded(EntityID entity, ComponentBase* component) +{ + if (componentFilter.find(component->getComponentType()) != componentFilter.end()) + { + for (auto it = componentFilter.begin(); it != componentFilter.end(); ++it) + { + if (*it == component->getComponentType()) + { + continue; + } + else if (!componentManager->getComponent(entity, *it)) + { + return; + } + } + + registerMember(entity); + } +} + +void EntityGroupBase::componentRemoved(EntityID entity, ComponentBase* component) +{ + if (componentFilter.find(component->getComponentType()) != componentFilter.end()) + { + if (isRegistered(entity)) + { + unregisterMember(entity); + } + } +} + diff --git a/src/entity/entity-group.hpp b/src/entity/entity-group.hpp new file mode 100644 index 0000000..5ebe0b0 --- /dev/null +++ b/src/entity/entity-group.hpp @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef ENTITY_GROUP_HPP +#define ENTITY_GROUP_HPP + +#include "component-observer.hpp" +#include "component-manager.hpp" +#include "entity-group-member.hpp" +#include "entity-group-observer.hpp" +#include +#include +#include +#include +#include + +enum class ComponentType; + +/// A set of component types used to filter entities +typedef std::set ComponentFilter; + +/** + * Abstract base class for entity groups. + */ +class EntityGroupBase: protected ComponentObserver +{ +public: + /** + * Creates a entity group base. + * + * @param componentManager The component manager with which to associate this entity group. + * @param componentFilter Set of component types which an entity must possess in order to join this entity group. + */ + EntityGroupBase(ComponentManager* componentManager, const ComponentFilter& componentFilter); + + /// Returns the set of components which an entity must possess in order to join this entity group. + const ComponentFilter& getComponentFilter() const; + + /** + * Returns true if the specified entity is registered with this entity group. + * + * @param entity ID of the entity to check. + */ + virtual bool isRegistered(EntityID entity) const = 0; + +private: + virtual void componentAdded(EntityID entity, ComponentBase* component); + virtual void componentRemoved(EntityID entity, ComponentBase* component); + + /** + * Called each time an entity joins the entity group by obtaining the necessary component types. + * + * @param entity Entity ID of the new member. + */ + virtual void registerMember(EntityID entity) = 0; + + /** + * Called each time an entity leaves an the entity group by no longer possessing the necessary component types. + * + * @param entity Entity ID of the former member. + */ + virtual void unregisterMember(EntityID entity) = 0; + + ComponentFilter componentFilter; +}; + +inline const ComponentFilter& EntityGroupBase::getComponentFilter() const +{ + return componentFilter; +} + +/** + * A group of entities which share a set of specified component types. + * + * @tparam T Set of components which are required for group membership. + */ +template +class EntityGroup: public EntityGroupBase +{ +public: + typedef EntityGroupMember Member; + typedef EntityGroupObserver Observer; + + /** + * Creates a entity group. + * + * @param componentManager Component manager with which to associate this entity group. + */ + EntityGroup(ComponentManager* componentManager); + + /// Destroys a entity group. + ~EntityGroup(); + + /** + * Adds a group observer. + * + * @param observer Observer to add. + */ + void addGroupObserver(Observer* observer); + + /** + * Removes a group observer. + * + * @param observer Observer to remove. + */ + void removeGroupObserver(Observer* observer); + + /// Removes all group observers. + void removeGroupObservers(); + + /// @copydoc EntityGroupBase::isRegistered(EntityID) const + virtual bool isRegistered(EntityID entity) const; + + /** + * Returns the member list. + * + * @return List of members. + */ + const std::list* getMembers() const; + + /** + * Returns the member with the specified ID. + * + * @param entity Entity ID of a group member. + * @return Member with the specified ID, or nullptr if an entity with that ID is not registered. + */ + const Member* getMemberByEntity(EntityID entity) const; + +private: + template + typename std::enable_if<(sizeof...(V) == 0), void>::type attachComponents(Member* member) + { + std::get(member->components) = static_cast(componentManager->getComponent(member->entity, U::TYPE)); + } + + template + typename std::enable_if<(sizeof...(V) > 0), void>::type attachComponents(Member* member) + { + std::get(member->components) = static_cast(componentManager->getComponent(member->entity, U::TYPE)); + attachComponents(member); + } + + virtual void registerMember(EntityID entity); + virtual void unregisterMember(EntityID entity); + + std::list members; + std::map memberMap; + std::list observers; +}; + +template +EntityGroup::EntityGroup(ComponentManager* componentManager): + EntityGroupBase(componentManager, ComponentFilter({(T::TYPE)...})) +{} + +template +EntityGroup::~EntityGroup() +{ + while (!members.empty()) + { + Member* member = members.back(); + + members.pop_back(); + memberMap.erase(memberMap.find(member->entity)); + + for (Observer* observer: observers) + { + observer->memberUnregistered(member); + } + + delete member; + } +} + + +template +void EntityGroup::addGroupObserver(Observer* observer) +{ + observers.push_back(observer); +} + +template +void EntityGroup::removeGroupObserver(Observer* observer) +{ + observers.remove(observer); +} + +template +void EntityGroup::removeGroupObservers() +{ + observers.clear(); +} + +template +inline bool EntityGroup::isRegistered(EntityID entity) const +{ + return (memberMap.find(entity) != memberMap.end()); +} + +template +inline const std::list::Member*>* EntityGroup::getMembers() const +{ + return &members; +} + +template +inline const typename EntityGroup::Member* EntityGroup::getMemberByEntity(EntityID entity) const +{ + auto it = memberMap.find(entity); + if (it != memberMap.end()) + { + return it->second; + } + + return nullptr; +} + +template +void EntityGroup::registerMember(EntityID entity) +{ + Member* member = new Member(); + member->entity = entity; + attachComponents<0, T...>(member); + + members.push_back(member); + memberMap[entity] = member; + + for (Observer* observer: observers) + { + observer->memberRegistered(member); + } +} + +template +void EntityGroup::unregisterMember(EntityID entity) +{ + auto it = memberMap.find(entity); + Member* member = it->second; + + memberMap.erase(it); + members.remove(member); + + for (Observer* observer: observers) + { + observer->memberUnregistered(member); + } + + delete member; +} + +#endif // ENTITY_GROUP_HPP + diff --git a/src/entity/entity-id-pool.cpp b/src/entity/entity-id-pool.cpp new file mode 100644 index 0000000..7850dff --- /dev/null +++ b/src/entity/entity-id-pool.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "entity-id-pool.hpp" + +EntityIDPool::EntityIDPool(): + nextID(0) +{} + +EntityIDPool::~EntityIDPool() +{} + +EntityID EntityIDPool::reserveNextID() +{ + EntityID id; + + if (!availableIDs.empty()) + { + id = *availableIDs.begin(); + availableIDs.erase(availableIDs.begin()); + reservedIDs.insert(id); + } + else + { + id = nextID; + reservedIDs.insert(id); + findNextID(); + } + + return id; +} + +void EntityIDPool::reserveID(EntityID id) +{ + availableIDs.erase(id); + reservedIDs.insert(id); + + if (nextID == id) + { + findNextID(); + } +} + +inline void EntityIDPool::findNextID() +{ + do + { + ++nextID; + } + while (reservedIDs.find(nextID) != reservedIDs.end()); +} + diff --git a/src/entity/entity-id-pool.hpp b/src/entity/entity-id-pool.hpp new file mode 100644 index 0000000..b34fe38 --- /dev/null +++ b/src/entity/entity-id-pool.hpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef ENTITY_ID_POOL_HPP +#define ENTITY_ID_POOL_HPP + +#include "entity-id.hpp" +#include + +/** + * Manages the creation and destruction of entities. + */ +class EntityIDPool +{ +public: + /** + * Creates an instance of EntityIDPool. + */ + EntityIDPool(); + + /** + * Destroys an instance of EntityIDPool. + */ + ~EntityIDPool(); + + /** + * Reserves the next available ID. + * + * @return Reserved ID. + */ + EntityID reserveNextID(); + + /** + * Reserves the specified ID. + * + * @param id Specifies an ID to reserve. + */ + void reserveID(EntityID id); + + /** + * Frees the specified ID. + * + * @param id Specifies an ID to free. + */ + void freeID(EntityID id); + + /** + * Returns `true` if the specified ID is reserved. + */ + bool isReserved(EntityID id) const; + +private: + void findNextID(); + + EntityID nextID; + std::set reservedIDs; + std::set availableIDs; +}; + +inline void EntityIDPool::freeID(EntityID id) +{ + reservedIDs.erase(id); + availableIDs.insert(id); +} + +inline bool EntityIDPool::isReserved(EntityID id) const +{ + return (reservedIDs.find(id) != reservedIDs.end()); +} + +#endif // ENTITY_ID_POOL_HPP diff --git a/src/entity/entity-id.hpp b/src/entity/entity-id.hpp new file mode 100644 index 0000000..dca665d --- /dev/null +++ b/src/entity/entity-id.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef ENTITY_ID_HPP +#define ENTITY_ID_HPP + +#include + +typedef std::uint32_t EntityID; + +#endif // ENTITY_ID_HPP + diff --git a/src/entity/entity-manager.cpp b/src/entity/entity-manager.cpp new file mode 100644 index 0000000..1dfe052 --- /dev/null +++ b/src/entity/entity-manager.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015 Christopher J. Howard + * + * This file is part of Ecosys. + * + * Ecosys is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Ecosys is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Ecosys. If not, see . + */ + +#include "entity-manager.hpp" +#include "component-manager.hpp" + +EntityManager::EntityManager(ComponentManager* componentManager): + componentManager(componentManager) +{} + +EntityManager::~EntityManager() +{} + + +EntityID EntityManager::createEntity() +{ + return idPool.reserveNextID(); +} + +bool EntityManager::createEntity(EntityID id) +{ + if (idPool.isReserved(id)) + { + return false; + } + + idPool.reserveID(id); + + return true; +} + +bool EntityManager::destroyEntity(EntityID id) +{ + if (!idPool.isReserved(id)) + { + return false; + } + + // Delete components + ComponentMap* components = componentManager->getComponents(id); + for (auto it = components->begin(); it != components->end(); it = components->begin()) + { + ComponentBase* component = it->second; + componentManager->removeComponent(id, component->getComponentType()); + delete component; + } + + // Free ID + idPool.freeID(id); + + return true; +} + diff --git a/src/entity/entity-manager.hpp b/src/entity/entity-manager.hpp new file mode 100644 index 0000000..a629f7f --- /dev/null +++ b/src/entity/entity-manager.hpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef ENTITY_MANAGER_HPP +#define ENTITY_MANAGER_HPP + +#include "entity-id.hpp" +#include "entity-id-pool.hpp" + +class ComponentManager; + +/** + * Manages the creation and destruction of entities. + */ +class EntityManager +{ +public: + /** + * Creates an entity manager. + * + * @param componentManager Component manager with which to associate this entity manag.er + */ + EntityManager(ComponentManager* componentManager); + + /** + * Destroys an entity manager. + */ + ~EntityManager(); + + /** + * Creates an entity with the next available ID. + * + * @return ID of the created entity. + */ + EntityID createEntity(); + + /** + * Creates an entity with the specified ID. + * + * @param id ID of the entity to be created. + * @return `true` if the entity was created, and `false` if an entity with the specified ID already exists. + */ + bool createEntity(EntityID id); + + /** + * Destroys an entity with the specified ID. + * + * @param id ID of the entity to be destroyed. + * + * @return `true` if the entity was destroyed, and `false` if an invalid ID was supplied. + */ + bool destroyEntity(EntityID id); + + /** + * Returns the component manager associated with this entity manager. + */ + const ComponentManager* getComponentManager() const; + + /// @copydoc EntityManager::getComponentManager() const + ComponentManager* getComponentManager(); + +private: + EntityManager(const EntityManager&) = delete; + EntityManager& operator=(const EntityManager&) = delete; + + EntityIDPool idPool; + ComponentManager* componentManager; +}; + +inline const ComponentManager* EntityManager::getComponentManager() const +{ + return componentManager; +} + +inline ComponentManager* EntityManager::getComponentManager() +{ + return componentManager; +} + +#endif // ENTITY_MANAGER_HPP + diff --git a/src/entity/entity-template.cpp b/src/entity/entity-template.cpp new file mode 100644 index 0000000..668fcc1 --- /dev/null +++ b/src/entity/entity-template.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "component.hpp" +#include "component-manager.hpp" +#include "entity-template.hpp" + +EntityTemplate::EntityTemplate(const std::list& components) +{ + for (ComponentBase* component: components) + { + this->components.push_back(component->clone()); + } +} + +EntityTemplate::~EntityTemplate() +{ + for (ComponentBase* component: components) + { + delete component; + } +} + +void EntityTemplate::apply(EntityID entity, ComponentManager* componentManager) +{ + for (ComponentBase* component: components) + { + ComponentBase* oldComponent = componentManager->getComponent(entity, component->getComponentType()); + if (oldComponent != nullptr) + { + componentManager->removeComponent(entity, component->getComponentType()); + delete oldComponent; + } + + componentManager->addComponent(entity, component->clone()); + } +} + diff --git a/src/entity/entity-template.hpp b/src/entity/entity-template.hpp new file mode 100644 index 0000000..bf00e95 --- /dev/null +++ b/src/entity/entity-template.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef ENTITY_TEMPLATE_HPP +#define ENTITY_TEMPLATE_HPP + +#include +#include "entity-id.hpp" + +class ComponentBase; +class ComponentManager; + +/** + * A template which can be applied to entities. + */ +class EntityTemplate +{ +public: + /** + * Creates an entity template. + * + * @param components List of components which make up the template. The components in the list will be cloned and the cloned data managed by this template. + */ + EntityTemplate(const std::list& components); + + /** + * Destroys an entity template. + */ + ~EntityTemplate(); + + /** + * Applies the template to an entity. + * + * @param entity ID of an entity to which the template should be applied. + * @param componentManager Component manager with which the entity is associated. + */ + void apply(EntityID entity, ComponentManager* componentManager); + +private: + std::list components; +}; + +#endif // ENTITY_TEMPLATE_HPP + diff --git a/src/entity/system-manager.cpp b/src/entity/system-manager.cpp new file mode 100644 index 0000000..ddb69e3 --- /dev/null +++ b/src/entity/system-manager.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "system-manager.hpp" +#include "system.hpp" + +SystemManager::SystemManager() +{} + +SystemManager::~SystemManager() +{} + +void SystemManager::update(float t, float dt) +{ + for (System* system: systems) + { + system->update(t, dt); + } +} + +void SystemManager::addSystem(System* system) +{ + systems.push_back(system); +} + +void SystemManager::removeSystem(System* system) +{ + systems.remove(system); +} diff --git a/src/entity/system-manager.hpp b/src/entity/system-manager.hpp new file mode 100644 index 0000000..b70704e --- /dev/null +++ b/src/entity/system-manager.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef SYSTEM_MANAGER_HPP +#define SYSTEM_MANAGER_HPP + +#include + +class System; + +/** + * Manages a series of systems. + */ +class SystemManager +{ +public: + /** + * Creates a system manager. + */ + SystemManager(); + + /** + * Destroys a system manager. + */ + ~SystemManager(); + + /** + * Updates all systems. Systems will be updated in the order that they were added to the system manager. + * + * @param t Total elapsed time, in seconds. + * @param dt Time elapsed since last update, in seconds. + */ + void update(float t, float dt); + + /** + * Adds a system to the system manager. + * + * @param system System to add. + */ + void addSystem(System* system); + + /** + * Removes a system from the system manager. + */ + void removeSystem(System* system); + +private: + std::list systems; +}; + +#endif // SYSTEM_MANAGER_HPP + diff --git a/src/application-state.cpp b/src/entity/system.cpp similarity index 78% rename from src/application-state.cpp rename to src/entity/system.cpp index 23c4c3e..1688afb 100644 --- a/src/application-state.cpp +++ b/src/entity/system.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -17,11 +17,9 @@ * along with Antkeeper Source Code. If not, see . */ -#include "application-state.hpp" +#include "system.hpp" -ApplicationState::ApplicationState(Application* application): - application(application) +System::System(ComponentManager* componentManager): + componentManager(componentManager) {} -ApplicationState::~ApplicationState() -{} \ No newline at end of file diff --git a/src/application-state.hpp b/src/entity/system.hpp similarity index 54% rename from src/application-state.hpp rename to src/entity/system.hpp index b6ea47c..0f4c67b 100644 --- a/src/application-state.hpp +++ b/src/entity/system.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -17,32 +17,40 @@ * along with Antkeeper Source Code. If not, see . */ -#ifndef APPLICATION_STATE_HPP -#define APPLICATION_STATE_HPP +#ifndef SYSTEM_HPP +#define SYSTEM_HPP -class Application; +class ComponentManager; /** - * Abstract base class for application states. + * Abstract base class for entity systems. */ -class ApplicationState +class System { public: - ApplicationState(Application* application); - - virtual ~ApplicationState(); - - // Run once when the state is initially entered - virtual void enter() = 0; - - // Run continually while the state is valid - virtual void execute() = 0; - - // Run once when the state is exited - virtual void exit() = 0; + /** + * Creates a system. + * + * @param componentManager Component manager with which to associate the system. + */ + System(ComponentManager* componentManager); + + /** + * Destroys a system. + */ + virtual ~System() = default; + + /** + * Updates the system. + * + * @param t Total elapsed time, in seconds. + * @param dt Time elapsed since last update, in seconds. + */ + virtual void update(float t, float dt) = 0; protected: - Application* application; + ComponentManager* componentManager; }; -#endif // APPLICATION_STATE_HPP \ No newline at end of file +#endif // SYSTEM_HPP + diff --git a/src/entity/systems/animation-system.cpp b/src/entity/systems/animation-system.cpp new file mode 100644 index 0000000..3cc7443 --- /dev/null +++ b/src/entity/systems/animation-system.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "animation-system.hpp" + +AnimationSystem::AnimationSystem(ComponentManager* componentManager): + System(componentManager), + animationGroup(componentManager) +{} + +AnimationSystem::~AnimationSystem() +{} + +void AnimationSystem::update(float t, float dt) +{ + auto members = animationGroup.getMembers(); + + for (const AnimationEntityGroup::Member* member: *members) + { + AnimationComponent* animationComponent = std::get<0>(member->components); + ModelComponent* modelComponent = std::get<1>(member->components); + + Pose* pose = modelComponent->model.getPose(); + + for (const std::pair, float>& clipWeightPair: animationComponent->animations) + { + const AnimationClip& clip = clipWeightPair.first; + float weight = clipWeightPair.second; + } + } +} + diff --git a/src/entity/systems/animation-system.hpp b/src/entity/systems/animation-system.hpp new file mode 100644 index 0000000..b7ffc28 --- /dev/null +++ b/src/entity/systems/animation-system.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef ANIMATION_SYSTEM_HPP +#define ANIMATION_SYSTEM_HPP + +#include "../entity-group.hpp" +#include "../components/animation-component.hpp" +#include "../components/model-component.hpp" +#include "../system.hpp" + +#include +using namespace Emergent; + +typedef EntityGroup AnimationEntityGroup; + +class AnimationSystem: public System +{ +public: + AnimationSystem(ComponentManager* componentManager); + virtual ~AnimationSystem(); + + virtual void update(float t, float dt); + +private: + AnimationEntityGroup animationGroup; +}; + +#endif // ANIMATION_SYSTEM_HPP + diff --git a/src/entity/systems/behavior-system.cpp b/src/entity/systems/behavior-system.cpp new file mode 100644 index 0000000..d63fd31 --- /dev/null +++ b/src/entity/systems/behavior-system.cpp @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "behavior-system.hpp" +#include + +BehaviorSystem::BehaviorSystem(ComponentManager* componentManager): + System(componentManager), + behaviorGroup(componentManager), + antHillGroup(componentManager) +{ + behaviorGroup.addGroupObserver(this); +} + +BehaviorSystem::~BehaviorSystem() +{} + +void BehaviorSystem::update(float t, float dt) +{ + auto members = behaviorGroup.getMembers(); + + for (const BehaviorGroup::Member* member: *members) + { + BehaviorComponent* behavior = std::get<0>(member->components); + LeggedLocomotionComponent* leggedLocomotion = std::get<1>(member->components); + SteeringComponent* steering = std::get<2>(member->components); + TransformComponent* transform = std::get<3>(member->components); + + steering->maxSpeed = leggedLocomotion->speed; + + steering->behaviorCount = 2; + steering->behaviors[0].priority = 2.0f; + steering->behaviors[0].weight = 1.0f; + steering->behaviors[0].function = std::bind(&BehaviorSystem::containment, this, member); + + steering->behaviors[1].priority = 1.0f; + steering->behaviors[1].weight = 0.5f; + steering->behaviors[1].function = std::bind(&BehaviorSystem::wander, this, dt, member); + } +} + +void BehaviorSystem::memberRegistered(const BehaviorGroup::Member* member) +{ + BehaviorComponent* behavior = std::get<0>(member->components); + LeggedLocomotionComponent* leggedLocomotion = std::get<1>(member->components); + + behavior->wanderDirection = Vector3(0.0f); + while (glm::length2(behavior->wanderDirection) == 0.0f) + { + behavior->wanderDirection = Vector3(frand(-1, 1), frand(-1, 1), frand(-1, 1)); + } + + behavior->wanderTriangle = leggedLocomotion->surface; + behavior->wanderDirection = glm::normalize(behavior->wanderDirection); + behavior->wanderCircleDistance = 3.0f; + behavior->wanderCircleRadius = 2.0f; + behavior->wanderRate = glm::radians(180.0f) * 5.0f; + + leggedLocomotion->speed = 2.0f; +} + +void BehaviorSystem::memberUnregistered(const BehaviorGroup::Member* member) +{} + + +Vector3 BehaviorSystem::containment(const BehaviorGroup::Member* agent) +{ + LeggedLocomotionComponent* leggedLocomotion = std::get<1>(agent->components); + TransformComponent* transform = std::get<3>(agent->components); + + float probeAngle = glm::radians(30.0f); + float probeDistance = 5.0f; + + Vector3 direction = transform->transform.rotation * Vector3(0, 0, 1); + + TriangleMesh::Triangle* surface = leggedLocomotion->surface; + + Vector3 forward = transform->transform.rotation * Vector3(0, 0, 1); + Vector3 up = surface->normal; + Vector3 right = glm::normalize(glm::cross(forward, up)); + + Vector3 force(0.0f); + + return force; +} + +Vector3 BehaviorSystem::wander(float dt, const BehaviorGroup::Member* agent) +{ + BehaviorComponent* behavior = std::get<0>(agent->components); + LeggedLocomotionComponent* leggedLocomotion = std::get<1>(agent->components); + SteeringComponent* steering = std::get<2>(agent->components); + TransformComponent* transform = std::get<3>(agent->components); + + // Reorientate wander direction + if (behavior->wanderTriangle != leggedLocomotion->surface) + { + if (behavior->wanderTriangle) + { + behavior->wanderDirection = glm::normalize(glm::rotation(behavior->wanderTriangle->normal, leggedLocomotion->surface->normal) * behavior->wanderDirection); + } + behavior->wanderTriangle = leggedLocomotion->surface; + } + + // Make wander direction coplanar with surface triangle + TriangleMesh::Triangle* triangle = leggedLocomotion->surface; + Vector3 triangleCenter = (triangle->edge->vertex->position + triangle->edge->next->vertex->position + triangle->edge->previous->vertex->position) * (1.0f / 3.0f); + behavior->wanderDirection = glm::normalize(projectOnPlane(transform->transform.translation + behavior->wanderDirection, triangleCenter, triangle->normal) - transform->transform.translation); + + Vector3 forward = transform->transform.rotation * Vector3(0, 0, 1); + Vector3 up = triangle->normal; + + // Calculate center of wander circle + Vector3 wanderCircleCenter = forward * behavior->wanderCircleDistance; + + // Calculate wander force + Vector3 wanderForce = wanderCircleCenter + behavior->wanderDirection * behavior->wanderCircleRadius; + + // Displace wander direction + float displacementAngle = frand(-behavior->wanderRate, behavior->wanderRate) * 0.5f * dt; + behavior->wanderDirection = glm::normalize(glm::angleAxis(displacementAngle, up) * behavior->wanderDirection); + + return wanderForce; +} + +Vector3 BehaviorSystem::forage(const BehaviorGroup::Member* agent) +{ + return Vector3(0.0f); +} + +Vector3 BehaviorSystem::homing(const BehaviorGroup::Member* agent) +{ + // Get ant position + const Vector3& antPosition = std::get<3>(agent->components)->transform.translation; + + // Find nearest ant-hill + bool found = false; + float minDistanceSquared = 0.0f; + Vector3 homingDirection(0.0f); + + auto antHills = antHillGroup.getMembers(); + for (const AntHillGroup::Member* antHill: *antHills) + { + // Get ant-hill position + const Vector3& antHillPosition = std::get<1>(antHill->components)->transform.translation; + + // Determine distance to ant-hill + Vector3 difference = antHillPosition - antPosition; + float distanceSquared = glm::length2(difference); + + if (!found || distanceSquared < minDistanceSquared) + { + minDistanceSquared = distanceSquared; + homingDirection = difference; + found = true; + } + } + + if (found) + { + homingDirection = glm::normalize(homingDirection); + } + + return homingDirection; +} + diff --git a/src/entity/systems/behavior-system.hpp b/src/entity/systems/behavior-system.hpp new file mode 100644 index 0000000..2612af1 --- /dev/null +++ b/src/entity/systems/behavior-system.hpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef BEHAVIOR_SYSTEM_HPP +#define BEHAVIOR_SYSTEM_HPP + +#include "../entity-group.hpp" +#include "../components/ant-hill-component.hpp" +#include "../components/behavior-component.hpp" +#include "../components/legged-locomotion-component.hpp" +#include "../components/steering-component.hpp" +#include "../components/transform-component.hpp" +#include "../system.hpp" + +#include +using namespace Emergent; + +typedef EntityGroup BehaviorGroup; +typedef EntityGroup AntHillGroup; + +class BehaviorSystem: public + System, + BehaviorGroup::Observer +{ +public: + BehaviorSystem(ComponentManager* componentManager); + virtual ~BehaviorSystem(); + + virtual void update(float t, float dt); + +private: + BehaviorGroup behaviorGroup; + AntHillGroup antHillGroup; + + virtual void memberRegistered(const BehaviorGroup::Member* member); + virtual void memberUnregistered(const BehaviorGroup::Member* member); + + Vector3 containment(const BehaviorGroup::Member* agent); + Vector3 wander(float dt, const BehaviorGroup::Member* agent); + Vector3 forage(const BehaviorGroup::Member* agent); + Vector3 homing(const BehaviorGroup::Member* agent); +}; + +#endif // BEHAVIOR_SYSTEM_HPP + diff --git a/src/entity/systems/collision-system.cpp b/src/entity/systems/collision-system.cpp new file mode 100644 index 0000000..d921937 --- /dev/null +++ b/src/entity/systems/collision-system.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "collision-system.hpp" + +CollisionSystem::CollisionSystem(ComponentManager* componentManager): + System(componentManager), + entityGroup(componentManager) +{} + +CollisionSystem::~CollisionSystem() +{} + +void CollisionSystem::update(float t, float dt) +{ + auto members = entityGroup.getMembers(); + + // INEFFICIENT! Currently done twice (A vs B, then B vs A) + // Also no octrees or other structure + for (const CollisionEntityGroup::Member* memberA: *members) + { + CollisionComponent* collisionA = std::get<0>(memberA->components); + TransformComponent* transformA = std::get<1>(memberA->components); + + // Clear previous collisions + collisionA->collisions.clear(); + + for (const CollisionEntityGroup::Member* memberB: *members) + { + if (memberA == memberB) + { + continue; + } + + CollisionComponent* collisionB = std::get<0>(memberB->components); + TransformComponent* transformB = std::get<1>(memberB->components); + + Vector3 difference = transformA->transform.translation - transformB->transform.translation; + float distanceSquared = glm::length2(difference); + float collisionRadius = collisionA->radius + collisionB->radius; + + if (distanceSquared <= collisionRadius * collisionRadius) + { + collisionA->collisions.push_back(memberB->entity); + } + } + } +} + diff --git a/src/entity/systems/collision-system.hpp b/src/entity/systems/collision-system.hpp new file mode 100644 index 0000000..6eb6491 --- /dev/null +++ b/src/entity/systems/collision-system.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef COLLISION_SYSTEM_HPP +#define COLLISION_SYSTEM_HPP + +#include "../entity-group.hpp" +#include "../components/collision-component.hpp" +#include "../components/transform-component.hpp" +#include "../system.hpp" + +#include +using namespace Emergent; + +typedef EntityGroup CollisionEntityGroup; + +class CollisionSystem: public System +{ +public: + CollisionSystem(ComponentManager* componentManager); + virtual ~CollisionSystem(); + + virtual void update(float t, float dt); + +private: + CollisionEntityGroup entityGroup; +}; + +#endif // COLLISION_SYSTEM_HPP + diff --git a/src/entity/systems/locomotion-system.cpp b/src/entity/systems/locomotion-system.cpp new file mode 100644 index 0000000..377e9c2 --- /dev/null +++ b/src/entity/systems/locomotion-system.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "locomotion-system.hpp" +#include "../../triangle-mesh-operations.hpp" + +LocomotionSystem::LocomotionSystem(ComponentManager* componentManager): + System(componentManager), + leggedLocomotionGroup(componentManager) +{ + leggedLocomotionGroup.addGroupObserver(this); +} + +LocomotionSystem::~LocomotionSystem() +{} + +void LocomotionSystem::update(float t, float dt) +{ + auto members = leggedLocomotionGroup.getMembers(); + + // Perform legged locomotion + for (const LeggedLocomotionGroup::Member* member: *members) + { + LeggedLocomotionComponent* leggedLocomotion = std::get<0>(member->components); + SteeringComponent* steering = std::get<1>(member->components); + TransformComponent* transform = std::get<2>(member->components); + + // Skip entities which are not on a surface + if (!leggedLocomotion->surface) + { + continue; + } + + // Determine target position + Vector3 force = steering->force * dt; + float speed = steering->speed * dt; + + if (speed == 0.0f) + { + continue; + } + + // Calculate direction vector + Vector3 direction = force * (1.0f / speed); + + std::vector segments; + float wrapDistance = wrap(leggedLocomotion->surface, transform->transform.translation, direction, speed, &segments); + + WrapOperationSegment segment = segments.back(); + + Vector3 cartesianStart = cartesian(segment.startPosition, + segment.triangle->edge->vertex->position, + segment.triangle->edge->next->vertex->position, + segment.triangle->edge->previous->vertex->position); + Vector3 cartesianEnd = cartesian(segment.endPosition, + segment.triangle->edge->vertex->position, + segment.triangle->edge->next->vertex->position, + segment.triangle->edge->previous->vertex->position); + + // Calculate wrap direction of final segment + Vector3 segmentDirection(0.0f); + if (cartesianStart != cartesianEnd) + { + segmentDirection = glm::normalize(cartesianEnd - cartesianStart); + } + + // Determine angle between the triangles + float angle = std::acos(glm::dot(leggedLocomotion->surface->normal, segment.triangle->normal)); + if (std::abs(angle) > glm::radians(35.0f)) + { + // Transition + } + + + leggedLocomotion->surface = segment.triangle; + leggedLocomotion->barycentricPosition = segment.endPosition; + transform->transform.translation = cartesianEnd; + if (cartesianStart != cartesianEnd) + { + transform->transform.rotation = lookRotation(segmentDirection, segment.triangle->normal); + } + } +} + +void LocomotionSystem::memberRegistered(const LeggedLocomotionGroup::Member* member) +{} + +void LocomotionSystem::memberUnregistered(const LeggedLocomotionGroup::Member* member) +{} + + diff --git a/src/entity/systems/locomotion-system.hpp b/src/entity/systems/locomotion-system.hpp new file mode 100644 index 0000000..5d64ddb --- /dev/null +++ b/src/entity/systems/locomotion-system.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef LOCOMOTION_SYSTEM_HPP +#define LOCOMOTION_SYSTEM_HPP + +#include "../entity-group.hpp" +#include "../components/legged-locomotion-component.hpp" +#include "../components/steering-component.hpp" +#include "../components/transform-component.hpp" +#include "../system.hpp" + +#include +using namespace Emergent; + +typedef EntityGroup LeggedLocomotionGroup; + +class LocomotionSystem: public + System, + LeggedLocomotionGroup::Observer +{ +public: + LocomotionSystem(ComponentManager* componentManager); + virtual ~LocomotionSystem(); + + virtual void update(float t, float dt); + +private: + LeggedLocomotionGroup leggedLocomotionGroup; + + virtual void memberRegistered(const LeggedLocomotionGroup::Member* member); + virtual void memberUnregistered(const LeggedLocomotionGroup::Member* member); +}; + +#endif // LOCOMOTION_SYSTEM_HPP + diff --git a/src/entity/systems/particle-system.cpp b/src/entity/systems/particle-system.cpp new file mode 100755 index 0000000..0c83a93 --- /dev/null +++ b/src/entity/systems/particle-system.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "particle-system.hpp" +#include "../../game/curl-noise.hpp" + +ParticleSystem::ParticleSystem(ComponentManager* componentManager): + System(componentManager), + range(nullptr), + material(nullptr) +{ + batch.setTransform(Transform::getIdentity()); + batch.setCullingEnabled(false); +} + +ParticleSystem::~ParticleSystem() +{} + +void ParticleSystem::resize(std::size_t count) +{ + particles.resize(count); + batch.resize(count); + range = batch.addRange(); + range->start = 0; + range->length = particles.size(); + range->material = material; + + while (!stack.empty()) + stack.pop(); + + for (int i = 0; i < particles.size(); ++i) + { + Particle& particle = particles[i]; + particle.life = 0.0f; + particle.size = 0.0f; + + Billboard* billboard = batch.getBillboard(i); + billboard->setDimensions(Vector2(particle.size)); + billboard->resetTweens(); + + stack.push(i); + } +} + +void ParticleSystem::emit(const Vector3& position) +{ + if (!stack.empty()) + { + std::size_t index = stack.top(); + stack.pop(); + + Particle& particle = particles[index]; + particle.life = frand(1.0f, 5.0f); + particle.translation = position; + particle.size = frand(0.01f, 0.2f); + particle.speed = frand(2.0f, 3.0f); + particle.direction = direction + Vector3(frand(-1, 1), 0, frand(-1, 1)) * 0.1f; + particle.direction = glm::normalize(particle.direction); + + Billboard* billboard = batch.getBillboard(index); + billboard->setTranslation(particle.translation); + billboard->setRotation(Quaternion(1, 0, 0, 0)); + billboard->setDimensions(Vector2(particle.size)); + billboard->setTintColor(Vector4(1.0f)); + billboard->resetTweens(); + } +} + +void ParticleSystem::update(float t, float dt) +{ + if (stack.size() == particles.size()) + { + // Inactive + return; + } + + batch.reset(); + + const Vector3 wind = glm::normalize(Vector3(1.0f, 0.0f, -1.0f)) * 1.5f * dt; + float frequency = 0.4f; + Vector3 noiseOffset = Vector3(77.7f, 33.3f, 11.1f) * t * 0.01f; + + for (std::size_t i = 0; i < particles.size(); ++i) + { + Particle& particle = particles[i]; + if (particle.life <= 0.0f) + { + continue; + } + + + Billboard* billboard = batch.getBillboard(i); + + bool reset = false; + + Vector3 smoke = curl(particle.translation, noiseOffset, frequency) * 8.0f; + + + particle.translation += particle.direction * particle.speed * dt + smoke * dt + wind; + particle.size += 0.1f * dt; + particle.life -= dt; + if (particle.life <= 0.0f) + { + particle.size = 0.0f; + reset = true; + + stack.push(i); + } + + billboard->setTranslation(particle.translation); + billboard->setRotation(Quaternion(1, 0, 0, 0)); + billboard->setDimensions(Vector2(particle.size)); + billboard->setTintColor(Vector4(0.5f)); + + if (reset) + { + billboard->resetTweens(); + } + } +} + +void ParticleSystem::setMaterial(Material* material) +{ + this->material = material; + if (range) + { + range->material = material; + } +} + +void ParticleSystem::setDirection(const Vector3& direction) +{ + this->direction = direction; +} + +void ParticleSystem::setLifeTime(float time) +{ + this->lifeTime = time; +} + +void ParticleSystem::setEmissionRate(float frequency) +{ + this->emissionRate = frequency; +} + diff --git a/src/entity/systems/particle-system.hpp b/src/entity/systems/particle-system.hpp new file mode 100755 index 0000000..7345791 --- /dev/null +++ b/src/entity/systems/particle-system.hpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef PARTICLE_SYSTEM_HPP +#define PARTICLE_SYSTEM_HPP + +#include "../components/model-component.hpp" +#include "../components/transform-component.hpp" +#include "../entity-group.hpp" +#include "../system.hpp" + +#include +using namespace Emergent; + +#include + +class ParticleSystem: + public System +{ +public: + ParticleSystem(ComponentManager* componentManager); + ~ParticleSystem(); + + void setMaterial(Material* material); + + void setParticleCount(std::size_t count); + void setDirection(const Vector3& direction); + void setLifeTime(float time); + void setEmissionRate(float frequency); + + const BillboardBatch* getBillboardBatch() const; + BillboardBatch* getBillboardBatch(); + + void resize(std::size_t count); + virtual void update(float t, float dt); + + void emit(const Vector3& position); + +private: + struct Particle + { + Vector3 translation; + float size; + float life; + float speed; + Vector3 direction; + }; + + BillboardBatch batch; + BillboardBatch::Range* range; + + Material* material; + std::vector particles; + Vector3 direction; + float lifeTime; + float emissionRate; + std::stack stack; +}; + +inline const BillboardBatch* ParticleSystem::getBillboardBatch() const +{ + return &batch; +} + +inline BillboardBatch* ParticleSystem::getBillboardBatch() +{ + return &batch; +} + +#endif // PARTICLE_SYSTEM_HPP diff --git a/src/entity/systems/render-system.cpp b/src/entity/systems/render-system.cpp new file mode 100644 index 0000000..86416f5 --- /dev/null +++ b/src/entity/systems/render-system.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "render-system.hpp" + +RenderSystem::RenderSystem(ComponentManager* componentManager, SceneLayer* scene): + System(componentManager), + modelEntityGroup(componentManager), + scene(scene) +{ + modelEntityGroup.addGroupObserver(this); +} + +RenderSystem::~RenderSystem() +{} + +void RenderSystem::update(float t, float dt) +{ + auto members = modelEntityGroup.getMembers(); + for (const ModelEntityGroup::Member* member: *members) + { + ModelComponent* model = std::get<0>(member->components); + TransformComponent* transform = std::get<1>(member->components); + + model->model.setTransform(transform->transform); + } +} + +void RenderSystem::memberRegistered(const ModelEntityGroup::Member* member) +{ + ModelComponent* model = std::get<0>(member->components); + scene->addObject(&model->model); +} + +void RenderSystem::memberUnregistered(const ModelEntityGroup::Member* member) +{ + ModelComponent* model = std::get<0>(member->components); + scene->removeObject(&model->model); +} + diff --git a/src/entity/systems/render-system.hpp b/src/entity/systems/render-system.hpp new file mode 100644 index 0000000..444ad3f --- /dev/null +++ b/src/entity/systems/render-system.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef RENDER_SYSTEM_HPP +#define RENDER_SYSTEM_HPP + +#include "../components/model-component.hpp" +#include "../components/transform-component.hpp" +#include "../entity-group.hpp" +#include "../system.hpp" + +#include +using namespace Emergent; + +typedef EntityGroup ModelEntityGroup; + +/** + * Abstract base class for entity systems. + */ +class RenderSystem: public + System, + ModelEntityGroup::Observer +{ +public: + RenderSystem(ComponentManager* componentManager, SceneLayer* scene); + virtual ~RenderSystem(); + + virtual void update(float t, float dt); + +private: + ModelEntityGroup modelEntityGroup; + SceneLayer* scene; + + virtual void memberRegistered(const ModelEntityGroup::Member* member); + virtual void memberUnregistered(const ModelEntityGroup::Member* member); +}; + +#endif // RENDER_SYSTEM_HPP + diff --git a/src/entity/systems/sound-system.cpp b/src/entity/systems/sound-system.cpp new file mode 100644 index 0000000..e109c65 --- /dev/null +++ b/src/entity/systems/sound-system.cpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "sound-system.hpp" +#include +#include "dr_libs/dr_wav.h" + +SoundSystem::SoundSystem(ComponentManager* componentManager): + System(componentManager), + entityGroup(componentManager), + device(nullptr), + context(nullptr) +{ + device = alcOpenDevice(nullptr); + if (!device) + { + throw std::runtime_error("SoundSystem::SoundSystem(): Failed to open audio device."); + } + + context = alcCreateContext(device, nullptr); + if (!alcMakeContextCurrent(context)) + { + throw std::runtime_error("SoundSystem::SoundSystem(): Failed to create audio context."); + } + + ALfloat listenerOrientation[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f }; + alListener3f(AL_POSITION, 0.0f, 0.0f, 1.0f); + alListener3f(AL_VELOCITY, 0.0f, 0.0f, 0.0f); + alListenerfv(AL_ORIENTATION, listenerOrientation); + + alGenSources((ALuint)1, &source); + alSourcef(source, AL_PITCH, 1); + alSourcef(source, AL_GAIN, 1); + alSource3f(source, AL_POSITION, 0, 0, 0); + alSource3f(source, AL_VELOCITY, 0, 0, 0); + alSourcei(source, AL_LOOPING, AL_FALSE); + + alGenBuffers((ALuint)1, &buffer); + + // Load wav file + { + const char* filename = "/home/cjhoward/projects/antkeeper/modules/antkeeper-data/sounds/shutter.wav"; + unsigned int channels; + unsigned int sampleRate; + drwav_uint64 frameCount; + std::int16_t* sampleData = drwav_open_file_and_read_pcm_frames_s16(filename, &channels, &sampleRate, &frameCount); + + if (sampleData == nullptr) + { + throw std::runtime_error("Couldn't load wav file"); + drwav_free(sampleData); + } + + bool stereo = (channels > 1); + ALenum format = (stereo) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + + std::size_t sampleCount = frameCount * channels; + std::size_t sampleDataSize = sampleCount * sizeof(drwav_int16); + + alBufferData(buffer, format, sampleData, sampleDataSize, sampleRate); + } + + alSourcei(source, AL_BUFFER, buffer); + //alSourcePlay(source); + +} + +SoundSystem::~SoundSystem() +{ + alDeleteSources(1, &source); + alDeleteBuffers(1, &buffer); + alcMakeContextCurrent(nullptr); + alcDestroyContext(context); + alcCloseDevice(device); +} + +void SoundSystem::scrot() +{ + alSourcePlay(source); +} + +void SoundSystem::update(float t, float dt) +{ + auto members = entityGroup.getMembers(); + for (const SoundSourceEntityGroup::Member* member: *members) + { + TransformComponent* transform = std::get<0>(member->components); + SoundSourceComponent* sound = std::get<1>(member->components); + } +} + +void SoundSystem::memberRegistered(const SoundSourceEntityGroup::Member* member) +{ + TransformComponent* transform = std::get<0>(member->components); + SoundSourceComponent* sound = std::get<1>(member->components); +} + +void SoundSystem::memberUnregistered(const SoundSourceEntityGroup::Member* member) +{ + TransformComponent* transform = std::get<0>(member->components); + SoundSourceComponent* sound = std::get<1>(member->components); +} + diff --git a/src/entity/systems/sound-system.hpp b/src/entity/systems/sound-system.hpp new file mode 100644 index 0000000..013909d --- /dev/null +++ b/src/entity/systems/sound-system.hpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef SOUND_SYSTEM_HPP +#define SOUND_SYSTEM_HPP + +#include "../entity-group.hpp" +#include "../system.hpp" +#include "../components/sound-source-component.hpp" +#include "../components/transform-component.hpp" +#include +#include + +typedef EntityGroup SoundSourceEntityGroup; + +class SoundSystem: + public System, + public SoundSourceEntityGroup::Observer +{ +public: + SoundSystem(ComponentManager* componentManager); + virtual ~SoundSystem(); + + virtual void update(float t, float dt); + + void scrot(); + +private: + virtual void memberRegistered(const SoundSourceEntityGroup::Member* member); + virtual void memberUnregistered(const SoundSourceEntityGroup::Member* member); + + SoundSourceEntityGroup entityGroup; + ALCdevice* device; + ALCcontext* context; + ALuint source; + ALuint buffer; +}; + +#endif // SOUND_SYSTEM_HPP + diff --git a/src/entity/systems/steering-system.cpp b/src/entity/systems/steering-system.cpp new file mode 100644 index 0000000..d8f0ee0 --- /dev/null +++ b/src/entity/systems/steering-system.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "steering-system.hpp" + +SteeringSystem::SteeringSystem(ComponentManager* componentManager): + System(componentManager), + boids(componentManager) +{ + boids.addGroupObserver(this); +} + +SteeringSystem::~SteeringSystem() +{} + +void SteeringSystem::update(float t, float dt) +{ + auto members = boids.getMembers(); + + for (const SteeringGroup::Member* member: *members) + { + SteeringComponent* steering = std::get<0>(member->components); + + // Initialize summed steering force + steering->force = Vector3(0.0f); + steering->speed = 0.0f; + float speedSquared = 0.0f; + float maxSpeedSquared = steering->maxSpeed * steering->maxSpeed; + bool truncated = false; + + if (steering->behaviorCount > 0) + { + // Sort steering beaviors by priority + std::sort(steering->behaviors, steering->behaviors + steering->behaviorCount, + [](const SteeringBehavior& a, const SteeringBehavior& b) -> bool + { + return (a.priority >= b.priority); + } + ); + } + + // Evaluate steering forces in order + for (std::size_t i = 0; i < steering->behaviorCount; ++i) + { + const SteeringBehavior& behavior = steering->behaviors[i]; + + // Skip zero-weighted steering behaviors + if (behavior.weight == 0.0f) + { + continue; + } + + // Add weighted steering behavior force + steering->force += behavior.function() * behavior.weight; + + // Limit speed + speedSquared = glm::length2(steering->force); + if (speedSquared >= maxSpeedSquared) + { + steering->force *= (1.0f / std::sqrt(speedSquared)) * steering->maxSpeed; + steering->speed = steering->maxSpeed; + truncated = true; + break; + } + } + + if (!truncated) + { + steering->speed = std::sqrt(speedSquared); + } + } +} + +void SteeringSystem::memberRegistered(const SteeringGroup::Member* member) +{} + +void SteeringSystem::memberUnregistered(const SteeringGroup::Member* member) +{} + + diff --git a/src/entity/systems/steering-system.hpp b/src/entity/systems/steering-system.hpp new file mode 100644 index 0000000..798816f --- /dev/null +++ b/src/entity/systems/steering-system.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef STEERING_SYSTEM_HPP +#define STEERING_SYSTEM_HPP + +#include "../entity-group.hpp" +#include "../components/steering-component.hpp" +#include "../system.hpp" + +#include +using namespace Emergent; + +typedef EntityGroup SteeringGroup; + +class SteeringSystem: public + System, + SteeringGroup::Observer +{ +public: + SteeringSystem(ComponentManager* componentManager); + virtual ~SteeringSystem(); + + /** + * Calculates the steering force for each steering component using weighted truncated running sums with prioritization. + */ + virtual void update(float t, float dt); + +private: + SteeringGroup boids; + + virtual void memberRegistered(const SteeringGroup::Member* member); + virtual void memberUnregistered(const SteeringGroup::Member* member); +}; + +#endif // STEERING_SYSTEM_HPP + diff --git a/src/entity/systems/tool-system.cpp b/src/entity/systems/tool-system.cpp new file mode 100644 index 0000000..91311ea --- /dev/null +++ b/src/entity/systems/tool-system.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "tool-system.hpp" + +ToolSystem::ToolSystem(ComponentManager* componentManager): + System(componentManager), + tools(componentManager), + picked(false) +{ + tools.addGroupObserver(this); +} + +ToolSystem::~ToolSystem() +{} + +void ToolSystem::update(float t, float dt) +{ + pick(); + + auto members = tools.getMembers(); + for (const ToolGroup::Member* member: *members) + { + ModelComponent* model = std::get<0>(member->components); + ToolComponent* tool = std::get<1>(member->components); + TransformComponent* transform = std::get<2>(member->components); + + model->model.setActive(tool->active); + + if (picked) + { + transform->transform.translation = mouseWorldPosition; + } + } + + picked = false; +} + +void ToolSystem::setPickingCamera(const Camera* camera) +{ + pickingCamera = camera; +} + +void ToolSystem::setPickingViewport(const Vector4& viewport) +{ + pickingViewport = viewport; +} + +void ToolSystem::pick() +{ + Vector3 mouseNear = pickingCamera->unproject(Vector3(mouseScreenPosition, 0.0f), pickingViewport); + Vector3 mouseFar = pickingCamera->unproject(Vector3(mouseScreenPosition, 1.0f), pickingViewport); + + Ray pickingRay; + pickingRay.origin = mouseNear; + pickingRay.direction = glm::normalize(mouseFar - mouseNear); + Plane pickingPlane(Vector3(0.0f, 1.0f, 0.0f), Vector3(0.0f)); + + auto pickingIntersection = pickingRay.intersects(pickingPlane); + picked = std::get<0>(pickingIntersection); + if (picked) + { + mouseWorldPosition = pickingRay.extrapolate(std::get<1>(pickingIntersection)); + } +} + +void ToolSystem::memberRegistered(const ToolGroup::Member* member) +{ + ToolComponent* tool = std::get<1>(member->components); + tool->active = false; +} + +void ToolSystem::memberUnregistered(const ToolGroup::Member* member) +{} + +void ToolSystem::handleEvent(const MouseMovedEvent& event) +{ + mouseScreenPosition = Vector2(event.x, pickingViewport[3] - event.y); +} + diff --git a/src/entity/systems/tool-system.hpp b/src/entity/systems/tool-system.hpp new file mode 100644 index 0000000..1b9e214 --- /dev/null +++ b/src/entity/systems/tool-system.hpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef TOOL_SYSTEM_HPP +#define TOOL_SYSTEM_HPP + +#include "../entity-group.hpp" +#include "../components/model-component.hpp" +#include "../components/tool-component.hpp" +#include "../components/transform-component.hpp" +#include "../system.hpp" + +#include +using namespace Emergent; + +typedef EntityGroup ToolGroup; + +/** + * Abstract base class for entity systems. + */ +class ToolSystem: + public System, + public ToolGroup::Observer, + public EventHandler +{ +public: + ToolSystem(ComponentManager* componentManager); + virtual ~ToolSystem(); + + virtual void update(float t, float dt); + + void setPickingCamera(const Camera* camera); + void setPickingViewport(const Vector4& viewport); + +private: + void pick(); + virtual void memberRegistered(const ToolGroup::Member* member); + virtual void memberUnregistered(const ToolGroup::Member* member); + virtual void handleEvent(const MouseMovedEvent& event); + + Vector2 mouseScreenPosition; + Vector3 mouseWorldPosition; + const Camera* pickingCamera; + Vector4 pickingViewport; + bool picked; + ToolGroup tools; +}; + +#endif // TOOL_SYSTEM_HPP + diff --git a/src/game.cpp b/src/game.cpp new file mode 100644 index 0000000..6d8668c --- /dev/null +++ b/src/game.cpp @@ -0,0 +1,1719 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "game.hpp" +#include "states/game-state.hpp" +#include "states/splash-state.hpp" +#include "states/sandbox-state.hpp" +#include "paths.hpp" +#include "ui/ui.hpp" +#include "graphics/ui-render-pass.hpp" +#include "graphics/shadow-map-render-pass.hpp" +#include "graphics/clear-render-pass.hpp" +#include "graphics/sky-render-pass.hpp" +#include "graphics/lighting-render-pass.hpp" +#include "graphics/silhouette-render-pass.hpp" +#include "graphics/final-render-pass.hpp" +#include "resources/resource-manager.hpp" +#include "resources/text-file.hpp" +#include "game/camera-rig.hpp" +#include "game/lens.hpp" +#include "game/forceps.hpp" +#include "game/brush.hpp" +#include "entity/component-manager.hpp" +#include "entity/components/transform-component.hpp" +#include "entity/components/model-component.hpp" +#include "entity/entity-manager.hpp" +#include "entity/entity-template.hpp" +#include "entity/system-manager.hpp" +#include "entity/systems/sound-system.hpp" +#include "entity/systems/collision-system.hpp" +#include "entity/systems/render-system.hpp" +#include "entity/systems/tool-system.hpp" +#include "entity/systems/locomotion-system.hpp" +#include "entity/systems/behavior-system.hpp" +#include "entity/systems/steering-system.hpp" +#include "entity/systems/particle-system.hpp" +#include "stb/stb_image_write.h" +#include +#include +#include +#include +#include +#include +#include +#include + +Game::Game(int argc, char* argv[]): + currentState(nullptr), + window(nullptr) +{ + // Get paths + dataPath = getDataPath(); + configPath = getConfigPath(); + + // Create config path if it doesn't exist + if (!pathExists(configPath)) + { + createDirectory(configPath); + } + + std::cout << configPath << std::endl; + + // Setup resource manager + resourceManager = new ResourceManager(); + resourceManager->include(dataPath); + resourceManager->include(configPath); + + // Read strings file + stringTable = resourceManager->load("strings.csv"); + + // Build string map + for (int row = 0; row < stringTable->size(); ++row) + { + stringMap[(*stringTable)[row][0]] = row; + } + + // Determine number of languages + languageCount = (*stringTable)[0].size() - 1; + + // Set current language to English + currentLanguage = 0; + + splashState = new SplashState(this); + sandboxState = new SandboxState(this); +} + +Game::~Game() +{ + if (window) + { + windowManager->destroyWindow(window); + } +} + +void Game::changeState(GameState* state) +{ + if (currentState != nullptr) + { + currentState->exit(); + } + + currentState = state; + if (currentState != nullptr) + { + currentState->enter(); + } +} + +std::string Game::getString(std::size_t languageIndex, const std::string& name) const +{ + std::string value; + + auto it = stringMap.find(name); + if (it != stringMap.end()) + { + value = (*stringTable)[it->second][languageIndex + 1]; + if (value.empty()) + { + value = std::string("# EMPTY STRING: ") + name + std::string(" #"); + } + } + else + { + value = std::string("# MISSING STRING: ") + name + std::string(" #"); + } + + return value; +} + +void Game::changeLanguage(std::size_t languageIndex) +{ + currentLanguage = languageIndex; + window->setTitle(getString(getCurrentLanguage(), "title").c_str()); + + restringUI(); + resizeUI(w, h); +} + +void Game::toggleFullscreen() +{ + fullscreen = !fullscreen; + window->setFullscreen(fullscreen); +} + +void Game::setUpdateRate(double frequency) +{ + stepScheduler.setStepFrequency(frequency); +} + +void Game::setup() +{ + // Initialize default parameters + title = getString(currentLanguage, "title"); + float windowSizeRatio = 3.0f / 4.0f; + const Display* display = deviceManager->getDisplays()->front(); + w = std::get<0>(display->getDimensions()) * windowSizeRatio; + h = std::get<1>(display->getDimensions()) * windowSizeRatio; + w = 1600; + h = 900; + int x = std::get<0>(display->getPosition()) + std::get<0>(display->getDimensions()) / 2 - w / 2; + int y = std::get<1>(display->getPosition()) + std::get<1>(display->getDimensions()) / 2 - h / 2; + unsigned int flags = WindowFlag::RESIZABLE; + fullscreen = false; + bool vsync = true; + double maxFrameDuration = 0.25; + double stepFrequency = 60.0; + + // Create window + window = windowManager->createWindow(title.c_str(), x, y, w, h, fullscreen, flags); + if (!window) + { + throw std::runtime_error("Game::Game(): Failed to create window."); + } + + // Set v-sync mode + window->setVSync(vsync); + + // Setup step scheduler + stepScheduler.setMaxFrameDuration(maxFrameDuration); + stepScheduler.setStepFrequency(stepFrequency); + timestep = stepScheduler.getStepPeriod(); + + // Setup performance sampling + performanceSampler.setSampleSize(15); + + // Get DPI and font size + dpi = display->getDPI(); + fontSizePT = 14; + fontSizePX = fontSizePT * (1.0f / 72.0f) * dpi; + + // Create scene + scene = new Scene(&stepInterpolator); + + // Setup control profile + keyboard = deviceManager->getKeyboards()->front(); + mouse = deviceManager->getMice()->front(); + closeControl.bindKey(keyboard, Scancode::ESCAPE); + closeControl.setActivatedCallback(std::bind(&Application::close, this, EXIT_SUCCESS)); + fullscreenControl.bindKey(keyboard, Scancode::F11); + fullscreenControl.setActivatedCallback(std::bind(&Game::toggleFullscreen, this)); + openRadialMenuControl.bindKey(keyboard, Scancode::LSHIFT); + moveForwardControl.bindKey(keyboard, Scancode::W); + moveBackControl.bindKey(keyboard, Scancode::S); + moveLeftControl.bindKey(keyboard, Scancode::A); + moveRightControl.bindKey(keyboard, Scancode::D); + //rotateCCWControl.bindKey(keyboard, Scancode::Q); + //rotateCWControl.bindKey(keyboard, Scancode::E); + moveRightControl.bindKey(keyboard, Scancode::D); + zoomInControl.bindKey(keyboard, Scancode::EQUALS); + zoomInControl.bindMouseWheelAxis(mouse, MouseWheelAxis::POSITIVE_Y); + zoomOutControl.bindKey(keyboard, Scancode::MINUS); + zoomOutControl.bindMouseWheelAxis(mouse, MouseWheelAxis::NEGATIVE_Y); + adjustCameraControl.bindMouseButton(mouse, 2); + dragCameraControl.bindMouseButton(mouse, 3); + toggleNestViewControl.bindKey(keyboard, Scancode::N); + toggleWireframeControl.bindKey(keyboard, Scancode::V); + screenshotControl.bindKey(keyboard, Scancode::B); + toggleEditModeControl.bindKey(keyboard, Scancode::TAB); + controlProfile.registerControl("close", &closeControl); + controlProfile.registerControl("fullscreen", &fullscreenControl); + controlProfile.registerControl("open-radial-menu", &openRadialMenuControl); + controlProfile.registerControl("move-forward", &moveForwardControl); + controlProfile.registerControl("move-back", &moveBackControl); + controlProfile.registerControl("move-left", &moveLeftControl); + controlProfile.registerControl("move-right", &moveRightControl); + controlProfile.registerControl("rotate-ccw", &rotateCCWControl); + controlProfile.registerControl("rotate-cw", &rotateCWControl); + controlProfile.registerControl("zoom-in", &zoomInControl); + controlProfile.registerControl("zoom-out", &zoomOutControl); + controlProfile.registerControl("adjust-camera", &adjustCameraControl); + controlProfile.registerControl("drag-camera", &dragCameraControl); + controlProfile.registerControl("toggle-nest-view", &toggleNestViewControl); + controlProfile.registerControl("toggle-wireframe", &toggleWireframeControl); + controlProfile.registerControl("screenshot", &screenshotControl); + controlProfile.registerControl("toggle-edit-mode", &toggleEditModeControl); + + wireframe = false; + toggleWireframeControl.setActivatedCallback(std::bind(&Game::toggleWireframe, this)); + screenshotControl.setActivatedCallback(std::bind(&Game::queueScreenshot, this)); + screenshotQueued = false; + + TestEvent event1, event2, event3; + event1.id = 1; + event2.id = 2; + event3.id = 3; + + + eventDispatcher.subscribe(this); + eventDispatcher.schedule(event1, 1.0); + eventDispatcher.schedule(event2, 10.0); + eventDispatcher.schedule(event3, 1.0); + + // Load model resources + try + { + lensModel = resourceManager->load("lens.mdl"); + forcepsModel = resourceManager->load("forceps.mdl"); + brushModel = resourceManager->load("brush.mdl"); + smokeMaterial = resourceManager->load("smoke.mtl"); + } + catch (const std::exception& e) + { + std::cerr << "Failed to load one or more models: \"" << e.what() << "\"" << std::endl; + close(EXIT_FAILURE); + } + + try + { + splashTexture = resourceManager->load("epigraph.png"); + hudSpriteSheetTexture = resourceManager->load("hud.png"); + + // Read texture atlas file + CSVTable* atlasTable = resourceManager->load("hud-atlas.csv"); + + // Build texture atlas + for (int row = 0; row < atlasTable->size(); ++row) + { + std::stringstream ss; + float x; + float y; + float w; + float h; + + ss << (*atlasTable)[row][1]; + ss >> x; + ss.str(std::string()); + ss.clear(); + ss << (*atlasTable)[row][2]; + ss >> y; + ss.str(std::string()); + ss.clear(); + ss << (*atlasTable)[row][3]; + ss >> w; + ss.str(std::string()); + ss.clear(); + ss << (*atlasTable)[row][4]; + ss >> h; + ss.str(std::string()); + + y = static_cast(hudSpriteSheetTexture->getHeight()) - y - h; + x = (int)(x + 0.5f); + y = (int)(y + 0.5f); + w = (int)(w + 0.5f); + h = (int)(h + 0.5f); + + hudTextureAtlas.insert((*atlasTable)[row][0], Rect(Vector2(x, y), Vector2(x + w, y + h))); + } + } + catch (const std::exception& e) + { + std::cerr << "Failed to load one or more textures: \"" << e.what() << "\"" << std::endl; + close(EXIT_FAILURE); + } + + // Load font resources + try + { + //labelTypeface = resourceManager->load("open-sans-regular.ttf"); + labelTypeface = resourceManager->load("caveat-bold.ttf"); + labelFont = labelTypeface->createFont(fontSizePX); + + debugTypeface = resourceManager->load("inconsolata-bold.ttf"); + debugFont = debugTypeface->createFont(fontSizePX); + debugTypeface->loadCharset(debugFont, UnicodeRange::BASIC_LATIN); + + + std::set charset; + charset.emplace(U'æ–¹'); + charset.emplace(U'è•´'); + labelTypeface->loadCharset(labelFont, UnicodeRange::BASIC_LATIN); + labelTypeface->loadCharset(labelFont, charset); + } + catch (const std::exception& e) + { + std::cerr << "Failed to load one or more fonts: \"" << e.what() << "\"" << std::endl; + close(EXIT_FAILURE); + } + + + Shader* shader = resourceManager->load("depth-pass.glsl"); + + /* + VertexFormat format; + format.addAttribute(0); + format.addAttribute(1); + format.addAttribute(2); + + VertexBuffer* vb = graphicsContext->createVertexBuffer(format); + vb->resize(1000); + vb->setData(bla, 0, 1000); + + IndexBuffer* ib = graphicsContext->createIndexBuffer(); + ib->resize(300); + ib->setData(bla, 0, 300); + + graphicsContext->bind(framebuffer); + graphicsContext->bind(shader); + graphicsContext->bind(vb); + graphicsContext->bind(ib); + graphicsContext->draw(100, TRIANGLES); + */ + + cameraRig = nullptr; + orbitCam = new OrbitCam(); + orbitCam->attachCamera(&camera); + freeCam = new FreeCam(); + freeCam->attachCamera(&camera); + + silhouetteRenderTarget.width = w; + silhouetteRenderTarget.height = h; + + // Silhouette framebuffer texture + glGenTextures(1, &silhouetteRenderTarget.texture); + glBindTexture(GL_TEXTURE_2D, silhouetteRenderTarget.texture); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Generate framebuffer + glGenFramebuffers(1, &silhouetteRenderTarget.framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, silhouetteRenderTarget.framebuffer); + + // Attach textures to framebuffer + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, silhouetteRenderTarget.texture, 0); + glDrawBuffer(GL_COLOR_ATTACHMENT0); + glReadBuffer(GL_NONE); + + // Unbind framebuffer and texture + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Setup rendering + defaultRenderTarget.width = w; + defaultRenderTarget.height = h; + defaultRenderTarget.framebuffer = 0; + clearPass = new ClearRenderPass(); + clearPass->setRenderTarget(&defaultRenderTarget); + clearPass->setClear(true, true, false); + clearPass->setClearColor(Vector4(0.0f)); + clearPass->setClearDepth(1.0f); + skyPass = new SkyRenderPass(resourceManager); + skyPass->setRenderTarget(&defaultRenderTarget); + uiPass = new UIRenderPass(resourceManager); + uiPass->setRenderTarget(&defaultRenderTarget); + uiCompositor.addPass(uiPass); + uiCompositor.load(nullptr); + + // Setup UI batching + uiBatch = new BillboardBatch(); + uiBatch->resize(1024); + uiBatcher = new UIBatcher(); + + // Setup root UI element + uiRootElement = new UIContainer(); + eventDispatcher.subscribe(uiRootElement); + eventDispatcher.subscribe(uiRootElement); + eventDispatcher.subscribe(uiRootElement); + + // Create splash screen background element + splashBackgroundImage = new UIImage(); + splashBackgroundImage->setLayerOffset(-1); + splashBackgroundImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + splashBackgroundImage->setVisible(false); + uiRootElement->addChild(splashBackgroundImage); + + // Create splash screen element + splashImage = new UIImage(); + splashImage->setTexture(splashTexture); + splashImage->setVisible(false); + uiRootElement->addChild(splashImage); + + Rect hudTextureAtlasBounds(Vector2(0), Vector2(hudSpriteSheetTexture->getWidth(), hudSpriteSheetTexture->getHeight())); + auto normalizeTextureBounds = [](const Rect& texture, const Rect& atlas) + { + Vector2 atlasDimensions = Vector2(atlas.getWidth(), atlas.getHeight()); + return Rect(texture.getMin() / atlasDimensions, texture.getMax() / atlasDimensions); + }; + + // Create HUD elements + hudContainer = new UIContainer(); + hudContainer->setVisible(false); + uiRootElement->addChild(hudContainer); + + + toolIndicatorBGImage = new UIImage(); + toolIndicatorBGImage->setTexture(hudSpriteSheetTexture); + toolIndicatorBGImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator"), hudTextureAtlasBounds)); + hudContainer->addChild(toolIndicatorBGImage); + + toolIndicatorsBounds = new Rect[8]; + toolIndicatorsBounds[0] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-brush"), hudTextureAtlasBounds); + toolIndicatorsBounds[1] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-spade"), hudTextureAtlasBounds); + toolIndicatorsBounds[2] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-lens"), hudTextureAtlasBounds); + toolIndicatorsBounds[3] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-test-tube"), hudTextureAtlasBounds); + toolIndicatorsBounds[4] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator-forceps"), hudTextureAtlasBounds); + toolIndicatorsBounds[5] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator"), hudTextureAtlasBounds); + toolIndicatorsBounds[6] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator"), hudTextureAtlasBounds); + toolIndicatorsBounds[7] = normalizeTextureBounds(hudTextureAtlas.getBounds("tool-indicator"), hudTextureAtlasBounds); + + toolIndicatorIconImage = new UIImage(); + toolIndicatorIconImage->setTexture(hudSpriteSheetTexture); + toolIndicatorIconImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-brush"), hudTextureAtlasBounds)); + toolIndicatorBGImage->addChild(toolIndicatorIconImage); + + buttonContainer = new UIContainer(); + hudContainer->addChild(buttonContainer); + + playButtonBGImage = new UIImage(); + playButtonBGImage->setTexture(hudSpriteSheetTexture); + playButtonBGImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-background"), hudTextureAtlasBounds)); + //buttonContainer->addChild(playButtonBGImage); + + pauseButtonBGImage = new UIImage(); + pauseButtonBGImage->setTexture(hudSpriteSheetTexture); + pauseButtonBGImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-background"), hudTextureAtlasBounds)); + //buttonContainer->addChild(pauseButtonBGImage); + + fastForwardButtonBGImage = new UIImage(); + fastForwardButtonBGImage->setTexture(hudSpriteSheetTexture); + fastForwardButtonBGImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-background"), hudTextureAtlasBounds)); + //buttonContainer->addChild(fastForwardButtonBGImage); + + playButtonImage = new UIImage(); + playButtonImage->setTexture(hudSpriteSheetTexture); + playButtonImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-play"), hudTextureAtlasBounds)); + //buttonContainer->addChild(playButtonImage); + + fastForwardButtonImage = new UIImage(); + fastForwardButtonImage->setTexture(hudSpriteSheetTexture); + fastForwardButtonImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-fast-forward-2x"), hudTextureAtlasBounds)); + //buttonContainer->addChild(fastForwardButtonImage); + + pauseButtonImage = new UIImage(); + pauseButtonImage->setTexture(hudSpriteSheetTexture); + pauseButtonImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("button-pause"), hudTextureAtlasBounds)); + //buttonContainer->addChild(pauseButtonImage); + + radialMenuContainer = new UIContainer(); + radialMenuContainer->setVisible(false); + uiRootElement->addChild(radialMenuContainer); + + radialMenuBackgroundImage = new UIImage(); + radialMenuBackgroundImage->setTintColor(Vector4(Vector3(0.0f), 0.25f)); + radialMenuContainer->addChild(radialMenuBackgroundImage); + + radialMenuImage = new UIImage(); + radialMenuImage->setTexture(hudSpriteSheetTexture); + radialMenuImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("radial-menu"), hudTextureAtlasBounds)); + radialMenuContainer->addChild(radialMenuImage); + + radialMenuSelectorImage = new UIImage(); + radialMenuSelectorImage->setTexture(hudSpriteSheetTexture); + radialMenuSelectorImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("radial-menu-selector"), hudTextureAtlasBounds)); + radialMenuContainer->addChild(radialMenuSelectorImage); + + toolIconBrushImage = new UIImage(); + toolIconBrushImage->setTexture(hudSpriteSheetTexture); + toolIconBrushImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-brush"), hudTextureAtlasBounds)); + radialMenuImage->addChild(toolIconBrushImage); + + toolIconLensImage = new UIImage(); + toolIconLensImage->setTexture(hudSpriteSheetTexture); + toolIconLensImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-lens"), hudTextureAtlasBounds)); + radialMenuImage->addChild(toolIconLensImage); + + toolIconForcepsImage = new UIImage(); + toolIconForcepsImage->setTexture(hudSpriteSheetTexture); + toolIconForcepsImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-forceps"), hudTextureAtlasBounds)); + radialMenuImage->addChild(toolIconForcepsImage); + + toolIconSpadeImage = new UIImage(); + toolIconSpadeImage->setTexture(hudSpriteSheetTexture); + toolIconSpadeImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-spade"), hudTextureAtlasBounds)); + //radialMenuImage->addChild(toolIconSpadeImage); + + toolIconCameraImage = new UIImage(); + toolIconCameraImage->setTexture(hudSpriteSheetTexture); + toolIconCameraImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-camera"), hudTextureAtlasBounds)); + radialMenuImage->addChild(toolIconCameraImage); + + toolIconTestTubeImage = new UIImage(); + toolIconTestTubeImage->setTexture(hudSpriteSheetTexture); + toolIconTestTubeImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("tool-icon-test-tube"), hudTextureAtlasBounds)); + //radialMenuImage->addChild(toolIconTestTubeImage); + + + antTag = new UIContainer(); + antTag->setLayerOffset(-10); + antTag->setVisible(false); + + uiRootElement->addChild(antTag); + + antLabelContainer = new UIContainer(); + antTag->addChild(antLabelContainer); + + antLabelTL = new UIImage(); + antLabelTR = new UIImage(); + antLabelBL = new UIImage(); + antLabelBR = new UIImage(); + antLabelCC = new UIImage(); + antLabelCT = new UIImage(); + antLabelCB = new UIImage(); + antLabelCL = new UIImage(); + antLabelCR = new UIImage(); + + antLabelTL->setTexture(hudSpriteSheetTexture); + antLabelTR->setTexture(hudSpriteSheetTexture); + antLabelBL->setTexture(hudSpriteSheetTexture); + antLabelBR->setTexture(hudSpriteSheetTexture); + antLabelCC->setTexture(hudSpriteSheetTexture); + antLabelCT->setTexture(hudSpriteSheetTexture); + antLabelCB->setTexture(hudSpriteSheetTexture); + antLabelCL->setTexture(hudSpriteSheetTexture); + antLabelCR->setTexture(hudSpriteSheetTexture); + + Rect labelTLBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-tl"), hudTextureAtlasBounds); + Rect labelTRBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-tr"), hudTextureAtlasBounds); + Rect labelBLBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-bl"), hudTextureAtlasBounds); + Rect labelBRBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-br"), hudTextureAtlasBounds); + Rect labelCCBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-cc"), hudTextureAtlasBounds); + Rect labelCTBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-ct"), hudTextureAtlasBounds); + Rect labelCBBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-cb"), hudTextureAtlasBounds); + Rect labelCLBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-cl"), hudTextureAtlasBounds); + Rect labelCRBounds = normalizeTextureBounds(hudTextureAtlas.getBounds("label-cr"), hudTextureAtlasBounds); + + Vector2 labelTLMin = labelTLBounds.getMin(); + Vector2 labelTRMin = labelTRBounds.getMin(); + Vector2 labelBLMin = labelBLBounds.getMin(); + Vector2 labelBRMin = labelBRBounds.getMin(); + Vector2 labelCCMin = labelCCBounds.getMin(); + Vector2 labelCTMin = labelCTBounds.getMin(); + Vector2 labelCBMin = labelCBBounds.getMin(); + Vector2 labelCLMin = labelCLBounds.getMin(); + Vector2 labelCRMin = labelCRBounds.getMin(); + Vector2 labelTLMax = labelTLBounds.getMax(); + Vector2 labelTRMax = labelTRBounds.getMax(); + Vector2 labelBLMax = labelBLBounds.getMax(); + Vector2 labelBRMax = labelBRBounds.getMax(); + Vector2 labelCCMax = labelCCBounds.getMax(); + Vector2 labelCTMax = labelCTBounds.getMax(); + Vector2 labelCBMax = labelCBBounds.getMax(); + Vector2 labelCLMax = labelCLBounds.getMax(); + Vector2 labelCRMax = labelCRBounds.getMax(); + + antLabelTL->setTextureBounds(labelTLBounds); + antLabelTR->setTextureBounds(labelTRBounds); + antLabelBL->setTextureBounds(labelBLBounds); + antLabelBR->setTextureBounds(labelBRBounds); + antLabelCC->setTextureBounds(labelCCBounds); + antLabelCT->setTextureBounds(labelCTBounds); + antLabelCB->setTextureBounds(labelCBBounds); + antLabelCL->setTextureBounds(labelCLBounds); + antLabelCR->setTextureBounds(labelCRBounds); + + antLabelContainer->addChild(antLabelTL); + antLabelContainer->addChild(antLabelTR); + antLabelContainer->addChild(antLabelBL); + antLabelContainer->addChild(antLabelBR); + antLabelContainer->addChild(antLabelCC); + antLabelContainer->addChild(antLabelCT); + antLabelContainer->addChild(antLabelCB); + antLabelContainer->addChild(antLabelCL); + antLabelContainer->addChild(antLabelCR); + + antLabel = new UILabel(); + antLabel->setFont(labelFont); + antLabel->setText("Boggy B."); + antLabel->setTintColor(Vector4(Vector3(0.0f), 1.0f)); + antLabel->setLayerOffset(1); + antLabelContainer->addChild(antLabel); + + fpsLabel = new UILabel(); + fpsLabel->setFont(debugFont); + fpsLabel->setTintColor(Vector4(1, 1, 0, 1)); + fpsLabel->setLayerOffset(50); + fpsLabel->setAnchor(Anchor::TOP_LEFT); + uiRootElement->addChild(fpsLabel); + + + antPin = new UIImage(); + antPin->setTexture(hudSpriteSheetTexture); + antPin->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("label-pin"), hudTextureAtlasBounds)); + antTag->addChild(antPin); + + antLabelPinHole = new UIImage(); + antLabelPinHole->setTexture(hudSpriteSheetTexture); + antLabelPinHole->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("label-pin-hole"), hudTextureAtlasBounds)); + antLabelContainer->addChild(antLabelPinHole); + + notificationBoxImage = new UIImage(); + notificationBoxImage->setTexture(hudSpriteSheetTexture); + notificationBoxImage->setTextureBounds(normalizeTextureBounds(hudTextureAtlas.getBounds("notification-box"), hudTextureAtlasBounds)); + notificationBoxImage->setVisible(false); + hudContainer->addChild(notificationBoxImage); + + // Construct show notification animation clip + notificationCount = 0; + AnimationChannel* channel2; + showNotificationClip.setInterpolator(easeOutQuint); + channel2 = showNotificationClip.addChannel(0); + channel2->insertKeyframe(0.0f, Vector2(0.0f, 0.0f)); + channel2->insertKeyframe(0.5f, Vector2(32.0f, 1.0f)); + showNotificationAnimation.setClip(&showNotificationClip); + showNotificationAnimation.setSpeed(1.0f); + showNotificationAnimation.setTimeFrame(showNotificationClip.getTimeFrame()); + animator.addAnimation(&showNotificationAnimation); + + showNotificationAnimation.setAnimateCallback + ( + [this](std::size_t id, const Vector2& values) + { + notificationBoxImage->setVisible(true); + notificationBoxImage->setTintColor(Vector4(Vector3(1.0f), values.y)); + //notificationBoxImage->setTranslation(Vector2(0.0f, -32.0f + values.x)); + } + ); + showNotificationAnimation.setEndCallback + ( + [this]() + { + hideNotificationAnimation.rewind(); + hideNotificationAnimation.play(); + } + ); + + // Construct hide notification animation clip + hideNotificationClip.setInterpolator(easeOutQuint); + AnimationChannel* channel; + channel = hideNotificationClip.addChannel(0); + channel->insertKeyframe(0.0f, 1.0f); + channel->insertKeyframe(10.5f, 1.0f); + channel->insertKeyframe(12.0f, 0.0f); + hideNotificationAnimation.setClip(&hideNotificationClip); + hideNotificationAnimation.setSpeed(1.0f); + hideNotificationAnimation.setTimeFrame(hideNotificationClip.getTimeFrame()); + animator.addAnimation(&hideNotificationAnimation); + hideNotificationAnimation.setAnimateCallback + ( + [this](std::size_t id, float opacity) + { + notificationBoxImage->setTintColor(Vector4(Vector3(1.0f), opacity)); + } + ); + hideNotificationAnimation.setEndCallback + ( + [this]() + { + notificationBoxImage->setVisible(false); + --notificationCount; + popNotification(); + } + ); + + // Construct box selection + boxSelectionImageBackground = new UIImage(); + boxSelectionImageBackground->setAnchor(Anchor::CENTER); + boxSelectionImageTop = new UIImage(); + boxSelectionImageTop->setAnchor(Anchor::TOP_LEFT); + boxSelectionImageBottom = new UIImage(); + boxSelectionImageBottom->setAnchor(Anchor::BOTTOM_LEFT); + boxSelectionImageLeft = new UIImage(); + boxSelectionImageLeft->setAnchor(Anchor::TOP_LEFT); + boxSelectionImageRight = new UIImage(); + boxSelectionImageRight->setAnchor(Anchor::TOP_RIGHT); + boxSelectionContainer = new UIContainer(); + boxSelectionContainer->setLayerOffset(80); + boxSelectionContainer->addChild(boxSelectionImageBackground); + boxSelectionContainer->addChild(boxSelectionImageTop); + boxSelectionContainer->addChild(boxSelectionImageBottom); + boxSelectionContainer->addChild(boxSelectionImageLeft); + boxSelectionContainer->addChild(boxSelectionImageRight); + boxSelectionContainer->setVisible(false); + uiRootElement->addChild(boxSelectionContainer); + boxSelectionImageBackground->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.5f)); + boxSelectionContainer->setTintColor(Vector4(1.0f, 0.0f, 0.0f, 1.0f)); + boxSelectionBorderWidth = 2.0f; + + cameraGridColor = Vector4(1, 1, 1, 0.5f); + cameraGridY0Image = new UIImage(); + cameraGridY0Image->setAnchor(Vector2(0.5f, (1.0f / 3.0f))); + cameraGridY0Image->setTintColor(cameraGridColor); + cameraGridY1Image = new UIImage(); + cameraGridY1Image->setAnchor(Vector2(0.5f, (2.0f / 3.0f))); + cameraGridY1Image->setTintColor(cameraGridColor); + cameraGridX0Image = new UIImage(); + cameraGridX0Image->setAnchor(Vector2((1.0f / 3.0f), 0.5f)); + cameraGridX0Image->setTintColor(cameraGridColor); + cameraGridX1Image = new UIImage(); + cameraGridX1Image->setAnchor(Vector2((2.0f / 3.0f), 0.5f)); + cameraGridX1Image->setTintColor(cameraGridColor); + cameraGridContainer = new UIContainer(); + cameraGridContainer->addChild(cameraGridY0Image); + cameraGridContainer->addChild(cameraGridY1Image); + cameraGridContainer->addChild(cameraGridX0Image); + cameraGridContainer->addChild(cameraGridX1Image); + cameraGridContainer->setVisible(true); + uiRootElement->addChild(cameraGridContainer); + + cameraFlashImage = new UIImage(); + cameraFlashImage->setLayerOffset(99); + cameraFlashImage->setTintColor(Vector4(1.0f)); + cameraFlashImage->setVisible(false); + uiRootElement->addChild(cameraFlashImage); + + blackoutImage = new UIImage(); + blackoutImage->setLayerOffset(98); + blackoutImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + blackoutImage->setVisible(false); + uiRootElement->addChild(blackoutImage); + + // Construct fade-in animation clip + fadeInClip.setInterpolator(easeOutCubic); + channel = fadeInClip.addChannel(0); + channel->insertKeyframe(0.0f, 1.0f); + channel->insertKeyframe(1.0f, 0.0f); + + // Construct fade-out animation clip + fadeOutClip.setInterpolator(easeOutCubic); + channel = fadeOutClip.addChannel(0); + channel->insertKeyframe(0.0f, 0.0f); + channel->insertKeyframe(1.0f, 1.0f); + + // Setup fade-in animation callbacks + fadeInAnimation.setAnimateCallback + ( + [this](std::size_t id, float opacity) + { + Vector3 color = Vector3(blackoutImage->getTintColor()); + blackoutImage->setTintColor(Vector4(color, opacity)); + } + ); + fadeInAnimation.setEndCallback + ( + [this]() + { + blackoutImage->setVisible(false); + if (fadeInEndCallback != nullptr) + { + fadeInEndCallback(); + } + } + ); + + // Setup fade-out animation callbacks + fadeOutAnimation.setAnimateCallback + ( + [this](std::size_t id, float opacity) + { + Vector3 color = Vector3(blackoutImage->getTintColor()); + blackoutImage->setTintColor(Vector4(color, opacity)); + } + ); + fadeOutAnimation.setEndCallback + ( + [this]() + { + blackoutImage->setVisible(false); + if (fadeOutEndCallback != nullptr) + { + fadeOutEndCallback(); + } + } + ); + + animator.addAnimation(&fadeInAnimation); + animator.addAnimation(&fadeOutAnimation); + + + // Construct camera flash animation clip + cameraFlashClip.setInterpolator(easeOutQuad); + channel = cameraFlashClip.addChannel(0); + channel->insertKeyframe(0.0f, 1.0f); + channel->insertKeyframe(1.0f, 0.0f); + + // Setup camera flash animation + float flashDuration = 0.5f; + cameraFlashAnimation.setSpeed(1.0f / flashDuration); + cameraFlashAnimation.setLoop(false); + cameraFlashAnimation.setClip(&cameraFlashClip); + cameraFlashAnimation.setTimeFrame(cameraFlashClip.getTimeFrame()); + cameraFlashAnimation.setAnimateCallback + ( + [this](std::size_t id, float opacity) + { + cameraFlashImage->setTintColor(Vector4(Vector3(1.0f), opacity)); + } + ); + cameraFlashAnimation.setStartCallback + ( + [this]() + { + cameraFlashImage->setVisible(true); + cameraFlashImage->setTintColor(Vector4(1.0f)); + cameraFlashImage->resetTweens(); + } + ); + cameraFlashAnimation.setEndCallback + ( + [this]() + { + cameraFlashImage->setVisible(false); + } + ); + animator.addAnimation(&cameraFlashAnimation); + + // Setup default compositor + { + defaultCompositor.load(nullptr); + } + + // Setup shadow map pass and compositor + { + // Set shadow map resolution + shadowMapResolution = 4096; + + // Generate shadow map framebuffer + glGenFramebuffers(1, &shadowMapFramebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, shadowMapFramebuffer); + + // Generate shadow map depth texture + glGenTextures(1, &shadowMapDepthTextureID); + glBindTexture(GL_TEXTURE_2D, shadowMapDepthTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, shadowMapResolution, shadowMapResolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); + + // Attach depth texture to framebuffer + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowMapDepthTextureID, 0); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + + // Unbind shadow map depth texture + glBindTexture(GL_TEXTURE_2D, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Setup shadow map render target + shadowMapRenderTarget.width = shadowMapResolution; + shadowMapRenderTarget.height = shadowMapResolution; + shadowMapRenderTarget.framebuffer = shadowMapFramebuffer; + + // Setup texture class + shadowMapDepthTexture.setTextureID(shadowMapDepthTextureID); + shadowMapDepthTexture.setWidth(shadowMapResolution); + shadowMapDepthTexture.setHeight(shadowMapResolution); + + // Setup shadow map render pass + shadowMapPass = new ShadowMapRenderPass(resourceManager); + shadowMapPass->setRenderTarget(&shadowMapRenderTarget); + shadowMapPass->setViewCamera(&camera); + shadowMapPass->setLightCamera(&sunlightCamera); + + // Setup shadow map compositor + shadowMapCompositor.addPass(shadowMapPass); + shadowMapCompositor.load(nullptr); + } + + // Setup scene + { + defaultLayer = scene->addLayer(); + + // Setup lighting pass + lightingPass = new LightingRenderPass(resourceManager); + lightingPass->setRenderTarget(&defaultRenderTarget); + lightingPass->setShadowMapPass(shadowMapPass); + lightingPass->setShadowMap(&shadowMapDepthTexture); + + // Setup clear silhouette pass + clearSilhouettePass = new ClearRenderPass(); + clearSilhouettePass->setRenderTarget(&silhouetteRenderTarget); + clearSilhouettePass->setClear(true, false, false); + clearSilhouettePass->setClearColor(Vector4(0.0f)); + + // Setup silhouette pass + silhouettePass = new SilhouetteRenderPass(resourceManager); + silhouettePass->setRenderTarget(&silhouetteRenderTarget); + + // Setup final pass + finalPass = new FinalRenderPass(resourceManager); + finalPass->setRenderTarget(&defaultRenderTarget); + finalPass->setSilhouetteRenderTarget(&silhouetteRenderTarget); + + // Setup default compositor + defaultCompositor.addPass(clearPass); + defaultCompositor.addPass(skyPass); + defaultCompositor.addPass(lightingPass); + defaultCompositor.addPass(clearSilhouettePass); + defaultCompositor.addPass(silhouettePass); + defaultCompositor.addPass(finalPass); + defaultCompositor.load(nullptr); + + // Setup camera + camera.setPerspective(glm::radians(40.0f), static_cast(w) / static_cast(h), 0.1, 100.0f); + camera.lookAt(Vector3(0.0f, 4.0f, 2.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(0.0f, 1.0f, 0.0f)); + camera.setCompositor(&defaultCompositor); + camera.setCompositeIndex(1); + defaultLayer->addObject(&camera); + + // Setup sun + sunlight.setDirection(Vector3(0, -1, 0)); + setTimeOfDay(11.0f); + defaultLayer->addObject(&sunlight); + + // Setup sunlight camera + sunlightCamera.setOrthographic(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f); + sunlightCamera.setCompositor(&shadowMapCompositor); + sunlightCamera.setCompositeIndex(0); + sunlightCamera.setCullingEnabled(true); + sunlightCamera.setCullingMask(&camera.getViewFrustum()); + defaultLayer->addObject(&sunlightCamera); + } + + // Setup UI scene + uiLayer = scene->addLayer(); + uiLayer->addObject(uiBatch); + uiLayer->addObject(&uiCamera); + + // Setup UI camera + uiCamera.lookAt(Vector3(0), Vector3(0, 0, -1), Vector3(0, 1, 0)); + uiCamera.resetTweens(); + uiCamera.setCompositor(&uiCompositor); + uiCamera.setCompositeIndex(0); + uiCamera.setCullingEnabled(false); + + restringUI(); + resizeUI(w, h); + + + time = 0.0f; + + + // Tools + currentTool = nullptr; + + lens = new Lens(lensModel, &animator); + lens->setOrbitCam(orbitCam); + defaultLayer->addObject(lens->getModelInstance()); + defaultLayer->addObject(lens->getSpotlight()); + lens->setSunDirection(-sunlightCamera.getForward()); + + ModelInstance* modelInstance = lens->getModelInstance(); + for (std::size_t i = 0; i < modelInstance->getModel()->getGroupCount(); ++i) + { + Material* material = modelInstance->getModel()->getGroup(i)->material->clone(); + material->setFlags(material->getFlags() | 256); + modelInstance->setMaterialSlot(i, material); + } + + // Forceps + forceps = new Forceps(forcepsModel, &animator); + forceps->setOrbitCam(orbitCam); + defaultLayer->addObject(forceps->getModelInstance()); + + // Brush + brush = new Brush(brushModel, &animator); + brush->setOrbitCam(orbitCam); + defaultLayer->addObject(brush->getModelInstance()); + + glEnable(GL_MULTISAMPLE); + // + + performanceSampler.setSampleSize(30); + + // Initialize component manager + componentManager = new ComponentManager(); + + // Initialize entity manager + entityManager = new EntityManager(componentManager); + + // Initialize systems + soundSystem = new SoundSystem(componentManager); + collisionSystem = new CollisionSystem(componentManager); + renderSystem = new RenderSystem(componentManager, defaultLayer); + toolSystem = new ToolSystem(componentManager); + toolSystem->setPickingCamera(&camera); + toolSystem->setPickingViewport(Vector4(0, 0, w, h)); + eventDispatcher.subscribe(toolSystem); + behaviorSystem = new BehaviorSystem(componentManager); + steeringSystem = new SteeringSystem(componentManager); + locomotionSystem = new LocomotionSystem(componentManager); + particleSystem = new ParticleSystem(componentManager); + particleSystem->resize(1000); + particleSystem->setMaterial(smokeMaterial); + particleSystem->setDirection(Vector3(0, 1, 0)); + lens->setParticleSystem(particleSystem); + particleSystem->getBillboardBatch()->setAlignment(&camera, BillboardAlignmentMode::SPHERICAL); + defaultLayer->addObject(particleSystem->getBillboardBatch()); + + + // Initialize system manager + systemManager = new SystemManager(); + systemManager->addSystem(soundSystem); + systemManager->addSystem(behaviorSystem); + systemManager->addSystem(steeringSystem); + systemManager->addSystem(locomotionSystem); + systemManager->addSystem(collisionSystem); + systemManager->addSystem(toolSystem); + systemManager->addSystem(particleSystem); + systemManager->addSystem(renderSystem); + + EntityID sidewalkPanel; + sidewalkPanel = createInstanceOf("sidewalk-panel"); + + EntityID antHill = createInstanceOf("ant-hill"); + setTranslation(antHill, Vector3(20, 0, 40)); + + EntityID antNest = createInstanceOf("ant-nest"); + setTranslation(antNest, Vector3(20, 0, 40)); + + lollipop = createInstanceOf("lollipop"); + setTranslation(lollipop, Vector3(30.0f, 3.5f * 0.5f, -30.0f)); + setRotation(lollipop, glm::angleAxis(glm::radians(8.85f), Vector3(1.0f, 0.0f, 0.0f))); + + // Load navmesh + TriangleMesh* navmesh = resourceManager->load("sidewalk.mesh"); + + // Find surface + TriangleMesh::Triangle* surface = nullptr; + Vector3 barycentricPosition; + Ray ray; + ray.origin = Vector3(0, 100, 0); + ray.direction = Vector3(0, -1, 0); + auto intersection = ray.intersects(*navmesh); + if (std::get<0>(intersection)) + { + surface = (*navmesh->getTriangles())[std::get<3>(intersection)]; + + Vector3 position = ray.extrapolate(std::get<1>(intersection)); + Vector3 a = surface->edge->vertex->position; + Vector3 b = surface->edge->next->vertex->position; + Vector3 c = surface->edge->previous->vertex->position; + + barycentricPosition = barycentric(position, a, b, c); + } + + for (int i = 0; i < 0; ++i) + { + EntityID ant = createInstanceOf("worker-ant"); + setTranslation(ant, Vector3(0.0f, 0, 0.0f)); + + BehaviorComponent* behavior = new BehaviorComponent(); + SteeringComponent* steering = new SteeringComponent(); + LeggedLocomotionComponent* locomotion = new LeggedLocomotionComponent(); + componentManager->addComponent(ant, behavior); + componentManager->addComponent(ant, steering); + componentManager->addComponent(ant, locomotion); + + locomotion->surface = surface; + behavior->wanderTriangle = surface; + locomotion->barycentricPosition = barycentricPosition; + } + + + EntityID tool0 = createInstanceOf("lens"); + + changeState(splashState); +} + +void Game::input() +{ +} + +void Game::update(float t, float dt) +{ + this->time = t; + + // Dispatch scheduled events + eventDispatcher.update(t); + + // Execute current state + if (currentState != nullptr) + { + currentState->execute(); + } + + // Update systems + systemManager->update(t, dt); + + // Update animations + animator.animate(dt); + + std::stringstream stream; + stream.precision(2); + stream << std::fixed << (performanceSampler.getMeanFrameDuration() * 1000.0f); + fpsLabel->setText(stream.str()); + + uiRootElement->update(); + + controlProfile.update(); +} + +void Game::render() +{ + // Perform sub-frame interpolation on UI elements + uiRootElement->interpolate(stepScheduler.getScheduledSubsteps()); + + // Update and batch UI elements + uiBatcher->batch(uiBatch, uiRootElement); + + // Perform sub-frame interpolation particles + particleSystem->getBillboardBatch()->interpolate(stepScheduler.getScheduledSubsteps()); + particleSystem->getBillboardBatch()->batch(); + + // Render scene + renderer.render(*scene); + + // Swap window framebuffers + window->swapBuffers(); + + if (screenshotQueued) + { + screenshot(); + screenshotQueued = false; + } +} + +void Game::exit() +{ + +} + +void Game::handleEvent(const WindowResizedEvent& event) +{ + w = event.width; + h = event.height; + + defaultRenderTarget.width = event.width; + defaultRenderTarget.height = event.height; + glViewport(0, 0, event.width, event.height); + + + camera.setPerspective(glm::radians(40.0f), static_cast(w) / static_cast(h), 0.1, 100.0f); + + + toolSystem->setPickingViewport(Vector4(0, 0, w, h)); + + resizeUI(event.width, event.height); +} + +void Game::handleEvent(const KeyPressedEvent& event) +{ + if (event.scancode == Scancode::SPACE) + { + changeLanguage((getCurrentLanguage() + 1) % getLanguageCount()); + } +} + +void Game::handleEvent(const TestEvent& event) +{ + std::cout << "Event received!!! ID: " << event.id << std::endl; +} + +void Game::resizeUI(int w, int h) +{ + // Adjust root element dimensions + uiRootElement->setDimensions(Vector2(w, h)); + uiRootElement->update(); + + splashBackgroundImage->setDimensions(Vector2(w, h)); + splashBackgroundImage->setAnchor(Anchor::TOP_LEFT); + + + // Resize splash screen image + splashImage->setAnchor(Anchor::CENTER); + splashImage->setDimensions(Vector2(splashTexture->getWidth(), splashTexture->getHeight())); + + // Adjust UI camera projection matrix + uiCamera.setOrthographic(0.0f, w, h, 0.0f, -1.0f, 1.0f); + uiCamera.resetTweens(); + + // Resize camera flash image + cameraFlashImage->setDimensions(Vector2(w, h)); + cameraFlashImage->setAnchor(Anchor::CENTER); + + // Resize blackout image + blackoutImage->setDimensions(Vector2(w, h)); + blackoutImage->setAnchor(Anchor::CENTER); + + // Resize HUD + float hudPadding = 20.0f; + hudContainer->setDimensions(Vector2(w - hudPadding * 2.0f, h - hudPadding * 2.0f)); + hudContainer->setAnchor(Anchor::CENTER); + + // Tool indicator + Rect toolIndicatorBounds = hudTextureAtlas.getBounds("tool-indicator"); + toolIndicatorBGImage->setDimensions(Vector2(toolIndicatorBounds.getWidth(), toolIndicatorBounds.getHeight())); + toolIndicatorBGImage->setAnchor(Anchor::TOP_LEFT); + + Rect toolIndicatorIconBounds = hudTextureAtlas.getBounds("tool-indicator-lens"); + toolIndicatorIconImage->setDimensions(Vector2(toolIndicatorIconBounds.getWidth(), toolIndicatorIconBounds.getHeight())); + toolIndicatorIconImage->setAnchor(Anchor::CENTER); + + + // Buttons + Rect playButtonBounds = hudTextureAtlas.getBounds("button-play"); + Rect fastForwardButtonBounds = hudTextureAtlas.getBounds("button-fast-forward-2x"); + Rect pauseButtonBounds = hudTextureAtlas.getBounds("button-pause"); + Rect buttonBackgroundBounds = hudTextureAtlas.getBounds("button-background"); + Vector2 buttonBGDimensions = Vector2(buttonBackgroundBounds.getWidth(), buttonBackgroundBounds.getHeight()); + float buttonMargin = 10.0f; + float buttonDepth = 15.0f; + + float buttonContainerWidth = fastForwardButtonBounds.getWidth(); + float buttonContainerHeight = fastForwardButtonBounds.getHeight(); + buttonContainer->setDimensions(Vector2(buttonContainerWidth, buttonContainerHeight)); + buttonContainer->setAnchor(Anchor::TOP_RIGHT); + + playButtonImage->setDimensions(Vector2(playButtonBounds.getWidth(), playButtonBounds.getHeight())); + playButtonImage->setAnchor(Vector2(0.0f, 0.0f)); + playButtonBGImage->setDimensions(buttonBGDimensions); + playButtonBGImage->setAnchor(Vector2(0.0f, 1.0f)); + + fastForwardButtonImage->setDimensions(Vector2(fastForwardButtonBounds.getWidth(), fastForwardButtonBounds.getHeight())); + fastForwardButtonImage->setAnchor(Vector2(0.5f, 5.0f)); + fastForwardButtonBGImage->setDimensions(buttonBGDimensions); + fastForwardButtonBGImage->setAnchor(Vector2(0.5f, 0.5f)); + + pauseButtonImage->setDimensions(Vector2(pauseButtonBounds.getWidth(), pauseButtonBounds.getHeight())); + pauseButtonImage->setAnchor(Vector2(1.0f, 0.0f)); + pauseButtonBGImage->setDimensions(buttonBGDimensions); + pauseButtonBGImage->setAnchor(Vector2(1.0f, 1.0f)); + + // Radial menu + Rect radialMenuBounds = hudTextureAtlas.getBounds("radial-menu"); + radialMenuContainer->setDimensions(Vector2(w, h)); + radialMenuContainer->setAnchor(Anchor::CENTER); + radialMenuContainer->setLayerOffset(30); + + radialMenuBackgroundImage->setDimensions(Vector2(w, h)); + radialMenuBackgroundImage->setAnchor(Anchor::CENTER); + radialMenuBackgroundImage->setLayerOffset(-1); + + //radialMenuImage->setDimensions(Vector2(w * 0.5f, h * 0.5f)); + radialMenuImage->setDimensions(Vector2(radialMenuBounds.getWidth(), radialMenuBounds.getHeight())); + radialMenuImage->setAnchor(Anchor::CENTER); + + Rect radialMenuSelectorBounds = hudTextureAtlas.getBounds("radial-menu-selector"); + radialMenuSelectorImage->setDimensions(Vector2(radialMenuSelectorBounds.getWidth(), radialMenuSelectorBounds.getHeight())); + radialMenuSelectorImage->setAnchor(Anchor::CENTER); + + Rect toolIconBrushBounds = hudTextureAtlas.getBounds("tool-icon-brush"); + toolIconBrushImage->setDimensions(Vector2(toolIconBrushBounds.getWidth(), toolIconBrushBounds.getHeight())); + toolIconBrushImage->setAnchor(Anchor::CENTER); + + Rect toolIconLensBounds = hudTextureAtlas.getBounds("tool-icon-lens"); + toolIconLensImage->setDimensions(Vector2(toolIconLensBounds.getWidth(), toolIconLensBounds.getHeight())); + toolIconLensImage->setAnchor(Anchor::CENTER); + + Rect toolIconForcepsBounds = hudTextureAtlas.getBounds("tool-icon-forceps"); + toolIconForcepsImage->setDimensions(Vector2(toolIconForcepsBounds.getWidth(), toolIconForcepsBounds.getHeight())); + toolIconForcepsImage->setAnchor(Anchor::CENTER); + + Rect toolIconSpadeBounds = hudTextureAtlas.getBounds("tool-icon-spade"); + toolIconSpadeImage->setDimensions(Vector2(toolIconSpadeBounds.getWidth(), toolIconSpadeBounds.getHeight())); + toolIconSpadeImage->setAnchor(Anchor::CENTER); + + Rect toolIconCameraBounds = hudTextureAtlas.getBounds("tool-icon-camera"); + toolIconCameraImage->setDimensions(Vector2(toolIconCameraBounds.getWidth(), toolIconCameraBounds.getHeight())); + toolIconCameraImage->setAnchor(Anchor::CENTER); + + + Rect toolIconTestTubeBounds = hudTextureAtlas.getBounds("tool-icon-test-tube"); + toolIconTestTubeImage->setDimensions(Vector2(toolIconTestTubeBounds.getWidth(), toolIconTestTubeBounds.getHeight())); + toolIconTestTubeImage->setAnchor(Anchor::CENTER); + + Rect labelCornerBounds = hudTextureAtlas.getBounds("label-tl"); + Vector2 labelCornerDimensions(labelCornerBounds.getWidth(), labelCornerBounds.getHeight()); + + Vector2 antLabelPadding(10.0f, 6.0f); + antLabelContainer->setDimensions(antLabel->getDimensions() + antLabelPadding * 2.0f); + antLabelContainer->setTranslation(Vector2(0.0f, (int)(-antPin->getDimensions().y * 0.125f))); + antLabelTL->setDimensions(labelCornerDimensions); + antLabelTR->setDimensions(labelCornerDimensions); + antLabelBL->setDimensions(labelCornerDimensions); + antLabelBR->setDimensions(labelCornerDimensions); + antLabelCC->setDimensions(Vector2(antLabel->getDimensions().x - labelCornerDimensions.x * 2.0f + antLabelPadding.x * 2.0f, antLabel->getDimensions().y - labelCornerDimensions.y * 2.0f + antLabelPadding.y * 2.0f)); + antLabelCT->setDimensions(Vector2(antLabel->getDimensions().x - labelCornerDimensions.x * 2.0f + antLabelPadding.x * 2.0f, labelCornerDimensions.y)); + antLabelCB->setDimensions(Vector2(antLabel->getDimensions().x - labelCornerDimensions.x * 2.0f + antLabelPadding.x * 2.0f, labelCornerDimensions.y)); + + antLabelCL->setDimensions(Vector2(labelCornerDimensions.x, antLabel->getDimensions().y - labelCornerDimensions.y * 2.0f + antLabelPadding.y * 2.0f)); + antLabelCR->setDimensions(Vector2(labelCornerDimensions.x, antLabel->getDimensions().y - labelCornerDimensions.y * 2.0f + antLabelPadding.y * 2.0f)); + + + + antLabelContainer->setAnchor(Vector2(0.5f, 0.5f)); + antLabelTL->setAnchor(Anchor::TOP_LEFT); + antLabelTR->setAnchor(Anchor::TOP_RIGHT); + antLabelBL->setAnchor(Anchor::BOTTOM_LEFT); + antLabelBR->setAnchor(Anchor::BOTTOM_RIGHT); + antLabelCC->setAnchor(Anchor::CENTER); + antLabelCT->setAnchor(Vector2(0.5f, 0.0f)); + antLabelCB->setAnchor(Vector2(0.5f, 1.0f)); + antLabelCL->setAnchor(Vector2(0.0f, 0.5f)); + antLabelCR->setAnchor(Vector2(1.0f, 0.5f)); + antLabel->setAnchor(Anchor::CENTER); + + Rect antPinBounds = hudTextureAtlas.getBounds("label-pin"); + antPin->setDimensions(Vector2(antPinBounds.getWidth(), antPinBounds.getHeight())); + antPin->setAnchor(Vector2(0.5f, 1.0f)); + + Rect pinHoleBounds = hudTextureAtlas.getBounds("label-pin-hole"); + antLabelPinHole->setDimensions(Vector2(pinHoleBounds.getWidth(), pinHoleBounds.getHeight())); + antLabelPinHole->setAnchor(Vector2(0.5f, 0.0f)); + antLabelPinHole->setTranslation(Vector2(0.0f, -antLabelPinHole->getDimensions().y * 0.5f)); + antLabelPinHole->setLayerOffset(2); + + + float pinDistance = 20.0f; + antTag->setAnchor(Anchor::CENTER); + antTag->setDimensions(Vector2(antLabelContainer->getDimensions().x, antPin->getDimensions().y)); + + Rect notificationBoxBounds = hudTextureAtlas.getBounds("notification-box"); + notificationBoxImage->setDimensions(Vector2(notificationBoxBounds.getWidth(), notificationBoxBounds.getHeight())); + notificationBoxImage->setAnchor(Vector2(0.5f, 0.0f)); + + float cameraGridLineWidth = 2.0f; + cameraGridContainer->setDimensions(Vector2(w, h)); + cameraGridY0Image->setDimensions(Vector2(w, cameraGridLineWidth)); + cameraGridY1Image->setDimensions(Vector2(w, cameraGridLineWidth)); + cameraGridX0Image->setDimensions(Vector2(cameraGridLineWidth, h)); + cameraGridX1Image->setDimensions(Vector2(cameraGridLineWidth, h)); + cameraGridY0Image->setTranslation(Vector2(0)); + cameraGridY1Image->setTranslation(Vector2(0)); + cameraGridX0Image->setTranslation(Vector2(0)); + cameraGridX1Image->setTranslation(Vector2(0)); + + UIImage* icons[] = + { + toolIconBrushImage, + nullptr, + toolIconLensImage, + nullptr, + toolIconForcepsImage, + nullptr, + toolIconCameraImage, + nullptr + }; + + Rect radialMenuIconRingBounds = hudTextureAtlas.getBounds("radial-menu-icon-ring"); + float iconOffset = radialMenuIconRingBounds.getWidth() * 0.5f; + float sectorAngle = (2.0f * 3.14159264f) / 8.0f; + for (int i = 0; i < 8; ++i) + { + float angle = sectorAngle * static_cast(i - 4); + Vector2 translation = Vector2(std::cos(angle), std::sin(angle)) * iconOffset; + translation.x = (int)(translation.x + 0.5f); + translation.y = (int)(translation.y + 0.5f); + + if (icons[i] != nullptr) + { + icons[i]->setTranslation(translation); + } + } +} + +void Game::restringUI() +{ + +} + +void Game::setTimeOfDay(float time) +{ + Vector3 midnight = Vector3(0.0f, 1.0f, 0.0f); + Vector3 sunrise = Vector3(-1.0f, 0.0f, 0.0f); + Vector3 noon = Vector3(0, -1.0f, 0.0f); + Vector3 sunset = Vector3(1.0f, 0.0f, 0.0f); + + float angles[4] = + { + glm::radians(270.0f), // 00:00 + glm::radians(0.0f), // 06:00 + glm::radians(90.0f), // 12:00 + glm::radians(180.0f) // 18:00 + }; + + int index0 = static_cast(fmod(time, 24.0f) / 6.0f); + int index1 = (index0 + 1) % 4; + + float t = (time - (static_cast(index0) * 6.0f)) / 6.0f; + + Quaternion rotation0 = glm::angleAxis(angles[index0], Vector3(1, 0, 0)); + Quaternion rotation1 = glm::angleAxis(angles[index1], Vector3(1, 0, 0)); + Quaternion rotation = glm::normalize(glm::slerp(rotation0, rotation1, t)); + + Vector3 direction = glm::normalize(rotation * Vector3(0, 0, 1)); + + sunlight.setDirection(direction); + + Vector3 up = glm::normalize(rotation * Vector3(0, 1, 0)); + + sunlightCamera.lookAt(Vector3(0, 0, 0), sunlight.getDirection(), up); +} + +void Game::toggleWireframe() +{ + wireframe = !wireframe; + + float width = (wireframe) ? 1.0f : 0.0f; + lightingPass->setWireframeLineWidth(width); +} + +void Game::queueScreenshot() +{ + screenshotQueued = true; + cameraFlashImage->setVisible(false); + cameraGridContainer->setVisible(false); + + soundSystem->scrot(); +} + +void Game::screenshot() +{ + screenshotQueued = false; + + // Read pixel data from framebuffer + unsigned char* pixels = new unsigned char[w * h * 3]; + glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, pixels); + + // Get game title in current language + std::string title = getString(getCurrentLanguage(), "title"); + + // Convert title to lowercase + std::transform(title.begin(), title.end(), title.begin(), ::tolower); + + // Get system time + auto now = std::chrono::system_clock::now(); + std::time_t tt = std::chrono::system_clock::to_time_t(now); + std::size_t ms = (std::chrono::duration_cast(now.time_since_epoch()) % 1000).count(); + + // Create screenshot directory if it doesn't exist + std::string screenshotDirectory = configPath + std::string("/screenshots/"); + if (!pathExists(screenshotDirectory)) + { + createDirectory(screenshotDirectory); + } + + // Build screenshot file name + std::stringstream stream; + stream << screenshotDirectory; + stream << title; + stream << std::put_time(std::localtime(&tt), "-%Y%m%d-%H%M%S-"); + stream << std::setfill('0') << std::setw(3) << ms; + stream << ".png"; + std::string filename = stream.str(); + + // Write screenshot to file in separate thread + std::thread screenshotThread(Game::saveScreenshot, filename, w, h, pixels); + screenshotThread.detach(); + + // Play camera flash animation + cameraFlashAnimation.stop(); + cameraFlashAnimation.rewind(); + cameraFlashAnimation.play(); + + // Play camera shutter sound + + // Restore camera UI visibility + cameraGridContainer->setVisible(true); +} + +void Game::boxSelect(float x, float y, float w, float h) +{ + boxSelectionContainer->setTranslation(Vector2(x, y)); + boxSelectionContainer->setDimensions(Vector2(w, h)); + boxSelectionImageBackground->setDimensions(Vector2(w, h)); + boxSelectionImageTop->setDimensions(Vector2(w, boxSelectionBorderWidth)); + boxSelectionImageBottom->setDimensions(Vector2(w, boxSelectionBorderWidth)); + boxSelectionImageLeft->setDimensions(Vector2(boxSelectionBorderWidth, h)); + boxSelectionImageRight->setDimensions(Vector2(boxSelectionBorderWidth, h)); + boxSelectionContainer->setVisible(true); +} + +void Game::fadeIn(float duration, const Vector3& color, std::function callback) +{ + if (fadeInAnimation.isPlaying()) + { + return; + } + + fadeOutAnimation.stop(); + this->fadeInEndCallback = callback; + blackoutImage->setTintColor(Vector4(color, 1.0f)); + blackoutImage->setVisible(true); + fadeInAnimation.setSpeed(1.0f / duration); + fadeInAnimation.setLoop(false); + fadeInAnimation.setClip(&fadeInClip); + fadeInAnimation.setTimeFrame(fadeInClip.getTimeFrame()); + fadeInAnimation.rewind(); + fadeInAnimation.play(); + + blackoutImage->resetTweens(); + uiRootElement->update(); +} + +void Game::fadeOut(float duration, const Vector3& color, std::function callback) +{ + if (fadeOutAnimation.isPlaying()) + { + return; + } + + fadeInAnimation.stop(); + this->fadeOutEndCallback = callback; + blackoutImage->setVisible(true); + blackoutImage->setTintColor(Vector4(color, 0.0f)); + fadeOutAnimation.setSpeed(1.0f / duration); + fadeOutAnimation.setLoop(false); + fadeOutAnimation.setClip(&fadeOutClip); + fadeOutAnimation.setTimeFrame(fadeOutClip.getTimeFrame()); + fadeOutAnimation.rewind(); + fadeOutAnimation.play(); + + blackoutImage->resetTweens(); + uiRootElement->update(); +} + +void Game::selectTool(int toolIndex) +{ + Tool* tools[] = + { + brush, + nullptr, + lens, + nullptr, + forceps, + nullptr, + nullptr, + nullptr + }; + + Tool* nextTool = tools[toolIndex]; + if (nextTool != currentTool) + { + if (currentTool) + { + currentTool->setActive(false); + currentTool->update(0.0f); + } + + currentTool = nextTool; + if (currentTool) + { + currentTool->setActive(true); + } + else + { + pushNotification("Invalid tool."); + } + } + + if (1) + { + toolIndicatorIconImage->setTextureBounds(toolIndicatorsBounds[toolIndex]); + toolIndicatorIconImage->setVisible(true); + } + else + { + toolIndicatorIconImage->setVisible(false); + } +} + +void Game::pushNotification(const std::string& text) +{ + std::cout << text << std::endl; + ++notificationCount; + + if (notificationCount == 1) + { + popNotification(); + } +} + +void Game::popNotification() +{ + if (notificationCount > 0) + { + showNotificationAnimation.rewind(); + showNotificationAnimation.play(); + } +} + +EntityID Game::createInstanceOf(const std::string& templateName) +{ + + EntityTemplate* entityTemplate = resourceManager->load(templateName + ".ent"); + + EntityID entity = entityManager->createEntity(); + entityTemplate->apply(entity, componentManager); + + return entity; +} + +void Game::destroyInstance(EntityID entity) +{ + entityManager->destroyEntity(entity); +} + +void Game::setTranslation(EntityID entity, const Vector3& translation) +{ + TransformComponent* component = static_cast(componentManager->getComponent(entity, ComponentType::TRANSFORM)); + if (!component) + { + return; + } + + component->transform.translation = translation; +} + +void Game::setRotation(EntityID entity, const Quaternion& rotation) +{ + TransformComponent* component = static_cast(componentManager->getComponent(entity, ComponentType::TRANSFORM)); + if (!component) + { + return; + } + + component->transform.rotation = rotation; +} + +void Game::setScale(EntityID entity, const Vector3& scale) +{ + TransformComponent* component = static_cast(componentManager->getComponent(entity, ComponentType::TRANSFORM)); + if (!component) + { + return; + } + + component->transform.scale = scale; +} + + +void Game::saveScreenshot(const std::string& filename, unsigned int width, unsigned int height, unsigned char* pixels) +{ + stbi_flip_vertically_on_write(1); + stbi_write_png(filename.c_str(), width, height, 3, pixels, width * 3); + + delete[] pixels; +} diff --git a/src/game.hpp b/src/game.hpp new file mode 100644 index 0000000..6ef1bc7 --- /dev/null +++ b/src/game.hpp @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef GAME_HPP +#define GAME_HPP + +#include +using namespace Emergent; + +#include "entity/entity-id.hpp" +#include +#include +#include + +class GameState; +class SplashState; +class SandboxState; +class UIContainer; +class UIBatcher; +class UIImage; +class UILabel; +class UIRenderPass; +class ClearRenderPass; +class SkyRenderPass; +class ShadowMapRenderPass; +class LightingRenderPass; +class SilhouetteRenderPass; +class FinalRenderPass; +class ResourceManager; +typedef std::vector> CSVTable; +class CameraRig; +class OrbitCam; +class FreeCam; +class Tool; +class Lens; +class Forceps; +class Brush; +class ParticleSystem; +class EntityManager; +class ComponentManager; +class SystemManager; +class SoundSystem; +class CollisionSystem; +class RenderSystem; +class ToolSystem; +class BehaviorSystem; +class SteeringSystem; +class LocomotionSystem; + +class TestEvent: public Event +{ + public: + inline EventBase* clone() const + { + TestEvent* event = new TestEvent(); + event->id = id; + return event; + } + + int id; +}; + + +class Game: + public Application, + public EventHandler +{ +public: + /** + * Creates a game instance. + * + * @param argc Argument count + * @param argv Argument list + */ + Game(int argc, char* argv[]); + + /// Destroys a game instance. + virtual ~Game(); + + /** + * Gets a string in the specified language. + * + * @param languageIndex Index of a language. + * @param name Name of the string. + * @return String in the specified language. + */ + std::string getString(std::size_t languageIndex, const std::string& name) const; + + /** + * Changes the current language. + * + * @param languageIndex Index of the language to use. + */ + void changeLanguage(std::size_t languageIndex); + + /// Returns the number of available languages. + std::size_t getLanguageCount() const; + + /// Returns the index of the current language. + std::size_t getCurrentLanguage() const; + + void toggleFullscreen(); + void setUpdateRate(double frequency); + + /** + * Changes the game state. + * + * @param state New game state. + */ + void changeState(GameState* state); + + const EventDispatcher* getEventDispatcher() const; + EventDispatcher* getEventDispatcher(); + + const Animator* getAnimator() const; + Animator* getAnimator(); + + void fadeIn(float duration, const Vector3& color, std::function callback); + void fadeOut(float duration, const Vector3& color, std::function callback); + + void selectTool(int toolIndex); + void pushNotification(const std::string& text); + void popNotification(); + +private: + virtual void setup(); + virtual void input(); + virtual void update(float t, float dt); + virtual void render(); + virtual void exit(); + virtual void handleEvent(const WindowResizedEvent& event); + virtual void handleEvent(const KeyPressedEvent& event); + virtual void handleEvent(const TestEvent& event); + void resizeUI(int w, int h); + void restringUI(); + void resizeRenderTargets(); + + void setTimeOfDay(float time); + void toggleWireframe(); + void screenshot(); + void queueScreenshot(); + + +public: + EntityID createInstanceOf(const std::string& templateName); + void destroyInstance(EntityID entity); + void setTranslation(EntityID entity, const Vector3& translation); + void setRotation(EntityID entity, const Quaternion& rotation); + void setScale(EntityID entity, const Vector3& scale); + + void boxSelect(float x, float y, float w, float h); + +public: + // States + GameState* currentState; + SplashState* splashState; + SandboxState* sandboxState; + + // Paths + std::string dataPath; + std::string configPath; + + // Localization + CSVTable* stringTable; + std::map stringMap; + std::size_t languageCount; + std::size_t currentLanguage; + + // Window management + Window* window; + bool fullscreen; + std::string title; + int w, h; + float dpi; + float fontSizePT; + float fontSizePX; + + // Input + Mouse* mouse; + Keyboard* keyboard; + ControlProfile controlProfile; + Control fullscreenControl; + Control closeControl; + Control openRadialMenuControl; + Control moveForwardControl; + Control moveBackControl; + Control moveLeftControl; + Control moveRightControl; + Control rotateCCWControl; + Control rotateCWControl; + Control zoomInControl; + Control zoomOutControl; + Control adjustCameraControl; + Control dragCameraControl; + Control toggleNestViewControl; + Control toggleWireframeControl; + Control screenshotControl; + Control toggleEditModeControl; + + // Logic + float time; + float timestep; + + // UI + Typeface* labelTypeface; + Font* labelFont; + Typeface* debugTypeface; + Font* debugFont; + BillboardBatch* uiBatch; + UIBatcher* uiBatcher; + UIContainer* uiRootElement; + UIImage* splashBackgroundImage; + UIImage* splashImage; + UIContainer* hudContainer; + UIImage* toolIndicatorBGImage; + UIImage* toolIndicatorIconImage; + Rect* toolIndicatorsBounds; + UIImage* toolIconBrushImage; + UIImage* toolIconLensImage; + UIImage* toolIconForcepsImage; + UIImage* toolIconSpadeImage; + UIImage* toolIconCameraImage; + UIImage* toolIconTestTubeImage; + UIContainer* buttonContainer; + UIImage* playButtonBGImage; + UIImage* fastForwardButtonBGImage; + UIImage* pauseButtonBGImage; + UIImage* playButtonImage; + UIImage* fastForwardButtonImage; + UIImage* pauseButtonImage; + UIContainer* radialMenuContainer; + UIImage* radialMenuBackgroundImage; + UIImage* radialMenuImage; + UIImage* radialMenuSelectorImage; + UIImage* blackoutImage; + UIImage* cameraFlashImage; + UIImage* notificationBoxImage; + int notificationCount; + UIContainer* antTag; + UIContainer* antLabelContainer; + UILabel* fpsLabel; + UILabel* antLabel; + UIImage* antLabelTL; // Top-left + UIImage* antLabelTR; // Top-right + UIImage* antLabelBL; // Bottom-left + UIImage* antLabelBR; // Bottom-right + UIImage* antLabelCC; // Center-center + UIImage* antLabelCT; // Center-top + UIImage* antLabelCB; // Center-bottom + UIImage* antLabelCL; // Center-left + UIImage* antLabelCR; // Center-right + UIImage* antLabelPinHole; + UIImage* antPin; + + UIImage* boxSelectionImageBackground; + UIImage* boxSelectionImageTop; + UIImage* boxSelectionImageBottom; + UIImage* boxSelectionImageLeft; + UIImage* boxSelectionImageRight; + UIContainer* boxSelectionContainer; + float boxSelectionBorderWidth; + + UIImage* cameraGridY0Image; + UIImage* cameraGridY1Image; + UIImage* cameraGridX0Image; + UIImage* cameraGridX1Image; + UIContainer* cameraGridContainer; + Vector4 cameraGridColor; + + // Rendering + Renderer renderer; + RenderTarget defaultRenderTarget; + ClearRenderPass* clearPass; + ClearRenderPass* clearSilhouettePass; + SkyRenderPass* skyPass; + UIRenderPass* uiPass; + Compositor uiCompositor; + Compositor defaultCompositor; + int shadowMapResolution; + GLuint shadowMapDepthTextureID; + GLuint shadowMapFramebuffer; + RenderTarget shadowMapRenderTarget; + ShadowMapRenderPass* shadowMapPass; + Compositor shadowMapCompositor; + Texture2D shadowMapDepthTexture; + LightingRenderPass* lightingPass; + SilhouetteRenderPass* silhouettePass; + FinalRenderPass* finalPass; + RenderTarget silhouetteRenderTarget; + + // Scene + Scene* scene; + SceneLayer* defaultLayer; + SceneLayer* uiLayer; + DirectionalLight sunlight; + Camera camera; + Camera sunlightCamera; + Camera uiCamera; + + // Animation + Animator animator; + Animation fadeInAnimation; + Animation fadeOutAnimation; + AnimationClip fadeInClip; + AnimationClip fadeOutClip; + std::function fadeInEndCallback; + std::function fadeOutEndCallback; + Animation showNotificationAnimation; + Animation hideNotificationAnimation; + AnimationClip showNotificationClip; + AnimationClip hideNotificationClip; + Animation cameraFlashAnimation; + AnimationClip cameraFlashClip; + + + // Assets + ResourceManager* resourceManager; + Texture2D* splashTexture; + Texture2D* hudSpriteSheetTexture; + TextureAtlas hudTextureAtlas; + Material* smokeMaterial; + Model* lensModel; + Model* forcepsModel; + Model* brushModel; + + // Game + CameraRig* cameraRig; + OrbitCam* orbitCam; + FreeCam* freeCam; + Tool* currentTool; + Lens* lens; + Forceps* forceps; + Brush* brush; + ParticleSystem* particleSystem; + + // ECS + EntityManager* entityManager; + ComponentManager* componentManager; + SystemManager* systemManager; + SoundSystem* soundSystem; + CollisionSystem* collisionSystem; + RenderSystem* renderSystem; + ToolSystem* toolSystem; + BehaviorSystem* behaviorSystem; + SteeringSystem* steeringSystem; + LocomotionSystem* locomotionSystem; + + EntityID lollipop; + + bool wireframe; + bool screenshotQueued; + +private: + static void saveScreenshot(const std::string& filename, unsigned int width, unsigned int height, unsigned char* pixels); +}; + +inline const EventDispatcher* Game::getEventDispatcher() const +{ + return &eventDispatcher; +} + +inline EventDispatcher* Game::getEventDispatcher() +{ + return &eventDispatcher; +} + +inline const Animator* Game::getAnimator() const +{ + return &animator; +} + +inline Animator* Game::getAnimator() +{ + return &animator; +} + +inline std::size_t Game::getLanguageCount() const +{ + return languageCount; +} + +inline std::size_t Game::getCurrentLanguage() const +{ + return currentLanguage; +} + +#endif // GAME_HPP + diff --git a/src/game/agent.cpp b/src/game/agent.cpp deleted file mode 100644 index 255240c..0000000 --- a/src/game/agent.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#include "agent.hpp" - -Agent::Agent(): - navmeshTriangle(nullptr), - barycentricPosition(0.0f), - position(0.0f), - forward(0, 0, -1), - up(0, 1, 0), - right(1, 0, 0), - rotation(1, 0, 0, 0) - //wanderDirection(0, 0, -1), - //velocity(0.0f) -{} - -/* -void Agent::applyForce(const Vector3& force) -{ - acceleration += force; -} - -void Agent::updateVelocity() -{ - // Limit acceleration - acceleration = limit(acceleration / mass, maxAcceleration); - - // Add acceleration to velocity and limit - velocity = limit(velocity + acceleration, maxSpeed); - - // Reset acceleration to zero - acceleration = Vector3(0.0f); -} - -Vector3 Agent::wander(float dt) -{ - // Calculate center of wander circle - Vector3 wanderCircleCenter = position + forward * wanderCircleDistance; - - // Calculate wander force - Vector3 target = wanderCircleCenter + wanderDirection * wanderCircleRadius; - - // Rotate wander direction by a random displacement angle - float displacement = frand(-wanderRate * 0.5f, wanderRate * 0.5f); - wanderDirection = glm::normalize(glm::angleAxis(displacement, up) * wanderDirection); - - return seek(target); -} - -Vector3 Agent::seek(const Vector3& target) const -{ - Vector3 desiredVelocity = glm::normalize(target - position) * maxSpeed; - return desiredVelocity - velocity; -} - -Vector3 Agent::flee(const Vector3& target) const -{ - Vector3 desiredVelocity = glm::normalize(position - target) * maxSpeed; - return desiredVelocity - velocity; -} - -Vector3 Agent::containment(const Vector3& probe) const -{ - std::vector traversal; - Navmesh::traverse(navmeshTriangle, barycentricPosition, probe, &traversal); - - if (traversal.empty()) - { - return Vector3(0.0f); - } - - const Navmesh::Step& step = traversal.back(); - - // If not on edge or on connected edge - if (step.edge == nullptr || step.edge->symmetric != nullptr) - { - return Vector3(0.0f); - } - - // Calculate difference between probe position and position on edge - //Vector3 end = cartesian(step.end, - // step.triangle->edge->vertex->position, - // step.triangle->edge->next->vertex->position, - // step.triangle->edge->previous->vertex->position); - - //Vector3 difference = probe - end; - - //float depth = 0.0f; - //if (nonzero(difference)) - //{ - // depth = glm::length(difference); - //} - - // Calculate edge normal - const Vector3& a = step.edge->vertex->position; - const Vector3& b = step.edge->next->vertex->position; - Vector3 ab = glm::normalize(b - a); - Vector3 edgeNormal = glm::cross(up, ab); - - // Calculate reflection vector of forward vector and edge normal - //Vector3 reflection = glm::reflect(forward, edgeNormal); - - //Vector3 target = cartesian(step.end, - // step.triangle->edge->vertex->position, - // step.triangle->edge->next->vertex->position, - // step.triangle->edge->previous->vertex->position) + reflection * 0.1f; - - //std::cout << "reflection: " << reflection.x << ", " << reflection.y << ", " << reflection.z << std::endl; - - return edgeNormal; -} - -Vector3 Agent::separation(const std::list& neighbors) const -{ - Vector3 force(0.0f); - - for (Agent* neighbor: neighbors) - { - Vector3 difference = position - neighbor->position; - - float distanceSquared = glm::dot(difference, difference); - if (distanceSquared > 0.0f && distanceSquared < separationRadiusSquared) - { - force += difference * (1.0f / distanceSquared); - } - } - - if (nonzero(force)) - { - force = glm::normalize(force); - } - - return force; -} -*/ - -void Agent::setPosition(Navmesh::Triangle* triangle, const Vector3& position) -{ - // Update navmesh triangle and position - navmeshTriangle = triangle; - barycentricPosition = position; - - // Convert navmesh-space barycentric position to world-space cartesian position - const Vector3& a = triangle->edge->vertex->position; - const Vector3& b = triangle->edge->next->vertex->position; - const Vector3& c = triangle->edge->previous->vertex->position; - this->position = cartesian(position, a, b, c); -} - -void Agent::setOrientation(const Vector3& newForward, const Vector3& newUp) -{ - // Calculate alignment quaternion - Quaternion alignment = glm::rotation(up, newUp); - - // Rebuild vector basis - forward = newForward; - right = glm::normalize(glm::cross(newUp, forward)); - up = glm::cross(forward, right); - - // Calculate rotation quaternion from vector basis - rotation = glm::normalize(glm::quat_cast(Matrix3(right, up, forward))); - - // Align wander direction - //wanderDirection = glm::normalize(project_on_plane(alignment * wanderDirection, Vector3(0.0f), up)); -} - -/* -void Agent::setMaxSpeed(float speed) -{ - maxSpeed = speed; -} - -void Agent::setVelocity(const Vector3& velocity) -{ - this->velocity = velocity; -} - -void Agent::setWanderCircleDistance(float distance) -{ - wanderCircleDistance = distance; -} - -void Agent::setWanderCircleRadius(float radius) -{ - wanderCircleRadius = radius; -} - -void Agent::setWanderRate(float angle) -{ - wanderRate = angle; -} - -void Agent::setSeparationRadius(float radius) -{ - separationRadius = radius; - separationRadiusSquared = separationRadius * separationRadius; -} -*/ - -/** EXAMPLE USAGE -Vector3 wanderForce = wander(dt) * wanderWeight; -Vector3 fleeForce = flee(mouse) * fleeWeight; -Vector3 steerForce = wanderForce + fleeForce; -steer(steerForce); -**/ diff --git a/src/game/agent.hpp b/src/game/agent.hpp deleted file mode 100644 index bc508af..0000000 --- a/src/game/agent.hpp +++ /dev/null @@ -1,216 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef AGENT_HPP -#define AGENT_HPP - -#include "navmesh.hpp" - -#include - -#include -using namespace Emergent; - -class Obstacle; - - -/*************88 - -Ant is an agent. - -Ant combines steering behaviors with different weights. - -I.E. - -seek pheromones * 0.5 -separation * 0.1 -alignment * 0.1 -cohesion * 0.1 -followWall * 0.2 - -*/ - -/** - * An agent which navigates on a navmesh. - */ -class Agent -{ -public: - Agent(); - - - - /** - * Adds a force to the agent's acceleration vector. - * - * @param force Acceleration force - */ - //void applyForce(const Vector3& force); - - /** - * Calculates velocity based on current acceleration vector, then resets acceleration to zero. - */ - //void updateVelocity(); - - /** - * Calculates steering force for the wander behavior. - */ - //Vector3 wander(float dt); - - /** - * Calculates steering force for the seek behavior. - */ - //Vector3 seek(const Vector3& target) const; - - /** - * Calculates steering force for the flee behavior. - */ - //Vector3 flee(const Vector3& target) const; - - //Vector3 containment(const Vector3& probe) const; - - //Vector3 separation(const std::list& neighbors) const; - - //Vector3 forage(const Vector3& leftProbe, const Vector3& rightProbe); - - /* - void setMaxSpeed(float speed); - void setVelocity(const Vector3& velocity); - void setMaxAcceleration(float acceleration); - void setMass(float mass); - void setWanderCircleDistance(float distance); - void setWanderCircleRadius(float radius); - void setWanderRate(float angle); - void setSeparationRadius(float radius); - */ - - /** - * Sets the position of the agent on a navmesh. - * - * @param triangle Navmesh triangle on which the agent resides - * @param position Barycentric position on the specified triangle - */ - void setPosition(Navmesh::Triangle* triangle, const Vector3& position); - - /** - * Sets the orientation of the agent. This effectively updates the agent's vector basis and rotation quaternion. - * - * @param forward Normalized forward vector - * @param up Normalized up vector - */ - void setOrientation(const Vector3& newForward, const Vector3& newUp); - - /* - Vector3 followWall(); - // or - Vector3 followEdge(); - Vector3 avoidObstacle(const Obstacle* obstacle); - - Vector3 alignment(const std::vector* neighbors); - Vector3 cohesion(const std::vector* neighbors); - */ - - const Navmesh::Triangle* getNavmeshTriangle() const; - Navmesh::Triangle* getNavmeshTriangle(); - const Vector3& getBarycentricPosition() const; - const Vector3& getPosition() const; - const Vector3& getForward() const; - const Vector3& getUp() const; - const Vector3& getRight() const; - const Quaternion& getRotation() const; - - //const Vector3& getVelocity() const; - -private: - Navmesh::Triangle* navmeshTriangle; - Vector3 barycentricPosition; - Vector3 position; - Vector3 forward; - Vector3 up; - Vector3 right; - Quaternion rotation; - - /* - // Limits - float maxSpeed; - float maxAcceleration; - - // Steering forces - float mass; - Vector3 acceleration; - Vector3 velocity; - - // Wander variables - float wanderCircleDistance; - float wanderCircleRadius; - float wanderRate; - Vector3 wanderDirection; - float separationRadius; - float separationRadiusSquared; - */ -}; - -inline const Navmesh::Triangle* Agent::getNavmeshTriangle() const -{ - return navmeshTriangle; -} - -inline Navmesh::Triangle* Agent::getNavmeshTriangle() -{ - return navmeshTriangle; -} - -inline const Vector3& Agent::getBarycentricPosition() const -{ - return barycentricPosition; -} - -inline const Vector3& Agent::getPosition() const -{ - return position; -} - -inline const Vector3& Agent::getForward() const -{ - return forward; -} - -inline const Vector3& Agent::getUp() const -{ - return up; -} - -inline const Vector3& Agent::getRight() const -{ - return right; -} - -inline const Quaternion& Agent::getRotation() const -{ - return rotation; -} - -/* -inline const Vector3& Agent::getVelocity() const -{ - return velocity; -} -*/ - -#endif // AGENT_HPP \ No newline at end of file diff --git a/src/game/ant.cpp b/src/game/ant.cpp deleted file mode 100644 index ff714e0..0000000 --- a/src/game/ant.cpp +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#include "ant.hpp" -#include "colony.hpp" -#include "pheromone-matrix.hpp" -#include - -float FRAMES_PER_SECOND = 60; -float TIMESTEP = 1.0f / FRAMES_PER_SECOND; -float ANT_LENGTH = 0.5f; // 0.5 cm, head to abdomen (not including legs / antennae) -float ANT_COLLISION_RADIUS = ANT_LENGTH * 1.25f; -float RECEPTOR_RADIUS = 0.4f; -float RECEPTOR_SEPARATION = 0.882f; -float RECEPTOR_DISTANCE = 0.588f; -float MOUTH_DISTANCE = 0.2646f; -float BITE_RADIUS = 0.0294f; -float FOOD_PARTICLE_RADIUS = 0.1176f; -float MAX_RECEPTOR_NOISE = 0.05f; // essentially an epsilon -float MAX_EXCITEMENT = 1.0f; -float MAX_PHEROMONE_TURNING_ANGLE = glm::radians(8.5f); -float MIN_WALK_TIME = 0.5f; // seconds -float MAX_WALK_TIME = 8.0f; // seconds -float MIN_REST_TIME = 0.15f; -float MAX_REST_TIME = 0.7f; -float MIN_CHEW_TIME = 0.25f; -float MAX_CHEW_TIME = 0.5f; -float DEEXCITEMENT_FACTOR = 0.999f; // This should probably always be less than the evaporation factor -float CALM_FACTOR = 0.995f; -float MAX_WALK_FORCE = 1.5; -float MAX_PANIC_FORCE = 0.1029f; -float MAX_WALK_SPEED = 3.0f; // cm/s -float MAX_PANIC_SPEED = 8.82f; // cm/s -float PANIC_RADIUS = 7.35f; -float WANDER_CIRCLE_DISTANCE = 0.441f; -float WANDER_CIRCLE_RADIUS = 0.0294f; -float MAX_WANDER_ANGLE = 0.15f; - -inline float fwrap(float angle, float limit) -{ - return angle - std::floor(angle / limit) * limit; -} - -Ant::Ant(Colony* colony): - colony(colony), - state(Ant::State::IDLE), - transform(Transform::getIdentity()), - pose(nullptr) -{ - pose = new Pose(colony->getAntModel()->getSkeleton()); - pose->reset(); - pose->concatenate(); - - modelInstance.setModel(colony->getAntModel()); - modelInstance.setPose(pose); - - animationTime = frand(0.0f, 60.0f); - - velocity = Vector3(0); - acceleration = Vector3(0); - wanderDirection = getForward(); - excitement = MAX_EXCITEMENT; -} - -Ant::~Ant() -{ - delete pose; -} - -void Ant::animate() -{ - colony->getTripodGaitAnimation()->animate(pose, animationTime); - pose->concatenate(); - animationTime = fwrap(animationTime + 4.0f, colony->getTripodGaitAnimation()->getEndTime()); -} - -void Ant::suspend(const Vector3& suspensionPoint, const Quaternion& suspensionRotation) -{ - transform.translation = suspensionPoint; - transform.rotation = suspensionRotation; - modelInstance.setTransform(transform); -} - -void Ant::move(const Vector3& velocity) -{ - std::vector traversal; - Navmesh::traverse(getNavmeshTriangle(), getBarycentricPosition(), velocity, &traversal); - - if (!traversal.empty()) - { - const Navmesh::Step& step = traversal.back(); - - if (step.start != step.end) - { - if (step.triangle != getNavmeshTriangle()) - { - Quaternion alignment = glm::rotation(getNavmeshTriangle()->normal, step.triangle->normal); - Vector3 newForward = glm::normalize(project_on_plane(alignment * getForward(), Vector3(0.0f), step.triangle->normal)); - - setOrientation(newForward, step.triangle->normal); - } - } - - setPosition(step.triangle, step.end); - } -} - -void Ant::turn(float angle) -{ - // Rotate forward vector - Vector3 newForward = glm::normalize(glm::angleAxis(angle, getUp()) * getForward()); - setOrientation(newForward, getUp()); -} - -void Ant::update(float dt) -{ - float probeLateralOffset = 0.1f; - float probeForwardOffset = 0.3f; - - animate(); - - // Calculate positions of receptors - receptorL = getPosition() + getForward() * RECEPTOR_DISTANCE; - receptorR = receptorL; - receptorL -= getRight() * RECEPTOR_SEPARATION * 0.5f; - receptorR += getRight() * RECEPTOR_SEPARATION * 0.5f; - - // Steering - if (state == Ant::State::WANDER) - { - //setWanderCircleDistance(4.0f); - //setWanderCircleRadius(0.3f); - //setWanderRate(glm::radians(90.0f)); - //setSeparationRadius(0.5f); - //setMaxSpeed(0.025f); - - // Calculate wander force - Vector3 wanderForce = wander() * 1.5f; - Vector3 followForce = follow() * 3.0f; - - // Setup containment probes - //Vector3 leftProbe = getForward() * probeForwardOffset - getRight() * probeLateralOffset; - //Vector3 rightProbe = getForward() * probeForwardOffset + getRight() * probeLateralOffset; - - // Calculate containment force - //Vector3 containmentForce = containment(leftProbe) + containment(rightProbe); - - // Determine neighbors - //float neighborhoodSize = 2.0f; - //AABB neighborhoodAABB(getPosition() - Vector3(neighborhoodSize * 0.5f), getPosition() + Vector3(neighborhoodSize * 0.5f)); - //std::list neighbors; - //colony->queryAnts(neighborhoodAABB, &neighbors); - - // Calculate separation force - //Vector3 separationForce = separation(neighbors); - - applyForce(wanderForce); - applyForce(followForce); - - float maxSpeed = MAX_WALK_SPEED * TIMESTEP; - - // Limit acceleration - float accelerationMagnitudeSquared = glm::dot(acceleration, acceleration); - if (accelerationMagnitudeSquared > MAX_WALK_FORCE * MAX_WALK_FORCE) - { - acceleration = glm::normalize(acceleration) * MAX_WALK_FORCE; - } - - // Accelerate - velocity += acceleration; - - // Limit speed - float speedSquared = glm::dot(velocity, velocity); - if (speedSquared > maxSpeed * maxSpeed) - { - velocity = glm::normalize(velocity) * maxSpeed; - } - - Vector3 direction = glm::normalize(velocity); - setOrientation(direction, getUp()); - - // Deposit pheromones - Vector2 position2D = Vector2(getPosition().x, getPosition().z); - colony->getHomingMatrix()->deposit(position2D, excitement); - excitement *= DEEXCITEMENT_FACTOR; - - // Move ant - move(velocity); - } - else if (state == Ant::State::IDLE) - { - velocity = Vector3(0.0f); - - // Move ant - move(velocity); - } - - // Update transform - if (state == Ant::State::WANDER || state == Ant::State::IDLE) - { - transform.translation = getPosition(); - transform.rotation = getRotation(); - - // Update model instance - modelInstance.setTransform(transform); - } -} - -void Ant::setState(Ant::State state) -{ - this->state = state; -} - -Vector3 Ant::seek(const Vector3& target) -{ - Vector3 steer(0.0f); - Vector3 difference = target - getPosition(); - - float distanceSquared = glm::dot(difference, difference); - if (distanceSquared > 0.0f) - { - float maxForce = MAX_WALK_FORCE; - - steer = glm::normalize(difference) * maxForce - velocity; - } - - return steer; -} - -Vector3 Ant::flee(const Vector3& target) -{ - return -seek(target); -} - -Vector3 Ant::wander() -{ - // Determine center of wander circle - Vector3 center = getPosition() + getForward() * WANDER_CIRCLE_DISTANCE; - - // Calculate wander target - Vector3 target = center + wanderDirection * WANDER_CIRCLE_RADIUS; - - // Rotate wander direction by a random displacement angle - float displacement = frand(-MAX_WANDER_ANGLE, MAX_WANDER_ANGLE); - wanderDirection = glm::normalize(glm::angleAxis(displacement, getUp()) * wanderDirection); - - return seek(target); -} - -Vector3 Ant::follow() -{ - const PheromoneMatrix* pheromoneMatrix = colony->getRecruitmentMatrix(); - - Vector2 receptorL2D = Vector2(receptorL.x, receptorL.z); - Vector2 receptorR2D = Vector2(receptorR.x, receptorR.z); - - float signalL = pheromoneMatrix->query(receptorL2D, RECEPTOR_RADIUS); - signalL += frand(0.0f, MAX_RECEPTOR_NOISE); - - float signalR = pheromoneMatrix->query(receptorR2D, RECEPTOR_RADIUS); - signalR += frand(0.0f, MAX_RECEPTOR_NOISE); - - if (signalL + signalR > 0.0f) - { - float angle = -MAX_PHEROMONE_TURNING_ANGLE * ((signalL - signalR) / (signalL + signalR)); - - Vector3 steer = glm::normalize(glm::angleAxis(angle, getUp()) * getForward()); - return steer; - } - - return Vector3(0.0f); -} - -void Ant::applyForce(const Vector3& force) -{ - acceleration += force; -} diff --git a/src/game/ant.hpp b/src/game/ant.hpp deleted file mode 100644 index ac80911..0000000 --- a/src/game/ant.hpp +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef ANT_HPP -#define ANT_HPP - -#include -#include "navmesh.hpp" -#include "agent.hpp" - -#include -using namespace Emergent; - -class Colony; -class Gait; - -/** - * An individual ant which belongs to a colony. - */ -class Ant: public Agent -{ -public: - /** - * Named constants corresponding to leg indices. - * - * \_/ - * L1 --| |-- R1 - * L2 --| |-- R2 - * L3 --|_|-- R3 - */ - enum class LegIndex - { - L1, - L2, - L3, - R1, - R2, - R3 - }; - - enum class State - { - IDLE, - WANDER, - DEAD, - SUSPENDED - }; - - /** - * Creates an instance of Ant. - */ - Ant(Colony* colony); - ~Ant(); - - void animate(); - - void suspend(const Vector3& suspensionPoint, const Quaternion& suspensionRotation); - - void move(const Vector3& velocity); - - void turn(float angle); - - void update(float dt); - - void setState(Ant::State state); - - const Colony* getColony() const; - Colony* getColony(); - const Transform& getTransform() const; - - const ModelInstance* getModelInstance() const; - ModelInstance* getModelInstance(); - - - - - // Boid functions - Vector3 seek(const Vector3& target); - Vector3 flee(const Vector3& target); - Vector3 wander(); - Vector3 follow(); - void applyForce(const Vector3& force); - -private: - Vector3 forage(const Vector3& leftReceptor, const Vector3& rightReceptor); - - /** - * Calculates the surface normal averaged between the surface normals at each of the ant's grounded feet. - */ - Vector3 calculateAverageSurfaceNormal() const; - - void updateTransform(); - - Colony* colony; - Ant::State state; - float animationTime; - - Transform transform; - ModelInstance modelInstance; - Pose* pose; - - // Boid variables - //Vector3 position; - //Quaternion rotation; - //Vector3 forward; - Vector3 velocity; - Vector3 acceleration; - Vector3 wanderDirection; - float excitement; - Vector3 receptorL; - Vector3 receptorR; -}; - -inline const Colony* Ant::getColony() const -{ - return colony; -} - -inline Colony* Ant::getColony() -{ - return colony; -} - -inline const Transform& Ant::getTransform() const -{ - return transform; -} - -inline const ModelInstance* Ant::getModelInstance() const -{ - return &modelInstance; -} - -inline ModelInstance* Ant::getModelInstance() -{ - return &modelInstance; -} - -#endif // ANT_HPP \ No newline at end of file diff --git a/src/game/biome.hpp b/src/game/biome.hpp deleted file mode 100644 index 4cd72f8..0000000 --- a/src/game/biome.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef BIOME_HPP -#define BIOME_HPP - -#include -#include -#include - -using namespace Emergent; - -class Biome -{ -public: - Biome(); - ~Biome(); - - bool load(); - - std::string filename; - std::string name; - std::string soilHorizonOFilename; - std::string soilHorizonAFilename; - std::string soilHorizonBFilename; - std::string soilHorizonCFilename; - std::string cubemapName; - - Texture2D* soilHorizonO; - Texture2D* soilHorizonA; - Texture2D* soilHorizonB; - Texture2D* soilHorizonC; - TextureCube* diffuseCubemap; - TextureCube* specularCubemap; -}; - -class Biosphere -{ -public: - bool load(const std::string& directory); - - std::map biomes; -}; - -#endif // BIOME_HPP diff --git a/src/game/brush.cpp b/src/game/brush.cpp new file mode 100755 index 0000000..5b873b7 --- /dev/null +++ b/src/game/brush.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "brush.hpp" +#include "camera-rig.hpp" +#include + +Brush::Brush(const Model* model, Animator* animator): + wasActive(false) +{ + // Setup model instance + modelInstance.setModel(model); + + pressedDistance = -0.25f; + releasedDistance = 0.5f; + pressDuration = 0.25f; + releaseDuration = 0.175f; + + tipDistance = releasedDistance; + lastTipDistance = tipDistance; + painting = false; + speed = 0.0f; + mousePosition = Vector2(0.0f); + lastMousePosition = mousePosition; + screenDimensions = Vector2(1.0f); + + // Construct press animation clip + AnimationChannel* channel; + pressClip.setInterpolator(easeOutCubic); + channel = pressClip.addChannel(0); + channel->insertKeyframe(0.0f, 0.0f); + channel->insertKeyframe(pressDuration, 1.0f); + + // Construct release animation clip + releaseClip.setInterpolator(easeOutCubic); + channel = releaseClip.addChannel(0); + channel->insertKeyframe(0.0f, 0.0f); + channel->insertKeyframe(releaseDuration, 1.0f); + + // Setup press animation callbacks + pressAnimation.setTimeFrame(pressClip.getTimeFrame()); + pressAnimation.setClip(&pressClip); + pressAnimation.setAnimateCallback + ( + [this](std::size_t id, float t) + { + this->tipDistance = lerp(lastTipDistance, pressedDistance, t); + } + ); + pressAnimation.setEndCallback + ( + [this]() + { + this->painting = true; + } + ); + + // Setup release animation callbacks + releaseAnimation.setTimeFrame(releaseClip.getTimeFrame()); + releaseAnimation.setClip(&releaseClip); + releaseAnimation.setAnimateCallback + ( + [this](std::size_t id, float t) + { + this->tipDistance = lerp(lastTipDistance, releasedDistance, t); + } + ); + + // Add animations to animator + animator->addAnimation(&pressAnimation); + animator->addAnimation(&releaseAnimation); +} + +Brush::~Brush() +{} + +void Brush::update(float dt) +{ + Vector2 screenCenter = Vector2(screenDimensions.x * 0.5f, screenDimensions.y * 0.5f); + + // Calculate mouse movement speed + Vector2 mouseDifference = (mousePosition - lastMousePosition) / std::min(screenDimensions.x, screenDimensions.y); + Vector2 mouseDirection(0.0f); + float mouseSpeed = glm::length2(mouseDifference); + if (mouseSpeed != 0.0f) + { + mouseSpeed = std::sqrt(mouseSpeed); + mouseDirection = mouseDifference * (1.0f / mouseSpeed); + } + + this->lastMousePosition = this->mousePosition; + + Vector3 tiltDirection = Vector3(mouseDirection.x, 0.0f, mouseDirection.y); + float tiltMagnitude = std::min(0.5f, mouseSpeed * 10.0f); + if (!tiltMagnitude) + { + tiltDirection = Vector3(0, 1, 0); + } + + Vector2 tiltForce = mouseDirection * mouseSpeed; + velocity += tiltForce; + + Quaternion tilt = glm::normalize(glm::slerp(Quaternion(1, 0, 0, 0), glm::normalize(glm::rotation(Vector3(0, 1, 0), tiltDirection)), tiltMagnitude * 0.0f)); + + + Quaternion alignment = glm::angleAxis(orbitCam->getAzimuth(), Vector3(0, 1, 0)); + Quaternion rotation = glm::normalize(alignment * tilt); + Vector3 translation = pick + rotation * Vector3(0, tipDistance, 0); + + // Set tool position + modelInstance.setTranslation(translation); + modelInstance.setRotation(rotation); + + if (active && !wasActive) + { + modelInstance.resetTweens(); + modelInstance.setActive(true); + } + else if (!active && wasActive) + { + modelInstance.setActive(false); + } + + wasActive = active; +} + +void Brush::press() +{ + lastTipDistance = tipDistance; + + releaseAnimation.stop(); + pressAnimation.rewind(); + pressAnimation.play(); +} + +void Brush::release() +{ + lastTipDistance = tipDistance; + + pressAnimation.stop(); + releaseAnimation.rewind(); + releaseAnimation.play(); + painting = false; +} + + +void Brush::setTiltParams(const Vector2& mousePosition, const Vector2& screenDimensions) +{ + this->mousePosition = mousePosition; + this->screenDimensions = screenDimensions; +} + diff --git a/src/game/brush.hpp b/src/game/brush.hpp new file mode 100755 index 0000000..c406acb --- /dev/null +++ b/src/game/brush.hpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef BRUSH_HPP +#define BRUSH_HPP + +#include "tool.hpp" + +class Brush: public Tool +{ +public: + Brush(const Model* model, Animator* animator); + ~Brush(); + + /** + * Updates the brush. + * + * @param dt Game timestep. + */ + virtual void update(float dt); + + void press(); + void release(); + + void setTiltParams(const Vector2& mousePosition, const Vector2& screenDimensions); + +private: + float pressedDistance; + float releasedDistance; + float tipDistance; + float lastTipDistance; + bool painting; + + float pressDuration; + float releaseDuration; + + Animation pressAnimation; + Animation releaseAnimation; + AnimationClip pressClip; + AnimationClip releaseClip; + + Vector2 mousePosition; + Vector2 lastMousePosition; + Vector2 screenDimensions; + float speed; + + Vector2 velocity; + bool wasActive; +}; + +#endif // BRUSH_HPP diff --git a/src/camera-rig.cpp b/src/game/camera-rig.cpp old mode 100644 new mode 100755 similarity index 97% rename from src/camera-rig.cpp rename to src/game/camera-rig.cpp index 0a03f1a..7323e81 --- a/src/camera-rig.cpp +++ b/src/game/camera-rig.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -79,7 +79,7 @@ void FreeCam::update(float dt) void FreeCam::move(const Vector2& velocity) { - setTranslation(getTranslation() + getForward() * velocity.x + getRight() * velocity.y); + setTranslation(getTranslation() + getForward() * -velocity.y + getRight() * velocity.x); } float wrapAngle(float x) diff --git a/src/camera-rig.hpp b/src/game/camera-rig.hpp old mode 100644 new mode 100755 similarity index 98% rename from src/camera-rig.hpp rename to src/game/camera-rig.hpp index 60c759a..8f0afa1 --- a/src/camera-rig.hpp +++ b/src/game/camera-rig.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -271,4 +271,4 @@ private: const PunctualLight* light; }; -#endif // CAMERA_RIG_HPP \ No newline at end of file +#endif // CAMERA_RIG_HPP diff --git a/src/game/colony.cpp b/src/game/colony.cpp deleted file mode 100644 index 3c842de..0000000 --- a/src/game/colony.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#include "colony.hpp" -#include "ant.hpp" -#include "pheromone-matrix.hpp" -#include "../configuration.hpp" - -Colony::Colony(): - antModel(nullptr), - tripodGaitAnimation(nullptr) -{ - Vector3 octreeMin = Vector3(-ANTKEEPER_TERRAIN_WIDTH, -ANTKEEPER_TERRAIN_BASE_HEIGHT, -ANTKEEPER_TERRAIN_DEPTH) * 0.5f - Vector3(ANTKEEPER_OCTREE_PADDING); - Vector3 octreeMax = Vector3( ANTKEEPER_TERRAIN_WIDTH, ANTKEEPER_TERRAIN_BASE_HEIGHT, ANTKEEPER_TERRAIN_DEPTH) * 0.5f + Vector3(ANTKEEPER_OCTREE_PADDING); - AABB octreeBounds(octreeMin, octreeMax); - - antOctree = new Octree(5, octreeBounds); - - // Create pheromone matrices - homingMatrix = new PheromoneMatrix(PHEROMONE_MATRIX_COLUMNS, PHEROMONE_MATRIX_ROWS, WORLD_BOUNDS_MIN, WORLD_BOUNDS_MAX); - recruitmentMatrix = new PheromoneMatrix(PHEROMONE_MATRIX_COLUMNS, PHEROMONE_MATRIX_ROWS, WORLD_BOUNDS_MIN, WORLD_BOUNDS_MAX); -} - -Colony::~Colony() -{ - killAll(); - delete antOctree; - delete homingMatrix; - delete recruitmentMatrix; -} - -Ant* Colony::spawn(Navmesh* navmesh, Navmesh::Triangle* triangle, const Vector3& position) -{ - // Allocate ant - Ant* ant = new Ant(this); - - // Position it on the navmesh - ant->setPosition(triangle, position); - - // Add ant to the colony - ants.push_back(ant); - - return ant; -} - -void Colony::update(float dt) -{ - // Rebuild octree - antOctree->clear(); - for (Ant* ant: ants) - { - antOctree->insert(ant->getModelInstance()->getBounds(), ant); - } - - // Update ants - for (Ant* ant: ants) - { - ant->update(dt); - } -} - -void Colony::setAntModel(Model* model) -{ - this->antModel = model; - - // Find tripod gait animation - tripodGaitAnimation = model->getSkeleton()->getAnimation("tripod-gait"); - if (!tripodGaitAnimation) - { - std::cerr << "Ant tripod gait animation not found" << std::endl; - } -} - -void Colony::queryAnts(const BoundingVolume& volume, std::list* results) const -{ - antOctree->query(volume, results); -} - -void Colony::killAll() -{ - antOctree->clear(); - homingMatrix->clear(); - recruitmentMatrix->clear(); - - for (Ant* ant: ants) - { - delete ant; - } - ants.clear(); -} diff --git a/src/game/colony.hpp b/src/game/colony.hpp deleted file mode 100644 index 2275d27..0000000 --- a/src/game/colony.hpp +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef COLONY_HPP -#define COLONY_HPP - -#include -#include "navmesh.hpp" - -#include -using namespace Emergent; - -class Ant; -class Agent; -class Pheromone; -class Gait; -class PheromoneMatrix; - -/** - * A colony of ants. - */ -class Colony -{ -public: - Colony(); - ~Colony(); - - Ant* spawn(Navmesh* navmesh, Navmesh::Triangle* triangle, const Vector3& position); - - void update(float dt); - - void setAntModel(Model* model); - const Model* getAntModel() const; - Model* getAntModel(); - const Animation* getTripodGaitAnimation() const; - - void queryAnts(const BoundingVolume& volume, std::list* results) const; - - std::size_t getAntCount() const; - const Ant* getAnt(std::size_t index) const; - Ant* getAnt(std::size_t index); - - void killAll(); - - - const Octree* getAntOctree() const; - - const PheromoneMatrix* getHomingMatrix() const; - PheromoneMatrix* getHomingMatrix(); - - const PheromoneMatrix* getRecruitmentMatrix() const; - PheromoneMatrix* getRecruitmentMatrix(); - -private: - // Rendering - Model* antModel; - const Animation* tripodGaitAnimation; - - // Locomotion - float walkSpeed; - float turnSpeed; - Gait* tripodGait; - Gait* rippleGait; - Gait* slowWaveGait; - - - std::vector ants; - Octree* antOctree; - - PheromoneMatrix* homingMatrix; - PheromoneMatrix* recruitmentMatrix; -}; - -inline const Model* Colony::getAntModel() const -{ - return antModel; -} - -inline Model* Colony::getAntModel() -{ - return antModel; -} - -inline const Animation* Colony::getTripodGaitAnimation() const -{ - return tripodGaitAnimation; -} - -inline std::size_t Colony::getAntCount() const -{ - return ants.size(); -} - -inline const Ant* Colony::getAnt(std::size_t index) const -{ - return ants[index]; -} - -inline Ant* Colony::getAnt(std::size_t index) -{ - return ants[index]; -} - - -inline const Octree* Colony::getAntOctree() const -{ - return antOctree; -} - -inline const PheromoneMatrix* Colony::getHomingMatrix() const -{ - return homingMatrix; -} - -inline PheromoneMatrix* Colony::getHomingMatrix() -{ - return homingMatrix; -} - -inline const PheromoneMatrix* Colony::getRecruitmentMatrix() const -{ - return recruitmentMatrix; -} - -inline PheromoneMatrix* Colony::getRecruitmentMatrix() -{ - return recruitmentMatrix; -} - -#endif // COLONY_HPP \ No newline at end of file diff --git a/src/game/curl-noise.cpp b/src/game/curl-noise.cpp new file mode 100755 index 0000000..7d9816e --- /dev/null +++ b/src/game/curl-noise.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "curl-noise.hpp" + +inline static Vector3 gradient(const Vector3& p, float frequency) +{ + const float epsilon = 0.0001f; + + float n0 = glm::perlin(p * frequency); + float nx = glm::perlin((p + Vector3(epsilon, 0, 0)) * frequency); + float ny = glm::perlin((p + Vector3(0, epsilon, 0)) * frequency); + float nz = glm::perlin((p + Vector3(0, 0, epsilon)) * frequency); + + return Vector3(nx - n0, ny - n0, nz - n0) * (1.0f / epsilon); +} + +Vector3 curl(const Vector3& p, const Vector3& offset, float frequency) +{ + Vector3 g1 = gradient(p, frequency); + Vector3 g2 = gradient(p + offset, frequency); + return glm::cross(g1, g2); +} + diff --git a/src/game/curl-noise.hpp b/src/game/curl-noise.hpp new file mode 100755 index 0000000..cba105f --- /dev/null +++ b/src/game/curl-noise.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef CURL_NOISE_HPP +#define CURL_NOISE_HPP + +#include +using namespace Emergent; + +Vector3 curl(const Vector3& p, const Vector3& offset, float frequency); + +#endif // CURL_NOISE_HPP diff --git a/src/game/forceps.cpp b/src/game/forceps.cpp new file mode 100755 index 0000000..0df4cac --- /dev/null +++ b/src/game/forceps.cpp @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "forceps.hpp" +#include "camera-rig.hpp" +#include + +Forceps::Forceps(const Model* model, Animator* animator): + wasActive(false) +{ + // Allocate pose and initialize to bind pose + pose = new Pose(model->getSkeleton()); + pose->reset(); + + // Setup model instance + modelInstance.setModel(model); + modelInstance.setPose(pose); + + // Find pinch animation + pinchClip = model->getSkeleton()->getAnimationClip("pinch"); + if (!pinchClip) + { + throw std::runtime_error("Forceps pinch animation clip not found."); + } + + // Find release animation + releaseClip = model->getSkeleton()->getAnimationClip("release"); + if (!releaseClip) + { + throw std::runtime_error("Forceps release animation clip not found."); + } + + // Scale animation speed + float pinchDuration = 0.1f; + float releaseDuration = 0.05f; + float pinchSpeed = std::get<1>(pinchClip->getTimeFrame()) / pinchDuration; + float releaseSpeed = std::get<1>(releaseClip->getTimeFrame()) / releaseDuration; + + std::cout << std::get<1>(pinchClip->getTimeFrame()) << std::endl; + std::cout << std::get<1>(releaseClip->getTimeFrame()) << std::endl; + + // Setup pinch animation callbacks + pinchAnimation.setSpeed(pinchSpeed); + pinchAnimation.setTimeFrame(pinchClip->getTimeFrame()); + pinchAnimation.setClip(pinchClip); + pinchAnimation.setAnimateCallback + ( + [this](std::size_t id, const Transform& transform) + { + this->pose->setRelativeTransform(id, transform); + } + ); + pinchAnimation.setEndCallback + ( + [this]() + { + this->pinched = true; + } + ); + + // Setup release animation callbacks + releaseAnimation.setSpeed(releaseSpeed); + releaseAnimation.setTimeFrame(releaseClip->getTimeFrame()); + releaseAnimation.setClip(releaseClip); + releaseAnimation.setAnimateCallback + ( + [this](std::size_t id, const Transform& transform) + { + this->pose->setRelativeTransform(id, transform); + } + ); + + hoverDistance = 1.0f; + + // Setup timing + float descentDuration = 0.125f; + float ascentDuration = 0.125f; + + /* + // Allocate tweener and and setup tweens + tweener = new Tweener(); + descentTween = new Tween(EaseFunction::OUT_CUBIC, 0.0f, descentDuration, hoverDistance, -hoverDistance); + ascentTween = new Tween(EaseFunction::IN_CUBIC, 0.0f, ascentDuration, 0.0f, hoverDistance); + descentTween->setEndCallback(std::bind(&TweenBase::start, ascentTween)); + tweener->addTween(descentTween); + tweener->addTween(ascentTween); + */ + + // Setup initial state + state = Forceps::State::RELEASED; + for (std::size_t i = 0; i < pinchClip->getChannelCount(); ++i) + { + const AnimationChannel* channel = pinchClip->getChannelByIndex(i); + pose->setRelativeTransform(channel->getChannelID(), *std::get<1>(channel->getKeyframe(0))); + } + pose->concatenate(); + + animator->addAnimation(&pinchAnimation); + animator->addAnimation(&releaseAnimation); +} + +Forceps::~Forceps() +{ + delete pose; +} + +void Forceps::update(float dt) +{ + // Determine distance from pick point + float forcepsDistance = hoverDistance; + + Quaternion alignment = glm::angleAxis(orbitCam->getAzimuth(), Vector3(0, 1, 0)); + Quaternion tilt = glm::angleAxis(glm::radians(15.0f), Vector3(0, 0, -1)); + tilt = tilt * glm::angleAxis(glm::radians(-70.0f), tilt * Vector3(0, 1, 0)); + Quaternion rotation = glm::normalize(alignment * tilt); + Vector3 translation = pick + rotation * Vector3(0, forcepsDistance, 0); + + // Set tool position + modelInstance.setTranslation(translation); + modelInstance.setRotation(rotation); + + + pose->concatenate(); + + if (active && !wasActive) + { + modelInstance.resetTweens(); + modelInstance.setActive(true); + } + else if (!active && wasActive) + { + modelInstance.setActive(false); + } + + wasActive = active; + + + /* + + if (state == Forceps::State::RELEASED) + { + + } + else if (state == Forceps::State::RELEASING) + { + // Perform release animation + releaseAnimation->animate(pose, animationTime); + pose->concatenate(); + + // If release animation is finished + if (animationTime >= releaseAnimation->getEndTime()) + { + // Changed to released state + state = Forceps::State::RELEASED; + } + } + else if (state == Forceps::State::PINCHED) + { + if (!ascentTween->isStopped()) + { + // Calculate interpolation factor + float interpolationFactor = (ascentTween->getTweenValue() - ascentTween->getStartValue()) / ascentTween->getDeltaValue(); + + // Form tilt quaternion + //Quaternion tilt = glm::angleAxis(glm::radians(15.0f), Vector3(0, 0, -1)); + tilt = glm::angleAxis(glm::radians(15.0f), Vector3(0, 0, -1)); + + // Project camera forward onto XZ plane + Vector3 cameraForwardXZ = orbitCam->getCamera()->getForward(); + cameraForwardXZ.y = 0.0f; + cameraForwardXZ = glm::normalize(cameraForwardXZ); + + // Form alignment quaternion + //Quaternion alignment = glm::rotation(Vector3(0, 0, -1), cameraForwardXZ); + alignment = glm::angleAxis(orbitCam->getAzimuth(), Vector3(0, 1, 0)); + + // Calculate target rotation at the top of the ascentTween + rotationTop = glm::normalize(alignment * tilt); + + // Interpolate between bottom and top rotations + Quaternion interpolatedRotation = glm::normalize(glm::slerp(rotationBottom, rotationTop, interpolationFactor)); + + // Set target translation at the top of the ascent + translationTop = pick + rotationTop * Vector3(0, hoverDistance, 0); + + // Interpolate between bottom and top translations + Vector3 interpolatedTranslation = glm::lerp(translationBottom, translationTop, interpolationFactor); + + // Update model instance transform + modelInstance.setTranslation(interpolatedTranslation); + modelInstance.setRotation(interpolatedRotation); + } + + if (suspendedAnt != nullptr) + { + // Project forceps forward vector onto XZ plane + Vector3 forward = glm::normalize(modelInstance.getRotation() * Vector3(0, 0, -1)); + forward.y = 0.0f; + forward = glm::normalize(forward); + + // Calculate suspension quaternion + Quaternion suspensionRotation = glm::normalize(glm::rotation(Vector3(0, 0, -1), ((flipRotation) ? -forward : forward))); + + // Suspend ant + suspendedAnt->suspend(modelInstance.getTranslation(), suspensionRotation); + } + } + else if (state == Forceps::State::PINCHING) + { + // Perform pinch animation + pinchAnimation->animate(pose, animationTime); + pose->concatenate(); + + // Rotate to align forceps with ant + if (targetedAnt != nullptr) + { + // Calculate interpolation factor + float interpolationFactor = (descentTween->getTweenValue() - descentTween->getStartValue()) / descentTween->getDeltaValue(); + + // Set target translation at the bottom of the descent + translationBottom = targetedAnt->getPosition(); + + // Interpolate between top and bottom translations + Vector3 interpolatedTranslation = glm::lerp(translationTop, translationBottom, interpolationFactor); + + // Project camera forward onto XZ plane + Vector3 cameraForwardXZ = orbitCam->getCamera()->getForward(); + cameraForwardXZ.y = 0.0f; + cameraForwardXZ = glm::normalize(cameraForwardXZ); + + // Form tilt quaternion + tilt = glm::angleAxis(glm::radians(15.0f), -cameraForwardXZ); + + // Project ant forward onto XZ plane + Vector3 antForwardXZ = targetedAnt->getForward(); + antForwardXZ.y = 0.0f; + antForwardXZ = glm::normalize(antForwardXZ); + + // Form alignment quaternion + alignment = glm::rotation(Vector3(0, 0, -1), (flipRotation) ? antForwardXZ : -antForwardXZ); + + // Calculate target rotation at the bottom of the descent + rotationBottom = glm::normalize(tilt * alignment); + + // Interpolate between top and bottom rotations + Quaternion interpolatedRotation = glm::normalize(glm::slerp(rotationTop, rotationBottom, interpolationFactor)); + + // Update model instance transform + modelInstance.setTranslation(interpolatedTranslation); + modelInstance.setRotation(interpolatedRotation); + } + + // If pinch animation is finished + if (animationTime >= pinchAnimation->getEndTime() && descentTween->isStopped()) + { + // If an ant was targeted + if (targetedAnt != nullptr) + { + // Suspend targeted ant + suspendedAnt = targetedAnt; + suspendedAnt->setState(Ant::State::SUSPENDED); + //suspendedAnt->suspend(modelInstance.getTranslation()); + targetedAnt = nullptr; + } + + // Change to pinched state + state = Forceps::State::PINCHED; + } + } + */ +} + +void Forceps::pinch() +{ + releaseAnimation.stop(); + pinchAnimation.rewind(); + pinchAnimation.play(); + + /* + // Change state to pinching + state = Forceps::State::PINCHING; + animationTime = 0.0f; + + if (colony != nullptr) + { + // Target nearest ant in pinching radius + Sphere pinchingBounds = Sphere(pick, 0.35f); + + // Build a list of ants which intersect the pinching bounds + std::list ants; + colony->queryAnts(pinchingBounds, &ants); + + // Target ant closest to the center of the pinching bounds + float closestDistance = std::numeric_limits::infinity(); + for (Agent* agent: ants) + { + Ant* ant = static_cast(agent); + + Vector3 difference = ant->getPosition() - pinchingBounds.getCenter(); + float distanceSquared = glm::dot(difference, difference); + if (distanceSquared < closestDistance) + { + closestDistance = distanceSquared; + targetedAnt = ant; + } + } + + if (targetedAnt != nullptr) + { + // Start descent tweener + descentTween->start(); + + // Save translation & rotation + translationTop = modelInstance.getTranslation(); + rotationTop = modelInstance.getRotation(); + + // Project ant forward onto XZ plane + Vector3 antForwardXZ = targetedAnt->getForward(); + antForwardXZ.y = 0.0f; + antForwardXZ = glm::normalize(antForwardXZ); + + // Project camera forward onto XZ plane + Vector3 cameraForwardXZ = orbitCam->getCamera()->getForward(); + cameraForwardXZ.y = 0.0f; + cameraForwardXZ = glm::normalize(cameraForwardXZ); + + // Find angle between ant and camera on XZ plane + float angle = std::acos(glm::dot(cameraForwardXZ, antForwardXZ)); + + // Determine direction to rotate + flipRotation = (angle > glm::radians(90.0f)); + } + } + */ +} + +void Forceps::release() +{ + pinchAnimation.stop(); + releaseAnimation.rewind(); + releaseAnimation.play(); + + /* + // Change state to releasing + state = Forceps::State::RELEASING; + animationTime = 0.0f; + targetedAnt = nullptr; + + if (suspendedAnt != nullptr) + { + Ray pickingRay; + pickingRay.origin = pick + Vector3(0, 1, 0); + pickingRay.direction = Vector3(0, -1, 0); + + const std::vector* navmeshTriangles = navmesh->getTriangles(); + for (Navmesh::Triangle* triangle: *navmeshTriangles) + { + auto result = intersects(pickingRay, triangle); + if (std::get<0>(result)) + { + Vector3 barycentricPosition = Vector3(std::get<2>(result), std::get<3>(result), 1.0f - std::get<2>(result) - std::get<3>(result)); + suspendedAnt->setPosition(triangle, barycentricPosition); + + break; + } + } + + // Release suspended ant + suspendedAnt->setState(Ant::State::WANDER); + suspendedAnt = nullptr; + } + + // Reset tweens + descentTween->reset(); + descentTween->stop(); + ascentTween->reset(); + ascentTween->stop(); + */ +} + diff --git a/src/game/forceps.hpp b/src/game/forceps.hpp new file mode 100755 index 0000000..d5f0735 --- /dev/null +++ b/src/game/forceps.hpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef FORCEPS_HPP +#define FORCEPS_HPP + +#include "tool.hpp" + +/** + * The forceps tool can pick up ants and place them anywhere in the world. + */ +class Forceps: public Tool +{ +public: + enum class State + { + RELEASED, + RELEASING, + PINCHED, + PINCHING + }; + + /** + * Creates an instance of Forceps. + * + * @param model Forceps model + */ + Forceps(const Model* model, Animator* animator); + + /** + * Destroys an instance of Forceps. + */ + ~Forceps(); + + /** + * Updates the forceps. + * + * @param dt Application timestep. + */ + virtual void update(float dt); + + /** + * Pinches the forceps. + */ + void pinch(); + + /** + * Releases the forceps. + */ + void release(); + + /** + * Returns the current state of the forceps. + */ + Forceps::State getState() const; + +private: + Forceps::State state; + Pose* pose; + const AnimationClip* pinchClip; + const AnimationClip* releaseClip; + Animation pinchAnimation; + Animation releaseAnimation; + float hoverDistance; + Vector3 translationBottom; + Vector3 translationTop; + Quaternion rotationTop; + Quaternion rotationBottom; + bool flipRotation; + bool pinched; + bool wasActive; +}; + +inline Forceps::State Forceps::getState() const +{ + return state; +} + +#endif // FORCEPS_HPP diff --git a/src/game/lens.cpp b/src/game/lens.cpp new file mode 100755 index 0000000..3970df8 --- /dev/null +++ b/src/game/lens.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "lens.hpp" +#include "camera-rig.hpp" +#include "../entity/systems/particle-system.hpp" + +Lens::Lens(const Model* model, Animator* animator): + wasActive(false), + particleSystem(nullptr) +{ + // Setup model instance + modelInstance.setModel(model); + modelInstance.setCullingEnabled(false); + + // Setup spotlight + spotlight.setColor(Vector3(1.0f)); + spotlight.setIntensity(1000.0f); + spotlight.setAttenuation(Vector3(1, 0, 1)); + spotlight.setCutoff(std::cos(glm::radians(45.0f))); + spotlight.setExponent(150.0f); + spotlight.setActive(false); + + unfocusedDistance = 18.0f; + unfocusDuration = 0.25f; + focusedDistance = 12.0f; + focusDuration = 0.75f; + + lensDistance = unfocusedDistance; + focused = false; + lastDistance = unfocusedDistance; + sunDirection = Vector3(0, -1, 0); + + // Construct focus animation clip + AnimationChannel* channel; + focusClip.setInterpolator(easeOutCubic); + channel = focusClip.addChannel(0); + channel->insertKeyframe(0.0f, 0.0f); + channel->insertKeyframe(focusDuration, 1.0f); + + // Construct unfocus animation clip + unfocusClip.setInterpolator(easeOutCubic); + channel = unfocusClip.addChannel(0); + channel->insertKeyframe(0.0f, 0.0f); + channel->insertKeyframe(unfocusDuration, 1.0f); + + // Setup focus animation callbacks + focusAnimation.setTimeFrame(focusClip.getTimeFrame()); + focusAnimation.setClip(&focusClip); + focusAnimation.setAnimateCallback + ( + [this](std::size_t id, float distance) + { + this->lensDistance = lerp(lastDistance, focusedDistance, distance); + } + ); + focusAnimation.setEndCallback + ( + [this]() + { + this->focused = true; + } + ); + + // Setup unfocus animation callbacks + unfocusAnimation.setTimeFrame(unfocusClip.getTimeFrame()); + unfocusAnimation.setClip(&unfocusClip); + unfocusAnimation.setAnimateCallback + ( + [this](std::size_t id, float distance) + { + this->lensDistance = lerp(lastDistance, unfocusedDistance, distance); + } + ); + + // Add animations to animator + animator->addAnimation(&focusAnimation); + animator->addAnimation(&unfocusAnimation); +} + +Lens::~Lens() +{} + +void Lens::update(float dt) +{ + //Quaternion alignment = glm::rotation(Vector3(0, 1, 0), -sunDirection) * glm::angleAxis(glm::radians(90.0f), Vector3(0, 1, 0)); + Quaternion alignment = glm::rotation(Vector3(0, 1, 0), -sunDirection) * glm::angleAxis(orbitCam->getAzimuth() + glm::radians(90.0f), Vector3(0, 1, 0)); + Quaternion rotation = glm::normalize(alignment); + Vector3 translation = pick + sunDirection * -lensDistance; + + modelInstance.setTranslation(translation); + modelInstance.setRotation(rotation); + + float t = (1.0 - (lensDistance - focusedDistance) / (unfocusedDistance - focusedDistance)); + + float unfocusedIntensity = 250.0f * 2.0f; + float focusedIntensity = 1000.0f * 2.0f; + float intensity = easeInExpo(unfocusedIntensity, focusedIntensity, t); + + float unfocusedExponent = 100.0f; + float focusedExponent = 5000.0f; + float exponent = easeInExpo(unfocusedExponent, focusedExponent, t); + + spotlight.setIntensity(intensity); + spotlight.setExponent(exponent); + spotlight.setTranslation(pick + sunDirection * -lensDistance); + spotlight.setDirection(sunDirection); + + if (active && !wasActive) + { + modelInstance.resetTweens(); + spotlight.resetTweens(); + modelInstance.setActive(true); + spotlight.setActive(true); + } + else if (!active && wasActive) + { + modelInstance.setActive(false); + spotlight.setActive(false); + } + + if (active && focused) + { + int count = 10; + for (int i = 0; i < count; ++i) + particleSystem->emit(pick); + } + + wasActive = active; +} + +void Lens::focus() +{ + lastDistance = lensDistance; + + unfocusAnimation.stop(); + focusAnimation.rewind(); + focusAnimation.play(); +} + +void Lens::unfocus() +{ + lastDistance = lensDistance; + + focusAnimation.stop(); + unfocusAnimation.rewind(); + unfocusAnimation.play(); + focused = false; +} + +void Lens::setSunDirection(const Vector3& direction) +{ + sunDirection = direction; +} + +void Lens::setParticleSystem(ParticleSystem* particleSystem) +{ + this->particleSystem = particleSystem; +} + diff --git a/src/game/lens.hpp b/src/game/lens.hpp new file mode 100755 index 0000000..352a53a --- /dev/null +++ b/src/game/lens.hpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef LENS_HPP +#define LENS_HPP + +#include "tool.hpp" + +class ParticleSystem; + +/** + * The lens tool can be used to burn ants. + * + * @see https://taylorpetrick.com/blog/post/dispersion-opengl + * @see https://taylorpetrick.com/portfolio/webgl/lense + */ +class Lens: public Tool +{ +public: + /** + * Creates an instance of Lens. + * + * @param model Lens model + */ + Lens(const Model* model, Animator* animator); + + /** + * Destroys an instance of Lens. + */ + ~Lens(); + + /** + * Updates the lens. + * + * @param dt Application timestep. + */ + virtual void update(float dt); + + + void focus(); + void unfocus(); + + void setSunDirection(const Vector3& direction); + void setParticleSystem(ParticleSystem* particleSystem); + + /** + * Returns the spotlight. + */ + const Spotlight* getSpotlight() const; + Spotlight* getSpotlight(); + +private: + Spotlight spotlight; + float focusedDistance; + float unfocusedDistance; + float focusDuration; + float unfocusDuration; + float lensDistance; + float lastDistance; + bool focused; + Vector3 sunDirection; + Animation focusAnimation; + Animation unfocusAnimation; + AnimationClip focusClip; + AnimationClip unfocusClip; + bool wasActive; + ParticleSystem* particleSystem; +}; + +inline const Spotlight* Lens::getSpotlight() const +{ + return &spotlight; +} + +inline Spotlight* Lens::getSpotlight() +{ + return &spotlight; +} + +#endif // LENS_HPP diff --git a/src/game/level.cpp b/src/game/level.cpp deleted file mode 100644 index 51a10ac..0000000 --- a/src/game/level.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include "level.hpp" -#include "../settings.hpp" -#include "../configuration.hpp" - -#include -#include -#include - -LevelParameterSet::LevelParameterSet() -{} - -LevelParameterSet::~LevelParameterSet() -{} - -bool LevelParameterSet::load(const std::string& filename) -{ - this->filename = filename; - - ParameterDict parameters; - if (!parameters.load(filename)) - { - return false; - } - - parameters.get("biome", &biome); - parameters.get("heightmap", &heightmap); - - return true; -} - -Level::Level() -{ - terrain.create(255, 255, Vector3(ANTKEEPER_TERRAIN_WIDTH, ANTKEEPER_TERRAIN_BASE_HEIGHT, ANTKEEPER_TERRAIN_DEPTH)); -} - -Level::~Level() -{} - -bool Level::load(const LevelParameterSet& params) -{ - // Load terrain from heightmap - std::string heightmapFilename = std::string("data/textures/") + params.heightmap; - if (!terrain.load(heightmapFilename)) - { - std::cerr << "Failed to load terrain from heightmap file \"" << heightmapFilename << "\" for level \"" << params.filename << "\"" << std::endl; - return false; - } - - //application->currentLevelTerrain->getSurfaceModel()->getGroup(0)->material = application->materialLoader->load("data/materials/debug-terrain-surface.mtl"); - - // Setup terrain surface model instance - terrainSurface.setModel(terrain.getSurfaceModel()); - terrainSurface.setTranslation(Vector3(0, 0, 0)); - - // Setup terrain subsurface model instance - terrainSubsurface.setModel(terrain.getSubsurfaceModel()); - terrainSubsurface.setTranslation(Vector3(0, 0, 0)); - - return true; -} - -Campaign::Campaign() -{} - -Campaign::~Campaign() -{} - -bool Campaign::load(const std::string& directory) -{ - // Open levels directory - DIR* dir = opendir(directory.c_str()); - if (dir == nullptr) - { - std::cout << "Failed to open levels directory \"" << directory << "\"" << std::endl; - return false; - } - - // Scan directory for .lvl files - for (struct dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir)) - { - if (entry->d_type == DT_DIR || *entry->d_name == '.') - { - continue; - } - - std::string filename = entry->d_name; - std::string::size_type delimeter = filename.find_last_of('.'); - if (delimeter == std::string::npos) - { - continue; - } - - std::string extension = filename.substr(delimeter + 1); - if (extension != "lvl") - { - continue; - } - - std::string worldIndexString = filename.substr(0, 2); - std::string levelIndexString = filename.substr(3, 2); - int worldIndex = -1; - int levelIndex = -1; - - std::stringstream stream; - stream << worldIndexString; - stream >> worldIndex; - worldIndex -= 1; - - stream.str(std::string()); - stream.clear(); - stream << levelIndexString; - stream >> levelIndex; - levelIndex -= 1; - - if (worldIndex < 0 || levelIndex < 0) - { - std::cout << "Invalid level parameters file \"" << filename << "\"" << std::endl; - continue; - } - - // Resize vector to accommodate maximum world index - if (worldIndex >= static_cast(levelParameterSets.size())) - { - levelParameterSets.resize(worldIndex + 1); - } - - // Resize vector to accommodate maximum level file index - if (levelIndex >= static_cast(levelParameterSets[worldIndex].size())) - { - levelParameterSets[worldIndex].resize(levelIndex + 1); - } - - // Load level parameters - LevelParameterSet* levelParams = &levelParameterSets[worldIndex][levelIndex]; - if (!levelParams->load(directory + filename)) - { - std::cout << "Failed to load parameters for level " << (worldIndex + 1) << "-" << (levelIndex + 1) << std::endl; - } - else - { - std::cout << "Loaded level parameters for level " << (worldIndex + 1) << "-" << (levelIndex + 1) << std::endl; - } - } - - // Close levels directory - closedir(dir); - - return true; -} diff --git a/src/game/level.hpp b/src/game/level.hpp deleted file mode 100644 index 5b1eab8..0000000 --- a/src/game/level.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef LEVEL_HPP -#define LEVEL_HPP - -#include "../configuration.hpp" -#include "terrain.hpp" -#include -#include - -/** - * Contains the parameters required to load a level. - */ -class LevelParameterSet -{ -public: - LevelParameterSet(); - ~LevelParameterSet(); - - // Loads level parameters from a .lvl file - bool load(const std::string& filename); - - std::string filename; - std::string biome; - std::string heightmap; -}; - -/** - * A level. - */ -class Level -{ -public: - Level(); - ~Level(); - - // Loads a level from a level file - bool load(const LevelParameterSet& params); - - Terrain terrain; - ModelInstance terrainSurface; - ModelInstance terrainSubsurface; -}; - -/** - * A collection of level parameters which constitute a campaign. - */ -class Campaign -{ -public: - Campaign(); - ~Campaign(); - - // Loads all level parameter sets in a directory with the file name pattern `-.lvl` - bool load(const std::string& directory); - - // Returns the number of worlds in the campaign - std::size_t getWorldCount() const; - - // Returns the number of levels in a world - std::size_t getLevelCount(std::size_t worldIndex) const; - - // Returns the file for the level with the specified indices - const LevelParameterSet* getLevelParams(std::size_t worldIndex, std::size_t levelIndex) const; - -private: - std::vector> levelParameterSets; -}; - -inline std::size_t Campaign::getWorldCount() const -{ - return levelParameterSets.size(); -} - -inline std::size_t Campaign::getLevelCount(std::size_t worldIndex) const -{ - return levelParameterSets[worldIndex].size(); -} - -inline const LevelParameterSet* Campaign::getLevelParams(std::size_t worldIndex, std::size_t levelIndex) const -{ - return &levelParameterSets[worldIndex][levelIndex]; -} - -#endif // LEVEL_HPP diff --git a/src/game/navmesh.cpp b/src/game/navmesh.cpp deleted file mode 100644 index 92ee01e..0000000 --- a/src/game/navmesh.cpp +++ /dev/null @@ -1,692 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#include "navmesh.hpp" -#include -#include -#include -#include -#include - -Navmesh::Navmesh() -{} - -Navmesh::~Navmesh() -{ - destroy(); -} - -bool Navmesh::create(const std::vector& vertices, const std::vector& indices) -{ - destroy(); - - if (indices.size() % 3 != 0) - { - std::cerr << "Navmesh::create(): index count is non multiple of 3\n"; - return false; - } - - // Copy vertices - this->vertices.resize(vertices.size()); - for (std::size_t i = 0; i < vertices.size(); ++i) - { - Navmesh::Vertex* vertex = new Navmesh::Vertex(); - vertex->edge = nullptr; - vertex->position = vertices[i]; - vertex->flags = 0; - vertex->index = i; - - this->vertices[i] = vertex; - } - - // Allocate triangles - triangles.resize(indices.size() / 3, nullptr); - std::size_t currentTriangle = 0; - - // Load triangles - std::map, Navmesh::Edge*> edgeMap; - for (std::size_t i = 0; i < indices.size(); i += 3) - { - std::size_t a = indices[i]; - std::size_t b = indices[i + 1]; - std::size_t c = indices[i + 2]; - - if (a >= vertices.size() || b >= vertices.size() || c >= vertices.size()) - { - std::cerr << "Navmesh::create(): Mesh contains invalid index.\n"; - destroy(); - return false; - } - - // Allocate three edges and a triangle - Navmesh::Edge* ab = new Navmesh::Edge(); - Navmesh::Edge* bc = new Navmesh::Edge(); - Navmesh::Edge* ca = new Navmesh::Edge(); - Navmesh::Triangle* triangle = new Navmesh::Triangle(); - - // Zero triangle flags - triangle->flags = 0; - - // Zero edge flags - ab->flags = 0; - bc->flags = 0; - ca->flags = 0; - - // Set triangle start edge - triangle->edge = ab; - - // For each edge in this triangle - std::size_t triangleIndices[] = {a, b, c}; - Navmesh::Edge* triangleEdges[] = {ab, bc, ca}; - for (std::size_t j = 0; j < 3; ++j) - { - // Set edge properties - Navmesh::Edge* edge = triangleEdges[j]; - edge->triangle = triangle; - edge->vertex = this->vertices[triangleIndices[j]]; - edge->previous = triangleEdges[(j + 2) % 3]; - edge->next = triangleEdges[(j + 1) % 3]; - edge->symmetric = nullptr; - - // Point vertex to this edge - edge->vertex->edge = edge; - - // Check for symmetry - std::pair symmetricPair(triangleIndices[(j + 1) % 3], triangleIndices[j]); - std::map, Navmesh::Edge*>::iterator it = edgeMap.find(symmetricPair); - if (it == edgeMap.end()) - { - // No symmetric edge found, insert this edge into the map - std::pair pair(triangleIndices[j], triangleIndices[(j + 1) % 3]); - edgeMap[pair] = edge; - } - else - { - // Symmetric edge found, connect - edge->symmetric = it->second; - it->second->symmetric = edge; - } - } - - // Set edge indices and add edges to the edge list - ab->index = edges.size(); - edges.push_back(ab); - bc->index = edges.size(); - edges.push_back(bc); - ca->index = edges.size(); - edges.push_back(ca); - - // Set triangle index and add triangle to the triangle list - triangle->index = currentTriangle; - triangles[currentTriangle++] = triangle; - } - - calculateNormals(); - calculateBounds(); - - return true; -} - -void Navmesh::destroy() -{ - for (std::size_t i = 0; i < vertices.size(); ++i) - delete vertices[i]; - for (std::size_t i = 0; i < edges.size(); ++i) - delete edges[i]; - for (std::size_t i = 0; i < triangles.size(); ++i) - delete triangles[i]; - - vertices.clear(); - edges.clear(); - triangles.clear(); -} - -bool Navmesh::loadOBJ(const std::string& filename) -{ - // Open OBJ file - std::ifstream file(filename.c_str()); - if (!file.is_open()) - { - std::cerr << "Navmesh::loadOBJ(): Failed to open Wavefront OBJ file \"" << filename << "\"" << std::endl; - return false; - } - - // Read OBJ file from file stream - if (!readOBJ(&file, filename)) - { - std::cerr << "Navmesh::loadOBJ(): Failed to read Wavefront OBJ file \"" << filename << "\"" << std::endl; - file.close(); - return false; - } - - // Close OBJ file - file.close(); - - return true; -} - -void Navmesh::traverse(Navmesh::Triangle* startTriangle, const Vector3& startPosition, const Vector3& startVelocity, std::vector* traversal) -{ - // Form initial traversal step - Navmesh::Step step; - step.triangle = startTriangle; - step.start = normalize_barycentric(startPosition); - step.end = step.start; - step.edge = nullptr; - - // Determine the maximum distance of the traversal - float maxDistance = glm::length(startVelocity); - - // Set initial velocity - Vector3 velocity = startVelocity; - - // Traverse navmesh - float distance = 0.0f; - while (distance < maxDistance) - { - // Grab triangle coordinates - const Vector3& a = step.triangle->edge->vertex->position; - const Vector3& b = step.triangle->edge->next->vertex->position; - const Vector3& c = step.triangle->edge->previous->vertex->position; - - // Calculate target position - Vector3 cartesianStart = cartesian(step.start, a, b, c); - Vector3 target = cartesianStart + velocity; - - // Find closest point on triangle to target position - closestPointOnTriangle(target, step.triangle, &step.end, &step.edge); - step.end = normalize_barycentric(step.end); - - // Add step to the traversal - traversal->push_back(step); - - // Determine distance traveled by the step - Vector3 cartesianEnd = cartesian(step.end, a, b, c); - distance += glm::length(cartesianEnd - cartesianStart); - - // Check for no movement - if (cartesianEnd == cartesianStart) - { - /* - std::cout << "the same!\n"; - - if (step.edge == nullptr) - std::cout << "\tand no edge\n"; - else if (step.edge->symmetric == nullptr) - { - std::cout << "\tand disconnected\n"; - - //step.edge = step.edge->previous; - } - //break; - */ - - } - - // Check if traversal is complete or edge is disconnected - if (step.edge == nullptr || step.edge->symmetric == nullptr) - { - break; - } - - // Recalculate velocity - Quaternion rotation = glm::rotation(step.triangle->normal, step.edge->symmetric->triangle->normal); - velocity = glm::normalize(rotation * velocity) * (maxDistance - distance); - - // Move to the next triangle - step.triangle = step.edge->symmetric->triangle; - - // Ensure triangle wasn't already visited - for (const Step& visited: (*traversal)) - { - if (step.triangle == visited.triangle) - { - return; - } - } - - // Calculate barycentric starting coordinates of the next step - step.start = normalize_barycentric(barycentric(cartesianEnd, - step.triangle->edge->vertex->position, - step.triangle->edge->next->vertex->position, - step.triangle->edge->previous->vertex->position)); - step.end = step.start; - step.edge = nullptr; - } - - /* - // Add triangle to visited list - visited->push_back(triangle); - - // Grab triangle coordinates - const glm::vec3& a = triangle->edge->vertex->position; - const glm::vec3& b = triangle->edge->next->vertex->position; - const glm::vec3& c = triangle->edge->previous->vertex->position; - - // Project target onto triangle - glm::vec3 closestPoint; - int edgeIndex = -1; - WingedEdge::Edge* closestEdge = nullptr; - project_on_triangle(target, a, b, c, &closestPoint, &edgeIndex); - *end = closestPoint; - - // Determine if projected target is on an edge - switch (edgeIndex) - { - case -1: - // Projected target inside triangle - return; - case 0: - closestEdge = triangle->edge; - break; - case 1: - closestEdge = triangle->edge->next; - break; - case 2: - closestEdge = triangle->edge->previous; - break; - } - - // If edge is not loose, repeat with connected triangle - if (closestEdge->symmetric != nullptr) - { - for (std::size_t i = 0; i < visited->size() - 1; ++i) - { - if ((*visited)[i] == closestEdge->symmetric->triangle) - return; - } - - move(mesh, closestEdge->symmetric->triangle, closestPoint, target, visited, end); - } - */ -} - -/* - if (steerCCW.isTriggered()) - { - glm::quat rotation = glm::angleAxis(0.1f, navi.triangle->normal); - navi_forward = glm::normalize(rotation * navi_forward); - } - if (steerCW.isTriggered()) - { - glm::quat rotation = glm::angleAxis(-0.1f, navi.triangle->normal); - navi_forward = glm::normalize(rotation * navi_forward); - } - - if (navigate.isTriggered()) - { - - Mesh::Triangle* triangle = navi.triangle; - glm::vec3 start = navi.position; - glm::vec3 target = start + navi_forward * 0.02f; - std::vector visited; - glm::vec3 end; - - move(&sceneManager.getTerrain()->mesh, triangle, start, target, &visited, &end); - - Mesh::Triangle* end_triangle = visited[visited.size() - 1]; - const glm::vec3& a = end_triangle->edge->vertex->position; - const glm::vec3& b = end_triangle->edge->next->vertex->position; - const glm::vec3& c = end_triangle->edge->previous->vertex->position; - glm::vec3 p = (a + b + c) / 3.0f; - const glm::vec3& n = end_triangle->normal; - glm::vec3 projected_start = project_on_plane(start, p, n); - - // Calculate difference between positions - glm::vec3 difference = end - projected_start; - if (glm::dot(difference, difference) != 0.0f) - { - if (end_triangle != triangle) - { - glm::quat alignment = glm::rotation(triangle->normal, end_triangle->normal); - navi_forward = glm::normalize(alignment * navi_forward); - } - } - - navi.position = end; - navi.triangle = visited[visited.size() - 1]; - } -*/ - -void Navmesh::calculateNormals() -{ - for (std::size_t i = 0; i < triangles.size(); ++i) - { - Navmesh::Triangle* triangle = triangles[i]; - - // Calculate surface normal - const Vector3& a = triangle->edge->vertex->position; - const Vector3& b = triangle->edge->next->vertex->position; - const Vector3& c = triangle->edge->previous->vertex->position; - Vector3 ba = b - a; - Vector3 ca = c - a; - triangle->normal = glm::normalize(glm::cross(ba, ca)); - } -} - -void Navmesh::calculateBounds() -{ - Vector3 minPoint(std::numeric_limits::infinity()); - Vector3 maxPoint(-std::numeric_limits::infinity()); - - for (const Navmesh::Vertex* vertex: vertices) - { - minPoint.x = std::min(minPoint.x, vertex->position.x); - minPoint.y = std::min(minPoint.y, vertex->position.y); - minPoint.z = std::min(minPoint.z, vertex->position.z); - - maxPoint.x = std::max(maxPoint.x, vertex->position.x); - maxPoint.y = std::max(maxPoint.y, vertex->position.y); - maxPoint.z = std::max(maxPoint.z, vertex->position.z); - } - - bounds.setMin(minPoint); - bounds.setMax(maxPoint); -} - -bool Navmesh::readOBJ(std::istream* stream, const std::string& filename) -{ - std::string line; - std::vector vertices; - std::vector indices; - - while (stream->good() && std::getline(*stream, line)) - { - // Tokenize line - std::vector tokens; - std::string token; - std::istringstream linestream(line); - while (linestream >> token) - tokens.push_back(token); - - // Skip empty lines and comments - if (tokens.empty() || tokens[0][0] == '#') - continue; - - if (tokens[0] == "v") - { - if (tokens.size() != 4) - { - std::cerr << "Navmesh::readOBJ(): Invalid line \"" << line << "\" in file \"" << filename << "\"" << std::endl; - return false; - } - - Vector3 vertex; - - std::stringstream(tokens[1]) >> vertex.x; - std::stringstream(tokens[2]) >> vertex.y; - std::stringstream(tokens[3]) >> vertex.z; - - vertices.push_back(vertex); - } - else if (tokens[0] == "f") - { - if (tokens.size() != 4) - { - std::cerr << "Navmesh::readOBJ(): Invalid line \"" << line << "\" in file \"" << filename << "\"" << std::endl; - return false; - } - - std::size_t a, b, c; - std::stringstream(tokens[1]) >> a; - std::stringstream(tokens[2]) >> b; - std::stringstream(tokens[3]) >> c; - - a -= 1; - b -= 1; - c -= 1; - - indices.push_back(a); - indices.push_back(b); - indices.push_back(c); - } - } - - return create(vertices, indices); -} - -Vector3 Navmesh::barycentric(const Vector3& p, const Vector3& a, const Vector3& b, const Vector3& c) -{ - Vector3 v0 = b - a; - Vector3 v1 = c - a; - Vector3 v2 = p - a; - - float d00 = glm::dot(v0, v0); - float d01 = glm::dot(v0, v1); - float d11 = glm::dot(v1, v1); - float d20 = glm::dot(v2, v0); - float d21 = glm::dot(v2, v1); - float denom = d00 * d11 - d01 * d01; - - Vector3 result; - result.y = (d11 * d20 - d01 * d21) / denom; // v - result.z = (d00 * d21 - d01 * d20) / denom; // w - result.x = 1.0f - result.y - result.z; // u - - return result; -} - -Vector3 Navmesh::cartesian(const Vector3& p, const Vector3& a, const Vector3& b, const Vector3& c) -{ - return a * p.x + b * p.y + c * p.z; -} - -// code taken from Detour's dtClosestPtPointTriangle -// @see https://github.com/recastnavigation/recastnavigation/blob/master/Detour/Source/DetourCommon.cpp -// (zlib license) -void Navmesh::closestPointOnTriangle(const Vector3& p, const Navmesh::Triangle* triangle, Vector3* closestPoint, Navmesh::Edge** closestEdge) -{ - // Grab triangle coordinates - const Vector3& a = triangle->edge->vertex->position; - const Vector3& b = triangle->edge->next->vertex->position; - const Vector3& c = triangle->edge->previous->vertex->position; - - // Check if P in vertex region outside A - Vector3 ab = b - a; - Vector3 ac = c - a; - Vector3 ap = p - a; - float d1 = glm::dot(ab, ap); - float d2 = glm::dot(ac, ap); - if (d1 <= 0.0f && d2 <= 0.0f) - { - // Barycentric coordinates (1, 0, 0) - *closestPoint = Vector3(1.0f, 0.0f, 0.0f); - *closestEdge = triangle->edge; - return; - } - - // Check if P in vertex region outside B - Vector3 bp = p - b; - float d3 = glm::dot(ab, bp); - float d4 = glm::dot(ac, bp); - if (d3 >= 0.0f && d4 <= d3) - { - // Barycentric coordinates (0, 1, 0) - *closestPoint = Vector3(0.0f, 1.0f, 0.0f); - *closestEdge = triangle->edge->next; - return; - } - - // Check if P in edge region of AB, if so return projection of P onto AB - float vc = d1 * d4 - d3 * d2; - if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) - { - // barycentric coordinates (1-v,v,0) - float v = d1 / (d1 - d3); - *closestPoint = Vector3(1.0f - v, v, 0.0f); - *closestEdge = triangle->edge; - return; - } - - // Check if P in vertex region outside C - Vector3 cp = p - c; - float d5 = glm::dot(ab, cp); - float d6 = glm::dot(ac, cp); - if (d6 >= 0.0f && d5 <= d6) - { - // Barycentric coordinates (0, 0, 1) - *closestPoint = Vector3(0.0f, 0.0f, 1.0f); - *closestEdge = triangle->edge->previous; - return; - } - - // Check if P in edge region of AC, if so return projection of P onto AC - float vb = d5 * d2 - d1 * d6; - if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) - { - // Barycentric coordinates (1 - w, 0, w) - float w = d2 / (d2 - d6); - *closestPoint = Vector3(1.0f - w, 0.0f, w); - *closestEdge = triangle->edge->previous; - return; - } - - // Check if P in edge region of BC, if so return projection of P onto BC - float va = d3 * d6 - d5 * d4; - if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) - { - // Barycentric coordinates (0, 1 - w, w) - float w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); - *closestPoint = Vector3(0.0f, 1.0f - w, w); - *closestEdge = triangle->edge->next; - return; - } - - // P inside face region. Compute Q through its barycentric coordinates (u, v, w) - float denom = 1.0f / (va + vb + vc); - float v = vb * denom; - float w = vc * denom; - *closestPoint = Vector3(1.0f - v - w, v, w); - *closestEdge = nullptr; -} - -std::tuple intersects(const Ray& ray, const Navmesh::Triangle* triangle) -{ - return ray.intersects(triangle->edge->vertex->position, triangle->edge->next->vertex->position, triangle->edge->previous->vertex->position); -} - -std::tuple intersects(const Ray& ray, const std::list& triangles) -{ - bool intersection = false; - float t0 = std::numeric_limits::infinity(); - float t1 = -std::numeric_limits::infinity(); - std::size_t index0 = triangles.size(); - std::size_t index1 = triangles.size(); - - for (const Navmesh::Triangle* triangle: triangles) - { - auto result = intersects(ray, triangle); - - if (std::get<0>(result)) - { - intersection = true; - - float t = std::get<1>(result); - float cosTheta = glm::dot(ray.direction, triangle->normal); - - if (cosTheta <= 0.0f) - { - // Front-facing - if (t < t0) - { - t0 = t; - index0 = triangle->index; - } - - } - else - { - // Back-facing - if (t > t1) - { - t1 = t; - index1 = triangle->index; - } - } - } - } - - return std::make_tuple(intersection, t0, t1, index0, index1); -} - -std::tuple intersects(const Ray& ray, const Navmesh& mesh) -{ - const std::vector& triangles = *mesh.getTriangles(); - - bool intersection = false; - float t0 = std::numeric_limits::infinity(); - float t1 = -std::numeric_limits::infinity(); - std::size_t index0 = triangles.size(); - std::size_t index1 = triangles.size(); - - for (std::size_t i = 0; i < triangles.size(); ++i) - { - const Navmesh::Triangle* triangle = triangles[i]; - const Vector3& a = triangle->edge->vertex->position; - const Vector3& b = triangle->edge->next->vertex->position; - const Vector3& c = triangle->edge->previous->vertex->position; - - auto result = ray.intersects(a, b, c); - - if (std::get<0>(result)) - { - intersection = true; - - float t = std::get<1>(result); - float cosTheta = glm::dot(ray.direction, triangle->normal); - - if (cosTheta <= 0.0f) - { - // Front-facing - t0 = std::min(t0, t); - index0 = i; - } - else - { - // Back-facing - t1 = std::max(t1, t); - index1 = i; - } - } - } - - return std::make_tuple(intersection, t0, t1, index0, index1); -} - -Octree* Navmesh::createOctree(std::size_t maxDepth) -{ - Octree* result = new Octree(maxDepth, bounds); - - for (Navmesh::Triangle* triangle: triangles) - { - Vector3 min; - min.x = std::min(triangle->edge->vertex->position.x, std::min(triangle->edge->next->vertex->position.x, triangle->edge->previous->vertex->position.x)); - min.y = std::min(triangle->edge->vertex->position.y, std::min(triangle->edge->next->vertex->position.y, triangle->edge->previous->vertex->position.y)); - min.z = std::min(triangle->edge->vertex->position.z, std::min(triangle->edge->next->vertex->position.z, triangle->edge->previous->vertex->position.z)); - - Vector3 max; - max.x = std::max(triangle->edge->vertex->position.x, std::max(triangle->edge->next->vertex->position.x, triangle->edge->previous->vertex->position.x)); - max.y = std::max(triangle->edge->vertex->position.y, std::max(triangle->edge->next->vertex->position.y, triangle->edge->previous->vertex->position.y)); - max.z = std::max(triangle->edge->vertex->position.z, std::max(triangle->edge->next->vertex->position.z, triangle->edge->previous->vertex->position.z)); - - result->insert(AABB(min, max), triangle); - } - - return result; -} diff --git a/src/game/navmesh.hpp b/src/game/navmesh.hpp deleted file mode 100644 index 910b551..0000000 --- a/src/game/navmesh.hpp +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef NAVMESH_HPP -#define NAVMESH_HPP - -#include -#include -#include -#include - -#include -using namespace Emergent; - -/** - * Navigation mesh represented by a half-edge structure. - * - * @ingroup geometry - */ -class Navmesh -{ -public: - struct Vertex; - struct Edge; - struct Triangle; - struct Step; - - /** - * Creates an instance of Navmesh. - */ - Navmesh(); - - /** - * Destroys an instance of Navmesh. - */ - ~Navmesh(); - - /** - * Forms a navmesh from a list of vertices and indices. - * - * @param vertices Specifies a list of vertices. - * @param indices Specifies a list of indices. - * @return `true` if the navmesh was successfully created, `false` otherwise. - */ - bool create(const std::vector& vertices, const std::vector& indices); - - /** - * Destroys the navmesh. - */ - void destroy(); - - /** - * Loads this navmesh from a triangulated Wavefront OBJ file. This method only supported **triangulated** Wavefront OBJ files. The supported commands are `v`, `f` and comment lines beginning with `#`. - * - * @param filename Path to the Wavefront OBJ file. - * @return `true` if the navmesh was successfully loaded from the OBJ file, `false` otherwise. - */ - bool loadOBJ(const std::string& filename); - - /** - * Traverses the navmesh. - * - * @param[in] startTriangle Initial triangle - * @param[in] startPosition Initial barycentric coordinates on the start triangle - * @param[in] startVelocity Initial cartesian velocity vector - * @param[out] traversal Traversal information - */ - static void traverse(Navmesh::Triangle* startTriangle, const Vector3& startPosition, const Vector3& startVelocity, std::vector* traversal); - - /** - * Creates an octree of navmesh triangles - */ - Octree* createOctree(std::size_t maxDepth); - - /// Returns a pointer to the navmesh vertices - const std::vector* getVertices() const; - - /// Returns a pointer to the navmesh edges - const std::vector* getEdges() const; - - /// Returns a pointer to the navmesh triangles - const std::vector* getTriangles() const; - - /// @copydoc Navmesh::getVertices() const - std::vector* getVertices(); - - /// @copydoc Navmesh::getEdges() const - std::vector* getEdges(); - - /// @copydoc Navmesh::getTriangles() const - std::vector* getTriangles(); - - /// Returns an AABB which contains this navmesh - const AABB& getBounds() const; - - /** - * Half-edge vertex which contains a pointer to its parent edge, a position vector, and an index. - */ - struct Vertex - { - /// Pointer to the edge to which this vertex belongs - Navmesh::Edge* edge; - - /// Vertex position vector - Vector3 position; - - /// Vertex flags - unsigned char flags; - - /// Index of this vertex - std::size_t index; - }; - - /** - * Half-edge edge which contains pointers to its starting vertex, parent triangle, and related edges. - */ - struct Edge - { - /// Pointer to the vertex at which the edge starts - Navmesh::Vertex* vertex; - - /// Pointer to the triangle to which this edge belongs - Navmesh::Triangle* triangle; - - /// Pointer to the previous edge in the parent triangle - Navmesh::Edge* previous; - - /// Pointer to the next edge in the parent triangle - Navmesh::Edge* next; - - /// Pointer to the symmetric edge - Navmesh::Edge* symmetric; - - /// Edge flags - unsigned char flags; - - /// Index of this edge - std::size_t index; - }; - - /** - * Half-edge triangle which contains a pointer to its first edge and its normal vector. - */ - struct Triangle - { - /// Pointer to the first edge in this triangle - Navmesh::Edge* edge; - - /// Faceted surface normal - Vector3 normal; - - /// Triangle flags - unsigned char flags; - - /// Index of this triangle - std::size_t index; - }; - - /** - * Contains informations about a single step in a navmesh traversal operation. - */ - struct Step - { - /// Pointer to the triangle on which the step occured - Triangle* triangle; - - /// Barycentric coordinates of the step's starting position - Vector3 start; - - /// Barycentric coordinates of the step's ending position - Vector3 end; - - /// Pointer to the edge on which the step exited the triangle, or `nullptr` if the step is within the triangle - Edge* edge; - }; - - /** - * Calculates the faceted surface normals for each triangle. - */ - void calculateNormals(); - - /** - * Calculates an AABB which contains the navmesh. - */ - void calculateBounds(); - -private: - /** - * Reads Wavefront OBJ data from an input stream - * - * @param stream Input stream containing OBJ data - * @param filename Path to the OBJ file - * @return `true` if OBJ data was successfully read from the file. - */ - bool readOBJ(std::istream* stream, const std::string& filename); - - /** - * Calculates barycentric coordinates from cartesian coordinates. - * - * @param p Cartesian point - * @param a First vertex in triangle - * @param b Second vertex in triangle - * @param c Third vertex in triangle - */ - static Vector3 barycentric(const Vector3& p, const Vector3& a, const Vector3& b, const Vector3& c); - - /** - * Calculates cartesian coordinates from barycentric coordinates. - * - * @param p Barycentric point - * @param a First vertex in triangle - * @param b Second vertex in triangle - * @param c Third vertex in triangle - */ - static Vector3 cartesian(const Vector3& p, const Vector3& a, const Vector3& b, const Vector3& c); - - /** - * Finds the closest point on a triangle. - * - * @param[in] p Point to project - * @param[in] triangle Triangle on which to find the closest point - * @param[out] closest Closest point on triangle - * @param[out] edge Edge on which the closest point is located, or `nullptr` if the closest point is not on an edge. - */ - static void closestPointOnTriangle(const Vector3& p, const Navmesh::Triangle* triangle, Vector3* closestPoint, Navmesh::Edge** closestEdge); - - std::vector vertices; - std::vector edges; - std::vector triangles; - AABB bounds; -}; - -inline const std::vector* Navmesh::getVertices() const -{ - return &vertices; -} - -inline const std::vector* Navmesh::getEdges() const -{ - return &edges; -} - -inline const std::vector* Navmesh::getTriangles() const -{ - return &triangles; -} - -inline std::vector* Navmesh::getVertices() -{ - return &vertices; -} - -inline std::vector* Navmesh::getEdges() -{ - return &edges; -} - -inline std::vector* Navmesh::getTriangles() -{ - return &triangles; -} - -inline const AABB& Navmesh::getBounds() const -{ - return bounds; -} - -std::tuple intersects(const Ray& ray, const Navmesh::Triangle* triangle); -std::tuple intersects(const Ray& ray, const std::list& triangles); - -std::tuple intersects(const Ray& ray, const Navmesh& mesh); - -#endif // NAVMESH_HPP diff --git a/src/game/nest.cpp b/src/game/nest.cpp deleted file mode 100644 index 39ac350..0000000 --- a/src/game/nest.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#include "nest.hpp" -#include -#include - -#undef min -#undef max - -Vector3 Shaft::getHelixPosition(float depth) const -{ - Vector3 position; - position.x = entrance.x + std::cos(initialHelixAngle + depth / helixPitch) * helixRadius; - position.y = entrance.y + depth; - position.z = entrance.z + std::sin(initialHelixAngle + depth / helixPitch) * helixRadius; - - return position; -} - -float Shaft::getHelixAngle(float depth) const -{ - return initialHelixAngle + depth / helixPitch; -} - -Nest::Nest(): - root(nullptr) -{} - -Nest::~Nest() -{ - if (root != nullptr) - { - free(root); - } -} - -void Nest::generate() -{ - if (root != nullptr) - { - // Delete existing shafts and chambers - free(root); - } - - // Seed random number generator - rng.seed(parameters.randomSeed); - - // Generate shafts and chambers - root = dig(nullptr); - - // Merge intersecting chambers - - // Create nest map - map(); -} - -Shaft* Nest::dig(Chamber* parent) const -{ - Shaft* shaft = new Shaft(); - - // Set child's parent - shaft->parent = parent; - - if (parent != nullptr) - { - // Set parent's child to this shaft - parent->child = shaft; - - // Increment generation - shaft->generation = parent->parent->generation + 1; - - // Determine initial helix angle - shaft->initialHelixAngle = parent->parent->getHelixAngle(parent->relativeDepth); - } - else - { - // Shaft is root shaft - shaft->generation = 0; - shaft->entrance = Vector3(0.0f); - - // Choose initial helix angle - - } - - shaft->initialHelixAngle = random(glm::radians(-180.0f), glm::radians(180.0f)); - - if (parent == nullptr) - { - // Choose initial random parameters - shaft->shaftRadius = random(parameters.minShaftRadius, parameters.maxShaftRadius); - shaft->helixRadius = random(parameters.minShaftHelixRadius, parameters.maxShaftHelixRadius); - shaft->helixPitch = random(parameters.minShaftHelixPitch, parameters.maxShaftHelixPitch); - } - else - { - // Copy initial random parameters from grandparent - shaft->shaftRadius = parent->parent->shaftRadius; - shaft->helixRadius = parent->parent->helixRadius; - shaft->helixPitch = parent->parent->helixPitch; - } - - // Choose random depth - shaft->shaftDepth = random(parameters.minShaftDepth, parameters.maxShaftDepth); - - // Calculate entrance position - if (parent != nullptr) - { - Vector3 helixPosition = parent->parent->getHelixPosition(parent->relativeDepth); - float helixAngle = parent->parent->getHelixAngle(parent->relativeDepth); - - shaft->entrance.x = helixPosition.x + std::cos(helixAngle) * (parent->outerRadius - parent->innerRadius) - std::cos(shaft->initialHelixAngle) * shaft->helixRadius; - shaft->entrance.y = helixPosition.y; - shaft->entrance.z = helixPosition.z + std::sin(helixAngle) * (parent->outerRadius - parent->innerRadius) - std::sin(shaft->initialHelixAngle) * shaft->helixRadius; - } - - // Determine potential child count (may be less, according to spacing between chambers) - int maxChildCount = std::max(1, random(parameters.minShaftChamberCount, parameters.maxShaftChamberCount)); - - // Generate chambers, starting with final chamber (shaft must end with a chamber) - for (float depth = shaft->shaftDepth; depth >= 0.0f;) - { - Chamber* chamber = new Chamber(); - shaft->children.push_back(chamber); - - chamber->parent = shaft; - chamber->child = nullptr; - chamber->relativeDepth = depth; - chamber->absoluteDepth = shaft->entrance.y + chamber->relativeDepth; - chamber->innerRadius = random(parameters.minChamberInnerRadius, parameters.maxChamberInnerRadius); - chamber->outerRadius = random(parameters.minChamberOuterRadius, parameters.maxChamberOuterRadius); - chamber->centralAngle = random(parameters.minChamberCentralAngle, parameters.maxChamberCentralAngle); - - // Check if maximum child count has been reached - if (shaft->children.size() >= maxChildCount) - { - break; - } - - // Decrease depth by random amount - depth -= random(parameters.minShaftChamberPitch, parameters.maxShaftChamberPitch); - } - - // Generate subshafts from chambers - if (shaft->generation < parameters.maxShaftGeneration) - { - for (Chamber* chamber: shaft->children) - { - dig(chamber); - } - } - - return shaft; -} - -void Nest::merge() -{ - -} - -void Nest::map() -{ - -} - -void Nest::free(Shaft* shaft) -{ - for (Chamber* chamber: shaft->children) - { - if (chamber->child != nullptr) - { - free(chamber->child); - } - - delete chamber; - } - - delete shaft; -} - -inline float Nest::random(float minValue, float maxValue) const -{ - std::uniform_real_distribution distribution(minValue, std::nextafter(maxValue, std::numeric_limits::max())); - return distribution(rng); -} - -inline int Nest::random(int minValue, int maxValue) const -{ - std::uniform_int_distribution distribution(minValue, std::nextafter(maxValue, std::numeric_limits::max())); - return distribution(rng); -} - -void Nest::setParameters(const NestParameters& parameters) -{ - this->parameters = parameters; -} - -const NestParameters& Nest::getParameters() const -{ - return parameters; -} \ No newline at end of file diff --git a/src/game/nest.hpp b/src/game/nest.hpp deleted file mode 100644 index fb54abc..0000000 --- a/src/game/nest.hpp +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef NEST_HPP -#define NEST_HPP - -#include -#include -#include - -using namespace Emergent; - -/** - * General algorithm: Draw a series of lines straight down. Select multiple elevation levels on each line at which to create a chamber. Create helixes around lines. At the selected elevation levels on each line, create a corresponding chambers on the helixes at the same elevations, in the direction of the outside of the helix. Check for intersections between chambers and tunnels, and merge as necessary. - */ - -struct Chamber; - -/** - * A vertically-directed helical shaft with one or more connected chambers. - */ -struct Shaft -{ - Chamber* parent; // The parent chamber from which this shaft begins - std::vector children; // A list of chambers which are formed from this shaft - int generation; // The generation index of this shaft. The root shaft is gen 0, the shafts from its chambers are gen 1, and so on. - Vector3 entrance; // Position of the entrance point of this shaft - float shaftRadius; // Radius of the shaft - float shaftDepth; // Total depth of this shaft - float initialHelixAngle; // Angle at which the shaft helix begins - float helixRadius; // Radius of the shaft helix - float helixPitch; // Pitch of the shaft helix - - /** - * Returns the position on the shaft's helix at the specified depth. - * - * @param depth Depth relative to the entrance of this shaft. - * @return Point on the helix at the specified depth. - */ - Vector3 getHelixPosition(float depth) const; - - /** - * Returns the angle to the helix at the specified depth. - * - * @param depth Depth relative to the entrance of this shaft. - * @return Angle to the helix at the specified depth. - */ - float getHelixAngle(float depth) const; -}; - -/** - * A horizontal annular chamber with one parent shaft and a max of one child shaft. Chambers always face toward the outside of the parent shaft's helix. - */ -struct Chamber -{ - Shaft* parent; // Parent shaft from which this chamber was formed - Shaft* child; // Child shaft which begins in this chamber - int generation; // The number of chambers from this chamber to the root shaft. - float relativeDepth; // Depth from the entrance of the parent shaft to this chamber - float absoluteDepth; // Depth from the entrance of the root shaft to this chamber - float innerRadius; // Inner radius of the annulus - float outerRadius; // Outer radius of the annulus - float centralAngle; // Angle of the annular sector - float height; // Height of the annular sector - float childAngle; // The angle on the annulus at which the child shaft begins -}; - -/** - * Describes the parameters required to generate a nest. - */ -struct NestParameters -{ - // Random params - unsigned int randomSeed; - - // Shaft params - int maxShaftGeneration; - float minShaftRadius; - float maxShaftRadius; - float minShaftDepth; - float maxShaftDepth; - float minShaftHelixRadius; - float maxShaftHelixRadius; - float minShaftHelixPitch; - float maxShaftHelixPitch; - int minShaftChamberCount; - int maxShaftChamberCount; - float minShaftChamberPitch; - float maxShaftChamberPitch; - - // Chamber params - float minChamberInnerRadius; - float maxChamberInnerRadius; - float minChamberOuterRadius; - float maxChamberOuterRadius; - float minChamberCentralAngle; - float maxChamberCentralAngle; -}; - -class Nest -{ -public: - Nest(); - ~Nest(); - - /** - * Generates the nest and all of its shafts and chambers. - */ - void generate(); - - /** - * Sets the nest generation parameters. - */ - void setParameters(const NestParameters& parameters); - - /** - * Returns the nest generation parameters. - */ - const NestParameters& getParameters() const; - - /** - * Returns a pointer to the root shaft of the nest. - */ - const Shaft* getRootShaft() const; - -private: - /** - * Recursive function which generates a connected system of shafts and chambers. - * - * @param parent Specifies the parent chamber of the shaft. A value of `nullptr` indicates the root shaft. - * @param generation The index of the shaft's generation. The root shaft is generation 0, the shafts formed from the root shaft's chambers are generation 1, and so on. - * @return The newly created shaft. - */ - Shaft* dig(Chamber* parent) const; - - /** - * Determines intersections between chambers and connects them. - */ - void merge(); - - /** - * Creates a map (interconnected system of nodes) with which can be used to navigate the nest. - */ - void map(); - - /** - * Recursive function which deletes a shaft and all of its connect chambers and subshafts. - * - * @param Specifies a shaft to delete. - */ - void free(Shaft* shaft); - - float random(float minValue, float maxValue) const; - int random(int minValue, int maxValue) const; - - NestParameters parameters; - Shaft* root; - mutable std::default_random_engine rng; -}; - -inline const Shaft* Nest::getRootShaft() const -{ - return root; -} - -#endif // NEST_HPP diff --git a/src/game/pheromone-matrix.cpp b/src/game/pheromone-matrix.cpp deleted file mode 100644 index ead03b5..0000000 --- a/src/game/pheromone-matrix.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#include "pheromone-matrix.hpp" -#include "../configuration.hpp" -#include -#include - -PheromoneMatrix::PheromoneMatrix(int columns, int rows, const Vector2& boundsMin, const Vector2& boundsMax) -{ - this->columns = columns; - this->rows = rows; - size = columns * rows; - bufferA = new float[size]; - bufferB = new float[size]; - activeBuffer = bufferA; - this->boundsMin = boundsMin; - this->boundsMax = boundsMax; - matrixWidth = boundsMax.x - boundsMin.x; - matrixHeight = boundsMax.y - boundsMin.y; - matrixHalfWidth = matrixWidth * 0.5f; - matrixHalfHeight = matrixHeight * 0.5f; - cellWidth = matrixWidth / static_cast(columns); - cellHeight = matrixHeight / static_cast(rows); - - diffusionKernelSize = 3; - diffusionKernelRadius = 1; - diffusionKernel = new float*[diffusionKernelSize]; - for (int i = 0; i < diffusionKernelSize; ++i) - { - diffusionKernel[i] = new float[diffusionKernelSize]; - } - - diffusionKernel[0][0] = 0.0083333f; diffusionKernel[0][1] = 0.0166667f; diffusionKernel[0][2] = 0.0083333f; - diffusionKernel[1][0] = 0.0166667f; diffusionKernel[1][1] = 0.9f; diffusionKernel[1][2] = 0.0166667f; - diffusionKernel[2][0] = 0.0083333f; diffusionKernel[2][1] = 0.0166667f; diffusionKernel[2][2] = 0.0083333f; - - clear(); -} - -PheromoneMatrix::~PheromoneMatrix() -{ - delete[] bufferA; - delete[] bufferB; - - for (int i = 0; i < diffusionKernelSize; ++i) - { - delete[] diffusionKernel[i]; - } - delete[] diffusionKernel; -} - -void PheromoneMatrix::clear() -{ - for (int i = 0; i < size; ++i) - { - activeBuffer[i] = 0.0f; - } -} - -void PheromoneMatrix::evaporate() -{ - for (int i = 0; i < size; ++i) - { - activeBuffer[i] *= EVAPORATION_FACTOR; - } -} - -void PheromoneMatrix::diffuse() -{ - float* diffusionBuffer = (activeBuffer == bufferA) ? bufferB : bufferA; - - int index = 0; - for (int i = 0; i < rows; ++i) - { - for (int j = 0; j < columns; ++j) - { - float concentration = 0.0f; - - for (int k = -diffusionKernelRadius; k <= diffusionKernelRadius; ++k) - { - int row = std::max(0, std::min(rows - 1, i + k)); - - for (int l = -diffusionKernelRadius; l <= diffusionKernelRadius; ++l) - { - int column = std::max(0, std::min(columns - 1, j + l)); - concentration += activeBuffer[row * columns + column] * diffusionKernel[k + diffusionKernelRadius][l + diffusionKernelRadius]; - } - } - - diffusionBuffer[index++] = concentration; - } - } - - activeBuffer = diffusionBuffer; -} - -float PheromoneMatrix::query(const Vector2& position) const -{ - int column = static_cast((matrixHalfWidth + position.x) / cellWidth); - int row = static_cast((matrixHalfHeight + position.y) / cellHeight); - - if (columns < 0 || column >= columns || row < 0 || row >= rows) - { - return 0.0f; - } - - int index = row * columns + column; - return activeBuffer[index]; -} - -float PheromoneMatrix::query(const Vector2& position, float radius) const -{ - float radiusSquared = radius * radius; - float concentration = 0.0f; - - for (float y = position.y - radius; y <= position.y + radius; y += cellHeight) - { - int row = static_cast((matrixHalfHeight + y) / cellHeight); - if (row < 0) - continue; - else if (row >= rows) - break; - - float dy = y - position.y; - - for (float x = position.x - radius; x <= position.x + radius; x += cellWidth) - { - int column = floor((matrixHalfWidth + x) / cellWidth); - if (column < 0) - continue; - else if (column >= columns) - break; - - float dx = x - position.x; - - float distanceSquared = dx * dx + dy * dy; - if (distanceSquared <= radiusSquared) - { - concentration += activeBuffer[row * columns + column]; - } - } - } - - return concentration; -} - -void PheromoneMatrix::deposit(const Vector2& position, float concentration) -{ - int column = static_cast((matrixHalfWidth + position.x) / cellWidth); - int row = static_cast((matrixHalfHeight + position.y) / cellHeight); - - if (columns < 0 || column >= columns || row < 0 || row >= rows) - { - return; - } - - int index = row * columns + column; - activeBuffer[index] += concentration; -} diff --git a/src/game/pheromone-matrix.hpp b/src/game/pheromone-matrix.hpp deleted file mode 100644 index ce463bb..0000000 --- a/src/game/pheromone-matrix.hpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef PHEROMONE_MATRIX_HPP -#define PHEROMONE_MATRIX_HPP - -#include -using namespace Emergent; - -class PheromoneMatrix -{ -public: - PheromoneMatrix(int columns, int rows, const Vector2& boundsMin, const Vector2& boundsMax); - - ~PheromoneMatrix(); - - void clear(); - void evaporate(); - void diffuse(); - - float query(const Vector2& position) const; - float query(const Vector2& position, float radius) const; - - void deposit(const Vector2& position, float concentration); - - float getCellWidth() const; - float getCellHeight() const; - - const float* getActiveBuffer() const; - -private: - int columns; - int rows; - int size; - float* bufferA; - float* bufferB; - float* activeBuffer; - float evaporationFactor; - Vector2 boundsMin; - Vector2 boundsMax; - float matrixWidth; - float matrixHeight; - float matrixHalfWidth; - float matrixHalfHeight; - float cellWidth; - float cellHeight; - int diffusionKernelRadius; - int diffusionKernelSize; - float** diffusionKernel; -}; - -inline float PheromoneMatrix::getCellWidth() const -{ - return cellWidth; -} - -inline float PheromoneMatrix::getCellHeight() const -{ - return cellHeight; -} - -inline const float* PheromoneMatrix::getActiveBuffer() const -{ - return activeBuffer; -} - -#endif // PHEROMONE_MATRIX_HPP \ No newline at end of file diff --git a/src/game/terrain.cpp b/src/game/terrain.cpp deleted file mode 100644 index d3808ab..0000000 --- a/src/game/terrain.cpp +++ /dev/null @@ -1,949 +0,0 @@ -#include "terrain.hpp" - -Terrain::Terrain() -{ - surfaceOctree = nullptr; -} - -Terrain::~Terrain() -{ - delete surfaceOctree; -} - -void Terrain::create(int columns, int rows, const Vector3& dimensions) -{ - this->columns = columns; - this->rows = rows; - this->dimensions = dimensions; - - createSurface(); - createSubsurface(); -} - -void Terrain::createSurface() -{ - surfaceVertexSize = 3 + 3 + 2; - surfaceVertexCount = (columns + 1) * (rows + 1); - surfaceTriangleCount = columns * rows * 2; - surfaceIndexCount = surfaceTriangleCount * 3; - surfaceVertexData = new float[surfaceVertexSize * surfaceVertexCount]; - surfaceIndexData = new std::uint32_t[surfaceIndexCount]; - surfaceVertices.resize(surfaceVertexCount); - surfaceIndices.resize(surfaceIndexCount); - - // Calculate scale and offset - Vector2 scale(dimensions.x / (float)columns, dimensions.z / (float)rows); - Vector2 offset(dimensions.x * -0.5f, dimensions.z * -0.5f); - - // Calculate vertex positions - for (int i = 0; i <= rows; ++i) - { - for (int j = 0; j <= columns; ++j) - { - std::size_t index = i * (columns + 1) + j; - - Vector3* vertex = &surfaceVertices[index]; - vertex->x = (float)j * scale.x + offset.x; - vertex->y = 0.0f; - vertex->z = (float)i * scale.y + offset.y; - - float* data = &surfaceVertexData[index * surfaceVertexSize]; - *(data++) = vertex->x; - *(data++) = vertex->y; - *(data++) = vertex->z; - *(data++) = 0.0f; - *(data++) = 1.0f; - *(data++) = 0.0f; - *(data++) = static_cast(j) / static_cast(columns); - *(data++) = static_cast(i) / static_cast(rows); - } - } - - // Generate indices - for (int i = 0; i < rows; ++i) - { - for (int j = 0; j < columns; ++j) - { - unsigned int a = i * (columns + 1) + j; - unsigned int b = (i + 1) * (columns + 1) + j; - unsigned int c = i * (columns + 1) + j + 1; - unsigned int d = (i + 1) * (columns + 1) + j + 1; - - std::size_t index = (i * columns + j) * 2 * 3; - surfaceIndices[index++] = a; - surfaceIndices[index++] = b; - surfaceIndices[index++] = c; - surfaceIndices[index++] = c; - surfaceIndices[index++] = b; - surfaceIndices[index] = d; - } - } - - // Generate index data - for (std::size_t i = 0; i < surfaceIndexCount; ++i) - { - surfaceIndexData[i] = surfaceIndices[i]; - } - - // Generate navmesh - surfaceNavmesh.create(surfaceVertices, surfaceIndices); - - // Calculate vertex normals - calculateSurfaceNormals(); - - // Create and load VAO, VBO, and IBO - glGenVertexArrays(1, &surfaceVAO); - glBindVertexArray(surfaceVAO); - glGenBuffers(1, &surfaceVBO); - glBindBuffer(GL_ARRAY_BUFFER, surfaceVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(float) * surfaceVertexSize * surfaceVertexCount, surfaceVertexData, GL_STATIC_DRAW); - glEnableVertexAttribArray(EMERGENT_VERTEX_POSITION); - glVertexAttribPointer(EMERGENT_VERTEX_POSITION, 3, GL_FLOAT, GL_FALSE, surfaceVertexSize * sizeof(float), (char*)0 + 0 * sizeof(float)); - glEnableVertexAttribArray(EMERGENT_VERTEX_NORMAL); - glVertexAttribPointer(EMERGENT_VERTEX_NORMAL, 3, GL_FLOAT, GL_FALSE, surfaceVertexSize * sizeof(float), (char*)0 + 3 * sizeof(float)); - glEnableVertexAttribArray(EMERGENT_VERTEX_TEXCOORD); - glVertexAttribPointer(EMERGENT_VERTEX_TEXCOORD, 2, GL_FLOAT, GL_FALSE, surfaceVertexSize * sizeof(float), (char*)0 + 6 * sizeof(float)); - glGenBuffers(1, &surfaceIBO); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, surfaceIBO); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(std::uint32_t) * surfaceIndexCount, surfaceIndexData, GL_STATIC_DRAW); - - // Setup material - //surfaceMaterial.flags = static_cast(PhysicalMaterial::Flags::OBJECT); - - // Setup buffers - surfaceModel.setVAO(surfaceVAO); - surfaceModel.setVBO(surfaceVBO); - surfaceModel.setIBO(surfaceIBO); - - // Create model group - Model::Group* group = new Model::Group(); - group->name = "default"; - group->material = nullptr;//&surfaceMaterial; - group->indexOffset = 0; - group->triangleCount = surfaceTriangleCount; - - // Add group to the model - surfaceModel.addGroup(group); - - // Set model bounds - surfaceModel.setBounds(surfaceNavmesh.getBounds()); - - // Calculate octree - surfaceOctree = surfaceNavmesh.createOctree(5); -} - -void Terrain::createSubsurface() -{ - subsurfaceVertexSize = 3 + 3 + 2; - subsurfaceVertexCount = (columns + 1) * 4 + (rows + 1) * 4; - subsurfaceTriangleCount = columns * 4 + rows * 4 + 2; - subsurfaceIndexCount = subsurfaceTriangleCount * 3; - subsurfaceVertexData = new float[subsurfaceVertexSize * subsurfaceVertexCount]; - subsurfaceIndexData = new std::uint32_t[subsurfaceIndexCount]; - subsurfaceVertices.resize(subsurfaceVertexCount); - subsurfaceIndices.resize(subsurfaceIndexCount); - - float maxDimension = dimensions.y; - float textureScaleX = dimensions.x / maxDimension; - float textureScaleY = dimensions.y / maxDimension; - float textureScaleZ = dimensions.z / maxDimension; - - // Calculate floor position - float subsurfaceFloor = -dimensions.y; - - // Calculate vertex positions - Vector3* vertex = &subsurfaceVertices[0]; - float* data = &subsurfaceVertexData[0]; - - // Top row - for (int j = 0; j <= columns; ++j) - { - int i = 0; - std::size_t surfaceIndex = i * (columns + 1) + j; - const Vector3& surfaceVertex = surfaceVertices[surfaceIndex]; - float u = 1.0f - (static_cast(j) / static_cast(columns)) * textureScaleX; - - *(vertex++) = surfaceVertex; - *(data++) = surfaceVertex.x; - *(data++) = surfaceVertex.y; - *(data++) = surfaceVertex.z; - *(data++) = 0.0f; - *(data++) = 0.0f; - *(data++) = 1.0f; - *(data++) = u; - *(data++) = 0.0f; - - *(vertex++) = Vector3(surfaceVertex.x, subsurfaceFloor, surfaceVertex.z); - *(data++) = surfaceVertex.x; - *(data++) = subsurfaceFloor; - *(data++) = surfaceVertex.z; - *(data++) = 0.0f; - *(data++) = 0.0f; - *(data++) = 1.0f; - *(data++) = u; - *(data++) = textureScaleY; - } - - // Bottom row - for (int j = 0; j <= columns; ++j) - { - int i = rows; - std::size_t surfaceIndex = i * (columns + 1) + j; - const Vector3& surfaceVertex = surfaceVertices[surfaceIndex]; - float u = (static_cast(j) / static_cast(columns)) * textureScaleX; - - *(vertex++) = surfaceVertex; - *(data++) = surfaceVertex.x; - *(data++) = surfaceVertex.y; - *(data++) = surfaceVertex.z; - *(data++) = 0.0f; - *(data++) = 0.0f; - *(data++) = 1.0f; - *(data++) = u; - *(data++) = 0.0f; - - *(vertex++) = Vector3(surfaceVertex.x, subsurfaceFloor, surfaceVertex.z); - *(data++) = surfaceVertex.x; - *(data++) = subsurfaceFloor; - *(data++) = surfaceVertex.z; - *(data++) = 0.0f; - *(data++) = 0.0f; - *(data++) = 1.0f; - *(data++) = u; - *(data++) = textureScaleY; - } - - // Left column - for (int i = 0; i <= rows; ++i) - { - int j = 0; - std::size_t surfaceIndex = i * (columns + 1) + j; - const Vector3& surfaceVertex = surfaceVertices[surfaceIndex]; - float u = (static_cast(i) / static_cast(rows)) * textureScaleZ; - - *(vertex++) = surfaceVertex; - *(data++) = surfaceVertex.x; - *(data++) = surfaceVertex.y; - *(data++) = surfaceVertex.z; - *(data++) = 0.0f; - *(data++) = 0.0f; - *(data++) = 1.0f; - *(data++) = u; - *(data++) = 0.0f; - - *(vertex++) = Vector3(surfaceVertex.x, subsurfaceFloor, surfaceVertex.z); - *(data++) = surfaceVertex.x; - *(data++) = subsurfaceFloor; - *(data++) = surfaceVertex.z; - *(data++) = 0.0f; - *(data++) = 0.0f; - *(data++) = 1.0f; - *(data++) = u; - *(data++) = textureScaleY; - } - - // Right column - for (int i = 0; i <= rows; ++i) - { - int j = columns; - std::size_t surfaceIndex = i * (columns + 1) + j; - const Vector3& surfaceVertex = surfaceVertices[surfaceIndex]; - float u = 1.0f - (static_cast(i) / static_cast(rows)) * textureScaleZ; - - *(vertex++) = surfaceVertex; - *(data++) = surfaceVertex.x; - *(data++) = surfaceVertex.y; - *(data++) = surfaceVertex.z; - *(data++) = 0.0f; - *(data++) = 0.0f; - *(data++) = 1.0f; - *(data++) = u; - *(data++) = 0.0f; - - *(vertex++) = Vector3(surfaceVertex.x, subsurfaceFloor, surfaceVertex.z); - *(data++) = surfaceVertex.x; - *(data++) = subsurfaceFloor; - *(data++) = surfaceVertex.z; - *(data++) = 0.0f; - *(data++) = 0.0f; - *(data++) = 1.0f; - *(data++) = u; - *(data++) = textureScaleY; - } - - // Generate indices - std::size_t* index = &subsurfaceIndices[0]; - - for (int i = 0; i < columns; ++i) - { - std::size_t a = i * 2; - std::size_t b = i * 2 + 1; - std::size_t c = (i + 1) * 2; - std::size_t d = (i + 1) * 2 + 1; - - (*(index++)) = b; - (*(index++)) = a; - (*(index++)) = c; - (*(index++)) = b; - (*(index++)) = c; - (*(index++)) = d; - - a += (columns + 1) * 2; - b += (columns + 1) * 2; - c += (columns + 1) * 2; - d += (columns + 1) * 2; - - (*(index++)) = a; - (*(index++)) = b; - (*(index++)) = c; - (*(index++)) = c; - (*(index++)) = b; - (*(index++)) = d; - } - - for (int i = 0; i < rows; ++i) - { - std::size_t a = (columns + 1) * 4 + i * 2; - std::size_t b = (columns + 1) * 4 + i * 2 + 1; - std::size_t c = (columns + 1) * 4 + (i + 1) * 2; - std::size_t d = (columns + 1) * 4 + (i + 1) * 2 + 1; - - (*(index++)) = a; - (*(index++)) = b; - (*(index++)) = c; - (*(index++)) = c; - (*(index++)) = b; - (*(index++)) = d; - - a += (rows + 1) * 2; - b += (rows + 1) * 2; - c += (rows + 1) * 2; - d += (rows + 1) * 2; - - (*(index++)) = b; - (*(index++)) = a; - (*(index++)) = c; - (*(index++)) = b; - (*(index++)) = c; - (*(index++)) = d; - } - - // Floor - std::size_t a = 1; - std::size_t b = 1 + (rows + 1) * 2; - std::size_t c = columns * 2 + 1; - std::size_t d = columns * 2 + 1 + (columns + 1) * 2; - (*(index++)) = a; - (*(index++)) = c; - (*(index++)) = b; - (*(index++)) = b; - (*(index++)) = c; - (*(index++)) = d; - - // Generate index data - for (std::size_t i = 0; i < subsurfaceIndexCount; ++i) - { - subsurfaceIndexData[i] = subsurfaceIndices[i]; - } - - // Generate navmesh - subsurfaceNavmesh.create(subsurfaceVertices, subsurfaceIndices); - - // Create and load VAO, VBO, and IBO - glGenVertexArrays(1, &subsurfaceVAO); - glBindVertexArray(subsurfaceVAO); - glGenBuffers(1, &subsurfaceVBO); - glBindBuffer(GL_ARRAY_BUFFER, subsurfaceVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(float) * subsurfaceVertexSize * subsurfaceVertexCount, subsurfaceVertexData, GL_STATIC_DRAW); - glEnableVertexAttribArray(EMERGENT_VERTEX_POSITION); - glVertexAttribPointer(EMERGENT_VERTEX_POSITION, 3, GL_FLOAT, GL_FALSE, subsurfaceVertexSize * sizeof(float), (char*)0 + 0 * sizeof(float)); - glEnableVertexAttribArray(EMERGENT_VERTEX_NORMAL); - glVertexAttribPointer(EMERGENT_VERTEX_NORMAL, 3, GL_FLOAT, GL_FALSE, subsurfaceVertexSize * sizeof(float), (char*)0 + 3 * sizeof(float)); - glEnableVertexAttribArray(EMERGENT_VERTEX_TEXCOORD); - glVertexAttribPointer(EMERGENT_VERTEX_TEXCOORD, 2, GL_FLOAT, GL_FALSE, subsurfaceVertexSize * sizeof(float), (char*)0 + 6 * sizeof(float)); - glGenBuffers(1, &subsurfaceIBO); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, subsurfaceIBO); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(std::uint32_t) * subsurfaceIndexCount, subsurfaceIndexData, GL_STATIC_DRAW); - - // Setup material - //subsurfaceMaterial.flags = static_cast(PhysicalMaterial::Flags::SOIL); - - // Setup buffers - subsurfaceModel.setVAO(subsurfaceVAO); - subsurfaceModel.setVBO(subsurfaceVBO); - subsurfaceModel.setIBO(subsurfaceIBO); - - // Create model group - Model::Group* group = new Model::Group(); - group->name = "default"; - group->material = nullptr;//&subsurfaceMaterial; - group->indexOffset = 0; - group->triangleCount = subsurfaceTriangleCount; - - // Add group to the model - subsurfaceModel.addGroup(group); - - // Set model bounds - subsurfaceModel.setBounds(subsurfaceNavmesh.getBounds()); -} - -void Terrain::calculateSurfaceNormals() -{ - for (std::size_t i = 0; i < surfaceVertexCount; ++i) - { - const Navmesh::Vertex* vertex = (*surfaceNavmesh.getVertices())[i]; - - Vector3 normal(0.0f); - const Navmesh::Edge* start = vertex->edge; - const Navmesh::Edge* e = start; - do - { - normal += e->triangle->normal; - e = e->previous->symmetric; - } - while (e != start && e != nullptr); - normal = glm::normalize(normal); - - float* data = &surfaceVertexData[i * surfaceVertexSize]; - data[3] = normal.x; - data[4] = normal.y; - data[5] = normal.z; - } -} - -bool Terrain::load(const std::string& filename) -{ - int width; - int height; - int channels; - - stbi_set_flip_vertically_on_load(true); - - // Load image data - unsigned char* pixels = stbi_load(filename.c_str(), &width, &height, &channels, 1); - - if (width != columns + 1 || height != rows + 1) - { - // Free loaded image data - stbi_image_free(pixels); - return false; - } - - // Set surface vertex heights - for (int i = 0; i <= rows; ++i) - { - for (int j = 0; j <= columns; ++j) - { - std::size_t index = i * (columns + 1) + j; - - float elevation = (float)pixels[index] / 255.0f * 5.0f; - - surfaceVertexData[index * surfaceVertexSize + 1] = elevation; - surfaceVertices[index].y = elevation; - (*surfaceNavmesh.getVertices())[index]->position.y = elevation; - } - } - - // Free loaded image data - stbi_image_free(pixels); - - // Set subsurface vertex heights - std::size_t subsurfaceIndex = 0; - - // Top row - for (int j = 0; j <= columns; ++j) - { - int i = 0; - std::size_t surfaceIndex = i * (columns + 1) + j; - float elevation = surfaceVertices[surfaceIndex].y; - - subsurfaceVertexData[subsurfaceIndex * subsurfaceVertexSize + 1] = elevation; - subsurfaceVertices[subsurfaceIndex].y = elevation; - (*subsurfaceNavmesh.getVertices())[subsurfaceIndex]->position.y = elevation; - subsurfaceIndex += 2; - } - // Bottom row - for (int j = 0; j <= columns; ++j) - { - int i = rows; - std::size_t surfaceIndex = i * (columns + 1) + j; - float elevation = surfaceVertices[surfaceIndex].y; - - subsurfaceVertexData[subsurfaceIndex * subsurfaceVertexSize + 1] = elevation; - subsurfaceVertices[subsurfaceIndex].y = elevation; - (*subsurfaceNavmesh.getVertices())[subsurfaceIndex]->position.y = elevation; - subsurfaceIndex += 2; - } - // Left column - for (int i = 0; i <= rows; ++i) - { - int j = 0; - std::size_t surfaceIndex = i * (columns + 1) + j; - float elevation = surfaceVertices[surfaceIndex].y; - - subsurfaceVertexData[subsurfaceIndex * subsurfaceVertexSize + 1] = elevation; - subsurfaceVertices[subsurfaceIndex].y = elevation; - (*subsurfaceNavmesh.getVertices())[subsurfaceIndex]->position.y = elevation; - subsurfaceIndex += 2; - } - // Right column - for (int i = 0; i <= rows; ++i) - { - int j = columns; - std::size_t surfaceIndex = i * (columns + 1) + j; - float elevation = surfaceVertices[surfaceIndex].y; - - subsurfaceVertexData[subsurfaceIndex * subsurfaceVertexSize + 1] = elevation; - subsurfaceVertices[subsurfaceIndex].y = elevation; - (*subsurfaceNavmesh.getVertices())[subsurfaceIndex]->position.y = elevation; - subsurfaceIndex += 2; - } - - // Calculate navmesh normals - surfaceNavmesh.calculateNormals(); - subsurfaceNavmesh.calculateNormals(); - - // Calculate navmesh bounds - surfaceNavmesh.calculateBounds(); - subsurfaceNavmesh.calculateBounds(); - - // Calculate vertex normals - calculateSurfaceNormals(); - - // Update VBOs - glBindBuffer(GL_ARRAY_BUFFER, surfaceVBO); - glBufferSubData(GL_ARRAY_BUFFER, 0, surfaceVertexCount * surfaceVertexSize * sizeof(float), surfaceVertexData); - glBindBuffer(GL_ARRAY_BUFFER, subsurfaceVBO); - glBufferSubData(GL_ARRAY_BUFFER, 0, subsurfaceVertexCount * subsurfaceVertexSize * sizeof(float), subsurfaceVertexData); - - // Update bounds - surfaceModel.setBounds(surfaceNavmesh.getBounds()); - subsurfaceModel.setBounds(subsurfaceNavmesh.getBounds()); - - // Calculate octree - delete surfaceOctree; - surfaceOctree = surfaceNavmesh.createOctree(5); - - return true; -} - -struct voxel -{ - glm::vec3 vertices[8]; - float values[8]; -}; - -// LUT to map isosurface vertices to intersecting edges -static const int EDGE_TABLE[256] = -{ - 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, - 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, - 0x190, 0x099, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, - 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, - 0x230, 0x339, 0x033, 0x13a, 0x636, 0x73f, 0x435, 0x53c, - 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, - 0x3a0, 0x2a9, 0x1a3, 0x0aa, 0x7a6, 0x6af, 0x5a5, 0x4ac, - 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, - 0x460, 0x569, 0x663, 0x76a, 0x066, 0x16f, 0x265, 0x36c, - 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, - 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0x0ff, 0x3f5, 0x2fc, - 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, - 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x055, 0x15c, - 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, - 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0x0cc, - 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, - 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, - 0x0cc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, - 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, - 0x15c, 0x055, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, - 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, - 0x2fc, 0x3f5, 0x0ff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, - 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, - 0x36c, 0x265, 0x16f, 0x066, 0x76a, 0x663, 0x569, 0x460, - 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, - 0x4ac, 0x5a5, 0x6af, 0x7a6, 0x0aa, 0x1a3, 0x2a9, 0x3a0, - 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, - 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x033, 0x339, 0x230, - 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, - 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x099, 0x190, - 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, - 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000 -}; - -static const int TRIANGLE_TABLE[256][16] = -{ - {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1}, - {3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1}, - {3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1}, - {3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1}, - {9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1}, - {9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, - {2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1}, - {8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1}, - {9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, - {4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1}, - {3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1}, - {1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1}, - {4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1}, - {4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, - {5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1}, - {2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1}, - {9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1}, - {0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1}, - {2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1}, - {10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1}, - {4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1}, - {5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1}, - {5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1}, - {9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1}, - {0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1}, - {1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1}, - {10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1}, - {8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1}, - {2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1}, - {7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1}, - {2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1}, - {11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1}, - {5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1}, - {11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1}, - {11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, - {1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1}, - {9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1}, - {5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1}, - {2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1}, - {5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1}, - {6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1}, - {3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1}, - {6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1}, - {5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1}, - {1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1}, - {10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1}, - {6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1}, - {8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1}, - {7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1}, - {3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1}, - {5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1}, - {0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1}, - {9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1}, - {8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1}, - {5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1}, - {0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1}, - {6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1}, - {10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1}, - {10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1}, - {8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1}, - {1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1}, - {0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1}, - {10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1}, - {3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1}, - {6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1}, - {9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1}, - {8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1}, - {3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1}, - {6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1}, - {0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1}, - {10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1}, - {10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1}, - {2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1}, - {7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1}, - {7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1}, - {2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1}, - {1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1}, - {11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1}, - {8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1}, - {0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1}, - {7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, - {10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, - {2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1}, - {6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1}, - {7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1}, - {2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1}, - {1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1}, - {10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1}, - {10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1}, - {0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1}, - {7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1}, - {6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1}, - {8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1}, - {9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1}, - {6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1}, - {4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1}, - {10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1}, - {8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1}, - {0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1}, - {1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1}, - {8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1}, - {10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1}, - {4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1}, - {10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1}, - {5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, - {11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1}, - {9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1}, - {6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1}, - {7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1}, - {3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1}, - {7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1}, - {9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1}, - {3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1}, - {6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1}, - {9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1}, - {1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1}, - {4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1}, - {7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1}, - {6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1}, - {3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1}, - {0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1}, - {6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1}, - {0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1}, - {11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1}, - {6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1}, - {5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1}, - {9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1}, - {1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1}, - {1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1}, - {10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1}, - {0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1}, - {5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1}, - {10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1}, - {11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1}, - {9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1}, - {7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1}, - {2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1}, - {8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1}, - {9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1}, - {9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1}, - {1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1}, - {9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1}, - {9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1}, - {5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1}, - {0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1}, - {10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1}, - {2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1}, - {0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1}, - {0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1}, - {9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1}, - {5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1}, - {3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1}, - {5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1}, - {8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1}, - {0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1}, - {9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1}, - {0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1}, - {1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1}, - {3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1}, - {4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1}, - {9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1}, - {11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1}, - {11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1}, - {2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1}, - {9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1}, - {3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1}, - {1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1}, - {4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1}, - {4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1}, - {0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1}, - {3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1}, - {3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1}, - {0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1}, - {9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1}, - {1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}, - {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1} -}; - -// Lookup table which contains the indices of the vertices which define an edge. -static const int VERTEX_TABLE[12][2] = -{ - {0, 1}, - {1, 2}, - {2, 3}, - {3, 0}, - - {4, 5}, - {5, 6}, - {6, 7}, - {7, 4}, - - {0, 4}, - {1, 5}, - {2, 6}, - {3, 7} -}; - -/* - * The marching cubes algorithm can produce a maximum of 5 triangles per cell. Therefore the maximum triangle count of a grid is `w * h * d * 5`. - */ - -struct triangle -{ - glm::vec3 vertices[3]; -}; - -bool less_than(const glm::vec3& a, const glm::vec3& b) -{ - if (a.x < b.x) - return true; - else if (a.x > b.x) - return false; - if (a.y < b.y) - return true; - else if (a.y > b.y) - return false; - if (a.z < b.z) - return true; - return false; -} - -glm::vec3 interpolate(float isolevel, glm::vec3 p0, glm::vec3 p1, float v0, float v1) -{ - static const float epsilon = 0.00001f; - - if (less_than(p1, p0)) - { - glm::vec3 ptemp = p0; - p0 = p1; - p1 = ptemp; - - float vtemp = v0; - v0 = v1; - v1 = vtemp; - } - - if (std::fabs(v0 - v1) > epsilon) - { - return p0 + ((p1 - p0) / (v1 - v0) * (isolevel - v0)); - } - - return p0; -} - -int polygonize(const voxel& vox, float isolevel, triangle* triangles) -{ - // Set bitflags for each of the cube's 8 vertices, indicating whether or not they are inside the isosurface. - int edge_index = 0; - for (int i = 0; i < 8; ++i) - { - if (vox.values[i] < isolevel) - edge_index |= (1 << i); - } - - // Get edge flags from lookup table - int edge_flags = EDGE_TABLE[edge_index]; - - if (edge_flags == 0) - { - // No intersections, cube is completely in or out of the isosurface. - return 0; - } - - // Calculate vertex positions - glm::vec3 vertices[12]; - - // For each edge - for (int i = 0; i < 12; ++i) - { - // If this edge is intersected - if (edge_flags & (1 << i)) - { - int a = VERTEX_TABLE[i][0]; - int b = VERTEX_TABLE[i][1]; - vertices[i] = interpolate(isolevel, vox.vertices[a], vox.vertices[b], vox.values[a], vox.values[b]); - } - } - - // Form triangles - int triangle_count = 0; - for (int i = 0; TRIANGLE_TABLE[edge_index][i] != -1; i += 3) - { - int a = TRIANGLE_TABLE[edge_index][i]; - int b = TRIANGLE_TABLE[edge_index][i + 1]; - int c = TRIANGLE_TABLE[edge_index][i + 2]; - triangles[triangle_count].vertices[0] = vertices[a]; - triangles[triangle_count].vertices[1] = vertices[b]; - triangles[triangle_count].vertices[2] = vertices[c]; - ++triangle_count; - } - - return triangle_count; -} - - diff --git a/src/game/terrain.hpp b/src/game/terrain.hpp deleted file mode 100644 index 52b3a18..0000000 --- a/src/game/terrain.hpp +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef TERRAIN_HPP -#define TERRAIN_HPP - -#include "navmesh.hpp" -#include - -using namespace Emergent; - -class Terrain -{ -public: - Terrain(); - ~Terrain(); - - /** - * Creates a flat terrain surface. - * - * @param columns Specifies the width of the terrain, in cells. - * @param rows Specifies the depth of the terrain, in cells. - * @param dimensions Specifies the dimensions of the terrain. - */ - void create(int columns, int rows, const Vector3& dimensions); - - /// Loads a heightmap - bool load(const std::string& filename); - - /// Returns the navmesh representing the terrain surface. - const Navmesh* getSurfaceNavmesh() const; - - /// Returns the navmesh representing the terrain surface. - Navmesh* getSurfaceNavmesh(); - - /// Returns the navmesh representing the terrain subsurface. - const Navmesh* getSubsurfaceNavmesh() const; - - /// Returns the navmesh representing the terrain subsurface. - Navmesh* getSubsurfaceNavmesh(); - - /// Returns the model representing the terrain surface. - const Model* getSurfaceModel() const; - - /// Returns the model representing the terrain surface. - Model* getSurfaceModel(); - - /// Returns the model representing the terrain subsurface. - const Model* getSubsurfaceModel() const; - - /// Returns the model representing the terrain subsurface. - Model* getSubsurfaceModel(); - - const Octree* getSurfaceOctree() const; - -private: - void createSurface(); - void createSubsurface(); - - void calculateSurfaceNormals(); - - int columns; - int rows; - Vector3 dimensions; - - // Surface - std::size_t surfaceVertexSize; - std::size_t surfaceVertexCount; - std::size_t surfaceTriangleCount; - std::size_t surfaceIndexCount; - float* surfaceVertexData; - std::uint32_t* surfaceIndexData; - std::vector surfaceVertices; - std::vector surfaceIndices; - GLuint surfaceVAO; - GLuint surfaceVBO; - GLuint surfaceIBO; - //PhysicalMaterial surfaceMaterial; - Model surfaceModel; - Navmesh surfaceNavmesh; - Octree* surfaceOctree; - - // Subsurface - std::size_t subsurfaceVertexSize; - std::size_t subsurfaceVertexCount; - std::size_t subsurfaceTriangleCount; - std::size_t subsurfaceIndexCount; - float* subsurfaceVertexData; - std::uint32_t* subsurfaceIndexData; - std::vector subsurfaceVertices; - std::vector subsurfaceIndices; - GLuint subsurfaceVAO; - GLuint subsurfaceVBO; - GLuint subsurfaceIBO; - //PhysicalMaterial subsurfaceMaterial; - Model subsurfaceModel; - Navmesh subsurfaceNavmesh; -}; - -inline Navmesh* Terrain::getSurfaceNavmesh() -{ - return &surfaceNavmesh; -}; - -inline const Navmesh* Terrain::getSurfaceNavmesh() const -{ - return &surfaceNavmesh; -}; - -inline Navmesh* Terrain::getSubsurfaceNavmesh() -{ - return &subsurfaceNavmesh; -}; - -inline const Navmesh* Terrain::getSubsurfaceNavmesh() const -{ - return &subsurfaceNavmesh; -}; - -inline const Model* Terrain::getSurfaceModel() const -{ - return &surfaceModel; -} - -inline Model* Terrain::getSurfaceModel() -{ - return &surfaceModel; -} - -inline const Model* Terrain::getSubsurfaceModel() const -{ - return &subsurfaceModel; -} - -inline Model* Terrain::getSubsurfaceModel() -{ - return &subsurfaceModel; -} - -inline const Octree* Terrain::getSurfaceOctree() const -{ - return surfaceOctree; -} - -#endif // TERRAIN_HPP diff --git a/src/game/tool.cpp b/src/game/tool.cpp old mode 100644 new mode 100755 index 0b3e29d..f1c5d41 --- a/src/game/tool.cpp +++ b/src/game/tool.cpp @@ -1,12 +1,23 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + #include "tool.hpp" -#include "ant.hpp" -#include "colony.hpp" -#include "navmesh.hpp" -#include "pheromone-matrix.hpp" -#include "../camera-rig.hpp" -#include "../configuration.hpp" -#include -#include Tool::Tool(): active(false), @@ -14,6 +25,7 @@ Tool::Tool(): orbitCam(nullptr) { modelInstance.setActive(active); + modelInstance.setCullingEnabled(false); } Tool::~Tool() @@ -24,7 +36,7 @@ void Tool::setActive(bool active) this->active = active; if (!active) { - modelInstance.setActive(active); + modelInstance.setActive(false); } } @@ -33,643 +45,3 @@ void Tool::setOrbitCam(const OrbitCam* orbitCam) this->orbitCam = orbitCam; } -Forceps::Forceps(const Model* model) -{ - // Allocate pose and initialize to bind pose - pose = new Pose(model->getSkeleton()); - pose->reset(); - - // Setup model instance - modelInstance.setModel(model); - modelInstance.setPose(pose); - - // Find pinch animation - pinchAnimation = model->getSkeleton()->getAnimation("pinch"); - if (!pinchAnimation) - { - std::cerr << "Forceps pinch animation not found" << std::endl; - } - - // Find release animation - releaseAnimation = model->getSkeleton()->getAnimation("release"); - if (!releaseAnimation) - { - std::cerr << "Forceps release animation not found" << std::endl; - } - - hoverDistance = 1.0f; - - // Setup timing - float descentDuration = 0.125f; - float ascentDuration = 0.125f; - float descentFrameCount = descentDuration / (1.0f / 60.0f); - animationTimeStep = pinchAnimation->getEndTime() / descentFrameCount; - - // Allocate tweener and and setup tweens - tweener = new Tweener(); - descentTween = new Tween(EaseFunction::OUT_CUBIC, 0.0f, descentDuration, hoverDistance, -hoverDistance); - ascentTween = new Tween(EaseFunction::IN_CUBIC, 0.0f, ascentDuration, 0.0f, hoverDistance); - descentTween->setEndCallback(std::bind(&TweenBase::start, ascentTween)); - tweener->addTween(descentTween); - tweener->addTween(ascentTween); - - // Setup initial state - state = Forceps::State::RELEASED; - animationTime = 0.0f; - colony = nullptr; - targetedAnt = nullptr; - suspendedAnt = nullptr; - pick = Vector3(0.0f); - - // Open forceps - pinchAnimation->animate(pose, 0.0f); - pose->concatenate(); -} - -Forceps::~Forceps() -{ - delete pose; - delete descentTween; - delete ascentTween; - delete tweener; -} - -void Forceps::update(float dt) -{ - modelInstance.setActive(active); - - // Update tweener - tweener->update(dt); - - // Determine distance from pick point - float forcepsDistance = hoverDistance; - if (!ascentTween->isStopped()) - { - forcepsDistance = ascentTween->getTweenValue(); - } - else if (!descentTween->isStopped()) - { - forcepsDistance = descentTween->getTweenValue(); - } - - Quaternion alignment = glm::angleAxis(orbitCam->getAzimuth(), Vector3(0, 1, 0)); - Quaternion tilt = glm::angleAxis(glm::radians(15.0f), Vector3(0, 0, -1)); - Quaternion rotation = glm::normalize(alignment * tilt); - Vector3 translation = pick + rotation * Vector3(0, forcepsDistance, 0); - - // Set tool position - modelInstance.setTranslation(translation); - modelInstance.setRotation(rotation); - - if (state == Forceps::State::RELEASED) - { - - } - else if (state == Forceps::State::RELEASING) - { - // Perform release animation - releaseAnimation->animate(pose, animationTime); - pose->concatenate(); - - // If release animation is finished - if (animationTime >= releaseAnimation->getEndTime()) - { - // Changed to released state - state = Forceps::State::RELEASED; - } - } - else if (state == Forceps::State::PINCHED) - { - if (!ascentTween->isStopped()) - { - // Calculate interpolation factor - float interpolationFactor = (ascentTween->getTweenValue() - ascentTween->getStartValue()) / ascentTween->getDeltaValue(); - - // Form tilt quaternion - //Quaternion tilt = glm::angleAxis(glm::radians(15.0f), Vector3(0, 0, -1)); - tilt = glm::angleAxis(glm::radians(15.0f), Vector3(0, 0, -1)); - - // Project camera forward onto XZ plane - Vector3 cameraForwardXZ = orbitCam->getCamera()->getForward(); - cameraForwardXZ.y = 0.0f; - cameraForwardXZ = glm::normalize(cameraForwardXZ); - - // Form alignment quaternion - //Quaternion alignment = glm::rotation(Vector3(0, 0, -1), cameraForwardXZ); - alignment = glm::angleAxis(orbitCam->getAzimuth(), Vector3(0, 1, 0)); - - // Calculate target rotation at the top of the ascentTween - rotationTop = glm::normalize(alignment * tilt); - - // Interpolate between bottom and top rotations - Quaternion interpolatedRotation = glm::normalize(glm::slerp(rotationBottom, rotationTop, interpolationFactor)); - - // Set target translation at the top of the ascent - translationTop = pick + rotationTop * Vector3(0, hoverDistance, 0); - - // Interpolate between bottom and top translations - Vector3 interpolatedTranslation = glm::lerp(translationBottom, translationTop, interpolationFactor); - - // Update model instance transform - modelInstance.setTranslation(interpolatedTranslation); - modelInstance.setRotation(interpolatedRotation); - } - - if (suspendedAnt != nullptr) - { - // Project forceps forward vector onto XZ plane - Vector3 forward = glm::normalize(modelInstance.getRotation() * Vector3(0, 0, -1)); - forward.y = 0.0f; - forward = glm::normalize(forward); - - // Calculate suspension quaternion - Quaternion suspensionRotation = glm::normalize(glm::rotation(Vector3(0, 0, -1), ((flipRotation) ? -forward : forward))); - - // Suspend ant - suspendedAnt->suspend(modelInstance.getTranslation(), suspensionRotation); - } - } - else if (state == Forceps::State::PINCHING) - { - // Perform pinch animation - pinchAnimation->animate(pose, animationTime); - pose->concatenate(); - - // Rotate to align forceps with ant - if (targetedAnt != nullptr) - { - // Calculate interpolation factor - float interpolationFactor = (descentTween->getTweenValue() - descentTween->getStartValue()) / descentTween->getDeltaValue(); - - // Set target translation at the bottom of the descent - translationBottom = targetedAnt->getPosition(); - - // Interpolate between top and bottom translations - Vector3 interpolatedTranslation = glm::lerp(translationTop, translationBottom, interpolationFactor); - - // Project camera forward onto XZ plane - Vector3 cameraForwardXZ = orbitCam->getCamera()->getForward(); - cameraForwardXZ.y = 0.0f; - cameraForwardXZ = glm::normalize(cameraForwardXZ); - - // Form tilt quaternion - tilt = glm::angleAxis(glm::radians(15.0f), -cameraForwardXZ); - - // Project ant forward onto XZ plane - Vector3 antForwardXZ = targetedAnt->getForward(); - antForwardXZ.y = 0.0f; - antForwardXZ = glm::normalize(antForwardXZ); - - // Form alignment quaternion - alignment = glm::rotation(Vector3(0, 0, -1), (flipRotation) ? antForwardXZ : -antForwardXZ); - - // Calculate target rotation at the bottom of the descent - rotationBottom = glm::normalize(tilt * alignment); - - // Interpolate between top and bottom rotations - Quaternion interpolatedRotation = glm::normalize(glm::slerp(rotationTop, rotationBottom, interpolationFactor)); - - // Update model instance transform - modelInstance.setTranslation(interpolatedTranslation); - modelInstance.setRotation(interpolatedRotation); - } - - // If pinch animation is finished - if (animationTime >= pinchAnimation->getEndTime() && descentTween->isStopped()) - { - // If an ant was targeted - if (targetedAnt != nullptr) - { - // Suspend targeted ant - suspendedAnt = targetedAnt; - suspendedAnt->setState(Ant::State::SUSPENDED); - //suspendedAnt->suspend(modelInstance.getTranslation()); - targetedAnt = nullptr; - } - - // Change to pinched state - state = Forceps::State::PINCHED; - } - } - - // Increment animation time - animationTime += animationTimeStep; -} - -void Forceps::setColony(Colony* colony) -{ - this->colony = colony; -} - -void Forceps::setNavmesh(Navmesh* navmesh) -{ - this->navmesh = navmesh; -} - -void Forceps::pinch() -{ - // Change state to pinching - state = Forceps::State::PINCHING; - animationTime = 0.0f; - - if (colony != nullptr) - { - // Target nearest ant in pinching radius - Sphere pinchingBounds = Sphere(pick, 0.35f); - - // Build a list of ants which intersect the pinching bounds - std::list ants; - colony->queryAnts(pinchingBounds, &ants); - - // Target ant closest to the center of the pinching bounds - float closestDistance = std::numeric_limits::infinity(); - for (Agent* agent: ants) - { - Ant* ant = static_cast(agent); - - Vector3 difference = ant->getPosition() - pinchingBounds.getCenter(); - float distanceSquared = glm::dot(difference, difference); - if (distanceSquared < closestDistance) - { - closestDistance = distanceSquared; - targetedAnt = ant; - } - } - - if (targetedAnt != nullptr) - { - // Start descent tweener - descentTween->start(); - - // Save translation & rotation - translationTop = modelInstance.getTranslation(); - rotationTop = modelInstance.getRotation(); - - // Project ant forward onto XZ plane - Vector3 antForwardXZ = targetedAnt->getForward(); - antForwardXZ.y = 0.0f; - antForwardXZ = glm::normalize(antForwardXZ); - - // Project camera forward onto XZ plane - Vector3 cameraForwardXZ = orbitCam->getCamera()->getForward(); - cameraForwardXZ.y = 0.0f; - cameraForwardXZ = glm::normalize(cameraForwardXZ); - - // Find angle between ant and camera on XZ plane - float angle = std::acos(glm::dot(cameraForwardXZ, antForwardXZ)); - - // Determine direction to rotate - flipRotation = (angle > glm::radians(90.0f)); - } - } -} - -void Forceps::release() -{ - // Change state to releasing - state = Forceps::State::RELEASING; - animationTime = 0.0f; - targetedAnt = nullptr; - - if (suspendedAnt != nullptr) - { - Ray pickingRay; - pickingRay.origin = pick + Vector3(0, 1, 0); - pickingRay.direction = Vector3(0, -1, 0); - - const std::vector* navmeshTriangles = navmesh->getTriangles(); - for (Navmesh::Triangle* triangle: *navmeshTriangles) - { - auto result = intersects(pickingRay, triangle); - if (std::get<0>(result)) - { - Vector3 barycentricPosition = Vector3(std::get<2>(result), std::get<3>(result), 1.0f - std::get<2>(result) - std::get<3>(result)); - suspendedAnt->setPosition(triangle, barycentricPosition); - - break; - } - } - - // Release suspended ant - suspendedAnt->setState(Ant::State::WANDER); - suspendedAnt = nullptr; - } - - // Reset tweens - descentTween->reset(); - descentTween->stop(); - ascentTween->reset(); - ascentTween->stop(); -} - -Lens::Lens(const Model* model) -{ - // Setup model instance - modelInstance.setModel(model); - - // Setup spotlight - spotlight.setColor(Vector3(1.0f)); - spotlight.setIntensity(10000.0f); - spotlight.setAttenuation(Vector3(1, 0, 1)); - spotlight.setCutoff(glm::radians(45.0f)); - spotlight.setExponent(1000.0f); - spotlight.setActive(false); - - unfocusedDistance = 18.0f; - focusedDistance = 12.0f; - focused = false; - sunDirection = Vector3(0, -1, 0); - - // Setup timing - float descentDuration = 0.75f; - float ascentDuration = 0.25f; - - // Allocate tweener and and setup tweens - tweener = new Tweener(); - descentTween = new Tween(EaseFunction::OUT_CUBIC, 0.0f, descentDuration, unfocusedDistance, focusedDistance - unfocusedDistance); - ascentTween = new Tween(EaseFunction::OUT_CUBIC, 0.0f, ascentDuration, focusedDistance, unfocusedDistance - focusedDistance); - descentTween->setEndCallback - ( - [this](float t) - { - focused = true; - } - ); - tweener->addTween(descentTween); - tweener->addTween(ascentTween); -} - -Lens::~Lens() -{ - delete descentTween; - delete ascentTween; - delete tweener; -} - -void Lens::update(float dt) -{ - /* - // Rotate to face camera - hoverDistance = 30.0f; - Vector3 direction = glm::normalize(cameraController->getCamera()->getTranslation() - pick); - //direction = cameraController->getCamera()->getForward(); - float distance = glm::distance(pick, cameraController->getCamera()->getTranslation()); - - Quaternion alignment = glm::angleAxis(cameraController->getAzimuth() + glm::radians(90.0f), Vector3(0, 1, 0)); - Quaternion tilt = glm::rotation(Vector3(0, 1, 0), -direction); - Quaternion rotation = glm::normalize(tilt * alignment); - - Vector3 translation = pick + rotation * Vector3(0, -distance + hoverDistance, 0); - - modelInstance.setTranslation(translation); - modelInstance.setRotation(rotation); - */ - modelInstance.setActive(active); - spotlight.setActive(active); - - // Update tweener - tweener->update(dt); - - float lensDistance = (focused) ? focusedDistance : unfocusedDistance; - if (!ascentTween->isStopped()) - { - lensDistance = ascentTween->getTweenValue(); - } - else if (!descentTween->isStopped()) - { - lensDistance = descentTween->getTweenValue(); - } - - //Quaternion alignment = glm::angleAxis(cameraController->getAzimuth() + glm::radians(90.0f), Vector3(0, 1, 0)); - Quaternion alignment = glm::rotation(Vector3(0, 1, 0), -sunDirection) * glm::angleAxis(glm::radians(90.0f), Vector3(0, 1, 0)); - Quaternion rotation = glm::normalize(alignment); - Vector3 translation = pick + sunDirection * -lensDistance; - - modelInstance.setTranslation(translation); - modelInstance.setRotation(rotation); - - float spotlightDistanceFactor = (1.0 - (lensDistance - focusedDistance) / (unfocusedDistance - focusedDistance)) * 2.0f - 1.0f; - - spotlight.setTranslation(pick + sunDirection * (-lensDistance + 5.0f * spotlightDistanceFactor)); - spotlight.setDirection(sunDirection); -} - -void Lens::setActive(bool active) -{ - this->active = active; - if (!active) - { - modelInstance.setActive(active); - spotlight.setActive(active); - } -} - -void Lens::focus() -{ - ascentTween->stop(); - descentTween->reset(); - descentTween->start(); -} - -void Lens::unfocus() -{ - descentTween->stop(); - focused = false; - ascentTween->reset(); - ascentTween->start(); -} - -void Lens::setSunDirection(const Vector3& direction) -{ - sunDirection = direction; -} - -Brush::Brush(const Model* model) -{ - // Allocate pose and initialize to bind pose - pose = new Pose(model->getSkeleton()); - pose->reset(); - pose->concatenate(); - - // Setup model instance - modelInstance.setModel(model); - modelInstance.setPose(pose); - - hoverDistance = 0.5f; - - // Setup timing - float descentDuration = 0.05f; - float ascentDuration = 0.1f; - - // Allocate tweener and and setup tweens - tweener = new Tweener(); - descentTween = new Tween(EaseFunction::OUT_CUBIC, 0.0f, descentDuration, hoverDistance, -hoverDistance); - ascentTween = new Tween(EaseFunction::OUT_CUBIC, 0.0f, ascentDuration, 0.0f, hoverDistance); - descentTween->setEndCallback - ( - [this](float t) - { - descended = true; - paint(Vector2(pick.x, pick.z), BRUSH_RADIUS); - } - ); - tweener->addTween(descentTween); - tweener->addTween(ascentTween); - descended = false; - - oldPick = pick; - tiltAngle = 0.0f; - targetTiltAngle = 0.0f; - tiltAxis = Vector3(1.0f, 0.0f, 0.0f); - targetTiltAxis = tiltAxis; - - colony = nullptr; -} - -Brush::~Brush() -{ - delete pose; - delete descentTween; - delete ascentTween; - delete tweener; -} - -void Brush::update(float dt) -{ - modelInstance.setActive(active); - - // Update tweener - tweener->update(dt); - - float brushDistance = (descended) ? 0.0f : hoverDistance; - if (!ascentTween->isStopped()) - { - brushDistance = ascentTween->getTweenValue(); - } - else if (!descentTween->isStopped()) - { - brushDistance = descentTween->getTweenValue(); - } - - targetTiltAngle = 0.0f; - if (descended) - { - Vector3 difference = pick - oldPick; - float distanceSquared = glm::dot(difference, difference); - - // Calculate tilt - if (distanceSquared > 0.005f) - { - float maxDistance = 0.25f; - float maxTiltAngle = glm::radians(45.0f); - float distance = std::sqrt(distanceSquared); - float tiltFactor = std::min(maxDistance, distance) / maxDistance; - - targetTiltAngle = maxTiltAngle * tiltFactor; - targetTiltAxis = glm::normalize(Vector3(difference.z, 0.0f, -difference.x)); - } - - // Paint pheromones - Vector2 difference2D = Vector2(pick.x, pick.z) - Vector2(oldPick.x, oldPick.z); - float distance2DSquared = glm::dot(difference2D, difference2D); - if (distance2DSquared != 0.0f) - { - float distance2D = sqrt(distance2DSquared); - Vector2 direction2D = difference2D / distance2D; - - if (distance2D <= BRUSH_RADIUS) - { - paint(Vector2(pick.x, pick.z), BRUSH_RADIUS); - } - else - { - float stepDistance = BRUSH_RADIUS * 0.5f; - int stepCount = static_cast(distance2D / stepDistance + 0.5f); - - for (int i = 0; i < stepCount; ++i) - { - Vector2 circleCenter = Vector2(oldPick.x, oldPick.z) + direction2D * (stepDistance * i); - - paint(circleCenter, BRUSH_RADIUS); - } - - paint(Vector2(pick.x, pick.z), BRUSH_RADIUS); - } - } - } - - float angleInterpolationFactor = 0.1f / (1.0 / 60.0f) * dt; - float axisInterpolationFactor = 0.2f / (1.0 / 60.0f) * dt; - tiltAngle = glm::mix(tiltAngle, targetTiltAngle, angleInterpolationFactor); - tiltAxis = glm::mix(tiltAxis, targetTiltAxis, axisInterpolationFactor); - - Quaternion tilt = glm::angleAxis(tiltAngle, tiltAxis); - - Quaternion alignment = glm::angleAxis(orbitCam->getAzimuth(), Vector3(0, 1, 0)); - Quaternion rotation = glm::normalize(tilt); - Vector3 translation = pick + Vector3(0, brushDistance, 0); - - modelInstance.setTranslation(translation); - modelInstance.setRotation(rotation); - - - if (descended) - { - Vector2 paintPosition = Vector2(pick.x, pick.z); - paint(paintPosition, BRUSH_RADIUS); - - - } - - oldPick = pick; -} - -void Brush::press() -{ - ascentTween->stop(); - descentTween->reset(); - descentTween->start(); -} - -void Brush::release() -{ - descentTween->stop(); - descended = false; - ascentTween->reset(); - ascentTween->start(); -} - -void Brush::setColony(Colony* colony) -{ - this->colony = colony; -} - -void Brush::paint(const Vector2& position, float radius) -{ - if (!colony) - { - return; - } - - PheromoneMatrix* pheromoneMatrix = colony->getRecruitmentMatrix(); - - float concentration = 1.0f; - float radiusSquared = radius * radius; - Vector2 cell; - Vector2 difference; - - for (cell.y = position.y - radius; cell.y <= position.y + radius; cell.y += pheromoneMatrix->getCellHeight()) - { - difference.y = cell.y - position.y; - - for (cell.x = position.x - radius; cell.x <= position.x + radius; cell.x += pheromoneMatrix->getCellWidth()) - { - difference.x = cell.x - position.x; - - if (glm::dot(difference, difference) <= radiusSquared) - { - pheromoneMatrix->deposit(cell, concentration); - } - } - } -} diff --git a/src/game/tool.hpp b/src/game/tool.hpp old mode 100644 new mode 100755 index 7f1f7b2..b1277f6 --- a/src/game/tool.hpp +++ b/src/game/tool.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -20,14 +20,9 @@ #ifndef TOOL_HPP #define TOOL_HPP -#include "../ui/tween.hpp" - #include using namespace Emergent; -class Ant; -class Colony; -class Navmesh; class OrbitCam; /** @@ -49,14 +44,14 @@ public: /** * Updates the tool. * - * @param dt Application timestep. + * @param dt Game timestep. */ virtual void update(float dt) = 0; /** * Activates or deactivates the tool. */ - virtual void setActive(bool active); + void setActive(bool active); /** * Sets the picking position. @@ -73,6 +68,8 @@ public: void setOrbitCam(const OrbitCam* orbitCam); bool isActive() const; + + const Vector3& getPick() const; /** * Returns the model instance. @@ -97,220 +94,19 @@ inline void Tool::setPick(const Vector3& pick) this->pick = pick; } -inline const ModelInstance* Tool::getModelInstance() const +inline const Vector3& Tool::getPick() const { - return &modelInstance; + return pick; } -inline ModelInstance* Tool::getModelInstance() +inline const ModelInstance* Tool::getModelInstance() const { return &modelInstance; } -/** - * The forceps tool can pick up ants and place them anywhere in the world. - */ -class Forceps: public Tool -{ -public: - enum class State - { - RELEASED, - RELEASING, - PINCHED, - PINCHING - }; - - /** - * Creates an instance of Forceps. - * - * @param model Forceps model - */ - Forceps(const Model* model); - - /** - * Destroys an instance of Forceps. - */ - ~Forceps(); - - /** - * Updates the forceps. - * - * @param dt Application timestep. - */ - virtual void update(float dt); - - /** - * Pinches the forceps. - */ - void pinch(); - - /** - * Releases the forceps. - */ - void release(); - - /** - * Associates a colony with this forceps. - * - * @param colony Colony with which to associate. - */ - void setColony(Colony* colony); - - void setNavmesh(Navmesh* navmesh); - - /** - * Returns the current state of the forceps. - */ - Forceps::State getState() const; - - /** - * Returns the suspended ant, if any. - */ - Ant* getSuspendedAnt() const; - -private: - Forceps::State state; - Pose* pose; - const Animation* pinchAnimation; - const Animation* releaseAnimation; - float animationTime; - float animationTimeStep; - float hoverDistance; - Tweener* tweener; - Tween* descentTween; - Tween* ascentTween; - Vector3 translationBottom; - Vector3 translationTop; - Quaternion rotationTop; - Quaternion rotationBottom; - bool flipRotation; - Colony* colony; - Ant* targetedAnt; - Ant* suspendedAnt; - Navmesh* navmesh; -}; - -inline Forceps::State Forceps::getState() const -{ - return state; -} - -inline Ant* Forceps::getSuspendedAnt() const -{ - return suspendedAnt; -} - -/** - * The lens tool can be used to burn ants. - * - * @see https://taylorpetrick.com/blog/post/dispersion-opengl - * @see https://taylorpetrick.com/portfolio/webgl/lense - */ -class Lens: public Tool -{ -public: - /** - * Creates an instance of Lens. - * - * @param model Lens model - */ - Lens(const Model* model); - - /** - * Destroys an instance of Lens. - */ - ~Lens(); - - /** - * Updates the lens. - * - * @param dt Application timestep. - */ - virtual void update(float dt); - - /** - * Activates or deactivates the lens. - */ - virtual void setActive(bool active); - - void focus(); - void unfocus(); - - /** - * Associates a colony with this lens. - * - * @param colony Colony with which to associate. - */ - void setColony(Colony* colony); - - void setSunDirection(const Vector3& direction); - - /** - * Returns the spotlight. - */ - const Spotlight* getSpotlight() const; - Spotlight* getSpotlight(); - -private: - Spotlight spotlight; - float unfocusedDistance; - float focusedDistance; - bool focused; - Vector3 sunDirection; - Tweener* tweener; - Tween* descentTween; - Tween* ascentTween; - Colony* colony; -}; - -inline const Spotlight* Lens::getSpotlight() const -{ - return &spotlight; -} - -inline Spotlight* Lens::getSpotlight() +inline ModelInstance* Tool::getModelInstance() { - return &spotlight; + return &modelInstance; } -/** - * The brush tool can paint pheromones on the terrain. - */ -class Brush: public Tool -{ -public: - Brush(const Model* model); - ~Brush(); - - virtual void update(float dt); - - void press(); - void release(); - - /** - * Associates a colony with this brush. - * - * @param colony Colony with which to associate. - */ - void setColony(Colony* colony); - -private: - void paint(const Vector2& position, float radius); - - Pose* pose; - float hoverDistance; - bool descended; - Vector3 oldPick; - - Tweener* tweener; - Tween* descentTween; - Tween* ascentTween; - float tiltAngle; - float targetTiltAngle; - Vector3 tiltAxis; - Vector3 targetTiltAxis; - Colony* colony; -}; - #endif // TOOL_HPP diff --git a/src/graphics/clear-render-pass.cpp b/src/graphics/clear-render-pass.cpp new file mode 100755 index 0000000..42c32ca --- /dev/null +++ b/src/graphics/clear-render-pass.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "clear-render-pass.hpp" + +ClearRenderPass::ClearRenderPass(): + clearColor(true), + clearDepth(true), + clearStencil(true), + color(0.0f), + depth(1.0f), + index(0) +{} + +bool ClearRenderPass::load(const RenderContext* renderContext) +{ + return true; +} + +void ClearRenderPass::unload() +{} + +void ClearRenderPass::render(RenderContext* renderContext) +{ + glBindFramebuffer(GL_FRAMEBUFFER, renderTarget->framebuffer); + + GLbitfield mask = 0; + if (clearColor) + { + mask |= GL_COLOR_BUFFER_BIT; + glClearColor(color[0], color[1], color[2], color[3]); + } + + if (clearDepth) + { + mask |= GL_DEPTH_BUFFER_BIT; + glClearDepth(depth); + } + + if (clearStencil) + { + mask |= GL_STENCIL_BUFFER_BIT; + glClearStencil(index); + } + + glClear(mask); +} + +void ClearRenderPass::setClear(bool color, bool depth, bool stencil) +{ + clearColor = color; + clearDepth = depth; + clearStencil = stencil; +} + +void ClearRenderPass::setClearColor(const Vector4& color) +{ + this->color = color; +} + +void ClearRenderPass::setClearDepth(float depth) +{ + this->depth = depth; +} + +void ClearRenderPass::setClearStencil(int index) +{ + this->index = index; +} + diff --git a/src/debug.hpp b/src/graphics/clear-render-pass.hpp old mode 100644 new mode 100755 similarity index 54% rename from src/debug.hpp rename to src/graphics/clear-render-pass.hpp index c0ce801..db1b200 --- a/src/debug.hpp +++ b/src/graphics/clear-render-pass.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -17,48 +17,35 @@ * along with Antkeeper Source Code. If not, see . */ -#ifndef DEBUG_HPP -#define DEBUG_HPP +#ifndef CLEAR_RENDER_PASS_HPP +#define CLEAR_RENDER_PASS_HPP #include - using namespace Emergent; -class LineBatcher +/** + * Clears framebuffers + */ +class ClearRenderPass: public RenderPass { public: - LineBatcher(std::size_t lineCount); - ~LineBatcher(); - - void setWidth(float width); - void setColor(const Vector4& color); - - void begin(); - void end(); - - void draw(const Vector3& start, const Vector3& end); + ClearRenderPass(); + virtual bool load(const RenderContext* renderContext); + virtual void unload(); + virtual void render(RenderContext* renderContext); - const BillboardBatch* getBatch() const; - BillboardBatch* getBatch(); + void setClear(bool color, bool depth, bool stencil); + void setClearColor(const Vector4& color); + void setClearDepth(float depth); + void setClearStencil(int index); private: - std::size_t lineCount; - std::size_t currentLine; - BillboardBatch batch; - BillboardBatch::Range* range; - float width; + bool clearColor; + bool clearDepth; + bool clearStencil; Vector4 color; - //PhysicalMaterial material; + float depth; + int index; }; -inline const BillboardBatch* LineBatcher::getBatch() const -{ - return &batch; -} - -inline BillboardBatch* LineBatcher::getBatch() -{ - return &batch; -} - -#endif // DEBUG_HPP +#endif // CLEAR_RENDER_PASS_HPP diff --git a/src/graphics/final-render-pass.cpp b/src/graphics/final-render-pass.cpp new file mode 100755 index 0000000..fa0b491 --- /dev/null +++ b/src/graphics/final-render-pass.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "final-render-pass.hpp" +#include "resources/resource-manager.hpp" + +FinalRenderPass::FinalRenderPass(ResourceManager* resourceManager): + resourceManager(resourceManager), + shader(nullptr) +{} + +bool FinalRenderPass::load(const RenderContext* renderContext) +{ + const float quadVertexData[] = + { + -1.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f + }; + + const std::uint32_t quadIndexData[] = + { + 0, 1, 3, + 3, 1, 2 + }; + + quadVertexCount = 4; + quadIndexCount = 6; + + // Create quad geometry + glGenVertexArrays(1, &quadVAO); + glBindVertexArray(quadVAO); + glGenBuffers(1, &quadVBO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * quadVertexCount, quadVertexData, GL_STATIC_DRAW); + glEnableVertexAttribArray(EMERGENT_VERTEX_POSITION); + glVertexAttribPointer(EMERGENT_VERTEX_POSITION, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (char*)0 + 0*sizeof(float)); + glGenBuffers(1, &quadIBO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadIBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(std::uint32_t) * quadIndexCount, quadIndexData, GL_STATIC_DRAW); + + // Load shader + shader = resourceManager->load("final.glsl"); + + // Generate shader permutation + if (!shader->generatePermutation(0)) + { + std::cerr << std::string("FinalRenderPass: failed to generate shader permutation.") << std::endl; + return false; + } + + // Connect shader parameters + silhouetteTextureParam.connect(shader->getInput("silhouetteTexture")); + + return true; +} + +void FinalRenderPass::unload() +{ + // Free quad geometry + glDeleteBuffers(1, &quadIBO); + glDeleteBuffers(1, &quadVBO); + glDeleteVertexArrays(1, &quadVAO); + + silhouetteTextureParam.disconnect(); + + shader->deleteAllPermutations(); +} + +void FinalRenderPass::render(RenderContext* renderContext) +{ + const Camera& camera = *(renderContext->camera); + + // Determine scissor box coordinates using AABBs projected onto screen coordinates + for (const RenderOperation& operation: *renderContext->queue->getOperations()) + { + // Only use operations with the outline material. + if (operation.material != nullptr) + { + if (operation.material->getFlags() & 256) + { + + } + } + } + + // Bind framebuffer and setup viewport + glBindFramebuffer(GL_FRAMEBUFFER, renderTarget->framebuffer); + glViewport(0, 0, renderTarget->width, renderTarget->height); + + //glScissor(0,0,renderTarget->width * 0.5f,renderTarget->height); + //glEnable(GL_SCISSOR_TEST); + + // Disable depth testing and writing + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + // Enable backface culling + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + // Disable alpha blending + glDisable(GL_BLEND); + + // Bind shader + shader->activate(0); + + // Upload shader parameters + silhouetteTextureParam.setValue(&silhouetteTexture); + silhouetteTextureParam.upload(); + + // Draw quad + glBindVertexArray(quadVAO); + glDrawElementsBaseVertex(GL_TRIANGLES, quadIndexCount, GL_UNSIGNED_INT, (void*)0, 0); + + //glDisable(GL_SCISSOR_TEST); +} + +void FinalRenderPass::setSilhouetteRenderTarget(const RenderTarget* renderTarget) +{ + silhouetteRenderTarget = renderTarget; + silhouetteTexture.setWidth(renderTarget->width); + silhouetteTexture.setHeight(renderTarget->height); + silhouetteTexture.setTextureID(renderTarget->texture); +} diff --git a/src/graphics/final-render-pass.hpp b/src/graphics/final-render-pass.hpp new file mode 100755 index 0000000..b61ae5a --- /dev/null +++ b/src/graphics/final-render-pass.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef FINAL_RENDER_PASS_HPP +#define FINAL_RENDER_PASS_HPP + +#include +using namespace Emergent; + +class ResourceManager; + +class FinalRenderPass: public RenderPass +{ +public: + FinalRenderPass(ResourceManager* resourceManager); + virtual bool load(const RenderContext* renderContext); + virtual void unload(); + virtual void render(RenderContext* renderContext); + + void setSilhouetteRenderTarget(const RenderTarget* renderTarget); + +private: + ResourceManager* resourceManager; + const RenderTarget* silhouetteRenderTarget; + Texture2D silhouetteTexture; + + Shader* shader; + ShaderTexture2D silhouetteTextureParam; + int quadVertexCount; + int quadIndexCount; + GLuint quadVAO; + GLuint quadVBO; + GLuint quadIBO; +}; + +#endif // FINAL_RENDER_PASS_HPP diff --git a/src/graphics/lighting-render-pass.cpp b/src/graphics/lighting-render-pass.cpp new file mode 100755 index 0000000..892539e --- /dev/null +++ b/src/graphics/lighting-render-pass.cpp @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "lighting-render-pass.hpp" +#include "resources/resource-manager.hpp" +#include "shadow-map-render-pass.hpp" + +const std::uint64_t MATERIAL_FLAG_RIGGED = 0x0000000001; +const std::uint64_t MATERIAL_FLAG_TRANSLUCENT = 0x0000000002; +const std::uint64_t MATERIAL_FLAG_DISABLE_SHADOW_CASTING = 0x0000000004; + +LightingRenderPass::LightingRenderPass(ResourceManager* resourceManager): + resourceManager(resourceManager), + shader(nullptr) +{} + +bool LightingRenderPass::load(const RenderContext* renderContext) +{ + // Setup constants + maxBoneCount = 64; + frustumSplitCount = 4; + wireframeLineWidth = 0.0f; + + // Create bias matrix + biasMatrix = glm::translate(Vector3(0.5f)) * glm::scale(Vector3(0.5f)); + + // Setup permutation values + unskinnedPermutation = 0; + skinnedPermutation = 1; + + // Load shader + shader = resourceManager->load("lighting.glsl"); + + // Generate unskinned and skinned permutations + if (!shader->generatePermutation(unskinnedPermutation) || !shader->generatePermutation(skinnedPermutation)) + { + std::cerr << std::string("LightingRenderPass: failed to generate shader permutation.") << std::endl; + return false; + } + + // Allocate array shader parameters + matrixPaletteParam = new ShaderMatrix4(maxBoneCount); + lightViewProjectionMatricesParam = new ShaderMatrix4(frustumSplitCount); + + // Connect shader parameters + modelMatrixParam.connect(shader->getInput("modelMatrix")); + modelViewMatrixParam.connect(shader->getInput("modelViewMatrix")); + modelViewProjectionMatrixParam.connect(shader->getInput("modelViewProjectionMatrix")); + normalModelMatrixParam.connect(shader->getInput("normalModelMatrix")); + normalModelViewMatrixParam.connect(shader->getInput("normalModelViewMatrix")); + matrixPaletteParam->connect(shader->getInput("matrixPalette")); + lightViewProjectionMatricesParam->connect(shader->getInput("lightViewProjectionMatrices")); + splitDistancesParam.connect(shader->getInput("splitDistances")); + shadowMapParam.connect(shader->getInput("shadowMap")); + cameraPositionParam.connect(shader->getInput("cameraPosition")); + timeParam.connect(shader->getInput("time")); + directionalLightCountParam.connect(shader->getInput("directionalLightCount")); + directionalLightColorsParam.connect(shader->getInput("directionalLightColors")); + directionalLightDirectionsParam.connect(shader->getInput("directionalLightDirections")); + spotlightCountParam.connect(shader->getInput("spotlightCount")); + spotlightColorsParam.connect(shader->getInput("spotlightColors")); + spotlightPositionsParam.connect(shader->getInput("spotlightPositions")); + spotlightAttenuationsParam.connect(shader->getInput("spotlightAttenuations")); + spotlightDirectionsParam.connect(shader->getInput("spotlightDirections")); + spotlightCutoffsParam.connect(shader->getInput("spotlightCutoffs")); + spotlightExponentsParam.connect(shader->getInput("spotlightExponents")); + + #if defined(DEBUG) + wireframeLineWidthParam.connect(shader->getInput("wireframeLineWidth")); + #endif + + // Load tree shadow + time = 0.0f; + + return true; +} + +void LightingRenderPass::unload() +{ +} + +void LightingRenderPass::render(RenderContext* renderContext) +{ + // Get camera parameters + const Camera& camera = *(renderContext->camera); + const Matrix4& view = camera.getViewTween()->getSubstate(); + const Matrix4& projection = camera.getProjectionTween()->getSubstate(); + const Vector3& cameraPosition = camera.getTransformTween()->getSubstate().translation; + + // Gather lights + const std::list* lights = renderContext->layer->getObjects(SceneObjectType::LIGHT); + + // Gather ambient cubes + AmbientCube* ambientCube = nullptr; + for (SceneObject* object: *lights) + { + Light* light = reinterpret_cast(object); + if (light->getLightType() == LightType::AMBIENT_CUBE) + { + ambientCube = reinterpret_cast(light); + break; + } + } + + // Gather directional lights + const int MAX_DIRECTIONAL_LIGHT_COUNT = 3; + int directionalLightCount = 0; + Vector3 directionalLightColors[MAX_DIRECTIONAL_LIGHT_COUNT]; + Vector3 directionalLightDirections[MAX_DIRECTIONAL_LIGHT_COUNT]; + if (lights != nullptr) + { + for (auto object: *lights) + { + const Light* light = static_cast(object); + LightType lightType = light->getLightType(); + + if (lightType == LightType::DIRECTIONAL && light->isActive()) + { + const DirectionalLight* directionalLight = static_cast(light); + + directionalLightColors[directionalLightCount] = directionalLight->getColorTween()->getSubstate() * directionalLight->getIntensityTween()->getSubstate(); + directionalLightDirections[directionalLightCount] = glm::normalize(Vector3(view * Vector4(-directionalLight->getDirectionTween()->getSubstate(), 0.0f))); + ++directionalLightCount; + } + } + } + + directionalLightCountParam.setValue(directionalLightCount); + directionalLightColorsParam.setValues(0, directionalLightColors, directionalLightCount); + directionalLightDirectionsParam.setValues(0, directionalLightDirections, directionalLightCount); + + // Gather spotlights + const int MAX_SPOTLIGHT_COUNT = 3; + int spotlightCount = 0; + Vector3 spotlightColors[MAX_SPOTLIGHT_COUNT]; + Vector3 spotlightPositions[MAX_SPOTLIGHT_COUNT]; + Vector3 spotlightAttenuations[MAX_SPOTLIGHT_COUNT]; + Vector3 spotlightDirections[MAX_SPOTLIGHT_COUNT]; + float spotlightCutoffs[MAX_SPOTLIGHT_COUNT]; + float spotlightExponents[MAX_SPOTLIGHT_COUNT]; + if (lights != nullptr) + { + for (auto object: *lights) + { + const Light* light = static_cast(object); + LightType lightType = light->getLightType(); + + if (lightType == LightType::SPOTLIGHT && light->isActive()) + { + const Spotlight* spotlight = static_cast(light); + + spotlightColors[spotlightCount] = spotlight->getColorTween()->getSubstate() * spotlight->getIntensityTween()->getSubstate(); + spotlightPositions[spotlightCount] = Vector3(view * Vector4(spotlight->getTransformTween()->getSubstate().translation, 1.0f)); + spotlightAttenuations[spotlightCount] = spotlight->getAttenuationTween()->getSubstate(); + spotlightDirections[spotlightCount] = glm::normalize(Vector3(view * Vector4(-spotlight->getDirectionTween()->getSubstate(), 0.0f))); + spotlightCutoffs[spotlightCount] = spotlight->getCutoffTween()->getSubstate(); + spotlightExponents[spotlightCount] = spotlight->getExponentTween()->getSubstate(); + + ++spotlightCount; + } + } + } + + spotlightCountParam.setValue(spotlightCount); + spotlightColorsParam.setValues(0, spotlightColors, spotlightCount); + spotlightPositionsParam.setValues(0, spotlightPositions, spotlightCount); + spotlightAttenuationsParam.setValues(0, spotlightAttenuations, spotlightCount); + spotlightDirectionsParam.setValues(0, spotlightDirections, spotlightCount); + spotlightCutoffsParam.setValues(0, spotlightCutoffs, spotlightCount); + spotlightExponentsParam.setValues(0, spotlightExponents, spotlightCount); + + #if defined(DEBUG) + wireframeLineWidthParam.setValue(wireframeLineWidth); + #endif + + std::list* operations = renderContext->queue->getOperations(); + + // Bind framebuffer and setup viewport + glBindFramebuffer(GL_FRAMEBUFFER, renderTarget->framebuffer); + glViewport(0, 0, renderTarget->width, renderTarget->height); + + // Enable depth testing + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + glDepthFunc(GL_LEQUAL); + + // Enable backface culling + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + // Disable alpha blending + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_BLEND); + + // Load shadow map pass parameters + Vector4 splitDistances; + for (int i = 0; i < 4; ++i) + { + splitDistances[i] = shadowMapPass->getSplitViewFrustum().getSplitDistance(i + 1); + } + + // Calculate the (light-space) view-projection matrices + Matrix4 lightViewProjectionMatrices[4]; + for (int i = 0; i < 4; ++i) + { + lightViewProjectionMatrices[i] = shadowMapPass->getTileMatrix(i) * biasMatrix * shadowMapPass->getCropMatrix(i) * shadowMapPass->getLightCamera()->getViewProjection(); + } + + // Set shader parameter values + lightViewProjectionMatricesParam->setValues(0, &lightViewProjectionMatrices[0], 4); + splitDistancesParam.setValue(splitDistances); + cameraPositionParam.setValue(cameraPosition); + shadowMapParam.setValue(shadowMap); + timeParam.setValue(time); + + std::uint32_t permutation = 0xDEADBEEF; + bool blending = false; + GLuint boundVAO = 0; + + // Sort operations + operations->sort(RenderOpCompare()); + + // Render operations + for (const RenderOperation& operation: *operations) + { + // Skip operations without materials + if (!operation.material) + { + continue; + } + + bool hasTranslucency = operation.material->getFlags() & MATERIAL_FLAG_TRANSLUCENT; + if (hasTranslucency && !blending) + { + glEnable(GL_BLEND); + blending = true; + } + + // Select permutation + std::uint32_t targetPermutation = (operation.pose != nullptr) ? skinnedPermutation : unskinnedPermutation; + if (permutation != targetPermutation) + { + permutation = targetPermutation; + shader->activate(permutation); + + // Pass static params + lightViewProjectionMatricesParam->upload(); + splitDistancesParam.upload(); + cameraPositionParam.upload(); + shadowMapParam.upload(); + timeParam.upload(); + + directionalLightCountParam.upload(); + if (directionalLightCount > 0) + { + directionalLightColorsParam.upload(); + directionalLightDirectionsParam.upload(); + } + + spotlightCountParam.upload(); + if (spotlightCount > 0) + { + spotlightColorsParam.upload(); + spotlightPositionsParam.upload(); + spotlightAttenuationsParam.upload(); + spotlightDirectionsParam.upload(); + spotlightCutoffsParam.upload(); + spotlightExponentsParam.upload(); + } + + #if defined(DEBUG) + wireframeLineWidthParam.upload(); + #endif + } + + const Matrix4& modelMatrix = operation.transform; + Matrix4 modelViewMatrix = view * modelMatrix; + Matrix4 modelViewProjectionMatrix = projection * modelViewMatrix; + Matrix3 normalModelViewMatrix = glm::transpose(glm::inverse(Matrix3(modelViewMatrix))); + Matrix3 normalModelMatrix = glm::transpose(glm::inverse(Matrix3(modelMatrix))); + + modelMatrixParam.setValue(modelMatrix); + modelViewMatrixParam.setValue(modelViewMatrix); + modelViewProjectionMatrixParam.setValue(modelViewProjectionMatrix); + normalModelViewMatrixParam.setValue(normalModelViewMatrix); + normalModelMatrixParam.setValue(normalModelMatrix); + + // Upload matrix parameters + modelMatrixParam.upload(); + modelViewMatrixParam.upload(); + modelViewProjectionMatrixParam.upload(); + normalModelViewMatrixParam.upload(); + normalModelMatrixParam.upload(); + + // Upload pose matrix palette + if (operation.pose != nullptr && matrixPaletteParam->isConnected()) + { + matrixPaletteParam->getConnectedInput()->upload(0, operation.pose->getMatrixPalette(), operation.pose->getSkeleton()->getBoneCount()); + } + + // Upload material parameters + if (operation.material != nullptr) + { + if (operation.material->getShader() != shader) + { + ((Material*)operation.material)->setShader(shader); + } + + operation.material->upload(); + } + + if (boundVAO != operation.vao) + { + glBindVertexArray(operation.vao); + boundVAO = operation.vao; + } + + glDrawElementsBaseVertex(GL_TRIANGLES, operation.triangleCount * 3, GL_UNSIGNED_INT, (void*)0, operation.indexOffset); + } +} + +bool LightingRenderPass::RenderOpCompare::operator()(const RenderOperation& opA, const RenderOperation& opB) const +{ + if (!opA.material) + return false; + else if (!opB.material) + return true; + + // Determine transparency + bool transparentA = opA.material->getFlags() & MATERIAL_FLAG_TRANSLUCENT; + bool transparentB = opB.material->getFlags() & MATERIAL_FLAG_TRANSLUCENT; + + if (transparentA) + { + if (transparentB) + { + // A and B are both transparent, render back to front + return (opA.depth >= opB.depth); + } + else + { + // A is transparent, B is opaque. Render B first + return false; + } + } + else + { + if (transparentB) + { + + // A is opaque, B is transparent. Render A first + return true; + } + else + { + // A and B are both opaque + if (opA.material->getShader() == opB.material->getShader()) + { + // A and B have the same shader + if (opA.vao == opB.vao) + { + // A and B have the same VAO, sort by depth + return (opA.depth < opB.depth); + } + else + { + // Sort by VAO + return (opA.vao < opB.vao); + } + } + } + } + + // A and B are both opaque and have different shaders, sort by shader + return (opA.material->getShader() < opB.material->getShader()); +} + diff --git a/src/graphics/lighting-render-pass.hpp b/src/graphics/lighting-render-pass.hpp new file mode 100755 index 0000000..1fbf12a --- /dev/null +++ b/src/graphics/lighting-render-pass.hpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef LIGHTING_RENDER_PASS_HPP +#define LIGHTING_RENDER_PASS_HPP + +#include +using namespace Emergent; + +class ResourceManager; +class ShadowMapRenderPass; + +class LightingRenderPass: public RenderPass +{ +public: + LightingRenderPass(ResourceManager* resourceManager); + + virtual bool load(const RenderContext* renderContext); + virtual void unload(); + virtual void render(RenderContext* renderContext); + + inline void setShadowMapPass(const ShadowMapRenderPass* shadowMapPass) { this->shadowMapPass = shadowMapPass; } + inline void setShadowMap(const Texture2D* shadowMap) { this->shadowMap = shadowMap; } + inline void setTime(float time) { this->time = time; } + inline void setWireframeLineWidth(float width) { this->wireframeLineWidth = width; } + +private: + class RenderOpCompare + { + public: + // Sort render opations + bool operator()(const RenderOperation& opA, const RenderOperation& opB) const; + }; + + ResourceManager* resourceManager; + Shader* shader; + int maxBoneCount; + int frustumSplitCount; + std::uint32_t unskinnedPermutation; + std::uint32_t skinnedPermutation; + Matrix4 biasMatrix; + const ShadowMapRenderPass* shadowMapPass; + const Texture2D* shadowMap; + float time; + float wireframeLineWidth; + + // Shader parameters + ShaderMatrix4 modelMatrixParam; + ShaderMatrix4 modelViewMatrixParam; + ShaderMatrix4 modelViewProjectionMatrixParam; + ShaderMatrix3 normalModelMatrixParam; + ShaderMatrix3 normalModelViewMatrixParam; + ShaderMatrix4* matrixPaletteParam; + ShaderMatrix4* lightViewProjectionMatricesParam; + ShaderVector4 splitDistancesParam; + ShaderTexture2D shadowMapParam; + ShaderVector3 cameraPositionParam; + ShaderFloat timeParam; + ShaderInt directionalLightCountParam; + ShaderVector3 directionalLightColorsParam; + ShaderVector3 directionalLightDirectionsParam; + ShaderInt spotlightCountParam; + ShaderVector3 spotlightColorsParam; + ShaderVector3 spotlightPositionsParam; + ShaderVector3 spotlightAttenuationsParam; + ShaderVector3 spotlightDirectionsParam; + ShaderFloat spotlightCutoffsParam; + ShaderFloat spotlightExponentsParam; + + #if defined(DEBUG) + ShaderFloat wireframeLineWidthParam; + #endif +}; + +#endif // LIGHTING_RENDER_PASS_HPP diff --git a/src/graphics/shadow-map-render-pass.cpp b/src/graphics/shadow-map-render-pass.cpp new file mode 100755 index 0000000..ac1a4e7 --- /dev/null +++ b/src/graphics/shadow-map-render-pass.cpp @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "shadow-map-render-pass.hpp" +#include "resources/resource-manager.hpp" + +ShadowMapRenderPass::ShadowMapRenderPass(ResourceManager* resourceManager): + resourceManager(resourceManager), + shader(nullptr), + croppedShadowMapViewports(nullptr), + viewCamera(nullptr), + splitViewFrustum(nullptr), + cropMatrices(nullptr), + tileMatrices(nullptr) +{} + +bool ShadowMapRenderPass::load(const RenderContext* renderContext) +{ + // Set maximum number of bones for skinned meshes + maxBoneCount = 64; + + // Create split view frustum + splitViewFrustum = new SplitViewFrustum(4); + splitViewFrustum->setSplitSchemeWeight(0.6f); + + // Determine resolution of shadow maps + shadowMapResolution = 4096; + croppedShadowMapResolution = shadowMapResolution >> 1; + + // Allocate viewports + croppedShadowMapViewports = new Vector4[splitViewFrustum->getSubfrustumCount()]; + + // Setup viewports + for (int i = 0; i < splitViewFrustum->getSubfrustumCount(); ++i) + { + int x = i % 2; + int y = i / 2; + + Vector4* viewport = &croppedShadowMapViewports[i]; + (*viewport)[0] = static_cast(x * croppedShadowMapResolution); + (*viewport)[1] = static_cast(y * croppedShadowMapResolution); + (*viewport)[2] = static_cast(croppedShadowMapResolution); + (*viewport)[3] = static_cast(croppedShadowMapResolution); + } + + // Allocate matrices + cropMatrices = new Matrix4[splitViewFrustum->getSubfrustumCount()]; + tileMatrices = new Matrix4[splitViewFrustum->getSubfrustumCount()]; + + // Setup tile matrices + Matrix4 tileScale = glm::scale(Vector3(0.5f, 0.5f, 1.0f)); + for (int i = 0; i < splitViewFrustum->getSubfrustumCount(); ++i) + { + float x = static_cast(i % 2) * 0.5f; + float y = static_cast(i / 2) * 0.5f; + tileMatrices[i] = glm::translate(Vector3(x, y, 0.0f)) * tileScale; + } + + // Setup permutation values + unskinnedPermutation = 0; + skinnedPermutation = 1; + + // Load shader + shader = resourceManager->load("depth-pass.glsl"); + + // Generate unskinned and skinned permutations + if (!shader->generatePermutation(unskinnedPermutation) || !shader->generatePermutation(skinnedPermutation)) + { + std::cerr << std::string("ShadowMapRenderPass: failed to generate shader permutation.") << std::endl; + return false; + } + + // Allocate bone palette parameter + matrixPaletteParam = new ShaderMatrix4(maxBoneCount); + + // Connect shader variables + modelViewProjectionParam.connect(shader->getInput("modelViewProjectionMatrix")); + matrixPaletteParam->connect(shader->getInput("matrixPalette")); + if (!modelViewProjectionParam.isConnected() || + !matrixPaletteParam->isConnected()) + { + std::cerr << std::string("ShadowMapRenderPass: one or more shader variables were not connected to shader inputs.") << std::endl; + return false; + } + + return true; +} + +void ShadowMapRenderPass::unload() +{ + modelViewProjectionParam.disconnect(); + matrixPaletteParam->disconnect(); + delete matrixPaletteParam; + shader->deleteAllPermutations(); + + delete[] croppedShadowMapViewports; + croppedShadowMapViewports = nullptr; + + delete splitViewFrustum; + splitViewFrustum = nullptr; + + delete[] cropMatrices; + cropMatrices = nullptr; + + delete[] tileMatrices; + tileMatrices = nullptr; +} + +void ShadowMapRenderPass::render(RenderContext* renderContext) +{ + // Bind framebuffer and setup viewport + glBindFramebuffer(GL_FRAMEBUFFER, renderTarget->framebuffer); + glViewport(0, 0, renderTarget->width, renderTarget->height); + + // Enable depth testing + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + glDepthFunc(GL_LESS); + + // Clear the framebuffer depth + glClear(GL_DEPTH_BUFFER_BIT); + + // Draw back faces only + glDisable(GL_CULL_FACE); + glCullFace(GL_FRONT); + + // Disable alpha blending + glDisable(GL_BLEND); + + //const Camera& lightCamera = *(renderContext->camera); + std::list* operations = renderContext->queue->getOperations(); + + GLuint boundVAO = 0; + + const Matrix4& viewCameraView = viewCamera->getViewTween()->getSubstate(); + const Matrix4& viewCameraProjection = viewCamera->getProjectionTween()->getSubstate(); + const Matrix4& lightCameraViewProjection = lightCamera->getViewProjectionTween()->getSubstate(); + splitViewFrustum->setMatrices(viewCameraView, viewCameraProjection); + + + // Sort operations + operations->sort(RenderOpCompare()); + + std::uint32_t permutation = 0xDEADBEEF; + std::uint32_t noShadowCastingFlag = 4; + + // For each frustum split + for (int i = 0; i < splitViewFrustum->getSubfrustumCount(); ++i) + { + // Calculate crop matrix + { + const ViewFrustum& subfrustum = splitViewFrustum->getSubfrustum(i); + + // Create AABB containing the subfrustum corners + AABB subfrustumBounds(subfrustum.getCorner(0), subfrustum.getCorner(0)); + for (std::size_t j = 1; j < 8; ++j) + { + subfrustumBounds.add(subfrustum.getCorner(j)); + } + + // Transform subfrustum bounds into light's clip space + AABB croppingBounds = subfrustumBounds.transformed(lightCameraViewProjection); + Vector3 cropMax = croppingBounds.getMax(); + Vector3 cropMin = croppingBounds.getMin(); + + // Calculate scale + Vector3 scale; + scale.x = 2.0f / (cropMax.x - cropMin.x); + scale.y = 2.0f / (cropMax.y - cropMin.y); + scale.z = 1.0f / (cropMax.z - cropMin.z); + + // Quantize scale + float scaleQuantizer = 64.0f; + scale.x = 1.0f / std::ceil(1.0f / scale.x * scaleQuantizer) * scaleQuantizer; + scale.y = 1.0f / std::ceil(1.0f / scale.y * scaleQuantizer) * scaleQuantizer; + + // Calculate offset + Vector3 offset; + offset.x = (cropMax.x + cropMin.x) * scale.x * -0.5f; + offset.y = (cropMax.y + cropMin.y) * scale.y * -0.5f; + offset.z = -cropMin.z * scale.z; + + // Quantize offset + float halfTextureSize = static_cast(croppedShadowMapResolution) * 0.5f; + offset.x = std::ceil(offset.x * halfTextureSize) / halfTextureSize; + offset.y = std::ceil(offset.y * halfTextureSize) / halfTextureSize; + + cropMatrices[i] = glm::translate(offset) * glm::scale(scale); + } + + Matrix4 croppedViewProjection = cropMatrices[i] * lightCameraViewProjection; + + // Activate viewport for corresponding cropped shadow map + const Vector4& viewport = croppedShadowMapViewports[i]; + glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); + + // Render operations + for (const RenderOperation& operation: *operations) + { + // Skip operations with no materials and materials with no shadows + if (operation.material == nullptr || (operation.material->getFlags() & noShadowCastingFlag)) + { + continue; + } + + // TODO: Perform culling for subfrustums + + // Select permutation + std::uint32_t targetPermutation = (operation.pose != nullptr) ? skinnedPermutation : unskinnedPermutation; + if (permutation != targetPermutation) + { + permutation = targetPermutation; + shader->activate(permutation); + } + + // Pass matrix palette + if (operation.pose != nullptr) + { + matrixPaletteParam->getConnectedInput()->upload(0, operation.pose->getMatrixPalette(), operation.pose->getSkeleton()->getBoneCount()); + } + + const Matrix4& modelMatrix = operation.transform; + Matrix4 modelViewProjectionMatrix = croppedViewProjection * modelMatrix; + + modelViewProjectionParam.setValue(modelViewProjectionMatrix); + modelViewProjectionParam.upload(); + + if (boundVAO != operation.vao) + { + glBindVertexArray(operation.vao); + boundVAO = operation.vao; + } + + glDrawElementsBaseVertex(GL_TRIANGLES, operation.triangleCount * 3, GL_UNSIGNED_INT, (void*)0, operation.indexOffset); + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +bool ShadowMapRenderPass::RenderOpCompare::operator()(const RenderOperation& opA, const RenderOperation& opB) const +{ + // If A is rigged + if (opA.pose != nullptr) + { + // And B is rigged + if (opB.pose != nullptr) + { + // Sort by VAO ID + return (opA.vao <= opB.vao); + } + else + { + // Render A first + return true; + } + } + + // Sort by VAO ID + return (opA.vao <= opB.vao); +} + diff --git a/src/graphics/shadow-map-render-pass.hpp b/src/graphics/shadow-map-render-pass.hpp new file mode 100755 index 0000000..07f2af0 --- /dev/null +++ b/src/graphics/shadow-map-render-pass.hpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef SHADOW_MAP_RENDER_PASS_HPP +#define SHADOW_MAP_RENDER_PASS_HPP + +#include +using namespace Emergent; + +class ResourceManager; + +/** + * Renders the distance from the view frustum's near clipping plane to scene geometry. The render target should have a depth only framebuffer. + */ +class ShadowMapRenderPass: public RenderPass +{ +public: + ShadowMapRenderPass(ResourceManager* resourceManager); + + virtual bool load(const RenderContext* renderContext); + virtual void unload(); + virtual void render(RenderContext* renderContext); + + inline void setViewCamera(const Camera* camera) { this->viewCamera = camera; } + inline void setLightCamera(Camera* camera) { this->lightCamera = camera; } + + inline const Camera* getLightCamera() const { return lightCamera; } + inline const SplitViewFrustum& getSplitViewFrustum() const { return *splitViewFrustum; } + inline const Matrix4& getCropMatrix(std::size_t index) const { return cropMatrices[index]; } + inline const Matrix4& getTileMatrix(std::size_t index) const { return tileMatrices[index]; } + +private: + class RenderOpCompare + { + public: + // Sort render opations + bool operator()(const RenderOperation& opA, const RenderOperation& opB) const; + }; + + ResourceManager* resourceManager; + Shader* shader; + std::uint32_t unskinnedPermutation; + std::uint32_t skinnedPermutation; + ShaderMatrix4 modelViewProjectionParam; + ShaderMatrix4* matrixPaletteParam; // data not used, just getConnectedInput() then pass pose matrix palette pointer directly + + int maxBoneCount; + int shadowMapResolution; + int croppedShadowMapResolution; + Vector4* croppedShadowMapViewports; + Matrix4* cropMatrices; + Matrix4* tileMatrices; + const Camera* viewCamera; + Camera* lightCamera; + SplitViewFrustum* splitViewFrustum; +}; + +#endif // SHADOW_MAP_RENDER_PASS_HPP diff --git a/src/graphics/silhouette-render-pass.cpp b/src/graphics/silhouette-render-pass.cpp new file mode 100755 index 0000000..7def36c --- /dev/null +++ b/src/graphics/silhouette-render-pass.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "silhouette-render-pass.hpp" +#include "resources/resource-manager.hpp" + +SilhouetteRenderPass::SilhouetteRenderPass(ResourceManager* resourceManager): + resourceManager(resourceManager), + shader(nullptr) +{} + +bool SilhouetteRenderPass::load(const RenderContext* renderContext) +{ + // Load shader + shader = resourceManager->load("silhouette.glsl"); + + // Setup permutation values + unskinnedPermutation = 0; + skinnedPermutation = 1; + + // Generate unskinned and skinned permutations + if (!shader->generatePermutation(unskinnedPermutation) || !shader->generatePermutation(skinnedPermutation)) + { + std::cerr << std::string("SilhouetteRenderPass: failed to generate shader permutation.") << std::endl; + return false; + } + + maxBoneCount = 64; + matrixPaletteParam = new ShaderMatrix4(maxBoneCount); + + // Connect shader parameters + matrixPaletteParam->connect(shader->getInput("matrixPalette")); + modelViewProjectionMatrixParam.connect(shader->getInput("modelViewProjectionMatrix")); + + return true; +} + +void SilhouetteRenderPass::unload() +{ + + // Disconnect shader parameters + matrixPaletteParam->disconnect(); + modelViewProjectionMatrixParam.disconnect(); + + delete matrixPaletteParam; + + // Delete shader permutations + shader->deleteAllPermutations(); +} + +void SilhouetteRenderPass::render(RenderContext* renderContext) +{ + const Camera& camera = *(renderContext->camera); + + // Bind framebuffer and setup viewport + glBindFramebuffer(GL_FRAMEBUFFER, renderTarget->framebuffer); + glViewport(0, 0, renderTarget->width, renderTarget->height); + + // Disable depth testing and writing + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + // Disable backface culling + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + // Disable alpha blending + glDisable(GL_BLEND); + + // Get view-projection matrix + const Matrix4& viewProjection = renderContext->camera->getViewProjectionTween()->getSubstate(); + + // Filter render operations + std::list operations; + for (const RenderOperation& operation: *renderContext->queue->getOperations()) + { + // Only use operations with the outline material. + if (operation.material != nullptr) + { + if (operation.material->getFlags() & 256) + { + operations.push_back(operation); + } + } + } + + // Sort render operations + operations.sort(RenderOpCompare()); + + std::uint32_t permutation = 0xDEADBEEF; + GLuint boundVAO = 0; + + for (const RenderOperation& operation: operations) + { + std::uint32_t targetPermutation = (operation.pose != nullptr) ? skinnedPermutation : unskinnedPermutation; + if (permutation != targetPermutation) + { + permutation = targetPermutation; + shader->activate(permutation); + } + + + Matrix4 modelViewProjectionMatrix = viewProjection * operation.transform; + + modelViewProjectionMatrixParam.setValue(modelViewProjectionMatrix); + modelViewProjectionMatrixParam.upload(); + + // Pass matrix palette + if (operation.pose != nullptr) + { + matrixPaletteParam->getConnectedInput()->upload(0, operation.pose->getMatrixPalette(), operation.pose->getSkeleton()->getBoneCount()); + } + + if (boundVAO != operation.vao) + { + glBindVertexArray(operation.vao); + boundVAO = operation.vao; + } + + glDrawElementsBaseVertex(GL_TRIANGLES, operation.triangleCount * 3, GL_UNSIGNED_INT, (void*)0, operation.indexOffset); + } +} + +bool SilhouetteRenderPass::RenderOpCompare::operator()(const RenderOperation& opA, const RenderOperation& opB) const +{ + if (opA.pose) + { + if (opB.pose) + { + return false; + } + else + { + return true; + } + } +} + diff --git a/src/graphics/silhouette-render-pass.hpp b/src/graphics/silhouette-render-pass.hpp new file mode 100755 index 0000000..0cc2faf --- /dev/null +++ b/src/graphics/silhouette-render-pass.hpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef SILHOUETTE_RENDER_PASS_HPP +#define SILHOUETTE_RENDER_PASS_HPP + +#include +using namespace Emergent; + +class ResourceManager; + +/** + * Renders the user interface + */ +class SilhouetteRenderPass: public RenderPass +{ +public: + SilhouetteRenderPass(ResourceManager* resourceManager); + virtual bool load(const RenderContext* renderContext); + virtual void unload(); + virtual void render(RenderContext* renderContext); + +private: + class RenderOpCompare + { + public: + // Sort render opations + bool operator()(const RenderOperation& opA, const RenderOperation& opB) const; + }; + + ResourceManager* resourceManager; + + Shader* shader; + std::uint32_t unskinnedPermutation; + std::uint32_t skinnedPermutation; + ShaderMatrix4 modelViewProjectionMatrixParam; + ShaderMatrix4* matrixPaletteParam; + int maxBoneCount; +}; + +#endif // SILHOUETTE_RENDER_PASS_HPP diff --git a/src/graphics/sky-render-pass.cpp b/src/graphics/sky-render-pass.cpp new file mode 100755 index 0000000..8b83d50 --- /dev/null +++ b/src/graphics/sky-render-pass.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "sky-render-pass.hpp" +#include "resources/resource-manager.hpp" + +SkyRenderPass::SkyRenderPass(ResourceManager* resourceManager): + resourceManager(resourceManager), + shader(nullptr) +{} + +bool SkyRenderPass::load(const RenderContext* renderContext) +{ + const float quadVertexData[] = + { + -1.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f + }; + + const std::uint32_t quadIndexData[] = + { + 0, 1, 3, + 3, 1, 2 + }; + + quadVertexCount = 4; + quadIndexCount = 6; + + // Create quad geometry + glGenVertexArrays(1, &quadVAO); + glBindVertexArray(quadVAO); + glGenBuffers(1, &quadVBO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * quadVertexCount, quadVertexData, GL_STATIC_DRAW); + glEnableVertexAttribArray(EMERGENT_VERTEX_POSITION); + glVertexAttribPointer(EMERGENT_VERTEX_POSITION, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (char*)0 + 0*sizeof(float)); + glGenBuffers(1, &quadIBO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, quadIBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(std::uint32_t) * quadIndexCount, quadIndexData, GL_STATIC_DRAW); + + // Load sky shader + shader = resourceManager->load("sky.glsl"); + + // Generate shader permutation + if (!shader->generatePermutation(0)) + { + std::cerr << std::string("SkyRenderPass: failed to generate shader permutation.") << std::endl; + return false; + } + + // Connect shader variables + matrixParam.connect(shader->getInput("matrix")); + sunDirectionParam.connect(shader->getInput("sunDirection")); + sunAngularRadiusParam.connect(shader->getInput("sunAngularRadius")); + skyGradientParam.connect(shader->getInput("skyGradient")); + + // Load sky gradient texture + skyGradientTexture = resourceManager->load("sky-gradient-noon.png"); + + // Set sun angular radius + sunAngularRadius = glm::radians(2.0f); + + return true; +} + +void SkyRenderPass::unload() +{ + // Free quad geometry + glDeleteBuffers(1, &quadIBO); + glDeleteBuffers(1, &quadVBO); + glDeleteVertexArrays(1, &quadVAO); + + matrixParam.disconnect(); + sunDirectionParam.disconnect(); + sunAngularRadiusParam.disconnect(); + skyGradientParam.disconnect(); + shader->deleteAllPermutations(); +} + +void SkyRenderPass::render(RenderContext* renderContext) +{ + const Camera& camera = *(renderContext->camera); + + // Get sun light + const std::list* lights = renderContext->layer->getObjects(SceneObjectType::LIGHT); + DirectionalLight* sun = nullptr; + for (SceneObject* object: *lights) + { + Light* light = reinterpret_cast(object); + if (light->getLightType() == LightType::DIRECTIONAL) + { + sun = reinterpret_cast(light); + break; + } + } + + // Bind framebuffer and setup viewport + glBindFramebuffer(GL_FRAMEBUFFER, renderTarget->framebuffer); + glViewport(0, 0, renderTarget->width, renderTarget->height); + + // Disable depth testing and writing + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + // Enable backface culling + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + // Disable alpha blending + glDisable(GL_BLEND); + + // Calculate matrix + Matrix4 modelView = Matrix4(Matrix3(camera.getViewTween()->getSubstate())); + const Matrix4& inverseProjection = camera.getInverseProjectionTween()->getSubstate(); + Matrix4 matrix = glm::inverse(modelView) * inverseProjection; + + // Get sun direction + Vector3 sunDirection = Vector3(0, 0, 1); + if (sun) + { + sunDirection = glm::normalize(sun->getDirectionTween()->getSubstate()); + } + + // Bind shader + shader->activate(0); + + // Set shader param values + matrixParam.setValue(matrix); + sunDirectionParam.setValue(sunDirection); + sunAngularRadiusParam.setValue(sunAngularRadius); + skyGradientParam.setValue(skyGradientTexture); + + // Upload shader params + matrixParam.upload(); + sunDirectionParam.upload(); + sunAngularRadiusParam.upload(); + skyGradientParam.upload(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Draw quad + glBindVertexArray(quadVAO); + glDrawElementsBaseVertex(GL_TRIANGLES, quadIndexCount, GL_UNSIGNED_INT, (void*)0, 0); +} + diff --git a/src/graphics/sky-render-pass.hpp b/src/graphics/sky-render-pass.hpp new file mode 100755 index 0000000..d647667 --- /dev/null +++ b/src/graphics/sky-render-pass.hpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef SKY_RENDER_PASS_HPP +#define SKY_RENDER_PASS_HPP + +#include +using namespace Emergent; + +class ResourceManager; + +class SkyRenderPass: public RenderPass +{ +public: + SkyRenderPass(ResourceManager* resourceManager); + virtual bool load(const RenderContext* renderContext); + virtual void unload(); + virtual void render(RenderContext* renderContext); + +private: + ResourceManager* resourceManager; + Shader* shader; + float sunAngularRadius; + Texture2D* skyGradientTexture; + + ShaderMatrix4 matrixParam; + ShaderVector3 sunDirectionParam; + ShaderFloat sunAngularRadiusParam; + ShaderTexture2D skyGradientParam; + + int quadVertexCount; + int quadIndexCount; + GLuint quadVAO; + GLuint quadVBO; + GLuint quadIBO; +}; + +#endif // SKY_RENDER_PASS_HPP diff --git a/src/graphics/ui-render-pass.cpp b/src/graphics/ui-render-pass.cpp new file mode 100755 index 0000000..ea83e48 --- /dev/null +++ b/src/graphics/ui-render-pass.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "ui-render-pass.hpp" +#include "ui/ui.hpp" +#include "resources/resource-manager.hpp" + +UIRenderPass::UIRenderPass(ResourceManager* resourceManager): + resourceManager(resourceManager), + shader(nullptr) +{} + +bool UIRenderPass::load(const RenderContext* renderContext) +{ + shader = resourceManager->load("ui.glsl"); + + // Set permutation values + int gammaFlag = 0x2; + untexturedPermutation = 0 | gammaFlag; + texturedPermutation = 1 | gammaFlag; + + // Generate shader permutations + if (!shader->generatePermutation(untexturedPermutation) || !shader->generatePermutation(texturedPermutation)) + { + std::cerr << std::string("UIRenderPass: failed to generate shader permutation.") << std::endl; + return false; + } + + // Connect shader variables + modelViewProjectionMatrixParam.connect(shader->getInput("modelViewProjectionMatrix")); + textureParam.connect(shader->getInput("tex")); + textureOffsetParam.connect(shader->getInput("texcoordOffset")); + textureScaleParam.connect(shader->getInput("texcoordScale")); + if (!modelViewProjectionMatrixParam.isConnected() || + !textureParam.isConnected() || + !textureOffsetParam.isConnected() || + !textureScaleParam.isConnected()) + { + std::cerr << std::string("UIRenderPass: one or more shader variables were not connected to shader inputs.") << std::endl; + return false; + } + + return true; +} + +void UIRenderPass::unload() +{ + modelViewProjectionMatrixParam.disconnect(); + textureParam.disconnect(); + textureOffsetParam.disconnect(); + textureScaleParam.disconnect(); + shader->deleteAllPermutations(); +} + +void UIRenderPass::render(RenderContext* renderContext) +{ + const Camera& camera = *(renderContext->camera); + const Matrix4& viewProjection = camera.getViewProjectionTween()->getSubstate(); + + // Bind framebuffer and setup viewport + glBindFramebuffer(GL_FRAMEBUFFER, renderTarget->framebuffer); + glViewport(0, 0, renderTarget->width, renderTarget->height); + + // Disable depth testing + glDisable(GL_DEPTH_TEST); + //glDepthMask(GL_FALSE); + //glDepthFunc(GL_LESS); + + // Disable backface culling + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + // Enable alpha blending + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + //glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + // Render operations + const std::list* operations = renderContext->queue->getOperations(); + for (const RenderOperation& operation: *operations) + { + // Skip render operations with unsupported materials + /* + if (operation.material->getMaterialFormatID() != static_cast(MaterialFormat::UI)) + { + continue; + } + */ + + const UIMaterial* material = static_cast(operation.material); + + if (material->texture->getValue() != nullptr) + { + shader->activate(texturedPermutation); + + textureParam.setValue(material->texture->getValue()); + textureOffsetParam.setValue(material->textureOffset->getValue()); + textureScaleParam.setValue(material->textureScale->getValue()); + + textureParam.upload(); + textureOffsetParam.upload(); + textureScaleParam.upload(); + } + else + { + shader->activate(untexturedPermutation); + } + + const Matrix4& modelMatrix = operation.transform; + Matrix4 modelViewProjectionMatrix = viewProjection * modelMatrix; + + // Upload matrix parameters + modelViewProjectionMatrixParam.setValue(modelViewProjectionMatrix); + modelViewProjectionMatrixParam.upload(); + + // Upload material parameters + //operation.material->upload(); + + // Draw geometry + glBindVertexArray(operation.vao); + glDrawElementsBaseVertex(GL_TRIANGLES, operation.triangleCount * 3, GL_UNSIGNED_INT, (void*)0, operation.indexOffset); + } +} + diff --git a/src/graphics/ui-render-pass.hpp b/src/graphics/ui-render-pass.hpp new file mode 100755 index 0000000..72f2cfa --- /dev/null +++ b/src/graphics/ui-render-pass.hpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef UI_RENDER_PASS_HPP +#define UI_RENDER_PASS_HPP + +#include +using namespace Emergent; + +class ResourceManager; + +/** + * Renders the user interface + */ +class UIRenderPass: public RenderPass +{ +public: + UIRenderPass(ResourceManager* resourceManager); + virtual bool load(const RenderContext* renderContext); + virtual void unload(); + virtual void render(RenderContext* renderContext); + +private: + ResourceManager* resourceManager; + + Shader* shader; + std::uint32_t texturedPermutation; + std::uint32_t untexturedPermutation; + + ShaderMatrix4 modelViewProjectionMatrixParam; + ShaderTexture2D textureParam; + ShaderVector2 textureOffsetParam; + ShaderVector2 textureScaleParam; +}; + +#endif // UI_RENDER_PASS_HPP diff --git a/src/graphics/vertex-format.hpp b/src/graphics/vertex-format.hpp new file mode 100644 index 0000000..242e105 --- /dev/null +++ b/src/graphics/vertex-format.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef VERTEX_FORMAT_HPP +#define VERTEX_FORMAT_HPP + +#define VERTEX_POSITION 0 +#define VERTEX_NORMAL 1 +#define VERTEX_TEXCOORD 2 +#define VERTEX_TANGENT 3 +#define VERTEX_BITANGENT 4 +#define VERTEX_BONE_INDICES 5 +#define VERTEX_BONE_WEIGHTS 6 +#define VERTEX_COLOR 7 +#define VERTEX_BARYCENTRIC 8 + +#endif // VERTEX_FORMAT_HPP diff --git a/src/input.hpp b/src/input.hpp deleted file mode 100644 index 771eaf4..0000000 --- a/src/input.hpp +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef INPUT_HPP -#define INPUT_HPP - -#include -using namespace Emergent; - -#include -#include -#include -#include -#include - -class KeyObserver -{ -public: - virtual void keyPressed(int scancode) = 0; - virtual void keyReleased(int scancode) = 0; -}; - -class MouseMotionObserver -{ -public: - virtual void mouseMoved(int x, int y) = 0; -}; - -class MouseButtonObserver -{ -public: - virtual void mouseButtonPressed(int button, int x, int y) = 0; - virtual void mouseButtonReleased(int button, int x, int y) = 0; -}; - -class MouseWheelObserver -{ -public: - virtual void mouseWheelScrolled(int x, int y) = 0; -}; - -class GamepadButtonObserver -{ -public: - virtual void gamepadButtonPressed(int button) = 0; - virtual void gamepadButtonReleased(int button) = 0; -}; - -class GamepadAxisObserver -{ -public: - virtual void gamepadAxisMoved(int axis, bool negative, float value) = 0; -}; - -class WindowObserver -{ -public: - virtual void windowClosed() = 0; - virtual void windowResized(int width, int height) = 0; -}; - -class InputDevice -{ -public: - enum class Type - { - KEYBOARD, - MOUSE, - GAMEPAD - }; - - InputDevice(const std::string& name); - const std::string& getName() const; - virtual InputDevice::Type getType() const = 0; - - void setDisconnected(bool disconnected); - bool isDisconnected() const; - -private: - std::string name; - bool disconnected; -}; - -inline const std::string& InputDevice::getName() const -{ - return name; -} - -inline bool InputDevice::isDisconnected() const -{ - return disconnected; -} - -class Keyboard: public InputDevice -{ -public: - Keyboard(const std::string& name); - virtual ~Keyboard(); - - InputDevice::Type getType() const; - - void addKeyObserver(KeyObserver* observer); - void removeKeyObserver(KeyObserver* observer); - void removeKeyObservers(); - - void press(int scancode); - void release(int scancode); - -private: - std::list keyObservers; -}; - -inline InputDevice::Type Keyboard::getType() const -{ - return InputDevice::Type::KEYBOARD; -} - -class Mouse: public InputDevice -{ -public: - Mouse(const std::string& name); - virtual ~Mouse(); - - InputDevice::Type getType() const; - - void addMouseMotionObserver(MouseMotionObserver* observer); - void addMouseButtonObserver(MouseButtonObserver* observer); - void addMouseWheelObserver(MouseWheelObserver* observer); - void removeMouseMotionObserver(MouseMotionObserver* observer); - void removeMouseButtonObserver(MouseButtonObserver* observer); - void removeMouseWheelObserver(MouseWheelObserver* observer); - void removeMouseMotionObservers(); - void removeMouseButtonObservers(); - void removeMouseWheelObservers(); - - void press(int button, int x, int y); - void release(int button, int x, int y); - void move(int x, int y); - void scroll(int x, int y); - - const glm::ivec2& getCurrentPosition() const; - const glm::ivec2& getPreviousPosition() const; - -private: - void processFlaggedMotionObservers(); - void processFlaggedButtonObservers(); - void processFlaggedWheelObservers(); - - glm::ivec2 currentPosition; - glm::ivec2 previousPosition; - std::list motionObservers; - std::list buttonObservers; - std::list wheelObservers; - bool notifyingMotionObservers; - bool notifyingButtonObservers; - bool notifyingWheelObservers; - std::list additionFlaggedMotionObservers; - std::list additionFlaggedButtonObservers; - std::list additionFlaggedWheelObservers; - std::list removalFlaggedMotionObservers; - std::list removalFlaggedButtonObservers; - std::list removalFlaggedWheelObservers; -}; - -inline InputDevice::Type Mouse::getType() const -{ - return InputDevice::Type::MOUSE; -} - -inline const glm::ivec2& Mouse::getCurrentPosition() const -{ - return currentPosition; -} - -inline const glm::ivec2& Mouse::getPreviousPosition() const -{ - return previousPosition; -} - -class Gamepad: public InputDevice -{ -public: - Gamepad(const std::string& name); - virtual ~Gamepad(); - - InputDevice::Type getType() const; - - void addGamepadButtonObserver(GamepadButtonObserver* observer); - void removeGamepadButtonObserver(GamepadButtonObserver* observer); - void removeGamepadButtonObservers(); - - void addGamepadAxisObserver(GamepadAxisObserver* observer); - void removeGamepadAxisObserver(GamepadAxisObserver* observer); - void removeGamepadAxisObservers(); - - void press(int button); - void release(int button); - void move(int axis, bool negative, float value); - -private: - std::list buttonObservers; - std::list axisObservers; -}; - -inline InputDevice::Type Gamepad::getType() const -{ - return InputDevice::Type::GAMEPAD; -} - -struct InputEvent -{ -public: - enum class Type - { - NONE, - KEY, - MOUSE_BUTTON, - MOUSE_WHEEL, - GAMEPAD_BUTTON, - GAMEPAD_AXIS - }; - - InputEvent(); - - InputEvent::Type type; - std::pair key; - std::pair mouseButton; - std::tuple mouseWheel; - std::pair gamepadButton; - std::tuple gamepadAxis; -}; - -class InputManager -{ -public: - InputManager(); - - // Processes input events - virtual void update() = 0; - - // Listens for the next input event, should be called BEFORE update() - virtual void listen(InputEvent* inputEvent) = 0; - - bool wasClosed() const; - - void addWindowObserver(WindowObserver* observer); - void removeWindowObserver(WindowObserver* observer); - void removeWindowObservers(); - - void registerKeyboard(Keyboard* keyboard); - void registerMouse(Mouse* mouse); - void registerGamepad(Gamepad* gamepad); - - void unregisterKeyboard(Keyboard* keyboard); - void unregisterMouse(Mouse* mouse); - void unregisterGamepad(Gamepad* gamepad); - - bool isRegistered(const Keyboard* keyboard) const; - bool isRegistered(const Mouse* mouse) const; - bool isRegistered(const Gamepad* gamepad) const; - - const Gamepad* getGamepad(const std::string& name) const; - Gamepad* getGamepad(const std::string& name); - - const std::list* getKeyboards() const; - const std::list* getMice() const; - const std::list* getGamepads() const; - -protected: - bool closed; - std::list windowObservers; - -private: - std::list keyboards; - std::list mice; - std::list gamepads; -}; - -inline bool InputManager::wasClosed() const -{ - return closed; -} - -inline const std::list* InputManager::getKeyboards() const -{ - return &keyboards; -} - -inline const std::list* InputManager::getMice() const -{ - return &mice; -} - -inline const std::list* InputManager::getGamepads() const -{ - return &gamepads; -} - -class SDLInputManager: public InputManager -{ -public: - SDLInputManager(); - ~SDLInputManager(); - - virtual void update(); - virtual void listen(InputEvent* event); - -private: - Keyboard* keyboard; - Mouse* mouse; - std::map gamepadMap; - SDL_Event event; - std::list allocatedGamepads; -}; - -#endif - diff --git a/src/main.cpp b/src/main.cpp old mode 100644 new mode 100755 index d14e57b..2968b88 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,25 +1,35 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#include "application.hpp" - -int main(int argc, char* argv[]) -{ - return Application(argc, argv).execute(); -} +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "game.hpp" + +int main(int argc, char* argv[]) +{ + try + { + return Game(argc, argv).execute(); + } + catch (const std::exception& e) + { + std::cerr << "Exception caught: \"" << e.what() << "\"" << std::endl; + } + + return EXIT_FAILURE; +} + diff --git a/src/material-loader.hpp b/src/material-loader.hpp deleted file mode 100644 index ef596d7..0000000 --- a/src/material-loader.hpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef MATERIAL_LOADER_HPP -#define MATERIAL_LOADER_HPP - -#include -using namespace Emergent; - -#include -#include - -class MaterialLoader -{ -public: - MaterialLoader(); - ~MaterialLoader(); - - void unload(); - - Material* load(const std::string& filename); - -private: - Shader* loadShader(const std::string& filename); - Texture2D* loadTexture2D(const std::string& filename); - TextureCube* loadTextureCube(const std::string& filename); - - bool loadShaderInt(ShaderInt* variable, const std::vector>& elements); - bool loadShaderFloat(ShaderFloat* variable, const std::vector>& elements); - bool loadShaderVector2(ShaderVector2* variable, const std::vector>& elements); - bool loadShaderVector3(ShaderVector3* variable, const std::vector>& elements); - bool loadShaderVector4(ShaderVector4* variable, const std::vector>& elements); - bool loadShaderMatrix3(ShaderMatrix3* variable, const std::vector>& elements); - bool loadShaderMatrix4(ShaderMatrix4* variable, const std::vector>& elements); - bool loadShaderTexture2D(ShaderTexture2D* variable, const std::vector>& elements); - bool loadShaderTextureCube(ShaderTextureCube* variable, const std::vector>& elements); - - std::map shaderCache; - std::map texture2DCache; - std::map textureCubeCache; - std::map materialCache; - - TextureLoader textureLoader; -}; - -#endif // MATERIAL_LOADER_HPP diff --git a/src/mesh.cpp b/src/mesh.cpp deleted file mode 100644 index 92f792e..0000000 --- a/src/mesh.cpp +++ /dev/null @@ -1,402 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#include "mesh.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -bool loadHeightmap(const std::string& filename, glm::vec3 scale, WingedEdge* mesh) -{ - int width; - int height; - int components; - - // Load image data - unsigned char* pixels = stbi_load(filename.c_str(), &width, &height, &components, 1); - if (!pixels) - { - std::cerr << "Failed to load heightmap image \"" << filename << "\"\n"; - return false; - } - - std::size_t vertexCount = width * height; - std::size_t triangleCount = (width - 1) * (height - 1) * 2; - std::size_t indexCount = triangleCount * 3; - - std::vector vertices(vertexCount); - std::vector indices(indexCount); - - // Adjust scale - scale.x *= 1.0f / ((float)width - 1); - scale.y *= 1.0f / 255.0f; - scale.z *= 1.0f / ((float)height - 1); - if (width > height) scale.z *= (float)height / (float) width; - else if (height > width) scale.x *= (float)width / (float)height; - - // Calculate centered offset - glm::vec3 offset; - offset.x = (float)width * -0.5f * scale.x; - offset.y = 0.0f; - offset.z = (float)height * -0.5f * scale.z; - - // Calculate vertex positions - for (int i = 0; i < height; ++i) - { - for (int j = 0; j < width; ++j) - { - std::size_t index = i * width + j; - - glm::vec3* vertex = &vertices[index]; - vertex->x = (float)j * scale.x + offset.x; - vertex->y = (float)pixels[index] * scale.y; - vertex->z = (float)i * scale.z + offset.z; - } - } - - // Free loaded image - stbi_image_free(pixels); - - // Generate indices - for (int i = 0; i < height - 1; ++i) - { - for (int j = 0; j < width - 1; ++j) - { - std::size_t a = i * width + j; - std::size_t b = (i + 1) * width + j; - std::size_t c = i * width + j + 1; - std::size_t d = (i + 1) * width + j + 1; - - std::size_t index = (i * (width - 1) + j) * 2 * 3; - indices[index] = a; - indices[index + 1] = b; - indices[index + 2] = c; - indices[index + 3] = c; - indices[index + 4] = b; - indices[index + 5] = d; - } - } - - return mesh->create(vertices, indices); -} - -bool loadHeightmapBase(const std::string& filename, glm::vec3 scale, float floor, WingedEdge* mesh) -{ - int width; - int height; - int components; - - // Load image data - unsigned char* pixels = stbi_load(filename.c_str(), &width, &height, &components, 1); - if (!pixels) - { - std::cerr << "Failed to load heightmap image \"" << filename << "\"\n"; - return false; - } - - std::size_t vertexCount = width * 4 + height * 4; - std::size_t triangleCount = (width - 1) * 4 + (height - 1) * 4; - std::size_t indexCount = triangleCount * 3; - - std::vector vertices(vertexCount); - std::vector indices(indexCount); - - // Adjust scale - scale.x *= 1.0f / ((float)width - 1); - scale.y *= 1.0f / 255.0f; - scale.z *= 1.0f / ((float)height - 1); - if (width > height) scale.z *= (float)height / (float) width; - else if (height > width) scale.x *= (float)width / (float)height; - - // Calculate centered offset - glm::vec3 offset; - offset.x = (float)width * -0.5f * scale.x; - offset.y = 0.0f; - offset.z = (float)height * -0.5f * scale.z; - - glm::vec3* vertex = &vertices[0]; - - // Top row - for (int j = 0; j < width; ++j) - { - int i = 0; - std::size_t index = i * width + j; - - vertex->x = (float)j * scale.x + offset.x; - vertex->y = (float)pixels[index] * scale.y; - vertex->z = (float)i * scale.z + offset.z; - ++vertex; - - vertex->x = (float)j * scale.x + offset.x; - vertex->y = floor; - vertex->z = (float)i * scale.z + offset.z; - ++vertex; - } - - // Bottom row - for (int j = 0; j < width; ++j) - { - int i = (height - 1); - std::size_t index = i * width + j; - - vertex->x = (float)j * scale.x + offset.x; - vertex->y = (float)pixels[index] * scale.y; - vertex->z = (float)i * scale.z + offset.z; - ++vertex; - - vertex->x = (float)j * scale.x + offset.x; - vertex->y = floor; - vertex->z = (float)i * scale.z + offset.z; - ++vertex; - } - - // Left column - for (int i = 0; i < height; ++i) - { - int j = 0; - std::size_t index = i * width + j; - - vertex->x = (float)j * scale.x + offset.x; - vertex->y = (float)pixels[index] * scale.y; - vertex->z = (float)i * scale.z + offset.z; - ++vertex;; - - vertex->x = (float)j * scale.x + offset.x; - vertex->y = floor; - vertex->z = (float)i * scale.z + offset.z; - ++vertex; - } - - // Right column - for (int i = 0; i < height; ++i) - { - int j = (width - 1); - std::size_t index = i * width + j; - - vertex->x = (float)j * scale.x + offset.x; - vertex->y = (float)pixels[index] * scale.y; - vertex->z = (float)i * scale.z + offset.z; - ++vertex; - - vertex->x = (float)j * scale.x + offset.x; - vertex->y = floor; - vertex->z = (float)i * scale.z + offset.z; - ++vertex; - } - - // Free loaded image - stbi_image_free(pixels); - - // Generate indices - std::size_t* index = &indices[0]; - - for (int i = 0; i < width - 1; ++i) - { - std::size_t a = i * 2; - std::size_t b = i * 2 + 1; - std::size_t c = (i + 1) * 2; - std::size_t d = (i + 1) * 2 + 1; - - (*(index++)) = b; - (*(index++)) = a; - (*(index++)) = c; - (*(index++)) = b; - (*(index++)) = c; - (*(index++)) = d; - - a += width * 2; - b += width * 2; - c += width * 2; - d += width * 2; - - (*(index++)) = a; - (*(index++)) = b; - (*(index++)) = c; - (*(index++)) = c; - (*(index++)) = b; - (*(index++)) = d; - } - - for (int i = 0; i < height - 1; ++i) - { - std::size_t a = width * 4 + i * 2; - std::size_t b = width * 4 + i * 2 + 1; - std::size_t c = width * 4 + (i + 1) * 2; - std::size_t d = width * 4 + (i + 1) * 2 + 1; - - (*(index++)) = a; - (*(index++)) = b; - (*(index++)) = c; - (*(index++)) = c; - (*(index++)) = b; - (*(index++)) = d; - - a += height * 2; - b += height * 2; - c += height * 2; - d += height * 2; - - (*(index++)) = b; - (*(index++)) = a; - (*(index++)) = c; - (*(index++)) = b; - (*(index++)) = c; - (*(index++)) = d; - } - - return mesh->create(vertices, indices); -} - -void move(const WingedEdge* mesh, WingedEdge::Triangle* triangle, const glm::vec3& start, const glm::vec3& target, std::vector* visited, glm::vec3* end) -{ - // Add triangle to visited list - visited->push_back(triangle); - - // Grab triangle coordinates - const glm::vec3& a = triangle->edge->vertex->position; - const glm::vec3& b = triangle->edge->next->vertex->position; - const glm::vec3& c = triangle->edge->previous->vertex->position; - - // Project target onto triangle - glm::vec3 closestPoint; - int edgeIndex = -1; - WingedEdge::Edge* closestEdge = nullptr; - project_on_triangle(target, a, b, c, &closestPoint, &edgeIndex); - *end = closestPoint; - - // Determine if projected target is on an edge - switch (edgeIndex) - { - case -1: - // Projected target inside triangle - return; - case 0: - closestEdge = triangle->edge; - break; - case 1: - closestEdge = triangle->edge->next; - break; - case 2: - closestEdge = triangle->edge->previous; - break; - } - - // If edge is not loose, repeat with connected triangle - if (closestEdge->symmetric != nullptr) - { - for (std::size_t i = 0; i < visited->size() - 1; ++i) - { - if ((*visited)[i] == closestEdge->symmetric->triangle) - return; - } - - move(mesh, closestEdge->symmetric->triangle, closestPoint, target, visited, end); - } -} - -std::tuple intersects(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& a, const glm::vec3& b, const glm::vec3& c) -{ - // Find edges - glm::vec3 edge10 = b - a; - glm::vec3 edge20 = c - a; - - // Calculate determinant - glm::vec3 pv = glm::cross(direction, edge20); - float det = glm::dot(edge10, pv); - if (!det) - { - return std::make_tuple(false, std::numeric_limits::infinity(), 0.0f, 0.0f); - } - float inverseDet = 1.0f / det; - - // Calculate u - glm::vec3 tv = origin - a; - float u = glm::dot(tv, pv) * inverseDet; - - if (u < 0.0f || u > 1.0f) - { - return std::make_tuple(false, std::numeric_limits::infinity(), 0.0f, 0.0f); - } - - // Calculate v - glm::vec3 qv = glm::cross(tv, edge10); - float v = glm::dot(direction, qv) * inverseDet; - - if (v < 0.0f || u + v > 1.0f) - { - return std::make_tuple(false, std::numeric_limits::infinity(), 0.0f, 0.0f); - } - - // Calculate t - float t = glm::dot(edge20, qv) * inverseDet; - - if (t > 0.0f) - { - return std::make_tuple(true, t, u, v); - } - - return std::make_tuple(false, std::numeric_limits::infinity(), 0.0f, 0.0f); -} - -std::tuple intersects(const glm::vec3& origin, const glm::vec3& direction, const WingedEdge& mesh) -{ - const std::vector* triangles = mesh.getTriangles(); - bool intersection = false; - float t0 = std::numeric_limits::infinity(); - float t1 = -std::numeric_limits::infinity(); - std::size_t index0 = triangles->size(); - std::size_t index1 = index0; - - for (std::size_t i = 0; i < triangles->size(); ++i) - { - const WingedEdge::Triangle* triangle = (*triangles)[i]; - const glm::vec3& a = triangle->edge->vertex->position; - const glm::vec3& b = triangle->edge->next->vertex->position; - const glm::vec3& c = triangle->edge->previous->vertex->position; - - auto result = intersects(origin, direction, a, b, c); - if (std::get<0>(result)) - { - intersection = true; - - float t = std::get<1>(result); - float cosTheta = glm::dot(direction, triangle->normal); - - if (cosTheta <= 0.0f) - { - // Front-facing - t0 = std::min(t0, t); - index0 = i; - } - else - { - // Back-facing - t1 = std::max(t1, t); - index1 = i; - } - } - } - - return std::make_tuple(intersection, t0, t1, index0, index1); -} diff --git a/src/mesh.hpp b/src/mesh.hpp deleted file mode 100644 index 903ea40..0000000 --- a/src/mesh.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef MESH_HPP -#define MESH_HPP - -#include -#include -#include -#include - -using namespace Emergent; - -struct navigator -{ - glm::vec3 position; // World-space cartesian coordinates - - float heading; - - WingedEdge::Triangle* triangle; - glm::vec3 barycentric; // Current barycentric coordinates -}; - - -bool loadHeightmap(const std::string& filename, glm::vec3 scale, WingedEdge* mesh); - -bool loadHeightmapBase(const std::string& filename, glm::vec3 scale, float floor, WingedEdge* mesh); - -// Recursive function which moves a navigator along the surface of a navmesh. -void move(const WingedEdge* mesh, WingedEdge::Triangle* triangle, const glm::vec3& start, const glm::vec3& target, std::vector* visited, glm::vec3* end); - -// Checks for intersection between a ray and a triangle -// The first element in the tuple indicates whether or not an intersection occurred. The second element indicates the distance from the origin to point of intersection. The third element is the barycentric U coordinate. The fourth element is the barycentric V coordinate. -// @see http://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm -// @see http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-9-ray-triangle-intersection/m-ller-trumbore-algorithm/ -std::tuple intersects(const glm::vec3& origin, const glm::vec3& direction, const WingedEdge::Triangle& triangle); - -// Checks for intersection between a ray and a mesh. -// The first element in the tuple indicates whether or not an intersection occurred. The second and third elements indicate the distance from the origin to the nearest and farthest points of intersection, respectively. The fourth and fifth elements contain the indices of the nearest and farthest intersected triangles, respectively. -std::tuple intersects(const glm::vec3& origin, const glm::vec3& direction, const WingedEdge& mesh); - -#endif // MESH_HPP - diff --git a/src/model-loader.hpp b/src/model-loader.hpp deleted file mode 100644 index 5008d29..0000000 --- a/src/model-loader.hpp +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef MODEL_LOADER_HPP -#define MODEL_LOADER_HPP - -#include -#include - -using namespace Emergent; - -class MaterialLoader; - -class ModelLoader -{ -public: - ModelLoader(); - ~ModelLoader(); - Model* load(const std::string& filename); - - void setMaterialLoader(MaterialLoader* materialLoader); - -private: - struct MaterialGroup - { - std::string materialName; - std::uint32_t indexOffset; - std::uint32_t triangleCount; - AABB bounds; - }; - - enum VertexFlags - { - UV = 1, - TANGENT = 2, - WEIGHTS = 4 - }; - - struct ModelData - { - std::uint32_t groupCount; - MaterialGroup* groups; - std::uint32_t vertexFormat; - std::uint32_t vertexCount; - AABB bounds; - float* vertexData; - std::uint32_t* indexData; - }; - - struct BoneData - { - std::string name; - std::uint16_t parent; - std::uint16_t childCount; - std::uint16_t* children; - Vector3 translation; - Quaternion rotation; - float length; - }; - - struct KeyFrameData - { - float time; - Transform transform; - }; - - struct ChannelData - { - std::uint16_t id; - std::uint16_t keyFrameCount; - KeyFrameData* keyFrames; - }; - - struct AnimationData - { - std::string name; - float startTime; - float endTime; - std::uint16_t channelCount; - ChannelData* channels; - }; - - struct SkeletonData - { - std::uint16_t boneCount; - BoneData* bones; - std::uint16_t animationCount; - AnimationData* animations; - }; - - static void constructBoneHierarchy(Bone* bone, const BoneData* data, std::uint16_t index); - - MaterialLoader* materialLoader; -}; - -#endif // MODEL_LOADER_HPP diff --git a/src/paths.cpp b/src/paths.cpp new file mode 100644 index 0000000..ce6b646 --- /dev/null +++ b/src/paths.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "paths.hpp" +#include +#include +#include +#include +#include +#include +#include + +#include + +static std::string getExecutablePath() +{ + char path[PATH_MAX]; + ssize_t length = ::readlink("/proc/self/exe", path, sizeof(path) - 1); + if (length != -1) + { + path[length] = '\0'; + return std::string(path); + } + + return std::string(); +} + +std::string getDataPath() +{ + std::string executablePath = getExecutablePath(); + std::size_t delimeter = executablePath.find_last_of("\\/") + 1; + std::string executableName = executablePath.substr(delimeter, executablePath.size() - delimeter + 1); + return executablePath.substr(0, delimeter) + std::string("../share/") + executableName + std::string("/"); +} + +std::string getConfigPath() +{ + std::string executablePath = getExecutablePath(); + std::size_t delimeter = executablePath.find_last_of("\\/") + 1; + std::string executableName = executablePath.substr(delimeter, executablePath.size() - delimeter + 1); + std::string configPath; + + // Determine home path + std::string homePath = std::string(getpwuid(getuid())->pw_dir); + + // Determine config path + char* xdgConfigHome = std::getenv("XDG_CONFIG_HOME"); + if (!xdgConfigHome) + { + // Default to $HOME/.config/ as per: + // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables + configPath = homePath + std::string("/.config/") + executableName + std::string("/"); + } + else + { + configPath = xdgConfigHome + std::string("/") + executableName + std::string("/"); + } + + return configPath; +} + +bool pathExists(const std::string& path) +{ + struct stat info; + return (stat(path.c_str(), &info) == 0); +} + +bool createDirectory(const std::string& path) +{ + int error = 0; + #if defined(_WIN32) + error = _mkdir(path.c_str()); + #else + error = mkdir(path.c_str(), 0777); + #endif + + return (error == 0); +} + diff --git a/src/paths.hpp b/src/paths.hpp new file mode 100644 index 0000000..cc9d805 --- /dev/null +++ b/src/paths.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef PATHS_HPP +#define PATHS_HPP + +#include + +/// Returns the path to the executable's data +std::string getDataPath(); + +/// Returns the path to the executable's config files +std::string getConfigPath(); + +/// Checks if a file or directory exists +bool pathExists(const std::string& path); + +/// Creates a directory +bool createDirectory(const std::string& path); + +#endif // PATHS_HPP + diff --git a/src/render-passes.hpp b/src/render-passes.hpp deleted file mode 100644 index 50c935f..0000000 --- a/src/render-passes.hpp +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef RENDER_PASSES_HPP -#define RENDER_PASSES_HPP - -#include -using namespace Emergent; - -#include "material-loader.hpp" -#include "model-loader.hpp" -#include - -/** - * Clears framebuffers - */ -class ClearRenderPass: public RenderPass -{ -public: - ClearRenderPass(); - virtual bool load(const RenderContext* renderContext); - virtual void unload(); - virtual void render(RenderContext* renderContext); - - void setClear(bool color, bool depth, bool stencil); - void setClearColor(const Vector4& color); - void setClearDepth(float depth); - void setClearStencil(int index); - -private: - bool clearColor; - bool clearDepth; - bool clearStencil; - Vector4 color; - float depth; - int index; -}; - -/** - * Blurs a texture - */ -class BlurRenderPass: public RenderPass -{ -public: - BlurRenderPass(); - virtual bool load(const RenderContext* renderContext); - virtual void unload(); - virtual void render(RenderContext* renderContext); - - inline void setGammaCorrect(bool gammaCorrect) { this->gammaCorrect = gammaCorrect; } - - /** - * Sets the texture to blur. - */ - inline void setTexture(const Texture2D* texture) { textureParam.setValue(texture); } - - /** - * Sets the direction of the blur. - */ - inline void setDirection(const Vector2& direction) { directionParam.setValue(direction); } - -private: - bool gammaCorrect; - - Shader shader; - std::uint32_t permutation; - ShaderTexture2D textureParam; - ShaderVector2 resolutionParam; - ShaderVector2 directionParam; - - int quadVertexCount; - int quadIndexCount; - GLuint quadVAO; - GLuint quadVBO; - GLuint quadIBO; -}; - -/** - * Renders the distance from the view frustum's near clipping plane to scene geometry. The render target should have a depth only framebuffer. - */ -class ShadowMapRenderPass: public RenderPass -{ -public: - ShadowMapRenderPass(); - - virtual bool load(const RenderContext* renderContext); - virtual void unload(); - virtual void render(RenderContext* renderContext); - - inline void setViewCamera(const Camera* camera) { this->viewCamera = camera; } - inline void setLightCamera(Camera* camera) { this->lightCamera = camera; } - - inline const SplitViewFrustum& getSplitViewFrustum() const { return *splitViewFrustum; } - inline const Matrix4& getCropMatrix(std::size_t index) const { return cropMatrices[index]; } - inline const Matrix4& getTileMatrix(std::size_t index) const { return tileMatrices[index]; } - -private: - class RenderOpCompare - { - public: - // Sort render opations - bool operator()(const RenderOperation& opA, const RenderOperation& opB) const; - }; - - Shader shader; - std::uint32_t unskinnedPermutation; - std::uint32_t skinnedPermutation; - ShaderMatrix4 modelViewProjectionParam; - ShaderMatrix4* matrixPaletteParam; // data not used, just getConnectedInput() then pass pose matrix palette pointer directly - - int maxBoneCount; - int shadowMapResolution; - int croppedShadowMapResolution; - Vector4* croppedShadowMapViewports; - Matrix4* cropMatrices; - Matrix4* tileMatrices; - const Camera* viewCamera; - Camera* lightCamera; - SplitViewFrustum* splitViewFrustum; -}; - -/** - * Renders scene geometry. - */ -class LightingRenderPass: public RenderPass -{ -public: - LightingRenderPass(); - - virtual bool load(const RenderContext* renderContext); - virtual void unload(); - virtual void render(RenderContext* renderContext); - - inline void setShadowMap(const Texture2D* shadowMap) { this->shadowMap = shadowMap; } - inline void setShadowCamera(const Camera* camera) { this->shadowCamera = camera; } - inline void setShadowMapPass(const ShadowMapRenderPass* shadowMapPass) { this->shadowMapPass = shadowMapPass; } - inline void setDiffuseCubemap(const TextureCube* cubemap) { this->diffuseCubemap = cubemap; } - inline void setSpecularCubemap(const TextureCube* cubemap) { this->specularCubemap = cubemap; } - -private: - class RenderOpCompare - { - public: - // Sort render opations - bool operator()(const RenderOperation& opA, const RenderOperation& opB) const; - }; - - struct ParameterSet - { - ShaderMatrix4* matrixPalette; - ShaderMatrix4 modelMatrix; - ShaderMatrix4 modelViewMatrix; - ShaderMatrix4 modelViewProjectionMatrix; - ShaderMatrix3 normalModelViewMatrix; - ShaderMatrix3 normalModelMatrix; - ShaderMatrix4* lightViewProjectionMatrices; - ShaderVector4 splitDistances; - ShaderTexture2D shadowMap; - ShaderVector3 cameraPosition; - - ShaderTextureCube diffuseCubemap; - ShaderTextureCube specularCubemap; - - ShaderInt directionalLightCount; - ShaderVector3* directionalLightColors; - ShaderVector3* directionalLightDirections; - - ShaderInt spotlightCount; - ShaderVector3 spotlightColors; - ShaderVector3 spotlightPositions; - ShaderVector3 spotlightAttenuations; - ShaderVector3 spotlightDirections; - ShaderFloat spotlightCutoffs; - ShaderFloat spotlightExponents; - - ShaderTexture2D albedoOpacityMap; - ShaderTexture2D metalnessRoughnessMap; - ShaderTexture2D normalOcclusionMap; - - // Material shader parameters (uploaded by material directly) - //ShaderTexture2D albedoOpacityMap; - //ShaderTexture2D metalnessRoughnessMap; - //ShaderTexture2D normalOcclusionMap; - }; - - //std::map parameterSets; - - Shader shader; - ParameterSet parameters; - std::uint32_t unskinnedPermutation; - std::uint32_t skinnedPermutation; - - int maxBoneCount; - int maxDirectionalLightCount; - int maxSpotlightCount; - - - Matrix4 biasMatrix; - const Texture2D* shadowMap; - const TextureCube* diffuseCubemap; - const TextureCube* specularCubemap; - const Camera* shadowCamera; - float time; - const ShadowMapRenderPass* shadowMapPass; -}; - -/** - * Renders bounding boxes, skeletons - */ -class DebugRenderPass: public RenderPass -{ -public: - DebugRenderPass(); - virtual bool load(const RenderContext* renderContext); - virtual void unload(); - virtual void render(RenderContext* renderContext); - - //void setDrawBounds(bool enabled); - //void setDrawSkeletons(bool enabled); - //void setDrawCameras(bool enabled); - //void setDrawLights(bool enabled); - -private: - Shader shader; - std::uint32_t permutation; - ShaderMatrix4 modelViewProjectionMatrixParam; - - int aabbVertexCount; - int aabbIndexCount; - GLuint aabbVAO; - GLuint aabbVBO; - GLuint aabbIBO; -}; - -/** - * Renders the user interface - */ -class UIRenderPass: public RenderPass -{ -public: - UIRenderPass(); - virtual bool load(const RenderContext* renderContext); - virtual void unload(); - virtual void render(RenderContext* renderContext); - -private: - Shader shader; - std::uint32_t texturedPermutation; - std::uint32_t untexturedPermutation; - - ShaderMatrix4 modelViewProjectionMatrixParam; - ShaderTexture2D textureParam; - ShaderVector2 textureOffsetParam; - ShaderVector2 textureScaleParam; -}; - -/** - * Renders a vignette - */ -/* -class VignetteRenderPass: public RenderPass -{ -public: - VignetteRenderPass(); - virtual bool load(const RenderContext* renderContext); - virtual void unload(); - virtual void render(RenderContext* renderContext); - -private: - ShaderParameterSet parameterSet; - const ShaderParameter* modelViewProjectionParam; - const ShaderParameter* bayerTextureParam; - - ShaderLoader shaderLoader; - Shader* shader; - GLuint bayerTextureID; -}; -*/ - -/** - * Renders a skybox - */ -class SkyboxRenderPass: public RenderPass -{ -public: - SkyboxRenderPass(); - - inline void setCubemap(TextureCube* cubemap) { this->cubemap = cubemap; } - virtual bool load(const RenderContext* renderContext); - virtual void unload(); - virtual void render(RenderContext* renderContext); - -private: - Shader shader; - std::uint32_t permutation; - ShaderMatrix4 matrixParam; - ShaderTextureCube cubemapParam; - TextureCube* cubemap; - - int quadVertexCount; - int quadIndexCount; - GLuint quadVAO; - GLuint quadVBO; - GLuint quadIBO; -}; - -#endif // RENDER_PASSES_HPP diff --git a/src/resources/csv-table-loader.cpp b/src/resources/csv-table-loader.cpp new file mode 100644 index 0000000..9c34009 --- /dev/null +++ b/src/resources/csv-table-loader.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "resource-loader.hpp" +#include "csv-table.hpp" + +static CSVRow parseRow(const std::string& line) +{ + std::vector row; + std::string column; + bool quoted = false; + bool escape = false; + + for (char c: line) + { + if (escape) + { + switch (c) + { + case 'n': + column.push_back('\n'); + break; + + case 't': + column.push_back('\t'); + break; + + default: + column.push_back(c); + break; + } + + escape = false; + } + else + { + switch (c) + { + case '\\': + escape = true; + break; + + case ',': + if (quoted) + { + column.push_back(c); + } + else + { + row.push_back(column); + column.clear(); + } + break; + + case '"': + if (!quoted) + { + quoted = true; + } + else + { + quoted = false; + } + break; + + default: + column.push_back(c); + break; + } + } + } + + row.push_back(column); + + return row; +} + +template <> +CSVTable* ResourceLoader::load(ResourceManager* resourceManager, std::istream* is) +{ + CSVTable* table = new CSVTable(); + std::string line; + + while (!is->eof()) + { + std::getline(*is, line); + if (is->bad() || is->fail()) + { + break; + } + + table->push_back(parseRow(line)); + } + + return table; +} + diff --git a/src/resources/csv-table.hpp b/src/resources/csv-table.hpp new file mode 100644 index 0000000..c257bc7 --- /dev/null +++ b/src/resources/csv-table.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef CSV_TABLE_HPP +#define CSV_TABLE_HPP + +#include +#include + +typedef std::vector CSVRow; +typedef std::vector CSVTable; + +#endif // CSV_TABLE_HPP + diff --git a/src/resources/entity-template-loader.cpp b/src/resources/entity-template-loader.cpp new file mode 100644 index 0000000..edad6ab --- /dev/null +++ b/src/resources/entity-template-loader.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "resource-loader.hpp" +#include "resource-manager.hpp" +#include "csv-table.hpp" +#include "../entity/components/ant-hill-component.hpp" +#include "../entity/components/collision-component.hpp" +#include "../entity/components/model-component.hpp" +#include "../entity/components/tool-component.hpp" +#include "../entity/components/transform-component.hpp" +#include "../entity/entity-template.hpp" + +#include +#include + +static ComponentBase* loadAntHillComponent(const std::vector& parameters) +{ + if (parameters.size() != 1) + { + throw std::runtime_error("loadAntHillComponent(): Invalid parameter count."); + } + + AntHillComponent* component = new AntHillComponent(); + + return component; +} + +static ComponentBase* loadCollisionComponent(ResourceManager* resourceManager, const std::vector& parameters) +{ + if (parameters.size() != 2) + { + throw std::runtime_error("loadCollisionComponent(): Invalid parameter count."); + } + + CollisionComponent* component = new CollisionComponent(); + component->mesh = resourceManager->load(parameters[1]); + + return component; +} + +static ComponentBase* loadModelComponent(ResourceManager* resourceManager, const std::vector& parameters) +{ + if (parameters.size() != 2) + { + throw std::runtime_error("loadModelComponent(): Invalid parameter count."); + } + + std::string filename = parameters[1]; + Model* model = resourceManager->load(filename); + if (!model) + { + std::string message = std::string("loadModelComponent(): Failed to load model \"") + filename + std::string("\""); + throw std::runtime_error(message); + } + + ModelComponent* component = new ModelComponent(); + component->model.setModel(model); + component->model.setPose(nullptr); + + return component; +} + +static ComponentBase* loadToolComponent(const std::vector& parameters) +{ + if (parameters.size() != 1) + { + throw std::runtime_error("loadToolComponent(): Invalid parameter count."); + } + + ToolComponent* component = new ToolComponent(); + + return component; +} + +static ComponentBase* loadTransformComponent(const std::vector& parameters) +{ + if (parameters.size() != 11) + { + throw std::runtime_error("loadTransformComponent(): Invalid parameter count."); + } + + Vector3 translation; + Quaternion rotation; + Vector3 scale; + + std::stringstream stream; + for (std::size_t i = 1; i < parameters.size(); ++i) + { + stream << parameters[i]; + if (i < parameters.size() - 1) + stream << ' '; + } + + stream >> translation.x; + stream >> translation.y; + stream >> translation.z; + stream >> rotation.w; + stream >> rotation.x; + stream >> rotation.y; + stream >> rotation.z; + stream >> scale.x; + stream >> scale.y; + stream >> scale.z; + + TransformComponent* component = new TransformComponent(); + component->transform.translation = translation; + component->transform.rotation = rotation; + component->transform.scale = scale; + + return component; +} + +static ComponentBase* loadComponent(ResourceManager* resourceManager, const std::vector& parameters) +{ + if (parameters[0] == "ant-hill") return loadAntHillComponent(parameters); + if (parameters[0] == "collision") return loadCollisionComponent(resourceManager, parameters); + if (parameters[0] == "model") return loadModelComponent(resourceManager, parameters); + if (parameters[0] == "tool") return loadToolComponent(parameters); + if (parameters[0] == "transform") return loadTransformComponent(parameters); + + std::string message = std::string("loadComponent(): Unknown component type \"") + parameters[0] + std::string("\""); + throw std::runtime_error(message); +} + +template <> +EntityTemplate* ResourceLoader::load(ResourceManager* resourceManager, std::istream* is) +{ + std::list components; + EntityTemplate* entityTemplate = nullptr; + + // Load CSV table from input stream + CSVTable* table = ResourceLoader::load(resourceManager, is); + + // Ensure table is not empty. + if (!table || table->empty()) + { + delete table; + return nullptr; + } + + // Load components from table rows + for (const CSVRow& row: *table) + { + // Skip empty rows and comments + if (row.empty() || row[0].empty() || row[0][0] == '#') + { + continue; + } + + ComponentBase* component = loadComponent(resourceManager, row); + if (component != nullptr) + { + components.push_back(component); + } + } + + // Create entity template and free loaded components + entityTemplate = new EntityTemplate(components); + for (ComponentBase* component: components) + { + delete component; + } + + return entityTemplate; +} + diff --git a/src/resources/image-loader.cpp b/src/resources/image-loader.cpp new file mode 100644 index 0000000..9a48a15 --- /dev/null +++ b/src/resources/image-loader.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "resource-loader.hpp" +#include "stb/stb_image.h" +#include "image.hpp" +#include + +template <> +Image* ResourceLoader::load(ResourceManager* resourceManager, std::istream* is) +{ + unsigned char* buffer; + std::size_t size; + int width; + int height; + int channels; + bool hdr; + void* pixels; + + // Read input stream into buffer + is->seekg(0, is->end); + size = is->tellg(); + buffer = new unsigned char[size]; + is->seekg(0, is->beg); + is->read(reinterpret_cast(&buffer[0]), size); + + // Determine if image is in an HDR format + hdr = (stbi_is_hdr_from_memory(buffer, size) != 0); + + // Set vertical flip on load in order to upload pixels correctly to OpenGL + stbi_set_flip_vertically_on_load(true); + + // Load image data + if (hdr) + { + pixels = stbi_loadf_from_memory(buffer, size, &width, &height, &channels, 0); + } + else + { + pixels = stbi_load_from_memory(buffer, size, &width, &height, &channels, 0); + } + + // Free file buffer + delete[] buffer; + + // Check if image was loaded + if (!pixels) + { + throw std::runtime_error("STBI failed to load image from memory."); + } + + Image* image = new Image(); + image->format(static_cast(channels), hdr); + image->resize(static_cast(width), static_cast(height)); + std::memcpy(image->getPixels(), pixels, width * height * channels * ((hdr) ? sizeof(float) : sizeof(unsigned char))); + + // Free loaded image data + stbi_image_free(pixels); + + return image; +} + diff --git a/src/resources/image.cpp b/src/resources/image.cpp new file mode 100644 index 0000000..7bd6331 --- /dev/null +++ b/src/resources/image.cpp @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "image.hpp" + +Image::Image(): + hdr(false), + width(0), + height(0), + channels(4), + pixels(nullptr) +{} + +Image::~Image() +{ + freePixels(); +} + +void Image::format(unsigned int channels, bool hdr) +{ + if (this->channels == channels && this->hdr == hdr) + { + return; + } + + freePixels(); + this->channels = channels; + this->hdr = hdr; + allocatePixels(); +} + +void Image::resize(unsigned int width, unsigned int height) +{ + if (this->width == width && this->height == height) + { + return; + } + + freePixels(); + this->width = width; + this->height = height; + allocatePixels(); +} + +void Image::allocatePixels() +{ + if (hdr) + { + pixels = new float[width * height * channels]; + } + else + { + pixels = new unsigned char[width * height * channels]; + } +} + +void Image::freePixels() +{ + if (pixels != nullptr) + { + if (hdr) + { + delete[] reinterpret_cast(pixels); + } + else + { + delete[] reinterpret_cast(pixels); + } + + pixels = nullptr; + } +} + diff --git a/src/resources/image.hpp b/src/resources/image.hpp new file mode 100644 index 0000000..b1968b5 --- /dev/null +++ b/src/resources/image.hpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef IMAGE_HPP +#define IMAGE_HPP + +/** + * Stores basic image data. + */ +class Image +{ +public: + /// Creates an image. + Image(); + + /// Destroys an image. + ~Image(); + + /** + * Changes the format of the image. Existing pixel data will be erased if the format has changed. + * + * @param channels Number of color channels. + * @param hdr Whether or not the image will contain HDR data. HDR pixels are stored as floats. Standard pixels are stored as unsigned chars. + */ + void format(unsigned int channels, bool hdr); + + /** + * Resizes the image. Existing pixel data will be erased if the size has changed. + * + * @param width New width of the image, in pixels. + * @param height New height of the image, in pixels. + */ + void resize(unsigned int width, unsigned int height); + + /// Returns whether or not the image contains HDR data. + bool isHDR() const; + + /// Returns the width of the image, in pixels. + unsigned int getWidth() const; + + /// Returns the height of the image, in pixels. + unsigned int getHeight() const; + + /// Returns the number of color channels in the image. + unsigned int getChannels() const; + + /// Returns a pointer to the pixel data. + const void* getPixels() const; + + /// @copydoc Image::getPixels() const + void* getPixels(); + +private: + void allocatePixels(); + void freePixels(); + + bool hdr; + unsigned int width; + unsigned int height; + unsigned int channels; + void* pixels; +}; + +inline bool Image::isHDR() const +{ + return hdr; +} + +inline unsigned int Image::getWidth() const +{ + return width; +} + +inline unsigned int Image::getHeight() const +{ + return height; +} + +inline unsigned int Image::getChannels() const +{ + return channels; +} + +inline const void* Image::getPixels() const +{ + return pixels; +} + +inline void* Image::getPixels() +{ + return pixels; +} + +#endif // IMAGE_HPP + diff --git a/src/material-loader.cpp b/src/resources/material-loader.cpp old mode 100644 new mode 100755 similarity index 67% rename from src/material-loader.cpp rename to src/resources/material-loader.cpp index 386c6ff..0dbc9d3 --- a/src/material-loader.cpp +++ b/src/resources/material-loader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -17,74 +17,205 @@ * along with Antkeeper Source Code. If not, see . */ -#include "material-loader.hpp" +#include "resource-loader.hpp" +#include "resource-manager.hpp" #include -#include -#include +#include #include #include -MaterialLoader::MaterialLoader() +#include +using namespace Emergent; + +static bool loadShaderInt(ShaderInt* variable, const std::vector>& elements) { - textureLoader.setGamma(1.0f); - textureLoader.setMipmapChain(false); - textureLoader.setMaxAnisotropy(1.0f); + for (int i = 0; i < elements.size(); ++i) + { + int value; + std::stringstream stream; + stream << elements[i][0]; + stream >> value; + + variable->setValue(i, value); + } + + return true; +} + +static bool loadShaderFloat(ShaderFloat* variable, const std::vector>& elements) +{ + for (int i = 0; i < elements.size(); ++i) + { + float value; + std::stringstream stream; + stream << elements[i][0]; + stream >> value; + + variable->setValue(i, value); + } + + return true; } -MaterialLoader::~MaterialLoader() +static bool loadShaderVector2(ShaderVector2* variable, const std::vector>& elements) { - unload(); + for (int i = 0; i < elements.size(); ++i) + { + Vector2 value; + + for (int j = 0; j < 2; ++j) + { + std::stringstream stream; + stream << elements[i][j]; + stream >> value[j]; + } + + variable->setValue(i, value); + } + + return true; } -void MaterialLoader::unload() +static bool loadShaderVector3(ShaderVector3* variable, const std::vector>& elements) { - for (auto it = materialCache.begin(); it != materialCache.end(); ++it) + for (int i = 0; i < elements.size(); ++i) { - delete it->second; + Vector3 value; + + for (int j = 0; j < 3; ++j) + { + std::stringstream stream; + stream << elements[i][j]; + stream >> value[j]; + } + + variable->setValue(i, value); } - materialCache.clear(); - for (auto it = texture2DCache.begin(); it != texture2DCache.end(); ++it) + return true; +} + +static bool loadShaderVector4(ShaderVector4* variable, const std::vector>& elements) +{ + for (int i = 0; i < elements.size(); ++i) { - delete it->second; + Vector4 value; + + for (int j = 0; j < 4; ++j) + { + std::stringstream stream; + stream << elements[i][j]; + stream >> value[j]; + } + + variable->setValue(i, value); } - texture2DCache.clear(); - for (auto it = textureCubeCache.begin(); it != textureCubeCache.end(); ++it) + return true; +} + +static bool loadShaderMatrix3(ShaderMatrix3* variable, const std::vector>& elements) +{ + for (int i = 0; i < elements.size(); ++i) { - delete it->second; + Matrix3 value; + + for (int j = 0; j < 3; ++j) + { + for (int k = 0; k < 3; ++k) + { + std::stringstream stream; + stream << elements[i][k * 3 + j]; + stream >> value[j][k]; + } + } + + variable->setValue(i, value); } - textureCubeCache.clear(); + + return true; } -Material* MaterialLoader::load(const std::string& filename) +static bool loadShaderMatrix4(ShaderMatrix4* variable, const std::vector>& elements) { - // Check if material exists in cache - auto it = materialCache.find(filename); - if (it != materialCache.end()) + for (int i = 0; i < elements.size(); ++i) { - return it->second; + Matrix4 value; + + for (int j = 0; j < 4; ++j) + { + for (int k = 0; k < 4; ++k) + { + std::stringstream stream; + stream << elements[i][k * 4 + j]; + stream >> value[j][k]; + } + } + + variable->setValue(i, value); } - // Allocate new material - Material* material = new Material(); + return true; +} + +static bool loadShaderTexture2D(ResourceManager* resourceManager, ShaderTexture2D* variable, const std::vector>& elements) +{ + for (int i = 0; i < elements.size(); ++i) + { + std::string filename; + std::stringstream stream; + stream << elements[i][0]; + stream >> filename; + + Texture2D* value = resourceManager->load(filename); + + variable->setValue(i, value); + } - // Open file - std::ifstream file(filename.c_str(), std::ifstream::in); - if (!file.is_open()) + return true; +} + +static bool loadShaderTextureCube(ResourceManager* resourceManager, ShaderTextureCube* variable, const std::vector>& elements) +{ + for (int i = 0; i < elements.size(); ++i) { +<<<<<<< HEAD:src/resources/material-loader.cpp + std::string filename; + std::stringstream stream; + stream << elements[i][0]; + stream >> filename; + + TextureCube* value = resourceManager->load(filename); + + variable->setValue(i, value); +======= std::cerr << std::string("MaterialLoader::load(): Failed to open material file \"") << filename << std::string("\"") << std::endl; delete material; return nullptr; +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036:src/material-loader.cpp } + return true; +} + +template <> +Material* ResourceLoader::load(ResourceManager* resourceManager, std::istream* is) +{ + // Allocate new material + Material* material = new Material(); + std::string line; std::size_t lineNumber = 0; const std::string whitespace = " \t\r\n"; // Parse lines - while (file.good() && std::getline(file, line)) + while (is->good() && std::getline(*is, line)) { + if (is->bad() || is->fail()) + { + break; + } + // Increment current line number ++lineNumber; @@ -112,18 +243,32 @@ Material* MaterialLoader::load(const std::string& filename) std::size_t equalsSignPosition = line.find_first_of("=", commandPosition); if (equalsSignPosition == std::string::npos) { +<<<<<<< HEAD:src/resources/material-loader.cpp + // Line has no equals sign + std::stringstream stream; + stream << "ResourceLoader::load(): Invalid line " << lineNumber << "."; + throw std::runtime_error(stream.str().c_str()); +======= // Skip lines with no equals sign std::cerr << std::string("MaterialLoader::load(): Invalid line ") << lineNumber << std::string(" in \"") << filename << std::string("\"") << std::endl; continue; +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036:src/material-loader.cpp } // Find position of first character in the value string std::size_t valueStartPosition = line.find_first_not_of(whitespace, equalsSignPosition + 1); if (valueStartPosition == std::string::npos) { +<<<<<<< HEAD:src/resources/material-loader.cpp + // Line has no value + std::stringstream stream; + stream << "ResourceLoader::load(): Invalid line " << lineNumber << "."; + throw std::runtime_error(stream.str().c_str()); +======= // Skip lines with no value std::cerr << std::string("MaterialLoader::load(): Invalid line ") << lineNumber << std::string(" in \"") << filename << std::string("\"") << std::endl; continue; +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036:src/material-loader.cpp } // Find position the end of the value string @@ -144,15 +289,22 @@ Material* MaterialLoader::load(const std::string& filename) if (command == "shader") { // Load shader - Shader* shader = loadShader(valueString); - if (!shader) + Shader* shader = nullptr; + try { +<<<<<<< HEAD:src/resources/material-loader.cpp + shader = resourceManager->load(valueString); +======= std::cerr << std::string("MaterialLoader::load(): Failed to load shader \"") << valueString << std::string("\" on line ") << lineNumber << std::string(" in \"") << filename << std::string("\"") << std::endl; +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036:src/material-loader.cpp } - else + catch (const std::exception& e) { - material->setShader(shader); + std::string error = std::string("ResourceLoader::load(): Failed to load shader \"") + valueString + std::string("\": \"") + e.what() + std::string("\""); + throw std::runtime_error(error.c_str()); } + + material->setShader(shader); } else { @@ -161,7 +313,7 @@ Material* MaterialLoader::load(const std::string& filename) std::stringstream stream; stream << valueString; stream >> flags; - + material->setFlags(flags); } } @@ -172,8 +324,14 @@ Material* MaterialLoader::load(const std::string& filename) if (variableNamePosition == std::string::npos) { // Skip lines with no variable name +<<<<<<< HEAD:src/resources/material-loader.cpp + std::stringstream stream; + stream << "ResourceLoader::load(): Invalid variable on line " << lineNumber << "."; + throw std::runtime_error(stream.str().c_str()); +======= std::cerr << std::string("MaterialLoader::load(): Invalid variable on line ") << lineNumber << std::string(" in \"") << filename << std::string("\"") << std::endl; continue; +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036:src/material-loader.cpp } // Find position of equals sign @@ -181,8 +339,14 @@ Material* MaterialLoader::load(const std::string& filename) if (equalsSignPosition == std::string::npos) { // Skip lines with no equals sign +<<<<<<< HEAD:src/resources/material-loader.cpp + std::stringstream stream; + stream << "ResourceLoader::load(): Invalid variable on line " << lineNumber << "."; + throw std::runtime_error(stream.str().c_str()); +======= std::cerr << std::string("MaterialLoader::load(): Invalid variable on line ") << lineNumber << std::string(" in \"") << filename << std::string("\"") << std::endl; continue; +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036:src/material-loader.cpp } // Find position of first character in variable type @@ -190,8 +354,14 @@ Material* MaterialLoader::load(const std::string& filename) if (variableTypePosition == std::string::npos) { // Skip lines with no variable type definition +<<<<<<< HEAD:src/resources/material-loader.cpp + std::stringstream stream; + stream << "ResourceLoader::load(): Invalid variable on line " << lineNumber << "."; + throw std::runtime_error(stream.str().c_str()); +======= std::cerr << std::string("MaterialLoader::load(): Invalid variable on line ") << lineNumber << std::string(" in \"") << filename << std::string("\"") << std::endl; continue; +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036:src/material-loader.cpp } // Count parentheses @@ -200,8 +370,14 @@ Material* MaterialLoader::load(const std::string& filename) if (leftParenthesisCount != rightParenthesisCount || leftParenthesisCount == 0) { // Skip lines with invalid number of parentheses +<<<<<<< HEAD:src/resources/material-loader.cpp + std::stringstream stream; + stream << "ResourceLoader::load(): Invalid variable on line " << lineNumber << "."; + throw std::runtime_error(stream.str().c_str()); +======= std::cerr << std::string("MaterialLoader::load(): Invalid variable on line ") << lineNumber << std::string(" in \"") << filename << std::string("\"") << std::endl; continue; +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036:src/material-loader.cpp } std::string variableName = line.substr(variableNamePosition, line.find_first_of(" \t=", variableNamePosition) - variableNamePosition); @@ -245,8 +421,9 @@ Material* MaterialLoader::load(const std::string& filename) if (invalid) { - // Unable to parse element - break; + std::stringstream stream; + stream << "ResourceLoader::load(): Unable to parse element on line " << lineNumber << "."; + throw std::runtime_error(stream.str().c_str()); } elements.push_back(arguments); @@ -257,7 +434,9 @@ Material* MaterialLoader::load(const std::string& filename) if (invalid) { // Unable to parse line - continue; + std::stringstream stream; + stream << "ResourceLoader::load(): Unable to parse line " << lineNumber << "."; + throw std::runtime_error(stream.str().c_str()); } if (variableType == "int") @@ -277,6 +456,7 @@ Material* MaterialLoader::load(const std::string& filename) } else if (variableType == "vec3") { + ShaderVector3* variable = material->addVariable(variableName, elements.size()); loadShaderVector3(variable, elements); } @@ -298,12 +478,12 @@ Material* MaterialLoader::load(const std::string& filename) else if (variableType == "texture") { ShaderTexture2D* variable = material->addVariable(variableName, elements.size()); - loadShaderTexture2D(variable, elements); + loadShaderTexture2D(resourceManager, variable, elements); } else if (variableType == "textureCube") { ShaderTextureCube* variable = material->addVariable(variableName, elements.size()); - loadShaderTextureCube(variable, elements); + loadShaderTextureCube(resourceManager, variable, elements); } } else @@ -315,6 +495,11 @@ Material* MaterialLoader::load(const std::string& filename) } // Invalid command +<<<<<<< HEAD:src/resources/material-loader.cpp + std::stringstream stream; + stream << "ResourceLoader::load(): Invalid command \"" << command << "\" on line " << lineNumber << "."; + throw std::runtime_error(stream.str().c_str()); +======= std::cerr << std::string("MaterialLoader::load(): Invalid command \"") << command << std::string("\" on line ") << lineNumber << std::string(" in \"") << filename << std::string("\"") << std::endl; } } @@ -549,14 +734,15 @@ bool MaterialLoader::loadShaderTexture2D(ShaderTexture2D* variable, const std::v { std::cerr << std::string("MaterialLoader::loadShaderTexture2D(): Failed to load 2D texture \"") << filename << std::string("\"") << std::endl; return false; +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036:src/material-loader.cpp } - - variable->setValue(i, value); } - return true; + return material; } +<<<<<<< HEAD:src/resources/material-loader.cpp +======= bool MaterialLoader::loadShaderTextureCube(ShaderTextureCube* variable, const std::vector>& elements) { for (int i = 0; i < elements.size(); ++i) @@ -578,3 +764,4 @@ bool MaterialLoader::loadShaderTextureCube(ShaderTextureCube* variable, const st return true; } +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036:src/material-loader.cpp diff --git a/src/model-loader.cpp b/src/resources/model-loader.cpp old mode 100644 new mode 100755 similarity index 64% rename from src/model-loader.cpp rename to src/resources/model-loader.cpp index a2b42e0..d112ce3 --- a/src/model-loader.cpp +++ b/src/resources/model-loader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -18,9 +18,97 @@ */ -#include "model-loader.hpp" -#include "material-loader.hpp" -#include +#include "resource-loader.hpp" +#include "resource-manager.hpp" +#include "../graphics/vertex-format.hpp" + +#include +using namespace Emergent; + +struct MaterialGroup +{ + std::string materialName; + std::uint32_t indexOffset; + std::uint32_t triangleCount; + AABB bounds; +}; + +enum VertexFlags +{ + UV = 1, + TANGENT = 2, + WEIGHTS = 4 +}; + +struct ModelData +{ + std::uint32_t groupCount; + MaterialGroup* groups; + std::uint32_t vertexFormat; + std::uint32_t vertexCount; + AABB bounds; + float* vertexData; + std::uint32_t* indexData; +}; + +struct BoneData +{ + std::string name; + std::uint16_t parent; + std::uint16_t childCount; + std::uint16_t* children; + Vector3 translation; + Quaternion rotation; + float length; +}; + +struct KeyframeData +{ + float time; + Transform transform; +}; + +struct ChannelData +{ + std::uint16_t id; + std::uint16_t keyframeCount; + KeyframeData* keyframes; +}; + +struct AnimationData +{ + std::string name; + float startTime; + float endTime; + std::uint16_t channelCount; + ChannelData* channels; +}; + +struct SkeletonData +{ + std::uint16_t boneCount; + BoneData* bones; + std::uint16_t animationCount; + AnimationData* animations; +}; + +static void constructBoneHierarchy(Bone* bone, const BoneData* data, std::uint16_t index) +{ + bone->setName(data[index].name); + + Transform transform; + transform.translation = data[index].translation; + transform.rotation = data[index].rotation; + transform.scale = Vector3(1.0f); + + bone->setRelativeTransform(transform); + bone->setLength(data[index].length); + + for (std::uint16_t i = 0; i < data[index].childCount; ++i) + { + constructBoneHierarchy(bone->createChild(), data, data[index].children[i]); + } +} template inline static void read8(T* result, unsigned char** data) @@ -57,15 +145,11 @@ inline static void readString(std::string* result, unsigned char** data) *data += result->size() + 1; } -ModelLoader::ModelLoader(): - materialLoader(nullptr) -{} - -ModelLoader::~ModelLoader() -{} - -Model* ModelLoader::load(const std::string& filename) +template <> +Model* ResourceLoader::load(ResourceManager* resourceManager, std::istream* is) { +<<<<<<< HEAD:src/resources/model-loader.cpp +======= // Open file std::ifstream file(filename.c_str(), std::ifstream::in | std::ifstream::binary | std::ifstream::ate); if (!file.is_open()) @@ -74,18 +158,17 @@ Model* ModelLoader::load(const std::string& filename) return nullptr; } +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036:src/model-loader.cpp // Allocate file data buffer - int filesize = file.tellg(); + is->seekg(0, is->end); + int filesize = is->tellg(); unsigned char* buffer = new unsigned char[filesize]; // Read file data into buffer - file.seekg(0, file.beg); - file.read(reinterpret_cast(&buffer[0]), filesize); + is->seekg(0, is->beg); + is->read(reinterpret_cast(&buffer[0]), filesize); unsigned char* bufferOffset = &buffer[0]; - // Close file - file.close(); - // Allocate model data ModelData* modelData = new ModelData(); SkeletonData* skeletonData = nullptr; @@ -241,34 +324,34 @@ Model* ModelLoader::load(const std::string& filename) read16(&channel->id, &bufferOffset); // Read keyframe count - read16(&channel->keyFrameCount, &bufferOffset); + read16(&channel->keyframeCount, &bufferOffset); // Allocate keyframes - channel->keyFrames = new KeyFrameData[channel->keyFrameCount]; + channel->keyframes = new KeyframeData[channel->keyframeCount]; // Read keyframes - for (std::size_t k = 0; k < channel->keyFrameCount; ++k) + for (std::size_t k = 0; k < channel->keyframeCount; ++k) { - KeyFrameData* keyFrame = &channel->keyFrames[k]; + KeyframeData* keyframe = &channel->keyframes[k]; // Read keyframe time - read32(&keyFrame->time, &bufferOffset); + read32(&keyframe->time, &bufferOffset); // Read keyframe translation - read32(&keyFrame->transform.translation.x, &bufferOffset); - read32(&keyFrame->transform.translation.y, &bufferOffset); - read32(&keyFrame->transform.translation.z, &bufferOffset); + read32(&keyframe->transform.translation.x, &bufferOffset); + read32(&keyframe->transform.translation.y, &bufferOffset); + read32(&keyframe->transform.translation.z, &bufferOffset); // Read keyframe rotation - read32(&keyFrame->transform.rotation.w, &bufferOffset); - read32(&keyFrame->transform.rotation.x, &bufferOffset); - read32(&keyFrame->transform.rotation.y, &bufferOffset); - read32(&keyFrame->transform.rotation.z, &bufferOffset); + read32(&keyframe->transform.rotation.w, &bufferOffset); + read32(&keyframe->transform.rotation.x, &bufferOffset); + read32(&keyframe->transform.rotation.y, &bufferOffset); + read32(&keyframe->transform.rotation.z, &bufferOffset); // Read keyframe scale - read32(&keyFrame->transform.scale.x, &bufferOffset); - read32(&keyFrame->transform.scale.y, &bufferOffset); - read32(&keyFrame->transform.scale.z, &bufferOffset); + read32(&keyframe->transform.scale.x, &bufferOffset); + read32(&keyframe->transform.scale.y, &bufferOffset); + read32(&keyframe->transform.scale.z, &bufferOffset); } } } @@ -277,7 +360,49 @@ Model* ModelLoader::load(const std::string& filename) // Free file data buffer delete[] buffer; - + + #if defined(DEBUG) + std::uint32_t newVertexCount = triangleCount * 3; + std::uint32_t newVertexSize = vertexSize + 3; + float* newVertexData = new float[newVertexCount * newVertexSize]; + const Vector3 barycentricCoordinates[3] = + { + Vector3(1, 0, 0), + Vector3(0, 1, 0), + Vector3(0, 0, 1) + }; + + for (std::size_t i = 0; i < indexCount; i += 3) + { + // For each triangle vertex + for (std::size_t j = 0; j < 3; ++j) + { + float* oldVertex = &modelData->vertexData[modelData->indexData[i + j] * vertexSize]; + float* newVertex = &newVertexData[(i + j) * newVertexSize]; + + // Copy old vertex data + for (std::size_t k = 0; k < vertexSize; ++k) + { + *(newVertex++) = *(oldVertex++); + } + + // Add barycentric coordinates + *(newVertex++) = barycentricCoordinates[j].x; + *(newVertex++) = barycentricCoordinates[j].y; + *(newVertex) = barycentricCoordinates[j].z; + + // Reassign indices + modelData->indexData[i + j] = i + j; + } + } + + // Replace old vertex buffer with new vertex buffer + vertexSize = newVertexSize; + delete[] modelData->vertexData; + modelData->vertexData = newVertexData; + modelData->vertexCount = newVertexCount; + #endif // DEBUG + GLuint vao; GLuint vbo; GLuint ibo; @@ -297,22 +422,22 @@ Model* ModelLoader::load(const std::string& filename) // Vertex position attribute attribSize = 3; - glEnableVertexAttribArray(EMERGENT_VERTEX_POSITION); - glVertexAttribPointer(EMERGENT_VERTEX_POSITION, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); + glEnableVertexAttribArray(VERTEX_POSITION); + glVertexAttribPointer(VERTEX_POSITION, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); attribOffset += attribSize; // Vertex normal attribute attribSize = 3; - glEnableVertexAttribArray(EMERGENT_VERTEX_NORMAL); - glVertexAttribPointer(EMERGENT_VERTEX_NORMAL, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); + glEnableVertexAttribArray(VERTEX_NORMAL); + glVertexAttribPointer(VERTEX_NORMAL, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); attribOffset += attribSize; // Vertex UV attribute if ((modelData->vertexFormat & UV) != 0) { attribSize = 2; - glEnableVertexAttribArray(EMERGENT_VERTEX_TEXCOORD); - glVertexAttribPointer(EMERGENT_VERTEX_TEXCOORD, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); + glEnableVertexAttribArray(VERTEX_TEXCOORD); + glVertexAttribPointer(VERTEX_TEXCOORD, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); attribOffset += attribSize; } @@ -321,32 +446,42 @@ Model* ModelLoader::load(const std::string& filename) { // Tangent attribSize = 4; - glEnableVertexAttribArray(EMERGENT_VERTEX_TANGENT); - glVertexAttribPointer(EMERGENT_VERTEX_TANGENT, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); + glEnableVertexAttribArray(VERTEX_TANGENT); + glVertexAttribPointer(VERTEX_TANGENT, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); attribOffset += attribSize; // Bitangent attribSize = 4; - glEnableVertexAttribArray(EMERGENT_VERTEX_BITANGENT); - glVertexAttribPointer(EMERGENT_VERTEX_BITANGENT, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); + glEnableVertexAttribArray(VERTEX_BITANGENT); + glVertexAttribPointer(VERTEX_BITANGENT, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); attribOffset += attribSize; } // Vertex indices and weights attributes - if ((modelData->vertexFormat & TANGENT) != 0) + if ((modelData->vertexFormat & WEIGHTS) != 0) { // Indices attribSize = 4; - glEnableVertexAttribArray(EMERGENT_VERTEX_BONE_INDICES); - glVertexAttribPointer(EMERGENT_VERTEX_BONE_INDICES, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); + glEnableVertexAttribArray(VERTEX_BONE_INDICES); + glVertexAttribPointer(VERTEX_BONE_INDICES, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); attribOffset += attribSize; // Weights attribSize = 4; - glEnableVertexAttribArray(EMERGENT_VERTEX_BONE_WEIGHTS); - glVertexAttribPointer(EMERGENT_VERTEX_BONE_WEIGHTS, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); + glEnableVertexAttribArray(VERTEX_BONE_WEIGHTS); + glVertexAttribPointer(VERTEX_BONE_WEIGHTS, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); + attribOffset += attribSize; + } + + #if defined(DEBUG) + { + // Vertex barycentric coordinates attribute + attribSize = 3; + glEnableVertexAttribArray(VERTEX_BARYCENTRIC); + glVertexAttribPointer(VERTEX_BARYCENTRIC, attribSize, GL_FLOAT, GL_FALSE, sizeof(float) * vertexSize, (char*)0 + attribOffset * sizeof(float)); attribOffset += attribSize; } + #endif // DEBUG // Generate and bind IBO, then upload index data glGenBuffers(1, &ibo); @@ -368,7 +503,7 @@ Model* ModelLoader::load(const std::string& filename) // Create model groups for (std::size_t i = 0; i < modelData->groupCount; ++i) { - ModelLoader::MaterialGroup* modelDataGroup = &modelData->groups[i]; + MaterialGroup* modelDataGroup = &modelData->groups[i]; // Allocate model group Model::Group* modelGroup = new Model::Group(); @@ -377,6 +512,10 @@ Model* ModelLoader::load(const std::string& filename) modelGroup->name = modelDataGroup->materialName; // Load material +<<<<<<< HEAD:src/resources/model-loader.cpp + std::string materialFilename = modelDataGroup->materialName + std::string(".mtl"); + modelGroup->material = resourceManager->load(materialFilename); +======= std::string materialFilename = std::string("data/materials/") + modelDataGroup->materialName + std::string(".mtl"); if (materialLoader != nullptr) { @@ -391,6 +530,7 @@ Model* ModelLoader::load(const std::string& filename) modelGroup->material = nullptr; std::cerr << std::string("ModelLoader::load(): No valid material loader, material file \"") << materialFilename << std::string("\" not loaded") << std::endl; } +>>>>>>> df8405f4e83febb81a5ce8f772bd7f5b9e9b6036:src/model-loader.cpp // Setup model group geometry modelGroup->indexOffset = modelDataGroup->indexOffset; @@ -417,24 +557,22 @@ Model* ModelLoader::load(const std::string& filename) for (std::size_t i = 0; i < skeletonData->animationCount; ++i) { AnimationData* animationData = &skeletonData->animations[i]; - Animation* animation = new Animation(); - animation->setName(animationData->name); - animation->setTimeFrame(animationData->startTime, animationData->endTime); + AnimationClip* clip = new AnimationClip(); + clip->setInterpolator(lerp); for (std::size_t j = 0; j < animationData->channelCount; ++j) { ChannelData* channelData = &animationData->channels[j]; - AnimationChannel* channel = animation->createChannel(channelData->id); - for (std::size_t k = 0; k < channelData->keyFrameCount; ++k) + AnimationChannel* channel = clip->addChannel(channelData->id); + for (std::size_t k = 0; k < channelData->keyframeCount; ++k) { - KeyFrameData* keyFrameData = &channelData->keyFrames[k]; - KeyFrame* keyFrame = channel->insertKeyFrame(keyFrameData->time); - keyFrame->setTransform(keyFrameData->transform); + KeyframeData* keyframeData = &channelData->keyframes[k]; + channel->insertKeyframe(keyframeData->time, keyframeData->transform); } } - // Add animation to skeleton - skeleton->addAnimation(animation); + // Add animation clip to skeleton + skeleton->addAnimationClip(animationData->name, clip); } // Add skeleton to model @@ -461,7 +599,7 @@ Model* ModelLoader::load(const std::string& filename) AnimationData* animation = &skeletonData->animations[i]; for (std::size_t j = 0; j < animation->channelCount; ++j) { - delete[] animation->channels[j].keyFrames; + delete[] animation->channels[j].keyframes; } delete[] animation->channels; @@ -474,25 +612,3 @@ Model* ModelLoader::load(const std::string& filename) return model; } -void ModelLoader::setMaterialLoader(MaterialLoader* materialLoader) -{ - this->materialLoader = materialLoader; -} - -void ModelLoader::constructBoneHierarchy(Bone* bone, const BoneData* data, std::uint16_t index) -{ - bone->setName(data[index].name); - - Transform transform; - transform.translation = data[index].translation; - transform.rotation = data[index].rotation; - transform.scale = Vector3(1.0f); - - bone->setRelativeTransform(transform); - bone->setLength(data[index].length); - - for (std::uint16_t i = 0; i < data[index].childCount; ++i) - { - constructBoneHierarchy(bone->createChild(), data, data[index].children[i]); - } -} diff --git a/src/resources/resource-handle.cpp b/src/resources/resource-handle.cpp new file mode 100644 index 0000000..f869e7c --- /dev/null +++ b/src/resources/resource-handle.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "resource-handle.hpp" + +ResourceHandleBase::ResourceHandleBase(): + referenceCount(0) +{} + diff --git a/src/resources/resource-handle.hpp b/src/resources/resource-handle.hpp new file mode 100644 index 0000000..bff0445 --- /dev/null +++ b/src/resources/resource-handle.hpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef RESOURCE_HANDLE_HPP +#define RESOURCE_HANDLE_HPP + +#include + +/** + * Base class for resource handles. + */ +class ResourceHandleBase +{ +public: + /// Creates a resource handle base. + ResourceHandleBase(); + + /// Destroys a resource handle base. + virtual ~ResourceHandleBase() = default; + + /// Number of times the handle is referenced. + std::size_t referenceCount; +}; + +/** + * Templated resource handle class. + */ +template +class ResourceHandle: public ResourceHandleBase +{ +public: + /// Creates a resource handle + ResourceHandle(); + + /// Destroys a resource handle and deletes its data. + virtual ~ResourceHandle(); + + /// Pointer to resource data + T* data; +}; + +template +ResourceHandle::ResourceHandle(): + data(nullptr) +{} + +template +ResourceHandle::~ResourceHandle() +{ + delete data; +} + +#endif // RESOURCE_HANDLE_HPP + diff --git a/src/resources/resource-loader.hpp b/src/resources/resource-loader.hpp new file mode 100644 index 0000000..32fa45f --- /dev/null +++ b/src/resources/resource-loader.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef RESOURCE_LOADER_HPP +#define RESOURCE_LOADER_HPP + +#include + +class ResourceManager; + +/** + * Templated resource class. Different resources types should specialize the constructor and destructor to load and free resource data, respectively. + */ +template +class ResourceLoader +{ +public: + /** + * Loads resource data. + */ + static T* load(ResourceManager* resourceManager, std::istream* is); +}; + +#endif // RESOURCE_LOADER_HPP + diff --git a/src/resources/resource-manager.cpp b/src/resources/resource-manager.cpp new file mode 100644 index 0000000..87d03c0 --- /dev/null +++ b/src/resources/resource-manager.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "resource-manager.hpp" + +ResourceManager::ResourceManager() +{} + +ResourceManager::~ResourceManager() +{ + for (auto it = resourceCache.begin(); it != resourceCache.end(); ++it) + { + delete it->second; + } +} + +void ResourceManager::unload(const std::string& path) +{ + // Check if resource is in the cache + auto it = resourceCache.find(path); + if (it != resourceCache.end()) + { + // Decrement the resource handle reference count + --it->second->referenceCount; + + // Free the resource if the resource handle is unreferenced + if (it->second->referenceCount <= 0) + { + delete it->second; + } + + // Remove resource from the cache + resourceCache.erase(it); + } +} + +void ResourceManager::include(const std::string& path) +{ + paths.push_back(path); +} + diff --git a/src/resources/resource-manager.hpp b/src/resources/resource-manager.hpp new file mode 100644 index 0000000..beaaf1d --- /dev/null +++ b/src/resources/resource-manager.hpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef RESOURCE_MANAGER_HPP +#define RESOURCE_MANAGER_HPP + +#include "resource-handle.hpp" +#include "resource-loader.hpp" +#include +#include +#include +#include +#include + +/** + * Loads resources. + */ +class ResourceManager +{ +public: + /** + * Creates a resource manager. + */ + ResourceManager(); + + /** + * Destroys a resource manager and frees all of its resources. + */ + ~ResourceManager(); + + /** + * Adds a path to be searched when a resource is requested. + * + * @param path Search path. + */ + void include(const std::string& path); + + /** + * Loads the requested resource. If the resource has already been loaded it will be retrieved from the resource cache and its reference count incremented. + * + * @tparam T Resource type. + * @param path Path to the resource, relative to the search paths. + * @return Pointer to the requested resource, or nullptr if the resource could not be found nor loaded. + */ + template + T* load(const std::string& path); + + /** + * Decrements a resource's reference count and unloads the resource if it's unreferenced. + * + * @param path Path to the resource, relative to the search paths. + */ + void unload(const std::string& path); + +private: + std::map resourceCache; + std::list paths; +}; + +template +T* ResourceManager::load(const std::string& path) +{ + // Check if resource is in the cache + auto it = resourceCache.find(path); + if (it != resourceCache.end()) + { + // Resource found + ResourceHandle* resource = static_cast*>(it->second); + + // Increment resource handle reference count + ++resource->referenceCount; + + // Return resource data + return resource->data; + } + + // Resource not found, load resource data + T* data = nullptr; + try + { + // For each directory in search paths + bool opened = false; + for (const std::string& directory: paths) + { + // Attempt to open file + std::string fullPath = directory + path; + std::ifstream fs; + fs.open(fullPath.c_str(), std::ios::in | std::ios::binary); + + // If unable to open file + if (!fs.is_open() || !fs.good()) + { + if (fs.is_open()) + { + fs.close(); + } + + // Try again in next search path + continue; + } + + // File opened, load it + opened = true; + data = ResourceLoader::load(this, &fs); + fs.close(); + } + + if (!opened) + { + throw std::runtime_error("Unable to open file."); + } + } + catch (const std::exception& e) + { + std::string error = std::string("ResourceManager::load(): Failed to load resource \"") + path + std::string("\": \"") + e.what() + std::string("\""); + throw std::runtime_error(error.c_str()); + } + + // Create a resource handle for the resource data + ResourceHandle* resource = new ResourceHandle(); + resource->data = data; + resource->referenceCount = 1; + + // Add resource to the cache + resourceCache[path] = resource; + + return resource->data; +} + +#endif // RESOURCE_MANAGER_HPP + diff --git a/src/resources/shader-loader.cpp b/src/resources/shader-loader.cpp new file mode 100644 index 0000000..74d0b72 --- /dev/null +++ b/src/resources/shader-loader.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "resource-loader.hpp" +#include "resource-manager.hpp" +#include "text-file.hpp" +#include + +#include +using namespace Emergent; + +static void preprocess(ResourceManager* resourceManager, TextFile* source) +{ + // For each line in the source + for (std::size_t i = 0; i < source->size(); ++i) + { + // Tokenize line + std::vector tokens; + std::string token; + std::istringstream linestream((*source)[i]); + while (linestream >> token) + tokens.push_back(token); + + // Inject DEBUG and NDEBUG macros + if (!tokens.empty() && tokens[0] == "#version") + { + #if defined(NDEBUG) + source->insert(source->begin() + i + 1, "#define NDEBUG"); + #else + source->insert(source->begin() + i + 1, "#define DEBUG"); + #endif + + ++i; + } + // Handle #pragma include directives + else if (tokens.size() == 3 && tokens[0] == "#pragma" && tokens[1] == "include") + { + // Get path to include file + std::string path = tokens[2].substr(1, tokens[2].length() - 2); + + // Load include file + TextFile* includeFile = resourceManager->load(path); + + // Preprocess include file + preprocess(resourceManager, includeFile); + + // Replace #pragma include directive with include file contents + source->erase(source->begin() + i); + source->insert(source->begin() + i, includeFile->begin(), includeFile->end()); + i += includeFile->size() - 1; + } + } +} + +template <> +Shader* ResourceLoader::load(ResourceManager* resourceManager, std::istream* is) +{ + // Allocate shader source + TextFile* source = new TextFile(); + + while (!is->eof()) + { + // For each line in input stream + std::string line; + std::getline(*is, line); + if (is->bad() || is->fail()) + { + break; + } + + // Add line to the source + source->push_back(line); + } + + // Preprocess source + preprocess(resourceManager, source); + + // Create shader + Shader* shader = new Shader(); + shader->setSource(*source); + + // Free the shader source + delete source; + + return shader; +} + diff --git a/src/resources/text-file-loader.cpp b/src/resources/text-file-loader.cpp new file mode 100644 index 0000000..5c2e916 --- /dev/null +++ b/src/resources/text-file-loader.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "resource-loader.hpp" +#include "text-file.hpp" + +template <> +TextFile* ResourceLoader::load(ResourceManager* resourceManager, std::istream* is) +{ + TextFile* file = new TextFile(); + std::string line; + + while (!is->eof()) + { + std::getline(*is, line); + if (is->bad() || is->fail()) + { + break; + } + + file->push_back(line); + } + + return file; +} + diff --git a/src/resources/text-file.hpp b/src/resources/text-file.hpp new file mode 100644 index 0000000..f743924 --- /dev/null +++ b/src/resources/text-file.hpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef TEXT_FILE_HPP +#define TEXT_FILE_HPP + +#include +#include + +typedef std::vector TextFile; + +#endif // TEXT_FILE_HPP + diff --git a/src/resources/texture-2d-loader.cpp b/src/resources/texture-2d-loader.cpp new file mode 100644 index 0000000..d6557d0 --- /dev/null +++ b/src/resources/texture-2d-loader.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "resource-loader.hpp" +#include "image.hpp" +#include + +#include +using namespace Emergent; + +template <> +Texture2D* ResourceLoader::load(ResourceManager* resourceManager, std::istream* is) +{ + Image* image = ResourceLoader::load(resourceManager, is); + + // Generate OpenGL texture ID + GLuint textureID; + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_2D, textureID); + + // Set wrapping and filtering parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Select texture formats according to number of color channels in the image + GLint internalFormat; + GLenum format; + GLenum type = (image->isHDR()) ? GL_FLOAT : GL_UNSIGNED_BYTE; + if (image->getChannels() == 1) + { + // Grey + internalFormat = (image->isHDR()) ? GL_R32F : GL_R8; + format = GL_RED; + + GLint swizzleMask[] = {GL_RED, GL_RED, GL_RED, GL_ONE}; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (image->getChannels() == 2) + { + // Grey, alpha + internalFormat = (image->isHDR()) ? GL_RG32F : GL_RG8; + format = GL_RG; + + GLint swizzleMask[] = {GL_RED, GL_RED, GL_RED, GL_GREEN}; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (image->getChannels() == 3) + { + // Red, green, blue + internalFormat = (image->isHDR()) ? GL_RGB32F : GL_RGB8; + format = GL_RGB; + + GLint swizzleMask[] = {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (image->getChannels() == 4) + { + // Red, green, blue, alpha + internalFormat = (image->isHDR()) ? GL_RGBA32F : GL_RGBA8; + format = GL_RGBA; + } + else + { + std::stringstream stream; + stream << std::string("Texture cannot be created from an image with an unsupported number of color channels (") << image->getChannels() << std::string(")."); + delete image; + glDeleteTextures(1, &textureID); + throw std::runtime_error(stream.str().c_str()); + } + + // Upload image data to OpenGL + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, image->getWidth(), image->getHeight(), 0, format, type, image->getPixels()); + + // Generate mipmaps + glGenerateMipmap(GL_TEXTURE_2D); + + // Create Texture2D + Texture2D* texture = new Texture2D(); + texture->setTextureID(textureID); + texture->setWidth(image->getWidth()); + texture->setHeight(image->getHeight()); + + // Free loaded image + delete image; + + return texture; +} + diff --git a/src/resources/texture-cube-loader.cpp b/src/resources/texture-cube-loader.cpp new file mode 100644 index 0000000..34aa53c --- /dev/null +++ b/src/resources/texture-cube-loader.cpp @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "resource-loader.hpp" +#include "image.hpp" +#include "resource-manager.hpp" +#include "text-file.hpp" +#include +#include + +#include +using namespace Emergent; + +namespace CubemapLayout +{ + enum + { + VERTICAL_CROSS, + HORIZONTAL_CROSS, + VERTICAL_STRIP, + HORIZONTAL_STRIP, + BLENDER + }; +} + +static const int CUBEMAP_LAYOUT_DIMENSIONS[5][2] = +{ + {3, 4}, // Vertical cross + {4, 3}, // Horizontal cross + {1, 6}, // Vertical strip + {6, 1}, // Horizontal strip + {3, 2} // Blender +}; + +static const int CUBEMAP_LAYOUT_OFFSETS[5][6][2] = +{ + // Vertical cross + { + {0, 1}, // +X + {2, 1}, // -X + {1, 0}, // +Y + {1, 2}, // -Y + {1, 1}, // +Z + {1, 3} // -Z + }, + + // Horizontal cross + { + {0, 1}, // +X + {2, 1}, // -X + {1, 0}, // +Y + {1, 2}, // -Y + {1, 1}, // +Z + {3, 1} // -Z + }, + + // Vertical strip + { + {0, 1}, // +X + {0, 0}, // -X + {0, 2}, // +Y + {0, 3}, // -Y + {0, 4}, // +Z + {0, 5} // -Z + }, + + // Horizontal strip + { + {1, 0}, // +X + {0, 0}, // -X + {2, 0}, // +Y + {3, 0}, // -Y + {4, 0}, // +Z + {5, 0} // -Z + }, + + // Blender + { + {0, 0}, // +X + {2, 0}, // -X + {1, 1}, // +Y + {0, 1}, // -Y + {1, 0}, // +Z + {2, 1} // -Z + } +}; + +static const bool CUBEMAP_LAYOUT_FLIPS[5][6][2] = +{ + // Vertical cross + { + {true, true}, // +X + {true, true}, // -X + {true, true}, // +Y + {true, true}, // -Y + {true, true}, // +Z + {false, false}, // -Z + }, + + // Horizontal cross + { + {true, true}, // +X + {true, true}, // -X + {true, true}, // +Y + {true, true}, // -Y + {true, true}, // +Z + {true, true}, // -Z + }, + + // Vertical strip + { + {true, true}, // +X + {true, true}, // -X + {true, true}, // +Y + {true, true}, // -Y + {true, true}, // +Z + {true, true}, // -Z + }, + + // Horizontal strip + { + {true, true}, // +X + {true, true}, // -X + {true, true}, // +Y + {true, true}, // -Y + {true, true}, // +Z + {true, true}, // -Z + }, + + // Blender + { + {true, true}, // +X + {true, true}, // -X + {true, true}, // +Y + {true, true}, // -Y + {true, true}, // +Z + {true, true}, // -Z + } +}; + +static void loadCubemapMipmap(TextureCube* texture, std::size_t level, Image* image) +{ + // Select texture formats according to number of color channels in the image + GLint internalFormat; + GLenum format; + GLenum type = (image->isHDR()) ? GL_FLOAT : GL_UNSIGNED_BYTE; + if (image->getChannels() == 1) + { + // Grey + internalFormat = (image->isHDR()) ? GL_R32F : GL_R8; + format = GL_RED; + + GLint swizzleMask[] = {GL_RED, GL_RED, GL_RED, GL_ONE}; + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (image->getChannels() == 2) + { + // Grey, alpha + internalFormat = (image->isHDR()) ? GL_RG32F : GL_RG8; + format = GL_RG; + + GLint swizzleMask[] = {GL_RED, GL_RED, GL_RED, GL_GREEN}; + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (image->getChannels() == 3) + { + // Red, green, blue + internalFormat = (image->isHDR()) ? GL_RGB32F : GL_RGB8; + format = GL_RGB; + + GLint swizzleMask[] = {GL_RED, GL_GREEN, GL_BLUE, GL_ONE}; + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (image->getChannels() == 4) + { + // Red, green, blue, alpha + internalFormat = (image->isHDR()) ? GL_RGBA32F : GL_RGBA8; + format = GL_RGBA; + } + else + { + std::stringstream stream; + stream << std::string("Mipmap cannot be loaded from an image with an unsupported number of color channels (") << image->getChannels() << std::string(")."); + throw std::runtime_error(stream.str().c_str()); + } + + // Determine cubemap layout + int layout = -1; + for (std::size_t i = 0; i < 5; ++i) + { + // Compare aspect ratio of image to known cubemap layouts + float aspectRatio = static_cast(CUBEMAP_LAYOUT_DIMENSIONS[i][0]) / static_cast(CUBEMAP_LAYOUT_DIMENSIONS[i][1]); + if (static_cast((static_cast(image->getHeight()) * aspectRatio)) == image->getWidth()) + { + layout = i; + break; + } + } + + // Check that a layout was determined + if (layout == -1) + { + throw std::runtime_error("Unsupported cubemap layout."); + } + + // Calculate cubemap face size + int faceSize = image->getWidth() / CUBEMAP_LAYOUT_DIMENSIONS[layout][0]; + if (!level) + { + texture->setFaceSize(faceSize); + } + + // Allocate cubemap face pixels + void* facePixels = nullptr; + if (image->isHDR()) + { + facePixels = new float[faceSize * faceSize * image->getChannels()]; + } + else + { + facePixels = new unsigned char[faceSize * faceSize * image->getChannels()]; + } + + // Upload mipmap data to OpenGL + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + for (std::size_t faceIndex = 0; faceIndex < 6; ++faceIndex) + { + int offsetX = faceSize * CUBEMAP_LAYOUT_OFFSETS[layout][faceIndex][0]; + int offsetY = faceSize * ((CUBEMAP_LAYOUT_DIMENSIONS[layout][1] - 1) - CUBEMAP_LAYOUT_OFFSETS[layout][faceIndex][1]); + bool flipX = CUBEMAP_LAYOUT_FLIPS[layout][faceIndex][0]; + bool flipY = CUBEMAP_LAYOUT_FLIPS[layout][faceIndex][1]; + + for (int y = 0; y < faceSize; ++y) + { + for (int x = 0; x < faceSize; ++x) + { + std::size_t facePixelIndex; + + int faceX = (flipX) ? (faceSize - 1 - x) : x; + int faceY = (flipY) ? (faceSize - 1 - y) : y; + facePixelIndex = (faceY * faceSize + faceX) * image->getChannels(); + + std::size_t cubemapPixelIndex = ((offsetY + y) * image->getWidth() + (offsetX + x)) * image->getChannels(); + + if (image->isHDR()) + { + for (int c = 0; c < image->getChannels(); ++c) + { + reinterpret_cast(facePixels)[facePixelIndex + c] = static_cast(image->getPixels())[cubemapPixelIndex + c]; + } + } + else + { + for (int c = 0; c < image->getChannels(); ++c) + { + reinterpret_cast(facePixels)[facePixelIndex + c] = static_cast(image->getPixels())[cubemapPixelIndex + c]; + } + } + } + } + + // Upload cubemap face pixels to OpenGL + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + faceIndex, level, internalFormat, faceSize, faceSize, 0, format, type, facePixels); + } + + // Free cubemap face pixels + if (image->isHDR()) + { + delete[] reinterpret_cast(facePixels); + } + else + { + delete[] reinterpret_cast(facePixels); + } +} + +template <> +TextureCube* ResourceLoader::load(ResourceManager* resourceManager, std::istream* is) +{ + // Load list of mipmap filenames + TextFile* mipmapFilenames = ResourceLoader::load(resourceManager, is); + + // Allocate and initialize mipmap array + std::size_t mipmapCount = mipmapFilenames->size(); + Image** mipmaps = new Image*[mipmapCount]; + for (std::size_t i = 0; i < mipmapCount; ++i) + { + mipmaps[i] = nullptr; + } + + // Generate OpenGL texture ID + GLuint textureID; + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); + + // Set wrapping and filtering parameters + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + // Create texture cube + TextureCube* texture = new TextureCube(); + texture->setTextureID(textureID); + + // Load mipmaps + std::size_t loadedMipmapCount = 0; + try + { + for (std::size_t i = 0; i < mipmapCount; ++i) + { + // Load mipmap image data + mipmaps[i] = resourceManager->load((*mipmapFilenames)[i]); + + // Load mipmap from image data + loadCubemapMipmap(texture, i, mipmaps[i]); + + // Unload mipmap image data + resourceManager->unload((*mipmapFilenames)[i]); + mipmaps[i] = nullptr; + + ++loadedMipmapCount; + } + } + catch (const std::exception& e) + { + std::string error = std::string("ResourceLoader::load(): Failed to load mipmap \"") + (*mipmapFilenames)[loadedMipmapCount] + std::string("\": \"") + e.what() + std::string("\""); + + // Free TextureCube + delete texture; + + // Delete OpenGL texture + glDeleteTextures(1, &textureID); + + // Free loaded mipmap images + for (std::size_t i = 0; i < mipmapCount; ++i) + { + if (mipmaps[i] != nullptr) + { + resourceManager->unload((*mipmapFilenames)[i]); + } + } + delete[] mipmaps; + + // Free list of mipmap filenames + delete mipmapFilenames; + + throw std::runtime_error(error.c_str()); + } + + // Free mipmap array + delete[] mipmaps; + + // Free list of mipmap filenames + delete mipmapFilenames; + + return texture; +} + diff --git a/src/resources/triangle-mesh-loader.cpp b/src/resources/triangle-mesh-loader.cpp new file mode 100644 index 0000000..04cd92c --- /dev/null +++ b/src/resources/triangle-mesh-loader.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "resource-loader.hpp" +#include +#include + +#include +using namespace Emergent; + +template <> +TriangleMesh* ResourceLoader::load(ResourceManager* resourceManager, std::istream* is) +{ + std::string line; + std::vector vertices; + std::vector indices; + + while (is->good() && std::getline(*is, line)) + { + // Tokenize line + std::vector tokens; + std::string token; + std::istringstream linestream(line); + while (linestream >> token) + tokens.push_back(token); + + // Skip empty lines and comments + if (tokens.empty() || tokens[0][0] == '#') + continue; + + if (tokens[0] == "v") + { + if (tokens.size() != 4) + { + std::stringstream stream; + stream << "ResourceLoader::load(): Invalid line \"" << line << "\"" << std::endl; + throw std::runtime_error(stream.str()); + } + + Vector3 vertex; + + std::stringstream(tokens[1]) >> vertex.x; + std::stringstream(tokens[2]) >> vertex.y; + std::stringstream(tokens[3]) >> vertex.z; + + vertices.push_back(vertex); + } + else if (tokens[0] == "f") + { + if (tokens.size() != 4) + { + std::stringstream stream; + stream << "ResourceLoader::load(): Invalid line \"" << line << "\"" << std::endl; + throw std::runtime_error(stream.str()); + + } + + std::size_t a, b, c; + std::stringstream(tokens[1]) >> a; + std::stringstream(tokens[2]) >> b; + std::stringstream(tokens[3]) >> c; + + a -= 1; + b -= 1; + c -= 1; + + indices.push_back(a); + indices.push_back(b); + indices.push_back(c); + } + } + + return new TriangleMesh(vertices, indices); +} + diff --git a/src/resources/typeface-loader.cpp b/src/resources/typeface-loader.cpp new file mode 100644 index 0000000..52b53c8 --- /dev/null +++ b/src/resources/typeface-loader.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "resource-loader.hpp" + +#include +using namespace Emergent; + +template <> +Typeface* ResourceLoader::load(ResourceManager* resourceManager, std::istream* is) +{ + return new Typeface(is); +} + diff --git a/src/settings.hpp b/src/settings.hpp deleted file mode 100644 index 02d9c03..0000000 --- a/src/settings.hpp +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef SETTINGS_HPP -#define SETTINGS_HPP - -#include -#include -#include -#include -#include - -class ParameterDict -{ -public: - template - bool get(const std::string& name, T* value) const; - - template - void set(const std::string& name, const T& value); - - bool load(const std::string& filename); - bool save(const std::string& filename); - void clear(); - - const std::map* getParameters() const; - -private: - std::map parameters; -}; - -template -bool ParameterDict::get(const std::string& name, T* value) const -{ - auto it = parameters.find(name); - if (it == parameters.end()) - return false; - - std::stringstream stream(it->second); - stream >> (*value); - - if (stream.fail()) - return false; - - return true; -} - -template <> -inline bool ParameterDict::get(const std::string& name, std::string* value) const -{ - auto it = parameters.find(name); - if (it == parameters.end()) - return false; - - *value = it->second; - - return true; -} - -template <> -inline bool ParameterDict::get(const std::string& name, std::u32string* value) const -{ - auto it = parameters.find(name); - if (it == parameters.end()) - return false; - - // Convert UTF-8 string to UTF-32 string - #if _MSC_VER >= 1900 - //std::wstring_convert, uint32_t> convert; - //*value = convert.from_bytes(it->second); - std::wstring_convert, uint32_t> convert; - auto uint32string = convert.from_bytes(it->second); - - value->resize(uint32string.size()); - for (std::size_t i = 0; i < uint32string.size(); ++i) - { - (*value)[i] = static_cast(uint32string[i]); - } - #else - std::wstring_convert, char32_t> convert; - *value = convert.from_bytes(it->second); - #endif - - return true; -} - -template -void ParameterDict::set(const std::string& name, const T& value) -{ - std::stringstream stream; - stream << value; - parameters[name] = stream.str(); -} - -inline const std::map*ParameterDict:: getParameters() const -{ - return ¶meters; -} - -#endif - diff --git a/src/states/game-state.cpp b/src/states/game-state.cpp old mode 100644 new mode 100755 index 0e7af28..db0cea8 --- a/src/states/game-state.cpp +++ b/src/states/game-state.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -18,489 +18,10 @@ */ #include "game-state.hpp" -#include "title-state.hpp" -#include "../application.hpp" -#include "../camera-rig.hpp" -#include "../game/colony.hpp" -#include "../game/ant.hpp" -#include "../game/tool.hpp" -#include "../game/pheromone-matrix.hpp" -#include "../ui/toolbar.hpp" -#include "../ui/menu.hpp" -#include "../ui/pie-menu.hpp" -#include -inline void cmykToRGB(const float* cmyk, float* rgb) -{ - rgb[0] = -((cmyk[0] * (1.0f - cmyk[3])) + cmyk[3] - 1.0f); - rgb[1] = -((cmyk[1] * (1.0f - cmyk[3])) + cmyk[3] - 1.0f); - rgb[2] = -((cmyk[2] * (1.0f - cmyk[3])) + cmyk[3] - 1.0f); -} - -GameState::GameState(Application* application): - ApplicationState(application) +GameState::GameState(Game* game): + game(game) {} GameState::~GameState() {} - -void GameState::enter() -{ - int continueWorld = -1; - int continueLevel = -1; - application->settings.get("continue_world", &continueWorld); - application->settings.get("continue_level", &continueLevel); - - if (continueWorld != application->currentWorldIndex || continueLevel != application->currentLevelIndex) - { - // Save continue world and level indices - application->settings.set("continue_world", application->currentWorldIndex); - application->settings.set("continue_level", application->currentLevelIndex); - application->saveUserSettings(); - } - - // Setup HUD - //application->rectangularPaletteImage->setVisible(true); - //application->rectangularPaletteImage->setActive(true); - application->toolbar->getContainer()->setVisible(true); - application->toolbar->getContainer()->setActive(true); - - Navmesh* navmesh = application->currentLevel->terrain.getSurfaceNavmesh(); - - // Setup tools - application->forceps->setColony(application->colony); - application->forceps->setNavmesh(navmesh); - - // Add tools to scene - application->defaultLayer->addObject(application->forceps->getModelInstance()); - application->defaultLayer->addObject(application->lens->getModelInstance()); - application->defaultLayer->addObject(application->lens->getSpotlight()); - application->defaultLayer->addObject(application->brush->getModelInstance()); - - - // Add terrain to scene - application->defaultLayer->addObject(&application->currentLevel->terrainSurface); - application->currentLevel->terrainSurface.setTranslation(Vector3(0.0f, 0.01f, 0.0f)); - //application->defaultLayer->addObject(&application->currentLevel->terrainSubsurface); - application->defaultLayer->addObject(&application->sidewalkPanelInstance); - application->defaultLayer->addObject(&application->sidewalkPanelInstance1); - application->defaultLayer->addObject(&application->sidewalkPanelInstance2); - application->defaultLayer->addObject(&application->sidewalkPanelInstance3); - application->defaultLayer->addObject(&application->sidewalkPanelInstance4); - application->defaultLayer->addObject(&application->soilInstance); - - // Spawn ants - for (int i = 0; i < 200; ++i) - { - Navmesh::Triangle* triangle = (*navmesh->getTriangles())[0]; - - Ant* ant = application->colony->spawn(navmesh, triangle, normalize_barycentric(Vector3(0.5f))); - - Vector3 forward = glm::normalize(triangle->edge->vertex->position - triangle->edge->next->vertex->position); - Vector3 up = triangle->normal; - ant->setOrientation(forward, up); - - application->defaultLayer->addObject(ant->getModelInstance()); - ant->setState(Ant::State::WANDER); - } - - // Setup camera controller - application->orbitCam->attachCamera(&application->camera); - //application->orbitCam->setFocalPoint(Vector3(0.0f)); - //application->orbitCam->setFocalDistance(250.0f); - //application->orbitCam->setElevation(glm::radians(35.0f)); - //application->orbitCam->setAzimuth(glm::radians(-45.0f)); - application->orbitCam->setTargetFocalPoint(Vector3(0.0f)); - application->orbitCam->setTargetFocalDistance(250.0f); - application->orbitCam->setTargetElevation(glm::radians(35.0f)); - //application->orbitCam->setTargetAzimuth(glm::radians(-45.0f)); - application->orbitCam->update(0.0f); - - application->simulationPaused = false; - - // Select forceps tool - //application->deselectTool(application->currentTool); - //application->selectTool(application->forceps); - application->pieMenu->select(1); - - // Position options menu - application->optionsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.5f)); - application->controlsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.5f)); - application->levelsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.5f)); - - // Show level name - application->levelNameLabel->setText(application->getLevelName(application->currentWorldIndex, application->currentLevelIndex)); - //application->levelNameLabel->setVisible(true); - - // Begin fade-in - application->fadeInTween->start(); - - application->mouse->addMouseButtonObserver(this); - application->mouse->addMouseMotionObserver(this); -} - -void GameState::execute() -{ - // Pause simulation - if (application->togglePause.isTriggered() && !application->togglePause.wasTriggered()) - { - if (application->simulationPaused) - { - application->unpauseSimulation(); - } - else - { - application->pauseSimulation(); - } - } - // Open pause menu - else if (application->togglePauseMenu.isTriggered() && !application->togglePauseMenu.wasTriggered()) - { - if (application->activeMenu == application->pauseMenu) - { - application->closePauseMenu(); - } - else - { - application->openPauseMenu(); - } - } - - // Navigate menu - if (application->activeMenu != nullptr) - { - MenuItem* selectedItem = application->activeMenu->getSelectedItem(); - - if (application->menuDown.isTriggered() && !application->menuDown.wasTriggered()) - { - if (selectedItem != nullptr) - { - if (selectedItem->getItemIndex() < application->activeMenu->getItemCount() - 1) - { - application->selectMenuItem(selectedItem->getItemIndex() + 1); - } - else - { - application->selectMenuItem(0); - } - } - else - { - application->selectMenuItem(0); - } - } - else if (application->menuUp.isTriggered() && !application->menuUp.wasTriggered()) - { - if (selectedItem != nullptr) - { - if (selectedItem->getItemIndex() > 0) - { - application->selectMenuItem(selectedItem->getItemIndex() - 1); - } - else - { - application->selectMenuItem(application->activeMenu->getItemCount() - 1); - } - } - else - { - application->selectMenuItem(application->activeMenu->getItemCount() - 1); - } - } - - if (application->menuLeft.isTriggered() && !application->menuLeft.wasTriggered()) - { - application->decrementMenuItem(); - } - else if (application->menuRight.isTriggered() && !application->menuRight.wasTriggered()) - { - application->incrementMenuItem(); - } - - if (application->menuSelect.isTriggered() && !application->menuSelect.wasTriggered()) - { - application->activateMenuItem(); - } - } - else - { - // Select rig - if (application->switchRig.isTriggered() && !application->switchRig.wasTriggered()) - { - if (application->activeRig == application->orbitCam) - { - application->freeCam->setTranslation(application->orbitCam->getTranslation()); - //application->freeCam->setRotation(application->orbitCam->getRotation()); - - application->orbitCam->detachCamera(); - application->freeCam->attachCamera(&application->camera); - application->activeRig = application->freeCam; - } - else if (application->activeRig == application->freeCam) - { - application->freeCam->detachCamera(); - application->orbitCam->attachCamera(&application->camera); - application->activeRig = application->orbitCam; - } - } - - // Move camera - if (application->activeRig == application->orbitCam) - { - Vector2 movementVector(0.0f); - if (application->cameraMoveLeft.isTriggered()) - movementVector.x -= application->cameraMoveLeft.getCurrentValue(); - if (application->cameraMoveRight.isTriggered()) - movementVector.x += application->cameraMoveRight.getCurrentValue(); - if (application->cameraMoveForward.isTriggered()) - movementVector.y -= application->cameraMoveForward.getCurrentValue(); - if (application->cameraMoveBack.isTriggered()) - movementVector.y += application->cameraMoveBack.getCurrentValue(); - if (movementVector.x != 0.0f || movementVector.y != 0.0f) - { - movementVector *= 0.005f * application->orbitCam->getFocalDistance() * application->dt / (1.0f / 60.0f); - application->orbitCam->move(movementVector); - } - - // Zoom camera - float zoomFactor = application->orbitCam->getFocalDistance() / 10.0f * application->dt / (1.0f / 60.0f); - if (application->cameraZoomIn.isTriggered()) - application->orbitCam->zoom(zoomFactor * application->cameraZoomIn.getCurrentValue()); - if (application->cameraZoomOut.isTriggered()) - application->orbitCam->zoom(-zoomFactor * application->cameraZoomOut.getCurrentValue()); - - // Rotate camera - if (application->cameraRotateCW.isTriggered() && !application->cameraRotateCW.wasTriggered()) - { - application->orbitCam->rotate(glm::radians(-45.0f)); - } - if (application->cameraRotateCCW.isTriggered() && !application->cameraRotateCCW.wasTriggered()) - { - application->orbitCam->rotate(glm::radians(45.0f)); - } - } - else if (application->activeRig == application->freeCam) - { - Vector2 movementVector(0.0f); - if (application->cameraMoveForward.isTriggered()) - movementVector.x += application->cameraMoveForward.getCurrentValue(); - if (application->cameraMoveBack.isTriggered()) - movementVector.x -= application->cameraMoveBack.getCurrentValue(); - if (application->cameraMoveLeft.isTriggered()) - movementVector.y -= application->cameraMoveLeft.getCurrentValue(); - if (application->cameraMoveRight.isTriggered()) - movementVector.y += application->cameraMoveRight.getCurrentValue(); - if (movementVector.x != 0.0f || movementVector.y != 0.0f) - { - movementVector = glm::normalize(movementVector) * 0.15f; - application->freeCam->move(movementVector); - } - } - } - - // Update camera rig - application->activeRig->update(application->dt); - - // Picking - if (!application->simulationPaused) - { - glm::ivec2 mousePosition = application->mouse->getCurrentPosition(); - mousePosition.y = application->resolution.y - mousePosition.y; - Vector4 viewport(0.0f, 0.0f, application->resolution.x, application->resolution.y); - Vector3 mouseNear = application->camera.unproject(Vector3(mousePosition.x, mousePosition.y, 0.0f), viewport); - Vector3 mouseFar = application->camera.unproject(Vector3(mousePosition.x, mousePosition.y, 1.0f), viewport); - - pickingRay.origin = mouseNear; - pickingRay.direction = glm::normalize(mouseFar - mouseNear); - - std::list triangles; - application->currentLevel->terrain.getSurfaceOctree()->query(pickingRay, &triangles); - - auto result = intersects(pickingRay, triangles); - if (std::get<0>(result)) - { - pick = pickingRay.extrapolate(std::get<1>(result)); - - std::size_t triangleIndex = std::get<3>(result); - pickTriangle = (*application->currentLevel->terrain.getSurfaceNavmesh()->getTriangles())[triangleIndex]; - - /* - float forcepsDistance = application->forcepsSwoopTween->getTweenValue(); - - //Quaternion rotation = glm::rotation(Vector3(0, 1, 0), triangle->normal); - Quaternion rotation = glm::angleAxis(application->orbitCam->getAzimuth(), Vector3(0, 1, 0)) * - glm::angleAxis(glm::radians(15.0f), Vector3(0, 0, -1)); - - Vector3 translation = pick + rotation * Vector3(0, forcepsDistance, 0); - - // Set tool position - application->forcepsModelInstance.setTranslation(translation); - application->forcepsModelInstance.setRotation(rotation); - */ - } - - // Update tools - if (application->currentTool != nullptr) - { - application->currentTool->setPick(pick); - application->currentTool->update(application->dt); - } - - int iterations = application->fastForward.isTriggered() ? 10 : 1; - for (int iteration = 0; iteration < iterations; ++iteration) - { - application->colony->getHomingMatrix()->evaporate(); - application->colony->getRecruitmentMatrix()->evaporate(); - - static int frame = 0; - if (frame++ % DIFFUSION_FRAME == 0) - { - application->colony->getHomingMatrix()->diffuse(); - application->colony->getRecruitmentMatrix()->diffuse(); - } - - application->colony->update(application->dt); - } - - static bool bla = false; - bla = !bla; - // Update pheromone texture - if (bla) - { - float cmyk[4]; - float rgb[3]; - const float* bufferH = application->colony->getHomingMatrix()->getActiveBuffer(); - const float* bufferR = application->colony->getRecruitmentMatrix()->getActiveBuffer(); - - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, application->pheromonePBO); - GLubyte* data = (GLubyte*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); - GLubyte* channel = data; - - std::size_t index = 0; - for (int y = 0; y < application->pheromoneTexture.getHeight(); ++y) - { - for (int x = 0; x < application->pheromoneTexture.getWidth(); ++x) - { - float concentrationH = std::min(1.0f, bufferH[index]); - float concentrationR = std::min(1.0f, bufferR[index]); - - cmyk[0] = std::min(1.0f, concentrationH * HOMING_PHEROMONE_COLOR[0] + concentrationR * RECRUITMENT_PHEROMONE_COLOR[0]); - cmyk[1] = std::min(1.0f, concentrationH * HOMING_PHEROMONE_COLOR[1] + concentrationR * RECRUITMENT_PHEROMONE_COLOR[1]); - cmyk[2] = std::min(1.0f, concentrationH * HOMING_PHEROMONE_COLOR[2] + concentrationR * RECRUITMENT_PHEROMONE_COLOR[2]); - cmyk[3] = 0.35f; - - cmykToRGB(cmyk, rgb); - - GLubyte b = static_cast(std::min(255.0f, rgb[2] * 255.0f)); - GLubyte g = static_cast(std::min(255.0f, rgb[1] * 255.0f)); - GLubyte r = static_cast(std::min(255.0f, rgb[0] * 255.0f)); - GLubyte a = static_cast(std::min(255.0f, std::max(concentrationH, concentrationR) * 64.0f)); - - *(channel++) = b; - *(channel++) = g; - *(channel++) = r; - *(channel++) = a; - - ++index; - } - } - - glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER); - - glBindTexture(GL_TEXTURE_2D, application->pheromoneTextureID); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, application->pheromoneTexture.getWidth(), application->pheromoneTexture.getHeight(), GL_BGRA, GL_UNSIGNED_BYTE, nullptr); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); - //glGenerateMipmap(GL_TEXTURE_2D); - } - } -} - -void GameState::exit() -{ - // Remove input observers - application->mouse->removeMouseButtonObserver(this); - application->mouse->removeMouseMotionObserver(this); - - // Clear scene - //application->defaultLayer->removeObject(&application->currentLevel->terrainSurface); - //application->defaultLayer->removeObject(&application->currentLevel->terrainSubsurface); - application->defaultLayer->removeObject(&application->sidewalkPanelInstance); - application->defaultLayer->removeObject(application->forceps->getModelInstance()); - application->defaultLayer->removeObject(application->lens->getModelInstance()); - application->defaultLayer->removeObject(application->lens->getSpotlight()); - application->defaultLayer->removeObject(application->brush->getModelInstance()); - for (std::size_t i = 0; i < application->colony->getAntCount(); ++i) - { - Ant* ant = application->colony->getAnt(i); - application->defaultLayer->removeObject(ant->getModelInstance()); - } - - // Kill all ants - application->colony->killAll(); - - // Hide HUD - application->rectangularPaletteImage->setVisible(false); - application->rectangularPaletteImage->setActive(false); - application->toolbar->getContainer()->setVisible(false); - application->toolbar->getContainer()->setActive(false); -} - -void GameState::mouseButtonPressed(int button, int x, int y) -{ - if (button == 1) - { - if (application->forceps->isActive()) - { - application->forceps->pinch(); - } - else if (application->brush->isActive()) - { - application->brush->press(); - } - else if (application->lens->isActive()) - { - application->lens->focus(); - } - - dragging = true; - } - -} - -void GameState::mouseButtonReleased(int button, int x, int y) -{ - if (button == 1) - { - if (application->forceps->isActive()) - { - application->forceps->release(); - } - else if (application->brush->isActive()) - { - application->brush->release(); - } - else if (application->lens->isActive()) - { - application->lens->unfocus(); - } - - dragging = false; - } -} - -void GameState::mouseMoved(int x, int y) -{ - oldMousePosition = mousePosition; - mousePosition = Vector2(x, y); - - if (application->activeRig == application->freeCam && dragging) - { - float rotationScale = glm::radians(180.0f) / application->resolution.y; - Vector2 difference = mousePosition - oldMousePosition; - - float pan = -difference.x * rotationScale; - float tilt = -difference.y * rotationScale; - - application->freeCam->rotate(pan, tilt); - } -} diff --git a/src/states/game-state.hpp b/src/states/game-state.hpp old mode 100644 new mode 100755 index da4e4dc..6a6a904 --- a/src/states/game-state.hpp +++ b/src/states/game-state.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -20,37 +20,30 @@ #ifndef GAME_STATE_HPP #define GAME_STATE_HPP -#include "../application-state.hpp" -#include "../input.hpp" -#include "../game/ant.hpp" -#include "../game/navmesh.hpp" +class Game; -#include -using namespace Emergent; - -class GameState: public ApplicationState, public MouseButtonObserver, public MouseMotionObserver +/** + * Abstract base class for game states. + */ +class GameState { public: - GameState(Application* application); + GameState(Game* game); + virtual ~GameState(); - virtual void enter(); - virtual void execute(); - virtual void exit(); + // Run once when the state is initially entered + virtual void enter() = 0; - virtual void mouseButtonPressed(int button, int x, int y); - virtual void mouseButtonReleased(int button, int x, int y); - virtual void mouseMoved(int x, int y); + // Run continually while the state is valid + virtual void execute() = 0; -private: - ModelInstance terrainSurface; - ModelInstance terrainSubsurface; - Vector3 pick; - Ray pickingRay; - Navmesh::Triangle* pickTriangle; - bool dragging; - Vector2 oldMousePosition; - Vector2 mousePosition; + // Run once when the state is exited + virtual void exit() = 0; + +protected: + Game* game; }; -#endif // GAME_STATE_HPP \ No newline at end of file +#endif // GAME_STATE_HPP + diff --git a/src/states/sandbox-state.cpp b/src/states/sandbox-state.cpp new file mode 100755 index 0000000..bfcb2a6 --- /dev/null +++ b/src/states/sandbox-state.cpp @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "sandbox-state.hpp" +#include "game.hpp" +#include "ui/ui.hpp" +#include "graphics/lighting-render-pass.hpp" +#include "game/camera-rig.hpp" +#include "game/tool.hpp" +#include "game/lens.hpp" +#include "game/forceps.hpp" +#include "game/brush.hpp" +#include + +SandboxState::SandboxState(Game* game): + GameState(game) +{} + +SandboxState::~SandboxState() +{} + +void SandboxState::enter() +{ + // Subscribe this state to input events + game->getEventDispatcher()->subscribe(this); + game->getEventDispatcher()->subscribe(this); + game->getEventDispatcher()->subscribe(this); + + // Make HUD visible + //game->hudContainer->setVisible(true); + game->radialMenuContainer->setVisible(false); + + // Show mouse + game->mouse->setVisible(false); + + game->fadeIn(1.0f, Vector3(0.0f), nullptr); + + Vector3 focalPoint = Vector3(0.0f); + float focalDistance = 5.0f; + float elevation = glm::radians(30.0f); + float azimuth = glm::radians(-45.0f); + + game->cameraRig = game->orbitCam; + game->orbitCam->setFocalPoint(focalPoint); + game->orbitCam->setTargetFocalPoint(focalPoint); + game->orbitCam->setFocalDistance(focalDistance); + game->orbitCam->setTargetFocalDistance(focalDistance); + game->orbitCam->setElevation(elevation); + game->orbitCam->setTargetElevation(elevation); + game->orbitCam->setAzimuth(azimuth); + game->orbitCam->setTargetAzimuth(azimuth); + + game->freeCam->setTranslation(Vector3(-5, 5.0f, -5.0f)); + //game->cameraRig = game->freeCam; + game->mouse->setRelativeMode(true); + + toolIndex = 0; + game->selectTool(toolIndex); + game->currentTool->setActive(false); + + zoom = 0.5f; + noPick = false; + + //game->boxSelect(100, 100, 500, 300); +} + +void SandboxState::execute() +{ + game->lightingPass->setTime(game->time); + bool menuClosed = false; + + if (game->openRadialMenuControl.isActive() && !game->openRadialMenuControl.wasActive()) + { + game->radialMenuContainer->setVisible(true); + game->hudContainer->setVisible(false); + + savedMousePosition = game->mouse->getCurrentPosition(); + selectorVector = Vector2(0.0f); + game->mouse->setRelativeMode(true); + } + else if (!game->openRadialMenuControl.isActive() && game->openRadialMenuControl.wasActive()) + { + game->radialMenuContainer->setVisible(false); + //game->hudContainer->setVisible(true); + game->mouse->setRelativeMode(false); + game->mouse->warp(game->window, std::get<0>(savedMousePosition), std::get<1>(savedMousePosition)); + menuClosed = true; + + game->selectTool(toolIndex); + } + + float speed = 3.0f * game->timestep; + Vector2 forward = Vector2(0, -1); + Vector2 right = Vector2(1, 0); + Vector2 direction = Vector2(0); + if (game->moveForwardControl.isActive()) + { + direction += forward; + } + if (game->moveBackControl.isActive()) + { + direction -= forward; + } + if (game->moveLeftControl.isActive()) + { + direction -= right; + } + if (game->moveRightControl.isActive()) + { + direction += right; + } + if (glm::length2(direction) != 0.0f) + { + direction = glm::normalize(direction); + } + + float rotationAngle = glm::radians(180.0f) * game->timestep; + if (game->rotateCCWControl.isActive()) + { + game->orbitCam->rotate(-rotationAngle); + } + if (game->rotateCWControl.isActive()) + { + game->orbitCam->rotate(rotationAngle); + } + + + + float zoomSpeed = 3.0f * game->timestep; + if (game->zoomInControl.isActive()) + { + zoom += zoomSpeed * game->zoomInControl.getCurrentValue(); + } + if (game->zoomOutControl.isActive()) + { + zoom -= zoomSpeed * game->zoomOutControl.getCurrentValue(); + } + zoom = std::max(0.0f, std::min(1.0f, zoom)); + + float minFocalDistance = 5.0f; + float maxFocalDistance = 70.0f; + float logMinFocalDistance = std::log(minFocalDistance); + float logMaxFocalDistance = std::log(maxFocalDistance); + float logFocalDistance = lerp(logMinFocalDistance, logMaxFocalDistance, 1.0f - zoom); + float focalDistance = std::exp(logFocalDistance); + + float minFOV = glm::radians(30.0f); + float maxFOV = glm::radians(60.0f); + float logMinFOV = std::log(minFOV); + float logMaxFOV = std::log(maxFOV); + float logFOV = lerp(logMinFOV, logMaxFOV, 1.0f - zoom); + float fov = std::exp(logFOV); + + float minClipNear = 1.0f; + float maxClipNear = 10.0f; + float minClipFar = 80.0f; + float maxClipFar = 350.0f; + float logMinClipNear = std::log(minClipNear); + float logMaxClipNear = std::log(maxClipNear); + float logMinClipFar = std::log(minClipFar); + float logMaxClipFar = std::log(maxClipFar); + float logClipNear = lerp(logMinClipNear, logMaxClipNear, 1.0f - zoom); + float logClipFar = lerp(logMinClipFar, logMaxClipFar, 1.0f - zoom); + float clipNear = std::exp(logClipNear); + float clipFar = std::exp(logClipFar); + + float nearElevation = glm::radians(40.0f); + float farElevation = glm::radians(80.0f); + float logNearElevation = std::log(nearElevation); + float logFarElevation = std::log(farElevation); + float logElevation = lerp(logNearElevation, logFarElevation, 1.0f - zoom); + float elevation = std::exp(logElevation); + + float minMovementSpeed = 2.5f * game->timestep; + float maxMovementSpeed = 40.0f * game->timestep; + float logMinMovementSpeed = std::log(minMovementSpeed); + float logMaxMovementSpeed = std::log(maxMovementSpeed); + float logMovementSpeed = lerp(logMinMovementSpeed, logMaxMovementSpeed, 1.0f - zoom); + float movementSpeed = std::exp(logMovementSpeed); + + game->orbitCam->setTargetFocalDistance(focalDistance); + game->orbitCam->getCamera()->setPerspective(fov, (float)game->w / (float)game->h, clipNear, clipFar); + game->orbitCam->move(direction * movementSpeed); + game->orbitCam->setFocalPoint(game->orbitCam->getTargetFocalPoint()); + //game->orbitCam->setTargetElevation(elevation); + // + + if (game->cameraRig == game->freeCam) + { + game->freeCam->move(direction * movementSpeed); + } + + if (game->cameraRig) + { + game->cameraRig->update(game->timestep); + } + + float nearLabelDistance = 0.25f; + float farLabelDistance = 1.0f; + float logNearLabelDistance = std::log(nearLabelDistance); + float logFarLabelDistance = std::log(farLabelDistance); + float logLabelDistance = lerp(logNearLabelDistance, logFarLabelDistance, 1.0f - zoom); + float labelDistance = std::exp(logLabelDistance); + + if (!game->radialMenuContainer->isVisible() && !menuClosed) + { + // Picking + Vector3 pick; + std::tuple mousePosition = game->mouse->getCurrentPosition(); + + std::get<1>(mousePosition) = game->h - std::get<1>(mousePosition); + Vector4 viewport(0.0f, 0.0f, game->w, game->h); + Vector3 mouseNear = game->cameraRig->getCamera()->unproject(Vector3(std::get<0>(mousePosition), std::get<1>(mousePosition), 0.0f), viewport); + Vector3 mouseFar = game->cameraRig->getCamera()->unproject(Vector3(std::get<0>(mousePosition), std::get<1>(mousePosition), 1.0f), viewport); + + Ray pickingRay; + pickingRay.origin = mouseNear; + pickingRay.direction = glm::normalize(mouseFar - mouseNear); + + Plane plane(Vector3(0.0f, 1.0f, 0.0f), Vector3(0.0f)); + auto pickingIntersection = pickingRay.intersects(plane); + bool picked = std::get<0>(pickingIntersection); + if (picked) + { + pick = pickingRay.extrapolate(std::get<1>(pickingIntersection)); + } + + if (picked) + { + if (game->currentTool) + { + if (!noPick) + { + game->currentTool->setPick(pick); + } + game->currentTool->update(game->timestep); + } + + if (game->adjustCameraControl.isActive() || game->dragCameraControl.isActive()) + { + noPick = true; + } + else + { + noPick = false; + } + + + + if (game->adjustCameraControl.isActive() && !game->adjustCameraControl.wasActive()) + { + Vector3 focalPoint = pick; + game->orbitCam->setTargetFocalPoint(focalPoint); + savedMousePosition = game->mouse->getCurrentPosition(); + game->mouse->setRelativeMode(true); + } + else if (game->dragCameraControl.isActive()) + { + if (!game->dragCameraControl.wasActive()) + { + dragMousePosition = mousePosition; + dragReferencePoint = pick; + dragOffset = dragReferencePoint; + } + else + { + Vector4 viewport(0.0f, 0.0f, game->w, game->h); + Vector3 dragMouseNear = game->cameraRig->getCamera()->unproject(Vector3(std::get<0>(dragMousePosition), std::get<1>(dragMousePosition), 0.0f), viewport); + Vector3 dragMouseFar = game->cameraRig->getCamera()->unproject(Vector3(std::get<0>(dragMousePosition), std::get<1>(dragMousePosition), 1.0f), viewport); + + Ray dragRay; + dragRay.origin = dragMouseNear; + dragRay.direction = glm::normalize(dragMouseFar - dragMouseNear); + auto dragPickResult = pickingRay.intersects(plane); + + Vector3 dragPick; + if (std::get<0>(dragPickResult)) + { + dragPick = dragRay.extrapolate(std::get<1>(dragPickResult)); + + + game->orbitCam->setTargetFocalPoint(game->orbitCam->getTargetFocalPoint() + dragOffset - pick); + + game->orbitCam->setFocalPoint(game->orbitCam->getTargetFocalPoint()); + } + + + } + } + } + } + + if (!game->adjustCameraControl.isActive() && game->adjustCameraControl.wasActive()) + { + game->mouse->setRelativeMode(false); + game->mouse->warp(game->window, game->w / 2, game->h / 2); + noPick = false; + } + + if (game->toggleNestViewControl.isActive() && !game->toggleNestViewControl.wasActive()) + { + game->fadeOut(0.5f, Vector3(0.0f), std::bind(&Game::fadeIn, game, 0.5f, Vector3(0.0f), nullptr)); + } +} + +void SandboxState::exit() +{ + // Unsubscribe this state from input events + game->getEventDispatcher()->unsubscribe(this); + game->getEventDispatcher()->unsubscribe(this); + game->getEventDispatcher()->unsubscribe(this); + + // Make HUD invisible + game->hudContainer->setVisible(false); +} + +void SandboxState::handleEvent(const MouseMovedEvent& event) +{ + float dx = event.dx; + float dy = event.dy; + if (game->radialMenuContainer->isVisible()) + { + selectorVector.x += event.dx * 0.5f; + selectorVector.y += event.dy * 0.5f; + + float lengthSquared = glm::length2(selectorVector); + if (lengthSquared > 0.0f) + { + // Limit selector vector magnitude + float maxLength = 100.0f; + if (lengthSquared > maxLength * maxLength) + { + selectorVector = (selectorVector / std::sqrt(lengthSquared)) * maxLength; + lengthSquared = maxLength * maxLength; + } + + float minLength = 3.0f; + if (lengthSquared >= minLength * minLength) + { + Vector2 selectorDirection = selectorVector / std::sqrt(lengthSquared); + + float angle = std::atan2(-selectorDirection.y, selectorDirection.x) + twoPi(); + float sectorAngle = twoPi() / 8.0f; + int sector = static_cast((angle + sectorAngle * 0.5f) / sectorAngle); + game->radialMenuSelectorImage->setRotation(static_cast(sector) * sectorAngle); + game->radialMenuImage->setRotation(static_cast(sector) * sectorAngle); + toolIndex = (8 - ((sector - 4) % 8)) % 8; + } + } + } + else if (game->adjustCameraControl.isActive()) + { + bool invertX = true; + bool invertY = false; + + float rotationFactor = event.dx; + float elevationFactor = event.dy; + + if (invertX) + { + rotationFactor *= -1.0f; + } + if (invertY) + { + elevationFactor *= -1.0f; + } + + float rotation = glm::radians(22.5f) * rotationFactor * game->timestep; + + float minElevation = glm::radians(-80.0f); + float maxElevation = glm::radians(80.0f); + float elevation = game->orbitCam->getTargetElevation() + elevationFactor * 0.25f * game->timestep; + elevation = std::min(maxElevation, std::max(minElevation, elevation)); + + + game->orbitCam->rotate(rotation); + game->orbitCam->setTargetElevation(elevation); + } + + if (game->cameraRig == game->freeCam) + { + float angle = glm::radians(0.5f); + bool invertX = false; + bool invertY = false; + + float pan = -dx * angle; + float tilt = -dy * angle; + + if (invertX) + pan = -pan; + if (invertY) + tilt = -tilt; + game->freeCam->rotate(pan, tilt); + } + + game->brush->setTiltParams(Vector2(event.x, event.y), Vector2(game->w, game->h)); +} + +void SandboxState::handleEvent(const MouseButtonPressedEvent& event) +{ + if (event.button == 1) + { + game->lens->focus(); + game->forceps->pinch(); + game->brush->press(); + } +} + +void SandboxState::handleEvent(const MouseButtonReleasedEvent& event) +{ + if (event.button == 1) + { + game->lens->unfocus(); + game->forceps->release(); + game->brush->release(); + } +} + diff --git a/src/states/sandbox-state.hpp b/src/states/sandbox-state.hpp new file mode 100755 index 0000000..2c0d800 --- /dev/null +++ b/src/states/sandbox-state.hpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef SANDBOX_STATE_HPP +#define SANDBOX_STATE_HPP + +#include "game-state.hpp" +#include + +#include +using namespace Emergent; + +class SandboxState: + public GameState, + public EventHandler, + public EventHandler, + public EventHandler +{ +public: + SandboxState(Game* game); + virtual ~SandboxState(); + virtual void enter(); + virtual void execute(); + virtual void exit(); + +private: + virtual void handleEvent(const MouseMovedEvent& event); + virtual void handleEvent(const MouseButtonPressedEvent& event); + virtual void handleEvent(const MouseButtonReleasedEvent& event); + + std::tuple savedMousePosition; + Vector2 selectorVector; + std::tuple dragMousePosition; + Vector3 dragReferencePoint; + Vector3 dragOffset; + Plane dragPlane; + float zoom; + int toolIndex; + bool noPick; +}; + +#endif // SANDBOX_STATE_HPP diff --git a/src/states/splash-state.cpp b/src/states/splash-state.cpp old mode 100644 new mode 100755 index 7ec5f97..caad285 --- a/src/states/splash-state.cpp +++ b/src/states/splash-state.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -18,11 +18,12 @@ */ #include "splash-state.hpp" -#include "../application.hpp" -#include "title-state.hpp" +#include "game.hpp" +#include "sandbox-state.hpp" +#include "ui/ui.hpp" -SplashState::SplashState(Application* application): - ApplicationState(application) +SplashState::SplashState(Game* game): + GameState(game) {} SplashState::~SplashState() @@ -30,55 +31,121 @@ SplashState::~SplashState() void SplashState::enter() { - application->splashBackgroundImage->setVisible(true); - application->splashImage->setVisible(true); - application->splashFadeInTween->start(); -} + AnimationChannel* channel; -void SplashState::execute() -{ - // Listen for splash screen skip - InputEvent event; - application->inputManager->listen(&event); - if (event.type != InputEvent::Type::NONE) - { - // Update control profile and input manager - application->menuControlProfile->update(); - application->inputManager->update(); - - // Check if application was closed - if (application->inputManager->wasClosed() || application->escape.isTriggered()) - { - application->close(EXIT_SUCCESS); - return; - } - - // Check if fullscreen was toggled - else if (application->toggleFullscreen.isTriggered() && !application->toggleFullscreen.wasTriggered()) + // Construct fade-in animation clip + fadeInClip.setInterpolator(lerp); + channel = fadeInClip.addChannel(0); + channel->insertKeyframe(0.0f, 0.0f); + channel->insertKeyframe(0.5f, 0.0f); + channel->insertKeyframe(1.25f, 1.0f); + channel->insertKeyframe(5.25f, 1.0f); + + // Construct fade-out animation clip + fadeOutClip.setInterpolator(lerp); + channel = fadeOutClip.addChannel(0); + channel->insertKeyframe(0.0f, 1.0f); + channel->insertKeyframe(0.75f, 0.0f); + channel->insertKeyframe(1.25f, 0.0f); + + // Setup animate callback to change splash screen opacity + fadeAnimation.setAnimateCallback + ( + [this](std::size_t id, float opacity) { - application->changeFullscreen(); + this->game->splashImage->setTintColor(Vector4(1.0f, 1.0f, 1.0f, opacity)); } - else + ); + + // Setup end callback to fade-out after fading in + fadeAnimation.setEndCallback + ( + [this]() { - // Clear screen - glClear(GL_COLOR_BUFFER_BIT); - SDL_GL_SwapWindow(application->window); - - // Stop splash tweens - application->splashFadeInTween->stop(); - application->splashHangTween->stop(); - application->splashFadeOutTween->stop(); - - // Change to title state - application->changeState(application->titleState); - return; + // Change clip to fade-out + this->fadeAnimation.setClip(&fadeOutClip); + this->fadeAnimation.setTimeFrame(this->fadeOutClip.getTimeFrame()); + this->fadeAnimation.rewind(); + this->fadeAnimation.play(); + + // Setup end callback to change to title state after fading out + this->fadeAnimation.setEndCallback + ( + [this]() + { + //this->game->changeState(this->game->titleState); + this->game->changeState(this->game->sandboxState); + } + ); } - } + ); + + // Setup fade animation to fade-in + fadeAnimation.setSpeed(1.0f); + fadeAnimation.setLoop(false); + fadeAnimation.setClip(&fadeInClip); + fadeAnimation.setTimeFrame(fadeInClip.getTimeFrame()); + + // Play the fade animation + fadeAnimation.play(); + + // Add fade animation to the animator + game->getAnimator()->addAnimation(&fadeAnimation); + + // Subscribe this state to input events + game->getEventDispatcher()->subscribe(this); + game->getEventDispatcher()->subscribe(this); + game->getEventDispatcher()->subscribe(this); + + // Make splash screen visible + game->splashBackgroundImage->setVisible(true); + game->splashImage->setVisible(true); + game->splashImage->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.0f)); + game->splashBackgroundImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 1.0f)); + game->splashImage->resetTweens(); + game->splashBackgroundImage->resetTweens(); + game->uiRootElement->update(); + + // Hide mouse + game->mouse->setVisible(false); } +void SplashState::execute() +{} + void SplashState::exit() { - // Hide splash screen - application->splashBackgroundImage->setVisible(false); - application->splashImage->setVisible(false); + // Remove fade animation from the animator + game->getAnimator()->removeAnimation(&fadeAnimation); + + // Unsubscribe this state from input events + game->getEventDispatcher()->unsubscribe(this); + game->getEventDispatcher()->unsubscribe(this); + game->getEventDispatcher()->unsubscribe(this); + + // Make splash screen invisible + game->splashBackgroundImage->setVisible(false); + game->splashImage->setVisible(false); +} + +void SplashState::handleEvent(const KeyPressedEvent& event) +{ + skip(); +} + +void SplashState::handleEvent(const MouseButtonPressedEvent& event) +{ + skip(); +} + +void SplashState::handleEvent(const GamepadButtonPressedEvent& event) +{ + skip(); } + +void SplashState::skip() +{ + game->splashImage->setVisible(false); + game->changeState(game->sandboxState); +} + diff --git a/src/states/splash-state.hpp b/src/states/splash-state.hpp old mode 100644 new mode 100755 index 8744db2..94cf572 --- a/src/states/splash-state.hpp +++ b/src/states/splash-state.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -20,24 +20,36 @@ #ifndef SPLASH_STATE_HPP #define SPLASH_STATE_HPP -#include "../application-state.hpp" -#include "../input.hpp" +#include "game-state.hpp" #include using namespace Emergent; /** - * Displays a splash screen. + * Displays the splash screen. */ -class SplashState: public ApplicationState +class SplashState: + public GameState, + public EventHandler, + public EventHandler, + public EventHandler { public: - SplashState(Application* application); + SplashState(Game* game); virtual ~SplashState(); - virtual void enter(); virtual void execute(); virtual void exit(); + +private: + virtual void handleEvent(const KeyPressedEvent& event); + virtual void handleEvent(const MouseButtonPressedEvent& event); + virtual void handleEvent(const GamepadButtonPressedEvent& event); + void skip(); + + Animation fadeAnimation; + AnimationClip fadeInClip; + AnimationClip fadeOutClip; }; -#endif // SPLASH_STATE_HPP \ No newline at end of file +#endif // SPLASH_STATE_HPP diff --git a/src/states/title-state.cpp b/src/states/title-state.cpp deleted file mode 100644 index 76aaa7d..0000000 --- a/src/states/title-state.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#include "title-state.hpp" -#include "../application.hpp" -#include "../camera-rig.hpp" -#include "../ui/menu.hpp" -#include -#include - -TitleState::TitleState(Application* application): - ApplicationState(application) -{} - -TitleState::~TitleState() -{} - -void TitleState::enter() -{ - application->inputManager->addWindowObserver(this); - windowResized(application->resolution.x, application->resolution.y); - - application->camera.setPerspective( - glm::radians(30.0f), - application->resolution.x / application->resolution.y, - 0.5f, - 500.0f); - - // Setup camera controller - application->orbitCam->attachCamera(&application->camera); - application->orbitCam->setFocalPoint(Vector3(0.0f)); - application->orbitCam->setFocalDistance(50.0f); - application->orbitCam->setElevation(glm::radians(-30.0f)); - application->orbitCam->setAzimuth(glm::radians(180.0f)); - application->orbitCam->setTargetFocalPoint(application->orbitCam->getFocalPoint()); - application->orbitCam->setTargetFocalDistance(application->orbitCam->getFocalDistance()); - application->orbitCam->setTargetElevation(application->orbitCam->getElevation()); - application->orbitCam->setTargetAzimuth(application->orbitCam->getAzimuth()); - application->orbitCam->update(0.0f); - - // Dim background - application->darkenImage->setVisible(true); - - // Show title - application->titleImage->setVisible(true); - application->titleImage->setTintColor(Vector4(1.0f)); - application->copyrightLabel->setVisible(true); - - // Show "Press any key" - application->anyKeyLabel->setVisible(true); - application->anyKeyLabel->setActive(true); - application->anyKeyFadeInTween->reset(); - application->anyKeyFadeInTween->start(); - - // Position options menu - application->optionsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.8f)); - application->controlsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.8f)); - application->levelsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.8f)); - - // Setup fade-in - application->blackoutImage->setVisible(true); - application->fadeInTween->start(); -} - -void TitleState::execute() -{ - if (application->anyKeyLabel->isActive()) - { - InputEvent event; - application->inputManager->listen(&event); - - if (event.type != InputEvent::Type::NONE) - { - application->anyKeyLabel->setVisible(false); - application->anyKeyLabel->setActive(false); - application->anyKeyFadeInTween->stop(); - application->anyKeyFadeOutTween->stop(); - application->inputManager->update(); - - application->openMenu(application->mainMenu); - } - } - else - { - // Navigate menu - if (application->activeMenu != nullptr) - { - MenuItem* selectedItem = application->activeMenu->getSelectedItem(); - - if (application->menuDown.isTriggered() && !application->menuDown.wasTriggered()) - { - if (selectedItem != nullptr) - { - if (selectedItem->getItemIndex() < application->activeMenu->getItemCount() - 1) - { - application->selectMenuItem(selectedItem->getItemIndex() + 1); - } - else - { - application->selectMenuItem(0); - } - } - else - { - application->selectMenuItem(0); - } - } - else if (application->menuUp.isTriggered() && !application->menuUp.wasTriggered()) - { - if (selectedItem != nullptr) - { - if (selectedItem->getItemIndex() > 0) - { - application->selectMenuItem(selectedItem->getItemIndex() - 1); - } - else - { - application->selectMenuItem(application->activeMenu->getItemCount() - 1); - } - } - else - { - application->selectMenuItem(application->activeMenu->getItemCount() - 1); - } - } - - if (application->menuLeft.isTriggered() && !application->menuLeft.wasTriggered()) - { - application->decrementMenuItem(); - } - else if (application->menuRight.isTriggered() && !application->menuRight.wasTriggered()) - { - application->incrementMenuItem(); - } - - if (application->menuSelect.isTriggered() && !application->menuSelect.wasTriggered()) - { - application->activateMenuItem(); - } - } - } - - if (application->escape.isTriggered()) - { - application->close(EXIT_SUCCESS); - } -} - -void TitleState::exit() -{ - // Remove input observers - application->inputManager->removeWindowObserver(this); - - // Hide UI - application->titleImage->setVisible(false); - application->copyrightLabel->setVisible(false); - application->anyKeyLabel->setVisible(false); - application->darkenImage->setVisible(false); -} - -void TitleState::windowClosed() -{ - application->close(EXIT_SUCCESS); -} - -void TitleState::windowResized(int width, int height) -{ - /* - // Update application dimensions - application->width = width; - application->height = height; - if (application->fullscreen) - { - application->fullscreenWidth = width; - application->fullscreenHeight = height; - } - else - { - application->windowedWidth = width; - application->windowedHeight = height; - } - - // Setup default render target - application->defaultRenderTarget.width = application->width; - application->defaultRenderTarget.height = application->height; - - // Resize UI - application->resizeUI(); - - // 3D camera - application->camera.setPerspective( - glm::radians(25.0f), - (float)application->width / (float)application->height, - 0.1f, - 1000.0f); - */ -} diff --git a/src/stb/stb_image.cpp b/src/stb/stb_image.cpp new file mode 100644 index 0000000..88b2d4b --- /dev/null +++ b/src/stb/stb_image.cpp @@ -0,0 +1,3 @@ +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + diff --git a/src/stb/stb_image.h b/src/stb/stb_image.h new file mode 100644 index 0000000..3f92e3b --- /dev/null +++ b/src/stb/stb_image.h @@ -0,0 +1,6755 @@ +/* stb_image - v2.12 - public domain image loader - http://nothings.org/stb_image.h + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8-bit-per-channel (16 bpc not supported) + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + + Revision 2.00 release notes: + + - Progressive JPEG is now supported. + + - PPM and PGM binary formats are now supported, thanks to Ken Miller. + + - x86 platforms now make use of SSE2 SIMD instructions for + JPEG decoding, and ARM platforms can use NEON SIMD if requested. + This work was done by Fabian "ryg" Giesen. SSE2 is used by + default, but NEON must be enabled explicitly; see docs. + + With other JPEG optimizations included in this version, we see + 2x speedup on a JPEG on an x86 machine, and a 1.5x speedup + on a JPEG on an ARM machine, relative to previous versions of this + library. The same results will not obtain for all JPGs and for all + x86/ARM machines. (Note that progressive JPEGs are significantly + slower to decode than regular JPEGs.) This doesn't mean that this + is the fastest JPEG decoder in the land; rather, it brings it + closer to parity with standard libraries. If you want the fastest + decode, look elsewhere. (See "Philosophy" section of docs below.) + + See final bullet items below for more info on SIMD. + + - Added STBI_MALLOC, STBI_REALLOC, and STBI_FREE macros for replacing + the memory allocator. Unlike other STBI libraries, these macros don't + support a context parameter, so if you need to pass a context in to + the allocator, you'll have to store it in a global or a thread-local + variable. + + - Split existing STBI_NO_HDR flag into two flags, STBI_NO_HDR and + STBI_NO_LINEAR. + STBI_NO_HDR: suppress implementation of .hdr reader format + STBI_NO_LINEAR: suppress high-dynamic-range light-linear float API + + - You can suppress implementation of any of the decoders to reduce + your code footprint by #defining one or more of the following + symbols before creating the implementation. + + STBI_NO_JPEG + STBI_NO_PNG + STBI_NO_BMP + STBI_NO_PSD + STBI_NO_TGA + STBI_NO_GIF + STBI_NO_HDR + STBI_NO_PIC + STBI_NO_PNM (.ppm and .pgm) + + - You can request *only* certain decoders and suppress all other ones + (this will be more forward-compatible, as addition of new decoders + doesn't require you to disable them explicitly): + + STBI_ONLY_JPEG + STBI_ONLY_PNG + STBI_ONLY_BMP + STBI_ONLY_PSD + STBI_ONLY_TGA + STBI_ONLY_GIF + STBI_ONLY_HDR + STBI_ONLY_PIC + STBI_ONLY_PNM (.ppm and .pgm) + + Note that you can define multiples of these, and you will get all + of them ("only x" and "only y" is interpreted to mean "only x&y"). + + - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still + want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB + + - Compilation of all SIMD code can be suppressed with + #define STBI_NO_SIMD + It should not be necessary to disable SIMD unless you have issues + compiling (e.g. using an x86 compiler which doesn't support SSE + intrinsics or that doesn't support the method used to detect + SSE2 support at run-time), and even those can be reported as + bugs so I can refine the built-in compile-time checking to be + smarter. + + - The old STBI_SIMD system which allowed installing a user-defined + IDCT etc. has been removed. If you need this, don't upgrade. My + assumption is that almost nobody was doing this, and those who + were will find the built-in SIMD more satisfactory anyway. + + - RGB values computed for JPEG images are slightly different from + previous versions of stb_image. (This is due to using less + integer precision in SIMD.) The C code has been adjusted so + that the same RGB values will be computed regardless of whether + SIMD support is available, so your app should always produce + consistent results. But these results are slightly different from + previous versions. (Specifically, about 3% of available YCbCr values + will compute different RGB results from pre-1.49 versions by +-1; + most of the deviating values are one smaller in the G channel.) + + - If you must produce consistent results with previous versions of + stb_image, #define STBI_JPEG_OLD and you will get the same results + you used to; however, you will not get the SIMD speedups for + the YCbCr-to-RGB conversion step (although you should still see + significant JPEG speedup from the other changes). + + Please note that STBI_JPEG_OLD is a temporary feature; it will be + removed in future versions of the library. It is only intended for + near-term back-compatibility use. + + + Latest revision history: + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) partial animated GIF support + limited 16-bit PSD support + minor bugs, code cleanup, and compiler warnings + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) additional corruption checking + stbi_set_flip_vertically_on_load + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPEG, including x86 SSE2 & ARM NEON SIMD + progressive JPEG + PGM/PPM support + STBI_MALLOC,STBI_REALLOC,STBI_FREE + STBI_NO_*, STBI_ONLY_* + GIF bugfix + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + urraka@github (animated gif) Junggon Kim (PNM comments) + Daniel Gibson (16-bit TGA) + + Optimizations & bugfixes + Fabian "ryg" Giesen + Arseny Kapoulkine + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Martin Golini Jerry Jansson Joseph Thomson + Dave Moore Roy Eltham Hayaki Saito Phil Jordan + Won Chun Luke Graham Johan Duparc Nathan Reed + the Horde3D community Thomas Ruf Ronny Chevalier Nick Verigakis + Janez Zemva John Bartholomew Michal Cichon svdijk@github + Jonathan Blow Ken Hamada Tero Hanninen Baldur Karlsson + Laurent Gomila Cort Stratton Sergio Gonzalez romigrou@github + Aruelien Pocheville Thibault Reuille Cass Everitt Matthew Gregan + Ryamond Barbiero Paul Du Bois Engin Manap snagar@github + Michaelangel007@github Oriol Ferrer Mesia socks-the-fox + Blazej Dariusz Roszkowski + + +LICENSE + +This software is dual-licensed to the public domain and under the following +license: you are granted a perpetual, irrevocable license to copy, modify, +publish, and distribute this file as you see fit. + +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 16-bit-per-channel PNG +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - no 1-bit BMP +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *comp -- outputs # of image components in image file +// int req_comp -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'req_comp' if req_comp is non-zero, or *comp otherwise. +// If req_comp is non-zero, *comp has the number of components that _would_ +// have been output otherwise. E.g. if you set req_comp to 4, you will always +// get RGBA output, but you can check *comp to see if it's trivially opaque +// because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *comp will be unchanged. The function stbi_failure_reason() +// can be queried for an extremely brief, end-user unfriendly explanation +// of why the load failed. Define STBI_NO_FAILURE_STRINGS to avoid +// compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy to use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries do not emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// make more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// The output of the JPEG decoder is slightly different from versions where +// SIMD support was introduced (that is, for versions before 1.49). The +// difference is only +-1 in the 8-bit RGB channels, and only on a small +// fraction of pixels. You can force the pre-1.49 behavior by defining +// STBI_JPEG_OLD, but this will disable some of the SIMD decoding path +// and hence cost some performance. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image now supports loading HDR images in general, and currently +// the Radiance .HDR file format, although the support is provided +// generically. You can still load any file through the existing interface; +// if you attempt to load an HDR file, it will be automatically remapped to +// LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// By default we convert iphone-formatted PNGs back to RGB, even though +// they are internally encoded differently. You can disable this conversion +// by by calling stbi_convert_iphone_png_to_rgb(0), in which case +// you will always just get the native iphone "format" through (which +// is BGR stored in RGB). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// + + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for req_comp + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +typedef unsigned char stbi_uc; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *comp, int req_comp); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *comp, int req_comp); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *comp, int req_comp); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// NOT THREADSAFE +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); + +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y)))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// NOTE: not clear do we actually need this for the 64-bit path? +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// (but compiling with -msse2 allows the compiler to use SSE2 everywhere; +// this is just broken and gcc are jerks for not fixing it properly +// http://www.virtualdub.org/blog/pivot/entry.php?id=363 ) +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +static int stbi__sse2_available() +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +static int stbi__sse2_available() +{ +#if defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 // GCC 4.8 or later + // GCC 4.8+ has a nice way to do this + return __builtin_cpu_supports("sse2"); +#else + // portable way to do this, preferably without using GCC inline ASM? + // just bail for now. + return 0; +#endif +} +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +// assume GCC or Clang on ARM targets +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + fseek((FILE*) user, n, SEEK_CUR); +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static stbi_uc *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static stbi_uc *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static stbi_uc *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +// this is not threadsafe +static const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load = flag_true_if_should_flip; +} + +static unsigned char *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static unsigned char *stbi__load_flip(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result = stbi__load_main(s, x, y, comp, req_comp); + + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + stbi_uc temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } + + return result; +} + +#ifndef STBI_NO_HDR +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int w = *x, h = *y; + int depth = req_comp ? req_comp : *comp; + int row,col,z; + float temp; + + // @OPTIMIZE: use a bigger temp buffer and memcpy multiple pixels at once + for (row = 0; row < (h>>1); row++) { + for (col = 0; col < w; col++) { + for (z = 0; z < depth; z++) { + temp = result[(row * w + col) * depth + z]; + result[(row * w + col) * depth + z] = result[((h - row - 1) * w + col) * depth + z]; + result[((h - row - 1) * w + col) * depth + z] = temp; + } + } + } + } +} +#endif + +#ifndef STBI_NO_STDIO + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_flip(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} +#endif //!STBI_NO_STDIO + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_flip(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_flip(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_flip(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_file(&s,f); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} + +static void stbi__skip(stbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} + +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} + +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + return z + (stbi__get16le(s) << 16); +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + + +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc(req_comp * x * y); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define COMBO(a,b) ((a)*8+(b)) + #define CASE(a,b) case COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (COMBO(img_n, req_comp)) { + CASE(1,2) dest[0]=src[0], dest[1]=255; break; + CASE(1,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(1,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; break; + CASE(2,1) dest[0]=src[0]; break; + CASE(2,3) dest[0]=dest[1]=dest[2]=src[0]; break; + CASE(2,4) dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; break; + CASE(3,4) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; break; + CASE(3,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(3,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; break; + CASE(4,1) dest[0]=stbi__compute_y(src[0],src[1],src[2]); break; + CASE(4,2) dest[0]=stbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; break; + CASE(4,3) dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; break; + default: STBI_ASSERT(0); + } + #undef CASE + } + + STBI_FREE(data); + return good; +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output = (float *) stbi__malloc(x * y * comp * sizeof(float)); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + if (k < comp) output[i*comp + k] = data[i*comp+k]/255.0f; + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output = (stbi_uc *) stbi__malloc(x * y * comp); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi_uc dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0,code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1 << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (-1 << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k << 8) + (run << 4) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB + k = stbi_lrot(j->code_buffer, n); + STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask))); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & ~sgn); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi_uc *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc << j->succ_low); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) << shift); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) << shift); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) << 12) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0] << 2; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi_uc *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4; + int t = q & 15,i; + if (p != 0) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = stbi__get8(z->s); + L -= 65; + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + stbi__skip(z->s, stbi__get16be(z->s)-2); + return 1; + } + return 0; +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + c = stbi__get8(s); + if (c != 3 && c != 1) return stbi__err("bad component count","Corrupt JPEG"); // JFIF requires + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (z->img_comp[i].id != i+1) // JFIF requires + if (z->img_comp[i].id != i) { // some version of jpegtran outputs non-JFIF-compliant files! + // somethings output this (see http://fileformats.archiveteam.org/wiki/JPEG#Color_format) + if (z->img_comp[i].id != rgb[i]) + return stbi__err("bad component ID","Corrupt JPEG"); + ++z->rgb; + } + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].raw_data = stbi__malloc(z->img_comp[i].w2 * z->img_comp[i].h2+15); + + if (z->img_comp[i].raw_data == NULL) { + for(--i; i >= 0; --i) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + } + return stbi__err("outofmem", "Out of memory"); + } + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + z->img_comp[i].linebuf = NULL; + if (z->progressive) { + z->img_comp[i].coeff_w = (z->img_comp[i].w2 + 7) >> 3; + z->img_comp[i].coeff_h = (z->img_comp[i].h2 + 7) >> 3; + z->img_comp[i].raw_coeff = STBI_MALLOC(z->img_comp[i].coeff_w * z->img_comp[i].coeff_h * 64 * sizeof(short) + 15); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } else { + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } else if (x != 0) { + return stbi__err("junk before marker", "Corrupt JPEG"); + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +#ifdef STBI_JPEG_OLD +// this is the same YCbCr-to-RGB calculation that stb_image has used +// historically before the algorithm changes in 1.49 +#define float2fixed(x) ((int) ((x) * 65536 + 0.5)) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 16) + 32768; // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr*float2fixed(1.40200f); + g = y_fixed - cr*float2fixed(0.71414f) - cb*float2fixed(0.34414f); + b = y_fixed + cb*float2fixed(1.77200f); + r >>= 16; + g >>= 16; + b >>= 16; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#else +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + (cr*-float2fixed(0.71414f)) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* float2fixed(1.40200f); + g = y_fixed + cr*-float2fixed(0.71414f) + ((cb*-float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + #ifndef STBI_JPEG_OLD + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + #endif + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + int i; + for (i=0; i < j->s->img_n; ++i) { + if (j->img_comp[i].raw_data) { + STBI_FREE(j->img_comp[i].raw_data); + j->img_comp[i].raw_data = NULL; + j->img_comp[i].data = NULL; + } + if (j->img_comp[i].raw_coeff) { + STBI_FREE(j->img_comp[i].raw_coeff); + j->img_comp[i].raw_coeff = 0; + j->img_comp[i].coeff = 0; + } + if (j->img_comp[i].linebuf) { + STBI_FREE(j->img_comp[i].linebuf); + j->img_comp[i].linebuf = NULL; + } + } +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n; + + if (z->s->img_n == 3 && n < 3) + decode_n = 1; + else + decode_n = z->s->img_n; + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4]; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc(n * z->s->img_x * z->s->img_y + 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (z->rgb == 3) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) *out++ = y[i], *out++ = 255; + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n; // report original components, not output + return output; + } +} + +static unsigned char *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg j; + j.s = s; + stbi__setup_jpeg(&j); + r = stbi__decode_jpeg_header(&j, STBI__SCAN_type); + stbi__rewind(s); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[288]; + stbi__uint16 value[288]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) stbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < hlit + hdist) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else if (c == 16) { + c = stbi__zreceive(a,2)+3; + memset(lencodes+n, lencodes[n-1], c); + n += c; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + memset(lencodes+n, 0, c); + n += c; + } else { + STBI_ASSERT(c == 18); + c = stbi__zreceive(a,7)+11; + memset(lencodes+n, 0, c); + n += c; + } + } + if (n != hlit+hdist) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +// @TODO: should statically initialize these for optimal thread safety +static stbi_uc stbi__zdefault_length[288], stbi__zdefault_distance[32]; +static void stbi__init_zdefaults(void) +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zdefault_distance[31]) stbi__init_zdefaults(); + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc(x * y * output_bytes); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + if (s->img_x == x && s->img_y == y) { + if (raw_len != img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } else { // interlaced: + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + } + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior = cur - stride; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); break; + } + #undef CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + CASE(STBI__F_none) cur[k] = raw[k]; break; + CASE(STBI__F_sub) cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); break; + CASE(STBI__F_up) cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + CASE(STBI__F_avg) cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); break; + CASE(STBI__F_paeth) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); break; + CASE(STBI__F_avg_first) cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); break; + CASE(STBI__F_paeth_first) cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); break; + } + #undef CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc(a->s->img_x * a->s->img_y * out_n); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_n + out_x*out_n, + a->out + (j*x+i)*out_n, out_n); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc(pixel_count * pal_img_n); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__reduce_png(stbi__png *p) +{ + int i; + int img_len = p->s->img_x * p->s->img_y * p->s->img_out_n; + stbi_uc *reduced; + stbi__uint16 *orig = (stbi__uint16*)p->out; + + if (p->depth != 16) return 1; // don't need to do anything if not 16-bit data + + reduced = (stbi_uc *)stbi__malloc(img_len); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is a decent approx of 16->8 bit scaling + + p->out = reduced; + STBI_FREE(orig); + + return 1; +} + +static int stbi__unpremultiply_on_load = 0; +static int stbi__de_iphone_flag = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag = flag_true_if_should_convert; +} + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + p[0] = p[2] * 255 / a; + p[1] = p[1] * 255 / a; + p[2] = t * 255 / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((a) << 24) + ((b) << 16) + ((c) << 8) + (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static unsigned char *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp) +{ + unsigned char *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth == 16) { + if (!stbi__reduce_png(p)) { + return result; + } + } + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + result = stbi__convert_format(result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static unsigned char *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) n += 16, z >>= 16; + if (z >= 0x00100) n += 8, z >>= 8; + if (z >= 0x00010) n += 4, z >>= 4; + if (z >= 0x00004) n += 2, z >>= 2; + if (z >= 0x00002) n += 1, z >>= 1; + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +static int stbi__shiftsigned(int v, int shift, int bits) +{ + int result; + int z=0; + + if (shift < 0) v <<= -shift; + else v >>= shift; + result = v; + + z = bits; + while (z < 8) { + result += v >> z; + z += bits; + } + return result; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; +} stbi__bmp_data; + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (info->bpp == 1) return stbi__errpuc("monochrome", "BMP type not supported: 1-bit"); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static stbi_uc *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - 14 - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - 14 - info.hsz) >> 2; + } + + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + out = (stbi_uc *) stbi__malloc(target * s->img_x * s->img_y); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - 14 - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - 14 - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i], p1[i] = p2[i], p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if(is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // else: fall-through + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fall-through + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (r * 255)/31; + out[1] = (g * 255)/31; + out[2] = (b * 255)/31; + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static stbi_uc *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4]; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + tga_data = (unsigned char*)stbi__malloc( (size_t)tga_width * tga_height * tga_comp ); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc( tga_palette_len * tga_comp ); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static stbi_uc *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + int pixelCount; + int channelCount, compression; + int channel, i, count, len; + int bitdepth; + int w,h; + stbi_uc *out; + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Create the destination image. + out = (stbi_uc *) stbi__malloc(4 * w*h); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceeded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + count = 0; + while (count < pixelCount) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len ^= 0x0FF; + len += 2; + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out + channel; + if (channel >= channelCount) { + // Fill this channel with default data. + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } else { + // Read the data. + if (bitdepth == 16) { + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + + if (channelCount >= 4) { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + // remove weird white matte from PSD + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + + if (req_comp && req_comp != 4) { + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static stbi_uc *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp) +{ + stbi_uc *result; + int i, x,y; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if ((1 << 28) / x < y) return stbi__errpuc("too large", "Image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc(x*y*4); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out, *old_out; // output buffer (always 4 components) + int flags, bgindex, ratio, transparent, eflags, delay; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[4096]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + p = &g->out[g->cur_x + g->cur_y]; + c = &g->color_table[g->codes[code].suffix * 4]; + + if (c[3] >= 128) { + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) return stbi__errpuc("no clear code", "Corrupt GIF"); + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 4096) return stbi__errpuc("too many codes", "Corrupt GIF"); + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +static void stbi__fill_gif_background(stbi__gif *g, int x0, int y0, int x1, int y1) +{ + int x, y; + stbi_uc *c = g->pal[g->bgindex]; + for (y = y0; y < y1; y += 4 * g->w) { + for (x = x0; x < x1; x += 4) { + stbi_uc *p = &g->out[y + x]; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = 0; + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp) +{ + int i; + stbi_uc *prev_out = 0; + + if (g->out == 0 && !stbi__gif_header(s, g, comp,0)) + return 0; // stbi__g_failure_reason set by stbi__gif_header + + prev_out = g->out; + g->out = (stbi_uc *) stbi__malloc(4 * g->w * g->h); + if (g->out == 0) return stbi__errpuc("outofmem", "Out of memory"); + + switch ((g->eflags & 0x1C) >> 2) { + case 0: // unspecified (also always used on 1st frame) + stbi__fill_gif_background(g, 0, 0, 4 * g->w, 4 * g->w * g->h); + break; + case 1: // do not dispose + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + g->old_out = prev_out; + break; + case 2: // dispose to background + if (prev_out) memcpy(g->out, prev_out, 4 * g->w * g->h); + stbi__fill_gif_background(g, g->start_x, g->start_y, g->max_x, g->max_y); + break; + case 3: // dispose to previous + if (g->old_out) { + for (i = g->start_y; i < g->max_y; i += 4 * g->w) + memcpy(&g->out[i + g->start_x], &g->old_out[i + g->start_x], g->max_x - g->start_x); + } + break; + } + + for (;;) { + switch (stbi__get8(s)) { + case 0x2C: /* Image Descriptor */ + { + int prev_trans = -1; + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + if (g->transparent >= 0 && (g->eflags & 0x01)) { + prev_trans = g->pal[g->transparent][3]; + g->pal[g->transparent][3] = 0; + } + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (o == NULL) return NULL; + + if (prev_trans != -1) + g->pal[g->transparent][3] = (stbi_uc) prev_trans; + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + if (stbi__get8(s) == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = stbi__get16le(s); + g->transparent = stbi__get8(s); + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) + stbi__skip(s, len); + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } + + STBI_NOTUSED(req_comp); +} + +static stbi_uc *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *u = 0; + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + memset(g, 0, sizeof(*g)); + + u = stbi__gif_load_next(s, g, comp, req_comp); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g->w; + *y = g->h; + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g->w, g->h); + } + else if (g->out) + STBI_FREE(g->out); + STBI_FREE(g); + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s) +{ + const char *signature = "#?RADIANCE\n"; + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s); + stbi__rewind(s); + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + + + // Check identifier + if (strcmp(stbi__hdr_gettoken(s,buffer), "#?RADIANCE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + // Read data + hdr_data = (float *) stbi__malloc(height * width * req_comp * sizeof(float)); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) scanline = (stbi_uc *) stbi__malloc(width * 4); + + for (k = 0; k < 4; ++k) { + i = 0; + while (i < width) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + stbi__rewind( s ); + if (p == NULL) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = info.ma ? 4 : 3; + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + if (stbi__get16be(s) != 8) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained; + stbi__pic_packet packets[10]; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) +// Does not support 16-bit-per-channel + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static stbi_uc *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi_uc *out; + if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n)) + return 0; + *x = s->img_x; + *y = s->img_y; + *comp = s->img_n; + + out = (stbi_uc *) stbi__malloc(s->img_n * s->img_x * s->img_y); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv; + char c, p, t; + + stbi__rewind( s ); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + + if (maxv > 255) + return stbi__err("max value > 255", "PPM image not 8-bit"); + else + return 1; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ diff --git a/src/stb/stb_image_write.cpp b/src/stb/stb_image_write.cpp new file mode 100644 index 0000000..b386fa0 --- /dev/null +++ b/src/stb/stb_image_write.cpp @@ -0,0 +1,3 @@ +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + diff --git a/src/stb/stb_image_write.h b/src/stb/stb_image_write.h new file mode 100644 index 0000000..00ab092 --- /dev/null +++ b/src/stb/stb_image_write.h @@ -0,0 +1,1625 @@ +/* stb_image_write - v1.10 - public domain - http://nothings.org/stb/stb_image_write.h + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + + If using a modern Microsoft Compiler, non-safe versions of CRT calls may cause + compilation warnings or even errors. To avoid this, also before #including, + + #define STBI_MSC_SECURE_CRT + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +extern int stbi_write_tga_with_rle; +extern int stbi_write_png_compression_level; +extern int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBI_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi__flip_vertically_on_write=0; +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi__flip_vertically_on_write=0; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + return 0; + +#if _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + unsigned char arr[3]; + arr[0] = a, arr[1] = b, arr[2] = c; + s->func(s->context, arr, 3); +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + s->func(s->context, &d[comp - 1], 1); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + s->func(s->context, d, 1); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + s->func(s->context, &d[comp - 1], 1); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) + j_end = -1, j = y-1; + else + j_end = y, j = 0; + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + s->func(s->context, &header, 1); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + s->func(s->context, &header, 1); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef STBI_MSC_SECURE_CRT + len = sprintf_s(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) best=d,bestloc=hlist[j]; + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,0x11,0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + const unsigned char *imageData = (const unsigned char *)data; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + int x, y, pos; + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float YDU[64], UDU[64], VDU[64]; + for(row = y, pos = 0; row < y+8; ++row) { + int p; + if(row < height) { + p = (stbi__flip_vertically_on_write ? (height-1-row) : row)*width*comp; + } else { + // row >= height => use last input row (=> first if flipping) + p = stbi__flip_vertically_on_write ? 0 : ((height-1)*width*comp); + } + for(col = x; col < x+8; ++col, ++pos) { + float r, g, b; + // if col >= width => use pixel from last input column + p += ((col < width) ? col : (width-1))*comp; + + r = imageData[p+0]; + g = imageData[p+ofsG]; + b = imageData[p+ofsB]; + YDU[pos]=+0.29900f*r+0.58700f*g+0.11400f*b-128; + UDU[pos]=-0.16874f*r-0.33126f*g+0.50000f*b; + VDU[pos]=+0.50000f*r-0.41869f*g-0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/src/triangle-mesh-operations.cpp b/src/triangle-mesh-operations.cpp new file mode 100644 index 0000000..50abaab --- /dev/null +++ b/src/triangle-mesh-operations.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#include "triangle-mesh-operations.hpp" + +float wrap(TriangleMesh::Triangle* triangle, Vector3 position, Vector3 direction, float length, std::vector* segments) +{ + // Get vertex positions and center of starting triangle + const Vector3* a = &triangle->edge->vertex->position; + const Vector3* b = &triangle->edge->next->vertex->position; + const Vector3* c = &triangle->edge->previous->vertex->position; + Vector3 center = ((*a) + (*b) + (*c)) * (1.0f / 3.0f); + + // Project start position and target position onto plane of the starting triangle + Vector3 coplanarStart = projectOnPlane(position, center, triangle->normal); + Vector3 coplanarTarget = projectOnPlane(position + direction * length, center, triangle->normal); + + // Constrain coplanar position and coplanar target to triangle bounds + int edgeIndex = -1; + int vertexIndex = -1; + Vector3 barycentricStart = projectOnTriangle(coplanarStart, *a, *b, *c, &edgeIndex, &vertexIndex); + Vector3 offset = cartesian(barycentricStart, *a, *b, *c) - coplanarStart; + coplanarStart += offset; + coplanarTarget += offset; + + // Calculate coplanar direction + Vector3 coplanarDirection = glm::normalize(coplanarTarget - coplanarStart); + + // Form initial wrap operation segment + WrapOperationSegment segment; + segment.triangle = triangle; + segment.startEdge = nullptr; + segment.endEdge = nullptr; + segment.startVertex = nullptr; + segment.endVertex = nullptr; + segment.startPosition = barycentricStart; + segment.endPosition = barycentricStart; + segment.length = 0.0f; + + // Determine starting edge + if (edgeIndex == 0) + segment.startEdge = triangle->edge; + else if (edgeIndex == 1) + segment.startEdge = triangle->edge->next; + else if (edgeIndex == 2) + segment.startEdge = triangle->edge->previous; + + // Determine starting vertex + if (vertexIndex == 0) + segment.startVertex = triangle->edge->vertex; + else if (vertexIndex == 1) + segment.startVertex = triangle->edge->next->vertex; + else if (vertexIndex == 2) + segment.startVertex = triangle->edge->previous->vertex; + + // Begin wrap operation + float distance = 0.0f; + while (1) + { + // Calculate coplanar Cartesian start and target positions + coplanarStart = cartesian(segment.startPosition, *a, *b, *c); + coplanarTarget = coplanarStart + coplanarDirection * (length - distance); + + // Calculate barycentric end position by projecting coplanar Cartesian target onto triangle + segment.endPosition = projectOnTriangle(coplanarTarget, *a, *b, *c, &edgeIndex, &vertexIndex); + + // Determine ending edge + segment.endEdge = nullptr; + if (edgeIndex == 0) + segment.endEdge = segment.triangle->edge; + else if (edgeIndex == 1) + segment.endEdge = segment.triangle->edge->next; + else if (edgeIndex == 2) + segment.endEdge = segment.triangle->edge->previous; + + // Determine ending vertex + segment.endVertex = nullptr; + if (vertexIndex == 0) + segment.endVertex = segment.triangle->edge->vertex; + else if (vertexIndex == 1) + segment.endVertex = segment.triangle->edge->next->vertex; + else if (vertexIndex == 2) + segment.endVertex = segment.triangle->edge->previous->vertex; + + // Calculate coplanar Cartesian end position + Vector3 coplanarEnd = cartesian(segment.endPosition, *a, *b, *c); + + // Determine distance traveled + segment.length = glm::length(coplanarEnd - coplanarStart); + distance += segment.length; + + // Add segment to wrapped segments + segments->push_back(segment); + + // Check if wrap has completed + if ((length - distance ) < 0.0001f || (!segment.endEdge && !segment.endVertex)) + { + distance = length; + break; + } + + // Check if a disconnectd edge was hit + if (segment.endEdge && !segment.endEdge->symmetric) + { + break; + } + + // Reorientate coplanar direction + if (segment.triangle->normal != segment.endEdge->symmetric->triangle->normal) + { + coplanarDirection = glm::normalize(glm::rotation(segment.triangle->normal, segment.endEdge->symmetric->triangle->normal) * coplanarDirection); + } + + // Move to the connected triangle + segment.triangle = segment.endEdge->symmetric->triangle; + segment.startEdge = segment.endEdge->symmetric; + segment.endEdge = nullptr; + segment.startVertex = nullptr; + segment.endVertex = nullptr; + + // Get vertex positions of triangle + a = &segment.triangle->edge->vertex->position; + b = &segment.triangle->edge->next->vertex->position; + c = &segment.triangle->edge->previous->vertex->position; + + // Calculate barycentric starting position of the next segment + segment.startPosition = normalize_barycentric(barycentric(coplanarEnd, *a, *b, *c)); + } + + return distance; +} + diff --git a/src/triangle-mesh-operations.hpp b/src/triangle-mesh-operations.hpp new file mode 100644 index 0000000..7ff2de0 --- /dev/null +++ b/src/triangle-mesh-operations.hpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017-2019 Christopher J. Howard + * + * This file is part of Antkeeper Source Code. + * + * Antkeeper Source Code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper Source Code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper Source Code. If not, see . + */ + +#ifndef TRIANGLE_MESH_OPERATIONS_HPP +#define TRIANGLE_MESH_OPERATIONS_HPP + +#include +using namespace Emergent; + +/** + * A single segment in a triangle mesh wrap operation. + */ +struct WrapOperationSegment +{ + /// Pointer to the triangle on which this segment is located + TriangleMesh::Triangle* triangle; + + /// Pointer to the starting edge of this segment (if any) + TriangleMesh::Edge* startEdge; + + /// Pointer to the ending edge of this segment (if any) + TriangleMesh::Edge* endEdge; + + /// Pointer to the starting vertex of this segment (if any) + TriangleMesh::Vertex* startVertex; + + /// Pointer to the ending vertex of this segment (if any) + TriangleMesh::Vertex* endVertex; + + /// Barycentric coordinates of the start position of the segment + Vector3 startPosition; // barycentric + + /// Barycentric coordinates of the end position of the segment + Vector3 endPosition; // barycentric + + /// Length of the segment + float length; +}; + +/** + * Wraps a vector around a triangle mesh. + * + * @param triangle Pointer to the triangle on which the wrap operation will begin. + * @param position Cartesian coordinates of the starting position. + * @param direction Initial normalized direction of the wrap operation. + * @param length Total length of the wrap operation. + * @param[out] segments List which will be populated with the wrap operation segments + * @return Total length of the wrap operation. If this quantity is less than the length parameter, this indicates the wrap operation hit a disconnected edge. + */ +float wrap(TriangleMesh::Triangle* triangle, Vector3 position, Vector3 direction, float length, std::vector* segments); + +#endif // TRIANGLE_MESH_OPERATIONS_HPP + diff --git a/src/ui/menu.cpp b/src/ui/menu.cpp deleted file mode 100644 index e77bf3b..0000000 --- a/src/ui/menu.cpp +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#include "menu.hpp" -#include "ui.hpp" - -#include - -MenuItem::MenuItem(Menu* parent, std::size_t index): - parent(parent), - index(index), - selectedCallback(nullptr), - deselectedCallback(nullptr), - activatedCallback(nullptr), - valueChangedCallback(nullptr), - valueIndex(0), - nameLabel(nullptr), - valueLabel(nullptr), - rowContainer(nullptr) - -{ - nameLabel = new UILabel(); - nameLabel->setAnchor(Vector2(0.0f, 0.0f)); - - valueLabel = new UILabel(); - valueLabel->setAnchor(Vector2(1.0f, 0.0f)); - - rowContainer = new UIContainer(); - rowContainer->addChild(nameLabel); - rowContainer->addChild(valueLabel); - - rowContainer->setMouseOverCallback(std::bind(&Menu::select, parent, index)); - rowContainer->setMouseMovedCallback(std::bind(&Menu::select, parent, index)); - rowContainer->setMousePressedCallback(std::bind(&Menu::activate, parent)); -} - -MenuItem::~MenuItem() -{ - delete nameLabel; - delete valueLabel; - delete rowContainer; -} - -void MenuItem::select() -{ - if (selectedCallback != nullptr) - { - selectedCallback(); - } -} - -void MenuItem::deselect() -{ - if (deselectedCallback != nullptr) - { - deselectedCallback(); - } -} - -void MenuItem::activate() -{ - if (activatedCallback != nullptr) - { - activatedCallback(); - } -} - -void MenuItem::setSelectedCallback(std::function callback) -{ - this->selectedCallback = callback = callback; -} - -void MenuItem::setDeselectedCallback(std::function callback) -{ - this->deselectedCallback = callback; -} - -void MenuItem::setActivatedCallback(std::function callback) -{ - this->activatedCallback = callback; -} - -void MenuItem::setValueChangedCallback(std::function callback) -{ - this->valueChangedCallback = callback; -} - -void MenuItem::setName(const std::u32string& text) -{ - nameLabel->setText(text); - parent->resize(); -} - -std::size_t MenuItem::addValue() -{ - values.push_back(std::u32string()); - return (values.size() - 1); -} - -void MenuItem::removeValues() -{ - values.clear(); - valueIndex = 0; - valueLabel->setText(std::u32string()); - parent->resize(); -} - -void MenuItem::setValueName(std::size_t index, const std::u32string& text) -{ - values[index] = text; - - if (index == valueIndex) - { - valueLabel->setText(text); - parent->resize(); - } -} - -void MenuItem::setValueIndex(std::size_t index) -{ - if (valueIndex != index) - { - valueIndex = index; - valueLabel->setText(values[index]); - - if (valueChangedCallback != nullptr) - { - valueChangedCallback(index); - } - } -} - -bool MenuItem::isSelected() const -{ - return (parent->getSelectedItem() == this); -} - -Menu::Menu(): - selectedItem(nullptr), - enteredCallback(nullptr), - exitedCallback(nullptr), - font(nullptr), - lineSpacing(1.0f), - columnMargin(0.0f), - activationDelay(0) -{ - container = new UIContainer(); - resize(); -} - -Menu::~Menu() -{ - removeItems(); - delete container; -} - -void Menu::enter() -{ - if (enteredCallback != nullptr) - { - enteredCallback(); - } -} - -void Menu::exit() -{ - if (exitedCallback != nullptr) - { - exitedCallback(); - } -} - -MenuItem* Menu::addItem() -{ - // Allocate item and add to items - MenuItem* item = new MenuItem(this, items.size()); - items.push_back(item); - - // Set item fonts - item->nameLabel->setFont(font); - item->valueLabel->setFont(font); - - // Set item colors - item->nameLabel->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.35f)); - item->valueLabel->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.35f)); - - // Add item labels to UI container - container->addChild(item->rowContainer); - - // Resize UI container - resize(); - - return item; -} - -void Menu::removeItems() -{ - for (MenuItem* item: items) - { - // Remove labels from UI container - container->removeChild(item->rowContainer); - - delete item; - } - items.clear(); - - resize(); -} - -void Menu::setEnteredCallback(std::function callback) -{ - this->enteredCallback = callback; -} - -void Menu::setExitedCallback(std::function callback) -{ - this->exitedCallback = callback; -} - -void Menu::setFont(Font* font) -{ - this->font = font; - for (MenuItem* item: items) - { - item->nameLabel->setFont(font); - item->valueLabel->setFont(font); - } - - resize(); -} - -void Menu::setLineSpacing(float spacing) -{ - lineSpacing = spacing; - resize(); -} - -void Menu::setColumnMargin(float margin) -{ - columnMargin = margin; - resize(); -} - -void Menu::update(float dt) -{ - if (activationDelay > 0) - { - --activationDelay; - } -} - -void Menu::deselect() -{ - if (selectedItem != nullptr) - { - selectedItem->deselect(); - - selectedItem->nameLabel->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.35f)); - selectedItem->valueLabel->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.35f)); - - selectedItem = nullptr; - } -} - -void Menu::select(std::size_t index) -{ - deselect(); - - MenuItem* item = items[index]; - item->select(); - - selectedItem = item; - selectedItem->nameLabel->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); - selectedItem->valueLabel->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); -} - -void Menu::activate() -{ - if (selectedItem != nullptr && !activationDelay) - { - selectedItem->activate(); - activationDelay = 1; - } -} - -void Menu::resize() -{ - if (!font) - { - container->setDimensions(Vector2(0.0f)); - } - else - { - // Determine maximum width of menu - float menuWidth = 0.0f; - - // For each menu item - for (std::size_t i = 0; i < items.size(); ++i) - { - const MenuItem* item = items[i]; - - // Calculate width of item name label - float nameLabelWidth = item->nameLabel->getDimensions().x; - - // For each item value - for (std::size_t j = 0; j < item->getValueCount(); ++j) - { - // Calculate width of item value label - float valueLabelWidth = font->getWidth(item->getValue(j).c_str()); - - menuWidth = std::max(menuWidth, nameLabelWidth + columnMargin + valueLabelWidth); - } - menuWidth = std::max(menuWidth, nameLabelWidth); - } - - Vector2 dimensions(menuWidth, 0.0f); - for (std::size_t i = 0; i < items.size(); ++i) - { - const MenuItem* item = items[i]; - - float translationY = static_cast(static_cast(font->getMetrics().getHeight() * lineSpacing * static_cast(i))); - - item->rowContainer->setDimensions(Vector2(menuWidth, font->getMetrics().getHeight())); - item->rowContainer->setTranslation(Vector2(0.0f, translationY)); - - if (!i) - { - dimensions.y += font->getMetrics().getHeight(); - } - else - { - dimensions.y += font->getMetrics().getHeight() * lineSpacing; - } - } - - container->setDimensions(dimensions); - } -} \ No newline at end of file diff --git a/src/ui/menu.hpp b/src/ui/menu.hpp deleted file mode 100644 index 31689db..0000000 --- a/src/ui/menu.hpp +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef MENU_HPP -#define MENU_HPP - -#include -using namespace Emergent; - -#include -#include -#include - -class Menu; -class UIContainer; -class UILabel; - -class MenuItem -{ -public: - void setSelectedCallback(std::function callback); - void setDeselectedCallback(std::function callback); - void setActivatedCallback(std::function callback); - void setValueChangedCallback(std::function callback); - - void setName(const std::u32string& text); - std::size_t addValue(); - void removeValues(); - void setValueName(std::size_t index, const std::u32string& text); - void setValueIndex(std::size_t index); - - std::size_t getValueCount() const; - const std::u32string& getValue(std::size_t index) const; - std::size_t getValueIndex() const; - - - std::size_t getItemIndex() const; - bool isSelected() const; - -private: - friend class Menu; - - MenuItem(Menu* parent, std::size_t index); - ~MenuItem(); - void select(); - void deselect(); - void activate(); - - Menu* parent; - std::size_t index; - std::function selectedCallback; - std::function deselectedCallback; - std::function activatedCallback; - std::function valueChangedCallback; - std::vector values; - std::size_t valueIndex; - UILabel* nameLabel; - UILabel* valueLabel; - UIContainer* rowContainer; -}; - -inline std::size_t MenuItem::getItemIndex() const -{ - return index; -} - -inline std::size_t MenuItem::getValueCount() const -{ - return values.size(); -} - -inline const std::u32string& MenuItem::getValue(std::size_t index) const -{ - return values[index]; -} - -inline std::size_t MenuItem::getValueIndex() const -{ - return valueIndex; -} - -class Menu -{ -public: - Menu(); - ~Menu(); - - void enter(); - void exit(); - - MenuItem* addItem(); - void removeItems(); - - void setEnteredCallback(std::function callback); - void setExitedCallback(std::function callback); - void setFont(Font* font); - void setLineSpacing(float spacing); - void setColumnMargin(float margin); - - std::size_t getItemCount(); - const MenuItem* getItem(std::size_t index) const; - MenuItem* getItem(std::size_t index); - const MenuItem* getSelectedItem() const; - MenuItem* getSelectedItem(); - - const UIContainer* getUIContainer() const; - UIContainer* getUIContainer(); - - void update(float dt); - - /** - * Deselects the currently selected item (if any) - */ - void deselect(); - - /** - * Selects the item at the specified index - */ - void select(std::size_t index); - - /** - * Activates the selected item (if any) - */ - void activate(); - - /** - * Recalculates the dimensions of the UI container according the dimensions of the menu item labels and the line spacing. - */ - void resize(); - -private: - friend class MenuItem; - - std::vector items; - MenuItem* selectedItem; - std::function enteredCallback; - std::function exitedCallback; - Font* font; - float lineSpacing; - float columnMargin; - UIContainer* container; - - // Prevents activation of multiple menu items in the same frame - int activationDelay; -}; - -inline std::size_t Menu::getItemCount() -{ - return items.size(); -} - -inline const MenuItem* Menu::getItem(std::size_t index) const -{ - return items[index]; -} - -inline MenuItem* Menu::getItem(std::size_t index) -{ - return items[index]; -} - -inline const MenuItem* Menu::getSelectedItem() const -{ - return selectedItem; -} - -inline MenuItem* Menu::getSelectedItem() -{ - return selectedItem; -} - -inline const UIContainer* Menu::getUIContainer() const -{ - return container; -} - -inline UIContainer* Menu::getUIContainer() -{ - return container; -} - -#endif // MENU_HPP \ No newline at end of file diff --git a/src/ui/pie-menu.cpp b/src/ui/pie-menu.cpp deleted file mode 100644 index 019e336..0000000 --- a/src/ui/pie-menu.cpp +++ /dev/null @@ -1,218 +0,0 @@ -#include "pie-menu.hpp" -#include - -PieMenu::PieMenu(Tweener* tweener): - tweener(tweener), - scaleUpTween(nullptr), - scaleDownTween(nullptr), - scale(1.0f), - selectionIndex(0), - dragging(false), - dragStart(0.0f) -{ - // Setup fullscreen container - fullscreenContainer.addChild(&croppedContainer); - fullscreenContainer.setMouseMovedCallback(std::bind(&PieMenu::mouseMoved, this, std::placeholders::_1, std::placeholders::_2)); - fullscreenContainer.setMousePressedCallback(std::bind(&PieMenu::mouseButtonPressed, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); - fullscreenContainer.setMouseReleasedCallback(std::bind(&PieMenu::mouseButtonReleased, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); - - // Setup cropped container - croppedContainer.addChild(&scalingContainer); - - // Setup scaling container - scalingContainer.setAnchor(Vector2(0.5f)); - - // Create tweens - scaleUpTween = new Tween(EaseFunction::OUT_SINE, 0.0f, 0.1f, 0.0f, 1.0f); - scaleUpTween->setUpdateCallback(std::bind(&PieMenu::setScale, this, std::placeholders::_1)); - scaleDownTween = new Tween(EaseFunction::IN_SINE, 0.0f, 0.1f, 1.0f, -1.0f); - scaleDownTween->setUpdateCallback(std::bind(&PieMenu::setScale, this, std::placeholders::_1)); - - // Add tweens - tweener->addTween(scaleUpTween); - tweener->addTween(scaleDownTween); -} - -void PieMenu::resize() -{ - float iconDistance = 0.0f; - - if (fullscreenContainer.getParent() == nullptr) - { - return; - } - - // Resize fullscreen container - fullscreenContainer.setDimensions(fullscreenContainer.getParent()->getDimensions()); - - // Resize cropped container - croppedContainer.setDimensions(Vector2(options[0]->getTexture()->getWidth(), options[0]->getTexture()->getHeight())); - - // Place options - for (std::size_t i = 0; i < options.size(); ++i) - { - float angle = glm::radians(360.0f) / static_cast(i + 1) / static_cast(options.size()); - - options[i]->setAnchor(Vector2(0.5f, 0.5f)); - options[i]->setTranslation(Vector2(0.0f, 0.0f)); - - icons[i]->setAnchor(Vector2(0.5f, 0.5f)); - icons[i]->setTranslation(Vector2(0.0f, 0.0f)); - } -} - -void PieMenu::setScale(float scale) -{ - for (std::size_t i = 0; i < options.size(); ++i) - { - options[i]->setDimensions(Vector2(options[i]->getTexture()->getWidth(), options[i]->getTexture()->getHeight()) * scale); - icons[i]->setDimensions(Vector2(icons[i]->getTexture()->getWidth(), icons[i]->getTexture()->getHeight()) * scale); - } -} - -void PieMenu::addOption(Texture2D* backgroundTexture, Texture2D* iconTexture, std::function selectedCallback, std::function deselectedCallback) -{ - // Allocate new option - UIImage* option = new UIImage(); - option->setTexture(backgroundTexture); - option->setDimensions(Vector2(backgroundTexture->getWidth(), backgroundTexture->getHeight())); - option->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.50f)); - options.push_back(option); - - UIImage* icon = new UIImage(); - icon->setTexture(iconTexture); - icon->setDimensions(Vector2(iconTexture->getWidth(), iconTexture->getHeight())); - icon->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.50f)); - icons.push_back(icon); - - // Add icon to option - scalingContainer.addChild(icon); - - // Add option to menu - scalingContainer.addChild(option); - - // Setup callbacks - selectedCallbacks.push_back(selectedCallback); - deselectedCallbacks.push_back(deselectedCallback); -} - -void PieMenu::select(std::size_t index) -{ - if (index != selectionIndex && selectionIndex < options.size()) - { - deselect(selectionIndex); - } - - selectionIndex = index; - selectedCallbacks[index](); -} - -void PieMenu::deselect(std::size_t index) -{ - deselectedCallbacks[index](); -} - -void PieMenu::highlight(std::size_t index) -{ - options[index]->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); - icons[index]->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); -} - -void PieMenu::unhighlight(std::size_t index) -{ - options[index]->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.50f)); - icons[index]->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.50f)); -} - -void PieMenu::mouseMoved(int x, int y) -{ - if (dragging) - { - Vector2 direction = Vector2(x, y) - dragStart; - if (direction.x != 0.0f || direction.y != 0.0f) - { - direction = glm::normalize(direction); - - // Calculate arc length - float arcLength = glm::radians(360.0f) / static_cast(options.size()); - - // Calculate angle between cursor and pie menu - float angle = std::atan2(direction.y, direction.x) + glm::radians(90.0f) + arcLength * 0.5f; - while (angle < 0.0f) angle += glm::radians(360.0f); - - // Determine option index from angle - std::size_t index = static_cast(angle / arcLength); - - if (index != highlightedIndex) - { - if (highlightedIndex < options.size()) - { - unhighlight(highlightedIndex); - } - - highlight(index); - highlightedIndex = index; - } - } - } -} - -void PieMenu::mouseButtonPressed(int button, int x, int y) -{ - if (button == 3) - { - // Start dragging - dragging = true; - dragStart.x = x; - dragStart.y = y; - - // Set pie menu position - Vector2 halfDimensions = croppedContainer.getDimensions() * 0.5f; - croppedContainer.setTranslation(Vector2(x - halfDimensions.x, y - halfDimensions.y)); - - // Clear highlights - for (std::size_t i = 0; i < options.size(); ++i) - { - unhighlight(i); - } - - // Reset highlighted index - highlightedIndex = options.size(); - - // Show pie menu - fullscreenContainer.setVisible(true); - - // Start scale-up tween - scaleDownTween->stop(); - scaleUpTween->start(); - } -} - -void PieMenu::mouseButtonReleased(int button, int x, int y) -{ - if (button == 3) - { - // Stop dragging - dragging = false; - - // Select highlighted index - if (highlightedIndex != selectionIndex && highlightedIndex < options.size()) - { - select(highlightedIndex); - } - - // Clear highlights - for (std::size_t i = 0; i < options.size(); ++i) - { - unhighlight(i); - } - - // Reset highlighted index - highlightedIndex = options.size(); - - // Start scale-down tween - scaleUpTween->stop(); - scaleDownTween->start(); - //fullscreenContainer.setVisible(false); - } -} diff --git a/src/ui/pie-menu.hpp b/src/ui/pie-menu.hpp deleted file mode 100644 index da11eba..0000000 --- a/src/ui/pie-menu.hpp +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef PIE_MENU_HPP -#define PIE_MENU_HPP - -#include "ui.hpp" -#include "tween.hpp" -#include -#include - -#include -using namespace Emergent; - -class PieMenu -{ -public: - PieMenu(Tweener* tweener); - - void resize(); - - void addOption(Texture2D* backgroundTexture, Texture2D* iconTexture, std::function selectedCallback, std::function deselectedCallback); - - void select(std::size_t index); - void deselect(std::size_t index); - - const UIContainer* getContainer() const; - UIContainer* getContainer(); - void mouseMoved(int x, int y); - void mouseButtonPressed(int button, int x, int y); - void mouseButtonReleased(int button, int x, int y); - - void setScale(float scale); - -private: - void highlight(std::size_t index); - void unhighlight(std::size_t index); - - Tweener* tweener; - Tween* scaleUpTween; - Tween* scaleDownTween; - float scale; - - UIContainer fullscreenContainer; - UIContainer croppedContainer; - UIContainer scalingContainer; - std::vector options; - std::vector icons; - std::vector> selectedCallbacks; - std::vector> deselectedCallbacks; - std::size_t selectionIndex; - - bool dragging; - Vector2 dragStart; - std::size_t highlightedIndex; -}; - -inline const UIContainer* PieMenu::getContainer() const -{ - return &fullscreenContainer; -} - -inline UIContainer* PieMenu::getContainer() -{ - return &fullscreenContainer; -} - -#endif // PIE_MENU_HPP diff --git a/src/ui/toolbar.cpp b/src/ui/toolbar.cpp deleted file mode 100644 index aabe1fd..0000000 --- a/src/ui/toolbar.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "toolbar.hpp" -#include - -Toolbar::Toolbar(): - toolbarTopTexture(nullptr), - toolbarBottomTexture(nullptr), - toolbarMiddleTexture(nullptr), - buttonRaisedTexture(nullptr), - buttonDepressedTexture(nullptr), - depressedButtonIndex(0) -{ - toolbarContainer.addChild(&toolbarTopImage); - toolbarContainer.addChild(&toolbarBottomImage); - toolbarContainer.addChild(&toolbarMiddleImage); -} - -void Toolbar::setToolbarTopTexture(Texture2D* texture) -{ - toolbarTopTexture = texture; - toolbarTopImage.setTexture(toolbarTopTexture); -} - -void Toolbar::setToolbarBottomTexture(Texture2D* texture) -{ - toolbarBottomTexture = texture; - toolbarBottomImage.setTexture(toolbarBottomTexture); -} - -void Toolbar::setToolbarMiddleTexture(Texture2D* texture) -{ - toolbarMiddleTexture = texture; - toolbarMiddleImage.setTexture(toolbarMiddleTexture); -} - -void Toolbar::setButtonRaisedTexture(Texture2D* texture) -{ - buttonRaisedTexture = texture; -} - -void Toolbar::setButtonDepressedTexture(Texture2D* texture) -{ - buttonDepressedTexture = texture; -} - -void Toolbar::resize() -{ - int toolbarWidth = toolbarMiddleTexture->getWidth(); - int toolbarHeight = toolbarTopTexture->getHeight() + toolbarBottomTexture->getHeight() + toolbarMiddleTexture->getHeight() * std::max(0, (int)buttons.size() - 1); - float borderSpacing = 8.0f; - float buttonOffsetY = ((toolbarTopTexture->getHeight() + toolbarBottomTexture->getHeight()) - buttonRaisedTexture->getHeight()) / 2; - - // Resize toolbar - toolbarContainer.setAnchor(Vector2(0.0f, 0.5f)); - toolbarContainer.setDimensions(Vector2(toolbarWidth, toolbarHeight)); - toolbarContainer.setTranslation(Vector2(borderSpacing, 0.0f)); - - toolbarTopImage.setAnchor(Vector2(0.0f, 0.0f)); - toolbarTopImage.setDimensions(Vector2(toolbarTopTexture->getWidth(), toolbarTopTexture->getHeight())); - toolbarTopImage.setTranslation(Vector2(0.0f, 0.0f)); - - toolbarBottomImage.setAnchor(Vector2(0.0f, 1.0f)); - toolbarBottomImage.setDimensions(Vector2(toolbarBottomTexture->getWidth(), toolbarBottomTexture->getHeight())); - toolbarBottomImage.setTranslation(Vector2(0.0f, 0.0f)); - - toolbarMiddleImage.setAnchor(Vector2(0.0f, 0.5f)); - toolbarMiddleImage.setDimensions(Vector2(toolbarMiddleTexture->getWidth(), toolbarMiddleTexture->getHeight() * std::max(0, (int)buttons.size() - 1))); - toolbarMiddleImage.setTranslation(Vector2(0.0f, 0.0f)); - - // Resize buttons and icons - for (std::size_t i = 0; i < buttons.size(); ++i) - { - UIImage* button = buttons[i]; - button->setAnchor(Vector2(0.5f, 0.0f)); - button->setDimensions(Vector2(buttonRaisedTexture->getWidth(), buttonRaisedTexture->getHeight())); - button->setTranslation(Vector2(0.0f, buttonOffsetY + i * toolbarMiddleTexture->getHeight())); - - UIImage* icon = icons[i]; - icon->setAnchor(Vector2(0.5f, 0.5f)); - icon->setDimensions(Vector2(icon->getTexture()->getWidth(), icon->getTexture()->getHeight())); - icon->setTranslation(Vector2(0.0f, 0.0f)); - } -} - -void Toolbar::addButton(Texture2D* iconTexture, std::function pressCallback, std::function releaseCallback) -{ - if (depressedButtonIndex == buttons.size()) - { - ++depressedButtonIndex; - } - - // Allocate new button and icon - UIImage* button = new UIImage(); - button->setTexture(buttonRaisedTexture); - buttons.push_back(button); - - UIImage* icon = new UIImage(); - icon->setTexture(iconTexture); - icon->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.30f)); - icons.push_back(icon); - - // Add button to toolbar - toolbarContainer.addChild(button); - - // Add icon to button - button->addChild(icon); - - // Setup callbacks - std::size_t buttonIndex = buttons.size() - 1; - //button->setMouseOverCallback(std::bind(&Toolbar::selectMenuItem, this, buttonIndex)); - //button->setMouseMovedCallback(std::bind(&Toolbar::selectMenuItem, this, buttonIndex)); - button->setMousePressedCallback(std::bind(&Toolbar::pressButton, this, buttonIndex)); - - pressCallbacks.push_back(pressCallback); - releaseCallbacks.push_back(releaseCallback); -} - -void Toolbar::pressButton(std::size_t index) -{ - releaseButton(depressedButtonIndex); - - if (index == depressedButtonIndex) - { - depressedButtonIndex = buttons.size(); - } - else - { - depressedButtonIndex = index; - buttons[index]->setTexture(buttonDepressedTexture); - //icons[index]->setTranslation(Vector2(2.0f, 2.0f)); - icons[index]->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 1.0f)); - - pressCallbacks[index](); - } -} - -void Toolbar::releaseButton(std::size_t index) -{ - if (index < buttons.size()) - { - buttons[index]->setTexture(buttonRaisedTexture); - //icons[index]->setTranslation(Vector2(0.0f, 0.0f)); - icons[index]->setTintColor(Vector4(1.0f, 1.0f, 1.0f, 0.30f)); - - releaseCallbacks[index](); - } -} diff --git a/src/ui/toolbar.hpp b/src/ui/toolbar.hpp deleted file mode 100644 index 4985a41..0000000 --- a/src/ui/toolbar.hpp +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef TOOLBAR_HPP -#define TOOLBAR_HPP - -#include "ui.hpp" -#include - -#include - -using namespace Emergent; - -class Toolbar -{ -public: - Toolbar(); - - void setToolbarTopTexture(Texture2D* texture); - void setToolbarBottomTexture(Texture2D* texture); - void setToolbarMiddleTexture(Texture2D* texture); - void setButtonRaisedTexture(Texture2D* texture); - void setButtonDepressedTexture(Texture2D* texture); - - void resize(); - - void addButton(Texture2D* iconTexture, std::function pressCallback, std::function releaseCallback); - - void pressButton(std::size_t index); - void releaseButton(std::size_t index); - - const UIContainer* getContainer() const; - UIContainer* getContainer(); - -private: - Texture2D* toolbarTopTexture; - Texture2D* toolbarBottomTexture; - Texture2D* toolbarMiddleTexture; - Texture2D* buttonRaisedTexture; - Texture2D* buttonDepressedTexture; - - UIContainer toolbarContainer; - UIImage toolbarTopImage; - UIImage toolbarBottomImage; - UIImage toolbarMiddleImage; - std::vector buttons; - std::vector icons; - std::vector> pressCallbacks; - std::vector> releaseCallbacks; - - std::size_t depressedButtonIndex; -}; - -inline const UIContainer* Toolbar::getContainer() const -{ - return &toolbarContainer; -} - -inline UIContainer* Toolbar::getContainer() -{ - return &toolbarContainer; -} - -#endif // TOOLBAR_HPP diff --git a/src/ui/tween.cpp b/src/ui/tween.cpp deleted file mode 100644 index b84b6f5..0000000 --- a/src/ui/tween.cpp +++ /dev/null @@ -1,390 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#include "tween.hpp" -#include - -const TweenBase::EaseFunctionPointer TweenBase::easeFunctionPointers[] = -{ - &TweenBase::easeLinear, - &TweenBase::easeInSine, - &TweenBase::easeOutSine, - &TweenBase::easeInOutSine, - &TweenBase::easeInQuad, - &TweenBase::easeOutQuad, - &TweenBase::easeInOutQuad, - &TweenBase::easeInCubic, - &TweenBase::easeOutCubic, - &TweenBase::easeInOutCubic, - &TweenBase::easeInQuart, - &TweenBase::easeOutQuart, - &TweenBase::easeInOutQuart, - &TweenBase::easeInQuint, - &TweenBase::easeOutQuint, - &TweenBase::easeInOutQuint, - &TweenBase::easeInExpo, - &TweenBase::easeOutExpo, - &TweenBase::easeInOutExpo, - &TweenBase::easeInCirc, - &TweenBase::easeOutCirc, - &TweenBase::easeInOutCirc, - &TweenBase::easeInBack, - &TweenBase::easeOutBack, - &TweenBase::easeInOutBack, - &TweenBase::easeInBounce, - &TweenBase::easeOutBounce, - &TweenBase::easeInOutBounce, -}; - -TweenBase::TweenBase(EaseFunction function, float time, float duration): - time(time), - duration(duration), - stopped(true), - oldStopped(true), - paused(false) -{ - setEaseFunction(function); -} - -TweenBase::TweenBase(): - time(0.0f), - duration(0.0f), - stopped(true), - oldStopped(true), - paused(false) -{ - setEaseFunction(EaseFunction::LINEAR); -} - -TweenBase::~TweenBase() -{} - -void TweenBase::start() -{ - if (stopped) - { - stopped = false; - oldStopped = true; - time = 0.0f; - } - else if (paused) - { - paused = false; - } -} - -void TweenBase::stop() -{ - if (!stopped) - { - stopped = true; - oldStopped = false; - paused = false; - } -} - -void TweenBase::pause() -{ - if (!stopped && !paused) - { - paused = true; - oldStopped = false; - } -} - -void TweenBase::reset() -{ - time = 0.0f; -} - -inline void TweenBase::setEaseFunction(EaseFunction function) -{ - easeFunction = function; - easeFunctionPointer = easeFunctionPointers[static_cast(easeFunction)]; -} - -void TweenBase::setTime(float time) -{ - this->time = time; -} - -void TweenBase::setDuration(float duration) -{ - this->duration = duration; -} - -float TweenBase::easeLinear(float t, float b, float c, float d) -{ - return c * t / d + b; -} - -float TweenBase::easeInSine(float t, float b, float c, float d) -{ - return -c * std::cos(t / d * glm::half_pi()) + c + b; -} - -float TweenBase::easeOutSine(float t, float b, float c, float d) -{ - return c * std::sin(t / d * glm::half_pi()) + b; -} - -float TweenBase::easeInOutSine(float t, float b, float c, float d) -{ - return -c * 0.5f * (std::cos(glm::pi() * t / d) - 1.0f) + b; -} - -float TweenBase::easeInQuad(float t, float b, float c, float d) -{ - t /= d; - return c * t * t + b; -} - -float TweenBase::easeOutQuad(float t, float b, float c, float d) -{ - t /= d; - return -c * t * (t - 2.0f) + b; -} - -float TweenBase::easeInOutQuad(float t, float b, float c, float d) -{ - t /= d; - if ((t * 0.5f) < 1.0f) - { - return c * 0.5f * t * t + b; - } - - t -= 1.0f; - return -c * 0.5f * (t * (t - 2.0f) - 1.0f) + b; -} - -float TweenBase::easeInCubic(float t, float b, float c, float d) -{ - t /= d; - return c * t * t * t + b; -} - -float TweenBase::easeOutCubic(float t, float b, float c, float d) -{ - t = t / d - 1.0f; - return c * (t * t * t + 1.0f) + b; -} - -float TweenBase::easeInOutCubic(float t, float b, float c, float d) -{ - t /= d * 0.5f; - if (t < 1.0f) - { - return c * 0.5f * t * t * t + b; - } - - t -= 2.0f; - return c * 0.5f * (t * t * t + 2.0f) + b; -} - -float TweenBase::easeInQuart(float t, float b, float c, float d) -{ - t /= d; - return c * t * t * t * t + b; -} - -float TweenBase::easeOutQuart(float t, float b, float c, float d) -{ - t = t / d - 1.0f; - return -c * (t * t * t * t - 1.0f) + b; -} - -float TweenBase::easeInOutQuart(float t, float b, float c, float d) -{ - t /= d * 0.5f; - if (t < 1.0f) - { - return c * 0.5f * t * t * t * t + b; - } - - t -= 2.0f; - return -c * 0.5f * (t * t * t * t - 2.0f) + b; -} - -float TweenBase::easeInQuint(float t, float b, float c, float d) -{ - t /= d; - return c * t * t * t * t * t + b; -} - -float TweenBase::easeOutQuint(float t, float b, float c, float d) -{ - t = t / d - 1.0f; - return c * (t * t * t * t * t + 1.0f) + b; -} - -float TweenBase::easeInOutQuint(float t, float b, float c, float d) -{ - t /= d * 0.5f; - if (t < 1.0f) - { - return c * 0.5f * t * t * t * t * t + b; - } - - t -= 2.0f; - return c * 0.5f * (t * t * t * t * t + 2.0f) + b; -} - -float TweenBase::easeInExpo(float t, float b, float c, float d) -{ - return (t == 0.0f) ? b : c * std::pow(2.0f, 10.0f * (t / d - 1.0f)) + b - c * 0.001f; -} - -float TweenBase::easeOutExpo(float t, float b, float c, float d) -{ - return (t == d) ? b + c : c * 1.001f * (-std::pow(2.0f, -10.0f * t / d) + 1.0f) + b; -} - -float TweenBase::easeInOutExpo(float t, float b, float c, float d) -{ - if (t == 0.0f) - { - return b; - } - - if (t == d) - { - return b + c; - } - - t /= d * 0.5f; - if (t < 1.0f) - { - return c * 0.5f * std::pow(2.0f, 10.0f * (t - 1.0f)) + b - c * 0.0005f; - } - - t -= 1.0f; - return c * 0.5f * 1.0005f * (-std::pow(2.0f, -10.0f * t) + 2.0f) + b; -} - -float TweenBase::easeInCirc(float t, float b, float c, float d) -{ - t /= d; - return -c * (std::sqrt(1.0f - t * t) - 1.0f) + b; -} -float TweenBase::easeOutCirc(float t, float b, float c, float d) -{ - t = t / d - 1.0f; - return c * std::sqrt(1.0f - t * t) + b; -} - -float TweenBase::easeInOutCirc(float t, float b, float c, float d) -{ - t /= d * 0.5f; - if (t < 1.0f) - { - return -c * 0.5f * (std::sqrt(1.0f - t * t) - 1.0f) + b; - } - - t -= 2.0f; - return c * 0.5f * (std::sqrt(1.0f - t * t) + 1.0f) + b; -} - -float TweenBase::easeInBack(float t, float b, float c, float d) -{ - float s = 1.70158f; - t /= d; - return c * t * t * ((s + 1.0f) * t - s) + b; -} - -float TweenBase::easeOutBack(float t, float b, float c, float d) -{ - float s = 1.70158f; - t = t / d - 1.0f; - return c * (t * t * ((s + 1.0f) * t + s) + 1.0f) + b; -} - -float TweenBase::easeInOutBack(float t, float b, float c, float d) -{ - float s = 1.70158f; - t /= d * 0.5f; - if (t < 1.0f) - { - s *= 1.525f; - return c * 0.5f * (t * t * ((s + 1.0f) * t - s)) + b; - } - - t -= 2.0f; - s *= 1.525f; - return c * 0.5f * (t * t * ((s + 1.0f) * t + s) + 2.0f) + b; -} - -float TweenBase::easeInBounce(float t, float b, float c, float d) -{ - return c - easeOutBounce(d - t, 0.0f, c, d) + b; -} - -float TweenBase::easeOutBounce(float t, float b, float c, float d) -{ - t /= d; - if (t < (1.0f / 2.75f)) - { - return c * (7.5625f * t * t) + b; - } - else if (t < (2.0f / 2.75f)) - { - t -= (1.5f / 2.75f); - return c * (7.5625f * t * t + 0.75f) + b; - } - else if (t < (2.5f / 2.75f)) - { - t -= (2.25f / 2.75f); - return c * (7.5625f * t * t + 0.9375f) + b; - } - - t -= (2.625f / 2.75f); - return c * (7.5625f * t * t + 0.984375f) + b; -} - -float TweenBase::easeInOutBounce(float t, float b, float c, float d) -{ - if (t < d * 0.5f) - { - return easeInBounce(t * 2.0f, 0.0f, c, d) * 0.5f + b; - } - - return easeOutBounce(t * 2.0f - d, 0.0f, c, d) * 0.5f + c * 0.5f + b; -} - -void Tweener::update(float dt) -{ - for (TweenBase* tween: tweens) - { - tween->update(dt); - } -} - -void Tweener::addTween(TweenBase* tween) -{ - tweens.push_back(tween); -} - -void Tweener::removeTween(TweenBase* tween) -{ - tweens.remove(tween); -} - -void Tweener::removeTweens() -{ - tweens.clear(); -} diff --git a/src/ui/tween.hpp b/src/ui/tween.hpp deleted file mode 100644 index 7d9d97f..0000000 --- a/src/ui/tween.hpp +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2017 Christopher J. Howard - * - * This file is part of Antkeeper Source Code. - * - * Antkeeper Source Code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper Source Code is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper Source Code. If not, see . - */ - -#ifndef TWEEN_HPP -#define TWEEN_HPP - -#include -#include -#include -#include -#include - -using namespace Emergent; - -/// @see http://easings.net/ -/// @see http://wiki.unity3d.com/index.php?title=Tween -enum class EaseFunction -{ - LINEAR, - IN_SINE, - OUT_SINE, - IN_OUT_SINE, - IN_QUAD, - OUT_QUAD, - IN_OUT_QUAD, - IN_CUBIC, - OUT_CUBIC, - IN_OUT_CUBIC, - IN_QUART, - OUT_QUART, - IN_OUT_QUART, - IN_QUINT, - OUT_QUINT, - IN_OUT_QUINT, - IN_EXPO, - OUT_EXPO, - IN_OUT_EXPO, - IN_CIRC, - OUT_CIRC, - IN_OUT_CIRC, - IN_BACK, - OUT_BACK, - IN_OUT_BACK, - IN_BOUNCE, - OUT_BOUNCE, - IN_OUT_BOUNCE -}; - -class TweenBase -{ -public: - TweenBase(EaseFunction function, float time, float duration); - TweenBase(); - virtual ~TweenBase(); - - void start(); - void stop(); - void pause(); - void reset(); - - void setEaseFunction(EaseFunction function); - void setTime(float time); - void setDuration(float duration); - - EaseFunction getEaseFunction() const; - float getTime() const; - float getDuration() const; - bool isStopped() const; - bool wasStopped() const; - bool isPaused() const; - -protected: - typedef float (*EaseFunctionPointer)(float, float, float, float); - - EaseFunction easeFunction; - EaseFunctionPointer easeFunctionPointer; - float time; - float duration; - bool stopped; - bool oldStopped; - bool paused; - -private: - friend class Tweener; - - static const EaseFunctionPointer easeFunctionPointers[28]; - - virtual void update(float dt) = 0; - - static float easeLinear(float t, float b, float c, float d); - static float easeInSine(float t, float b, float c, float d); - static float easeOutSine(float t, float b, float c, float d); - static float easeInOutSine(float t, float b, float c, float d); - static float easeInQuad(float t, float b, float c, float d); - static float easeOutQuad(float t, float b, float c, float d); - static float easeInOutQuad(float t, float b, float c, float d); - static float easeInCubic(float t, float b, float c, float d); - static float easeOutCubic(float t, float b, float c, float d); - static float easeInOutCubic(float t, float b, float c, float d); - static float easeInQuart(float t, float b, float c, float d); - static float easeOutQuart(float t, float b, float c, float d); - static float easeInOutQuart(float t, float b, float c, float d); - static float easeInQuint(float t, float b, float c, float d); - static float easeOutQuint(float t, float b, float c, float d); - static float easeInOutQuint(float t, float b, float c, float d); - static float easeInExpo(float t, float b, float c, float d); - static float easeOutExpo(float t, float b, float c, float d); - static float easeInOutExpo(float t, float b, float c, float d); - static float easeInCirc(float t, float b, float c, float d); - static float easeOutCirc(float t, float b, float c, float d); - static float easeInOutCirc(float t, float b, float c, float d); - static float easeInBack(float t, float b, float c, float d); - static float easeOutBack(float t, float b, float c, float d); - static float easeInOutBack(float t, float b, float c, float d); - static float easeInBounce(float t, float b, float c, float d); - static float easeOutBounce(float t, float b, float c, float d); - static float easeInOutBounce(float t, float b, float c, float d); -}; - -inline EaseFunction TweenBase::getEaseFunction() const -{ - return easeFunction; -} - -inline float TweenBase::getTime() const -{ - return time; -} - -inline float TweenBase::getDuration() const -{ - return duration; -} - -inline bool TweenBase::isStopped() const -{ - return stopped; -} - -inline bool TweenBase::wasStopped() const -{ - return oldStopped; -} - -inline bool TweenBase::isPaused() const -{ - return paused; -} - -template -class Tween: public TweenBase -{ -public: - Tween(EaseFunction function, float time, float duration, const T& startValue, const T& deltaValue); - Tween(); - virtual ~Tween(); - - void setStartValue(const T& startValue); - void setDeltaValue(const T& deltaValue); - void setStartCallback(std::function callback); - void setUpdateCallback(std::function callback); - void setEndCallback(std::function callback); - - const T& getStartValue() const; - const T& getDeltaValue() const; - const T& getTweenValue() const; - - std::function getStartCallback() const; - std::function getUpdateCallback() const; - std::function getEndCallback() const; - -private: - virtual void update(float dt); - - void calculateTweenValue(); - - T startValue; - T deltaValue; - T tweenValue; - std::function startCallback; - std::function updateCallback; - std::function endCallback; -}; - -template -Tween::Tween(EaseFunction function, float time, float duration, const T& startValue, const T& deltaValue): - TweenBase(function, time, duration), - startValue(startValue), - deltaValue(deltaValue), - tweenValue(startValue), - startCallback(nullptr), - updateCallback(nullptr), - endCallback(nullptr) -{} - -template -Tween::Tween(): - startCallback(nullptr), - updateCallback(nullptr), - endCallback(nullptr) -{} - -template -Tween::~Tween() -{} - -template -void Tween::update(float dt) -{ - if (isStopped() || isPaused()) - { - return; - } - - // Check if tween was just started - if (!isStopped() && wasStopped()) - { - // Execute start callback - if (startCallback != nullptr) - { - startCallback(startValue); - } - } - oldStopped = stopped; - - // Add delta time to time and calculate tween value - time = std::min(duration, time + dt); - calculateTweenValue(); - - // Execute update callback - if (updateCallback != nullptr) - { - updateCallback(tweenValue); - } - - // Check if tween has ended - if (time >= duration) - { - if (!isStopped()) - { - // Stop tween - stop(); - - // Execute end callback - if (endCallback != nullptr) - { - endCallback(tweenValue); - } - } - } -} - -template -inline void Tween::setStartValue(const T& startValue) -{ - this->startValue = startValue; -} - -template -inline void Tween::setDeltaValue(const T& deltaValue) -{ - this->deltaValue = deltaValue; -} - -template -inline void Tween::setStartCallback(std::function callback) -{ - this->startCallback = callback; -} - -template -inline void Tween::setUpdateCallback(std::function callback) -{ - this->updateCallback = callback; -} - -template -inline void Tween::setEndCallback(std::function callback) -{ - this->endCallback = callback; -} - -template -inline const T& Tween::getStartValue() const -{ - return startValue; -} - -template -inline const T& Tween::getDeltaValue() const -{ - return deltaValue; -} - -template -inline const T& Tween::getTweenValue() const -{ - return tweenValue; -} - -template -inline std::function Tween::getStartCallback() const -{ - return startCallback; -} - -template -inline std::function Tween::getUpdateCallback() const -{ - return updateCallback; -} - -template -inline std::function Tween::getEndCallback() const -{ - return endCallback; -} - -template -inline void Tween::calculateTweenValue() -{ - tweenValue = easeFunctionPointer(time, startValue, deltaValue, duration); -} - -template <> -inline void Tween::calculateTweenValue() -{ - tweenValue.x = easeFunctionPointer(time, startValue.x, deltaValue.x, duration); - tweenValue.y = easeFunctionPointer(time, startValue.y, deltaValue.y, duration); -} - -template <> -inline void Tween::calculateTweenValue() -{ - tweenValue.x = easeFunctionPointer(time, startValue.x, deltaValue.x, duration); - tweenValue.y = easeFunctionPointer(time, startValue.y, deltaValue.y, duration); - tweenValue.z = easeFunctionPointer(time, startValue.z, deltaValue.z, duration); - -} - -template <> -inline void Tween::calculateTweenValue() -{ - tweenValue.x = easeFunctionPointer(time, startValue.x, deltaValue.x, duration); - tweenValue.y = easeFunctionPointer(time, startValue.y, deltaValue.y, duration); - tweenValue.z = easeFunctionPointer(time, startValue.z, deltaValue.z, duration); - tweenValue.w = easeFunctionPointer(time, startValue.w, deltaValue.w, duration); -} - -class Tweener -{ -public: - void update(float dt); - - void addTween(TweenBase* tween); - void removeTween(TweenBase* tween); - void removeTweens(); - -private: - std::list tweens; -}; - -#endif // TWEEN_HPP \ No newline at end of file diff --git a/src/ui/ui.cpp b/src/ui/ui.cpp old mode 100644 new mode 100755 index 213b733..f1780ea --- a/src/ui/ui.cpp +++ b/src/ui/ui.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -18,13 +18,6 @@ */ #include "ui.hpp" -#include "../application.hpp" - -// UI callbacks -void menuPrint(Application* application, const std::string& string) -{ - std::cout << string << std::endl; -} UIMaterial::UIMaterial() { @@ -42,12 +35,7 @@ UIElement::UIElement(): anchor(Anchor::TOP_LEFT), layerOffset(0), layer(0), - origin(0.0f), - translation(0.0f), - rotation(0.0f), - dimensions(0.0f), - position(0.0f), - bounds(position, position), + bounds(Vector2(0.0f), Vector2(0.0f)), tintColor(1.0f), color(tintColor), visible(true), @@ -57,14 +45,27 @@ UIElement::UIElement(): mouseOutCallback(nullptr), mouseMovedCallback(nullptr), mousePressedCallback(nullptr), - mouseReleasedCallback(nullptr) + mouseReleasedCallback(nullptr), + origin(0.0f), + translation(0.0f), + rotation(0.0f), + dimensions(0.0f), + position(0.0f), + originTween(&origin, lerp), + translationTween(&translation, lerp), + rotationTween(&rotation, lerp), + dimensionsTween(&dimensions, lerp), + positionTween(&position, lerp), + tintColorTween(&tintColor, lerp) {} UIElement::~UIElement() {} void UIElement::update() -{ +{ + resetTweens(); + // Calculate position if (parent != nullptr) { @@ -139,14 +140,14 @@ void UIElement::setMouseReleasedCallback(std::function call mouseReleasedCallback = callback; } -void UIElement::mouseMoved(int x, int y) +void UIElement::handleEvent(const MouseMovedEvent& event) { if (!active) { return; } - if (bounds.contains(Vector2(x, y))) + if (bounds.contains(Vector2(event.x, event.y))) { if (!mouseOver) { @@ -159,7 +160,7 @@ void UIElement::mouseMoved(int x, int y) if (mouseMovedCallback) { - mouseMovedCallback(x, y); + mouseMovedCallback(event.x, event.y); } } else if (mouseOver) @@ -173,52 +174,77 @@ void UIElement::mouseMoved(int x, int y) for (UIElement* child: children) { - child->mouseMoved(x, y); + child->handleEvent(event); } } -void UIElement::mouseButtonPressed(int button, int x, int y) +void UIElement::handleEvent(const MouseButtonPressedEvent& event) { if (!active) { return; } - if (bounds.contains(Vector2(x, y))) + if (bounds.contains(Vector2(event.x, event.y))) { if (mousePressedCallback) { - mousePressedCallback(button, x, y); + mousePressedCallback(event.button, event.x, event.y); } for (UIElement* child: children) { - child->mouseButtonPressed(button, x, y); + child->handleEvent(event); } } } -void UIElement::mouseButtonReleased(int button, int x, int y) +void UIElement::handleEvent(const MouseButtonReleasedEvent& event) { if (!active) { return; } - if (bounds.contains(Vector2(x, y))) + if (bounds.contains(Vector2(event.x, event.y))) { if (mouseReleasedCallback) { - mouseReleasedCallback(button, x , y); + mouseReleasedCallback(event.button, event.x , event.y); } for (UIElement* child: children) { - child->mouseButtonReleased(button, x, y); + child->handleEvent(event); } } } +void UIElement::interpolate(float dt) +{ + originTween.interpolate(dt); + translationTween.interpolate(dt); + rotationTween.interpolate(dt); + dimensionsTween.interpolate(dt); + positionTween.interpolate(dt); + tintColorTween.interpolate(dt); + + for (UIElement* child: children) + { + child->interpolate(dt); + } +} + +void UIElement::resetTweens() +{ + originTween.reset(); + translationTween.reset(); + rotationTween.reset(); + dimensionsTween.reset(); + positionTween.reset(); + tintColorTween.reset(); +} + UILabel::UILabel(): font(nullptr) {} @@ -233,7 +259,7 @@ void UILabel::setFont(Font* font) calculateDimensions(); } -void UILabel::setText(const std::u32string& text) +void UILabel::setText(const std::string& text) { this->text = text; calculateDimensions(); @@ -292,7 +318,7 @@ void UIBatcher::batch(BillboardBatch* result, const UIElement* ui) } // Update batch - result->update(); + result->batch(); } void UIBatcher::queueElements(std::list* elements, const UIElement* element) const @@ -368,13 +394,19 @@ void UIBatcher::batchLabel(BillboardBatch* result, const UILabel* label) BillboardBatch::Range* range = getRange(result, label); // Pixel-perfect - Vector3 origin = Vector3((int)label->getPosition().x, (int)label->getPosition().y, label->getLayer() * 0.01f); + //Vector3 origin = Vector3((int)(label->getPosition().x + 0.5f), (int)(label->getPosition().y + 0.5f), label->getLayer() * 0.01f); + Vector3 origin = Vector3((int)(label->getPositionTween()->getSubstate().x), (int)(label->getPositionTween()->getSubstate().y), label->getLayer() * 0.01f); // Print billboards const Font* font = label->getFont(); std::size_t index = range->start + range->length; std::size_t count = 0; font->puts(result, origin, label->getText(), label->getColor(), index, &count); + + for (std::size_t i = index; i < index + count; ++i) + { + result->getBillboard(i)->resetTweens(); + } // Increment range length range->length += count; @@ -387,23 +419,32 @@ void UIBatcher::batchImage(BillboardBatch* result, const UIImage* image) BillboardBatch::Range* range = getRange(result, image); // Pixel-perfect - //Vector3 translation = Vector3((int)(image->getPosition().x + image->getDimensions().x * 0.5f), (int)(image->getPosition().y + image->getDimensions().y * 0.5f), image->getLayer() * 0.01f); - - Vector3 translation = Vector3(image->getPosition() + image->getDimensions() * 0.5f, image->getLayer() * 0.01f); + //Vector2 position = Vector2((int)(image->getPosition().x + 0.5f), (int)(image->getPosition().y + 0.5f)); + //Vector2 dimensions = Vector2((int)(image->getDimensions().x + 0.5f), (int)(image->getDimensions().y + 0.5f)); + //Vector3 translation = Vector3(position.x + dimensions.x * 0.5f, position.y + dimensions.y * 0.5f, image->getLayer() * 0.01f); + + Vector2 position = Vector2((int)(image->getPositionTween()->getSubstate().x), (int)(image->getPositionTween()->getSubstate().y)); + Vector2 dimensions = Vector2((int)(image->getDimensionsTween()->getSubstate().x), (int)(image->getDimensionsTween()->getSubstate().y)); + Vector3 translation = Vector3(position.x + dimensions.x * 0.5f, position.y + dimensions.y * 0.5f, image->getLayer() * 0.01f); // Create billboard std::size_t index = range->start + range->length; Billboard* billboard = result->getBillboard(index); - billboard->setDimensions(image->getDimensions()); + billboard->setDimensions(dimensions); billboard->setTranslation(translation); - if (image->getRotation() != 0.0f) + if (image->getRotationTween()->getSubstate() != 0.0f) + { + billboard->setRotation(glm::angleAxis(image->getRotationTween()->getSubstate(), Vector3(0, 0, -1.0f))); + } + else { - billboard->setRotation(glm::angleAxis(image->getRotation(), Vector3(0, 0, -1.0f))); + billboard->setRotation(Quaternion(1, 0, 0, 0)); } billboard->setTextureCoordinates(image->getTextureBounds().getMin(), image->getTextureBounds().getMax()); - billboard->setTintColor(image->getColor()); + billboard->setTintColor(image->getTintColorTween()->getSubstate()); + billboard->resetTweens(); // Increment range length ++(range->length); diff --git a/src/ui/ui.hpp b/src/ui/ui.hpp old mode 100644 new mode 100755 index 5ee9751..8956162 --- a/src/ui/ui.hpp +++ b/src/ui/ui.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 Christopher J. Howard + * Copyright (C) 2017-2019 Christopher J. Howard * * This file is part of Antkeeper Source Code. * @@ -20,13 +20,12 @@ #ifndef UI_HPP #define UI_HPP -#include "../input.hpp" -#include -#include - #include using namespace Emergent; +#include +#include + namespace Anchor { static const Vector2& TOP_LEFT = Vector2(0.0f, 0.0f); @@ -46,7 +45,10 @@ public: ShaderVector2* textureScale; }; -class UIElement: public MouseMotionObserver, public MouseButtonObserver +class UIElement: + public EventHandler, + public EventHandler, + public EventHandler { public: enum class Type @@ -77,7 +79,7 @@ public: /// Sets the dimensions of the element void setDimensions(const Vector2& dimensions); - /// Sets the tint color of the element + /// Sets the (sRGB) tint color of the element void setTintColor(const Vector4& color); /// Sets the visibility of the element @@ -163,10 +165,26 @@ public: void setMouseMovedCallback(std::function callback); void setMousePressedCallback(std::function callback); void setMouseReleasedCallback(std::function callback); - - void mouseMoved(int x, int y); - void mouseButtonPressed(int button, int x, int y); - void mouseButtonReleased(int button, int x, int y); + + virtual void handleEvent(const MouseMovedEvent& event); + virtual void handleEvent(const MouseButtonPressedEvent& event); + virtual void handleEvent(const MouseButtonReleasedEvent& event); + + void interpolate(float dt); + void resetTweens(); + + const Tween* getOriginTween() const; + Tween* getOriginTween(); + const Tween* getTranslationTween() const; + Tween* getTranslationTween(); + const Tween* getRotationTween() const; + Tween* getRotationTween(); + const Tween* getDimensionsTween() const; + Tween* getDimensionsTween(); + const Tween* getPositionTween() const; + Tween* getPositionTween(); + const Tween* getTintColorTween() const; + Tween* getTintColorTween(); protected: UIMaterial material; @@ -177,11 +195,6 @@ private: Vector2 anchor; int layerOffset; int layer; - Vector2 origin; - Vector2 translation; - float rotation; - Vector2 dimensions; - Vector2 position; Rect bounds; Vector4 tintColor; Vector4 color; @@ -193,6 +206,19 @@ private: std::function mouseMovedCallback; std::function mousePressedCallback; std::function mouseReleasedCallback; + + Vector2 origin; + Vector2 translation; + float rotation; + Vector2 dimensions; + Vector2 position; + + Tween originTween; + Tween translationTween; + Tween rotationTween; + Tween dimensionsTween; + Tween positionTween; + Tween tintColorTween; }; inline void UIElement::setAnchor(const Vector2& anchor) @@ -340,6 +366,66 @@ inline bool UIElement::isActive() const return active; } +inline const Tween* UIElement::getOriginTween() const +{ + return &originTween; +} + +inline Tween* UIElement::getOriginTween() +{ + return &originTween; +} + +inline const Tween* UIElement::getTranslationTween() const +{ + return &translationTween; +} + +inline Tween* UIElement::getTranslationTween() +{ + return &translationTween; +} + +inline const Tween* UIElement::getRotationTween() const +{ + return &rotationTween; +} + +inline Tween* UIElement::getRotationTween() +{ + return &rotationTween; +} + +inline const Tween* UIElement::getDimensionsTween() const +{ + return &dimensionsTween; +} + +inline Tween* UIElement::getDimensionsTween() +{ + return &dimensionsTween; +} + +inline const Tween* UIElement::getPositionTween() const +{ + return &positionTween; +} + +inline Tween* UIElement::getPositionTween() +{ + return &positionTween; +} + +inline const Tween* UIElement::getTintColorTween() const +{ + return &tintColorTween; +} + +inline Tween* UIElement::getTintColorTween() +{ + return &tintColorTween; +} + class UIContainer: public UIElement { public: @@ -360,17 +446,17 @@ public: virtual UIElement::Type getElementType() const; void setFont(Font* font); - void setText(const std::u32string& text); + void setText(const std::string& text); const Font* getFont() const; Font* getFont(); - const std::u32string& getText() const; + const std::string& getText() const; private: void calculateDimensions(); Font* font; - std::u32string text; + std::string text; }; inline UIElement::Type UILabel::getElementType() const @@ -388,7 +474,7 @@ inline Font* UILabel::getFont() return font; } -inline const std::u32string& UILabel::getText() const +inline const std::string& UILabel::getText() const { return text; } diff --git a/src/windows/antkeeper.manifest b/src/windows/antkeeper.manifest old mode 100644 new mode 100755 index 23da6f4..b25d695 --- a/src/windows/antkeeper.manifest +++ b/src/windows/antkeeper.manifest @@ -1,8 +1,8 @@ - - - - - true - - + + + + + true + + \ No newline at end of file