JavaScript Loops when calling Async - javascript

Problem: JavaScript is able to successfully draw several arcs on the screen, but they disappear when run. I am running a simple loop calling the ARC function, I don't get it!?
All I want to do is run each call step by step and sleep! This could be done in any language outside of JavaScript with ease, its a simple procedural Loop.
The problem is my graph shows up for a split second before disappearing. However if I trace through the code via JavaScript debugger it works until it disappears again!
Frustrated! I tried to wrap this with a timeOut, so I can sleep to show a slow animation. What on earth is going on my friends, is this a bug in JavaScript or are my fundamentals not there. I can't understand the codeflow when its so logically written.
https://jsfiddle.net/nd6gktmf/
var array_length = attack_list.length;
//EDITED: declare local variables
var coordinates,
origin_longitude,
origin_latitude,
dest_longitude,
dest_latitude;
for(var i=0; i < array_length; i++) {
coordinates = attack_list[i];
//EDITED: consider using dot notation
origin_longitude = coordinates.origin.longitude;
origin_latitude = coordinates.origin.latitude;
dest_longitude = coordinates.destination.longitude;
dest_latitude = coordinates.destination.latitude;
draw_arc(origin_longitude, origin_latitude, dest_longitude, dest_latitude);
}
This Works only in debug mode! What the....
function draw_arc(origin_longitude, origin_latitude, dest_longitude, dest_latitude) {
var data_to_map =
[{
origin: {
latitude: origin_latitude,
longitude: origin_longitude
},
destination: {
latitude: dest_latitude,
longitude: dest_longitude
}
}];
console.log("****** Begin******");
console.log(origin_longitude);
console.log(origin_latitude);
console.log(dest_longitude);
console.log(dest_latitude);
console.log("****** End ******");
election.arc(data_to_map, {strokeWidth: 2});
}

As Kevin pointed out: Delete all the javascript code you added to your question (of course leave your data: election and attack_list) and replace it with:
// loop over the attack_list
attack_list.forEach(function(attack_item, i){
// now attack_item is one item from the array, i is the index in the array
// we set up a timer FOR EACH item in the array
setTimeout(function(){
// we make an empty array, because election.arc needs an array,
// eventhough we'll only send 1 item in it, we wrap it in an array
var draw_list = [];
draw_list.push(attack_item);
// put the item into the array
election.arc(draw_list,{strokeWidth: 2});
}, i*2000);
// note: the time is i*2000ms = i*2s
// the "1st" item in attack_list is at index 0
// so 0*2000=0 => it'll start drawing it immediately (t=0)
// it animates for about 1s (t=1)
// the last drawn line is still displayed (t=1..2)
//
// t=2: now the timer for the "2nd" item (i=1) starts drawing
// but because we created a new empty array and only added the 2nd item
// into it, when it draws, it erases everything that we drew before
// and now you only see the 2nd item is getting animated.
// etc...
});
https://jsfiddle.net/flocsy/nd6gktmf/3/

If you look at the doc, it seems like you gotta put all of your arcs in the same array.
Calling election.arc probably re-renders the entire image which is why you can only see the last one in the end.

Related

Cesium JS - Accessing primitive attributes without using id

So essentially, I have a function that draws a colored rectangle at every globe coordinate point via coordinate and color arrays. (Frequency means new rectangle every x coordinates)
//Given an array of coordinates, respective colors, and level of detail,
//Draws heatmap on the globe
function DrawMapGivenArrays(CoordinateArray, Colors, frequency)
{
var instances = [];
for(var i = 0; i < CoordinateArray.length; i++)
{
var Cartesian1 = new Cesium.Cartesian3.fromDegrees(CoordinateArray[i].lon,
CoordinateArray[i].lat);
var Cartesian2 = new Cesium.Cartesian3.fromDegrees(CoordinateArray[i].lon+frequency,
CoordinateArray[i].lat-frequency);
var CartesianArray = Array();
CartesianArray.push(Cartesian1);
CartesianArray.push(Cartesian2);
var newPrim = new Cesium.GeometryInstance
({
geometry : new Cesium.RectangleGeometry
({
rectangle : Cesium.Rectangle.fromCartesianArray(CartesianArray),
vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT
}),
attributes :
{
color : Cesium.ColorGeometryInstanceAttribute.fromColor(Colors[i])
},
id: "Rectangle" + i,
});
instances.push(newPrim);
numberOfRectangles++;
}
var primitive = new Cesium.Primitive
({
releaseGeometryInstances : false,
geometryInstances : instances,
appearance : new Cesium.PerInstanceColorAppearance(),
});
scene.primitives._primitives[1] = primitive;
}
That works fine.. After I draw the map, I'm using this small function below to individually edit the color of one rectangle. (I call this for every rectangle to change all of them).
//Changes the color of a rectangle primitive given its unique id and a color value
function setPrimitiveRectangle(id, color)
{
var CesiumColor = Cesium.ColorGeometryInstanceAttribute.toValue(color);
scene.primitives._primitives[1].getGeometryInstanceAttributes(id).color = CesiumColor; //this line is 10x slower for every instance after it runs the 1st time
//".getGeometryInstanceAttributes(id)" This specific phrase runs 10x slower after 1st instance
}
That works as well. But, for some reason, it has issues.
For example, when I re-color all of the rectangles the first time, it runs very fast. However, every time I re-fun that function again after the first time, it's 10x slower. I narrowed it down to the phrase that was giving me problems( ".getGeometryInstanceAttributes(id)" ).
I tried to circumvent calling the get function by modifying color values directly with this: (Where i is iterating over every rectangle).
viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[0] = 0;
viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[1] = 0;
viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[2] = 0;
viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[3] = 0;
Once I do this, I can check in chrome and see that the values located at those areas changed, however, the colors of the rectangles do not update.
I don't understand why ".getGeometryInstanceAttributes(id)" runs 10x slower after it's called the first time, and why I cannot directly modify viewer.scene.primitives._primitives[1].geometryInstances[i].attributes.color.value[0].
Thanks

How can I create multiple instances of a class without defining them?

So, I'm making a game on HTML5 canvas. It's a top down shooter, and I need to create a bullet every time you click to make the character shoot.
Initially, I just prevented the player from firing another bullet until it went out of bounds or it hit an enemy, as seen here. This worked percetly, but of course, makes for uninteresting gameplay.
Then, I began researching about JS classes, and I thought that it would be the key to the problem. I created a bullet class, and moved all the logic for the bullet to the class. Then, I created an instance of it, and called it in other parts of the code to execute its logic. This worked exactly as it did before, which is good, because it meant I could translate the thing I had before to a class, but it had a similar issue.
This is how the class is defined:
class bullet{
constructor(_img, _piercing){
this.bulletPic = document.createElement("img");
this.img = this.bulletPic.src = _img;
this.piercing = _piercing;
}
shoot(){
this.bulletAngle = playerAngle;
this.bulletX = playerX;
this.bulletY = playerY;
bulletShot = true;
shots = 0;
}
draw(){
canvasContext.save();
canvasContext.translate(this.bulletX, this.bulletY);
canvasContext.rotate(this.bulletAngle);
canvasContext.drawImage(this.bulletPic, -this.bulletPic.width / 2, -this.bulletPic.height / 2);
canvasContext.restore();
if(bulletShot){
this.bulletX += Math.sin(this.bulletAngle) * BULLET_SPEED;
this.bulletY -= Math.cos(this.bulletAngle) * BULLET_SPEED;
}
}
}
And here is the object definition:
let bullet1 = new bullet("Textures/player.png", true);
If I want to shoot another bullet at the same time, I need to have already defined a new instance of the bullet class, is there any way for me to define a new instance every time I click?
Edit: The shoot and draw methods are called in another file that follow logic that's not shown here. Mainly what this other code does, is detect when it hits an enemy or when it goes out of bounds to set "bulletShot" to false, that makes it "despawn", and I can shoot another bullet. This is part of the 1 bullet at a time limitation I'm trying to remove here, but that can go once this central issue is fixed.
If I understand your situation, you could use a function that returns a new class:
function bulletFactory( className ) {
return new className();
}
If you want to achieve that there could be several bullets in "mid-air", after a series of fast consecutive clicks, then create an array of bullets. You would initialise that array like this:
const bullets = Array({length: ammo}, () => new Bullet());
ammo would be the number of bullets that the user can shoot in total.
NB: I simplified the call of the constructor. Add the arguments you want to pass. Secondly, it is common practice to start class names with a capital.
Then add a state property in the Bullet instances that indicates whether the bullet is:
Hidden: it is not visible yet, but part of the total ammunition that can still be used in the future
Ready: it is the one bullet that is visible at the start location, ready to be fired by the user
Shot: a bullet that has been shot and is currently flying through the game area
At first this state is "hidden":
constructor(_img, _piercing){
this.state = "hidden";
// ...
}
draw() {
if (this.state === "hidden") return; // Don't draw bullets that are not available
// ...
}
Then at the start of the game, make one bullet visible (where it should be clicked):
bullets[0].state = "ready"; // From now on it will be drawn when `draw()` is called
In the click handler do the following:
// Fire the bullet the user clicked on:
bullets.find(bullet => bullet.state === "ready").shoot(playerAngle, playerX, playerY);
// See if there is a next bullet remaining in the user's ammo:
const nextBullet = bullets.find(bullet => bullet.state === "hidden");
if (nextBullet) nextBullet.state = "ready"; // Otherwise ammo is depleted.
The shoot method should not rely on global variables, but get the necessary external info as arguments:
shoot(playerAngle, playerX, playerY) {
this.bulletAngle = playerAngle;
this.bulletX = playerX;
this.bulletY = playerY;
this.state = "shot";
}
Don't use global variables inside your class methods (shot, ammo,...). Instead use arguments or other instance properties.
The draw method should also work with that state:
draw() {
if (this.state === "hidden") return; // Don't draw bullets that are not available
// ...
if(this.state === "shot") {
this.bulletX += Math.sin(this.bulletAngle) * BULLET_SPEED;
this.bulletY -= Math.cos(this.bulletAngle) * BULLET_SPEED;
}
}
In your animation loop, you should call draw on all bullets. Something like:
bullets.forEach(bullet => bullet.draw());
I did not see any code for when a bullet has left the game area, either by hitting something or just flying out of range. In such case the bullet should be removed from the bullets array to avoid that the draw method keeps drawing things without (visual) significance.
Here is how you could delete a specific bullet:
function deleteBullet(bullet) {
const i = bullets.indexOf(bullet);
if (i > -1) bullets.splice(i, 1);
}
I hope this gets you going on your project.
I ended up making an array that contains multiple instances of the class. I defined a variable that I used as a limit and then set up a for statement to create all the objects, then, I can call them using the array name and the position.
for(var i = 0; i < arraySize; i++){
arrayName[i] = new className(parameters);
}
Examples of usage:
arrayName[5].method();

