Javascript canvas draw line strange behavior using algorithm - javascript

There are plenty of examples on how to draw lines on canvas, in js.
But for only educational purposes i want to draw line using algorithm. basically method gets two Vector2 points, from them it finds middle point, then it continues like that recursively until minimum distance of 2 pixels is reached.
I have DrawPoint method to basically draw 1 point on canvas, and DrawLine method that does all the job.
For now I have 2 problems:
1: points are not colored red, as they should be.
2:
It doesnt look like a line.
For Vector2 i used "Victor.js" plugin, and it seems to be working well.
this is code i have:
JS:
var point2 = new Victor(100, 100);
var point3 = new Victor(150, 150);
DrawLine(point2, point3);
function DrawLine(vec0, vec1)
{
var point0 = new Victor(vec0.x, vec0.y);
var point1 = new Victor(vec1.x, vec1.y);
var dist = point1.distance(point0);
if (dist < 2)
return;
//this is how it should look like in c# var middlePoint = point0 + (point1 - point0)/2; But looks like i cant just divide by 2 using victor js because i can only divide vector by vector.
var middlePoint = point0.add(point1.subtract(point0).divide(new Victor(2,2)));
DrawPoint(middlePoint);
DrawLine(point0, middlePoint);
DrawLine(middlePoint, point1);
}
function DrawPoint(point){
var c = document.getElementById("screen");
var ctx = c.getContext("2d");
ctx.fillStyle = "FF0000";
ctx.fillRect(point.x, point.y, 3,1);
}
I really appreciate any help you can provide.

The victor.js documentation shows that most functions of Victors do not return new Victors, but operate on the current instance. In a way, v1.add(v2) is semantically more like v1 += v2 and not v1 + v2.
The problem is with calculating the midpoint. You could use the mix() method, which blends two vectors with a weight. You must clone() the Victor first, otherwise point0will be midofied:
var middlePoint = point0.clone().mix(point1, 0.5);
If you don't change the original Vectors, you don't need to create new instances of Victors from the arguments, you can use the arguments directly:
function DrawLine(point0, point1)
{
var dist = point1.distance(point0);
if (dist < 2) return;
var middlePoint = point0.clone().mix(point1, 0.5);
DrawPoint(middlePoint);
DrawLine(point0, middlePoint);
DrawLine(middlePoint, point1);
}
Finally, as Sven the Surfer has already said in a comment, "FF0000" isn't a valid colour. Use "#FF0000", note the hash mark, or one of the named web colours such as "crimson".

Related

Javascript chaining the same animation function with different parameters

I am trying to animate a line two lines along a path, one then the other. Basically it will look like one line being drawn, stopping at a point, then another line being drawn somewhere else. So far I have come across promises and callbacks to achieve this, but being a javascript newbie this is confusing
Current animate function:
/*
* Animation function draws a line between every point
*/
var animate = function(p){
return new Promise(function(resolve) {
t = 1;
var runAnimation = function(){
if(t<p.length-1){
context.beginPath();
context.moveTo(p[t-1].x,p[t-1].y);
context.lineTo(p[t].x,p[t].y);
context.stroke();
t++;
requestAnimationFrame(function(){runAnimation()});
} else {
resolve()
}
};
runAnimation();
});
}
Current call to animate function:
animate(points).then(animate(secondary_points));
The points are similar to:
var points = [{x:100, y:200}];
And the paths the lines need to follow are just the multiple coordinates inside points and secondary_points
Ive tried many solutions on SO that were similar, but small differences cause me to either mess up or not understand the solution. The biggest issue I seem to have is calling the SAME animate function, with that animate function needing to be run on different parameters.
Without this solution, using
animate(points);
animate(secondary_points);
the lines are drawn somewhat at the same time, but the result is actually just randomly placed dots along the path instead of smooth lines, I assume because both are running at the same time.
How would I go about fixing this so that one line is drawn along path1 and then the second line is drawn along path2?
It is probably a simple solution, but Ive worked with JS for 3 days and my head is still spinning from getting used to some of the syntax of the old code Ive had to fix
Thank you
EDIT:
The full flow of the animation is as follows:
I have a php file that contains 2 canvases, each containing an image of a map. The php file has a couple <script/> tags, one of which calls the js script I am writing the animation on via drawPath(source,destination,true) or drawPath(source,destination,false)
The drawPath function uses the boolean to determine which canvas to get the context for, and then draw on the path from point A to point B via finding the path and creating the points mentioned above, then drawing using animate(). There are a couple breaks in the maps that require separate lines, which prompted my original question. I was able to fix that thanks to suggestions, but now I am having a larger issue.
If I need to go from point A on map A to point B on map B, ie
drawPath(source, end_point_of_map_A, true); is called then
drawPath(start_point_of_map_B, destination, false);, the lines are drawn only on one map, and they are similar to before where they are 1. random and 2. incomplete/only dots
I am assuming this is due to the animation again, because it worked when just drawing the lines statically, and each animation works when going from point A to B on a single map
Any help is appreciated!
Edit:
DrawPath()
function drawPath(source, desti, flag) {
/*
* Define context
*/
//lower
if(!flag){
var c = document.getElementById("myCanvas");
context = c.getContext("2d");
//upper
} else {
var cUpr = document.getElementById("myCanvasUpr");
context = cUpr.getContext("2d");
}
/*
* Clear the variables
*/
points = [];
secondary_points = [];
vertices = [];
secondary_vertices = [];
t = 1;
done = false;
//check for invalid locations
if (source != "" && desti != "") {
context.lineCap = 'round';
context.beginPath();
/*
* Get the coordinates from source and destination strings
*/
var src = dict[source];
var dst = dict[desti];
/*
* Get the point number of the point on the path that the source and destination connect to
*/
var begin = point_num[source];
var finish = point_num[desti];
/*
* Draw the green and red starting/ending circles (green is start, red is end)
*/
context.beginPath();
context.arc(src[0], src[1], 8, 0, 2 * Math.PI);
context.fillStyle = 'green';
context.fill();
context.beginPath();
context.arc(dst[0], dst[1], 6, 0, 2 * Math.PI);
context.fillStyle = 'red';
context.fill();
/*
* Call the function that draws the entire path
*/
draw_segments(begin, finish, src, dst, flag);
//window.alert(JSON.stringify(vertices, null, 4))
/*
* Edit what the line looks like
*/
context.lineWidth = 5;
context.strokeStyle = "#ff0000";
context.stroke();
}
}
A nice way to handle this is to put your lines into a an array where each element is a set of points of the line. Then you can call reduce() on that triggering each promise in turn. reduce() takes a little getting used to if you're new to javascript, but it basically takes each element of the array c in this case, does something and that something becomes the next a. You start the whole thing off with a resolve promise which will be the initial a. The promise chain will be returned by reduce to you can tack on a final then to know when the whole thing is finished.
For example:
let canvas = document.getElementById('canvas')
let context = canvas.getContext('2d');
var animate = function(p){
return new Promise(function(resolve) {
t = 1;
var runAnimation = function(){
if(t<p.length-1){
context.beginPath();
context.moveTo(p[t-1].x,p[t-1].y);
context.lineTo(p[t].x,p[t].y);
context.stroke();
t++;
requestAnimationFrame(function(){runAnimation()});
} else {
resolve()
}
};
runAnimation();
});
}
// make some points:
let points = Array.from({length: 200}, (_,i) => ({x:i+1, y:i+2}))
let points2 = Array.from({length: 200}, (_,i) => ({x:300-i, y:i+2}))
let points3 = Array.from({length: 200}, (_,i) => ({x:i*2, y:100+100*Math.sin(i/10)}))
// create an array holding each set
let sets = [points, points2, points3]
// use reduce to call each in sequence returning the promise each time
sets.reduce((a, c) => a.then(() => animate(c)), Promise.resolve())
.then(() => console.log("done"))
<canvas id="canvas" height="300" width="500"></canvas>

Repair normals on possibly bad .stl files

I am new to Three.js and have been assigned the task of trying to repair the normals on files that have been coming in occasionally that appear to be bad. We do not know if they are bad scans or possibly bad uploads. We are looking into the upload function, but also would like to try and repair them if possible. Can anyone provide any ideas or tips to repair the file or find the correct normals?
Below is the code where we grab the normals and how we grab them. NOTE: this code works fine generally, it is only a problem when the normals are bad. I am also attaching one of the files so you can see the types of normals and "bad file" I am dealing with. Get File here
We are also using VTK on the backend with C++, so a solution or idea using either of these is helpful.
my.geometry = geometry;
var front = new THREE.MeshPhongMaterial(
{color: 0xe2e4dc, shininess: 50, side: THREE.DoubleSide});
var mesh = [new THREE.Mesh(geometry, front)];
my.scene.add(mesh[0]);
my.objects.push(mesh[0]);
var rc = new THREE.Raycaster();
var modelData = {'objects': [mesh[0].id], 'id': mesh[0].id};
var normalFound = false;
for (var dy = 80; dy >= -80; dy = dy - 10) {
console.log('finding a normal on', 0, dy, -200);
rc.set(new THREE.Vector3(0, dy, -200), new THREE.Vector3(0, 0, 1));
var hit = rc.intersectObjects([mesh[0]]);
if (hit.length) {
my.normal = hit[0].face.normal.normalize();
console.log('normal', my.normal.z);
modelData['normal'] = my.normal;
if ((my.normal.z > 0.9 && my.normal.z < 1.1)) {
my.requireOrienteering = true;
modelData['arch'] = 'lower';
normalFound = true;
console.log('we have a lower arch');
} else if ((my.normal.z < -0.9 && my.normal.z > -1.1)) {
modelData['arch'] = 'upper';
normalFound = true;
console.log('we have an upper arch');
}
break;
}
}
Calculating the normals is an easy step. If you calculate the cross product of two vectors (geometrical one), you will get a vector, that is orthogonal to the two, you input. All you have to do now is normalize it, since normals should be normalised to not mess up lightning calculations.
For smooth surfaces, you have to calculate all normals on the point and average them. For flat surfaces each vertex has multiple normales (one for each surface).
In pseudo code it will look like this for quads:
foreach quad : mesh
foreach vertex : quad
vector1 = neighborVertex.pos - vertex.pos;
vector2 = otherNeighborVertex.pos - vertex.pos;
vertex.normal = normalize(cross(vector1, vector2));
end foreach;
end foreach;
VTK has a filter named vtkPolyDataNormals that you can run on your file to compute normals. You probably want to call ConsistencyOn(), NonManifoldTraversalOn(), and AutoOrientNormalsOn() before running it.
If you want point-normals (instead of per-cell normals) and your shape has sharp corners, you probably want to provide a feature angle with SetFeatureAngle() and call SplittingOn().

How to connect objects with Bezier-like curves using Paper.js

I have a web app prototype where nodes similar to Blender shader editor are connected to each other. I am using Paper.js framework
I want them to be connected using those smooth Bezier-like curves. So I have 2 shapes and I can connect them by making a straight line, but now I want to have handles at the endpoints that smooth these objects out, kinda like this:
So 2 handles on endpoints, pointing horizontally for half the bounding box of the path.
The problem is I can't figure out how to add and edit those handles using Paper.js
The code I have is this:
function makeRectangle(topLeft, size, cornerSize, colour){
var rectangle = new Rectangle(topLeft, size);
var cornerSize = cornerSize;
var path = new Path.RoundRectangle(rectangle, cornerSize);
path.fillColor = colour;
return path;
}
var xy1 = new Point(50,50); //Position of 1st rectangle.
var size = new Size(100, 80); //Size
var c = new Size(8,8); //Corner radius
var col = "#167ee5"; //Colour
var r1 = makeRectangle(xy1, size, c, col); //Make first rectangle
var xy2 = new Point(467,310); //Position of second rectangle
var size2 = new Size(115, 70); //Size of second rectangle
var r2 = makeRectangle(xy2, size2, c, col); //Make secont rectangle
var r1cent = r1.bounds.center; //Get the center points, they will be used as endpoints for the curve.
var r2cent = r2.bounds.center;
var connector = new Path(r1cent, r2cent); //Ok so I made this path... Now what? How do access and edit the handlers at endpoints like in the image?
connector.strokeColor = 'black'; //Give it some colour so we can see it.
You can paste all this code here without any setup, it's a good way to test the framework.
You can use Segment objects when defining the connector rather than using Points (or you can set the handleIn and handleOut properties after creating the path).
The doc is here: Segment
And here is a sketch showing how to use handleIn and handleOut with your code:
sketch.paperjs.org solution

Get curved result set rather than angular in JavaScript (maths help needed)

I've got a script that creates a gradient by shading cells based on their distance from a set of coordinates. What I want to do is make the gradient circular rather than the diamond shape that it currently is. You can see an en example here: http://jsbin.com/uwivev/9/edit
var row = 5, col = 5, total_rows = 20, total_cols = 20;
$('table td').each(function(index, item) {
// Current row and column
var current_row = $(item).parent().index(),
current_col = $(item).index();
// Percentage based on location, always using positive numbers
var percentage_row = Math.abs(current_row-row)/total_rows;
var percentage_col = Math.abs(current_col-col)/total_cols;
// I'm thinking this is what I need to change to achieve the curve I'm after
var percentage = (percentage_col+percentage_row)/2;
$(this).find('div').fadeTo(0,percentage*3);
});
If you can give me hand with the right maths function to get the curve I'm after that would be great! Thanks!
Darren
// Current row and column
var current_row = $(item).parent().index(),
current_col = $(item).index();
// distance away from the bright pixel
var dist = Math.sqrt(Math.pow(current_row - row, 2) + Math.pow(current_col - col, 2))
// do something with dist, you might change this
var percentage = dist / total_cols;
$(this).find('div').fadeTo(0,percentage*3);
You can use the square of the distance formula:
((current_row - row)*(current_row - row) + (current_col - col)*(current_col - col))
then multiply it by whatever scale factor you need.
Here is a circle drawing procudure I wrote many moons ago in Pascal which you can use as pseudo code to understand how to color pixels at the radius from an (X,Y) and work your way in. Multiple shrinking circles should cover the entire area you need. The code also gives you the formula for accessing the radius.
PROCEDURE DrawCircle(X,Y,Radius:Integer);
VAR A,B,Z:LongInt;
BEGIN
Z:=Round(Sqrt(Sqr(LongInt(Radius))/2));
FOR A:=Z TO Radius DO
FOR B:=0 TO Z DO
IF Radius=Round(Sqrt(A*A+B*B)) THEN
BEGIN
PutPixel(X+A,Y+B,8);
PutPixel(X+A,Y-B,9);
PutPixel(X-A,Y+B,10);
PutPixel(X-A,Y-B,11);
PutPixel(X+B,Y+A,12);
PutPixel(X+B,Y-A,13);
PutPixel(X-B,Y+A,14);
PutPixel(X-B,Y-A,15);
END;
END;
NB: "Longint()" is a compiler typecast for larger numeric computations so don't let that worry you.
NB: Inner-most brackets are executed first.

Get distance in pixels between 2 LonLat objects in OpenLayers

I have 2 OpenLayers.LonLat objects, and I want to determine the distance in pixels for the current zoom between the 2. I'm using OpenLayers.Layer.getViewPortPxFromLonLat() to determine the x and y of the points and then subtract to see the difference between the 2, but the values that I get are very small for points that are 2000km apart.
Here is my code:
var center_lonlat = new OpenLayers.LonLat(geometry.lon, geometry.lat);
var center_px = layer.getViewPortPxFromLonLat(center_lonlat);
var radius_m = parseFloat(feature.attributes["radius"]);
var radius_lonlat = OpenLayers.Util.destinationVincenty(center_lonlat, 0, radius_m);
var radius_px = layer.getViewPortPxFromLonLat(radius_lonlat);
var radius = radius_px.y - center_px.y;
I'm trying here to draw a circle, giving that I receive a center point and a radius in meters. The LonLat object seems to be ok.
Am I doing something wrong ?
I found the issue: destinationVincenty() need and returns coordinates in wgs84 where my map was using spherical mercator projection.
I hope I got correctly the answer, because projections make me dizzy and never really understood them :(. I was looking in the console to the numbers for my coordinates and the coordinates from the map.getExtent() that is used to calculate the getViewPortPxFromLonLat() and I realised they are not in the right order of magnitude, and then it hit me.
So, the code is now:
var spherical_mercator = new OpenLayers.Projection("EPSG:900913");
var wgs84 = new OpenLayers.Projection("EPSG:4326");
var map = feature.layer.map;
var geometry = feature.geometry;
var center_lonlat = new OpenLayers.LonLat(geometry.y, geometry.x);
var center_px = map.getViewPortPxFromLonLat(center_lonlat);
var radius_m = parseFloat(feature.attributes["radius"]);
var radius_lonlat = OpenLayers.Util.destinationVincenty(center_lonlat.clone().transform(spherical_mercator, wgs84), 0, radius_m).transform(wgs84, spherical_mercator);
var radius_px = map.getViewPortPxFromLonLat(radius_lonlat);
var radius = Math.abs(radius_px.y - center_px.y);
Measured the circles with the OpenLayers.Control.ScaleLine, and the size is dead on :D
You seem to be doing right. If the distance you get is too small, maybe there is a problem with OpenLayers.Util.destinationVincenty function? Have you tried to replace the bearing (0) with anything else - its value seem to be not important in your case. But frankly speaking, I wasn't able to understand how it works while browsing the source

Categories

Resources