Added quad alignment/snapping to other quads/points

This commit is contained in:
archimede67 2023-12-13 20:54:59 +01:00 committed by Corantin H
parent a41da8182b
commit 5dbd5a1f49
4 changed files with 589 additions and 35 deletions

View file

@ -101,6 +101,8 @@ MACRO_CONFIG_INT(EdSmoothZoomTime, ed_smooth_zoom_time, 250, 0, 5000, CFGFLAG_CL
MACRO_CONFIG_INT(EdLimitMaxZoomLevel, ed_limit_max_zoom_level, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Specifies, if zooming in the editor should be limited or not (0 = no limit)")
MACRO_CONFIG_INT(EdZoomTarget, ed_zoom_target, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Zoom to the current mouse target")
MACRO_CONFIG_INT(EdShowkeys, ed_showkeys, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show pressed keys")
MACRO_CONFIG_INT(EdAlignQuads, ed_align_quads, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Enable/disable quad alignment. When enabled, red lines appear to show how quad/points are aligned and snapped to other quads/points when moving them")
MACRO_CONFIG_INT(EdShowQuadsRect, ed_show_quads_rect, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show the bounds of the selected quad. In case of multiple quads, it shows the bounds of the englobing rect. Can be helpful when aligning a group of quads")
MACRO_CONFIG_INT(ClShowWelcome, cl_show_welcome, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show welcome message indicating the first launch of the client")
MACRO_CONFIG_INT(ClMotdTime, cl_motd_time, 10, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "How long to show the server message of the day")

View file

@ -1536,9 +1536,8 @@ void CEditor::PreparePointDrag(const std::shared_ptr<CLayerQuads> &pLayer, CQuad
void CEditor::DoPointDrag(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex, int OffsetX, int OffsetY)
{
EAxis Axis = GetDragAxis(OffsetX, OffsetY);
pQuad->m_aPoints[PointIndex].x = m_QuadDragOriginalPoints[QuadIndex][PointIndex].x + ((Axis == EAxis::AXIS_NONE || Axis == EAxis::AXIS_X) ? OffsetX : 0);
pQuad->m_aPoints[PointIndex].y = m_QuadDragOriginalPoints[QuadIndex][PointIndex].y + ((Axis == EAxis::AXIS_NONE || Axis == EAxis::AXIS_Y) ? OffsetY : 0);
pQuad->m_aPoints[PointIndex].x = m_QuadDragOriginalPoints[QuadIndex][PointIndex].x + OffsetX;
pQuad->m_aPoints[PointIndex].y = m_QuadDragOriginalPoints[QuadIndex][PointIndex].y + OffsetY;
}
CEditor::EAxis CEditor::GetDragAxis(int OffsetX, int OffsetY)
@ -1552,7 +1551,7 @@ CEditor::EAxis CEditor::GetDragAxis(int OffsetX, int OffsetY)
return EAxis::AXIS_NONE;
}
void CEditor::DrawAxis(EAxis Axis, CPoint &Point)
void CEditor::DrawAxis(EAxis Axis, CPoint &OriginalPoint, CPoint &Point)
{
if(Axis == EAxis::AXIS_NONE)
return;
@ -1560,20 +1559,431 @@ void CEditor::DrawAxis(EAxis Axis, CPoint &Point)
Graphics()->SetColor(1, 0, 0.1f, 1);
if(Axis == EAxis::AXIS_X)
{
IGraphics::CQuadItem QuadItem(0, fx2f(Point.y), Graphics()->ScreenWidth() * m_MouseWScale, 1.0f * m_MouseWScale);
Graphics()->QuadsDraw(&QuadItem, 1);
IGraphics::CQuadItem Line(fx2f(OriginalPoint.x + Point.x) / 2.0f, fx2f(OriginalPoint.y), fx2f(Point.x - OriginalPoint.x), 1.0f * m_MouseWScale);
Graphics()->QuadsDraw(&Line, 1);
}
else if(Axis == EAxis::AXIS_Y)
{
IGraphics::CQuadItem QuadItem(fx2f(Point.x), 0, 1.0f * m_MouseWScale, Graphics()->ScreenHeight() * m_MouseWScale);
Graphics()->QuadsDraw(&QuadItem, 1);
IGraphics::CQuadItem Line(fx2f(OriginalPoint.x), fx2f(OriginalPoint.y + Point.y) / 2.0f, 1.0f * m_MouseWScale, fx2f(Point.y - OriginalPoint.y));
Graphics()->QuadsDraw(&Line, 1);
}
// Draw ghost of original point
IGraphics::CQuadItem QuadItem(fx2f(Point.x), fx2f(Point.y), 5.0f * m_MouseWScale, 5.0f * m_MouseWScale);
IGraphics::CQuadItem QuadItem(fx2f(OriginalPoint.x), fx2f(OriginalPoint.y), 5.0f * m_MouseWScale, 5.0f * m_MouseWScale);
Graphics()->QuadsDraw(&QuadItem, 1);
}
void CEditor::ComputePointAlignments(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex, int OffsetX, int OffsetY, std::vector<SAlignmentInfo> &vAlignments, bool Append) const
{
if(!Append)
vAlignments.clear();
if(!g_Config.m_EdAlignQuads)
return;
// Perform computation from the original position of this point
int Threshold = f2fx(maximum(10.0f, 10.0f * m_MouseWScale));
CPoint OrigPoint = m_QuadDragOriginalPoints.at(QuadIndex)[PointIndex];
// Get the "current" point by applying the offset
CPoint Point = OrigPoint + ivec2(OffsetX, OffsetY);
// Save smallest diff on both axis to only keep closest alignments
int SmallestDiffX = Threshold + 1, SmallestDiffY = Threshold + 1;
// Store both axis alignments in separate vectors
std::vector<SAlignmentInfo> vAlignmentsX, vAlignmentsY;
// Check if we can align/snap to a specific point
auto &&CheckAlignment = [&](CPoint *pQuadPoint) {
int DX = pQuadPoint->x - Point.x;
int DY = pQuadPoint->y - Point.y;
int DiffX = absolute(DX);
int DiffY = absolute(DY);
// Check the X axis
if(DiffX <= Threshold)
{
// Only store alignments that have the smallest difference
if(DiffX < SmallestDiffX)
{
vAlignmentsX.clear();
SmallestDiffX = DiffX;
}
// We can have multiple alignments having the same difference/distance
if(DiffX == SmallestDiffX)
{
vAlignmentsX.push_back(SAlignmentInfo{
*pQuadPoint, // Aligned point
{OrigPoint.y}, // Value that can change (which is not snapped), original position
EAxis::AXIS_Y, // The alignment axis
PointIndex, // The index of the point
DX,
});
}
}
if(DiffY <= Threshold)
{
// Only store alignments that have the smallest difference
if(DiffY < SmallestDiffY)
{
vAlignmentsY.clear();
SmallestDiffY = DiffY;
}
if(DiffY == SmallestDiffY)
{
vAlignmentsY.push_back(SAlignmentInfo{
*pQuadPoint,
{OrigPoint.x},
EAxis::AXIS_X,
PointIndex,
DY,
});
}
}
};
// Iterate through all the quads of the current layer
// Check alignment with each point of the quad (corners & pivot)
// Compute an AABB (Axis Aligned Bounding Box) to get the center of the quad
// Check alignment with the center of the quad
for(size_t i = 0; i < pLayer->m_vQuads.size(); i++)
{
auto *pCurrentQuad = &pLayer->m_vQuads[i];
CPoint Min = pCurrentQuad->m_aPoints[0];
CPoint Max = pCurrentQuad->m_aPoints[0];
for(int v = 0; v < 5; v++)
{
CPoint *pQuadPoint = &pCurrentQuad->m_aPoints[v];
if(v != 4)
{ // Don't use pivot to compute AABB
if(pQuadPoint->x < Min.x)
Min.x = pQuadPoint->x;
if(pQuadPoint->y < Min.y)
Min.y = pQuadPoint->y;
if(pQuadPoint->x > Max.x)
Max.x = pQuadPoint->x;
if(pQuadPoint->y > Max.y)
Max.y = pQuadPoint->y;
}
// Don't check alignment with current point
if(pQuadPoint == &pQuad->m_aPoints[PointIndex])
continue;
// Don't check alignment with other selected points
bool IsCurrentPointSelected = IsQuadSelected(i) && (IsQuadCornerSelected(v) || (v == PointIndex && PointIndex == 4));
if(IsCurrentPointSelected)
continue;
CheckAlignment(pQuadPoint);
}
CPoint Center = (Min + Max) / 2.0f;
CheckAlignment(&Center);
}
// Finally concatenate both alignment vectors into the output
vAlignments.reserve(vAlignmentsX.size() + vAlignmentsY.size());
vAlignments.insert(vAlignments.end(), vAlignmentsX.begin(), vAlignmentsX.end());
vAlignments.insert(vAlignments.end(), vAlignmentsY.begin(), vAlignmentsY.end());
}
void CEditor::ComputePointsAlignments(const std::shared_ptr<CLayerQuads> &pLayer, bool Pivot, int OffsetX, int OffsetY, std::vector<SAlignmentInfo> &vAlignments) const
{
// This method is used to compute alignments from selected points
// and only apply the closest alignment on X and Y to the offset.
vAlignments.clear();
std::vector<SAlignmentInfo> vAllAlignments;
for(int Selected : m_vSelectedQuads)
{
CQuad *pQuad = &pLayer->m_vQuads[Selected];
if(!Pivot)
{
for(int m = 0; m < 4; m++)
{
if(IsQuadPointSelected(Selected, m))
{
ComputePointAlignments(pLayer, pQuad, Selected, m, OffsetX, OffsetY, vAllAlignments, true);
}
}
}
else
{
ComputePointAlignments(pLayer, pQuad, Selected, 4, OffsetX, OffsetY, vAllAlignments, true);
}
}
int SmallestDiffX, SmallestDiffY;
SmallestDiffX = SmallestDiffY = std::numeric_limits<int>::max();
std::vector<SAlignmentInfo> vAlignmentsX, vAlignmentsY;
for(const auto &Alignment : vAllAlignments)
{
int AbsDiff = absolute(Alignment.m_Diff);
if(Alignment.m_Axis == EAxis::AXIS_X)
{
if(AbsDiff < SmallestDiffY)
{
SmallestDiffY = AbsDiff;
vAlignmentsY.clear();
}
if(AbsDiff == SmallestDiffY)
vAlignmentsY.emplace_back(Alignment);
}
else if(Alignment.m_Axis == EAxis::AXIS_Y)
{
if(AbsDiff < SmallestDiffX)
{
SmallestDiffX = AbsDiff;
vAlignmentsX.clear();
}
if(AbsDiff == SmallestDiffX)
vAlignmentsX.emplace_back(Alignment);
}
}
vAlignments.reserve(vAlignmentsX.size() + vAlignmentsY.size());
vAlignments.insert(vAlignments.end(), vAlignmentsX.begin(), vAlignmentsX.end());
vAlignments.insert(vAlignments.end(), vAlignmentsY.begin(), vAlignmentsY.end());
}
void CEditor::ComputeAABBAlignments(const std::shared_ptr<CLayerQuads> &pLayer, const SAxisAlignedBoundingBox &AABB, int OffsetX, int OffsetY, std::vector<SAlignmentInfo> &vAlignments) const
{
vAlignments.clear();
if(!g_Config.m_EdAlignQuads)
return;
// This method is a bit different than the point alignment in the way where instead of trying to aling 1 point to all quads,
// we try to align 5 points to all quads, these 5 points being 5 points of an AABB.
// Otherwise, the concept is the same, we use the original position of the AABB to make the computations.
int Threshold = f2fx(maximum(10.0f, 10.0f * m_MouseWScale));
int SmallestDiffX = Threshold + 1, SmallestDiffY = Threshold + 1;
std::vector<SAlignmentInfo> vAlignmentsX, vAlignmentsY;
auto &&CheckAlignment = [&](CPoint &Aligned, int Point) {
CPoint ToCheck = AABB.m_aPoints[Point] + ivec2(OffsetX, OffsetY);
int DX = Aligned.x - ToCheck.x;
int DY = Aligned.y - ToCheck.y;
int DiffX = absolute(DX);
int DiffY = absolute(DY);
if(DiffX <= Threshold)
{
if(DiffX < SmallestDiffX)
{
SmallestDiffX = DiffX;
vAlignmentsX.clear();
}
if(DiffX == SmallestDiffX)
{
vAlignmentsX.push_back(SAlignmentInfo{
Aligned,
{AABB.m_aPoints[Point].y},
EAxis::AXIS_Y,
Point,
DX,
});
}
}
if(DiffY <= Threshold)
{
if(DiffY < SmallestDiffY)
{
SmallestDiffY = DiffY;
vAlignmentsY.clear();
}
if(DiffY == SmallestDiffY)
{
vAlignmentsY.push_back(SAlignmentInfo{
Aligned,
{AABB.m_aPoints[Point].x},
EAxis::AXIS_X,
Point,
DY,
});
}
}
};
auto &&CheckAABBAlignment = [&](CPoint &QuadMin, CPoint &QuadMax) {
CPoint QuadCenter = (QuadMin + QuadMax) / 2.0f;
CPoint aQuadPoints[5] = {
QuadMin, // Top left
{QuadMax.x, QuadMin.y}, // Top right
{QuadMin.x, QuadMax.y}, // Bottom left
QuadMax, // Bottom right
QuadCenter,
};
// Check all points with all the other points
for(auto &QuadPoint : aQuadPoints)
{
// i is the quad point which is "aligned" and that we want to compare with
for(int j = 0; j < 5; j++)
{
// j is the point we try to align
CheckAlignment(QuadPoint, j);
}
}
};
// Iterate through all quads of the current layer
// Compute AABB of all quads and check if the dragged AABB can be aligned to this AABB.
for(size_t i = 0; i < pLayer->m_vQuads.size(); i++)
{
auto *pCurrentQuad = &pLayer->m_vQuads[i];
if(IsQuadSelected(i)) // Don't check with other selected quads
continue;
// Get AABB of this quad
CPoint QuadMin = pCurrentQuad->m_aPoints[0], QuadMax = pCurrentQuad->m_aPoints[0];
for(int v = 1; v < 4; v++)
{
QuadMin.x = minimum(QuadMin.x, pCurrentQuad->m_aPoints[v].x);
QuadMin.y = minimum(QuadMin.y, pCurrentQuad->m_aPoints[v].y);
QuadMax.x = maximum(QuadMax.x, pCurrentQuad->m_aPoints[v].x);
QuadMax.y = maximum(QuadMax.y, pCurrentQuad->m_aPoints[v].y);
}
CheckAABBAlignment(QuadMin, QuadMax);
}
// Finally, concatenate both alignment vectors into the output
vAlignments.reserve(vAlignmentsX.size() + vAlignmentsY.size());
vAlignments.insert(vAlignments.end(), vAlignmentsX.begin(), vAlignmentsX.end());
vAlignments.insert(vAlignments.end(), vAlignmentsY.begin(), vAlignmentsY.end());
}
void CEditor::DrawPointAlignments(const std::vector<SAlignmentInfo> &vAlignments, int OffsetX, int OffsetY)
{
if(!g_Config.m_EdAlignQuads)
return;
// Drawing an alignment is easy, we convert fixed to float for the aligned point coords
// and we also convert the "changing" value after applying the offset (which might be edited to actually align the value with the alignment).
Graphics()->SetColor(1, 0, 0.1f, 1);
for(const SAlignmentInfo &Alignment : vAlignments)
{
// We don't use IGraphics::CLineItem to draw because we don't want to stop QuadsBegin(), quads work just fine.
if(Alignment.m_Axis == EAxis::AXIS_X)
{ // Alignment on X axis is same Y values but different X values
IGraphics::CQuadItem Line(fx2f(Alignment.m_AlignedPoint.x), fx2f(Alignment.m_AlignedPoint.y), fx2f(Alignment.m_X + OffsetX - Alignment.m_AlignedPoint.x), 1.0f * m_MouseWScale);
Graphics()->QuadsDrawTL(&Line, 1);
}
else if(Alignment.m_Axis == EAxis::AXIS_Y)
{ // Alignment on Y axis is same X values but different Y values
IGraphics::CQuadItem Line(fx2f(Alignment.m_AlignedPoint.x), fx2f(Alignment.m_AlignedPoint.y), 1.0f * m_MouseWScale, fx2f(Alignment.m_Y + OffsetY - Alignment.m_AlignedPoint.y));
Graphics()->QuadsDrawTL(&Line, 1);
}
}
}
void CEditor::DrawAABB(const SAxisAlignedBoundingBox &AABB, int OffsetX, int OffsetY)
{
// Drawing an AABB is simply converting the points from fixed to float
// Then making lines out of quads and drawing them
vec2 TL = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_TL].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_TL].y + OffsetY)};
vec2 TR = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_TR].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_TR].y + OffsetY)};
vec2 BL = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_BL].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_BL].y + OffsetY)};
vec2 BR = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_BR].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_BR].y + OffsetY)};
vec2 Center = {fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_CENTER].x + OffsetX), fx2f(AABB.m_aPoints[SAxisAlignedBoundingBox::POINT_CENTER].y + OffsetY)};
// We don't use IGraphics::CLineItem to draw because we don't want to stop QuadsBegin(), quads work just fine.
IGraphics::CQuadItem Lines[4] = {
{TL.x, TL.y, TR.x - TL.x, 1.0f * m_MouseWScale},
{TL.x, TL.y, 1.0f * m_MouseWScale, BL.y - TL.y},
{TR.x, TR.y, 1.0f * m_MouseWScale, BR.y - TR.y},
{BL.x, BL.y, BR.x - BL.x, 1.0f * m_MouseWScale},
};
Graphics()->SetColor(1, 0, 1, 1);
Graphics()->QuadsDrawTL(Lines, 4);
IGraphics::CQuadItem CenterQuad(Center.x, Center.y, 5.0f * m_MouseWScale, 5.0f * m_MouseWScale);
Graphics()->QuadsDraw(&CenterQuad, 1);
}
void CEditor::QuadSelectionAABB(const std::shared_ptr<CLayerQuads> &pLayer, SAxisAlignedBoundingBox &OutAABB)
{
// Compute an englobing AABB of the current selection of quads
CPoint Min{
std::numeric_limits<int>::max(),
std::numeric_limits<int>::max(),
};
CPoint Max{
std::numeric_limits<int>::min(),
std::numeric_limits<int>::min(),
};
for(int Selected : m_vSelectedQuads)
{
CQuad *pQuad = &pLayer->m_vQuads[Selected];
for(auto &Point : pQuad->m_aPoints)
{
Min.x = minimum(Min.x, Point.x);
Min.y = minimum(Min.y, Point.y);
Max.x = maximum(Max.x, Point.x);
Max.y = maximum(Max.y, Point.y);
}
}
CPoint Center = (Min + Max) / 2.0f;
CPoint aPoints[SAxisAlignedBoundingBox::NUM_POINTS] = {
Min, // Top left
{Max.x, Min.y}, // Top right
{Min.x, Max.y}, // Bottom left
Max, // Bottom right
Center,
};
mem_copy(OutAABB.m_aPoints, aPoints, sizeof(CPoint) * SAxisAlignedBoundingBox::NUM_POINTS);
}
void CEditor::ApplyAlignments(const std::vector<SAlignmentInfo> &vAlignments, int &OffsetX, int &OffsetY)
{
if(vAlignments.empty())
return;
// Find X and Y aligment
const int *pAlignedX = nullptr;
const int *pAlignedY = nullptr;
// To Find the alignments we simply iterate through the vector of alignments and find the first
// X and Y alignments.
// Then, we use the saved m_Diff to adjust the offset
int AdjustX = 0, AdjustY = 0;
for(const SAlignmentInfo &Alignment : vAlignments)
{
if(Alignment.m_Axis == EAxis::AXIS_X && !pAlignedY)
{
pAlignedY = &Alignment.m_AlignedPoint.y;
AdjustY = Alignment.m_Diff;
}
else if(Alignment.m_Axis == EAxis::AXIS_Y && !pAlignedX)
{
pAlignedX = &Alignment.m_AlignedPoint.x;
AdjustX = Alignment.m_Diff;
}
}
// Adjust offset
OffsetX += AdjustX;
OffsetY += AdjustY;
}
void CEditor::ApplyAxisAlignment(int &OffsetX, int &OffsetY)
{
// This is used to preserve axis alignment when pressing `Shift`
// Should be called before any other computation
EAxis Axis = GetDragAxis(OffsetX, OffsetY);
OffsetX = ((Axis == EAxis::AXIS_NONE || Axis == EAxis::AXIS_X) ? OffsetX : 0);
OffsetY = ((Axis == EAxis::AXIS_NONE || Axis == EAxis::AXIS_Y) ? OffsetY : 0);
}
void CEditor::DoQuad(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, int Index)
{
enum
@ -1597,6 +2007,10 @@ void CEditor::DoQuad(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, i
float wx = UI()->MouseWorldX();
float wy = UI()->MouseWorldY();
static CPoint s_OriginalPosition;
static std::vector<SAlignmentInfo> s_PivotAlignments; // Alignments per pivot per quad
static std::vector<SAlignmentInfo> s_vAABBAlignments; // Alignments for one AABB (single quad or selection of multiple quads)
static SAxisAlignedBoundingBox s_SelectionAABB; // Selection AABB
static ivec2 s_LastOffset; // Last offset, stored as static so we can use it to draw every frame
// get pivot
float CenterX = fx2f(pQuad->m_aPoints[4].x);
@ -1643,21 +2057,26 @@ void CEditor::DoQuad(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, i
if(Input()->ShiftIsPressed())
{
s_Operation = OP_MOVE_PIVOT;
for(size_t i = 0; i < m_vSelectedQuads.size(); ++i)
// When moving, we need to save the original position of all selected pivots
for(int Selected : m_vSelectedQuads)
{
CQuad *pCurrentQuad = &pLayer->m_vQuads[m_vSelectedQuads[i]];
PreparePointDrag(pLayer, pCurrentQuad, m_vSelectedQuads[i], 4);
CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected];
PreparePointDrag(pLayer, pCurrentQuad, Selected, 4);
}
}
else
{
s_Operation = OP_MOVE_ALL;
for(size_t i = 0; i < m_vSelectedQuads.size(); ++i)
// When moving, we need to save the original position of all selected quads points
for(int Selected : m_vSelectedQuads)
{
CQuad *pCurrentQuad = &pLayer->m_vQuads[m_vSelectedQuads[i]];
CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected];
for(size_t v = 0; v < 5; v++)
PreparePointDrag(pLayer, pCurrentQuad, m_vSelectedQuads[i], v);
PreparePointDrag(pLayer, pCurrentQuad, Selected, v);
}
// And precompute AABB of selection since it will not change during drag
if(g_Config.m_EdAlignQuads)
QuadSelectionAABB(pLayer, s_SelectionAABB);
}
}
}
@ -1667,26 +2086,36 @@ void CEditor::DoQuad(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, i
{
m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads);
ivec2 Offset = GetDragOffset();
s_LastOffset = GetDragOffset(); // Update offset
ApplyAxisAlignment(s_LastOffset.x, s_LastOffset.y); // Apply axis alignment to the offset
ComputePointsAlignments(pLayer, true, s_LastOffset.x, s_LastOffset.y, s_PivotAlignments);
ApplyAlignments(s_PivotAlignments, s_LastOffset.x, s_LastOffset.y);
for(auto &Selected : m_vSelectedQuads)
{
CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected];
DoPointDrag(pLayer, pCurrentQuad, Selected, 4, Offset.x, Offset.y);
DoPointDrag(pLayer, pCurrentQuad, Selected, 4, s_LastOffset.x, s_LastOffset.y);
}
}
else if(s_Operation == OP_MOVE_ALL)
{
m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads);
// move all points including pivot
ivec2 Offset = GetDragOffset();
for(size_t i = 0; i < m_vSelectedQuads.size(); i++)
// Compute drag offset
s_LastOffset = GetDragOffset();
ApplyAxisAlignment(s_LastOffset.x, s_LastOffset.y);
// Then compute possible alignments with the selection AABB
ComputeAABBAlignments(pLayer, s_SelectionAABB, s_LastOffset.x, s_LastOffset.y, s_vAABBAlignments);
// Apply alignments before drag
ApplyAlignments(s_vAABBAlignments, s_LastOffset.x, s_LastOffset.y);
// Then do the drag
for(int Selected : m_vSelectedQuads)
{
int Selected = m_vSelectedQuads[i];
CQuad *pCurrentQuad = &pLayer->m_vQuads[Selected];
for(int v = 0; v < 5; v++)
DoPointDrag(pLayer, pCurrentQuad, Selected, v, Offset.x, Offset.y);
DoPointDrag(pLayer, pCurrentQuad, Selected, v, s_LastOffset.x, s_LastOffset.y);
}
}
else if(s_Operation == OP_ROTATE)
@ -1710,12 +2139,22 @@ void CEditor::DoQuad(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, i
}
}
// Draw axis when moving
// Draw axis and aligments when moving
if(s_Operation == OP_MOVE_PIVOT || s_Operation == OP_MOVE_ALL)
{
ivec2 Offset = GetDragOffset();
EAxis Axis = GetDragAxis(Offset.x, Offset.y);
DrawAxis(Axis, s_OriginalPosition);
EAxis Axis = GetDragAxis(s_LastOffset.x, s_LastOffset.y);
DrawAxis(Axis, s_OriginalPosition, pQuad->m_aPoints[4]);
}
if(s_Operation == OP_MOVE_PIVOT)
DrawPointAlignments(s_PivotAlignments, s_LastOffset.x, s_LastOffset.y);
if(s_Operation == OP_MOVE_ALL)
{
DrawPointAlignments(s_vAABBAlignments, s_LastOffset.x, s_LastOffset.y);
if(g_Config.m_EdShowQuadsRect)
DrawAABB(s_SelectionAABB, s_LastOffset.x, s_LastOffset.y);
}
if(s_Operation == OP_CONTEXT_MENU)
@ -1791,6 +2230,11 @@ void CEditor::DoQuad(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, i
UI()->DisableMouseLock();
s_Operation = OP_NONE;
UI()->SetActiveItem(nullptr);
s_LastOffset = ivec2();
s_OriginalPosition = ivec2();
s_vAABBAlignments.clear();
s_PivotAlignments.clear();
}
}
@ -1892,6 +2336,8 @@ void CEditor::DoQuadPoint(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQu
static float s_MouseXStart = 0.0f;
static float s_MouseYStart = 0.0f;
static CPoint s_OriginalPoint;
static std::vector<SAlignmentInfo> s_Alignments; // Alignments
static ivec2 s_LastOffset;
const bool IgnoreGrid = Input()->AltIsPressed();
@ -1929,6 +2375,7 @@ void CEditor::DoQuadPoint(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQu
else
{
s_Operation = OP_MOVEPOINT;
// Save original positions before moving
s_OriginalPoint = pQuad->m_aPoints[V];
for(int m = 0; m < 4; m++)
if(IsQuadPointSelected(QuadIndex, m))
@ -1941,11 +2388,19 @@ void CEditor::DoQuadPoint(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQu
{
m_QuadTracker.BeginQuadTrack(pLayer, m_vSelectedQuads);
ivec2 Offset = GetDragOffset();
s_LastOffset = GetDragOffset(); // Update offset
ApplyAxisAlignment(s_LastOffset.x, s_LastOffset.y); // Apply axis alignment to offset
ComputePointsAlignments(pLayer, false, s_LastOffset.x, s_LastOffset.y, s_Alignments);
ApplyAlignments(s_Alignments, s_LastOffset.x, s_LastOffset.y);
for(int m = 0; m < 4; m++)
{
if(IsQuadPointSelected(QuadIndex, m))
DoPointDrag(pLayer, pQuad, QuadIndex, m, Offset.x, Offset.y);
{
DoPointDrag(pLayer, pQuad, QuadIndex, m, s_LastOffset.x, s_LastOffset.y);
}
}
}
else if(s_Operation == OP_MOVEUV)
{
@ -1972,12 +2427,17 @@ void CEditor::DoQuadPoint(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQu
}
}
// Draw axis when dragging
// Draw axis and alignments when dragging
if(s_Operation == OP_MOVEPOINT)
{
ivec2 Offset = GetDragOffset();
EAxis Axis = GetDragAxis(Offset.x, Offset.y);
DrawAxis(Axis, s_OriginalPoint);
Graphics()->SetColor(1, 0, 0.1f, 1);
// Axis
EAxis Axis = GetDragAxis(s_LastOffset.x, s_LastOffset.y);
DrawAxis(Axis, s_OriginalPoint, pQuad->m_aPoints[V]);
// Alignments
DrawPointAlignments(s_Alignments, s_LastOffset.x, s_LastOffset.y);
}
if(s_Operation == OP_CONTEXT_MENU)
@ -7402,7 +7862,7 @@ void CEditor::RenderMenubar(CUIRect MenuBar)
if(DoButton_Menu(&s_SettingsButton, "Settings", 0, &SettingsButton, 0, nullptr))
{
static SPopupMenuId s_PopupMenuEntitiesId;
UI()->DoPopupMenu(&s_PopupMenuEntitiesId, SettingsButton.x, SettingsButton.y + SettingsButton.h - 1.0f, 200.0f, 64.0f, this, PopupMenuSettings, PopupProperties);
UI()->DoPopupMenu(&s_PopupMenuEntitiesId, SettingsButton.x, SettingsButton.y + SettingsButton.h - 1.0f, 200.0f, 92.0f, this, PopupMenuSettings, PopupProperties);
}
CUIRect ChangedIndicator, Info, Close;

View file

@ -859,6 +859,19 @@ public:
AXIS_X,
AXIS_Y
};
struct SAxisAlignedBoundingBox
{
enum
{
POINT_TL = 0,
POINT_TR,
POINT_BL,
POINT_BR,
POINT_CENTER,
NUM_POINTS
};
CPoint m_aPoints[NUM_POINTS];
};
void DoMapEditor(CUIRect View);
void DoToolbarLayers(CUIRect Toolbar);
void DoToolbarSounds(CUIRect Toolbar);
@ -866,9 +879,36 @@ public:
void PreparePointDrag(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex);
void DoPointDrag(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex, int OffsetX, int OffsetY);
EAxis GetDragAxis(int OffsetX, int OffsetY);
void DrawAxis(EAxis Axis, CPoint &Point);
void DrawAxis(EAxis Axis, CPoint &OriginalPoint, CPoint &Point);
void DrawAABB(const SAxisAlignedBoundingBox &AABB, int OffsetX = 0, int OffsetY = 0);
ColorRGBA GetButtonColor(const void *pID, int Checked);
// Alignment methods
// These methods take `OffsetX` and `OffsetY` because the calculations are made with the original positions
// of the quad(s), before we started dragging. This allows us to edit `OffsetX` and `OffsetY` based on the previously
// calculated alignments.
struct SAlignmentInfo
{
CPoint m_AlignedPoint; // The "aligned" point, which we want to align/snap to
union
{
// The current changing value when aligned to this point. When aligning to a point on the X axis, then the X value is changing because
// we aligned the Y values (X axis aligned => Y values are the same, Y axis aligned => X values are the same).
int m_X;
int m_Y;
};
EAxis m_Axis; // The axis we are aligning on
int m_PointIndex; // The point index we are aligning
int m_Diff; // Store the difference
};
void ComputePointAlignments(const std::shared_ptr<CLayerQuads> &pLayer, CQuad *pQuad, int QuadIndex, int PointIndex, int OffsetX, int OffsetY, std::vector<SAlignmentInfo> &vAlignments, bool Append = false) const;
void ComputePointsAlignments(const std::shared_ptr<CLayerQuads> &pLayer, bool Pivot, int OffsetX, int OffsetY, std::vector<SAlignmentInfo> &vAlignments) const;
void ComputeAABBAlignments(const std::shared_ptr<CLayerQuads> &pLayer, const SAxisAlignedBoundingBox &AABB, int OffsetX, int OffsetY, std::vector<SAlignmentInfo> &vAlignments) const;
void DrawPointAlignments(const std::vector<SAlignmentInfo> &vAlignments, int OffsetX, int OffsetY);
void QuadSelectionAABB(const std::shared_ptr<CLayerQuads> &pLayer, SAxisAlignedBoundingBox &OutAABB);
void ApplyAlignments(const std::vector<SAlignmentInfo> &vAlignments, int &OffsetX, int &OffsetY);
void ApplyAxisAlignment(int &OffsetX, int &OffsetY);
bool ReplaceImage(const char *pFilename, int StorageType, bool CheckDuplicate);
static bool ReplaceImageCallback(const char *pFilename, int StorageType, void *pUser);
bool ReplaceSound(const char *pFileName, int StorageType, bool CheckDuplicate);
@ -1034,7 +1074,7 @@ private:
void RedoLastAction();
private:
std::map<int, std::map<int, CPoint>> m_QuadDragOriginalPoints;
std::map<int, CPoint[5]> m_QuadDragOriginalPoints;
};
// make sure to inline this function

View file

@ -328,6 +328,58 @@ CUI::EPopupMenuFunctionResult CEditor::PopupMenuSettings(void *pContext, CUIRect
}
}
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
{
Slot.VMargin(5.0f, &Slot);
CUIRect Label, Selector;
Slot.VSplitMid(&Label, &Selector);
CUIRect No, Yes;
Selector.VSplitMid(&No, &Yes);
pEditor->UI()->DoLabel(&Label, "Align quads", 10.0f, TEXTALIGN_ML);
if(pEditor->m_AllowPlaceUnusedTiles != -1)
{
static int s_ButtonNo = 0;
static int s_ButtonYes = 0;
if(pEditor->DoButton_ButtonDec(&s_ButtonNo, "No", !g_Config.m_EdAlignQuads, &No, 0, "Do not perform quad alignment to other quads/points when moving quads"))
{
g_Config.m_EdAlignQuads = false;
}
if(pEditor->DoButton_ButtonInc(&s_ButtonYes, "Yes", g_Config.m_EdAlignQuads, &Yes, 0, "Allow quad alignment to other quads/points when moving quads"))
{
g_Config.m_EdAlignQuads = true;
}
}
}
View.HSplitTop(2.0f, nullptr, &View);
View.HSplitTop(12.0f, &Slot, &View);
{
Slot.VMargin(5.0f, &Slot);
CUIRect Label, Selector;
Slot.VSplitMid(&Label, &Selector);
CUIRect No, Yes;
Selector.VSplitMid(&No, &Yes);
pEditor->UI()->DoLabel(&Label, "Show quads bounds", 10.0f, TEXTALIGN_ML);
if(pEditor->m_AllowPlaceUnusedTiles != -1)
{
static int s_ButtonNo = 0;
static int s_ButtonYes = 0;
if(pEditor->DoButton_ButtonDec(&s_ButtonNo, "No", !g_Config.m_EdShowQuadsRect, &No, 0, "Do not show quad bounds when moving quads"))
{
g_Config.m_EdShowQuadsRect = false;
}
if(pEditor->DoButton_ButtonInc(&s_ButtonYes, "Yes", g_Config.m_EdShowQuadsRect, &Yes, 0, "Show quad bounds when moving quads"))
{
g_Config.m_EdShowQuadsRect = true;
}
}
}
return CUI::POPUP_KEEP_OPEN;
}