Skip to content

Instantly share code, notes, and snippets.

@grschafer
Last active May 4, 2022 05:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save grschafer/6708981 to your computer and use it in GitHub Desktop.
Save grschafer/6708981 to your computer and use it in GitHub Desktop.
Convert Dota 2 in-game coordinates to image coordinates

Helper for converting Dota 2 in-game coordinates to pixel coordinates for any image.

<html>
<head>
<style>
/*body { margin: 0; }*/
.strike { text-decoration: line-through; }
pre {
background-color: #f8f8f8;
border: 1px solid #ddd;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px;
}
canvas { position: relative; }
</style>
</head>
<body>
<h3>Purpose</h3>
<p>To provide code (scale/offset factors) for converting from in-game world coordinates to image coordinates</p>
<canvas id="canvas" width="10" height="10"></canvas>
<br />
<input type='button' name='reset' size='65' id='resetbtn' value='Reset to Step 2' disabled/> |
<input type='file' name='img' size='65' id='uploadimage' />
<div>
<h3>Instructions</h3>
<ol>
<li id="step1">Select a Dota2 map image file on your computer or paste a URL</li>
<li id="step2">Left-click on the Radiant ancient</li>
<li id="step3">Left-click on the Dire ancient (towers will be drawn, click Reset if they look wrong, otherwise continue)</li>
<li id="step4">Use the provided code or given offset/scale values for converting from World coordinates to Image coordinates</li>
</ol>
<table>
<tr><td>Offset X: <span class="offset_x">FINISH STEP 3</span></td></tr>
<tr><td>Offset Y: <span class="offset_y">FINISH STEP 3</span></td></tr>
<tr><td>Scale X: <span class="scale_x">FINISH STEP 3</span></td></tr>
<tr><td>Scale Y: <span class="scale_y">FINISH STEP 3</span></td></tr>
</table>
<pre>
def imgCoordFromWorld(x,y):
return (8576.0 + x) * <span class="scale_x">FINISH STEP 3</span> + <span class="offset_x">FINISH STEP 3</span>, (8192.0 - y) * <span class="scale_y">FINISH STEP 3</span> + <span class="offset_y">FINISH STEP 3</span>
</pre>
<h3>Example Use</h3>
<pre>
ancient = world.find_by_dt('DT_DOTA_BaseNPC_Fort')[1]
print "entity name: {0}".format(ancient[(u'DT_BaseEntity', u'm_iName')])
# entity name: dota_badguys_fort
print "cellX:{0}, cellY:{1}, vecOrigin:{2}".format(ancient[(u'DT_DOTA_BaseNPC', u'm_cellX')],
ancient[(u'DT_DOTA_BaseNPC', u'm_cellY')],
ancient[(u'DT_DOTA_BaseNPC', u'm_vecOrigin')])
# cellX:171, cellY:167, vecOrigin:(24.0, 8.0)
worldCoords = coordFromCell(ancient)
print "worldCoords:{0}".format(worldCoords)
# worldCoords:(5528.0, 5000.0)
imgCoords = imgCoordFromWorld(*worldCoords)
print "imgCoords:{0}".format(imgCoords)
# imgCoords:(<span id="example_answer">ANSWER WILL SHOW HERE WHEN YOU FINISH STEP 3</span>)
</pre>
</div>
</body>
<script type="text/javascript">
var dots = [];
var img = new Image();
var imgWidth, imgHeight;
var upscale = 1.0;
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// extending the DOM -- eek! just want to try it
Element.prototype.addClass = function(cls) {
this.className = this.className + " " + cls;
}
Element.prototype.removeClass = function(cls) {
this.className = this.className.replace(new RegExp(cls, 'g'), "");
}
var forEach = Array.prototype.forEach;
function resetText() {
document.getElementById("step2").removeClass("strike");
document.getElementById("step3").removeClass("strike");
["offset_x", "offset_y", "scale_x", "scale_y"].forEach(function (attr) {
forEach.call(document.getElementsByClassName(attr), function(elem) {
elem.textContent = "FINISH STEP 3";
});
});
document.getElementById("example_answer").textContent = "ANSWER WILL SHOW HERE WHEN YOU FINISH STEP 3";
dots = [];
}
function reset() {
ctx.clearRect(0,0, canvas.width, canvas.height);
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
resetText();
}
function addImage(ev) {
resetText();
console.log(ev);
var f = document.getElementById("uploadimage").files[0],
url = window.URL || window.webkitURL,
src = url.createObjectURL(f);
img.src = src;
img.onload = function() {
// scale down too-large images
imgWidth = img.width;
imgHeight = img.height;
canvas.width = 600;
canvas.height = 600;
if (img.width > canvas.width) {
upscale = img.width / canvas.width;
imgWidth = canvas.width;
imgHeight *= canvas.width / img.width;
}
ctx.drawImage(img, 0, 0, imgWidth, imgHeight);
url.revokeObjectURL(src);
document.getElementById("step1").addClass("strike");
// allow marking of radiant/dire ancients by user
document.getElementById("canvas").addEventListener("click", drawDot, false);
// allow use of Reset button
document.getElementById("resetbtn").removeAttribute("disabled");
}
}
function drawDot(ev) {
var click_x = ev.pageX - canvas.offsetLeft;
var click_y = ev.pageY - canvas.offsetTop;
ctx.fillStyle = "rgba(255,255,255,1.0)";
ctx.fillRect(click_x - 5, click_y - 6, 8, 8);
// mult by upscale to map back into fullsize image coordinates
dots.push({x:click_x * upscale, y:click_y * upscale});
console.log('click pos: ' + click_x + ',' + click_y);
document.getElementById("step2").addClass("strike");
if (dots.length == 2) {
console.log(dots);
document.getElementById("step3").addClass("strike");
document.getElementById("canvas").removeEventListener("mousedown", drawDot, false);
computeScaleOffset();
}
}
var World = {};
World.width = 9216.0 - -8576.0;
World.height = 8192.0 - -7680.0;
World.shiftOrigin = function(x,y) {
return {x: x + 8576.0, y: 8192.0 - y};
}
World.rAncient = World.shiftOrigin(-5888.0, -5376.0);
World.dAncient = World.shiftOrigin(5528.0, 5000.0);
World.toImgCoords = function(x,y, scale) {
// TODO: throw error if scale/offset values undefined
var t = World.shiftOrigin(x,y);
return {x: scale * (t['x'] * World.scale_x + World.offset_x), y: scale * (t['y'] * World.scale_y + World.offset_y)};
}
World.toShrunkImgCoords = function(x,y) {
var t = World.shiftOrigin(x,y);
return {x: t['x'] * World.scale_x * scale + World.offset_x, y: t['y'] * World.scale_y * scale + World.offset_y};
}
World.fromImgCoords = function(x,y) {
// TODO
}
World.computeScaleOffset = function(rAncient, dAncient) {
World.scale_x = Math.abs((rAncient['x'] - dAncient['x']) / (World.rAncient['x'] - World.dAncient['x']));
World.scale_y = Math.abs((rAncient['y'] - dAncient['y']) / (World.rAncient['y'] - World.dAncient['y']));
console.log('scale: ' + World.scale_x + ' ' + World.scale_y);
World.offset_x = rAncient['x'] - World.rAncient['x'] * World.scale_x;
World.offset_y = rAncient['y'] - World.rAncient['y'] * World.scale_y;
console.log('offset: ' + World.offset_x + ' ' + World.offset_y);
}
function computeScaleOffset() {
World.computeScaleOffset(dots[0], dots[1]);
var topleft = World.toImgCoords(-8576.0, 8192.0, 1.0 / upscale);
var botright = World.toImgCoords(9216.0, -7680.0, 1.0 / upscale);
ctx.fillStyle = "rgba(0,255,0,0.3)";
ctx.fillRect(topleft['x'], topleft['y'], botright['x'] - topleft['x'], botright['y'] - topleft['y']);
["offset_x", "offset_y", "scale_x", "scale_y"].forEach(function (attr) {
forEach.call(document.getElementsByClassName(attr), function(elem) {
// mult by upscale to map back into fullsize image coordinates
elem.textContent = World[attr].toFixed(4);
});
});
// scale by upscale to convert back into fullsize image coordinates
var exAns = World.toImgCoords(5528.0, 5000.0, 1.0);
document.getElementById("example_answer").textContent = exAns['x'].toFixed(3) + "," + exAns['y'].toFixed(3);
drawTowers();
}
var towers = [[-3873.0, -6112.0], [-5392.0, -5168.0], [-4608.0, -4096.0], [-5680.0, -4880.0], [-6624.0, -3328.0], [-6096.0, 1840.0], [-6144.0, -832.0], [-1504.0, -1376.0], [-3512.0, -2776.0], [-560.0, -6096.0], [4928.0, -6080.0], [6276.0, 2984.0], [5280.0, 4432.0], [4960.0, 4784.0], [3504.0, 5776.0], [-4736.0, 6016.0], [0.0, 6016.0], [2496.0, 2112.0], [1024.0, 320.0], [6080.0, -1664.0], [6272.0, 256.0], [4224.0, 3712.0]];
function drawTowers() {
ctx.fillStyle = "rgba(0,0,255,0.8)";
var pos;
towers.forEach(function (tower) {
pos = World.toImgCoords(tower[0], tower[1], 1.0 / upscale);
//console.log('mapped ' + tower[0] + ',' + tower[1] + ' to ' + pos['x'] + ',' + pos['y']);
ctx.fillRect(pos['x'] - 4, pos['y'] - 4, 8, 8);
});
}
document.getElementById("uploadimage").addEventListener("change", addImage, false);
document.getElementById("resetbtn").addEventListener("click", reset, false);
//document.getElementById("canvas").addEventListener("click", tracePos, false);
//function tracePos(ev) {
// console.log(ev);
//}
</script>
</html>
@swimmwatch
Copy link

@grschafer, thanks for interesting tool! I have one question: where did you find towers coordinates?

var towers = [[-3873.0, -6112.0], [-5392.0, -5168.0], [-4608.0, -4096.0], [-5680.0, -4880.0], [-6624.0, -3328.0], [-6096.0, 1840.0], [-6144.0, -832.0], [-1504.0, -1376.0], [-3512.0, -2776.0], [-560.0, -6096.0], [4928.0, -6080.0], [6276.0, 2984.0], [5280.0, 4432.0], [4960.0, 4784.0], [3504.0, 5776.0], [-4736.0, 6016.0], [0.0, 6016.0], [2496.0, 2112.0], [1024.0, 320.0], [6080.0, -1664.0], [6272.0, 256.0], [4224.0, 3712.0]];

@grschafer
Copy link
Author

@grschafer, thanks for interesting tool! I have one question: where did you find towers coordinates?

Sorry @swimmwatch, this script was made long ago and I don't remember where I got those coordinates from. They might be from the Dota 2 Workshop Tools and the map editor Hammer.

Most likely these coordinates aren't accurate anymore. I haven't followed dota2 patch notes too closely the last few years, but I think towers have been moved several times.

@swimmwatch
Copy link

@grschafer, thanks for interesting tool! I have one question: where did you find towers coordinates?

Sorry @swimmwatch, this script was made long ago and I don't remember where I got those coordinates from. They might be from the Dota 2 Workshop Tools and the map editor Hammer.

Most likely these coordinates aren't accurate anymore. I haven't followed dota2 patch notes too closely the last few years, but I think towers have been moved several times.

Ok, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment