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
2
.clangd
2
.clangd
|
|
@ -1,3 +1,3 @@
|
|||
CompileFlags:
|
||||
CompilationDatabase: build/ # https://www.reddit.com/r/neovim/comments/vj0e16/comment/idgkg55/
|
||||
Add: [-std=c++20]
|
||||
Remove: [-mno-direct-extern-access]
|
||||
1
.vscode/launch.json
vendored
1
.vscode/launch.json
vendored
|
|
@ -18,7 +18,6 @@
|
|||
"program": "${workspaceFolder}/build/bin/editor",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"preLaunchTask": "buildDebug"
|
||||
},
|
||||
{
|
||||
"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(editor)
|
||||
|
||||
|
||||
set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/tests )
|
||||
enable_testing()
|
||||
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/defaultmeshes.h
|
||||
src/rendering/texture3d.cpp
|
||||
src/rendering/frustum.h
|
||||
src/rendering/frustum.cpp
|
||||
src/physics/world.h
|
||||
src/physics/world.cpp
|
||||
src/logger.cpp
|
||||
|
|
@ -91,8 +89,6 @@ set(SOURCES
|
|||
src/objects/joint/weld.h
|
||||
src/objects/joint/snap.cpp
|
||||
src/objects/joint/rotatev.cpp
|
||||
src/objects/joint/motor6d.h
|
||||
src/objects/joint/motor6d.cpp
|
||||
src/objects/base/service.h
|
||||
src/objects/base/member.h
|
||||
src/objects/base/instance.h
|
||||
|
|
@ -146,7 +142,6 @@ set(AUTOGEN_SOURCES
|
|||
src/objects/script.h
|
||||
src/objects/joint/snap.h
|
||||
src/objects/joint/jointinstance.h
|
||||
src/objects/joint/motor6d.h
|
||||
src/objects/joint/rotatev.h
|
||||
src/objects/joint/rotate.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("gh:zeux/pugixml@1.15")
|
||||
|
||||
# TODO: Figure out why this repo causes ft2 to keep rebuilding every time
|
||||
# For now, I'll just make it use CPM as a fallback
|
||||
# CPMAddPackage(
|
||||
CPMFindPackage(
|
||||
NAME Freetype
|
||||
CPMAddPackage(
|
||||
NAME freetype
|
||||
GIT_REPOSITORY https://github.com/aseprite/freetype2.git
|
||||
GIT_TAG VER-2-10-0
|
||||
VERSION 2.10.0
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class NoSuchInstance : public Error {
|
|||
|
||||
class NoSuchService : public Error {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -34,13 +34,8 @@ void DataModel::Init(bool runMode) {
|
|||
// Init all services
|
||||
for (auto [_, service] : this->services) {
|
||||
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) {
|
||||
|
|
@ -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) {
|
||||
if (services.count(className) != 0)
|
||||
return std::dynamic_pointer_cast<Service>(services[className]);
|
||||
|
||||
if (!INSTANCE_MAP[className] || ~(INSTANCE_MAP[className]->flags & (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) == 0) {
|
||||
if (!INSTANCE_MAP[className] || (INSTANCE_MAP[className]->flags ^ (INSTANCE_NOTCREATABLE | INSTANCE_SERVICE)) != 0) {
|
||||
return NoSuchService(className);
|
||||
}
|
||||
|
||||
if (services.count(className) != 0)
|
||||
return std::dynamic_pointer_cast<Service>(services[className]);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,20 +20,12 @@ void JointInstance::OnAncestryChanged(nullable std::shared_ptr<Instance>, nullab
|
|||
void JointInstance::OnPartParamsUpdated() {
|
||||
}
|
||||
|
||||
void JointInstance::OnPhysicsStep(float deltaTime) {
|
||||
}
|
||||
|
||||
bool JointInstance::isDrivenJoint() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void JointInstance::Update() {
|
||||
// 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
|
||||
// 2. If the new configuration is valid, rebuild our joints
|
||||
|
||||
if (!jointWorkspace.expired()) {
|
||||
if (isDrivenJoint()) jointWorkspace.lock()->UntrackDrivenJoint(shared<JointInstance>());
|
||||
jointWorkspace.lock()->DestroyJoint(joint);
|
||||
if (!oldPart0.expired())
|
||||
oldPart0.lock()->untrackJoint(shared<JointInstance>());
|
||||
|
|
@ -62,7 +54,6 @@ void JointInstance::Update() {
|
|||
|
||||
part0.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) {
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ protected:
|
|||
inline void onUpdated(std::string property) { Update(); };
|
||||
|
||||
virtual void buildJoint() = 0;
|
||||
virtual bool isDrivenJoint();
|
||||
public:
|
||||
void Update();
|
||||
virtual void OnPartParamsUpdated();
|
||||
|
|
@ -42,8 +41,6 @@ public:
|
|||
DEF_PROP_PHYS CFrame c0;
|
||||
DEF_PROP_PHYS CFrame c1;
|
||||
|
||||
virtual void OnPhysicsStep(float deltaTime);
|
||||
|
||||
JointInstance(const InstanceType*);
|
||||
~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;
|
||||
// Do NOT use Abs() in this scenario. For some reason that breaks it
|
||||
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());
|
||||
jointWorkspace = workspace;
|
||||
|
|
|
|||
|
|
@ -1,27 +1,22 @@
|
|||
#include "meta.h"
|
||||
|
||||
#define DECLTYPE(className) class className { public: const static InstanceType TYPE; };
|
||||
|
||||
DECLTYPE(DataModel);
|
||||
DECLTYPE(BasePart);
|
||||
DECLTYPE(Part);
|
||||
DECLTYPE(WedgePart);
|
||||
DECLTYPE(Snap);
|
||||
DECLTYPE(Weld);
|
||||
DECLTYPE(Rotate);
|
||||
DECLTYPE(RotateV);
|
||||
DECLTYPE(Motor6D);
|
||||
DECLTYPE(JointInstance);
|
||||
DECLTYPE(Script);
|
||||
DECLTYPE(Model);
|
||||
DECLTYPE(Message);
|
||||
DECLTYPE(Hint);
|
||||
// DECLTYPE(Folder);
|
||||
DECLTYPE(Workspace);
|
||||
DECLTYPE(JointsService);
|
||||
DECLTYPE(ScriptContext);
|
||||
DECLTYPE(ServerScriptService);
|
||||
DECLTYPE(Selection);
|
||||
#include "objects/folder.h"
|
||||
#include "objects/hint.h"
|
||||
#include "objects/joint/jointinstance.h"
|
||||
#include "objects/joint/rotate.h"
|
||||
#include "objects/joint/rotatev.h"
|
||||
#include "objects/joint/weld.h"
|
||||
#include "objects/message.h"
|
||||
#include "objects/part/wedgepart.h"
|
||||
#include "objects/service/jointsservice.h"
|
||||
#include "objects/model.h"
|
||||
#include "objects/part/part.h"
|
||||
#include "objects/joint/snap.h"
|
||||
#include "objects/script.h"
|
||||
#include "objects/service/script/scriptcontext.h"
|
||||
#include "objects/service/script/serverscriptservice.h"
|
||||
#include "objects/service/selection.h"
|
||||
#include "objects/service/workspace.h"
|
||||
#include "objects/datamodel.h"
|
||||
|
||||
std::map<std::string, const InstanceType*> INSTANCE_MAP = {
|
||||
{ "Instance", &Instance::TYPE },
|
||||
|
|
@ -34,7 +29,6 @@ std::map<std::string, const InstanceType*> INSTANCE_MAP = {
|
|||
{ "Weld", &Weld::TYPE },
|
||||
{ "Rotate", &Rotate::TYPE },
|
||||
{ "RotateV", &RotateV::TYPE },
|
||||
{ "Motor6D", &Motor6D::TYPE },
|
||||
{ "JointInstance", &JointInstance::TYPE },
|
||||
{ "Script", &Script::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) {
|
||||
if (!joint->part0.expired() && joint->part0.lock() == shared_from_this()) {
|
||||
for (auto it = primaryJoints.begin(); it != primaryJoints.end();) {
|
||||
|
|
|
|||
|
|
@ -119,7 +119,6 @@ public:
|
|||
|
||||
void MakeJoints();
|
||||
void BreakJoints();
|
||||
void UpdateNoBreakJoints();
|
||||
|
||||
// Calculate size of axis-aligned bounding box
|
||||
Vector3 GetAABB();
|
||||
|
|
|
|||
|
|
@ -137,7 +137,6 @@ void ScriptContext::RunSleepingThreads() {
|
|||
for (i = 0; i < sleepingThreads.size();) {
|
||||
bool deleted = false;
|
||||
|
||||
// TODO: Remove threads that belong to non-existent scripts
|
||||
SleepingThread sleep = sleepingThreads[i];
|
||||
if (tu_clock_micros() >= sleep.targetTimeMicros) {
|
||||
// Time args
|
||||
|
|
@ -168,12 +167,6 @@ void ScriptContext::RunSleepingThreads() {
|
|||
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) {
|
||||
lua_newtable(L); // Env table
|
||||
lua_newtable(L); // Metatable
|
||||
|
|
|
|||
|
|
@ -21,19 +21,16 @@ class DEF_INST_SERVICE_(hidden) ScriptContext : public Service {
|
|||
std::vector<SleepingThread> sleepingThreads;
|
||||
int lastScriptSourceId = 0;
|
||||
protected:
|
||||
void InitService() override;
|
||||
bool initialized = false;
|
||||
|
||||
public:
|
||||
ScriptContext();
|
||||
~ScriptContext();
|
||||
|
||||
void InitService() override;
|
||||
|
||||
lua_State* state;
|
||||
void PushThreadSleep(lua_State* thread, float delay);
|
||||
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
|
||||
void NewEnvironment(lua_State* state);
|
||||
|
|
|
|||
|
|
@ -8,14 +8,13 @@
|
|||
class DEF_INST_SERVICE_(explorer_icon="server-scripts", hidden) ServerScriptService : public Service {
|
||||
AUTOGEN_PREAMBLE
|
||||
protected:
|
||||
void InitService() override;
|
||||
void OnRun() override;
|
||||
bool initialized = false;
|
||||
|
||||
public:
|
||||
ServerScriptService();
|
||||
~ServerScriptService();
|
||||
|
||||
void InitService() override;
|
||||
void OnRun() override;
|
||||
|
||||
static inline std::shared_ptr<Instance> Create() { return std::make_shared<ServerScriptService>(); };
|
||||
};
|
||||
|
|
@ -67,18 +67,3 @@ void Workspace::PhysicsStep(float deltaTime) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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/base/service.h"
|
||||
#include "objects/joint/jointinstance.h"
|
||||
#include "physics/world.h"
|
||||
#include "rendering/frustum.h"
|
||||
#include "utils.h"
|
||||
#include <glm/ext/vector_float3.hpp>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
|
@ -34,15 +34,14 @@ class DEF_INST_SERVICE_(explorer_icon="workspace") Workspace : public Service {
|
|||
std::shared_ptr<PhysWorld> physicsWorld;
|
||||
friend PhysWorld;
|
||||
protected:
|
||||
void InitService() override;
|
||||
void OnRun() override;
|
||||
bool initialized = false;
|
||||
|
||||
public:
|
||||
Workspace();
|
||||
~Workspace();
|
||||
|
||||
void InitService() override;
|
||||
void OnRun() override;
|
||||
|
||||
std::recursive_mutex queueLock;
|
||||
|
||||
DEF_PROP float fallenPartsDestroyHeight = -500;
|
||||
|
|
@ -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 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);
|
||||
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 "enum/part.h"
|
||||
#include "logger.h"
|
||||
#include "objects/joint/jointinstance.h"
|
||||
#include "objects/part/basepart.h"
|
||||
#include "objects/part/part.h"
|
||||
#include "objects/part/wedgepart.h"
|
||||
|
|
@ -38,8 +37,6 @@
|
|||
#include <Jolt/Physics/Collision/NarrowPhaseQuery.h>
|
||||
#include <Jolt/Physics/Constraints/FixedConstraint.h>
|
||||
#include <Jolt/Physics/Constraints/HingeConstraint.h>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
||||
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)));
|
||||
}
|
||||
|
||||
// Update joints
|
||||
for (std::shared_ptr<JointInstance> joint : drivenJoints) {
|
||||
joint->OnPhysicsStep(deltaTime);
|
||||
}
|
||||
|
||||
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.mNormalAxis2 = convert<JPH::Vec3>(info->c1.RightVector());
|
||||
settings.mHingeAxis2 = convert<JPH::Vec3>(info->c1.LookVector());
|
||||
|
||||
// settings for Motor6D
|
||||
settings.mMotorSettings.mSpringSettings.mFrequency = 20;
|
||||
settings.mMotorSettings.mSpringSettings.mDamping = 1;
|
||||
// settings.mMotorSettings = JPH::MotorSettings(1.0f, 1.0f);
|
||||
constraint = settings.Create(*part0->rigidBody.bodyImpl, *part1->rigidBody.bodyImpl);
|
||||
|
||||
if (PhysMotorizedJointInfo* info = dynamic_cast<PhysMotorizedJointInfo*>(&type)) {
|
||||
if (info->motorized) {
|
||||
static_cast<JPH::HingeConstraint*>(constraint)->SetMotorState(JPH::EMotorState::Velocity);
|
||||
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 {
|
||||
panic();
|
||||
}
|
||||
|
||||
worldImpl.AddConstraint(constraint);
|
||||
return { constraint, this };
|
||||
}
|
||||
|
||||
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++;
|
||||
}
|
||||
return { constraint };
|
||||
}
|
||||
|
||||
// WATCH OUT! This should only be called for HingeConstraints.
|
||||
// Can't use dynamic_cast because TwoBodyConstraint is not virtual
|
||||
void PhysJoint::setAngularVelocity(float velocity) {
|
||||
JPH::HingeConstraint* constraint = static_cast<JPH::HingeConstraint*>(jointImpl);
|
||||
if (!constraint) return;
|
||||
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) {
|
||||
worldImpl.RemoveConstraint(joint.jointImpl);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@
|
|||
#include <Jolt/Physics/Constraints/TwoBodyConstraint.h>
|
||||
|
||||
class BasePart;
|
||||
class JointInstance;
|
||||
class PhysWorld;
|
||||
|
||||
struct PhysJointInfo { virtual ~PhysJointInfo() = default; protected: PhysJointInfo() = default; };
|
||||
|
|
@ -29,31 +28,19 @@ struct PhysFixedJointInfo : PhysJointInfo {
|
|||
struct PhysRotatingJointInfo : PhysJointInfo {
|
||||
CFrame c0;
|
||||
CFrame c1;
|
||||
|
||||
inline PhysRotatingJointInfo(CFrame c0, CFrame c1) : c0(c0), c1(c1) {}
|
||||
};
|
||||
|
||||
struct PhysMotorizedJointInfo : PhysRotatingJointInfo {
|
||||
bool motorized;
|
||||
float initialVelocity;
|
||||
|
||||
inline PhysMotorizedJointInfo(CFrame c0, CFrame c1, float initialVelocity) : PhysRotatingJointInfo(c0, c1), 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) {}
|
||||
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) {}
|
||||
};
|
||||
|
||||
class PhysWorld;
|
||||
struct PhysJoint {
|
||||
public:
|
||||
JPH::TwoBodyConstraint* jointImpl;
|
||||
PhysWorld* parentWorld;
|
||||
|
||||
void setAngularVelocity(float velocity);
|
||||
void setTargetAngle(float angle);
|
||||
};
|
||||
|
||||
struct RaycastResult;
|
||||
|
|
@ -117,9 +104,7 @@ class PhysWorld : public std::enable_shared_from_this<PhysWorld> {
|
|||
ObjectLayerPairFilter objectLayerPairFilter;
|
||||
JPH::PhysicsSystem worldImpl;
|
||||
std::list<std::shared_ptr<BasePart>> simulatedBodies;
|
||||
std::list<std::shared_ptr<JointInstance>> drivenJoints;
|
||||
|
||||
friend PhysJoint;
|
||||
public:
|
||||
PhysWorld();
|
||||
~PhysWorld();
|
||||
|
|
@ -132,11 +117,6 @@ public:
|
|||
PhysJoint createJoint(PhysJointInfo& type, std::shared_ptr<BasePart> part0, std::shared_ptr<BasePart> part1);
|
||||
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; }
|
||||
void syncBodyProperties(std::shared_ptr<BasePart>);
|
||||
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
|
||||
glDisable(GL_CULL_FACE);
|
||||
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->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 <cmath>
|
||||
#include <cstdio>
|
||||
#include <glm/ext.hpp>
|
||||
#include <glm/ext/matrix_clip_space.hpp>
|
||||
#include <glm/ext/matrix_float4x4.hpp>
|
||||
|
|
@ -77,6 +79,8 @@ void renderInit(int width, int height) {
|
|||
glEnable(GL_BLEND);
|
||||
glEnable(GL_MULTISAMPLE);
|
||||
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);
|
||||
|
||||
|
|
@ -166,7 +170,7 @@ void renderParts() {
|
|||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_BACK);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Use shader
|
||||
shader->use();
|
||||
|
|
@ -230,7 +234,7 @@ void renderSurfaceExtras() {
|
|||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_BACK);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Use shader
|
||||
ghostShader->use();
|
||||
|
|
@ -354,7 +358,7 @@ void renderAABB() {
|
|||
glCullFace(GL_BACK);
|
||||
glFrontFace(GL_CW);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Use shader
|
||||
ghostShader->use();
|
||||
|
|
@ -393,7 +397,7 @@ void renderWireframe() {
|
|||
glCullFace(GL_BACK);
|
||||
glFrontFace(GL_CW);
|
||||
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 );
|
||||
|
||||
// Use shader
|
||||
|
|
@ -433,7 +437,7 @@ void renderOutlines() {
|
|||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_BACK);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Use shader
|
||||
outlineShader->use();
|
||||
|
|
@ -496,7 +500,7 @@ void renderSelectionAssembly() {
|
|||
glEnable(GL_CULL_FACE);
|
||||
glCullFace(GL_BACK);
|
||||
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>());
|
||||
|
||||
|
|
@ -534,7 +538,7 @@ void renderRotationArcs() {
|
|||
glCullFace(GL_BACK);
|
||||
glFrontFace(GL_CW);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
// Use shader
|
||||
handleShader->use();
|
||||
|
|
@ -632,6 +636,7 @@ void renderMessages() {
|
|||
// glEnable(GL_DEPTH_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
// glEnable(GL_BLEND);
|
||||
// glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
|
||||
if (!it->IsA<Message>()) continue;
|
||||
|
|
@ -648,7 +653,7 @@ void renderMessages() {
|
|||
if (message->text == "") continue;
|
||||
|
||||
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 - 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);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
// For some reason this is unset by QPainter, so we override it here
|
||||
glEnable(GL_MULTISAMPLE);
|
||||
|
||||
renderSkyBox();
|
||||
renderHandles();
|
||||
|
|
@ -681,25 +684,11 @@ void render() {
|
|||
// renderAABB();
|
||||
|
||||
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) {
|
||||
// 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);
|
||||
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
|
||||
// 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);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
|
||||
include(CPM)
|
||||
|
||||
CPMAddPackage("gh:m-doescode/qcursorconstraints#eb674e1fab418c4c5ccb7c599c19bd2e8a062faf")
|
||||
CPMAddPackage("gh:m-doescode/qcursorconstraints#cef1a31c0afad8ed3c95ee1a6bc531090805b510")
|
||||
|
|
@ -1,17 +1,29 @@
|
|||
#include <glad/gl.h>
|
||||
#include <glm/common.hpp>
|
||||
#include <glm/vector_relational.hpp>
|
||||
#include <memory>
|
||||
#include <miniaudio.h>
|
||||
#include <qcursorconstraints.h>
|
||||
#include <QPainter>
|
||||
|
||||
#include <qnamespace.h>
|
||||
#include <qguiapplication.h>
|
||||
#include <string>
|
||||
#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 "common.h"
|
||||
#include "math_helper.h"
|
||||
#include "objects/base/instance.h"
|
||||
#include "objects/pvinstance.h"
|
||||
#include "objects/service/selection.h"
|
||||
#include "partassembly.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "mainglwidget.h"
|
||||
#include "rendering/shader.h"
|
||||
#include "datatypes/variant.h"
|
||||
#include "undohistory.h"
|
||||
|
||||
#define PI 3.14159
|
||||
#define M_mainWindow dynamic_cast<MainWindow*>(window())
|
||||
|
|
@ -56,16 +68,9 @@ glm::vec2 secondPoint;
|
|||
|
||||
extern std::weak_ptr<BasePart> draggingObject;
|
||||
extern std::optional<HandleFace> draggingHandle;
|
||||
extern Shader* shader;
|
||||
void MainGLWidget::paintGL() {
|
||||
QPainter painter(this);
|
||||
|
||||
painter.beginNativePainting();
|
||||
::render();
|
||||
painter.endNativePainting();
|
||||
|
||||
painter.setPen(QColor(200, 200, 200));
|
||||
painter.drawRect(selectionLasso);
|
||||
painter.end();
|
||||
}
|
||||
|
||||
bool isMouseRightDragging = false;
|
||||
|
|
@ -321,7 +326,7 @@ std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
|
|||
}
|
||||
|
||||
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();
|
||||
|
||||
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:
|
||||
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) {
|
||||
|
|
@ -474,9 +400,63 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
|
|||
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;
|
||||
} default:
|
||||
|
|
@ -491,7 +471,6 @@ void MainGLWidget::mouseReleaseEvent(QMouseEvent* evt) {
|
|||
isMouseDragging = false;
|
||||
draggingObject = {};
|
||||
draggingHandle = std::nullopt;
|
||||
selectionLasso = {0,0,0,0};
|
||||
setCursor(Qt::ArrowCursor);
|
||||
|
||||
if (!initialTransforms.empty()) {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
#ifndef MAINGLWIDGET_H
|
||||
#define MAINGLWIDGET_H
|
||||
|
||||
#include <glm/fwd.hpp>
|
||||
#include <memory>
|
||||
#include <QEvent>
|
||||
#include "objects/part/part.h"
|
||||
#include "qevent.h"
|
||||
#include <QOpenGLWidget>
|
||||
#include <QMenu>
|
||||
#include <QWidget>
|
||||
#include <memory>
|
||||
#include <qmenu.h>
|
||||
|
||||
class BasePart;
|
||||
class HandleFace;
|
||||
class MainWindow;
|
||||
|
||||
|
|
@ -28,7 +28,6 @@ protected:
|
|||
void handleLinearTransform(QMouseEvent* evt);
|
||||
void handleRotationalTransform(QMouseEvent* evt);
|
||||
void handleCursorChange(QMouseEvent* evt);
|
||||
bool handlePartClick(QMouseEvent* evt);
|
||||
void startLinearTransform(QMouseEvent* evt);
|
||||
std::optional<HandleFace> raycastHandle(glm::vec3 pointDir);
|
||||
|
||||
|
|
@ -43,8 +42,6 @@ protected:
|
|||
|
||||
MainWindow* mainWindow();
|
||||
float snappingFactor();
|
||||
|
||||
QRect selectionLasso;
|
||||
};
|
||||
|
||||
#endif // MAINGLWIDGET_H
|
||||
|
|
|
|||
|
|
@ -95,9 +95,8 @@ void PlaceDocument::timerEvent(QTimerEvent* evt) {
|
|||
|
||||
placeWidget->repaint();
|
||||
placeWidget->updateCycle();
|
||||
if (_runState != RUN_RUNNING) return;
|
||||
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
|
||||
src/common.cpp
|
||||
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)
|
||||
# https://stackoverflow.com/a/36729074/16255372
|
||||
add_custom_target(check ${CMAKE_CTEST_COMMAND} --output-on-failure WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
|
||||
|
|
@ -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
|
||||
|
||||
#include "objects/datamodel.h"
|
||||
#include "objects/script.h"
|
||||
#include "objects/service/script/serverscriptservice.h"
|
||||
#include "testcommon.h"
|
||||
// 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);
|
||||
|
||||
inline std::string luaEvalOut(std::shared_ptr<DataModel> m, std::string source) {
|
||||
testLogOutput.seekp(0, std::ios::end);
|
||||
size_t offset = testLogOutput.tellp();
|
||||
testLogOutput.seekp(0);
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
auto ss = m->GetService<ServerScriptService>();
|
||||
auto s = Script::New();
|
||||
m->AddChild(s);
|
||||
s->source = source;
|
||||
s->Run();
|
||||
int TEST_STATUS = 0;
|
||||
|
||||
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) {
|
||||
auto ss = m->GetService<ServerScriptService>();
|
||||
auto s = Script::New();
|
||||
ss->AddChild(s);
|
||||
s->source = source;
|
||||
s->Run();
|
||||
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
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