hexworks / mixite Goto Github PK
View Code? Open in Web Editor NEWA GUI agnostic hexagonal grid library. Supports a multitude of grid layouts including hexagonal, triangular, rectangular and more.
License: Apache License 2.0
A GUI agnostic hexagonal grid library. Supports a multitude of grid layouts including hexagonal, triangular, rectangular and more.
License: Apache License 2.0
The Iterable
returned by getHexagons
should lazily create Hexagon
objects only when iterating over them. The point of this feature is to be able to iterate over Hexagon
s without keeping them all in memory. This will be useful when the user wants to stream the data for an 1000 * 1000 grid to an ui.
Hi,
I stumbled upon your excellent work here, thinking to use it on my project. After spending a day tinkering with it, I sadly concluded that it may not be possible, since it seems to be based on a pixel format.
I'm using LibGDX where my game world is logically scaled to 1 world unit: 32 pixels. When I try to render a grid, for example, 100 world units long and wide, with a radius of 0.7, the library fails to resolve the hexes, drawing lines and triangles of what I think are what's supposed to be the hex. I have to use the pixel size, to get it to draw, but then, the hexes and grid are too large. The only way I got things to draw at the appropriate scale is to affect the polygon drawer, scaling its matrix down, to draw smaller.
Needless to say, while I finally got the grid to draw to size, the rest of the library can't work under these conditions. Stuff such as getting the grid coordinates, or coordinates through pixel, resolve wrongly due to the scaling, especially getting the center of each hex, which I need to properly place objects within hexes.
I know it's likely a lot of work on your part, so this is just a suggestion to have the library be able to use world coordinates, instead of pixels units. Most, if not all, game development frameworks strongly urge developing under world coords, to simplify development, and not have to worry about resolution etc, across platforms such as mobile devices. This would make your library a perfect fit as a drop-in solution for Java-based game dev, as there aren't any existing solutions out there, and people have to, basically, roll their own.
After saying all this, I realize that, if this works for Android dev, it might already have a means to work in world scale. If so, please advise.
Thanks, and great job regardless!
Kotlin is not my forte, so this might not be possible. Boolean fields have getter methods prefixed with get
and not is
.
But I'm currently in the process of implementing SatelliteData
and Lombok is not playing nice with these methods since they break naming convention. (https://github.com/Hexworks/mixite/blob/master/mixite.core/core/src/main/kotlin/org/hexworks/mixite/core/api/contract/SatelliteData.kt#L20,L30).
On another note, awesome library! Excellent work.
Now Mixite is written in Kotlin (still a new kid on the block), it suddenly becomes a little scary for Java devs (despite saying Don't Panic on the cover). It might do well to provide a basic Java usage of mixite.core both
This is an enhancement request.
The current implementation of getByPixelCoordinate()
provides the nearest hexagon when called with a pixel which lies outside the grid
but within the distance of one hexagon. It is therefore an approximation. This makes good sense for many applications whereby a user is clicking a screen pixel, and some tolerance is acceptable. There are certain use cases however, such as the binning of points into hexagons which require a guarantee that the coordinate lies within the hexagon.
While this behaviour remains, a workaround for this scenario is proposed as:
flatTop
you need to increase by 2 hexagons left and right to achieve 1 complete hexagon.flat topped
hexagons with a buffer of 4 hexagons horizontally (2 left, 2 right) and 2 vertically, the offsetX
is 1.5*hexWidth
, and the offsetY
is 1*hexHeight
before calling getByPixelCoordinate(x,y)
As a quick fix I suggest adding a sentence in the Javadoc for the method as follows:
Note that at the boundaries this is a lenient match. When the pixel provided does not fall within a hexagon on the grid, but is within a close distance of the grid, the nearest hexagon will be returned. Where strict hexagon matching is required, please refer to the workaround offered on https://github.com/Hexworks/hexameter/issues/32
This might not actually be desirable for other implementations right now, but it might be good to include in the next major version release.
Basically SatelliteData
currently requires that you override setter methods if you implement the interface directly. If the calculators require this benefit of mutability, then there's not a nice way around this.
However, I'm trying to define my own class of data for a hex, and I want those objects to be immutable. If the board state changes, I want to be able to place newly constructed satellite data objects on those hexes in the grid.
So, this is barely a big deal since I can still just override them to have them do nothing anyway, but it does look like this:
public class MySatelliteData implements SatelliteData
{
. . .
public void setPassable( final boolean passable )
{
return;
}
public void setOpaque( final boolean opaque )
{
return;
}
public void setMovementCost( final double movementCost )
{
return;
}
. . .
}
Definitely just being nit-picky with this one. Immutability is definitely a good practice to be eliciting. It does result in a tad less flexibility from this library, but you are probably more than likely saving a consumer from potentially getting themselves into trouble via mutability.
checkstyle
findbugs
pmd
coverage
A CONTRIBUTING.md also needs to be added
When an application uses getSatelliteData() there is a compiler error:
...\dev\test-mixite> gradle build
> Task :app:compileJava FAILED
...\dev\test-mixite\app\src\main\java\test\mixite\App.java:29: error: cannot access Maybe
h.getSatelliteData();
^
class file for org.hexworks.cobalt.datatypes.Maybe not found
1 error
FAILURE: Build failed with an exception.
To reproduce the error, build a grid and attempt to access the satellite data:
package test.mixite;
import org.hexworks.mixite.core.api.Hexagon;
import org.hexworks.mixite.core.api.HexagonalGrid;
import org.hexworks.mixite.core.api.HexagonalGridBuilder;
import org.hexworks.mixite.core.api.defaults.DefaultSatelliteData;
public class App {
public static void main(String[] args) {
HexagonalGridBuilder<DefaultSatelliteData> builder = new HexagonalGridBuilder<DefaultSatelliteData>()
.setGridHeight(9)
.setGridWidth(9)
.setRadius(30.0);
HexagonalGrid<DefaultSatelliteData> grid = builder.build();
for (final Hexagon<DefaultSatelliteData> h : grid.getHexagons()) {
h.getSatelliteData();
// ^error here
}
}
}
The error occurs whether using the latest release, or the latest commit:
dependencies {
implementation 'com.github.Hexworks.mixite:mixite.core-jvm:2018.2.0-RELEASE'
// ^ this works
// implementation 'com.github.Hexworks.mixite:mixite.core-jvm:2020.1.0'
// ^ this fails
// implementation 'com.github.Hexworks.mixite:mixite.core-jvm:master-SNAPSHOT'
// ^ this fails
}
The problem is not present in the 2018.2.0-RELEASE
Let me know if there is more information that I can provide. Thank you.
Bespoke licenses force developers to pause and become trainee lawers before progressing. Given the apparent free-for-all nature of Hexameter LICENSE file, why not just use BSD2?
Height of a pointy top hex of radius is 2*138 = 276. When the centerY of a hex is 138 the y value of the bounding box should be zero. This is not the value returned by getExternalBoundingBox() .
``
@test
public void yValue() {
HexagonalGridBuilder builder = new HexagonalGridBuilder()
.setGridHeight(3)
.setGridWidth(3)
.setGridLayout(HexagonalGridLayout.HEXAGONAL)
.setOrientation(HexagonOrientation.POINTY_TOP)
.setRadius(138);
HexagonalGrid grid = builder.build();
Hexagon hex = (Hexagon) grid.getHexagons().iterator().next();
Rectangle rect = hex.getExternalBoundingBox();
double ctrY = hex.getCenterY(); // 138
double ht = 2*138;
assertEquals(ctrY - ht/2, rect.getY(), 0.01f); // expected 0, actual 207
}
It is not necessary to hold Hexagon
instances since SharedHexagonData
contains all necessary information. Map<String, Hexagon> hexagonStorage
thus should be refactored to Map<AxialCoordinate, SatelliteData>
or something similar.
Only 60° needs to be implemented at the moment according to:
As an API interface SatelliteData
is actually a very opinionated choice of data. There is no hard requirement for any enforced supertype to the storage data (e.g. the core class, HexagonalGridImpl
, doesn't use it even once). SatelliteDataexists so Mixite can supply some useful algorithms via the extended
HexagonalGridCalculator. If the user doesn't need
movementCostor
isPassableor
isOpaquethen it's just cruft (note for example
movementCost` is not used anywhere in the code base!).
Ergo, SatelliteData
interface should be optional.
However, those additional utilities are very useful and key part of the value of this library.
The current in memory implementation should implement it. This is the first step towards the Hexagonal Architecture milestone. All data operations (storing/fetching) on Hexagon
s and the HexagonalGrid
should be abstracted away in the interface.
Amazing library!
Just wondering if I can start putting these releases in my POM file.
Use expect
/ actual
to allow Java 8 to provide the proper Optional implementation.
This is the current major bottleneck which affects memory usage. The GridData
instance in HexagonalGridImpl
holds all the necessary information for generating the possible coordinates for a given grid so we need to work around this.
As part of this task HexagonalGrid#containsCubeCoordinate
also needs to be refactored. The implementation should delegate to the GridLayoutStrategy
since it is used to fetch the coordinates in the first place.
This is a feature suggestion.
Currently the methods getGridX()
, getGridY()
, getGridZ()
of the CubeCoordinate
class show the grid cube coordinates with the origin (0, 0, 0) on the top left, which is outside of the grid if the layout is HEXAGONAL
. Having the origin in the center of the grid would be more useful.
Could you provide documentation for the Maybe class. Specifically:
It looks like when HexagonalGridBuilder
has GRID_LAYOUT
set, the origin of the grid coordinate system is somewhere on the top left (when calling coordinate.getGrid_()
). It would be easier for the user if the origin of the grid's coordinate system started in the center (as mentioned here).
The format of the map file is as follows:
{
"name": "testmap", // name of the map (parameter)
"version": "1.0.0", // version of the map format
"width": 4, // width of the map
"height": 3, // height of the map
"radius": 40.0, // radius of the hexagons
"orientation": "POINTY_TOP", // enumerated: POINTY_TOP or FLAT_TOP
"layout" : "RECTANGULAR", // enumerated: RECTANGULAR, HEXAGONAL, TRIANGULAR, TRAPEZOID, EMPTY
"tilesetUrl": "http://example.com", // the url of the tileset. the tileset defines the tileIds as well
"cells" : [
[0, 0, 1, 1.0, 1] // x, z, isPassable, movementCost, tileId
]
}
Since map import/exports are not part of the core functionality it goes to the hexameter-util
module.
In fact the iteration order should be from left to right and top to bottom. Now this is not the case.
When using hexameter in Android I receive the error message:
`AGPBI: {"kind":"error","text":"trouble processing "javax/xml/parsers/DocumentBuilder.class":","sources":[{}]}
AGPBI: {"kind":"error","text":"Ill-advised or mistaken usage of a core class (java.* or javax.*)","sources":[{}]}
AGPBI: {"kind":"error","text":"when not building a core library.","sources":[{}]}
AGPBI: {"kind":"error","text":"This is often due to inadvertently including a core library file","sources":[{}]}
AGPBI: {"kind":"error","text":"in your application\u0027s project, when using an IDE (such as","sources":[{}]}
AGPBI: {"kind":"error","text":"Eclipse). If you are sure you\u0027re not intentionally defining a","sources":[{}]}
AGPBI: {"kind":"error","text":"core class, then this is the most likely explanation of what\u0027s","sources":[{}]}
AGPBI: {"kind":"error","text":"going on.","sources":[{}]}`
... and so on.
That dependency of a core class is somewhere in hexameter-core. I use the version 3.0.0 of that library.
Every Hexagon
contains 6 Points
then 12 "vertices" which are just the x,y of each Point
. Get the Point...
/**
* Returns a list containing the [Point]s of this [Hexagon].
*/
val points: List<Point>
/**
* Returns an array of the vertices of this [Hexagon].
*/
val vertices: List<Double>
Perhaps it was meant to be an Array of primitive types? (doc clearly says array in contradiction to the actual type).
call to Hexagon.getExternalBoundingBox() returns a negative value for height.
``@Test
public void negativeHeight() {
HexagonalGridBuilder builder = new HexagonalGridBuilder()
.setGridHeight(3)
.setGridWidth(3)
.setGridLayout(HexagonalGridLayout.HEXAGONAL)
.setOrientation(HexagonOrientation.FLAT_TOP)
.setRadius(138);
HexagonalGrid grid = builder.build();
Hexagon hex = (Hexagon) grid.getHexagons().iterator().next();
Rectangle rect = hex.getExternalBoundingBox();
assertTrue(rect.getHeight()>0); // [207.0, 358.5, 276.0 ,-239.0]
}```
I see this code base were left behind but i still want to raise this issue, maybe if someone has issue with performance can learn from it.
Would be great functions for be able to avoid creating a Hexagon and using instead CubeCoordinate would improve performance drasticly.
For example one of the mostly used function is the getNeighboursOf.
Firstly i am ok with the current one if you really need the list of hexagon neighbours.
I am writing about the cases when you do not need the hexagon(the most cases as they likely wont change position once they are created, if they move you just change the projection and so theri coordinate wont be affected)
So here are my results with using only CubeCoord:
getNeighboursOf on 100x100 grid(sure the grid size does not matter for neighbours, only maybe for looking up time in the storage)
Using hexagons:
Avg Duration: 2.991 seconds
Using CubeCoord only:
Avg Duration: 0.139 seconds
So roughly 20x improvent and so it could be improved even further beyond.
It also uses the user's collection to avoid object creation, it is nice and much more fitting for a game.
The code:
override fun getNeighborsOf(coordinate: CubeCoordinate, outputCollection: HashSet) {
for (i in NEIGHBORS.indices) {
val cube = getNeighborCoordinateByIndex(coordinate, i)
if(hexagonDataStorage.containsCoordinate(cube)){
outputCollection.add(cube)
}
}
}
Test:
long start = System.currentTimeMillis();
for(int i=0; i<1000000; i++) {
arr.clear();
grid.getNeighborsOf(hexagons[18].getCubeCoordinate(), arr);
}
long end = System.currentTimeMillis();
This value is only used in a single internal algorithm (HexagonImpl#calculatePoints
). Many other similar calculations operate by checking the orientation rather than redirecting through it.
Mixite should be consistent and internalise this value?
The calculateRingFrom function is not correct since the return set is never modified, so it's always empty.
Take a look at the implementation here:
CalculateRingFrom fails to find all hexs when the center is close to an edge/off edge.
Lombok proved to be a nuisance and it acts as a barrier for possible contributors. It also interferes with coverage and adds mental overhead.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.