Generate new array with .pop() value

I am drawing points on a canvas with javascript, like so:
function addPointToMap(point) {
var pointRadius = (document.getElementById(point.canvasId).height * (2 / 66)) / 2;
var context = document.getElementById(point.canvasId).getContext("2d");
context.beginPath();
context.arc(point.x, point.y, pointRadius, 0, 2 * Math.PI);
context.fillStyle = "red";
context.fill();
}
and all the points I am drawing are stored in an array, pointMap. I want the user to only be able to draw one point if a checkbox is ticked, and draw many points if it is not ticked. A new point should override an old one. In order to do this, I have decided to add the new point to the array, and then remove the old one and refresh the canvas. The problem is that pointMap = pointMap.pop(); is returning an empty array. How do I get the most recent entry in an array and delete all the other entries? Here is what I have so far:
if (questionId == 41) {
if (pointMap.length == 1) {
//do nothing, user only has 1 point
} else {
console.log("PointMap: " + pointMap); //ex. returns [Point, Point] (Point is a custom class I wrote to store the point x and y values)
pointMap = pointMap.pop(); //this line does not work
console.log("PointMap: " + pointMap); //ex. returns []
refreshCanvas();
}
}
Where am I going wrong? can anyone steer me in the right direction?
pop returns the popped value, so pointMap = pointMap.pop() will replace your array reference with a point.
If you want to only have a single point in the array when the checkbox is checked, simply overwrite it:
if (checkboxIsChecked) {
// Only want one point, assign to index 0 (works whether the
// array already has a point or not)
pointMap[0] = theNewPoint;
} else {
// Want to allow multiple points, push the point onto the array
pointMap.push(theNewPoint);
}
If the user can check the checkbox while there are already values in pointMap, you'll want to remove all but the last one when they check it. In your event handler for the checkbox:
if (checkboxIsChecked && pointMap.length > 1) {
// Remove all entries except the last pushed one
pointMap.splice(0, pointMap.length - 1);
}

unable to applyMatrix in three.js

I am trying to run an animation from a JSON file. I am using a custom JSON loader, (i.e. not the one included with three.js).
So I have an object named frames, which contain many frames, all of them have shape information, and a simulation_matrix, which contains data required for animation in the form of a 4by4 transformation matrix(generated from a python script).
So I am using this code for animation ..
and this is a sample JSON script to load.
// This method is for adding static shapes
// This works perfectly fine ..
parent.add_shape = function(frame)
{
var material = new THREE.MeshLambertMaterial({
color: frame.shape.color,
wireframe: true,
wireframeLinewidth: 0.1,
opacity: 0.5
})
var geometry = new THREE.CylinderGeometry(frame.shape.radius,frame.shape.radius,frame.shape.height,50,50);
// mesh_dict dictionary maps a mesh(shape) to its frame
parent.mesh_dict[frame] = new THREE.Mesh(geometry,material);
var init_orientation = frame.simulation_matrix[0];
var orienter = new THREE.Matrix4();
orienter.elements = [];
//Since simulation_matrix is generated from python, it is a
// list of lists, We need to push it to the elemens of Matrix4 manually ..
for(var i in init_orientation)
{
for(var j in init_orientation[i])
{
orienter.elements.push(init_orientation[i][j]) ;
}
}
parent.mesh_dict[frame].applyMatrix(new THREE.Matrix4());
parent.mesh_dict[frame].applyMatrix(orienter);
parent.scene.add(parent.mesh_dict[frame]);
parent.renderer.render(parent.scene,parent.camera);
}
// This method basically takes the meshes defined in add_shape, and
// applies simulation matrix to it, and requests animation frame for
// animation.
parent.animate = function()
{
for(var frame in JSONObj.frames)
{
// defining simulation_matrix in a var.
var matrix = JSONObj.frames[frame].simulation_matrix[parent.animation_counter];
var animation_matrix = new THREE.Matrix4();
animation_matrix.elements = [];
// pushing it to a Matrix4
for(var i in matrix)
{
for(var j in matrix[i])
{
animation_matrix.elements.push(matrix[i][j]) ;
}
}
console.log(animation_matrix);
console.log(animation_matrix.elements);
// Making sure we are not applying matrix to the earlier transform
//mesh_dict is a dictionary of meshes, used in creating shapes,mapped to the
//frame which contains them
parent.mesh_dict[JSONObj.frames[frame]].applyMatrix(new THREE.Matrix4());
// now applying transform, after setting to identity matrix ...
parent.mesh_dict[JSONObj.frames[frame]].applyMatrix(animation_matrix);
}
console.log(parent.animation_counter);
//update timestep ...
parent.animation_counter++;
// This is to loop over again and again ...
// assuming 10 animations frames
if(parent.animation_counter == 10){ parent.animation_counter = 0; }
requestAnimationFrame(parent.animate);
}
The problem is that I am able to create the multiple shapes, but when I apply simulation matrix to them in the loop, only one of them is animating, that too in very unexpected manner.
Well I have figured out what was wrong. Somehow, all the dictionary parent.mesh_dict[] keys were mapped to a same single object, instead of all objects as required. Now I debugged it, and it is working like a charm. Also your point is valid #WestLangley, as I now use mesh.matrix.identity() to get things done. Thanks, I will close this question now.

ThreeJS - how to set current time in Animation

I'm using skinning / skeletal animation in ThreeJS. I have an animation, and I want to be able to move backward and forward through it, and jump to different locations within it, rather than the usual looping behaviour.
The animation is created like this, as in the example:
var animation = new THREE.Animation( mesh, geometry.animation.name );
I have tried updating the animation with negative deltas, as well as setting animation.currentTime directly:
animation.currentTime = animationLocation;
These appear to work only if I move forward in time, but if I go backward the animation breaks and I get an error:
THREE.Animation.update: Warning! Scale out of bounds: ... on bone ...
One thing that does actually work without error is to call stop() and then play() with a new start time:
animation.stop();
animation.play( true, animationLocation );
...however when I look at what these functions are actually doing, they involve many many function calls, looping, resetting transforms etc. This seems like a horrible way to do it, even if it works as a hack.
It may be that this functionality does not exist yet, in which case I'll try to dig in and create a function that does a minimal amount of work, but I'm hoping there is another way that I haven't found.
Can anyone help with this?
[UPDATE]
As an update on my progress, I'll post the best solution I have at this time...
I pulled out the contents of the stop() and play() functions, and stripped out everything I could, making some assumptions about certain values having already been set by 'play()'.
This still seems like it is probably not the best way to do it, but it is doing a bit less work than by just calling stop() then play().
This is what I was able to get it down to:
THREE.Animation.prototype.gotoTime = function( time ) {
//clamp to duration of the animation:
time = THREE.Math.clamp( time, 0, this.length );
this.currentTime = time;
// reset key cache
var h, hl = this.hierarchy.length,
object;
for ( h = 0; h < hl; h ++ ) {
object = this.hierarchy[ h ];
var prevKey = object.animationCache.prevKey;
var nextKey = object.animationCache.nextKey;
prevKey.pos = this.data.hierarchy[ h ].keys[ 0 ];
prevKey.rot = this.data.hierarchy[ h ].keys[ 0 ];
prevKey.scl = this.data.hierarchy[ h ].keys[ 0 ];
nextKey.pos = this.getNextKeyWith( "pos", h, 1 );
nextKey.rot = this.getNextKeyWith( "rot", h, 1 );
nextKey.scl = this.getNextKeyWith( "scl", h, 1 );
}
//isPlaying must be true for update to work due to "early out"
//so remember the current play state:
var wasPlaying = this.isPlaying;
this.isPlaying = true;
//update with a delta time of zero:
this.update( 0 );
//reset the play state:
this.isPlaying = wasPlaying;
}
The main limitation of the function in terms of usefulness is that you can't interpolate from one arbitrary time to another. You can basically just scrub around in the animation.
You can use THREE.Clock and assign startTime, oldTime, elapsedTime.

Categories

Resources