Merge branch 'main' into break-literally-everything
This commit is contained in:
@@ -9,7 +9,6 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
run-tests:
|
run-tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -84,6 +84,11 @@ target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
|||||||
# Add main code
|
# Add main code
|
||||||
add_subdirectory(${DUSK_SOURCES_DIR})
|
add_subdirectory(${DUSK_SOURCES_DIR})
|
||||||
|
|
||||||
|
# Include generated headers
|
||||||
|
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||||
|
${DUSK_GENERATED_HEADERS_DIR}
|
||||||
|
)
|
||||||
|
|
||||||
# Handle tests
|
# Handle tests
|
||||||
if(ENABLE_TESTS)
|
if(ENABLE_TESTS)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|||||||
@@ -187,7 +187,6 @@ function sceneRender()
|
|||||||
if INPUT_POINTER then
|
if INPUT_POINTER then
|
||||||
mouseX = inputGetValue(INPUT_ACTION_POINTERX) * screenGetWidth()
|
mouseX = inputGetValue(INPUT_ACTION_POINTERX) * screenGetWidth()
|
||||||
mouseY = inputGetValue(INPUT_ACTION_POINTERY) * screenGetHeight()
|
mouseY = inputGetValue(INPUT_ACTION_POINTERY) * screenGetHeight()
|
||||||
end
|
|
||||||
|
|
||||||
-- Draw cursor
|
-- Draw cursor
|
||||||
spriteBatchPush(
|
spriteBatchPush(
|
||||||
@@ -198,8 +197,10 @@ function sceneRender()
|
|||||||
0, 0,
|
0, 0,
|
||||||
1, 1
|
1, 1
|
||||||
)
|
)
|
||||||
|
end
|
||||||
|
|
||||||
textDraw(10, 10, "Minesweeper")
|
|
||||||
|
textDraw(10, 10, "Hello World")
|
||||||
|
|
||||||
-- centerX = math.floor(screenGetWidth() / 2)
|
-- centerX = math.floor(screenGetWidth() / 2)
|
||||||
-- centerY = math.floor(screenGetHeight() / 2)
|
-- centerY = math.floor(screenGetHeight() / 2)
|
||||||
|
|||||||
Binary file not shown.
BIN
assets/ui/minogram.dtf
Normal file
BIN
assets/ui/minogram.dtf
Normal file
Binary file not shown.
63
cmake/configure/dolphin.cmake
Normal file
63
cmake/configure/dolphin.cmake
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
message(FATAL_ERROR "Configure Dolphin")
|
||||||
|
|
||||||
|
if(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii")
|
||||||
|
# Override to make library and binary be the same.
|
||||||
|
set(DUSK_LIBRARY_TARGET_NAME "${DUSK_LIBRARY_TARGET_NAME}.elf" CACHE INTERNAL ${DUSK_CACHE_TARGET})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fno-exceptions")
|
||||||
|
# configure_file(opengl.pc.in opengl.pc @ONLY)
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
pkg_check_modules(zip IMPORTED_TARGET libzip)
|
||||||
|
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||||
|
DOLPHIN
|
||||||
|
)
|
||||||
|
|
||||||
|
# Disable all warnings
|
||||||
|
target_compile_options(${DUSK_LIBRARY_TARGET_NAME} PRIVATE -w)
|
||||||
|
|
||||||
|
# Custom flags for cglm
|
||||||
|
set(CGLM_SHARED OFF CACHE BOOL "Build cglm shared" FORCE)
|
||||||
|
set(CGLM_STATIC ON CACHE BOOL "Build cglm static" FORCE)
|
||||||
|
find_package(cglm REQUIRED)
|
||||||
|
|
||||||
|
# Compile lua
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
liblua
|
||||||
|
URL https://www.lua.org/ftp/lua-5.5.0.tar.gz
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(liblua)
|
||||||
|
set(LUA_SRC_DIR "${liblua_SOURCE_DIR}/src")
|
||||||
|
set(LUA_C_FILES
|
||||||
|
lapi.c lauxlib.c lbaselib.c lcode.c lcorolib.c lctype.c ldblib.c ldebug.c
|
||||||
|
ldo.c ldump.c lfunc.c lgc.c linit.c liolib.c llex.c lmathlib.c lmem.c
|
||||||
|
loadlib.c lobject.c lopcodes.c loslib.c lparser.c lstate.c lstring.c
|
||||||
|
lstrlib.c ltable.c ltablib.c ltm.c lundump.c lutf8lib.c lvm.c lzio.c
|
||||||
|
)
|
||||||
|
list(TRANSFORM LUA_C_FILES PREPEND "${LUA_SRC_DIR}/")
|
||||||
|
add_library(liblua STATIC ${LUA_C_FILES})
|
||||||
|
target_include_directories(liblua PUBLIC "${LUA_SRC_DIR}")
|
||||||
|
target_compile_definitions(liblua PRIVATE LUA_USE_C89)
|
||||||
|
add_library(lua::lua ALIAS liblua)
|
||||||
|
set(Lua_FOUND TRUE CACHE BOOL "Lua found" FORCE)
|
||||||
|
|
||||||
|
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
|
||||||
|
cglm
|
||||||
|
liblua
|
||||||
|
m
|
||||||
|
fat
|
||||||
|
PkgConfig::zip
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
DISPLAY_WINDOW_WIDTH_DEFAULT=640
|
||||||
|
DISPLAY_WINDOW_HEIGHT_DEFAULT=480
|
||||||
|
DISPLAY_WIDTH=640
|
||||||
|
DISPLAY_HEIGHT=480
|
||||||
|
DISPLAY_SIZE_DYNAMIC=0
|
||||||
|
INPUT_GAMEPAD=1
|
||||||
|
THREAD_PTHREAD=1
|
||||||
|
TIME_FIXED=1
|
||||||
|
)
|
||||||
1
cmake/configure/gamecube.cmake
Normal file
1
cmake/configure/gamecube.cmake
Normal file
@@ -0,0 +1 @@
|
|||||||
|
include(cmake/configure/gamecube.cmake)
|
||||||
26
cmake/configure/linux.cmake
Normal file
26
cmake/configure/linux.cmake
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
find_package(SDL2 REQUIRED)
|
||||||
|
find_package(OpenGL REQUIRED)
|
||||||
|
|
||||||
|
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||||
|
SDL2
|
||||||
|
pthread
|
||||||
|
OpenGL::GL
|
||||||
|
GL
|
||||||
|
m
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
DISPLAY_SDL2=1
|
||||||
|
DISPLAY_WINDOW_WIDTH_DEFAULT=1080
|
||||||
|
DISPLAY_WINDOW_HEIGHT_DEFAULT=810
|
||||||
|
DISPLAY_SCREEN_HEIGHT_DEFAULT=270
|
||||||
|
DISPLAY_SHADER=1
|
||||||
|
INPUT_SDL2=1
|
||||||
|
INPUT_KEYBOARD=1
|
||||||
|
INPUT_POINTER=1
|
||||||
|
INPUT_GAMEPAD=1
|
||||||
|
THREAD_PTHREAD=1
|
||||||
|
TIME_SDL2=1
|
||||||
|
TIME_FIXED=0
|
||||||
|
)
|
||||||
36
cmake/configure/psp.cmake
Normal file
36
cmake/configure/psp.cmake
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
find_package(pspsdk REQUIRED)
|
||||||
|
find_package(SDL2 REQUIRED)
|
||||||
|
find_package(OpenGL REQUIRED)
|
||||||
|
|
||||||
|
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||||
|
${SDL2_LIBRARIES}
|
||||||
|
SDL2
|
||||||
|
pthread
|
||||||
|
OpenGL::GL
|
||||||
|
zip
|
||||||
|
bz2
|
||||||
|
z
|
||||||
|
mbedtls
|
||||||
|
mbedcrypto
|
||||||
|
lzma
|
||||||
|
m
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
|
||||||
|
${SDL2_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
|
PUBLIC
|
||||||
|
DISPLAY_SDL2=1
|
||||||
|
DISPLAY_WINDOW_WIDTH_DEFAULT=480
|
||||||
|
DISPLAY_WINDOW_HEIGHT_DEFAULT=272
|
||||||
|
DISPLAY_WIDTH=480
|
||||||
|
DISPLAY_HEIGHT=272
|
||||||
|
DISPLAY_SIZE_DYNAMIC=0
|
||||||
|
DISPLAY_COLOR_TABLE=1
|
||||||
|
INPUT_SDL2=1
|
||||||
|
INPUT_GAMEPAD=1
|
||||||
|
THREAD_PTHREAD=1
|
||||||
|
TIME_FIXED=1
|
||||||
|
)
|
||||||
1
cmake/configure/wii.cmake
Normal file
1
cmake/configure/wii.cmake
Normal file
@@ -0,0 +1 @@
|
|||||||
|
include(cmake/configure/dolphin.cmake)
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "map/mapchunk.h"
|
#include "map/mapchunk.h"
|
||||||
|
#include "util/endian.h"
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -56,7 +57,7 @@ errorret_t assetMapChunkHandler(assetcustom_t custom) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fix endianess if necessary
|
// Fix endianess if necessary
|
||||||
header.tileCount = le32toh(header.tileCount);
|
header.tileCount = endianLittleToHost32(header.tileCount);
|
||||||
|
|
||||||
if(header.tileCount != CHUNK_TILE_COUNT) {
|
if(header.tileCount != CHUNK_TILE_COUNT) {
|
||||||
zip_fclose(custom.zipFile);
|
zip_fclose(custom.zipFile);
|
||||||
@@ -111,7 +112,7 @@ errorret_t assetMapChunkHandler(assetcustom_t custom) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fix endianess if necessary
|
// Fix endianess if necessary
|
||||||
modelHeader.vertexCount = le32toh(modelHeader.vertexCount);
|
modelHeader.vertexCount = endianLittleToHost32(modelHeader.vertexCount);
|
||||||
|
|
||||||
if(
|
if(
|
||||||
vertexIndex + modelHeader.vertexCount >
|
vertexIndex + modelHeader.vertexCount >
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
#include "display/palette/palette.h"
|
#include "display/texture/palette.h"
|
||||||
|
|
||||||
typedef struct assetentire_s assetentire_t;
|
typedef struct assetentire_s assetentire_t;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
#include "assettexture.h"
|
#include "assettexture.h"
|
||||||
#include "asset/assettype.h"
|
#include "asset/assettype.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "display/texture.h"
|
#include "display/texture/texture.h"
|
||||||
|
#include "util/endian.h"
|
||||||
|
|
||||||
errorret_t assetTextureLoad(assetentire_t entire) {
|
errorret_t assetTextureLoad(assetentire_t entire) {
|
||||||
assertNotNull(entire.data, "Data pointer cannot be NULL.");
|
assertNotNull(entire.data, "Data pointer cannot be NULL.");
|
||||||
@@ -32,8 +33,8 @@ errorret_t assetTextureLoad(assetentire_t entire) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fix endian
|
// Fix endian
|
||||||
assetData->width = le32toh(assetData->width);
|
assetData->width = endianLittleToHost32(assetData->width);
|
||||||
assetData->height = le32toh(assetData->height);
|
assetData->height = endianLittleToHost32(assetData->height);
|
||||||
|
|
||||||
// Check dimensions.
|
// Check dimensions.
|
||||||
if(
|
if(
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "error/error.h"
|
#include "error/error.h"
|
||||||
|
|
||||||
#define ASSET_TEXTURE_WIDTH_MAX 256
|
#define ASSET_TEXTURE_WIDTH_MAX 2048
|
||||||
#define ASSET_TEXTURE_HEIGHT_MAX 256
|
#define ASSET_TEXTURE_HEIGHT_MAX 2048
|
||||||
#define ASSET_TEXTURE_SIZE_MAX ( \
|
#define ASSET_TEXTURE_SIZE_MAX ( \
|
||||||
ASSET_TEXTURE_WIDTH_MAX * ASSET_TEXTURE_HEIGHT_MAX \
|
ASSET_TEXTURE_WIDTH_MAX * ASSET_TEXTURE_HEIGHT_MAX \
|
||||||
)
|
)
|
||||||
@@ -20,10 +20,8 @@ typedef struct assetentire_s assetentire_t;
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
char_t header[3];
|
char_t header[3];
|
||||||
uint8_t version;
|
uint8_t version;
|
||||||
|
|
||||||
uint32_t width;
|
uint32_t width;
|
||||||
uint32_t height;
|
uint32_t height;
|
||||||
uint8_t paletteIndex;
|
|
||||||
uint8_t palette[ASSET_TEXTURE_SIZE_MAX];
|
uint8_t palette[ASSET_TEXTURE_SIZE_MAX];
|
||||||
} assettexture_t;
|
} assettexture_t;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
|
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "display/tileset/tileset.h"
|
#include "display/texture/tileset.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
#include "util/endian.h"
|
||||||
|
|
||||||
errorret_t assetTilesetLoad(assetentire_t entire) {
|
errorret_t assetTilesetLoad(assetentire_t entire) {
|
||||||
assertNotNull(entire.data, "Asset data cannot be null");
|
assertNotNull(entire.data, "Asset data cannot be null");
|
||||||
@@ -28,5 +30,41 @@ errorret_t assetTilesetLoad(assetentire_t entire) {
|
|||||||
errorThrow("Unsupported tileset version");
|
errorThrow("Unsupported tileset version");
|
||||||
}
|
}
|
||||||
|
|
||||||
errorThrow("unfinished");
|
// Fix endianness
|
||||||
|
tilesetData->tileWidth = endianLittleToHost16(tilesetData->tileWidth);
|
||||||
|
tilesetData->tileHeight = endianLittleToHost16(tilesetData->tileHeight);
|
||||||
|
tilesetData->columnCount = endianLittleToHost16(tilesetData->columnCount);
|
||||||
|
tilesetData->rowCount = endianLittleToHost16(tilesetData->rowCount);
|
||||||
|
tilesetData->right = endianLittleToHost16(tilesetData->right);
|
||||||
|
tilesetData->bottom = endianLittleToHost16(tilesetData->bottom);
|
||||||
|
|
||||||
|
if(tilesetData->tileWidth == 0) {
|
||||||
|
errorThrow("Tile width cannot be 0");
|
||||||
|
}
|
||||||
|
if(tilesetData->tileHeight == 0) {
|
||||||
|
errorThrow("Tile height cannot be 0");
|
||||||
|
}
|
||||||
|
if(tilesetData->columnCount == 0) {
|
||||||
|
errorThrow("Column count cannot be 0");
|
||||||
|
}
|
||||||
|
if(tilesetData->rowCount == 0) {
|
||||||
|
errorThrow("Row count cannot be 0");
|
||||||
|
}
|
||||||
|
|
||||||
|
tilesetData->u0 = endianLittleToHostFloat(tilesetData->u0);
|
||||||
|
tilesetData->v0 = endianLittleToHostFloat(tilesetData->v0);
|
||||||
|
|
||||||
|
if(tilesetData->v0 < 0.0f || tilesetData->v0 > 1.0f) {
|
||||||
|
errorThrow("Invalid v0 value in tileset");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup tileset
|
||||||
|
tileset->tileWidth = tilesetData->tileWidth;
|
||||||
|
tileset->tileHeight = tilesetData->tileHeight;
|
||||||
|
tileset->tileCount = tilesetData->columnCount * tilesetData->rowCount;
|
||||||
|
tileset->columns = tilesetData->columnCount;
|
||||||
|
tileset->rows = tilesetData->rowCount;
|
||||||
|
tileset->uv[0] = tilesetData->u0;
|
||||||
|
tileset->uv[1] = tilesetData->v0;
|
||||||
|
errorOk();
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,14 @@
|
|||||||
typedef struct {
|
typedef struct {
|
||||||
char_t header[3];
|
char_t header[3];
|
||||||
uint8_t version;
|
uint8_t version;
|
||||||
|
uint16_t tileWidth;
|
||||||
|
uint16_t tileHeight;
|
||||||
|
uint16_t columnCount;
|
||||||
|
uint16_t rowCount;
|
||||||
|
uint16_t right;
|
||||||
|
uint16_t bottom;
|
||||||
|
float_t u0;
|
||||||
|
float_t v0;
|
||||||
} assettileset_t;
|
} assettileset_t;
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
|||||||
@@ -8,5 +8,3 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
debug.c
|
debug.c
|
||||||
)
|
)
|
||||||
|
|
||||||
# Subdirs
|
|
||||||
@@ -9,7 +9,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
display.c
|
display.c
|
||||||
framebuffer.c
|
framebuffer.c
|
||||||
screen.c
|
screen.c
|
||||||
texture.c
|
|
||||||
spritebatch.c
|
spritebatch.c
|
||||||
text.c
|
text.c
|
||||||
)
|
)
|
||||||
@@ -17,38 +16,9 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
# Subdirectories
|
# Subdirectories
|
||||||
add_subdirectory(camera)
|
add_subdirectory(camera)
|
||||||
add_subdirectory(mesh)
|
add_subdirectory(mesh)
|
||||||
add_subdirectory(palette)
|
add_subdirectory(texture)
|
||||||
add_subdirectory(tileset)
|
|
||||||
|
|
||||||
if(DUSK_TARGET_SYSTEM STREQUAL "linux")
|
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
DISPLAY_SDL2=1
|
|
||||||
DISPLAY_WINDOW_WIDTH_DEFAULT=1080
|
|
||||||
DISPLAY_WINDOW_HEIGHT_DEFAULT=810
|
|
||||||
DISPLAY_SCREEN_HEIGHT_DEFAULT=270
|
|
||||||
)
|
|
||||||
elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
|
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
DISPLAY_SDL2=1
|
|
||||||
DISPLAY_WINDOW_WIDTH_DEFAULT=480
|
|
||||||
DISPLAY_WINDOW_HEIGHT_DEFAULT=272
|
|
||||||
DISPLAY_WIDTH=480
|
|
||||||
DISPLAY_HEIGHT=272
|
|
||||||
DISPLAY_SIZE_DYNAMIC=0
|
|
||||||
)
|
|
||||||
elseif(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii")
|
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
DISPLAY_WINDOW_WIDTH_DEFAULT=640
|
|
||||||
DISPLAY_WINDOW_HEIGHT_DEFAULT=480
|
|
||||||
DISPLAY_WIDTH=640
|
|
||||||
DISPLAY_HEIGHT=480
|
|
||||||
DISPLAY_SIZE_DYNAMIC=0
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
|
# Color definitions
|
||||||
dusk_run_python(
|
dusk_run_python(
|
||||||
dusk_color_defs
|
dusk_color_defs
|
||||||
tools.display.color.csv
|
tools.display.color.csv
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ errorret_t displayInit(void) {
|
|||||||
#else
|
#else
|
||||||
GLint mask = 0;
|
GLint mask = 0;
|
||||||
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &mask);
|
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &mask);
|
||||||
|
DISPLAY.usingShaderedPalettes = true;
|
||||||
if(mask & GL_CONTEXT_CORE_PROFILE_BIT) {
|
if(mask & GL_CONTEXT_CORE_PROFILE_BIT) {
|
||||||
GLint numExtens = 0;
|
GLint numExtens = 0;
|
||||||
glGetIntegerv(GL_NUM_EXTENSIONS, &numExtens);
|
glGetIntegerv(GL_NUM_EXTENSIONS, &numExtens);
|
||||||
@@ -100,7 +101,7 @@ errorret_t displayInit(void) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const char* ext = (const char*)glGetString(GL_EXTENSIONS);
|
const char* ext = (const char*)glGetString(GL_EXTENSIONS);
|
||||||
DISPLAY.usingShaderedPalettes = (
|
DISPLAY.usingShaderedPalettes = !(
|
||||||
ext && strstr(ext, "GL_EXT_paletted_texture")
|
ext && strstr(ext, "GL_EXT_paletted_texture")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "display/texture.h"
|
#include "display/texture/texture.h"
|
||||||
|
|
||||||
#define FRAMEBUFFER_CLEAR_COLOR (1 << 0)
|
#define FRAMEBUFFER_CLEAR_COLOR (1 << 0)
|
||||||
#define FRAMEBUFFER_CLEAR_DEPTH (1 << 1)
|
#define FRAMEBUFFER_CLEAR_DEPTH (1 << 1)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "display/mesh/quad.h"
|
#include "display/mesh/quad.h"
|
||||||
#include "display/texture.h"
|
#include "display/texture/texture.h"
|
||||||
|
|
||||||
#define SPRITEBATCH_SPRITES_MAX 128
|
#define SPRITEBATCH_SPRITES_MAX 128
|
||||||
#define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT)
|
#define SPRITEBATCH_VERTEX_COUNT (SPRITEBATCH_SPRITES_MAX * QUAD_VERTEX_COUNT)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ errorret_t textInit(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void textDispose(void) {
|
void textDispose(void) {
|
||||||
// textureDispose(&DEFAULT_FONT_TEXTURE);
|
textureDispose(&DEFAULT_FONT_TEXTURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void textDrawChar(
|
void textDrawChar(
|
||||||
@@ -36,7 +36,6 @@ void textDrawChar(
|
|||||||
if(tileIndex < 0 || tileIndex >= tileset->tileCount) {
|
if(tileIndex < 0 || tileIndex >= tileset->tileCount) {
|
||||||
tileIndex = ((int32_t)'@') - TEXT_CHAR_START;
|
tileIndex = ((int32_t)'@') - TEXT_CHAR_START;
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTrue(
|
assertTrue(
|
||||||
tileIndex >= 0 && tileIndex <= tileset->tileCount,
|
tileIndex >= 0 && tileIndex <= tileset->tileCount,
|
||||||
"Character is out of bounds for font tiles"
|
"Character is out of bounds for font tiles"
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "display/texture.h"
|
#include "display/texture/texture.h"
|
||||||
#include "display/tileset/tileset.h"
|
#include "display/texture/tileset.h"
|
||||||
|
|
||||||
#define TEXT_CHAR_START '!'
|
#define TEXT_CHAR_START '!'
|
||||||
|
|
||||||
|
|||||||
@@ -6,5 +6,7 @@
|
|||||||
# Sources
|
# Sources
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
|
tileset.c
|
||||||
|
texture.c
|
||||||
palette.c
|
palette.c
|
||||||
)
|
)
|
||||||
@@ -8,6 +8,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "display/color.h"
|
#include "display/color.h"
|
||||||
|
|
||||||
|
#if DISPLAY_SDL2
|
||||||
|
#if DISPLAY_SHADER == 1
|
||||||
|
|
||||||
|
#elif DISPLAY_COLOR_TABLE == 1
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error "Unsupported palette mode"
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#error "Unsupported palette mode"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define PALETTE_COUNT_MAX 4
|
#define PALETTE_COUNT_MAX 4
|
||||||
#define PALETTE_COLOR_COUNT_MAX 0xFF
|
#define PALETTE_COLOR_COUNT_MAX 0xFF
|
||||||
|
|
||||||
51
src/display/texture/palettetexture.c
Normal file
51
src/display/texture/palettetexture.c
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "palettetexture.h"
|
||||||
|
#include "assert/assert.h"
|
||||||
|
|
||||||
|
void paletteTextureInit(
|
||||||
|
palettetexture_t *texture,
|
||||||
|
const int32_t width,
|
||||||
|
const int32_t height,
|
||||||
|
const uint8_t *data
|
||||||
|
) {
|
||||||
|
assertNotNull(texture, "Palette texture cannot be NULL");
|
||||||
|
assertTrue(width > 0 && height > 0, "width/height must be greater than 0");
|
||||||
|
assertNotNull(data, "Palette texture data cannot be NULL");
|
||||||
|
|
||||||
|
#if DISPLAY_SDL2
|
||||||
|
#if DISPLAY_SHADER == 1
|
||||||
|
// Palette textures not supported, convert to GL_RED style texture
|
||||||
|
// so shader can perform the lookup.
|
||||||
|
uint8_t formatted[width * height];
|
||||||
|
for(int32_t i = 0; i < width * height; i++) {
|
||||||
|
uint8_t index = data.paletteData[i];
|
||||||
|
formatted[i] = index * 128;
|
||||||
|
}
|
||||||
|
glTexImage2D(
|
||||||
|
GL_TEXTURE_2D, 0, GL_R8, width, height, 0,
|
||||||
|
GL_RED, GL_UNSIGNED_BYTE, (void*)formatted
|
||||||
|
);
|
||||||
|
|
||||||
|
#else
|
||||||
|
glTexImage2D(
|
||||||
|
GL_TEXTURE_2D,
|
||||||
|
0, GL_COLOR_INDEX8_EXT,
|
||||||
|
width, height,
|
||||||
|
0, GL_COLOR_INDEX8_EXT,
|
||||||
|
GL_UNSIGNED_BYTE, (void*)data.paletteData
|
||||||
|
);
|
||||||
|
// glColorTableEXT(
|
||||||
|
// GL_TEXTURE_2D, GL_RGBA, data.palette.palette->colorCount, GL_RGBA,
|
||||||
|
// GL_UNSIGNED_BYTE, (const void*)data.palette.palette->colors
|
||||||
|
// );
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#error "Palette textures not supported on this platform"
|
||||||
|
#endif
|
||||||
|
}
|
||||||
28
src/display/texture/palettetexture.h
Normal file
28
src/display/texture/palettetexture.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
} palettetexture_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a palette texture.
|
||||||
|
*
|
||||||
|
* @param texture The palette texture to initialize.
|
||||||
|
* @param width The width of the texture. Must be a power of 2.
|
||||||
|
* @param height The height of the texture. Must be a power of 2.
|
||||||
|
* @param data The palette index data for the texture.
|
||||||
|
*/
|
||||||
|
void paletteTextureInit(
|
||||||
|
palettetexture_t *texture,
|
||||||
|
const int32_t width,
|
||||||
|
const int32_t height,
|
||||||
|
const uint8_t *data
|
||||||
|
);
|
||||||
@@ -52,12 +52,13 @@ void textureInit(
|
|||||||
uint8_t formatted[width * height];
|
uint8_t formatted[width * height];
|
||||||
for(int32_t i = 0; i < width * height; i++) {
|
for(int32_t i = 0; i < width * height; i++) {
|
||||||
uint8_t index = data.paletteData[i];
|
uint8_t index = data.paletteData[i];
|
||||||
formatted[i] = index;
|
formatted[i] = index * 128;
|
||||||
}
|
}
|
||||||
glTexImage2D(
|
glTexImage2D(
|
||||||
GL_TEXTURE_2D, 0, GL_R, width, height, 0,
|
GL_TEXTURE_2D, 0, GL_R8, width, height, 0,
|
||||||
GL_RGBA, GL_UNSIGNED_BYTE, (void*)formatted
|
GL_RED, GL_UNSIGNED_BYTE, (void*)formatted
|
||||||
);
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
glTexImage2D(
|
glTexImage2D(
|
||||||
GL_TEXTURE_2D,
|
GL_TEXTURE_2D,
|
||||||
@@ -66,12 +67,17 @@ void textureInit(
|
|||||||
0, GL_COLOR_INDEX8_EXT,
|
0, GL_COLOR_INDEX8_EXT,
|
||||||
GL_UNSIGNED_BYTE, (void*)data.paletteData
|
GL_UNSIGNED_BYTE, (void*)data.paletteData
|
||||||
);
|
);
|
||||||
|
|
||||||
// glColorTableEXT(
|
// glColorTableEXT(
|
||||||
// GL_TEXTURE_2D, GL_RGBA, data.palette.palette->colorCount, GL_RGBA,
|
// GL_TEXTURE_2D, GL_RGBA, data.palette.palette->colorCount, GL_RGBA,
|
||||||
// GL_UNSIGNED_BYTE, (const void*)data.palette.palette->colors
|
// GL_UNSIGNED_BYTE, (const void*)data.palette.palette->colors
|
||||||
// );
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GLenum err = glGetError();
|
||||||
|
if(err != GL_NO_ERROR) {
|
||||||
|
printf("GL Error uploading palette texture: %d\n", err);
|
||||||
|
assertUnreachable("GL error uploading palette texture");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "display/color.h"
|
#include "display/color.h"
|
||||||
#include "display/displaydefs.h"
|
#include "display/displaydefs.h"
|
||||||
#include "display/palette/palette.h"
|
#include "display/texture/palette.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
#if DISPLAY_SDL2
|
#if DISPLAY_SDL2
|
||||||
@@ -9,14 +9,12 @@
|
|||||||
#include "dusk.h"
|
#include "dusk.h"
|
||||||
|
|
||||||
typedef struct tileset_s {
|
typedef struct tileset_s {
|
||||||
const char_t *name;
|
uint16_t tileWidth;
|
||||||
const uint16_t tileWidth;
|
uint16_t tileHeight;
|
||||||
const uint16_t tileHeight;
|
uint16_t tileCount;
|
||||||
const uint16_t tileCount;
|
uint16_t columns;
|
||||||
const uint16_t columns;
|
uint16_t rows;
|
||||||
const uint16_t rows;
|
vec2 uv;
|
||||||
const vec2 uv;
|
|
||||||
const char_t *image;
|
|
||||||
} tileset_t;
|
} tileset_t;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
# Copyright (c) 2025 Dominic Masters
|
|
||||||
#
|
|
||||||
# This software is released under the MIT License.
|
|
||||||
# https://opensource.org/licenses/MIT
|
|
||||||
|
|
||||||
# Sources
|
|
||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
tileset.c
|
|
||||||
)
|
|
||||||
@@ -33,11 +33,6 @@
|
|||||||
#include <ogcsys.h>
|
#include <ogcsys.h>
|
||||||
#include <gccore.h>
|
#include <gccore.h>
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
#include <sys/endian.h>
|
|
||||||
#else
|
|
||||||
#ifndef le32toh
|
|
||||||
#define le32toh(x) (x)
|
|
||||||
#endif
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
typedef bool bool_t;
|
typedef bool bool_t;
|
||||||
|
|||||||
@@ -11,30 +11,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
inputaction.c
|
inputaction.c
|
||||||
)
|
)
|
||||||
|
|
||||||
if(DUSK_TARGET_SYSTEM STREQUAL "linux")
|
# Input Action Definitions
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
INPUT_SDL2=1
|
|
||||||
INPUT_KEYBOARD=1
|
|
||||||
INPUT_POINTER=1
|
|
||||||
INPUT_GAMEPAD=1
|
|
||||||
)
|
|
||||||
|
|
||||||
elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
|
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
INPUT_SDL2=1
|
|
||||||
INPUT_GAMEPAD=1
|
|
||||||
)
|
|
||||||
|
|
||||||
elseif(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii")
|
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
INPUT_GAMEPAD=1
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# CSV
|
|
||||||
dusk_run_python(
|
dusk_run_python(
|
||||||
dusk_input_csv_defs
|
dusk_input_csv_defs
|
||||||
tools.input.csv
|
tools.input.csv
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
backpack.c
|
backpack.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Item Definitions
|
||||||
dusk_run_python(
|
dusk_run_python(
|
||||||
dusk_item_csv_defs
|
dusk_item_csv_defs
|
||||||
tools.item.csv
|
tools.item.csv
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include "moduletexture.h"
|
#include "moduletexture.h"
|
||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "display/texture.h"
|
#include "display/texture/texture.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include "moduletileset.h"
|
#include "moduletileset.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "display/tileset/tileset.h"
|
#include "display/texture/tileset.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
#include "util/string.h"
|
#include "util/string.h"
|
||||||
#include "debug/debug.h"
|
#include "debug/debug.h"
|
||||||
@@ -41,13 +41,7 @@ int moduleTilesetIndex(lua_State *l) {
|
|||||||
tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt");
|
tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt");
|
||||||
assertNotNull(ts, "Tileset pointer cannot be NULL.");
|
assertNotNull(ts, "Tileset pointer cannot be NULL.");
|
||||||
|
|
||||||
if(stringCompare(key, "name") == 0) {
|
if(stringCompare(key, "tileWidth") == 0) {
|
||||||
lua_pushstring(l, ts->name);
|
|
||||||
return 1;
|
|
||||||
} else if(stringCompare(key, "texture") == 0) {
|
|
||||||
lua_pushstring(l, ts->image);
|
|
||||||
return 1;
|
|
||||||
} else if(stringCompare(key, "tileWidth") == 0) {
|
|
||||||
lua_pushnumber(l, ts->tileWidth);
|
lua_pushnumber(l, ts->tileWidth);
|
||||||
return 1;
|
return 1;
|
||||||
} else if(stringCompare(key, "tileHeight") == 0) {
|
} else if(stringCompare(key, "tileHeight") == 0) {
|
||||||
@@ -72,7 +66,9 @@ int moduleTilesetToString(lua_State *l) {
|
|||||||
tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt");
|
tileset_t *ts = (tileset_t *)luaL_checkudata(l, 1, "tileset_mt");
|
||||||
assertNotNull(ts, "Tileset pointer cannot be NULL.");
|
assertNotNull(ts, "Tileset pointer cannot be NULL.");
|
||||||
|
|
||||||
lua_pushfstring(l, "Tileset: %s", ts->name);
|
lua_pushfstring(l, "Tileset: %dx%d tile, %d columns, %d rows",
|
||||||
|
ts->tileWidth, ts->tileHeight, ts->columns, ts->rows
|
||||||
|
);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
storyflag.c
|
storyflag.c
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Story Flag Definitions
|
||||||
dusk_run_python(
|
dusk_run_python(
|
||||||
dusk_story_defs
|
dusk_story_defs
|
||||||
tools.story.csv
|
tools.story.csv
|
||||||
|
|||||||
@@ -9,20 +9,3 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
thread.c
|
thread.c
|
||||||
threadmutex.c
|
threadmutex.c
|
||||||
)
|
)
|
||||||
|
|
||||||
# Compiler flags.
|
|
||||||
if(DUSK_TARGET_SYSTEM STREQUAL "linux")
|
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
THREAD_PTHREAD=1
|
|
||||||
)
|
|
||||||
elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
|
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
THREAD_PTHREAD=1
|
|
||||||
)
|
|
||||||
elseif(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii")
|
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
@@ -8,22 +8,3 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
|||||||
PUBLIC
|
PUBLIC
|
||||||
time.c
|
time.c
|
||||||
)
|
)
|
||||||
|
|
||||||
# Compiler defs
|
|
||||||
if(DUSK_TARGET_SYSTEM STREQUAL "linux")
|
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
TIME_SDL2=1
|
|
||||||
TIME_FIXED=0
|
|
||||||
)
|
|
||||||
elseif(DUSK_TARGET_SYSTEM STREQUAL "psp")
|
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
TIME_FIXED=1
|
|
||||||
)
|
|
||||||
elseif(DUSK_TARGET_SYSTEM STREQUAL "gamecube" OR DUSK_TARGET_SYSTEM STREQUAL "wii")
|
|
||||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME}
|
|
||||||
PUBLIC
|
|
||||||
TIME_FIXED=1
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||||
PUBLIC
|
PUBLIC
|
||||||
array.c
|
array.c
|
||||||
|
endian.c
|
||||||
memory.c
|
memory.c
|
||||||
string.c
|
string.c
|
||||||
math.c
|
math.c
|
||||||
|
|||||||
67
src/util/endian.c
Normal file
67
src/util/endian.c
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "util/endian.h"
|
||||||
|
#include "util/memory.h"
|
||||||
|
|
||||||
|
bool_t isHostLittleEndian(void) {
|
||||||
|
uint32_t value = ENDIAN_MAGIC;
|
||||||
|
uint8_t *bytePtr = (uint8_t *)&value;
|
||||||
|
return bytePtr[0] == 0x04;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t endianLittleToHost32(uint32_t value) {
|
||||||
|
if(isHostLittleEndian()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
((value & 0x000000FF) << 24) |
|
||||||
|
((value & 0x0000FF00) << 8) |
|
||||||
|
((value & 0x00FF0000) >> 8) |
|
||||||
|
((value & 0xFF000000) >> 24)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t endianLittleToHost16(uint16_t value) {
|
||||||
|
if(isHostLittleEndian()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return (uint16_t)(((value & 0x00FF) << 8) |
|
||||||
|
((value & 0xFF00) >> 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t endianLittleToHost64(uint64_t value) {
|
||||||
|
if(isHostLittleEndian()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
((value & 0x00000000000000FFULL) << 56) |
|
||||||
|
((value & 0x000000000000FF00ULL) << 40) |
|
||||||
|
((value & 0x0000000000FF0000ULL) << 24) |
|
||||||
|
((value & 0x00000000FF000000ULL) << 8) |
|
||||||
|
((value & 0x000000FF00000000ULL) >> 8) |
|
||||||
|
((value & 0x0000FF0000000000ULL) >> 24) |
|
||||||
|
((value & 0x00FF000000000000ULL) >> 40) |
|
||||||
|
((value & 0xFF00000000000000ULL) >> 56)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
float_t endianLittleToHostFloat(float_t value) {
|
||||||
|
if(isHostLittleEndian()) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t temp;
|
||||||
|
float_t result;
|
||||||
|
memoryCopy(&temp, &value, sizeof(uint32_t));
|
||||||
|
temp = endianLittleToHost32(temp);
|
||||||
|
memoryCopy(&result, &temp, sizeof(uint32_t));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
48
src/util/endian.h
Normal file
48
src/util/endian.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2026 Dominic Masters
|
||||||
|
*
|
||||||
|
* This software is released under the MIT License.
|
||||||
|
* https://opensource.org/licenses/MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "dusk.h"
|
||||||
|
|
||||||
|
static const uint32_t ENDIAN_MAGIC = 0x01020304;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the host system is little-endian.
|
||||||
|
*
|
||||||
|
* @return true if the host is little-endian, false otherwise.
|
||||||
|
*/
|
||||||
|
bool_t isHostLittleEndian(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a 32-bit integer from little-endian to host byte order.
|
||||||
|
*
|
||||||
|
* @param value The little-endian value to convert.
|
||||||
|
* @return The value in host byte order.
|
||||||
|
*/
|
||||||
|
uint32_t endianLittleToHost32(uint32_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a 16-bit integer from little-endian to host byte order.
|
||||||
|
* @param value The little-endian value to convert.
|
||||||
|
* @return The value in host byte order.
|
||||||
|
*/
|
||||||
|
uint16_t endianLittleToHost16(uint16_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a 64-bit integer from little-endian to host byte order.
|
||||||
|
* @param value The little-endian value to convert.
|
||||||
|
* @return The value in host byte order.
|
||||||
|
*/
|
||||||
|
uint64_t endianLittleToHost64(uint64_t value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a float from little-endian to host byte order.
|
||||||
|
*
|
||||||
|
* @param value The little-endian value to convert.
|
||||||
|
* @return The value in host byte order.
|
||||||
|
*/
|
||||||
|
float_t endianLittleToHostFloat(float_t value);
|
||||||
@@ -661,7 +661,7 @@
|
|||||||
const header = new Uint8Array([0x44, 0x50, 0x54, 0x01]); // 'DPT' + version 1
|
const header = new Uint8Array([0x44, 0x50, 0x54, 0x01]); // 'DPT' + version 1
|
||||||
|
|
||||||
// Dimensions
|
// Dimensions
|
||||||
const widthBytes = new Uint8Array([ imageWidth ]);
|
const widthBytes = new Uint32Array([ imageWidth ]);
|
||||||
const heightBytes = new Uint32Array([ imageHeight ]);
|
const heightBytes = new Uint32Array([ imageHeight ]);
|
||||||
|
|
||||||
// add indexed image data (imageWidth * imageHeight bytes)
|
// add indexed image data (imageWidth * imageHeight bytes)
|
||||||
|
|||||||
@@ -94,6 +94,9 @@
|
|||||||
return onBadImages('Please select 2 or more image images.');
|
return onBadImages('Please select 2 or more image images.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort images by name to ensure consistent output
|
||||||
|
images = Object.fromEntries(Object.entries(images).sort(([nameA], [nameB]) => nameA.localeCompare(nameB)));
|
||||||
|
|
||||||
elFileError.style.display = 'none';
|
elFileError.style.display = 'none';
|
||||||
|
|
||||||
let strInfo = `Selected ${Object.keys(images).length} images:\n`;
|
let strInfo = `Selected ${Object.keys(images).length} images:\n`;
|
||||||
@@ -119,7 +122,16 @@
|
|||||||
outputHeight = firstHeight;
|
outputHeight = firstHeight;
|
||||||
outputWidth = nextPowerOfTwo(Object.values(images).reduce((sum, img) => sum + img.width, 0));
|
outputWidth = nextPowerOfTwo(Object.values(images).reduce((sum, img) => sum + img.width, 0));
|
||||||
} else {
|
} else {
|
||||||
onBadImages('All images must share the same width or height, and that dimension must be a power of two.');
|
if(allImagesShareWidth) {
|
||||||
|
outputWidth = nextPowerOfTwo(firstWidth);
|
||||||
|
outputHeight = nextPowerOfTwo(Object.values(images).reduce((sum, img) => sum + img.height, 0));
|
||||||
|
} else if(allImagesShareHeight) {
|
||||||
|
outputHeight = nextPowerOfTwo(firstHeight);
|
||||||
|
outputWidth = nextPowerOfTwo(Object.values(images).reduce((sum, img) => sum + img.width, 0));
|
||||||
|
} else {
|
||||||
|
onBadImages('All images must share the same width or height to be joined together.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update preview
|
// Update preview
|
||||||
|
|||||||
@@ -30,6 +30,26 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Tileset Settings</h2>
|
<h2>Tileset Settings</h2>
|
||||||
|
<div>
|
||||||
|
<button data-load-tileset>Load Tileset</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Define tile count by:
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
Tile Size
|
||||||
|
<input type="radio" name="define-by" value="size" checked />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
Tile Count
|
||||||
|
<input type="radio" name="define-by" value="count" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div data-tile-sizes>
|
||||||
<div>
|
<div>
|
||||||
<label>Tile Width:</label>
|
<label>Tile Width:</label>
|
||||||
<input type="number" value="8" data-tile-width min="1" step="1" />
|
<input type="number" value="8" data-tile-width min="1" step="1" />
|
||||||
@@ -38,6 +58,8 @@
|
|||||||
<label>Tile Height:</label>
|
<label>Tile Height:</label>
|
||||||
<input type="number" value="8" data-tile-height min="1" step="1" />
|
<input type="number" value="8" data-tile-height min="1" step="1" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div data-tile-counts style="display: none;">
|
||||||
<div>
|
<div>
|
||||||
<label>Column Count:</label>
|
<label>Column Count:</label>
|
||||||
<input type="number" value="10" data-column-count min="1" step="1" />
|
<input type="number" value="10" data-column-count min="1" step="1" />
|
||||||
@@ -48,6 +70,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label>Unused Space on Right of Texture:</label>
|
||||||
|
<input type="number" value="0" data-right min="0" step="1" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Unused Space on Bottom of Texture:</label>
|
||||||
|
<input type="number" value="0" data-bottom min="0" step="1" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2>Preview</h2>
|
<h2>Preview</h2>
|
||||||
<div>
|
<div>
|
||||||
@@ -80,9 +112,15 @@
|
|||||||
<div>
|
<div>
|
||||||
<button data-tileset-download>Download Tileset</button>
|
<button data-tileset-download>Download Tileset</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
// Element selectors
|
||||||
|
const elDefineBySize = document.querySelector('input[name="define-by"][value="size"]');
|
||||||
|
const elDefineByCount = document.querySelector('input[name="define-by"][value="count"]');
|
||||||
|
const elTileSizes = document.querySelector('[data-tile-sizes]');
|
||||||
|
const elTileCounts = document.querySelector('[data-tile-counts]');
|
||||||
const elTileWidth = document.querySelector('[data-tile-width]');
|
const elTileWidth = document.querySelector('[data-tile-width]');
|
||||||
const elTileHeight = document.querySelector('[data-tile-height]');
|
const elTileHeight = document.querySelector('[data-tile-height]');
|
||||||
const elColumnCount = document.querySelector('[data-column-count]');
|
const elColumnCount = document.querySelector('[data-column-count]');
|
||||||
@@ -92,6 +130,8 @@
|
|||||||
const elOutputInformation = document.querySelector('[data-output-information]');
|
const elOutputInformation = document.querySelector('[data-output-information]');
|
||||||
const elOutputPreview = document.querySelector('[data-output-preview]');
|
const elOutputPreview = document.querySelector('[data-output-preview]');
|
||||||
const elScale = document.querySelector('[data-indexed-preview-scale]');
|
const elScale = document.querySelector('[data-indexed-preview-scale]');
|
||||||
|
const elRight = document.querySelector('[data-right]');
|
||||||
|
const elBottom = document.querySelector('[data-bottom]');
|
||||||
const btnDownloadTileset = document.querySelector('[data-tileset-download]');
|
const btnDownloadTileset = document.querySelector('[data-tileset-download]');
|
||||||
const btnBackgroundWhite = document.querySelector('[data-page-bg-white]');
|
const btnBackgroundWhite = document.querySelector('[data-page-bg-white]');
|
||||||
const btnBackgroundTransparent = document.querySelector('[data-page-bg-transparent]');
|
const btnBackgroundTransparent = document.querySelector('[data-page-bg-transparent]');
|
||||||
@@ -99,130 +139,181 @@
|
|||||||
const btnBackgroundMagenta = document.querySelector('[data-page-bg-magenta]');
|
const btnBackgroundMagenta = document.querySelector('[data-page-bg-magenta]');
|
||||||
const btnBackgroundBlue = document.querySelector('[data-page-bg-blue]');
|
const btnBackgroundBlue = document.querySelector('[data-page-bg-blue]');
|
||||||
const btnBackgroundGreen = document.querySelector('[data-page-bg-green]');
|
const btnBackgroundGreen = document.querySelector('[data-page-bg-green]');
|
||||||
const btnDownloadPalette = document.querySelector('[data-palette-download]');
|
const btnLoadTileset = document.querySelector('[data-load-tileset]');
|
||||||
const btnDownloadImage = document.querySelector('[data-indexed-download]');
|
|
||||||
|
|
||||||
|
// State
|
||||||
let imageWidth = 0;
|
let imageWidth = 0;
|
||||||
let imageHeight = 0;
|
let imageHeight = 0;
|
||||||
let image = null;
|
let pixels = null;
|
||||||
let hoveredX = -1;
|
let hoveredX = -1;
|
||||||
let hoveredY = -1;
|
let hoveredY = -1;
|
||||||
|
|
||||||
const updatePreview = () => {
|
const getValues = () => {
|
||||||
if(!image) return;
|
if(!pixels) return null;
|
||||||
|
|
||||||
|
let tileWidth, tileHeight, columnCount, rowCount;
|
||||||
|
if(elDefineBySize.checked) {
|
||||||
|
console.log('Defining by size');
|
||||||
|
tileWidth = parseInt(elTileWidth.value) || 0;
|
||||||
|
tileHeight = parseInt(elTileHeight.value) || 0;
|
||||||
|
columnCount = Math.floor(imageWidth / tileWidth);
|
||||||
|
rowCount = Math.floor(imageHeight / tileHeight);
|
||||||
|
} else {
|
||||||
|
console.log('Defining by count');
|
||||||
|
columnCount = parseInt(elColumnCount.value) || 0;
|
||||||
|
rowCount = parseInt(elRowCount.value) || 0;
|
||||||
|
tileWidth = Math.floor(imageWidth / columnCount);
|
||||||
|
tileHeight = Math.floor(imageHeight / rowCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
const right = parseInt(elRight.value) || 0;
|
||||||
|
const bottom = parseInt(elBottom.value) || 0;
|
||||||
|
|
||||||
const scale = parseInt(elScale.value) || 1;
|
const scale = parseInt(elScale.value) || 1;
|
||||||
elOutputPreview.width = imageWidth * scale;
|
const scaledWidth = imageWidth * scale;
|
||||||
elOutputPreview.height = imageHeight * scale;
|
const scaledHeight = imageHeight * scale;
|
||||||
|
const scaledTileWidth = tileWidth * scale;
|
||||||
|
const scaledTileHeight = tileHeight * scale;
|
||||||
|
const scaledRight = right * scale;
|
||||||
|
const scaledBottom = bottom * scale;
|
||||||
|
|
||||||
|
const u0 = (tileWidth / imageWidth);
|
||||||
|
const v0 = (tileHeight / imageHeight);
|
||||||
|
|
||||||
|
const hoveredTileX = isNaN(hoveredX) || hoveredX < 0 ? 0 : hoveredX;
|
||||||
|
const hoveredTileY = isNaN(hoveredY) || hoveredY < 0 ? 0 : hoveredY;
|
||||||
|
const hoveredU0 = hoveredTileX * u0;
|
||||||
|
const hoveredV0 = hoveredTileY * v0;
|
||||||
|
const hoveredU1 = hoveredU0 + u0;
|
||||||
|
const hoveredV1 = hoveredV0 + v0;
|
||||||
|
const hoveredTileIndex = hoveredTileY * columnCount + hoveredTileX;
|
||||||
|
|
||||||
|
return {
|
||||||
|
tileWidth,
|
||||||
|
tileHeight,
|
||||||
|
columnCount,
|
||||||
|
rowCount,
|
||||||
|
right,
|
||||||
|
bottom,
|
||||||
|
scale,
|
||||||
|
scaledWidth,
|
||||||
|
scaledHeight,
|
||||||
|
scaledTileWidth,
|
||||||
|
scaledTileHeight,
|
||||||
|
scaledRight,
|
||||||
|
scaledBottom,
|
||||||
|
u0,
|
||||||
|
v0,
|
||||||
|
hoveredU0,
|
||||||
|
hoveredV0,
|
||||||
|
hoveredU1,
|
||||||
|
hoveredV1,
|
||||||
|
hoveredTileX,
|
||||||
|
hoveredTileY,
|
||||||
|
hoveredTileIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePreview = () => {
|
||||||
|
const v = getValues();
|
||||||
|
if(!v) return;
|
||||||
|
// console.log('Updating preview with values', v);
|
||||||
|
|
||||||
|
// Prepare canvas
|
||||||
|
elOutputPreview.width = v.scaledWidth;
|
||||||
|
elOutputPreview.height = v.scaledHeight;
|
||||||
const ctx = elOutputPreview.getContext('2d');
|
const ctx = elOutputPreview.getContext('2d');
|
||||||
ctx.clearRect(0, 0, elOutputPreview.width, elOutputPreview.height);
|
ctx.clearRect(0, 0, elOutputPreview.width, elOutputPreview.height);
|
||||||
ctx.imageSmoothingEnabled = false;
|
ctx.imageSmoothingEnabled = false;
|
||||||
ctx.drawImage(image, 0, 0, elOutputPreview.width, elOutputPreview.height);
|
|
||||||
|
|
||||||
// Draw grid lines
|
// Resize pixels
|
||||||
const tileWidth = parseInt(elTileWidth.value) || 0;
|
const tempCanvas = document.createElement('canvas');
|
||||||
const tileHeight = parseInt(elTileHeight.value) || 0;
|
tempCanvas.width = imageWidth;
|
||||||
const scaledTileWidth = tileWidth * scale;
|
tempCanvas.height = imageHeight;
|
||||||
const scaledTileHeight = tileHeight * scale;
|
const tempCtx = tempCanvas.getContext('2d');
|
||||||
const columnCount = parseInt(elColumnCount.value) || 0;
|
const imageData = tempCtx.createImageData(imageWidth, imageHeight);
|
||||||
const rowCount = parseInt(elRowCount.value) || 0;
|
imageData.data.set(pixels);
|
||||||
|
tempCtx.putImageData(imageData, 0, 0);
|
||||||
|
ctx.drawImage(tempCanvas, 0, 0, elOutputPreview.width, elOutputPreview.height);
|
||||||
|
|
||||||
|
// Draw blue overflow area for right and bottom cutoff
|
||||||
|
ctx.fillStyle = 'rgba(0,0,255,0.5)';
|
||||||
|
if(v.right > 0) {
|
||||||
|
ctx.fillRect(v.scaledWidth - v.scaledRight, 0, v.scaledRight, elOutputPreview.height);
|
||||||
|
}
|
||||||
|
if(v.bottom > 0) {
|
||||||
|
ctx.fillRect(0, v.scaledHeight - v.scaledBottom, elOutputPreview.width, v.scaledBottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw red grid lines for tile boundaries
|
||||||
ctx.strokeStyle = 'rgba(255,0,0,1)';
|
ctx.strokeStyle = 'rgba(255,0,0,1)';
|
||||||
for(let x = scaledTileWidth; x < elOutputPreview.width; x += scaledTileWidth) {
|
for (let x = v.scaledTileWidth; x < elOutputPreview.width; x += v.scaledTileWidth) {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(x, 0);
|
ctx.moveTo(x, 0);
|
||||||
ctx.lineTo(x, elOutputPreview.height);
|
ctx.lineTo(x, elOutputPreview.height);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
for(let y = scaledTileHeight; y < elOutputPreview.height; y += scaledTileHeight) {
|
for (let y = v.scaledTileHeight; y < elOutputPreview.height; y += v.scaledTileHeight) {
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(0, y);
|
ctx.moveTo(0, y);
|
||||||
ctx.lineTo(elOutputPreview.width, y);
|
ctx.lineTo(elOutputPreview.width, y);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
const u0 = tileWidth / imageWidth;
|
|
||||||
const v0 = tileHeight / imageHeight;
|
|
||||||
const hoveredU0 = hoveredX != -1 && hoveredY != -1 ? (hoveredX * tileWidth / imageWidth) : 0;
|
|
||||||
const hoveredV0 = hoveredX != -1 && hoveredY != -1 ? (hoveredY * tileHeight / imageHeight) : 0;
|
|
||||||
const hoveredU1 = hoveredU0 + u0;
|
|
||||||
const hoveredV1 = hoveredV0 + v0;
|
|
||||||
|
|
||||||
elOutputInformation.value = [
|
elOutputInformation.value = [
|
||||||
hoveredX != -1 ? `Hovered Tile: ${hoveredX}, ${hoveredY} (${hoveredY * columnCount + hoveredX})` : 'Hovered Tile: None',
|
v.hoveredX != -1 ? `Hovered Tile: ${v.hoveredTileX}, ${v.hoveredTileY} (${v.hoveredTileIndex})` : 'Hovered Tile: None',
|
||||||
hoveredX != -1 ? `Hovered UV: ${(hoveredU0).toFixed(4)}, ${(hoveredV0).toFixed(4)} -> ${(hoveredU1).toFixed(4)}, ${(hoveredV1).toFixed(4)}` : 'Hovered UV: None',
|
v.hoveredX != -1 ? `Hovered UV: ${(v.hoveredU0).toFixed(4)}, ${(v.hoveredV0).toFixed(4)} -> ${(v.hoveredU1).toFixed(4)}, ${(v.hoveredV1).toFixed(4)}` : 'Hovered UV: None',
|
||||||
`Image Width: ${imageWidth}`,
|
`Image Width: ${imageWidth}`,
|
||||||
`Image Height: ${imageHeight}`,
|
`Image Height: ${imageHeight}`,
|
||||||
`Tile Width: ${elTileWidth.value}`,
|
`Tile Width: ${v.tileWidth}`,
|
||||||
`Tile Height: ${elTileHeight.value}`,
|
`Tile Height: ${v.tileHeight}`,
|
||||||
`Column Count: ${columnCount}`,
|
`Column Count: ${v.columnCount}`,
|
||||||
`uv: ${u0.toFixed(4)}, ${v0.toFixed(4)}`,
|
`uv: ${v.u0.toFixed(4)}, ${v.v0.toFixed(4)}`,
|
||||||
`Row Count: ${rowCount}`,
|
`Row Count: ${v.rowCount}`,
|
||||||
`Tile count: ${columnCount * rowCount}`,
|
`Tile count: ${v.columnCount * v.rowCount}`,
|
||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
elTileWidth.addEventListener('input', () => {
|
elTileWidth.addEventListener('input', updatePreview);
|
||||||
if(imageWidth) {
|
elTileHeight.addEventListener('input', updatePreview);
|
||||||
const tileWidth = parseInt(elTileWidth.value);
|
elColumnCount.addEventListener('input', updatePreview);
|
||||||
const columnCount = Math.floor(imageWidth / tileWidth);
|
elRowCount.addEventListener('input', updatePreview);
|
||||||
elColumnCount.value = columnCount;
|
elRight.addEventListener('input', updatePreview);
|
||||||
|
elBottom.addEventListener('input', updatePreview);
|
||||||
|
elScale.addEventListener('input', updatePreview);
|
||||||
|
btnBackgroundWhite.addEventListener('click', () => document.body.style.background = 'white');
|
||||||
|
btnBackgroundTransparent.addEventListener('click', () => document.body.style.background = 'black');
|
||||||
|
btnBackgroundCheckerboard.addEventListener('click', () => document.body.style.background = 'repeating-conic-gradient(#ccc 0% 25%, #eee 0% 50%) 50% / 20px 20px');
|
||||||
|
btnBackgroundMagenta.addEventListener('click', () => document.body.style.background = 'magenta');
|
||||||
|
btnBackgroundBlue.addEventListener('click', () => document.body.style.background = 'blue');
|
||||||
|
btnBackgroundGreen.addEventListener('click', () => document.body.style.background = 'green');
|
||||||
|
|
||||||
|
elDefineBySize.addEventListener('change', () => {
|
||||||
|
if (elDefineBySize.checked) {
|
||||||
|
elTileSizes.style.display = '';
|
||||||
|
elTileCounts.style.display = 'none';
|
||||||
}
|
}
|
||||||
updatePreview();
|
updatePreview();
|
||||||
});
|
});
|
||||||
|
|
||||||
elTileHeight.addEventListener('input', () => {
|
elDefineByCount.addEventListener('change', () => {
|
||||||
if(imageHeight) {
|
if (elDefineByCount.checked) {
|
||||||
const tileHeight = parseInt(elTileHeight.value);
|
elTileSizes.style.display = 'none';
|
||||||
const rowCount = Math.floor(imageHeight / tileHeight);
|
elTileCounts.style.display = '';
|
||||||
elRowCount.value = rowCount;
|
|
||||||
}
|
}
|
||||||
updatePreview();
|
updatePreview();
|
||||||
});
|
});
|
||||||
|
|
||||||
elColumnCount.addEventListener('input', () => {
|
|
||||||
if(!imageWidth) {
|
|
||||||
alert('Set an image first to calculate tile width from column count');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const columnCount = parseInt(elColumnCount.value);
|
|
||||||
const tileWidth = Math.floor(imageWidth / columnCount);
|
|
||||||
elTileWidth.value = tileWidth;
|
|
||||||
updatePreview();
|
|
||||||
});
|
|
||||||
|
|
||||||
elRowCount.addEventListener('input', () => {
|
|
||||||
if(!imageHeight) {
|
|
||||||
alert('Set an image first to calculate tile height from row count');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rowCount = parseInt(elRowCount.value);
|
|
||||||
const tileHeight = Math.floor(imageHeight / rowCount);
|
|
||||||
elTileHeight.value = tileHeight;
|
|
||||||
updatePreview();
|
|
||||||
});
|
|
||||||
|
|
||||||
elScale.addEventListener('input', () => {
|
|
||||||
updatePreview();
|
|
||||||
});
|
|
||||||
|
|
||||||
elOutputPreview.addEventListener('mousemove', (e) => {
|
elOutputPreview.addEventListener('mousemove', (e) => {
|
||||||
if(!image) return;
|
const values = getValues();
|
||||||
|
if(!values) return;
|
||||||
const scale = parseInt(elScale.value) || 1;
|
|
||||||
const tileWidth = parseInt(elTileWidth.value) || 0;
|
|
||||||
const tileHeight = parseInt(elTileHeight.value) || 0;
|
|
||||||
const columnCount = parseInt(elColumnCount.value) || 0;
|
|
||||||
const rowCount = parseInt(elRowCount.value) || 0;
|
|
||||||
|
|
||||||
const rect = elOutputPreview.getBoundingClientRect();
|
const rect = elOutputPreview.getBoundingClientRect();
|
||||||
const x = Math.floor((e.clientX - rect.left) / scale);
|
const x = Math.floor((e.clientX - rect.left) / values.scale);
|
||||||
const y = Math.floor((e.clientY - rect.top) / scale);
|
const y = Math.floor((e.clientY - rect.top) / values.scale);
|
||||||
hoveredX = Math.floor(x / tileWidth);
|
hoveredX = Math.floor(x / values.tileWidth);
|
||||||
hoveredY = Math.floor(y / tileHeight);
|
hoveredY = Math.floor(y / values.tileHeight);
|
||||||
|
if(hoveredX < 0 || hoveredX >= values.columnCount || hoveredY < 0 || hoveredY >= values.rowCount) {
|
||||||
if(hoveredX < 0 || hoveredX >= columnCount || hoveredY < 0 || hoveredY >= rowCount) {
|
|
||||||
hoveredX = -1;
|
hoveredX = -1;
|
||||||
hoveredY = -1;
|
hoveredY = -1;
|
||||||
}
|
}
|
||||||
@@ -239,65 +330,144 @@
|
|||||||
// File
|
// File
|
||||||
elFileInput.addEventListener('change', (e) => {
|
elFileInput.addEventListener('change', (e) => {
|
||||||
elOutputError.style.display = 'none';
|
elOutputError.style.display = 'none';
|
||||||
|
pixels = null;
|
||||||
|
|
||||||
if(!elFileInput.files.length) {
|
if (!elFileInput.files.length) {
|
||||||
elOutputError.textContent = 'No file selected';
|
elOutputError.textContent = 'No file selected';
|
||||||
elOutputError.style.display = 'block';
|
elOutputError.style.display = 'block';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const file = elFileInput.files[0];
|
const file = elFileInput.files[0];
|
||||||
image = new Image();
|
|
||||||
|
if (file.name.endsWith('.dpt')) {
|
||||||
|
// Load DPT file
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = () => {
|
||||||
|
const arrayBuffer = reader.result;
|
||||||
|
const data = new Uint8Array(arrayBuffer);
|
||||||
|
if (data[0] !== 'D'.charCodeAt(0) || data[1] !== 'P'.charCodeAt(0) || data[2] !== 'T'.charCodeAt(0)) {
|
||||||
|
elOutputError.textContent = 'Invalid DPT file';
|
||||||
|
elOutputError.style.display = 'block';
|
||||||
|
return;
|
||||||
|
} else if (data[3] !== 0x01) {
|
||||||
|
elOutputError.textContent = 'Unsupported DPT version';
|
||||||
|
elOutputError.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin color indexes
|
||||||
|
const width = (
|
||||||
|
data[4] |
|
||||||
|
(data[5] << 8) |
|
||||||
|
(data[6] << 16) |
|
||||||
|
(data[7] << 24)
|
||||||
|
)
|
||||||
|
const height = (
|
||||||
|
data[8] |
|
||||||
|
(data[9] << 8) |
|
||||||
|
(data[10] << 16) |
|
||||||
|
(data[11] << 24)
|
||||||
|
);
|
||||||
|
|
||||||
|
imageWidth = width;
|
||||||
|
imageHeight = height;
|
||||||
|
|
||||||
|
if(data.length < 12 + width * height) {
|
||||||
|
elOutputError.textContent = 'Invalid DPT file: not enough pixel data';
|
||||||
|
elOutputError.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uniqueIndexes = [];
|
||||||
|
for (let i = 0; i < width * height; i++) {
|
||||||
|
const colorIndex = data[12 + i];
|
||||||
|
if (!uniqueIndexes.includes(colorIndex)) {
|
||||||
|
uniqueIndexes.push(colorIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const adhocPalette = [];
|
||||||
|
for (let i = 0; i < uniqueIndexes.length; i++) {
|
||||||
|
const index = uniqueIndexes[i];
|
||||||
|
// Get the most different possible color for this index
|
||||||
|
const color = [
|
||||||
|
(index * 37) % 256,
|
||||||
|
(index * 61) % 256,
|
||||||
|
(index * 97) % 256,
|
||||||
|
255
|
||||||
|
];
|
||||||
|
adhocPalette[index] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
pixels = new Uint8Array(width * height * 4);
|
||||||
|
for (let i = 0; i < width * height; i++) {
|
||||||
|
const colorIndex = data[12 + i];
|
||||||
|
const color = adhocPalette[colorIndex];
|
||||||
|
pixels[i * 4] = color[0];
|
||||||
|
pixels[i * 4 + 1] = color[1];
|
||||||
|
pixels[i * 4 + 2] = color[2];
|
||||||
|
pixels[i * 4 + 3] = color[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.onerror = () => {
|
||||||
|
elOutputError.textContent = 'Failed to read file';
|
||||||
|
elOutputError.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
|
} else {
|
||||||
|
const image = new Image();
|
||||||
image.onload = () => {
|
image.onload = () => {
|
||||||
imageWidth = image.width;
|
imageWidth = image.width;
|
||||||
imageHeight = image.height;
|
imageHeight = image.height;
|
||||||
const tileWidth = parseInt(elTileWidth.value);
|
|
||||||
const tileHeight = parseInt(elTileHeight.value);
|
// Pixels
|
||||||
const columnCount = Math.floor(imageWidth / tileWidth);
|
const tempCanvas = document.createElement('canvas');
|
||||||
const rowCount = Math.floor(imageHeight / tileHeight);
|
tempCanvas.width = imageWidth;
|
||||||
elColumnCount.value = columnCount;
|
tempCanvas.height = imageHeight;
|
||||||
elRowCount.value = rowCount;
|
const tempCtx = tempCanvas.getContext('2d');
|
||||||
|
tempCtx.drawImage(image, 0, 0);
|
||||||
|
const imageData = tempCtx.getImageData(0, 0, imageWidth, imageHeight);
|
||||||
|
pixels = imageData.data;
|
||||||
|
|
||||||
updatePreview();
|
updatePreview();
|
||||||
};
|
};
|
||||||
image.onerror = () => {
|
image.onerror = () => {
|
||||||
image = null;
|
pixels = null;
|
||||||
elOutputError.textContent = 'Failed to load image';
|
elOutputError.textContent = 'Failed to load image';
|
||||||
elOutputError.style.display = 'block';
|
elOutputError.style.display = 'block';
|
||||||
updatePreview();
|
updatePreview();
|
||||||
};
|
};
|
||||||
image.src = URL.createObjectURL(file);
|
image.src = URL.createObjectURL(file);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
btnDownloadTileset.addEventListener('click', () => {
|
btnDownloadTileset.addEventListener('click', () => {
|
||||||
if(!image) {
|
const v = getValues();
|
||||||
alert('No image loaded');
|
if(!v) {
|
||||||
|
alert('No valid tileset to download');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tileWidth = parseInt(elTileWidth.value);
|
// Header: DTF0, tileWidth, tileHeight, columnCount, rowCount, right, bottom, u0, v0
|
||||||
const tileHeight = parseInt(elTileHeight.value);
|
|
||||||
const columnCount = parseInt(elColumnCount.value);
|
|
||||||
const rowCount = parseInt(elRowCount.value);
|
|
||||||
const u0 = tileWidth / imageWidth;
|
|
||||||
const v0 = tileHeight / imageHeight;
|
|
||||||
const tileCount = columnCount * rowCount;
|
|
||||||
|
|
||||||
const headerBytes = new Uint8Array([
|
const headerBytes = new Uint8Array([
|
||||||
'D'.charCodeAt(0),// Dusk
|
'D'.charCodeAt(0), // Dusk
|
||||||
'T'.charCodeAt(0),// Tileset
|
'T'.charCodeAt(0), // Tileset
|
||||||
'F'.charCodeAt(0),// File/Format
|
'F'.charCodeAt(0), // File/Format
|
||||||
0x00, // version
|
0x00, // version
|
||||||
tileWidth & 0xFF,// Tile width (uint16_t)
|
v.tileWidth & 0xFF, (v.tileWidth >> 8) & 0xFF,
|
||||||
(tileWidth >> 8) & 0xFF,
|
v.tileHeight & 0xFF, (v.tileHeight >> 8) & 0xFF,
|
||||||
tileHeight & 0xFF,// Tile height (uint16_t)
|
v.columnCount & 0xFF, (v.columnCount >> 8) & 0xFF,
|
||||||
(tileHeight >> 8) & 0xFF,
|
v.rowCount & 0xFF, (v.rowCount >> 8) & 0xFF,
|
||||||
columnCount & 0xFF,// Column count (uint16_t)
|
v.right & 0xFF, (v.right >> 8) & 0xFF,
|
||||||
(columnCount >> 8) & 0xFF,
|
v.bottom & 0xFF, (v.bottom >> 8) & 0xFF,
|
||||||
rowCount & 0xFF,// Row count (uint16_t)
|
...new Uint8Array(new Float32Array([v.u0]).buffer),
|
||||||
(rowCount >> 8) & 0xFF,
|
...new Uint8Array(new Float32Array([v.v0]).buffer),
|
||||||
// Float32_t UV step (u0, v0)
|
|
||||||
...new Uint8Array(new Float32Array([u0]).buffer),
|
|
||||||
...new Uint8Array(new Float32Array([v0]).buffer),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Download file
|
// Download file
|
||||||
@@ -307,25 +477,65 @@
|
|||||||
a.href = url;
|
a.href = url;
|
||||||
a.download = 'tileset.dtf';
|
a.download = 'tileset.dtf';
|
||||||
a.click();
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
});
|
});
|
||||||
|
|
||||||
btnBackgroundWhite.addEventListener('click', () => {
|
btnLoadTileset.addEventListener('click', () => {
|
||||||
document.body.style.background = 'white';
|
// Browse for file.
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.accept = '.dtf';
|
||||||
|
input.addEventListener('change', (e) => {
|
||||||
|
const files = e?.target?.files;
|
||||||
|
if (!files || !files.length || !files[0]) {
|
||||||
|
alert('No file selected');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = files[0];
|
||||||
|
if (!file.name.endsWith('.dtf')) {
|
||||||
|
alert('Invalid file type. Please select a .dtf file.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => {
|
||||||
|
const arrayBuffer = reader.result;
|
||||||
|
const data = new Uint8Array(arrayBuffer);
|
||||||
|
if (data[0] !== 'D'.charCodeAt(0) || data[1] !== 'T'.charCodeAt(0) || data[2] !== 'F'.charCodeAt(0)) {
|
||||||
|
alert('Invalid DTF file');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[3] !== 0x00) {
|
||||||
|
alert('Unsupported DTF version');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tileWidth = data[4] | (data[5] << 8);
|
||||||
|
const tileHeight = data[6] | (data[7] << 8);
|
||||||
|
const columnCount = data[8] | (data[9] << 8);
|
||||||
|
const rowCount = data[10] | (data[11] << 8);
|
||||||
|
const right = data[12] | (data[13] << 8);
|
||||||
|
const bottom = data[14] | (data[15] << 8);
|
||||||
|
|
||||||
|
// Switch to using size definition
|
||||||
|
elDefineBySize.checked = true;
|
||||||
|
elTileWidth.value = tileWidth;
|
||||||
|
elTileHeight.value = tileHeight;
|
||||||
|
elTileSizes.style.display = '';
|
||||||
|
elTileCounts.style.display = 'none';
|
||||||
|
elRight.value = right;
|
||||||
|
elBottom.value = bottom;
|
||||||
|
|
||||||
|
updatePreview();
|
||||||
|
};
|
||||||
|
reader.onerror = () => {
|
||||||
|
alert('Failed to read file');
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(file);
|
||||||
});
|
});
|
||||||
btnBackgroundTransparent.addEventListener('click', () => {
|
input.click();
|
||||||
document.body.style.background = 'black';
|
|
||||||
});
|
|
||||||
btnBackgroundCheckerboard.addEventListener('click', () => {
|
|
||||||
document.body.style.background = 'repeating-conic-gradient(#ccc 0% 25%, #eee 0% 50%) 50% / 20px 20px';
|
|
||||||
});
|
|
||||||
btnBackgroundMagenta.addEventListener('click', () => {
|
|
||||||
document.body.style.background = 'magenta';
|
|
||||||
});
|
|
||||||
btnBackgroundBlue.addEventListener('click', () => {
|
|
||||||
document.body.style.background = 'blue';
|
|
||||||
});
|
|
||||||
btnBackgroundGreen.addEventListener('click', () => {
|
|
||||||
document.body.style.background = 'green';
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Init
|
// Init
|
||||||
|
|||||||
Reference in New Issue
Block a user