holyghost has asked for the wisdom of the Perl Monks concerning the following question:

Hi fellow monks, I've tried to alleviate an octtree by using a hash of levels where each level stands for 8 smaller cubes within the first. I show you the code with keeping in mind that if I add each worldobject ($obj) to a lower level I should gain speed. This code was used for remaking Ultima8 - pagan, the dos game. The meaning is to add world objects to the level's hash number with a collision detection on the side. I do not know if I made any mistakes or if it works out of the box but if you can help out the algorithm I'd be glad. The thing is you walk around in ultima 8 on mostly non-enormously mountainous areas, even if this would be coded into an octtree after my build method you can hash out enemies and use e.g. another real octtree for the level of platforms you're at. Especially you walk around on a a level which are mostly done with calculation in a thread where you know where you are as you walk around upper levels when you are on a mostly plain area and where you walk on lower levels which are more subdivided cubes containing more world objects with each cube corner a collision with a world object and thus that world object shall be put into the hash of rootnodes at the specified level of collision. You walk around levels with only collisions on its subsides which leaves you walking on a higher dimensional cube without colliding except when you exit the cube. As withi an octtree you only have to check 8 sides of your cube when colliding as the corner may be contained by your world object. If you have a world object which is intersected by a cube you can immediately check for a collision (also you do not encounter world objects which are bigger than your cube as you walk on cube's sides. Thus a thread can check for collision only when your game timer only _can_ encounter any cubes without objects e.g. when you are walking in a cube without objects in it (the $level) you can not check collision detection. The lower the level, the higher dimensions of your cube, the less you encounter as you are generally more away from its sides (corners enveloped by world objects).
use lib "../../HollyGame"; package Ultima8::OctTree; sub OctTree { my ($class, $worldwidth, $worldheight, $worlddepth, @worldobje +cts) = @_; $self = { worldwidth => $worldwidth, worldheight => $worldheig +ht, worlddepth => $worlddepth }; $self->{level} = 0; $self->{rootnodes} = {}; bless $self, ref($class) || $class; $self->build_rec($self->{worldwidth}, $self->{worldheight}, $self->{worlddepth}, $self->{worldwidth}, $self->{worldheight}, $self->{worlddepth}, $self->{level}, @worldobjects); return $self; } sub build_rec { my ($self, $middlex, $middley, $middlez, $worldwidth, $worldhe +ight, $worlddepth, $level, @worldobjects) = @_; my $middlex /= 2; my $middley /= 2; my $middlez /= 2; my $worldwidth /= 2; my $worldheight /= 2; my $worlddepth /= 2; if ($middlex <= 1 or $middley <= 1 or $middlez <= 1) { return; } my @nextobjects = @worldobjects; foreach $obj (@worldobjects) { if (collision_object_with_divisor($obj, $middlex, $mid +dley, $middleyz)) { $self->{rootnodes}{$level} .= $obj; splice(@nextobjects, $obj, 1); } } $self->build_rec($middlex, $middley, $middlez,$worldwidth, $wo +rldheight, $worlddepth, $level++, @nextobjects); $self->build_rec($middlex + $worldwidth, $middley - $worldheig +ht, $middlez - $worlddepth,$worldwidth, $worldheight, $worlddepth, $l +evel, @nextobjects); $self->build_rec($middlex - $worldwidth, $middley + $worldheig +ht, $middlez - $worlddepth,$worldwidth, $worldheight, $worlddepth, $l +evel, @nextobjects); $self->build_rec($middlex + $worldwidth, $middley + $worldheig +ht, $middlez - $worlddepth,$worldwidth, $worldheight, $worlddepth, $l +evel, @nextobjects); $self->build_rec($middlex - $worldwidth, $middley - $worldheig +ht, $middlez + $worlddepth,$worldwidth, $worldheight, $worlddepth, $l +evel, @nextobjects); $self->build_rec($middlex + $worldwidth, $middley - $worldheig +ht, $middlez + $worlddepth,$worldwidth, $worldheight, $worlddepth, $l +evel, @nextobjects); $self->build_rec($middlex - $worldwidth, $middley + $worldheig +ht, $middlez + $worlddepth,$worldwidth, $worldheight, $worlddepth, $l +evel, @nextobjects); $self->build_rec($middlex + $worldwidth, $middley + $worldheig +ht, $middlez + $worlddepth,$worldwidth, $worldheight, $worlddepth, $l +evel, @nextobjects); } sub collision_object_with_divisor { my ($self, $obj, $x, $y, $z) = @_; if ($obj->{x} > $x and $obj->{x} + $obj->{w} < $x) { return 0; } if ($obj->{y} > $y and $obj->{y} + $obj->{h} < $y) { return 0; } if ($obj->{z} > $z and $obj->{z} + $obj->{d} < $z) { return 0; } return 1; } sub update { } sub draw { }

Replies are listed 'Best First'.
Re: octtree using hashes
by Corion (Patriarch) on Nov 09, 2017 at 10:28 UTC

    If you have problems with an algorithm, it usually helps to give a short description of how you think the algorithm should work, and to also show examples where your implementation of the algorithm fails to behave like you expect it to.

    Your Octree divides the world into same-sized octants, but you never seem to check the subdivision of an octant into smaller octants. This will not give you the benefit of using an octree at all.

    You have left the interesting parts of the octree implementation empty or I can't find them. You don't really show where you insert objects into the world, and you don't describe what ->update and ->draw should do. You only need to update an octree if objects actually move between octants.

    Your collision checker only checks the top level of the octree for collisions, but if you find a potential collision of the object with an octant, you need to look into the objects within that octant for further collisions.

    In your situation, I would read a book that discusses the algorithms for spatial trees, and inserting elements and updating the octree, and then reimplement that in Perl. Maybe it is easier and more instructive to start with a "quadtree" that only subdivides the plane instead of 3D space.

    For your code, what is this line supposed to do:

    $self->{rootnodes}{$level} .= $obj;
      This is a $obj from @worldobjects :
      use lib "../../HollyGame"; package Ultima8::WorldObject; sub WorldObject { my ($class, $x, $y, $z, $w, $h, $d) = @_; $self = { x => $x, y => $y, z => $z, w => $w, h => $h, d => $d + }; return bless $self, ref($class) || $class; }
      This is a instantiation of the hash octtree, which has an object in it which is collisioned against at 4000 / (2 * 20):
      $self->{octtree} = Ultima8::OctTree->OctTree(4000,4000,200,(WorldObjec +t->WorldObject(100,100,100,100,100,100)));
      The line :
      $self->{rootnodes}{$level} .= $obj;
      concatenates an world object to the member rootnodes with a key which is a level in subdivision of the space (in 2) You check for 8 collisions which are the intersection of 2 squares, one above the other in an cube subdivision. The methods draw and update I haven't done yet.

        Why do you "concatenate" objects?

        I would assume that you keep a list of nodes, which is not done with the string concatenation operator ".", but by using push on an array(ref).

        Also, the idea behind an octree is to have a hierarchical organisation (a tree), so that you don't have to check all octants but ideally only the top-level octant and not even all octants that actually contain the object(s) you want to check for collision.

Re: octtree using hashes
by AnomalousMonk (Archbishop) on Nov 09, 2017 at 13:54 UTC
    sub build_rec { my ($self, $middlex, ...) = @_; my $middlex /= 2; ...; if ($middlex <= 1 or ...) { return; } ...; }

    Do yourself a big favor and use warnings and strict. Even if these modules do not explicitly identify all of them, they will at least alert you to many questionable practices in your code:

    c:\@Work\Perl\monks>perl -MData::Dump -le "use warnings; use strict; ;; my $br = build_rec(100); dd 'build_rec returned', $br; ;; sub build_rec { my ($middlex) = @_; ;; my $middlex /= 2; if ($middlex <= 1) { return; } ;; print qq{did not return. will do something useful.}; return $middlex * 1_000; } " "my" variable $middlex masks earlier declaration in same scope at -e l +ine 1. Use of uninitialized value in division (/) at -e line 1. ("build_rec returned", undef)
    See also diagnostics for (much) more verbose messages.


    Give a man a fish:  <%-{-{-{-<

Re: octtree using hashes
by LanX (Saint) on Nov 09, 2017 at 15:20 UTC