Skip to content

Commit dd68696

Browse files
committed
refactor(MapLoader): refactored maploader so it's easier to read
- also added a very detailed step by step explanation of how the webgraph generator works to the documentation with images of each step
1 parent ddb21ab commit dd68696

File tree

4 files changed

+199
-37
lines changed

4 files changed

+199
-37
lines changed

.github/workflows/release-branch.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,16 @@ jobs:
3535
run: |
3636
git add .
3737
git commit -m "Sync release with main and strip comments" || echo "No changes to commit"
38-
git push origin release --force
38+
git push origin release --force
39+
40+
- name: Check release branch size
41+
run: |
42+
echo "Release branch size:"
43+
du -sh .
44+
45+
- name: Check main branch size
46+
run: |
47+
git fetch origin main
48+
git checkout -b temp-main origin/main
49+
echo "Main branch size:"
50+
du -sh .

osrs/position/map/maploader.simba

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,71 @@ begin
381381
end;
382382

383383

384+
(*
385+
## MapLoader.CacheGraph
386+
```pascal
387+
procedure TRSMapLoader.CacheGraph(constref graph: TWebGraph);
388+
```
389+
Caches a {ref}`TWebGraph` for future uses so we don't need to regenerate it.
390+
391+
```{note}
392+
This is an internal method. Don't use it if you don't know what you are doing.
393+
```
394+
*)
395+
procedure TRSMapLoader.CacheGraph(constref graph: TWebGraph; path: String);
396+
begin
397+
if graph.Nodes.IsEmpty then
398+
raise GetDebugLn('MapLoader', 'Graph has no nodes for some reason.');
399+
if graph.Paths.IsEmpty then
400+
raise GetDebugLn('MapLoader', 'Graph has no paths for some reason.');
401+
402+
if not FileWriteBytes(path + 'nodes.txt', CompressBytes(ECompressAlgo.ZLIB, graph.Nodes.ToBytes())) then
403+
raise GetDebugLn('MapLoader', 'Failed to create cache for graph nodes: ' + path);
404+
405+
if not FileWriteBytes(path + 'paths.txt', CompressBytes(ECompressAlgo.ZLIB, graph.Paths.ToBytes())) then
406+
raise GetDebugLn('MapLoader', 'Failed to create cache for graph paths: ' + path);
407+
408+
if not graph.WalkableSpace.IsEmpty then
409+
if not FileWriteBytes(path + 'walkablespace.txt', CompressBytes(ECompressAlgo.ZLIB, graph.WalkableSpace.ToBytes())) then
410+
raise GetDebugLn('MapLoader', 'Failed to create cache for graph WalkableSpace: ' + path);
411+
412+
if not graph.WalkableClusters.IsEmpty then
413+
if not FileWriteBytes(path + 'walkableclusters.txt', CompressBytes(ECompressAlgo.ZLIB, graph.WalkableClusters.ToBytes())) then
414+
raise GetDebugLn('MapLoader', 'Failed to create cache for graph WalkableClusters: ' + path);
415+
416+
if not graph.ObjectClusters.IsEmpty then
417+
if not FileWriteBytes(path + 'objectclusters.txt', CompressBytes(ECompressAlgo.ZLIB, graph.ObjectClusters.ToBytes())) then
418+
raise GetDebugLn('MapLoader', 'Failed to create cache for graph ObjectClusters: ' + path);
419+
end;
420+
421+
(*
422+
## MapLoader.GetGraph
423+
```pascal
424+
function TRSMapLoader.GraphFromCache(path: String): TWebGraph;
425+
```
426+
Loads a previously cached {ref}`TWebGraph`.
427+
428+
```{note}
429+
This is an internal method. Don't use it if you don't know what you are doing.
430+
```
431+
*)
432+
function TRSMapLoader.GraphFromCache(path: String): TWebGraph;
433+
begin
434+
if FileExists(path + 'nodes.txt') then
435+
Result.Nodes := TGraphNodeArray.CreateFromBytes(DecompressBytes(ECompressAlgo.ZLIB, FileReadBytes(path + 'nodes.txt')));
436+
if FileExists(path + 'paths.txt') then
437+
Result.Paths := T2DIntegerArray.CreateFromBytes(DecompressBytes(ECompressAlgo.ZLIB, FileReadBytes(path + 'paths.txt')));
438+
439+
if FileExists(path + 'walkablespace.txt') then
440+
Result.WalkableSpace := TPointArray.CreateFromBytes(DecompressBytes(ECompressAlgo.ZLIB, FileReadBytes(path + 'walkablespace.txt')));
441+
if FileExists(path + 'walkableclusters.txt') then
442+
Result.WalkableClusters := T2DPointArray.CreateFromBytes(DecompressBytes(ECompressAlgo.ZLIB, FileReadBytes(path + 'walkableclusters.txt')));
443+
if FileExists(path + 'objectclusters.txt') then
444+
Result.ObjectClusters := T2DPointArray.CreateFromBytes(DecompressBytes(ECompressAlgo.ZLIB, FileReadBytes(path + 'objectclusters.txt')));
445+
446+
SetLength(Result.Paths, Length(Result.Nodes));
447+
end;
448+
384449
(*
385450
## MapLoader.GetGraph
386451
```pascal
@@ -415,40 +480,11 @@ begin
415480
raise GetDebugLn('MapLoader', 'Failed to create cache directory: ' + path);
416481

417482
Result := WebGraphGenerator.BuildGraph(name, map);
418-
419-
if (Result.Nodes <> []) then
420-
if not FileWriteBytes(path + 'nodes.txt', CompressBytes(ECompressAlgo.ZLIB, Result.Nodes.ToBytes())) then
421-
raise GetDebugLn('MapLoader', 'Failed to create cache for graph nodes: ' + path);
422-
423-
if (Result.Paths <> []) then
424-
if not FileWriteBytes(path + 'paths.txt', CompressBytes(ECompressAlgo.ZLIB, Result.Paths.ToBytes())) then
425-
raise GetDebugLn('MapLoader', 'Failed to create cache for graph paths: ' + path);
426-
427-
if (Result.WalkableSpace <> []) then
428-
if not FileWriteBytes(path + 'walkablespace.txt', CompressBytes(ECompressAlgo.ZLIB, Result.WalkableSpace.ToBytes())) then
429-
raise GetDebugLn('MapLoader', 'Failed to create cache for graph WalkableSpace: ' + path);
430-
if (Result.WalkableClusters <> []) then
431-
if not FileWriteBytes(path + 'walkableclusters.txt', CompressBytes(ECompressAlgo.ZLIB, Result.WalkableClusters.ToBytes())) then
432-
raise GetDebugLn('MapLoader', 'Failed to create cache for graph WalkableClusters: ' + path);
433-
if (Result.ObjectClusters <> []) then
434-
if not FileWriteBytes(path + 'objectclusters.txt', CompressBytes(ECompressAlgo.ZLIB, Result.ObjectClusters.ToBytes())) then
435-
raise GetDebugLn('MapLoader', 'Failed to create cache for graph ObjectClusters: ' + path);
483+
Self.CacheGraph(Result, path);
436484
Exit;
437485
end;
438486

439-
if FileExists(path + 'nodes.txt') then
440-
Result.Nodes := TGraphNodeArray.CreateFromBytes(DecompressBytes(ECompressAlgo.ZLIB, FileReadBytes(path + 'nodes.txt')));
441-
if FileExists(path + 'paths.txt') then
442-
Result.Paths := T2DIntegerArray.CreateFromBytes(DecompressBytes(ECompressAlgo.ZLIB, FileReadBytes(path + 'paths.txt')));
443-
444-
if FileExists(path + 'walkablespace.txt') then
445-
Result.WalkableSpace := TPointArray.CreateFromBytes(DecompressBytes(ECompressAlgo.ZLIB, FileReadBytes(path + 'walkablespace.txt')));
446-
if FileExists(path + 'walkableclusters.txt') then
447-
Result.WalkableClusters := T2DPointArray.CreateFromBytes(DecompressBytes(ECompressAlgo.ZLIB, FileReadBytes(path + 'walkableclusters.txt')));
448-
if FileExists(path + 'objectclusters.txt') then
449-
Result.ObjectClusters := T2DPointArray.CreateFromBytes(DecompressBytes(ECompressAlgo.ZLIB, FileReadBytes(path + 'objectclusters.txt')));
450-
451-
SetLength(Result.Paths, Length(Result.Nodes));
487+
Result := Self.GraphFromCache(path);
452488
end;
453489

454490

utils/webgraphgen.simba

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ The collision map can only have 4 colors:
408408
This is an internal method. Don't use it if you don't know what you are doing.
409409
```
410410

411-
It's hard to go into detail on how this works but if you want to see the result
411+
How it works in detail is quite technical but if you want to see the result
412412
run this example code:
413413
```pascal
414414
{$I WaspLib/osrs.simba}
@@ -440,10 +440,11 @@ var
440440
doorMapA, doorMapB: TIntegerMatrix;
441441
connectA, connectB: TIntegerArray;
442442
minRadius: Boolean;
443+
timer: TCountDown;
443444
begin
444445
minLen := Self.MinimumTiles * RSTranslator.TileArea;
445446

446-
Self.WhiteClusters := white.Cluster(1);
447+
Self.WhiteClusters := white.Cluster(1).SortBySize(True);
447448
Self.Doors := Self.FindDoors(red.Cluster(1.5), map);
448449

449450
SetLength(connectA, Length(Self.Doors));
@@ -462,18 +463,24 @@ begin
462463
begin
463464
if Length(Self.WhiteClusters[i]) < minLen then
464465
Continue;
465-
466466
for pt in Self.WhiteClusters[i] do
467467
Self.Matrix[pt.Y, pt.X] := i;
468468
end;
469469

470470
for i := 0 to High(Self.Doors) do
471471
Result.Nodes += Self.ProcessDoors(Self.Doors[i], i, n, doorMapA, doorMapB);
472472

473+
timer.Start(4 * ONE_SECOND);
473474
SetLength(Result.Paths, Length(Result.Nodes));
474475

475476
for i := 0 to High(Self.WhiteClusters) do
476477
begin
478+
if timer.IsFinished or (i = 0) or (i = High(Self.WhiteClusters)) then
479+
begin
480+
WriteLn GetDebugLn('Generating webgraph part ' + ToStr(i) + ' of ' + ToStr(High(Self.WhiteClusters)));
481+
timer.Restart();
482+
end;
483+
477484
if not minRadius then
478485
begin
479486
Self.ProcessSmallCluster(Result, minRadius, doorMapA[i], doorMapB[i], i, minLen, connectA, connectB);
@@ -511,7 +518,7 @@ var
511518
i, j: Integer;
512519
t: UInt64;
513520
begin
514-
WriteLn GetDebugLn('Generating webgraph for region: ' + name + ' this can take a few seconds.');
521+
WriteLn GetDebugLn('Generating webgraph: ' + name + ' this can take a few seconds.');
515522
t := GetTimeRunning();
516523

517524
white := map.FindColor($FFFFFF, 0);
@@ -545,6 +552,113 @@ begin
545552
WriteLn GetDebugLn('WebGraphGenerator', 'Generating webgraph took ' + ToStr(Round(((GetTimeRunning()-t)/1000), 2)) + ' seconds.', ELogLevel.SUCCESS);
546553
end;
547554

555+
(*
556+
## Webgraph Generation
557+
If you do wish to understand the technical details a little bit more, the
558+
following is a simplified explanation with images of more or less how it works.
559+
560+
First of all, you need to have a collision map. WaspLib's webgraph generator
561+
assumes the walking space is white and that the doors are red, other than that,
562+
it doesn't really matter what colors your collision map, for this explanation
563+
I'm going to use a small piece of varrock with a bit of the wilderness:
564+
```{figure} ../../images/graphgen0.png
565+
```
566+
567+
We start off by extracting all the white:
568+
```{figure} ../../images/graphgen1.png
569+
```
570+
571+
Then, we cluster the white, grouping each white pixel that is within any other
572+
white pixel horizontal or vertically in the same cluster. It's important to not
573+
include diagonal pixels or your clusters will cross certain walls, at least in
574+
WaspLib's collision maps. We also sort our clusters by size as we can have some
575+
performance improvements by having the clusters sorted.
576+
577+
Doing this will look something like this:
578+
```{figure} ../../images/graphgen2.png
579+
```
580+
581+
We also need to find all the red to know where the doors are, in this image I've
582+
expanded the color a little so it's visble but you should only get the door:
583+
```{figure} ../../images/graphgen3.png
584+
```
585+
586+
While doing this, you also want to get and store for later the point in front
587+
and behind the door and map those points to each of your clusters.
588+
589+
The next step is to start processing our clusters. Because we sorted them by size
590+
we start by skipping very small ones, usually clusters that are less than the
591+
size of a tile.
592+
593+
Clusters that are less than `WebGraphGenerator.MinimumTiles` we add a single node
594+
in the middle, it's a cluster too small to be worth extra processing.
595+
Which would be the red nodes on the image below:
596+
```{figure} ../../images/graphgen4.png
597+
```
598+
599+
We also check our mapped doors to see if any of the points in front or behind
600+
each door belongs to our cluster, if it does, we connect it to our node.
601+
602+
Then we start processing bigger clusters if you have
603+
`WebGraphGenerator.Skeletonize` set to `True` which is the default, we will
604+
sekeletonize the cluster which should look something like this:
605+
```{figure} ../../images/graphgen5.png
606+
```
607+
608+
And then we partition it with `WebGraphGenerator.Spacing`:
609+
```{figure} ../../images/graphgen6.png
610+
```
611+
612+
If you have `WebGraphGenerator.Skeletonize` set to `False`, we simply parition
613+
the cluster as it is, again with `WebGraphGenerator.Spacing`.
614+
615+
It's hard to see much difference here but it's much denser than the above and
616+
later on you will see the actual difference when paths are added:
617+
```{figure} ../../images/graphgen7.png
618+
```
619+
620+
Whichever case your settings followed, those will be your nodes for that cluster.
621+
622+
Then next step is to try and connect the nodes we just created by proximity.
623+
We check `WebGraphGenerator.MaxConnections*2` closest nodes to each node and
624+
connect up to `WebGraphGenerator.MaxConnections` if:
625+
- We have a straight path between the 2 nodes
626+
- If we can have a path using AStar
627+
628+
It should look something like this:
629+
```{figure} ../../images/graphgen8.png
630+
```
631+
632+
For AStar path finding we use the original skeleton/cluster and this is an
633+
example of a connection where AStar was used:
634+
```{figure} ../../images/graphgen9.png
635+
```
636+
As you can see that crossing over the wilderness river crossing black, it's
637+
connected because AStar has found a path around it. That's also what's happening
638+
on those nodes around trees with lines crossing the trees, there's a valid path
639+
because AStar found a way around.
640+
641+
If you were to have `WebGraphGenerator.Skeletonize` set to `False`, this is what
642+
the same thing would look like:
643+
```{figure} ../../images/graphgen10.png
644+
```
645+
646+
Lastly, we our mapped doors that has their front or behind point within our
647+
cluster and connect that door to the closest
648+
`WebGraphGenerator.MaxDoorConnections` amount of nodes:
649+
```{figure} ../../images/graphgen11.png
650+
```
651+
652+
And that's about it. The final graph should look like this if you
653+
`WebGraphGenerator.Skeletonize` set to `True`, which again, is the default:
654+
```{figure} ../../images/graphgen12.png
655+
```
656+
657+
And this would be `WebGraphGenerator.Skeletonize` set to `False`:
658+
```{figure} ../../images/graphgen13.png
659+
```
660+
*)
661+
548662
var
549663
(*
550664
## WebGraphGenerator variable

wasplib-docs

0 commit comments

Comments
 (0)