Initial Commit

This commit is contained in:
Yousef Khadadeh 2025-07-06 20:43:53 +03:00
commit 6088fc29ed
978 changed files with 353647 additions and 0 deletions

View file

@ -0,0 +1,399 @@
#include "DrawBrushStroke.hpp"
#include <Helpers/Serializers.hpp>
#include <cereal/types/vector.hpp>
#include <limits>
#ifndef IS_SERVER
#include <Eigen/Geometry>
#include "Helpers/MathExtras.hpp"
#include "../TimePoint.hpp"
#include <include/core/SkVertices.h>
#include "../DrawCollision.hpp"
#endif
#define AABB_PRECHECK_BVH_LEVELS 2
void DrawBrushStroke::save(cereal::PortableBinaryOutputArchive& a) const {
a(d.points, d.color, d.hasRoundCaps);
}
void DrawBrushStroke::load(cereal::PortableBinaryInputArchive& a) {
a(d.points, d.color, d.hasRoundCaps);
}
DrawComponentType DrawBrushStroke::get_type() const {
return DRAWCOMPONENT_BRUSHSTROKE;
}
#ifndef IS_SERVER
std::shared_ptr<DrawComponent> DrawBrushStroke::copy() const {
auto a = std::make_shared<DrawBrushStroke>();
a->d = d;
a->coords = coords;
return a;
}
void DrawBrushStroke::update_from_delayed_ptr() {
std::shared_ptr<DrawBrushStroke> newPtr = std::static_pointer_cast<DrawBrushStroke>(delayedUpdatePtr);
d = newPtr->d;
}
Vector2f two_dim_vec_slerp(const Vector2f& v1, const Vector2f& v2) {
Vector2f v1Norm = v1.normalized();
Vector2f v2Norm = v2.normalized();
Vector2f avg = (v1Norm + v2Norm) * 0.5f;
if(std::fabs(avg.x()) < 0.00001f && std::fabs(avg.y()) < 0.00001f)
return perpendicular_vec2(v1Norm).normalized();
else
return avg.normalized();
}
void DrawBrushStroke::draw(SkCanvas* canvas, const DrawData& drawData) {
if(drawSetupData.shouldDraw) {
//bool deepPrecheck = false;
//for(auto& aabb : precheckAABBLevels) {
// if(SCollision::collide(drawData.cam.viewingAreaGenerousCollider, aabb)) {
// deepPrecheck = true;
// break;
// }
//}
//if(!deepPrecheck && !precheckAABBLevels.empty())
// return;
SkPaint paint;
if(d.color.w() == 1.0) // Fully opaque, dont bother with blending
canvas->save();
else
canvas->saveLayerAlphaf(nullptr, d.color.w());
paint.setColor4f(SkColor4f{d.color.x(), d.color.y(), d.color.z(), 1.0f});
canvas_do_calculated_transform(canvas);
canvas->drawVertices(vertices, SkBlendMode::kClear, paint);
//SkPaint p2(SkColor4f{1, 0, 0, 1});
//p2.setStroke(true);
//SkPaint p3(SkColor4f{0, 1, 0, 1});
//SkPaint p4(SkColor4f{0, 0, 1, 1});
//p3.setStroke(true);
//p4.setStroke(true);
//for(auto& precheckAABBLevel : precheckAABBLevels)
// draw_collider(canvas, drawData, precheckAABBLevel, p4);
//draw_collider(canvas, drawData, collisionTree, p3);
canvas->restore();
}
}
void DrawBrushStroke::initialize_draw_data(DrawingProgram& drawP) {
std::vector<SkPoint> vertexData;
create_triangles([&](Vector2f a, Vector2f b, Vector2f c) {
vertexData.emplace_back(a.x(), a.y());
vertexData.emplace_back(b.x(), b.y());
vertexData.emplace_back(c.x(), c.y());
return false;
});
vertices_to_draw_data(vertexData);
}
std::vector<size_t> DrawBrushStroke::get_wedge_indices(const std::vector<DrawBrushStrokePoint>& points) {
if(points.size() < 2)
return {};
std::vector<size_t> toRet;
toRet.emplace_back(0);
for(size_t i = 1; i < points.size() - 1; i++) {
Vector2f v1Norm = (points[i - 1].pos - points[i].pos).normalized();
Vector2f v2Norm = (points[i + 1].pos - points[i].pos).normalized();
if(v1Norm.dot(v2Norm) > -0.6f)
toRet.emplace_back(i);
}
toRet.emplace_back(points.size() - 1);
return toRet;
}
std::vector<DrawBrushStrokePoint> DrawBrushStroke::smooth_points(size_t beginIndex, size_t endIndex) {
std::vector<DrawBrushStrokePoint>& points = d.points;
size_t pointsSize = endIndex - beginIndex + 1;
if(pointsSize < 2)
return {points[beginIndex], points[endIndex]};
std::vector<DrawBrushStrokePoint> toRet;
DrawBrushStrokePoint p0 = points[beginIndex];
p0.pos = p0.pos + p0.width * (p0.pos - points[beginIndex + 1].pos).normalized() * DRAW_MINIMUM_LIMIT;
DrawBrushStrokePoint p1 = points[beginIndex];
DrawBrushStrokePoint p2 = points[beginIndex + 1];
DrawBrushStrokePoint p3;
for(size_t i = beginIndex; i < endIndex; i++) {
if(i == endIndex - 1) {
p3 = points[i + 1];
p3.pos = p3.pos + p3.width * (p3.pos - points[i].pos).normalized() * DRAW_MINIMUM_LIMIT;
}
else
p3 = points[i + 2];
toRet.emplace_back(p1);
const unsigned numOfDivisions = 5; // How smooth the stroke should be
float tMove = 1.0 / static_cast<float>(numOfDivisions);
for(unsigned d = 1; d < numOfDivisions; d++) {
float t = tMove * static_cast<float>(d);
DrawBrushStrokePoint pToAdd;
pToAdd.pos = catmull_rom(p0.pos, p1.pos, p2.pos, p3.pos, t);
pToAdd.width = std::lerp(p1.width, p2.width, t);
if(!(std::isnan(pToAdd.pos.x()) || std::isnan(pToAdd.pos.y()))) // Don't add NAN values
toRet.emplace_back(pToAdd);
}
p0 = p1;
p1 = p2;
p2 = p3;
}
toRet.emplace_back(points[endIndex]);
return toRet;
}
std::vector<DrawBrushStrokePoint> DrawBrushStroke::smooth_points_avg(size_t beginIndex, size_t endIndex) {
std::vector<DrawBrushStrokePoint>& points = d.points;
size_t pointsSize = endIndex - beginIndex + 1;
if(pointsSize < 2)
return {points[beginIndex], points[endIndex]};
std::vector<DrawBrushStrokePoint> toRet;
for(size_t i = beginIndex; i < endIndex; i++) {
const DrawBrushStrokePoint& p1 = points[i];
const DrawBrushStrokePoint& p2 = points[i + 1];
const unsigned numOfDivisions = 5; // How smooth the stroke should be
float tMove = 1.0 / static_cast<float>(numOfDivisions);
for(unsigned d = 0; d < numOfDivisions; d++) {
float t = tMove * static_cast<float>(d);
DrawBrushStrokePoint pToAdd;
pToAdd.pos = lerp_vec(p1.pos, p2.pos, t);
pToAdd.width = std::lerp(p1.width, p2.width, t);
if(!(std::isnan(pToAdd.pos.x()) || std::isnan(pToAdd.pos.y()))) // Don't add NAN values
toRet.emplace_back(pToAdd);
}
}
toRet.emplace_back(points[endIndex]);
return toRet;
}
void DrawBrushStroke::create_triangles(const std::function<bool(Vector2f, Vector2f, Vector2f)>& passTriangleFunc) {
const int ARC_SMOOTHNESS = 10;
const int CIRCLE_SMOOTHNESS = 20;
std::vector<DrawBrushStrokePoint>& pointsN = d.points;
if(pointsN.size() == 1 && d.hasRoundCaps) {
std::vector<Vector2f> circlePoints = gen_circle_points(pointsN.front().pos, pointsN.front().width / 2.0f, CIRCLE_SMOOTHNESS);
for(size_t i = 0; i < circlePoints.size(); i++)
if(passTriangleFunc(pointsN.front().pos, circlePoints[i], circlePoints[(i + 1) % circlePoints.size()])) return;
}
if(pointsN.size() < 2)
return;
std::vector<DrawBrushStrokePoint> points = smooth_points(0, pointsN.size() - 1);
std::vector<size_t> wedgeIndices = get_wedge_indices(points);
for(size_t i = 0; i < wedgeIndices.size() - 1; i++) {
size_t pointsBegin = wedgeIndices[i];
size_t pointsEnd = wedgeIndices[i + 1];
std::array<Vector2f, 3> vertArray;
size_t newIndex = 0;
Vector2f perpPrev = perpendicular_vec2((points[pointsBegin + 1].pos - points[pointsBegin].pos).eval());
perpPrev.normalize();
vertArray[0] = points[pointsBegin].pos + perpPrev * points[pointsBegin].width * 0.5f;
vertArray[1] = points[pointsBegin].pos - perpPrev * points[pointsBegin].width * 0.5f;
newIndex = 2;
if(d.hasRoundCaps && pointsBegin == 0) {
Vector2f arcDirStart = (convert_vec2<Vector2f>(vertArray[0]) - points[0].pos).normalized();
Vector2f arcDirEnd = (convert_vec2<Vector2f>(vertArray[1]) - points[0].pos).normalized();
Vector2f arcDirCenter = (points[0].pos - points[1].pos).normalized();
std::vector<Vector2f> arcPoints = arc_vec<float>(points[0].pos, arcDirStart, arcDirEnd, arcDirCenter, points[0].width * 0.5f, ARC_SMOOTHNESS);
arcPoints.emplace(arcPoints.begin(), convert_vec2<Vector2f>(vertArray[0]));
arcPoints.emplace_back(convert_vec2<Vector2f>(vertArray[1]));
for(size_t i = 0; i < arcPoints.size() - 1; i++)
if(passTriangleFunc(points[0].pos, arcPoints[i], arcPoints[i + 1])) return;
}
for(size_t j = pointsBegin + 1; j < pointsEnd; j++) {
Vector2f v1 = points[j - 1].pos - points[j].pos;
Vector2f v2 = points[j].pos - points[j + 1].pos;
Vector2f perp = perpendicular_vec2((v1 + v2).eval()).normalized();
if(std::isnan(perp.dot(perpPrev))) {
std::cout << "NAN FOUND" << std::endl;
std::cout << v1 << std::endl;
std::cout << v2 << std::endl;
std::cout << perp << std::endl;
std::cout << perpPrev << std::endl;
throw 1;
}
if(perp.dot(perpPrev) < 0.0)
perp = -perp;
vertArray[newIndex] = points[j].pos + perp * 0.5f * points[j].width;
newIndex = (newIndex + 1) % 3;
if(passTriangleFunc(vertArray[0], vertArray[1], vertArray[2])) return;
vertArray[newIndex] = points[j].pos - perp * 0.5f * points[j].width;
newIndex = (newIndex + 1) % 3;
if(passTriangleFunc(vertArray[0], vertArray[1], vertArray[2])) return;
perpPrev = perp;
}
Vector2f rectDir = points[pointsEnd - 1].pos - points[pointsEnd].pos;
rectDir.normalize();
Vector2f perp = perpendicular_vec2(rectDir);
if(perp.dot(perpPrev) < 0.0)
perp = -perp;
vertArray[newIndex] = points[pointsEnd].pos + perp * points[pointsEnd].width * 0.5;
newIndex = (newIndex + 1) % 3;
if(passTriangleFunc(vertArray[0], vertArray[1], vertArray[2])) return;
vertArray[newIndex] = points[pointsEnd].pos - perp * points[pointsEnd].width * 0.5;
newIndex = (newIndex + 1) % 3;
if(passTriangleFunc(vertArray[0], vertArray[1], vertArray[2])) return;
// Render wedge
if(pointsEnd != points.size() - 1) {
// Find 4 points involved in intersection
Vector2f currentP1 = points[pointsEnd].pos - perp * points[pointsEnd].width * 0.5;
Vector2f currentP2 = points[pointsEnd].pos + perp * points[pointsEnd].width * 0.5;
Vector2f nextRectDir = points[pointsEnd + 1].pos - points[pointsEnd].pos;
nextRectDir.normalize();
Vector2f nextPerp = perpendicular_vec2(nextRectDir);
Vector2f nextP1 = points[pointsEnd].pos - nextPerp * points[pointsEnd].width * 0.5;
Vector2f nextP2 = points[pointsEnd].pos + nextPerp * points[pointsEnd].width * 0.5;
// Find center of wedge
Vector2f wedgeCenter = line_line_intersection(currentP1, currentP2, nextP1, nextP2);
Vector2f wedgeP1 = ((currentP1 - currentP2).dot(nextRectDir)) <= 0.0 ? currentP1 : currentP2;
Vector2f wedgeP2 = ((nextP1 - nextP2).dot(rectDir)) <= 0.0 ? nextP1 : nextP2;
float arcRadius;
std::vector<Vector2f> arcPoints;
if(wedgeCenter.x() == std::numeric_limits<float>::max()) {
wedgeCenter = (currentP1 + currentP2) * 0.5;
wedgeP1 = currentP1;
wedgeP2 = currentP2;
arcRadius = points[pointsEnd].width * 0.5;
arcPoints = arc_vec(wedgeCenter, (wedgeP1 - wedgeCenter).normalized(), (wedgeP2 - wedgeCenter).normalized(), (-rectDir).eval(), arcRadius, ARC_SMOOTHNESS);
}
else {
arcRadius = (wedgeCenter - wedgeP1).norm();
arcPoints = arc_vec(wedgeCenter, (wedgeP1 - wedgeCenter).normalized(), (wedgeP2 - wedgeCenter).normalized(), lerp_vec((-rectDir).eval(), (-nextRectDir).eval(), 0.5).normalized(), arcRadius, ARC_SMOOTHNESS);
}
arcPoints.emplace(arcPoints.begin(), wedgeP1);
arcPoints.emplace_back(wedgeP2);
for(size_t i = 0; i < arcPoints.size() - 1; i++)
if(passTriangleFunc(wedgeCenter, arcPoints[i], arcPoints[i + 1])) return;
}
else if(d.hasRoundCaps) { // It's the last point, cap it off
Vector2f arcDirStart = (convert_vec2<Vector2f>(vertArray[(newIndex + 1) % 3]) - points.back().pos).normalized();
Vector2f arcDirEnd = (convert_vec2<Vector2f>(vertArray[(newIndex + 2) % 3]) - points.back().pos).normalized();
Vector2f arcDirCenter = -rectDir;
std::vector<Vector2f> arcPoints = arc_vec<float>(points.back().pos, arcDirStart, arcDirEnd, arcDirCenter, points.back().width * 0.5f, ARC_SMOOTHNESS);
arcPoints.emplace(arcPoints.begin(), convert_vec2<Vector2f>(vertArray[(newIndex + 1) % 3]));
arcPoints.emplace_back(convert_vec2<Vector2f>(vertArray[(newIndex + 2) % 3]));
for(size_t i = 0; i < arcPoints.size() - 1; i++)
if(passTriangleFunc(points.back().pos, arcPoints[i], arcPoints[i + 1])) return;
}
}
}
void DrawBrushStroke::create_collider(bool colliderAllocated) {
using namespace SCollision;
ColliderCollection<float> strokeObjects;
create_triangles([&](Vector2f a, Vector2f b, Vector2f c) {
strokeObjects.triangle.emplace_back(a, b, c);
return false;
});
strokeObjects.recalculate_bounds();
collisionTree.clear();
collisionTree.calculate_bvh_recursive(strokeObjects);
precheckAABBLevels.clear();
add_precheck_aabb_level(0, collisionTree.children);
if(!colliderAllocated) {
collisionTree.objects.aabb.clear();
collisionTree.objects.circle.clear();
collisionTree.objects.triangle.clear();
collisionTree.children.clear();
}
calculate_world_bounds();
}
void DrawBrushStroke::add_precheck_aabb_level(size_t level, const std::vector<SCollision::BVHContainer<float>>& levelArray) {
for(const auto& a : levelArray) {
if(level == AABB_PRECHECK_BVH_LEVELS || a.children.empty())
precheckAABBLevels.emplace_back(a.objects.bounds);
else
add_precheck_aabb_level(level + 1, a.children);
}
}
void DrawBrushStroke::vertices_to_draw_data(const std::vector<SkPoint>& vertexData) {
if((d.points.size() == 1 && d.hasRoundCaps) || d.points.size() >= 2)
vertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, vertexData.size(), vertexData.data(), nullptr, nullptr);
else
vertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, 0, nullptr, nullptr, nullptr);
}
void DrawBrushStroke::update(DrawingProgram& drawP) {
}
bool DrawBrushStroke::collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst, bool colliderAllocated) {
if(colliderAllocated)
return collisionTree.is_collide(checkAgainst);
if(!SCollision::collide(checkAgainst, collisionTree.objects.bounds))
return false;
bool deepPrecheck = false;
for(auto& aabb : precheckAABBLevels) {
if(SCollision::collide(checkAgainst, aabb)) {
deepPrecheck = true;
break;
}
}
if(!deepPrecheck && !precheckAABBLevels.empty())
return false;
bool toRet = false;
create_triangles([&](Vector2f a, Vector2f b, Vector2f c) {
toRet = SCollision::collide(checkAgainst, SCollision::Triangle<float>{a, b, c});
return toRet;
});
return toRet;
}
void DrawBrushStroke::free_collider() {
collisionTree.objects.aabb.clear();
collisionTree.objects.circle.clear();
collisionTree.objects.triangle.clear();
collisionTree.children.clear();
}
void DrawBrushStroke::allocate_collider() {
create_collider(true);
}
SCollision::AABB<float> DrawBrushStroke::get_obj_coord_bounds() const {
return collisionTree.objects.bounds;
}
#endif

