2025-07-24 16:35:48 +03:00
|
|
|
#include "DrawingProgramSelection.hpp"
|
|
|
|
|
#include "DrawingProgram.hpp"
|
|
|
|
|
#include "../World.hpp"
|
|
|
|
|
#include "../MainProgram.hpp"
|
2025-09-29 15:15:16 +03:00
|
|
|
#include "DrawingProgramToolBase.hpp"
|
2025-08-04 12:02:29 +03:00
|
|
|
#include "Helpers/FixedPoint.hpp"
|
2025-07-24 19:35:51 +03:00
|
|
|
#include <Helpers/MathExtras.hpp>
|
|
|
|
|
#include <Helpers/SCollision.hpp>
|
2025-10-04 09:10:09 +03:00
|
|
|
#include <Helpers/Logger.hpp>
|
2025-07-24 19:35:51 +03:00
|
|
|
#include <Helpers/Parallel.hpp>
|
2025-07-24 16:35:48 +03:00
|
|
|
|
2025-08-03 18:20:39 +03:00
|
|
|
#ifdef USE_SKIA_BACKEND_GRAPHITE
|
|
|
|
|
#include <include/gpu/graphite/Surface.h>
|
|
|
|
|
#elif USE_SKIA_BACKEND_GANESH
|
|
|
|
|
#include <include/gpu/ganesh/GrDirectContext.h>
|
|
|
|
|
#include <include/gpu/ganesh/SkSurfaceGanesh.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <include/effects/SkImageFilters.h>
|
2025-11-18 18:15:50 +03:00
|
|
|
#include <include/core/SkPathBuilder.h>
|
2025-08-03 18:20:39 +03:00
|
|
|
|
2025-07-25 10:00:39 +03:00
|
|
|
#define ROTATION_POINT_RADIUS_MULTIPLIER 0.7f
|
|
|
|
|
#define ROTATION_POINTS_DISTANCE 20.0f
|
|
|
|
|
|
2025-07-24 16:35:48 +03:00
|
|
|
DrawingProgramSelection::DrawingProgramSelection(DrawingProgram& initDrawP):
|
|
|
|
|
cache(initDrawP),
|
|
|
|
|
drawP(initDrawP)
|
|
|
|
|
{}
|
|
|
|
|
|
2025-07-26 21:20:14 +03:00
|
|
|
const std::unordered_set<CollabListType::ObjectInfoPtr>& DrawingProgramSelection::get_selected_set() {
|
|
|
|
|
return selectedSet;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-27 16:30:45 +03:00
|
|
|
void DrawingProgramSelection::erase_component(const CollabListType::ObjectInfoPtr& objToCheck) {
|
|
|
|
|
if(is_selected(objToCheck)) {
|
|
|
|
|
cache.erase_component(objToCheck);
|
|
|
|
|
selectedSet.erase(objToCheck);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 19:15:43 +03:00
|
|
|
void DrawingProgramSelection::fully_collided_erase_select_objects_func(std::unordered_set<CollabListType::ObjectInfoPtr>& selectedComponents, const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode) {
|
|
|
|
|
if(bvhNode) {
|
|
|
|
|
for(auto& c : bvhNode->components)
|
|
|
|
|
selectedComponents.emplace(c);
|
|
|
|
|
for(auto& p : bvhNode->children)
|
|
|
|
|
fully_collided_erase_select_objects_func(selectedComponents, p);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-05 19:04:47 +03:00
|
|
|
|
2025-08-05 19:15:43 +03:00
|
|
|
std::function<bool(const std::shared_ptr<DrawingProgramCacheBVHNode>&, std::vector<CollabListType::ObjectInfoPtr>&)> DrawingProgramSelection::erase_select_objects_in_bvh_func(std::unordered_set<CollabListType::ObjectInfoPtr>& selectedComponents, const SCollision::ColliderCollection<float>& cC, const SCollision::ColliderCollection<WorldScalar>& cCWorld) {
|
2025-08-05 19:04:47 +03:00
|
|
|
auto toRet = [&](const auto& bvhNode, auto& comps) {
|
2025-07-27 16:30:45 +03:00
|
|
|
if(bvhNode && (bvhNode->coords.inverseScale << 9) < drawP.world.drawData.cam.c.inverseScale &&
|
2025-07-24 16:35:48 +03:00
|
|
|
SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.min)) &&
|
|
|
|
|
SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.max)) &&
|
|
|
|
|
SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.top_right())) &&
|
|
|
|
|
SCollision::collide(cC, drawP.world.drawData.cam.c.to_space(bvhNode->bounds.bottom_left()))) {
|
2025-08-05 19:15:43 +03:00
|
|
|
fully_collided_erase_select_objects_func(selectedComponents, bvhNode);
|
2025-07-24 16:35:48 +03:00
|
|
|
drawP.compCache.invalidate_cache_at_aabb_before_pos(bvhNode->bounds, 0);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
std::erase_if(comps, [&](auto& c) {
|
2025-07-28 14:25:28 +03:00
|
|
|
if(c->obj->collides_with(drawP.world.drawData.cam.c, cCWorld, cC)) {
|
2025-07-24 16:35:48 +03:00
|
|
|
selectedComponents.emplace(c);
|
|
|
|
|
drawP.compCache.invalidate_cache_at_aabb_before_pos(c->obj->worldAABB.value(), c->obj->collabListInfo.lock()->pos);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
return false;
|
2025-08-05 19:04:47 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return toRet;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-29 15:44:01 +03:00
|
|
|
void DrawingProgramSelection::add_from_cam_coord_collider_to_selection(const SCollision::ColliderCollection<float>& cC, bool frontObjectOnly) {
|
2025-08-05 19:04:47 +03:00
|
|
|
std::unordered_set<CollabListType::ObjectInfoPtr> selectedComponents;
|
2025-09-29 15:44:01 +03:00
|
|
|
if(frontObjectOnly) {
|
|
|
|
|
CollabListType::ObjectInfoPtr a = drawP.compCache.get_front_object_colliding_with(cC);
|
|
|
|
|
if(a) {
|
|
|
|
|
selectedComponents.emplace(a);
|
|
|
|
|
drawP.compCache.erase_component(a);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
auto cCWorld = drawP.world.drawData.cam.c.collider_to_world<SCollision::ColliderCollection<WorldScalar>, SCollision::ColliderCollection<float>>(cC);
|
|
|
|
|
drawP.compCache.traverse_bvh_erase_function(cCWorld.bounds, erase_select_objects_in_bvh_func(selectedComponents, cC, cCWorld));
|
2025-09-29 11:39:00 +03:00
|
|
|
}
|
2025-08-05 19:04:47 +03:00
|
|
|
add_to_selection(selectedComponents);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-29 15:44:01 +03:00
|
|
|
void DrawingProgramSelection::remove_from_cam_coord_collider_to_selection(const SCollision::ColliderCollection<float>& cC, bool frontObjectOnly) {
|
2025-08-05 19:04:47 +03:00
|
|
|
auto cCWorld = drawP.world.drawData.cam.c.collider_to_world<SCollision::ColliderCollection<WorldScalar>, SCollision::ColliderCollection<float>>(cC);
|
|
|
|
|
std::unordered_set<CollabListType::ObjectInfoPtr> selectedComponents;
|
2025-09-29 15:44:01 +03:00
|
|
|
if(frontObjectOnly) {
|
|
|
|
|
CollabListType::ObjectInfoPtr a = cache.get_front_object_colliding_with(cC);
|
|
|
|
|
if(a) {
|
|
|
|
|
selectedComponents.emplace(a);
|
|
|
|
|
cache.erase_component(a);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
cache.traverse_bvh_erase_function(cCWorld.bounds, erase_select_objects_in_bvh_func(selectedComponents, cC, cCWorld));
|
|
|
|
|
cache.clear_own_cached_surfaces();
|
2025-09-29 11:39:00 +03:00
|
|
|
}
|
2025-08-05 19:04:47 +03:00
|
|
|
std::erase_if(selectedSet, [&](auto& c) {
|
|
|
|
|
return selectedComponents.contains(c);
|
2025-07-24 16:35:48 +03:00
|
|
|
});
|
2025-08-05 19:04:47 +03:00
|
|
|
for(auto& obj : selectedComponents)
|
|
|
|
|
drawP.compCache.add_component(obj);
|
|
|
|
|
if(!is_something_selected()) {
|
|
|
|
|
reset_selection_data();
|
|
|
|
|
reset_transform_data();
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
calculate_aabb();
|
|
|
|
|
calculate_initial_rotate_center_location();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-24 16:35:48 +03:00
|
|
|
|
2025-08-05 19:04:47 +03:00
|
|
|
void DrawingProgramSelection::add_to_selection(const std::unordered_set<CollabListType::ObjectInfoPtr>& newSelection) {
|
|
|
|
|
selectedSet.insert(newSelection.begin(), newSelection.end());
|
|
|
|
|
for(auto& obj : newSelection)
|
|
|
|
|
cache.add_component(obj);
|
|
|
|
|
calculate_aabb();
|
|
|
|
|
calculate_initial_rotate_center_location();
|
2025-07-28 11:09:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DrawingProgramSelection::set_to_selection(const std::unordered_set<CollabListType::ObjectInfoPtr>& newSelection) {
|
|
|
|
|
selectedSet = newSelection;
|
2025-07-24 16:35:48 +03:00
|
|
|
for(auto& obj : selectedSet)
|
|
|
|
|
cache.add_component(obj);
|
|
|
|
|
calculate_aabb();
|
2025-07-25 10:00:39 +03:00
|
|
|
calculate_initial_rotate_center_location();
|
2025-07-24 16:35:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DrawingProgramSelection::is_something_selected() {
|
|
|
|
|
return !selectedSet.empty();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DrawingProgramSelection::calculate_aabb() {
|
|
|
|
|
if(is_something_selected()) {
|
2025-07-24 19:35:51 +03:00
|
|
|
initialSelectionAABB = (*selectedSet.begin())->obj->worldAABB.value();
|
2025-07-24 16:35:48 +03:00
|
|
|
for(auto& c : selectedSet)
|
2025-07-24 19:35:51 +03:00
|
|
|
initialSelectionAABB.include_aabb_in_bounds(c->obj->worldAABB.value());
|
2025-07-24 16:35:48 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-05 11:35:22 +03:00
|
|
|
void DrawingProgramSelection::deselect_all() {
|
2025-07-24 19:35:51 +03:00
|
|
|
if(is_something_selected()) {
|
2025-08-05 11:35:22 +03:00
|
|
|
bool cacheWillRebuildAnyway = selectedSet.size() >= DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD;
|
|
|
|
|
if(cacheWillRebuildAnyway)
|
2025-08-05 19:54:53 +03:00
|
|
|
drawP.compCache.test_rebuild(drawP.components.client_list(), true);
|
2025-08-05 11:35:22 +03:00
|
|
|
else {
|
|
|
|
|
for(auto& obj : selectedSet)
|
|
|
|
|
drawP.compCache.add_component(obj);
|
|
|
|
|
}
|
2025-07-28 09:17:11 +03:00
|
|
|
reset_selection_data();
|
2025-08-05 11:35:22 +03:00
|
|
|
reset_transform_data();
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-27 16:30:45 +03:00
|
|
|
|
2025-08-05 11:35:22 +03:00
|
|
|
void DrawingProgramSelection::commit_transform_selection() {
|
|
|
|
|
auto selectedSetTemp = selectedSet;
|
|
|
|
|
|
|
|
|
|
std::vector<CollabListType::ObjectInfoPtr> a(selectedSet.begin(), selectedSet.end());
|
|
|
|
|
std::vector<std::pair<CollabListType::ObjectInfoPtr, CoordSpaceHelper>> transformsFrom;
|
|
|
|
|
for(auto& transformedObj : a)
|
|
|
|
|
transformsFrom.emplace_back(transformedObj, transformedObj->obj->coords);
|
|
|
|
|
bool isSingleThread = a.size() < DrawingProgramCache::MINIMUM_COMPONENTS_TO_START_REBUILD;
|
|
|
|
|
parallel_loop_container(a, [&](auto& obj) {
|
|
|
|
|
obj->obj->coords = selectionTransformCoords.other_coord_space_from_this_space(obj->obj->coords);
|
|
|
|
|
obj->obj->commit_transform(drawP, false);
|
|
|
|
|
}, isSingleThread);
|
|
|
|
|
std::vector<std::pair<CollabListType::ObjectInfoPtr, CoordSpaceHelper>> transformsTo;
|
|
|
|
|
std::vector<std::pair<ServerClientID, CoordSpaceHelper>> transformsToSend;
|
|
|
|
|
for(auto& transformedObj : a) {
|
2025-08-27 11:38:36 +03:00
|
|
|
transformsToSend.emplace_back(transformedObj->obj->id, transformedObj->obj->coords);
|
2025-08-05 11:35:22 +03:00
|
|
|
transformsTo.emplace_back(transformedObj, transformedObj->obj->coords);
|
|
|
|
|
}
|
|
|
|
|
DrawComponent::client_send_transform_many(drawP, transformsToSend);
|
|
|
|
|
drawP.world.undo.push(UndoManager::UndoRedoPair{
|
2025-10-25 17:31:55 +03:00
|
|
|
[isSingleThread, &drawP = drawP, transformsFrom = transformsFrom]() {
|
2025-08-05 11:35:22 +03:00
|
|
|
std::vector<std::pair<ServerClientID, CoordSpaceHelper>> transformsToSend;
|
|
|
|
|
for(auto& [comp, coords] : transformsFrom) {
|
|
|
|
|
auto lockObjInfo = comp->obj->collabListInfo.lock();
|
|
|
|
|
if(lockObjInfo)
|
2025-08-27 11:38:36 +03:00
|
|
|
transformsToSend.emplace_back(lockObjInfo->obj->id, coords);
|
2025-08-05 11:35:22 +03:00
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parallel_loop_container(transformsFrom, [&](auto& p) {
|
|
|
|
|
auto& [comp, coords] = p;
|
|
|
|
|
comp->obj->coords = coords;
|
|
|
|
|
comp->obj->commit_transform(drawP, isSingleThread);
|
|
|
|
|
}, isSingleThread);
|
|
|
|
|
|
|
|
|
|
DrawComponent::client_send_transform_many(drawP, transformsToSend);
|
2025-08-04 13:30:47 +03:00
|
|
|
|
|
|
|
|
if(!isSingleThread)
|
|
|
|
|
drawP.force_rebuild_cache();
|
2025-08-05 11:35:22 +03:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
},
|
2025-10-25 17:31:55 +03:00
|
|
|
[isSingleThread, &drawP = drawP, transformsTo = transformsTo]() {
|
2025-08-05 11:35:22 +03:00
|
|
|
std::vector<std::pair<ServerClientID, CoordSpaceHelper>> transformsToSend;
|
|
|
|
|
for(auto& [comp, coords] : transformsTo) {
|
|
|
|
|
auto lockObjInfo = comp->obj->collabListInfo.lock();
|
|
|
|
|
if(lockObjInfo)
|
2025-08-27 11:38:36 +03:00
|
|
|
transformsToSend.emplace_back(lockObjInfo->obj->id, coords);
|
2025-08-05 11:35:22 +03:00
|
|
|
else
|
|
|
|
|
return false;
|
2025-08-04 13:30:47 +03:00
|
|
|
}
|
2025-07-27 16:30:45 +03:00
|
|
|
|
2025-08-05 11:35:22 +03:00
|
|
|
parallel_loop_container(transformsTo, [&](auto& p) {
|
|
|
|
|
auto& [comp, coords] = p;
|
|
|
|
|
comp->obj->coords = coords;
|
|
|
|
|
comp->obj->commit_transform(drawP, isSingleThread);
|
|
|
|
|
}, isSingleThread);
|
2025-07-27 16:30:45 +03:00
|
|
|
|
2025-08-05 11:35:22 +03:00
|
|
|
DrawComponent::client_send_transform_many(drawP, transformsToSend);
|
2025-07-27 11:56:34 +03:00
|
|
|
|
2025-08-05 11:35:22 +03:00
|
|
|
if(!isSingleThread)
|
|
|
|
|
drawP.force_rebuild_cache();
|
2025-07-27 11:56:34 +03:00
|
|
|
|
2025-08-05 11:35:22 +03:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-07-24 16:35:48 +03:00
|
|
|
|
2025-08-05 11:35:22 +03:00
|
|
|
reset_selection_data();
|
|
|
|
|
reset_transform_data();
|
2025-08-04 13:30:47 +03:00
|
|
|
set_to_selection(selectedSetTemp);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-29 15:44:01 +03:00
|
|
|
CollabListType::ObjectInfoPtr DrawingProgramSelection::get_front_object_colliding_with(const SCollision::ColliderCollection<float>& cC) {
|
|
|
|
|
return cache.get_front_object_colliding_with(cC);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 19:28:18 +03:00
|
|
|
void DrawingProgramSelection::invalidate_cache_at_optional_aabb_before_pos(const std::optional<SCollision::AABB<WorldScalar>>& aabb, uint64_t placementToInvalidateAt) {
|
|
|
|
|
cache.invalidate_cache_at_optional_aabb_before_pos(aabb, placementToInvalidateAt);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-13 19:23:14 +03:00
|
|
|
void DrawingProgramSelection::clear_own_cached_surfaces() {
|
|
|
|
|
cache.clear_own_cached_surfaces();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-31 19:28:18 +03:00
|
|
|
void DrawingProgramSelection::preupdate_component(const CollabListType::ObjectInfoPtr& objToCheck) {
|
|
|
|
|
cache.preupdate_component(objToCheck);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-26 21:20:14 +03:00
|
|
|
bool DrawingProgramSelection::is_selected(const CollabListType::ObjectInfoPtr& objToCheck) {
|
|
|
|
|
return selectedSet.contains(objToCheck);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-27 20:04:26 +03:00
|
|
|
void DrawingProgramSelection::delete_all() {
|
2025-07-24 16:35:48 +03:00
|
|
|
if(is_something_selected()) {
|
2025-07-27 20:04:26 +03:00
|
|
|
drawP.client_erase_set(selectedSet);
|
2025-07-28 09:17:11 +03:00
|
|
|
reset_selection_data();
|
|
|
|
|
reset_transform_data();
|
2025-07-27 20:04:26 +03:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-26 21:20:14 +03:00
|
|
|
|
2025-07-28 11:09:59 +03:00
|
|
|
// NOTE: Only run this if no transformations happened
|
|
|
|
|
void DrawingProgramSelection::selection_to_clipboard() {
|
|
|
|
|
auto& clipboard = drawP.world.main.clipboard;
|
|
|
|
|
std::unordered_set<ServerClientID> resourceSet;
|
|
|
|
|
for(auto& c : selectedSet)
|
|
|
|
|
c->obj->get_used_resources(resourceSet);
|
|
|
|
|
std::vector<CollabListType::ObjectInfoPtr> compVecSort(selectedSet.begin(), selectedSet.end());
|
|
|
|
|
std::sort(compVecSort.begin(), compVecSort.end(), [&](auto& a, auto& b) {
|
|
|
|
|
return a->pos < b->pos;
|
|
|
|
|
});
|
|
|
|
|
clipboard.components.clear();
|
|
|
|
|
for(auto& c : compVecSort)
|
2025-11-16 16:49:47 +03:00
|
|
|
clipboard.components.emplace_back(c->obj->deep_copy(drawP));
|
2025-07-28 11:09:59 +03:00
|
|
|
clipboard.pos = initialSelectionAABB.center();
|
2025-10-04 09:10:09 +03:00
|
|
|
clipboard.inverseScale = drawP.world.drawData.cam.c.inverseScale;
|
2025-08-17 10:38:49 +03:00
|
|
|
drawP.world.rMan.copy_resource_set_to_map(resourceSet, clipboard.resources);
|
2025-07-28 11:09:59 +03:00
|
|
|
}
|
|
|
|
|
|
2025-07-27 20:04:26 +03:00
|
|
|
void DrawingProgramSelection::update() {
|
|
|
|
|
if(is_something_selected()) {
|
2025-07-26 21:20:14 +03:00
|
|
|
if(cache.check_if_rebuild_should_occur())
|
|
|
|
|
cache.test_rebuild(std::vector<CollabListType::ObjectInfoPtr>(selectedSet.begin(), selectedSet.end()), true);
|
|
|
|
|
|
2025-07-25 10:00:39 +03:00
|
|
|
selectionRectPoints[0] = selectionTransformCoords.from_space_world(initialSelectionAABB.min);
|
|
|
|
|
selectionRectPoints[1] = selectionTransformCoords.from_space_world(initialSelectionAABB.bottom_left());
|
|
|
|
|
selectionRectPoints[2] = selectionTransformCoords.from_space_world(initialSelectionAABB.max);
|
|
|
|
|
selectionRectPoints[3] = selectionTransformCoords.from_space_world(initialSelectionAABB.top_right());
|
2025-07-24 19:35:51 +03:00
|
|
|
|
2025-08-04 18:14:28 +03:00
|
|
|
scaleData.handlePoint = drawP.world.drawData.cam.c.to_space(initialSelectionAABB.max);
|
|
|
|
|
rotateData.centerHandlePoint = drawP.world.drawData.cam.c.to_space(rotateData.centerPos);
|
2025-07-25 10:00:39 +03:00
|
|
|
rotateData.handlePoint = get_rotation_point_pos_from_angle(rotateData.rotationAngle);
|
2025-07-24 20:36:40 +03:00
|
|
|
|
2025-07-24 19:35:51 +03:00
|
|
|
rebuild_cam_space();
|
2025-07-24 20:36:40 +03:00
|
|
|
|
2025-10-03 13:16:38 +03:00
|
|
|
if(drawP.is_actual_selection_tool(drawP.drawTool->get_type())) {
|
|
|
|
|
switch(transformOpHappening) {
|
|
|
|
|
case TransformOperation::NONE:
|
|
|
|
|
if(drawP.controls.leftClick && !drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held && !drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held) {
|
|
|
|
|
if(mouse_collided_with_scale_point()) {
|
|
|
|
|
scaleData.currentPos = scaleData.startPos = selectionTransformCoords.from_space_world(initialSelectionAABB.max);
|
|
|
|
|
scaleData.centerPos = selectionTransformCoords.from_space_world(initialSelectionAABB.center());
|
|
|
|
|
transformOpHappening = TransformOperation::SCALE;
|
|
|
|
|
}
|
|
|
|
|
else if(mouse_collided_with_rotate_center_handle_point())
|
|
|
|
|
transformOpHappening = TransformOperation::ROTATE_RELOCATE_CENTER;
|
|
|
|
|
else if(mouse_collided_with_rotate_handle_point()) {
|
|
|
|
|
rotateData.rotationAngle = 0.0;
|
|
|
|
|
transformOpHappening = TransformOperation::ROTATE;
|
|
|
|
|
}
|
|
|
|
|
else if(mouse_collided_with_selection_aabb()) {
|
|
|
|
|
translateData.startPos = drawP.world.get_mouse_world_pos();
|
|
|
|
|
transformOpHappening = TransformOperation::TRANSLATE;
|
|
|
|
|
}
|
2025-07-24 20:36:40 +03:00
|
|
|
}
|
2025-10-03 13:16:38 +03:00
|
|
|
break;
|
|
|
|
|
case TransformOperation::TRANSLATE:
|
|
|
|
|
if(drawP.controls.leftClickHeld)
|
|
|
|
|
selectionTransformCoords = CoordSpaceHelperTransform(drawP.world.get_mouse_world_pos() - translateData.startPos);
|
|
|
|
|
else {
|
|
|
|
|
commit_transform_selection();
|
|
|
|
|
return;
|
2025-07-25 10:00:39 +03:00
|
|
|
}
|
2025-10-03 13:16:38 +03:00
|
|
|
break;
|
|
|
|
|
case TransformOperation::SCALE:
|
|
|
|
|
if(drawP.controls.leftClickHeld) {
|
|
|
|
|
WorldVec centerToScaleStart = scaleData.startPos - scaleData.centerPos;
|
|
|
|
|
Vector2f centerToScaleStartInCamSpace = drawP.world.drawData.cam.c.normalized_dir_to_space(centerToScaleStart.normalized());
|
|
|
|
|
Vector2f scaleCenterPointInCamSpace = drawP.world.drawData.cam.c.to_space(scaleData.centerPos);
|
|
|
|
|
scaleData.currentPos = drawP.world.drawData.cam.c.from_space(project_point_on_line(drawP.world.main.input.mouse.pos, scaleCenterPointInCamSpace, (scaleCenterPointInCamSpace + centerToScaleStartInCamSpace).eval()));
|
|
|
|
|
|
|
|
|
|
WorldVec centerToScaleCurrent = scaleData.currentPos - scaleData.centerPos;
|
|
|
|
|
|
|
|
|
|
WorldScalar centerToScaleStartNorm = centerToScaleStart.norm();
|
|
|
|
|
WorldScalar centerToScaleCurrentNorm = centerToScaleCurrent.norm();
|
|
|
|
|
bool isAnyNumberZero = centerToScaleCurrentNorm == WorldScalar(0) || centerToScaleStartNorm == WorldScalar(0);
|
|
|
|
|
|
|
|
|
|
if(!isAnyNumberZero) {
|
|
|
|
|
WorldMultiplier scaleAmount = WorldMultiplier(centerToScaleStartNorm) / WorldMultiplier(centerToScaleCurrentNorm);
|
|
|
|
|
|
|
|
|
|
selectionTransformCoords = CoordSpaceHelperTransform(scaleData.centerPos, scaleAmount);
|
|
|
|
|
}
|
2025-07-28 09:17:11 +03:00
|
|
|
}
|
2025-10-03 13:16:38 +03:00
|
|
|
else {
|
|
|
|
|
commit_transform_selection();
|
|
|
|
|
return;
|
2025-07-24 20:36:40 +03:00
|
|
|
}
|
2025-10-03 13:16:38 +03:00
|
|
|
break;
|
|
|
|
|
case TransformOperation::ROTATE_RELOCATE_CENTER:
|
|
|
|
|
if(drawP.controls.leftClickHeld)
|
|
|
|
|
rotateData.centerPos = drawP.world.get_mouse_world_pos();
|
|
|
|
|
else
|
|
|
|
|
transformOpHappening = TransformOperation::NONE;
|
|
|
|
|
break;
|
|
|
|
|
case TransformOperation::ROTATE:
|
|
|
|
|
if(drawP.controls.leftClickHeld) {
|
|
|
|
|
Vector2f rotationPointDiff = drawP.world.main.input.mouse.pos - rotateData.centerHandlePoint;
|
|
|
|
|
rotateData.rotationAngle = std::atan2(rotationPointDiff.y(), rotationPointDiff.x());
|
|
|
|
|
selectionTransformCoords = CoordSpaceHelperTransform(rotateData.centerPos, rotateData.rotationAngle);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
commit_transform_selection();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(transformOpHappening != TransformOperation::NONE) {
|
|
|
|
|
commit_transform_selection();
|
|
|
|
|
return;
|
2025-07-24 20:36:40 +03:00
|
|
|
}
|
2025-07-28 11:09:59 +03:00
|
|
|
|
2025-09-29 15:15:16 +03:00
|
|
|
if(drawP.world.main.input.key(InputManager::KEY_GENERIC_ESCAPE).pressed)
|
2025-07-28 11:09:59 +03:00
|
|
|
deselect_all();
|
|
|
|
|
else if(drawP.world.main.input.key(InputManager::KEY_DRAW_DELETE).pressed)
|
|
|
|
|
delete_all();
|
2025-09-29 15:15:16 +03:00
|
|
|
else if(drawP.world.main.input.key(InputManager::KEY_COPY).pressed && !is_being_transformed())
|
2025-07-28 11:09:59 +03:00
|
|
|
selection_to_clipboard();
|
2025-09-29 15:15:16 +03:00
|
|
|
else if(drawP.world.main.input.key(InputManager::KEY_CUT).pressed && !is_being_transformed()) {
|
2025-07-28 11:09:59 +03:00
|
|
|
selection_to_clipboard();
|
|
|
|
|
delete_all();
|
|
|
|
|
}
|
|
|
|
|
else if(drawP.world.main.input.key(InputManager::KEY_PASTE).pressed) {
|
|
|
|
|
deselect_all();
|
|
|
|
|
paste_clipboard();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if(drawP.world.main.input.key(InputManager::KEY_PASTE).pressed && !drawP.prevent_undo_or_redo())
|
|
|
|
|
paste_clipboard();
|
2025-09-29 15:15:16 +03:00
|
|
|
else if(drawP.world.main.input.key(InputManager::KEY_GENERIC_ESCAPE).pressed)
|
|
|
|
|
drawP.switch_to_tool(DrawingProgramToolType::EDIT, true);
|
2025-07-28 11:09:59 +03:00
|
|
|
}
|
|
|
|
|
|
2025-08-05 19:04:47 +03:00
|
|
|
bool DrawingProgramSelection::is_being_transformed() {
|
|
|
|
|
return transformOpHappening != TransformOperation::NONE;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-28 11:09:59 +03:00
|
|
|
void DrawingProgramSelection::paste_clipboard() {
|
|
|
|
|
auto& clipboard = drawP.world.main.clipboard;
|
2025-08-02 20:10:33 +03:00
|
|
|
|
2025-08-02 20:39:21 +03:00
|
|
|
drawP.switch_to_tool(DrawingProgramToolType::RECTSELECT);
|
2025-08-02 20:10:33 +03:00
|
|
|
|
2025-07-28 11:09:59 +03:00
|
|
|
uint64_t allPlacement = drawP.components.client_list().size();
|
|
|
|
|
std::vector<std::shared_ptr<DrawComponent>> placedComponents;
|
|
|
|
|
WorldVec mousePos = drawP.world.get_mouse_world_pos();
|
|
|
|
|
WorldVec moveVec = drawP.world.main.clipboard.pos - mousePos;
|
2025-10-04 09:10:09 +03:00
|
|
|
WorldMultiplier scaleMultiplier = WorldMultiplier(drawP.world.main.clipboard.inverseScale) / WorldMultiplier(drawP.world.drawData.cam.c.inverseScale);
|
2025-10-04 09:27:40 +03:00
|
|
|
WorldScalar scaleLimit(0.001);
|
2025-10-04 09:10:09 +03:00
|
|
|
for(auto& c : drawP.world.main.clipboard.components) {
|
2025-10-04 09:27:40 +03:00
|
|
|
if((c->coords.inverseScale / scaleMultiplier) < scaleLimit) {
|
2025-10-04 09:10:09 +03:00
|
|
|
Logger::get().log("WORLDFATAL", "Some pasted objects will be too small! Scale up the canvas first (zoom in alot)");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-28 11:09:59 +03:00
|
|
|
std::unordered_map<ServerClientID, ServerClientID> resourceRemapIDs;
|
|
|
|
|
for(auto& r : clipboard.resources)
|
|
|
|
|
resourceRemapIDs[r.first] = drawP.world.rMan.add_resource(r.second);
|
2025-07-28 13:49:46 +03:00
|
|
|
for(auto& c : clipboard.components)
|
2025-11-16 16:49:47 +03:00
|
|
|
placedComponents.emplace_back(c->deep_copy(drawP));
|
2025-07-28 11:09:59 +03:00
|
|
|
drawP.addToCompCacheOnInsert = false;
|
|
|
|
|
auto compListInserted = drawP.components.client_insert_ordered_vector_items(allPlacement, placedComponents);
|
2025-07-28 13:49:46 +03:00
|
|
|
parallel_loop_container(compListInserted, [&](auto& c) {
|
|
|
|
|
c->obj->remap_resource_ids(resourceRemapIDs);
|
|
|
|
|
c->obj->coords.translate(-moveVec);
|
2025-10-04 09:10:09 +03:00
|
|
|
c->obj->coords.scale_about(mousePos, scaleMultiplier);
|
|
|
|
|
//std::cout << c->obj->coords.inverseScale << std::endl;
|
|
|
|
|
//if(c->obj->coords.inverseScale == WorldScalar(0))
|
|
|
|
|
// throw std::runtime_error("DONE");
|
2025-07-31 09:34:17 +03:00
|
|
|
c->obj->commit_transform(drawP, false);
|
2025-07-28 13:49:46 +03:00
|
|
|
});
|
|
|
|
|
DrawComponent::client_send_place_many(drawP, compListInserted);
|
2025-07-28 11:09:59 +03:00
|
|
|
drawP.addToCompCacheOnInsert = true;
|
|
|
|
|
std::unordered_set<CollabListType::ObjectInfoPtr> compSetInserted(compListInserted.begin(), compListInserted.end());
|
|
|
|
|
set_to_selection(compSetInserted);
|
|
|
|
|
drawP.add_undo_place_components(compSetInserted);
|
2025-07-24 16:35:48 +03:00
|
|
|
}
|
|
|
|
|
|
2025-07-24 19:35:51 +03:00
|
|
|
void DrawingProgramSelection::rebuild_cam_space() {
|
|
|
|
|
SCollision::ColliderCollection<WorldScalar> collideRectWorld;
|
2025-07-25 10:00:39 +03:00
|
|
|
collideRectWorld.triangle.emplace_back(selectionRectPoints[0], selectionRectPoints[1], selectionRectPoints[2]);
|
|
|
|
|
collideRectWorld.triangle.emplace_back(selectionRectPoints[0], selectionRectPoints[2], selectionRectPoints[3]);
|
2025-07-24 19:35:51 +03:00
|
|
|
collideRectWorld.recalculate_bounds();
|
|
|
|
|
|
|
|
|
|
camSpaceSelection = drawP.world.drawData.cam.c.world_collider_to_coords<SCollision::ColliderCollection<float>>(collideRectWorld);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-25 10:00:39 +03:00
|
|
|
void DrawingProgramSelection::calculate_initial_rotate_center_location() {
|
|
|
|
|
rotateData.centerPos = initialSelectionAABB.center();
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-24 20:36:40 +03:00
|
|
|
bool DrawingProgramSelection::mouse_collided_with_selection() {
|
2025-07-25 10:00:39 +03:00
|
|
|
return mouse_collided_with_selection_aabb() || mouse_collided_with_scale_point() || mouse_collided_with_rotate_center_handle_point() || mouse_collided_with_rotate_handle_point();
|
2025-07-24 20:36:40 +03:00
|
|
|
}
|
|
|
|
|
|
2025-07-24 16:35:48 +03:00
|
|
|
bool DrawingProgramSelection::mouse_collided_with_selection_aabb() {
|
2025-07-24 19:35:51 +03:00
|
|
|
return SCollision::collide(camSpaceSelection, drawP.world.main.input.mouse.pos);
|
2025-07-24 16:35:48 +03:00
|
|
|
}
|
|
|
|
|
|
2025-07-24 20:36:40 +03:00
|
|
|
bool DrawingProgramSelection::mouse_collided_with_scale_point() {
|
2025-11-23 15:02:02 +03:00
|
|
|
return SCollision::collide(SCollision::Circle(scaleData.handlePoint, drawP.drag_point_radius()), drawP.world.main.input.mouse.pos);
|
2025-07-24 20:36:40 +03:00
|
|
|
}
|
|
|
|
|
|
2025-07-25 10:00:39 +03:00
|
|
|
bool DrawingProgramSelection::mouse_collided_with_rotate_center_handle_point() {
|
2025-11-23 15:02:02 +03:00
|
|
|
return SCollision::collide(SCollision::Circle(rotateData.centerHandlePoint, drawP.drag_point_radius()), drawP.world.main.input.mouse.pos);
|
2025-07-25 10:00:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool DrawingProgramSelection::mouse_collided_with_rotate_handle_point() {
|
2025-11-23 15:02:02 +03:00
|
|
|
return SCollision::collide(SCollision::Circle(rotateData.handlePoint, drawP.drag_point_radius() * ROTATION_POINT_RADIUS_MULTIPLIER), drawP.world.main.input.mouse.pos);
|
2025-07-25 10:00:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Vector2f DrawingProgramSelection::get_rotation_point_pos_from_angle(double angle) {
|
|
|
|
|
return Vector2f{rotateData.centerHandlePoint + ROTATION_POINTS_DISTANCE * Vector2f{std::cos(angle), std::sin(angle)}};
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-24 19:35:51 +03:00
|
|
|
void DrawingProgramSelection::draw_components(SkCanvas* canvas, const DrawData& drawData) {
|
2025-07-24 16:35:48 +03:00
|
|
|
if(is_something_selected()) {
|
2025-08-04 13:30:47 +03:00
|
|
|
selectionRectPoints[0] = selectionTransformCoords.from_space_world(initialSelectionAABB.min);
|
|
|
|
|
selectionRectPoints[1] = selectionTransformCoords.from_space_world(initialSelectionAABB.bottom_left());
|
|
|
|
|
selectionRectPoints[2] = selectionTransformCoords.from_space_world(initialSelectionAABB.max);
|
|
|
|
|
selectionRectPoints[3] = selectionTransformCoords.from_space_world(initialSelectionAABB.top_right());
|
|
|
|
|
|
|
|
|
|
scaleData.handlePoint = drawP.world.drawData.cam.c.to_space(selectionTransformCoords.from_space_world(initialSelectionAABB.max));
|
|
|
|
|
rotateData.centerHandlePoint = drawP.world.drawData.cam.c.to_space(selectionTransformCoords.from_space_world(rotateData.centerPos));
|
|
|
|
|
rotateData.handlePoint = get_rotation_point_pos_from_angle(rotateData.rotationAngle);
|
|
|
|
|
|
|
|
|
|
rebuild_cam_space();
|
|
|
|
|
|
2025-07-24 16:35:48 +03:00
|
|
|
DrawData selectionDrawData = drawData;
|
|
|
|
|
selectionDrawData.cam.c = selectionTransformCoords.other_coord_space_to_this_space(selectionDrawData.cam.c);
|
|
|
|
|
selectionDrawData.cam.set_viewing_area(drawP.world.main.window.size.cast<float>());
|
|
|
|
|
selectionDrawData.refresh_draw_optimizing_values();
|
|
|
|
|
|
|
|
|
|
cache.refresh_all_draw_cache(selectionDrawData);
|
2025-08-03 18:20:39 +03:00
|
|
|
|
|
|
|
|
SkImageInfo imgInfo = canvas->imageInfo();
|
|
|
|
|
sk_sp<SkSurface> surface;
|
|
|
|
|
#ifdef USE_SKIA_BACKEND_GRAPHITE
|
|
|
|
|
surface = SkSurfaces::RenderTarget(drawP.world.main.window.recorder(), imgInfo, skgpu::Mipmapped::kNo, drawP.world.main.window.defaultMSAASurfaceProps);
|
|
|
|
|
#elif USE_SKIA_BACKEND_GANESH
|
|
|
|
|
surface = SkSurfaces::RenderTarget(drawP.world.main.window.ctx.get(), skgpu::Budgeted::kNo, imgInfo, drawP.world.main.window.defaultMSAASampleCount, &drawP.world.main.window.defaultMSAASurfaceProps);
|
|
|
|
|
#endif
|
|
|
|
|
if(!surface)
|
|
|
|
|
throw std::runtime_error("[DrawingProgramSelection::draw_components] Could not make temporary surface");
|
|
|
|
|
|
2025-08-07 21:30:40 +03:00
|
|
|
cache.draw_components_to_canvas(surface->getCanvas(), selectionDrawData, {});
|
2025-08-03 18:20:39 +03:00
|
|
|
|
|
|
|
|
SkPaint glowBlurP;
|
|
|
|
|
glowBlurP.setImageFilter(SkImageFilters::Blur(5, 5, nullptr));
|
|
|
|
|
|
|
|
|
|
canvas->drawImage(surface->makeTemporaryImage(), 0, 0, SkSamplingOptions{SkFilterMode::kLinear}, &glowBlurP);
|
|
|
|
|
canvas->drawImage(surface->makeTemporaryImage(), 0, 0, SkSamplingOptions{SkFilterMode::kLinear}, nullptr);
|
2025-07-24 19:35:51 +03:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-24 16:35:48 +03:00
|
|
|
|
2025-07-24 19:35:51 +03:00
|
|
|
void DrawingProgramSelection::draw_gui(SkCanvas* canvas, const DrawData& drawData) {
|
|
|
|
|
if(is_something_selected() && !camSpaceSelection.triangle.empty()) {
|
2025-11-18 18:15:50 +03:00
|
|
|
SkPathBuilder selectionRectPath;
|
2025-07-24 19:35:51 +03:00
|
|
|
selectionRectPath.moveTo(convert_vec2<SkPoint>(camSpaceSelection.triangle[0].p[0]));
|
|
|
|
|
selectionRectPath.lineTo(convert_vec2<SkPoint>(camSpaceSelection.triangle[0].p[1]));
|
|
|
|
|
selectionRectPath.lineTo(convert_vec2<SkPoint>(camSpaceSelection.triangle[0].p[2]));
|
|
|
|
|
selectionRectPath.lineTo(convert_vec2<SkPoint>(camSpaceSelection.triangle[1].p[2]));
|
|
|
|
|
selectionRectPath.close();
|
|
|
|
|
SkPaint p{SkColor4f{0.3f, 0.6f, 0.9f, 0.4f}};
|
2025-11-18 18:15:50 +03:00
|
|
|
canvas->drawPath(selectionRectPath.detach(), p);
|
2025-07-24 20:36:40 +03:00
|
|
|
|
2025-10-03 13:16:38 +03:00
|
|
|
if(drawP.is_actual_selection_tool(drawP.drawTool->get_type())) {
|
|
|
|
|
if(transformOpHappening == TransformOperation::NONE || transformOpHappening == TransformOperation::ROTATE || transformOpHappening == TransformOperation::ROTATE_RELOCATE_CENTER)
|
|
|
|
|
drawP.draw_drag_circle(canvas, rotateData.centerHandlePoint, {0.9f, 0.5f, 0.1f, 1.0f}, drawData);
|
|
|
|
|
if(transformOpHappening == TransformOperation::NONE || transformOpHappening == TransformOperation::ROTATE)
|
|
|
|
|
drawP.draw_drag_circle(canvas, rotateData.handlePoint, {0.9f, 0.5f, 0.1f, 1.0f}, drawData, ROTATION_POINT_RADIUS_MULTIPLIER);
|
|
|
|
|
if(transformOpHappening == TransformOperation::NONE || transformOpHappening == TransformOperation::SCALE)
|
|
|
|
|
drawP.draw_drag_circle(canvas, scaleData.handlePoint, {0.1f, 0.9f, 0.9f, 1.0f}, drawData);
|
|
|
|
|
}
|
2025-07-24 16:35:48 +03:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-28 09:17:11 +03:00
|
|
|
|
|
|
|
|
void DrawingProgramSelection::reset_selection_data() {
|
|
|
|
|
selectedSet.clear();
|
|
|
|
|
cache.clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DrawingProgramSelection::reset_transform_data() {
|
2025-08-04 10:47:24 +03:00
|
|
|
selectionTransformCoords = CoordSpaceHelperTransform();
|
2025-07-28 09:17:11 +03:00
|
|
|
transformOpHappening = TransformOperation::NONE;
|
|
|
|
|
translateData = TranslationData();
|
|
|
|
|
scaleData = ScaleData();
|
|
|
|
|
rotateData = RotationData();
|
|
|
|
|
}
|