Compare commits

...

34 commits

Author SHA1 Message Date
e2054a51a8 feat(editor): recent files 2025-08-19 22:43:52 +02:00
93984ce1c0 fix(editor): hidden internal services 2025-08-14 17:21:55 +02:00
6803a659cb fix(clangd): annoying error 2025-08-14 16:59:44 +02:00
a619fa3afc fix(physics): reduced iteration count 2025-08-13 23:46:08 +02:00
43e41caebf fix(editor): lock the cursor on wayland, too 2025-08-13 03:40:20 +02:00
1bd9b00c47 fix(cmake); find qscintilla on fedora 2025-08-13 03:40:20 +02:00
ae9a4adf67 fix(editor): miniaudio crash in wasapi due to loading order 2025-08-12 14:53:28 +02:00
7bd3e70c3a feat(editor): lock the cursor on platforms other than wayland 2025-08-12 03:38:42 +02:00
52cfa69a6e fix(editor): replaced QSoundEffect with Miniaudio 2025-08-12 01:05:33 +02:00
62743d8998 fix(editor): use OpenGL 3.3 2025-08-12 00:35:10 +02:00
2fec4cc7f2 fix(cmake): client no longer opens with console 2025-08-12 00:26:16 +02:00
56ffc3f88c fix(cmake): copy assets 2025-08-12 00:19:15 +02:00
384c249874 fix(cmake): copy the right dlls in debug mode 2025-08-12 00:06:00 +02:00
692bb17d44 feat(cmake): move to qt6 2025-08-11 22:01:28 +02:00
2ed8c83ec3 tempfix(editor): gl 3.3 and qsoundeffect not working on linux 2025-08-11 21:59:09 +02:00
801b00ad97 fix(cmake): cleaner output 2025-08-04 22:26:36 +02:00
4940b07403 fix(editor): handles crashing on hover 2025-08-01 00:59:08 +02:00
2f09c6eb9c fix(logger): use whole seconds rather than fractions for log filenames 2025-07-25 23:28:56 +02:00
3521f50d1b fix: glfw error not properly formatted, so format args were ignored 2025-07-25 19:14:25 +02:00
11df6595c0 feat(cmake): replaced glew with glad 2025-07-25 19:13:53 +02:00
44c28b6825 feat(test): lua signal test 2025-07-25 18:15:18 +02:00
92ab9f6fb9 feat(test): lua timing checks 2025-07-24 23:36:28 +02:00
b117f3cd4d feat(test): some few lua testing 2025-07-24 22:57:00 +02:00
d086cf629b feat(test): added testing setup 2025-07-24 21:42:49 +02:00
143d3769c7 fix(autogen): opening log file before created generated directory 2025-07-24 19:59:36 +02:00
243af95a3b chore: note for llvm 2025-07-24 02:46:57 +02:00
3df575314e misc(cmake): use ccache 2025-07-24 02:00:08 +02:00
8d5fb3b2c6 chore: updated build instructions 2025-07-24 01:52:40 +02:00
471b2472e3 fix(autogen): autogen failing in msbuild because of "error" in stdout 2025-07-24 01:45:55 +02:00
be324e0aa8 refactor(cmake): made sources explicit rather than globbed 2025-07-24 01:25:39 +02:00
330f128dd3 refactor(cmake): (Thanks @FloofyPlasma!) replaced vcpkg with CPM 2025-07-23 03:24:34 +02:00
8b8776cbd7 fix(part): temporarily soft-disabled wedges as they crash the engine currently 2025-07-23 03:24:34 +02:00
74a4a01ebf refactor(cmake): windeployqt deploys debug dlls which the binary doesn't link against 2025-07-23 03:24:34 +02:00
12fc8906bc refactor(cmake): removed vcpkg 2025-07-23 03:24:34 +02:00
61 changed files with 6864 additions and 332 deletions

View file

@ -1,2 +1,3 @@
CompileFlags:
Add: [-std=c++20]
Remove: [-mno-direct-extern-access]

View file