View file

@ -0,0 +1,62 @@
#pragma once
#include "../SharedTypes.hpp"
#include "DrawComponent.hpp"
#include "Helpers/SCollision.hpp"
#include <Helpers/Serializers.hpp>
#ifndef IS_SERVER
#include <include/core/SkPath.h>
#endif
#define DRAW_MINIMUM_LIMIT 0.75
struct DrawBrushStrokePoint {
Vector2f pos;
float width;
template <typename Archive> void serialize(Archive& a) {
a(pos, width);
}
};
class DrawBrushStroke : public DrawComponent {
public:
virtual DrawComponentType get_type() const override;
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
struct Data {
std::vector<DrawBrushStrokePoint> points;
Vector4f color;
bool hasRoundCaps;
} d;
#ifndef IS_SERVER
virtual std::shared_ptr<DrawComponent> copy() const override;
virtual void draw(SkCanvas* canvas, const DrawData& drawData) override;
virtual void initialize_draw_data(DrawingProgram& drawP) override;
virtual void update(DrawingProgram& drawP) override;
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst, bool colliderAllocated) override;
virtual void create_collider(bool colliderAllocated) override;
virtual void update_from_delayed_ptr() override;
virtual void free_collider() override;
virtual void allocate_collider() override;
virtual SCollision::AABB<float> get_obj_coord_bounds() const override;
sk_sp<SkVertices> vertices;
SCollision::BVHContainer<float> collisionTree;
private:
void add_precheck_aabb_level(size_t level, const std::vector<SCollision::BVHContainer<float>>& levelArray);
std::vector<SCollision::AABB<float>> precheckAABBLevels;
void create_triangles(const std::function<bool(Vector2f, Vector2f, Vector2f)>& passTriangleFunc);
void vertices_to_draw_data(const std::vector<SkPoint>& vertexData);
std::vector<size_t> get_wedge_indices(const std::vector<DrawBrushStrokePoint>& points);
std::vector<DrawBrushStrokePoint> smooth_points(size_t beginIndex, size_t endIndex);
std::vector<DrawBrushStrokePoint> smooth_points_avg(size_t beginIndex, size_t endIndex);
#endif
};

