chad-autry / hex-grid-map-3D

A 3D re-write of hex-grid-map using babylon.js
http://chad-autry.github.io/hex-grid-map-3D
MIT License
23 stars 6 forks source link

Question: Where does the draw-the-hexagonal-grid action happen? #1

Closed ghost closed 8 years ago

ghost commented 8 years ago

Hi!

I am currently trying to to get a hexagonal 3d grid like you have it here. i gues you are drawing it with webgl and i am very interested in that matter, but this project is way to complex for me to understand everything you joined here together or even get it running.

Can you give me a hint were to look for the actual drawing the grid code? Since your example is exactly what i am looking for, i hope you can get me in the right direction.

chad-autry commented 8 years ago

Yeah, I'm using Babylon.js which is a wrapper around webgl The grid itself is currently a series of semi-transparent hexagons, its most obvious if you look at the sun. You can see a bit of it below the grid through the hexagons.

https://github.com/chad-autry/hex-grid-map-3D/blob/master/src/contexts/InverseGridContext.js Is where the grid action happens.

Apologies for how messy that code is, I need to do a cleanup pass over it.

  1. In the init method it creates a static set of hexagons using babyon.js.
    • createPositionArray() is figureing out the co-ordinates of each hex in the set.
    • The babylon.js particle system draws them all as a single positionable object using the SolidParticleSystem. Where it says triangle, it is actualy a hexagon (copy paste naming I should fix)
    • babylon.js is rather efficient at drawing, there are actually a ton of hexes off screen that I haven't bothered removing. I just made a large hexagonal shaped set of them in the create position array
    • I currently have the view locked, in the demo, but if you disable the built in camera control, and enable Babylon.js's camera control at https://github.com/chad-autry/hex-grid-map-3D/blob/master/src/HexBoard.js#L231 you can see off the edge of the grid
  2. The updatePosition function of InverseGridContext is responsible for keeping that SolidParticle system invisibly centered in the view, so that it looks like an infinite grid. As the grid is dragged, it gets called with the center x, y coordinates. It then centers the finite grid

The strategy of "draw transparent hexagons to represent the grid" really depends on a 3D library I think. If you have any more questions, or want to be pointed at some actual grid line drawing resources, don't hesitate to ask.

ghost commented 8 years ago

Hi, thanks for the reply! As i understand you create the scene in HexBoard.js, then the InverseGridContext calculates the grid. What i dont get is where you put the grid into the scene and say somethig like render() to show the result. I also saw a pane or something.

What you mean with actual grid line drawing resources?

chad-autry commented 8 years ago

InverseGridContext calculates the grid and adds the shapes to Babylonjs, the grid is made up of a SolidParticleSystem

https://github.com/chad-autry/hex-grid-map-3D/blob/master/src/contexts/InverseGridContext.js#L8 var babylon = require('babylonjs');

https://github.com/chad-autry/hex-grid-map-3D/blob/master/src/contexts/InverseGridContext.js#L62 var SPS = new babylon.SolidParticleSystem('SPS', scene, {updatable: false, pickable: false});

Starting at line 36 of the HexBoard is where Babylon.js is passed the canvas, and its render loop is set up. https://github.com/chad-autry/hex-grid-map-3D/blob/master/src/HexBoard.js#L36

    this.engine = new babylon.Engine(canvas, true);

    //Run the engines render loop
    this.engine.runRenderLoop(function () {
        if (!!board.scene) {
            board.scene.render();
        }
    });

require gives access to the same babylon.js object to each module.

By an actual grid drawing resource, I mean I'm drawing a hexagon for each cell of the grid. It gives a bit of a short cut, since I just need to calculate the center point of each hex. If you wanted to draw the grid lines themselves, you'd need to concern yourself even more with the geometry of the hexagon. If you haven't seen it, http://www.redblobgames.com/grids/hexagons/ is pretty much the Authority =P

ghost commented 8 years ago

Well, I only understand half of what you are doing, but i got a basic example running: http://labs.setanodus.net/babylon-hex-grid/

Next is to throw out everything “weird” and see what happens and understand what you made me do here and the mousemove so i can load new tiles with ajax.

Yes, that reblobgames was were i started :)

