Compare commits

..

1 Commits

Author SHA1 Message Date
5919881568 Vita progress 2026-03-28 19:09:21 -05:00
70 changed files with 415 additions and 842 deletions

View File

@@ -9,7 +9,6 @@ module('text')
module('tileset')
module('texture')
module('input')
module('shader')
CELL_STATE_DEFAULT = 0
CELL_STATE_HOVER = 1
@@ -63,7 +62,7 @@ function cellDraw(x, y, type)
slice = cellSliceDisabled
end
spriteBatchPush(
spriteBatchPush(textureCell,
x, y,
x + tilesetCell.tileWidth, y + tilesetCell.tileHeight,
colorWhite(),
@@ -180,28 +179,21 @@ end
function sceneUpdate()
x = x + inputAxis(INPUT_ACTION_LEFT, INPUT_ACTION_RIGHT)
y = y - inputAxis(INPUT_ACTION_UP, INPUT_ACTION_DOWN)
y = y + inputAxis(INPUT_ACTION_UP, INPUT_ACTION_DOWN)
end
function sceneRender()
-- Update camera
camera.bottom = 0
camera.top = screenGetHeight()
cameraPushMatrix(camera)
camera.bottom = screenGetHeight()
camera.right = screenGetWidth()
shaderBind(SHADER_UNLIT)
proj = cameraGetProjectionMatrix(camera)
shaderSetMatrix(SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj)
view = cameraGetViewMatrix(camera)
shaderSetMatrix(SHADER_UNLIT, SHADER_UNLIT_VIEW, view)
textDraw(10, 10, "Hello World\nHow are you?")
-- spriteBatchPush(
-- x, y,
-- x + 32, y + 32,
-- colorWhite()
-- )
spriteBatchPush(
nil,
x, y, x + 32, y + 32,
colorWhite()
)
-- Update mouse position
-- if INPUT_POINTER then
@@ -259,4 +251,6 @@ function sceneRender()
-- end
-- end
spriteBatchFlush()
cameraPopMatrix()
end

BIN
assets/ui/minogram.dpt Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,17 @@
include(FetchContent)
set(ENABLE_ZSTD OFF CACHE BOOL "" FORCE)
set(BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(BUILD_REGRESS OFF CACHE BOOL "" FORCE)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(BUILD_DOC OFF CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(LIBZIP_DO_INSTALL OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
libzip
GIT_REPOSITORY https://github.com/nih-at/libzip.git
GIT_TAG v1.11.4
)
FetchContent_MakeAvailable(libzip)

View File

@@ -0,0 +1,20 @@
# 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)

View File

@@ -1,17 +1,43 @@
# Allow user to manually specify libzip paths
# LIBZIP_ROOT: root directory for libzip (optional)
# LIBZIP_INCLUDE_DIR: path to zip.h (optional)
# LIBZIP_LIBRARY: path to libzip library (optional)
find_package(ZLIB REQUIRED)
find_path(LIBZIP_INCLUDE_DIR NAMES zip.h)
if(NOT LIBZIP_INCLUDE_DIR AND LIBZIP_ROOT)
find_path(LIBZIP_INCLUDE_DIR NAMES zip.h HINTS "${LIBZIP_ROOT}/include")
endif()
if(NOT LIBZIP_INCLUDE_DIR)
find_path(LIBZIP_INCLUDE_DIR NAMES zip.h)
endif()
mark_as_advanced(LIBZIP_INCLUDE_DIR)
find_library(LIBZIP_LIBRARY NAMES zip)
if(NOT LIBZIP_LIBRARY AND LIBZIP_ROOT)
find_library(LIBZIP_LIBRARY NAMES zip HINTS "${LIBZIP_ROOT}/lib")
endif()
if(NOT LIBZIP_LIBRARY)
find_library(LIBZIP_LIBRARY NAMES zip)
endif()
mark_as_advanced(LIBZIP_LIBRARY)
get_filename_component(_libzip_libdir ${LIBZIP_LIBRARY} DIRECTORY)
find_file(_libzip_pkgcfg libzip.pc
HINTS ${_libzip_libdir} ${LIBZIP_INCLUDE_DIR}/..
PATH_SUFFIXES pkgconfig lib/pkgconfig libdata/pkgconfig
NO_DEFAULT_PATH
)
if(LIBZIP_LIBRARY)
get_filename_component(_libzip_libdir ${LIBZIP_LIBRARY} DIRECTORY)
endif()
if(NOT _libzip_pkgcfg AND LIBZIP_ROOT)
find_file(_libzip_pkgcfg libzip.pc
HINTS "${LIBZIP_ROOT}/lib/pkgconfig" "${LIBZIP_ROOT}/libdata/pkgconfig"
NO_DEFAULT_PATH
)
endif()
if(NOT _libzip_pkgcfg AND LIBZIP_LIBRARY)
find_file(_libzip_pkgcfg libzip.pc
HINTS ${_libzip_libdir} ${LIBZIP_INCLUDE_DIR}/..
PATH_SUFFIXES pkgconfig lib/pkgconfig libdata/pkgconfig
NO_DEFAULT_PATH
)
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(

View File

@@ -21,26 +21,7 @@ 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)
include(cmake/modules/CompileLua.cmake)
# Link libraries
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PRIVATE

62
cmake/targets/vita.cmake Normal file
View File

@@ -0,0 +1,62 @@
include("${VITASDK}/share/vita.cmake" REQUIRED)
# Manually define libzip for Vita
set(LIBZIP_LIBRARY "${VITASDK}/lib/libzip.a" CACHE FILEPATH "libzip library for Vita")
set(LIBZIP_INCLUDE_DIR "${VITASDK}/include" CACHE PATH "libzip include dir for Vita")
set(VITA_APP_NAME "Red Rectangle")
set(VITA_TITLEID "VSDK00017")
set(VITA_VERSION "01.00")
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
mathneon
vitashark
kubridge_stub
SceAppMgr_stub
SceAudio_stub
SceCtrl_stub
SceCommonDialog_stub
SceDisplay_stub
SceKernelDmacMgr_stub
SceGxm_stub
SceShaccCg_stub
SceSysmodule_stub
ScePower_stub
SceTouch_stub
SceVshBridge_stub
SceIofilemgr_stub
SceShaccCgExt
SDL2-static
libtaihen_stub.a
lua::lua
zip
pthread
m
)
target_include_directories(${DUSK_LIBRARY_TARGET_NAME} PRIVATE
${SDL2_INCLUDE_DIRS}
)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SDL2
DUSK_OPENGL
DUSK_VITA
DUSK_INPUT_GAMEPAD
DUSK_PLATFORM_ENDIAN_LITTLE
DUSK_DISPLAY_WIDTH=960
DUSK_DISPLAY_HEIGHT=544
)
vita_create_self(${DUSK_BINARY_TARGET_NAME}.self ${DUSK_BINARY_TARGET_NAME})
vita_create_vpk(${DUSK_BINARY_TARGET_NAME}.vpk ${VITA_TITLEID} ${DUSK_BINARY_TARGET_NAME}.self
VERSION ${VITA_VERSION}
NAME ${VITA_APP_NAME}
# FILE sce_sys/icon0.png sce_sys/icon0.png
# FILE sce_sys/livearea/contents/bg.png sce_sys/livearea/contents/bg.png
# FILE sce_sys/livearea/contents/startup.png sce_sys/livearea/contents/startup.png
# FILE sce_sys/livearea/contents/template.xml sce_sys/livearea/contents/template.xml
)

64
docker/vita/Dockerfile Normal file
View File

@@ -0,0 +1,64 @@
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
cmake \
git \
curl \
sudo \
wget \
libarchive-tools \
python3 \
python3-pip \
python3-dotenv \
python3-polib \
python3-pil \
python3-pyqt5 \
python3-opengl \
&& rm -rf /var/lib/apt/lists/*
RUN git clone https://github.com/vitasdk/vdpm /vdpm
WORKDIR /vdpm
RUN ./bootstrap-vitasdk.sh
ENV VITASDK=/usr/local/vitasdk
ENV PATH="${VITASDK}/bin:${PATH}"
RUN git clone https://github.com/vitasdk/packages.git /vitapackages
WORKDIR /vitapackages
RUN bash -lc '\
dir_array=( \
zlib \
bzip2 \
henkaku \
taihen \
kubridge \
openal-soft \
openssl \
curl \
curlpp \
expat \
opus \
opusfile \
glm \
kuio \
vitaShaRK \
libmathneon \
vitaGL \
SceShaccCgExt \
sdl2 \
libzip \
luajit \
); \
curdir=$(pwd); \
for d in "${dir_array[@]}"; do \
echo "${curdir}/${d}"; \
cd "${curdir}/${d}"; \
vita-makepkg; \
vdpm *-arm.tar.xz; \
done \
'
WORKDIR /workdir

View File

@@ -1,6 +1,5 @@
#!/bin/bash
cmake -S . -B build-knulli -G Ninja \
-DDUSK_BUILD_TESTS=ON \
-DDUSK_TARGET_SYSTEM=knulli \
-DCMAKE_TOOLCHAIN_FILE=./cmake/toolchains/aarch64-linux-gnu.cmake \
-DCMAKE_BUILD_TYPE=Release

3
scripts/build-vita-docker.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
docker build -t dusk-vita -f docker/vita/Dockerfile .
docker run --rm -v $(pwd):/workdir dusk-vita /bin/bash -c "./scripts/build-vita.sh"

6
scripts/build-vita.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/bash
cd /workdir
cmake -S . -B build-vita \
-DDUSK_TARGET_SYSTEM=vita \
-DCMAKE_TOOLCHAIN_FILE="$VITASDK/share/vita.toolchain.cmake"
cmake --build build-vita -- -j$(nproc)

View File

@@ -4,9 +4,8 @@
# https://opensource.org/licenses/MIT
add_subdirectory(dusk)
add_subdirectory(duskrpg)
if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli")
if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli" OR DUSK_TARGET_SYSTEM STREQUAL "vita")
add_subdirectory(dusklinux)
add_subdirectory(dusksdl2)
add_subdirectory(duskgl)

View File

@@ -52,9 +52,12 @@ add_subdirectory(engine)
add_subdirectory(error)
add_subdirectory(event)
add_subdirectory(input)
add_subdirectory(item)
add_subdirectory(locale)
add_subdirectory(map)
add_subdirectory(scene)
add_subdirectory(script)
add_subdirectory(story)
add_subdirectory(time)
add_subdirectory(ui)
add_subdirectory(util)

View File

@@ -19,9 +19,12 @@ typedef enum {
ASSET_TYPE_NULL,
ASSET_TYPE_TEXTURE,
// ASSET_TYPE_PALETTE,
ASSET_TYPE_TILESET,
ASSET_TYPE_LANGUAGE,
ASSET_TYPE_SCRIPT,
ASSET_TYPE_MAP,
ASSET_TYPE_MAP_CHUNK,
ASSET_TYPE_COUNT,
} assettype_t;
@@ -57,14 +60,21 @@ static const assettypedef_t ASSET_TYPE_DEFINITIONS[ASSET_TYPE_COUNT] = {
},
[ASSET_TYPE_TEXTURE] = {
.extension = "DTX",
.extension = "dpt",
.loadStrategy = ASSET_LOAD_STRAT_ENTIRE,
.dataSize = sizeof(assettexture_t),
.entire = assetTextureLoad
},
// [ASSET_TYPE_PALETTE] = {
// .extension = "dpf",
// .loadStrategy = ASSET_LOAD_STRAT_ENTIRE,
// .dataSize = sizeof(palette_t),
// .entire = assetPaletteLoad
// },
[ASSET_TYPE_TILESET] = {
.extension = "DTF",
.extension = "dtf",
.loadStrategy = ASSET_LOAD_STRAT_ENTIRE,
.dataSize = sizeof(assettileset_t),
.entire = assetTilesetLoad
@@ -81,4 +91,16 @@ static const assettypedef_t ASSET_TYPE_DEFINITIONS[ASSET_TYPE_COUNT] = {
.loadStrategy = ASSET_LOAD_STRAT_CUSTOM,
.custom = assetScriptHandler
},
// [ASSET_TYPE_MAP] = {
// .extension = "DMF",
// .loadStrategy = ASSET_LOAD_STRAT_CUSTOM,
// .custom = assetMapHandler
// },
// [ASSET_TYPE_MAP_CHUNK] = {
// .extension = "DMC",
// .loadStrategy = ASSET_LOAD_STRAT_CUSTOM,
// .custom = assetMapChunkHandler
// },
};

View File

@@ -12,67 +12,50 @@
#include "util/endian.h"
errorret_t assetTextureLoad(assetentire_t entire) {
assertNotNull(entire.data, "Data pointer cannot be NULL.");
assertNotNull(entire.output, "Output pointer cannot be NULL.");
// assertNotNull(entire.data, "Data pointer cannot be NULL.");
// assertNotNull(entire.output, "Output pointer cannot be NULL.");
assettexture_t *assetData = (assettexture_t *)entire.data;
texture_t *texture = (texture_t *)entire.output;
// assettexture_t *assetData = (assettexture_t *)entire.data;
// texture_t *texture = (texture_t *)entire.output;
// Read header and version (first 4 bytes)
if(
assetData->header[0] != 'D' ||
assetData->header[1] != 'T' ||
assetData->header[2] != 'X'
) {
errorThrow("Invalid texture header");
}
// // Read header and version (first 4 bytes)
// if(
// assetData->header[0] != 'D' ||
// assetData->header[1] != 'P' ||
// assetData->header[2] != 'T'
// ) {
// errorThrow("Invalid texture header");
// }
// Version (can only be 1 atm)
if(assetData->version != 0x01) {
errorThrow("Unsupported texture version");
}
// // Version (can only be 1 atm)
// if(assetData->version != 0x01) {
// errorThrow("Unsupported texture version");
// }
// Fix endian
assetData->width = endianLittleToHost32(assetData->width);
assetData->height = endianLittleToHost32(assetData->height);
// // Fix endian
// assetData->width = endianLittleToHost32(assetData->width);
// assetData->height = endianLittleToHost32(assetData->height);
// Check dimensions.
if(
assetData->width == 0 || assetData->width > ASSET_TEXTURE_WIDTH_MAX ||
assetData->height == 0 || assetData->height > ASSET_TEXTURE_HEIGHT_MAX
) {
errorThrow("Invalid texture dimensions");
}
// // Check dimensions.
// if(
// assetData->width == 0 || assetData->width > ASSET_TEXTURE_WIDTH_MAX ||
// assetData->height == 0 || assetData->height > ASSET_TEXTURE_HEIGHT_MAX
// ) {
// errorThrow("Invalid texture dimensions");
// }
// textureInit(
// texture,
// assetData->width,
// assetData->height,
// TEXTURE_FORMAT_PALETTE,
// (texturedata_t){
// .paletted = {
// .indices = NULL,
// .palette = NULL
// }
// }
// );
// Validate format
textureformat_t format;
texturedata_t data;
switch(assetData->type) {
case 0x00: // RGBA8888
format = TEXTURE_FORMAT_RGBA;
data.rgbaColors = (color_t *)assetData->data;
break;
// case 0x01:
// format = TEXTURE_FORMAT_RGB;
// break;
// case 0x02:
// format = TEXTURE_FORMAT_RGB565;
// break;
// case 0x03:
// format = TEXTURE_FORMAT_RGB5A3;
// break;
default:
errorThrow("Unsupported texture format");
}
errorChain(textureInit(
texture, assetData->width, assetData->height, format, data
));
errorOk();
// errorOk();
}

View File

@@ -7,7 +7,6 @@
#pragma once
#include "error/error.h"
#include "display/color.h"
#define ASSET_TEXTURE_WIDTH_MAX 2048
#define ASSET_TEXTURE_HEIGHT_MAX 2048
@@ -21,10 +20,9 @@ typedef struct assetentire_s assetentire_t;
typedef struct {
char_t header[3];
uint8_t version;
uint8_t type;
uint32_t width;
uint32_t height;
uint8_t data[ASSET_TEXTURE_SIZE_MAX * sizeof(color4b_t)];
uint8_t palette[ASSET_TEXTURE_SIZE_MAX];
} assettexture_t;
#pragma pack(pop)

View File

@@ -20,4 +20,5 @@ pink,1,0.75,0.8,1
lime,0.75,1,0,1
navy,0,0,0.5,1
teal,0,0.5,0.5,1
cornflower_blue,0.39,0.58,0.93,1
cornflower_blue,0.39,0.58,0.93,1
salmon,1,0.5,0.5,1
1 name r g b a
20 lime 0.75 1 0 1
21 navy 0 0 0.5 1
22 teal 0 0.5 0.5 1
23 cornflower_blue 0.39 0.58 0.93 1
24 salmon 1 0.5 0.5 1

View File

@@ -20,36 +20,76 @@
#include "display/shader/shaderunlit.h"
#include "time/time.h"
#include "script/module/display/moduleshader.h"
display_t DISPLAY = { 0 };
texture_t PALETTE_TEXTURE;
texture_t UNCOMPRESSED_TEXTURE;
texture_t COMPRESSED_TEXTURE;
errorret_t displayInit(void) {
memoryZero(&DISPLAY, sizeof(DISPLAY));
#ifdef displayPlatformInit
errorChain(displayPlatformInit());
#endif
errorChain(shaderInit(&SHADER_UNLIT, &SHADER_UNLIT_DEFINITION));
errorChain(quadInit());
errorChain(frameBufferInitBackBuffer());
errorChain(spriteBatchInit());
errorChain(textInit());
errorChain(screenInit());
// Setup initial shader with default values
errorChain(shaderInit(&SHADER_UNLIT, &SHADER_UNLIT_DEFINITION));
camera_t cam;
cameraInit(&cam);
mat4 mat;
cameraGetProjectionMatrix(&cam, mat);
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, mat));
cameraGetViewMatrix(&cam, mat);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, mat));
glm_mat4_identity(mat);
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, mat));
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, NULL));
// PALETTES[0].colors[0] = COLOR_RED;
// PALETTES[0].colors[1] = COLOR_GREEN;
// PALETTES[0].colors[2] = COLOR_BLUE;
// PALETTES[0].colors[3] = COLOR_WHITE;
// PALETTES[0].colors[4] = COLOR_MAGENTA;
// PALETTES[0].colors[5] = COLOR_CYAN;
// PALETTES[0].colors[6] = COLOR_YELLOW;
// PALETTES[0].colors[7] = COLOR_BLACK;
// PALETTES[0].count = 8;
// uint8_t indices[64] = {
// 0,0,0,0,0,0,0,0,
// 1,1,1,1,1,1,1,1,
// 2,2,2,2,2,2,2,2,
// 3,3,3,3,3,3,3,3,
// 4,4,4,4,4,4,4,4,
// 5,5,5,5,5,5,5,5,
// 6,6,6,6,6,6,6,6,
// 7,7,7,7,7,7,7,7
// };
// errorChain(textureInit(
// &PALETTE_TEXTURE,
// 8, 8,
// TEXTURE_FORMAT_PALETTE,
// (texturedata_t){
// .paletted = {
// .indices = indices,
// .palette = &PALETTES[0]
// }
// }
// ));
errorChain(textureInit(
&UNCOMPRESSED_TEXTURE,
8, 8,
TEXTURE_FORMAT_RGBA,
(texturedata_t){
.rgbaColors = (color_t[]){
COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_WHITE, COLOR_MAGENTA, COLOR_CYAN, COLOR_YELLOW, COLOR_BLACK,
COLOR_GREEN, COLOR_BLUE, COLOR_WHITE, COLOR_MAGENTA, COLOR_CYAN, COLOR_YELLOW, COLOR_BLACK, COLOR_RED,
COLOR_BLUE, COLOR_WHITE, COLOR_MAGENTA, COLOR_CYAN, COLOR_YELLOW, COLOR_BLACK, COLOR_RED, COLOR_GREEN,
COLOR_WHITE, COLOR_MAGENTA, COLOR_CYAN, COLOR_YELLOW, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE,
COLOR_MAGENTA, COLOR_CYAN, COLOR_YELLOW, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_WHITE,
COLOR_CYAN, COLOR_YELLOW, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_WHITE, COLOR_MAGENTA,
COLOR_YELLOW, COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_WHITE, COLOR_MAGENTA, COLOR_CYAN,
COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_BLUE, COLOR_WHITE, COLOR_MAGENTA, COLOR_CYAN, COLOR_YELLOW
}
}
));
errorOk();
}
@@ -70,7 +110,38 @@ errorret_t displayUpdate(void) {
SCREEN.background
);
errorChain(sceneRender());
camera_t camera;
// cameraInitOrthographic(&camera);
// camera.orthographic.left = 0.0f;
// camera.orthographic.right = SCREEN.width;
// camera.orthographic.top = SCREEN.height;
// camera.orthographic.bottom = 0.0f;
cameraInitPerspective(&camera);
camera.lookat.position[0] = 3.0f;
camera.lookat.position[1] = 3.0f;
camera.lookat.position[2] = 3.0f;
mat4 proj, view, model;
cameraGetProjectionMatrix(&camera, proj);
cameraGetViewMatrix(&camera, view);
glm_mat4_identity(model);
errorChain(shaderBind(&SHADER_UNLIT));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
// errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, &PALETTE_TEXTURE));
errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, &UNCOMPRESSED_TEXTURE));
errorChain(spriteBatchPush(
0.0f, 0.0f,
1.0f, 1.0f,
COLOR_WHITE,
0.0f, 0.0f,
1.0f, 1.0f
));
errorChain(spriteBatchFlush());
// errorCatch(errorPrint(sceneRender()));
// Render UI
// uiRender();

View File

@@ -7,5 +7,4 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
shader.c
shaderunlit.c
)

View File

@@ -8,19 +8,15 @@
#include "shader.h"
#include "assert/assert.h"
shader_t *bound = NULL;
errorret_t shaderInit(shader_t *shader, const shaderdefinition_t *def) {
assertNotNull(shader, "Shader cannot be null");
errorChain(shaderInitPlatform(shader, def));
bound = NULL;
errorOk();
}
errorret_t shaderBind(shader_t *shader) {
assertNotNull(shader, "Shader cannot be null");
errorChain(shaderBindPlatform(shader));
bound = shader;
errorOk();
}
@@ -32,7 +28,6 @@ errorret_t shaderSetMatrix(
assertNotNull(shader, "Shader cannot be null");
assertStrLenMin(name, 1, "Uniform name cannot be empty");
assertNotNull(matrix, "Matrix cannot be null");
assertTrue(bound == shader, "Shader must be bound.");
errorChain(shaderSetMatrixPlatform(shader, name, matrix));
errorOk();
}
@@ -44,7 +39,6 @@ errorret_t shaderSetTexture(
) {
assertNotNull(shader, "Shader cannot be null");
assertStrLenMin(name, 1, "Uniform name cannot be empty");
assertTrue(bound == shader, "Shader must be bound.");
errorChain(shaderSetTexturePlatform(shader, name, texture));
errorOk();
}
@@ -62,7 +56,6 @@ errorret_t shaderSetTexture(
errorret_t shaderDispose(shader_t *shader) {
assertNotNull(shader, "Shader cannot be null");
bound = NULL;
errorChain(shaderDisposePlatform(shader));
errorOk();
}

View File

@@ -1,10 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "shaderunlit.h"
shader_t SHADER_UNLIT = { 0 };

View File

@@ -15,4 +15,4 @@
// #define SHADER_UNLIT_COLOR "u_Color"
extern shaderdefinition_t SHADER_UNLIT_DEFINITION;
extern shader_t SHADER_UNLIT;
static shader_t SHADER_UNLIT;

View File

@@ -11,19 +11,17 @@
#include "display/spritebatch/spritebatch.h"
#include "asset/asset.h"
#include "display/shader/shaderunlit.h"
texture_t DEFAULT_FONT_TEXTURE;
tileset_t DEFAULT_FONT_TILESET;
errorret_t textInit(void) {
errorChain(assetLoad("ui/minogram.dtx", &DEFAULT_FONT_TEXTURE));
errorChain(assetLoad("ui/minogram.dtf", &DEFAULT_FONT_TILESET));
// errorChain(assetLoad("ui/minogram.dpt", &DEFAULT_FONT_TEXTURE));
// errorChain(assetLoad("ui/minogram.dtf", &DEFAULT_FONT_TILESET));
errorOk();
}
errorret_t textDispose(void) {
errorChain(textureDispose(&DEFAULT_FONT_TEXTURE));
// errorChain(textureDispose(&DEFAULT_FONT_TEXTURE));
errorOk();
}
@@ -47,7 +45,6 @@ errorret_t textDrawChar(
vec4 uv;
tilesetTileGetUV(tileset, tileIndex, uv);
errorChain(spriteBatchPush(
// texture,
x, y,
@@ -73,8 +70,6 @@ errorret_t textDraw(
float_t posX = x;
float_t posY = y;
// errorChain(shaderSetTexture(&SHADER_UNLIT, SHADER_UNLIT_TEXTURE, texture));
char_t c;
int32_t i = 0;
while((c = text[i++]) != '\0') {

View File

@@ -7,8 +7,11 @@
add_subdirectory(display)
add_subdirectory(event)
add_subdirectory(input)
add_subdirectory(item)
add_subdirectory(locale)
add_subdirectory(map)
add_subdirectory(system)
add_subdirectory(scene)
add_subdirectory(story)
add_subdirectory(time)
add_subdirectory(ui)

View File

@@ -15,5 +15,4 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
modulescreen.c
moduletileset.c
moduletexture.c
moduleshader.c
)

View File

@@ -69,16 +69,6 @@ void moduleCamera(scriptcontext_t *context) {
// Methods
lua_register(context->luaState, "cameraCreate", moduleCameraCreate);
lua_register(
context->luaState,
"cameraGetProjectionMatrix",
moduleCameraGetProjectionMatrix
);
lua_register(
context->luaState,
"cameraGetViewMatrix",
moduleCameraGetViewMatrix
);
}
int moduleCameraCreate(lua_State *L) {
@@ -126,7 +116,7 @@ int moduleCameraCreate(lua_State *L) {
return 1;
}
int moduleCameraIndex(lua_State *l) {
int moduleCameraIndex(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
const char_t *key = luaL_checkstring(l, 2);
@@ -298,36 +288,4 @@ int moduleCameraNewIndex(lua_State *l) {
}
return 0;
}
int moduleCameraGetProjectionMatrix(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
camera_t *cam = (camera_t *)luaL_checkudata(L, 1, "camera_mt");
assertNotNull(cam, "Camera pointer cannot be NULL.");
// Create mat4
mat4 test;
cameraGetProjectionMatrix(cam, test);
// Lua needs to own this matrix now
mat4 *m = (mat4 *)lua_newuserdata(L, sizeof(mat4));
memoryCopy(m, test, sizeof(mat4));
return 1;
}
int moduleCameraGetViewMatrix(lua_State *L) {
assertNotNull(L, "Lua state cannot be NULL.");
camera_t *cam = (camera_t *)luaL_checkudata(L, 1, "camera_mt");
assertNotNull(cam, "Camera pointer cannot be NULL.");
// Create mat4
mat4 test;
cameraGetViewMatrix(cam, test);
// Lua needs to own this matrix now
mat4 *m = (mat4 *)lua_newuserdata(L, sizeof(mat4));
memoryCopy(m, test, sizeof(mat4));
return 1;
}

View File

@@ -35,20 +35,4 @@ int moduleCameraIndex(lua_State *l);
*
* @param l The Lua state.
*/
int moduleCameraNewIndex(lua_State *l);
/**
* Script binding for getting a camera's projection matrix.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleCameraGetProjectionMatrix(lua_State *L);
/**
* Script binding for getting a camera's view matrix.
*
* @param L The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleCameraGetViewMatrix(lua_State *L);
int moduleCameraNewIndex(lua_State *l);

View File

@@ -1,94 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "moduleshader.h"
#include "assert/assert.h"
#include "display/shader/shader.h"
#include "display/shader/shaderunlit.h"
#include "display/camera/camera.h"
void moduleShader(scriptcontext_t *context) {
assertNotNull(context, "Context cannot be NULL.");
// Shader unlit defs
lua_pushlightuserdata(context->luaState, &SHADER_UNLIT);
lua_setglobal(context->luaState, "SHADER_UNLIT");
lua_pushstring(context->luaState, SHADER_UNLIT_PROJECTION);
lua_setglobal(context->luaState, "SHADER_UNLIT_PROJECTION");
lua_pushstring(context->luaState, SHADER_UNLIT_VIEW);
lua_setglobal(context->luaState, "SHADER_UNLIT_VIEW");
lua_pushstring(context->luaState, SHADER_UNLIT_MODEL);
lua_setglobal(context->luaState, "SHADER_UNLIT_MODEL");
lua_pushstring(context->luaState, SHADER_UNLIT_TEXTURE);
lua_setglobal(context->luaState, "SHADER_UNLIT_TEXTURE");
// Shader methods
lua_register(context->luaState, "shaderBind", moduleShaderBind);
lua_register(context->luaState, "shaderSetMatrix", moduleShaderSetMatrix);
lua_register(context->luaState, "shaderSetTexture", moduleShaderSetTexture);
}
int moduleShaderBind(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
// Should be passed a shader userdata pointer only.
shader_t *shader = (shader_t *)lua_touserdata(l, 1);
assertNotNull(shader, "Shader pointer cannot be NULL.");
errorret_t ret = shaderBind(shader);
if(ret.code != ERROR_OK) {
luaL_error(l, "Failed to bind shader: %s", ret.state->message);
errorCatch(errorPrint(ret));
return 0;
}
return 0;
}
int moduleShaderSetMatrix(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
// Expect shader, string and matrix.
if(!lua_isuserdata(l, 1)) {
luaL_error(l, "First argument must be a shader_mt userdata.");
return 0;
}
if(!lua_isstring(l, 2)) {
luaL_error(l, "Second argument must be a string.");
return 0;
}
if(!lua_isuserdata(l, 3)) {
luaL_error(l, "Third argument must be a mat4_mt userdata.");
return 0;
}
shader_t *shader = (shader_t *)lua_touserdata(l, 1);
assertNotNull(shader, "Shader pointer cannot be NULL.");
const char_t *uniformName = luaL_checkstring(l, 2);
assertStrLenMin(uniformName, 1, "Uniform name cannot be empty.");
mat4 *mat = (mat4 *)lua_touserdata(l, 3);
assertNotNull(mat, "Matrix pointer cannot be NULL.");
errorret_t ret = shaderSetMatrix(shader, uniformName, *mat);
if(ret.code != ERROR_OK) {
luaL_error(l, "Failed to set shader matrix: %s", ret.state->message);
errorCatch(errorPrint(ret));
return 0;
}
return 0;
}
int moduleShaderSetTexture(lua_State *l) {
assertNotNull(l, "Lua state cannot be NULL.");
return 0;
}

View File

@@ -1,42 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/scriptcontext.h"
/**
* Register shader functions to the given script context.
*
* @param context The script context to register shader functions to.
*/
void moduleShader(scriptcontext_t *context);
/**
* Script binding for binding a shader.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleShaderBind(lua_State *l);
/**
* Script binding for setting a matrix uniform in a shader.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleShaderSetMatrix(lua_State *l);
/**
* Script binding for setting a texture uniform in a shader.
*
* @param l The Lua state.
* @return Number of return values on the Lua stack.
*/
int moduleShaderSetTexture(lua_State *l);
errorret_t doThing();

View File

@@ -10,6 +10,7 @@
#include "script/module/input/moduleinput.h"
#include "script/module/moduleplatform.h"
#include "script/module/scene/modulescene.h"
#include "script/module/item/moduleitem.h"
#include "script/module/locale/modulelocale.h"
#include "script/module/time/moduletime.h"
#include "script/module/event/moduleevent.h"
@@ -17,13 +18,13 @@
#include "script/module/display/modulespritebatch.h"
#include "script/module/display/modulecamera.h"
#include "script/module/display/moduleglm.h"
#include "script/module/display/moduleshader.h"
#include "script/module/ui/moduleui.h"
#include "script/module/display/moduletext.h"
#include "script/module/display/modulescreen.h"
#include "script/module/story/modulestoryflag.h"
#include "script/module/map/modulemap.h"
#include "script/module/display/moduletexture.h"
#include "script/module/display/moduletileset.h"
#include "script/scriptgame.h"
#include "util/string.h"
const scriptmodule_t SCRIPT_MODULE_LIST[] = {
@@ -32,6 +33,7 @@ const scriptmodule_t SCRIPT_MODULE_LIST[] = {
{ .name = "platform", .callback = modulePlatform },
{ .name = "color", .callback = moduleColor },
{ .name = "scene", .callback = moduleScene },
{ .name = "item", .callback = moduleItem },
{ .name = "locale", .callback = moduleLocale },
{ .name = "time", .callback = moduleTime },
{ .name = "event", .callback = moduleEvent },
@@ -41,13 +43,10 @@ const scriptmodule_t SCRIPT_MODULE_LIST[] = {
{ .name = "ui", .callback = moduleUi },
{ .name = "text", .callback = moduleText },
{ .name = "screen", .callback = moduleScreen },
{ .name = "storyflag", .callback = moduleStoryFlag },
{ .name = "map", .callback = moduleMap },
{ .name = "texture", .callback = moduleTexture },
{ .name = "tileset", .callback = moduleTileset },
{ .name = "shader", .callback = moduleShader },
#ifdef SCRIPT_GAME_LIST
SCRIPT_GAME_LIST
#endif
};
#define SCRIPT_MODULE_COUNT ( \

View File

@@ -157,10 +157,11 @@ errorret_t shaderParamGetLocationGL(
#ifdef DUSK_OPENGL_LEGACY
assertUnreachable("Cannot get uniform locations on legacy opengl.");
#else
*location = glGetUniformLocation(shader->shaderProgramId, name);
errorChain(errorGLCheck());
if(*location == -1) {
errorThrow("Uniform '%s' not found in shader.", name);
shadergl_t *shaderGL = (shadergl_t *)shader;
*location = glGetUniformLocation(shaderGL->shaderProgramId, name);
errorret_t err = errorGLCheck();
if(err.code != ERROR_OK) {
errorChain(err);
}
#endif
@@ -173,8 +174,8 @@ errorret_t shaderSetMatrixGL(
mat4 mat
) {
assertNotNull(shader, "Shader cannot be null");
assertStrLenMin(name, 1, "Uniform name cannot be empty");
assertNotNull(mat, "Matrix data cannot be null");
assertStrLenMin(name, 1, "Uniform name cannot be empty");
#ifdef DUSK_OPENGL_LEGACY
assertTrue(
@@ -232,8 +233,6 @@ errorret_t shaderSetTextureGL(
errorChain(errorGLCheck());
glBindTexture(GL_TEXTURE_2D, texture->id);
errorChain(errorGLCheck());
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
errorChain(errorGLCheck());
#else
if(shader->setTexture == NULL) {

View File

@@ -25,8 +25,8 @@ errorret_t textureInitGL(
switch(format) {
case TEXTURE_FORMAT_RGBA:
glTexImage2D(
GL_TEXTURE_2D, 0, format, width, height, 0,
format, GL_UNSIGNED_BYTE, (void*)data.rgbaColors
GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, (void*)data.rgbaColors
);
errorChain(errorGLCheck());
break;
@@ -77,6 +77,10 @@ errorret_t textureInitGL(
errorChain(errorGLCheck());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
errorChain(errorGLCheck());
#ifdef DUSK_OPENGL_LEGACY
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
#endif
glBindTexture(GL_TEXTURE_2D, 0);
errorChain(errorGLCheck());

View File

@@ -20,6 +20,9 @@
#define glClearDepth(depth) glClearDepthf(depth)
#else
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#include <vitaGL.h>
#define GL_COLOR_INDEX8_EXT 0x80E5
// #include <GL/glext.h>
#endif

View File

@@ -1,16 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Includes
target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
${CMAKE_CURRENT_LIST_DIR}
)
# Subdirs
add_subdirectory(item)
add_subdirectory(map)
add_subdirectory(story)
add_subdirectory(script)

View File

@@ -1,7 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirs
add_subdirectory(module)

View File

@@ -1,9 +0,0 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Subdirectories
add_subdirectory(item)
add_subdirectory(map)
add_subdirectory(story)

View File

@@ -1,16 +0,0 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "script/module/item/moduleitem.h"
#include "script/module/story/modulestoryflag.h"
#include "script/module/map/modulemap.h"
#define SCRIPT_GAME_LIST \
{ .name = "item", .callback = moduleItem }, \
{ .name = "storyflag", .callback = moduleStoryFlag }, \
{ .name = "map", .callback = moduleMap },

View File

@@ -1,417 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Dusk Tools / Texture Creator</title>
<style type="text/css">
* {
box-sizing: border-box;
}
body {
font-size: 16px;
}
canvas {
image-rendering: pixelated;
}
</style>
</head>
<body>
<h1>Dusk Texture Creator</h1>
<p>
Creates texture files. This will not create palletized textures, use the
palette-indexer.html tool for that. This will instead work for all other
kinds of textures.
</p>
<div>
<div>
<input type="file" data-file-input />
</div>
</div>
<div>
<h2>Settings</h2>
<div>
Texture Format:
<div>
<label>
RGBA
<input type="radio" name="texture-type" value="rgba" />
</label><br />
<label>
RGB
<input type="radio" name="texture-type" value="rgb" />
</label><br />
<label>
RGB565
<input type="radio" name="texture-type" value="rgb565" checked />
</label><br />
<label>
RGB5A3 (Dolphin Only)
<input type="radio" name="texture-type" value="rgb5a3" />
</label><br />
</div>
</div>
<div>
<label for="pad-power-of-two">
Pad to power of two
<input type="checkbox" id="pad-power-of-two" name="pad-power-of-two" checked />
</label>
</div>
</div>
<div>
<h2>Preview</h2>
<div>
<label>
Preview Scale:
<input type="number" value="2" data-preview-scale min="1" step="1" />
</label>
</div>
<div>
<label>
Preview Background:
<button data-page-bg-white>White</button>
<button data-page-bg-transparent>Black</button>
<button data-page-bg-checkerboard>Checkerboard</button>
<button data-page-bg-magenta>Magenta</button>
<button data-page-bg-blue>Blue</button>
<button data-page-bg-green>Green</button>
</label>
</div>
<div>
<canvas data-output-preview style="border:1px solid black;"></canvas>
</div>
<div>
<textarea data-output-information readonly rows="15" style="width: 500px;"></textarea>
</div>
<div>
<button data-download>Download Texture</button>
</div>
</div>
</body>
<script type="text/javascript">
const elFileInput = document.querySelector('[data-file-input]');
const elFileError = document.querySelector('[data-file-error]');
const elPreviewScale = document.querySelector('[data-preview-scale]');
const elOutputPreview = document.querySelector('[data-output-preview]');
const elOutputInformation = document.querySelector('[data-output-information]');
const elTextureType = document.querySelectorAll('input[name="texture-type"]');
const elPadPowerOfTwo = document.querySelector('#pad-power-of-two');
const btnDownload = document.querySelector('[data-download]');
const btnBackgroundWhite = document.querySelector('[data-page-bg-white]');
const btnBackgroundTransparent = document.querySelector('[data-page-bg-transparent]');
const btnBackgroundCheckerboard = document.querySelector('[data-page-bg-checkerboard]');
const btnBackgroundMagenta = document.querySelector('[data-page-bg-magenta]');
const btnBackgroundBlue = document.querySelector('[data-page-bg-blue]');
const btnBackgroundGreen = document.querySelector('[data-page-bg-green]');
let image;
let imageName;
let textureData;
let rawData;
let width, height, paddedWidth, paddedHeight;
// Methods
const nextPowerOfTwo = n => {
if(n <= 0) return 1;
return 2 ** Math.ceil(Math.log2(n));
}
const updatePreview = () => {
if(!image) return;
console.log('Updating preview with image:', image);
const scale = parseInt(elPreviewScale.value) || 1;
const padToPowerOfTwo = elPadPowerOfTwo.checked;
const textureType = Array.from(elTextureType).find(r => r.checked)?.value || 'rgba';
width = image.width;
height = image.height;
let scaledWidth = width * scale;
let scaledHeight = height * scale;
paddedWidth = padToPowerOfTwo ? nextPowerOfTwo(width) : width;
paddedHeight = padToPowerOfTwo ? nextPowerOfTwo(height) : height;
let paddedScaledWidth = paddedWidth * scale;
let paddedScaledHeight = paddedHeight * scale;
const tempCanvas = document.createElement('canvas');
tempCanvas.width = paddedWidth;
tempCanvas.height = paddedHeight;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.imageSmoothingEnabled = false;
tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
tempCtx.drawImage(image, 0, 0, width, height);
const pixels = tempCtx.getImageData(0, 0, paddedWidth, paddedHeight);
let sizeBytes;
let strTextureType = Array.from(elTextureType).find(r => r.checked)?.value || 'rgba';
strTextureType = strTextureType.toLowerCase();
rawData = new Uint8Array(paddedWidth * paddedHeight * 4);
if(strTextureType === 'rgba') {
textureData = new Uint8Array(pixels.data.buffer);
rawData = new Uint8Array(pixels.data.buffer);
sizeBytes = paddedWidth * paddedHeight * 4;
} else if(strTextureType === 'rgb') {
sizeBytes = paddedWidth * paddedHeight * 3;
textureData = new Uint8Array(sizeBytes);
for(let i = 0, j = 0; i < textureData.length; i += 4, j += 3) {
textureData[j] = pixels.data[i]; // R
textureData[j + 1] = pixels.data[i + 1]; // G
textureData[j + 2] = pixels.data[i + 2]; // B
rawData[i] = pixels.data[i]; // R
rawData[i + 1] = pixels.data[i + 1]; // G
rawData[i + 2] = pixels.data[i + 2]; // B
rawData[i + 3] = 255;
}
} else if(strTextureType === 'rgb565') {
sizeBytes = paddedWidth * paddedHeight * 2;
textureData = new Uint8Array(sizeBytes);
let j = 0;
for (let i = 0; i < pixels.data.length; i += 4) {
const r = pixels.data[i];
const g = pixels.data[i + 1];
const b = pixels.data[i + 2];
const value =
((r & 0xf8) << 8) |
((g & 0xfc) << 3) |
(b >> 3);
textureData[j++] = (value >> 8) & 0xff; // high byte
textureData[j++] = value & 0xff; // low byte
}
// Now convert back to RGBA for preview
for(let i = 0, j = 0; i < textureData.length; i += 2, j += 4) {
const value = (textureData[i] << 8) | textureData[i + 1];
const r = (value >> 11) & 0x1f;
const g = (value >> 5) & 0x3f;
const b = value & 0x1f;
rawData[j] = (r << 3) | (r >> 2); // R
rawData[j + 1] = (g << 2) | (g >> 4); // G
rawData[j + 2] = (b << 3) | (b >> 2); // B
rawData[j + 3] = 255; // A
}
} else if(strTextureType === 'rgb5a3') {
sizeBytes = paddedWidth * paddedHeight * 2;
textureData = new Uint8Array(sizeBytes);
let j = 0;
for (let i = 0; i < pixels.data.length; i += 4) {
const r8 = pixels.data[i];
const g8 = pixels.data[i + 1];
const b8 = pixels.data[i + 2];
const a8 = pixels.data[i + 3];
let value;
// Opaque: 1RRRRRGGGGGBBBBB
if (a8 >= 224) {
const r5 = r8 >> 3;
const g5 = g8 >> 3;
const b5 = b8 >> 3;
value =
0x8000 |
(r5 << 10) |
(g5 << 5) |
b5;
} else {
// Transparent/translucent: 0AAARRRRGGGGBBBB
const a3 = a8 >> 5;
const r4 = r8 >> 4;
const g4 = g8 >> 4;
const b4 = b8 >> 4;
value =
(a3 << 12) |
(r4 << 8) |
(g4 << 4) |
b4;
}
textureData[j++] = (value >> 8) & 0xff; // high byte
textureData[j++] = value & 0xff; // low byte
}
// Convert back to RGBA for preview
for (let i = 0, j = 0; i < textureData.length; i += 2, j += 4) {
const value = (textureData[i] << 8) | textureData[i + 1];
if (value & 0x8000) {
// 1RRRRRGGGGGBBBBB
const r5 = (value >> 10) & 0x1f;
const g5 = (value >> 5) & 0x1f;
const b5 = value & 0x1f;
rawData[j] = (r5 << 3) | (r5 >> 2);
rawData[j + 1] = (g5 << 3) | (g5 >> 2);
rawData[j + 2] = (b5 << 3) | (b5 >> 2);
rawData[j + 3] = 255;
} else {
// 0AAARRRRGGGGBBBB
const a3 = (value >> 12) & 0x7;
const r4 = (value >> 8) & 0xf;
const g4 = (value >> 4) & 0xf;
const b4 = value & 0xf;
rawData[j] = (r4 << 4) | r4;
rawData[j + 1] = (g4 << 4) | g4;
rawData[j + 2] = (b4 << 4) | b4;
rawData[j + 3] = (a3 << 5) | (a3 << 2) | (a3 >> 1);
}
}
} else {
return alert('Unsupported texture type selected.');
}
// Write out pixels.
const imageData = new ImageData(new Uint8ClampedArray(rawData.buffer), paddedWidth, paddedHeight);
tempCanvas.width = paddedWidth;
tempCanvas.height = paddedHeight;
const tempCtx2 = tempCanvas.getContext('2d');
tempCtx2.imageSmoothingEnabled = false;
tempCtx2.putImageData(imageData, 0, 0);
elOutputPreview.width = paddedScaledWidth;
elOutputPreview.height = paddedScaledHeight;
const ctx = elOutputPreview.getContext('2d');
ctx.imageSmoothingEnabled = false;
ctx.clearRect(0, 0, elOutputPreview.width, elOutputPreview.height);
ctx.drawImage(tempCanvas, 0, 0, paddedScaledWidth, paddedScaledHeight);
// Output information
elOutputInformation.value = `Original Size: ${width}x${height}\n`;
elOutputInformation.value += `New Size: ${paddedWidth}x${paddedHeight}\n`;
elOutputInformation.value += `Texture Format: ${textureType}\n`;
elOutputInformation.value += `Size: ${sizeBytes} bytes / (${(sizeBytes / 1024).toFixed(2)} KB)\n`;
}
const onFile = async file => {
elFileInput.disabled = true;
console.log('Selected file:', file);
imageName = file.name;
// Load as image
const img = new Image();
img.onload = () => {
image = img;
elFileInput.disabled = false;
updatePreview();
};
img.onerror = () => {
alert('Failed to load image. Please select a valid image file.');
elFileInput.disabled = false;
};
img.src = URL.createObjectURL(file);
}
// Listeners
elFileInput.addEventListener('change', event => {
if(!event.target.files || event.target.files.length <= 0) {
return alert('No file selected.');
}
const file = event.target.files[0];
if(!file.type.startsWith('image/')) {
return alert('Selected file is not an image.');
}
onFile(file);
});
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';
});
elPreviewScale.addEventListener('input', () => {
updatePreview();
});
elPadPowerOfTwo.addEventListener('change', () => {
updatePreview();
});
elTextureType.forEach(radio => {
radio.addEventListener('change', () => {
updatePreview();
});
})
btnDownload.addEventListener('click', () => {
if(!image) {
return alert('Please select an image file before downloading.');
}
// DTX, then texture type, then width, then height, then raw data
const dtfHeader = new TextEncoder().encode('DTX');
const versionHeader = new Uint8Array([1]); // Version 1
let strTextureType = Array.from(elTextureType).find(r => r.checked)?.value || 'rgba';
strTextureType = strTextureType.toLowerCase();
let typeHeader;
if(strTextureType === 'rgba') {
typeHeader = new Uint8Array([0]);
} else if(strTextureType === 'rgb') {
typeHeader = new Uint8Array([1]);
} else if(strTextureType === 'rgb565') {
typeHeader = new Uint8Array([2]);
} else if(strTextureType === 'rgb5a3') {
typeHeader = new Uint8Array([3]);
} else {
return alert('Unsupported texture type selected.');
}
const widthBytes = new Uint32Array([ paddedWidth ]);
const heightBytes = new Uint32Array([ paddedHeight ]);
const blob = new Blob([dtfHeader, versionHeader, typeHeader, widthBytes, heightBytes, textureData], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${imageName.replace(/\.[^/.]+$/, "")}.dtx`;
a.click();
URL.revokeObjectURL(url);
});
btnBackgroundCheckerboard.click();
</script>
</html>

View File

@@ -151,24 +151,23 @@
const getValues = () => {
if(!pixels) return null;
const right = parseInt(elRight.value) || 0;
const bottom = parseInt(elBottom.value) || 0;
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 - right) / tileWidth);
rowCount = Math.floor((imageHeight - bottom) / tileHeight);
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 - right) / columnCount);
tileHeight = Math.floor((imageHeight - bottom) / rowCount);
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 scaledWidth = imageWidth * scale;