Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Pixels to Hex Coordinates

Anyone out there have anything to translate left, top pixels and last move positions into a hex coordinate?&nbsp; I'm going through Amit Patel's page on pixels at <a href="https://www.redblobgames.com/grids/hexagons/" rel="nofollow">https://www.redblobgames.com/grids/hexagons/</a> but am very slow in understanding it.&nbsp; I can't rely in middle of a hex, because my players will be moved around in a hex (alt key) as per this screen shot: If anyone out there has any javascript to accomplish this, I would be very grateful. Thanks in advance, -- Tim PS, I've posted a similar topic under Specific Use Questions &amp; Macros, but probably should have posted here.
1603479002
David M.
Pro
API Scripter
Played around with this a bit today, since I've never worked with hex maps and it sounded interesting. Kind of regretting it now, lol, but here we go. The first challenge is that Roll20 Hex grids have weird sizes. So, instead of h=w=70px, it is instead h=66.96... w=75.19... pixels for Hex (V). If you use Hex (H), you get different dimensions. The following summarizes this, with a proposed coordinate ordering system for the two Hex systems. Looks like you use Hex (V), so we'll stick with that for now. Expanding on the premise in the other forum thread: We'll use the Roll20 persistent state object to store a collection of maps, with each map having the properties "name" and an array of points each point in the array contains properties "x", "y" (corresponding to the pixel locations of the center of each hex), and "cost" (the movement cost for the given hex, defaulted to 1 in the current example) The "new" command will make or update the map (named after the pageName - put something other than the default Untitled) Once the map coords are stored in state, you would have another script command (e.g. "update") to replace the default movement cost for a given hex coord to something else, using a dummy token to tell the script where on the map you want to update. I didn't try this part yet, didn't want to hog all the fun! Listen for token movement events, using waypoints. Perform a lookup for each waypoint against the map's "cost" property for the nearest hex coord. Output the total movement cost to the chat, possibly comparing it to a movement point attribute on the token's character sheet&nbsp; The partial script a few lines below seems to work properly in getting the default map array for the page associated with a selected token, using the "new" command !HexMap new Again, this assumes a Hex (V) map, so the coords for the (0,0) hex are hardcoded for this case, and the rest of the map is calculated relative to this point, using spacings given in the Excel screenshot above. Note that grid scaling is taken into account, in case you use something other than 1. There are still some console logs peppered in there from testing, so you can remove later. Just to give you an example of how to find the nearest hex to a selected token's position, I added some chat output to the !HexMap new command. You would want to remove this and instead integrate something similar into your waypoint calculation routine. I should also note: I have been having some issues with the api sandbox today, requiring frequent restarts and sometimes hanging or crashing at random times, even performing the same commands with no changes to the code. Many others have reported this lately, as well, so just a word of warning. Final caveat: I am still pretty new to js, so I'm sure there are more efficient ways to do what I've written here. Hopefully it at least gives you some ideas on the proposed approach. This code uses a Revealing Module structure. If you are not familiar with this, check out more&nbsp; here .&nbsp; const HexMap = (() =&gt; { const version = '0.0.1'; const scriptName = 'HexMap'; const schemaVersion = 0.1; const checkInstall = () =&gt; { log('-=&gt; ' + scriptName + ' v'+version); //check state object schema. ANY TIME you change the structure of state.HexMap, you will need to update the schemaVersion to apply the changes if( ! state.hasOwnProperty(scriptName) || state.HexMap.version !== schemaVersion) { log(` &gt; Updating Schema to v${schemaVersion} &lt;`); state.HexMap = { version: schemaVersion, maps: [] //collection of map objects }; } }; const hexPt = function (x, y, cost) { this.x = x; this.y = y; this.cost = cost; //movement cost for given hex }; const pt = function(x,y) { this.x = x, this.y = y }; const emptyMap = function (name) { this.name = name; this.pts = []; //a collection of hexPts }; const distBetweenPts = function(x1, x2, y1, y2) { dist = Math.sqrt( Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2) ); return dist; } const findNearestHex = function (mapName, tokX, tokY) { let dist let minDist = 99999999999; let nearestIndex; let currentMap = state.HexMap.maps.filter(mapObj =&gt; mapObj.name == mapName); if (currentMap === undefined) { return undefined; } else { for (let i = 0; i &lt; currentMap[0].pts.length; i+=1) { dist = distBetweenPts(tokX, currentMap[0].pts[i].x, tokY, currentMap[0].pts[i].y); if (dist &lt; minDist) { minDist = dist; nearestIndex = i; } } return currentMap[0].pts[nearestIndex]; } }; const handleInput = (msg) =&gt; { if (msg.type !== "api" &amp;&amp; msg.content.indexOf("!HexMap") === 0 ) { return; } who = getObj('player',msg.playerid).get('_displayname'); let tok = getObj("graphic",msg.selected[0]._id); let pageID = tok.get("pageid"); let page = getObj("page", pageID); let pageWidthPx = page.get("width") * 70; let pageHeightPx = page.get("height") * 70; let snapIncr = page.get("snapping_increment") //assumes Hex(V) page setting! let xSpacing = 75.1985619844599 * snapIncr let ySpacing = 66.9658278242677 * snapIncr let startX = 37.5992809922301; let startY = 43.8658278242683; const defaultCost = 1 let pageName = page.get("name"); //state.HexMap.maps = []; //log(state.HexMap); let args = msg.content.split(/\s+/); switch(args[1]) { case 'new': //Build hexagonal (V) grid pts with default movement cost let newMap = new emptyMap(pageName); let halfToggleX = xSpacing/2; for (let j = startY; j &lt;= pageHeightPx; j+=ySpacing) { for (let i = startX; i &lt;= pageWidthPx; i+=xSpacing) { let newPt; newPt = new hexPt(i, j, defaultCost) newMap.pts.push(newPt); } startX += halfToggleX; halfToggleX = -halfToggleX; } //check if this map exists in the state object. If not, add it. If it exists, replace it. let checkMapIndex = state.HexMap.maps.findIndex(mapObj =&gt; mapObj.name == pageName); log(checkMapIndex); if (checkMapIndex === -1) { //create new map in the state object log('creating new map'); state.HexMap.maps.push(newMap); } else { //replace existing map coords with newMap log('replacing existing map with newMap'); state.HexMap.maps[checkMapIndex] = newMap } log(state.HexMap) //---------------------------------------- //For testing/example purposes only. //---------------------------------------- /* //output list of coords for (let i = 0; i&lt;newMap.pts.length; i+=1){ log(newMap.pts[i].x + "," + newMap.pts[i].y); } */ //find the hex object nearest to the selected token, and output the movement cost of that hex to chat let tokX = tok.get("left"); let tokY = tok.get("top"); let nearestHex = findNearestHex(pageName, tokX, tokY); if (nearestHex !== undefined) { //build default template output let title = 'HexMap Test' let content = '{{Token Coords=(' + Math.round((tokX + Number.EPSILON) * 100) / 100 + ', ' + Math.round((tokY + Number.EPSILON) * 100) / 100 + ')}}'; content = content + '{{Nearest Hex Coords=(' + Math.round((nearestHex.x + Number.EPSILON) * 100) / 100 + ', ' + Math.round((nearestHex.y + Number.EPSILON) * 100) / 100 + ')}}'; content = content + '{{Movement cost=' + nearestHex.cost + '}}'; sendChat(scriptName,`/w "${who}" `+ `&amp;{template:default} {{name=${title}}}` + content); } //---------------------------------------- break; } }; const registerEventHandlers = () =&gt; { on('chat:message', handleInput); }; on('ready', () =&gt; { checkInstall(); registerEventHandlers(); }); })(); The weirdly-named halfToggleX variable used in the map build is for the alternating rows, where the x coord is shifted back and forth by a half hex width. This value goes +/- for each row. Plotted the coords for a "25x25" grid in Excel below, using Hex (V) and cell width (or "snapping increment") of 1. Note I made the Excel y-coords negative because positive Y is "down" on Roll20 maps. Note that there are fewer than 25 hexes in each row, due to the non-70px spacing. Apparently, Roll20 just makes the maps 70*w pixels wide, etc., regardless of the grid style.&nbsp;&nbsp; Here is some sample output for a token in 3 different positions on the hex map. The upper left hex of each screenshot is the (0,0) hex of the map (top left). Of course, all the movement costs for the nearest hex currently return the default "1", since there is nothing in the script to update the default map, yet. &nbsp; Token in center of (0,0) hex: Shifted slightly, but still closest to (0,0) position: Shifted more, now closer to the (0,1) hex. Whew!
David M. said: Played around with this a bit today, since I've never worked with hex maps and it sounded interesting. Kind of regretting it now, lol, but here we go. Wow, David, thanks for this, so much.&nbsp; I'm sorry I did not reply immediately, but I was consumed by the PAI sandbox lag everyone was experiencing.&nbsp; I'm preparing for a new job in a couple of days, so it might be a week or more until I get back to this. Thanks and all the best, -- Tim