Rewrite collision detection
This commit is contained in:
parent
7b472fe18d
commit
60ab8f3c73
35 changed files with 292 additions and 258 deletions
|
|
@ -27,39 +27,9 @@
|
|||
|
||||
namespace BrushComponentCode {
|
||||
|
||||
MeshShapeData brush_stroke_to_mesh_points(const std::vector<BrushPoint>& brushPoints, bool hasRoundCaps) {
|
||||
SkPath brush_stroke_to_skpath(const std::vector<BrushPoint>& brushPoints, bool hasRoundCaps) {
|
||||
std::vector<BrushPoint> points = smooth_points(brushPoints, 0, brushPoints.size() - 1, DEFAULT_SMOOTHNESS);
|
||||
std::vector<Vector2f> unprocessedPoints;
|
||||
create_triangles(unprocessedPoints, brushPoints, points, hasRoundCaps);
|
||||
using namespace Clipper2Lib;
|
||||
PathsD clippingSubjects;
|
||||
auto& shape = clippingSubjects.emplace_back();
|
||||
for(const Vector2f& p : unprocessedPoints)
|
||||
shape.emplace_back(p.x(), p.y());
|
||||
clippingSubjects = SimplifyPaths(clippingSubjects, 0.1);
|
||||
|
||||
ClipperD clipper;
|
||||
PolyTreeD solutionTree;
|
||||
|
||||
clipper.AddSubject(clippingSubjects);
|
||||
clipper.AddClip(clippingSubjects);
|
||||
clipper.Execute(ClipType::Union, FillRule::NonZero, solutionTree);
|
||||
|
||||
MeshShapeData meshData;
|
||||
|
||||
for(auto& treeChild : solutionTree) {
|
||||
meshData.contours.emplace_back();
|
||||
for(auto& point : treeChild->Polygon())
|
||||
meshData.contours.back().emplace_back(point.x, point.y);
|
||||
for(auto& hole : *treeChild) {
|
||||
meshData.contours.emplace_back();
|
||||
for(auto& point : hole->Polygon())
|
||||
meshData.contours.back().emplace_back(point.x, point.y);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return meshData;
|
||||
return create_triangles(brushPoints, points, hasRoundCaps);
|
||||
}
|
||||
|
||||
std::vector<size_t> get_wedge_indices(const std::vector<BrushPoint>& points) {
|
||||
|
|
@ -119,7 +89,8 @@ std::vector<BrushPoint> smooth_points(const std::vector<BrushPoint>& points, siz
|
|||
return toRet;
|
||||
}
|
||||
|
||||
void create_triangles(std::vector<Vector2f>& brushMeshPointList, const std::vector<BrushPoint>& regularPoints, const std::vector<BrushPoint>& smoothedPoints, bool hasRoundCaps) {
|
||||
SkPath create_triangles(const std::vector<BrushPoint>& regularPoints, const std::vector<BrushPoint>& smoothedPoints, bool hasRoundCaps) {
|
||||
SkPathBuilder pathBuilder;
|
||||
const int ARC_SMOOTHNESS = 10;
|
||||
const int CIRCLE_SMOOTHNESS = 20;
|
||||
const std::vector<BrushPoint>& pointsN = regularPoints;
|
||||
|
|
@ -135,10 +106,10 @@ void create_triangles(std::vector<Vector2f>& brushMeshPointList, const std::vect
|
|||
|
||||
if(pointsN.size() < 2) {
|
||||
if(!topPoints.empty()) {
|
||||
for(auto& p : topPoints)
|
||||
brushMeshPointList.emplace_back(convert_vec2<Vector2f>(p));
|
||||
pathBuilder.addPolygon({topPoints.data(), topPoints.size()}, true);
|
||||
return pathBuilder.detach();
|
||||
}
|
||||
return;
|
||||
return SkPath();
|
||||
}
|
||||
|
||||
auto& points = smoothedPoints;
|
||||
|
|
@ -279,9 +250,10 @@ void create_triangles(std::vector<Vector2f>& brushMeshPointList, const std::vect
|
|||
|
||||
if(!topPoints.empty()) {
|
||||
topPoints.insert(topPoints.begin(), bottomPoints.rbegin(), bottomPoints.rend());
|
||||
for(auto& p : topPoints)
|
||||
brushMeshPointList.emplace_back(convert_vec2<Vector2f>(p));
|
||||
pathBuilder.addPolygon({topPoints.data(), topPoints.size()}, true);
|
||||
}
|
||||
|
||||
return pathBuilder.detach();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ namespace BrushComponentCode {
|
|||
}
|
||||
};
|
||||
|
||||
MeshShapeData brush_stroke_to_mesh_points(const std::vector<BrushPoint>& brushPoints, bool hasRoundCaps);
|
||||
void create_triangles(std::vector<Vector2f>& brushMeshPointList, const std::vector<BrushPoint>& regularPoints, const std::vector<BrushPoint>& smoothedPoints, bool hasRoundCaps);
|
||||
SkPath brush_stroke_to_skpath(const std::vector<BrushPoint>& brushPoints, bool hasRoundCaps);
|
||||
SkPath create_triangles(const std::vector<BrushPoint>& regularPoints, const std::vector<BrushPoint>& smoothedPoints, bool hasRoundCaps);
|
||||
std::vector<size_t> get_wedge_indices(const std::vector<BrushPoint>& points);
|
||||
std::vector<BrushPoint> smooth_points(const std::vector<BrushPoint>& points, size_t beginIndex, size_t endIndex, unsigned numOfDivisions);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class CanvasComponent {
|
|||
|
||||
virtual void draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const = 0;
|
||||
virtual void initialize_draw_data(DrawingProgram& drawP) = 0;
|
||||
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const = 0;
|
||||
virtual bool collides_within_coords_point(const Vector2f& checkAgainst) const = 0;
|
||||
virtual bool collides_within_coords_skpath(const SkPath& checkAgainst) const = 0;
|
||||
|
||||
virtual SCollision::AABB<float> get_obj_coord_bounds() const = 0;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include "../World.hpp"
|
||||
#include "CanvasComponent.hpp"
|
||||
#include <Helpers/NetworkingObjects/DelayUpdateSerializedClassManager.hpp>
|
||||
#include <include/core/SkScalar.h>
|
||||
#include "../ScaleUpCanvas.hpp"
|
||||
#include "Helpers/NetworkingObjects/NetObjManager.hpp"
|
||||
|
||||
|
|
@ -141,31 +142,38 @@ void CanvasComponentContainer::commit_transform_dont_invalidate_cache() {
|
|||
calculate_world_bounds();
|
||||
}
|
||||
|
||||
bool CanvasComponentContainer::collides_with_world_coords(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld) const {
|
||||
return collides_with(camCoords, checkAgainstWorld, camCoords.world_collider_to_coords<SCollision::ColliderCollection<float>>(checkAgainstWorld));
|
||||
}
|
||||
|
||||
bool CanvasComponentContainer::collides_with_cam_coords(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<float>& checkAgainstCam) const {
|
||||
return collides_with(camCoords, camCoords.collider_to_world<SCollision::ColliderCollection<WorldScalar>>(checkAgainstCam), checkAgainstCam);
|
||||
}
|
||||
|
||||
// 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 CanvasComponentContainer::collides_with(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld, const SCollision::ColliderCollection<float>& checkAgainstCam) const {
|
||||
bool CanvasComponentContainer::collides_with(const CoordSpaceHelper& camCoords, const SkPath& checkAgainstCam) const {
|
||||
if(!worldAABB.has_value())
|
||||
return false;
|
||||
if((camCoords.inverseScale << COMP_MAX_SHIFT_BEFORE_STOP_COLLISIONS) < coords.inverseScale) // Object is too large, just dismiss the collision
|
||||
return false;
|
||||
else if(coords.inverseScale < (camCoords.inverseScale >> COMP_COLLIDE_MIN_SHIFT_TINY))
|
||||
return SCollision::collide(checkAgainstCam, camCoords.to_space(worldAABB.value().min));
|
||||
return checkAgainstCam.contains(convert_vec2<SkPoint>(camCoords.to_space(worldAABB.value().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 get_comp().collides_within_coords(c);
|
||||
TransformData drawTransform = calculate_draw_transform(camCoords);
|
||||
SkMatrix m = SkMatrix::I();
|
||||
m.postScale(1.0 / drawTransform.scale, 1.0 / drawTransform.scale).postRotate(-drawTransform.rotation).postTranslate(-drawTransform.translation.x(), -drawTransform.translation.y());
|
||||
std::optional<SkPath> transformedPath = checkAgainstCam.tryMakeTransform(m);
|
||||
if(!transformedPath.has_value())
|
||||
return false;
|
||||
return get_comp().collides_within_coords_skpath(transformedPath.value());
|
||||
}
|
||||
}
|
||||
|
||||
bool CanvasComponentContainer::collides_with_point(const CoordSpaceHelper& camCoords, const Vector2f& checkAgainstCam) const {
|
||||
if(!worldAABB.has_value())
|
||||
return false;
|
||||
if((camCoords.inverseScale << COMP_MAX_SHIFT_BEFORE_STOP_COLLISIONS) < coords.inverseScale) // Object is too large, just dismiss the collision
|
||||
return false;
|
||||
else if(coords.inverseScale < (camCoords.inverseScale >> COMP_COLLIDE_MIN_SHIFT_TINY)) // Object is too tiny, just dismiss the collision
|
||||
return false;
|
||||
else {
|
||||
TransformData drawTransform = calculate_draw_transform(camCoords);
|
||||
SkMatrix m = SkMatrix::I();
|
||||
m.postScale(1.0 / drawTransform.scale, 1.0 / drawTransform.scale).postRotate(-drawTransform.rotation).postTranslate(-drawTransform.translation.x(), -drawTransform.translation.y());
|
||||
SkPoint p = m.mapPoint(convert_vec2<SkPoint>(checkAgainstCam));
|
||||
return get_comp().collides_within_coords_point(convert_vec2<Vector2f>(p));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +189,7 @@ bool CanvasComponentContainer::should_draw(const DrawData& drawData) const {
|
|||
|
||||
CanvasComponentContainer::PreDrawData CanvasComponentContainer::calculate_predraw_data(const DrawData& drawData) const {
|
||||
PreDrawData toRet;
|
||||
toRet.transformData = calculate_draw_transform(drawData);
|
||||
toRet.transformData = calculate_draw_transform(drawData.cam.c);
|
||||
if(toRet.transformData.scale < COMP_MAX_BEFORE_STOP_SCALING)
|
||||
toRet.extraData = get_comp().get_predraw_data(drawData);
|
||||
else
|
||||
|
|
@ -189,11 +197,11 @@ CanvasComponentContainer::PreDrawData CanvasComponentContainer::calculate_predra
|
|||
return toRet;
|
||||
}
|
||||
|
||||
CanvasComponentContainer::TransformData CanvasComponentContainer::calculate_draw_transform(const DrawData& drawData) const {
|
||||
CanvasComponentContainer::TransformData CanvasComponentContainer::calculate_draw_transform(const CoordSpaceHelper& camCoords) const {
|
||||
TransformData toRet;
|
||||
toRet.translation = -coords.to_space(drawData.cam.c.pos);
|
||||
toRet.rotation = (coords.rotation - drawData.cam.c.rotation) * 180.0 / std::numbers::pi;
|
||||
toRet.scale = std::min(static_cast<float>(coords.inverseScale / drawData.cam.c.inverseScale), COMP_MAX_BEFORE_STOP_SCALING);
|
||||
toRet.translation = -coords.to_space(camCoords.pos);
|
||||
toRet.rotation = (coords.rotation - camCoords.rotation) * 180.0 / std::numbers::pi;
|
||||
toRet.scale = std::min(static_cast<float>(coords.inverseScale / camCoords.inverseScale), COMP_MAX_BEFORE_STOP_SCALING);
|
||||
return toRet;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,15 +77,14 @@ class CanvasComponentContainer {
|
|||
void draw(SkCanvas* canvas, const DrawData& drawData) const;
|
||||
void draw_with_predraw_data(SkCanvas* canvas, const DrawData& drawData, const PreDrawData& preDrawData) const;
|
||||
PreDrawData calculate_predraw_data(const DrawData& drawData) const;
|
||||
TransformData calculate_draw_transform(const DrawData& drawData) const;
|
||||
TransformData calculate_draw_transform(const CoordSpaceHelper& camCoords) const;
|
||||
void commit_update(DrawingProgram& drawP);
|
||||
void commit_transform_dont_invalidate_cache(); // Must be thread safe
|
||||
void commit_transform(DrawingProgram& drawP);
|
||||
void commit_update_dont_invalidate_cache(DrawingProgram& drawP); // Must be thread safe
|
||||
bool should_draw(const DrawData& drawData) const;
|
||||
bool collides_with_world_coords(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld) const;
|
||||
bool collides_with_cam_coords(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<float>& checkAgainstCam) const;
|
||||
bool collides_with(const CoordSpaceHelper& camCoords, const SCollision::ColliderCollection<WorldScalar>& checkAgainstWorld, const SCollision::ColliderCollection<float>& checkAgainstCam) const;
|
||||
bool collides_with(const CoordSpaceHelper& camCoords, const SkPath& checkAgainstCam) const;
|
||||
bool collides_with_point(const CoordSpaceHelper& camCoords, const Vector2f& checkAgainstCam) const;
|
||||
void send_comp_update(DrawingProgram& drawP, bool finalUpdate);
|
||||
void scale_up(const WorldScalar& scaleUpAmount);
|
||||
|
||||
|
|
|
|||
|
|
@ -90,12 +90,19 @@ void EllipseCanvasComponent::set_data_from(const CanvasComponent& other) {
|
|||
d = otherEllipse.d;
|
||||
}
|
||||
|
||||
bool EllipseCanvasComponent::collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const {
|
||||
return false;
|
||||
bool EllipseCanvasComponent::collides_within_coords_point(const Vector2f& checkAgainst) const {
|
||||
bool intersectsAABB = ellipsePath.getBounds().contains(checkAgainst.x(), checkAgainst.y());
|
||||
if(!intersectsAABB)
|
||||
return false;
|
||||
return ellipsePath.contains(checkAgainst.x(), checkAgainst.y());
|
||||
}
|
||||
|
||||
bool EllipseCanvasComponent::collides_within_coords_skpath(const SkPath& checkAgainst) const {
|
||||
return checkAgainst.getBounds().intersects(ellipsePath.getBounds()) && Op(checkAgainst, ellipsePath, SkPathOp::kIntersect_SkPathOp);
|
||||
bool intersectsAABB = ellipsePath.getBounds().intersects(checkAgainst.getBounds());
|
||||
if(!intersectsAABB)
|
||||
return false;
|
||||
std::optional<SkPath> pathIntersectCheck = Op(checkAgainst, ellipsePath, SkPathOp::kIntersect_SkPathOp);
|
||||
return pathIntersectCheck.has_value() && !pathIntersectCheck.value().isEmpty();
|
||||
}
|
||||
|
||||
SCollision::AABB<float> EllipseCanvasComponent::get_obj_coord_bounds() const {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ class EllipseCanvasComponent : public CanvasComponent {
|
|||
void create_draw_data();
|
||||
virtual void draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const override;
|
||||
virtual void initialize_draw_data(DrawingProgram& drawP) override;
|
||||
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const override;
|
||||
virtual bool collides_within_coords_point(const Vector2f& checkAgainst) const override;
|
||||
virtual bool collides_within_coords_skpath(const SkPath& checkAgainst) const override;
|
||||
virtual SCollision::AABB<float> get_obj_coord_bounds() const override;
|
||||
void create_triangles(const std::function<bool(Vector2f, Vector2f, Vector2f)>& passTriangleFunc);
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ void ImageCanvasComponent::draw_download_progress_bar(SkCanvas* canvas, const Dr
|
|||
return;
|
||||
if(compContainer->should_draw(drawData)) {
|
||||
canvas->save();
|
||||
compContainer->canvas_do_transform(canvas, compContainer->calculate_draw_transform(drawData));
|
||||
compContainer->canvas_do_transform(canvas, compContainer->calculate_draw_transform(drawData.cam.c));
|
||||
SkPaint p;
|
||||
p.setStroke(true);
|
||||
p.setStrokeWidth(10.0f);
|
||||
|
|
@ -157,8 +157,12 @@ void ImageCanvasComponent::update(DrawingProgram& drawP) {
|
|||
void ImageCanvasComponent::initialize_draw_data(DrawingProgram& drawP) {
|
||||
}
|
||||
|
||||
bool ImageCanvasComponent::collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const {
|
||||
return false;
|
||||
bool ImageCanvasComponent::collides_within_coords_point(const Vector2f& checkAgainst) const {
|
||||
SkPath p = SkPath::Rect(SCollision::AABB<float>(d.p1, d.p2).get_sk_rect());
|
||||
bool intersectsAABB = p.getBounds().contains(checkAgainst.x(), checkAgainst.y());
|
||||
if(!intersectsAABB)
|
||||
return false;
|
||||
return p.contains(checkAgainst.x(), checkAgainst.y());
|
||||
}
|
||||
|
||||
bool ImageCanvasComponent::collides_within_coords_skpath(const SkPath& checkAgainst) const {
|
||||
|
|
@ -168,7 +172,11 @@ bool ImageCanvasComponent::collides_within_coords_skpath(const SkPath& checkAgai
|
|||
else
|
||||
startingRect = get_cropped_rectangle(d.p1, d.p2, d.cropP1, d.cropP2);
|
||||
SkPath p = SkPath::Rect(startingRect);
|
||||
return startingRect.intersects(checkAgainst.getBounds()) && Op(checkAgainst, p, SkPathOp::kIntersect_SkPathOp);
|
||||
bool intersectsAABB = p.getBounds().intersects(checkAgainst.getBounds());
|
||||
if(!intersectsAABB)
|
||||
return false;
|
||||
std::optional<SkPath> pathIntersectCheck = Op(checkAgainst, p, SkPathOp::kIntersect_SkPathOp);
|
||||
return pathIntersectCheck.has_value() && !pathIntersectCheck.value().isEmpty();
|
||||
}
|
||||
|
||||
SCollision::AABB<float> ImageCanvasComponent::get_obj_coord_bounds() const {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class ImageCanvasComponent : public CanvasComponent {
|
|||
private:
|
||||
virtual void draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const override;
|
||||
virtual void initialize_draw_data(DrawingProgram& drawP) override;
|
||||
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const override;
|
||||
virtual bool collides_within_coords_point(const Vector2f& checkAgainst) const override;
|
||||
virtual bool collides_within_coords_skpath(const SkPath& checkAgainst) const override;
|
||||
virtual SCollision::AABB<float> get_obj_coord_bounds() const override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@
|
|||
#include "MeshCanvasComponent.hpp"
|
||||
#include <Helpers/Serializers.hpp>
|
||||
#include <cereal/types/vector.hpp>
|
||||
#include <include/core/SkPathTypes.h>
|
||||
#include <limits>
|
||||
#include <Eigen/Geometry>
|
||||
#include "BrushComponentCode.hpp"
|
||||
#include "Eigen/Core"
|
||||
#include "Helpers/MathExtras.hpp"
|
||||
#include "../TimePoint.hpp"
|
||||
|
|
@ -34,26 +36,75 @@
|
|||
|
||||
#define BVH_LEVELS 3
|
||||
|
||||
template <typename Archive> void skpath_write(const SkPath& p, Archive& a) {
|
||||
std::vector<uint32_t> contourSizes;
|
||||
uint32_t contourSize = 0;
|
||||
auto verbs = p.verbs();
|
||||
auto points = p.points();
|
||||
for(size_t verbIndex = 0; verbIndex < verbs.size(); verbIndex++) {
|
||||
switch(verbs[verbIndex]) {
|
||||
case SkPathVerb::kClose:
|
||||
contourSizes.emplace_back(contourSize);
|
||||
contourSize = 0;
|
||||
break;
|
||||
case SkPathVerb::kMove:
|
||||
case SkPathVerb::kLine:
|
||||
contourSize++;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("[skpath_write] Illegal verb " + std::to_string(static_cast<unsigned>(verbs[verbIndex])));
|
||||
break;
|
||||
}
|
||||
}
|
||||
a(contourSizes);
|
||||
for(const SkPoint& p : points)
|
||||
a(p);
|
||||
}
|
||||
|
||||
template <typename Archive> SkPath skpath_read(Archive& a) {
|
||||
SkPathBuilder builder;
|
||||
std::vector<uint32_t> contourSizes;
|
||||
a(contourSizes);
|
||||
for(uint32_t contourSize : contourSizes) {
|
||||
SkPoint p;
|
||||
if(contourSize == 0)
|
||||
continue;
|
||||
a(p);
|
||||
builder.moveTo(p);
|
||||
for(uint32_t i = 1; i < contourSize; i++) {
|
||||
a(p);
|
||||
builder.lineTo(p);
|
||||
}
|
||||
builder.close();
|
||||
}
|
||||
return builder.detach();
|
||||
}
|
||||
|
||||
void MeshCanvasComponent::save(cereal::PortableBinaryOutputArchive& a) const {
|
||||
a(d.points, d.color);
|
||||
skpath_write(d.meshPath, a);
|
||||
a(d.color);
|
||||
}
|
||||
|
||||
void MeshCanvasComponent::load(cereal::PortableBinaryInputArchive& a) {
|
||||
a(d.points, d.color);
|
||||
d.meshPath = skpath_read(a);
|
||||
a(d.color);
|
||||
}
|
||||
|
||||
void MeshCanvasComponent::save_file(cereal::PortableBinaryOutputArchive& a) const {
|
||||
a(d.points, d.color);
|
||||
skpath_write(d.meshPath, a);
|
||||
a(d.color);
|
||||
}
|
||||
|
||||
void MeshCanvasComponent::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
|
||||
if(version >= VersionNumber(0, 6, 0))
|
||||
a(d.points, d.color);
|
||||
if(version >= VersionNumber(0, 6, 0)) {
|
||||
d.meshPath = skpath_read(a);
|
||||
a(d.color);
|
||||
}
|
||||
else {
|
||||
std::vector<BrushComponentCode::BrushPoint> brushPoints;
|
||||
bool hasRoundCaps;
|
||||
a(brushPoints, d.color, hasRoundCaps);
|
||||
d.points = BrushComponentCode::brush_stroke_to_mesh_points(brushPoints, hasRoundCaps);
|
||||
d.meshPath = BrushComponentCode::brush_stroke_to_skpath(brushPoints, hasRoundCaps);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +130,7 @@ void MeshCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData, const
|
|||
SkPaint paint;
|
||||
paint.setColor4f(SkColor4f{d.color.x(), d.color.y(), d.color.z(), d.color.w()});
|
||||
paint.setAntiAlias(drawData.skiaAA);
|
||||
canvas->drawPath(meshPath, paint);
|
||||
canvas->drawPath(d.meshPath, paint);
|
||||
|
||||
//SkPaint paint2;
|
||||
//paint2.setColor4f(SkColor4f{1.0f, 0.0f, 0.0f, 1.0f});
|
||||
|
|
@ -187,35 +238,31 @@ void MeshCanvasComponent::set_data_from(const CanvasComponent& other) {
|
|||
}
|
||||
|
||||
void MeshCanvasComponent::initialize_draw_data(DrawingProgram& drawP) {
|
||||
if(!d.points.contours.empty()) {
|
||||
SkPathBuilder meshPathBuilder;
|
||||
|
||||
for(auto& contour : d.points.contours) {
|
||||
if(!contour.empty())
|
||||
meshPathBuilder.moveTo(contour.front().x(), contour.front().y());
|
||||
for(size_t i = 1; i < contour.size(); i++) {
|
||||
auto& p = contour[i];
|
||||
meshPathBuilder.lineTo(p.x(), p.y());
|
||||
}
|
||||
meshPathBuilder.close();
|
||||
}
|
||||
|
||||
meshPath = meshPathBuilder.detach();
|
||||
}
|
||||
}
|
||||
|
||||
bool MeshCanvasComponent::collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const {
|
||||
return false;
|
||||
bool MeshCanvasComponent::collides_within_coords_point(const Vector2f& checkAgainst) const {
|
||||
bool intersectsAABB = d.meshPath.getBounds().contains(checkAgainst.x(), checkAgainst.y());
|
||||
if(!intersectsAABB)
|
||||
return false;
|
||||
return d.meshPath.contains(checkAgainst.x(), checkAgainst.y());
|
||||
}
|
||||
|
||||
bool MeshCanvasComponent::collides_within_coords_skpath(const SkPath& checkAgainst) const {
|
||||
bool intersectsAABB = d.meshPath.getBounds().intersects(checkAgainst.getBounds());
|
||||
if(!intersectsAABB)
|
||||
return false;
|
||||
std::optional<SkPath> pathIntersectCheck = Op(checkAgainst, d.meshPath, SkPathOp::kIntersect_SkPathOp);
|
||||
return pathIntersectCheck.has_value() && !pathIntersectCheck.value().isEmpty();
|
||||
}
|
||||
|
||||
bool MeshCanvasComponent::should_draw_extra(const DrawData& drawData, const CoordSpaceHelper& coords) const {
|
||||
SCollision::AABB<float> viewGenerousColliderInObjSpace = coords.world_collider_to_coords<SCollision::AABB<float>>(drawData.cam.viewingAreaGenerousCollider);
|
||||
viewGenerousColliderInObjSpace.min -= Vector2f{1.0f, 1.0f};
|
||||
viewGenerousColliderInObjSpace.max += Vector2f{1.0f, 1.0f};
|
||||
return false;
|
||||
return true;
|
||||
//return collisionTree.is_collide(viewGenerousColliderInObjSpace);
|
||||
}
|
||||
|
||||
SCollision::AABB<float> MeshCanvasComponent::get_obj_coord_bounds() const {
|
||||
return meshPath.getBounds();
|
||||
return d.meshPath.getBounds();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class MeshCanvasComponent : public CanvasComponent {
|
|||
virtual void set_data_from(const CanvasComponent& other) override;
|
||||
|
||||
struct Data {
|
||||
MeshShapeData points;
|
||||
SkPath meshPath;
|
||||
Vector4f color;
|
||||
} d;
|
||||
private:
|
||||
|
|
@ -45,11 +45,10 @@ class MeshCanvasComponent : public CanvasComponent {
|
|||
virtual bool accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords, const std::shared_ptr<void>& predrawData) const override;
|
||||
virtual std::shared_ptr<void> get_predraw_data_accurate(const DrawData& drawData, const CoordSpaceHelper& coords) const override;
|
||||
virtual void initialize_draw_data(DrawingProgram& drawP) override;
|
||||
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const override;
|
||||
virtual bool collides_within_coords_point(const Vector2f& checkAgainst) const override;
|
||||
virtual bool collides_within_coords_skpath(const SkPath& checkAgainst) const override;
|
||||
bool should_draw_extra(const DrawData& drawData, const CoordSpaceHelper& coords) const override;
|
||||
virtual SCollision::AABB<float> get_obj_coord_bounds() const override;
|
||||
SkPath meshPath;
|
||||
SCollision::BVHContainer<float> collisionTree;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -87,10 +87,6 @@ void RectangleCanvasComponent::initialize_draw_data(DrawingProgram& drawP) {
|
|||
create_draw_data();
|
||||
}
|
||||
|
||||
bool RectangleCanvasComponent::collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void RectangleCanvasComponent::create_draw_data() {
|
||||
SkPathBuilder rectPathBuilder;
|
||||
if(d.p1.x() == d.p2.x() || d.p1.y() == d.p2.y()) {
|
||||
|
|
@ -105,8 +101,19 @@ void RectangleCanvasComponent::create_draw_data() {
|
|||
}
|
||||
}
|
||||
|
||||
bool RectangleCanvasComponent::collides_within_coords_point(const Vector2f& checkAgainst) const {
|
||||
bool intersectsAABB = rectPath.getBounds().contains(checkAgainst.x(), checkAgainst.y());
|
||||
if(!intersectsAABB)
|
||||
return false;
|
||||
return rectPath.contains(checkAgainst.x(), checkAgainst.y());
|
||||
}
|
||||
|
||||
bool RectangleCanvasComponent::collides_within_coords_skpath(const SkPath& checkAgainst) const {
|
||||
return checkAgainst.getBounds().intersects(rectPath.getBounds()) && Op(checkAgainst, rectPath, SkPathOp::kIntersect_SkPathOp);
|
||||
bool intersectsAABB = rectPath.getBounds().intersects(checkAgainst.getBounds());
|
||||
if(!intersectsAABB)
|
||||
return false;
|
||||
std::optional<SkPath> pathIntersectCheck = Op(checkAgainst, rectPath, SkPathOp::kIntersect_SkPathOp);
|
||||
return pathIntersectCheck.has_value() && !pathIntersectCheck.value().isEmpty();
|
||||
}
|
||||
|
||||
SCollision::AABB<float> RectangleCanvasComponent::get_obj_coord_bounds() const {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class RectangleCanvasComponent : public CanvasComponent {
|
|||
private:
|
||||
virtual void draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const override;
|
||||
virtual void initialize_draw_data(DrawingProgram& drawP) override;
|
||||
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const override;
|
||||
virtual bool collides_within_coords_point(const Vector2f& checkAgainst) const override;
|
||||
virtual bool collides_within_coords_skpath(const SkPath& checkAgainst) const override;
|
||||
void create_draw_data();
|
||||
virtual SCollision::AABB<float> get_obj_coord_bounds() const override;
|
||||
|
|
|
|||
|
|
@ -128,13 +128,21 @@ void TextBoxCanvasComponent::initialize_draw_data(DrawingProgram& drawP) {
|
|||
init_text_box(drawP);
|
||||
}
|
||||
|
||||
bool TextBoxCanvasComponent::collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const {
|
||||
return false;
|
||||
bool TextBoxCanvasComponent::collides_within_coords_point(const Vector2f& checkAgainst) const {
|
||||
SkPath p = SkPath::Rect(SCollision::AABB<float>(d.p1, d.p2).get_sk_rect());
|
||||
bool intersectsAABB = p.getBounds().contains(checkAgainst.x(), checkAgainst.y());
|
||||
if(!intersectsAABB)
|
||||
return false;
|
||||
return p.contains(checkAgainst.x(), checkAgainst.y());
|
||||
}
|
||||
|
||||
bool TextBoxCanvasComponent::collides_within_coords_skpath(const SkPath& checkAgainst) const {
|
||||
SkPath p = SkPath::Rect(SCollision::AABB<float>(d.p1, d.p2).get_sk_rect());
|
||||
return checkAgainst.getBounds().intersects(p.getBounds()) && Op(checkAgainst, p, SkPathOp::kIntersect_SkPathOp);
|
||||
bool intersectsAABB = p.getBounds().intersects(checkAgainst.getBounds());
|
||||
if(!intersectsAABB)
|
||||
return false;
|
||||
std::optional<SkPath> pathIntersectCheck = Op(checkAgainst, p, SkPathOp::kIntersect_SkPathOp);
|
||||
return pathIntersectCheck.has_value() && !pathIntersectCheck.value().isEmpty();
|
||||
}
|
||||
|
||||
SCollision::AABB<float> TextBoxCanvasComponent::get_obj_coord_bounds() const {
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class TextBoxCanvasComponent : public CanvasComponent {
|
|||
|
||||
virtual void draw(SkCanvas* canvas, const DrawData& drawData, const std::shared_ptr<void>& predrawData) const override;
|
||||
virtual void initialize_draw_data(DrawingProgram& drawP) override;
|
||||
virtual bool collides_within_coords(const SCollision::ColliderCollection<float>& checkAgainst) const override;
|
||||
virtual bool collides_within_coords_point(const Vector2f& checkAgainst) const override;
|
||||
virtual bool collides_within_coords_skpath(const SkPath& checkAgainst) const override;
|
||||
void init_text_box(DrawingProgram& drawP);
|
||||
Vector2f get_mouse_pos(DrawingProgram& drawP) const;
|
||||
|
|
|
|||
|
|
@ -143,12 +143,12 @@ void DrawingProgramCache::clear_own_cached_surfaces() {
|
|||
windowCache.attachedDrawingProgramCache = nullptr;
|
||||
}
|
||||
|
||||
CanvasComponentContainer::ObjInfo* DrawingProgramCache::get_front_object_colliding_with_in_editing_layer(const SCollision::ColliderCollection<float>& cC) {
|
||||
auto cCWorld = drawP.world.drawData.cam.c.collider_to_world<SCollision::ColliderCollection<WorldScalar>, SCollision::ColliderCollection<float>>(cC);
|
||||
CanvasComponentContainer::ObjInfo* DrawingProgramCache::get_front_object_colliding_with_in_editing_layer(const SkPath& cC) {
|
||||
auto cCWorldBounds = drawP.world.drawData.cam.c.collider_to_world<SCollision::AABB<WorldScalar>, SCollision::AABB<float>>(cC.getBounds());
|
||||
CanvasComponentContainer::ObjInfo* p = nullptr;
|
||||
traverse_bvh_run_function(cCWorld.bounds, [&](const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode) {
|
||||
traverse_bvh_run_function(cCWorldBounds, [&](const std::shared_ptr<DrawingProgramCacheBVHNode>& bvhNode) {
|
||||
node_loop_components(bvhNode, [&](const auto& c) {
|
||||
if(drawP.layerMan.component_passes_layer_selector(c, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED) && (!p || c->pos >= p->pos) && c->obj->collides_with_world_coords(drawP.world.drawData.cam.c, cCWorld))
|
||||
if(drawP.layerMan.component_passes_layer_selector(c, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED) && (!p || c->pos >= p->pos) && c->obj->collides_with(drawP.world.drawData.cam.c, cC))
|
||||
p = c;
|
||||
});
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class DrawingProgramCache {
|
|||
void invalidate_cache_at_optional_aabb(const std::optional<SCollision::AABB<WorldScalar>>& aabb);
|
||||
static void delete_all_draw_cache();
|
||||
|
||||
CanvasComponentContainer::ObjInfo* get_front_object_colliding_with_in_editing_layer(const SCollision::ColliderCollection<float>& cC);
|
||||
CanvasComponentContainer::ObjInfo* get_front_object_colliding_with_in_editing_layer(const SkPath& cC);
|
||||
~DrawingProgramCache();
|
||||
private:
|
||||
struct NodeCache {
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ void DrawingProgramSelection::check_add_stroke_color_change_undo() {
|
|||
}
|
||||
}
|
||||
|
||||
void DrawingProgramSelection::add_from_cam_coord_collider_to_selection(const SCollision::ColliderCollection<float>& cC, DrawingProgramLayerManager::LayerSelector layerSelector, bool frontObjectOnly) {
|
||||
void DrawingProgramSelection::add_from_cam_coord_collider_to_selection(const SkPath& cC, DrawingProgramLayerManager::LayerSelector layerSelector, bool frontObjectOnly) {
|
||||
check_add_stroke_color_change_undo();
|
||||
|
||||
std::vector<CanvasComponentContainer::ObjInfo*> selectedComponents;
|
||||
|
|
@ -264,13 +264,13 @@ void DrawingProgramSelection::add_from_cam_coord_collider_to_selection(const SCo
|
|||
}
|
||||
}
|
||||
else {
|
||||
auto cCWorld = drawP.world.drawData.cam.c.collider_to_world<SCollision::ColliderCollection<WorldScalar>, SCollision::ColliderCollection<float>>(cC);
|
||||
drawP.drawCache.traverse_bvh_run_function(cCWorld.bounds, erase_select_objects_in_bvh_func(selectedComponents, cC, cCWorld, layerSelector));
|
||||
auto cCWorldBounds = drawP.world.drawData.cam.c.collider_to_world<SCollision::AABB<WorldScalar>, SCollision::AABB<float>>(cC.getBounds());
|
||||
drawP.drawCache.traverse_bvh_run_function(cCWorldBounds, erase_select_objects_in_bvh_func(selectedComponents, cC, layerSelector));
|
||||
}
|
||||
add_to_selection(selectedComponents);
|
||||
}
|
||||
|
||||
void DrawingProgramSelection::remove_from_cam_coord_collider_to_selection(const SCollision::ColliderCollection<float>& cC, DrawingProgramLayerManager::LayerSelector layerSelector, bool frontObjectOnly) {
|
||||
void DrawingProgramSelection::remove_from_cam_coord_collider_to_selection(const SkPath& cC, DrawingProgramLayerManager::LayerSelector layerSelector, bool frontObjectOnly) {
|
||||
check_add_stroke_color_change_undo();
|
||||
|
||||
if(frontObjectOnly) {
|
||||
|
|
@ -281,9 +281,8 @@ void DrawingProgramSelection::remove_from_cam_coord_collider_to_selection(const
|
|||
}
|
||||
}
|
||||
else {
|
||||
auto cCWorld = drawP.world.drawData.cam.c.collider_to_world<SCollision::ColliderCollection<WorldScalar>, SCollision::ColliderCollection<float>>(cC);
|
||||
std::erase_if(selectedSet, [&](auto& c) {
|
||||
if(drawP.layerMan.component_passes_layer_selector(c, layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, cCWorld, cC)) {
|
||||
if(drawP.layerMan.component_passes_layer_selector(c, layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, cC)) {
|
||||
drawP.drawCache.add_component(c);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -294,13 +293,13 @@ void DrawingProgramSelection::remove_from_cam_coord_collider_to_selection(const
|
|||
calculate_aabb();
|
||||
}
|
||||
|
||||
std::function<bool(const std::shared_ptr<DrawingProgramCacheBVHNode>&)> DrawingProgramSelection::erase_select_objects_in_bvh_func(std::vector<CanvasComponentContainer::ObjInfo*>& selectedComponents, const SCollision::ColliderCollection<float>& cC, const SCollision::ColliderCollection<WorldScalar>& cCWorld, DrawingProgramLayerManager::LayerSelector layerSelector) {
|
||||
std::function<bool(const std::shared_ptr<DrawingProgramCacheBVHNode>&)> DrawingProgramSelection::erase_select_objects_in_bvh_func(std::vector<CanvasComponentContainer::ObjInfo*>& selectedComponents, const SkPath& cC, DrawingProgramLayerManager::LayerSelector layerSelector) {
|
||||
auto toRet = [&](const auto& bvhNode) {
|
||||
if(bvhNode && (bvhNode->coords.inverseScale << 9) < drawP.world.drawData.cam.c.inverseScale &&
|
||||
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()))) {
|
||||
cC.contains(convert_vec2<SkPoint>(drawP.world.drawData.cam.c.to_space(bvhNode->bounds.min))) &&
|
||||
cC.contains(convert_vec2<SkPoint>(drawP.world.drawData.cam.c.to_space(bvhNode->bounds.max))) &&
|
||||
cC.contains(convert_vec2<SkPoint>(drawP.world.drawData.cam.c.to_space(bvhNode->bounds.top_right()))) &&
|
||||
cC.contains(convert_vec2<SkPoint>(drawP.world.drawData.cam.c.to_space(bvhNode->bounds.bottom_left())))) {
|
||||
drawP.drawCache.invalidate_cache_at_aabb(bvhNode->bounds);
|
||||
drawP.drawCache.traverse_bvh_run_function_starting_at_node_no_collision_check(bvhNode, [&](const auto& bvhNodeChild) {
|
||||
drawP.drawCache.node_loop_erase_if_components(bvhNodeChild, [&](auto c) {
|
||||
|
|
@ -315,7 +314,7 @@ std::function<bool(const std::shared_ptr<DrawingProgramCacheBVHNode>&)> DrawingP
|
|||
return false;
|
||||
}
|
||||
drawP.drawCache.node_loop_erase_if_components(bvhNode, [&](auto c) {
|
||||
if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, cCWorld, cC)) {
|
||||
if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, cC)) {
|
||||
selectedComponents.emplace_back(c);
|
||||
drawP.drawCache.invalidate_cache_at_optional_aabb(c->obj->get_world_bounds());
|
||||
return true;
|
||||
|
|
@ -741,11 +740,10 @@ void DrawingProgramSelection::erase_component(CanvasComponentContainer::ObjInfo*
|
|||
reset_all();
|
||||
}
|
||||
|
||||
CanvasComponentContainer::ObjInfo* DrawingProgramSelection::get_front_object_colliding_with_in_editing_layer(const SCollision::ColliderCollection<float>& cC) {
|
||||
auto cCWorld = drawP.world.drawData.cam.c.collider_to_world<SCollision::ColliderCollection<WorldScalar>, SCollision::ColliderCollection<float>>(cC);
|
||||
CanvasComponentContainer::ObjInfo* DrawingProgramSelection::get_front_object_colliding_with_in_editing_layer(const SkPath& cC) {
|
||||
CanvasComponentContainer::ObjInfo* p = nullptr;
|
||||
for(auto& c : selectedSet) {
|
||||
if(drawP.layerMan.component_passes_layer_selector(c, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED) && (!p || c->pos >= p->pos) && c->obj->collides_with(drawP.world.drawData.cam.c, cCWorld, cC))
|
||||
if(drawP.layerMan.component_passes_layer_selector(c, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED) && (!p || c->pos >= p->pos) && c->obj->collides_with(drawP.world.drawData.cam.c, cC))
|
||||
p = c;
|
||||
}
|
||||
return p;
|
||||
|
|
|
|||
|
|
@ -32,8 +32,8 @@ class DrawingProgramSelection {
|
|||
Vector4f* color_picker_color(Vector4f* oldColor);
|
||||
void phone_selection_gui(PhoneDrawingProgramScreen& t);
|
||||
void phone_selection_bottom_toolbar(PhoneDrawingProgramScreen& t);
|
||||
void add_from_cam_coord_collider_to_selection(const SCollision::ColliderCollection<float>& cC, DrawingProgramLayerManager::LayerSelector layerSelector, bool frontObjectOnly);
|
||||
void remove_from_cam_coord_collider_to_selection(const SCollision::ColliderCollection<float>& cC, DrawingProgramLayerManager::LayerSelector layerSelector, bool frontObjectOnly);
|
||||
void add_from_cam_coord_collider_to_selection(const SkPath& cC, DrawingProgramLayerManager::LayerSelector layerSelector, bool frontObjectOnly);
|
||||
void remove_from_cam_coord_collider_to_selection(const SkPath& cC, DrawingProgramLayerManager::LayerSelector layerSelector, bool frontObjectOnly);
|
||||
void erase_component(CanvasComponentContainer::ObjInfo* objToCheck);
|
||||
bool is_something_selected();
|
||||
bool is_selected(CanvasComponentContainer::ObjInfo* objToCheck);
|
||||
|
|
@ -50,7 +50,7 @@ class DrawingProgramSelection {
|
|||
void push_selection_to_back();
|
||||
void delete_all();
|
||||
void selection_to_clipboard();
|
||||
CanvasComponentContainer::ObjInfo* get_front_object_colliding_with_in_editing_layer(const SCollision::ColliderCollection<float>& cC);
|
||||
CanvasComponentContainer::ObjInfo* get_front_object_colliding_with_in_editing_layer(const SkPath& cC);
|
||||
void input_key_callback_modify_selection(const InputManager::KeyCallbackArgs& key);
|
||||
void input_key_callback_display_selection(const InputManager::KeyCallbackArgs& key);
|
||||
void input_mouse_button_on_canvas_callback_modify_selection(const InputManager::MouseButtonCallbackArgs& button);
|
||||
|
|
@ -85,7 +85,7 @@ class DrawingProgramSelection {
|
|||
void calculate_aabb();
|
||||
void reset_all();
|
||||
void reset_transform_data();
|
||||
std::function<bool(const std::shared_ptr<DrawingProgramCacheBVHNode>&)> erase_select_objects_in_bvh_func(std::vector<CanvasComponentContainer::ObjInfo*>& selectedComponents, const SCollision::ColliderCollection<float>& cC, const SCollision::ColliderCollection<WorldScalar>& cCWorld, DrawingProgramLayerManager::LayerSelector layerSelector);
|
||||
std::function<bool(const std::shared_ptr<DrawingProgramCacheBVHNode>&)> erase_select_objects_in_bvh_func(std::vector<CanvasComponentContainer::ObjInfo*>& selectedComponents, const SkPath& cC, DrawingProgramLayerManager::LayerSelector layerSelector);
|
||||
|
||||
SCollision::ColliderCollection<float> camSpaceSelection;
|
||||
std::array<WorldVec, 4> selectionRectPoints;
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ void BrushTool::commit_data(bool final) {
|
|||
if(objInfoBeingEdited) {
|
||||
NetworkingObjects::NetObjOwnerPtr<CanvasComponentContainer>& containerPtr = objInfoBeingEdited->obj;
|
||||
MeshCanvasComponent& newMesh = static_cast<MeshCanvasComponent&>(containerPtr->get_comp());
|
||||
newMesh.d.points = BrushComponentCode::brush_stroke_to_mesh_points(brushPoints, drawP.world.main.toolConfig.brush.hasRoundCaps);
|
||||
newMesh.d.meshPath = BrushComponentCode::brush_stroke_to_skpath(brushPoints, drawP.world.main.toolConfig.brush.hasRoundCaps);
|
||||
containerPtr->commit_update(drawP);
|
||||
containerPtr->send_comp_update(drawP, final);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,19 +108,11 @@ void EditTool::input_mouse_button_on_canvas_callback(const InputManager::MouseBu
|
|||
if(!objInfoBeingEdited) {
|
||||
WorldVec mouseWorldPos = drawP.world.drawData.cam.c.from_space(button.pos);
|
||||
|
||||
SCollision::AABB<WorldScalar> mouseAABB{mouseWorldPos - WorldVec{0.5f, 0.5f}, mouseWorldPos + WorldVec{0.5f, 0.5f}};
|
||||
SCollision::ColliderCollection<WorldScalar> cMouseAABB;
|
||||
cMouseAABB.aabb.emplace_back(mouseAABB);
|
||||
cMouseAABB.recalculate_bounds();
|
||||
|
||||
SCollision::AABB<float> camMouseAABB{drawP.world.main.input.mouse.pos - Vector2f{0.5f, 0.5f}, button.pos + Vector2f{0.5f, 0.5f}};
|
||||
SCollision::ColliderCollection<float> camCMouseAABB;
|
||||
camCMouseAABB.aabb.emplace_back(camMouseAABB);
|
||||
camCMouseAABB.recalculate_bounds();
|
||||
SkPath camMouseAABB = SkPath::Rect(SkRect::MakeLTRB(drawP.world.main.input.mouse.pos.x() - 0.5f, drawP.world.main.input.mouse.pos.y() - 0.5f, drawP.world.main.input.mouse.pos.x() + 0.5f, drawP.world.main.input.mouse.pos.y() + 0.5f));
|
||||
|
||||
bool modifySelection = !drawP.selection.is_being_transformed();
|
||||
if(button.clicks >= 2 && !drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held && !drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held) {
|
||||
CanvasComponentContainer::ObjInfo* selectedObjectToEdit = drawP.selection.get_front_object_colliding_with_in_editing_layer(camCMouseAABB);
|
||||
CanvasComponentContainer::ObjInfo* selectedObjectToEdit = drawP.selection.get_front_object_colliding_with_in_editing_layer(camMouseAABB);
|
||||
|
||||
if(selectedObjectToEdit && is_editable(selectedObjectToEdit)) {
|
||||
drawP.selection.deselect_all();
|
||||
|
|
@ -130,43 +122,38 @@ void EditTool::input_mouse_button_on_canvas_callback(const InputManager::MouseBu
|
|||
}
|
||||
if(modifySelection) {
|
||||
if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held)
|
||||
drawP.selection.add_from_cam_coord_collider_to_selection(camCMouseAABB, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED, true);
|
||||
drawP.selection.add_from_cam_coord_collider_to_selection(camMouseAABB, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED, true);
|
||||
else if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LALT).held)
|
||||
drawP.selection.remove_from_cam_coord_collider_to_selection(camCMouseAABB, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED, true);
|
||||
drawP.selection.remove_from_cam_coord_collider_to_selection(camMouseAABB, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED, true);
|
||||
else {
|
||||
drawP.selection.deselect_all();
|
||||
drawP.selection.add_from_cam_coord_collider_to_selection(camCMouseAABB, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED, true);
|
||||
drawP.selection.add_from_cam_coord_collider_to_selection(camMouseAABB, DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
SCollision::Circle<float> mouseCircle{button.pos, 1.0f};
|
||||
SCollision::ColliderCollection<float> cMouseCircle;
|
||||
cMouseCircle.circle.emplace_back(mouseCircle);
|
||||
cMouseCircle.recalculate_bounds();
|
||||
|
||||
bool isMovingPoint = false;
|
||||
bool clickedAway = false;
|
||||
|
||||
if(!pointDragging) {
|
||||
for(HandleData& h : pointHandles) {
|
||||
if(SCollision::collide(mouseCircle, SCollision::Circle<float>(drawP.world.drawData.cam.c.to_space(objInfoBeingEdited->obj->coords.from_space(h.coordMatrix * (*h.p))), drawP.drag_point_radius()))) {
|
||||
if(SCollision::collide(button.pos, SCollision::Circle<float>(drawP.world.drawData.cam.c.to_space(objInfoBeingEdited->obj->coords.from_space(h.coordMatrix * (*h.p))), drawP.drag_point_radius()))) {
|
||||
pointDragging = &h;
|
||||
isMovingPoint = true;
|
||||
}
|
||||
}
|
||||
if(!isMovingPoint && !objInfoBeingEdited->obj->collides_with_cam_coords(drawP.world.drawData.cam.c, cMouseCircle))
|
||||
if(!isMovingPoint && !objInfoBeingEdited->obj->collides_with_point(drawP.world.drawData.cam.c, button.pos))
|
||||
clickedAway = true;
|
||||
}
|
||||
|
||||
for(HandleData& h : pointHandles) {
|
||||
if(SCollision::collide(mouseCircle, SCollision::Circle<float>(drawP.world.drawData.cam.c.to_space(objInfoBeingEdited->obj->coords.from_space(h.coordMatrix * (*h.p))), drawP.drag_point_radius()))) {
|
||||
if(SCollision::collide(button.pos, SCollision::Circle<float>(drawP.world.drawData.cam.c.to_space(objInfoBeingEdited->obj->coords.from_space(h.coordMatrix * (*h.p))), drawP.drag_point_radius()))) {
|
||||
pointDragging = &h;
|
||||
isMovingPoint = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!isMovingPoint && !objInfoBeingEdited->obj->collides_with_cam_coords(drawP.world.drawData.cam.c, cMouseCircle))
|
||||
if(!isMovingPoint && !objInfoBeingEdited->obj->collides_with_point(drawP.world.drawData.cam.c, button.pos))
|
||||
clickedAway = true;
|
||||
|
||||
if(clickedAway)
|
||||
|
|
|
|||
|
|
@ -591,13 +591,11 @@ void TextBoxEditTool::input_mouse_button_on_canvas_callback(const InputManager::
|
|||
if(button.button == InputManager::MouseButton::LEFT && !isDraggingPoint && userInput) {
|
||||
auto& a = static_cast<TextBoxCanvasComponent&>(comp->obj->get_comp());
|
||||
|
||||
SCollision::ColliderCollection<float> mousePointCollection;
|
||||
mousePointCollection.circle.emplace_back(button.pos, 1.0f);
|
||||
mousePointCollection.recalculate_bounds();
|
||||
SkPath mousePoint = SkPath::Circle(button.pos.x(), button.pos.y(), 1.0f);
|
||||
|
||||
InputManager& input = drawP.world.main.input;
|
||||
|
||||
bool collidesWithBox = comp->obj->collides_with_cam_coords(drawP.world.drawData.cam.c, mousePointCollection);
|
||||
bool collidesWithBox = comp->obj->collides_with(drawP.world.drawData.cam.c, mousePoint);
|
||||
|
||||
auto oldCursor = *a.cursor;
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include "../../DrawData.hpp"
|
||||
#include <include/core/SkBlendMode.h>
|
||||
#include <include/core/SkPathBuilder.h>
|
||||
#include "../../CanvasComponents/BrushComponentCode.hpp"
|
||||
|
||||
#include "../../GUIStuff/ElementHelpers/TextLabelHelpers.hpp"
|
||||
#include "../../GUIStuff/ElementHelpers/RadioButtonHelpers.hpp"
|
||||
|
|
@ -74,7 +75,7 @@ void EraserTool::input_mouse_button_on_canvas_callback(const InputManager::Mouse
|
|||
if(button.button == InputManager::MouseButton::LEFT) {
|
||||
if(button.down && !isErasing && !drawP.world.main.g.gui.cursor_obstructed()) {
|
||||
isErasing = true;
|
||||
erase_between_points(button.pos, button.pos);
|
||||
start = end = button.pos;
|
||||
}
|
||||
else if(!button.down && isErasing)
|
||||
switch_tool(get_type());
|
||||
|
|
@ -82,29 +83,17 @@ void EraserTool::input_mouse_button_on_canvas_callback(const InputManager::Mouse
|
|||
}
|
||||
|
||||
void EraserTool::input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) {
|
||||
if(isErasing)
|
||||
erase_between_points(motion.pos, motion.pos - motion.move);
|
||||
end = motion.pos;
|
||||
}
|
||||
|
||||
void EraserTool::erase_between_points(const Vector2f& start, const Vector2f& end) {
|
||||
auto relativeWidthResult = drawP.world.main.toolConfig.get_relative_width_stroke_size(drawP, drawP.world.drawData.cam.c.inverseScale);
|
||||
if(!relativeWidthResult.first.has_value()) {
|
||||
drawP.world.main.toolConfig.print_relative_width_fail_message(relativeWidthResult.second);
|
||||
isErasing = false;
|
||||
return;
|
||||
}
|
||||
float width = relativeWidthResult.first.value() * 0.5f;
|
||||
|
||||
SCollision::ColliderCollection<float> cC;
|
||||
SCollision::generate_wide_line(cC, start, end, width * 2.0f, true);
|
||||
auto cCWorld = drawP.world.drawData.cam.c.collider_to_world<SCollision::ColliderCollection<WorldScalar>, SCollision::ColliderCollection<float>>(cC);
|
||||
|
||||
drawP.drawCache.traverse_bvh_run_function(cCWorld.bounds, [&](const auto& bvhNode) {
|
||||
void EraserTool::erase_between_points() {
|
||||
auto cCWorldBounds = drawP.world.drawData.cam.c.collider_to_world<SCollision::AABB<WorldScalar>, SCollision::AABB<float>>(erasePath.getBounds());
|
||||
drawP.drawCache.traverse_bvh_run_function(cCWorldBounds, [&](const auto& bvhNode) {
|
||||
if(bvhNode &&
|
||||
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()))) {
|
||||
erasePath.contains(convert_vec2<SkPoint>(drawP.world.drawData.cam.c.to_space(bvhNode->bounds.min))) &&
|
||||
erasePath.contains(convert_vec2<SkPoint>(drawP.world.drawData.cam.c.to_space(bvhNode->bounds.max))) &&
|
||||
erasePath.contains(convert_vec2<SkPoint>(drawP.world.drawData.cam.c.to_space(bvhNode->bounds.top_right()))) &&
|
||||
erasePath.contains(convert_vec2<SkPoint>(drawP.world.drawData.cam.c.to_space(bvhNode->bounds.bottom_left())))) {
|
||||
drawP.drawCache.invalidate_cache_at_aabb(bvhNode->bounds);
|
||||
drawP.drawCache.traverse_bvh_run_function_starting_at_node_no_collision_check(bvhNode, [&](const auto& bvhNodeChild) {
|
||||
drawP.drawCache.node_loop_erase_if_components(bvhNodeChild, [&](auto c) {
|
||||
|
|
@ -119,7 +108,7 @@ void EraserTool::erase_between_points(const Vector2f& start, const Vector2f& end
|
|||
return false;
|
||||
}
|
||||
drawP.drawCache.node_loop_erase_if_components(bvhNode, [&](auto c) {
|
||||
if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, cCWorld, cC)) {
|
||||
if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, erasePath)) {
|
||||
erasedComponents.emplace(c);
|
||||
drawP.drawCache.invalidate_cache_at_optional_aabb(c->obj->get_world_bounds());
|
||||
return true;
|
||||
|
|
@ -142,6 +131,31 @@ void EraserTool::switch_tool(DrawingProgramToolType newTool) {
|
|||
void EraserTool::tool_update() {
|
||||
if(!drawP.world.main.g.gui.cursor_obstructed())
|
||||
drawP.world.main.input.hideCursor = true;
|
||||
|
||||
auto relativeWidthResult = drawP.world.main.toolConfig.get_relative_width_stroke_size(drawP, drawP.world.drawData.cam.c.inverseScale);
|
||||
if(!relativeWidthResult.first.has_value()) {
|
||||
isErasing = false;
|
||||
erasePath = eraseBorderPath = SkPath();
|
||||
return;
|
||||
}
|
||||
|
||||
float width = relativeWidthResult.first.value() * 0.5f;
|
||||
using namespace BrushComponentCode;
|
||||
std::vector<BrushPoint> brushPoints;
|
||||
brushPoints.emplace_back(start, width);
|
||||
if(start != end)
|
||||
brushPoints.emplace_back(end, width);
|
||||
erasePath = brush_stroke_to_skpath(brushPoints, true);
|
||||
brushPoints.clear();
|
||||
brushPoints.emplace_back(start, width + 1.0f);
|
||||
if(start != end)
|
||||
brushPoints.emplace_back(end, width + 1.0f);
|
||||
eraseBorderPath = brush_stroke_to_skpath(brushPoints, true);
|
||||
|
||||
if(isErasing)
|
||||
erase_between_points();
|
||||
|
||||
start = end;
|
||||
}
|
||||
|
||||
bool EraserTool::prevent_undo_or_redo() {
|
||||
|
|
@ -149,34 +163,14 @@ bool EraserTool::prevent_undo_or_redo() {
|
|||
}
|
||||
|
||||
void EraserTool::draw(SkCanvas* canvas, const DrawData& drawData) {
|
||||
if(!drawP.world.main.input.isTouchDevice && !drawData.main->g.gui.cursor_obstructed()) {
|
||||
if(!lastPosOpt.has_value())
|
||||
lastPosOpt = drawData.main->input.mouse.pos;
|
||||
auto& lastPos = lastPosOpt.value();
|
||||
if(!drawP.world.main.input.isTouchDevice && !drawData.main->g.gui.cursor_obstructed() && !erasePath.isEmpty() && !eraseBorderPath.isEmpty()) {
|
||||
SkPaint linePaint;
|
||||
linePaint.setAntiAlias(drawData.skiaAA);
|
||||
linePaint.setColor4f({0.0f, 0.0f, 0.0f, 0.4f});
|
||||
linePaint.setStyle(SkPaint::kFill_Style);
|
||||
canvas->drawPath(erasePath, linePaint);
|
||||
|
||||
auto relativeWidthResult = drawP.world.main.toolConfig.get_relative_width_stroke_size(drawP, drawP.world.drawData.cam.c.inverseScale);
|
||||
if(relativeWidthResult.first.has_value()) {
|
||||
float width = relativeWidthResult.first.value() * 0.5f;
|
||||
SkPaint linePaint;
|
||||
linePaint.setAntiAlias(drawData.skiaAA);
|
||||
linePaint.setColor4f({0.0f, 0.0f, 0.0f, 0.4f});
|
||||
linePaint.setStyle(SkPaint::kStroke_Style);
|
||||
linePaint.setStrokeCap(SkPaint::kRound_Cap);
|
||||
linePaint.setStrokeWidth(width * 2.0f + 1.0f);
|
||||
SkPathBuilder erasePath;
|
||||
erasePath.moveTo(convert_vec2<SkPoint>(lastPos));
|
||||
erasePath.lineTo(convert_vec2<SkPoint>(drawData.main->input.mouse.pos));
|
||||
|
||||
SkPath erasePathDetach = erasePath.detach();
|
||||
canvas->drawPath(erasePathDetach, linePaint);
|
||||
|
||||
linePaint.setStrokeWidth(width * 2.0f);
|
||||
linePaint.setColor4f({1.0f, 1.0f, 1.0f, 0.4f});
|
||||
canvas->drawPath(erasePathDetach, linePaint);
|
||||
}
|
||||
|
||||
lastPos = drawData.main->input.mouse.pos;
|
||||
linePaint.setColor4f({1.0f, 1.0f, 1.0f, 0.4f});
|
||||
canvas->drawPath(eraseBorderPath, linePaint);
|
||||
}
|
||||
else
|
||||
lastPosOpt = std::nullopt;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,12 @@ class EraserTool : public DrawingProgramToolBase {
|
|||
|
||||
std::unordered_set<CanvasComponentContainer::ObjInfo*> erasedComponents; // Pointers will be erased from this set if theyre erased in the main list (done by callback)
|
||||
private:
|
||||
void erase_between_points(const Vector2f& start, const Vector2f& end);
|
||||
std::optional<Vector2f> lastPosOpt;
|
||||
SkPath erasePath;
|
||||
SkPath eraseBorderPath;
|
||||
|
||||
Vector2f start;
|
||||
Vector2f end;
|
||||
|
||||
void erase_between_points();
|
||||
bool isErasing = false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -59,10 +59,10 @@ void FillTool::input_mouse_button_on_canvas_callback(const InputManager::MouseBu
|
|||
MeshCanvasComponent& newMesh = static_cast<MeshCanvasComponent&>(newContainer->get_comp());
|
||||
|
||||
newMesh.d.color = toolConfig.globalConf.foregroundColor;
|
||||
newMesh.d.points.fill.emplace_back();
|
||||
newMesh.d.points.fill.back().emplace_back(button.pos + Vector2f{0.0f, 100.0f});
|
||||
newMesh.d.points.fill.back().emplace_back(button.pos + Vector2f{-100.0f, -100.0f});
|
||||
newMesh.d.points.fill.back().emplace_back(button.pos + Vector2f{100.0f, -100.0f});
|
||||
//newMesh.d.points.contours.emplace_back();
|
||||
//newMesh.d.points.contours.back().emplace_back(button.pos + Vector2f{0.0f, 100.0f});
|
||||
//newMesh.d.points.contours.back().emplace_back(button.pos + Vector2f{-100.0f, -100.0f});
|
||||
//newMesh.d.points.contours.back().emplace_back(button.pos + Vector2f{100.0f, -100.0f});
|
||||
newContainer->coords = drawP.world.drawData.cam.c;
|
||||
|
||||
objInfoBeingEdited = drawP.layerMan.add_component_to_layer_being_edited(newContainer);
|
||||
|
|
|
|||
|
|
@ -71,25 +71,14 @@ void LassoSelectTool::input_mouse_button_on_canvas_callback(const InputManager::
|
|||
}
|
||||
else if(controls.isSelecting && button.button == InputManager::MouseButton::LEFT && !button.down) {
|
||||
if(controls.lassoPoints.size() > 3) {
|
||||
SCollision::ColliderCollection<float> cC;
|
||||
SkPathBuilder cCBuild;
|
||||
|
||||
std::vector<std::vector<Vector2f>> polygon;
|
||||
polygon.emplace_back(controls.lassoPoints);
|
||||
auto& poly = polygon[0];
|
||||
for(auto& v : poly)
|
||||
v = controls.coords.from_this_to_cam_space(drawP.world, v);
|
||||
cCBuild.moveTo(convert_vec2<SkPoint>(controls.coords.from_this_to_cam_space(drawP.world, controls.lassoPoints[0])));
|
||||
for(size_t i = 1; i < controls.lassoPoints.size(); i++)
|
||||
cCBuild.lineTo(convert_vec2<SkPoint>(controls.coords.from_this_to_cam_space(drawP.world, controls.lassoPoints[i])));
|
||||
cCBuild.close();
|
||||
|
||||
std::vector<unsigned> indices = mapbox::earcut(polygon);
|
||||
|
||||
for(size_t i = 0; i < indices.size(); i += 3) {
|
||||
cC.triangle.emplace_back(
|
||||
poly[indices[i]],
|
||||
poly[indices[i + 1]],
|
||||
poly[indices[i + 2]]
|
||||
);
|
||||
}
|
||||
|
||||
cC.recalculate_bounds();
|
||||
SkPath cC = cCBuild.detach();
|
||||
|
||||
if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held)
|
||||
drawP.selection.add_from_cam_coord_collider_to_selection(cC, drawP.controls.layerSelector, false);
|
||||
|
|
|
|||
|
|
@ -68,12 +68,8 @@ void RectSelectTool::input_mouse_button_on_canvas_callback(const InputManager::M
|
|||
controls.selectEndAt = controls.coords.from_cam_space_to_this(drawP.world, button.pos);
|
||||
Vector2f newP1 = controls.coords.from_this_to_cam_space(drawP.world, cwise_vec_min(controls.selectStartAt, controls.selectEndAt));
|
||||
Vector2f newP2 = controls.coords.from_this_to_cam_space(drawP.world, cwise_vec_max(controls.selectStartAt, controls.selectEndAt));
|
||||
std::array<Vector2f, 4> newT = triangle_from_rect_points(newP1, newP2);
|
||||
|
||||
ColliderCollection<float> cC;
|
||||
cC.triangle.emplace_back(newT[0], newT[1], newT[2]);
|
||||
cC.triangle.emplace_back(newT[2], newT[3], newT[0]);
|
||||
cC.recalculate_bounds();
|
||||
SkPath cC = SkPath::Rect(SkRect::MakeLTRB(newP1.x(), newP1.y(), newP2.x(), newP2.y()));
|
||||
|
||||
if(drawP.world.main.input.key(InputManager::KEY_GENERIC_LSHIFT).held)
|
||||
drawP.selection.add_from_cam_coord_collider_to_selection(cC, drawP.controls.layerSelector, false);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ VersionNumber header_to_version_number(const std::string& header) {
|
|||
m["INFPNT000003"] = VersionNumber(0, 2, 0);
|
||||
m["INFPNT000004"] = VersionNumber(0, 3, 0);
|
||||
m["INFPNT000005"] = VersionNumber(0, 4, 0);
|
||||
m["INFPNT000006"] = VersionNumber(0, 6, 0);
|
||||
}
|
||||
auto it = m.find(header);
|
||||
if(it == m.end())
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ namespace VersionConstants {
|
|||
VersionNumber header_to_version_number(const std::string& header);
|
||||
|
||||
constexpr int SAVEFILE_HEADER_LEN = 12; // DO NOT CHANGE THIS HEADER LENGTH
|
||||
const std::string CURRENT_SAVEFILE_HEADER = "INFPNT000005"; // Change whenever the save file is incompatible with the previous version
|
||||
const std::string CURRENT_VERSION_STRING = "0.5.0";
|
||||
constexpr VersionNumber CURRENT_VERSION_NUMBER(0, 5, 0);
|
||||
const std::string CURRENT_SAVEFILE_HEADER = "INFPNT000006"; // Change whenever the save file is incompatible with the previous version
|
||||
const std::string CURRENT_VERSION_STRING = "0.6.0";
|
||||
constexpr VersionNumber CURRENT_VERSION_NUMBER(0, 6, 0);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue