Javascript only draws last of multiple drawings - javascript

Okay so i'm embarrassingly unknowledgeable of javascript. I know my ruby and rails pretty solid but have never used js as extensively as i am in my current project.
i have a map that's been drawn on a canvas. on that map, i want drawn multiple position markers (via the provided function). i've given it a list (via rails) of locations to mark. for some reason, it's only drawing the last coordinate.
the javascript is improvised from another source, not mine. this is just the problematic portion:
function plotPosition(long,lat) {
// Grab a handle to the canvas
var canvas = document.getElementById('map'),
ctx;
// Canvas supported?
if (canvas.getContext) {
// Grab the context
ctx = canvas.getContext('2d');
ctx.beginPath();
// Draw a arc that represent the geo-location of the request
ctx.arc(
degreesOfLongitudeToScreenX(long),
degreesOfLatitudeToScreenY(lat),
5,
0,
2 * Math.PI,
false
);
// Point style
ctx.fillStyle = 'rgb(0,0,0)';
ctx.fill();
ctx.stroke();
}
}
function draw() {
// Main entry point got the map canvas example
var canvas = document.getElementById('map'),
ctx;
// Canvas supported?
if (canvas.getContext) {
ctx = canvas.getContext('2d');
// Draw the background
drawBackground(ctx);
// Draw the map background
drawMapBackground(ctx);
// Draw the map background
// drawGraticule(ctx);
// Draw the land
drawLandMass(ctx);
<% #events.each do |e| %>
plotPosition('<%= e.longitude %>','<%= e.latitude %>');
<% end %>
} else {
alert("Canvas not supported!");
}
}
so my thinking is that the plotPosition function is drawing each coordinate, but every time it's given a new coordinate, the old one is erased/moved and replaced with the new one. probably a simple fix here, but i've been at this for hours and to no avail. banging my head.
any ideas as to the problem?

From the code example it seems as you are clearing the background too - you're not showing the calls to these methods but I will assume you do this before drawing the new point(s) (?) somewhere else in the code.
If so -
You need to redraw all the points you have drawn before you clear the canvas.
One way to do this is to store the points you already have in an array and then call a redraw function to draw everything in there. Or store the points from server directly to the array with converted values and when all points has been converted redraw everything.
Simplified example based on latter approach (which will require some re-factoring):
/// GLOBALS (put ctx here instead as well..)
var points = [],
canvas = document.getElementById('map'),
ctx = canvas.getContext ? canvas.getContext('2d') : null;
if (ctx === null) {...sorry...};
...
/// somewhere comes the logic to get the points themselves.
...
<% #events.each do |e| %>
storePosition('<%= e.longitude %>','<%= e.latitude %>');
<% end %>
...
/// after new points are added, render them all
renderAll();
function storePosition(long, lat) {
points.push{
x: degreesOfLongitudeToScreenX(long)
y: degreesOfLatitudeToScreenY(lat),
long: long,
lat: lat
}
}
function renderAll();
/// clear and redraw background of canvas
drawBackground(ctx);
// Draw the map background
drawMapBackground(ctx);
// Draw the map background
// drawGraticule(ctx);
// Draw the land
drawLandMass(ctx);
/// now plot all the stored points
ctx.fillStyle = 'rgb(0,0,0)';
for(var i = 0, point; point = points[i]; i++) {
ctx.beginPath();
ctx.arc(
point.x,
point.y,
5,
0,
2 * Math.PI
);
ctx.closePath();
ctx.fill();
}
}
You can implement a check of x and y to see that they are inside canvas but this is strictly not necessary as canvas will clip them for you and the internal clipping do faster checking than JavaScript will. But for debugging purposes you could always do a check if you suspect this. But you write that the point is plotted but erased when a new point is drawn so I don't think this is the problem here.

Related

javascript canvas intersection between two path2D

I'm looking for a way to check if two path2D are intersecting but can't find a way...
Exemple :
// My circle
let circlePath = new Path2D();
circlePath.ellipse(x, y, radiusX, radiusY, 0, 0, Math.PI*2, false);
// My rectangle
let rectPath = new Path2D();
rectPath.rect(x, y, width, height);
// Intersect boolean
let intersect = circlePath.intersect(rectPath); // Does not exists
Is there a function to do that ?
I found isPointInPath(path2D, x, y) (that I use with my paths to check intersect with mouse) but can't use it between two paths.
Or maybe a way to get an array of all points in a Path2D to use isPointInPath with all points ?
Edit:
FYI, I want this for a game development, I want to be able to check collision between my entities (an entity is defined by some data and a Path2D). My entities can be squares, circles or more complex shapes.
There currently is nothing in the API to do this.
The Path2D interface is still just an opaque object, from which we can't even extract the path-data. I actually did start working on a future proposal to expose this path-data and add a few more methods to the Path2D object, like a getPointAtLength(), or an export to SVG path commands, which would help doing what you want, but I wouldn't hold my breath until it's part of the API, and I must admit I didn't even consider including such a path-intersect method...
However as part of this effort I did build a prototype of that API that currently exposes only a few of the expected methods: https://github.com/Kaiido/path2D-inspection/
Among these methods there is a toSVGString() that can be used with this project (that I didn't extensively test).
So we could build a Path2D#intersects() by merging both libraries. However note that at least mine (path2d-inspection) hasn't been extensively tested yet and I don't recommend using it in production just yet.
Anyway, here is a very quickly made hack as a proof-of-concept:
const circlePath = new Path2D();
circlePath.ellipse(rand(100, 20), rand(100, 20), rand(80, 10), rand(80, 10), rand(Math.PI*2), 0, Math.PI*2, false);
// My rectangle
const rectPath = new Path2D();
rectPath.rect(rand(150), rand(100), rand(200), rand(200));
// Intersect boolean
const intersect = rectPath.intersects(circlePath);
console.log("intersect:", intersect);
// List of intersection points
const intersections = rectPath.getIntersections(circlePath);
console.log(intersections);
// Render on a canvas to verify
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.strokeStyle = "green";
ctx.stroke(circlePath);
ctx.strokeStyle = "red";
ctx.stroke(rectPath);
function rand(max=1, min=0) {
return Math.random() * (max - min) + min;
}
.as-console-wrapper { max-height: 50px !important }
<script src="https://cdn.jsdelivr.net/gh/Kaiido/path2D-inspection#master/build/path2D-inspection.min.js"></script>
<script>
// a really quick hack to grasp 'intersect' from their esm
globalThis.module = {};
</script>
<script src="https://cdn.jsdelivr.net/gh/bpmn-io/path-intersection#master/intersect.js"></script>
<script>
// part 2 of the hack to grasp 'intersect', let's make it a Path2D method :P
{
const intersect = module.exports;
delete globalThis.module;
Path2D.prototype.getIntersections = function(path2) {
return intersect(this.toSVGString(), path2.toSVGString());
};
Path2D.prototype.intersects = function(path2) {
return this.getIntersections(path2).length > 0;
};
}
</script>
<canvas></canvas>

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>

THREE.js line drawn with BufferGeometry not rendering if the origin of the line isn't in the camera's view

I am writing a trace-line function for a visualization project that requires jumping between time step values. My issue is that during rendering, the line created using THREE.js's BufferGeometry and the setDrawRange method, will only be visible if the origin of the line is in the camera's view. Panning away will result in the line disappearing and panning toward the origin of the line (usually 0,0,0) will make it appear again. Is there a reason for this and a way around it? I have tried playing around with render settings.
The code I have included is being used in testing and draws the trace of the object as time progresses.
var traceHandle = {
/* setup() returns trace-line */
setup : function (MAX_POINTS) {
var lineGeo = new THREE.BufferGeometry();
//var MAX_POINTS = 500*10;
var positions = new Float32Array( MAX_POINTS * 3 ); // 3 vertices per point
lineGeo.addAttribute('position', new THREE.BufferAttribute(positions, 3));
var lineMaterial = new THREE.LineBasicMaterial({color:0x00ff00 });
var traceLine = new THREE.Line(lineGeo, lineMaterial);
scene.add(traceLine);
return traceLine;
},
/****
* updateTrace() updates and draws trace line
* Need 'index' saved globally for this
****/
updateTrace : function (traceLine, obj, timeStep, index) {
traceLine.geometry.setDrawRange( 0, timeStep );
traceLine.geometry.dynamic = true;
var positions = traceLine.geometry.attributes.position.array;
positions[index++]=obj.position.x;
positions[index++]=obj.position.y;
positions[index++]=obj.position.z;
// required after the first render
traceLine.geometry.attributes.position.needsUpdate = true;
return index;
}
};
Thanks a lot!
Likely, the bounding sphere is not defined or has radius zero. Since you are adding points dynamically, you can set:
traceLine.frustumCulled = false;
The other option is to make sure the bounding sphere is current, but given your use case, that seems too computationally expensive.
three.js r.73

How to set id for drawn canvas shape?

I see this question and I dont know how I can set id for each circles and access them from javascript codes and css codes? (e.g. click)
You can solve this by defining click objects when drawing the circles. Inside the loop drawing the circles (ref. the fiddle made by #MonicaOlejniczak):
...
// push circle info as objects:
circles.push({
id: i + "," + j, // some ID
x: x,
y: y,
radius: radius
});
...
Then:
add a click handler to canvas
correct mouse position
loop through the objects finding if (x,y) is inside the circle:
Function example:
canvas.onclick = function(e) {
// correct mouse coordinates:
var rect = canvas.getBoundingClientRect(), // make x/y relative to canvas
x = e.clientX - rect.left,
y = e.clientY - rect.top,
i = 0, circle;
// check which circle:
while(circle = circles[i++]) {
context.beginPath(); // we build a path to check with, but not to draw
context.arc(circle.x, circle.y, circle.radius, 0, 2*Math.PI);
if (context.isPointInPath(x, y)) {
alert("Clicked circle: " + circle.id);
break;
}
}
};
You can optionally use math instead of the isPointInPath(), but the latter is simpler and is fast enough for this purpose.
Modified version of the same fiddle
You can't set an ID on something that has been drawn to a canvas.
The element on its own is just a bitmap and does not provide information about any drawn objects.
If you need to interact with the items inside the canvas you need to manually keep a reference to where everything is drawn, or use a system like "object picking" or using the built in hit regions.

HTML canvas double buffering frame-rate issues

I have a full-screen canvas with 3 images drawn on it. When I resize the window, these images change position; however, it appears to be very glitchy, more so in Firefox.
I've been reading that double-buffering should resolve this issue, but I'm wondering how I would double buffer when the next position is unknown. That is to say, I cannot determine what should be buffered in the future, so how would this be possible?
Here is one source that seems doable, but I do not fully understand the concept Fedor is trying to explain.
Does HTML5/Canvas Support Double Buffering?
So far I have,
$canvas = $('#myclouds')[0];
$canvas_buffer = $('canvas')[0].insertAfter($canvas).css('visibility', 'hidden');
context = $canvas.getContext('2d');
context_buffer = $canvas_buffer.getContext('2d');
clouds_arr = [$canvas, $canvas_buffer];
$(window).resize(function () {
drawCanvas();
};
function initCanvas() {
// Sources for cloud images
var cloud1 = '/js/application/home/images/cloud1.png',
cloud2 = '/js/application/home/images/cloud2.png',
cloud3 = '/js/application/home/images/cloud3.png';
// add clouds to be drawn
// parameters are as follows:
// image source, x, y, ratio, adjustment)
addCloud(cloud1, null, 125, .03);
addCloud(cloud2, null, 75, .15);
addCloud(cloud3, null, 50, .55);
addCloud(cloud1, null, 125, .97, 300);
addCloud(cloud2, null, 70, .85, 300);
addCloud(cloud3, null, 45, .5, 300);
// Draw the canvas
drawCanvas();
}
function drawCanvas() {
// Reset
$canvas.attr('height', $window.height()).attr('width', $window.width());
// draw the clouds
var l = clouds.length;
for (var i = 0; i < l; i++) {
clouds[i].x = ($window.width() * clouds[i].ratio) - clouds[i].offset;
drawimage(context, clouds[i]);
}
}
function Cloud() {
this.x = 0;
this.y = 0;
}
function addCloud(path, x, y, ratio, offset) {
var c = new Cloud;
c.x = x;
c.y = y;
c.path = path;
c.ratio = ratio || 0;
c.offset = offset || 0;
clouds.push(c);
}
function drawimage(ctx, image) {
var clouds_obj = new Image();
clouds_obj.src = image.path;
clouds_obj.onload = function() {
ctx.drawImage(clouds_obj, image.x, image.y);
};
}
I think maybe you are misunderstanding what double buffering is. Its a technique for smooth real-time rendering of graphics on a display.
The concept is you have two buffers. Only one is visible at any one time. When you go to draw the elements that make up a frame you draw them to the invisible buffer. In you case the clouds. Then you flip the buffers making the hidden one visible and the visible one hidden. Then on the next frame you draw to the now newly hidden buffer. Then at the end of drawing you flip back.
What this does is stop the user seeing partial rendering of elements before a frame is complete. On gaming systems this would also be synced up with the vertical refresh of the display to be really smooth and stop artefacts such as tearing to occur.
Looking at you code above you seem to have created the two canvas elements, but you're only using the first Context object. I assume this is incomplete as no flipping is taking place.
Its also worth noting that the window resize event can fire continuously when dragging which can cause frantic rendering. I usually create a timer on the resize event to actually re-render. This way the re-render only happens once the user stops resizing for a few milliseconds.
Also, your draw routine is creating new Image objects every time which you don't need to do. You can use one image object and render to the canvas multiple times. This will speed up your render considerably.
Hope this helps.

Categories

Resources