View file

@ -0,0 +1,208 @@
#include "DrawComponent.hpp"
#include "DrawBrushStroke.hpp"
#include "DrawEllipse.hpp"
#include "DrawRectangle.hpp"
#include "DrawTextBox.hpp"
#include "DrawImage.hpp"
#include "Helpers/Networking/NetLibrary.hpp"
#include "../SharedTypes.hpp"
#include "../Server/CommandList.hpp"
#include <chrono>
#ifndef IS_SERVER
#include "../MainProgram.hpp"
#endif
std::shared_ptr<DrawComponent> DrawComponent::allocate_comp_type(DrawComponentType type) {
switch(type) {
case DRAWCOMPONENT_BRUSHSTROKE:
return std::make_shared<DrawBrushStroke>();
case DRAWCOMPONENT_RECTANGLE:
return std::make_shared<DrawRectangle>();
case DRAWCOMPONENT_ELLIPSE:
return std::make_shared<DrawEllipse>();
case DRAWCOMPONENT_TEXTBOX:
return std::make_shared<DrawTextBox>();
case DRAWCOMPONENT_IMAGE:
return std::make_shared<DrawImage>();
}
return nullptr;
}
void DrawComponent::server_send_place(MainServer& server, ServerClientID id, uint64_t placement) {
server.netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_PLACE_COMPONENT, placement, get_type(), id, this->coords, *this);
}
void DrawComponent::server_send_erase(MainServer& server, ServerClientID id) {
server.netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_ERASE_COMPONENT, id);
}
void DrawComponent::server_send_update_temp(MainServer& server, ServerClientID id) {
server.netServer->send_items_to_all_clients(UNRELIABLE_COMMAND_CHANNEL, CLIENT_UPDATE_COMPONENT, true, id, *this);
tempServerUpdateTimer = std::chrono::steady_clock::now();
serverIsTempUpdate = true;
}
void DrawComponent::server_send_update_final(MainServer& server, ServerClientID id) {
server.netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_UPDATE_COMPONENT, false, id, *this);
serverIsTempUpdate = false;
}
void DrawComponent::server_send_transform_temp(MainServer& server, ServerClientID id) {
server.netServer->send_items_to_all_clients(UNRELIABLE_COMMAND_CHANNEL, CLIENT_TRANSFORM_COMPONENT, true, id, this->coords);
tempServerUpdateTimer = std::chrono::steady_clock::now();
serverIsTempUpdate = true;
}
void DrawComponent::server_send_transform_final(MainServer& server, ServerClientID id) {
server.netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_TRANSFORM_COMPONENT, false, id, this->coords);
serverIsTempUpdate = false;
}
void DrawComponent::server_update(MainServer& server, ServerClientID id) {
if(serverIsTempUpdate) {
float timeSince = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - tempServerUpdateTimer).count();
if(timeSince >= 1.0f) {
server_send_update_final(server, id);
serverIsTempUpdate = false;
}
}
if(serverIsTempTransform) {
float timeSince = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - tempServerTransformTimer).count();
if(timeSince >= 1.0f) {
server_send_transform_final(server, id);
serverIsTempTransform = false;
}
}
}
void DrawComponent::get_used_resources(std::unordered_set<ServerClientID>& v) const {
}
void DrawComponent::remap_resource_ids(std::unordered_map<ServerClientID, ServerClientID>& newIDs) {
}
#ifndef IS_SERVER
bool DrawComponent::bounds_draw_check(const DrawData& drawData) const {
bool a = !drawData.clampDrawBetween || ((drawData.clampDrawMaximum >= coords.inverseScale) && (drawData.clampDrawMinimum < coords.inverseScale));
return a;
}
void DrawComponent::check_timers(DrawingProgram& drawP) {
if(delayedCoordinateSpace) {
float transformDur = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - lastTransformTime).count();
if(transformDur > CLIENT_DRAWCOMP_DELAY_TIMER_DURATION) {
coords = *delayedCoordinateSpace;
delayedCoordinateSpace = nullptr;
finalize_update(drawP.colliderAllocated);
}
}
if(delayedUpdatePtr) {
float updateDur = std::chrono::duration_cast<std::chrono::duration<float>>(std::chrono::steady_clock::now() - lastUpdateTime).count();
if(updateDur > CLIENT_DRAWCOMP_DELAY_TIMER_DURATION) {
update_from_delayed_ptr();
delayedUpdatePtr = nullptr;
final_update(drawP);
}
}
}
void DrawComponent::temp_update(DrawingProgram& drawP) {
initialize_draw_data(drawP);
updateDraw = true;
}
void DrawComponent::final_update(DrawingProgram& drawP) {
initialize_draw_data(drawP);
finalize_update(drawP.colliderAllocated);
updateDraw = true;
}
void DrawComponent::client_send_place(DrawingProgram& drawP, uint64_t placement) {
drawP.world.con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_PLACE_COMPONENT, placement, get_type(), this->coords, *this);
}
void DrawComponent::client_send_erase(DrawingProgram& drawP, ServerClientID id) {
drawP.world.con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_ERASE_COMPONENT, id);
}
void DrawComponent::client_send_update_temp(DrawingProgram& drawP, ServerClientID id) {
drawP.world.con.client_send_items_to_server(UNRELIABLE_COMMAND_CHANNEL, SERVER_UPDATE_COMPONENT, true, id, *this);
}
void DrawComponent::client_send_update_final(DrawingProgram& drawP, ServerClientID id) {
drawP.world.con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_UPDATE_COMPONENT, false, id, *this);
}
void DrawComponent::client_send_transform_temp(DrawingProgram& drawP, ServerClientID id) {
drawP.world.con.client_send_items_to_server(UNRELIABLE_COMMAND_CHANNEL, SERVER_TRANSFORM_COMPONENT, true, id, this->coords);
}
void DrawComponent::client_send_transform_final(DrawingProgram& drawP, ServerClientID id) {
drawP.world.con.client_send_items_to_server(RELIABLE_COMMAND_CHANNEL, SERVER_TRANSFORM_COMPONENT, false, id, this->coords);
}
void DrawComponent::finalize_update(bool colliderAllocated) {
create_collider(colliderAllocated);
updateDraw = true;
}
bool DrawComponent::collides_with_world_coords(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld, bool colliderAllocated) {
return collides_with(camCoords, checkAgainstWorld, camCoords.world_collider_to_coords<SCollision::ColliderCollection<float>>(checkAgainstWorld), colliderAllocated);
}
bool DrawComponent::collides_with_cam_coords(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<float>& checkAgainstCam, bool colliderAllocated) {
return collides_with(camCoords, camCoords.collider_to_world<SCollision::ColliderCollection<WorldScalar>>(checkAgainstCam), checkAgainstCam, colliderAllocated);
}
// We could just send one of the checkAgainst colliders, since they represent the same thing, but sending both saves on redundant transformations, since we can save both of the versions on the executor side
bool DrawComponent::collides_with(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld, const SCollision::ColliderCollection<float>& checkAgainstCam, bool colliderAllocated) {
if((camCoords.inverseScale << DRAWCOMP_MAX_SHIFT_BEFORE_DISAPPEAR) < coords.inverseScale) // Object is too large, just dismiss the collision
return false;
else if((camCoords.inverseScale >> DRAWCOMP_MIN_SHIFT_BEFORE_DISAPPEAR) >= coords.inverseScale) {
if(!worldAABB)
return false;
return SCollision::collide(checkAgainstCam, camCoords.to_space(worldAABB->min));
}
else {
auto c = checkAgainstWorld.transform<float>(
[&coords = coords](const WorldVec& a) {
return coords.to_space(a);
},
[&coords = coords](const WorldScalar& a) {
return coords.scalar_to_space(a);
});
return collides_within_coords(c, colliderAllocated);
}
}
void DrawComponent::canvas_do_calculated_transform(SkCanvas* canvas) {
canvas->scale(drawSetupData.transformData.scale, drawSetupData.transformData.scale);
canvas->rotate(drawSetupData.transformData.rotation);
canvas->translate(drawSetupData.transformData.translation.x(), drawSetupData.transformData.translation.y());
}
void DrawComponent::calculate_draw_transform(const DrawData& drawData) {
drawSetupData.shouldDraw = !drawData.clampDrawBetween || ((drawData.clampDrawMaximum >= coords.inverseScale) && (drawData.clampDrawMinimum < coords.inverseScale));
if(drawSetupData.shouldDraw) {
drawSetupData.shouldDraw = updateDraw || !worldAABB || SCollision::collide(*worldAABB, drawData.cam.viewingAreaGenerousCollider);
if(drawSetupData.shouldDraw) {
drawSetupData.transformData.translation = -coords.to_space(drawData.cam.c.pos);
drawSetupData.transformData.rotation = (coords.rotation - drawData.cam.c.rotation) * 180.0 / std::numbers::pi;
drawSetupData.transformData.scale = static_cast<float>(coords.inverseScale / drawData.cam.c.inverseScale);
}
}
}
void DrawComponent::calculate_world_bounds() {
worldAABB = std::optional<SCollision::AABB<WorldScalar>>(coords.collider_to_world<SCollision::AABB<WorldScalar>, SCollision::AABB<float>>(get_obj_coord_bounds()));
}
std::optional<SCollision::AABB<WorldScalar>> DrawComponent::get_world_bounds() const {
return worldAABB;
}
#endif
DrawComponent::~DrawComponent() { }

