Compare commits
No commits in common. "master" and "feature/jolt" have entirely different histories.
master
...
feature/jo
47 changed files with 443 additions and 855 deletions
4
.clangd
4
.clangd
|
|
@ -1,3 +1,3 @@
|
||||||
CompileFlags:
|
CompileFlags:
|
||||||
CompilationDatabase: build/ # https://www.reddit.com/r/neovim/comments/vj0e16/comment/idgkg55/
|
Add: [-std=c++20]
|
||||||
Remove: [-mno-direct-extern-access]
|
Remove: [-mno-direct-extern-access]
|
||||||
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
|
|
@ -18,7 +18,6 @@
|
||||||
"program": "${workspaceFolder}/build/bin/editor",
|
"program": "${workspaceFolder}/build/bin/editor",
|
||||||
"args": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"preLaunchTask": "buildDebug"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "lldb",
|
"type": "lldb",
|
||||||
|
|
|
||||||
11
.vscode/tasks.json
vendored
11
.vscode/tasks.json
vendored
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"version": "2.0.0",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"label": "buildDebug",
|
|
||||||
"type": "process",
|
|
||||||
"command": "cmake",
|
|
||||||
"args": ["--build", "build", "-j16"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
// Project-local debug tasks
|
|
||||||
//
|
|
||||||
// For more documentation on how to configure debug tasks,
|
|
||||||
// see: https://zed.dev/docs/debugger
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"label": "Debug editor",
|
|
||||||
"build": {
|
|
||||||
"command": "cmake",
|
|
||||||
"args": ["--build", "build", "-j16"],
|
|
||||||
"cwd": "$ZED_WORKTREE_ROOT"
|
|
||||||
},
|
|
||||||
"program": "$ZED_WORKTREE_ROOT/build/bin/editor",
|
|
||||||
"request": "launch",
|
|
||||||
"adapter": "CodeLLDB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "Debug tests",
|
|
||||||
"build": {
|
|
||||||
"command": "cmake",
|
|
||||||
"args": ["--build", "build", "-j16"],
|
|
||||||
"cwd": "$ZED_WORKTREE_ROOT"
|
|
||||||
},
|
|
||||||
"program": "$ZED_WORKTREE_ROOT/build/bin/obtest",
|
|
||||||
"request": "launch",
|
|
||||||
"adapter": "CodeLLDB"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -24,4 +24,7 @@ add_subdirectory(core)
|
||||||
add_subdirectory(client)
|
add_subdirectory(client)
|
||||||
add_subdirectory(editor)
|
add_subdirectory(editor)
|
||||||
|
|
||||||
|
|
||||||
|
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests )
|
||||||
|
enable_testing()
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
Start testing: Dec 11 22:10 CET
|
|
||||||
----------------------------------------------------------
|
|
||||||
End testing: Dec 11 22:10 CET
|
|
||||||
1
compile_commands.json
Symbolic link
1
compile_commands.json
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
./build/compile_commands.json
|
||||||
|
|
@ -57,8 +57,6 @@ set(SOURCES
|
||||||
src/rendering/font.h
|
src/rendering/font.h
|
||||||
src/rendering/defaultmeshes.h
|
src/rendering/defaultmeshes.h
|
||||||
src/rendering/texture3d.cpp
|
src/rendering/texture3d.cpp
|
||||||
src/rendering/frustum.h
|
|
||||||
src/rendering/frustum.cpp
|
|
||||||
src/physics/world.h
|
src/physics/world.h
|
||||||
src/physics/world.cpp
|
src/physics/world.cpp
|
||||||
src/logger.cpp
|
src/logger.cpp
|
||||||
|
|
@ -91,8 +89,6 @@ set(SOURCES
|
||||||
src/objects/joint/weld.h
|
src/objects/joint/weld.h
|
||||||
src/objects/joint/snap.cpp
|
src/objects/joint/snap.cpp
|
||||||
src/objects/joint/rotatev.cpp
|
src/objects/joint/rotatev.cpp
|
||||||
src/objects/joint/motor6d.h
|
|
||||||
src/objects/joint/motor6d.cpp
|
|
||||||
src/objects/base/service.h
|
src/objects/base/service.h
|
||||||
src/objects/base/member.h
|
src/objects/base/member.h
|
||||||
src/objects/base/instance.h
|
src/objects/base/instance.h
|
||||||
|
|
@ -146,7 +142,6 @@ set(AUTOGEN_SOURCES
|
||||||
src/objects/script.h
|
src/objects/script.h
|
||||||
src/objects/joint/snap.h
|
src/objects/joint/snap.h
|
||||||
src/objects/joint/jointinstance.h
|
src/objects/joint/jointinstance.h
|
||||||
src/objects/joint/motor6d.h
|
|
||||||
src/objects/joint/rotatev.h
|
src/objects/joint/rotatev.h
|
||||||
src/objects/joint/rotate.h
|
src/objects/joint/rotate.h
|
||||||
src/objects/joint/weld.h
|
src/objects/joint/weld.h
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,8 @@ CPMAddPackage("gh:g-truc/glm#1.0.1")
|
||||||
CPMAddPackage(NAME Jolt GIT_REPOSITORY "https://github.com/jrouwe/JoltPhysics" VERSION 5.3.0 SOURCE_SUBDIR "Build")
|
CPMAddPackage(NAME Jolt GIT_REPOSITORY "https://github.com/jrouwe/JoltPhysics" VERSION 5.3.0 SOURCE_SUBDIR "Build")
|
||||||
CPMAddPackage("gh:zeux/pugixml@1.15")
|
CPMAddPackage("gh:zeux/pugixml@1.15")
|
||||||
|
|
||||||
# TODO: Figure out why this repo causes ft2 to keep rebuilding every time
|
CPMAddPackage(
|
||||||
# For now, I'll just make it use CPM as a fallback
|
NAME freetype
|
||||||
# CPMAddPackage(
|
|
||||||
CPMFindPackage(
|
|
||||||
NAME Freetype
|
|
||||||
GIT_REPOSITORY https://github.com/aseprite/freetype2.git
|
GIT_REPOSITORY https://github.com/aseprite/freetype2.git
|
||||||
GIT_TAG VER-2-10-0
|
GIT_TAG VER-2-10-0
|
||||||
VERSION 2.10.0
|
VERSION 2.10.0
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class NoSuchInstance : public Error {
|
||||||
|
|
||||||
class NoSuchService : public Error {
|
class NoSuchService : public Error {
|
||||||
public:
|
public:
|
||||||
inline NoSuchService(std::string className) : Error("NoSuchService", "Unknown service type " + className) {}
|
inline NoSuchService(std::string className) : Error("NoSuchService", "Cannot insert service of unknown type " + className) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class ServiceAlreadyExists : public Error {
|
class ServiceAlreadyExists : public Error {
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,8 @@ void DataModel::Init(bool runMode) {
|
||||||
// Init all services
|
// Init all services
|
||||||
for (auto [_, service] : this->services) {
|
for (auto [_, service] : this->services) {
|
||||||
service->InitService();
|
service->InitService();
|
||||||
|
if (runMode) service->OnRun();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deterministic run order
|
|
||||||
if (!runMode) return;
|
|
||||||
|
|
||||||
if (auto service = FindService<Workspace>()) service->OnRun();
|
|
||||||
if (auto service = FindService<ServerScriptService>()) service->OnRun();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DataModel::SaveToFile(std::optional<std::string> path) {
|
void DataModel::SaveToFile(std::optional<std::string> path) {
|
||||||
|
|
@ -119,13 +114,12 @@ result<std::shared_ptr<Service>, NoSuchService> DataModel::GetService(std::strin
|
||||||
}
|
}
|
||||||
|
|
||||||
result<nullable std::shared_ptr<Service>, NoSuchService> DataModel::FindService(std::string className) {
|
result<nullable std::shared_ptr<Service>, NoSuchService> DataModel::FindService(std::string className) {
|
||||||
if (services.count(className) != 0)
|
if (!INSTANCE_MAP[className] || (INSTANCE_MAP[className]->flags ^ (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) != 0) {
|
||||||
return std::dynamic_pointer_cast<Service>(services[className]);
|
|
||||||
|
|
||||||
if (!INSTANCE_MAP[className] || ~(INSTANCE_MAP[className]->flags & (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) == 0) {
|
|
||||||
return NoSuchService(className);
|
return NoSuchService(className);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (services.count(className) != 0)
|
||||||
|
return std::dynamic_pointer_cast<Service>(services[className]);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,20 +20,12 @@ void JointInstance::OnAncestryChanged(nullable std::shared_ptr<Instance>, nullab
|
||||||
void JointInstance::OnPartParamsUpdated() {
|
void JointInstance::OnPartParamsUpdated() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void JointInstance::OnPhysicsStep(float deltaTime) {
|
|
||||||
}
|
|
||||||
|
|
||||||
bool JointInstance::isDrivenJoint() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void JointInstance::Update() {
|
void JointInstance::Update() {
|
||||||
// To keep it simple compared to our previous algorithm, this one is pretty barebones:
|
// To keep it simple compared to our previous algorithm, this one is pretty barebones:
|
||||||
// 1. Every time we update, (whether our parent changed, or a property), destroy the current joints
|
// 1. Every time we update, (whether our parent changed, or a property), destroy the current joints
|
||||||
// 2. If the new configuration is valid, rebuild our joints
|
// 2. If the new configuration is valid, rebuild our joints
|
||||||
|
|
||||||
if (!jointWorkspace.expired()) {
|
if (!jointWorkspace.expired()) {
|
||||||
if (isDrivenJoint()) jointWorkspace.lock()->UntrackDrivenJoint(shared<JointInstance>());
|
|
||||||
jointWorkspace.lock()->DestroyJoint(joint);
|
jointWorkspace.lock()->DestroyJoint(joint);
|
||||||
if (!oldPart0.expired())
|
if (!oldPart0.expired())
|
||||||
oldPart0.lock()->untrackJoint(shared<JointInstance>());
|
oldPart0.lock()->untrackJoint(shared<JointInstance>());
|
||||||
|
|
@ -62,7 +54,6 @@ void JointInstance::Update() {
|
||||||
|
|
||||||
part0.lock()->trackJoint(shared<JointInstance>());
|
part0.lock()->trackJoint(shared<JointInstance>());
|
||||||
part1.lock()->trackJoint(shared<JointInstance>());
|
part1.lock()->trackJoint(shared<JointInstance>());
|
||||||
jointWorkspace.lock()->TrackDrivenJoint(shared<JointInstance>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nullable std::shared_ptr<Workspace> JointInstance::workspaceOfPart(std::shared_ptr<BasePart> part) {
|
nullable std::shared_ptr<Workspace> JointInstance::workspaceOfPart(std::shared_ptr<BasePart> part) {
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ protected:
|
||||||
inline void onUpdated(std::string property) { Update(); };
|
inline void onUpdated(std::string property) { Update(); };
|
||||||
|
|
||||||
virtual void buildJoint() = 0;
|
virtual void buildJoint() = 0;
|
||||||
virtual bool isDrivenJoint();
|
|
||||||
public:
|
public:
|
||||||
void Update();
|
void Update();
|
||||||
virtual void OnPartParamsUpdated();
|
virtual void OnPartParamsUpdated();
|
||||||
|
|
@ -42,8 +41,6 @@ public:
|
||||||
DEF_PROP_PHYS CFrame c0;
|
DEF_PROP_PHYS CFrame c0;
|
||||||
DEF_PROP_PHYS CFrame c1;
|
DEF_PROP_PHYS CFrame c1;
|
||||||
|
|
||||||
virtual void OnPhysicsStep(float deltaTime);
|
|
||||||
|
|
||||||
JointInstance(const InstanceType*);
|
JointInstance(const InstanceType*);
|
||||||
~JointInstance();
|
~JointInstance();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
#include "motor6d.h"
|
|
||||||
#include "datatypes/vector.h"
|
|
||||||
#include "objects/part/part.h"
|
|
||||||
#include "objects/service/workspace.h"
|
|
||||||
#include "rendering/renderer.h"
|
|
||||||
|
|
||||||
#define PI 3.14159
|
|
||||||
|
|
||||||
Motor6D::Motor6D(): JointInstance(&TYPE) {
|
|
||||||
}
|
|
||||||
|
|
||||||
Motor6D::~Motor6D() {
|
|
||||||
}
|
|
||||||
|
|
||||||
static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
|
|
||||||
|
|
||||||
void Motor6D::buildJoint() {
|
|
||||||
std::shared_ptr<Workspace> workspace = workspaceOfPart(part0.lock());
|
|
||||||
|
|
||||||
// Update Part1's rotation and cframe prior to creating the joint as reactphysics3d locks rotation based on how it
|
|
||||||
// used to be rather than specifying an anchor rotation, so whatever.
|
|
||||||
CFrame newFrame = part0.lock()->cframe * (c0 * c1.Inverse());
|
|
||||||
part1.lock()->cframe = newFrame;
|
|
||||||
PhysStepperJointInfo jointInfo(c0, c1, desiredAngle, maxVelocity);
|
|
||||||
|
|
||||||
this->joint = workspace->CreateJoint(jointInfo, part0.lock(), part1.lock());
|
|
||||||
jointWorkspace = workspace;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Motor6D::isDrivenJoint() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Motor6D::OnPhysicsStep(float deltaTime) {
|
|
||||||
// Tween currentAngle
|
|
||||||
float diffAngle = abs(currentAngle - desiredAngle);
|
|
||||||
if (diffAngle > abs(maxVelocity)) { // Don't tween if we're already close enough to being there
|
|
||||||
if (currentAngle < desiredAngle)
|
|
||||||
currentAngle += maxVelocity;
|
|
||||||
else
|
|
||||||
currentAngle -= maxVelocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shouldn't in theory be necessary, but just in case.
|
|
||||||
if (part0.expired() || part1.expired()) return;
|
|
||||||
|
|
||||||
// TODO: Re-add rotating only one part when both are unanchored, maybe?
|
|
||||||
joint.setTargetAngle(currentAngle);
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "objects/annotation.h"
|
|
||||||
#include "objects/base/instance.h"
|
|
||||||
#include "objects/joint/jointinstance.h"
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
class DEF_INST Motor6D : public JointInstance {
|
|
||||||
AUTOGEN_PREAMBLE
|
|
||||||
|
|
||||||
virtual void buildJoint() override;
|
|
||||||
void onUpdated(std::string);
|
|
||||||
|
|
||||||
void OnPhysicsStep(float deltaTime) override;
|
|
||||||
bool isDrivenJoint() override;
|
|
||||||
public:
|
|
||||||
Motor6D();
|
|
||||||
~Motor6D();
|
|
||||||
|
|
||||||
DEF_PROP float currentAngle = 0;
|
|
||||||
DEF_PROP float desiredAngle = 0;
|
|
||||||
DEF_PROP float maxVelocity = 0.1;
|
|
||||||
|
|
||||||
static inline std::shared_ptr<Motor6D> New() { return std::make_shared<Motor6D>(); };
|
|
||||||
static inline std::shared_ptr<Instance> Create() { return std::make_shared<Motor6D>(); };
|
|
||||||
};
|
|
||||||
|
|
@ -22,7 +22,7 @@ void RotateV::buildJoint() {
|
||||||
part1.lock()->cframe = newFrame;
|
part1.lock()->cframe = newFrame;
|
||||||
// Do NOT use Abs() in this scenario. For some reason that breaks it
|
// Do NOT use Abs() in this scenario. For some reason that breaks it
|
||||||
float vel = part0.lock()->GetSurfaceParamB(-c0.LookVector().Unit()) * 6.28;
|
float vel = part0.lock()->GetSurfaceParamB(-c0.LookVector().Unit()) * 6.28;
|
||||||
PhysMotorizedJointInfo jointInfo(c0, c1, vel);
|
PhysRotatingJointInfo jointInfo(c0, c1, vel);
|
||||||
|
|
||||||
this->joint = workspace->CreateJoint(jointInfo, part0.lock(), part1.lock());
|
this->joint = workspace->CreateJoint(jointInfo, part0.lock(), part1.lock());
|
||||||
jointWorkspace = workspace;
|
jointWorkspace = workspace;
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,22 @@
|
||||||
#include "meta.h"
|
#include "meta.h"
|
||||||
|
#include "objects/folder.h"
|
||||||
#define DECLTYPE(className) class className { public: const static InstanceType TYPE; };
|
#include "objects/hint.h"
|
||||||
|
#include "objects/joint/jointinstance.h"
|
||||||
DECLTYPE(DataModel);
|
#include "objects/joint/rotate.h"
|
||||||
DECLTYPE(BasePart);
|
#include "objects/joint/rotatev.h"
|
||||||
DECLTYPE(Part);
|
#include "objects/joint/weld.h"
|
||||||
DECLTYPE(WedgePart);
|
#include "objects/message.h"
|
||||||
DECLTYPE(Snap);
|
#include "objects/part/wedgepart.h"
|
||||||
DECLTYPE(Weld);
|
#include "objects/service/jointsservice.h"
|
||||||
DECLTYPE(Rotate);
|
#include "objects/model.h"
|
||||||
DECLTYPE(RotateV);
|
#include "objects/part/part.h"
|
||||||
DECLTYPE(Motor6D);
|
#include "objects/joint/snap.h"
|
||||||
DECLTYPE(JointInstance);
|
#include "objects/script.h"
|
||||||
DECLTYPE(Script);
|
#include "objects/service/script/scriptcontext.h"
|
||||||
DECLTYPE(Model);
|
#include "objects/service/script/serverscriptservice.h"
|
||||||
DECLTYPE(Message);
|
#include "objects/service/selection.h"
|
||||||
DECLTYPE(Hint);
|
#include "objects/service/workspace.h"
|
||||||
// DECLTYPE(Folder);
|
#include "objects/datamodel.h"
|
||||||
DECLTYPE(Workspace);
|
|
||||||
DECLTYPE(JointsService);
|
|
||||||
DECLTYPE(ScriptContext);
|
|
||||||
DECLTYPE(ServerScriptService);
|
|
||||||
DECLTYPE(Selection);
|
|
||||||
|
|
||||||
std::map<std::string, const InstanceType*> INSTANCE_MAP = {
|
std::map<std::string, const InstanceType*> INSTANCE_MAP = {
|
||||||
{ "Instance", &Instance::TYPE },
|
{ "Instance", &Instance::TYPE },
|
||||||
|
|
@ -34,7 +29,6 @@ std::map<std::string, const InstanceType*> INSTANCE_MAP = {
|
||||||
{ "Weld", &Weld::TYPE },
|
{ "Weld", &Weld::TYPE },
|
||||||
{ "Rotate", &Rotate::TYPE },
|
{ "Rotate", &Rotate::TYPE },
|
||||||
{ "RotateV", &RotateV::TYPE },
|
{ "RotateV", &RotateV::TYPE },
|
||||||
{ "Motor6D", &Motor6D::TYPE },
|
|
||||||
{ "JointInstance", &JointInstance::TYPE },
|
{ "JointInstance", &JointInstance::TYPE },
|
||||||
{ "Script", &Script::TYPE },
|
{ "Script", &Script::TYPE },
|
||||||
{ "Model", &Model::TYPE },
|
{ "Model", &Model::TYPE },
|
||||||
|
|
|
||||||
|
|
@ -305,11 +305,6 @@ void BasePart::MakeJoints() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasePart::UpdateNoBreakJoints() {
|
|
||||||
if (workspace() != nullptr)
|
|
||||||
workspace()->SyncPartPhysics(std::dynamic_pointer_cast<BasePart>(this->shared_from_this()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void BasePart::trackJoint(std::shared_ptr<JointInstance> joint) {
|
void BasePart::trackJoint(std::shared_ptr<JointInstance> joint) {
|
||||||
if (!joint->part0.expired() && joint->part0.lock() == shared_from_this()) {
|
if (!joint->part0.expired() && joint->part0.lock() == shared_from_this()) {
|
||||||
for (auto it = primaryJoints.begin(); it != primaryJoints.end();) {
|
for (auto it = primaryJoints.begin(); it != primaryJoints.end();) {
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,6 @@ public:
|
||||||
|
|
||||||
void MakeJoints();
|
void MakeJoints();
|
||||||
void BreakJoints();
|
void BreakJoints();
|
||||||
void UpdateNoBreakJoints();
|
|
||||||
|
|
||||||
// Calculate size of axis-aligned bounding box
|
// Calculate size of axis-aligned bounding box
|
||||||
Vector3 GetAABB();
|
Vector3 GetAABB();
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,6 @@ void ScriptContext::RunSleepingThreads() {
|
||||||
for (i = 0; i < sleepingThreads.size();) {
|
for (i = 0; i < sleepingThreads.size();) {
|
||||||
bool deleted = false;
|
bool deleted = false;
|
||||||
|
|
||||||
// TODO: Remove threads that belong to non-existent scripts
|
|
||||||
SleepingThread sleep = sleepingThreads[i];
|
SleepingThread sleep = sleepingThreads[i];
|
||||||
if (tu_clock_micros() >= sleep.targetTimeMicros) {
|
if (tu_clock_micros() >= sleep.targetTimeMicros) {
|
||||||
// Time args
|
// Time args
|
||||||
|
|
@ -168,12 +167,6 @@ void ScriptContext::RunSleepingThreads() {
|
||||||
schedTime = tu_clock_micros() - startTime;
|
schedTime = tu_clock_micros() - startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Temporary stopgap until RunSleepingThreads can clear threads that belong to
|
|
||||||
// scripts no longer parented to the DataModel
|
|
||||||
void ScriptContext::DebugClearSleepingThreads() {
|
|
||||||
sleepingThreads.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptContext::NewEnvironment(lua_State* L) {
|
void ScriptContext::NewEnvironment(lua_State* L) {
|
||||||
lua_newtable(L); // Env table
|
lua_newtable(L); // Env table
|
||||||
lua_newtable(L); // Metatable
|
lua_newtable(L); // Metatable
|
||||||
|
|
|
||||||
|
|
@ -21,19 +21,16 @@ class DEF_INST_SERVICE_(hidden) ScriptContext : public Service {
|
||||||
std::vector<SleepingThread> sleepingThreads;
|
std::vector<SleepingThread> sleepingThreads;
|
||||||
int lastScriptSourceId = 0;
|
int lastScriptSourceId = 0;
|
||||||
protected:
|
protected:
|
||||||
|
void InitService() override;
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ScriptContext();
|
ScriptContext();
|
||||||
~ScriptContext();
|
~ScriptContext();
|
||||||
|
|
||||||
void InitService() override;
|
|
||||||
|
|
||||||
lua_State* state;
|
lua_State* state;
|
||||||
void PushThreadSleep(lua_State* thread, float delay);
|
void PushThreadSleep(lua_State* thread, float delay);
|
||||||
void RunSleepingThreads();
|
void RunSleepingThreads();
|
||||||
// TEMPORARY. USED ONLY FOR TESTING
|
|
||||||
void DebugClearSleepingThreads();
|
|
||||||
|
|
||||||
// Generates an environment with a metatable and pushes it both the env table and metatable in order onto the stack
|
// Generates an environment with a metatable and pushes it both the env table and metatable in order onto the stack
|
||||||
void NewEnvironment(lua_State* state);
|
void NewEnvironment(lua_State* state);
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,13 @@
|
||||||
class DEF_INST_SERVICE_(explorer_icon="server-scripts", hidden) ServerScriptService : public Service {
|
class DEF_INST_SERVICE_(explorer_icon="server-scripts", hidden) ServerScriptService : public Service {
|
||||||
AUTOGEN_PREAMBLE
|
AUTOGEN_PREAMBLE
|
||||||
protected:
|
protected:
|
||||||
|
void InitService() override;
|
||||||
|
void OnRun() override;
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ServerScriptService();
|
ServerScriptService();
|
||||||
~ServerScriptService();
|
~ServerScriptService();
|
||||||
|
|
||||||
void InitService() override;
|
|
||||||
void OnRun() override;
|
|
||||||
|
|
||||||
static inline std::shared_ptr<Instance> Create() { return std::make_shared<ServerScriptService>(); };
|
static inline std::shared_ptr<Instance> Create() { return std::make_shared<ServerScriptService>(); };
|
||||||
};
|
};
|
||||||
|
|
@ -66,19 +66,4 @@ void Workspace::PhysicsStep(float deltaTime) {
|
||||||
parent->Destroy();
|
parent->Destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::shared_ptr<Instance>> Workspace::CastFrustum(Frustum frustum) {
|
|
||||||
std::vector<std::shared_ptr<Instance>> parts;
|
|
||||||
|
|
||||||
for (auto it = GetDescendantsStart(); it != GetDescendantsEnd(); it++) {
|
|
||||||
if (!it->IsA<BasePart>()) continue;
|
|
||||||
std::shared_ptr<BasePart> part = std::dynamic_pointer_cast<BasePart>(*it);
|
|
||||||
|
|
||||||
if (!part->locked && frustum.checkAABB(part->position(), part->GetAABB())) {
|
|
||||||
parts.push_back(part);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return parts;
|
|
||||||
}
|
}
|
||||||
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
#include "objects/annotation.h"
|
#include "objects/annotation.h"
|
||||||
#include "objects/base/service.h"
|
#include "objects/base/service.h"
|
||||||
#include "objects/joint/jointinstance.h"
|
|
||||||
#include "physics/world.h"
|
#include "physics/world.h"
|
||||||
#include "rendering/frustum.h"
|
#include "utils.h"
|
||||||
#include <glm/ext/vector_float3.hpp>
|
#include <glm/ext/vector_float3.hpp>
|
||||||
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
@ -34,14 +34,13 @@ class DEF_INST_SERVICE_(explorer_icon="workspace") Workspace : public Service {
|
||||||
std::shared_ptr<PhysWorld> physicsWorld;
|
std::shared_ptr<PhysWorld> physicsWorld;
|
||||||
friend PhysWorld;
|
friend PhysWorld;
|
||||||
protected:
|
protected:
|
||||||
|
void InitService() override;
|
||||||
|
void OnRun() override;
|
||||||
bool initialized = false;
|
bool initialized = false;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Workspace();
|
Workspace();
|
||||||
~Workspace();
|
~Workspace();
|
||||||
|
|
||||||
void InitService() override;
|
|
||||||
void OnRun() override;
|
|
||||||
|
|
||||||
std::recursive_mutex queueLock;
|
std::recursive_mutex queueLock;
|
||||||
|
|
||||||
|
|
@ -57,10 +56,6 @@ public:
|
||||||
inline PhysJoint CreateJoint(PhysJointInfo& info, std::shared_ptr<BasePart> part0, std::shared_ptr<BasePart> part1) { return physicsWorld->createJoint(info, part0, part1); }
|
inline PhysJoint CreateJoint(PhysJointInfo& info, std::shared_ptr<BasePart> part0, std::shared_ptr<BasePart> part1) { return physicsWorld->createJoint(info, part0, part1); }
|
||||||
inline void DestroyJoint(PhysJoint joint) { physicsWorld->destroyJoint(joint); }
|
inline void DestroyJoint(PhysJoint joint) { physicsWorld->destroyJoint(joint); }
|
||||||
|
|
||||||
inline void TrackDrivenJoint(std::shared_ptr<JointInstance> motor) { return physicsWorld->trackDrivenJoint(motor); }
|
|
||||||
inline void UntrackDrivenJoint(std::shared_ptr<JointInstance> motor) { return physicsWorld->untrackDrivenJoint(motor); }
|
|
||||||
|
|
||||||
void PhysicsStep(float deltaTime);
|
void PhysicsStep(float deltaTime);
|
||||||
inline std::optional<const RaycastResult> CastRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter = std::nullopt, unsigned short categoryMaskBits = 0xFFFF) { return physicsWorld->castRay(point, rotation, maxLength, filter, categoryMaskBits); }
|
inline std::optional<const RaycastResult> CastRayNearest(glm::vec3 point, glm::vec3 rotation, float maxLength, std::optional<RaycastFilter> filter = std::nullopt, unsigned short categoryMaskBits = 0xFFFF) { return physicsWorld->castRay(point, rotation, maxLength, filter, categoryMaskBits); }
|
||||||
std::vector<std::shared_ptr<Instance>> CastFrustum(Frustum frustum);
|
|
||||||
};
|
};
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
#include "datatypes/vector.h"
|
#include "datatypes/vector.h"
|
||||||
#include "enum/part.h"
|
#include "enum/part.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "objects/joint/jointinstance.h"
|
|
||||||
#include "objects/part/basepart.h"
|
#include "objects/part/basepart.h"
|
||||||
#include "objects/part/part.h"
|
#include "objects/part/part.h"
|
||||||
#include "objects/part/wedgepart.h"
|
#include "objects/part/wedgepart.h"
|
||||||
|
|
@ -38,8 +37,6 @@
|
||||||
#include <Jolt/Physics/Collision/NarrowPhaseQuery.h>
|
#include <Jolt/Physics/Collision/NarrowPhaseQuery.h>
|
||||||
#include <Jolt/Physics/Constraints/FixedConstraint.h>
|
#include <Jolt/Physics/Constraints/FixedConstraint.h>
|
||||||
#include <Jolt/Physics/Constraints/HingeConstraint.h>
|
#include <Jolt/Physics/Constraints/HingeConstraint.h>
|
||||||
#include <algorithm>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
static JPH::TempAllocator* allocator;
|
static JPH::TempAllocator* allocator;
|
||||||
|
|
@ -197,11 +194,6 @@ void PhysWorld::step(float deltaTime) {
|
||||||
part->cframe = CFrame(convert<Vector3>(interface.GetPosition(bodyID)), convert<glm::quat>(interface.GetRotation(bodyID)));
|
part->cframe = CFrame(convert<Vector3>(interface.GetPosition(bodyID)), convert<glm::quat>(interface.GetRotation(bodyID)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update joints
|
|
||||||
for (std::shared_ptr<JointInstance> joint : drivenJoints) {
|
|
||||||
joint->OnPhysicsStep(deltaTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
physTime = tu_clock_micros() - startTime;
|
physTime = tu_clock_micros() - startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -234,56 +226,28 @@ PhysJoint PhysWorld::createJoint(PhysJointInfo& type, std::shared_ptr<BasePart>
|
||||||
settings.mPoint2 = convert<JPH::Vec3>(info->c1.Position());
|
settings.mPoint2 = convert<JPH::Vec3>(info->c1.Position());
|
||||||
settings.mNormalAxis2 = convert<JPH::Vec3>(info->c1.RightVector());
|
settings.mNormalAxis2 = convert<JPH::Vec3>(info->c1.RightVector());
|
||||||
settings.mHingeAxis2 = convert<JPH::Vec3>(info->c1.LookVector());
|
settings.mHingeAxis2 = convert<JPH::Vec3>(info->c1.LookVector());
|
||||||
|
// settings.mMotorSettings = JPH::MotorSettings(1.0f, 1.0f);
|
||||||
// settings for Motor6D
|
|
||||||
settings.mMotorSettings.mSpringSettings.mFrequency = 20;
|
|
||||||
settings.mMotorSettings.mSpringSettings.mDamping = 1;
|
|
||||||
constraint = settings.Create(*part0->rigidBody.bodyImpl, *part1->rigidBody.bodyImpl);
|
constraint = settings.Create(*part0->rigidBody.bodyImpl, *part1->rigidBody.bodyImpl);
|
||||||
|
if (info->motorized) {
|
||||||
if (PhysMotorizedJointInfo* info = dynamic_cast<PhysMotorizedJointInfo*>(&type)) {
|
|
||||||
static_cast<JPH::HingeConstraint*>(constraint)->SetMotorState(JPH::EMotorState::Velocity);
|
static_cast<JPH::HingeConstraint*>(constraint)->SetMotorState(JPH::EMotorState::Velocity);
|
||||||
static_cast<JPH::HingeConstraint*>(constraint)->SetTargetAngularVelocity(-info->initialVelocity);
|
static_cast<JPH::HingeConstraint*>(constraint)->SetTargetAngularVelocity(-info->initialVelocity);
|
||||||
} else if (PhysStepperJointInfo* _ = dynamic_cast<PhysStepperJointInfo*>(&type)) {
|
|
||||||
static_cast<JPH::HingeConstraint*>(constraint)->SetMotorState(JPH::EMotorState::Position);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic();
|
panic();
|
||||||
}
|
}
|
||||||
|
|
||||||
worldImpl.AddConstraint(constraint);
|
worldImpl.AddConstraint(constraint);
|
||||||
return { constraint, this };
|
return { constraint };
|
||||||
}
|
|
||||||
|
|
||||||
void PhysWorld::trackDrivenJoint(std::shared_ptr<JointInstance> motor) {
|
|
||||||
drivenJoints.push_back(motor);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PhysWorld::untrackDrivenJoint(std::shared_ptr<JointInstance> motor) {
|
|
||||||
for (auto it = drivenJoints.begin(); it != drivenJoints.end();) {
|
|
||||||
if (*it == motor)
|
|
||||||
it = drivenJoints.erase(it);
|
|
||||||
else
|
|
||||||
it++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WATCH OUT! This should only be called for HingeConstraints.
|
// WATCH OUT! This should only be called for HingeConstraints.
|
||||||
// Can't use dynamic_cast because TwoBodyConstraint is not virtual
|
// Can't use dynamic_cast because TwoBodyConstraint is not virtual
|
||||||
void PhysJoint::setAngularVelocity(float velocity) {
|
void PhysJoint::setAngularVelocity(float velocity) {
|
||||||
JPH::HingeConstraint* constraint = static_cast<JPH::HingeConstraint*>(jointImpl);
|
JPH::HingeConstraint* constraint = static_cast<JPH::HingeConstraint*>(jointImpl);
|
||||||
|
if (!constraint) return;
|
||||||
constraint->SetTargetAngularVelocity(-velocity);
|
constraint->SetTargetAngularVelocity(-velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhysJoint::setTargetAngle(float angle) {
|
|
||||||
JPH::HingeConstraint* constraint = static_cast<JPH::HingeConstraint*>(jointImpl);
|
|
||||||
constraint->SetTargetAngle(angle);
|
|
||||||
|
|
||||||
// Wake up the part as it could be sleeping
|
|
||||||
JPH::BodyInterface& interface = parentWorld->worldImpl.GetBodyInterface();
|
|
||||||
JPH::BodyID bodies[] = {constraint->GetBody1()->GetID(), constraint->GetBody2()->GetID()};
|
|
||||||
interface.ActivateBodies(bodies, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PhysWorld::destroyJoint(PhysJoint joint) {
|
void PhysWorld::destroyJoint(PhysJoint joint) {
|
||||||
worldImpl.RemoveConstraint(joint.jointImpl);
|
worldImpl.RemoveConstraint(joint.jointImpl);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
#include <Jolt/Physics/Constraints/TwoBodyConstraint.h>
|
#include <Jolt/Physics/Constraints/TwoBodyConstraint.h>
|
||||||
|
|
||||||
class BasePart;
|
class BasePart;
|
||||||
class JointInstance;
|
|
||||||
class PhysWorld;
|
class PhysWorld;
|
||||||
|
|
||||||
struct PhysJointInfo { virtual ~PhysJointInfo() = default; protected: PhysJointInfo() = default; };
|
struct PhysJointInfo { virtual ~PhysJointInfo() = default; protected: PhysJointInfo() = default; };
|
||||||
|
|
@ -29,31 +28,19 @@ struct PhysFixedJointInfo : PhysJointInfo {
|
||||||
struct PhysRotatingJointInfo : PhysJointInfo {
|
struct PhysRotatingJointInfo : PhysJointInfo {
|
||||||
CFrame c0;
|
CFrame c0;
|
||||||
CFrame c1;
|
CFrame c1;
|
||||||
|
bool motorized;
|
||||||
inline PhysRotatingJointInfo(CFrame c0, CFrame c1) : c0(c0), c1(c1) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PhysMotorizedJointInfo : PhysRotatingJointInfo {
|
|
||||||
float initialVelocity;
|
float initialVelocity;
|
||||||
|
|
||||||
inline PhysMotorizedJointInfo(CFrame c0, CFrame c1, float initialVelocity) : PhysRotatingJointInfo(c0, c1), initialVelocity(initialVelocity) {}
|
inline PhysRotatingJointInfo(CFrame c0, CFrame c1) : c0(c0), c1(c1), motorized(false), initialVelocity(0.f){}
|
||||||
};
|
inline PhysRotatingJointInfo(CFrame c0, CFrame c1, float initialVelocity) : c0(c0), c1(c1), motorized(true), initialVelocity(initialVelocity) {}
|
||||||
|
|
||||||
struct PhysStepperJointInfo : PhysRotatingJointInfo {
|
|
||||||
float initialAngle;
|
|
||||||
float initialVelocity;
|
|
||||||
|
|
||||||
inline PhysStepperJointInfo(CFrame c0, CFrame c1, float initialAngle, float initialVelocity) : PhysRotatingJointInfo(c0, c1), initialAngle(initialAngle), initialVelocity(initialVelocity) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PhysWorld;
|
class PhysWorld;
|
||||||
struct PhysJoint {
|
struct PhysJoint {
|
||||||
public:
|
public:
|
||||||
JPH::TwoBodyConstraint* jointImpl;
|
JPH::TwoBodyConstraint* jointImpl;
|
||||||
PhysWorld* parentWorld;
|
|
||||||
|
|
||||||
void setAngularVelocity(float velocity);
|
void setAngularVelocity(float velocity);
|
||||||
void setTargetAngle(float angle);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RaycastResult;
|
struct RaycastResult;
|
||||||
|
|
@ -117,9 +104,7 @@ class PhysWorld : public std::enable_shared_from_this<PhysWorld> {
|
||||||
ObjectLayerPairFilter objectLayerPairFilter;
|
ObjectLayerPairFilter objectLayerPairFilter;
|
||||||
JPH::PhysicsSystem worldImpl;
|
JPH::PhysicsSystem worldImpl;
|
||||||
std::list<std::shared_ptr<BasePart>> simulatedBodies;
|
std::list<std::shared_ptr<BasePart>> simulatedBodies;
|
||||||
std::list<std::shared_ptr<JointInstance>> drivenJoints;
|
|
||||||
|
|
||||||
friend PhysJoint;
|
|
||||||
public:
|
public:
|
||||||
PhysWorld();
|
PhysWorld();
|
||||||
~PhysWorld();
|
~PhysWorld();
|
||||||
|
|
@ -132,11 +117,6 @@ public:
|
||||||
PhysJoint createJoint(PhysJointInfo& type, std::shared_ptr<BasePart> part0, std::shared_ptr<BasePart> part1);
|
PhysJoint createJoint(PhysJointInfo& type, std::shared_ptr<BasePart> part0, std::shared_ptr<BasePart> part1);
|
||||||
void destroyJoint(PhysJoint joint);
|
void destroyJoint(PhysJoint joint);
|
||||||
|
|
||||||
void trackDrivenJoint(std::shared_ptr<JointInstance> motor);
|
|
||||||
void untrackDrivenJoint(std::shared_ptr<JointInstance> motor);
|
|
||||||
|
|
||||||
void setCFrameInternal(std::shared_ptr<BasePart> part, CFrame frame);
|
|
||||||
|
|
||||||
inline const std::list<std::shared_ptr<BasePart>>& getSimulatedBodies() { return simulatedBodies; }
|
inline const std::list<std::shared_ptr<BasePart>>& getSimulatedBodies() { return simulatedBodies; }
|
||||||
void syncBodyProperties(std::shared_ptr<BasePart>);
|
void syncBodyProperties(std::shared_ptr<BasePart>);
|
||||||
std::optional<const RaycastResult> castRay(Vector3 point, Vector3 rotation, float maxLength, std::optional<RaycastFilter> filter, unsigned short categoryMaskBits);
|
std::optional<const RaycastResult> castRay(Vector3 point, Vector3 rotation, float maxLength, std::optional<RaycastFilter> filter, unsigned short categoryMaskBits);
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,6 @@ void drawText(std::shared_ptr<Font> font, std::string text, float x, float y, fl
|
||||||
// activate corresponding render state
|
// activate corresponding render state
|
||||||
glDisable(GL_CULL_FACE);
|
glDisable(GL_CULL_FACE);
|
||||||
glDisable(GL_DEPTH_TEST);
|
glDisable(GL_DEPTH_TEST);
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // TODO: Figure out why when changed to GL_ONE this causes graphical errors
|
|
||||||
|
|
||||||
fontShader->use();
|
fontShader->use();
|
||||||
fontShader->set("textColor", color);
|
fontShader->set("textColor", color);
|
||||||
|
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
||||||
#include "frustum.h"
|
|
||||||
#include "datatypes/vector.h"
|
|
||||||
#include <glm/ext/matrix_clip_space.hpp>
|
|
||||||
|
|
||||||
// https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling
|
|
||||||
|
|
||||||
// https://stackoverflow.com/q/66227192/16255372
|
|
||||||
FrustumPlane::FrustumPlane(Vector3 point, Vector3 normal) : normal(normal.Unit()), distance(normal.Unit().Dot(point)) {}
|
|
||||||
|
|
||||||
Frustum::Frustum() {}
|
|
||||||
|
|
||||||
Frustum::Frustum(const Camera cam, float aspect, float fovY, float zNear, float zFar) {
|
|
||||||
const float halfVSide = zFar * tanf(fovY * 0.5f);
|
|
||||||
const float halfHSide = halfVSide * aspect;
|
|
||||||
const glm::vec3 frontMultFar = zFar * -cam.cameraFront;
|
|
||||||
|
|
||||||
// Don't forget to normalize!!!
|
|
||||||
glm::vec3 camRight = glm::normalize(glm::cross(cam.cameraFront, cam.cameraUp)); // Technically this is left, but whatever
|
|
||||||
glm::vec3 trueCamUp = glm::cross(-cam.cameraFront, camRight);
|
|
||||||
near = { cam.cameraPos + zNear * -cam.cameraFront, -cam.cameraFront };
|
|
||||||
far = { cam.cameraPos + frontMultFar, cam.cameraFront };
|
|
||||||
right = { cam.cameraPos,
|
|
||||||
glm::cross(frontMultFar - camRight * halfHSide, trueCamUp) };
|
|
||||||
left = { cam.cameraPos,
|
|
||||||
glm::cross(trueCamUp,frontMultFar + camRight * halfHSide) };
|
|
||||||
top = { cam.cameraPos,
|
|
||||||
glm::cross(camRight, frontMultFar - trueCamUp * halfVSide) };
|
|
||||||
bottom = { cam.cameraPos,
|
|
||||||
glm::cross(frontMultFar + trueCamUp * halfVSide, camRight) };
|
|
||||||
}
|
|
||||||
|
|
||||||
Frustum Frustum::createSliced(const Camera cam, float width, float height, float left, float right, float top, float bottom, float fovY, float zNear, float zFar) {
|
|
||||||
Frustum frustum;
|
|
||||||
|
|
||||||
float aspect = width / height;
|
|
||||||
float halfVSide = zFar * tanf(fovY * 0.5f);
|
|
||||||
float halfHSide = halfVSide * aspect;
|
|
||||||
const glm::vec3 frontMultFar = zFar * -cam.cameraFront;
|
|
||||||
|
|
||||||
float leftSide = -halfHSide * (left / width * 2 - 1);
|
|
||||||
float rightSide = halfHSide * (right / width * 2 - 1);
|
|
||||||
float topSide = -halfVSide * (top / height * 2 - 1);
|
|
||||||
float bottomSide = halfVSide * (bottom / height * 2 - 1);
|
|
||||||
|
|
||||||
// Don't forget to normalize!!!
|
|
||||||
glm::vec3 camRight = glm::normalize(glm::cross(cam.cameraFront, cam.cameraUp)); // Technically this is left, but whatever
|
|
||||||
glm::vec3 trueCamUp = glm::cross(-cam.cameraFront, camRight);
|
|
||||||
frustum.near = { cam.cameraPos + zNear * -cam.cameraFront, -cam.cameraFront };
|
|
||||||
frustum.far = { cam.cameraPos + frontMultFar, cam.cameraFront };
|
|
||||||
frustum.right = { cam.cameraPos,
|
|
||||||
glm::cross(frontMultFar - camRight * rightSide, trueCamUp) };
|
|
||||||
frustum.left = { cam.cameraPos,
|
|
||||||
glm::cross(trueCamUp,frontMultFar + camRight * leftSide) };
|
|
||||||
frustum.top = { cam.cameraPos,
|
|
||||||
glm::cross(camRight, frontMultFar - trueCamUp * topSide) };
|
|
||||||
frustum.bottom = { cam.cameraPos,
|
|
||||||
glm::cross(frontMultFar + trueCamUp * bottomSide, camRight) };
|
|
||||||
return frustum;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FrustumPlane::checkPointForward(Vector3 point) {
|
|
||||||
return (normal.Dot(point) - distance) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool FrustumPlane::checkAABBForward(Vector3 center, Vector3 extents) {
|
|
||||||
// Not entirely sure how this algorithm works... but hey, when has that ever stopped me?
|
|
||||||
|
|
||||||
// https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling
|
|
||||||
// https://gdbooks.gitbooks.io/3dcollisions/content/Chapter2/static_aabb_plane.html
|
|
||||||
// Compute the projection interval radius of b onto L(t) = b.c + t * p.n
|
|
||||||
extents = extents * 0.5f;
|
|
||||||
const float r = extents.X() * std::abs(normal.X()) +
|
|
||||||
extents.Y() * std::abs(normal.Y()) + extents.Z() * std::abs(normal.Z());
|
|
||||||
|
|
||||||
return -r <= (normal.Dot(center) - distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Frustum::checkPoint(Vector3 point) {
|
|
||||||
return true
|
|
||||||
// TODO: Near and far are broken for some reason
|
|
||||||
// && near.checkPointForward(point)
|
|
||||||
// && far.checkPointForward(point)
|
|
||||||
&& left.checkPointForward(point)
|
|
||||||
&& right.checkPointForward(point)
|
|
||||||
&& top.checkPointForward(point)
|
|
||||||
&& bottom.checkPointForward(point)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Frustum::checkAABB(Vector3 center, Vector3 extents) {
|
|
||||||
return true
|
|
||||||
// TODO: Near and far are broken for some reason
|
|
||||||
// && near.checkAABBForward(center, extents)
|
|
||||||
// && far.checkAABBForward(center, extents)
|
|
||||||
&& left.checkAABBForward(center, extents)
|
|
||||||
&& right.checkAABBForward(center, extents)
|
|
||||||
&& top.checkAABBForward(center, extents)
|
|
||||||
&& bottom.checkAABBForward(center, extents)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "camera.h"
|
|
||||||
#include "datatypes/vector.h"
|
|
||||||
|
|
||||||
// https://learnopengl.com/Guest-Articles/2021/Scene/Frustum-Culling
|
|
||||||
|
|
||||||
struct FrustumPlane {
|
|
||||||
Vector3 normal;
|
|
||||||
float distance; // leastPoint = normal * distance
|
|
||||||
// leastPoint is the closest point to (0,0)
|
|
||||||
|
|
||||||
FrustumPlane(Vector3 point, Vector3 normal);
|
|
||||||
FrustumPlane() = default;
|
|
||||||
|
|
||||||
bool checkPointForward(Vector3);
|
|
||||||
bool checkAABBForward(Vector3 center, Vector3 extents);
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Frustum {
|
|
||||||
FrustumPlane near;
|
|
||||||
FrustumPlane far;
|
|
||||||
FrustumPlane left;
|
|
||||||
FrustumPlane right;
|
|
||||||
FrustumPlane top;
|
|
||||||
FrustumPlane bottom;
|
|
||||||
|
|
||||||
Frustum(const Camera cam, float aspect, float fovY, float zNear, float zFar);
|
|
||||||
static Frustum createSliced(const Camera cam, float width, float height, float left, float right, float top, float bottom, float fovY, float zNear, float zFar);
|
|
||||||
bool checkPoint(Vector3);
|
|
||||||
bool checkAABB(Vector3 center, Vector3 extents);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Frustum();
|
|
||||||
};
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
#include <glad/gl.h>
|
#include <glad/gl.h>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
#include <glm/ext.hpp>
|
#include <glm/ext.hpp>
|
||||||
#include <glm/ext/matrix_clip_space.hpp>
|
#include <glm/ext/matrix_clip_space.hpp>
|
||||||
#include <glm/ext/matrix_float4x4.hpp>
|
#include <glm/ext/matrix_float4x4.hpp>
|
||||||
|
|
@ -77,6 +79,8 @@ void renderInit(int width, int height) {
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glEnable(GL_MULTISAMPLE);
|
glEnable(GL_MULTISAMPLE);
|
||||||
glFrontFace(GL_CW);
|
glFrontFace(GL_CW);
|
||||||
|
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
debugFontTexture = new Texture("assets/textures/debugfnt.bmp", GL_RGB);
|
debugFontTexture = new Texture("assets/textures/debugfnt.bmp", GL_RGB);
|
||||||
|
|
||||||
|
|
@ -166,7 +170,7 @@ void renderParts() {
|
||||||
glEnable(GL_CULL_FACE);
|
glEnable(GL_CULL_FACE);
|
||||||
glCullFace(GL_BACK);
|
glCullFace(GL_BACK);
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
// Use shader
|
// Use shader
|
||||||
shader->use();
|
shader->use();
|
||||||
|
|
@ -230,7 +234,7 @@ void renderSurfaceExtras() {
|
||||||
glEnable(GL_CULL_FACE);
|
glEnable(GL_CULL_FACE);
|
||||||
glCullFace(GL_BACK);
|
glCullFace(GL_BACK);
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
// Use shader
|
// Use shader
|
||||||
ghostShader->use();
|
ghostShader->use();
|
||||||
|
|
@ -354,7 +358,7 @@ void renderAABB() {
|
||||||
glCullFace(GL_BACK);
|
glCullFace(GL_BACK);
|
||||||
glFrontFace(GL_CW);
|
glFrontFace(GL_CW);
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
// Use shader
|
// Use shader
|
||||||
ghostShader->use();
|
ghostShader->use();
|
||||||
|
|
@ -393,7 +397,7 @@ void renderWireframe() {
|
||||||
glCullFace(GL_BACK);
|
glCullFace(GL_BACK);
|
||||||
glFrontFace(GL_CW);
|
glFrontFace(GL_CW);
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
|
glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
|
||||||
|
|
||||||
// Use shader
|
// Use shader
|
||||||
|
|
@ -433,7 +437,7 @@ void renderOutlines() {
|
||||||
glEnable(GL_CULL_FACE);
|
glEnable(GL_CULL_FACE);
|
||||||
glCullFace(GL_BACK);
|
glCullFace(GL_BACK);
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
// Use shader
|
// Use shader
|
||||||
outlineShader->use();
|
outlineShader->use();
|
||||||
|
|
@ -496,7 +500,7 @@ void renderSelectionAssembly() {
|
||||||
glEnable(GL_CULL_FACE);
|
glEnable(GL_CULL_FACE);
|
||||||
glCullFace(GL_BACK);
|
glCullFace(GL_BACK);
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
PartAssembly selectionAssembly = PartAssembly::FromSelection(gDataModel->GetService<Selection>());
|
PartAssembly selectionAssembly = PartAssembly::FromSelection(gDataModel->GetService<Selection>());
|
||||||
|
|
||||||
|
|
@ -534,7 +538,7 @@ void renderRotationArcs() {
|
||||||
glCullFace(GL_BACK);
|
glCullFace(GL_BACK);
|
||||||
glFrontFace(GL_CW);
|
glFrontFace(GL_CW);
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
// Use shader
|
// Use shader
|
||||||
handleShader->use();
|
handleShader->use();
|
||||||
|
|
@ -632,6 +636,7 @@ void renderMessages() {
|
||||||
// glEnable(GL_DEPTH_TEST);
|
// glEnable(GL_DEPTH_TEST);
|
||||||
glDisable(GL_CULL_FACE);
|
glDisable(GL_CULL_FACE);
|
||||||
// glEnable(GL_BLEND);
|
// glEnable(GL_BLEND);
|
||||||
|
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
|
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
|
||||||
if (!it->IsA<Message>()) continue;
|
if (!it->IsA<Message>()) continue;
|
||||||
|
|
@ -648,7 +653,7 @@ void renderMessages() {
|
||||||
if (message->text == "") continue;
|
if (message->text == "") continue;
|
||||||
|
|
||||||
float strokedTextWidth = calcTextWidth(sansSerif, message->text, true);
|
float strokedTextWidth = calcTextWidth(sansSerif, message->text, true);
|
||||||
drawRect(0, 0, viewportWidth, viewportHeight, glm::vec4(0.5, 0.5, 0.5, 0.5));
|
drawRect(0, 0, viewportWidth, viewportHeight, glm::vec4(0.5));
|
||||||
drawText(sansSerif, message->text, ((float)viewportWidth - textWidth) / 2, ((float)viewportHeight - sansSerif->height) / 2, 1.f, glm::vec3(0), true);
|
drawText(sansSerif, message->text, ((float)viewportWidth - textWidth) / 2, ((float)viewportHeight - sansSerif->height) / 2, 1.f, glm::vec3(0), true);
|
||||||
drawText(sansSerif, message->text, ((float)viewportWidth - strokedTextWidth) / 2, ((float)viewportHeight - sansSerif->height) / 2, 1.f, glm::vec3(1), false);
|
drawText(sansSerif, message->text, ((float)viewportWidth - strokedTextWidth) / 2, ((float)viewportHeight - sansSerif->height) / 2, 1.f, glm::vec3(1), false);
|
||||||
}
|
}
|
||||||
|
|
@ -661,8 +666,6 @@ void render() {
|
||||||
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
// For some reason this is unset by QPainter, so we override it here
|
|
||||||
glEnable(GL_MULTISAMPLE);
|
|
||||||
|
|
||||||
renderSkyBox();
|
renderSkyBox();
|
||||||
renderHandles();
|
renderHandles();
|
||||||
|
|
@ -681,25 +684,11 @@ void render() {
|
||||||
// renderAABB();
|
// renderAABB();
|
||||||
|
|
||||||
renderTime = tu_clock_micros() - startTime;
|
renderTime = tu_clock_micros() - startTime;
|
||||||
|
|
||||||
identityShader->use();
|
|
||||||
identityShader->set("aColor", glm::vec4(1,0,0,1));
|
|
||||||
// Unbinding both is important or else it will mess up QPainter
|
|
||||||
// https://stackoverflow.com/a/47417780/16255372
|
|
||||||
glBindVertexArray(0);
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER,0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawRect(int x, int y, int width, int height, glm::vec4 color) {
|
void drawRect(int x, int y, int width, int height, glm::vec4 color) {
|
||||||
// GL_CULL_FACE has to be disabled as we are flipping the order of the vertices here, besides we don't really care about it
|
// GL_CULL_FACE has to be disabled as we are flipping the order of the vertices here, besides we don't really care about it
|
||||||
glDisable(GL_CULL_FACE);
|
glDisable(GL_CULL_FACE);
|
||||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
|
||||||
|
|
||||||
// Multiply color
|
|
||||||
float a = color.a;
|
|
||||||
color *= a;
|
|
||||||
color.a = a;
|
|
||||||
|
|
||||||
glm::mat4 model(1.0f); // Same applies to this VV
|
glm::mat4 model(1.0f); // Same applies to this VV
|
||||||
// Make sure to cast these to floats, as mat4<i> is a different type that is not compatible
|
// Make sure to cast these to floats, as mat4<i> is a different type that is not compatible
|
||||||
glm::mat4 proj = glm::ortho(0.f, (float)viewportWidth, (float)viewportHeight, 0.f, -1.f, 1.f);
|
glm::mat4 proj = glm::ortho(0.f, (float)viewportWidth, (float)viewportHeight, 0.f, -1.f, 1.f);
|
||||||
|
|
@ -719,4 +708,4 @@ void setViewport(int width, int height) {
|
||||||
|
|
||||||
void setDebugRendererEnabled(bool enabled) {
|
void setDebugRendererEnabled(bool enabled) {
|
||||||
debugRendererEnabled = enabled;
|
debugRendererEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
|
|
||||||
include(CPM)
|
include(CPM)
|
||||||
|
|
||||||
CPMAddPackage("gh:m-doescode/qcursorconstraints#eb674e1fab418c4c5ccb7c599c19bd2e8a062faf")
|
CPMAddPackage("gh:m-doescode/qcursorconstraints#cef1a31c0afad8ed3c95ee1a6bc531090805b510")
|
||||||
|
|
@ -1,17 +1,29 @@
|
||||||
#include <glad/gl.h>
|
#include <glad/gl.h>
|
||||||
|
#include <glm/common.hpp>
|
||||||
|
#include <glm/vector_relational.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <miniaudio.h>
|
#include <miniaudio.h>
|
||||||
#include <qcursorconstraints.h>
|
#include <qcursorconstraints.h>
|
||||||
#include <QPainter>
|
#include <qnamespace.h>
|
||||||
|
#include <qguiapplication.h>
|
||||||
|
#include <string>
|
||||||
#include "./ui_mainwindow.h"
|
#include "./ui_mainwindow.h"
|
||||||
#include "common.h"
|
#include "mainglwidget.h"
|
||||||
|
#include "datatypes/vector.h"
|
||||||
|
#include "enum/surface.h"
|
||||||
|
#include "handles.h"
|
||||||
|
#include "logger.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
|
#include "common.h"
|
||||||
#include "math_helper.h"
|
#include "math_helper.h"
|
||||||
|
#include "objects/base/instance.h"
|
||||||
|
#include "objects/pvinstance.h"
|
||||||
#include "objects/service/selection.h"
|
#include "objects/service/selection.h"
|
||||||
#include "partassembly.h"
|
#include "partassembly.h"
|
||||||
#include "rendering/renderer.h"
|
#include "rendering/renderer.h"
|
||||||
#include "mainglwidget.h"
|
#include "rendering/shader.h"
|
||||||
|
#include "datatypes/variant.h"
|
||||||
|
#include "undohistory.h"
|
||||||
|
|
||||||
#define PI 3.14159
|
#define PI 3.14159
|
||||||
#define M_mainWindow dynamic_cast<MainWindow*>(window())
|
#define M_mainWindow dynamic_cast<MainWindow*>(window())
|
||||||
|
|
@ -56,16 +68,9 @@ glm::vec2 secondPoint;
|
||||||
|
|
||||||
extern std::weak_ptr<BasePart> draggingObject;
|
extern std::weak_ptr<BasePart> draggingObject;
|
||||||
extern std::optional<HandleFace> draggingHandle;
|
extern std::optional<HandleFace> draggingHandle;
|
||||||
|
extern Shader* shader;
|
||||||
void MainGLWidget::paintGL() {
|
void MainGLWidget::paintGL() {
|
||||||
QPainter painter(this);
|
|
||||||
|
|
||||||
painter.beginNativePainting();
|
|
||||||
::render();
|
::render();
|
||||||
painter.endNativePainting();
|
|
||||||
|
|
||||||
painter.setPen(QColor(200, 200, 200));
|
|
||||||
painter.drawRect(selectionLasso);
|
|
||||||
painter.end();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isMouseRightDragging = false;
|
bool isMouseRightDragging = false;
|
||||||
|
|
@ -321,7 +326,7 @@ std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
|
void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
|
||||||
if (isMouseRightDragging || selectionLasso != QRect{0,0,0,0}) return; // Don't change the cursor while it is intentionally blank
|
if (isMouseRightDragging) return; // Don't change the cursor while it is intentionally blank
|
||||||
QPoint position = evt->pos();
|
QPoint position = evt->pos();
|
||||||
|
|
||||||
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
|
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
|
||||||
|
|
@ -364,85 +369,6 @@ void MainGLWidget::mouseMoveEvent(QMouseEvent* evt) {
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectionLasso != QRect {0,0,0,0}) {
|
|
||||||
selectionLasso = {selectionLasso.topLeft(), evt->pos()};
|
|
||||||
|
|
||||||
float left = std::min(selectionLasso.left(), selectionLasso.right());
|
|
||||||
float right = std::max(selectionLasso.left(), selectionLasso.right());
|
|
||||||
float top = std::min(selectionLasso.top(), selectionLasso.bottom());
|
|
||||||
float bottom = std::max(selectionLasso.top(), selectionLasso.bottom());
|
|
||||||
|
|
||||||
Frustum selectionFrustum = Frustum::createSliced(camera, width(), height(), left, right, top, bottom, glm::radians(45.f), 0.1f, 1000.0f);
|
|
||||||
|
|
||||||
std::vector<std::shared_ptr<Instance>> castedParts = gWorkspace()->CastFrustum(selectionFrustum);
|
|
||||||
gDataModel->GetService<Selection>()->Set(castedParts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MainGLWidget::handlePartClick(QMouseEvent* evt) {
|
|
||||||
QPoint position = evt->pos();
|
|
||||||
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
|
|
||||||
|
|
||||||
// raycast part
|
|
||||||
std::shared_ptr<Selection> selection = gDataModel->GetService<Selection>();
|
|
||||||
std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000);
|
|
||||||
if (!rayHit || !rayHit->hitPart) { selection->Set({}); return false; }
|
|
||||||
std::shared_ptr<BasePart> part = rayHit->hitPart;
|
|
||||||
if (part->locked) { selection->Set({}); return false; }
|
|
||||||
|
|
||||||
std::shared_ptr<PVInstance> selObject = part;
|
|
||||||
|
|
||||||
// Traverse to the root model
|
|
||||||
if (~evt->modifiers() & Qt::AltModifier) {
|
|
||||||
nullable std::shared_ptr<Instance> nextParent = selObject->GetParent();
|
|
||||||
while (nextParent && nextParent->IsA("Model")) {
|
|
||||||
selObject = std::dynamic_pointer_cast<PVInstance>(nextParent); nextParent = selObject->GetParent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
initialAssembly = PartAssembly::FromSelection({selObject});
|
|
||||||
initialFrame = initialAssembly.assemblyOrigin();
|
|
||||||
initialHitPos = rayHit->worldPoint;
|
|
||||||
initialHitNormal = rayHit->worldNormal;
|
|
||||||
|
|
||||||
// Handle surface tool
|
|
||||||
if (mainWindow()->selectedTool >= TOOL_SMOOTH) {
|
|
||||||
Vector3 localNormal = part->cframe.Inverse().Rotation() * rayHit->worldNormal;
|
|
||||||
NormalId face = faceFromNormal(localNormal);
|
|
||||||
SurfaceType surface = SurfaceType(mainWindow()->selectedTool - TOOL_SMOOTH);
|
|
||||||
std::string surfacePropertyName = EnumType::NormalId.FromValue(face)->Name() + "Surface";
|
|
||||||
|
|
||||||
// Get old surface and set new surface
|
|
||||||
EnumItem newSurface = EnumType::SurfaceType.FromValue((int)surface).value();
|
|
||||||
EnumItem oldSurface = part->GetProperty(surfacePropertyName).expect().get<EnumItem>();
|
|
||||||
part->SetProperty(surfacePropertyName, newSurface).expect();
|
|
||||||
|
|
||||||
M_mainWindow->undoManager.PushState({UndoStatePropertyChanged { part, surfacePropertyName, oldSurface, newSurface }});
|
|
||||||
|
|
||||||
if (mainWindow()->editSoundEffects && QFile::exists("./assets/excluded/electronicpingshort.wav"))
|
|
||||||
playSound("./assets/excluded/electronicpingshort.wav");
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
//part.selected = true;
|
|
||||||
isMouseDragging = true;
|
|
||||||
draggingObject = part;
|
|
||||||
initialTransforms = PartAssembly::FromSelection({part}).GetCurrentTransforms();
|
|
||||||
if (evt->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier)) {
|
|
||||||
auto sel = selection->Get();
|
|
||||||
if (std::find(sel.begin(), sel.end(), selObject) == sel.end())
|
|
||||||
selection->Add({ selObject });
|
|
||||||
else
|
|
||||||
selection->Remove({ selObject });
|
|
||||||
} else {
|
|
||||||
selection->Set({ selObject });
|
|
||||||
}
|
|
||||||
// Disable bit so that we can ignore the part while raycasting
|
|
||||||
// part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
||||||
|
|
@ -474,9 +400,63 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handlePartClick(evt)) return;
|
// raycast part
|
||||||
|
std::shared_ptr<Selection> selection = gDataModel->GetService<Selection>();
|
||||||
|
std::optional<const RaycastResult> rayHit = gWorkspace()->CastRayNearest(camera.cameraPos, pointDir, 50000);
|
||||||
|
if (!rayHit || !rayHit->hitPart) { selection->Set({}); return; }
|
||||||
|
std::shared_ptr<BasePart> part = rayHit->hitPart;
|
||||||
|
if (part->locked) { selection->Set({}); return; }
|
||||||
|
|
||||||
selectionLasso = {position, QSize {0, 0}};
|
std::shared_ptr<PVInstance> selObject = part;
|
||||||
|
|
||||||
|
// Traverse to the root model
|
||||||
|
if (~evt->modifiers() & Qt::AltModifier) {
|
||||||
|
nullable std::shared_ptr<Instance> nextParent = selObject->GetParent();
|
||||||
|
while (nextParent && nextParent->IsA("Model")) {
|
||||||
|
selObject = std::dynamic_pointer_cast<PVInstance>(nextParent); nextParent = selObject->GetParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initialAssembly = PartAssembly::FromSelection({selObject});
|
||||||
|
initialFrame = initialAssembly.assemblyOrigin();
|
||||||
|
initialHitPos = rayHit->worldPoint;
|
||||||
|
initialHitNormal = rayHit->worldNormal;
|
||||||
|
|
||||||
|
// Handle surface tool
|
||||||
|
if (mainWindow()->selectedTool >= TOOL_SMOOTH) {
|
||||||
|
Vector3 localNormal = part->cframe.Inverse().Rotation() * rayHit->worldNormal;
|
||||||
|
NormalId face = faceFromNormal(localNormal);
|
||||||
|
SurfaceType surface = SurfaceType(mainWindow()->selectedTool - TOOL_SMOOTH);
|
||||||
|
std::string surfacePropertyName = EnumType::NormalId.FromValue(face)->Name() + "Surface";
|
||||||
|
|
||||||
|
// Get old surface and set new surface
|
||||||
|
EnumItem newSurface = EnumType::SurfaceType.FromValue((int)surface).value();
|
||||||
|
EnumItem oldSurface = part->GetProperty(surfacePropertyName).expect().get<EnumItem>();
|
||||||
|
part->SetProperty(surfacePropertyName, newSurface).expect();
|
||||||
|
|
||||||
|
M_mainWindow->undoManager.PushState({UndoStatePropertyChanged { part, surfacePropertyName, oldSurface, newSurface }});
|
||||||
|
|
||||||
|
if (mainWindow()->editSoundEffects && QFile::exists("./assets/excluded/electronicpingshort.wav"))
|
||||||
|
playSound("./assets/excluded/electronicpingshort.wav");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//part.selected = true;
|
||||||
|
isMouseDragging = true;
|
||||||
|
draggingObject = part;
|
||||||
|
initialTransforms = PartAssembly::FromSelection({part}).GetCurrentTransforms();
|
||||||
|
if (evt->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier)) {
|
||||||
|
auto sel = selection->Get();
|
||||||
|
if (std::find(sel.begin(), sel.end(), selObject) == sel.end())
|
||||||
|
selection->Add({ selObject });
|
||||||
|
else
|
||||||
|
selection->Remove({ selObject });
|
||||||
|
} else {
|
||||||
|
selection->Set({ selObject });
|
||||||
|
}
|
||||||
|
// Disable bit so that we can ignore the part while raycasting
|
||||||
|
// part->rigidBody->getCollider(0)->setCollisionCategoryBits(0b10);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} default:
|
} default:
|
||||||
|
|
@ -491,7 +471,6 @@ void MainGLWidget::mouseReleaseEvent(QMouseEvent* evt) {
|
||||||
isMouseDragging = false;
|
isMouseDragging = false;
|
||||||
draggingObject = {};
|
draggingObject = {};
|
||||||
draggingHandle = std::nullopt;
|
draggingHandle = std::nullopt;
|
||||||
selectionLasso = {0,0,0,0};
|
|
||||||
setCursor(Qt::ArrowCursor);
|
setCursor(Qt::ArrowCursor);
|
||||||
|
|
||||||
if (!initialTransforms.empty()) {
|
if (!initialTransforms.empty()) {
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
#ifndef MAINGLWIDGET_H
|
#ifndef MAINGLWIDGET_H
|
||||||
#define MAINGLWIDGET_H
|
#define MAINGLWIDGET_H
|
||||||
|
|
||||||
#include <glm/fwd.hpp>
|
#include "objects/part/part.h"
|
||||||
#include <memory>
|
#include "qevent.h"
|
||||||
#include <QEvent>
|
|
||||||
#include <QOpenGLWidget>
|
#include <QOpenGLWidget>
|
||||||
#include <QMenu>
|
#include <QWidget>
|
||||||
|
#include <memory>
|
||||||
|
#include <qmenu.h>
|
||||||
|
|
||||||
class BasePart;
|
|
||||||
class HandleFace;
|
class HandleFace;
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
|
|
||||||
|
|
@ -28,7 +28,6 @@ protected:
|
||||||
void handleLinearTransform(QMouseEvent* evt);
|
void handleLinearTransform(QMouseEvent* evt);
|
||||||
void handleRotationalTransform(QMouseEvent* evt);
|
void handleRotationalTransform(QMouseEvent* evt);
|
||||||
void handleCursorChange(QMouseEvent* evt);
|
void handleCursorChange(QMouseEvent* evt);
|
||||||
bool handlePartClick(QMouseEvent* evt);
|
|
||||||
void startLinearTransform(QMouseEvent* evt);
|
void startLinearTransform(QMouseEvent* evt);
|
||||||
std::optional<HandleFace> raycastHandle(glm::vec3 pointDir);
|
std::optional<HandleFace> raycastHandle(glm::vec3 pointDir);
|
||||||
|
|
||||||
|
|
@ -43,8 +42,6 @@ protected:
|
||||||
|
|
||||||
MainWindow* mainWindow();
|
MainWindow* mainWindow();
|
||||||
float snappingFactor();
|
float snappingFactor();
|
||||||
|
|
||||||
QRect selectionLasso;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MAINGLWIDGET_H
|
#endif // MAINGLWIDGET_H
|
||||||
|
|
|
||||||
|
|
@ -95,9 +95,8 @@ void PlaceDocument::timerEvent(QTimerEvent* evt) {
|
||||||
|
|
||||||
placeWidget->repaint();
|
placeWidget->repaint();
|
||||||
placeWidget->updateCycle();
|
placeWidget->updateCycle();
|
||||||
if (_runState != RUN_RUNNING) return;
|
|
||||||
gDataModel->GetService<ScriptContext>()->RunSleepingThreads();
|
gDataModel->GetService<ScriptContext>()->RunSleepingThreads();
|
||||||
gDataModel->GetService<Workspace>()->PhysicsStep(0.033);
|
if (_runState == RUN_RUNNING) gDataModel->GetService<Workspace>()->PhysicsStep(0.033);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
22
justfile
22
justfile
|
|
@ -1,22 +0,0 @@
|
||||||
help:
|
|
||||||
just -l
|
|
||||||
|
|
||||||
configure:
|
|
||||||
cmake -Bbuild -DCMAKE_BUILD_TYPE=Debug .
|
|
||||||
|
|
||||||
# Commented out configure because it takes unnecessarily long
|
|
||||||
# Just run configure manually if you've made any changes
|
|
||||||
build: #configure
|
|
||||||
cmake --build build -j$(nproc)
|
|
||||||
|
|
||||||
editor: build
|
|
||||||
./build/bin/editor
|
|
||||||
|
|
||||||
test: build
|
|
||||||
ctest --test-dir=build
|
|
||||||
|
|
||||||
test-v: build
|
|
||||||
ctest --test-dir=build --rerun-failed --output-on-failure
|
|
||||||
|
|
||||||
test-dbg: build
|
|
||||||
gdb -q ./build/bin/obtest
|
|
||||||
|
|
@ -1,12 +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 ()
|
||||||
|
|
||||||
include(${CMAKE_CURRENT_SOURCE_DIR}/deps.cmake)
|
create_test(lua src/luatest.cpp)
|
||||||
|
create_test(luasched src/luaschedtest.cpp)
|
||||||
|
create_test(luasignal src/luasignaltest.cpp)
|
||||||
|
|
||||||
add_executable(obtest
|
# https://stackoverflow.com/a/36729074/16255372
|
||||||
src/common.cpp
|
add_custom_target(check ${CMAKE_CTEST_COMMAND} --output-on-failure WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||||
src/lua/luasched.cpp
|
|
||||||
src/lua/luasignal.cpp
|
|
||||||
src/lua/luageneric.cpp
|
|
||||||
)
|
|
||||||
target_link_libraries(obtest PRIVATE openblocks Catch2::Catch2WithMain)
|
|
||||||
target_include_directories(obtest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
|
||||||
catch_discover_tests(obtest)
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
|
|
||||||
include(CPM)
|
|
||||||
|
|
||||||
CPMAddPackage("gh:catchorg/Catch2@3.8.1")
|
|
||||||
|
|
||||||
list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras)
|
|
||||||
include(CTest)
|
|
||||||
include(Catch)
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
#include <catch2/reporters/catch_reporter_event_listener.hpp>
|
|
||||||
#include <catch2/reporters/catch_reporter_registrars.hpp>
|
|
||||||
|
|
||||||
#include "logger.h"
|
|
||||||
#include "objects/base/instance.h"
|
|
||||||
#include "objects/service/script/scriptcontext.h"
|
|
||||||
#include "objects/service/script/serverscriptservice.h"
|
|
||||||
#include "objects/service/workspace.h"
|
|
||||||
#include "testcommon.h"
|
|
||||||
|
|
||||||
std::shared_ptr<DataModel> gTestModel;
|
|
||||||
std::stringstream testLogOutput;
|
|
||||||
|
|
||||||
class commonTestListener : public Catch::EventListenerBase {
|
|
||||||
public:
|
|
||||||
using Catch::EventListenerBase::EventListenerBase;
|
|
||||||
|
|
||||||
void testRunStarting(Catch::TestRunInfo const&) override {
|
|
||||||
// TODO: Make physicsInit optional in headless environments
|
|
||||||
physicsInit();
|
|
||||||
|
|
||||||
gTestModel = DataModel::New();
|
|
||||||
gTestModel->Init(true);
|
|
||||||
Logger::initTest(&testLogOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testRunEnded(Catch::TestRunStats const&) override {
|
|
||||||
gTestModel = nullptr;
|
|
||||||
physicsDeinit();
|
|
||||||
Logger::initTest(nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
void testCasePartialStarting(const Catch::TestCaseInfo &testInfo, uint64_t partNumber) override {
|
|
||||||
// Clear the log output prior to each test
|
|
||||||
testLogOutput.str("");
|
|
||||||
}
|
|
||||||
|
|
||||||
void testCasePartialEnded(const Catch::TestCaseStats &testCaseStats, uint64_t partNumber) override {
|
|
||||||
auto ctx = gTestModel->GetService<ScriptContext>();
|
|
||||||
ctx->DebugClearSleepingThreads();
|
|
||||||
|
|
||||||
// Clean up remaining scripts from ServerScriptService
|
|
||||||
for (auto& obj : gTestModel->GetService<ServerScriptService>()->GetChildren()) {
|
|
||||||
obj->Destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also clear workspace
|
|
||||||
for (auto& obj : gTestModel->GetService<Workspace>()->GetChildren()) {
|
|
||||||
obj->Destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
CATCH_REGISTER_LISTENER(commonTestListener)
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
#include <catch2/catch_test_macros.hpp>
|
|
||||||
|
|
||||||
#include "testcommon.h"
|
|
||||||
#include "testutil.h"
|
|
||||||
|
|
||||||
TEST_CASE("Generic lua test", "[luageneric]") {
|
|
||||||
auto m = gTestModel;
|
|
||||||
|
|
||||||
SECTION("Script output") {
|
|
||||||
REQUIRE(luaEvalOut(m, "print('Hello, world!')") == "INFO: Hello, world!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// SECTION("Script warning") {
|
|
||||||
// REQUIRE(luaEvalOut(m, "warn('Some warning here.')") == "WARN: Some warning here.\n");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// SECTION("Script error") {
|
|
||||||
// REQUIRE(luaEvalOut(m, "error('An error!')") == "ERROR: An error!.\n");
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
#include <catch2/catch_test_macros.hpp>
|
|
||||||
|
|
||||||
#include "objects/service/script/scriptcontext.h"
|
|
||||||
#include "testcommon.h"
|
|
||||||
#include "testutil.h"
|
|
||||||
#include "timeutil.h"
|
|
||||||
|
|
||||||
static auto& m = gTestModel;
|
|
||||||
static auto& out = testLogOutput;
|
|
||||||
|
|
||||||
TEST_CASE("Wait with delay") {
|
|
||||||
auto ctx = m->GetService<ScriptContext>();
|
|
||||||
|
|
||||||
tu_set_override(0);
|
|
||||||
luaEval(m, "wait(1) print('Wait')");
|
|
||||||
|
|
||||||
SECTION("Empty output at 0s") {
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("Empty output at 0.5s") {
|
|
||||||
TT_ADVANCETIME(0.5);
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("Print output at 1s") {
|
|
||||||
TT_ADVANCETIME(1);
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "INFO: Wait\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Wait with minimum delay") {
|
|
||||||
auto ctx = m->GetService<ScriptContext>();
|
|
||||||
|
|
||||||
tu_set_override(0);
|
|
||||||
luaEval(m, "wait(0) print('Wait')");
|
|
||||||
|
|
||||||
SECTION("Empty output at 0s") {
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("Empty output at 0.02s") {
|
|
||||||
TT_ADVANCETIME(0.02);
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("Print output at 0.03s") {
|
|
||||||
TT_ADVANCETIME(0.03);
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "INFO: Wait\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Run callback after delay") {
|
|
||||||
auto ctx = m->GetService<ScriptContext>();
|
|
||||||
|
|
||||||
tu_set_override(0);
|
|
||||||
luaEval(m, "delay(1, function() print('Delay') end)");
|
|
||||||
|
|
||||||
SECTION("Empty output at 0s") {
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("Empty output at 0.5s") {
|
|
||||||
TT_ADVANCETIME(0.5);
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("Print output at 1s") {
|
|
||||||
TT_ADVANCETIME(1);
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "INFO: Delay\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
#include <catch2/catch_test_macros.hpp>
|
|
||||||
|
|
||||||
#include "objects/part/part.h"
|
|
||||||
#include "objects/service/script/scriptcontext.h"
|
|
||||||
#include "objects/service/workspace.h"
|
|
||||||
#include "testcommon.h"
|
|
||||||
#include "testutil.h"
|
|
||||||
#include "timeutil.h"
|
|
||||||
|
|
||||||
static auto& m = gTestModel;
|
|
||||||
static auto& out = testLogOutput;
|
|
||||||
|
|
||||||
TEST_CASE("Connect to event") {
|
|
||||||
auto ws = m->GetService<Workspace>();
|
|
||||||
auto part = Part::New();
|
|
||||||
ws->AddChild(part);
|
|
||||||
|
|
||||||
luaEval(m, "workspace.Part.Touched:Connect(function() print('Fired!') end)");
|
|
||||||
|
|
||||||
SECTION("Single fire") {
|
|
||||||
part->Touched->Fire();
|
|
||||||
REQUIRE(out.str() == "INFO: Fired!\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("Double fire") {
|
|
||||||
part->Touched->Fire();
|
|
||||||
part->Touched->Fire();
|
|
||||||
REQUIRE(out.str() == "INFO: Fired!\nINFO: Fired!\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Wait within event listener") {
|
|
||||||
auto ctx = m->GetService<ScriptContext>();
|
|
||||||
auto ws = m->GetService<Workspace>();
|
|
||||||
auto part = Part::New();
|
|
||||||
ws->AddChild(part);
|
|
||||||
|
|
||||||
tu_set_override(0);
|
|
||||||
luaEval(m, "workspace.Part.Touched:Connect(function() print('Fired!') wait(1) print('Waited') end)");
|
|
||||||
|
|
||||||
SECTION("Single fire") {
|
|
||||||
part->Touched->Fire();
|
|
||||||
REQUIRE(out.str() == "INFO: Fired!\n");
|
|
||||||
TT_ADVANCETIME(0.5);
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "INFO: Fired!\n");
|
|
||||||
TT_ADVANCETIME(0.5);
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "INFO: Fired!\nINFO: Waited\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
SECTION("Nested double fire") {
|
|
||||||
part->Touched->Fire();
|
|
||||||
TT_ADVANCETIME(0.2);
|
|
||||||
part->Touched->Fire();
|
|
||||||
REQUIRE(out.str() == "INFO: Fired!\nINFO: Fired!\n");
|
|
||||||
TT_ADVANCETIME(1-0.2); // Small extra delay is necessary because floating point math
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "INFO: Fired!\nINFO: Fired!\nINFO: Waited\n");
|
|
||||||
TT_ADVANCETIME(0.2);
|
|
||||||
ctx->RunSleepingThreads();
|
|
||||||
REQUIRE(out.str() == "INFO: Fired!\nINFO: Fired!\nINFO: Waited\nINFO: Waited\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_CASE("Wait for event") {
|
|
||||||
auto ctx = m->GetService<ScriptContext>();
|
|
||||||
auto ws = m->GetService<Workspace>();
|
|
||||||
auto part = Part::New();
|
|
||||||
ws->AddChild(part);
|
|
||||||
|
|
||||||
tu_set_override(0);
|
|
||||||
luaEval(m, "workspace.Part.Touched:Wait() print('Fired!')");
|
|
||||||
|
|
||||||
part->Touched->Fire();
|
|
||||||
REQUIRE(out.str() == "INFO: Fired!\n");
|
|
||||||
part->Touched->Fire(); // Firing again should not affect output
|
|
||||||
REQUIRE(out.str() == "INFO: Fired!\n");
|
|
||||||
}
|
|
||||||
77
tests/src/luaschedtest.cpp
Normal file
77
tests/src/luaschedtest.cpp
Normal 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
103
tests/src/luasignaltest.cpp
Normal 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
20
tests/src/luatest.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "objects/datamodel.h"
|
|
||||||
|
|
||||||
extern std::shared_ptr<DataModel> gTestModel;
|
|
||||||
extern std::stringstream testLogOutput;
|
|
||||||
|
|
@ -1,31 +1,70 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "objects/datamodel.h"
|
// https://bastian.rieck.me/blog/2017/simple_unit_tests/
|
||||||
#include "objects/script.h"
|
|
||||||
#include "objects/service/script/serverscriptservice.h"
|
#include <algorithm>
|
||||||
#include "testcommon.h"
|
#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 TU_TIME_EXPOSE_TEST
|
||||||
#define TT_ADVANCETIME(secs) tu_set_override(tu_clock_micros() + (secs) * 1'000'000);
|
#define TT_ADVANCETIME(secs) tu_set_override(tu_clock_micros() + (secs) * 1'000'000);
|
||||||
|
|
||||||
inline std::string luaEvalOut(std::shared_ptr<DataModel> m, std::string source) {
|
#include <cstdio>
|
||||||
testLogOutput.seekp(0, std::ios::end);
|
#include <cstring>
|
||||||
size_t offset = testLogOutput.tellp();
|
|
||||||
testLogOutput.seekp(0);
|
|
||||||
|
|
||||||
auto ss = m->GetService<ServerScriptService>();
|
int TEST_STATUS = 0;
|
||||||
auto s = Script::New();
|
|
||||||
m->AddChild(s);
|
|
||||||
s->source = source;
|
|
||||||
s->Run();
|
|
||||||
|
|
||||||
return testLogOutput.str().substr(offset);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void luaEval(std::shared_ptr<DataModel> m, std::string source) {
|
template <typename T>
|
||||||
auto ss = m->GetService<ServerScriptService>();
|
inline std::string quote(T value) {
|
||||||
auto s = Script::New();
|
return std::to_string(value);
|
||||||
ss->AddChild(s);
|
|
||||||
s->source = source;
|
|
||||||
s->Run();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
30
tests/src/testutillua.h
Normal 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();
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue