Auto-adjustable line between canvas objects - javascript

My intention is to create a line with canvas that adjusts itself whenever the objects change position, so that it always has a correct shape.
I currently have the following (uses a quadratic curve to make the turn):
I have found a GoJS library that does exactly what I want, which is the following:
The problem is that the library is not open source, so I wanted to know if this is some kind of generic algorithm to do it, or I have to do the calculation manually to identify the positions of the objects and calculate the best trajectory.
As extra information, I am making use of KonvaJS which natively does not implement anything like this.

It depends on how much complexity you need. This is a fiddle that does the basic pathfinding you have in the animation (without rounding the corners).
It figures out what side the paths should start from, and turns that into the offset for the path start and end, by comparing the x and y distances of the two objects:
let startsVertical = Math.abs(dx) < Math.abs(dy);
if (startsVertical) {
anchorPointOffset = {x: 0, y: Math.sign(dy) * circleRadius};
} else {
anchorPointOffset = {x: Math.sign(dx) * circleRadius, y: 0};
}
let stationaryAnchorPoint = {
x: stationaryPosition.x + anchorPointOffset.x,
y: stationaryPosition.y + anchorPointOffset.y
};
let movingAnchorPoint = {
x: movingPosition.x - anchorPointOffset.x,
y: movingPosition.y - anchorPointOffset.y
};
If your shapes do not have the same width and height, you will need to use two variables, instead of circleRadius.
Then it calculates the center point and uses that to create the middle two points of the path.
if (startsVertical) {
middleA = {
x: stationaryAnchorPoint.x,
y: centerPoint.y
}
middleB = {
x: movingAnchorPoint.x,
y: centerPoint.y
}
} else {
middleA = {
x: centerPoint.x,
y: stationaryAnchorPoint.y
}
middleB = {
x: centerPoint.x,
y: movingAnchorPoint.y
}
}
Rounding the corners is a little more complicated, but there are many guides for that.

Related

How to create a random ground in matter.js

I am creating the ground of a game using a Perlin noise function. This gives me an array of vertices. I then add a vertex at the front that is {x:0 y: WORLD_HEIGHT} and another at the end of the array that is {x: WORLD_WIDTH y: WORLD_HEIGHT}. I am hoping that will give me a flat base with a random top.
How then do I add this into the matter.js world?
I am trying to create the ground using;
var terrain = Bodies.fromVertices(???, ???, vertexSets, {
isStatic: true
}, true);
but I don't know what to use for the ??? co-ordinates. I think they are supposed to represent the center of the object. However, I don't know what that is because it is noise. What I would like to do is specify the x & y of the first perlin noise vertex.
I am not even sure that given these vertices matter.js is creating a single body or multiple.
Is this the right way to approach it or there another way to do this? I am really struggling with the docs and the examples.
I use Matter.Body.setPosition(body, position) to override the center of mass and put the ground where I want it based on its bounds property.
const engine = Matter.Engine.create();
const render = Matter.Render.create({
element: document.body,
engine: engine,
});
const w = 300;
const h = 300;
const vertices = [
...[...Array(16)].map((_, i) => ({
x: i * 20,
y: ~~(Math.random() * 40),
})),
{x: w, y: 100},
{x: 0, y: 100},
];
const ground = Matter.Bodies.fromVertices(
w - 10, h - 10, // offset by 10 pixels for illustration
vertices,
{isStatic: true},
/* flagInternal =*/ true,
);
Matter.Body.setPosition(ground, {
x: w - ground.bounds.min.x,
y: h - ground.bounds.max.y + 110,
});
const {min: {x}, max: {y}} = ground.bounds;
console.log(x, y); // 10 120
Matter.Composite.add(engine.world, [ground]);
Matter.Render.run(render);
Matter.Runner.run(engine);
<script src="https://cdn.jsdelivr.net/npm/poly-decomp#0.3.0/build/decomp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
Without setPosition, you can see things jump around if you run this snippet a few times (just to reproduce OP's error with a concrete example):
const engine = Matter.Engine.create();
const render = Matter.Render.create({
element: document.body,
engine: engine,
});
const vertices = [
...[...Array(16)].map((_, i) => ({
x: i * 20,
y: ~~(Math.random() * 40),
})),
{x: 300, y: 100},
{x: 0, y: 100},
];
const ground = Matter.Bodies.fromVertices(
200, 100, vertices,
{isStatic: true},
/* flagInternal =*/ true,
);
Matter.Composite.add(engine.world, [ground]);
Matter.Render.run(render);
Matter.Runner.run(engine);
<script src="https://cdn.jsdelivr.net/npm/poly-decomp#0.3.0/build/decomp.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
I'm not using Perlin noise and there are some internal vertices that aren't properly detected in the above examples, but the result should be the same either way.
should be integers, all width and height of the noise texture. values at those x, y integer places can be floats... no problem.
and same width and height should go to terrain and values at that places will be the height of the terrain.

Can't Create Random Point in PaperJs per Documentation

I am using the latest version of PaperJs but when I run the following code from their sample the output is "NaN."
window.onload = function () {
paper.setup('myCanvas');
with (paper) {
// Create a point whose x is between 0 and 50,
// and y is between 0 and 100
var point = new Point(50, 100) * Point.random();
console.log(point);
}
}
The code works online in sketch.paperjs.org but doesn't work when I try locally via the code above or the following (also outputs "NaN"):
// Make the paper scope global, by injecting it into window:
paper.install(window);
window.onload = function () {
// Setup directly from canvas id:
paper.setup('myCanvas');
// Create a point whose x is between 0 and 50,
// and y is between 0 and 100
var point = new Point(50, 100) * Point.random();
console.log(point);
}
The following works, and all my other PaperJs code works; it's just that I can't seem to create a random point per the documentation.
console.log(new Point(50, 100), Point.random());
Outputs:
Point {x: 50, y: 100} Point {x: 0.8624748098043336, y: 0.8705165661914955}
The documenation: http://paperjs.org/tutorials/geometry/mathematical-operations/#random-values
Are you sure you use the paper.js language and not javascript?
Because the multiply operator can't be overloaded in javascript, you have to use pointA.multiply(pointB);.

Negative coordinates in a grid based game

I have written a small 2D game in javascript that uses a grid where the player starts at position [0,0] and can move an almost infinite distance in either direction.
Now I want to implement A* pathfinding, but I'm having some problems finding the best way to store the world with all it's different obstacles, enemies and terrain. This is what I have tried or thought about so far.
Array of arrays
Here I store the world in an array of arrays [x][y].
var world = [[]];
world[312][11] = 0;
world[312][12] = 0;
world[312][13] = 1;
world[312][14] = 1;
...
This works great with A* pathfinding! It's easy and very fast to access a specific coordinate and populate the world. In the example above I just store passable (0) or impassable (1) terrain, but I can store pretty much whatever I want there. However, this doesn't work very well with negative coordinates like if my players is at [-12][-230]. Negative keys in a javascript array isn't actually part of the array, they won't be included in world.length or world[3].length and from what I understand, it's overall bad practice and might have some impact on the performance as well. I read somewhere that if you are using negative keys in your array, you are doing it wrong.
I would still not pass the entire world into the A* function for obvious reasons. Just a small part close to my player, but the coordinates would correspond to the positions in the array which is easy to work with.
A separate array of arrays just for A* pathfinding
This is where I'm at right now. I have a separate 50x50 grid called pathMap = [[]], that is only used for pathfinding.
var pathMap = [[]];
pathMap[0][0] = 0;
pathMap[0][1] = 0;
pathMap[0][2] = 1;
pathMap[0][3] = 1;
...
It starts at pathMap[0][0] and goes to pathMap[50][50] and is working as an overlay on my current position where I (as the player) will always be in the center position. My real coordinates may be something like [-5195,323], but it translates to pathMap[25][25] and everything close to me is put on the pathMap in relation to my position.
Now this works, but it's a huge mess. All the translations from one coordinate to another back and forth makes my brain hurt. Also, when I get the path back from A*, I have to translate each step of it back to the actual position my element should move to in the real world. I also have to populate the same object into 2 different grids every update which hurts performance a bit as well.
Array of objects
I think this is where I want to be, but I have some issues with this as well.
var world = [];
world[0] = { x: -10, y: 3, impassable: 0 };
world[1] = { x: -10, y: 4, impassable: 0 };
world[2] = { x: -10, y: 5, impassable: 1 };
world[3] = { x: -10, y: 6, impassable: 1 };
...
Works great with negative x or y values! However, it's not as easy to find for instance [10,3] in this array. I have to loop through the entire array to look for an object where x == 10 and y == 3 instead of the very easy and fast approach world[10][3] in the first example. Also, I can't really rely on the coordinates being in the right order using this version, sorting becomes harder, as does other things that was a lot easier with the array of arrays.
Rebuild the game to always be on the positive side
I would prefer not to do this, but I have considered placing the players starting position at something like [1000000,1000000] instead, and making negative coordinates off limits. It seems like a failure if I have to remove the vision I have of endlessness just to make the pathfinding work with less code. I know there will always be some upper or lower limits anyways, but I just want to start at [0,0] and not some arbitrary coordinate for array related reasons.
Other?
In javascript, is there another option that works better and is not described above? I'm very open to suggestions!
Is there a best practice for similar cases?
You have three coordinates system you must distinguish :
the world coordinates.
the world model / path-finding (array) coordinates.
the screen coordinates.
The screen coordinates system depends upon :
the viewport = the canvas. (width, height in pixels).
a camera = (x,y) center in world coordinates + a viewWidth (in world coordinates).
To avoid headaches, build a small abstraction layer that will do the math for you.
You might want to use Object.defineProperty to define properties, that will provide a fluent interface.
var canvas = ... ;
var canvasWidth = canvas.width;
var canvasHeigth = canvas.heigth;
var world = {
width : 1000, // meters
height : 1000, // meters
tileSize : 0.5, // height and width of a tile, in meter
model : null, // 2D array sized ( width/tileSize, XtileSize )
};
// possibles world coordinates range from -width/2 to width/2 ; - height/2 height/2.
var camera = {
x : -1,
y : -1,
viewWidth : 10, // we see 10 meters wide scene
viewHeight : -1 // height is deduced from canvas aspect ratio
};
camera.viewHeight = camera.viewWidth * canvasWidth / canvasHeight ;
Then your character looks like :
// (x,y) is the center of the character in centered world coordinates
// here (0,0) means (500,500) world coords
// (1000,1000) array coords
// (320, 240) screen coords in 640X480
function /*Class*/ Character(x, y) {
var _x=x;
var _y=y;
var _col=0;
var _row=0;
var _sx=0.0;
var _sy=0.0;
var dirty = true;
Object.defineProperty(this,'x',
{ get : function() {return _x; }
set : function(v) { _x=v;
dirty=true; } });
Object.defineProperty(this,'x',
{ get : function() {return _y; }
set : function(v) { _y=v;
dirty=true; } });
Object.defineProperty(this,'col',
{ get : function() {
if (dirty) updateCoords();
return _col; } });
Object.defineProperty(this,'row',
{ get : function() {
if (dirty) updateCoords();
return _row; } });
Object.defineProperty(this,'sx',
{ get : function() {
if (dirty) updateCoords();
return _sx; } });
Object.defineProperty(this,'sy',
{ get : function() {
if (dirty) updateCoords();
return _sy; } });
function updateCoords() {
_row = ( ( _x + 0.5 * world.width )/ world.tileSize ) | 0 ;
_col = ( ( _x + 0.5 * world.height )/ world.tileSize ) | 0 ;
_sx = canvasWidth * ( 0.5 + ( _x - camera.x ) / camera.viewWidth ) ;
_sy = canvasHeight * ( 0.5 + ( _y - camera.y ) / camera.viewHeight ) ;
dirty = false;
}
}

Ideal handle location for insertion of a new point in a bezier curve with Paper.js

I have built a tool in paper.js that allows for the insertion of new control points, which works great, the problem I am running into, is that I must also calculate the handle position for each new control point (unless I am missing something, it does not appear that paper does this for you), and that is proving to be quite the task. The code below is what I currently have working, the point is added with the handles successfully, however it deforms the curve. I want to add the handles in such a way that the curve is not deformed.
gEditor.MoveTool.onMouseDown = function (event) {
gEditor.HitResult = paper.project.hitTest(event.point, gEditor.HitOptions);
var location = gEditor.HitResult.location;
var newPoint = gEditor.HitResult.item.insert(location.index + 1, event.point);
var prevSegment, nextSegment;
if (location.index - 1 >= 0){
prevSegment = gEditor.HitResult.item.segments[location.index - 1];
}
if (location.index + 2 < gEditor.HitResult.item.length) {
nextSegment = gEditor.HitResult.item.segments[location.index + 2];
}
if (prevSegment && nextSegment) {
newPoint.handleIn = {
x: prevSegment.point.x - ((prevSegment.point.x + newPoint.point.x) / 2),
y: prevSegment.point.y - ((prevSegment.point.y + newPoint.point.y) / 2),
};
newPoint.handleOut = {
x: nextSegment.point.x - ((nextSegment.point.x + newPoint.point.x) / 2),
y: nextSegment.point.y - ((nextSegment.point.y + newPoint.point.y) / 2),
};
}
}
I have looked at de Cateljau's algorithm, and assume that what I need is some form of this, but I am at a loss as to how to go about implementing it, since every example I have seen basically draws the curve, not finds the X,Y location of the handles.
Yep, paper.js already has this function: curve.divide(). After you've done your hit-test:
path = HitResult.item;
if (HitResult.type == 'stroke') {
var location = HitResult.location;
path.curves[location.index].divide(location);
}

Convert an SVG-path to polygons for use within Javascript Clipper

I'm trying to perform Boolean Operations on SVG Paths (that contain beziers, both quadratic and cubic) using JS Clipper.
JS Clipper starts with polygons then performs the operation and then it seems to convert them back to SVG paths.
The function below gives an SVG path but the below example starts with 2 polygons.
An example function:
// Polygon Arrays are expanded for better readability
function clip() {
var subj_polygons = [
[{
X: 10,
Y: 10
}, {
X: 110,
Y: 10
}, {
X: 110,
Y: 110
}, {
X: 10,
Y: 110
}],
[{
X: 20,
Y: 20
}, {
X: 20,
Y: 100
}, {
X: 100,
Y: 100
}, {
X: 100,
Y: 20
}]
];
var clip_polygons = [
[{
X: 50,
Y: 50
}, {
X: 150,
Y: 50
}, {
X: 150,
Y: 150
}, {
X: 50,
Y: 150
}],
[{
X: 60,
Y: 60
}, {
X: 60,
Y: 140
}, {
X: 140,
Y: 140
}, {
X: 140,
Y: 60
}]
];
var scale = 100;
subj_polygons = scaleup(subj_polygons, scale);
clip_polygons = scaleup(clip_polygons, scale);
var cpr = new ClipperLib.Clipper();
cpr.AddPolygons(subj_polygons, ClipperLib.PolyType.ptSubject);
cpr.AddPolygons(clip_polygons, ClipperLib.PolyType.ptClip);
var subject_fillType = ClipperLib.PolyFillType.pftNonZero;
var clip_fillType = ClipperLib.PolyFillType.pftNonZero;
var clipTypes = [ClipperLib.ClipType.ctUnion];
var clipTypesTexts = "Union";
var solution_polygons, svg, cont = document.getElementById('svgcontainer');
var i;
for (i = 0; i < clipTypes.length; i++) {
solution_polygons = new ClipperLib.Polygons();
cpr.Execute(clipTypes[i], solution_polygons, subject_fillType, clip_fillType);
console.log(polys2path(solution_polygons, scale));
}
}
// helper function to scale up polygon coordinates
function scaleup(poly, scale) {
var i, j;
if (!scale) scale = 1;
for (i = 0; i < poly.length; i++) {
for (j = 0; j < poly[i].length; j++) {
poly[i][j].X *= scale;
poly[i][j].Y *= scale;
}
}
return poly;
}
// converts polygons to SVG path string
function polys2path(poly, scale) {
var path = "",
i, j;
if (!scale) scale = 1;
for (i = 0; i < poly.length; i++) {
for (j = 0; j < poly[i].length; j++) {
if (!j) path += "M";
else path += "L";
path += (poly[i][j].X / scale) + ", " + (poly[i][j].Y / scale);
}
path += "Z";
}
return path;
}
I assume that you mean some sort of svg path to polygon conversion.
I have searched a lot, but not found anything reliable and out-of-the-box solution.
SVG path can consist of ten different segment, or 20 if we take into account both relative and absolute coordinates. They are represented as letters in path element's d-attribute: relative ones are mhvlcqastz and absolute ones are MHVLCQASTZ. Each have different attributes, a (elliptical arc) being the most complicated one. The most usable and flexible of types is c (cubic bezier curve), because it can represent all other types in rather high precision as these examples show: http://jsbin.com/oqojan/32, http://jsbin.com/oqojan/42.
Raphael JS library has Path2Curve-function which can convert all path segments to cubic curves and it can handle also the complicated arc to cubic conversion. Unfortunately it has a bug, so that it cannot handle all possible path segment combinations, but fortunately there is a fixed version of library available: http://jsbin.com/oqojan/32/edit (look at the Javascript-window).
When all path segments are converted to cubic curves, they can be converted to individual line segments. There are few ways, and the best seems to be an adaptive recursive subdivision method, which produces more line segments in sharp turns of curve and fewer in other parts of curve to achieve a balance of curve fidelity and low count of segments to maximize rendering speed, but unfortunately it could not handle all collinear cases. I succeeded in converting AntiGrain's method to Javascript and added presplitting functionality, which splits the curve in local extremes (first derivative roots) and after that the AntiGrain method handles also all possible collinear cases:
Collinear horizontal: http://jsbin.com/ivomiq/6
Set of different cases: http://jsbin.com/ivomiq/7
Random: http://jsbin.com/ivomiq/8
Collinear rotated: http://jsbin.com/ivomiq/9
All the above samples have two paths in top of each other to show possible errors in adaptive algorithm: the red curve is splitted using very slow brute force method and the green one is splitted using AntiGrain method. If you see not red at all, the AntiGrain's method approximate()-function is working as expected.
OK, now we have repaired Raphael and repaired AntiGrain. If we combine these both methods, we can create a function that converts ANY svg path element to polygon (single or multiple subpolygons). I'm not 100% sure that this is the best or fastest method, but it should be usable. Of course the best would be native browser implementation...
you can use De Casteljau's algorithm to break bezier curve into smaller straight lines, and join them to create polygon.
Here is some references of De Casteljau's algorithm
http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/de-casteljau.html
http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/de-casteljau.html

Categories

Resources