Cuberite
A lightweight, fast and extensible game server for Minecraft
PieceGeneratorBFSTree.cpp
Go to the documentation of this file.
1 
2 // PieceGeneratorBFSTree.cpp
3 
4 // Implements the cPieceGeneratorBFSTree class for generating structures composed of individual "pieces" in a simple tree
5 /*
6 The generator keeps a pool of currently-open connectors, chooses one at random and tries to place a piece on it,
7 thus possibly extending the pool of open connectors with the new piece's ones (like breadth-first search).
8 */
9 
10 #include "Globals.h"
11 #include "PieceGeneratorBFSTree.h"
12 #include "VerticalStrategy.h"
13 #include "VerticalLimit.h"
14 
15 
16 
17 
18 
20 // cPieceGeneratorBFSTree:
21 
23  m_PiecePool(a_PiecePool),
24  m_Noise(a_Seed),
25  m_Seed(a_Seed)
26 {
27 }
28 
29 
30 
31 
32 
33 cPlacedPiecePtr cPieceGeneratorBFSTree::PlaceStartingPiece(int a_BlockX, int a_BlockZ, cFreeConnectors & a_OutConnectors)
34 {
36  int rnd = m_Noise.IntNoise2DInt(a_BlockX, a_BlockZ) / 7;
37 
38  // Choose a random one of the starting pieces:
39  cPieces StartingPieces = m_PiecePool.GetStartingPieces();
40  int Total = 0;
41  for (cPieces::const_iterator itr = StartingPieces.begin(), end = StartingPieces.end(); itr != end; ++itr)
42  {
43  Total += m_PiecePool.GetStartingPieceWeight(**itr);
44  }
45  cPiece * StartingPiece;
46  if (Total > 0)
47  {
48  int Chosen = rnd % Total;
49  StartingPiece = StartingPieces.front();
50  for (cPieces::const_iterator itr = StartingPieces.begin(), end = StartingPieces.end(); itr != end; ++itr)
51  {
52  Chosen -= m_PiecePool.GetStartingPieceWeight(**itr);
53  if (Chosen <= 0)
54  {
55  StartingPiece = *itr;
56  break;
57  }
58  }
59  }
60  else
61  {
62  // All pieces returned zero weight, but we need one to start. Choose with equal chance:
63  StartingPiece = StartingPieces[static_cast<size_t>(rnd) % StartingPieces.size()];
64  }
65  rnd = rnd >> 16;
66 
67  // Choose a random supported rotation:
68  int Rotations[4] = {0};
69  int NumRotations = 1;
70  for (size_t i = 1; i < ARRAYCOUNT(Rotations); i++)
71  {
72  if (StartingPiece->CanRotateCCW(static_cast<int>(i)))
73  {
74  Rotations[NumRotations] = static_cast<int>(i);
75  NumRotations += 1;
76  }
77  }
78  int Rotation = Rotations[rnd % NumRotations];
79  int BlockY = StartingPiece->GetStartingPieceHeight(a_BlockX, a_BlockZ);
80  ASSERT(BlockY >= 0); // The vertical strategy should have been provided and should give valid coords
81 
82  cPlacedPiece * res = new cPlacedPiece(nullptr, *StartingPiece, Vector3i(a_BlockX, BlockY, a_BlockZ), Rotation);
83 
84  // Place the piece's connectors into a_OutConnectors:
85  const cPiece::cConnectors & Conn = StartingPiece->GetConnectors();
86  for (cPiece::cConnectors::const_iterator itr = Conn.begin(), end = Conn.end(); itr != end; ++itr)
87  {
88  a_OutConnectors.push_back(
89  cFreeConnector(res, StartingPiece->RotateMoveConnector(*itr, Rotation, a_BlockX, BlockY, a_BlockZ))
90  );
91  }
92 
93  return cPlacedPiecePtr(res);
94 }
95 
96 
97 
98 
99 
101  const cPlacedPiece & a_ParentPiece,
102  const cPiece::cConnector & a_Connector,
103  cPlacedPieces & a_OutPieces,
105 )
106 {
107  // Get a list of available connections:
108  cConnections Connections;
109  int WantedConnectorType = -a_Connector.m_Type;
110  cPieces AvailablePieces = m_PiecePool.GetPiecesWithConnector(WantedConnectorType);
111  Connections.reserve(AvailablePieces.size());
112  Vector3i ConnPos = cPiece::cConnector::AddDirection(a_Connector.m_Pos, a_Connector.m_Direction); // The position at which the new connector should be placed - 1 block away from the current connector
113  int WeightTotal = 0;
114  for (cPieces::iterator itrP = AvailablePieces.begin(), endP = AvailablePieces.end(); itrP != endP; ++itrP)
115  {
116  // Get the relative chance of this piece being generated in this path:
117  int Weight = m_PiecePool.GetPieceWeight(a_ParentPiece, a_Connector, **itrP);
118  if (Weight <= 0)
119  {
120  continue;
121  }
122 
123  // Try fitting each of the piece's connector:
124  cPiece::cConnectors Connectors = (*itrP)->GetConnectors();
125  auto verticalLimit = (*itrP)->GetVerticalLimit();
126  for (cPiece::cConnectors::iterator itrC = Connectors.begin(), endC = Connectors.end(); itrC != endC; ++itrC)
127  {
128  if (itrC->m_Type != WantedConnectorType)
129  {
130  continue;
131  }
132  // This is a same-type connector, find out how to rotate to it:
133  int NumCCWRotations = cPiece::cConnector::GetNumCCWRotationsToFit(a_Connector.m_Direction, itrC->m_Direction);
134  if ((NumCCWRotations < 0) || !(*itrP)->CanRotateCCW(NumCCWRotations))
135  {
136  // Doesn't support this rotation
137  continue;
138  }
139 
140  // Check if the piece's VerticalLimit allows this connection:
141  if ((verticalLimit != nullptr) && (!verticalLimit->CanBeAtHeight(ConnPos.x, ConnPos.z, ConnPos.y - itrC->m_Pos.y)))
142  {
143  continue;
144  }
145 
146  if (!CheckConnection(a_Connector, ConnPos, **itrP, *itrC, NumCCWRotations, a_OutPieces))
147  {
148  // Doesn't fit in this rotation
149  continue;
150  }
151  // Fits, add it to list of possibile connections:
152  Connections.emplace_back(**itrP, *itrC, NumCCWRotations, Weight);
153  WeightTotal += Weight;
154  } // for itrC - Connectors[]
155  } // for itrP - AvailablePieces[]
156  if (Connections.empty())
157  {
158  // No available connections, bail out
159  return false;
160  }
161  ASSERT(WeightTotal > 0);
162 
163  // Choose a random connection from the list, based on the weights:
164  int rnd = (m_Noise.IntNoise3DInt(a_Connector.m_Pos.x, a_Connector.m_Pos.y, a_Connector.m_Pos.z) / 7) % WeightTotal;
165  size_t ChosenIndex = 0;
166  for (cConnections::const_iterator itr = Connections.begin(), end = Connections.end(); itr != end; ++itr, ++ChosenIndex)
167  {
168  rnd -= itr->m_Weight;
169  if (rnd <= 0)
170  {
171  // This is the piece to choose
172  break;
173  }
174  }
175  cConnection & Conn = Connections[ChosenIndex];
176 
177  // Place the piece:
178  Vector3i NewPos = Conn.m_Piece->RotatePos(Conn.m_Connector.m_Pos, Conn.m_NumCCWRotations);
179  ConnPos -= NewPos;
180  auto PlacedPiece = std::make_unique<cPlacedPiece>(&a_ParentPiece, *(Conn.m_Piece), ConnPos, Conn.m_NumCCWRotations);
181 
182  // Add the new piece's connectors to the list of free connectors:
183  cPiece::cConnectors Connectors = Conn.m_Piece->GetConnectors();
184  for (cPiece::cConnectors::const_iterator itr = Connectors.begin(), end = Connectors.end(); itr != end; ++itr)
185  {
186  if (itr->m_Pos.Equals(Conn.m_Connector.m_Pos))
187  {
188  // This is the connector through which we have been connected to the parent, don't add
189  continue;
190  }
191  a_OutConnectors.emplace_back(PlacedPiece.get(), Conn.m_Piece->RotateMoveConnector(*itr, Conn.m_NumCCWRotations, ConnPos.x, ConnPos.y, ConnPos.z));
192  }
193  a_OutPieces.push_back(std::move(PlacedPiece));
194 
195  return true;
196 }
197 
198 
199 
200 
201 
203  const cPiece::cConnector & a_ExistingConnector,
204  const Vector3i & a_ToPos,
205  const cPiece & a_Piece,
206  const cPiece::cConnector & a_NewConnector,
207  int a_NumCCWRotations,
208  const cPlacedPieces & a_OutPieces
209 )
210 {
211  // For each placed piece, test the hitbox against the new piece:
212  cCuboid RotatedHitBox = a_Piece.RotateHitBoxToConnector(a_NewConnector, a_ToPos, a_NumCCWRotations);
213  RotatedHitBox.Sort();
214  for (cPlacedPieces::const_iterator itr = a_OutPieces.begin(), end = a_OutPieces.end(); itr != end; ++itr)
215  {
216  if ((*itr)->GetHitBox().DoesIntersect(RotatedHitBox))
217  {
218  return false;
219  }
220  }
221  return true;
222 }
223 
224 
225 
226 
227 
228 void cPieceGeneratorBFSTree::PlacePieces(int a_BlockX, int a_BlockZ, int a_MaxDepth, cPlacedPieces & a_OutPieces)
229 {
230  a_OutPieces.clear();
231  cFreeConnectors ConnectorPool;
232 
233  // Place the starting piece:
234  a_OutPieces.push_back(PlaceStartingPiece(a_BlockX, a_BlockZ, ConnectorPool));
235 
236  /*
237  // DEBUG:
238  FLOGD("Placed the starting piece at {0}", Vector3i{a_BlockX, a_BlockY, a_BlockZ});
239  cCuboid Hitbox = a_OutPieces[0]->GetHitBox();
240  Hitbox.Sort();
241  FLOGD(" Hitbox: {0} - {1} ({2} * {3} * {4})\n",
242  Hitbox.p1, Hitbox.p2,
243  Hitbox.DifX() + 1, Hitbox.DifY() + 1, Hitbox.DifZ() + 1
244  );
245  DebugConnectorPool(ConnectorPool, 0);
246  //*/
247 
248  // Place pieces at the available connectors:
249  /*
250  Instead of removing them one by one from the pool, we process them sequentially and take note of the last
251  processed one. To save on memory, once the number of processed connectors reaches a big number, a chunk
252  of the connectors is removed.
253  */
254  size_t NumProcessed = 0;
255  while (ConnectorPool.size() > NumProcessed)
256  {
257  cFreeConnector & Conn = ConnectorPool[NumProcessed];
258  if (Conn.m_Piece->GetDepth() < a_MaxDepth)
259  {
260  if (TryPlacePieceAtConnector(*Conn.m_Piece, Conn.m_Connector, a_OutPieces, ConnectorPool))
261  {
262  /*
263  // DEBUG:
264  const cPlacedPiece * NewPiece = a_OutPieces.back();
265  const Vector3i & Coords = NewPiece->GetCoords();
266  FLOGD("Placed a new piece at {0}, rotation {1}\n", Coords, NewPiece->GetNumCCWRotations());
267  cCuboid Hitbox = NewPiece->GetHitBox();
268  Hitbox.Sort();
269  FLOGD(" Hitbox: {0} - {1} ({2} * {3} * {4})\n",
270  Hitbox.p1, Hitbox.p2,
271  Hitbox.DifX() + 1, Hitbox.DifY() + 1, Hitbox.DifZ() + 1
272  );
273  DebugConnectorPool(ConnectorPool, NumProcessed + 1);
274  //*/
275  }
276  }
277  NumProcessed++;
278  if (NumProcessed > 1000)
279  {
280  typedef cPieceGeneratorBFSTree::cFreeConnectors::difference_type difType;
281  ConnectorPool.erase(ConnectorPool.begin(), ConnectorPool.begin() + static_cast<difType>(NumProcessed));
282  NumProcessed = 0;
283  }
284  }
285 }
286 
287 
288 
289 
290 
291 //*
292 // DEBUG:
294 {
295  fmt::print(" Connector pool: {0} items\n", a_ConnectorPool.size() - a_NumProcessed);
296  size_t idx = 0;
297 
298  typedef cPieceGeneratorBFSTree::cFreeConnectors::difference_type difType;
299 
300  for (auto itr = a_ConnectorPool.cbegin() + static_cast<difType>(a_NumProcessed), end = a_ConnectorPool.cend(); itr != end; ++itr, ++idx)
301  {
302  fmt::print(" {0}: {{{1}, {2}, {3}}}, type {4}, direction {5}, depth {6}\n",
303  idx,
304  itr->m_Connector.m_Pos.x, itr->m_Connector.m_Pos.y, itr->m_Connector.m_Pos.z,
305  itr->m_Connector.m_Type,
306  cPiece::cConnector::DirectionToString(itr->m_Connector.m_Direction),
307  itr->m_Piece->GetDepth()
308  );
309  } // for itr - a_ConnectorPool[]
310 }
311 //*/
312 
313 
314 
315 
316 
318 // cPieceGeneratorBFSTree::cConnection:
319 
320 cPieceGeneratorBFSTree::cConnection::cConnection(cPiece & a_Piece, cPiece::cConnector & a_Connector, int a_NumCCWRotations, int a_Weight) :
321  m_Piece(&a_Piece),
322  m_Connector(a_Connector),
323  m_NumCCWRotations(a_NumCCWRotations),
324  m_Weight(a_Weight)
325 {
326 }
327 
328 
329 
330 
331 
333 // cPieceGeneratorBFSTree::cFreeConnector:
334 
336  m_Piece(a_Piece),
337  m_Connector(a_Connector)
338 {
339 }
340 
341 
342 
343 
std::vector< cPiece * > cPieces
Definition: PiecePool.h:262
std::unique_ptr< cPlacedPiece > cPlacedPiecePtr
Definition: PiecePool.h:369
std::vector< cPlacedPiecePtr > cPlacedPieces
Definition: PiecePool.h:370
#define ARRAYCOUNT(X)
Evaluates to the number of elements in an array (compile-time!)
Definition: Globals.h:231
#define ASSERT(x)
Definition: Globals.h:276
Vector3< int > Vector3i
Definition: Vector3.h:487
unsigned char Rotation(const BlockState Block)
Definition: Cuboid.h:10
void Sort(void)
Definition: Cuboid.cpp:23
bool CheckConnection(const cPiece::cConnector &a_ExistingConnector, const Vector3i &a_ToPos, const cPiece &a_Piece, const cPiece::cConnector &a_NewConnector, int a_NumCCWRotations, const cPlacedPieces &a_OutPieces)
Checks if the specified piece would fit with the already-placed pieces, using the specified connector...
bool TryPlacePieceAtConnector(const cPlacedPiece &a_ParentPiece, const cPiece::cConnector &a_Connector, cPlacedPieces &a_OutPieces, cFreeConnectors &a_OutConnectors)
Tries to place a new piece at the specified (placed) connector.
cPlacedPiecePtr PlaceStartingPiece(int a_BlockX, int a_BlockZ, cFreeConnectors &a_OutConnectors)
Selects a starting piece and places it, including its height and rotation.
void DebugConnectorPool(const cFreeConnectors &a_ConnectorPool, size_t a_NumProcessed)
DEBUG: Outputs all the connectors in the pool into stdout.
std::vector< cConnection > cConnections
cPieceGeneratorBFSTree(cPiecePool &a_PiecePool, int a_Seed)
Creates a new object tied to the specified PiecePool, using the specified seed.
cPiecePool & m_PiecePool
The pool from which pieces are taken.
void PlacePieces(int a_BlockX, int a_BlockZ, int a_MaxDepth, cPlacedPieces &a_OutPieces)
Generates a placement for pieces at the specified coords.
std::vector< cFreeConnector > cFreeConnectors
cNoise m_Noise
The noise used for random number generation.
The type used for storing a connection from one piece to another, while building the piece tree.
cConnection(cPiece &a_Piece, cPiece::cConnector &a_Connector, int a_NumCCWRotations, int a_Weight)
The type used for storing a pool of connectors that will be attempted to expand by another piece.
cFreeConnector(cPlacedPiece *a_Piece, const cPiece::cConnector &a_Connector)
Represents a single piece.
Definition: PiecePool.h:21
virtual cConnectors GetConnectors(void) const =0
Returns all of the available connectors that the piece has.
cCuboid RotateHitBoxToConnector(const cConnector &a_MyConnector, const Vector3i &a_ToConnectorPos, int a_NumCCWRotations) const
Returns the hitbox after the specified number of rotations and moved so that a_MyConnector is placed ...
Definition: PiecePool.cpp:152
virtual bool CanRotateCCW(int a_NumRotations) const =0
Returns true if the piece can be rotated CCW the specific number of 90-degree turns.
int GetStartingPieceHeight(int a_BlockX, int a_BlockZ)
Returns the height, based on m_VerticalStrategy, for this piece when used as the starting piece.
Definition: PiecePool.h:204
Vector3i RotatePos(const Vector3i &a_Pos, int a_NumCCWRotations) const
Returns a copy of the a_Pos after rotating the piece the specified number of CCW rotations.
Definition: PiecePool.cpp:73
cConnector RotateMoveConnector(const cConnector &a_Connector, int a_NumCCWRotations, int a_MoveX, int a_MoveY, int a_MoveZ) const
Returns a copy of the connector that is rotated and then moved by the specified amounts.
Definition: PiecePool.cpp:107
std::vector< cConnector > cConnectors
Definition: PiecePool.h:95
static int GetNumCCWRotationsToFit(eDirection a_FixedDir, eDirection a_RotatingDir)
Returns the number of CCW rotations that a_RotatingDir requires in order to be the counter-direction ...
Definition: PiecePool.cpp:370
int m_Type
Type of the connector.
Definition: PiecePool.h:54
Vector3i m_Pos
Position relative to the piece.
Definition: PiecePool.h:50
eDirection m_Direction
Direction in which the connector is facing.
Definition: PiecePool.h:58
static const char * DirectionToString(eDirection a_Direction)
Returns the string representation of the direction.
Definition: PiecePool.cpp:234
static Vector3i AddDirection(const Vector3i &a_Pos, eDirection a_Direction)
Returns the position of the block that has the specified direction from the specified position.
Definition: PiecePool.cpp:208
This class is an interface that stores pieces for a generator.
Definition: PiecePool.h:278
virtual int GetStartingPieceWeight(const cPiece &a_NewPiece)
Returns the relative weight with which the a_NewPiece is to be selected for placing as the first piec...
Definition: PiecePool.h:308
virtual int GetPieceWeight(const cPlacedPiece &a_PlacedPiece, const cPiece::cConnector &a_ExistingConnector, const cPiece &a_NewPiece)
Returns the relative weight with which the a_NewPiece is to be selected for placing under a_PlacedPie...
Definition: PiecePool.h:295
virtual cPieces GetStartingPieces(void)=0
Returns the pieces that should be used as the starting point.
virtual cPieces GetPiecesWithConnector(int a_ConnectorType)=0
Returns a list of pieces that contain the specified connector type.
virtual void Reset(void)=0
Called when the pool has finished the current structure and should reset any piece-counters it has fo...
Represents a single piece that has been placed to specific coords in the world.
Definition: PiecePool.h:328
int GetDepth(void) const
Definition: PiecePool.h:337
int IntNoise3DInt(int a_X, int a_Y, int a_Z) const
Definition: Noise.h:254
int IntNoise2DInt(int a_X, int a_Y) const
Definition: Noise.h:243
T x
Definition: Vector3.h:17
T y
Definition: Vector3.h:17
T z
Definition: Vector3.h:17