Skip to content

Commit b004611

Browse files
committed
Spatial Partition Markdown file
This commit includes minor changes to the Spatial Partition Markdown file.
1 parent b9d575f commit b004611

File tree

1 file changed

+31
-33
lines changed

1 file changed

+31
-33
lines changed

book/spatial-partition.markdown

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ can start to be a performance bottleneck.
2828

2929
Say we're making a real-time strategy game. Opposing armies with hundreds of
3030
units will clash together on the field of battle. Warriors need to know which
31-
nearby enemy to swing their blade at. The naïve way to handle this is by looking
31+
nearby enemy to swing their blades at. The naïve way to handle this is by looking
3232
at every pair of units and seeing how close they are to each other:
3333

3434
^code pairwise
3535

36-
Here we have a doubly-nested loop where each loop is walking <span
36+
Here we have a doubly nested loop where each loop is walking <span
3737
name="all">all</span> of the units on the battlefield. That means the number of
3838
pairwise tests we have to perform each frame increases with the *square* of the
3939
number of units. Each additional unit we add has to be compared to *all* of the
@@ -54,22 +54,22 @@ In Big-O terms, though, this is still *O(n&sup2;)*.
5454

5555
The problem we're running into is that there's no underlying order to the array
5656
of units. To find a unit near some location, we have to walk the entire array.
57-
Now imagine we simplify our game a bit. Instead of a 2D battle*field*, imagine
57+
Now, imagine we simplify our game a bit. Instead of a 2D battle*field*, imagine
5858
it's a 1D battle*line*.
5959

6060
<img src="images/spatial-partition-battle-line.png" alt="A number line with Units positioned at different coordinates on it." />
6161

6262
In that case, we could make things easier on ourselves by *sorting* the array of
63-
units by their position on the battleline. Once we do that, we can use something
63+
units by their positions on the battleline. Once we do that, we can use something
6464
<span name="array">like</span> a [binary
6565
search](http://en.wikipedia.org/wiki/Binary_search) to find nearby units without
6666
having to scan the entire array.
6767

6868
<aside name="array">
6969

7070
A binary search has *O(log n)* complexity, which means find all battling units
71-
goes from *O(n&sup2;)* to *O(n log n)*. Something like a [*pigeonhole
72-
sort*](http://en.wikipedia.org/wiki/Pigeonhole_sort) could get that down to
71+
goes from *O(n&sup2;)* to *O(n log n)*. Something like a [pigeonhole
72+
sort](http://en.wikipedia.org/wiki/Pigeonhole_sort) could get that down to
7373
*O(n)*.
7474

7575
</aside>
@@ -84,7 +84,7 @@ For a set of **objects**, each has a **position in space**. Store them in a
8484
**spatial data structure** that organizes the objects by their positions. This
8585
data structure lets you **efficiently query for objects at or near a location**.
8686
When an object's position changes, **update the spatial data structure** so that
87-
it can continue to find it.
87+
it can continue to find the object.
8888

8989
## When to Use It
9090

@@ -123,11 +123,11 @@ proposition.
123123

124124
## Sample Code
125125

126-
The nature of patterns is that they *vary*: each implementation will be a bit
127-
different and spatial partitions are no exception. Unlike other patterns,
126+
The nature of patterns is that they *vary* -- each implementation will be a bit
127+
different, and spatial partitions are no exception. Unlike other patterns,
128128
though, many of these <span name="variations">variations</span> are
129129
well-documented. Academia likes publishing papers that prove performance gains.
130-
Since I just care about the concept behind the pattern, I'm going to show you
130+
Since I only care about the concept behind the pattern, I'm going to show you
131131
the simplest spatial partition: a *fixed grid*.
132132

133133
<aside name="variations">
@@ -139,7 +139,7 @@ spatial partitions used in games.
139139

140140
### A sheet of graph paper
141141

142-
Imagine the entire field of battle. Now superimpose a grid of fixed-size squares
142+
Imagine the entire field of battle. Now, superimpose a grid of fixed-size squares
143143
onto it like a sheet of graph paper. Instead of storing our units in a single
144144
array, we put them in the cells of this grid. Each cell stores the list of units
145145
whose positions are within that cell's boundary.
@@ -153,7 +153,7 @@ units.
153153

154154
### A grid of linked units
155155

156-
OK, let's get coding. First, some prep work. Here's our basic unit class:
156+
OK, let's get coding. First, some prep work. Here's our basic `Unit` class:
157157

158158
^code unit-simple
159159

@@ -171,7 +171,7 @@ we'll extend `Unit` with `next` and `prev` pointers:
171171

172172
^code unit-linked
173173

174-
This lets us organize units into a [doubly-linked
174+
This lets us organize units into a [doubly linked
175175
list](http://en.wikipedia.org/wiki/Doubly_linked_list) instead of an array.
176176

177177
<img src="images/spatial-partition-linked-list.png" alt="A Cell pointing to a a doubly linked list of Units." />
@@ -224,35 +224,34 @@ after the new unit.
224224
### A clash of swords
225225

226226
Once all of the units are nestled in their cells, we can let them start hacking
227-
at each other. With this new grid, the main method for handling combat look like
227+
at each other. With this new grid, the main method for handling combat looks like
228228
this:
229229

230230
^code grid-melee
231231

232-
It walks each cell and then calls `handleCell()` on it. As you can see, we
233-
really have partitioned the battlefield into little isolated skirmishes. Each
232+
It walks each cell and then calls `handleCell()` on it. As you can see, we have partitioned the battlefield into little isolated skirmishes. Each
234233
cell then handles its combat like so:
235234

236235
^code handle-cell
237236

238237
Aside from the pointer shenanigans to deal with walking a linked list, note that
239238
this is exactly <span name="nested">like</span> our original naïve method for
240-
handling combat: it compares each pair of units to see if they're in the same
239+
handling combat. It compares each pair of units to see if they're in the same
241240
position.
242241

243242
The only difference is that we no longer have to compare *all* of the units in
244-
the battle to each other, just the ones close enough to be in the same cell.
243+
the battle to each other -- just the ones close enough to be in the same cell.
245244
That's the heart of the optimization.
246245

247246
<aside name="nested">
248247

249248
From a simple analysis, it looks like we've actually made the performance
250-
*worse*. We've gone from a doubly-nested loop over the units to a
251-
*triply*-nested loop over the cells and then the units. The trick here is that
249+
*worse*. We've gone from a doubly nested loop over the units to a
250+
*triply* nested loop over the cells and then the units. The trick here is that
252251
the two inner loops are now over a smaller number of units, which is enough to
253252
cancel out the cost of the outer loop over the cells.
254253

255-
However, that does depend a bit on the granularity of our cells: make them too
254+
However, that does depend a bit on the granularity of our cells. Make them too
256255
small and that outer loop can start to matter.
257256

258257
</aside>
@@ -284,7 +283,7 @@ If the unit *has* left its current cell, we remove it from that cell's linked
284283
list and then add it back to the grid. Like with adding a new unit, that will
285284
insert the unit in the linked list for its new cell.
286285

287-
This is why we're using a doubly-linked list: we can very quickly add and remove
286+
This is why we're using a doubly linked list -- we can very quickly add and remove
288287
units from lists by setting a few pointers. With lots of units moving around
289288
each frame, that can be important.
290289

@@ -337,17 +336,16 @@ The cell with the unit is `U`, and the neighboring cells it looks at are `X`.
337336
</aside>
338337

339338
We only look at *half* of the neighbors for the same reason that the inner loop
340-
starts *after* the current unit: to avoid comparing each pair of units twice.
339+
starts *after* the current unit -- to avoid comparing each pair of units twice.
341340
Consider what would happen if we did check all eight neighboring cells.
342341

343342
Let's say we have two units in adjacent cells close enough to hit each other,
344-
like the previous example. If, for each unit, we looked at all eight cells
345-
surrounding it, here's what would happen:
343+
like the previous example. Here's what would happen if we looked at all eight cells surrounding each unit:
346344

347-
1. When we are finding hits for A, we would look at its neighbor on the right
345+
1. When finding hits for A, we would look at its neighbor on the right
348346
and find B. So we'd register an attack between A and B.
349347

350-
2. Then, when we are finding hits for B, we would look at its neighbor on the
348+
2. Then, when finding hits for B, we would look at its neighbor on the
351349
*left* and find A. So we'd register a *second* attack between A and B.
352350

353351
Only looking at half of the neighboring cells fixes that. *Which* half we look
@@ -390,7 +388,7 @@ programmer.
390388
<aside name="simpler">
391389

392390
This is a design point I mention in almost every chapter, and for good
393-
reason: whenever you can, take the simpler option. Much of software
391+
reason. Whenever you can, take the simpler option. Much of software
394392
engineering is fighting against complexity.
395393

396394
</aside>
@@ -425,8 +423,8 @@ programmer.
425423

426424
### Does the partitioning depend on the set of objects?
427425

428-
In our sample code, the grid spacing was fixed beforehand and we slotted units
429-
into cells. Other partitioning schemes are adaptable: they pick partition
426+
In our sample code, the grid spacing was fixed beforehand, and we slotted units
427+
into cells. Other partitioning schemes are adaptable -- they pick partition
430428
boundaries based on the actual set of objects and where they are in the world.
431429

432430
The goal is have a *balanced* partitioning where each region has roughly the
@@ -457,7 +455,7 @@ to solve.
457455

458456
</aside>
459457

460-
* *The partitions can be imbalanced.* Of course the downside of this
458+
* *The partitions can be imbalanced.* Of course, the downside of this
461459
rigidity is that you have less control over your partitions being evenly
462460
distributed. If objects clump together, you get worse performance there
463461
while wasting memory in the empty areas.
@@ -529,14 +527,14 @@ to solve.
529527

530528
* *The partitions are balanced.* Since any given square will have less
531529
than some fixed maximum number of objects, even when objects are
532-
clustered together you don't have single partitions with a huge pile of
530+
clustered together, you don't have single partitions with a huge pile of
533531
objects in them.
534532

535533
### Are objects only stored in the partition?
536534

537535
You can treat your spatial partition as *the* place where the objects in your
538536
game live, or you can consider it just a secondary cache to make look-up faster
539-
while also having another collection that holds the list of objects directly.
537+
while also having another collection that directly holds the list of objects.
540538

541539
* **If it is the only place objects are stored:**
542540

0 commit comments

Comments
 (0)