View file

@ -0,0 +1,109 @@
#pragma once
#include "Helpers/SCollision.hpp"
#include <Helpers/Hashes.hpp>
#include "cereal/archives/portable_binary.hpp"
#include "../SharedTypes.hpp"
#include "../CoordSpaceHelper.hpp"
#include "../Server/MainServer.hpp"
#ifndef IS_SERVER
#include "../DrawData.hpp"
#include <chrono>
#include <include/core/SkCanvas.h>
#include <include/core/SkVertices.h>
#endif
#define CLIENT_DRAWCOMP_DELAY_TIMER_DURATION 5.0f
#define DRAWCOMP_MAX_SHIFT_BEFORE_DISAPPEAR 20
#define DRAWCOMP_MIN_SHIFT_BEFORE_DISAPPEAR 9
class DrawingProgram;
class DrawComponent {
public:
static std::shared_ptr<DrawComponent> allocate_comp_type(DrawComponentType type);
virtual void save(cereal::PortableBinaryOutputArchive& a) const = 0;
virtual void load(cereal::PortableBinaryInputArchive& a) = 0;
virtual DrawComponentType get_type() const = 0;
virtual void get_used_resources(std::unordered_set<ServerClientID>& v) const;
virtual void remap_resource_ids(std::unordered_map<ServerClientID, ServerClientID>& newIDs);
virtual ~DrawComponent();
// NOTE: Permanantly altering the coordinates during transformation can lead to loss of detail for smaller objects
CoordSpaceHelper coords;
struct DrawSetupData {
struct TransformDrawData {
Vector2f translation;
float rotation;
float scale;
} transformData;
bool shouldDraw = true;
} drawSetupData;
void canvas_do_calculated_transform(SkCanvas* canvas);
void calculate_draw_transform(const DrawData& drawData);
void server_send_place(MainServer& server, ServerClientID id, uint64_t placement);
static void server_send_erase(MainServer& server, ServerClientID id);
void server_send_update_temp(MainServer& server, ServerClientID id);
void server_send_update_final(MainServer& server, ServerClientID id);
void server_send_transform_temp(MainServer& server, ServerClientID id);
void server_send_transform_final(MainServer& server, ServerClientID id);
std::chrono::time_point<std::chrono::steady_clock> tempServerUpdateTimer;
bool serverIsTempUpdate = false;
std::chrono::time_point<std::chrono::steady_clock> tempServerTransformTimer;
bool serverIsTempTransform = false;
void server_update(MainServer& server, ServerClientID id);
#ifndef IS_SERVER
bool selected = false;
bool updateDraw = true;
bool globalCollisionCheck = false;
std::chrono::time_point<std::chrono::steady_clock> lastTransformTime;
std::chrono::time_point<std::chrono::steady_clock> lastUpdateTime;
std::shared_ptr<DrawComponent> delayedUpdatePtr = nullptr;
std::shared_ptr<CoordSpaceHelper> delayedCoordinateSpace = nullptr;
bool bounds_draw_check(const DrawData& drawData) const;
void check_timers(DrawingProgram& drawP);
void client_send_place(DrawingProgram& drawP, uint64_t placement);
static void client_send_erase(DrawingProgram& drawP, ServerClientID id);
void client_send_update_temp(DrawingProgram& drawP, ServerClientID id);
void client_send_update_final(DrawingProgram& drawP, ServerClientID id);
void client_send_transform_temp(DrawingProgram& drawP, ServerClientID id);
void client_send_transform_final(DrawingProgram& drawP, ServerClientID id);
std::optional<SCollision::AABB<WorldScalar>> worldAABB;
std::optional<SCollision::AABB<WorldScalar>> get_world_bounds() const;
void calculate_world_bounds();
virtual SCollision::AABB<float> get_obj_coord_bounds() const = 0;
virtual std::shared_ptr<DrawComponent> copy() const = 0;
virtual void create_collider(bool colliderAllocated) = 0;
virtual void draw(SkCanvas* canvas, const DrawData& drawData) = 0;
void temp_update(DrawingProgram& drawP);
void final_update(DrawingProgram& drawP);
virtual void initialize_draw_data(DrawingProgram& drawP) = 0;
virtual void finalize_update(bool colliderAllocated);
virtual void update(DrawingProgram& drawP) = 0;
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst, bool colliderAllocated) = 0;
bool collides_with_world_coords(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld, bool colliderAllocated);
bool collides_with_cam_coords(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<float>& checkAgainstCam, bool colliderAllocated);
bool collides_with(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld, const SCollision::ColliderCollection<float>& checkAgainstCam, bool colliderAllocated);
virtual void update_from_delayed_ptr() = 0;
virtual void free_collider() = 0;
virtual void allocate_collider() = 0;
#endif
};