Tried some other solutions, for example there is a complete css solution (http://labs.setanodus.net/csshexamap/ and here with svg: http://labs.setanodus.net/csshexamap/svgtest.php) but the css 3d transition always scales the whole thing to fit the screen so i cant drag and load fresh tiles) and when the game improves i would need webgl anyway in the future so why not start with it at all :)

Thank you very much for your explanations and for sharing that code at all.

chad-autry commented 8 years ago

So a bit of history might possibly help you next,

The various contexts are basically 'layers' inherited from my original https://github.com/chad-autry/hex-grid-map (It simplified a bit when going to 3D actually, but I kept the context concept). The contexts provide mouse event handlers for the layers, or if none of the contexts claimed the mouse action, handled updating the position (The HexBoard has the canvas mouse event handlers, and it calls into the context's handlers). In 2D, every layer had to draw itself offset for position, but now in 3D the Babylon.js camera moves. However, the hexagonal grid is really finite, it works the same in 2D or 3D, as the view is dragged, the context's updatePostion method recenters the finite grid on the hexagon/cell in the middle of the view.

Note: Even though the grid is finite, you should be able to individually size, texture, and rotate them based on the position they occupy when drawn..

Similarly the finite invisible 'pickerPlane' gets re-centered.

The main complexity in the mouse drag, is in finding the drag translation on our plane, vs on the canvas itself (movement at the top of the canvas moves further on our plane since it is further away than movement at the bottom where the plan is nearer)

Finally, the co-ordinates I chose back when I was in 2D don't match well with babylon.js's default co-ordinates. You might hit some confusion as you try and add shapes in to your scene.

ghost commented 8 years ago

ok, can drag now: http://labs.setanodus.net/babylon-hex-grid/test2.php

You say its finite, and in my example it actualy is, but in your demo you can drag it endlessly. When i am always dragging the plane, how do you find out when a tile is clicked?

chad-autry commented 8 years ago

scene.pick is used to convert canvas X, Y to babylon.js/planar X/Y https://github.com/chad-autry/hex-grid-map-3D/blob/master/src/HexBoard.js#L88

https://github.com/chad-autry/hex-grid-map-3D/blob/master/src/HexBoard.js#L192 passes that to a global mouse clicked function (if we didn't drag, and another context didn't claim the mouse event)

The demo uses hexDimensions.getReferencePoint to convert a planar X/Y into U, V. Then it broadcasts its alert. https://github.com/chad-autry/hex-grid-map-3D/blob/master/gh-pages/src/app/demo/demo.js#L105 Then

ghost commented 8 years ago

ya i just figured that out:

var hexagonalCoordinates = hexDimensions.getReferencePoint(pickResult.pickedPoint.x, pickResult.pickedPoint.y);
console.log('hexagonalCoordinates');
console.log(hexagonalCoordinates);
ghost commented 8 years ago

Cant find anywhere where you load new tiles, but your demo is infinite, pulled till -1000|-300. In the init you have positionArray until i<31.

What is that: var EmittingDataSource = require('data-chains/src/EmittingDataSource.js'); $scope.cellDataSource = new EmittingDataSource();

an external project to add the objects?

chad-autry commented 8 years ago

The grid is finite, but re-positioned so it appears infinte Positioning Method

If you can build and run the demo locally, changing 31 --> 5 at Finite Grid Loop should make it apparent.

ghost commented 8 years ago

Aaaaaah, got it: http://labs.setanodus.net/babylon-hex-grid/test3.php

So i can load the tiles with ajax line by line, during dragging, not complete areas on dragend.

very clever :D

ghost commented 8 years ago

Thats weird: i used code from this playground: http://babylonjs-playground.azurewebsites.net/#HSVQL, and the result is upside-down: http://labs.setanodus.net/babylon-hex-grid/test4.php Did you inverse the coordinate system or something? Or did I ?

chad-autry commented 8 years ago

Yeah, that's what I meant from the comment earlier.

Finally, the co-ordinates I chose back when I was in 2D don't match well with babylon.js's default co-ordinates. You might hit some confusion as you try and add shapes in to your scene.

Since I started over in 2D, I adopted canvas co-ordinates of x+ is to the right, y+ is down/out of the screen. Then I used z+ for height above the grid.

Babylon.js default is I believe x+ right, y+ up, z+ into the screen. So if we were looking at the grid edge on, we'd be rotated 90 degrees compared to Babylon.js, but then our top down view means we're rotated even further and yeah, looking at playground scene with Babylon.js defaults upside down.

I had to explicitly tell Babylon.js the camera is allowed to be upside down, or it would have auto-rotated the view Allow Upside Down

I hadn't considered the issue with billboards.

Luckily, I think rotating the texture, should give the desired effect outputplaneTexture.wAng = Math.PI;

ghost commented 8 years ago

Now I understand what you have done, but not why. You didnt wnat to change the coordinate system to fit the babylonjs Standard? Your tile enumeration is also different from http://www.redblobgames.com/grids/hexagons/ i think i have to change that to make the calculations (distance aso) work.

ghost commented 8 years ago

also weird is, that that text rotates when you drag around, since we never rotate the camera.

chad-autry commented 8 years ago

Yeah, I didn't want to change. The 3D hex grid map was initially just an experiment, I wanted to have it be as compatible as possible with the original 2D version. I literally started with the 2D version and started changing things to use Babylon.js. I also wanted to remain consistent with cartesian-hexagonal, which converts between U, V <--> X, Y and is shared with the 2D version.

My hexagonal-coordinates are the same as redblobgame's Axial Coordinates, with the minor caveat that pairs are swapped (I was looking at a ton of different hexagon sources and didn't write this soley from his site) More explicitlly, if you look at his demo my V is his green co-ordinate (it increases going up and to the right [+x, and -y]). He puts that axis first when writing a pair, it is simply second in my pair. My U is his blue axis (it increases going down [+y]). He puts that axis first when writing a pair. It is first for me.

