Skip to content

Commit

Permalink
Implement ObjectAlignment
Browse files Browse the repository at this point in the history
Closes #91
  • Loading branch information
Phlosioneer authored and bjorn committed Dec 18, 2019
1 parent 9372352 commit 52d1ed3
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 11 deletions.
1 change: 1 addition & 0 deletions docs/reference/json-map-format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Map
layers, array, "Array of :ref:`Layers <json-layer>`"
nextlayerid, int, "Auto-increments for each layer"
nextobjectid, int, "Auto-increments for each placed object"
objectalignment, string, "``unset``, ``top-left``, ``bottom-left``, or ``bottom-center``"
orientation, string, "``orthogonal``, ``isometric``, ``staggered`` or ``hexagonal``"
properties, array, "Array of :ref:`Properties <json-property>`"
renderorder, string, "``right-down`` (the default), ``right-up``, ``left-down`` or ``left-up`` (orthogonal maps only)"
Expand Down
6 changes: 6 additions & 0 deletions docs/reference/tmx-map-format.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ might be useful for XML-namespacing anyway.*
- **nextobjectid:** Stores the next available ID for new objects. This
number is stored to prevent reuse of the same ID after objects have
been removed. (since 0.11)
- **objectalignment:** Controls the origins for tile and shape objects.
Valid values are ``unset``, ``top-left``, ``bottom-left``, and ``bottom-center``.
In isometric mode, ``bottom-left`` is treated the same as ``bottom-center``.
The default value is ``unset``, for compatibility reasons. With ``unset``, tile
objects use ``bottom-left`` in orthogonal mode and ``bottom-center`` in isometric
mode, while shape objects use ``top-left`` everywhere. (since 1.4)

The ``tilewidth`` and ``tileheight`` properties determine the general
grid size of the map. The individual tiles may have different sizes.
Expand Down
33 changes: 27 additions & 6 deletions src/libtiled/isometricrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,17 @@ void IsometricRenderer::drawMapObject(QPainter *painter,

if (!cell.isEmpty()) {
const QSizeF size = object->size();
const QPointF pos = pixelToScreenCoords(object->position());
QPointF pos = pixelToScreenCoords(object->position());

switch (map()->objectAlignment()) {
case Map::BottomLeft:
case Map::BottomCenter:
pos.setY(pos.y() + size.height());
break;
case Map::TopLeft:
case Map::Unset:
break;
}

CellRenderer(painter, this).render(cell, pos, size,
CellRenderer::BottomCenter);
Expand Down Expand Up @@ -433,11 +443,22 @@ void IsometricRenderer::drawMapObject(QPainter *painter,
painter->setPen(pen);
painter->setRenderHint(QPainter::Antialiasing);

QRectF bounds(object->bounds());
switch (map()->objectAlignment()) {
case Map::TopLeft:
bounds.moveTopLeft(QPointF(-bounds.width(), -bounds.height()));
break;
case Map::Unset:
case Map::BottomLeft:
case Map::BottomCenter:
break;
}

// TODO: Do something sensible to make null-sized objects usable

switch (object->shape()) {
case MapObject::Ellipse: {
const QPolygonF rect = pixelRectToScreenPolygon(object->bounds());
const QPolygonF rect = pixelRectToScreenPolygon(bounds);
const QPainterPath ellipse = shape(object);

painter->drawPath(ellipse);
Expand All @@ -453,11 +474,11 @@ void IsometricRenderer::drawMapObject(QPainter *painter,
break;
}
case MapObject::Point:
painter->translate(pixelToScreenCoords(object->position()));
painter->translate(pixelToScreenCoords(bounds.topLeft()));
drawPointObject(painter, color);
break;
case MapObject::Rectangle: {
QPolygonF polygon = pixelRectToScreenPolygon(object->bounds());
QPolygonF polygon = pixelRectToScreenPolygon(bounds);
painter->drawPolygon(polygon);

painter->setPen(colorPen);
Expand All @@ -467,7 +488,7 @@ void IsometricRenderer::drawMapObject(QPainter *painter,
break;
}
case MapObject::Polygon: {
const QPointF &pos = object->position();
const QPointF &pos = bounds.topLeft();
const QPolygonF polygon = object->polygon().translated(pos);
QPolygonF screenPolygon = pixelToScreenCoords(polygon);

Expand All @@ -491,7 +512,7 @@ void IsometricRenderer::drawMapObject(QPainter *painter,
break;
}
case MapObject::Polyline: {
const QPointF &pos = object->position();
const QPointF &pos = bounds.topLeft();
const QPolygonF polygon = object->polygon().translated(pos);
QPolygonF screenPolygon = pixelToScreenCoords(polygon);

Expand Down
4 changes: 4 additions & 0 deletions src/libtiled/maprenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,8 @@ CellRenderer::CellRenderer(QPainter *painter, const MapRenderer *renderer, CellT
* kind of tile has to be drawn. For this reason it is necessary to call
* flush when finished doing drawCell calls. This function is also called by
* the destructor so usually an explicit call is not needed.
*
* This call expects `painter.translate(pos)` to correspond to the Origin point.
*/
void CellRenderer::render(const Cell &cell, const QPointF &pos, const QSizeF &size, Origin origin)
{
Expand Down Expand Up @@ -274,6 +276,7 @@ void CellRenderer::render(const Cell &cell, const QPointF &pos, const QSizeF &si
bool flippedVertically = cell.flippedVertically();

QPainter::PixmapFragment fragment;
// Calculate the position as if the origin is BottomLeft, and correct it later.
fragment.x = pos.x() + (offset.x() * scale.width()) + sizeHalf.x();
fragment.y = pos.y() + (offset.y() * scale.height()) + sizeHalf.y() - size.height();
fragment.sourceLeft = 0;
Expand All @@ -285,6 +288,7 @@ void CellRenderer::render(const Cell &cell, const QPointF &pos, const QSizeF &si
fragment.rotation = 0;
fragment.opacity = 1;

// Correct the position if the origin is not BottomLeft.
if (origin == BottomCenter)
fragment.x -= sizeHalf.x();

Expand Down
26 changes: 21 additions & 5 deletions src/libtiled/orthogonalrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,23 @@ void OrthogonalRenderer::drawMapObject(QPainter *painter,

const QRectF bounds = object->bounds();
QRectF rect(bounds);
QPointF tileAlignmentOffset = QPointF();
switch (map()->objectAlignment()) {
case Map::BottomLeft:
rect.moveTopLeft(QPointF(rect.x(), rect.y() - rect.height()));
tileAlignmentOffset.setY(rect.height());
break;
case Map::BottomCenter:
rect.moveTopLeft(QPointF(rect.x() - rect.width()/2, rect.y() - rect.height()));
tileAlignmentOffset.setY(rect.height());
break;
case Map::TopLeft:
tileAlignmentOffset.setY(rect.height());
break;
case Map::Unset:
break;
}


painter->translate(rect.topLeft());
rect.moveTopLeft(QPointF(0, 0));
Expand All @@ -391,18 +408,17 @@ void OrthogonalRenderer::drawMapObject(QPainter *painter,

if (!cell.isEmpty()) {
const QSizeF size = object->size();
CellRenderer(painter, this).render(cell, QPointF(), size,
CellRenderer::BottomLeft);

CellRenderer(painter, this).render(cell, tileAlignmentOffset, size, CellRenderer::BottomLeft);

if (testFlag(ShowTileObjectOutlines)) {
QPointF tileOffset;

if (const Tile *tile = cell.tile())
tileOffset = tile->offset();

QRectF rect(QPointF(tileOffset.x(),
tileOffset.y() - size.height()),
size);
rect = QRectF(QPointF(tileOffset.x(), tileOffset.y() - size.height()),
size);

QPen pen(Qt::SolidLine);
pen.setCosmetic(true);
Expand Down

0 comments on commit 52d1ed3

Please sign in to comment.