View file

@ -0,0 +1,141 @@
#include "DrawEllipse.hpp"
#include "Helpers/ConvertVec.hpp"
#include "Helpers/SCollision.hpp"
#ifndef IS_SERVER
#include "../DrawCollision.hpp"
#endif
DrawComponentType DrawEllipse::get_type() const {
return DRAWCOMPONENT_ELLIPSE;
}
void DrawEllipse::save(cereal::PortableBinaryOutputArchive& a) const {
a(d.strokeColor, d.fillColor, d.strokeWidth, d.p1, d.p2, d.fillStrokeMode);
}
void DrawEllipse::load(cereal::PortableBinaryInputArchive& a) {
a(d.strokeColor, d.fillColor, d.strokeWidth, d.p1, d.p2, d.fillStrokeMode);
}
#ifndef IS_SERVER
std::shared_ptr<DrawComponent> DrawEllipse::copy() const {
auto a = std::make_shared<DrawEllipse>();
a->d = d;
a->coords = coords;
return a;
}
void DrawEllipse::update_from_delayed_ptr() {
std::shared_ptr<DrawEllipse> newPtr = std::static_pointer_cast<DrawEllipse>(delayedUpdatePtr);
d = newPtr->d;
}
void DrawEllipse::draw(SkCanvas* canvas, const DrawData& drawData) {
if(drawSetupData.shouldDraw) {
canvas->save();
canvas_do_calculated_transform(canvas);
if(d.fillStrokeMode == 0 || d.fillStrokeMode == 2) {
SkPaint p;
p.setStyle(SkPaint::kFill_Style);
p.setColor4f(convert_vec4<SkColor4f>(d.fillColor));
canvas->drawPath(ellipsePath, p);
}
if(d.fillStrokeMode == 1 || d.fillStrokeMode == 2) {
SkPaint p;
p.setStyle(SkPaint::kStroke_Style);
p.setColor4f(convert_vec4<SkColor4f>(d.strokeColor));
p.setStrokeWidth(d.strokeWidth);
canvas->drawPath(ellipsePath, p);
}
canvas->restore();
}
}
void DrawEllipse::create_draw_data() {
ellipsePath = SkPath();
SkRect newRect = SkRect::MakeLTRB(d.p1.x(), d.p1.y(), d.p2.x(), d.p2.y());
ellipsePath.addOval(newRect);
}
void DrawEllipse::initialize_draw_data(DrawingProgram& drawP) {
create_draw_data();
}
void DrawEllipse::update(DrawingProgram& drawP) {
}
bool DrawEllipse::collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst, bool colliderAllocated) {
return collisionTree.is_collide(checkAgainst);
}
void DrawEllipse::create_collider(bool colliderAllocated) {
using namespace SCollision;
ColliderCollection<float> strokeObjects;
if(d.fillStrokeMode == 0) {
Vector2f ellipseCenter = (d.p1 + d.p2) * 0.5;
float a = ellipseCenter.x() - d.p1.x();
float b = ellipseCenter.y() - d.p1.y();
Vector2f prevPoint = ellipseCenter + Vector2f{a * std::cos(0), b * std::sin(0)};
unsigned numOfSegments = 20;
float segmentStep = 1.0 / numOfSegments;
float t = 0.0;
for(unsigned i = 0; i <= numOfSegments; i++) {
Vector2f nextPoint = ellipseCenter + Vector2f{a * std::cos(t), b * std::sin(t)};
strokeObjects.triangle.emplace_back(ellipseCenter, prevPoint, nextPoint);
prevPoint = nextPoint;
t += segmentStep * std::numbers::pi * 2.0;
}
}
else if(d.fillStrokeMode == 1) {
std::vector<Vector2f> points;
Vector2f ellipseCenter = (d.p1 + d.p2) * 0.5;
float a = ellipseCenter.x() - d.p1.x();
float b = ellipseCenter.y() - d.p1.y();
unsigned numOfSegments = 40;
float segmentStep = 1.0 / numOfSegments;
float t = 0.0;
for(unsigned i = 0; i < numOfSegments; i++) {
points.emplace_back(ellipseCenter + Vector2f{a * std::cos(t), b * std::sin(t)});
t += segmentStep * std::numbers::pi * 2.0;
}
generate_polyline(strokeObjects, points, d.strokeWidth, true);
}
else if(d.fillStrokeMode == 2) {
float strokeRadius = d.strokeWidth * 0.5f;
Vector2f wNewP1 = d.p1 - Vector2f{strokeRadius, strokeRadius};
Vector2f wNewP2 = d.p2 + Vector2f{strokeRadius, strokeRadius};
Vector2f ellipseCenter = (wNewP1 + wNewP2) * 0.5;
float a = ellipseCenter.x() - wNewP1.x();
float b = ellipseCenter.y() - wNewP1.y();
Vector2f prevPoint = ellipseCenter + Vector2f{a * std::cos(0), b * std::sin(0)};
unsigned numOfSegments = 20;
float segmentStep = 1.0 / numOfSegments;
float t = 0.0;
for(unsigned i = 0; i <= numOfSegments; i++) {
Vector2f nextPoint = ellipseCenter + Vector2f{a * std::cos(t), b * std::sin(t)};
strokeObjects.triangle.emplace_back(ellipseCenter, prevPoint, nextPoint);
prevPoint = nextPoint;
t += segmentStep * std::numbers::pi * 2.0;
}
}
collisionTree.clear();
collisionTree.calculate_bvh_recursive(strokeObjects);
calculate_world_bounds();
}
void DrawEllipse::free_collider() {
}
void DrawEllipse::allocate_collider() {
}
SCollision::AABB<float> DrawEllipse::get_obj_coord_bounds() const {
return collisionTree.objects.bounds;
}
#endif

View file

@ -0,0 +1,49 @@
#pragma once
#include "../SharedTypes.hpp"
#include "DrawComponent.hpp"
#include "../CoordSpaceHelper.hpp"
#ifndef IS_SERVER
#include <include/core/SkPath.h>
#endif
class DrawEllipse : public DrawComponent {
public:
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual DrawComponentType get_type() const override;
// User input data
struct Data {
Vector4f strokeColor;
Vector4f fillColor;
float strokeWidth;
Vector2f p1;
Vector2f p2;
uint8_t fillStrokeMode;
bool operator==(const Data& o) const = default;
} d;
#ifndef IS_SERVER
virtual std::shared_ptr<DrawComponent> copy() const override;
virtual void draw(SkCanvas* canvas, const DrawData& drawData) override;
virtual void initialize_draw_data(DrawingProgram& drawP) override;
virtual void update(DrawingProgram& drawP) override;
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst, bool colliderAllocated) override;
virtual void update_from_delayed_ptr() override;
void create_draw_data();
virtual void create_collider(bool colliderAllocated) override;
virtual void free_collider() override;
virtual void allocate_collider() override;
virtual SCollision::AABB<float> get_obj_coord_bounds() const override;
SkPath ellipsePath;
// Cache
SCollision::BVHContainer<float> collisionTree;
private:
void create_triangles(const std::function<bool(Vector2f, Vector2f, Vector2f)>& passTriangleFunc);
#endif
};

View file

@ -0,0 +1,102 @@
#include "DrawImage.hpp"
#include "Helpers/ConvertVec.hpp"
#include "Helpers/MathExtras.hpp"
#include "Helpers/SCollision.hpp"
#include "../SharedTypes.hpp"
#include "../World.hpp"
#ifndef IS_SERVER
#include <include/core/SkSamplingOptions.h>
#include "../DrawCollision.hpp"
#endif
DrawComponentType DrawImage::get_type() const {
return DRAWCOMPONENT_IMAGE;
}
void DrawImage::save(cereal::PortableBinaryOutputArchive& a) const {
a(d.p1, d.p2, d.imageID);
}
void DrawImage::load(cereal::PortableBinaryInputArchive& a) {
a(d.p1, d.p2, d.imageID);
}
void DrawImage::get_used_resources(std::unordered_set<ServerClientID>& v) const {
v.emplace(d.imageID);
}
void DrawImage::remap_resource_ids(std::unordered_map<ServerClientID, ServerClientID>& newIDs) {
auto it = newIDs.find(d.imageID);
if(it != newIDs.end())
d.imageID = it->second;
}
#ifndef IS_SERVER
std::shared_ptr<DrawComponent> DrawImage::copy() const {
auto a = std::make_shared<DrawImage>();
a->d = d;
a->coords = coords;
return a;
}
void DrawImage::update_from_delayed_ptr() {
std::shared_ptr<DrawImage> newPtr = std::static_pointer_cast<DrawImage>(delayedUpdatePtr);
d = newPtr->d;
}
void DrawImage::draw(SkCanvas* canvas, const DrawData& drawData) {
if(drawSetupData.shouldDraw) {
canvas->save();
canvas_do_calculated_transform(canvas);
std::shared_ptr<ResourceDisplay> display = drawData.rMan->get_display_data(d.imageID);
if(display)
display->draw(canvas, drawData, imRect);
else
canvas->drawRect(imRect, SkPaint({0.5f, 0.5f, 0.5f, 0.5f}));
canvas->restore();
}
}
void DrawImage::update(DrawingProgram& drawP) {
std::shared_ptr<ResourceDisplay> display = drawP.world.drawData.rMan->get_display_data(d.imageID);
if(display) {
if(display->update_draw())// && bounds_draw_check(drawP.world.drawData, collisionTree))
updateDraw = true;
}
}
void DrawImage::initialize_draw_data(DrawingProgram& drawP) {
create_draw_data();
}
bool DrawImage::collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst, bool colliderAllocated) {
return collisionTree.is_collide(checkAgainst);
}
void DrawImage::create_draw_data() {
imRect = SkRect::MakeLTRB(d.p1.x(), d.p1.y(), d.p2.x(), d.p2.y());
}
void DrawImage::create_collider(bool colliderAllocated) {
using namespace SCollision;
ColliderCollection<float> strokeObjects;
std::array<Vector2f, 4> newT = triangle_from_rect_points(d.p1, d.p2);
strokeObjects.triangle.emplace_back(newT[0], newT[1], newT[2]);
strokeObjects.triangle.emplace_back(newT[2], newT[3], newT[0]);
collisionTree.clear();
collisionTree.calculate_bvh_recursive(strokeObjects);
calculate_world_bounds();
}
void DrawImage::free_collider() {
}
void DrawImage::allocate_collider() {
}
SCollision::AABB<float> DrawImage::get_obj_coord_bounds() const {
return collisionTree.objects.bounds;
}
#endif

View file

@ -0,0 +1,46 @@
#pragma once
#include "../SharedTypes.hpp"
#include "DrawComponent.hpp"
#include "../CoordSpaceHelper.hpp"
#ifndef IS_SERVER
#include <include/core/SkPath.h>
#endif
class DrawImage : public DrawComponent {
public:
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual DrawComponentType get_type() const override;
virtual void get_used_resources(std::unordered_set<ServerClientID>& v) const override;
virtual void remap_resource_ids(std::unordered_map<ServerClientID, ServerClientID>& newIDs) override;
// User input data
struct Data {
Vector2f p1;
Vector2f p2;
ServerClientID imageID;
} d;
#ifndef IS_SERVER
virtual std::shared_ptr<DrawComponent> copy() const override;
virtual void draw(SkCanvas* canvas, const DrawData& drawData) override;
virtual void initialize_draw_data(DrawingProgram& drawP) override;
virtual void update(DrawingProgram& drawP) override;
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst, bool colliderAllocated) override;
void create_draw_data();
virtual void create_collider(bool colliderAllocated) override;
virtual void update_from_delayed_ptr() override;
virtual void free_collider() override;
virtual void allocate_collider() override;
virtual SCollision::AABB<float> get_obj_coord_bounds() const override;
SkRect imRect;
SCollision::BVHContainer<float> collisionTree;
#endif
};

View file

@ -0,0 +1,124 @@
#include "DrawRectangle.hpp"
#include "Helpers/ConvertVec.hpp"
#include "Helpers/MathExtras.hpp"
#include "Helpers/SCollision.hpp"
#include <include/core/SkPaint.h>
#include "../SharedTypes.hpp"
#ifndef IS_SERVER
#include "../DrawCollision.hpp"
#endif
DrawComponentType DrawRectangle::get_type() const {
return DRAWCOMPONENT_RECTANGLE;
}
void DrawRectangle::save(cereal::PortableBinaryOutputArchive& a) const {
a(d.strokeColor, d.fillColor, d.cornerRadius, d.strokeWidth, d.p1, d.p2, d.fillStrokeMode);
}
void DrawRectangle::load(cereal::PortableBinaryInputArchive& a) {
a(d.strokeColor, d.fillColor, d.cornerRadius, d.strokeWidth, d.p1, d.p2, d.fillStrokeMode);
}
#ifndef IS_SERVER
std::shared_ptr<DrawComponent> DrawRectangle::copy() const {
auto a = std::make_shared<DrawRectangle>();
a->d = d;
a->coords = coords;
return a;
}
void DrawRectangle::update_from_delayed_ptr() {
std::shared_ptr<DrawRectangle> newPtr = std::static_pointer_cast<DrawRectangle>(delayedUpdatePtr);
d = newPtr->d;
}
void DrawRectangle::draw(SkCanvas* canvas, const DrawData& drawData) {
if(drawSetupData.shouldDraw) {
canvas->save();
canvas_do_calculated_transform(canvas);
if(d.fillStrokeMode == 0 || d.fillStrokeMode == 2) {
SkPaint p;
p.setStyle(SkPaint::kFill_Style);
p.setStrokeCap(SkPaint::kRound_Cap);
p.setColor4f(convert_vec4<SkColor4f>(d.fillColor));
canvas->drawPath(rectPath, p);
}
if(d.fillStrokeMode == 1 || d.fillStrokeMode == 2) {
SkPaint p;
p.setStyle(SkPaint::kStroke_Style);
p.setStrokeCap(SkPaint::kRound_Cap);
p.setColor4f(convert_vec4<SkColor4f>(d.strokeColor));
p.setStrokeWidth(d.strokeWidth);
canvas->drawPath(rectPath, p);
}
canvas->restore();
}
}
void DrawRectangle::update(DrawingProgram& drawP) {
}
void DrawRectangle::initialize_draw_data(DrawingProgram& drawP) {
create_draw_data();
}
bool DrawRectangle::collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst, bool colliderAllocated) {
return collisionTree.is_collide(checkAgainst);
}
void DrawRectangle::create_draw_data() {
rectPath = SkPath();
if(d.p1.x() == d.p2.x() || d.p1.y() == d.p2.y()) {
rectPath.moveTo(convert_vec2<SkPoint>(d.p1));
rectPath.lineTo(convert_vec2<SkPoint>(d.p2));
}
else {
SkRect newRect = SkRect::MakeLTRB(d.p1.x(), d.p1.y(), d.p2.x(), d.p2.y());
rectPath.addRoundRect(newRect, d.cornerRadius, d.cornerRadius);
}
}
void DrawRectangle::create_collider(bool colliderAllocated) {
using namespace SCollision;
ColliderCollection<float> strokeObjects;
if(d.fillStrokeMode == 0) {
std::array<Vector2f, 4> newT = triangle_from_rect_points(d.p1, d.p2);
strokeObjects.triangle.emplace_back(newT[0], newT[1], newT[2]);
strokeObjects.triangle.emplace_back(newT[2], newT[3], newT[0]);
}
else if(d.fillStrokeMode == 1) {
if(d.p1.x() == d.p2.x() || d.p1.y() == d.p2.y()) {
std::vector<Vector2f> points = {d.p1, d.p2};
generate_polyline(strokeObjects, points, d.strokeWidth, true);
}
else {
std::array<Vector2f, 4> pointArr = triangle_from_rect_points(d.p1, d.p2);
std::vector<Vector2f> points(pointArr.begin(), pointArr.end());
generate_polyline(strokeObjects, points, d.strokeWidth, true);
}
}
else if(d.fillStrokeMode == 2) {
float strokeRadius = d.strokeWidth * 0.5f;
std::array<Vector2f, 4> newT = triangle_from_rect_points((d.p1 - Vector2f{strokeRadius, strokeRadius}).eval(), (d.p2 + Vector2f{strokeRadius, strokeRadius}).eval());
strokeObjects.triangle.emplace_back(newT[0], newT[1], newT[2]);
strokeObjects.triangle.emplace_back(newT[2], newT[3], newT[0]);
}
collisionTree.clear();
collisionTree.calculate_bvh_recursive(strokeObjects);
calculate_world_bounds();
}
void DrawRectangle::free_collider() {
}
void DrawRectangle::allocate_collider() {
}
SCollision::AABB<float> DrawRectangle::get_obj_coord_bounds() const {
return collisionTree.objects.bounds;
}
#endif

View file

@ -0,0 +1,48 @@
#pragma once
#include "../SharedTypes.hpp"
#include "DrawComponent.hpp"
#include "../CoordSpaceHelper.hpp"
#ifndef IS_SERVER
#include <include/core/SkPath.h>
#endif
class DrawRectangle : public DrawComponent {
public:
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
virtual DrawComponentType get_type() const override;
// User input data
struct Data {
Vector4f strokeColor;
Vector4f fillColor;
float cornerRadius;
float strokeWidth;
Vector2f p1;
Vector2f p2;
uint8_t fillStrokeMode;
bool operator==(const Data& o) const = default;
} d;
#ifndef IS_SERVER
virtual std::shared_ptr<DrawComponent> copy() const override;
virtual void draw(SkCanvas* canvas, const DrawData& drawData) override;
virtual void initialize_draw_data(DrawingProgram& drawP) override;
virtual void update(DrawingProgram& drawP) override;
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst, bool colliderAllocated) override;
void create_draw_data();
virtual void create_collider(bool colliderAllocated) override;
virtual void update_from_delayed_ptr() override;
virtual void free_collider() override;
virtual void allocate_collider() override;
virtual SCollision::AABB<float> get_obj_coord_bounds() const override;
SkPath rectPath;
SCollision::BVHContainer<float> collisionTree;
#endif
};

View file

@ -0,0 +1,134 @@
#include "DrawTextBox.hpp"
#include "Helpers/ConvertVec.hpp"
#include <cereal/types/string.hpp>
#ifndef IS_SERVER
#include "../MainProgram.hpp"
#include "Helpers/SCollision.hpp"
#include "../DrawingProgram/DrawingProgram.hpp"
#include "../DrawCollision.hpp"
#endif
DrawComponentType DrawTextBox::get_type() const {
return DRAWCOMPONENT_TEXTBOX;
}
void DrawTextBox::save(cereal::PortableBinaryOutputArchive& a) const {
a(d.editing, d.p1, d.p2, d.textColor, d.textSize, d.cursor, d.currentText);
}
void DrawTextBox::load(cereal::PortableBinaryInputArchive& a) {
a(d.editing, d.p1, d.p2, d.textColor, d.textSize, d.cursor, d.currentText);
#ifndef IS_SERVER
textboxUpdate = true;
#endif
}
#ifndef IS_SERVER
std::shared_ptr<DrawComponent> DrawTextBox::copy() const {
auto a = std::make_shared<DrawTextBox>();
a->d = d;
a->coords = coords;
a->textboxUpdate = true;
return a;
}
void DrawTextBox::update_from_delayed_ptr() {
std::shared_ptr<DrawTextBox> newPtr = std::static_pointer_cast<DrawTextBox>(delayedUpdatePtr);
d = newPtr->d;
textboxUpdate = true;
}
void DrawTextBox::init_text_box(DrawingProgram& drawP) {
textBox.setFontMgr(drawP.world.main.fonts.mgr);
SkFont f(drawP.world.main.fonts.map["Roboto"], d.textSize);
f.setLinearMetrics(true);
f.setHinting(SkFontHinting::kNormal);
//font.setForceAutoHinting(true);
f.setSubpixel(true);
f.setBaselineSnap(true);
f.setEdging(SkFont::Edging::kSubpixelAntiAlias);
textBox.setFont(f);
textBox.setWidth(d.p2.x() - d.p1.x());
textBox.fLines.clear();
textBox.insert({0, 0}, d.currentText);
}
void DrawTextBox::draw(SkCanvas* canvas, const DrawData& drawData) {
if(drawSetupData.shouldDraw) {
canvas->save();
canvas_do_calculated_transform(canvas);
SkRect clipR = SkRect::MakeLTRB(d.p1.x(), d.p1.y(), d.p2.x(), d.p2.y());
if(d.editing) {
SkPaint p;
p.setStyle(SkPaint::kStroke_Style);
p.setStrokeWidth(0.0f);
p.setColor4f({0.5f, 0.5f, 0.5f, 1.0f});
canvas->drawRect(clipR, p);
}
canvas->clipRect(clipR);
canvas->translate(d.p1.x(), d.p1.y());
CollabTextBox::Editor::PaintOpts paintOpts;
paintOpts.fForegroundColor = convert_vec4<SkColor4f>(d.textColor);
paintOpts.fBackgroundColor = {0.0f, 0.0f, 0.0f, 0.0f};
paintOpts.cursorColor = {0.2f, 0.2f, 0.7f, 1.0f};
paintOpts.showCursor = d.editing;
paintOpts.cursor = d.cursor;
textBox.paint(canvas, paintOpts);
canvas->restore();
}
}
void DrawTextBox::update(DrawingProgram& drawP) {
if(textboxUpdate)
set_textbox_string(d.currentText);
}
void DrawTextBox::set_textbox_string(const std::string& str) {
d.currentText = str;
textBox.fLines.clear();
textBox.insert({0, 0}, d.currentText);
textboxUpdate = false;
}
void DrawTextBox::update_contained_string() {
d.currentText = textBox.get_string();
updateDraw = true;
}
Vector2f DrawTextBox::get_mouse_pos(DrawingProgram& drawP) {
return coords.get_mouse_pos(drawP.world) - d.p1;
}
void DrawTextBox::initialize_draw_data(DrawingProgram& drawP) {
init_text_box(drawP);
}
bool DrawTextBox::collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst, bool colliderAllocated) {
return collisionTree.is_collide(checkAgainst);
}
void DrawTextBox::create_collider(bool colliderAllocated) {
using namespace SCollision;
ColliderCollection<float> strokeObjects;
std::array<Vector2f, 4> newT = triangle_from_rect_points(d.p1, d.p2);
strokeObjects.triangle.emplace_back(newT[0], newT[1], newT[2]);
strokeObjects.triangle.emplace_back(newT[2], newT[3], newT[0]);
collisionTree.clear();
collisionTree.calculate_bvh_recursive(strokeObjects);
calculate_world_bounds();
}
void DrawTextBox::free_collider() {
}
void DrawTextBox::allocate_collider() {
}
SCollision::AABB<float> DrawTextBox::get_obj_coord_bounds() const {
return collisionTree.objects.bounds;
}
#endif

View file

@ -0,0 +1,52 @@
#pragma once
#include "../SharedTypes.hpp"
#include "DrawComponent.hpp"
#include "../CollabTextBox/CollabTextBoxCursor.hpp"
#include "../CoordSpaceHelper.hpp"
#ifndef IS_SERVER
#include <include/core/SkPath.h>
#include "../CollabTextBox/CollabTextBox.hpp"
#endif
class DrawTextBox : public DrawComponent {
public:
virtual DrawComponentType get_type() const override;
virtual void save(cereal::PortableBinaryOutputArchive& a) const override;
virtual void load(cereal::PortableBinaryInputArchive& a) override;
// User input data
struct Data {
Vector4f textColor;
float textSize = -100000.0f;
Vector2f p1;
Vector2f p2;
std::string currentText;
CollabTextBox::Cursor cursor;
bool editing = false;
bool operator==(const Data& o) const = default;
} d;
#ifndef IS_SERVER
virtual std::shared_ptr<DrawComponent> copy() const override;
virtual void draw(SkCanvas* canvas, const DrawData& drawData) override;
virtual void initialize_draw_data(DrawingProgram& drawP) override;
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst, bool colliderAllocated) override;
virtual void update(DrawingProgram& drawP) override;
virtual void create_collider(bool colliderAllocated) override;
virtual void update_from_delayed_ptr() override;
void init_text_box(DrawingProgram& drawP);
void update_contained_string();
void set_textbox_string(const std::string& str);
Vector2f get_mouse_pos(DrawingProgram& drawP);
virtual void free_collider() override;
virtual void allocate_collider() override;
virtual SCollision::AABB<float> get_obj_coord_bounds() const override;
bool textboxUpdate = false;
CollabTextBox::Editor textBox;
SCollision::BVHContainer<float> collisionTree;
#endif
};