Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cuboid struct/class for df::coord bounds #4595

Open
Bumber64 opened this issue May 16, 2024 · 4 comments · May be fixed by #4675
Open

Cuboid struct/class for df::coord bounds #4595

Bumber64 opened this issue May 16, 2024 · 4 comments · May be fixed by #4675

Comments

@Bumber64
Copy link
Contributor

Bumber64 commented May 16, 2024

Testing if a df::coord is inside a given box is an issue that comes up when dealing with map positions. There doesn't seem to be anything handy in the DFHack library, so every tool has to implement its own logic.

Not sure where the struct should be defined. MiscUtils?

I created an example while working on an improved plugins/plants.cpp:

struct cuboid
{
    int16_t x_min = -1;
    int16_t x_max = -1;
    int16_t y_min = -1;
    int16_t y_max = -1;
    int16_t z_min = -1;
    int16_t z_max = -1;

    bool isValid() const
    {   // True if all max >= min >= 0
        return (x_min >= 0 && y_min >= 0 && z_min >= 0 &&
            x_max >= x_min && y_max >= y_min && z_max >= z_min);
    }

    bool addPos(int16_t x, int16_t y, int16_t z)
    {   // Expand cuboid to include point. Return true if bounds changed
        if (x < 0 || y < 0 || z < 0 || (isValid() && containsPos(x, y, z)))
            return false;

        x_min = (x_min < 0 || x < x_min) ? x : x_min;
        x_max = (x_max < 0 || x > x_max) ? x : x_max;

        y_min = (y_min < 0 || y < y_min) ? y : y_min;
        y_max = (y_max < 0 || y > y_max) ? y : y_max;

        z_min = (z_min < 0 || z < z_min) ? z : z_min;
        z_max = (z_max < 0 || z > z_max) ? z : z_max;

        return true;
    }
    inline bool addPos(const df::coord &pos) { return addPos(pos.x, pos.y, pos.z); }

    bool containsPos(int16_t x, int16_t y, int16_t z) const
    {   // Return true if point inside cuboid. Make sure cuboid is valid first!
        return x >= x_min && x <= x_max &&
            y >= y_min && y <= y_max &&
            z >= z_min && z <= z_max;
    }
    inline bool containsPos(const df::coord &pos) const { return containsPos(pos.x, pos.y, pos.z); }
};

This could probably be refined. Maybe add a constructor, a clear() function, and get/set (so we can keep track of isValid state.) I don't know if there's a use-case for supporting negative values (e.g., relative coordinates,) but this implementation doesn't.

Some existing plugins that could make use of this include plugins/blueprint.cpp, plugins/dig-now.cpp, and plugins/regrass.cpp. Units::isUnitInBox could be made to support this. plugins/cleaners.cpp could have this as an alternative to cleaning the entire map. gui/teleport.lua could probably make use of this if Lua doesn't make that a pain.

@Bumber64
Copy link
Contributor Author

Bumber64 commented Jun 1, 2024

Is this version any good?

class cuboid
{
    int16_t x_min_ = -1;
    int16_t x_max_ = -1;
    int16_t y_min_ = -1;
    int16_t y_max_ = -1;
    int16_t z_min_ = -1;
    int16_t z_max_ = -1;
    bool valid_ = false;

public:
    cuboid() {} // Default constructor

    cuboid(int16_t x1, int16_t y1, int16_t z1, int16_t x2, int16_t y2, int16_t z2)
    {   // Construct from two corners
        x_min_ = std::min(x1, x2);
        x_max_ = std::max(x1, x2);
        y_min_ = std::min(y1, y2);
        y_max_ = std::max(y1, y2);
        z_min_ = std::min(z1, z2);
        z_max_ = std::max(z1, z2);

        valid_ = isValid();
    }
    cuboid(const df::coord &p1, const df::coord &p2)
    {   // Construct from two corners
        cuboid(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z);
    }

    cuboid(int16_t x, int16_t y, int16_t z)
    {   // Construct as single tile
        x_min_ = x_max_ = x;
        y_min_ = y_max_ = y;
        z_min_ = z_max_ = z;

        valid_ = isValid();
    }
    cuboid(const df::coord &p)
    {   // Construct as single tile
        cuboid(p.x, p.y, p.z);
    }

    int16_t x_min() const { return x_min_; }
    void set_x_min(int16_t x) { x_min_ = x; valid_ = isValid(); }

    int16_t x_max() const { return x_max_; }
    void set_x_max(int16_t x) { x_max_ = x; valid_ = isValid(); }

    int16_t y_min() const { return y_min_; }
    void set_y_min(int16_t y) { y_min_ = y; valid_ = isValid(); }

    int16_t y_max() const { return y_max_; }
    void set_y_max(int16_t y) { y_max_ = y; valid_ = isValid(); }

    int16_t z_min() const { return z_min_; }
    void set_z_min(int16_t z) { z_min_ = z; valid_ = isValid(); }

    int16_t z_max() const { return z_max_; }
    void set_z_max(int16_t z) { z_max_ = z; valid_ = isValid(); }

    bool isValid() const
    {   // True if all max >= min >= 0
        return (x_min_ >= 0 && y_min_ >= 0 && z_min_ >= 0 &&
            x_max_ >= x_min_ && y_max_ >= y_min_ && z_max_ >= z_min_);
    }

    void clear()
    {   // Clear cuboid dimensions, making it invalid.
        x_min_ = x_max_ = -1;
        y_min_ = y_max_ = -1;
        z_min_ = z_max_ = -1;
        valid_ = false;
    }

    bool addPos(int16_t x, int16_t y, int16_t z)
    {   // Expand cuboid to include point. Return true if bounds changed.
        if (x < 0 || y < 0 || z < 0 || containsPos(x, y, z))
            return false;

        x_min_ = (x_min_ < 0 || x < x_min_) ? x : x_min_;
        x_max_ = (x_max_ < 0 || x > x_max_) ? x : x_max_;

        y_min_ = (y_min_ < 0 || y < y_min_) ? y : y_min_;
        y_max_ = (y_max_ < 0 || y > y_max_) ? y : y_max_;

        z_min_ = (z_min_ < 0 || z < z_min_) ? z : z_min_;
        z_max_ = (z_max_ < 0 || z > z_max_) ? z : z_max_;

        valid_ = true;

        return true;
    }
    inline bool addPos(const df::coord &pos) { return addPos(pos.x, pos.y, pos.z); }

    bool containsPos(int16_t x, int16_t y, int16_t z) const
    {   // Return true if point inside cuboid. Always false if cuboid is invalid.
        return valid_ && x >= x_min_ && x <= x_max_ &&
            y >= y_min_ && y <= y_max_ && z >= z_min_ && z <= z_max_;
    }
    inline bool containsPos(const df::coord &pos) const { return containsPos(pos.x, pos.y, pos.z); }
};

@myk002
Copy link
Member

myk002 commented Jun 1, 2024

I think the Maps module header might be a good place. I think the best way to determine if the abstraction is correct is to try porting existing tools to use it. If they look cleaner afterwards, then the abstraction is useful.

@Bumber64
Copy link
Contributor Author

Bumber64 commented Jun 3, 2024

Any thoughts on using get/set vs having the user check isValid() themselves? I find C++ get/set kind of messy compared to C#.

@myk002
Copy link
Member

myk002 commented Jun 4, 2024

We use the isValid pattern elsewhere, so I think it's fine here too

@Bumber64 Bumber64 linked a pull request Jun 5, 2024 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants