ondras / rot.js

ROguelike Toolkit in JavaScript. Cool dungeon-related stuff, interactive manual, documentation, tests!
https://ondras.github.io/rot.js/hp/
BSD 3-Clause "New" or "Revised" License
2.33k stars 254 forks source link

Hex calculations / var names hard to follow #93

Open ktiedt opened 8 years ago

ktiedt commented 8 years ago

So I have been working with the Hex renderer quite a bit lately and as I get into optimizing my project... Which is a very large (upwards of 30k points) interactive map, I have started to try and identify the viewable region of the map so that I can specifically render the group of points versus rendering it all...

The problem comes in when I started trying to understand the math behind the hexes... for that I dove into this website which explains the math behind hex shapes http://www.had2know.com/academics/hexagon-measurement-calculator.html

As I started trying to connect the dots between these calculations and the calculations used in the Hex renderer, I saw similarities but... not direct correlations. The meat of these calculations appear to be in the compute function here:

ROT.Display.Hex.prototype.compute = function(options) {
    this._options = options;

    /* FIXME char size computation does not respect transposed hexes */
    var charWidth = Math.ceil(this._context.measureText("W").width);
    this._hexSize = Math.floor(options.spacing * (options.fontSize + charWidth/Math.sqrt(3)) / 2);
    this._spacingX = this._hexSize * Math.sqrt(3) / 2;
    this._spacingY = this._hexSize * 1.5;

    if (options.transpose) {
        var xprop = "height";
        var yprop = "width";
    } else {
        var xprop = "width";
        var yprop = "height";
    }

    this._context.canvas[xprop] = Math.ceil( (options.width + 1) * this._spacingX );
    this._context.canvas[yprop] = Math.ceil( (options.height - 1) * this._spacingY + 2*this._hexSize );
}

I guess the simplest way to state this is, in terms of the formulas provided on the previous page:

h[eight] = (√3)s[ide]
d[diagnal] = 2s[ide]
a[rea] = (1.5√3)s[ide]²
p[erimeter] = 6s[ide]

Where does _spacingX, _spacingY and _hexSize fall into things? I had thought _hexSize would be side but.. when reversing the hexes by getting the coords of each point used and calculating each aspect from those differences, there does not appear to be any direct correlation there... Similarly the other vars did not fall into line with any step of the formulas that I could fine...

Nor does there appear to be a direct correlation to the height/width properties in the options object or the tile size properties... Any help would be appreciated here. Ultimately I am adding some functionality to Hexes that might be worth considering for all the renders, for example a grid to px conversion method that is then used to implement a stroke method to add custom borders to hexes. (This all actually works already, just needs to be cleaned up).

--- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/34355367-hex-calculations-var-names-hard-to-follow?utm_campaign=plugin&utm_content=tracker%2F297828&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F297828&utm_medium=issues&utm_source=github).
ondras commented 8 years ago

Hi @ktiedt,

it has been some time I wrote these, so my code knowledge is far from perfect.

The _hexSize is the side size, yes. _spacingX and _spacingY are variables that define distance between individual hex centers, so they directly influence the canvas size (last two lines in the compute function).

As the hexes are laid out in lines (their orientation can be checked at the hex explainer page in the rot.js manual), we see that _spacingX is half of the the hex's width (size * √3 / 2) and 2*_hexSize would be the height (=diagonal). But the height is not really used anywhere; hex lines are squished together, so the vertical distance beween two hex lines is only 1.5*_hexSize, which is _spacingY.

You might be confused by the fact that two sibling hexes on a line have a horizontal distance of two units (so their centers have a distance of 2 * _spacingX). This is due to the indexing approach used. See the manual page linked above for explanation.

The naming is probably far from perfect. Note that the names (_spacingX and _spacingY) are used consistently in the same manner as in the ROT.Display.Rect renderer, where their meaning might be more straightforward.

options.width and options.height specify the size of the canvas in hex counts, so having options.width = 10 means that the canvas shall be wide enough to accomodate 10 hexes. This corresponds to a line of 5 hexes, because such line spans 10 hexes total (remember: two sibling hexes have a distance of 2).

Graphical tiles are not really supported with the hex renderer. This is a known issue and I would love to add support, but so far I had no time to do so.

ondras commented 8 years ago

I might have omitted the fact that rot.js uses the "3: Double width" indexing approach mentioned in the interactive manual.

ktiedt commented 8 years ago

Thanks @ondras, that clears up a lot actually... the only thing that doesnt quite set in my mind now is the options.height/width... in my current test page (only local) but here is a copy of the image below, I have height: 6, width: 4 (and as I typed this I realize the height/width are transposed, which I believe explains the wide image vs tall image)... This image ends up being 6 wide by 2.5 high

image

However if I invert those numbers 4 height: 6 width... I end up with a square vs rectangle layout... and this one ends up being 4 wide by 3.5 high....

image

I believe the half heights come from the calculations you mentioned being between center points... but the rest I can't really reason out.

ondras commented 8 years ago

So, the first image:

and

The same holds for the second image: there are four hexes horizontally and six hexes vertically.

Having the images not transposed might make stuff a lot more cleaner.

I would say that the confusion is caused by the fact that the first image has a height of 4 hexes (and the second has a height of 6 hexes). Do not let your eyes fool you! :smile: (this is obviously due to the indexing approach that makes two adjacent hexes have a distance of 2)