From 043873cc2dab499fd3db15898ccd99fa0955d166 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 20 Oct 2022 21:50:52 -0700 Subject: [PATCH] Added texture, assets, tools and texture loading. --- .gitmodules | 3 + CMakeLists.txt | 3 + lib/CMakeLists.txt | 6 +- src/CMakeLists.txt | 5 +- src/dawn/CMakeLists.txt | 1 + src/dawn/asset/Asset.cpp | 18 ++ src/dawn/asset/Asset.hpp | 46 ++++ src/dawn/asset/AssetLoader.cpp | 87 ++++++++ src/dawn/asset/AssetLoader.hpp | 126 +++++++++++ src/dawn/asset/AssetManager.cpp | 37 +++ src/dawn/asset/AssetManager.hpp | 38 ++++ src/dawn/asset/CMakeLists.txt | 15 ++ src/dawn/asset/assets/CMakeLists.txt | 10 + src/dawn/asset/assets/TextureAsset.cpp | 62 ++++++ src/dawn/asset/assets/TextureAsset.hpp | 29 +++ src/dawn/game/DawnGame.hpp | 2 + src/dawnopengl/display/shader/Shader.cpp | 4 + .../display/shader/SimpleTexturedShader.hpp | 3 +- src/dawnpokergame/game/CMakeLists.txt | 6 +- src/dawnpokergame/game/DawnPokerGame.cpp | 16 +- src/dawnpokergame/game/DawnPokerGame.hpp | 3 +- src/dawntools/CMakeLists.txt | 7 + src/dawntools/display/CMakeLists.txt | 25 +++ .../display/texturegen/CMakeLists.txt | 24 ++ src/dawntools/display/texturegen/main.c | 87 ++++++++ .../display/truetypegen/CMakeLists.txt | 24 ++ src/dawntools/display/truetypegen/main.c | 140 ++++++++++++ src/dawntools/file/CMakeLists.txt | 27 +++ src/dawntools/utils/common.h | 13 ++ src/dawntools/utils/csv.c | 115 ++++++++++ src/dawntools/utils/csv.h | 29 +++ src/dawntools/utils/file.c | 195 ++++++++++++++++ src/dawntools/utils/file.h | 75 +++++++ src/dawntools/utils/image.c | 55 +++++ src/dawntools/utils/image.h | 22 ++ src/dawntools/utils/xml.c | 210 ++++++++++++++++++ src/dawntools/utils/xml.h | 67 ++++++ src/dawnwin32/CMakeLists.txt | 6 + 38 files changed, 1626 insertions(+), 15 deletions(-) create mode 100644 src/dawn/asset/Asset.cpp create mode 100644 src/dawn/asset/Asset.hpp create mode 100644 src/dawn/asset/AssetLoader.cpp create mode 100644 src/dawn/asset/AssetLoader.hpp create mode 100644 src/dawn/asset/AssetManager.cpp create mode 100644 src/dawn/asset/AssetManager.hpp create mode 100644 src/dawn/asset/CMakeLists.txt create mode 100644 src/dawn/asset/assets/CMakeLists.txt create mode 100644 src/dawn/asset/assets/TextureAsset.cpp create mode 100644 src/dawn/asset/assets/TextureAsset.hpp create mode 100644 src/dawntools/CMakeLists.txt create mode 100644 src/dawntools/display/CMakeLists.txt create mode 100644 src/dawntools/display/texturegen/CMakeLists.txt create mode 100644 src/dawntools/display/texturegen/main.c create mode 100644 src/dawntools/display/truetypegen/CMakeLists.txt create mode 100644 src/dawntools/display/truetypegen/main.c create mode 100644 src/dawntools/file/CMakeLists.txt create mode 100644 src/dawntools/utils/common.h create mode 100644 src/dawntools/utils/csv.c create mode 100644 src/dawntools/utils/csv.h create mode 100644 src/dawntools/utils/file.c create mode 100644 src/dawntools/utils/file.h create mode 100644 src/dawntools/utils/image.c create mode 100644 src/dawntools/utils/image.h create mode 100644 src/dawntools/utils/xml.c create mode 100644 src/dawntools/utils/xml.h diff --git a/.gitmodules b/.gitmodules index f965af73..b970b9e6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/glm"] path = lib/glm url = https://github.com/g-truc/glm.git +[submodule "lib/stb"] + path = lib/stb + url = https://github.com/nothings/stb diff --git a/CMakeLists.txt b/CMakeLists.txt index 535879b2..7d07c6d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,9 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT +# DEBUG +set(DAWN_BUILDING dawnpokergame) + cmake_minimum_required(VERSION 3.13) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 2b6b05bd..219e5d11 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -10,4 +10,8 @@ add_subdirectory(glad) add_subdirectory(glfw) # GLM -add_subdirectory(glm) \ No newline at end of file +add_subdirectory(glm) + +# STB +add_library(stb INTERFACE) +target_include_directories(stb INTERFACE stb) \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ed6bd793..c97f4438 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -3,8 +3,11 @@ # This software is released under the MIT License. # https://opensource.org/licenses/MIT +# Include tools +add_subdirectory(dawntools) + # Change what we are building -add_subdirectory(dawnpokergame) +add_subdirectory(${DAWN_BUILDING}) # Check the game project includes the target name if(NOT DEFINED DAWN_TARGET_NAME) diff --git a/src/dawn/CMakeLists.txt b/src/dawn/CMakeLists.txt index 69bf7cab..17cd2c31 100644 --- a/src/dawn/CMakeLists.txt +++ b/src/dawn/CMakeLists.txt @@ -16,5 +16,6 @@ target_include_directories(${DAWN_TARGET_NAME} ) # Subdirs +add_subdirectory(asset) add_subdirectory(display) add_subdirectory(scene) \ No newline at end of file diff --git a/src/dawn/asset/Asset.cpp b/src/dawn/asset/Asset.cpp new file mode 100644 index 00000000..59766405 --- /dev/null +++ b/src/dawn/asset/Asset.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "AssetManager.hpp" + +using namespace Dawn; + +Asset::Asset(AssetManager &assetManager, std::string name) : + assetManager(assetManager) +{ + this->name = name; +} + +Asset::~Asset() { + this->loaded = false; +} \ No newline at end of file diff --git a/src/dawn/asset/Asset.hpp b/src/dawn/asset/Asset.hpp new file mode 100644 index 00000000..400ec41b --- /dev/null +++ b/src/dawn/asset/Asset.hpp @@ -0,0 +1,46 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "dawnlibs.hpp" + +namespace Dawn { + class AssetManager; + + class Asset { + public: + AssetManager &assetManager; + std::string name; + uint8_t state = 0x00; + bool loaded = false; + + /** + * Create an abstract Asset object. + * + * @param assetManager Asset manager that this asset belongs to. + * @param name Name of the asset. + */ + Asset(AssetManager &assetManager, std::string name); + + /** + * Virtual function that will be called by the asset manager on a + * synchronous basis. This will only trigger if the blocks are false and + * the loaded is also false. + */ + virtual void updateSync() = 0; + + /** + * Virtual function called by the asset manager asynchronously every tick. + * This will only trigger if blocks are false and the loaded state is also + * false. + */ + virtual void updateAsync() = 0; + + /** + * Dispose the asset item. + */ + virtual ~Asset(); + }; +} \ No newline at end of file diff --git a/src/dawn/asset/AssetLoader.cpp b/src/dawn/asset/AssetLoader.cpp new file mode 100644 index 00000000..44009c70 --- /dev/null +++ b/src/dawn/asset/AssetLoader.cpp @@ -0,0 +1,87 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "AssetLoader.hpp" + +using namespace Dawn; + +AssetLoader::AssetLoader(std::string fileName) { + this->fileName = fileName; + this->handle = nullptr; +} + +void AssetLoader::open() { + std::string pathFull = DAWN_ASSET_BUILD_PREFIX + this->fileName; + this->handle = fopen(pathFull.c_str(), "rb"); + if(this->handle == NULL || this->handle == nullptr) { + throw "Failed to open file handle for " + this->fileName; + } +} + +int32_t AssetLoader::close() { + int32_t ret = fclose(this->handle); + this->handle = nullptr; + return ret; +} + +size_t AssetLoader::read(uint8_t *buffer, size_t size) { + return fread(buffer, 1, size, this->handle); +} + +int32_t AssetLoader::end() { + return fseek(this->handle, 0, SEEK_END); +} + +size_t AssetLoader::skip(size_t n) { + return fseek(this->handle, n, SEEK_CUR); +} + +int32_t AssetLoader::rewind() { + return fseek(this->handle, 0, SEEK_SET); +} + +size_t AssetLoader::getPosition() { + return ftell(this->handle); +} + +size_t AssetLoader::loadRaw(uint8_t **buffer) { + size_t length, read; + + // Open a buffer. + this->open(); + + // Read the count of bytes in the file + this->end(); + length = this->getPosition(); + + // Are we only reading the size? + if(buffer == nullptr) { + this->close(); + return length; + } + + // Reset to start + this->rewind(); + + // Read the string then close the file handle. + *buffer = static_cast(malloc(sizeof(uint8_t) * length)); + read = this->read(*buffer, length); + this->close(); + + // Did we read successfully? + if(read < length) { + throw "Failed to read all bytes of " + this->fileName; + } + + // Read successfully, return the read bytes. + return read; +} + +AssetLoader::~AssetLoader() { + if(this->handle != nullptr) { + this->close(); + this->handle = nullptr; + } +} \ No newline at end of file diff --git a/src/dawn/asset/AssetLoader.hpp b/src/dawn/asset/AssetLoader.hpp new file mode 100644 index 00000000..4d8fb20f --- /dev/null +++ b/src/dawn/asset/AssetLoader.hpp @@ -0,0 +1,126 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "dawnlibs.hpp" +#include "util/memory.hpp" + +namespace Dawn { + class AssetLoader { + private: + std::string fileName; + FILE *handle; + + public: + /** + * Create a new asset loader. Asset Loaders can be used to load data from + * a file in a myriad of ways. + * + * @param fileName File name of the asset that is to be loaded. + */ + AssetLoader(std::string fileName); + + /** + * Platform-centric method to open a file buffer to an asset. + * + * @return 0 if success, otherwise for failure. + */ + void open(); + + /** + * Closes the previously ppened asset. + * @return 0 if successful, otherwise false. + */ + int32_t close(); + + /** + * Read bytes from buffer. + * @param buffer Pointer to a ubyte array to buffer data into. + * @param size Length of the data buffer (How many bytes to read). + * @return The count of bytes read. + */ + size_t read(uint8_t *buffer, size_t size); + + /** + * Skip to the end of the buffer, useful to find the length of the buffer. + * @return 0 if successful, otherwise false. + */ + int32_t end(); + + /** + * Method to skip n bytes in the buffer + * @param n Count of bytes to skip. + * @return 0 if successful, otherwise unsuccessful. + */ + size_t skip(size_t n); + + /** + * Rewinds to the start of the asset buffer. + * @return 0 if successful, otherwise unsuccessful. + */ + int32_t rewind(); + + + /** + * Retreive the current byte position within the asset that the head is + * at. + * @return Position (in bytes) that the current seek is at. + */ + size_t getPosition(); + + /** + * Loads the entire file into a raw buffer. + * @param buffer Pointer to where a pointer to the buffer will be stored. + * @return Size of the buffer that was read (in bytes). + */ + size_t loadRaw(uint8_t **buffer); + + /** + * Run a callback for each byte within the asset. The callback will + * receive each byte individually. + * + * @tparam T Type of instance to run callback against. + * @param instance Instance of the object to run the callback against. + * @param callback Callback method on the class to run the callback for. + * @return The count of bytes read. + */ + template + size_t loadBufferedCallback(T *instance, bool (T::*callback)(uint8_t n)) { + uint8_t buffer[1024]; + size_t read, length; + int32_t i; + bool result; + + // Open the buffer. + this->open(); + + // Reset length size + length = 0; + + // Buffer from input + while((read = this->read(buffer, 1024)) != 0) { + for(i = 0; i < read; i++) { + result = ((*instance).*(callback))(buffer[i]); + if(!result) { + length += i; + break; + } + } + if(!result) break; + length += read; + } + + // Close the buffer + this->close(); + + return length; + } + + /** + * Cleanup the asset loader. + */ + virtual ~AssetLoader(); + }; +} \ No newline at end of file diff --git a/src/dawn/asset/AssetManager.cpp b/src/dawn/asset/AssetManager.cpp new file mode 100644 index 00000000..906f8f85 --- /dev/null +++ b/src/dawn/asset/AssetManager.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "AssetManager.hpp" + +#if !defined(DAWN_ASSET_BUILD_PREFIX) + #error Asset Prefix has not been defined. +#endif + +using namespace Dawn; + +void AssetManager::init() { + +} + +void AssetManager::update() { + auto it = this->assetsNotLoaded.begin(); + while(it != this->assetsNotLoaded.end()) { + auto asset = it->second; + if(asset->loaded) { + it = this->assetsNotLoaded.erase(it); + continue; + } + + asset->updateSync(); + asset->updateAsync(); + + if(asset->loaded) { + it = this->assetsNotLoaded.erase(it); + continue; + } + + ++it; + } +} \ No newline at end of file diff --git a/src/dawn/asset/AssetManager.hpp b/src/dawn/asset/AssetManager.hpp new file mode 100644 index 00000000..25fe5bb7 --- /dev/null +++ b/src/dawn/asset/AssetManager.hpp @@ -0,0 +1,38 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "asset/Asset.hpp" + +namespace Dawn { + class AssetManager { + private: + /** List of pointers to assets, mapped by their asset key. */ + std::map> assets; + std::map> assetsNotLoaded; + + public: + void init(); + void update(); + + /** + * Creates and queue an asset to load. + * + * @param name Name of the asset to load. + * @return The asset element to be loaded. + */ + template + std::shared_ptr load(std::string name) { + auto existing = this->assets.find(name); + if(existing != this->assets.end()) { + return std::dynamic_pointer_cast(existing->second); + } + auto asset = std::make_shared(*this, name); + this->assets[name] = asset; + this->assetsNotLoaded[name] = asset; + return asset; + } + }; +} \ No newline at end of file diff --git a/src/dawn/asset/CMakeLists.txt b/src/dawn/asset/CMakeLists.txt new file mode 100644 index 00000000..057b9c5a --- /dev/null +++ b/src/dawn/asset/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) 2022 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DAWN_TARGET_NAME} + PRIVATE + Asset.cpp + AssetLoader.cpp + AssetManager.cpp +) + +# Subdirs +add_subdirectory(assets) \ No newline at end of file diff --git a/src/dawn/asset/assets/CMakeLists.txt b/src/dawn/asset/assets/CMakeLists.txt new file mode 100644 index 00000000..e646d3b8 --- /dev/null +++ b/src/dawn/asset/assets/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2022 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Sources +target_sources(${DAWN_TARGET_NAME} + PRIVATE + TextureAsset.cpp +) \ No newline at end of file diff --git a/src/dawn/asset/assets/TextureAsset.cpp b/src/dawn/asset/assets/TextureAsset.cpp new file mode 100644 index 00000000..b6185ea3 --- /dev/null +++ b/src/dawn/asset/assets/TextureAsset.cpp @@ -0,0 +1,62 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "TextureAsset.hpp" + +using namespace Dawn; + +TextureAsset::TextureAsset(AssetManager &assetManager, std::string name) : + Asset(assetManager, name), + loader(name + ".texture") +{ + this->texture = std::make_shared(); +} + +void TextureAsset::updateSync() { + if(this->state == 0x00 || this->state == 0x01) return; + + this->state = 0x03; + this->texture->setSize(this->width, this->height); + this->texture->buffer(this->colors); + this->state = 0x04; + this->loaded = true; +} + +void TextureAsset::updateAsync() { + if(this->state != 0x00) return; + this->loader.loadRaw(&this->buffer); + + this->state = 0x01; + + // Parse header data. + char integer[32]; + size_t j = 0, i = 0; + while(true) { + auto c = this->buffer[i++]; + if(c == '|') { + integer[j] = '\0'; + if(this->width == -1) { + this->width = atoi(integer); + if(this->width <= 0) throw "Invalid width"; + j = 0; + continue; + } else { + this->height = atoi(integer); + if(this->height <= 0) throw "Invalid height"; + break; + } + } + integer[j++] = c; + } + + this->colors = (struct Color *)((void *)(this->buffer + i)); + this->state = 0x02; +} + +TextureAsset::~TextureAsset() { + if(this->buffer != nullptr) { + memoryFree(this->buffer); + } +} \ No newline at end of file diff --git a/src/dawn/asset/assets/TextureAsset.hpp b/src/dawn/asset/assets/TextureAsset.hpp new file mode 100644 index 00000000..641adb6e --- /dev/null +++ b/src/dawn/asset/assets/TextureAsset.hpp @@ -0,0 +1,29 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "../Asset.hpp" +#include "../AssetLoader.hpp" +#include "display/Texture.hpp" + +namespace Dawn { + class TextureAsset : public Asset { + protected: + AssetLoader loader; + uint8_t *buffer = nullptr; + int32_t width = -1, height = -1; + struct Color *colors; + + public: + std::shared_ptr texture; + + TextureAsset(AssetManager &assetManager, std::string name); + + void updateSync() override; + void updateAsync() override; + + ~TextureAsset(); + }; +} \ No newline at end of file diff --git a/src/dawn/game/DawnGame.hpp b/src/dawn/game/DawnGame.hpp index 85ec456d..64956a15 100644 --- a/src/dawn/game/DawnGame.hpp +++ b/src/dawn/game/DawnGame.hpp @@ -7,6 +7,7 @@ #include "dawnlibs.hpp" #include "scene/Scene.hpp" #include "display/RenderManager.hpp" +#include "asset/AssetManager.hpp" #define DAWN_GAME_INIT_RESULT_SUCCESS 0 #define DAWN_GAME_UPDATE_RESULT_SUCCESS 0 @@ -20,6 +21,7 @@ namespace Dawn { std::shared_ptr scene; DawnHost &host; RenderManager renderManager; + AssetManager assetManager; /** * Construct a new instance of the DawnGame. diff --git a/src/dawnopengl/display/shader/Shader.cpp b/src/dawnopengl/display/shader/Shader.cpp index 5a5d4525..59be3d3f 100644 --- a/src/dawnopengl/display/shader/Shader.cpp +++ b/src/dawnopengl/display/shader/Shader.cpp @@ -89,6 +89,10 @@ void Shader::setTexture( shaderparameter_t param, std::shared_ptr texture ) { + if(texture == nullptr || !texture->isReady()) { + this->bindTexture(param, nullptr); + return; + } this->bindTexture(param, texture); } diff --git a/src/dawnopengl/display/shader/SimpleTexturedShader.hpp b/src/dawnopengl/display/shader/SimpleTexturedShader.hpp index eac7b1ef..50396d62 100644 --- a/src/dawnopengl/display/shader/SimpleTexturedShader.hpp +++ b/src/dawnopengl/display/shader/SimpleTexturedShader.hpp @@ -30,7 +30,7 @@ namespace Dawn { } void setDefaultParameters(Material &material) override { - material.colorValues[this->paramColor] = COLOR_MAGENTA; + material.colorValues[this->paramColor] = COLOR_WHITE; } void setGlobalParameters(glm::mat4 proj, glm::mat4 view) override { @@ -97,7 +97,6 @@ namespace Dawn { this->paramHasTexture = this->getParameterByName("u_HasTexture"); this->setBoolean(this->paramHasTexture, false); - this->setColor(this->paramColor, COLOR_WHITE); } }; } \ No newline at end of file diff --git a/src/dawnpokergame/game/CMakeLists.txt b/src/dawnpokergame/game/CMakeLists.txt index 07f25903..d6f3c4f8 100644 --- a/src/dawnpokergame/game/CMakeLists.txt +++ b/src/dawnpokergame/game/CMakeLists.txt @@ -7,4 +7,8 @@ target_sources(${DAWN_TARGET_NAME} PRIVATE DawnPokerGame.cpp -) \ No newline at end of file +) + +tool_texture(texture_test texture_test.png texture_test) + +add_dependencies(${DAWN_TARGET_NAME} texture_test) \ No newline at end of file diff --git a/src/dawnpokergame/game/DawnPokerGame.cpp b/src/dawnpokergame/game/DawnPokerGame.cpp index 05ffeead..ff0964f0 100644 --- a/src/dawnpokergame/game/DawnPokerGame.cpp +++ b/src/dawnpokergame/game/DawnPokerGame.cpp @@ -4,7 +4,6 @@ // https://opensource.org/licenses/MIT #include "DawnPokerGame.hpp" -#include "event/Event.hpp" using namespace Dawn; @@ -15,6 +14,7 @@ DawnGame::DawnGame(DawnHost &host) : } int32_t DawnGame::init() { + this->assetManager.init(); this->renderManager.init(); this->scene = std::make_shared(*this); @@ -35,21 +35,19 @@ int32_t DawnGame::init() { 0, 0, 0 ); - auto testTexture = std::make_shared(); - testTexture->setSize(2, 2); - struct Color colors[4] = { - COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_WHITE - }; - testTexture->buffer(colors); - cubeMaterial->textureValues[cubeMaterial->getShader()->getParameterByName("u_Text")] = testTexture; - + auto textureAsset = this->assetManager.load("texture_test"); + cubeMaterial->textureValues[cubeMaterial->getShader()->getParameterByName("u_Text")] = std::shared_ptr(textureAsset->texture); return DAWN_GAME_INIT_RESULT_SUCCESS; } int32_t DawnGame::update(float_t delta) { + this->assetManager.update(); + if(this->scene != nullptr) this->scene->update(); + this->renderManager.update(); + return DAWN_GAME_UPDATE_RESULT_SUCCESS; } diff --git a/src/dawnpokergame/game/DawnPokerGame.hpp b/src/dawnpokergame/game/DawnPokerGame.hpp index a346b75c..2b65f68e 100644 --- a/src/dawnpokergame/game/DawnPokerGame.hpp +++ b/src/dawnpokergame/game/DawnPokerGame.hpp @@ -7,4 +7,5 @@ #include "game/DawnGame.hpp" #include "scene/components/Components.hpp" #include "display/mesh/QuadMesh.hpp" -#include "display/mesh/TriangleMesh.hpp" \ No newline at end of file +#include "display/mesh/TriangleMesh.hpp" +#include "asset/assets/TextureAsset.hpp" \ No newline at end of file diff --git a/src/dawntools/CMakeLists.txt b/src/dawntools/CMakeLists.txt new file mode 100644 index 00000000..97afa1e4 --- /dev/null +++ b/src/dawntools/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (c) 2021 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_subdirectory(display) +add_subdirectory(file) \ No newline at end of file diff --git a/src/dawntools/display/CMakeLists.txt b/src/dawntools/display/CMakeLists.txt new file mode 100644 index 00000000..50da7c0b --- /dev/null +++ b/src/dawntools/display/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (c) 2021 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +add_subdirectory(texturegen) +add_subdirectory(truetypegen) + +# Texture Tool +function(tool_texture target in out) + add_custom_target(${target} + COMMAND texturegen "${DAWN_ASSETS_SOURCE_DIR}/${in}" "${DAWN_ASSETS_BUILD_DIR}/${out}" + COMMENT "Generating texture ${target} from ${in}" + DEPENDS texturegen + ) +endfunction() + +# TrueType Tool +function(tool_truetype target in out width height fontSize) + add_custom_target(${target} + COMMAND truetypegen "${in}" "${DAWN_ASSETS_BUILD_DIR}/${out}" "${width}" "${height}" "${fontSize}" + COMMENT "Generating truetype ${target} from ${in}" + DEPENDS truetypegen + ) +endfunction() \ No newline at end of file diff --git a/src/dawntools/display/texturegen/CMakeLists.txt b/src/dawntools/display/texturegen/CMakeLists.txt new file mode 100644 index 00000000..0567df44 --- /dev/null +++ b/src/dawntools/display/texturegen/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (c) 2021 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Texture Build Tool +project(texturegen VERSION 1.0) +add_executable(texturegen) +target_sources(texturegen + PRIVATE + main.c + ../../utils/file.c + ../../utils/image.c +) +target_include_directories(texturegen + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/../../ + ${CMAKE_CURRENT_LIST_DIR} +) +target_link_libraries(texturegen + PUBLIC + ${LIBS_PLATFORM} + stb +) \ No newline at end of file diff --git a/src/dawntools/display/texturegen/main.c b/src/dawntools/display/texturegen/main.c new file mode 100644 index 00000000..dba01e5d --- /dev/null +++ b/src/dawntools/display/texturegen/main.c @@ -0,0 +1,87 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "../../utils/common.h" +#include "../../utils/file.h" +#include "../../utils/image.h" + +int main(int argc, char *argv[]) { + FILE *file; + char path[FILENAME_MAX + 1]; + uint8_t headerBuffer[32]; + char *in; + char *out; + int w, h, channels, headerBufferLength; + stbi_uc *dataImageRaw, dataIn; + float *dataImage; + size_t i, len; + + if(argc != 3) { + printf("Invalid number of arguments\n"); + return 1; + } + + // Set up strings + in = argv[1]; + out = argv[2]; + + // Normalize slashes + fileNormalizeSlashes(in); + fileNormalizeSlashes(out); + + // Check the output doesn't already exist + sprintf(path, "%s.texture", out); + file = fopen(path, "rb"); + if(file != NULL) { + fclose(file); + return 0; + } + + // Read in original texture + file = fopen(in, "rb"); + if(file == NULL) { + printf("Failed to open file!\n"); + return 1; + } + + dataImageRaw = stbi_load_from_file(file, &w, &h, &channels, STBI_rgb_alpha); + if(dataImageRaw == NULL) { + printf("Failed to load input texture!\n"); + return 1; + } + fclose(file); + + // Convert to floating points + len = STBI_rgb_alpha * w * h; + dataImage = malloc(sizeof(float) * len); + for(i = 0; i < len; i++) { + dataIn = dataImageRaw[i]; + dataImage[i] = ((float)dataIn) / 255.0f; + } + stbi_image_free(dataImageRaw); + + // Open output file + fileMkdirp(path); + file = fopen(path, "wb"); + if(file == NULL) { + printf("Invalid texture file out!\n"); + return 1; + } + + // Write info + headerBufferLength = sprintf(headerBuffer, "%i|%i|", w, h); + fwrite(headerBuffer, sizeof(uint8_t), headerBufferLength, file); + + // Write texture + fwrite(dataImage, sizeof(float), len, file); + + // Cleanup + fclose(file); + free(dataImage); + + return 0; +} \ No newline at end of file diff --git a/src/dawntools/display/truetypegen/CMakeLists.txt b/src/dawntools/display/truetypegen/CMakeLists.txt new file mode 100644 index 00000000..9a827edf --- /dev/null +++ b/src/dawntools/display/truetypegen/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (c) 2021 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +# Texture Build Tool +project(truetypegen VERSION 1.0) +add_executable(truetypegen) +target_sources(truetypegen + PRIVATE + main.c + ../../utils/file.c + ../../utils/image.c +) +target_include_directories(truetypegen + PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/../../ + ${CMAKE_CURRENT_LIST_DIR} +) +target_link_libraries(truetypegen + PUBLIC + ${LIBS_PLATFORM} + stb +) \ No newline at end of file diff --git a/src/dawntools/display/truetypegen/main.c b/src/dawntools/display/truetypegen/main.c new file mode 100644 index 00000000..bf06c886 --- /dev/null +++ b/src/dawntools/display/truetypegen/main.c @@ -0,0 +1,140 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "../../utils/common.h" +#include "../../utils/file.h" +#include "../../utils/image.h" +#ifndef STB_TRUETYPE_IMPLEMENTATION + #define STB_TRUETYPE_IMPLEMENTATION + #include +#endif + +#define TRUETYPE_FIRST_CHAR 32 +#define TRUETYPE_NUM_CHARS 96 + +int main(int argc, char *args[]) { + FILE *file; + char path[FILENAME_MAX + 1]; + int32_t width, height, fontSize, textureSize; + + /* + args0 - PATH + args1 - Input Filename (TTF file) + args2 - Output Filename + args3 - Width of the output texture (Resolution X) + args4 - Height of the output texture (Resolution Y) + args5 - Font size to draw + */ + + if(argc != 6) { + printf("Invalid number of arguments\n"); + return 1; + } + + char *fileIn = args[1]; + char *fileOut = args[2]; + char *strWidth = args[3]; + char *strHeight = args[4]; + char *strFontSize = args[5]; + + // Normalize slashes + fileNormalizeSlashes(fileIn); + fileNormalizeSlashes(fileOut); + + // Check the output doesn't already exist + sprintf(path, "%s.truetype", fileOut); + file = fopen(path, "rb"); + if(file != NULL) { + fclose(file); + return 0; + } + + width = atoi(strWidth); + if(width <= 0) { + printf("Width is invalid.\n"); + return 1; + } + + height = atoi(strHeight); + if(height <= 0) { + printf("Height is invalid.\n"); + return 1; + } + + fontSize = atoi(strFontSize); + if(fontSize <= 0) { + printf("Font size is invalid.\n"); + return 1; + } + + // Read in the TTF data + file = fopen(fileIn, "rb"); + if(file == NULL) { + printf("Failed to open input TTF file.\n"); + return 1; + } + + // Seek to end, get length, seek back to start. + fseek(file, 0, SEEK_END); + size_t fileSize = ftell(file); + fseek(file, 0, SEEK_SET); + + // Read in all data + char *ttfData = malloc(sizeof(char) * fileSize); + size_t readSize = fread(ttfData, 1, fileSize, file); + fclose(file); + if(readSize < fileSize) { + printf("Failed to read all data form TTF\n"); + return 1; + } + + // Create bitmap data. This is a single channel color (alpha). + textureSize = width * height; + stbi_uc *bitmapData = malloc(sizeof(stbi_uc) * textureSize); + stbtt_bakedchar characterData[TRUETYPE_NUM_CHARS]; + + // Now parse the TTF itself. + stbtt_BakeFontBitmap( + ttfData, 0, (float)fontSize, bitmapData, + width, height, + TRUETYPE_FIRST_CHAR, TRUETYPE_NUM_CHARS, + characterData + ); + + // Prepare output file for writing. + sprintf(path, "%s.truetype", fileOut); + fileMkdirp(path); + file = fopen(path, "wb"); + if(file == NULL) { + printf("Failed to create output TTF file\n"); + return 1; + } + + // Now prepare output data. + char headerBuffer[64]; + int32_t headerBufferLength = sprintf( + headerBuffer, "%i|%i|%i|", width, height, fontSize + ); + fwrite(headerBuffer, sizeof(char), headerBufferLength, file); + + // Write output pixels. + float outputPixels[4]; + for(int32_t i = 0; i < textureSize; i++) { + outputPixels[0] = 1.0f; + outputPixels[1] = 1.0f; + outputPixels[2] = 1.0f; + outputPixels[3] = ((float)bitmapData[i]) / 255.0f; + fwrite(outputPixels, sizeof(float), 4, file); + } + + // Now write output quads data. + fwrite(characterData, sizeof(stbtt_bakedchar), TRUETYPE_NUM_CHARS, file); + fclose(file); + free(bitmapData); + + return 0; +} \ No newline at end of file diff --git a/src/dawntools/file/CMakeLists.txt b/src/dawntools/file/CMakeLists.txt new file mode 100644 index 00000000..5cd001cb --- /dev/null +++ b/src/dawntools/file/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (c) 2022 Dominic Msters +# +# This software is released under the MIT License. +# https://opensource.org/licenses/MIT + +function(tool_copy target) + math(EXPR CARGSN "${ARGC} - 1") + set(LOOP_DEPENDENCIES) + + foreach(index RANGE 1 ${CARGSN} 2) + math(EXPR indexnext "${index} + 1") + set(LOOP_TARGET "item_${target}_${index}") + + LIST(GET ARGV ${index} from) + LIST(GET ARGV ${indexnext} to) + LIST(APPEND LOOP_DEPENDENCIES ${LOOP_TARGET}) + add_custom_command(OUTPUT ${LOOP_TARGET} + COMMAND ${CMAKE_COMMAND} -E copy "${from}" "${ASSETS_BUILD_DIR}/${to}" + COMMENT "Copying ${from} => ${to}" + ) + endforeach() + + add_custom_target(${target} + DEPENDS ${LOOP_DEPENDENCIES} + COMMENT "Creating dependency set ${target}" + ) +endfunction() \ No newline at end of file diff --git a/src/dawntools/utils/common.h b/src/dawntools/utils/common.h new file mode 100644 index 00000000..af02bd97 --- /dev/null +++ b/src/dawntools/utils/common.h @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/src/dawntools/utils/csv.c b/src/dawntools/utils/csv.c new file mode 100644 index 00000000..394baf3b --- /dev/null +++ b/src/dawntools/utils/csv.c @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "csv.h" + +void csvParse(char *string, csv_t *csv) { + char c; + size_t i, j, length; + csvparsestate_t state; + int32_t rowCellCount; + + length = strlen(string); + csv->buffer = malloc(sizeof(char) * length * 2); + csv->cellCounts = malloc(sizeof(int32_t) * CSV_ROW_COUNT_MAX); + csv->rows = malloc(sizeof(char *) * 32 * CSV_ROW_COUNT_MAX); + + i = 0; + j = 0; + rowCellCount = 0; + csv->rowCount = 0; + state = CSV_PARSE_STATE_FIND_CELL; + while(i < length) { + c = string[i++]; + + // What are we doing + switch(state) { + case CSV_PARSE_STATE_FIND_CELL: + if(c == '"') { + state = CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES; + csv->rows[(csv->rowCount * CSV_ROW_COUNT_MAX) + rowCellCount] = csv->buffer + j; + rowCellCount++; + continue; + } else if(c == '\r' || c == '\n') { + // Newline (todo: is this a blank line?) + state = CSV_PARSE_STATE_LINE_END; + continue; + } else if(c == ',') { + csv->rows[(csv->rowCount * CSV_ROW_COUNT_MAX) + rowCellCount] = csv->buffer + j; + csv->buffer[j++] = '\0'; + rowCellCount++; + continue; + } else { + state = CSV_PARSE_STATE_PARSE_CELL; + csv->rows[(csv->rowCount * CSV_ROW_COUNT_MAX) + rowCellCount] = csv->buffer + j; + csv->buffer[j++] = c; + rowCellCount++; + continue; + } + + case CSV_PARSE_STATE_PARSE_CELL: + if(c == '\r' || c == '\n') { + state = CSV_PARSE_STATE_LINE_END; + csv->buffer[j++] = '\0'; + continue; + } else if(c == ',') { + state = CSV_PARSE_STATE_FIND_CELL; + csv->buffer[j++] = '\0'; + continue; + } + csv->buffer[j++] = c; + continue; + + case CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES: + if((c == '\\' && string[i] == '"') || (c == '"' && string[i] == '"')) { + // Handle escaped quotes. I normally see [\"] but excel does [""] in + // most cases + csv->buffer[j++] = '"'; + i++; + continue; + } else if(c == '"') { + // Handle end of quoted string + state = CSV_PARSE_STATE_FIND_CELL; + csv->buffer[j++] = '\0'; + // Because we tend to do [",] at the end of a quoted cell, we do this + // to prevent [,,] cases being treated the same + if(string[i] == ',') i++; + continue; + } + + // Normal character. + csv->buffer[j++] = c; + continue; + + case CSV_PARSE_STATE_LINE_END: + // Skip blanks + if(c == '\r' || c == '\n') continue; + csv->cellCounts[csv->rowCount] = rowCellCount; + csv->rowCount++; + rowCellCount = 0; + state = CSV_PARSE_STATE_FIND_CELL; + i--; + continue; + + default: + printf("Error occured during parse operation."); + free(NULL); + } + } + csv->buffer[j++] = '\0'; + + if(rowCellCount != 0) { + csv->cellCounts[csv->rowCount] = rowCellCount; + csv->rowCount++; + } +} + +void csvDispose(csv_t *csv) { + free(csv->buffer); + free(csv->cellCounts); + free(csv->rows); +} \ No newline at end of file diff --git a/src/dawntools/utils/csv.h b/src/dawntools/utils/csv.h new file mode 100644 index 00000000..6c06d754 --- /dev/null +++ b/src/dawntools/utils/csv.h @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "common.h" + +#define CSV_ROW_COUNT_MAX 128 + +typedef enum { + CSV_PARSE_STATE_FIND_CELL,//0 + CSV_PARSE_STATE_PARSE_CELL_WITH_QUOTES, + CSV_PARSE_STATE_PARSE_CELL,//2 + CSV_PARSE_STATE_LINE_END +} csvparsestate_t; + +typedef struct { + char *buffer; + char **rows; + int32_t rowCount; + int32_t *cellCounts; +} csv_t; + +void csvParse(char *string, csv_t *csv); + +void csvDispose(csv_t *csv); \ No newline at end of file diff --git a/src/dawntools/utils/file.c b/src/dawntools/utils/file.c new file mode 100644 index 00000000..3cb40623 --- /dev/null +++ b/src/dawntools/utils/file.c @@ -0,0 +1,195 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "file.h" + +void fileNormalizeSlashes(char *string) { + char c; + int i = 0; + + while(c = string[i++]) { + if(c != '\\' && c != '/') continue; + string[i-1] = FILE_PATH_SEP; + } +} + +void fileMkdirp(char *path) { + char buffer[FILENAME_MAX]; + char c; + int i = 0; + bool inFile; + bool hasMore; + + inFile = false; + hasMore = false; + while(c = path[i]) { + if((c == '\\' || c == '/') && i > 0) { + buffer[i] = '\0'; + fileMkdir(buffer, 0755); + inFile = false; + hasMore = false; + buffer[i] = FILE_PATH_SEP; + i++; + continue; + } + + if(c == '.') inFile = true; + hasMore = true; + buffer[i] = c; + i++; + } + + if(!inFile && hasMore) { + buffer[i] = '\0'; + fileMkdir(buffer, 0755); + } +} + +size_t assetReadString(FILE *file, char *buffer) { + size_t length; + fseek(file, 0, SEEK_END);// Seek to the end + length = ftell(file);// Get our current position (the end) + fseek(file, 0, SEEK_SET);// Reset the seek + if(buffer == NULL) return length; + return fread(buffer, 1, length, file);// Read all the bytes +} + +int32_t readAhead( + char *bufferIn, int32_t start, + char *bufferOut, + char *needles, int32_t needleCount +) { + int32_t i = start, k = 0, j; + char c; + bool needleFound = false; + + if(bufferIn[i] == '\0') return 0; + + while((c = bufferIn[i++]) != '\0') { + for(j = 0; j < needleCount; j++) { + if(c != needles[j]) continue; + needleFound = true; + } + if(needleFound) break; + if(bufferOut != NULL) bufferOut[k] = c; + k++; + } + + if(bufferOut != NULL) bufferOut[k] = '\0'; + return k; +} + +int32_t skipAhead( + char *bufferIn, int32_t start, + char *needles, int32_t needleCount +) { + char c; + int32_t j, k = 0, i = start; + bool needleFound; + + while((c = bufferIn[i++]) != '\0') { + needleFound = false; + for(j = 0; j < needleCount; j++) { + if(c != needles[j]) continue; + needleFound = true; + break; + } + if(!needleFound) break; + k++; + } + return k; +} + +void fileGetDirectory(char *file, char* buffer) { + char *c, *p; + int32_t i; + p = strrchr(file, FILE_PATH_SEP); + c = file; + i = 0; + do { + buffer[i++] = *c; + } while(++c < p); + buffer[i] = '\0'; +} + +bool fileListChildren( + char *directory, + char *buffer, + int32_t *count, + uint8_t *types, + char **children +) { + #if defined(_MSC_VER) + WIN32_FIND_DATA fdFile; + HANDLE hFind = NULL; + char sPath[2048]; + int32_t i; + + // Append wildcard + sprintf(sPath, "%s\\*.*", directory); + + // Scan first + if((hFind = FindFirstFile(sPath, &fdFile)) == INVALID_HANDLE_VALUE) { + printf("Path not found: [%s]\n", directory); + return false; + } + + // Iterate + i = 0; + do { + if( + strcmp(fdFile.cFileName, ".") == 0 || + strcmp(fdFile.cFileName, "..") == 0 + ) continue; + + // Get Full path. + sprintf(sPath, "%s\\%s", directory, fdFile.cFileName); + + //Is the entity a File or Folder? + if(fdFile.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + types[i] = FILE_CHILD_TYPE_DIR; + } else { + types[i] = FILE_CHILD_TYPE_FILE; + } + + children[i] = buffer + (i * FILE_CHILD_NAME_MAX); + strcpy(children[i], fdFile.cFileName); + i++; + } while(FindNextFile(hFind, &fdFile)); + + *count = i; + return true; + #else + struct dirent *de; + DIR *dr; + int32_t i; + + // Open Dir + dr = opendir(directory); + if(dr == NULL) { + printf("Could not open directory"); + return false; + } + + + // Iterate + i = 0; + while ((de = readdir(dr)) != NULL) { + // File or folder? + if(de->d_type != DT_REG) continue; + + // Copy into child buffer + children[i] = buffer + (i * FILE_CHILD_NAME_MAX); + strcpy(children[i], de->d_name); + i++; + } + if(closedir(dr)) return false; + + *count = i; + return true; + #endif +} \ No newline at end of file diff --git a/src/dawntools/utils/file.h b/src/dawntools/utils/file.h new file mode 100644 index 00000000..3a6b0f66 --- /dev/null +++ b/src/dawntools/utils/file.h @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "common.h" + +#define FILE_CHILD_TYPE_DIR 0x00 +#define FILE_CHILD_TYPE_FILE 0x01 +#define FILE_CHILD_NAME_MAX 512 +#define FILE_CHILD_COUNT_MAX 64 + +#if defined(_MSC_VER) + #include + #include + #define getcwd _getcwd + #define FILE_PATH_SEP '\\' + #define fileMkdir(path, perms) _mkdir(path) +#elif defined(__GNUC__) + #include + #include + #include + #define FILE_PATH_SEP '/' + #define fileMkdir(path, perms) mkdir(path, perms) +#endif + +void fileNormalizeSlashes(char *string); + +void fileMkdirp(char *path); + +size_t assetReadString(FILE *file, char *buffer); + +void fileGetDirectory(char *file, char* buffer); + +bool fileListChildren( + char *directory, + char *buffer, + int32_t *count, + uint8_t *types, + char **children +); + +/** + * Reads ahead to the first instance of the given character you provide. + * + * @param bufferIn Buffer to scan. + * @param start Start position within the buffer to scan from (inclusive). + * @param bufferOut Where to write the temporary data that was read ahead. + * @param needles Array of characters to scan for. + * @param needleCount How many elements are within the needles array. + * @return The count of characters skipped. + */ +int32_t readAhead( + char *bufferIn, int32_t start, + char *bufferOut, + char *needles, int32_t needleCount +); + + +/** + * Skips any characters found in the needles. + * + * @param bufferIn Buffer of chars to read. + * @param start Start of the buffer. + * @param needles Needles you are trying to skip. + * @param needleCount Count of needles in the needles array. + * @return The count of chars to skip ahead. + */ +int32_t skipAhead( + char *bufferIn, int32_t start, + char *needles, int32_t needleCount +); \ No newline at end of file diff --git a/src/dawntools/utils/image.c b/src/dawntools/utils/image.c new file mode 100644 index 00000000..2a350cc1 --- /dev/null +++ b/src/dawntools/utils/image.c @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "image.h" + +#ifndef STB_IMAGE_IMPLEMENTATION + #define STB_IMAGE_IMPLEMENTATION + #include +#endif + +#ifndef STB_IMAGE_RESIZE_IMPLEMENTATION + #define STB_IMAGE_RESIZE_IMPLEMENTATION + #include +#endif + +#ifndef STB_IMAGE_WRITE_IMPLEMENTATION + #define STB_IMAGE_WRITE_IMPLEMENTATION + #include +#endif + +void imageCopy( + uint8_t *source, int32_t sourceWidth, int32_t sourceHeight, + uint8_t *dest, int32_t destWidth, int32_t destHeight, + int32_t cropX, int32_t cropY, int32_t cropWidth, int32_t cropHeight, + int32_t pasteX, int32_t pasteY, + int32_t channels +) { + int32_t x, y, c; + int32_t absX, absY; + int32_t sourceIndex, targetIndex; + + if(cropX == -1) cropX = 0; + if(cropY == -1) cropY = 0; + if(cropWidth == -1) cropWidth = sourceWidth; + if(cropHeight == -1) cropHeight = sourceHeight; + if(pasteX == -1) pasteX = 0; + if(pasteY == -1) pasteY = 0; + + for(x = cropX; x < cropX + cropWidth; x++) { + for(y = cropY; y < cropY + cropHeight; y++) { + absX = x - cropX + pasteX; + absY = y - cropY + pasteY; + if(absX >= destWidth || absY >= destHeight || absX < 0 || absY < 0)continue; + targetIndex = absY * destWidth + absX; + sourceIndex = y * sourceWidth + x; + for(c = 0; c < channels; c++) { + dest[(targetIndex*channels) + c] = source[(sourceIndex*channels) + c]; + } + } + } +} \ No newline at end of file diff --git a/src/dawntools/utils/image.h b/src/dawntools/utils/image.h new file mode 100644 index 00000000..040d5189 --- /dev/null +++ b/src/dawntools/utils/image.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "common.h" +#include "file.h" + +#include +#include +#include + +void imageCopy( + uint8_t *source, int32_t sourceWidth, int32_t sourceHeight, + uint8_t *dest, int32_t destWidth, int32_t destHeight, + int32_t cropX, int32_t cropY, int32_t cropWidth, int32_t cropHeight, + int32_t pasteX, int32_t pasteY, + int32_t channels +); \ No newline at end of file diff --git a/src/dawntools/utils/xml.c b/src/dawntools/utils/xml.c new file mode 100644 index 00000000..8c6c43ed --- /dev/null +++ b/src/dawntools/utils/xml.c @@ -0,0 +1,210 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "xml.h" + +int32_t xmlLoadChild(xml_t *xml, char *data, int32_t i) { + char c; + int32_t level = 0; + uint8_t doing = XML_DOING_NOTHING; + bool insideTag = false; + char* buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); + int32_t bufferLength = 0; + + xml->value = NULL; + xml->attributeCount = 0; + + xml->children = malloc(sizeof(xml_t) * XML_CHILD_COUNT_MAX); + xml->childrenCount = 0; + + while(c = data[i++]) { + switch(doing) { + case XML_DOING_NOTHING: + // Look for either an opening tag (<) or a word for a value. + if(c == '>') continue; + if(c == '<') { + if(insideTag) { + i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1); + doing = XML_PARSING_CHILD; + } else { + doing = XML_PARSING_TAG_NAME; + level++; + insideTag = true; + } + continue; + } + + if(xmlIsWhitespace(c)) continue; + doing = XML_PARSING_VALUE; + buffer[bufferLength++] = c; + break; + + case XML_PARSING_TAG_NAME: + // Just keep reading until we either hit a space (end of the tag name) + // or a closing tag value, either / or > + if(xmlIsWhitespace(c) || c == '>' || c == '/') { + buffer[bufferLength] = '\0'; + xml->node = buffer; + buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); + bufferLength = 0; + if(c == '/') { + level--; + insideTag = false; + doing = XML_PARSING_CLOSE; + } else { + doing = c == '>' ? XML_DOING_NOTHING : XML_LOOKING_FOR_ATTRIBUTE; + } + continue; + } + buffer[bufferLength++] = c; + break; + + case XML_LOOKING_FOR_ATTRIBUTE: + // Look until we hit either the end of a tag, or the attribute itself + if(xmlIsWhitespace(c) || c == '>' || c == '/' || c == '=') { + if(c == '>' || c == '/') { + doing = XML_DOING_NOTHING; + if(c == '/') { + level--; + insideTag = false; + doing = XML_PARSING_CLOSE; + } + } else if(c == '=') { + doing = XML_LOOKING_FOR_ATTRIBUTE_VALUE; + } else { + doing = XML_LOOKING_FOR_ATTRIBUTE; + } + + if(bufferLength > 0) { + buffer[bufferLength] = '\0'; + xml->attributeNames[xml->attributeCount++] = buffer; + xml->attributeDatas[xml->attributeCount] = NULL; + buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); + bufferLength = 0; + } + continue; + } + + buffer[bufferLength++] = c; + break; + + case XML_LOOKING_FOR_ATTRIBUTE_VALUE: + // Keep looking until we find a quote mark + if(xmlIsWhitespace(c)) continue; + if(c == '>' || c == '/') { + doing = XML_DOING_NOTHING; + insideTag = false; + continue; + } + + if(c != '"') continue; + doing = XML_PARSING_ATTRIBUTE_VALUE; + break; + + case XML_PARSING_ATTRIBUTE_VALUE: + // Parse the attribute value until we find a quote mark. + if(c == '"') { + doing = XML_LOOKING_FOR_ATTRIBUTE; + buffer[bufferLength] = '\0'; + xml->attributeDatas[xml->attributeCount - 1] = buffer; + buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); + bufferLength = 0; + continue; + } + + buffer[bufferLength++] = c; + break; + + case XML_PARSING_VALUE: + // Keep parsing child until we find a < for an opening/closing tag. + if(c == '<') { + // In HTML Spec there could be a child here but not in XML spec. + doing = XML_PARSING_CLOSE; + buffer[bufferLength] = '\0'; + bufferLength = 0; + xml->value = buffer; + buffer = malloc(sizeof(char) * XML_TEXT_BUFFER_MAX); + continue; + } + + buffer[bufferLength++] = c; + break; + + case XML_PARSING_CHILD: + if(c == '<') { + // Read ahead and confirm this is a close or not + if(data[i] == '/') { + doing = XML_PARSING_CLOSE; + continue; + } + + // Likely another child. + i = xmlLoadChild(xml->children + xml->childrenCount++, data, i-1); + } + + if(xmlIsWhitespace(c)) continue; + + // In HTML Spec there's a chance for there to be a value here, but not + // in the XML spec. + break; + + case XML_PARSING_CLOSE: + // Just keep parsing until the tag closer finishes. + if(c != '>') continue; + doing = XML_DOING_NOTHING; + + //TODO: Return index or something? + free(buffer); + return i; + + default: + break; + } + } + + free(buffer); + return i; +} + +void xmlLoad(xml_t *xml, char *data) { + xmlLoadChild(xml, data, 0); +} + +void xmlDispose(xml_t *xml) { + uint8_t i; + + // Dispose children recursively + for(i = 0; i < xml->childrenCount; i++) { + xmlDispose(xml->children + i); + } + + // Free children array. + free(xml->children); + + // Dispose attributes + for(i = 0; i < xml->attributeCount; i++) { + free(xml->attributeNames[i]); + if((xml->attributeDatas + i) != NULL) { + free(xml->attributeDatas[i]); + } + } + + free(xml->node); + if(xml-> value != NULL) free(xml->value); +} + +int16_t xmlGetAttributeByName(xml_t *xml, char *name) { + int16_t i; + for(i = 0; i < xml->attributeCount; i++) { + if(strcmp(xml->attributeNames[i], name) == 0) return i; + } + return -1; +} + +bool xmlIsWhitespace(char c) { + return c == ' ' || c == '\r' || c == '\n' || c == '\t'; +} \ No newline at end of file diff --git a/src/dawntools/utils/xml.h b/src/dawntools/utils/xml.h new file mode 100644 index 00000000..fab84903 --- /dev/null +++ b/src/dawntools/utils/xml.h @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "common.h" +#include "file.h" + +#define XML_DOING_NOTHING 0x00 +#define XML_PARSING_TAG_NAME 0x01 +#define XML_LOOKING_FOR_ATTRIBUTE 0x02 +#define XML_PARSING_ATTRIBUTE_NAME 0x03 +#define XML_LOOKING_FOR_ATTRIBUTE_VALUE 0x04 +#define XML_PARSING_ATTRIBUTE_VALUE 0x05 +#define XML_PARSING_VALUE 0x06 +#define XML_PARSING_CHILD 0x07 +#define XML_PARSING_CLOSE 0x08 + +#define XML_TEXT_BUFFER_MAX 256 +#define XML_CHILD_COUNT_MAX 16 +#define XML_ATTRIBUTE_MAX 16 + +typedef struct _xml_t xml_t; + +typedef struct _xml_t { + char *node; + char *value; + + char *attributeNames[XML_ATTRIBUTE_MAX]; + char *attributeDatas[XML_ATTRIBUTE_MAX]; + uint8_t attributeCount; + + xml_t *children; + uint8_t childrenCount; +} xml_t; + +/** + * Load an XML child from a string buffer. + * + * @param xml XML to load. + * @param data Data to parse + * @param i Character index within the data + * @return The index in the data string this XML node ends. + */ +int32_t xmlLoadChild(xml_t *xml, char *data, int32_t i); + +/** + * Load an XML String into an XML memory. + * + * @param xml XML to load into. + * @param data XML string. + */ +void xmlLoad(xml_t *xml, char *data); + +/** + * Dispose a previously loaded XML. + * + * @param xml XML to dispose. + */ +void xmlDispose(xml_t *xml); + +int16_t xmlGetAttributeByName(xml_t *xml, char *name); + +bool xmlIsWhitespace(char c); \ No newline at end of file diff --git a/src/dawnwin32/CMakeLists.txt b/src/dawnwin32/CMakeLists.txt index 4dd3a2f5..99252d61 100644 --- a/src/dawnwin32/CMakeLists.txt +++ b/src/dawnwin32/CMakeLists.txt @@ -9,5 +9,11 @@ target_include_directories(${DAWN_TARGET_NAME} ${CMAKE_CURRENT_LIST_DIR} ) +# Platform variables +target_compile_definitions(${DAWN_TARGET_NAME} + PUBLIC + DAWN_ASSET_BUILD_PREFIX="../../../assets/" +) + # Subdirs add_subdirectory(host) \ No newline at end of file