paper.js - achieving smoother edges with closed paths - javascript
This is a box with an irregular shape that I have generated:
And this is the end effect I'd like to achieve (note the smooth edges):
Here's the code for my sharp version:
var path1 = new Path({
segments: [[123, 6], [290, 6], [304, 142], [112, 142]],
strokeColor: 'white',
closed: true,
strokeWidth: 3,
strokeJoin: 'round'
});
Thing is, I'm already using the strokeJoin: 'round' option and the difference is hardly noticeable with a stroke width of 3px. It's a small thing but could turn into a game breaker as there are going to be multiple objects like this and the difference is huge.
Is there any way to achieve that with paper.js without overdoing it?
As markE mentioned, strokeJoin only changes the canvas style of a path's stroke. Paper.js does not come with a corner-rounding function, you'll have to make your own.
Here's a quick function that you can use a starting point. It will negatively offset the points of a polygon by a given distance and add the appropriate handles.
function roundPath(path,radius) {
var segments = path.segments.slice(0);
path.segments = [];
for(var i = 0, l = segments.length; i < l; i++) {
var curPoint = segments[i].point;
var nextPoint = segments[i + 1 == l ? 0 : i + 1].point;
var prevPoint = segments[i - 1 < 0 ? segments.length - 1 : i - 1].point;
var nextDelta = curPoint - nextPoint;
var prevDelta = curPoint - prevPoint;
nextDelta.length = radius;
prevDelta.length = radius;
path.add({
point:curPoint - prevDelta,
handleOut: prevDelta/2
});
path.add({
point:curPoint - nextDelta,
handleIn: nextDelta/2
});
}
path.closed = true;
return path;
}
Here it is in action.
I was looking for an exact implementation, as described here: http://shanfanhuang.com/everything/2015/10/27/rounding-corners
My implementation works as follows:
curPoint is the corner, prevPoint and nextPoint as above
nextNorm and prevNorm are the normalized versions of the points
angle is the angle of the corner, derived from the dot product
delta is the distance from the corner points to where the control points need to be inserted, this is derived from a right triangle formed by the control point, curPoint and the center of the corner arc. The corner is a half angle, and the side opposing that corner is the radius
prevDelta and nextDelta are the new endpoints of the sides, between those an arc is inserted
through is a point halfway on the arc, found by getting the hypotenuse of the above triangle and subtracting the radius.
var segments = path.segments.slice(0);
path.segments = [];
for(var i = 0, l = segments.length; i < l; i++) {
var curPoint = segments[i].point;
var nextPoint = segments[i + 1 == l ? 0 : i + 1].point;
var prevPoint = segments[i - 1 < 0 ? segments.length - 1 : i - 1].point;
var nextNorm = (curPoint - nextPoint).normalize();
var prevNorm = (curPoint - prevPoint).normalize();
var angle = Math.acos(nextNorm.dot(prevNorm));
var delta = radius/Math.tan(angle/2);
var prevDelta = prevNorm.normalize(delta);
var nextDelta = nextNorm.normalize(delta);
var through = curPoint - (prevNorm + nextNorm).normalize(Math.sqrt(delta*delta + radius*radius) - radius);
path.add(curPoint - prevDelta);
path.arcTo(through, curPoint - nextDelta);
}
Related
Function that generates a path along other path, from a starting to an end point
const coords = [ { name: "Rijnstraat vervolg", points: [ [695, 500], [680, 480], [580, 475], [520, 460], ], width: 10, types: [types.car, types.truck, types.pedestrian, types.bike], oneway: true, }, ... ] I have an array that looks like the above and I want to make a function that generates a path (along the other paths, which are the black lines in the image) from a black or gray circle to another black or gray circle. So I want the function to take in a start and end point (black or gray circle) and return an array of points that follow the already existings paths. (Which are sort of like roads) And the function can be described as someone who is trying to get to somewhere. I already tried a recursive function that looks like this: function calculatePathToShop(startPoint, shopPoint) { const targetShopPoint = findClosestPointOnPath(shopPoint); const targetPathIndex = findPathByPoint(targetShopPoint); const connectedPaths = calculateConnectedPaths(targetPathIndex); let startPathIndex = -1; connectedPaths.forEach(path => { const pathPoints = coords[path].points; pathPoints.forEach(pathPoint => { if (comparePoints(startPoint.point, pathPoint)) startPathIndex = path; }); }); if (startPathIndex == -1) return false; let startPathPoints = coords[startPathIndex].points; let targetPathPoints = coords[targetPathIndex].points; if (!comparePoints(startPoint.point, startPathPoints[0])) startPathPoints.reverse(); ctx.strokeStyle = "rgba(255, 0, 0, .05)"; } This one generated a path (along the existing ones) to a shop point, which is almost the same as a gray point. But this worked for some starting points, but the rest would just straight up fail So does anyone know an algorithm, or has a function/solution that I can use to generate the path that someone can walk along the road (the black lines in the image) Full coords array, and part of my already existing code is found here: https://raw.githubusercontent.com/CodeFoxDev/people-simulation/main/func/paths.js (The rest of the code is in the github repo itself)
Fixed step interpolation To interpolate a line segment you divide the vector from the start pointing to the end by the number of steps. EG steps = 100; start = {x: 50, y: 100} end = {x: 150, y: 300} step = {x: (end.x - start.x) / steps, y: (end.y - start.y) / steps}; Then loop that number of steps adding the vector to a position initialized to the start point. points = []; // array of interpolated points point = {...start} // set start position. while (steps--) { points.push({...point}); point.x += vec.x; point.y += vec.y; } points.push({...end}); // last point at end This will create different spacing for different line lengths. Fixed distance interpolation To get a constant spacing between points you will need to use the lines' length to get the number of steps. pixelsPerStep = 2; // distance between points. start = {x: 50, y: 100} end = {x: 150, y: 300} step = {x: end.x - start.x, y: end.y - start.y}; lineSteps = Math.hypot(step.x, step.y) / pixelsPerStep; points = []; // array of interpolated points for (i = 0; i < lineSteps ; i += 1) { u = i / lineSteps; points.push({x: start.x + step.x * u, y: start.y + step.y * u}); } // check to add end point Note that the last point may or may not be at the correct distance. Due to rounding errors in floating point numbers you will need to check if the last point is close to the correct spacing and whether or not to include it. eg from code above // add last point if within (0.01 * pixelsPerStep) pixels of correct spacing if (Math.abs(lineSteps - i) < 0.01) { points.push({...end}); } Note Use the overflow lineSteps - i when interpolating many line segments, to carry the correct start offset to each subsequent line segment. Example The code below is an example of a constant spaced set of points interpolated from another set of points. The example draws the new points in black dots. The original points are rendered in red. Note that the distance between new points is constant and thus may not fall on the original (red) points. Note that there is a check at the end to test if a last point should be added. const ctx = canvas.getContext("2d"); const P2 = (x, y) => ({x, y}); const points = [ P2(100,90), P2(300,210), P2(350,110), P2(50,10), P2(6,219), ]; const interpolatedPoints = interpolatePath(points, 35); drawPoints(interpolatedPoints, 2); ctx.fillStyle = "RED"; drawPoints(points); function drawPoints(points, size = 1) { ctx.beginPath(); for (const p of points) { ctx.rect(p.x - size, p.y - size, size * 2 + 1, size * 2 + 1); } ctx.fill(); } function interpolatePath(path, pixelStep) { const res = []; var p2, i = 1, overflow = 0; while (i < path.length) { const p1 = path[i - 1]; p2 = path[i]; const dx = p2.x - p1.x; const dy = p2.y - p1.y; const len = Math.hypot(dx, dy) / pixelStep; let j = overflow; while (j < len) { const u = j / len; res.push(P2(p1.x + dx * u, p1.y + dy * u)); j++; } overflow = j - len; i++; } // add last point if close to correct distance if (Math.abs(overflow) < 0.01) { res.push(P2(p2.x, p2.y)); } return res; } <canvas id="canvas" width="400" height="400"></canvas>
Animated footsteps in html svg
Is it possible to create small animated footsteps in HTML SVG or Canvas. I am just starting out with these technologies, and it is very much necessary for a small project i am working on. My current idea was to create and use a "gif" of animated footsteps. But i would like to know if it can be achieved in any other way through HTML/CSS/JS PS : The footsteps i keep mentioning should be similar to the footsteps that appear on the "Marauders Map" in harry potter Movies. Thanks for any help
Walk about. I have never seen the movie you talk about so I am guessing what you are after. To do it on the canvas is easy, and I am more than happy to write an example of how it's done for you. Draw a foot You need an image of a foot, or some way to draw a foot. I used a set of shapes I happened to have to draw a foot. Then create a function that draws the foot at a location and in a direction. You also need to specify if its a left or right step. If you have a foot image you want to use just replace the code in the drawFoot function after the comment // draw the foot... with ctx.drawImage(footImage,-footImage.width / 2, -footImage.height / 2); You may have to scale the image, and the foot image should be of the left foot toes pointing to the right of screen Path The path to walk along is just an array of points, [x,y,x,y...] in order that define the path to travel along, They are just a guide as each foot step is a fixed distance apart and the points can be any distance appart. Animate Each step is some time apart (demo is 500ms or half a second). I use setTimeout to call the function to draw the next step. When the function is called I find the next position on the path and draw the next foot step. When the walk has gone past the end of the path I clear the canvas and restart. Demo It's self explanatory in the demo. I track two positions on the path, one is where the foot step is, and the other is just ahead and used to get the direction the foot should be drawn. The function that gets the distance along the path is recursive because the path points are not uniform in distance apart, so may have to skip line segments if the distance to travel is greater than the current or next or few line segments. // create a canvas and add it to the DOM var createImage=function(w,h){var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i;} var canvas = createImage(1024,512); var ctx = canvas.ctx; document.body.appendChild(canvas); // shapes for the foot. Left foot const foot = [ [50.86759744022923,-21.798383338076917,54.16000854997335,-23.917474843549847,57.778065334829385,-25.310771525314685,61.579706305344985,-24.823762596975733,65.0168431653022,-22.69100336700319,65.22736925598322,-19.777045647294724,63.09649708656406,-16.826669881834157,59.66512409715465,-15.356252875428147,56.12216018899968,-14.92259970211097,52.06404407417057,-16.231839553378,50.2945579442406,-18.938589556263246], [60.12039562389232,-12.45714817900668,63.92394094034509,-14.994440399059425,68.75013312287521,-15.737202635924493,73.10937504616481,-14.878459739068003,76.36064492186433,-12.559833524837757,77.6863729180915,-9.181208344485064,75.4625672565435,-5.673231626251427,71.79886053681197,-3.7381471592011817,66.8618942467243,-3.4903609701416993,62.29264392518654,-5.1248680438596885,58.98975517513061,-8.760952968033395], [65.57414109270752,1.1797411270282658,69.37768640916029,-1.3575510930244814,74.20387859169041,-2.1003133298895467,78.56312051498001,-1.241570433033059,81.81439039067953,1.0770557811971881,83.14011838690669,4.455680961549877,80.9163127253587,7.963657679783514,77.25260600562717,9.898742146833756,72.3156397155395,10.146528335893239,67.74638939400174,8.512021262175251,64.44350064394581,4.875936338001546], [65.89915812212375,15.917267748549033,69.97688522977245,12.635767809535894,76.25232402290966,11.736330263416008,81.47328014710077,12.477566678331382,86.09545897877996,15.579569438835726,86.99032987455637,21.425830739951824,83.82647235747945,24.97297628549917,79.18353918133074,27.064789354098487,73.69821507617947,27.23418460503707,68.46309469655478,24.972976285499172,64.88602415530316,20.55351481505123], [58.48881251239855,36.09589759380796,65.7080603859302,29.82038065752831,74.19222753183148,28.331948884004674,82.75081761131048,31.085582242549528,88.10923922724437,34.28575070762116,91.45825273720305,41.65358042953028,87.067323913035,47.45853718012533,79.77391671356942,50.28659303297933,71.73628428966856,50.06332546564875,64.14518700042888,47.45853718012533,60.12637078847845,42.69549574373964], [-73.48953442580058,20.579088801900756,-80.48690958722098,13.959950657259256,-81.93681598574045,6.269142804242765,-81.49554012532147,-1.6107832746678348,-77.90207999991593,-9.181527272091415,-71.6611785393426,-14.98115303708649,-64.60076477263831,-17.880965834138024,-57.35123278004056,-19.078714598132443,-50.09111381970131,-19.902008890566687,-42.96765884171789,-19.08249722231963,-35.655087439697766,-18.51514254492067,-28.90987071615029,-18.578181953551955,-21.74774447703016,-19.60773669210723,-14.309090001741001,-20.364210136314323,-6.933479190821032,-21.688037717705875,0.33383104043200396,-22.888118253462963,7.772483543580581,-23.77067027395373,15.274173171058239,-24.338024951681817,22.460665755024706,-24.590182586206954,29.710197747622452,-23.707630865368966,36.8557533915613,-22.565778766908277,44.16832856198768,-19.28772830000901,51.48089996424725,-14.370654426435763,56.713170880643965,-7.499358885625703,60.24016927073608,-0.41616112008138906,61.75311234056134,6.518193267525415,62.38350642684393,13.515567625813127,61.67231220934209,20.50500542283382,59.08769637136125,27.56541945045329,54.35974072401175,34.87799085169213,48.686193947196124,39.41682827314464,41.87793781501736,42.379680478815025,34.313208779263185,43.26223219965301,27.063676786665432,42.25360166155246,19.625026568173826,38.28211891778152,13.457927624759604,31.720792179781256,9.486440924356895,25.290772320002496,6.019273449215143,18.7346738223298,0.21964785513691965,13.565442314564446,-5.832135373466421,9.467880753530935,-12.632472285211591,8.882365666622222,-19.188577231399584,11.277861070017005,-26.31203040678842,14.49287091019486,-32.99420772170462,17.833959567652954,-39.2981485848331,20.670732956060768,-45.854247082486715,23.192309301312157,-52.47338498877162,24.89437333435685,-59.5968381641068,25.020452151619416,-67.33461307855538,23.697386555044208], ] const footScale = 0.2; // how big the foot is const stepLen = 60; // distance between steps var stepCount = 0; // current set num so left and right can be known var stepTime = 500; // time ms between steps // path to walk const path = [56.20191403126277,162.4451015086347,83.38688266344644,172.80127602121507,107.98280549274708,192.86637096042372,121.57528916149568,221.34586055208607,124.81159479691195,256.2979614145819,141.64038410107662,293.8391067854107,168.82535143857336,311.9624183437419,203.77745230106916,336.5583411729056,238.0822920364817,344.9727358249879,278.2124819156436,341.0891690624884,316.40088841355566,329.43846877498976,343.58585575105235,310.6678960895754,370.77082308854904,275.7157952270795,359.12012280105046,244.64726112708325,344.23311687813566,207.10611575625444,355.23655603855093,168.9177092583423,394.0722236635463,137.2019140312628,438.0859803052077,137.84917515834604,487.27782596353507,174.0957982750084,507.9901820301992,221.9931216791693,513.1682710468652,269.243183956247,500.87030963228347,318.43502961457443,480.1579535656192,354.68165273123674,453.62744426338134,396.86543776550707,414.1445200788371,427.9340428271046,372.7198079555102,447.3518767949864,320.2916566617712,442.173787778395,272.39433325761024,427.9340429825634,218.02439858261675,441.5265266513118,185.66134222845398,472.59506075130815,160.418158272207,514.6670340117198,168.2291881671332,557.5405924870362,200.59229872785795,598.9654951914354,232.9553551615553,627.4449850627141,273.08554504131894,651.3936467669101,320.3356073183967,663.0443470544095,368.2329307225576,663.6916081814927,417.4247763808851,649.4518633856611,460.7912718954633,626.150462810664,509.33585642670744,593.1401453294179,530.6954736204549,556.8935222127556,559.9273870166451,517.9197870310509,582.4287517306153,484.11964343037323,597.1560832290169,459.03222473087396,621.0274949086466,438.11039022321154,651.3689440081158,429.43667093843567,686.3731150817684,432.05029606733103,726.1878666750503,421.6902139845064,748.5744620042316,397.8927935245363,778.6337708564557,367.5111094723503,792.6287871481064,335.0802046803193,795.4641381478963,294.8623601925252,790.3177294792127,255.26933447013639,776.3228370821212,225.344431214269,746.3711518226298,192.73203550406103,713.7991149596966,199.06094085265394,674.3068624609349,207.5062077919911,638.4763261746227,190.31310645331482,613.6509940547375,146.74931837304698,621.5992452450397,103.454341485492,665.5124383180124,60.96567428151931,716.1845355322713,48.49595708249788,763.6383682758693,51.23726133320403,810.1045243122669,71.53440096982465,842.407749982487,97.97907893142005,879.5993779794437,131.14717279570036,903.6790094213126,175.24174017377706,915.9471803279671,219.31612086267396,902.1335310600084,270.1561321514687,880.1365756762476,315.0232456643523,884.5103070340778,370.89556334366307,909.7723644212043,407.9691345807976,947.0675376346722,439.8492118274288,990.6429384281869,439.26727537005956,1036.8675099917996,414.23364852545194,1070.3264506272462,387.0500494883014,1100.6074853525497,351.4546920217324,1119.943854180156,306.7958514306488,1128.5371035594999,259.67124425611814,1122.6651029017848,208.79760059460207,1106.8898009575623,162.16340658911932,1082.4004208812885,108.81054339506417,1050.2046949092428,81.72759371897288,1016.627194271211,46.42875173061529]; const pLen = path.length; // fix the path so it starts at zero for(var i = 2; i < pLen; i += 2){ path[i] -= path[0]; path[i+1] -= path[1]; } path[0] = path[1] = 0; // tracks the foot pos var pos = { x : path[0], y : path[1], index : 0, }; // tracks the foot pointing to pos var pos1 = { x : path[0], y : path[1], index : 0, }; // get a distance alone the path function getDistOnPath(dist,pos){ var nx = path[(pos.index + 2) % pLen] - pos.x; var ny = path[(pos.index + 3) % pLen] - pos.y; var d = Math.hypot(nx, ny); if(d > dist){ pos.x += (nx / d) * dist; pos.y += (ny / d) * dist; return pos; } dist -= d; pos.index += 2; pos.x = path[pos.index % pLen]; pos.y = path[(pos.index + 1) % pLen]; return getDistOnPath(dist, pos); } function drawFoot(x,y,dir,left){ var i,j,shape; var xdx = Math.cos(dir) * footScale; var xdy = Math.sin(dir) * footScale; if(left){ ctx.setTransform(xdx, xdy, -xdy, xdx, x + xdy * 50, y - xdx * 50); ctx.rotate(-0.1); // make the foot turn out a bit }else{ ctx.setTransform(xdx, xdy, -xdy, xdx, x - xdy * 50, y + xdx * 50); ctx.rotate(-0.1); // make the foot turn out a bit ctx.scale(1,-1); // right foot needs to be mirrored } // draw the foot as a set of paths points ctx.beginPath(); for(j = 0; j < foot.length; j ++){ shape = foot[j]; i = 0; ctx.moveTo(shape[i++],shape[i++]); while(i < shape.length){ ctx.lineTo(shape[i++],shape[i++]); } ctx.closePath(); } ctx.fill(); } ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0,0,canvas.width,canvas.height); pos1 = getDistOnPath(stepLen/10,pos1); // put the second pos infront so that a direction can be found function drawStep(){ if(pos.index > pLen){ // if past end of path clear and restart ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(0,0,canvas.width,canvas.height); pos.index = 0; pos1.index = 0; pos1.x = pos.x = path[0]; pos1.y = pos.y = path[1]; pos1 = getDistOnPath(stepLen/10,pos1); } pos = getDistOnPath(stepLen,pos); pos1 = getDistOnPath(stepLen,pos1); drawFoot(pos.x,pos.y,Math.atan2(pos1.y - pos.y, pos1.x - pos.x),(stepCount++) % 2 === 0); setTimeout(drawStep,stepTime); } drawStep(); Note some of the code is ES6 and will require babel (or equivilent) to run on legacy browsers.
Draw "Squiggly" line along a curve in javascript
This is a bit complicated to describe, so please bear with me. I'm using the HTML5 canvas to extend a diagramming tool (Diagramo). It implements multiple types of line, straight, jagged (right angle) and curved (cubic or quadratic). These lines can be solid, dotted or dashed. The new feature I am implementing is a "squiggly" line, where instead of following a constant path, the line zigzags back and forth across the desired target path in smooth arcs. Below is an example of this that is correct. This works in most cases, however, in certain edge cases it does not. The implementation is to take the curve, use the quadratic or cubic functions to estimate equidistance points along the line, and draw squiggles along these straight lines by placing control points on either side of the straight line (alternating) and drawing multiple cubic curves. The issues occur when the line is relatively short, and doubles back on itself close to the origin. An example is below, this happens on longer lines too - the critical point is that there is a very small sharp curve immediately after the origin. In this situation the algorithm picks the first point after the sharp curve, in some cases immediately next to the origin, and considers that the first segment. Each squiggle has a minimum/maximum pixel length of 8px/14px (which I can change, but much below that and it becomes too sharp, and above becomes too wavy) the code tries to find the right sized squiggle for the line segment to fit with the minimum empty space, which is then filled by a straight line. I'm hoping there is a solution to this that can account for sharply curved lines, if I know all points along a line can I choose control points that alternate either side of the line, perpendicular too it? Would one option be to consider a point i and the points i-1 and i+1 and use that to determine the orientation of the line, and thus pick control points? Code follows below //fragment is either Cubic or Quadratic curve. paint(fragment){ var length = fragment.getLength(); var points = Util.equidistancePoints(fragment, length < 100 ? (length < 50 ? 3: 5): 11); points.splice(0, 1); //remove origin as that is the initial point of the delegate. //points.splice(0, 1); delegate.paint(context, points); } /** * * #param {QuadCurve} or {CubicCurbe} curve * #param {Number} m the number of points * #return [Point] a set of equidistance points along the polyline of points * #author Zack * #href http://math.stackexchange.com/questions/321293/find-coordinates-of-equidistant-points-in-bezier-curve */ equidistancePoints: function(curve, m){ var points = curve.getPoints(0.001); // Get fractional arclengths along polyline var n = points.length; var s = 1.0/(n-1); var dd = []; var cc = []; var QQ = []; function findIndex(dd, d){ var i = 0; for (var j = 0 ; j < dd.length ; j++){ if (d > dd[j]) { i = j; } else{ return i; } } return i; }; dd.push(0); cc.push(0); for (var i = 0; i < n; i++){ if(i >0) { cc.push(Util.distance(points[i], points[i - 1])); } } for (var i = 1 ; i < n ; i++) { dd.push(dd[i-1] + cc[i]); } for (var i = 1 ; i < n ; i++) { dd[i] = dd[i]/dd[n-1]; } var step = 1.0/(m-1); for (var r = 0 ; r < m ; r++){ var d = parseFloat(r)*step; var i = findIndex(dd, d); var u = (d - dd[i]) / (dd[i+1] - dd[i]); var t = (i + u)*s; QQ[r] = curve.getPoint(t); } return QQ; } SquigglyLineDelegate.prototype = { constructor: SquigglyLineDelegate, paint: function(context, points){ var squiggles = 0; var STEP = 0.1; var useStart = false; var bestSquiggles = -1; var bestA = 0; var distance = Util.distance(points[0], this.start); for(var a = SquigglyLineDelegate.MIN_SQUIGGLE_LENGTH; a < SquigglyLineDelegate.MAX_SQUIGGLE_LENGTH; a += STEP){ squiggles = distance / a; var diff = Math.abs(Math.floor(squiggles) - squiggles); if(diff < bestSquiggles || bestSquiggles == -1){ bestA = a; bestSquiggles = diff; } } squiggles = distance / bestA; for(var i = 0; i < points.length; i++){ context.beginPath(); var point = points[i]; for(var s = 0; s < squiggles-1; s++){ var start = Util.point_on_segment(this.start, point, s * bestA); var end = Util.point_on_segment(this.start, point, (s + 1) * bestA); var mid = Util.point_on_segment(this.start, point, (s + 0.5) * bestA); end.style.lineWidth = 1; var line1 = new Line(Util.point_on_segment(mid, end, -this.squiggleWidth), Util.point_on_segment(mid, end, this.squiggleWidth)); var mid1 = Util.getMiddle(line1.startPoint, line1.endPoint); line1.transform(Matrix.translationMatrix(-mid1.x, -mid1.y)); line1.transform(Matrix.rotationMatrix(radians = 90 * (Math.PI/180))); line1.transform(Matrix.translationMatrix(mid1.x, mid1.y)); var control1 = useStart ? line1.startPoint : line1.endPoint; var curve = new QuadCurve(start, control1, end); curve.style = null; curve.paint(context); useStart = !useStart; } this.start = point; context.lineTo(point.x, point.y); context.stroke(); } } }
Finding the distance between two hexagons
I have two hexagons which I am trying to make snap together when the edges hit a certain tolerance. How can I find which edges are the closest? Here is the code returning the two closest Hexagons: Canvas.getClosestPiece = function(){ var current = {}; current.x = selection.MidPoint.X; current.y = selection.MidPoint.Y; smallestDistance = null; closestHex = null; hexagons.forEach(function(hexagon){ if(hexagon !== selection){ testPiece = {}; testPiece.x = hexagon.MidPoint.X; testPiece.y = hexagon.MidPoint.Y; if((lineDistance(current, testPiece) < smallestDistance) || smallestDistance === null){ smallestDistance = lineDistance(current, testPiece) closestHex = hexagon hexagons.forEach(function(hexagon){ hexagon.lineColor = 'grey' }) hexagon.lineColor = 'red'; } } }) // console.log(smallestDistance) return [selection, closestHex] } Distance between two hexagon midpoints: function lineDistance( point1, point2 ){ var xs = 0; var ys = 0; xs = point2.x - point1.x; xs = xs * xs; ys = point2.y - point1.y; ys = ys * ys; return Math.sqrt( xs + ys ); } And here is a standard point array for one of the hexagons that getClosestPiece returns: Point {X: 658, Y: 284} Point {X: 704, Y: 304} Point {X: 704, Y: 354} Point {X: 658, Y: 375} Point {X: 613, Y: 354} Point {X: 613, Y: 304}
If your have 2 points with their coordinate like p1(x1, y1) and p2(x2, y2). You can do this: var disptance = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
For calculating if to snap, see the other answers. As to where to snap (which edges), which I think is your real question: calculate the relative angle using atan2(midy1-midy2, midx1-midx2). You get a value in radians, which describes the angle of the connection line between the hexes. 0 = horizontal line. Calculate Math.floor(value*6/(2*pi)) --> you get a number between 0..5 denoting the edge pairing. If your hexes are rotatable, you need to add/substract the rotatins (in rad) to/from value. (The signs are best figured out on a piece of paper). edit: regarding your distance calculation, it is advisable to work with the square of the distance as long as possible (e.g. compare x^2+y^2 against threshold^2), to avoid the expensive Math.sqrt operation. Especially when testing distance against a multitude of other objects.
Use Euclian Distance formula dist=sqrt((x2-xq)^2 + (y2-y1)^2)
to find which edges are the closest you hav to say us that how do you have information of the edge lines of each hexagon. here, i assume they are accessible through an array as a property of each hexagon. so we have 6 edges (edges[0] to edges[5]) for each hexagon. we can find closest edges by looping through them and measuring the distance between center of each two edges. a sample code will look like this: var dMin=-1, iMin=-1, jMin=-1; //info about the min distance for(var i=0; i<5; i++) //loop through hexagon1.edges { var p1 = midPointOfLine( hexagon1.edges[i] ); //center of this edge line for(var j=0; j<5; j++) //loop through hexagon2.edges { var p2 = midPointOfLine( hexagon2.edges[j] ); //center of this edge line var d = getDistance(p1, p2); //get distance of two points if (d<dMin || dMin==-1) {dMin=d; iMin=i; jMin=j;} //store the info about the min distance } } function midPointOfLine(edge) // return new point( X=(X1+X2)/2 , Y=(Y1+Y2)/2 ) { var mp; //define a new point mp.X = (edge.startPoint.X + edge.endPoint.X) / 2; mp.Y = (edge.startPoint.Y + edge.endPoint.Y) / 2; return mp; } function getDistance(p1, p2) //return sqrt( (X2-X1)^2 + (Y2-Y1)^2 ) { return Math.sqrt( Math.pow(p2.X - p1.X, 2) + Math.pow(p2.Y - p1.Y, 2) ); } In Summary: Check distance between center of each edge of hexagon1 and center of each edge of hexagon2. The center of each edge is mid point of its start and end points: ( (x1+x2)/2, (y1+y2)/2 ). The distance of two points can be calculated from sqrt(dx*dx + dy*dy) formula.
Point in Polygon falsely detected
Derived from this: How to tackle diagonally stacked, rounded image background element hovers? I made imagemap areas and transformed them for my case, but, now there is a problem with point in polygon hit detection. It appears that only the bottom right quadrant is always correct, but, only if looking outside the ring - inside the detection might be still be incorrect. Other quadrants, outside the ring, occasionally report a positive hit where it should be false. Fiddle: http://jsfiddle.net/psycketom/9J4dx/1/ The red lines are drawn from the polygon that's generated from data-map. The blue line represents the polygon we're currently checking. The point in polygon function comes from: https://github.com/substack/point-in-polygon var pointInPolygon = function(point, vs) { // ray-casting algorithm based on // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html var x = point[0], y = point[1]; var inside = false; for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) { var xi = vs[i][0], yi = vs[i][1]; var xj = vs[j][0], yj = vs[j][1]; var intersect = ((yi > y) != (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi); if (intersect) inside = !inside; } return inside; }; I cannot seem to understand what's the problem here.
Your mapToPolygon function doesn't convert the parsed points from string to number. Because of this, the pointInPolygon function ends up comparing the strings of the coordinates, not the actual coordinates. Using a parseInt on line 31 of the fiddle fixes the problem.
Create an off-screen canvas and use the context's .isPointInPath(x, y) function. Loop through all of your polygons (in your example you would loop through them in reverse because you have smallest last. The smallest would be the highest level / greatest z-index). On you get a hit (isPointInPath returns true) stop. Something like... var offcanvas = document.createElement("canvas"); ... var x = e.pageX - $ages.offset().left; var y = e.pageY - $ages.offset().top; revlayers.each(function() { var $elm = $(this); var poly = $elm.data("polygon"); var ctx = offcanvas.getContext("2d"); if(poly.length > 0) { ctx.beginPath(); ctx.moveTo(poly[0][0], poly[0][1]); for(var i=1; i<poly.length; i++) { ctx.lineTo(poly[i][0], poly[i][1]); } if(ctx.isPointInPath(x, y)) { hit.text($elm.attr("href")); return false; // end the .each() loop } } })