@ -1,20 +1,27 @@
# Building on Linux
For pre-requisite packages, check [deps.txt](./deps.txt)
You will need to install Qt and LLVM/libclang beforehand. All other packages will automatically be obtained through CPM.
If your distribution does not provide ReactPhysics3D, you will have to build (and install) it yourself prior to this step
ccache is highly recommended.
Use the following to generate the build files:
cmake -Bbuild .
> [!NOTE]
> Add -DCMAKE_BUILD_TYPE=Release to produce a release build
Then build the project using:
cmake --build build
The compiled binaries should then be located in `./build/bin/` and should be ready for redistribution without any further work.
If any of the compilation steps fail, or the binaries fail to execute, please create an issue so that this can be corrected.
# Building on Windows
The project will be built using VCPKG and MSVC
The process is very similar on Windows
## Pre-requisites
@ -22,6 +29,7 @@ The project will be built using VCPKG and MSVC
* Qt 6.8.3 or higher, with MSVC toolchain
* CMake
* Git (for cloning the repo, optional)
* LLVM/libclang 18.1.8+ (from https://github.com/llvm/llvm-project/releases/download/llvmorg-18.1.8/LLVM-18.1.8-win64.exe)
* QScintilla already built (see [docs/qscintilla.md](./docs/qscintilla.md)) *\*likely temporary\**
To start, clone the repository:
@ -34,18 +42,18 @@ Once in the directory, add Qt to the `CMAKE_PREFIX_PATH` variable (for instance,
set CMAKE_PREFIX_PATH=C:\Qt\6.8.3\msvc2022_64
Now, generate the build files with cmake via the vcpkg preset:
Now, generate the build files:
cmake -Bbuild . --preset vcpkg
cmake -Bbuild .
Then, finally, build in release mode\*:
Then, finally build:
cmake --build build --config Release
cmake --build build
The compiled binaries should then be placed in `./build/bin/` and should be ready for redistribution without any further work.
> [!NOTE]
> To build in release mode, add -DCMAKE_BUILD_TYPE=Release to the configure (first) command,
> and add --config Release to the build (second) command
If any of the compilation steps fail, or the binaries fail to execute, please create an issue so that this can be corrected.
The compiled binaries should then be located in `./build/bin/[Debug|Release]` and should be ready for redistribution without any further work.
\* Release mode is necessary as debug mode copies DLLs that are not linked to the output binary
DEVELOPER NOTE: AKA Not for you. If you get CUSTOM COMMAND BUILD errors just keep rerunning build
If any of the compilation steps fail, or the binaries fail to execute, please create an issue so that this can be corrected.

View file

@ -10,6 +10,7 @@ else()
endif()
set( CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" )
set(USE_CCACHE ON)
add_subdirectory(autogen)
@ -24,4 +25,6 @@ add_subdirectory(client)
add_subdirectory(editor)
install(FILES $<TARGET_RUNTIME_DLLS:editor> TYPE BIN)
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests )
enable_testing()
add_subdirectory(tests)

View file

@ -1,21 +0,0 @@
{
"version": 2,
"configurePresets": [
{
"name": "vcpkg",
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
},
{
"name": "vcpkg-linux",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake"
}
}
]
}

View file

@ -1,13 +0,0 @@
{
"version": 2,
"configurePresets": [
{
"name": "default",
"inherits": "vcpkg",
"environment": {
"VCPKG_ROOT": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\vcpkg\\vcpkg.exe"
}
}
]
}

View file

@ -26,6 +26,7 @@ int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
std::string srcRootStr = string_of(srcRoot);
std::string srcPathStr = string_of(srcPath);
std::string outPathStr = string_of(outPath);
std::string logFileStr = outPathStr + ".log";
const char* cargs[] = { "-xc++", "-std=c++17", "-I", srcRootStr.c_str(), "-D__AUTOGEN__", 0 };
// THANK YOU SO MUCH THIS STACKOVERFLOW ANSWER IS SO HELPFUL
@ -42,17 +43,26 @@ int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
return 1;
}
fs::create_directories(outPath.parent_path()); // Make sure generated dir exists before we try writing to it
// We write to a special log file instead of stdout/stderr to
// 1. avoid confusion
// 2. prevent MSBuild from reading the word "error" and detecting there's a problem with the program (there isn't)
FILE* logout = fopen(logFileStr.c_str(), "w");
// Print errors
int ndiags = clang_getNumDiagnostics(unit);
for (int i = 0; i < ndiags; i++) {
CXDiagnostic diag = clang_getDiagnostic(unit, i);
CXString str = clang_formatDiagnostic(diag, 0);
fprintf(stderr, "diag: %s\n", clang_getCString(str));
fprintf(logout, "diag: %s\n", clang_getCString(str));
clang_disposeString(str);
clang_disposeDiagnostic(diag);
}
fclose(logout);
CXCursor cursor = clang_getTranslationUnitCursor(unit);
object::AnalysisState objectAnlyState;
@ -66,8 +76,6 @@ int processHeader(fs::path srcRoot, fs::path srcPath, fs::path outPath) {
data::analyzeClasses(cursor, srcRootStr, &dataAnlyState);
enum_::analyzeClasses(cursor, srcRootStr, &enumAnlyState);
fs::create_directories(outPath.parent_path()); // Make sure generated dir exists before we try writing to it
printf("[AUTOGEN] Generating file %s...\n", relpathStr.c_str());
std::ofstream outStream(outPathStr);
@ -104,5 +112,7 @@ int main(int argc, char** argv) {
fs::path srcPath = argv[2];
fs::path outPath = argv[3];
// fprintf(stderr, "Some error here\n");
// return 0;
return processHeader(srcRoot, srcPath, outPath);
}

View file

@ -1,8 +1,24 @@
find_package(SDL2 REQUIRED)
include_directories(${SDL2_INCLUDE_DIRS})
find_package(glfw3 REQUIRED)
include(${CMAKE_CURRENT_SOURCE_DIR}/deps.cmake)
add_executable(client "src/main.cpp")
target_link_libraries(client PRIVATE ${SDL2_LIBRARIES} openblocks glfw)
add_dependencies(client openblocks)
target_link_libraries(client PRIVATE openblocks glfw)
add_dependencies(client openblocks)
if(WIN32)
# Copy assets
add_custom_command(
TARGET client POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets
$<TARGET_FILE_DIR:client>/assets)
endif()
set_target_properties(client PROPERTIES
WIN32_EXECUTABLE ON
)
# https://stackoverflow.com/a/73899349/16255372
if (WIN32)
# /ENTRY:mainCRTStartup keeps the same "main" function instead of requiring "WinMain"
target_link_options(client PRIVATE "/ENTRY:mainCRTStartup")
endif()

12
client/deps.cmake Normal file
View file

@ -0,0 +1,12 @@
# Declare/fetch packages
include(FetchContent)
FetchContent_Declare(
glfw3
GIT_REPOSITORY https://github.com/glfw/glfw
GIT_TAG 3.4
)
FetchContent_MakeAvailable(glfw3)
# Find/include packages

View file

@ -1,6 +1,8 @@
#include <GL/glew.h>
#include <glad/gl.h>
#include <GLFW/glfw3.h>
#include "logger.h"
#include "objects/part/part.h"
#include "panic.h"
#include "rendering/renderer.h"
#include "common.h"
#include "version.h"
@ -37,7 +39,13 @@ int main() {
glfwSetFramebufferSizeCallback(window, resizeCallback);
glfwMakeContextCurrent(window);
glewInit();
int version = gladLoadGL(glfwGetProcAddress);
if (version == 0) {
Logger::fatalError("Failed to initialize OpenGL context");
panic();
} else {
Logger::debugf("Initialized GL context version %d.%d", GLAD_VERSION_MAJOR(version), GLAD_VERSION_MINOR(version));
}
gDataModel->Init();
renderInit(1200, 900);
@ -83,7 +91,7 @@ int main() {
}
void errorCatcher(int id, const char* str) {
Logger::fatalErrorf("GLFW Error: [{}] {}", id, str);
Logger::fatalErrorf("GLFW Error: [%d] %s", id, str);
}
float lastTime;

23
cmake/CPM.cmake Normal file
View file

@ -0,0 +1,23 @@
set(CPM_DOWNLOAD_VERSION 0.42.0)
# Patch support is broken in CPM upstream atm, so we have to use a fork
set(CPM_HASH_SUM "f7d92592a257d184fd8de6fb496a711ce393676ed68999b96043aab2e5e6f9e6")
# set(CPM_HASH_SUM "2020b4fc42dba44817983e06342e682ecfc3d2f484a581f11cc5731fbe4dce8a")
if(CPM_SOURCE_CACHE)
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
elseif(DEFINED ENV{CPM_SOURCE_CACHE})
set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
else()
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
endif()
# Expand relative path. This is important if the provided path contains a tilde (~)
get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE)
file(DOWNLOAD
https://raw.githubusercontent.com/BohdanBuinich/CPM.cmake/refs/heads/feat/fix_patch_command/cmake/CPM.cmake
# https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM}
)
include(${CPM_DOWNLOAD_LOCATION})

View file

@ -0,0 +1,60 @@
# Modified from QGIS' FindQScintilla.cmake by Thomas Moenicke, Larry Schaffer
add_library(QScintilla::QScintilla UNKNOWN IMPORTED)
### NECESSARY TO PREVENT staticMetaObject ERROR!!! See qscintilla.prf AKA qmake config
if(WIN32)
add_compile_definitions(QSCINTILLA_DLL)
endif()
FIND_PATH(QSCINTILLA_INCLUDE_DIR
NAMES Qsci/qsciglobal.h
PATHS
${Qt6Core_INCLUDE_DIRS}
$ENV{LIB_DIR}/include
/usr/local/include
/usr/include
${VCPKG_INSTALLED_DIR}/x64-windows/include
PATH_SUFFIXES ${QSCINTILLA_PATH_SUFFIXES}
)
set(QSCINTILLA_LIBRARY_NAMES
qscintilla2-qt6
qscintilla2_qt6
libqt6scintilla2
libqscintilla2-qt6
qt6scintilla2
libqscintilla2-qt6.dylib
qscintilla2
)
find_library(QSCINTILLA_LIBRARY
NAMES ${QSCINTILLA_LIBRARY_NAMES}
PATHS
"${QT_LIBRARY_DIR}"
$ENV{LIB_DIR}/lib
/usr/local/lib
/usr/local/lib/qt6
/usr/lib
/usr/lib64
/usr/lib32
${VCPKG_INSTALLED_DIR}/x64-windows/lib
)
get_filename_component(QSCINTILLA_LIB_DIR ${QSCINTILLA_LIBRARY} DIRECTORY)
list(TRANSFORM QSCINTILLA_LIBRARY_NAMES APPEND ".dll" OUTPUT_VARIABLE QSCINTILLA_DLL_NAMES)
find_file(QSCINTILLA_DLLS
NAMES ${QSCINTILLA_DLL_NAMES}
PATHS
"${QT_LIBRARY_DIR}"
$ENV{LIB_DIR}/lib
/usr/local/lib
/usr/local/lib/qt6
/usr/lib64
/usr/lib32
/usr/lib
${QSCINTILLA_LIB_DIR}
${VCPKG_INSTALLED_DIR}/x64-windows/lib
)

View file

@ -1,30 +0,0 @@
# Distributed under the OSI-approved BSD 3-Clause License.
# Copyright Stefano Sinigardi
#.rst:
# FindStb
# ------------
#
# Find the Stb include headers.
#
# Result Variables
# ^^^^^^^^^^^^^^^^
#
# This module defines the following variables:
#
# ``Stb_FOUND``
# True if Stb library found
#
# ``Stb_INCLUDE_DIR``
# Location of Stb headers
#
include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
include(${CMAKE_ROOT}/Modules/SelectLibraryConfigurations.cmake)
if(NOT Stb_INCLUDE_DIR)
find_path(Stb_INCLUDE_DIR NAMES stb_image.h PATHS ${Stb_DIR} PATH_SUFFIXES include stb include/stb)
endif()
find_package_handle_standard_args(Stb DEFAULT_MSG Stb_INCLUDE_DIR)
mark_as_advanced(Stb_INCLUDE_DIR)

View file

@ -1,29 +1,176 @@
find_package(OpenGL REQUIRED COMPONENTS OpenGL)
include(${CMAKE_CURRENT_SOURCE_DIR}/deps.cmake)
find_package(GLEW REQUIRED)
include_directories(${GLEW_INCLUDE_DIRS})
## Sources
set(SOURCES
src/stb.cpp
src/glad.cpp
find_package(OpenGL)
find_package(glm CONFIG REQUIRED)
find_package(ReactPhysics3D REQUIRED)
find_package(pugixml 1.15 REQUIRED)
find_package(Freetype)
src/ptr_helpers.h
src/enum/part.h
src/enum/surface.cpp
src/enum/meta.h
src/enum/annotation.h
src/enum/surface.h
src/camera.cpp
src/datatypes/vector.cpp
src/datatypes/variant.h
src/datatypes/cframe.cpp
src/datatypes/signal.cpp
src/datatypes/base.h
src/datatypes/enum.h
src/datatypes/enum.cpp
src/datatypes/primitives.h
src/datatypes/cframe.h
src/datatypes/variant.cpp
src/datatypes/vector.h
src/datatypes/color3.h
src/datatypes/annotation.h
src/datatypes/color3.cpp
src/datatypes/primitives.cpp
src/datatypes/ref.cpp
src/datatypes/ref.h
src/datatypes/signal.h
src/common.cpp
src/utils.h
src/platform.h
src/math_helper.cpp
src/rendering/skybox.cpp
src/rendering/mesh2d.cpp
src/rendering/torus.cpp
src/rendering/mesh.h
src/rendering/font.cpp
src/rendering/debug/debugrenderer.cpp
src/rendering/texture.h
src/rendering/shader.h
src/rendering/defaultmeshes.cpp
src/rendering/skybox.h
src/rendering/mesh2d.h
src/rendering/light.h
src/rendering/renderer.cpp
src/rendering/texture3d.h
src/rendering/texture.cpp
src/rendering/renderer.h
src/rendering/shader.cpp
src/rendering/mesh.cpp
src/rendering/material.h
src/rendering/torus.h
src/rendering/font.h
src/rendering/defaultmeshes.h
src/rendering/texture3d.cpp
src/logger.cpp
src/handles.h
src/timeutil.h
src/error/error.h
src/error/result.h
src/error/instance.h
src/error/data.h
src/partassembly.h
src/objects/service/jointsservice.cpp
src/objects/service/script/serverscriptservice.h
src/objects/service/script/serverscriptservice.cpp
src/objects/service/script/scriptcontext.h
src/objects/service/script/scriptcontext.cpp
src/objects/service/workspace.cpp
src/objects/service/selection.cpp
src/objects/service/selection.h
src/objects/service/jointsservice.h
src/objects/service/workspace.h
src/objects/datamodel.cpp
src/objects/script.h
src/objects/joint/snap.h
src/objects/joint/jointinstance.h
src/objects/joint/rotatev.h
src/objects/joint/weld.cpp
src/objects/joint/jointinstance.cpp
src/objects/joint/rotate.cpp
src/objects/joint/rotate.h
src/objects/joint/weld.h
src/objects/joint/snap.cpp
src/objects/joint/rotatev.cpp
src/objects/base/service.h
src/objects/base/member.h
src/objects/base/instance.h
src/objects/base/service.cpp
src/objects/base/instance.cpp
src/objects/base/refstate.h
src/objects/message.h
src/objects/pvinstance.cpp
src/objects/hint.cpp
src/objects/pvinstance.h
src/objects/base.h
src/objects/folder.cpp
src/objects/model.cpp
src/objects/datamodel.h
src/objects/folder.h
src/objects/meta.cpp
src/objects/model.h
src/objects/part/part.cpp
src/objects/part/part.h
src/objects/part/wedgepart.h
src/objects/part/basepart.cpp
src/objects/part/wedgepart.cpp
src/objects/part/basepart.h
src/objects/meta.h
src/objects/hint.h
src/objects/annotation.h
src/objects/message.cpp
src/objects/script.cpp
src/partassembly.cpp
src/panic.cpp
src/logger.h
src/camera.h
src/handles.cpp
src/version.h
src/common.h
src/platform.cpp
src/panic.h
src/lua/instancelib.cpp
src/timeutil.cpp
src/physics/util.h
src/luaapis.h
src/math_helper.h
)
find_package(Stb REQUIRED)
include_directories(${Stb_INCLUDE_DIR})
# PkgConfig packages
find_package(PkgConfig REQUIRED)
pkg_check_modules(LUAJIT REQUIRED luajit)
link_directories(${LUAJIT_LIBRARY_DIRS})
set(AUTOGEN_SOURCES
# Objects
src/objects/service/script/serverscriptservice.h
src/objects/service/script/scriptcontext.h
src/objects/service/selection.h
src/objects/service/jointsservice.h
src/objects/service/workspace.h
src/objects/script.h
src/objects/joint/snap.h
src/objects/joint/jointinstance.h
src/objects/joint/rotatev.h
src/objects/joint/rotate.h
src/objects/joint/weld.h
src/objects/message.h
src/objects/pvinstance.h
src/objects/base.h
src/objects/datamodel.h
src/objects/folder.h
src/objects/model.h
src/objects/part/part.h
src/objects/part/wedgepart.h
src/objects/part/basepart.h
src/objects/meta.h
src/objects/hint.h
# Enum
src/enum/part.h
src/enum/surface.h
# Data types
src/datatypes/enum.h
src/datatypes/cframe.h
src/datatypes/vector.h
src/datatypes/color3.h
)
### Autogen
file(GLOB_RECURSE AUTOGEN_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/src" "src/objects/*.h" "src/datatypes/*.h" "src/enum/*.h")
# https://cmake.org/cmake/help/book/mastering-cmake/chapter/Custom%20Commands.html
foreach (SRC ${AUTOGEN_SOURCES})
string(REGEX REPLACE "[.]h$" ".cpp" OUT_SRC_NAME ${SRC})
set(SRC_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/${SRC}")
set(SRC_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${SRC}")
set(OUT_PATH "${CMAKE_BINARY_DIR}/generated/${OUT_SRC_NAME}")
add_custom_command(
@ -45,14 +192,13 @@ add_custom_target(autogen_build ALL
DEPENDS ${AUTOGEN_OUTS}
)
file(GLOB_RECURSE SOURCES "src/*.cpp" "src/*.h")
list(APPEND SOURCES ${AUTOGEN_OUTS})
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/src/version.cpp)
add_library(openblocks STATIC ${SOURCES})
set_target_properties(openblocks PROPERTIES OUTPUT_NAME "openblocks")
target_link_directories(openblocks PUBLIC ${LUAJIT_LIBRARY_DIRS})
target_link_libraries(openblocks ${GLEW_LIBRARIES} ${LUAJIT_LIBRARIES} OpenGL::GL ReactPhysics3D::ReactPhysics3D pugixml::pugixml Freetype::Freetype)
target_include_directories(openblocks PUBLIC "src" "../include" ${LUAJIT_INCLUDE_DIRS})
target_link_libraries(openblocks reactphysics3d pugixml::pugixml Freetype::Freetype glm::glm libluajit ${LuaJIT_LIBRARIES})
target_include_directories(openblocks PUBLIC "src" "../include" "${CMAKE_SOURCE_DIR}/external/glad" ${ReactPhysics3D_SOURCE_DIR}/include ${LUAJIT_INCLUDE_DIRS} ${stb_SOURCE_DIR})
add_dependencies(openblocks autogen_build autogen)
# Windows-specific dependencies

32
core/deps.cmake Normal file
View file

@ -0,0 +1,32 @@
include(CPM)
# Some packages will build helper binaries. This keeps them out of our own build output
set (PREV_BIN_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
unset (CMAKE_RUNTIME_OUTPUT_DIRECTORY)
CPMAddPackage("gh:g-truc/glm#1.0.1")
CPMAddPackage(NAME reactphysics3d GITHUB_REPOSITORY "DanielChappuis/reactphysics3d" VERSION 0.10.2 PATCHES ${CMAKE_SOURCE_DIR}/patches/std_chrono.patch)
# https://github.com/StereoKit/StereoKit/blob/0be056efebcee5e58ad1438f4cf6dfdb942f6cf9/CMakeLists.txt#L205
set_property(TARGET reactphysics3d PROPERTY POSITION_INDEPENDENT_CODE ON)
CPMAddPackage("gh:zeux/pugixml@1.15")
CPMAddPackage(
NAME freetype
GIT_REPOSITORY https://github.com/aseprite/freetype2.git
GIT_TAG VER-2-10-0
VERSION 2.10.0
PATCHES ${CMAKE_SOURCE_DIR}/patches/freetype_cmakever.patch
)
if (freetype_ADDED)
add_library(Freetype::Freetype ALIAS freetype)
endif()
CPMAddPackage("gh:nothings/stb#8cfb1605c02aee9fb6eb5d8ea559017745bd9a16") # 2.14
CPMAddPackage("gh:WohlSoft/LuaJIT#a5da8f4a31972b74254f00969111b8b7a07cf584") # v2.1
set(LUAJIT_INCLUDE_DIRS ${LuaJIT_SOURCE_DIR}/src)
CPMAddPackage("gh:mackron/miniaudio#0.11.22")
set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PREV_BIN_PATH})

2
core/src/glad.cpp Normal file
View file

@ -0,0 +1,2 @@
#define GLAD_GL_IMPLEMENTATION
#include <glad/gl.h>

View file

@ -58,7 +58,7 @@ std::optional<HandleFace> raycastHandle(rp3d::Ray ray) {
// Implement manual detection via boxes instead of... this shit
// This code also hardly works, and is not good at all... Hooo nope.
rp3d::RigidBody* body = world->createRigidBody(CFrame::IDENTITY + cframe.Position());
body->addCollider(common.createBoxShape(cframe.Rotation() * Vector3(handleSize(face) / 2.f)), rp3d::Transform::identity());
body->addCollider(common.createBoxShape((cframe.Rotation() * Vector3(handleSize(face) / 2.f)).Abs()), rp3d::Transform::identity());
rp3d::RaycastInfo info;
if (body->raycast(ray, info)) {

View file

@ -10,18 +10,26 @@
static std::ofstream logStream;
static std::vector<Logger::LogListener> logListeners;
std::string Logger::currentLogDir = "NULL";
static std::stringstream* rawOutputBuffer = nullptr;
void Logger::init() {
initProgramLogsDir();
const auto now = std::chrono::system_clock::now();
const auto nows = std::chrono::floor<std::chrono::seconds>(now);
std::string fileName = std::format("log_{0:%Y%m%d}_{0:%H%M%S}.txt", now);
std::string fileName = std::format("log_{0:%Y%m%d}_{0:%H%M%S}.txt", nows);
logStream = std::ofstream(currentLogDir = (getProgramLogsDir() + "/" + fileName));
Logger::debug("Logger initialized");
}
// Initializes the logger in a "void" mode for testing.
// It is not necessary to call Logger::finish
void Logger::initTest(std::stringstream* outputBuffer) {
rawOutputBuffer = outputBuffer;
}
void Logger::finish() {
Logger::debug("Closing logger...");
logStream.close();
@ -41,6 +49,7 @@ void Logger::log(std::string message, Logger::LogLevel logLevel, ScriptSource so
logStream << formattedLogLine << std::endl;
printf("%s\n", formattedLogLine.c_str());
if (rawOutputBuffer != nullptr) *rawOutputBuffer << logLevelStr << ": " << message << "\n";
for (Logger::LogListener listener : logListeners) {
listener(logLevel, message, source);
@ -53,4 +62,8 @@ void Logger::log(std::string message, Logger::LogLevel logLevel, ScriptSource so
void Logger::addLogListener(Logger::LogListener listener) {
logListeners.push_back(listener);
}
void Logger::resetLogListeners() {
logListeners.clear();
}

View file

@ -2,6 +2,7 @@
#include <functional>
#include <memory>
#include <ostream>
#include <string>
class Script;
@ -26,8 +27,10 @@ namespace Logger {
extern std::string currentLogDir;
void init();
void initTest(std::stringstream* out); // Testing only!
void finish();
void addLogListener(LogListener);
void resetLogListeners(); // Testing only!
void log(std::string message, LogLevel logLevel, ScriptSource source = {});
inline void info(std::string message) { log(message, LogLevel::INFO); }

View file

@ -6,6 +6,8 @@ extern "C" {
#include <lua.h>
}
LUALIB_API void *(luaL_testudata) (lua_State *L, int ud, const char *tname);
inline const char* x_luaL_udatatname (lua_State *L, int ud) {
void *p = lua_touserdata(L, ud);
if (p != NULL) {
@ -16,4 +18,6 @@ inline const char* x_luaL_udatatname (lua_State *L, int ud) {
return str;
}
return NULL;
}
}
#define LUA_OK 0

View file

@ -13,6 +13,7 @@ WedgePart::WedgePart(PartConstructParams params): BasePart(&TYPE, params) {
}
void WedgePart::updateCollider(rp::PhysicsCommon* common) {
Logger::fatalError("Wedges are currently disabled! Please do not use them or your editor may crash\n");
rp::ConvexMeshShape* shape = common->createConvexMeshShape(wedgePhysMesh, glmToRp(size * glm::vec3(0.5f)));
// Recreate the rigidbody if the shape changes
@ -87,5 +88,5 @@ void WedgePart::createWedgeShape(rp::PhysicsCommon* common) {
// Create the convex mesh
std::vector<rp3d::Message> messages;
wedgePhysMesh = common->createConvexMesh(polygonVertexArray, messages);
// wedgePhysMesh = common->createConvexMesh(polygonVertexArray, messages);
}

View file

@ -3,7 +3,7 @@
#include "basepart.h"
#include "objects/annotation.h"
class DEF_INST WedgePart : public BasePart {
class DEF_INST_(hidden) WedgePart : public BasePart {
AUTOGEN_PREAMBLE
protected:

View file

@ -3,7 +3,7 @@
#include "objects/annotation.h"
#include "objects/base/service.h"
class DEF_INST_SERVICE JointsService : public Service {
class DEF_INST_SERVICE_(hidden) JointsService : public Service {
AUTOGEN_PREAMBLE
private:
std::optional<std::shared_ptr<Workspace>> jointWorkspace();

View file

@ -15,7 +15,7 @@ struct SleepingThread {
class Script;
class DEF_INST_SERVICE ScriptContext : public Service {
class DEF_INST_SERVICE_(hidden) ScriptContext : public Service {
AUTOGEN_PREAMBLE
std::vector<SleepingThread> sleepingThreads;

View file

@ -5,7 +5,7 @@
// Container class for server scripts
// Also handles/manages running server scripts on run
class DEF_INST_SERVICE_(explorer_icon="server-scripts") ServerScriptService : public Service {
class DEF_INST_SERVICE_(explorer_icon="server-scripts", hidden) ServerScriptService : public Service {
AUTOGEN_PREAMBLE
protected:
void InitService() override;

View file

@ -6,7 +6,7 @@
#include <memory>
#include <vector>
class DEF_INST_SERVICE Selection : public Service {
class DEF_INST_SERVICE_(hidden) Selection : public Service {
AUTOGEN_PREAMBLE
private:
std::vector<std::shared_ptr<Instance>> selection;

View file

@ -76,8 +76,8 @@ void Workspace::InitService() {
physicsWorld->setGravity(rp::Vector3(0, -196.2, 0));
// world->setContactsPositionCorrectionTechnique(rp3d::ContactsPositionCorrectionTechnique::BAUMGARTE_CONTACTS);
physicsWorld->setNbIterationsPositionSolver(2000);
physicsWorld->setNbIterationsVelocitySolver(2000);
// physicsWorld->setNbIterationsPositionSolver(2000);
// physicsWorld->setNbIterationsVelocitySolver(2000);
// physicsWorld->setSleepLinearVelocity(10);
// physicsWorld->setSleepAngularVelocity(5);

View file

@ -1,8 +1,7 @@
#include "rendering/shader.h"
#include "rendering/texture.h"
#include "timeutil.h"
#include <GL/glew.h>
#include <GL/gl.h>
#include <glad/gl.h>
#include <glm/ext/vector_float4.hpp>
#include <string>

View file

@ -3,8 +3,7 @@
#include "panic.h"
#include "rendering/shader.h"
#include <GL/glew.h>
#include <GL/gl.h>
#include <glad/gl.h>
#include <glm/ext/matrix_clip_space.hpp>
#include <memory>

View file

@ -1,5 +1,4 @@
#include <GL/glew.h>
#include <GL/gl.h>
#include <glad/gl.h>
#include "mesh.h"

View file

@ -1,5 +1,4 @@
#include <GL/glew.h>
#include <GL/gl.h>
#include <glad/gl.h>
#include "mesh2d.h"

View file

@ -1,5 +1,4 @@
#include <GL/glew.h>
#include <GL/gl.h>
#include <glad/gl.h>
#include <cmath>
#include <cstdio>
#include <glm/ext.hpp>

View file

@ -1,6 +1,5 @@
#include <fstream>
#include <GL/glew.h>
#include <GL/gl.h>
#include <glad/gl.h>
#include <glm/gtc/type_ptr.hpp>
#include "logger.h"

View file

@ -1,5 +1,4 @@
#include <GL/glew.h>
#include <GL/gl.h>
#include <glad/gl.h>
#include <stb_image.h>
#include "logger.h"

View file

@ -1,7 +1,6 @@
#include "texture.h"
#include <GL/glew.h>
#include <GL/gl.h>
#include <glad/gl.h>
#include <stb_image.h>
#include "panic.h"

View file

@ -1,7 +1,6 @@
#include "texture3d.h"
#include <GL/glew.h>
#include <GL/gl.h>
#include <glad/gl.h>
#include <stb_image.h>
#include "panic.h"

View file

@ -1,7 +1,7 @@
#include "torus.h"
#include <cmath>
#include <GL/glew.h>
#include <glad/gl.h>
#define PI 3.1415926535f

View file

@ -4,9 +4,15 @@
tu_time_t TIME_STARTED_MICROS = std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count();
static tu_time_t timeOverride = -1UL;
tu_time_t tu_clock_micros() {
if (timeOverride != -1UL) return timeOverride;
tu_time_t now = std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count();;
return now - TIME_STARTED_MICROS;
}
void tu_set_override(tu_time_t destTime) {
timeOverride = destTime;
}

View file

@ -5,4 +5,8 @@
typedef uint64_t tu_time_t;
// Provides a high-accuracy time since the program started in microseconds (via std::chrono)
tu_time_t tu_clock_micros();
tu_time_t tu_clock_micros();
#ifdef TU_TIME_EXPOSE_TEST
void tu_set_override(tu_time_t destTime);
#endif

View file

@ -1,8 +1,7 @@
opengl (Linux: glvnd, Windows: [built-in/none])
glfw
glew
glad
glm
sdl2
stb
qt6
reactphysics3d

View file

@ -4,7 +4,7 @@ To do this, first download the source archive from [`https://www.riverbankcomput
Next, launch the *x64 Native Tools Command Prompt for VS 2022*, and cd into the directory that you extracted the archive to
Now, run `qmake` from your Qt's bin directory to configure it
Now, run `qmake src` from your Qt's bin directory to configure it
Once that's done, build and install the project using `nmake install`

View file

@ -1,21 +1,12 @@
cmake_minimum_required(VERSION 3.30.0)
project(editor VERSION 0.1 LANGUAGES CXX)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(QT_VERSION_MAJOR 6)
find_package(Qt6 REQUIRED COMPONENTS Widgets OpenGLWidgets REQUIRED)
find_package(QScintilla6 REQUIRED)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Multimedia LinguistTools)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Multimedia LinguistTools)
find_package(QScintilla REQUIRED)
set(TS_FILES editor_en_US.ts)
include(${CMAKE_CURRENT_SOURCE_DIR}/deps.cmake)
set(PROJECT_SOURCES
main.cpp
@ -47,59 +38,25 @@ set(PROJECT_SOURCES
${TS_FILES}
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(editor
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET editor APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
qt_add_executable(editor MANUAL_FINALIZATION ${PROJECT_SOURCES})
target_include_directories(editor PRIVATE .)
target_link_libraries(editor PRIVATE openblocks Qt6::Widgets Qt6::OpenGLWidgets ${QSCINTILLA_LIBRARY} miniaudio QCursorConstraints)
qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
else()
if(ANDROID)
add_library(editor SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(editor
${PROJECT_SOURCES}
mainglwidget.h mainglwidget.cpp
)
endif()
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
endif()
target_include_directories(editor PUBLIC "../core/src" "../include" ${QSCINTILLA_INCLUDE_DIR})
target_link_libraries(editor PRIVATE openblocks Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Multimedia ${QSCINTILLA_LIBRARY})
add_dependencies(editor openblocks)
# Qt6 does not include QOpenGLWidgets as part of Widgets base anymore, so
# we have to include it manually
if (${QT_VERSION} GREATER_EQUAL 6)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS OpenGL OpenGLWidgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS OpenGL OpenGLWidgets)
target_include_directories(editor PUBLIC Qt6::OpenGL Qt6::OpenGLWidgets)
target_link_libraries(editor PRIVATE Qt6::OpenGL Qt6::OpenGLWidgets)
endif()
# Copy assets
add_custom_command(
TARGET editor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets
$<TARGET_FILE_DIR:editor>/assets)
set_target_properties(editor PROPERTIES
WIN32_EXECUTABLE ON
)
# Copy Qt files
if (WIN32)
#include("${QT_DEPLOY_SUPPORT}")
# Copy assets
add_custom_command(
TARGET editor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/assets
$<TARGET_FILE_DIR:editor>/assets)
# Copy over QScintilla DLLs
# TODO: Use a better approach?
add_custom_command(
@ -107,45 +64,37 @@ if (WIN32)
COMMAND ${CMAKE_COMMAND} -E copy ${QSCINTILLA_DLLS} $<TARGET_FILE_DIR:editor>
)
set(WINDEPLOYQT_OPTIONS
--dir $<TARGET_FILE_DIR:editor>
--translations en
--no-compiler-runtime
--no-opengl-sw # No sense adding opengl-sw given that hardware acceleration is necessary, anyway
--no-system-d3d-compiler
--plugindir $<TARGET_FILE_DIR:editor>/qtplugins
# Also don't want to clutter with plugins, add only needed ones
)
# We split these into two commands because
# we might build a debug binary against a release qscintilla library
# TODO: Add other translations
add_custom_command(
TARGET editor POST_BUILD
COMMAND ${WINDEPLOYQT_EXECUTABLE} $<TARGET_FILE:editor> ${QSCINTILLA_DLLS} --dir $<TARGET_FILE_DIR:editor> --translations en --no-compiler-runtime --no-opengl-sw --no-system-d3d-compiler --plugindir $<TARGET_FILE_DIR:editor>/qtplugins
COMMAND ${WINDEPLOYQT_EXECUTABLE} $<TARGET_FILE:editor>
${WINDEPLOYQT_OPTIONS}
)
# No sense adding opengl-sw given that hardware acceleration is necessary, anyway
# Also don't want to clutter with plugins, add only needed ones
# Copy QScintilla dependencies too
add_custom_command(
TARGET editor POST_BUILD
COMMAND ${WINDEPLOYQT_EXECUTABLE} ${QSCINTILLA_DLLS}
${WINDEPLOYQT_OPTIONS}
)
# Copy qt.conf to override default plugins location
add_custom_command(
TARGET editor POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/qt.conf $<TARGET_FILE_DIR:editor>/qt.conf
)
endif ()
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.qtbasic)
endif()
set_target_properties(editor PROPERTIES
${BUNDLE_ID_OPTION}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
include(GNUInstallDirs)
install(TARGETS editor
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
install(FILES $<TARGET_RUNTIME_DLLS:editor> TYPE BIN)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(editor)
endif()
qt_finalize_executable(editor)

4
editor/deps.cmake Normal file
View file

@ -0,0 +1,4 @@
include(CPM)
CPMAddPackage("gh:m-doescode/qcursorconstraints#cef1a31c0afad8ed3c95ee1a6bc531090805b510")

View file

@ -7,25 +7,40 @@
#include <QStyleFactory>
#include <QBasicTimer>
#include <QSurfaceFormat>
#include <cstdio>
#include <qfont.h>
#include <qsurfaceformat.h>
#include <miniaudio.h>
#include <qcursorconstraints.h>
ma_engine miniaudio;
int main(int argc, char *argv[])
{
Logger::init();
// Has to happen before Qt application initializes or we get an error in WASAPI initialization
ma_result res = ma_engine_init(NULL, &miniaudio);
if (res != MA_SUCCESS) {
Logger::fatalErrorf("Failed to initialize Miniaudio withe error [%d]", res);
panic();
}
QSurfaceFormat format;
format.setSamples(4);
format.setRenderableType(QSurfaceFormat::OpenGL);
format.setVersion(3, 3);
format.setProfile(QSurfaceFormat::CompatibilityProfile); // Valid only in OpenGL 3.2+, see: https://stackoverflow.com/a/70519392/16255372
QSurfaceFormat::setDefaultFormat(format);
QApplication a(argc, argv);
Logger::init();
QCursorConstraints::init();
MainWindow w;
w.show();
int result = a.exec();
ma_engine_uninit(&miniaudio);
Logger::finish();
return result;
}

View file

@ -1,9 +1,11 @@
#include <GL/glew.h>
#include <glad/gl.h>
#include <glm/common.hpp>
#include <glm/vector_relational.hpp>
#include <memory>
#include <miniaudio.h>
#include <qcursorconstraints.h>
#include <qnamespace.h>
#include <qsoundeffect.h>
#include <qguiapplication.h>
#include <string>
#include "./ui_mainwindow.h"
#include "mainglwidget.h"
@ -35,15 +37,20 @@ MainGLWidget::MainGLWidget(QWidget* parent): QOpenGLWidget(parent), contextMenu(
}
void MainGLWidget::initializeGL() {
glewInit();
int version = gladLoaderLoadGL();
if (version == 0) {
Logger::fatalError("Failed to initialize OpenGL context");
panic();
} else {
Logger::debugf("Initialized GL context version %d.%d", GLAD_VERSION_MAJOR(version), GLAD_VERSION_MINOR(version));
}
renderInit(width(), height());
}
extern ma_engine miniaudio;
inline void playSound(QString path) {
QSoundEffect *sound = new QSoundEffect;
sound->setSource(QUrl::fromLocalFile(path));
sound->play();
sound->connect(sound, &QSoundEffect::playingChanged, [=]() { /* Thank you QSound source code! */ sound->deleteLater(); return false; });
ma_engine_stop(&miniaudio);
ma_engine_play_sound(&miniaudio, path.toStdString().c_str(), NULL);
}
extern int vpx, vpy;
@ -68,14 +75,11 @@ void MainGLWidget::paintGL() {
}
bool isMouseRightDragging = false;
QPoint lastMousePos;
QPoint mouseLockedPos;
void MainGLWidget::handleCameraRotate(QMouseEvent* evt) {
if (!isMouseRightDragging) return;
camera.processRotation(evt->pos().x() - lastMousePos.x(), evt->pos().y() - lastMousePos.y());
lastMousePos = evt->pos();
// QCursor::setPos(lastMousePos);
camera.processRotation(evt->pos().x() - mouseLockedPos.x(), evt->pos().y() - mouseLockedPos.y());
}
@ -323,6 +327,7 @@ std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
}
void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
if (isMouseRightDragging) return; // Don't change the cursor while it is intentionally blank
QPoint position = evt->pos();
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
@ -373,8 +378,10 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
switch(evt->button()) {
// Camera drag
case Qt::RightButton: {
lastMousePos = evt->pos();
mouseLockedPos = evt->pos();
isMouseRightDragging = true;
setCursor(Qt::BlankCursor);
QCursorConstraints::lockCursor(window()->windowHandle());
return;
// Clicking on objects
} case Qt::LeftButton: {
@ -459,11 +466,13 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
}
void MainGLWidget::mouseReleaseEvent(QMouseEvent* evt) {
QCursorConstraints::unlockCursor(window()->windowHandle());
// if (isMouseDragging) draggingObject.lock()->rigidBody->getCollider(0)->setCollisionCategoryBits(0b11);
isMouseRightDragging = false;
isMouseDragging = false;
draggingObject = {};
draggingHandle = std::nullopt;
setCursor(Qt::ArrowCursor);
if (!initialTransforms.empty()) {
UndoState historyState;

View file

@ -12,6 +12,8 @@
#include "undohistory.h"
#include "version.h"
#include <memory>
#include <QToolButton>
#include <QSettings>
#include <qclipboard.h>
#include <qevent.h>
#include <qglobal.h>
@ -20,13 +22,14 @@
#include <qmessagebox.h>
#include <qmimedata.h>
#include <qnamespace.h>
#include <qsoundeffect.h>
#include <qstylefactory.h>
#include <qstylehints.h>
#include <qmdisubwindow.h>
#include <pugixml.hpp>
#include <qtextcursor.h>
#include <qtextedit.h>
#include <miniaudio.h>
#include <qtoolbutton.h>
#include <vector>
#ifdef _NDEBUG
@ -58,17 +61,17 @@ void logQtMessage(QtMsgType type, const QMessageLogContext &context, const QStri
// if (defaultMessageHandler) defaultMessageHandler(type, context, msg);
}
extern ma_engine miniaudio;
inline void playSound(QString path) {
QSoundEffect *sound = new QSoundEffect;
sound->setSource(QUrl::fromLocalFile(path));
sound->play();
sound->connect(sound, &QSoundEffect::playingChanged, [=]() { /* Thank you QSound source code! */ sound->deleteLater(); return false; });
ma_engine_play_sound(&miniaudio, path.toStdString().c_str(), NULL);
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
loadState();
gDataModel->Init();
ui->setupUi(this);
@ -104,6 +107,28 @@ MainWindow::MainWindow(QWidget *parent)
connectActionHandlers();
// Add open recents menu
refreshRecentsMenu();
for (QObject* child : ui->fileTools->children()) {
if (auto toolButton = dynamic_cast<QToolButton*>(child)) {
if (toolButton->defaultAction() != ui->actionOpen) continue;
// https://stackoverflow.com/a/12283957/16255372
// https://stackoverflow.com/a/5365184/16255372
toolButton->setMenu(recentsMenu);
toolButton->setPopupMode(QToolButton::MenuButtonPopup);
}
}
// Add open recents dropdown to file menu
auto actions = ui->menuFile->actions();
for (int i = 0; i < actions.size(); i++) {
if (actions[i] != ui->actionOpen) continue;
ui->menuFile->insertMenu((i+1) < actions.size() ? actions[i+1] : nullptr, recentsMenu);
}
// ui->explorerView->Init(ui);
placeDocument = new PlaceDocument(this);
placeDocument->setAttribute(Qt::WA_DeleteOnClose, true);
@ -127,6 +152,7 @@ MainWindow::MainWindow(QWidget *parent)
}
void MainWindow::closeEvent(QCloseEvent* evt) {
saveState();
#ifdef NDEBUG
// Ask if the user wants to save their changes
// https://stackoverflow.com/a/33890731
@ -149,6 +175,64 @@ void MainWindow::closeEvent(QCloseEvent* evt) {
#endif
}
void MainWindow::refreshRecentsMenu() {
if (!recentsMenu) recentsMenu = new QMenu();
recentsMenu->setTitle("Recent files...");
recentsMenu->clear(); // Actions not shown in any other menu are automatically deleted
for (QString item : recentFiles) {
QAction* itemAction = new QAction();
itemAction->setText(item.split('/').last());
recentsMenu->addAction(itemAction);
connect(itemAction, &QAction::triggered, [item, this]{
if (!QFile::exists(item)) {
QMessageBox::warning(this, "File not found", "The file '" + item + "' could not longer be found at that location.");
return;
}
std::shared_ptr<DataModel> newModel = DataModel::LoadFromFile(item.toStdString());
editModeDataModel = newModel;
gDataModel = newModel;
newModel->Init();
ui->explorerView->updateRoot(newModel);
// Reset running state
placeDocument->setRunState(RUN_STOPPED);
undoManager.Reset();
updateToolbars();
pushRecentFile(item);
});
}
if (recentsMenu->isEmpty()) {
QAction* emptyAction = new QAction("No recents");
emptyAction->setEnabled(false);
recentsMenu->addAction(emptyAction);
}
}
void MainWindow::loadState() {
QSettings settings("openblocks");
recentFiles = settings.value("recentFiles").toStringList();
}
void MainWindow::saveState() {
QSettings settings("openblocks");
settings.setValue("recentFiles", recentFiles);
}
void MainWindow::pushRecentFile(QString file) {
// https://www.walletfox.com/course/qtopenrecentfiles.php
recentFiles.removeAll(file);
recentFiles.prepend(file);
while (recentFiles.size() > 10) recentFiles.removeLast();
refreshRecentsMenu();
saveState();
}
void MainWindow::setUpCommandBar() {
CommandEdit* commandEdit;
QToolBar* commandBar = ui->commandBar;
@ -282,6 +366,7 @@ void MainWindow::connectActionHandlers() {
}
editModeDataModel->SaveToFile(path);
if (path) pushRecentFile(QString::fromStdString(path.value()));
});
connect(ui->actionSaveAs, &QAction::triggered, this, [&]() {
@ -289,6 +374,7 @@ void MainWindow::connectActionHandlers() {
if (!path || path == "") return;
editModeDataModel->SaveToFile(path);
if (path) pushRecentFile(QString::fromStdString(path.value()));
});
connect(ui->actionOpen, &QAction::triggered, this, [&]() {
@ -309,6 +395,7 @@ void MainWindow::connectActionHandlers() {
placeDocument->setRunState(RUN_STOPPED);
undoManager.Reset();
updateToolbars();
if (path) pushRecentFile(QString::fromStdString(path.value()));
});
connect(ui->actionDelete, &QAction::triggered, this, [&]() {

View file

@ -64,10 +64,18 @@ public:
void openFile(std::string path);
Ui::MainWindow *ui;
QMenu* recentsMenu = nullptr;
QStringList recentFiles;
void refreshRecentsMenu();
void pushRecentFile(QString);
void loadState();
void saveState();
friend PlaceDocument;
private:
PlaceDocument* placeDocument;
PlaceDocument* placeDocument = nullptr;
void setUpCommandBar();
void connectActionHandlers();

View file

@ -8,6 +8,10 @@
#include <qmimedata.h>
#include <QWidget>
#ifdef _NDEBUG
#define NDEBUG
#endif
#define M_mainWindow dynamic_cast<MainWindow*>(dynamic_cast<QWidget*>(dynamic_cast<QObject*>(this)->parent())->window())
// https://doc.qt.io/qt-6/qtwidgets-itemviews-simpletreemodel-example.html#testing-the-model
@ -49,8 +53,14 @@ QModelIndex ExplorerModel::index(int row, int column, const QModelIndex &parent)
? static_cast<Instance*>(parent.internalPointer())
: rootItem.get();
#ifdef NDEBUG
if (parentItem->GetChildren().size() >= (size_t)row && !(parentItem->GetChildren()[row]->GetClass()->flags & INSTANCE_HIDDEN))
return createIndex(row, column, parentItem->GetChildren()[row].get());
#else
// Don't hide in debug builds
if (parentItem->GetChildren().size() >= (size_t)row)
return createIndex(row, column, parentItem->GetChildren()[row].get());
#endif
return {};
}
@ -97,7 +107,15 @@ int ExplorerModel::rowCount(const QModelIndex &parent) const {
? static_cast<Instance*>(parent.internalPointer())
: rootItem.get();
#ifdef NDEBUG
// Trim trailing hidden items as they make the branches look weird
int count = parentItem->GetChildren().size();
while (count > 0 && parentItem->GetChildren()[count-1]->GetClass()->flags & INSTANCE_HIDDEN) count--;
return count;
#else
// Don't hide in debug builds
return parentItem->GetChildren().size();
#endif
}
int ExplorerModel::columnCount(const QModelIndex &parent) const {

View file

@ -107,7 +107,7 @@ void ExplorerView::buildContextMenu() {
contextMenu.addMenu(insertObjectMenu);
for (const auto& [_, type] : INSTANCE_MAP) {
if (type->flags & INSTANCE_NOTCREATABLE || !type->constructor) continue;
if (type->flags & (INSTANCE_NOTCREATABLE | INSTANCE_HIDDEN) || !type->constructor) continue;
QAction* instAction = new QAction(model.iconOf(type), QString::fromStdString(type->className));
insertObjectMenu->addAction(instAction);

5914
external/glad/glad/gl.h vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,14 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 28dc3b3f6..37fd14713 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -85,7 +85,8 @@
# FreeType explicitly marks the API to be exported and relies on the compiler
# to hide all other symbols. CMake supports a C_VISBILITY_PRESET property
# starting with 2.8.12.
-cmake_minimum_required(VERSION 2.8.12)
+# cmake_minimum_required(VERSION 2.8.12)
+cmake_minimum_required(VERSION 3.5)
if (NOT CMAKE_VERSION VERSION_LESS 3.3)
# Allow symbol visibility settings also on static libraries. CMake < 3.3

14
tests/CMakeLists.txt Normal file
View file

@ -0,0 +1,14 @@
function (create_test TEST_NAME)
set(TARGET_NAME test_${TEST_NAME})
add_executable(${TARGET_NAME} ${ARGN})
target_link_libraries(${TARGET_NAME} PRIVATE openblocks)
add_dependencies(${TARGET_NAME} openblocks)
add_test(NAME ${TARGET_NAME} COMMAND ${TARGET_NAME})
endfunction ()
create_test(lua src/luatest.cpp)
create_test(luasched src/luaschedtest.cpp)
create_test(luasignal src/luasignaltest.cpp)
# https://stackoverflow.com/a/36729074/16255372
add_custom_target(check ${CMAKE_CTEST_COMMAND} --output-on-failure WORKING_DIRECTORY ${CMAKE_BINARY_DIR})

View file

@ -0,0 +1,77 @@
#include "testutillua.h"
#include "timeutil.h"
void test_wait1(DATAMODEL_REF m) {
auto ctx = m->GetService<ScriptContext>();
std::stringstream out;
Logger::initTest(&out);
tu_set_override(0);
luaEval(m, "wait(1) print('Wait')");
ctx->RunSleepingThreads();
ASSERT_EQ("", out.str());
TT_ADVANCETIME(0.5);
ctx->RunSleepingThreads();
ASSERT_EQ("", out.str());
TT_ADVANCETIME(0.5);
ctx->RunSleepingThreads();
ASSERT_EQ("INFO: Wait\n", out.str());
Logger::initTest(nullptr);
}
void test_wait0(DATAMODEL_REF m) {
auto ctx = m->GetService<ScriptContext>();
std::stringstream out;
Logger::initTest(&out);
tu_set_override(0);
luaEval(m, "wait(0) print('Wait')");
ASSERT_EQ("", out.str());
ctx->RunSleepingThreads();
ASSERT_EQ("", out.str());
TT_ADVANCETIME(0.03);
ctx->RunSleepingThreads();
ASSERT_EQ("INFO: Wait\n", out.str());
Logger::initTest(nullptr);
}
void test_delay(DATAMODEL_REF m) {
auto ctx = m->GetService<ScriptContext>();
std::stringstream out;
Logger::initTest(&out);
tu_set_override(0);
luaEval(m, "delay(1, function() print('Delay') end)");
ctx->RunSleepingThreads();
ASSERT_EQ("", out.str());
TT_ADVANCETIME(0.5);
ctx->RunSleepingThreads();
ASSERT_EQ("", out.str());
TT_ADVANCETIME(0.5);
ctx->RunSleepingThreads();
ASSERT_EQ("INFO: Delay\n", out.str());
Logger::initTest(nullptr);
}
int main() {
auto m = DataModel::New();
m->Init(true);
test_wait1(m);
test_wait0(m);
test_delay(m);
return TEST_STATUS;
}

103
tests/src/luasignaltest.cpp Normal file
View file

@ -0,0 +1,103 @@
#include "testutil.h"
#include "testutillua.h"
#include "timeutil.h"
#include "objects/part/part.h"
#include "objects/service/workspace.h"
#include <memory>
#include <sstream>
void test_connect(DATAMODEL_REF m) {
auto ctx = m->GetService<ScriptContext>();
auto part = Part::New();
m->GetService<Workspace>()->AddChild(part);
std::stringstream out;
Logger::initTest(&out);
luaEval(m, "workspace.Part.Touched:Connect(function() print('Fired!') end)");
ASSERT_EQ("", out.str());
part->Touched->Fire();
ASSERT_EQ("INFO: Fired!\n", out.str());
part->Touched->Fire();
ASSERT_EQ("INFO: Fired!\nINFO: Fired!\n", out.str());
Logger::initTest(nullptr);
part->Destroy();
}
void test_waitwithin(DATAMODEL_REF m) {
auto ctx = m->GetService<ScriptContext>();
auto part = Part::New();
m->GetService<Workspace>()->AddChild(part);
std::stringstream out;
Logger::initTest(&out);
tu_set_override(0);
luaEval(m, "workspace.Part.Touched:Connect(function() print('Fired!') wait(1) print('Waited') end)");
ASSERT_EQ("", out.str());
// One shot
part->Touched->Fire();
ctx->RunSleepingThreads();
ASSERT_EQ("INFO: Fired!\n", out.str());
TT_ADVANCETIME(0.5);
ctx->RunSleepingThreads();
ASSERT_EQ("INFO: Fired!\n", out.str());
TT_ADVANCETIME(0.5);
ctx->RunSleepingThreads();
ASSERT_EQ("INFO: Fired!\nINFO: Waited\n", out.str());
// Clear
out = std::stringstream();
Logger::initTest(&out); // Shouldn't *theoretically* be necessary, but just in principle...
// Double fire
part->Touched->Fire();
TT_ADVANCETIME(0.2);
part->Touched->Fire();
ASSERT_EQ("INFO: Fired!\nINFO: Fired!\n", out.str());
TT_ADVANCETIME(1-0.2); // Small extra delay is necessary because floating point math
ctx->RunSleepingThreads();
ASSERT_EQ("INFO: Fired!\nINFO: Fired!\nINFO: Waited\n", out.str());
TT_ADVANCETIME(0.2);
ctx->RunSleepingThreads();
ASSERT_EQ("INFO: Fired!\nINFO: Fired!\nINFO: Waited\nINFO: Waited\n", out.str());
tu_set_override(-1UL);
Logger::initTest(nullptr);
part->Destroy();
}
void test_await(DATAMODEL_REF m) {
auto ctx = m->GetService<ScriptContext>();
auto part = Part::New();
m->GetService<Workspace>()->AddChild(part);
std::stringstream out;
Logger::initTest(&out);
tu_set_override(0);
luaEval(m, "workspace.Part.Touched:Wait() print('Fired!')");
ASSERT_EQ("", out.str());
part->Touched->Fire();
ASSERT_EQ("INFO: Fired!\n", out.str());
part->Touched->Fire(); // Firing again should not affect output
ASSERT_EQ("INFO: Fired!\n", out.str());
tu_set_override(-1UL);
Logger::initTest(nullptr);
part->Destroy();
}
int main() {
auto m = DataModel::New();
m->Init(true);
test_connect(m);
test_waitwithin(m);
test_await(m);
return TEST_STATUS;
}

20
tests/src/luatest.cpp Normal file
View file

@ -0,0 +1,20 @@
#include "testutil.h"
#include "testutillua.h"
#include <memory>
void test_output(DATAMODEL_REF m) {
ASSERT_EQ("INFO: Hello, world!\n", luaEvalOut(m, "print('Hello, world!')"));
// ASSERT_EQ("WARN: Some warning here.\n", luaEvalOut(m, "warn('Some warning here.')"));
// ASSERT_EQ("ERROR: An error!.\n", luaEvalOut(m, "error('An error!')"));
}
int main() {
auto m = DataModel::New();
m->Init(true);
test_output(m);
return TEST_STATUS;
}

70
tests/src/testutil.h Normal file
View file

@ -0,0 +1,70 @@
#pragma once
// https://bastian.rieck.me/blog/2017/simple_unit_tests/
#include <algorithm>
#include <cstddef>
#include <iomanip>
#include <regex>
#include <sstream>
#include <string>
#ifdef __FUNCTION__
#define __FUNC_NAME __FUNCTION__
#else
#define __FUNC_NAME __func__
#endif
#define ASSERT(x, msg) __assert((x), __FILE__, __LINE__, __FUNC_NAME, msg)
#define ASSERT_EQ(x, y) __assert_eq((x) == (y), __FILE__, __LINE__, __FUNC_NAME, #x, (y))
// #define ASSERT_EQSTR(x, y) ASSERT(strcmp(x, y) == 0, #x " != " #y)
#define ASSERT_EQSTR(x, y) ASSERT_EQ(x, y)
#define DATAMODEL_REF std::shared_ptr<DataModel>
#define TU_TIME_EXPOSE_TEST
#define TT_ADVANCETIME(secs) tu_set_override(tu_clock_micros() + (secs) * 1'000'000);
#include <cstdio>
#include <cstring>
int TEST_STATUS = 0;
inline void __assert(bool cond, std::string file, int line, std::string func, std::string message) {
if (cond) return;
fprintf(stderr, "ASSERT FAILED : %s:%d : %s : '%s'\n", file.c_str(), line, func.c_str(), message.c_str());
TEST_STATUS = 1;
}
template <typename T>
inline std::string quote(T value) {
return std::to_string(value);
}
template <>
std::string quote<std::string>(std::string value) {
std::stringstream ss;
ss << std::quoted(value);
std::string newstr = ss.str();
newstr = std::regex_replace(newstr, std::regex("\n"), "\\n");
return newstr;
}
template <>
inline std::string quote<const char*>(const char* value) {
return quote<std::string>(value);
}
template <>
inline std::string quote<char*>(char* value) {
return quote<std::string>(value);
}
template <typename T>
void __assert_eq(bool cond, std::string file, int line, std::string func, std::string model, T value) {
if (cond) return;
std::string message = model + " != " + quote(value);
fprintf(stderr, "ASSERT FAILED : %s:%d : %s : '%s'\n", file.c_str(), line, func.c_str(), message.c_str());
TEST_STATUS = 1;
}

30
tests/src/testutillua.h Normal file
View file

@ -0,0 +1,30 @@
#pragma once
#include "testutil.h"
#include <sstream>
#include "logger.h"
#include "objects/datamodel.h"
#include "objects/script.h"
#include "objects/service/script/scriptcontext.h"
std::string luaEvalOut(DATAMODEL_REF m, std::string source) {
std::stringstream out;
Logger::initTest(&out);
auto s = Script::New();
m->AddChild(s);
s->source = source;
s->Run();
Logger::initTest(nullptr);
return out.str();
}
void luaEval(DATAMODEL_REF m, std::string source) {
auto s = Script::New();
m->AddChild(s);
s->source = source;
s->Run();
}

View file

@ -1,17 +0,0 @@
{
"default-registry": {
"kind": "git",
"baseline": "0c4cf19224a049cf82f4521e29e39f7bd680440c",
"repository": "https://github.com/microsoft/vcpkg"
},
"registries": [
{
"kind": "artifact",
"location": "https://github.com/microsoft/vcpkg-ce-catalog/archive/refs/heads/main.zip",
"name": "microsoft"
}
],
"overlay-ports": [
"./vcpkg-overlays"
]
}

View file

@ -1,23 +0,0 @@
vcpkg_check_linkage(ONLY_STATIC_LIBRARY)
vcpkg_from_github(
OUT_SOURCE_PATH SOURCE_PATH
REPO DanielChappuis/reactphysics3d
REF "cd958bbc0c6e84a869388cba6613f10cc645b3cb"
SHA512 9856c0e998473e0bfb97af9ced07952bbd4dfef79f7dc388b1ecf9b6c6406f7669333e441fe6cefdf40b32edc5a1b8e4cb35a8c15fccb64c28785aff5fd77113
HEAD_REF master
PATCHES "std_chrono.patch"
)
vcpkg_cmake_configure(
SOURCE_PATH "${SOURCE_PATH}"
)
vcpkg_cmake_install()
vcpkg_cmake_config_fixup(PACKAGE_NAME "reactphysics3d" CONFIG_PATH "lib/cmake/ReactPhysics3D")
file(REMOVE_RECURSE "${CURRENT_PACKAGES_DIR}/debug/include")
# file(INSTALL "${CMAKE_CURRENT_LIST_DIR}/usage" DESTINATION "${CURRENT_PACKAGES_DIR}/share/${PORT}")
vcpkg_install_copyright(FILE_LIST "${SOURCE_PATH}/LICENSE")

View file

@ -1,18 +0,0 @@
{
"name": "reactphysics3d",
"version": "0.10.2",
"homepage": "https://github.com/DanielChappuis/reactphysics3d",
"description": "Open source C++ physics engine library in 3D",
"license": "zlib",
"dependencies": [
{
"name" : "vcpkg-cmake",
"host" : true
},
{
"name" : "vcpkg-cmake-config",
"host" : true
},
"fmt"
]
}

View file

@ -1,20 +0,0 @@
{
"dependencies": [
"glew",
"glfw3",
"glm",
{ "name": "pugixml", "version>=": "1.15" },
"sdl2",
"stb",
"reactphysics3d",
"pkgconf",
"luajit",
"freetype"
],
"overrides": [
{
"name": "sdl2",
"version": "2.32.4"
}
]
}