Helper for converting Dota 2 in-game coordinates to pixel coordinates for any image.
Last active
May 4, 2022 05:27
-
-
Save grschafer/6708981 to your computer and use it in GitHub Desktop.
Convert Dota 2 in-game coordinates to image coordinates
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
@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
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.