29
/*****************************************************************************/
30
// Lightwheight Piece-lookalike.
31
// Used for collision detection. Supports slicing in half to sub-pieces.
32
// Collision-detection works by slicing and bounds-checking halves until either
33
// no collision, or we've found single Tiles that actually collide
36
QList<Tile*> m_children;
37
const Piece * m_piece;
38
const Board * m_board;
40
PieceHelper(const PieceHelper &p) {
45
PieceHelper(const Piece *piece, const Board * board) {
48
m_children = m_piece->children();
49
foreach (Tile* tile, m_children) {
50
m_rect = m_rect.united(tile->rect().translated(tile->pos()).translated(m_piece->position()));
54
void split(PieceHelper &a, PieceHelper &b) const
56
Q_ASSERT(m_children.size() > 1);
57
bool is_fat = m_rect.width() > m_rect.height();
60
splitline = m_rect.center().x() - m_piece->position().x();
62
splitline = m_rect.center().y() - m_piece->position().y();
64
foreach (Tile* tile, m_children) {
65
PieceHelper *target = &a;
67
if (tile->pos().x() >= splitline)
70
if (tile->pos().y() >= splitline)
73
target->m_children.append(tile);
74
target->m_rect = target->m_rect.united(tile->rect().translated(tile->pos()).translated(m_piece->position()));
78
bool collidesWith(const PieceHelper &other) const
80
int margin = m_board->margin();
81
QRect other_rect = other.m_rect.adjusted(-margin, -margin, margin, margin);
82
if (m_children.size() <= 1) {
83
if (other.m_children.size() <= 1)
84
return m_rect.intersects(other_rect);
86
return other.collidesWith(*this);
88
PieceHelper a(*this), b(*this);
90
if (a.m_rect.intersects(other_rect)) {
91
if (other.collidesWith(a))
94
if (b.m_rect.intersects(other_rect)) {
95
if (other.collidesWith(b))
103
/*****************************************************************************/
105
inline float roundUp(float value)
107
return value >= 0.0f ? ceil(value) : floor(value);
110
/*****************************************************************************/
112
Piece::Piece(int rotation, const QPoint& pos, Board* board)
113
: m_rotation(rotation),
27
//-----------------------------------------------------------------------------
29
Piece::Piece(const QPoint& pos, int rotation, const QList<Tile*>& tiles, Board* board)
42
// Add bevels to tiles
43
if (m_tiles.first()->bevel().y() == -1) {
44
int count = m_tiles.count();
45
for (int i = 0; i < count; ++i) {
46
Tile* tile = m_tiles.at(i);
48
sides |= containsTile(tile->column() - 1, tile->row());
49
sides |= containsTile(tile->column() + 1, tile->row()) << 1;
50
sides |= containsTile(tile->column(), tile->row() - 1) << 2;
51
sides |= containsTile(tile->column(), tile->row() + 1) << 3;
52
static const int bevels[14] = {13, 15, 11, 12, 5, 4, 1, 14, 6, 7, 3, 8, 2, 0};
53
tile->setBevel(bevels[sides - 1]);
120
/*****************************************************************************/
63
//-----------------------------------------------------------------------------
124
qDeleteAll(m_children);
127
/*****************************************************************************/
129
QPoint Piece::scenePos() const
134
/*****************************************************************************/
136
QRect Piece::marginRect() const
138
int margin = m_board->margin();
139
return m_rect.translated(m_pos).adjusted(-margin, -margin, margin, margin);
142
/*****************************************************************************/
144
bool Piece::collidesWith(const Piece * other) const
146
if (marginRect().intersects(other->boundingRect())) {
147
PieceHelper a(this, this->m_board), b(other, other->m_board);
148
bool retVal = a.collidesWith(b);
67
graphics_layer->removeArray(m_tile_array);
68
graphics_layer->removeArray(m_shadow_array);
73
//-----------------------------------------------------------------------------
75
bool Piece::collidesWith(const Piece* other) const
77
if (m_board->marginRect(boundingRect()).intersects(other->boundingRect())) {
78
return m_collision_region_expanded.intersects(other->m_collision_region);
155
/*****************************************************************************/
157
void Piece::rotateAround(Tile* tile)
159
m_rect.setRect(-m_rect.bottom() - 1 + m_board->tileSize(), m_rect.left(), m_rect.height(), m_rect.width());
162
QPoint pos = tile->scenePos() - scenePos();
163
m_pos += QPoint(pos.x() + pos.y(), pos.y() - pos.x());
171
foreach (Tile* tile, m_children) {
173
qSwap(pos.rx(), pos.ry());
179
/*****************************************************************************/
181
void Piece::attach(Tile* tile)
183
tile->setParent(this);
184
m_children.append(tile);
185
m_rect = m_rect.united(tile->rect().translated(tile->pos()));
188
/*****************************************************************************/
190
void Piece::attach(Piece* piece)
192
Q_ASSERT(piece != this);
194
QPoint delta = piece->m_pos - m_pos;
196
// Change parent of piece's children
197
QList<Tile*> tiles = piece->m_children;
198
foreach (Tile* tile, tiles) {
199
tile->setPos(tile->pos() + delta);
200
tile->setParent(this);
203
piece->m_children.clear();
205
// Update bounding rectangle
206
m_rect = m_rect.united(piece->m_rect.translated(delta));
209
m_board->removePiece(piece);
212
/*****************************************************************************/
84
//-----------------------------------------------------------------------------
86
QPoint Piece::randomPoint() const
88
Tile* tile = m_tiles.at(rand() % m_tiles.count());
89
return tile->scenePos() + QPoint(rand() % Tile::size, rand() % Tile::size);
92
//-----------------------------------------------------------------------------
214
94
void Piece::attachNeighbors()
216
int margin = m_board->margin();
218
// Create offset vectors
221
switch (m_rotation) {
223
cos_size = m_board->tileSize();
226
sin_size = -m_board->tileSize();
229
cos_size = -m_board->tileSize();
232
sin_size = m_board->tileSize();
235
QPoint left(-cos_size, sin_size);
236
QPoint right(cos_size, -sin_size);
237
QPoint above(-sin_size, -cos_size);
238
QPoint below(sin_size, cos_size);
240
// Find closest tiles
241
QSet<Piece*> closest_pieces;
244
foreach (Piece* piece, m_board->findCollidingPieces(this)) {
245
if (piece->m_rotation != m_rotation)
96
foreach (Piece* piece, m_neighbors) {
97
if (piece->m_rotation != m_rotation) {
248
foreach (Tile* child, m_children) {
249
foreach (Tile* tile, piece->children()) {
250
delta = tile->scenePos() - child->scenePos();
252
// Determine which neighbor the child is of the tile
253
column = tile->column() - child->column() + 2;
254
row = tile->row() - child->row() + 2;
255
switch ((column * 1000) + row) {
272
if (delta.manhattanLength() <= margin) {
273
closest_pieces.insert(piece);
274
piece->moveBy(-delta);
281
// Attach to closest pieces
282
foreach (Piece* piece, closest_pieces) {
287
/*****************************************************************************/
101
Tile* tile = m_tiles.first();
102
Tile* piece_tile = piece->m_tiles.first();
103
QPoint grid_delta = piece_tile->gridPos() - tile->gridPos();
104
for (int i = 0; i < m_rotation; ++i) {
105
QPoint pos = grid_delta;
106
grid_delta.setX( -pos.y() );
107
grid_delta.setY( pos.x() );
109
QPoint top_left = tile->scenePos() + grid_delta;
110
QPoint delta = top_left - piece_tile->scenePos();
112
if (delta.manhattanLength() <= m_board->margin()) {
113
piece->moveBy(delta);
119
//-----------------------------------------------------------------------------
121
void Piece::findNeighbors(const QList<Piece*>& pieces)
123
// Find neighbor tiles
124
static QList<QPoint> deltas = QList<QPoint>() << QPoint(-1,0) << QPoint(1,0) << QPoint(0,-1) << QPoint(0,1);
126
foreach (Tile* tile, m_shadow) {
127
QPoint pos(tile->column(), tile->row());
128
foreach (const QPoint& delta, deltas) {
129
QPoint neighbor = pos + delta;
130
if (!containsTile(neighbor.x(), neighbor.y()) && !tiles.contains(neighbor)) {
131
tiles.append(neighbor);
136
// Find neighbor pieces
137
foreach (Piece* piece, pieces) {
138
foreach (const QPoint& tile, tiles) {
139
if (piece->containsTile(tile.x(), tile.y())) {
140
m_neighbors.insert(piece);
147
//-----------------------------------------------------------------------------
289
149
void Piece::pushNeighbors(const QPointF& inertia)
291
151
while (Piece* neighbor = m_board->findCollidingPiece(this)) {
292
152
// Determine which piece to move
293
153
Piece *source, *target;
294
if (m_rect.width() >= neighbor->m_rect.width() || m_rect.height() >= neighbor->m_rect.height()) {
154
if (m_tiles.count() >= neighbor->m_tiles.count()) {
296
156
target = neighbor;
298
158
source = neighbor;
301
QRect source_rect = source->marginRect();
161
QRect source_rect = m_board->marginRect(source->boundingRect());
303
163
// Calculate valid movement vector for target; preserve some motion from last move
304
164
QPointF vector = target->boundingRect().center() - source_rect.center() + inertia;
305
while (fabs(vector.x()) + fabs(vector.y()) < 1)
306
vector = QPointF(rand() - (RAND_MAX/2), rand() - (RAND_MAX/2));
165
while (vector.manhattanLength() < 1.0) {
166
vector.setX( (rand() % Tile::size) - (Tile::size / 2) );
167
vector.setY( (rand() % Tile::size) - (Tile::size / 2) );
308
170
// Scale movement vector so that the largest dimension is 1
309
171
QPointF direction = vector / qMax(fabs(vector.x()), fabs(vector.y()));
311
173
// Push target until it is clear from current source
312
174
// We use a binary-search, pushing away if collision, retracting otherwise
313
QPoint orig = target->position();
175
QPoint orig = target->m_pos;
314
176
QRect united = source_rect.united(target->boundingRect());
315
177
float min = 0.0f;
316
178
float max = united.width() * united.height();
318
180
float test = (min + max) / 2.0f;
319
float x = orig.x() + roundUp(test * direction.x());
320
float y = orig.y() + roundUp(test * direction.y());
321
target->moveTo(x, y);
322
if (source->collidesWith(target))
181
target->m_pos = orig + (test * direction).toPoint();
182
if (source->collidesWith(target)) {
326
if (max - min < 1.0f)
186
if (max - min < 1.0f) {
328
189
Q_ASSERT(max - min > 0.01f);
192
target->updateVerts();
331
193
Q_ASSERT(min < max);
332
194
Q_ASSERT(!source->collidesWith(target));
339
/*****************************************************************************/
201
//-----------------------------------------------------------------------------
203
void Piece::rotate(int rotations)
206
for (int i = 0; i < rotations; ++i) {
214
//-----------------------------------------------------------------------------
216
void Piece::rotate(const QPoint& origin)
218
// Rotate 90 degrees counter-clockwise around origin
219
if (!origin.isNull()) {
221
pos.ry() += m_rect.height();
222
m_pos.setX( origin.y() + origin.x() - pos.y() );
223
m_pos.setY( origin.y() - origin.x() + pos.x() );
225
m_rect.setRect(0, 0, m_rect.height(), m_rect.width());
227
// Rotate tiles 90 degrees counter-clockwise
228
int count = m_tiles.count();
229
for (int i = 0; i < count; ++i) {
230
m_tiles.at(i)->rotate();
233
// Track how many rotations have occured
235
if (m_rotation > 3) {
242
//-----------------------------------------------------------------------------
244
void Piece::setDepth(int depth)
246
m_depth = (depth + 1) * 2;
250
//-----------------------------------------------------------------------------
252
void Piece::setSelected(bool selected)
254
m_selected = selected;
255
if (!m_selected && m_changed) {
256
updateCollisionRegions();
260
//-----------------------------------------------------------------------------
341
262
void Piece::save(QXmlStreamWriter& xml) const
343
xml.writeStartElement("group");
264
xml.writeStartElement("piece");
265
xml.writeAttribute("x", QString::number(m_pos.x()));
266
xml.writeAttribute("y", QString::number(m_pos.y()));
344
267
xml.writeAttribute("rotation", QString::number(m_rotation));
346
m_children.at(0)->save(xml, true);
347
for (int i = 1; i < m_children.count(); ++i)
348
m_children.at(i)->save(xml);
269
for (int i = 0; i < m_tiles.count(); ++i) {
270
m_tiles.at(i)->save(xml);
350
273
xml.writeEndElement();
353
/*****************************************************************************/
276
//-----------------------------------------------------------------------------
278
void Piece::attach(Piece* piece)
280
Q_ASSERT(piece != this);
283
m_pos.setX(qMin(m_pos.x(), piece->m_pos.x()));
284
m_pos.setY(qMin(m_pos.y(), piece->m_pos.y()));
286
// Update position of attached tiles
287
int rotation = m_rotation;
288
for (int i = rotation; i < 4; ++i) {
292
m_tiles += piece->m_tiles;
293
piece->m_tiles.clear();
298
m_shadow += piece->m_shadow;
302
m_neighbors += piece->m_neighbors;
303
m_neighbors.remove(piece);
304
m_neighbors.remove(this);
305
foreach (Piece* neighbor, m_neighbors) {
306
neighbor->m_neighbors.remove(piece);
307
neighbor->m_neighbors.insert(this);
310
// Remove attached piece
311
m_board->removePiece(piece);
314
//-----------------------------------------------------------------------------
316
bool Piece::containsTile(int column, int row)
319
for (int i = 0; i < m_tiles.count(); ++i) {
320
const Tile* tile = m_tiles.at(i);
321
if (tile->column() == column && tile->row() == row) {
329
//-----------------------------------------------------------------------------
331
void Piece::updateCollisionRegions()
334
m_collision_region = QRegion();
335
m_collision_region_expanded = QRegion();
336
QRect rect(0,0, Tile::size, Tile::size);
337
for (int i = 0; i < m_tiles.count(); ++i) {
338
rect.moveTo(m_tiles.at(i)->scenePos());
339
m_collision_region += rect;
340
m_collision_region_expanded += m_board->marginRect(rect);
344
//-----------------------------------------------------------------------------
346
void Piece::updateShadow()
348
QMutableListIterator<Tile*> i(m_shadow);
349
while (i.hasNext()) {
350
Tile* tile = i.next();
351
if ( containsTile(tile->column() - 1, tile->row())
352
&& containsTile(tile->column() + 1, tile->row())
353
&& containsTile(tile->column(), tile->row() - 1)
354
&& containsTile(tile->column(), tile->row() + 1) ) {
360
//-----------------------------------------------------------------------------
362
void Piece::updateTiles()
364
int count = m_tiles.count();
366
// Find bounding rectangle
367
QPoint top_left = m_tiles.first()->gridPos();
368
QPoint bottom_right = top_left + QPoint(Tile::size, Tile::size);
370
for (int i = 0; i < count; ++i) {
371
pos = m_tiles.at(i)->gridPos();
372
top_left.setX( qMin(pos.x(), top_left.x()) );
373
top_left.setY( qMin(pos.y(), top_left.y()) );
374
bottom_right.setX( qMax(pos.x() + Tile::size, bottom_right.x()) );
375
bottom_right.setY( qMax(pos.y() + Tile::size, bottom_right.y()) );
377
m_rect.setRect(0, 0, bottom_right.x() - top_left.x(), bottom_right.y() - top_left.y());
379
// Shift tiles to be inside rectangle
381
for (int i = 0; i < count; ++i) {
382
tile = m_tiles.at(i);
383
tile->setParent(this);
384
tile->setPos(tile->gridPos() - top_left);
388
//-----------------------------------------------------------------------------
390
void Piece::updateVerts()
394
updateCollisionRegions();
397
QVector<Vertex> verts;
401
verts.reserve(m_tiles.count() * 4);
402
for (int i = 0; i < m_tiles.count(); ++i) {
403
Tile* tile = m_tiles.at(i);
405
QPoint pos = tile->scenePos();
408
int x2 = x1 + Tile::size;
409
int y2 = y1 + Tile::size;
411
const QPointF* corners = m_board->corners(rotation());
412
float tx = tile->column() * m_board->tileTextureSize();
413
float ty = tile->row() * m_board->tileTextureSize();
415
float bx1 = tile->bevel().x();
416
float by1 = tile->bevel().y();
417
float bx2 = bx1 + 0.125;
418
float by2 = by1 + 0.125;
420
verts.append( Vertex(x1,y1,z, tx + corners[0].x(),ty + corners[0].y(), bx1,by1) );
421
verts.append( Vertex(x1,y2,z, tx + corners[1].x(),ty + corners[1].y(), bx1,by2) );
422
verts.append( Vertex(x2,y2,z, tx + corners[2].x(),ty + corners[2].y(), bx2,by2) );
423
verts.append( Vertex(x2,y1,z, tx + corners[3].x(),ty + corners[3].y(), bx2,by1) );
425
graphics_layer->updateArray(m_tile_array, verts);
427
// Update shadow verts
429
static const int offset = Tile::size / 2;
430
static const int size = Tile::size * 2;
432
verts.reserve(m_shadow.count() * 4);
433
for (int i = 0; i < m_shadow.count(); ++i) {
434
QPoint pos = m_shadow.at(i)->scenePos();
435
int x1 = pos.x() - offset;
436
int y1 = pos.y() - offset;
440
verts.append( Vertex(x1,y1,z, 0,0) );
441
verts.append( Vertex(x1,y2,z, 0,1) );
442
verts.append( Vertex(x2,y2,z, 1,1) );
443
verts.append( Vertex(x2,y1,z, 1,0) );
445
graphics_layer->updateArray(m_shadow_array, verts);
447
// Update scene rectangle
448
m_board->updateSceneRectangle(this);
451
//-----------------------------------------------------------------------------