Helper for converting Dota 2 in-game coordinates to pixel coordinates for any image.
-
-
Save grschafer/6708981 to your computer and use it in GitHub Desktop.
<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.
@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!
@grschafer, thanks for interesting tool! I have one question: where did you find towers coordinates?