Compare commits

...

9 commits

38 changed files with 405 additions and 204 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 610 B

View file

Before

Width:  |  Height:  |  Size: 294 B

After

Width:  |  Height:  |  Size: 294 B

View file

Before

Width:  |  Height:  |  Size: 688 B

After

Width:  |  Height:  |  Size: 688 B

View file

Before

Width:  |  Height:  |  Size: 774 B

After

Width:  |  Height:  |  Size: 774 B

View file

Before

Width:  |  Height:  |  Size: 774 B

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 537 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 605 B

View file

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 354 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

View file

@ -9,10 +9,10 @@ uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform vec3 scale;
uniform float thickness;
void main()
{
float thickness = 0.4;
vec3 distFromEdge = sign(aPos) * 0.5 - aPos;
vec3 tVec = (scale * sign(aPos) * 0.5 - distFromEdge * thickness) / scale;

View file

@ -44,7 +44,7 @@ static std::string castFromVariant(std::string valueStr, std::string fieldType)
}
std::string mappedType = MAPPED_TYPE[fieldType];
return valueStr + ".get<" + (!mappedType.empty() ? mappedType : fieldType) + ">()";
return "(" + fieldType + ")" + valueStr + ".get<" + (!mappedType.empty() ? mappedType : fieldType) + ">()";
}
static std::string castToVariant(std::string valueStr, std::string fieldType) {

View file

@ -11,7 +11,7 @@ std::shared_ptr<DataModel> gDataModel = DataModel::New();
std::shared_ptr<DataModel> editModeDataModel = gDataModel;
std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
std::shared_ptr<Handles> editorToolHandles = Handles::New();
Handles editorToolHandles;
std::vector<InstanceRefWeak> currentSelection;

View file

@ -1,6 +1,6 @@
#pragma once
#include "objects/base/instance.h"
#include "objects/handles.h"
#include "handles.h"
#include "objects/workspace.h"
#include "objects/datamodel.h"
#include "camera.h"
@ -22,7 +22,7 @@ extern std::shared_ptr<DataModel> editModeDataModel;
inline std::shared_ptr<Workspace> gWorkspace() { return std::dynamic_pointer_cast<Workspace>(gDataModel->services["Workspace"]); }
extern std::optional<HierarchyPreUpdateHandler> hierarchyPreUpdateHandler;
extern std::optional<HierarchyPostUpdateHandler> hierarchyPostUpdateHandler;
extern std::shared_ptr<Handles> editorToolHandles;
extern Handles editorToolHandles;
void setSelection(std::vector<InstanceRefWeak> newSelection, bool fromExplorer = false);
const std::vector<InstanceRefWeak> getSelection();

View file

@ -64,7 +64,11 @@ const Data::String Data::CFrame::ToString() const {
}
Data::CFrame Data::CFrame::pointToward(Vector3 position, Vector3 toward) {
return Data::CFrame(position, position + toward, (abs(glm::dot((glm::vec3)toward, glm::vec3(0, 1, 0))) > 0.999) ? glm::vec3(0, 0, 1) : glm::vec3(0, 1, 0));
return Data::CFrame(position, position + toward, (abs(toward.Dot(Vector3(0, 1, 0))) > 0.999) ? Vector3(0, 0, 1) : Vector3(0, 1, 0));
}
Data::CFrame Data::CFrame::pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right) {
return Data::CFrame(position, position + toward, (abs(toward.Dot(up)) > 0.999) ? right : up);
}
Data::CFrame::operator glm::mat4() const {

View file

@ -30,6 +30,11 @@ namespace Data {
// Same as CFrame(position, position + toward), but makes sure that up and toward are not linearly dependant
static CFrame pointToward(Vector3 position, Vector3 toward);
// Creates a cframe looking at position + toward, whilst aligning its up to up.
// If up and toward are approximately linearly dependent (their absolute dot product > 0.999),
// then the right is used instead
// Up and right must NOT be linearly dependent
static CFrame pointAligned(Vector3 position, Vector3 toward, Vector3 up, Vector3 right);
DEF_DATA_PROP static const CFrame IDENTITY;
static const CFrame YToZ;

136
core/src/handles.cpp Normal file
View file

@ -0,0 +1,136 @@
#include "handles.h"
#include "common.h"
#include "datatypes/cframe.h"
#include "datatypes/vector.h"
#include "math_helper.h"
#include <glm/ext/scalar_common.hpp>
#include <memory>
#include <optional>
#include <reactphysics3d/collision/RaycastInfo.h>
#include <reactphysics3d/engine/PhysicsCommon.h>
#include <reactphysics3d/engine/PhysicsWorld.h>
#include <reactphysics3d/mathematics/Transform.h>
HandleFace HandleFace::XPos(0, glm::vec3(1,0,0));
HandleFace HandleFace::XNeg(1, glm::vec3(-1,0,0));
HandleFace HandleFace::YPos(2, glm::vec3(0,1,0));
HandleFace HandleFace::YNeg(3, glm::vec3(0,-1,0));
HandleFace HandleFace::ZPos(4, glm::vec3(0,0,1));
HandleFace HandleFace::ZNeg(5, glm::vec3(0,0,-1));
std::array<HandleFace, 6> HandleFace::Faces { HandleFace::XPos, HandleFace::XNeg, HandleFace::YPos, HandleFace::YNeg, HandleFace::ZPos, HandleFace::ZNeg };
static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
// Shitty solution
static rp3d::PhysicsCommon common;
static rp3d::PhysicsWorld* world = common.createPhysicsWorld();
std::shared_ptr<Part> getHandleAdornee() {
for (std::weak_ptr<Instance> inst : getSelection()) {
if (!inst.expired() && inst.lock()->IsA<Part>())
return inst.lock()->CastTo<Part>().expect();
}
return {};
}
CFrame partCFrameFromHandlePos(HandleFace face, Vector3 newPos) {
auto adornee = getHandleAdornee();
if (adornee == nullptr) return CFrame(glm::vec3(0,0,0), (Vector3)glm::vec3(0,0,0));
CFrame localFrame = editorToolHandles.worldMode ? CFrame::IDENTITY + adornee->position() : adornee->cframe;
CFrame inverseFrame = localFrame.Inverse();
Vector3 handleOffset = editorToolHandles.worldMode ? ((Vector3::ONE * 2.f) + adornee->GetAABB() * 0.5f) : Vector3(2.f + adornee->size * 0.5f);
Vector3 handlePos = localFrame * (handleOffset * face.normal);
// glm::vec3 localPos = inverseFrame * newPos;
glm::vec3 newPartPos = newPos - localFrame.Rotation() * (handleOffset * face.normal);
return adornee->cframe.Rotation() + newPartPos;
}
std::optional<HandleFace> raycastHandle(rp3d::Ray ray) {
for (HandleFace face : HandleFace::Faces) {
CFrame cframe = getHandleCFrame(face);
// Implement manual detection via boxes instead of... this shit
// This code also hardly works, and is not good at all... Hooo nope.
rp3d::RigidBody* body = world->createRigidBody(CFrame::IDENTITY + cframe.Position());
body->addCollider(common.createBoxShape(cframe.Rotation() * Vector3(handleSize(face) / 2.f)), rp3d::Transform::identity());
rp3d::RaycastInfo info;
if (body->raycast(ray, info)) {
world->destroyRigidBody(body);
return face;
}
world->destroyRigidBody(body);
}
return std::nullopt;
}
Vector3 handleSize(HandleFace face) {
if (editorToolHandles.handlesType == HandlesType::MoveHandles)
return glm::vec3(0.5f, 0.5f, 2.f);
return glm::vec3(1,1,1);
}
static int getAABBOfSelection(glm::vec3& pos, glm::vec3& size, glm::vec3& min, glm::vec3& max) {
int count = 0;
for (std::weak_ptr<Instance> inst : getSelection()) {
if (inst.expired() || !inst.lock()->IsA<Part>()) continue;
std::shared_ptr<Part> part = inst.lock()->CastTo<Part>().expect();
if (count == 0)
min = part->position(), max = part->position();
count++;
Vector3 aabbSize = part->GetAABB();
expandAABB(min, max, part->position() - aabbSize / 2.f);
expandAABB(min, max, part->position() + aabbSize / 2.f);
}
getAABBCoords(pos, size, min, max);
return count;
}
static std::shared_ptr<Part> getFirstSelectedPart() {
for (std::weak_ptr<Instance> inst : getSelection()) {
if (inst.expired() || !inst.lock()->IsA<Part>()) continue;
return inst.lock()->CastTo<Part>().expect();
}
return {};
}
CFrame getLocalHandleCFrame(HandleFace face) {
glm::vec3 _boxPos, boxSize, _boxMin, _boxMax;
int count = getAABBOfSelection(_boxPos, boxSize, _boxMin, _boxMax);
Vector3 size;
if (count == 1 && !editorToolHandles.worldMode)
size = getFirstSelectedPart()->size;
else
size = boxSize;
// Since rotation displays rings, all handles must be the same distance from origin in order for the
// rings to be circular
if (editorToolHandles.handlesType == HandlesType::RotateHandles)
size = Vector3::ONE * fmax(fmax(size.X(), size.Y()), size.Z());
CFrame cframe = CFrame::pointToward(face.normal * ((glm::vec3)size * 0.5f + 2.f), -face.normal);
return cframe;
}
CFrame getHandleCFrame(HandleFace face) {
glm::vec3 boxPos, boxSize, _boxMin, _boxMax;
int count = getAABBOfSelection(boxPos, boxSize, _boxMin, _boxMax);
if (count == 1 && !editorToolHandles.worldMode) {
auto part = getFirstSelectedPart();
return part->cframe * getLocalHandleCFrame(face);
} else
return getLocalHandleCFrame(face) + boxPos;
}

46
core/src/handles.h Normal file
View file

@ -0,0 +1,46 @@
#pragma once
#include "datatypes/cframe.h"
#include "objects/part.h"
#include <array>
#include <memory>
class HandleFace {
HandleFace(int index, glm::vec3 normal) : index(index), normal(normal){}
public:
int index;
glm::vec3 normal;
static HandleFace XPos;
static HandleFace XNeg;
static HandleFace YPos;
static HandleFace YNeg;
static HandleFace ZPos;
static HandleFace ZNeg;
static std::array<HandleFace, 6> Faces;
};
enum HandlesType {
MoveHandles,
ScaleHandles,
RotateHandles,
};
struct Handles {
bool nixAxes = false; // XYZ -> ZXY, used with rotation
bool active;
HandlesType handlesType;
// World-space handles vs local-space handles
bool worldMode = false;
};
std::shared_ptr<Part> getHandleAdornee();
CFrame getHandleCFrame(HandleFace face);
CFrame partCFrameFromHandlePos(HandleFace face, Vector3 newPos);
Vector3 handleSize(HandleFace face);
std::optional<HandleFace> raycastHandle(rp3d::Ray ray);
// Gets the cframe of the handle local to the center of the selected objects
CFrame getLocalHandleCFrame(HandleFace face);

View file

@ -100,4 +100,26 @@ void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3
r_ps = (1 - s) * p_p0 + s * p_p1;
r_qt = (1 - t) * p_q0 + t * p_q1;
}
void expandAABB(glm::vec3& min, glm::vec3& max, glm::vec3 point) {
min = glm::vec3(glm::min(min.x, point.x), glm::min(min.y, point.y), glm::min(min.z, point.z));
max = glm::vec3(glm::max(max.x, point.x), glm::max(max.y, point.y), glm::max(max.z, point.z));
}
void computeAABBFromPoints(glm::vec3& min, glm::vec3& max, glm::vec3* points, int count) {
if (count == 0) return;
min = points[0];
max = points[0];
for (int i = 0; i < count; i++) {
min = glm::vec3(glm::min(min.x, points[i].x), glm::min(min.y, points[i].y), glm::min(min.z, points[i].z));
max = glm::vec3(glm::max(max.x, points[i].x), glm::max(max.y, points[i].y), glm::max(max.z, points[i].z));
}
}
void getAABBCoords(glm::vec3 &pos, glm::vec3 &size, glm::vec3 min, glm::vec3 max) {
pos = (max + min) / 2.f;
size = (max - min);
}

View file

@ -2,4 +2,8 @@
#include <glm/glm.hpp>
// From godot/editor/plugins/gizmos/gizmo_3d_helper.h
void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3 &p_p1, const glm::vec3 &p_q0, const glm::vec3 &p_q1, glm::vec3 &r_ps, glm::vec3 &r_qt);
void get_closest_points_between_segments(const glm::vec3 &p_p0, const glm::vec3 &p_p1, const glm::vec3 &p_q0, const glm::vec3 &p_q1, glm::vec3 &r_ps, glm::vec3 &r_qt);
void expandAABB(glm::vec3& min, glm::vec3& max, glm::vec3 point);
void computeAABBFromPoints(glm::vec3& min, glm::vec3& max, glm::vec3* points, int count);
void getAABBCoords(glm::vec3& pos, glm::vec3& size, glm::vec3 min, glm::vec3 max);

View file

@ -9,7 +9,7 @@ Service::Service(const InstanceType* type) : Instance(type){}
// Fail if parented to non-datamodel, otherwise lock parent
void Service::OnParentUpdated(std::optional<std::shared_ptr<Instance>> oldParent, std::optional<std::shared_ptr<Instance>> newParent) {
if (!newParent || newParent.value()->GetClass() != &DataModel::TYPE) {
Logger::fatalErrorf("Service %s was parented to object of type %s", GetClass()->className, newParent ? newParent.value()->GetClass()->className : "NULL");
Logger::fatalErrorf("Service %s was parented to object of type %s", GetClass()->className.c_str(), newParent ? newParent.value()->GetClass()->className.c_str() : "NULL");
panic();
}

View file

@ -1,89 +0,0 @@
#include "handles.h"
#include "common.h"
#include "datatypes/cframe.h"
#include "datatypes/vector.h"
#include <glm/ext/scalar_common.hpp>
#include <optional>
#include <reactphysics3d/collision/RaycastInfo.h>
#include <reactphysics3d/engine/PhysicsCommon.h>
#include <reactphysics3d/engine/PhysicsWorld.h>
#include <reactphysics3d/mathematics/Transform.h>
HandleFace HandleFace::XPos(0, glm::vec3(1,0,0));
HandleFace HandleFace::XNeg(1, glm::vec3(-1,0,0));
HandleFace HandleFace::YPos(2, glm::vec3(0,1,0));
HandleFace HandleFace::YNeg(3, glm::vec3(0,-1,0));
HandleFace HandleFace::ZPos(4, glm::vec3(0,0,1));
HandleFace HandleFace::ZNeg(5, glm::vec3(0,0,-1));
std::array<HandleFace, 6> HandleFace::Faces { HandleFace::XPos, HandleFace::XNeg, HandleFace::YPos, HandleFace::YNeg, HandleFace::ZPos, HandleFace::ZNeg };
static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
// Shitty solution
static rp3d::PhysicsCommon common;
static rp3d::PhysicsWorld* world = common.createPhysicsWorld();
Handles::Handles(): Instance(&TYPE) {
}
CFrame Handles::GetCFrameOfHandle(HandleFace face) {
if (adornee.expired()) return CFrame(glm::vec3(0,0,0), (Vector3)glm::vec3(0,0,0));
CFrame localFrame = worldMode ? CFrame::IDENTITY + adornee.lock()->position() : adornee.lock()->cframe;
Vector3 handleNormal = face.normal;
if (nixAxes)
handleNormal = XYZToZXY * face.normal;
// We don't want this to align with local * face.normal, or else we have problems.
glm::vec3 upAxis(0, 0, 1);
if (glm::abs(glm::dot(glm::vec3(localFrame.Rotation() * handleNormal), upAxis)) > 0.9999f)
upAxis = glm::vec3(0, 1, 0);
glm::vec3 partSize = handlesType == HandlesType::RotateHandles ? glm::vec3(glm::max(adornee.lock()->size.x, adornee.lock()->size.y, adornee.lock()->size.z)) : adornee.lock()->size;
Vector3 handleOffset = this->worldMode ? ((Vector3::ONE * 2.f) + adornee.lock()->GetAABB() * 0.5f) : Vector3(2.f + partSize * 0.5f);
Vector3 handlePos = localFrame * (handleOffset * handleNormal);
CFrame cframe(handlePos, handlePos + localFrame.Rotation() * -handleNormal, upAxis);
return cframe;
}
CFrame Handles::PartCFrameFromHandlePos(HandleFace face, Vector3 newPos) {
if (adornee.expired()) return CFrame(glm::vec3(0,0,0), (Vector3)glm::vec3(0,0,0));
CFrame localFrame = worldMode ? CFrame::IDENTITY + adornee.lock()->position() : adornee.lock()->cframe;
CFrame inverseFrame = localFrame.Inverse();
Vector3 handleOffset = this->worldMode ? ((Vector3::ONE * 2.f) + adornee.lock()->GetAABB() * 0.5f) : Vector3(2.f + adornee.lock()->size * 0.5f);
Vector3 handlePos = localFrame * (handleOffset * face.normal);
// glm::vec3 localPos = inverseFrame * newPos;
glm::vec3 newPartPos = newPos - localFrame.Rotation() * (handleOffset * face.normal);
return adornee.lock()->cframe.Rotation() + newPartPos;
}
std::optional<HandleFace> Handles::RaycastHandle(rp3d::Ray ray) {
for (HandleFace face : HandleFace::Faces) {
CFrame cframe = GetCFrameOfHandle(face);
// Implement manual detection via boxes instead of... this shit
// This code also hardly works, and is not good at all... Hooo nope.
rp3d::RigidBody* body = world->createRigidBody(CFrame::IDENTITY + cframe.Position());
body->addCollider(common.createBoxShape(cframe.Rotation() * Vector3(HandleSize(face) / 2.f)), rp3d::Transform::identity());
rp3d::RaycastInfo info;
if (body->raycast(ray, info)) {
world->destroyRigidBody(body);
return face;
}
world->destroyRigidBody(body);
}
return std::nullopt;
}
Vector3 Handles::HandleSize(HandleFace face) {
if (handlesType == HandlesType::MoveHandles)
return glm::vec3(0.5f, 0.5f, 2.f);
return glm::vec3(1,1,1);
}

View file

@ -1,55 +0,0 @@
#pragma once
#include "base.h"
#include "datatypes/cframe.h"
#include "objects/annotation.h"
#include "objects/base/service.h"
#include "objects/part.h"
#include <array>
#include <memory>
#include <reactphysics3d/body/RigidBody.h>
class HandleFace {
HandleFace(int index, glm::vec3 normal) : index(index), normal(normal){}
public:
int index;
glm::vec3 normal;
static HandleFace XPos;
static HandleFace XNeg;
static HandleFace YPos;
static HandleFace YNeg;
static HandleFace ZPos;
static HandleFace ZNeg;
static std::array<HandleFace, 6> Faces;
};
enum HandlesType {
MoveHandles,
ScaleHandles,
RotateHandles,
};
class DEF_INST_(abstract) Handles : public Instance {
AUTOGEN_PREAMBLE
public:
bool nixAxes = false; // XYZ -> ZXY, used with rotation
bool active;
std::weak_ptr<Part> adornee;
HandlesType handlesType;
// inline std::weak_ptr<Part> GetAdornee() { return adornee; }
// inline void SetAdornee(std::weak_ptr<Part> newAdornee) { this->adornee = newAdornee; updateAdornee(); };
Handles();
// World-space handles vs local-space handles
bool worldMode = false;
CFrame GetCFrameOfHandle(HandleFace face);
CFrame PartCFrameFromHandlePos(HandleFace face, Vector3 newPos);
Vector3 HandleSize(HandleFace face);
std::optional<HandleFace> RaycastHandle(rp3d::Ray ray);
static inline std::shared_ptr<Handles> New() { return std::make_shared<Handles>(); };
};

View file

@ -16,7 +16,8 @@
#include "datatypes/cframe.h"
#include "datatypes/color3.h"
#include "objects/handles.h"
#include "handles.h"
#include "math_helper.h"
#include "rendering/torus.h"
#include "shader.h"
#include "mesh.h"
@ -265,8 +266,10 @@ void renderSkyBox() {
glDrawArrays(GL_TRIANGLES, 0, 36);
}
static CFrame XYZToZXY(glm::vec3(0, 0, 0), -glm::vec3(1, 0, 0), glm::vec3(0, 0, 1));
void renderHandles() {
if (editorToolHandles->adornee.expired() || !editorToolHandles->active) return;
if (!editorToolHandles.active) return;
glDepthMask(GL_TRUE);
glCullFace(GL_BACK);
@ -292,18 +295,18 @@ void renderHandles() {
handleShader->set("viewPos", camera.cameraPos);
for (auto face : HandleFace::Faces) {
glm::mat4 model = editorToolHandles->GetCFrameOfHandle(face);
model = glm::scale(model, (glm::vec3)editorToolHandles->HandleSize(face));
glm::mat4 model = getHandleCFrame(face);
model = glm::scale(model, (glm::vec3)handleSize(face));
handleShader->set("model", model);
handleShader->set("material", Material {
.diffuse = glm::abs(face.normal),
.diffuse = editorToolHandles.handlesType == HandlesType::RotateHandles ? (glm::vec3)(XYZToZXY * glm::abs(face.normal)) : glm::abs(face.normal),
.specular = glm::vec3(0.5f, 0.5f, 0.5f),
.shininess = 16.0f,
});
glm::mat3 normalMatrix = glm::mat3(glm::transpose(glm::inverse(model)));
handleShader->set("normalMatrix", normalMatrix);
if (editorToolHandles->handlesType == HandlesType::MoveHandles) {
if (editorToolHandles.handlesType == HandlesType::MoveHandles) {
ARROW_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, ARROW_MESH->vertexCount);
} else {
@ -319,7 +322,7 @@ void renderHandles() {
identityShader->set("aColor", glm::vec3(0.f, 1.f, 1.f));
for (auto face : HandleFace::Faces) {
CFrame cframe = editorToolHandles->GetCFrameOfHandle(face);
CFrame cframe = getHandleCFrame(face);
glm::vec4 screenPos = projection * view * glm::vec4((glm::vec3)cframe.Position(), 1.0f);
if (screenPos.z < 0) continue;
@ -437,16 +440,27 @@ void renderOutlines() {
// Pass in the camera position
outlineShader->set("viewPos", camera.cameraPos);
outlineShader->set("thickness", 0.4f);
// outlineShader->set("color", glm::vec3(1.f, 0.f, 0.f));
// Sort by nearest
glm::vec3 min, max;
bool first = true;
for (auto it = gWorkspace()->GetDescendantsStart(); it != gWorkspace()->GetDescendantsEnd(); it++) {
InstanceRef inst = *it;
if (inst->GetClass() != &Part::TYPE) continue;
std::shared_ptr<Part> part = std::dynamic_pointer_cast<Part>(inst);
if (!part->selected) continue;
if (first)
min = part->position(), max = part->position();
first = false;
Vector3 aabbSize = part->GetAABB();
expandAABB(min, max, part->position() - aabbSize / 2.f);
expandAABB(min, max, part->position() + aabbSize / 2.f);
glm::mat4 model = part->cframe;
model = glm::scale(model, (glm::vec3)part->size + glm::vec3(0.2));
outlineShader->set("model", model);
@ -455,10 +469,26 @@ void renderOutlines() {
OUTLINE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, OUTLINE_MESH->vertexCount);
}
// Render AABB of selected parts
if (first) return;
glm::vec3 outlineSize, outlinePos;
outlineSize = (max - min);
outlinePos = (max + min) / 2.f;
glm::mat4 model = glm::translate(glm::mat4(1.0f), outlinePos);
model = glm::scale(model, outlineSize + glm::vec3(0.1));
outlineShader->set("model", model);
outlineShader->set("scale", outlineSize + glm::vec3(0.05));
outlineShader->set("thickness", 0.2f);
OUTLINE_MESH->bind();
glDrawArrays(GL_TRIANGLES, 0, OUTLINE_MESH->vertexCount);
}
void renderRotationArcs() {
if (editorToolHandles->adornee.expired() || editorToolHandles->handlesType != HandlesType::RotateHandles) return;
if (!editorToolHandles.active || editorToolHandles.handlesType != HandlesType::RotateHandles) return;
glDepthMask(GL_TRUE);
glEnable(GL_CULL_FACE);

View file

@ -5,6 +5,7 @@
#include <qsoundeffect.h>
#include <string>
#include "mainglwidget.h"
#include "handles.h"
#include "logger.h"
#include "mainwindow.h"
#include "common.h"
@ -171,21 +172,21 @@ inline glm::vec3 vec3fy(glm::vec4 vec) {
// Taken from Godot's implementation of moving handles (godot/editor/plugins/gizmos/gizmo_3d_helper.cpp)
void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
if (!isMouseDragging || !draggingHandle|| editorToolHandles->adornee.expired() || !editorToolHandles->active) return;
if (!isMouseDragging || !draggingHandle|| !editorToolHandles.active) return;
QPoint position = evt->pos();
auto part = editorToolHandles->adornee.lock();
auto part = getHandleAdornee();
// This was actually quite a difficult problem to solve, managing to get the handle to go underneath the cursor
glm::vec3 pointDir = camera.getScreenDirection(glm::vec2(position.x(), position.y()), glm::vec2(width(), height()));
pointDir = glm::normalize(pointDir);
CFrame handleCFrame = editorToolHandles->GetCFrameOfHandle(draggingHandle.value());
CFrame handleCFrame = getHandleCFrame(draggingHandle.value());
// Current frame. Identity frame if worldMode == true, selected object's frame if worldMode == false
CFrame frame = editorToolHandles->worldMode ? CFrame::IDENTITY + part->position() : part->cframe.Rotation();
CFrame frame = editorToolHandles.worldMode ? CFrame::IDENTITY + part->position() : part->cframe.Rotation();
// Segment from axis stretching -4096 to +4096 rel to handle's position
glm::vec3 axisSegment0 = handleCFrame.Position() + (-handleCFrame.LookVector() * 4096.0f);
@ -200,7 +201,7 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
get_closest_points_between_segments(axisSegment0, axisSegment1, mouseSegment0, mouseSegment1, handlePoint, rb);
// Find new part position
glm::vec3 centerPoint = editorToolHandles->PartCFrameFromHandlePos(draggingHandle.value(), handlePoint).Position();
glm::vec3 centerPoint = partCFrameFromHandlePos(draggingHandle.value(), handlePoint).Position();
// Apply snapping in the current frame
glm::vec3 diff = centerPoint - (glm::vec3)part->position();
@ -258,10 +259,10 @@ void MainGLWidget::handleLinearTransform(QMouseEvent* evt) {
// Also implemented based on Godot: [c7ea8614](godot/editor/plugins/canvas_item_editor_plugin.cpp#L1490)
glm::vec2 startPoint;
void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
if (!isMouseDragging || !draggingHandle || editorToolHandles->adornee.expired() || !editorToolHandles->active) return;
if (!isMouseDragging || !draggingHandle || !editorToolHandles.active) return;
glm::vec2 destPoint = glm::vec2(evt->pos().x(), evt->pos().y());
auto part = editorToolHandles->adornee.lock();
auto part = getHandleAdornee();
// Calculate part pos as screen point
glm::mat4 projection = glm::perspective(glm::radians(45.f), (float)width() / (float)height(), 0.1f, 1000.0f);
@ -282,14 +283,16 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
if (snappingFactor() > 0)
angle = roundf(angle * 4 / PI / snappingFactor()) / 4 * PI * snappingFactor();
glm::vec3 handleNormal = XYZToZXY * glm::abs(draggingHandle->normal);
// Checks if the rotation axis is facing towards, or away from the camera
// If it pointing away from the camera, then we need to invert the angle change
glm::vec4 rotationAxis = projection * view * glm::vec4((glm::vec3)(initialFrame * glm::abs(draggingHandle->normal)), 1.f);
glm::vec4 rotationAxis = projection * view * glm::vec4((glm::vec3)(initialFrame * handleNormal), 1.f);
rotationAxis /= rotationAxis.w;
glm::vec4 signVec = glm::normalize(rotationAxis - partCenterRaw);
float sign = -glm::sign(signVec.z);
glm::vec3 angles = glm::abs(draggingHandle->normal) * sign * glm::vec3(angle);
glm::vec3 angles = handleNormal * sign * glm::vec3(angle);
part->cframe = initialFrame * CFrame::FromEulerAnglesXYZ(-angles);
@ -299,8 +302,8 @@ void MainGLWidget::handleRotationalTransform(QMouseEvent* evt) {
}
std::optional<HandleFace> MainGLWidget::raycastHandle(glm::vec3 pointDir) {
if (editorToolHandles->adornee.expired() || !editorToolHandles->active) return std::nullopt;
return editorToolHandles->RaycastHandle(rp3d::Ray(glmToRp(camera.cameraPos), glmToRp(glm::normalize(pointDir)) * 50000));
if (!editorToolHandles.active) return std::nullopt;
return ::raycastHandle(rp3d::Ray(glmToRp(camera.cameraPos), glmToRp(glm::normalize(pointDir)) * 50000));
}
void MainGLWidget::handleCursorChange(QMouseEvent* evt) {
@ -363,7 +366,7 @@ void MainGLWidget::mousePressEvent(QMouseEvent* evt) {
auto handle = raycastHandle(pointDir);
if (handle.has_value()) {
startPoint = glm::vec2(evt->pos().x(), evt->pos().y());
initialFrame = editorToolHandles->adornee.lock()->cframe;
initialFrame = getHandleAdornee()->cframe;
isMouseDragging = true;
draggingHandle = handle;
return;

View file

@ -23,6 +23,7 @@
bool worldSpaceTransforms = false;
inline bool isDarkMode() {
// https://stackoverflow.com/a/78854851/16255372
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
const auto scheme = QGuiApplication::styleHints()->colorScheme();
return scheme == Qt::ColorScheme::Dark;
@ -62,7 +63,6 @@ MainWindow::MainWindow(QWidget *parent)
ui->setupUi(this);
setMouseTracking(true);
// https://stackoverflow.com/a/78854851/16255372
QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() + QStringList { "./assets/icons" });
if (isDarkMode())
QIcon::setFallbackThemeName("editor-dark");
@ -100,16 +100,6 @@ MainWindow::MainWindow(QWidget *parent)
});
connectActionHandlers();
// Update handles
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
editorToolHandles->adornee = {};
if (newSelection.size() == 0) return;
InstanceRef inst = newSelection[0].lock();
if (inst->GetClass() != &Part::TYPE) return;
editorToolHandles->adornee = std::dynamic_pointer_cast<Part>(inst);
});
// Update properties
addSelectionListener([&](auto oldSelection, auto newSelection, bool fromExplorer) {
@ -456,11 +446,11 @@ void MainWindow::updateToolbars() {
ui->actionGridSnap05->setChecked(snappingMode == GridSnappingMode::SNAP_05_STUDS);
ui->actionGridSnapOff->setChecked(snappingMode == GridSnappingMode::SNAP_OFF);
editorToolHandles->worldMode = (selectedTool == TOOL_SCALE || selectedTool == TOOL_ROTATE) ? false : worldSpaceTransforms;
editorToolHandles->nixAxes = selectedTool == TOOL_ROTATE;
editorToolHandles.worldMode = (selectedTool == TOOL_SCALE || selectedTool == TOOL_ROTATE) ? false : worldSpaceTransforms;
editorToolHandles.nixAxes = selectedTool == TOOL_ROTATE;
editorToolHandles->active = selectedTool > TOOL_SELECT && selectedTool < TOOL_SMOOTH;
editorToolHandles->handlesType =
editorToolHandles.active = selectedTool > TOOL_SELECT && selectedTool < TOOL_SMOOTH;
editorToolHandles.handlesType =
selectedTool == TOOL_MOVE ? HandlesType::MoveHandles
: selectedTool == TOOL_SCALE ? HandlesType::ScaleHandles
: selectedTool == TOOL_ROTATE ? HandlesType::RotateHandles

View file

@ -680,7 +680,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset theme="surface-weld"/>
<iconset theme="surface-hinge"/>
</property>
<property name="text">
<string>Hinge</string>
@ -697,7 +697,7 @@
<bool>true</bool>
</property>
<property name="icon">
<iconset theme="surface-weld"/>
<iconset theme="surface-motor"/>
</property>
<property name="text">
<string>Motor</string>

View file

@ -133,5 +133,5 @@ void PlaceDocument::init() {
std::shared_ptr<Script> script = Script::New();
gWorkspace()->AddChild(script);
MainWindow* mainWnd = dynamic_cast<MainWindow*>(window());
mainWnd->openScriptDocument(script);
// mainWnd->openScriptDocument(script);
}

View file

@ -4,6 +4,7 @@
#include <Qsci/qscilexerlua.h>
#include <Qsci/qsciscintillabase.h>
#include <Qsci/qscistyle.h>
#include <Qsci/qsciapis.h>
#include <map>
#include <memory>
#include <qboxlayout.h>
@ -12,9 +13,27 @@
#include <qdebug.h>
#include <qglobal.h>
#include <qlayout.h>
#include <qtextformat.h>
#include "mainwindow.h"
#include "objects/script.h"
#include "datatypes/meta.h"
#include <QPalette>
#include <QStyleHints>
QsciAPIs* makeApis(QsciLexer*);
inline bool isDarkMode() {
// https://stackoverflow.com/a/78854851/16255372
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
const auto scheme = QGuiApplication::styleHints()->colorScheme();
return scheme == Qt::ColorScheme::Dark;
#else
const QPalette defaultPalette;
const auto text = defaultPalette.color(QPalette::WindowText);
const auto window = defaultPalette.color(QPalette::Window);
return text.lightness() > window.lightness();
#endif // QT_VERSION
}
std::map<int, const QColor> DARK_MODE_COLOR_SCHEME = {{
{QsciLexerLua::Comment, QColor("#808080")},
@ -35,6 +54,61 @@ std::map<int, const QColor> DARK_MODE_COLOR_SCHEME = {{
}};
class ObLuaLexer : public QsciLexerLua {
const char * keywords(int set) const override {
// Taken from qscilexerlua.cpp
if (set == 1)
// Keywords.
return
"and break do else elseif end false for function if "
"in local nil not or repeat return then true until "
"while";
if (set == 2)
// Basic functions.
return
//"foreach foreachi getn "
// Openblocks extensions
"shared require game workspace "
"_G _VERSION getfenv getmetatable ipairs loadstring "
"next pairs pcall rawequal rawget rawset select "
"setfenv setmetatable xpcall string table tonumber "
"tostring type math newproxy coroutine io os";
if (set == 3)
// String, table and maths functions.
return
// "abs acos asin atan atan2 ceil cos deg exp floor "
// "format frexp gsub ldexp log log10 max min mod rad "
// "random randomseed sin sqrt tan "
"string.byte string.char string.dump string.find "
"string.len string.lower string.rep string.sub "
"string.upper string.format string.gfind string.gsub "
"table.concat table.foreach table.foreachi table.getn "
"table.sort table.insert table.remove table.setn "
"math.abs math.acos math.asin math.atan math.atan2 "
"math.ceil math.cos math.deg math.exp math.floor "
"math.frexp math.ldexp math.log math.log10 math.max "
"math.min math.mod math.pi math.rad math.random "
"math.randomseed math.sin math.sqrt math.tan";
if (set == 4)
// Coroutine, I/O and system facilities.
return
// "clock date difftime time "
"coroutine.create coroutine.resume coroutine.status "
"coroutine.wrap coroutine.yield os.clock os.date "
"os.difftime os.time";
return 0;
};
};
ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
script(script), QMdiSubWindow(parent) {
@ -49,18 +123,24 @@ ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
dynamic_cast<MainWindow*>(window())->closeScriptDocument(script);
});
// QFrame* frame = new QFrame;
// QVBoxLayout* frameLayout = new QVBoxLayout;
// frame->setLayout(frameLayout);
scintilla = new QsciScintilla;
// frameLayout->addWidget(scintilla);
// setWidget(frame);
setWidget(scintilla);
QFrame* frame = new QFrame;
QVBoxLayout* frameLayout = new QVBoxLayout;
frameLayout->setMargin(0);
frame->setLayout(frameLayout);
scintilla = new QsciScintilla(this);
frameLayout->addWidget(scintilla);
setWidget(frame);
// https://forum.qt.io/post/803690
QFont findFont("<NONE>");
findFont.setStyleHint(QFont::Monospace);
QFontInfo info(findFont);
QFont font;
font.setFamily("Consolas");
font.setStyleHint(QFont::Monospace);
font.setPointSize(12);
font.setFamily(info.family());
font.setPointSize(10);
font.setFixedPitch(true);
// scintilla->setMargins(2);
scintilla->setScrollWidth(1); // Hide scrollbars on empty document, it will grow automatically
@ -74,14 +154,27 @@ ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
scintilla->setText(QString::fromStdString(script->source));
QsciLexerLua* lexer = new QsciLexerLua;
ObLuaLexer* lexer = new ObLuaLexer;
lexer->setFont(font);
scintilla->setLexer(lexer);
// Remove background highlight color
lexer->setPaper(palette().light().color());
QsciAPIs* api = makeApis(lexer);
lexer->setAPIs(api);
scintilla->setAutoCompletionSource(QsciScintilla::AcsAPIs);
scintilla->setAutoCompletionThreshold(1);
scintilla->setAutoCompletionCaseSensitivity(false);
scintilla->setAutoCompletionReplaceWord(false);
scintilla->setCallTipsVisible(-1);
scintilla->setCallTipsPosition(QsciScintilla::CallTipsBelowText);
// Set color scheme
// https://stackoverflow.com/a/26318796/16255372
for (auto& [style, color] : DARK_MODE_COLOR_SCHEME) {
lexer->setColor(color, style);
if (isDarkMode()) {
for (auto& [style, color] : DARK_MODE_COLOR_SCHEME) {
lexer->setColor(color, style);
}
}
// lexer->setAutoIndentStyle(QsciScintilla::AiOpening | QsciScintilla::AiMaintain | QsciScintilla::AiClosing);
@ -96,3 +189,15 @@ ScriptDocument::ScriptDocument(std::shared_ptr<Script> script, QWidget* parent):
ScriptDocument::~ScriptDocument() {
}
QsciAPIs* makeApis(QsciLexer* lexer) {
QsciAPIs* apis = new QsciAPIs(lexer);
apis->add("workspace");
apis->add("game");
apis->add("wait(seconds: number)\n\nPauses the current thread for the specified number of seconds");
apis->prepare();
return apis;
}