I comprehend the "max" format for counting distance best myself,

max(abs(a.x - b.x), abs(a.y - b.y), abs(a.z - b.z))

Converting into my coords does give a formula he doesn't have directly, but I think it should be noticeable U and V are interchangeable. vs the cube distance formula he shows for axial.

max(abs(a.U - b.U), abs(a.V - b.V), abs(-a.U-a.V + b.U+b.V))
chad-autry commented 8 years ago

For the rotation, it is trying to orient the 'top' facing the camera. Which is really our bottom and is looking weird. You could constrain the rotation to the Z axis, though that won't 100% provide the same functionality. since it won't tilt up.

chad-autry commented 8 years ago

Just a note that I had another idea to try setting the camera's upVector. Unfortunately it does not appear to be used for billboards by the ArcRotate camera at least.

ghost commented 8 years ago

I tried to start from scratch with correct values but i do not get at all how that coordinate system works: http://labs.setanodus.net/babylon-hex-grid/babylon-hexagon-gameboard-v1.php they seem to be completly random.

ghost commented 8 years ago

Last thing i want to understand from your code is how exactly a placed object "claims" the mouseclick. You use another library for event handling that obfuscates how exactly shapes are created and placed.

chad-autry commented 8 years ago

I tried to start from scratch with correct values but i do not get at all how that coordinate system works: http://labs.setanodus.net/babylon-hex-grid/babylon-hexagon-gameboard-v1.php they seem to be completly random.

Converting co-ordinates is hard, which is why I didn't do it =P I notice your hexagons are flat up, don't know if that was intended or if you have a hidden 90 degree rotation (mine are point up).

Last thing i want to understand from your code is how exactly a placed object "claims" the mouseclick. Adding items and drawing is probably a bit over-complicated, in the pursuit of being extensible.

HexBoard has the direct mouse listener and delegates to each context to see if a context claims a click click delegation

Initially, each context was injected with a DrawnItemFactory, and listened to a listened to a DataSource(just a list with events). When a JavaScript object was added to the DataSource, the context would would create/draw the item. It was also responsible for delegating any mouse clicks to the item as applicable.

The Demo's pathContext (used to draw the orange line) is still that way. Path Context Initialization Orange Path Added to DataSource I notice now I have a bug with my drawn item context, clicking the orange path doesn't popup the an alert like it should.

The CellContext was a bit more complex, since it had to figure out stacking of items. And managed buckets and whatnot. Back in 2D land there was even the ability to drag the entire stack up and down to 'scroll' through it. Anyways, that DataSource (an evented list conceptually) could be chained together, and have logic place in it for filtering and transforming. So that is where I pushed the individual tasks of transforming a DTO --> Babylon.js object --> Translating the object to the correct cartesian coordinates for its axial --> Translating the object above the plain, given other items at the same axial. They each have their own DataSource/DataLink

So now the CellContext is only responsible for delegating mouse events, which it mostly does using Babylon's picking function, and a 'isCellItem' flag we decorated the mesh with. The CellContext doesn't even need to listen to the DataSource anymore, except that the way I started to show dragging was to introduce a drag target, which I want to swap control to (On second look, I could maybe even remove that by introducing a stationary drag source, and move/transform the clicked item itself)

ghost commented 8 years ago

i am not yet converting them, i'm just trying to paint some of them manually:

                var hexagonWidth = 72;
                var hexagonWidthHalf = Math.round(72/2);
                var hexagonMargin = 0.5;
                var hexagon = BABYLON.MeshBuilder.CreateDisc('t', {
                    radius: hexagonWidthHalf,
                    tessellation: 6,
                    sideOrientation: BABYLON.Mesh.DOUBLESIDE
                }, scene);
                var tilePositions = [{x:0,y:0},{x:54,y:54},{x:-54,y:-54},{x:-54,y:36}];
                console.log(tilePositions);
                var SPS = new BABYLON.SolidParticleSystem('SPS', scene, {updatable: false, pickable: false});
                SPS.addShape(hexagon, tilePositions.length, {
                    positionFunction: function(particle, i, s) {
                        particle.position.x = tilePositions[i].x;//(Math.random() - 0.5) * fact;
                        particle.position.z = tilePositions[i].y;//(Math.random() - 0.5) * fact;
                        particle.position.y = 0;//Math.floor(Math.random() * (50-1));
                        //particle.rotation.x = Math.random() * 3.15;
                        //particle.rotation.y = Math.random() * 3.15;
                        particle.rotation.y = 0;
                        particle.color = new BABYLON.Color4(1, 1, 1, 0.125);
                    }
                });
                var grid = SPS.buildMesh();

i also did not rotate any tile, i just put the camera to a standard position: all zero and distance 500

                var camera = new BABYLON.ArcRotateCamera('ArcRotateCamera', 0, 0, 0, BABYLON.Vector3.Zero(), scene);
                camera.setPosition(new BABYLON.Vector3(0, 500, 500));
                camera.setTarget(BABYLON.Vector3.Zero());
                var cameraTargetX = 0;
                var cameraTargetY = 0;
chad-autry commented 8 years ago
                        particle.position.z = tilePositions[i].y;//(Math.random() - 0.5) * fact;
                        particle.position.y = 0;//Math.floor(Math.random() * (50-1));

You're trying to flip the Y and Z co-ordinates, you also left out the particle rotation I had. That is what I meant by "converting." You're converting my cartesian orientation to Babylon.js's default. Its going to require some mental gymnastics to get that sorted, which I really didn't want to do, but now that I've thought of it a bit too late =P

The key you're missing, I think, is my Z <--> Babylon.js Y, but my Y <--> negative Babylon.js Z. So you need to invert the y/z co-ordinate of all your tile positions (assuming to mined them as my original Y positions).

Additionally your choice of camera position means that babylon.js spun the scene around and +X is to the left, probablly causing you additional confusion. (Because it has the constraint of +Y is up, but it has +Z as into the screen, so you're looking at the scene from behind as it keeps the camera oriented upwards.)

Sometimes I've found it helpful to insert colored spheres into a scene to visualize my orientation. Red in the + X, Green in the +Y, and Blue in the +Z directions.

ghost commented 8 years ago

nearly got it with the babylon coordinates, only adopting the pickerplane wont work. if i change here positioning from y to z, it is centered correctly, but it moves way to fast if i let it, it works but gets bigger/smaller on dragging an both variants give wronger coordinates the farer away you get from zero|zero: http://labs.setanodus.net/babylon-hex-grid/babylon-hexagon-gameboard-v2.php

i think i basically dont get something of how you place the plane.

chad-autry commented 8 years ago

We're well beyond the point of questions on hex-grid-map-3D, and into debugging your local changes.

I welcome further questions on how I've done things, but I'm not interested in debugging a re-write or co-ordinate transformation.

ghost commented 8 years ago

Of course, I should have been more specific: i made the plane smaller and gave it color so you can see it on one look and tell me if its still placed right - thats all.

chad-autry commented 8 years ago

Looks tilted