I have coded a neural network in JavaScript and implemented the Backpropagation algorithm described here.
Here is the code (typescript):
/**
* Net
*/
export class Net {
private layers: Layer[] = [];
private inputLayer: Layer;
private outputLayer: Layer;
public error: number = Infinity;
private eta: number = 0.15;
private alpha: number = 0.5;
constructor(...topology: number[]) {
topology.forEach((topologyLayer, iTL) => {
var nextLayerNeuronNumber = topology[iTL + 1] || 0;
this.layers.push(new Layer(topologyLayer, nextLayerNeuronNumber));
});
this.inputLayer = this.layers[0];
this.outputLayer = this.layers[this.layers.length - 1];
}
public loadWeights(weights) {
/*
[
[Layer
[Node weights, ..., ...]
]
]
*/
for (var iL = 0; iL < weights.length; iL++) {
var neuronWeights = weights[iL];
var layer = this.layers[iL];
for (var iN = 0; iN < neuronWeights.length; iN++) {
// Neuron
var connections = neuronWeights[iN];
for (var iC = 0; iC < connections.length; iC++) {
var connection = connections[iC];
this.layer(iL).neuron(iN).setWeights(iC, connection);
}
}
}
}
public train(data: number[][], iterartions = 2000) {
var inputs = this.inputLayer.neurons.length - 1;
for (var ite = 0; ite < iterartions; ite++) {
data.forEach(node => {
var inputData = [];
var outputData = [];
for (var i = 0; i < node.length; i++) {
if (i < inputs) {
inputData.push(node[i])
} else {
outputData.push(node[i])
}
}
this.feedForward(...inputData);
this.backProb(...outputData);
});
}
return this.calcDataError(data);
}
private calcDataError(data){
var overallDataErrorSum = 0;
var inputs = this.inputLayer.neurons.length - 1;
data.forEach(node => {
var outputData = node.splice(inputs);
var inputData = node;
this.feedForward(...inputData);
overallDataErrorSum += this.getNetError(outputData);
});
overallDataErrorSum /= data.length;
return overallDataErrorSum;
}
public saveWeights() {
// Ignore output layer
var ret = []
for (var iL = 0; iL < this.layers.length - 1; iL++) {
var layer = this.layers[iL];
var layer_ret = [];
layer.neurons.forEach(neuron => {
layer_ret.push(neuron.connections.map(c => c.weight));
});
ret.push(layer_ret);
}
return ret;
}
feedForward(...inputs: number[]) {
if (inputs.length != this.inputLayer.neurons.length - 1) return false;
this.inputLayer.neurons.forEach((neuron, i) => {
if (!neuron.isBias) {
neuron.output(inputs[i]);
}
});
this.layers.forEach((layer, i) => {
// Skip Input Layer
if (i > 0) {
var prevLayer = this.layers[i - 1]
layer.neurons.forEach(neuron => {
neuron.calcOutput(prevLayer);
});
}
});
}
public getNetError(targetVals) {
// Calc delta error of outputs
var deltas = [];
this.outputLayer.neurons.forEach((neuron, iN) => {
if (!neuron.isBias) {
neuron.calcOutputDelta(targetVals[iN]);
deltas.push(neuron.delta);
}
});
deltas = deltas.map(d => Math.pow(d, 2));
var sum = 0;
deltas.forEach(d => sum += d);
return sum / deltas.length;
}
backProb(...targetVals: number[]) {
// Calc delta error of outputs
this.outputLayer.neurons.forEach((neuron, iN) => {
if (!neuron.isBias) {
neuron.calcOutputDelta(targetVals[iN]);
}
});
// Backprop delta error through hidden layers
for (var iL = this.layers.length - 2; iL > 0; iL--) {
var layer = this.layers[iL];
var nextLayer = this.layers[iL + 1]
layer.neurons.forEach(neuron => {
neuron.calcHiddenDelta(nextLayer);
});
}
// Update weights
for (var iL = 1; iL < this.layers.length; iL++) {
var layer = this.layers[iL];
var prevLayer = this.layers[iL - 1];
layer.neurons.forEach(neuron => {
if (!neuron.isBias) {
neuron.updateWeights(prevLayer, this.eta);
}
});
}
this.error = this.getNetError(targetVals);
return this.error;
}
getOutputs(...inputs: number[]) {
var ret = [];
this.outputLayer.neurons.forEach(neuron => {
if (!neuron.isBias) {
ret.push(neuron.output())
}
});
return ret;
}
getResults(...inputs: number[]) {
this.feedForward(...inputs)
return this.getOutputs();
}
layer(i) {
return this.layers[i];
}
}
/**
* Layer
*/
class Layer {
public neurons: Neuron[] = [];
constructor(neuronNumber: number, nextLayerNeuronNumber: number) {
for (var iN = 0; iN < neuronNumber + 1; iN++) {
// +1 for bias neuron, which is last
if (iN < neuronNumber) {
// Create normal neuron
this.neurons.push(new Neuron(nextLayerNeuronNumber, iN, false));
} else {
this.neurons.push(new Neuron(nextLayerNeuronNumber, iN, true));
}
}
}
neuron(i) {
return this.neurons[i];
}
bias() {
return this.neurons[this.neurons.length - 1];
}
}
/**
* Neuron
*/
class Neuron {
public connections: Connection[] = [];
private outputVal: number;
public delta: number;
constructor(outputsTo: number, private index, public isBias = false) {
// Creates connections
for (var c = 0; c < outputsTo; c++) {
this.connections.push(new Connection());
}
this.outputVal = isBias ? 1 : 0;
}
calcOutput(prevLayer: Layer) {
// Only calcOutput when neuron is not a bias neuron
if (!this.isBias) {
var sum = 0;
prevLayer.neurons.forEach(prevLayerNeuron => {
sum += prevLayerNeuron.output() * prevLayerNeuron.getWeights(this.index).weight;
});
this.output(this.activationFunction(sum));
}
}
private activationFunction(x) {
//return Math.tanh(x);
return 1 / (1 + Math.exp(-x))
//return x;
};
private activationFunctionDerivative(x) {
// Small approximation of tanh derivative
//return 1 - x * x
// Sigmoid
var s = this.activationFunction(x);
return s * (1 - s);
// With general derivative formula where h = 1e-10
/*var h = 0.0001;
var dx = ((this.activationFunction(x + h) - this.activationFunction(x))/h)
return dx;*/
//return 1
};
// Backprop // Todo // Understand
public calcOutputDelta(targetVal) {
// Bias output neurons do not have delta error
if (!this.isBias) {
this.delta = targetVal - this.output();
}
}
public calcHiddenDelta(nextLayer: Layer) {
var sum = 0;
// Go through all neurons of next layer excluding bias
nextLayer.neurons.forEach((neuron, iN) => {
if (!neuron.isBias) {
sum += neuron.delta * this.getWeights(iN).weight;
}
});
this.delta = sum;
}
public updateWeights(prevLayer: Layer, eta: number) {
prevLayer.neurons.forEach((neuron, iN) => {
var weight = neuron.getWeights(this.index).weight;
var newWeight =
weight + // old weight
eta * // learning weight
this.delta * // delta error
this.activationFunctionDerivative(neuron.output())
neuron.getWeights(this.index).weight = newWeight;
});
}
// Backprop end
output(s?) {
if (s && !this.isBias) {
this.outputVal = s;
return this.outputVal;
} else {
return this.outputVal;
}
}
getWeights(i) {
return this.connections[i];
}
setWeights(i, s) {
return this.connections[i].weight = s;
}
}
/**
* Connection
*/
class Connection {
public weight: number;
public deltaWeight: number;
constructor() {
this.weight = Math.random();
this.deltaWeight = 0;
}
}
When training it for just one set of data, it works just fine. (example from here)
import {Net} from './ml';
var myNet = new Net(2, 2, 2);
var weights = [
[
[0.15, 0.25],
[0.20, 0.30],
[0.35, 0.35]
],
[
[0.40, 0.50],
[0.45, 0.55],
[0.60, 0.60]
]
];
// Just loads the weights given in the example
myNet.loadWeights(weights)
var error = myNet.train([[0.05, 0.10, 0.01, 0.99]]);
console.log('Error: ', error);
console.log(myNet.getResults(0.05, 0.10));
Console prints:
Error: 0.0000020735174706210714
[ 0.011556397089327321, 0.9886867357304885 ]
Basically, that's pretty good, right?
Then, I wanted to teach the network the XOR problem:
import {Net} from './ml';
var myNet = new Net(2, 3, 1);
var trainigData = [
[0, 0, 0],
[1, 0, 1],
[0, 1, 1],
[1, 1, 0]
]
var error = myNet.train(trainigData)
console.log('Error: ', error);
console.log('Input: 0, 0: ', myNet.getResults(0, 0));
console.log('Input: 1, 0: ', myNet.getResults(1, 0));
Here the network fails:
Error: 0.2500007370167383
Input: 0, 0: [ 0.5008584967899313 ]
Input: 1, 0: [ 0.5008584967899313 ]
What am I doing wrong?
Firstly perform gradient checks on the entire batch (meaining on the function calculating gradients on the batch), if you have not done so already. This will ensure you know what the problem is.
If gradients are not correctly computed, taking into account that your implementation works on single data sets, you are most likely mixing some values in the backwards pass.
If gradients are correctly computed, there is an error in your update function.
A working implementation of backpropagation for neural networks in javaScript can be found here
Here is the code snippet of the trainStep function using backpropagation
function trainStepBatch(details){
//we compute forward pass
//for each training sample in the batch
//and stored in the batch array
var batch=[];
var ks=[];
for(var a=0;a<details.data.in.length;a++){
var results=[];
var k=1;
results[0]={output:details.data.in[a]};
for(var i=1;i<this.layers.length;i++){
results[i]=layers[this.layers[i].type].evalForGrad(this.layers[i],results[i-1].output);
k++;
}
batch[a]=results;
ks[a]=k;
}
//We compute the backward pass
//first derivative of the cost function given the output
var grad=[];
for(i in batch)grad[i]={grad:costs[details.cost].df(batch[i][ks[i]-1].output,details.data.out[i])};
//for each layer we compute the backwards pass
//on the results of all forward passes at a given layer
for(var i=this.layers.length-1;i>0;i--){
var grads=[];
var test=true;
for(a in batch){
grads[a]=layers[this.layers[i].type].grad(this.layers[i],batch[a][i],batch[a][i-1],grad[a]);
if(grads[a]==null)test=false;
else grads[a].layer=i;
}
//we perform the update
if(test)stepBatch(this.layers[i].par,grads,details.stepSize);
}
}
And for the stepBatch function
function stepBatch(params,grads, stepSize){
for(i in params.w){
for(j in params.w[i]){
for(a in grads){
params.w[i][j]-=stepSize*grads[a].dw[i][j];
}
}
}
for(i in params.b){
for(a in grads){
params[a]-=stepSize*grads[a].db[i];
}
}
function stepBatch(params,grads, stepSize){
for(i in params.w){
for(j in params.w[i]){
for(a in grads){
params.w[i][j]-=stepSize*grads[a].dw[i][j];
}
}
}
for(i in params.b){
for(a in grads){
params[a]-=stepSize*grads[a].db[i];
}
}
}
Related
I am doing create multiple worker threads, in my case, i am trying to create 2:
This is my code to create work thread
function createWorker(data1, data2) {
return new Promise((resolve) => {
let worker = new Worker();
worker.postMessage(data1, data2);
worker.onmessage = (event) => {
postMessageRes = event.data;
if (postMessageRes == 200) {
// loadCOPC();
} else {
workerCount += 1;
let position = postMessageRes[0];
let color = postMessageRes[1];
for (let i = 0; i < position.length; i++) {
positions.push(position[i]);
colors.push(colors[i]);
}
resolve(true);
}
};
});
}
and using it in my loop
for (let m = 0; m < keyCountMap.length; ) {
let remaining = totalNodes - doneCount;
let numbWorker = Math.min(chunk, remaining);
for (let i = 0; i < numbWorker; i++) {
promises.push(createWorker([keyCountMap[m], keyCountMap[m + 1]]));
m += 2;
}
Promise.all(promises).then((response) => {
console.log("one chunk finishes");
});
}
The code works fine if i instead of all these use one static work thread and call postMessage in the loop for only one but not while i am trying to make chunk like here in the code.
When i run the code, my browser freeze
This is my worker file:
import { Copc, Key } from "copc";
import * as THREE from "three";
const color = new THREE.Color();
const colors = [];
let firstTime = true;
var nodePages, pages, receivedData, copc;
let x_min, y_min, z_min, x_max, y_max, z_max, width;
let positions = [];
let filename = "https://s3.amazonaws.com/data.entwine.io/millsite.copc.laz";
const readPoints = (id, getters) => {
let returnPoint = getXyzi(id, getters);
positions.push(
returnPoint[0] - x_min - 0.5 * width,
returnPoint[1] - y_min - 0.5 * width,
returnPoint[2] - z_min - 0.5 * width
);
const vx = (returnPoint[3] / 65535) * 255;
color.setRGB(vx, vx, vx);
colors.push(color.r, color.g, color.b);
firstTime = false;
};
function getXyzi(index, getters) {
return getters.map((get) => get(index));
}
async function load() {
copc = await Copc.create(filename);
let scale = copc.header.scale[0];
[x_min, y_min, z_min, x_max, y_max, z_max] = copc.info.cube;
width = Math.abs(x_max - x_min);
// let center_x = (x_min + x_max) / 2;
// let center_y = (y_min + y_max) / 2;
// let center_z = (z_min + z_max) / 2;
receivedData = await Copc.loadHierarchyPage(
filename,
copc.info.rootHierarchyPage
);
nodePages = receivedData.nodes;
pages = receivedData.pages;
postMessage(200);
}
async function loadData(myRoot, pointCount) {
const view = await Copc.loadPointDataView(filename, copc, myRoot);
let getters = ["X", "Y", "Z", "Intensity"].map(view.getter);
for (let j = 0; j < pointCount; j += 1) {
readPoints(j, getters);
}
postMessage([positions, colors]);
}
load();
onmessage = function (message) {
let mapIndex = message.data[0];
let pointCount = message.data[1];
console.log(mapIndex);
let myRoot = nodePages[mapIndex];
loadData(myRoot, pointCount);
};
The issue might be due to creating multiple worker threads in a loop, which could lead to a large number of worker threads and result in high memory usage and ultimately lead to freezing of the browser.
One approach to resolve this issue is to limit the number of worker threads created at a time by setting a maximum number of workers, and creating new workers only when some workers have completed their tasks.
Here's an example of the code that implements this approach:
const MAX_WORKERS = 4;
let promises = [];
let workerCount = 0;
function createWorker(data1, data2) {
return new Promise((resolve) => {
let worker = new Worker();
worker.postMessage(data1, data2);
worker.onmessage = (event) => {
postMessageRes = event.data;
if (postMessageRes == 200) {
// loadCOPC();
} else {
workerCount += 1;
let position = postMessageRes[0];
let color = postMessageRes[1];
for (let i = 0; i < position.length; i++) {
positions.push(position[i]);
colors.push(colors[i]);
}
if (workerCount === MAX_WORKERS) {
workerCount = 0;
promises = [];
}
resolve(true);
}
};
});
}
for (let m = 0; m < keyCountMap.length; ) {
let remaining = totalNodes - doneCount;
let numbWorker = Math.min(chunk, remaining);
for (let i = 0; i < numbWorker; i++) {
promises.push(createWorker([keyCountMap[m], keyCountMap[m + 1]]));
m += 2;
}
if (workerCount === MAX_WORKERS) {
Promise.all(promises).then((response) => {
console.log("one chunk finishes");
});
}
}
I'm new to both languages, and I'm trying to convert this program I found on github and edited, to p5.js so I can include it in a webpage. I tried following guides and replacing void() with function(), int i with var i etc.. but there seems to be something wrong. The first code is the original .pde and the second one is my attempt at converting it. Many thanks!
final int STAGE_WIDTH = 1200;
final int STAGE_HEIGHT = 950;
final int NB_PARTICLES = 60000;
final float MAX_PARTICLE_SPEED = 5;
final int MIN_LIFE_TIME = 20;
final int MAX_LIFE_TIME = 80;
final String IMAGE_PATH = "starrynight.jpg";
myVector tabParticles[];
float particleSize = 1.2;
PImage myImage;
int imageW;
int imageH;
color myPixels[];
FlowField ff;
GUI gui;
void setup()
{
size(1200, 950, P3D);
background(0);
initializeImage();
initializeParticles();
ff = new FlowField(5);
gui = new GUI(this);
gui.setup();
}
void initializeImage()
{
myImage = loadImage(IMAGE_PATH);
imageW = myImage.width;
imageH = myImage.height;
myPixels = new color[imageW * imageH];
myImage.loadPixels();
myPixels = myImage.pixels;
image(myImage, 0, 0);
}
void setParticle(int i) {
tabParticles[i] = new myVector((int)random(imageW), (int)random(imageH));
tabParticles[i].prevX = tabParticles[i].x;
tabParticles[i].prevY = tabParticles[i].y;
tabParticles[i].count = (int)random(MIN_LIFE_TIME, MAX_LIFE_TIME);
tabParticles[i].myColor = myPixels[(int)(tabParticles[i].y)*imageW + (int)(tabParticles[i].x)];
}
void initializeParticles()
{
tabParticles = new myVector[NB_PARTICLES];
for (int i = 0; i < NB_PARTICLES; i++)
{
setParticle(i);
}
}
void draw()
{
ff.setRadius(gui.getR());
ff.setForce(gui.getF());
particleSize = gui.getS();
float vx;
float vy;
PVector v;
for (int i = 0; i < NB_PARTICLES; i++)
{
tabParticles[i].prevX = tabParticles[i].x;
tabParticles[i].prevY = tabParticles[i].y;
v = ff.lookup(tabParticles[i].x, tabParticles[i].y);
vx = v.x;
vy = v.y;
vx = constrain(vx, -MAX_PARTICLE_SPEED, MAX_PARTICLE_SPEED);
vy = constrain(vy, -MAX_PARTICLE_SPEED, MAX_PARTICLE_SPEED);
tabParticles[i].x += vx;
tabParticles[i].y += vy;
tabParticles[i].count--;
if ((tabParticles[i].x < 0) || (tabParticles[i].x > imageW-1) ||
(tabParticles[i].y < 0) || (tabParticles[i].y > imageH-1) ||
tabParticles[i].count < 0) {
setParticle(i);
}
strokeWeight(1.5*particleSize);
stroke(tabParticles[i].myColor, 250);
line(tabParticles[i].prevX, tabParticles[i].prevY, tabParticles[i].x, tabParticles[i].y);
}
ff.updateField();
}
void mouseDragged() {
if(mouseX>950 && mouseY>830) return;
ff.onMouseDrag();
}
void keyPressed() {
//if (key =='s' || key == 'S') {
// ff.saveField();
//}
}
class myVector extends PVector
{
myVector (float p_x, float p_y) {
super(p_x, p_y);
}
float prevX;
float prevY;
int count;
color myColor;
}
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
class FlowField {
PVector[][] field;
PVector[][] tempField;
int cols, rows;
int resolution;
int affectRadius;
float force;
File file = new File(dataPath("field.txt"));
FlowField(int r) {
resolution = r;
cols = 1200 / resolution;
rows = 950 / resolution;
field = new PVector[cols][rows];
tempField = new PVector[cols][rows];
init();
affectRadius = 3;
force = 1;
}
void setRadius(int r) {
affectRadius = r;
}
void setForce(float f) {
force = f;
}
void init() {
try {
for (int i=0; i<cols; i++) {
for (int j=0; j<rows; j++) {
tempField[i][j] = new PVector(0, 0);
}
}
readField();
}
catch(Exception e) {
for (int i=0; i<cols; i++) {
for (int j=0; j<rows; j++) {
field[i][j] = new PVector(0, 0);
}
}
}
}
PVector lookup(float x, float y) {
int column = int(constrain(x/resolution, 0, cols-1));
int row = int(constrain(y/resolution, 0, rows-1));
return PVector.add(field[column][row],tempField[column][row]);
}
void drawBrush() {
pushStyle();
noFill();
stroke(255, 255, 255);
ellipse(mouseX, mouseY, affectRadius*10, affectRadius*10);
popStyle();
}
void drawField(float x, float y, PVector v) {
int column = int(constrain(x/resolution, 0, cols-1));
int row = int(constrain(y/resolution, 0, rows-1));
for (int i=-affectRadius; i<=affectRadius; i++) {
for (int j=-affectRadius; j<=affectRadius; j++) {
if (i*i+j*j<affectRadius*affectRadius) {
try {
tempField[column+i][row+j].add(v).mult(0.9);
}
catch(Exception e) {
}
}
}
}
}
void updateField(){
for (int i=0; i<cols; i++) {
for (int j=0; j<rows; j++) {
tempField[i][j].mult(0.992);
}
}
}
void onMouseDrag() {
PVector direc = new PVector(mouseX-pmouseX, mouseY-pmouseY).normalize();
drawField(pmouseX, pmouseY, direc.mult(force));
}
void saveField() {
try {
FileWriter out = new FileWriter(file);
for (int i=0; i<cols; i++) {
for (int j=0; j<rows; j++) {
out.write(field[i][j].x+","+field[i][j].y+"\t");
}
out.write("\r\n");
}
out.close();
}
catch(Exception e) {
}
}
void readField() throws IOException {
try {
BufferedReader in = new BufferedReader(new FileReader(file));
String line;
for (int i = 0; (line = in.readLine()) != null; i++) {
String[] temp = line.split("\t");
for (int j=0; j<temp.length; j++) {
String[] xy = temp[j].split(",");
float x = Float.parseFloat(xy[0]);
float y = Float.parseFloat(xy[1]);
field[i][j] = new PVector(x, y);
}
}
in.close();
}
catch(Exception e) {
throw new IOException("no field.txt");
}
}
}
import controlP5.*;
class GUI {
ControlP5 cp5;
Slider sliderR;
Slider sliderF;
Slider sliderS;
GUI(PApplet thePApplet){
cp5 = new ControlP5(thePApplet);
}
void setup(){
cp5.setColorBackground(0x141414);
sliderR = cp5.addSlider("Radius")
.setPosition(980,890)
.setRange(1,20)
.setValue(12).setSize(150,25);
sliderF = cp5.addSlider("Force")
.setPosition(980,918)
.setRange(0.1,0.5)
.setValue(0.3).setSize(150,25);
sliderS = cp5.addSlider("Particle Size")
.setPosition(980,862)
.setRange(0.8,2)
.setValue(1.5).setSize(150,25);
}
int getR(){
return int(sliderR.getValue());
}
float getF(){
return sliderF.getValue();
}
float getS(){
return sliderS.getValue();
}
}
final var STAGE_WIDTH = 1200;
final var STAGE_HEIGHT = 950;
final var NB_PARTICLES = 60000;
final let MAX_PARTICLE_SPEED = 5;
final var MIN_LIFE_TIME = 20;
final var MAX_LIFE_TIME = 80;
final let IMAGE_PATH = "starrynight.jpg";
myVector tabParticles[];
let particleSize = 1.2;
PImage myImage;
var imageW;
var imageH;
color myPixels[];
FlowField ff;
GUI gui;
function setup()
{
var canvas = createCanvas(1200, 950, P3D);
canvas.parent('canvasForHTML');
background(0);
initializeImage();
initializeParticles();
ff = new FlowField(5);
gui = new GUI(this);
gui.setup();
}
function preload() { img = loadImage('data/starrynight.jpg');
}
function initializeImage()
{ imageW = myImage.width;
imageH = myImage.height;
myPixels = new color[imageW * imageH];
myImage.loadPixels();
myPixels = myImage.pixels;
image(myImage, 0, 0);
}
function setParticle(var i) {
tabParticles[i] = new myVector((var)random(imageW), (var)random(imageH));
tabParticles[i].prevX = tabParticles[i].x;
tabParticles[i].prevY = tabParticles[i].y;
tabParticles[i].count = (var)random(MIN_LIFE_TIME, MAX_LIFE_TIME);
tabParticles[i].myColor = myPixels[(var)(tabParticles[i].y)*imageW + (var)(tabParticles[i].x)];
}
function initializeParticles()
{
tabParticles = new myVector[NB_PARTICLES];
for (var i = 0; i < NB_PARTICLES; i++)
{
setParticle(i);
}
}
function draw()
{
ff.setRadius(gui.getR());
ff.setForce(gui.getF());
particleSize = gui.getS();
let vx;
let vy;
PVector v;
for (var i = 0; i < NB_PARTICLES; i++)
{
tabParticles[i].prevX = tabParticles[i].x;
tabParticles[i].prevY = tabParticles[i].y;
v = ff.lookup(tabParticles[i].x, tabParticles[i].y);
vx = v.x;
vy = v.y;
vx = constrain(vx, -MAX_PARTICLE_SPEED, MAX_PARTICLE_SPEED);
vy = constrain(vy, -MAX_PARTICLE_SPEED, MAX_PARTICLE_SPEED);
tabParticles[i].x += vx;
tabParticles[i].y += vy;
tabParticles[i].count--;
if ((tabParticles[i].x < 0) || (tabParticles[i].x > imageW-1) ||
(tabParticles[i].y < 0) || (tabParticles[i].y > imageH-1) ||
tabParticles[i].count < 0) {
setParticle(i);
}
strokeWeight(1.5*particleSize);
stroke(tabParticles[i].myColor, 250);
line(tabParticles[i].prevX, tabParticles[i].prevY, tabParticles[i].x, tabParticles[i].y);
}
ff.updateField();
}
function mouseDragged() {
if(mouseX>950 && mouseY>830) return;
ff.onMouseDrag();
}
function keyPressed() {
//if (key =='s' || key == 'S') {
// ff.saveField();
//}
}
class myVector extends PVector
{
myVector (let p_x, let p_y) {
super(p_x, p_y);
}
let prevX;
let prevY;
var count;
color myColor;
}
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
class FlowField {
PVector[][] field;
PVector[][] tempField;
var cols, rows;
var resolution;
var affectRadius;
let force;
File file = new File(dataPath("field.txt"));
FlowField(var r) {
resolution = r;
cols = 1200 / resolution;
rows = 950 / resolution;
field = new PVector[cols][rows];
tempField = new PVector[cols][rows];
init();
affectRadius = 3;
force = 1;
}
function setRadius(var r) {
affectRadius = r;
}
function setForce(let f) {
force = f;
}
function init() {
try {
for (var i=0; i<cols; i++) {
for (var j=0; j<rows; j++) {
tempField[i][j] = new PVector(0, 0);
}
}
readField();
}
catch(Exception e) {
for (var i=0; i<cols; i++) {
for (var j=0; j<rows; j++) {
field[i][j] = new PVector(0, 0);
}
}
}
}
PVector lookup(let x, let y) {
var column = var(constrain(x/resolution, 0, cols-1));
var row = var(constrain(y/resolution, 0, rows-1));
return PVector.add(field[column][row],tempField[column][row]);
}
function drawBrush() {
pushStyle();
noFill();
stroke(255, 255, 255);
ellipse(mouseX, mouseY, affectRadius*10, affectRadius*10);
popStyle();
}
function drawField(let x, let y, PVector v) {
var column = var(constrain(x/resolution, 0, cols-1));
var row = var(constrain(y/resolution, 0, rows-1));
for (var i=-affectRadius; i<=affectRadius; i++) {
for (var j=-affectRadius; j<=affectRadius; j++) {
if (i*i+j*j<affectRadius*affectRadius) {
try {
tempField[column+i][row+j].add(v).mult(0.9);
}
catch(Exception e) {
}
}
}
}
}
function updateField(){
for (var i=0; i<cols; i++) {
for (var j=0; j<rows; j++) {
tempField[i][j].mult(0.992);
}
}
}
function onMouseDrag() {
PVector direc = new PVector(mouseX-pmouseX, mouseY-pmouseY).normalize();
drawField(pmouseX, pmouseY, direc.mult(force));
}
function saveField() {
try {
FileWriter out = new FileWriter(file);
for (var i=0; i<cols; i++) {
for (var j=0; j<rows; j++) {
out.write(field[i][j].x+","+field[i][j].y+"\t");
}
out.write("\r\n");
}
out.close();
}
catch(Exception e) {
}
}
function readField() throws IOException {
try {
BufferedReader in = new BufferedReader(new FileReader(file));
let line;
for (var i = 0; (line = in.readLine()) != null; i++) {
let[] temp = line.split("\t");
for (var j=0; j<temp.length; j++) {
let[] xy = temp[j].split(",");
let x = let.parselet(xy[0]);
let y = let.parselet(xy[1]);
field[i][j] = new PVector(x, y);
}
}
in.close();
}
catch(Exception e) {
throw new IOException("no field.txt");
}
}
}
import controlP5.*;
class GUI {
ControlP5 cp5;
Slider sliderR;
Slider sliderF;
Slider sliderS;
GUI(PApplet thePApplet){
cp5 = new ControlP5(thePApplet);
}
function setup(){
cp5.setColorBackground(0x141414);
sliderR = cp5.addSlider("Radius")
.setPosition(980,890)
.setRange(1,20)
.setValue(12).setSize(150,25);
sliderF = cp5.addSlider("Force")
.setPosition(980,918)
.setRange(0.1,0.5)
.setValue(0.3).setSize(150,25);
sliderS = cp5.addSlider("Particle Size")
.setPosition(980,862)
.setRange(0.8,2)
.setValue(1.5).setSize(150,25);
}
var getR(){
return var(sliderR.getValue());
}
let getF(){
return sliderF.getValue();
}
let getS(){
return sliderS.getValue();
}
}
I felt like doing nothing this evening, so here you go:
Also, I recommend getting the "Better Comments" extension/plugin for your code editor of choice, since I used those here a lot
const STAGE_WIDTH = 1200;
const STAGE_HEIGHT = 950;
const NB_PARTICLES = 60000;
const MAX_PARTICLE_SPEED = 5;
const MIN_LIFE_TIME = 20;
const MAX_LIFE_TIME = 80;
const IMAGE_PATH = "starrynight.jpg";
let tabParticles = [];
let particleSize = 1.2;
let myImage;
let imageW;
let imageH;
let myPixels = [];
let ff;
let gui;
function setup()
{
size(1200, 950, WEBGL);
background(0);
initializeImage();
initializeParticles();
ff = new FlowField(5);
// ! CHANGE THIS AND ALL OF THE OTHER GUI STUFF THAT COME AFTER IT!
// gui = new GUI(this);
// gui.setup();
}
function initializeImage()
{
myImage = loadImage(IMAGE_PATH);
imageW = myImage.width;
imageH = myImage.height;
myPixels = new color[imageW * imageH]; // ? dunno
// myImage.loadPixels();
// myPixels = myImage.pixels;
image(myImage, 0, 0);
}
function setParticle(i) {
tabParticles[i] = new myVector(random(imageW), random(imageH));
tabParticles[i].prevX = tabParticles[i].x;
tabParticles[i].prevY = tabParticles[i].y;
tabParticles[i].count = random(MIN_LIFE_TIME, MAX_LIFE_TIME);
tabParticles[i].myColor = myPixels[(int)(tabParticles[i].y)*imageW + (int)(tabParticles[i].x)];
}
function initializeParticles()
{
// tabParticles = new myVector[NB_PARTICLES];
for(let i = 0; i < NB_PARTICLES; i ++)
tabParticles.push(new myVector())
for (let i = 0; i < NB_PARTICLES; i++)
{
setParticle(i);
}
}
function draw()
{
ff.setRadius(gui.getR());
ff.setForce(gui.getF());
particleSize = gui.getS();
let vx;
let vy;
let v;
for (let i = 0; i < NB_PARTICLES; i++)
{
tabParticles[i].prevX = tabParticles[i].x;
tabParticles[i].prevY = tabParticles[i].y;
v = ff.lookup(tabParticles[i].x, tabParticles[i].y);
vx = v.x;
vy = v.y;
vx = constrain(vx, -MAX_PARTICLE_SPEED, MAX_PARTICLE_SPEED);
vy = constrain(vy, -MAX_PARTICLE_SPEED, MAX_PARTICLE_SPEED);
tabParticles[i].x += vx;
tabParticles[i].y += vy;
tabParticles[i].count--;
if ((tabParticles[i].x < 0) || (tabParticles[i].x > imageW-1) ||
(tabParticles[i].y < 0) || (tabParticles[i].y > imageH-1) ||
tabParticles[i].count < 0) {
setParticle(i);
}
strokeWeight(1.5*particleSize);
stroke(tabParticles[i].myColor, 250);
line(tabParticles[i].prevX, tabParticles[i].prevY, tabParticles[i].x, tabParticles[i].y);
}
ff.updateField();
}
function mouseDragged() {
if(mouseX>950 && mouseY>830) return;
ff.onMouseDrag();
}
function keyPressed() {
//if (key =='s' || key == 'S') {
// ff.saveField();
//}
}
class myVector extends PVector
{
constructor (p_x, p_y) {
super(p_x, p_y);
this.prevX
this.prevY
this.count
this.myColor
}
}
class FlowField {
constructor(r) {
this.field; // PVector[][]
this.tempField; // PVector[][]
this.cols; this.rows; // int
this.resolution; // int
this.affectRadius; // int
this.force; // float
this.file = new File(dataPath("field.txt")); // File
// ! You'll need to have a preload loadStrings(filepath) or fetch(filename).then(blob => blob.text()).then(text => ... )
// ! can't be bothered to do this right now
throw "Ult1: change this!!! also delete this line (just incase: ~129th line)"
resolution = r;
cols = 1200 / resolution;
rows = 950 / resolution;
field = new PVector[cols][rows];
tempField = new PVector[cols][rows];
init();
affectRadius = 3;
force = 1;
}
setRadius(r) {
affectRadius = r;
}
setForce(f) {
force = f;
}
// FROM HERE ON I CONVERTED IT WHILE HALF ASLEEP (11pm)
init() {
try {
for (let i=0; i<cols; i++) {
for (let j=0; j<rows; j++) {
tempField[i][j] = createVector()
}
}
readField();
}
catch(e) {
for (let i=0; i<cols; i++) {
for (let j=0; j<rows; j++) {
field[i][j] = createVector()
}
}
}
}
lookup(x, y) {
let column = int(constrain(x/resolution, 0, cols-1));
let row = int(constrain(y/resolution, 0, rows-1));
return p5.Vector.add(field[column][row],tempField[column][row]);
}
drawBrush() {
pushStyle();
noFill();
stroke(255, 255, 255);
ellipse(mouseX, mouseY, affectRadius*10, affectRadius*10);
popStyle();
}
drawField(x, y, v) {
let column = int(constrain(x/resolution, 0, cols-1));
let row = int(constrain(y/resolution, 0, rows-1));
for (let i=-affectRadius; i<=affectRadius; i++) {
for (let j=-affectRadius; j<=affectRadius; j++) {
if (i*i+j*j<affectRadius*affectRadius) {
try {
tempField[column+i][row+j].add(v).mult(0.9);
}
catch(e) {
}
}
}
}
}
updateField(){
for (let i=0; i<cols; i++) {
for (let j=0; j<rows; j++) {
tempField[i][j].mult(0.992);
}
}
}
onMouseDrag() {
let direc = createVector(mouseX-pmouseX, mouseY-pmouseY).normalize()
drawField(pmouseX, pmouseY, direc.mult(force));
}
saveField() {
try {
// FileWriter out = new FileWriter(file);
throw "FileWriter doesn't exist in Javascript, line: 215"
// ! this doesn't exist! in javascript, I don't remember how to do this, but you can just search it on Google
for (let i=0; i<cols; i++) {
for (let j=0; j<rows; j++) {
out.write(field[i][j].x+","+field[i][j].y+"\t");
}
out.write("\r\n");
}
out.close();
}
catch(e) {
}
}
readField() {
throw "once again, BufferedReader is just not a thing, but it's for file reading, so you can just loadString(\"file\") or fetch... line: 230"
try {
let _in = new BufferedReader(new FileReader(file));
let line;
for (let i = 0; (line = _in.readLine()) != null; i++) {
let temp = line.split("\t");
for (let j=0; j<temp.length; j++) {
let xy = temp[j].split(",");
let x = Float.parseFloat(xy[0]);
let y = Float.parseFloat(xy[1]);
field[i][j] = createVector(x, y);
}
}
_in.close();
}
catch(e) {
throw new IOException("no field.txt");
}
}
}
// I think you should just do this with html to be honest
// copy this into your body (below the <script src="sketch.js"></script>, or above it):
/*
<div id="GUI">
<label for="Radius">Radius</label>
<input type="range" name="Radius" id="slider-radius" min="1" max="20" step="0.5" value="10">
<br>
<label for="Force">Force</label>
<input type="range" name="Force" id="slider-force" min="0.1" max="0.5" step="0.01" value="0.3">
<br>
<label for="Particle-size">Particle size</label>
<input type="range" name="Particle-size" id="slider-particle-size" min="0.8" max="2" step="0.05" value="1.5">
</div>
<style>
html, body {
background-color: #141414;
}
label {
color: #ccc;
}
</style>
*/
// color: #ccc is the same as text-color = #CCCCCC in some other language, maybe...
class GUI {
constructor(){
throw "don't declare the GUI class, instead do GUI.getR(), GUI.getF() ... line: 288, you should be able to see line # in your console"
}
static getR(){
return document.getElementById("slider-radius").value;
}
static getF(){
return document.getElementById("slider-force").value
}
static getS(){
return document.getElementById("slider-particle-size").value
}
}
// static = same between ALL the classes(instances) of a class thing, so:
// class A { static x = 7; this.y = 5 }
// * console.log(A.x) -> 7
// ! console.log(A.y) -> probably null, since you need to do console.log(new A().y)
// here I have it to keep the GUI class, but there is no need to keep this stuff
You will still need to do A LOT of work to get this going, like translating classes, for example class myVector extends PVector {...}. In p5.js there is no PVector, but I'm pretty sure there's p5.Vector though, so class myVector extends p5.Vector might work. File reading and writing will also be messed up, since JavaScript doesn't have the whole new File() and all the stuff like that.
You will also need to deal with CORS, good luck! I also changed most of the GUI stuff to .html stuff and a static class, bit hard words there, but can't really change them to anything.
Anyways, I probably made mistakes here!
It's 11:40 pm here by now, so yeah! this is very more or less transferrrr
I'm implementing A*(A-star) algorithm in react.js but my program crashes whenever startNode(green) or destinationNode(blue) have more than one neighbour or if there is a cycle in the graph. There is something wrong when adding and deleting the neighbours from/to openList or when updating the parentId in the getPath() function. I cant even see the console because the website goes down.
Each node has: id, name, x, y, connectedToIdsList:[], gcost:Infinity, fcost:0, heuristic:0, parentId:null.
I'm sending the path to another component "TodoList" which prints out the path. My program should not return the path but keeps updating the path as i'm adding nodes and edges to the list. Please help, I've been stuck for hours now:/
My code:
export default class TurnByTurnComponent extends React.PureComponent {
constructor(props) {
super(props);
this.state = { shortestPath: [] }
}
render() {
const {
destinationLocationId,
locations,
originLocationId
} = this.props;
let path = []
if (destinationLocationId != null && originLocationId != null) {
if (originLocationId == destinationLocationId) { //check if the startNode node is the end node
return originLocationId;
}
var openList = [];
let startNode = getNodeById(originLocationId);
let destinationNode = getNodeById(destinationLocationId)
if (startNode.connectedToIds.length > 0 && destinationNode.connectedToIds.length > 0) { //check if start and destination nodes are connected first
startNode.gcost = 0
startNode.heuristic = manhattanDistance(startNode, destinationNode)
startNode.fcost = startNode.gcost + startNode.heuristic;
//perform A*
openList.push(startNode); //starting with the startNode
while (openList.length > 0) {
console.log("inside while")
var currentNode = getNodeOfMinFscore(openList); //get the node of the minimum f cost of all nodes in the openList
if (currentIsEqualDistanation(currentNode)) {
path = getPath(currentNode);
}
deleteCurrentFromOpenList(currentNode, openList);
for (let neighbourId of currentNode.connectedToIds) {
var neighbourNode = getNodeById(neighbourId);
currentNode.gcost = currentNode.gcost + manhattanDistance(currentNode, neighbourNode);
if (currentNode.gcost < neighbourNode.gcost) {
neighbourNode.parentId = currentNode.id; // keep track of the path
// total cost saved in neighbour.g
neighbourNode.gcost = currentNode.gcost;
neighbourNode.heuristic = manhattanDistance(neighbourNode, destinationNode);
neighbourNode.fcost = neighbourNode.gcost + neighbourNode.heuristic; //calculate f cost of the neighbourNode
addNeighbourNodeToOpenList(neighbourNode, openList);
}
}
}
path = path.reverse().join("->");
}
}
function addNeighbourNodeToOpenList(neighbourNode, openList) {
//add neighbourNode to the open list to be discovered later
if (!openList.includes(neighbourNode)) {
openList.push(neighbourNode);
}
}
function deleteCurrentFromOpenList(currNode, openList) {
const currIndex = openList.indexOf(currNode);
openList.splice(currIndex, 1); //deleting currentNode from openList
}
function currentIsEqualDistanation(currNode) {
//check if we reached out the distanation node
return (currNode.id == destinationLocationId)
}
function getNodeById(nid) {
var node;
for (let i = 0; i < locations.length; i++) {
if (locations[i].id == nid) {
node = locations[i]
}
}
return node
}
function getPath(destNode) {
console.log("inside getpath")
var parentPath = []
var parent;
while (destNode.parentId != null) {
parentPath.push(destNode.name)
parent = destNode.parentId;
destNode = getNodeById(parent);
}
//adding startNode to the path
parentPath.push(getNodeById(originLocationId).name)
return parentPath;
}
function getNodeOfMinFscore(openList) {
var minFscore = openList[0].fcost; //initValue
var nodeOfminFscore;
for (let i = 0; i < openList.length; i++) {
if (openList[i].fcost <= minFscore) {
minFscore = openList[i].fcost //minFvalue
nodeOfminFscore = openList[i]
}
}
return nodeOfminFscore
}
//manhattan distance is for heuristic and gScore. Here I use Manhattan instead of Euclidean
//because in this example we dont have diagnosal path.
function manhattanDistance(stNode, dstNode) {
var x = Math.abs(dstNode.x - stNode.x);
var y = Math.abs(dstNode.y - stNode.y);
var dist = x + y;
return dist;
}
return (
<div className="turn-by-turn-component">
<TodoList
list={"Shortest path: ", [path]}
/>
<TodoList
list={[]}
/>
</div>
);
}
}
TurnByTurnComponent.propTypes = {
destinationLocationId: PropTypes.number,
locations: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
connectedToIds: PropTypes.arrayOf(PropTypes.number.isRequired).isRequired
})),
originLocationId: PropTypes.number
};
Update new issue
Pictures of before and after linking a new node. When I update the graph and add a new node the path disappears. And sometimes come back and so on if I still add new nodes and edges to the graph. As I said, each node has: id, name, x, y, connectedToIdsList:[], gcost:Infinity, fcost:0, heuristic:0, parentId:null.
My new code now is:
export default class TurnByTurnComponent extends React.PureComponent {
constructor(props) {
super(props);
this.state = { shortestPath: [] }
}
render() {
const {
destinationLocationId,
locations,
originLocationId
} = this.props;
let path = []
if (destinationLocationId != null && originLocationId != null) {
console.log(JSON.stringify(locations))
path = [getNodeById(originLocationId).namne];
if (originLocationId != destinationLocationId) {
var openList = [];
let startNode = getNodeById(originLocationId);
let destinationNode = getNodeById(destinationLocationId)
if (startNode.connectedToIds.length > 0 && destinationNode.connectedToIds.length > 0) {
startNode.gcost = 0
startNode.heuristic = manhattanDistance(startNode, destinationNode)
startNode.fcost = startNode.gcost + startNode.heuristic;
openList.push(startNode); //starting with the startNode
while (openList.length > 0) {
var currentNode = getNodeOfMinFscore(openList); //get the node of the minimum f cost of all nodes in the openList
if (currentIsEqualDistanation(currentNode)) {
path = getPath(currentNode);
break;
}
deleteCurrentFromOpenList(currentNode, openList);
for (let neighbourId of currentNode.connectedToIds) {
var neighbourNode = getNodeById(neighbourId);
let gcost = currentNode.gcost + manhattanDistance(currentNode, neighbourNode);
if (gcost < (neighbourNode.gcost ?? Infinity)) {
neighbourNode.parentId = currentNode.id;
// keep track of the path
// total cost saved in neighbour.g
neighbourNode.gcost = gcost;
neighbourNode.heuristic = manhattanDistance(neighbourNode, destinationNode);
neighbourNode.fcost = neighbourNode.gcost + neighbourNode.heuristic; //calculate f cost of the neighbourNode
addNeighbourNodeToOpenList(neighbourNode, openList);
}
}
}
}
}
}
path = path.reverse().join("->");
function addNeighbourNodeToOpenList(neighbourNode, openList) {
//add neighbourNode to the open list to be discovered later
if (!openList.includes(neighbourNode)) {
openList.push(neighbourNode);
}
}
function deleteCurrentFromOpenList(currentNode, openList) {
const currIndex = openList.indexOf(currentNode);
openList.splice(currIndex, 1); //deleting currentNode from openList
}
function currentIsEqualDistanation(currentNode) {
//check if we reached out the distanation node
return (currentNode.id == destinationLocationId)
}
function getNodeById(id) {
var node;
for (let i = 0; i < locations.length; i++) {
if (locations[i].id == id) {
node = locations[i]
}
}
return node
}
function getPath(destinationNode) {
console.log("inside getpath")
var parentPath = []
var parent;
while (destinationNode.parentId != null) {
parentPath.push(destinationNode.name)
parent = destinationNode.parentId;
destinationNode = getNodeById(parent);
}
//adding startNode to the path
parentPath.push(getNodeById(originLocationId).name)
return parentPath;
}
function getNodeOfMinFscore(openList) {
var minFscore = openList[0].fcost; //initValue
var nodeOfminFscore;
for (let i = 0; i < openList.length; i++) {
if (openList[i].fcost <= minFscore) {
minFscore = openList[i].fcost //minFvalue
nodeOfminFscore = openList[i]
}
}
return nodeOfminFscore
}
//manhattan distance is for heuristic and gScore. Here I use Manhattan instead of Euclidean
//because in this example we dont have diagnosal path.
function manhattanDistance(startNode, destinationNode) {
var x = Math.abs(destinationNode.x - startNode.x);
var y = Math.abs(destinationNode.y - startNode.y);
var dist = x + y;
return dist;
}
return (
<div className="turn-by-turn-component">
<TodoList
title="Mandatory work"
list={[path]}
/>
<TodoList
title="Optional work"
list={[]}
/>
</div>
);
}
}
TurnByTurnComponent.propTypes = {
destinationLocationId: PropTypes.number,
locations: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
x: PropTypes.number.isRequired,
y: PropTypes.number.isRequired,
connectedToIds: PropTypes.arrayOf(PropTypes.number.isRequired).isRequired
})),
originLocationId: PropTypes.number
};
There are a few issues in your code:
return originLocationId; should not happen. When the source is equal to the target, then set the path, and make sure the final return of this function is executed, as you want to return the div element.
When the target is found in the loop, not only should the path be built, but the loop should be exited. So add a break
currentNode.gcost = currentNode.gcost + manhattanDistance(currentNode, neighbourNode); is not right: you don't want to modify currentNode.gcost: its value should not depend on the outgoing edge to that neighbour. Instead store this sum in a temporary variable (like gcost)
The comparison with neighbourNode.gcost will not work when that node does not yet have a gcost member. I don't see in your code that it got a default value that makes sure this condition is true, so you should use a default value here, like (neighbourNode.gcost ?? Infinity)
path = path.reverse().join("->"); should better be executed always, also when there is no solution (so path is a string) or when the source and target are the same node.
Here is a corrected version, slightly adapted to run here as a runnable snippet:
function render() {
const {
destinationLocationId,
locations,
originLocationId
} = this.props;
let path = [];
if (destinationLocationId != null && originLocationId != null) {
path = [originLocationId]; // The value for when the next if condition is not true
if (originLocationId != destinationLocationId) {
var openList = [];
let startNode = getNodeById(originLocationId);
let destinationNode = getNodeById(destinationLocationId)
if (startNode.connectedToIds.length > 0 && destinationNode.connectedToIds.length > 0) {
startNode.gcost = 0
startNode.heuristic = manhattanDistance(startNode, destinationNode)
startNode.fcost = startNode.gcost + startNode.heuristic;
openList.push(startNode);
while (openList.length > 0) {
var currentNode = getNodeOfMinFscore(openList);
if (currentIsEqualDistanation(currentNode)) {
path = getPath(currentNode);
break; // Should end the search here!
}
deleteCurrentFromOpenList(currentNode, openList);
for (let neighbourId of currentNode.connectedToIds) {
var neighbourNode = getNodeById(neighbourId);
// Should not modify the current node's gcost. Use a variable instead:
let gcost = currentNode.gcost + manhattanDistance(currentNode, neighbourNode);
// Condition should also work when neighbour has no gcost yet:
if (gcost < (neighbourNode.gcost ?? Infinity)) {
neighbourNode.parentId = currentNode.id;
neighbourNode.gcost = gcost; // Use the variable
neighbourNode.heuristic = manhattanDistance(neighbourNode, destinationNode);
neighbourNode.fcost = neighbourNode.gcost + neighbourNode.heuristic;
addNeighbourNodeToOpenList(neighbourNode, openList);
}
}
}
}
}
}
// Convert the path to string in ALL cases:
path = path.reverse().join("->");
function addNeighbourNodeToOpenList(neighbourNode, openList) {
if (!openList.includes(neighbourNode)) {
openList.push(neighbourNode);
}
}
function deleteCurrentFromOpenList(currNode, openList) {
const currIndex = openList.indexOf(currNode);
openList.splice(currIndex, 1);
}
function currentIsEqualDistanation(currNode) {
return (currNode.id == destinationLocationId)
}
function getNodeById(nid) {
var node;
for (let i = 0; i < locations.length; i++) {
if (locations[i].id == nid) {
node = locations[i]
}
}
return node
}
function getPath(destNode) {
var parentPath = []
var parentId;
while (destNode.parentId != null) {
parentPath.push(destNode.name)
parentId = destNode.parentId;
destNode = getNodeById(parentId);
}
parentPath.push(getNodeById(originLocationId).name)
return parentPath;
}
function getNodeOfMinFscore(openList) {
var minFscore = openList[0].fcost;
var nodeOfminFscore;
for (let i = 0; i < openList.length; i++) {
if (openList[i].fcost <= minFscore) {
minFscore = openList[i].fcost
nodeOfminFscore = openList[i]
}
}
return nodeOfminFscore
}
function manhattanDistance(stNode, dstNode) {
var x = Math.abs(dstNode.x - stNode.x);
var y = Math.abs(dstNode.y - stNode.y);
var dist = x + y;
return dist;
}
return "Shortest path: " + path;
}
// Demo
let props = {
locations: [
{ id: 1, x: 312, y: 152, connectedToIds: [4,2], name: "Thetaham" },
{ id: 2, x: 590, y: 388, connectedToIds: [1,3], name: "Deltabury" },
{ id: 3, x: 428, y: 737, connectedToIds: [2], name: "Gammation" },
{ id: 4, x: 222, y: 430, connectedToIds: [1], name: "Theta City" },
],
originLocationId: 1,
destinationLocationId: 3,
};
console.log(render.call({props}));
This is bothering my mind for a few weeks now. I have a working example of some Javascript that updates the DOM within a loop.
But I can't get this "trick" to work for my real code.
This is the link to my Plunk: https://plnkr.co/edit/oRf6ES74TJatPRetZEec?p=preview
The HTML:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css">
<link rel="stylesheet" href="style.css">
<script src="http://code.jquery.com/jquery-1.12.4.js"></script>
<script src="http://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script src="js/script.js"></script>
<script src="js/script2.js"></script>
<script src="js/json-util.js"></script>
<script src="js/scorito-dal.js"></script>
<script src="js/matching.js"></script>
</head>
<body>
<div id="log">..log here..</div>
<button onclick="doHeavyJob();">do heavy job</button>
<button onclick="doHeavyJobJquery();">do heavy job with jQuery</button>
<button onclick="match();">match</button>
</body>
</html>
The script in js/script.js, which is called from the button "do heavy job" works:
async function doHeavyJob() {
$('#log').html('');
for (var j=0; j<10; j++) {
await sleep(1000)
console.log('Iteration: ' + j);
(function (j) {
setTimeout(function() { // pause the loop, and update the DOM
var logPanel = document.getElementById('log');
var txt = 'DOM update' + j;
logPanel.innerHTML += txt + ', ';
}, 0);
})(j);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
The exact same code does not work within a jQuery each(), this is appearantly due to jQuery, and easy to work around by using the for-loop. If you're interested, check script2.js in my plunk.
My real script in js/matching, which is called from the button "match" does NOT work.
var _keeperCombinations = [];
var _defenderCombinations = [];
var _midfielderCombinations = [];
var _attackerCombinations = [];
var _mostPoints = 0;
var _bestCombination = [];
function updateStatus(keeperCount, ixKeeper, msg) {
msg = '%gereed: ' + Math.round(ixKeeper / keeperCount * 100, 0);
console.log(msg);
$('#log').html(msg);
}
function match() {
$('#log').html('');
updateStatus(1, 1, 'Match started');
var playersData = scoritoDal.getPlayersData();
this.determineKeepers(playersData);
this.determineDefenders(playersData);
this.determineMidfielders(playersData);
this.determineAttackers(playersData);
var keeperCount = _keeperCombinations.length
for (var ixKeeper=0; ixKeeper<keeperCount; ixKeeper++) {
var keepers = _keeperCombinations[ixKeeper];
doMatching(keepers, keeperCount, ixKeeper);
}
if (_bestCombination.length === 0) {
alert('Er kon geen beste combinatie worden bepaald');
}
else {
alert('Ready: ' + _bestCombination.toString());
$(_bestCombination).each(function(ix, playerId) {
});
}
}
/*
* Match 2 keepers, 5 defenders, 6 midfielders and 5 attackers
* First pick the 5 best keepers, 10 best defenders, 12 best midfielders and 10 best attackers.
* 3 / 2 (k), 7 / 3 (d & a) and 8 / 4 (m) >> most points / most points per prices
*/
var _confirmed = false;
function doMatching(keepers, keeperCount, ixKeeper) {
// Make a new promise
let p = new Promise(
// The executor function is called with the ability to resolve or reject the promise
(resolve, reject) => {
for (var ixDefenders=0; ixDefenders<_defenderCombinations.length; ixDefenders++) {
var defenders = _defenderCombinations[ixDefenders];
for (var ixMidfielders=0; ixMidfielders<_midfielderCombinations.length; ixMidfielders++) {
var midfielders = _midfielderCombinations[ixMidfielders];
for (var ixAttackers=0; ixAttackers<_attackerCombinations.length; ixAttackers++) {
var attackers = _attackerCombinations[ixAttackers];
procesPlayers(keepers, defenders, midfielders, attackers);
}
}
resolve(ixDefenders);
}
});
p.then(
function(ixDefenders){
console.log('LOG: ' + keeperCount + " - " + ixKeeper);
updateStatus(keeperCount, ixKeeper);
}
);
}
async function procesPlayers(keepers, defenders, midfielders, attackers) {
var totals = calculateTotals(keepers, defenders, midfielders, attackers);
// check if total price is within budget
if (totals.TotalPrice <= 56500000) {
if (totals.TotalPoints > _mostPoints) {
_mostPoints = totals.TotalPoints;
setBestCombination(keepers, defenders, midfielders, attackers);
}
}
}
function calculateTotals(keepers, defenders, midfielders, attackers) {
var playerIds = [];
var totalPoints = 0;
var totalPrice = 0;
var allPlayers = keepers.concat(defenders, midfielders, attackers);
var checkTeams = [];
$(allPlayers).each(function(ix, player){
var club = checkTeams.find(t => t.Name === player.Club);
if (!club) {
club = {"Name":player.Club, "Count":1};
checkTeams.push(club);
}
else club.Count++;
if (club.Count > 4) {
totalPoints = 0;
return false;
}
playerIds.push(player.ID);
var factor = 1;
if (player.Position === 'Keeper' && ix > 0) factor = 0.5; // 2nd keeper gets less points
if (player.Position === 'Defender' && ix > 2) factor = 0.5; // 4th defender gets less points
if (player.Position === 'Midfielder' && ix > 3) factor = 0.5; // 5th midfielder gets less points
if (player.Position === 'Attacker' && ix > 2) factor = 0.5; // 4th attacker gets less points
var playerPoints = player.Points * factor;
totalPoints += playerPoints;
totalPrice += player.Price;
});
return {"TotalPoints":totalPoints,"TotalPrice":totalPrice};
}
function determineKeepers(playersData) {
console.log('Determining keepers');
$('#progres').text('Determine 5 best keepers');
var bestKeepers = this.determineBestPlayers(playersData, 'Keeper', 3, 2); // 3, 2
if (bestKeepers && $(bestKeepers).length > 0) {
// now determine all combinations
this.determineCombinations(bestKeepers, 2);
_keeperCombinations = this._combinations;
}
}
function determineDefenders(playersData) {
console.log('Determining defenders');
$('#progres').text('Determining 10 best defenders');
var bestDefenders = this.determineBestPlayers(playersData, 'Defender', 5, 3); // 6, 3
if (bestDefenders && $(bestDefenders).length > 0) {
// now determine all combinations
this.determineCombinations(bestDefenders, 5);
_defenderCombinations = this._combinations;
}
}
function determineMidfielders(playersData) {
console.log('Determining midfielders');
$('#progres').text('Determine 12 best midfielders');
var bestMidfielders = this.determineBestPlayers(playersData, 'Midfielder', 5, 3); // 7, 3
if (bestMidfielders && $(bestMidfielders).length > 0) {
// now determine all combinations
console.log('*** Determining all midfielder combinations ***');
this.determineCombinations(bestMidfielders, 6);
_midfielderCombinations = this._combinations;
}
}
function determineAttackers(playersData) {
console.log('Determining attackers');
$('#progres').text('Determine 10 best attackers');
var bestAttackers = this.determineBestPlayers(playersData, 'Attacker', 5, 3); // 6, 3
if (bestAttackers && $(bestAttackers).length > 0) {
// now determine all combinations
this.determineCombinations(bestAttackers, 5);
_attackerCombinations = this._combinations;
}
}
/*
* Determine the best players for a position
* - position: Keeper|Defender|Midfielder|Attacker
* - byPoints: the nr of best players by points
* - byFactor: the nr of best players by factor
*/
function determineBestPlayers(playersData, position, byPoints, byFactor) {
var players = $.grep(playersData, function(p) {return p.Position === position});
var playersByPoints = players.sort(jsonUtil.sortByProperty('Points', true));
var bestPlayersByPoints = playersByPoints.slice(0, byPoints);
var playersByFactor = players.sort(jsonUtil.sortByProperty('Factor', true));
var bestPlayersByFactor = playersByFactor.slice(0, byFactor);
// players could be in both lists, make it unique
var bestPlayers = jsonUtil.joinArrays(bestPlayersByPoints, bestPlayersByFactor, true, 'ID');
// if not the nr wanted, add extra players
// take theze by turn on points / on factor
var cnt = $(bestPlayers).length;
var nextByPoints = true;
var cntExtra = 0;
while (cnt < byPoints + byFactor) {
cntExtra++;
var isOK = false;
while (!isOK) {
var ix=0; // if the next player is already chosen, move to the next
var extraPlayer = {};
if (nextByPoints) {
extraPlayer = playersByPoints[byPoints+cntExtra+ix-1];
}
else {
extraPlayer = playersByFactor[byFactor+cntExtra+ix-1];
}
if (!bestPlayers.find(x => x.ID === extraPlayer.ID)) {
bestPlayers.push(extraPlayer);
isOK = true;
}
else {
ix++;
}
}
nextByPoints = !nextByPoints;
cnt++;
}
bestPlayers = bestPlayers.sort(jsonUtil.sortByProperty('Points', true)); // add the end we want the players sorted by total points
console.log('Best player for position ' + position);
console.log(bestPlayersToString(bestPlayers));
return bestPlayers;
}
function bestPlayersToString(bestPlayers) {
var result = '';
var sep = '';
$(bestPlayers).each(function(ix, player) {
result += sep;
result += JSON.stringify(player);
if (sep === '') sep = ',';
});
return result;
}
function setBestCombination(keepers, defenders, midfielders, attackers) {
_bestCombination = [];
$(keepers).each(function(ix, keeper){
_bestCombination.push(keeper.ID);
});
$(defenders).each(function(ix, defender){
_bestCombination.push(defender.ID);
});
$(midfielders).each(function(ix, midfielder){
_bestCombination.push(midfielder.ID);
});
$(attackers).each(function(ix, attacker){
_bestCombination.push(attacker.ID);
});
}
/* Combinations */
var _combinations = [];
var _curCombination = [];
function determineCombinations(players, cnt) {
_combinations = [];
_curCombination = [];
this.addCombinations(players, cnt);
}
/*
* Take 2 from 5 (keepers), 5 from 10 (defenders, attackera) or 6 from 12 (midfielders)
* It is a rather complex recursive method with nested iterations over the
* (remaining players).
*/
var _curLevel = 1;
function addCombinations(players, cnt) {
for (var i=0; i<players.length; i++) {
var player = players[i];
_curCombination.push(player);
if (_curCombination.length == cnt) {
_combinations.push(_curCombination.slice(0)); // slicing creates a clone
//console.log(curCombinationToString());
_curCombination.pop();
continue;
}
var curPlayers = players.slice(i+1);
_curLevel++;
addCombinations(curPlayers, cnt);
};
_curCombination.pop();
_curLevel--;
}
function curCombinationToString() {
var result = '';
var sep = '';
$(_curCombination).each(function(ix, player) {
result += sep;
result += JSON.stringify(player);
if (sep === '') sep = ',';
});
return result;
}
Any ideas will be greatly appreciated!!!!
finally got this to work, after numerous, NUMEROUS attempts.
And off course I'd like to share this with you.
Here's the essence to getting this to work:
the loop must be inside an async function
the code with the logic for 1 loop must be inside a Promise
the code also needs to be within setTimeout
the call for this logic must be 'annotated' with await
So this works:
'use strict';
async function testPromise() {
var msg;
for (var ix=1; ix<10; ix++) {
msg = 'Before loop #'+ix
$('#log').html(msg);
await doActualWork();
msg = 'After loop #'+ix
$('#log').html(msg);
}
}
/*
* Perform logic for one loop
*/
function doActualWork() {
return new Promise(
function (resolve, reject) {
window.setTimeout(
function () {
// do the actual work like calling an API
resolve();
}, 100);
}
);
}
I have a code that performs animation. a sphere moves from the beginning of a line to the end of the line. When starts again ends the motion again. starts from the first vertex and ends at the last vertex of the line.
I want to put 20 or so spheres, doing the same animation, but at the same time and in the same distance.
How can I do it?
this is my code:
var vertices = mesh.geometry.vertices;
var duration = 10;
function startToEnd() {
var i = 0;
async.eachSeries(vertices, function(vertice, callback) {
if (i !== 0) {
sphere.position.copy(vertices[i - 1]);
new TWEEN.Tween(sphere.position).to(vertices[i], duration).delay(duration).onComplete(function() {
callback(null);
}).start();
} else {
callback(null);
}
i++;
}, startToEnd);
}
startToEnd();
this image is a example..
this is result of my code
I got something that I think is pretty close to what you want:
var vertices = mesh.geometry.vertices;
var duration = 20;
var spheres = [];
var amountOfSpheres = 20;
for (var i = 0; i < amountOfSpheres; i++) {
spheres.push(new THREE.Sprite(rttMaterial));
scene.add(spheres[i]);
}
function endlessArrayIndex(index, arrayLength) {
if (index >= arrayLength) {
return index % arrayLength;
}
return index;
}
function startToEnd() {
i = 0;
async.each(spheres, function(sphere, cb1) {
var j = 0;
var verticeIndex = endlessArrayIndex(i * Math.round(vertices.length / amountOfSpheres), vertices.length);
async.eachSeries(vertices, function(vertice, cb2) {
if (verticeIndex !== 0) {
var verticeToCopy = vertices[verticeIndex - 1];
sphere.position.copy(verticeToCopy);
new TWEEN.Tween(sphere.position).to(vertices[verticeIndex], duration).delay(duration).onComplete(function() {
cb2(null);
}).start();
} else {
cb2(null);
}
verticeIndex = endlessArrayIndex(verticeIndex + 1, vertices.length);
}, cb1);
i++;
}, startToEnd);
}
startToEnd();
Result of the above code: