Reputation: 866
I've been working on my first real HTML5 canvas project for awhile now, and I can't seem to get things to line up as I'd wish. I recently came across the context.clip() function, and it seems to help, but has other unintended consequences, so I thought I'd put my problem out there.
My project is a map of hexagonal tiles, with borders representing continents and travel routes. Here is a good photo of what my map looks like:
What I noticed, is that if I use the clip() function before drawing each hex, the inner light green hexes don't seem so off-centered. Here is a close-up comparison:
The problem is, when I use this clip() function, I seem to lose my borders/routes overlay. My code draws all the hexes, THEN draws the thick borders so that they don't get drawn on top of.
I'm not 100% sure I understand what is happening here. Why is clip() killing my borders? My entire list of code is on this github repo: https://github.com/boknows/hex-map-game, but the 2 main functions I'll list here. drawHexGrid() does the math to place each individual hex, drawHex() draws the hex with the proper fill, and drawHexBorders() draws the borders/routes after.
HexagonGrid.prototype.drawHexGrid = function (rows, cols, originX, originY, isDebug) {
this.canvasOriginX = originX;
this.canvasOriginY = originY;
this.rows = rows;
this.cols = cols;
var currentHexX;
var currentHexY;
var debugText = "";
var offsetColumn = false;
var hexNum = 1;
for (var col = 0; col < cols; col++) {
for (var row = 0; row < rows; row++) {
if (!offsetColumn) {
currentHexX = (col * this.side) + originX;
currentHexY = (row * this.height) + originY;
} else {
currentHexX = col * this.side + originX;
currentHexY = (row * this.height) + originY + (this.height * 0.5);
}
if (isDebug) {
debugText = hexNum;
hexNum++;
}
if(map.data[row][col].type=="land"){
this.drawHex(currentHexX, currentHexY, "#99CC66", debugText, false, map.data[row][col].owner);
}else if(map.data[row][col].type=="water"){
this.drawHex(currentHexX, currentHexY, "#3333FF", "", false, map.data[row][col].owner);
}else if(map.data[row][col].type=="forest"){
this.drawHex(currentHexX, currentHexY, "#009900", debugText, false, map.data[row][col].owner);
}else if(map.data[row][col].type=="desert"){
this.drawHex(currentHexX, currentHexY, "#F5E8C1", debugText, false, map.data[row][col].owner);
}else if(map.data[row][col].type=="mountains"){
this.drawHex(currentHexX, currentHexY, "#996600", debugText, false, map.data[row][col].owner);
}
}
offsetColumn = !offsetColumn;
}
var offsetColumn = false;
for (var col = 0; col < cols; col++) { //Draw borders separately so they don't get overlapped by other graphics.
for (var row = 0; row < rows; row++) {
if (!offsetColumn) {
currentHexX = (col * this.side) + originX;
currentHexY = (row * this.height) + originY;
} else {
currentHexX = col * this.side + originX;
currentHexY = (row * this.height) + originY + (this.height * 0.5);
}
this.drawHexBorders(currentHexX, currentHexY);
}
offsetColumn = !offsetColumn;
}
};
HexagonGrid.prototype.drawHex = function (x0, y0, fillColor, debugText, highlight, highlightColor, owner) {
this.context.font="bold 12px Helvetica";
this.owner = owner;
this.context.strokeStyle = "#000000";
this.context.lineWidth = 1;
this.context.lineCap='round';
this.context.restore();
var tile = this.getSelectedTile(x0 + this.width - this.side, y0);
var numberOfSides = 6,
size = this.radius,
Xcenter = x0 + (this.width / 2),
Ycenter = y0 + (this.height / 2);
this.context.beginPath();
this.context.lineWidth = 1;
this.context.moveTo (Xcenter + size * Math.cos(0), Ycenter + size * Math.sin(0));
for (var i = 1; i <= numberOfSides;i += 1) {
this.context.lineTo (Xcenter + size * Math.cos(i * 2 * Math.PI / numberOfSides), Ycenter + size * Math.sin(i * 2 * Math.PI / numberOfSides));
}
if(typeof(map.data[tile.row][tile.column]) != "undefined"){
if (fillColor && highlight == false && map.data[tile.row][tile.column].type =="land") {
this.context.fillStyle = map.data[tile.row][tile.column].color;
}else{
this.context.fillStyle = fillColor;
}
}
if (highlight == true){
this.context.fillStyle = highlightColor;
}
this.context.fill();
this.context.closePath();
this.context.save();
this.context.clip();
this.context.lineWidth *= 2;
this.context.stroke();
if(map.data[tile.row][tile.column].type != "water"){
//Draw smaller hex inside bigger hex - v2
var numberOfSides = 6,
size = this.radius*0.7,
Xcenter = x0 + (this.width / 2),
Ycenter = y0 + (this.height / 2);
this.context.fillStyle = fillColor;
this.context.strokeStyle = map.data[tile.row][tile.column].color;
this.context.beginPath();
this.context.lineWidth = .5;
this.context.moveTo (Xcenter + size * Math.cos(0), Ycenter + size * Math.sin(0));
for (var i = 1; i <= numberOfSides;i += 1) {
this.context.lineTo (Xcenter + size * Math.cos(i * 2 * Math.PI / numberOfSides), Ycenter + size * Math.sin(i * 2 * Math.PI / numberOfSides));
}
this.context.fill();
this.context.closePath();
this.context.stroke();
//if defensive boost active, draw grey dotted hex inside of owners colored hex.
var index = 0;
for(var i=0;i<map.dataProp.users.length;i++){
if(map.dataProp.users[i]==map.data[tile.row][tile.column].owner){
index = i;
}
}
var defTrigger = false;
for(var i=0;i<map.dataProp.turnModifiers[index].length;i++){
if(map.dataProp.turnModifiers[index][i].type=="defensiveBoost"){
defTrigger = true;
}
}
if(defTrigger == true){
var numberOfSides = 6,
size = this.radius-12,
Xcenter = x0 + (this.width / 2),
Ycenter = y0 + (this.height / 2);
this.context.strokeStyle = "#929292"
this.context.beginPath();
this.context.lineWidth = 5;
this.context.moveTo (Xcenter + size * Math.cos(0), Ycenter + size * Math.sin(0));
for (var i = 1; i <= numberOfSides;i += 1) {
this.context.lineTo (Xcenter + size * Math.cos(i * 2 * Math.PI / numberOfSides), Ycenter + size * Math.sin(i * 2 * Math.PI / numberOfSides));
}
this.context.fill();
this.context.closePath();
this.context.stroke();
}
//Print number of units
this.context.textAlign="center";
this.context.textBaseline = "middle";
this.context.font = 'bold 13pt Arial';
//Code for contrasting text with background color
/*var clr = getContrastYIQ(map.data[tile.row][tile.column].color); //contrast against player color
var clr = getContrastYIQ(fillColor); //contrast against land color (fillColor)
this.context.fillStyle = clr;
*/
this.context.fillStyle = "#000000";
this.context.fillText(map.data[tile.row][tile.column].units, x0 + (this.width / 2) , y0 + (this.height / 2));
this.context.fillStyle = "";
}
};
HexagonGrid.prototype.drawHexBorders = function (x0, y0) {
var tile = this.getSelectedTile(x0 + this.width - this.side, y0);
if(map.data[tile.row][tile.column].s != ""){
this.context.beginPath();
this.context.lineWidth = 5;
this.context.strokeStyle=map.data[tile.row][tile.column].s;
this.context.moveTo(x0 + this.side, y0 + this.height);
this.context.lineTo(x0 + this.width - this.side, y0 + this.height);
this.context.stroke();
}
if(map.data[tile.row][tile.column].n != ""){
this.context.beginPath();
this.context.lineWidth = 5;
this.context.strokeStyle=map.data[tile.row][tile.column].n;
this.context.moveTo(x0 + this.side, y0);
this.context.lineTo(x0 + this.width - this.side, y0);
this.context.stroke();
}
if(map.data[tile.row][tile.column].ne != ""){
this.context.beginPath();
this.context.lineWidth = 5;
this.context.strokeStyle=map.data[tile.row][tile.column].ne;
this.context.moveTo(x0 + this.side, y0);
this.context.lineTo(x0 + this.width, y0 + (this.height / 2));
this.context.stroke();
}
if(map.data[tile.row][tile.column].se != ""){
this.context.beginPath();
this.context.lineWidth = 5;
this.context.strokeStyle=map.data[tile.row][tile.column].se;
this.context.moveTo(x0 + this.width, y0 + (this.height / 2));
this.context.lineTo(x0 + this.side, y0 + this.height);
this.context.stroke();
}
if(map.data[tile.row][tile.column].sw != ""){
this.context.beginPath();
this.context.lineWidth = 5;
this.context.strokeStyle=map.data[tile.row][tile.column].sw;
this.context.moveTo(x0 + this.width - this.side, y0 + this.height);
this.context.lineTo(x0, y0 + (this.height/2));
this.context.stroke();
}
if(map.data[tile.row][tile.column].nw != ""){
this.context.beginPath();
this.context.lineWidth = 5;
this.context.strokeStyle=map.data[tile.row][tile.column].nw;
this.context.moveTo(x0, y0 + (this.height/2));
this.context.lineTo(x0 + this.width - this.side, y0);
this.context.stroke();
}
};
//Recusivly step up to the body to calculate canvas offset.
HexagonGrid.prototype.getRelativeCanvasOffset = function() {
var x = 0, y = 0;
var layoutElement = this.canvas;
var bound = layoutElement.getBoundingClientRect();
if (layoutElement.offsetParent) {
do {
x += layoutElement.offsetLeft;
y += layoutElement.offsetTop;
} while (layoutElement = layoutElement.offsetParent);
return { x: bound.left, y: bound.top };
}
}
Upvotes: 0
Views: 156
Reputation: 105015
Disclaimer: I haven't looked at your code--you post a lot of code! (Stackoverflow encourages you to post a reduced snippet that illustrates your issue).
But could your issue be due to the way strokes are done?
Strokes are done half-inside & half-outside their defined path. So if you stroke a rect and then clearRect that same rect, you will still see part of the stroke--you will see the half-outside part of the stroke.
// draw a stroked rect
context.strokeRect(10,10,30,30);
// clear the same rect
context.clearRect(10,10,30,30);
// the outside half of the stroke is still visible
Inversely, clipping will prevent the half-outside part of the stroke from being drawn. Perhaps your missing hex border is the half-outside stroke that's not being drawn.
Upvotes: 1