How to create a trail of circles that follows the mouse? - javascript

I am attempting to build something like this but instead of it being a line, I want it to be circles. That way I can add a different fill. So far, I am able to have it move on mouse over with this code but only for the first circle. How can I get them to follow in a line?
// The amount of points in the path:
var points = 30;
// The distance between the points:
var length = 10;
var path = new paper.Path({
strokeColor: "white",
strokeWidth: 50,
strokeCap: "round"
});
var start = view.center / [10, 1];
// Circle
var circlePath = new Path.Circle({
center: [80, 50],
fillColor: "transparent",
radius: 50
});
var thirdLayer = new Group();
for (var i = 0; i < points; i++) path.add(start + new Point(i * length, 0));
console.log(path);
// // for (var i = 0; i < points; i++) path.add(end + new Point(i * length, 0));
// // path.addSegments([[657.55, 455], [657.55, 500.5]]);
// path.closed = true;
function onMouseMove(event) {
path.firstSegment.point = event.point;
for (var i = 0; i < points - 1; i++) {
var segment = path.segments[i];
var nextSegment = segment.next;
var vector = segment.point - nextSegment.point;
vector.length = length;
nextSegment.point = segment.point - vector;
}
// rect.subtract(nextSegment.point);
path.smooth({ type: "continuous" });
var rect = new paper.Path.Rectangle({
point: [0, 0],
size: [view.size.width],
fillColor: "#E50069",
strokeWidth: 1
});
// path.offset(10);
var drilled = rect.subtract(path);
secondLayer.removeChildren();
secondLayer.addChild(drilled);
rect.remove();
secondLayer.addChild();
}
function onMouseDown(event) {
console.log(event);
path.fullySelected = true;
path.strokeColor = "#e08285";
}
function onMouseUp(event) {
path.fullySelected = false;
path.strokeColor = "#fff";
path.opacity = 1;
}
// function onFrame(event) {
// rect.unite(path);
// }
Any insight on what way to move forward would be appreciated.

Based on your reference, here is a sketch demonstrating a possible solution.
var points = 25;
var length = 35;
var path = new Path();
var start = view.center / [10, 1];
for (var i = 0; i < points; i++) {
path.add(start + new Point(i * length, 0));
}
// Create a circle for each segment of the path.
var circles = [];
for (var i = 0; i < path.segments.length; i++) {
var circle = new Path.Circle({
center: path.segments[i].point,
radius: 10,
strokeColor: 'red'
});
circles.push(circle);
}
function onMouseMove(event) {
path.firstSegment.point = event.point;
for (var i = 0; i < points - 1; i++) {
var segment = path.segments[i];
var nextSegment = segment.next;
var vector = segment.point - nextSegment.point;
vector.length = length;
nextSegment.point = segment.point - vector;
}
// Each time the path is updated, update circles position.
updateCirclesPosition();
}
function updateCirclesPosition() {
for (var i = 0; i < path.segments.length; i++) {
circles[i].position = path.segments[i].point;
}
}
Edit
Based on your comment below, here is a sketch demonstrating how to use the same logic to produce a "reveal image" effect.
The tricks relies on using blend mode to compose layers rather than having to use boolean operations (I originally posted it here).
// First draw an image as background.
var background = new Raster({
source: 'http://assets.paperjs.org/images/marilyn.jpg',
onLoad: function() {
// Make it fill all the screen.
this.fitBounds(view.bounds, true);
}
});
// Draw a rectangle to hide the background.
var maskBase = new Path.Rectangle({
rectangle: view.bounds,
fillColor: 'white'
});
// Prepare a group to store the circles that will make the background appear.
var circles = new Group({
blendMode: 'destination-out'
});
// Assemble both previous element in a group in order to make it display as we
// need.
var mask = new Group({
children: [maskBase, circles],
blendMode: 'source-over'
});
// Then prepare the path.
var points = 25;
var length = 35;
var path = new Path();
var start = view.center / [10, 1];
for (var i = 0; i < points; i++) {
path.add(start + new Point(i * length, 0));
}
// Create a circle for each segment of the path.
for (var i = 0; i < path.segments.length; i++) {
var circle = new Path.Circle({
center: path.segments[i].point,
radius: 10,
fillColor: 'red'
});
circles.addChild(circle);
}
// Update the path when the mouse moves.
function onMouseMove(event) {
path.firstSegment.point = event.point;
for (var i = 0; i < points - 1; i++) {
var segment = path.segments[i];
var nextSegment = segment.next;
var vector = segment.point - nextSegment.point;
vector.length = length;
nextSegment.point = segment.point - vector;
}
// Each time the path is updated, update circles position.
updateCirclesPosition();
}
function updateCirclesPosition() {
for (var i = 0; i < path.segments.length; i++) {
circles.children[i].position = path.segments[i].point;
}
}

Related

Adjusting lines positions on polygon

I am using FabricJS to generate a polygon with 6 points
var polygon = new fabric.Polygon([
new fabric.Point(150, 50),
new fabric.Point(250, 50),
new fabric.Point(250, 150),
new fabric.Point(150, 150),
new fabric.Point(50, 250),
]);
On each point I need to have a line, so I am iterating through points and adding lines successfully:
return new fabric.Line([p.x, p.y, fromX, fromY], {
stroke: colors[i],
strokeWidth: 10,
hasBorders: true,
strokeDashArray: [20, 5]
});
this code generates polygon successfully with my desired coloured line:
The issue is now I want to adjust lines positions, for example the red line I need to be sticky on top of the polygon, the green one I need it half inside of polygon, blue 5% distant from polygon, etc.
While searching I found, Check if Point Is Inside A Polygon and tried those functions, which gives different results, so I have two functions, isinPolygonFirst and isinPolygonSecond, which basically are checking if my point is inside polygon. I thought If I could know that if my points are inside polygon I can add some value, for example if point was [20,20 and if it is inside polygon I can increase to [21,21] and recheck if it is. I am posting debugging info(in snippet below needs to move polygon box to see debug messages):
// function to check if line exists in polygon
function isinPolygonFirst(points, longitude_x, latitude_y) {
vertices_y = new Array();
vertices_x = new Array();
var r = 0;
var i = 0;
var j = 0;
var c = 0;
var point = 0;
for (r = 0; r < points.length; r++) {
vertices_y.push(points[r].y);
vertices_x.push(points[r].x);
}
points_polygon = vertices_x.length;
for (i = 0, j = points_polygon; i < points_polygon; j = i++) {
point = i;
if (point == points_polygon)
point = 0;
if (((vertices_y[point] > latitude_y != (vertices_y[j] > latitude_y)) && (longitude_x < (vertices_x[j] - vertices_x[point]) * (latitude_y - vertices_y[point]) / (vertices_y[j] - vertices_y[point]) + vertices_x[point])))
c = !c;
}
return c;
}
// other function to check if line exist in polygon
function isinPolygonSecond(points, x, y) {
cornersX = new Array();
cornersY = new Array();
for (r = 0; r < points.length; r++) {
cornersX.push(points[r].x);
cornersY.push(points[r].y);
}
var i, j = cornersX.length - 1;
var odd = false;
var pX = cornersX;
var pY = cornersY;
for (i = 0; i < cornersX.length; i++) {
if ((pY[i] < y && pY[j] >= y || pY[j] < y && pY[i] >= y) &&
(pX[i] <= x || pX[j] <= x)) {
odd ^= (pX[i] + (y - pY[i]) * (pX[j] - pX[i]) / (pY[j] - pY[i])) < x;
}
j = i;
}
return odd;
}
var canvas = new fabric.Canvas("c", {
selection: false
});
var points = [];
var polygon = new fabric.Polygon([
new fabric.Point(150, 50),
new fabric.Point(250, 50),
new fabric.Point(250, 150),
new fabric.Point(150, 150),
new fabric.Point(50, 250),
]);
polygon.on("modified", function() {
//document.getElementById("p").innerHTML = JSON.stringify(this)+ "<br>";
var matrix = this.calcTransformMatrix();
var transformedPoints = this.get("points")
.map(function(p) {
return new fabric.Point(
p.x - polygon.pathOffset.x,
p.y - polygon.pathOffset.y);
})
.map(function(p) {
return fabric.util.transformPoint(p, matrix);
});
var circles = transformedPoints.map(function(p) {
return new fabric.Circle({
left: p.x,
top: p.y,
radius: 3,
fill: "red",
originX: "center",
originY: "center",
hasControls: false,
hasBorders: false,
selectable: false
});
});
//Lines Colors
var colors = ['red', 'green', 'blue', 'violet', 'teal', 'brown'];
//I need these distances from the polygon, where is negative value it should go inside polygon
var LinesDistances = [10, -5, -20, 0, 20, 40];
var lines = transformedPoints.map(function(p, i) {
var po = (i < polygon.points.length) ? i + 1 : 0;
if (typeof transformedPoints[po] === 'undefined')
po = 0;
var fromX = transformedPoints[po].x;
var fromY = transformedPoints[po].y;
var isinPolygon = isinPolygonFirst(transformedPoints, fromX, fromY);
var isinPolygon2 = isinPolygonSecond(transformedPoints, fromX, fromY);
var debug = '';
debug += 'fromX:' + fromX;
debug += ' ,fromY :' + fromY;
debug += ' ,isinPolygon:' + isinPolygon;
debug += ' ,isinPolygonSecond:' + isinPolygon2;
document.getElementById("p").innerHTML += '<p style="color:#fff;background:' + colors[i] + '"> ' + debug + "</p>";
return new fabric.Line([p.x, p.y, fromX, fromY], {
stroke: colors[i],
strokeWidth: 10,
hasBorders: true,
strokeDashArray: [20, 5]
});
});
this.canvas.clear().add(this).add.apply(this.canvas, lines).add.apply(this.canvas, circles).setActiveObject(this).renderAll();
polygon.set({
opacity: 0.5
});
polygon.sendToBack();
});
canvas.add(polygon).renderAll();
canvas {
border: 1px solid;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<div id="p"></div>
<canvas id="c" width="600" height="400"></canvas>
I am not certain if it's the right approach I am moving to, I wants to dynamically distance lines around the polygon as it will be required.
I need these distances from the polygon, where is negative value it should go inside polygon
var LinesDistances = [10, -5, -20, 0, 20, 40];
Here is fiddle and code. at https://jsfiddle.net/DivMaster/0e34Lnyx/211/
Any help? Thanks <3
I was able to solve it by using Intersects library available at https://github.com/davidfig/intersects
I calculated the midpoint of line and performed plus/minus operations on x/y points to see if points reside in polygon
pointPolygon(x1, y1, points)

Suggestions to create lines in javascript

I am new to JavaScript, and trying to create a function that will draw lines on the canvas. This is currently what I have and it's not working. I may be doing unnecessary things and I need some suggestions for this code.
function start(){
var COUNT = 5;
for(var i = 0; i < COUNT; i++){
var row = 0;
var rect = new Rectangle(100, 100);
rect.setPosition(0, row);
rect.setColor(Color.blue);
var rect1 = new Rectangle(100, 100);
rect1.setPosition(100, row);
rect1.setColor(Color.red);
var rect2 = new Rectangle(100, 100);
rect2.setPosition(200, row);
rect2.setColor(Color.blue);
var rect3 = new Rectangle(100, 100);
rect3.setPosition(300, row);
rect3.setColor(Color.red);
add(rect);
add(rect1);
add(rect2);
add(rect3);
row + 100;
}
}
Are you trying to do something like this :
var ctx = canvas.getContext('2d')
var colors = ['red','blue']
function start(){
var COUNT = 5;
for(var i = 0; i < COUNT; i++)
for(var j = 0 ; j<4;j++){
var rect = [10,10];
ctx.fillStyle = colors[(i+j)%2];
ctx.fillRect(j*10, i*10, rect[0],rect[1]);
}
}
start();
https://jsfiddle.net/dbo3htov/29/

Why is this neural network not rendering (only) full 3-layer pathways?

The function in question is rendering the neural network represented on the right side of this image based on a fairly simple data structure.
Trying to evolve:
After evolving:
Each dot represents a neuron, each line a connection. There's a problem with which neurons and connections are being rendered and which aren't, and I've been wrestling with this problem for 5 hours straight, no coffee breaks. One of you is likely going to point out one tiny stupid mistake causing the issue and I'll likely proceed to pull my hair out.
Put simply: Connections run from the top down. I only want to render complete paths from top to bottom. No dots (neurons) in the middle should be rendered if no connections lead to them from the top. If a top layer neuron connects to a middle layer neuron but that middle layer neuron doesn't connect to a bottom layer neuron, that connection isn't doing anything so it shouldn't be rendered.
As you can see, there are top level neurons that are rendered with no connections at all, and middle level neurons which are rendered with no connection to the top. All connections are top-down, no connections flow upward. The network is feed-forward in other words.
The data structure passed to this function is brain which is the same brain passed to the following Neuron and Connection constructors which are listed in abridged form only showing the properties relevant to the function in question (edited from the original with further attempts to fix the problem:
Neuron
function Neuron(brain, layer) {
var that = this;
brain.counter++;
brain.globalReferenceNeurons[brain.counter] = this;
this.active = true; //as the brain mutates, some neurons and
//connections are disabled via this property
this.layer = layer;
this.id = brain.counter;
this.connected = {};
this.connections = {};
this.connect = function(target) {
if (that.active == true) {
new Connection(brain, this, target, function(id, connection) {
brain.globalReferenceConnections[id] = connection;
that.connections[id] = connection;
});
}
};
}
Connection
function Connection(brain, source, target, callback) {
if (source.layer < target.layer) {
brain.counter++;
brain.globalReferenceConnections[brain.counter] = this;
this.active = true; //as the brain mutates, some neurons and
//connections are disabled via this property
this.id = brain.counter;
this.source = source;
this.target = target;
target.connected[this.id] = this; //connected references
//incoming connections to a neuron
callback(this.id, this);
}
}
As you can see, brain.globalReferenceNeurons contains the the data needed to render the neural network in the picture.
And here's the rendering function in question (updated again):
function renderBrain(brain, context, canvas) {
context.clearRect(0, 0, canvas.width, canvas.height);
var width = canvas.width;
var height = canvas.height;
var layers = brain.layers;
var heightDivision = height / layers;
var layerList = [];
for (var i1 = 0; i1 < brain.layers; i1++) {
layerList.push([]);
for (var prop1 in brain.globalReferenceNeurons) {
if (brain.globalReferenceNeurons[prop1].layer === i1) {
layerList[i1].push(brain.globalReferenceNeurons[prop1]);
}
}
}
function renderLayer(layer, layerCount, layerTotal) {
var length = layer.length;
var widthDivision = width / length;
var neuronCount = 0;
for (var i1 = 0; i1 < layer.length; i1++) {
neuronCount++;
const getActiveProps = obj => Object.keys(obj).filter(k => obj[k].active)
function hasActivePathAhead(obj, count) {
if (!count) {
count = 0;
}
if (obj.active) {
var targets = getActiveProps(obj.connections);
if (obj.layer === 2) {
return true;
} else if (obj.connections[targets[count]]) {
for (var i1 = 0; i1 < targets.length; i1++) {
var result = hasActivePathAhead(obj.connections[targets[count]].target,
count + 1);
return result;
}
return false;
} else {
return false;
}
} else {
return false;
}
}
function hasActivePathBehind(obj, count) {
if (!count) {
count = 0;
}
if (obj.active) {
var sources = getActiveProps(obj.connected);
if (obj.layer === 0) {
return true;
} else if (obj.connected[sources[count]]) {
for (var i1 = 0; i1 < sources.length; i1++) {
var result =
hasActivePathBehind(obj.connected[sources[count]].source, count + 1);
return result;
}
return false;
} else {
return false;
}
} else {
return false;
}
}
if (hasActivePathAhead(layer[i1]) && hasActivePathBehind(layer[i1])) {
context.beginPath();
context.arc((widthDivision * neuronCount)
- (0.5 * widthDivision),
(heightDivision * layerCount)
- (heightDivision * 0.5),
5, 0, 2 * Math.PI, false);
context.fillStyle = '#adf442';
context.fill();
context.lineWidth = 2;
context.strokeStyle = '#56cc41';
context.stroke();
var connectionCount = 0;
for (var i2 = 0; i2 < Object.keys(layer[i1].connections).length; i2++) {
var connection =
layer[i1].connections[Object.keys(layer[i1].connections)[i2]];
if (hasActivePathAhead(connection.target)
&& hasActivePathBehind(connection.target)) {
var targetLayer = connection.target.layer;
var index = layerList[targetLayer].findIndex(function(e) {
return e == connection.target
});
if (index > -1) {
var targetLayerLength = Object.keys(layerList[targetLayer]).length;
var targetLayerWidthDivision = width / targetLayerLength;
var p1 = {
x: (widthDivision * neuronCount) - (0.5 * widthDivision),
y: (heightDivision * layerCount) - (heightDivision * 0.5)
};
var p2 = {
x: (index * targetLayerWidthDivision)
+ (0.5 * targetLayerWidthDivision),
y: (targetLayer * heightDivision)
+ (heightDivision * 0.5)
};
connectionCount++;
context.beginPath();
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
context.lineWidth = 1;
context.stroke();
}
}
}
}
}
}
var layerCount = 0;
for (i1 = 0; i1 < layerList.length; i1++) {
layerCount++;
renderLayer(layerList[i1], layerCount, layerList.length);
}
}
"Working" test example: https://jsfiddle.net/au2Lt6na/4/
For 5 hours I've tinkered with this function trying to pinpoint the issue and for the life of me I haven't been able to figure it out. Can anyone tell me what's causing the rendering of non-top-to-bottom neural pathways?
Note: I've spent many more hours over the past few days trying to fix this, writing totally new ways of figuring out which paths are complete from top to bottom and it still suffers from the same issues as before. I'm missing something here.
for (var i1 = 0; i1 < targets.length; i1++) {
var result = hasActivePathAhead(obj.connections[targets[count]].target,
count + 1);
return result;
}
This snippet is weird. You may need this instead:
for (var i1 = 0; i1 < targets.length; i1++) {
var result = hasActivePathAhead(obj.connections[targets[count]].target,
count + 1);
if(result){
return true;
}
}
And the usage of count is weird there. I think the count should not be passed as a param, since it's used to index a target or source.
I think the snippet should be like this:
else if (targets.length) {
var target;
for (var i1 = 0; i1 < targets.length; i1++) {
target=targets[i1];
var result = hasActivePathAhead(obj.connections[target].target);
if(result){
return true;
}
}
return false;
}
Live demo here:
function Neuron(brain, layer) {
var that = this;
brain.counter++;
brain.globalReferenceNeurons[brain.counter] = this;
this.active = true; //as the brain mutates, some neurons and
//connections are disabled via this property
this.layer = layer;
this.id = brain.counter;
this.connected = {};
this.connections = {};
this.connect = function (target) {
if (that.active == true) {
new Connection(brain, this, target, function (id, connection) {
brain.globalReferenceConnections[id] = connection;
that.connections[id] = connection;
});
}
};
}
function Connection(brain, source, target, callback) {
if (source.layer < target.layer) {
brain.counter++;
brain.globalReferenceConnections[brain.counter] = this;
this.active = true; //as the brain mutates, some neurons and
//connections are disabled via this property
this.id = brain.counter;
this.source = source;
this.target = target;
target.connected[this.id] = this;
callback(this.id, this);
}
}
function renderBrain(brain, context, canvas) {
context.clearRect(0, 0, canvas.width, canvas.height);
var width = canvas.width;
var height = canvas.height;
var layers = brain.layers;
var heightDivision = height / layers;
var layerList = [];
for (var i1 = 0; i1 < brain.layers; i1++) {
layerList.push([]);
for (var prop1 in brain.globalReferenceNeurons) {
if (brain.globalReferenceNeurons[prop1].layer === i1) {
layerList[i1].push(brain.globalReferenceNeurons[prop1]);
}
}
}
function renderLayer(layer, layerCount, layerTotal) {
var length = layer.length;
var widthDivision = width / length;
var neuronCount = 0;
for (var i1 = 0; i1 < layer.length; i1++) {
neuronCount++;
const getActiveProps = obj => Object.keys(obj).filter(k => obj[k].active)
function hasActivePathAhead(obj) {
if (obj.active) {
var targets = getActiveProps(obj.connections);
if (obj.layer === 2) {
return true;
} else if (targets.length) {
var target;
for (var i1 = 0; i1 < targets.length; i1++) {
target = targets[i1];
var result = hasActivePathAhead(obj.connections[target].target);
if (result) {
return true;
}
}
return false;
} else {
return false;
}
} else {
return false;
}
}
function hasActivePathBehind(obj) {
if (obj.active) {
var sources = getActiveProps(obj.connected);
if (obj.layer === 0) {
return true;
} else if (sources.length) {
var source;
for (var i1 = 0; i1 < sources.length; i1++) {
source = sources[i1];
var result =
hasActivePathBehind(obj.connected[source].source);
return result;
}
return false;
} else {
return false;
}
} else {
return false;
}
}
if (hasActivePathAhead(layer[i1]) && hasActivePathBehind(layer[i1])) {
context.beginPath();
context.arc((widthDivision * neuronCount) -
(0.5 * widthDivision),
(heightDivision * layerCount) -
(heightDivision * 0.5),
5, 0, 2 * Math.PI, false);
context.fillStyle = '#adf442';
context.fill();
context.lineWidth = 2;
context.strokeStyle = '#56cc41';
context.stroke();
var connectionCount = 0;
for (var i2 = 0; i2 < Object.keys(layer[i1].connections).length; i2++) {
var connection =
layer[i1].connections[Object.keys(layer[i1].connections)[i2]];
if (hasActivePathAhead(connection.target) &&
hasActivePathBehind(connection.target)) {
var targetLayer = connection.target.layer;
var index = layerList[targetLayer].findIndex(function (e) {
return e == connection.target
});
if (index > -1) {
var targetLayerLength = Object.keys(layerList[targetLayer]).length;
var targetLayerWidthDivision = width / targetLayerLength;
var p1 = {
x: (widthDivision * neuronCount) - (0.5 * widthDivision),
y: (heightDivision * layerCount) - (heightDivision * 0.5)
};
var p2 = {
x: (index * targetLayerWidthDivision) +
(0.5 * targetLayerWidthDivision),
y: (targetLayer * heightDivision) +
(heightDivision * 0.5)
};
connectionCount++;
context.beginPath();
context.moveTo(p1.x, p1.y);
context.lineTo(p2.x, p2.y);
context.lineWidth = 1;
context.stroke();
}
}
}
}
}
}
var layerCount = 0;
for (i1 = 0; i1 < layerList.length; i1++) {
layerCount++;
renderLayer(layerList[i1], layerCount, layerList.length);
}
}
var brain = {
counter: 0,
layers: 3,
globalReferenceNeurons: {},
globalReferenceConnections: {},
}
var layer0 = [new Neuron(brain, 0), new Neuron(brain, 0), new Neuron(brain, 0),new Neuron(brain, 0), new Neuron(brain, 0)];
var layer1 = [new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1)];
var layer2 = [new Neuron(brain, 2), new Neuron(brain, 2), new Neuron(brain, 2), new Neuron(brain, 2)];
layer0[0].connect(layer1[1]);
layer0[1].connect(layer1[0]);
layer0[3].connect(layer1[0]);
layer1[0].connect(layer2[0]);
layer1[2].connect(layer2[2]);
layer1[1].connect(layer2[3]);
var canvas = document.getElementById('cav');
var ctx = canvas.getContext('2d');
renderBrain(brain, ctx, canvas);
<canvas id="cav" width="600" height="400"></canvas>
Could not fit into the comment and as you do not have a working example we can only guess.
Your recursion does not look right to me. The count variable makes no sense and you have several levels of redundancy checking for active 3 times for each iteration and not vetting when indexing into the key arrays with count.
Without working code and as your variable nomenclature is confusing this is only a guess at how to fix. Same applies to hasActivePathBehind
Ignore the following code
function hasActivePathAhead(obj) {
if (obj.active) {
if (obj.layer === 2) {
return true;
}
var targets = getActiveProps(obj.connections);
for (var i = 0; i < targets.length; i++) {
if(hasActivePathAhead(obj.connections[targets[i]].target)){
return true;
}
}
}
return false;
}
UPDATE and working fix.
Update because accepted answer does not work, it should have used a more rigorous test.
As you have provided a fiddle in the comments I had a look as see that you have removed the count from the functions. Though the functions are still incorrect and not working, the error is elsewhere in the code. The code is overly complex hiding the nature of the bug.
There is too much wrong to go into detail so I have just started from scratch.
Rather than test every node for a backward and forward link I traverse the layers forward from top to bottom. This negates the need to check backward connections. I have a function that checks if a node is connected to the bottom, a function that draws all nodes from a node to the bottom, and a function that draws all active nodes at a layer (active is connected from that layer down)
You can optimise it by adding a flag to nodes indicating that they have already been rendered as the code as is can render some nodes several times. But I did not add that as I did not want to modify the data structure you had. Or you can add a Map that holds node pairs that have been rendered and check that to see if a node pair needs to be rendered.
Using your fiddle as a template here is a working version using the randomised paths as provided in the fiddle.
function Neuron(brain, layer) {
var that = this;
brain.counter++;
brain.globalReferenceNeurons[brain.counter] = this;
this.active = true; //as the brain mutates, some neurons and
//connections are disabled via this property
this.layer = layer;
this.id = brain.counter;
this.connected = {};
this.connections = {};
this.connect = function (target) {
if (that.active == true) {
new Connection(brain, this, target, function (id, connection) {
brain.globalReferenceConnections[id] = connection;
that.connections[id] = connection;
});
}
};
}
function Connection(brain, source, target, callback) {
if (source.layer < target.layer) {
brain.counter++;
brain.globalReferenceConnections[brain.counter] = this;
this.active = true; //as the brain mutates, some neurons and
//connections are disabled via this property
this.id = brain.counter;
this.source = source;
this.target = target;
target.connected[this.id] = this;
callback(this.id, this);
}
}
function renderBrain(brain, ctx, canvas) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
var width = canvas.width;
var height = canvas.height;
var layers = brain.layers;
var heightDiv = height / layers;
var layerList = [];
for (var i1 = 0; i1 < brain.layers; i1++) {
layerList.push([]);
for (var prop1 in brain.globalReferenceNeurons) {
if (brain.globalReferenceNeurons[prop1].layer === i1) {
layerList[i1].push(brain.globalReferenceNeurons[prop1]);
}
}
}
var coord; // to hold node coordinates defined here to prevent pointless memory allocation dealocation cycle
// Gets the node position based on its ID and layer position
function nodePosition(node,coord = {}){
var pos;
pos = node.id - layerList[node.layer][0].id; // get pos from node id (if this does not hold true you should include the node position in the node data is it is important)
coord.x = (width / layerList[node.layer].length) * (pos + 0.5);
coord.y = heightDiv * (node.layer + 0.5);
return coord;
}
// draws a node
function drawNode(node){
ctx.strokeStyle = '#56cc41';
ctx.fillStyle = '#adf442';
ctx.lineWidth = 2;
coord = nodePosition(node,coord);
ctx.beginPath();
ctx.arc(coord.x,coord.y, 5, 0, 2 * Math.PI);
ctx.fill();
ctx.stroke();
}
// draws a link between two nodes
function drawLink(node,node1){
ctx.strokeStyle = '#56cc41';
ctx.lineWidth = 1;
coord = nodePosition(node,coord);
ctx.beginPath();
ctx.moveTo(coord.x,coord.y);
coord = nodePosition(node1,coord);
ctx.lineTo(coord.x,coord.y);
ctx.stroke();
}
// returns true if the path from this node jas a connection that leads to the end
function isPathActive(node){
var paths, i, nextNode;
if(node.active){
if(node.layer === 2){ // is node at end
return true;
}
paths = Object.keys(node.connections).map(key => node.connections[key]);
for(i = 0; i < paths.length; i ++){
nextNode = paths[i].target;
if(nextNode.active){
if(nextNode.layer === 2){
return true;
}
if(isPathActive(nextNode)){
return true;
}
}
}
}
return false;
}
// renders from a node all active pathes to end
function renderPath(node){
var i;
paths = Object.keys(node.connections).map(key => node.connections[key]);
for(i = 0; i < paths.length; i ++){
nextNode = paths[i].target;
if(isPathActive(nextNode)){
drawLink(node,nextNode)
renderPath(nextNode);
}
}
drawNode(node,i+ 1)
}
// renders from top layer all active paths
function renderActivePaths(layer){
var i;
for(i = 0; i < layer.length; i ++){
if(isPathActive(layer[i])){
renderPath(layer[i])
}
}
}
renderActivePaths(layerList[0]);
}
var brain = {
counter: 0,
layers: 3,
globalReferenceNeurons: {},
globalReferenceConnections: {},
}
var layer0 = [new Neuron(brain, 0), new Neuron(brain, 0), new Neuron(brain, 0),
new Neuron(brain, 0), new Neuron(brain, 0), new Neuron(brain, 0),
new Neuron(brain, 0), new Neuron(brain, 0),new Neuron(brain, 0),
new Neuron(brain, 0)]; //10
var layer1 = [new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1),
new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1),
new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1),
new Neuron(brain, 1), new Neuron(brain, 1), new Neuron(brain, 1),
new Neuron(brain, 1)]; //13
var layer2 = [new Neuron(brain, 2), new Neuron(brain, 2)]; //2
layer0[0].connect(layer1[0]);
layer0[1].connect(layer1[1]);
layer0[2].connect(layer1[2]);
layer0[3].connect(layer1[3]);
layer0[4].connect(layer1[4]);
layer0[5].connect(layer1[5]);
layer0[6].connect(layer1[6]);
layer0[7].connect(layer1[7]);
layer0[8].connect(layer1[8]);
layer0[9].connect(layer1[9]);
layer0[0].connect(layer1[3]);
layer0[1].connect(layer1[4]);
layer0[2].connect(layer1[5]);
layer0[3].connect(layer1[6]);
layer0[4].connect(layer1[7]);
layer0[5].connect(layer1[8]);
layer0[6].connect(layer1[9]);
layer0[7].connect(layer1[10]);
layer0[8].connect(layer1[11]);
layer0[9].connect(layer1[12]);
layer1[0].connect(layer2[0]);
layer1[1].connect(layer2[1]);
layer1[2].connect(layer2[0]);
layer1[3].connect(layer2[1]);
layer1[4].connect(layer2[0]);
layer1[5].connect(layer2[1]);
layer1[6].connect(layer2[0]);
layer1[7].connect(layer2[1]);
layer1[8].connect(layer2[0]);
layer1[9].connect(layer2[1]);
layer1[10].connect(layer2[0]);
layer1[11].connect(layer2[1]);
layer1[12].connect(layer2[0]);
//works! until...
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
for (prop in brain.globalReferenceNeurons) {
var rand = getRandomInt(1,6);
var neuron = brain.globalReferenceNeurons[prop];
if (rand == 1 && neuron.layer != 2) neuron.active = false;
}
for (prop in brain.globalReferenceConnections) {
var rand = getRandomInt(1,6);
var connection = brain.globalReferenceConnections[prop];
if (rand == 1) connection.active = false;
}
renderBrain(brain, canvas.getContext("2d"), canvas);
<canvas id="canvas" width= 512 height = 200></canvas>

Create color gradient describing discrete distribution of points

I am making a map that renders position of game objects (Project Zomboid zombies):
As user zooms out, single dots are no longer useful. Instead, I'd like to render distribution of zombies on an area using red color gradient. I tried to loop over all zombies for every rendered pixel and color it reciprocally to the sum of squared distances to the zombies. The result:
That's way too blurry. Also the results are more influenced by the zombies that are AWAY from the points - I need to influence them more by the zombies that are CLOSE. So what this is is just math. Here's the code I used:
var h = canvas.height;
var w = canvas.width;
// To loop over more than 1 pixel (performance)
var tileSize = 10;
var halfRadius = Math.floor(tileSize/2);
var time = performance.now();
// "Squared" because we didnt unsquare it
function distanceSquared(A, B) {
return (A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y);
}
// Loop for every x,y pixel (or region of pixels)
for(var y=0; y<h; y+=tileSize) {
for(var x=0; x<w; x+=tileSize) {
// Time security - stop rendering after 1 second
if(performance.now()-time>1000) {
x=w;y=h;break;
}
// Convert relative canvas offset to absolute point on the map
var point = canvasPixeltoImagePixel(x, y);
// For every zombie add sqrt(distance from this point to zombie)
var distancesRoot = 0;
// Loop over the zombies
var zombieCoords;
for(var i=0; i<zombies_length; i++) {
// Get single zombie coordinates as {x:0, y:0}
if((coords=zombies[i].pixel)==null)
coords = zombies[i].pixel = tileToPixel(zombies[i].coordinates[0], zombies[i].coordinates[1], drawer);
// square root is a) slow and b) probably not what I want anyway
var dist = distanceSquared(coords, point);
distancesRoot+=dist;
}
// The higher the sum of distances is, the more intensive should the color be
var style = 'rgba(255,0,0,'+300000000/distancesRoot+')';
// Kill the console immediatelly
//console.log(style);
// Maybe we should sample and cache the transparency styles since there's limited ammount of colors?
ctx.fillStyle = style;
ctx.fillRect(x-halfRadius,y-halfRadius,tileSize,tileSize);
}
}
I'm pretty fine with theoretical explanation how to do it, though if you make simple canvas example with some points, what would be awesome.
This is an example of a heat map. It's basically gradient orbs over points and then ramping the opacity through a heat ramp. The more orbs cluster together the more solid the color which can be shown as an amplified region with the proper ramp.
update
I cleaned up the variables a bit and put the zeeks in an animation loop. There's an fps counter to see how it's performing. The gradient circles can be expensive. We could probably do bigger worlds if we downscale the heat map. It won't be as smooth looking but will compute a lot faster.
update 2
The heat map now has an adjustable scale and as predicted we get an increase in fps.
if (typeof app === "undefined") {
var app = {};
}
app.zeeks = 200;
app.w = 600;
app.h = 400;
app.circleSize = 50;
app.scale = 0.25;
init();
function init() {
app.can = document.getElementById('can');
app.ctx = can.getContext('2d');
app.can.height = app.h;
app.can.width = app.w;
app.radius = Math.floor(app.circleSize / 2);
app.z = genZ(app.zeeks, app.w, app.h);
app.flip = false;
// Make temporary layer once.
app.layer = document.createElement('canvas');
app.layerCtx = app.layer.getContext('2d');
app.layer.width = Math.floor(app.w * app.scale);
app.layer.height = Math.floor(app.h * app.scale);
// Make the gradient canvas once.
var sCircle = Math.floor(app.circleSize * app.scale);
app.radius = Math.floor(sCircle / 2);
app.gCan = genGradientCircle(sCircle);
app.ramp = genRamp();
// fps counter
app.frames = 0;
app.fps = "- fps";
app.fpsInterval = setInterval(calcFps, 1000);
// start animation
ani();
flicker();
}
function calcFps() {
app.fps = app.frames + " fps";
app.frames = 0;
}
// animation loop
function ani() {
app.frames++;
var ctx = app.ctx;
var w = app.w;
var h = app.h;
moveZ();
//ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "#006600";
ctx.fillRect(0, 0, w, h);
if (app.flip) {
drawZ2();
drawZ();
} else {
drawZ2();
}
ctx.fillStyle = "#FFFF00";
ctx.fillText(app.fps, 10, 10);
requestAnimationFrame(ani);
}
function flicker() {
app.flip = !app.flip;
if (app.flip) {
setTimeout(flicker, 500);
} else {
setTimeout(flicker, 5000);
}
}
function genGradientCircle(size) {
// gradient image
var gCan = document.createElement('canvas');
gCan.width = gCan.height = size;
var gCtx = gCan.getContext('2d');
var radius = Math.floor(size / 2);
var grad = gCtx.createRadialGradient(radius, radius, radius, radius, radius, 0);
grad.addColorStop(1, "rgba(255,255,255,.65)");
grad.addColorStop(0, "rgba(255,255,255,0)");
gCtx.fillStyle = grad;
gCtx.fillRect(0, 0, gCan.width, gCan.height);
return gCan;
}
function genRamp() {
// Create heat gradient
var heat = document.createElement('canvas');
var hCtx = heat.getContext('2d');
heat.width = 256;
heat.height = 5;
var linGrad = hCtx.createLinearGradient(0, 0, heat.width, heat.height);
linGrad.addColorStop(1, "rgba(255,0,0,.75)");
linGrad.addColorStop(0.5, "rgba(255,255,0,.03)");
linGrad.addColorStop(0, "rgba(255,255,0,0)");
hCtx.fillStyle = linGrad;
hCtx.fillRect(0, 0, heat.width, heat.height);
// create ramp from gradient
var ramp = [];
var imageData = hCtx.getImageData(0, 0, heat.width, 1);
var d = imageData.data;
for (var x = 0; x < heat.width; x++) {
var i = x * 4;
ramp[x] = [d[i], d[i + 1], d[i + 2], d[i + 3]];
}
return ramp;
}
function genZ(n, w, h) {
var a = [];
for (var i = 0; i < n; i++) {
a[i] = [
Math.floor(Math.random() * w),
Math.floor(Math.random() * h),
Math.floor(Math.random() * 3) - 1,
Math.floor(Math.random() * 3) - 1
];
}
return a;
}
function moveZ() {
var w = app.w
var h = app.h;
var z = app.z;
for (var i = 0; i < z.length; i++) {
var s = z[i];
s[0] += s[2];
s[1] += s[3];
if (s[0] > w || s[0] < 0) s[2] *= -1;
if (s[1] > w || s[1] < 0) s[3] *= -1;
}
}
function drawZ() {
var ctx = app.ctx;
var z = app.z;
ctx.fillStyle = "#FFFF00";
for (var i = 0; i < z.length; i++) {
ctx.fillRect(z[i][0] - 2, z[i][1] - 2, 4, 4);
}
}
function drawZ2() {
var ctx = app.ctx;
var layer = app.layer;
var layerCtx = app.layerCtx;
var gCan = app.gCan;
var z = app.z;
var radius = app.radius;
// render gradients at coords onto layer
for (var i = 0; i < z.length; i++) {
var x = Math.floor((z[i][0] * app.scale) - radius);
var y = Math.floor((z[i][1] * app.scale) - radius);
layerCtx.drawImage(gCan, x, y);
}
// adjust layer for heat ramp
var ramp = app.ramp;
// apply ramp to layer
var imageData = layerCtx.getImageData(0, 0, layer.width, layer.height);
d = imageData.data;
for (var i = 0; i < d.length; i += 4) {
if (d[i + 3] != 0) {
var c = ramp[d[i + 3]];
d[i] = c[0];
d[i + 1] = c[1];
d[i + 2] = c[2];
d[i + 3] = c[3];
}
}
layerCtx.putImageData(imageData, 0, 0);
// draw layer on world
ctx.drawImage(layer, 0, 0, layer.width, layer.height, 0, 0, app.w, app.h);
}
<canvas id="can" width="600" height="400"></canvas>

Three.js getObjectByName delivers undefined

I started my first Three.js project. A solar system which you can see here.
I have a function addCelestrialObject() where I create the planets and I want this function to automatically create the orbit circles what it does for the planets but I want it also for moons.
So every Planet (mesh) becomes a name and I want to access this object to get its center position so I can add a circle to this position if I have a moon.
My problem is that the function scene.getObjectByName(parent,true); always delivers an undefined. You can see the console.log(scene) on my example when you inspect the site.
function addCelestrialObject(name, type, parent, surface, bump, specular,
positionX, positionY, positionZ, size, clouds, drawcircle
) {
var loader = new THREE.TextureLoader();
var group = new THREE.Group();
loader.load(surface, function (texture) {
var geometry = new THREE.SphereGeometry(size, 32, 32);
if (type == "sun") {
var material = new THREE.MeshBasicMaterial({ map: texture });
material.shading = true;
} else {
var material = new THREE.MeshPhongMaterial({ map: texture, overdraw: 0.5 });
material.shading = true;
if (bump) {
material.bumpMap = THREE.ImageUtils.loadTexture(bump);
material.bumpScale = 0.5;
}
if (specular) {
material.specularMap = THREE.ImageUtils.loadTexture(specular);
material.specular = new THREE.Color(0x222222);
}
}
var mesh = new THREE.Mesh(geometry, material);
mesh.name = name;
mesh.position.x = positionX;
mesh.position.y = positionY;
mesh.position.z = positionZ;
objectControls.add(mesh);
mesh.select = function () {
var position = { x: controls.target.x, y: controls.target.y, z: controls.target.z };
var target = { x: this.position.x, y: this.position.y, z: this.position.z };
var tween = new TWEEN.Tween(position).to(target, 500);
tween.easing(TWEEN.Easing.Exponential.InOut)
tween.onUpdate(function () {
controls.target.x = position.x;
controls.target.y = position.y;
controls.target.z = position.z;
controls.dollyIn(2);
});
tween.start();
controls.minDistance = size * 5;
}
onRenderFcts.push(function (delta, now) {
mesh.rotateY(1 / 32 * delta)
});
group.add(mesh);
});
if (clouds == true) {
var canvasResult = document.createElement('canvas')
canvasResult.width = 1024
canvasResult.height = 512
var contextResult = canvasResult.getContext('2d')
// load earthcloudmap
var imageMap = new Image();
imageMap.addEventListener("load", function () {
// create dataMap ImageData for earthcloudmap
var canvasMap = document.createElement('canvas')
canvasMap.width = imageMap.width
canvasMap.height = imageMap.height
var contextMap = canvasMap.getContext('2d')
contextMap.drawImage(imageMap, 0, 0)
var dataMap = contextMap.getImageData(0, 0, canvasMap.width, canvasMap.height)
// load earthcloudmaptrans
var imageTrans = new Image();
imageTrans.addEventListener("load", function () {
// create dataTrans ImageData for earthcloudmaptrans
var canvasTrans = document.createElement('canvas')
canvasTrans.width = imageTrans.width
canvasTrans.height = imageTrans.height
var contextTrans = canvasTrans.getContext('2d')
contextTrans.drawImage(imageTrans, 0, 0)
var dataTrans = contextTrans.getImageData(0, 0, canvasTrans.width, canvasTrans.height)
// merge dataMap + dataTrans into dataResult
var dataResult = contextMap.createImageData(canvasMap.width, canvasMap.height)
for (var y = 0, offset = 0; y < imageMap.height; y++) {
for (var x = 0; x < imageMap.width; x++, offset += 4) {
dataResult.data[offset + 0] = dataMap.data[offset + 0]
dataResult.data[offset + 1] = dataMap.data[offset + 1]
dataResult.data[offset + 2] = dataMap.data[offset + 2]
dataResult.data[offset + 3] = 255 - dataTrans.data[offset + 0]
}
}
// update texture with result
contextResult.putImageData(dataResult, 0, 0)
material.map.needsUpdate = true;
})
imageTrans.src = 'textures/earthcloudmaptrans.jpg';
}, false);
imageMap.src = 'textures/earthcloudmap.jpg';
var geometry = new THREE.SphereGeometry(size + 0.5, 32, 32)
var material = new THREE.MeshPhongMaterial({
map: new THREE.Texture(canvasResult),
side: THREE.DoubleSide,
transparent: true,
opacity: 1,
shading: true,
})
var cloudMesh = new THREE.Mesh(geometry, material);
cloudMesh.position.x = positionX;
cloudMesh.position.y = positionY;
cloudMesh.position.z = positionZ;
group.add(cloudMesh);
onRenderFcts.push(function (delta, now) {
cloudMesh.rotateY(1 / 16 * delta)
});
}
if (drawcircle == true) {
//circle
var radius = Math.abs(distance(0, positionX, 0, positionZ));
segments = 64;
materialLine = new THREE.LineBasicMaterial({ color: 0x00a8ff });
geometry = new THREE.CircleGeometry(radius, segments);
// Remove center vertex
geometry.vertices.shift();
circle = new THREE.Line(geometry, materialLine);
circle.rotation.x = 1.571;
if (parent) {
var object = scene.getObjectByName(parent, true);
//circle.position.x=object.position.x;
//circle.position.y=object.position.y;
//circle.position.z=object.position.z;
}
group.add(circle);
}
scene.add(group);
}

Categories