Scope issue with Coffeescript - javascript

I have some strange scope issue in Coffeescript.
I can't access to _this from the function #qr.callback, the _this doesn't seem to pass well. I never change this.imgName so the only reason that It doesn't work could be that _this is'nt passed well.
decode:(#callback) ->
_this= this
console.log 'before',_this.imgName
#qr= new QrCode()
#qr.callback= () ->
console.log "after:", _this.imgName
#qr.decode("data:image/png;base64,#{#base64Data}")
Edit:
I have tried using
console.log 'before',#imgName
#qr= new QrCode()
#qr.callback= () =>
console.log "after:", #imgName
#qr.decode("data:image/png;base64,#{#base64Data}")
But the output is the same
Edit2: QrCode code: This code comes from https://github.com/LazarSoft/jsqrcode. Howewer, as the source code of LazarSoft https://github.com/LazarSoft/jsqrcode/blob/master/src/qrcode.js did'nt contain a QrCode object that you could instantiate many times , I transformed the code to create many different instances of QrCode by creating a QrCode function instead of a global object qrcode.
QrCode= function ()
{
this.imagedata = null;
this.width = 0;
this.height = 0;
this.qrCodeSymbol = null;
this.debug = false;
this.sizeOfDataLengthInfo = [ [ 10, 9, 8, 8 ], [ 12, 11, 16, 10 ], [ 14, 13, 16, 12 ] ];
this.callback = null;
this.decode = function(src){
if(arguments.length==0)
{
var canvas_qr = document.getElementById("qr-canvas");
var context = canvas_qr.getContext('2d');
this.width = canvas_qr.width;
this.height = canvas_qr.height;
this.imagedata = context.getImageData(0, 0, this.width, this.height);
this.result = this.process(context);
if(this.callback!=null)
this.callback(this.result);
return this.result;
}
else
{
var image = new Image();
_this=this
image.onload=function(){
//var canvas_qr = document.getElementById("qr-canvas");
var canvas_qr = document.createElement('canvas');
var context = canvas_qr.getContext('2d');
var canvas_out = document.getElementById("out-canvas");
if(canvas_out!=null)
{
var outctx = canvas_out.getContext('2d');
outctx.clearRect(0, 0, 320, 240);
outctx.drawImage(image, 0, 0, 320, 240);
}
canvas_qr.width = image.width;
canvas_qr.height = image.height;
context.drawImage(image, 0, 0);
_this.width = image.width;
_this.height = image.height;
try{
_this.imagedata = context.getImageData(0, 0, image.width, image.height);
}catch(e){
_this.result = "Cross domain image reading not supported in your browser! Save it to your computer then drag and drop the file!";
if(_this.callback!=null)
_this.callback(_this.result);
return;
}
try
{
_this.result = _this.process(context);
}
catch(e)
{
// console.log('error:'+e);
_this.result = "error decoding QR Code";
}
if(_this.callback!=null)
_this.callback(_this.result);
}
image.src = src;
}
}
this.decode_utf8 = function ( s )
{
return decodeURIComponent( escape( s ) );
}
this.process = function(ctx){
var start = new Date().getTime();
var image = this.grayScaleToBitmap(this.grayscale());
//var image = this.binarize(128);
if(this.debug)
{
for (var y = 0; y < this.height; y++)
{
for (var x = 0; x < this.width; x++)
{
var point = (x * 4) + (y * this.width * 4);
this.imagedata.data[point] = image[x+y*this.width]?0:0;
this.imagedata.data[point+1] = image[x+y*this.width]?0:0;
this.imagedata.data[point+2] = image[x+y*this.width]?255:0;
}
}
ctx.putImageData(this.imagedata, 0, 0);
}
//var finderPatternInfo = new FinderPatternFinder().findFinderPattern(image);
var detector = new Detector(image,this);
var qRCodeMatrix = detector.detect();
/*for (var y = 0; y < qRCodeMatrix.bits.Height; y++)
{
for (var x = 0; x < qRCodeMatrix.bits.Width; x++)
{
var point = (x * 4*2) + (y*2 * this.width * 4);
this.imagedata.data[point] = qRCodeMatrix.bits.get_Renamed(x,y)?0:0;
this.imagedata.data[point+1] = qRCodeMatrix.bits.get_Renamed(x,y)?0:0;
this.imagedata.data[point+2] = qRCodeMatrix.bits.get_Renamed(x,y)?255:0;
}
}*/
if(this.debug)
ctx.putImageData(this.imagedata, 0, 0);
var reader = Decoder.decode(qRCodeMatrix.bits,this);
var data = reader.DataByte;
var str="";
for(var i=0;i<data.length;i++)
{
for(var j=0;j<data[i].length;j++)
str+=String.fromCharCode(data[i][j]);
}
var end = new Date().getTime();
var time = end - start;
console.log(time);
return this.decode_utf8(str);
//alert("Time:" + time + " Code: "+str);
}
this.getPixel = function(x,y){
if (this.width < x) {
throw "point error";
}
if (this.height < y) {
throw "point error";
}
point = (x * 4) + (y * this.width * 4);
p = (this.imagedata.data[point]*33 + this.imagedata.data[point + 1]*34 + this.imagedata.data[point + 2]*33)/100;
return p;
}
this.binarize = function(th){
var ret = new Array(this.width*this.height);
for (var y = 0; y < this.height; y++)
{
for (var x = 0; x < this.width; x++)
{
var gray = this.getPixel(x, y);
ret[x+y*this.width] = gray<=th?true:false;
}
}
return ret;
}
this.getMiddleBrightnessPerArea=function(image)
{
var numSqrtArea = 4;
//obtain middle brightness((min + max) / 2) per area
var areaWidth = Math.floor(this.width / numSqrtArea);
var areaHeight = Math.floor(this.height / numSqrtArea);
var minmax = new Array(numSqrtArea);
for (var i = 0; i < numSqrtArea; i++)
{
minmax[i] = new Array(numSqrtArea);
for (var i2 = 0; i2 < numSqrtArea; i2++)
{
minmax[i][i2] = new Array(0,0);
}
}
for (var ay = 0; ay < numSqrtArea; ay++)
{
for (var ax = 0; ax < numSqrtArea; ax++)
{
minmax[ax][ay][0] = 0xFF;
for (var dy = 0; dy < areaHeight; dy++)
{
for (var dx = 0; dx < areaWidth; dx++)
{
var target = image[areaWidth * ax + dx+(areaHeight * ay + dy)*this.width];
if (target < minmax[ax][ay][0])
minmax[ax][ay][0] = target;
if (target > minmax[ax][ay][1])
minmax[ax][ay][1] = target;
}
}
//minmax[ax][ay][0] = (minmax[ax][ay][0] + minmax[ax][ay][1]) / 2;
}
}
var middle = new Array(numSqrtArea);
for (var i3 = 0; i3 < numSqrtArea; i3++)
{
middle[i3] = new Array(numSqrtArea);
}
for (var ay = 0; ay < numSqrtArea; ay++)
{
for (var ax = 0; ax < numSqrtArea; ax++)
{
middle[ax][ay] = Math.floor((minmax[ax][ay][0] + minmax[ax][ay][1]) / 2);
//Console.out.print(middle[ax][ay] + ",");
}
//Console.out.println("");
}
//Console.out.println("");
return middle;
}
this.grayScaleToBitmap=function(grayScale)
{
var middle = this.getMiddleBrightnessPerArea(grayScale);
var sqrtNumArea = middle.length;
var areaWidth = Math.floor(this.width / sqrtNumArea);
var areaHeight = Math.floor(this.height / sqrtNumArea);
var bitmap = new Array(this.height*this.width);
for (var ay = 0; ay < sqrtNumArea; ay++)
{
for (var ax = 0; ax < sqrtNumArea; ax++)
{
for (var dy = 0; dy < areaHeight; dy++)
{
for (var dx = 0; dx < areaWidth; dx++)
{
bitmap[areaWidth * ax + dx+ (areaHeight * ay + dy)*this.width] = (grayScale[areaWidth * ax + dx+ (areaHeight * ay + dy)*this.width] < middle[ax][ay])?true:false;
}
}
}
}
return bitmap;
}
this.grayscale = function(){
var ret = new Array(this.width*this.height);
for (var y = 0; y < this.height; y++)
{
for (var x = 0; x < this.width; x++)
{
var gray = this.getPixel(x, y);
ret[x+y*this.width] = gray;
}
}
return ret;
}
}

var image = new Image();
_this=this
May lightning strike you, you're coding CoffeeScript too much! You forgot a var declaration here, so the ninth invocation of decode overwrites the global _this with its this instance - and when each decoding is finished, they all call the same callback.
Fix it by using
var _this = this;
or
var image = new Image,
_this = this;
or by using CoffeeScript everywhere :-)

Related

Firefox, Edge, Safari canvas drawing with putImageData fails

jsfiddle: https://jsfiddle.net/nragrkb8/3/
I'm trying to get a spectrogram drawn, works in chrome but draws nothing in all other browsers. I'm using getImageData and putImageData to move existing contents down by a pixel every time new data is pushed in. That new data is then drawn into an offscreen image and then putimagedata to apply it to the full canvas.
Spectrogram = (function() {
//constructor function
var Spectrogram = function(options) {
//compose options
this.canvas = options.canvas;
this.width = options.size; //the size of the FFT
this.height = options.length; //the number of FFT to keep
if (typeof options.min !== 'undefined' && typeof options.max !== 'undefined') {
this.rangeType = 'static';
this.min = options.min;
this.max = options.max;
} else {
this.rangeType = 'auto';
this.min = Infinity;
this.max = -Infinity;
}
//set the function used to determine a value's color
this.colorScaleFn = options.colorScaleFn || defaultColorScaling;
//precalculate the range used in color scaling
this.range = this.max - this.min;
//the 2d drawing context
this.ctx = this.canvas.getContext('2d', { alpha: false });
//single pixel image used to draw new data points
this.im = this.ctx.createImageData(this.width, 1);
this.canvas.width = this.width;
this.canvas.height = this.height;
//adds a new row of data to the edge of the spectrogram, shifting existing
//contents by 1px
this.addData = function (newData) {
if (newData.length != this.width) {
console.error('Unmatched dataset size. Expected ' + this.width + ', was + ' + newData.length);
return;
}
if (this.rangeType == 'auto') {
var changed = false;
for (var i = 0; i < newData.length; ++i) {
if (newData[i] < this.min) {
this.min = newData[i];
changed = true;
} else if (newData[i] > this.max) {
this.max = newData[i];
changed = true;
}
}
if (changed) {
this.range = this.max - this.min;
}
}
//move the current contents by 1px
var im = this.ctx.getImageData(0, 0, this.width, this.height - 1);
this.ctx.putImageData(im, 0, 1);
//draw the new data values into the temporary image
var j = 0;
for (var i = 0; i < newData.length; ++i) {
var c = this.colorScaleFn(newData[i]);
this.im.data[j] = c[0];
this.im.data[j + 1] = c[1];
this.im.data[j + 2] = c[2];
j += 4;
}
//put the new data colors into the full canvas
this.ctx.putImageData(this.im, 0, 0);
}
}
function defaultColorScaling(value) {
var x = (value - this.min) / this.range;
if (x < 0) {
x = 0;
} else if (x > 1) {
x = 1;
}
var g = Math.pow(x, 2);
var b = 0.75 - 1.5 * Math.abs(x - 0.45);
var r = 0;
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
return Spectrogram;
})();
const size = 1024;
const length = 200;
const dataMax = 100;
const velMax = 1;
const canvas = document.getElementById('spec');
canvas.width = size;
canvas.height = length;
const spec = new Spectrogram({
canvas: canvas,
size: size,
length: length
});
const data = [];
const vel = [];
for (var i = 0; i < size; ++i) {
data.push(0);
vel.push(0);
}
setInterval(function() {
for (var i = 0; i < size; ++i) {
data[i] += vel[i];
if (i > 0) data[i] += vel[i - 1];
if (i < size - 1) data[i] += vel[i + 1];
if (data[i] >= dataMax) {
data[i] = dataMax;
vel[i] = 0;
} else if (data[i] <= -dataMax) {
data[i] = -dataMax;
vel[i] = 0;
}
if (vel[i] == 0) {
vel[i] = (Math.random() - 0.5) * 100;
}
}
spec.addData(data);
}, 100);
Your problem was induced by a misconception:
getContext('2d', {alpha: false}) should have no incidence on createImageData function, so you still have to set the alpha value of your ImageData to something else than 0.
There are also other things in your code that don't go well, even if unrealted with your current issue:
You should avoid at all costs an high frequency setInterval where you perform a task that can take more than the interval (and in your case it can), but rather use something like a requestAnimationFrame loop.
Don't use getImageData + putImageData to draw your context on itself, simply use ctx.drawImage(ctx.canvas, x, y).
There might be other things to fix in your code, but here is an update with these three major things:
Spectrogram = (function() {
//constructor function
var Spectrogram = function(options) {
//compose options
this.canvas = options.canvas;
this.width = options.size; //the size of the FFT
this.height = options.length; //the number of FFT to keep
if (typeof options.min !== 'undefined' && typeof options.max !== 'undefined') {
this.rangeType = 'static';
this.min = options.min;
this.max = options.max;
} else {
this.rangeType = 'auto';
this.min = Infinity;
this.max = -Infinity;
}
//set the function used to determine a value's color
this.colorScaleFn = options.colorScaleFn || defaultColorScaling;
//precalculate the range used in color scaling
this.range = this.max - this.min;
//the 2d drawing context
this.ctx = this.canvas.getContext('2d', {
alpha: false
});
//single pixel image used to draw new data points
this.im = this.ctx.createImageData(this.width, 1);
this.canvas.width = this.width;
this.canvas.height = this.height;
//adds a new row of data to the edge of the spectrogram, shifting existing
//contents by 1px
this.addData = function(newData) {
if (newData.length != this.width) {
console.error('Unmatched dataset size. Expected ' + this.width + ', was + ' + newData.length);
return;
}
if (this.rangeType == 'auto') {
var changed = false;
for (var i = 0; i < newData.length; ++i) {
if (newData[i] < this.min) {
this.min = newData[i];
changed = true;
} else if (newData[i] > this.max) {
this.max = newData[i];
changed = true;
}
}
if (changed) {
this.range = this.max - this.min;
}
}
//move the current contents by 1px
this.ctx.drawImage(this.ctx.canvas, 0, 1)
//draw the new data values into the temporary image
var j = 0;
for (var i = 0; i < newData.length; ++i) {
var c = this.colorScaleFn(newData[i]);
this.im.data[j] = c[0];
this.im.data[j + 1] = c[1];
this.im.data[j + 2] = c[2];
// don't forget the alpha channel, createImageData is always full of zeroes
this.im.data[j + 3] = 255;
j += 4;
}
this.ctx.putImageData(this.im, 0, 0);
}
}
function defaultColorScaling(value) {
var x = (value - this.min) / this.range;
if (x < 0) {
x = 0;
} else if (x > 1) {
x = 1;
}
var g = Math.pow(x, 2);
var b = 0.75 - 1.5 * Math.abs(x - 0.45);
var r = 0;
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
return Spectrogram;
})();
const size = 1024;
const length = 200;
const dataMax = 100;
const velMax = 1;
const canvas = document.getElementById('spec');
canvas.width = size;
canvas.height = length;
const spec = new Spectrogram({
canvas: canvas,
size: size,
length: length
});
const data = [];
const vel = [];
for (var i = 0; i < size; ++i) {
data.push(0);
vel.push(0);
}
// our animation loop
function draw() {
for (var i = 0; i < size; ++i) {
data[i] += vel[i];
if (i > 0) data[i] += vel[i - 1];
if (i < size - 1) data[i] += vel[i + 1];
if (data[i] >= dataMax) {
data[i] = dataMax;
vel[i] = 0;
} else if (data[i] <= -dataMax) {
data[i] = -dataMax;
vel[i] = 0;
}
if (vel[i] == 0) {
vel[i] = (Math.random() - 0.5) * 100;
}
}
spec.addData(data);
requestAnimationFrame(draw);
}
draw();
<div style="width: 100%; height: 100%; padding: 0; margin: 0;">
<canvas style="width: 100%; height: 100%;" id="spec"></canvas>
</div>

Codehs and KhanAcademy different use of JavaScript?

At my school I am learning how to code in JS using a site called codehs.com. After a while I learned about graphics with JS. There was this one point where I had to create a circle:
var circle = new Circle(50);
circle.setPosition(100,100);
add(circle);
After a few days I came across another website that was teaching students how code using JS. The website was called khanacademy.org I was interested and saw that the first lesson was making drawings. I looked at the video provided and it had a different code to make a circle.
ellipse(203, 197, 300, 350);
I am confused on how to make a circle using JS since I just started.
I'm one of the founders of CodeHS. CodeHS uses a custom JavaScript library on top of regular JavaScript. Khan Academy uses Processing JS, which is a different library (You can use Processing on CodeHS as well if you like).
You can see the documentation for everything in the CodeHS JS library at https://codehs.com/docs and learn how to use it in the Intro CS in JavaScript course.
We have designed this library to be great for learning -- it gives you experience using Object Oriented Programming while making it simple to create and manipulate shapes for programs like a Helicopter Game.
Additionally, you can include the library on an HTML page that runs JavaScript by adding this script tag to your page.
<script type="text/javascript" src="https://static.codehs.com/gulp/3d065bc81d3b7edf21e928ce2c764374a03c5cd6/chs-js-lib/chs.js"></script>
Here's an example of a full HTML page that runs JavaScript and uses the CodeHS library on it to draw a circle.
<html>
<head>
<title>Circle Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript" src="https://static.codehs.com/gulp/3d065bc81d3b7edf21e928ce2c764374a03c5cd6/chs-js-lib/chs.js"></script>
<style>
canvas {
border: 1px solid black;
display: inline-block;
vertical-align: top;
}
pre {
border: 1px solid black;
display: inline-block;
width: 400px;
height: 500px;
background-color: #F5F5F5;
}
</style>
</head>
<body>
<h1>Circle Example</h1>
<canvas
width="400"
height="500"
class="codehs-editor-canvas"></canvas>
<script>
window.onload = function() {
var circle = new Circle(50);
circle.setPosition(100,100);
add(circle);
};
</script>
</body>
</html>
Looks like KHAN ACADEMY uses ProcessingJS to draw the circle
I was unable to check what is the library CodeHS uses to draw a circle, but has to be a different one. But the fact is that there are so many good libraries developed in javascript to make whatever you can imagine. They're generally different one from another but their goal is to make our life easier.
JavaScript library | Wikipedia
What's a JS library? | KHAN ACADEMY
I tried using the processing platform for CodeHs, just copying and pasting this code:
/**
* This program finds the shortest path through a series of obstacles using A* search,
* smooths the path, then uses PID control to drive a robot to the goal.
**/
// You can play around with these constants---
var NUM_BLOCKS = 20;
var OBSTACLE_PROBABILITY = 0.2;
var MOVE_NOISE = 0.1;
var STEERING_NOISE = 0.1;
var ROBOT_SPEED = 0.5;
var MAX_STEERING_ANGLE = Math.PI/4.0;
var tP = 2.0;
var tI = 0.0001;
var tD = 16.0;
var weightData = 0.1;
var weightSmooth = 0.1;
// Search types
var A_STAR = 0;
var GREEDY = 1;
var BREADTH_FIRST = 2;
var DEPTH_FIRST = 3;
var SEARCH_MODE = A_STAR;
// --------------------------------------------
var BLOCK_SIZE = width/NUM_BLOCKS;
var START = 2;
var GOAL_X = NUM_BLOCKS-1;
var GOAL_Y = NUM_BLOCKS-1;
var GOAL = 3;
var START_COLOR = color(23, 33, 176);
var GOAL_COLOR = color(199, 188, 68);
var FRONTIER_COLOR = color(105, 179, 105);
var EXPLORED_COLOR = color(117, 117, 117, 100);
var PATH_COLOR = color(166, 53, 53);
var SMOOTH_PATH_COLOR = color(53, 68, 166);
var PLAN = 0;
var SMOOTH = 1;
var CALIBRATE = 2;
var NAVIGATE = 3;
var DELTA = [[-1, 0], [1, 0], [0, -1], [0, 1]];
var frontier = [[0, 0, 0, 0]];
var explored = [];
var predecessors = [];
var path = [];
var smoothPath = [];
var mode = PLAN;
var index = 0;
var cte = 0;
var sigma = 0;
angleMode = "radians";
frameRate(60);
// Initialize world
var world = [];
for (var i = 0; i < NUM_BLOCKS; i++) {
var row = [];
for (var j = 0; j < NUM_BLOCKS; j++) {
var r = random();
if (r < OBSTACLE_PROBABILITY) {
row.push(1);
}
else {
row.push(0);
}
}
world.push(row);
}
world[0][0] = START;
world[GOAL_Y][GOAL_X] = GOAL;
for (var i = 0; i < NUM_BLOCKS; i++) {
var row = [];
for (var j = 0; j < NUM_BLOCKS; j++) {
row.push(false);
}
explored.push(row);
}
explored[0][0] = true;
for (var i = 0; i < NUM_BLOCKS; i++) {
var row = [];
for (var j = 0; j < NUM_BLOCKS; j++) {
row.push(null);
}
predecessors.push(row);
}
var Robot = function(x, y, size) {
this.x = x;
this.y = y;
this.vx = 0;
this.vy = 0;
this.orientation = 0;
this.size = size;
// Thanks to Sebastian Thrun for this code:
// https://www.udacity.com/course/viewer#!/c-cs373/l-48696626/e-48403941/m-48729137
// My code for this movement can be found at:
// https://www.khanacademy.org/computer-programming/bicycle-model/5496951953031168
this.move = function(d, theta) {
var generator = new Random(millis());
var moveDistance = (generator.nextGaussian() * MOVE_NOISE) + d;
var turnAngle = (generator.nextGaussian() * STEERING_NOISE) + theta;
turnAngle = min(turnAngle, MAX_STEERING_ANGLE);
turnAngle = max(turnAngle, -MAX_STEERING_ANGLE);
var turn = tan(turnAngle)*moveDistance/this.size;
// Approximately straight motion
if (abs(turn) < 0.001) {
this.x += moveDistance*cos(this.orientation);
this.y += moveDistance*sin(this.orientation);
this.orientation = (this.orientation+turn)%(2.0*Math.PI);
}
// Move using the bicyle model
else {
var radius = moveDistance/turn;
var cx = this.x-(sin(this.orientation)*radius);
var cy = this.y+(cos(this.orientation)*radius);
this.orientation = (this.orientation+turn)%(2.0*Math.PI);
this.x = cx + (sin(this.orientation)*radius);
this.y = cy - (cos(this.orientation)*radius);
}
};
this.draw = function() {
pushMatrix();
translate(this.x, this.y);
rotate(this.orientation);
fill(128, 27, 27);
stroke(255, 0, 0);
rect(-this.size/2, -(this.size*0.75*0.5), this.size, this.size*0.75);
popMatrix();
};
};
var robot;
var addToFrontier = function(node) {
// Insert the node into the frontier
// Order by lowest cost
var i = frontier.length-1;
if (SEARCH_MODE === A_STAR) {
while (i > 0 && node[2]+node[3] < frontier[i][2]+frontier[i][3]) {
i--;
}
}
else if (SEARCH_MODE === GREEDY) {
while (i > 0 && node[3] < frontier[i][3]) {
i--;
}
}
else if (SEARCH_MODE === BREADTH_FIRST) {
frontier.push(node);
}
else if (SEARCH_MODE === DEPTH_FIRST) {
frontier.splice(0, 0, node);
}
frontier.splice(i+1, 0, node);
};
var distance = function(x1, y1, x2, y2) {
return sqrt(pow(x1-x2, 2) + pow(y1-y2, 2));
};
var manhattanDistance = function(x1, y1, x2, y2) {
return abs(x1-x2) + abs(y1-y2);
};
var drawWorld = function() {
background(255, 255, 255);
for (var i = 0; i < world.length; i++) {
for (var j = 0; j < world[0].length; j++) {
if (world[i][j] === 1) {
stroke(0, 0, 0);
fill(0, 0, 0);
}
else if (world[i][j] === START) {
fill(START_COLOR);
stroke(START_COLOR);
}
else if (world[i][j] === GOAL) {
fill(GOAL_COLOR);
stroke(GOAL_COLOR);
}
else {
fill(255, 255, 255);
noStroke();
}
rect(j*BLOCK_SIZE, i*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
}
};
var simulate = function(steps) {
var error = 0;
var crosstrackError = 0;
var r = new Robot(0, 1, BLOCK_SIZE/2);
var sigma = 0;
var diff = 0;
for (var i = 0; i < steps*2; i++) {
// Compute cte
diff = r.y-crosstrackError;
crosstrackError = r.y;
sigma += crosstrackError;
if (i > steps) {
error += pow(crosstrackError, 2);
}
// Update robot
r.move(ROBOT_SPEED, -tP*crosstrackError - tD*diff - tI*sigma);
}
return error;
};
draw = function() {
drawWorld();
// Use A* to find a path to the goal
if (mode === PLAN) {
// Draw explored
fill(EXPLORED_COLOR);
noStroke();
for (var i = 0; i < explored.length; i++) {
for (var j = 0; j < explored[0].length; j++) {
if (explored[i][j]) {
rect(j*BLOCK_SIZE, i*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
}
}
// Draw frontier
fill(FRONTIER_COLOR);
noStroke();
for (var i = 0; i < frontier.length; i++) {
rect(frontier[i][0]*BLOCK_SIZE, frontier[i][1]*BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE);
}
// A*
if (frontier.length > 0) {
// Remove a node from the frontier
var x = frontier[0][0];
var y = frontier[0][1];
var cost = frontier[0][2];
//println(cost + ", " + frontier[0][3]);
frontier.splice(0, 1);
// Goal check
if (world[y][x] === GOAL) {
mode = SMOOTH;
}
else {
// Add all adjacent unexplored nodes
for (var i = 0; i < DELTA.length; i++) {
// If the new position is in the world
var x2 = x + DELTA[i][0];
var y2 = y + DELTA[i][1];
if (x2 >= 0 && x2 < world[0].length && y2 >= 0 && y2 < world.length) {
// If the position is unexplored
if (!explored[y2][x2] && world[y2][x2] !== 1) {
explored[y2][x2] = true;
predecessors[y2][x2] = [x, y];
addToFrontier([x2, y2, cost+1, dist(x2, y2, GOAL_X, GOAL_Y)]);
}
}
}
}
}
else {
mode = -1;
println("No possible path to goal.");
}
}
// Smooth the path
else if (mode === SMOOTH) {
// Build path
if (path.length === 0) {
var x = GOAL_X;
var y = GOAL_Y;
path.splice(0, 0, [x, y]);
smoothPath.splice(0, 0, [x, y]);
while (x !== 0 || y !== 0) {
var newX = predecessors[y][x][0];
var newY = predecessors[y][x][1];
x = newX;
y = newY;
path.splice(0, 0, [x, y]);
smoothPath.splice(0, 0, [x, y]);
}
}
// Draw the original path
stroke(PATH_COLOR);
strokeWeight(5);
for (var i = 0; i < path.length; i++) {
point(BLOCK_SIZE*path[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*path[i][1]+BLOCK_SIZE/2);
}
strokeWeight(1);
for (var i = 0; i < path.length-1; i++) {
line(BLOCK_SIZE*path[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*path[i][1]+BLOCK_SIZE/2, BLOCK_SIZE*path[i+1][0]+BLOCK_SIZE/2, BLOCK_SIZE*path[i+1][1]+BLOCK_SIZE/2);
}
// Draw the new path
stroke(SMOOTH_PATH_COLOR);
strokeWeight(5);
for (var i = 0; i < smoothPath.length; i++) {
point(BLOCK_SIZE*smoothPath[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i][1]+BLOCK_SIZE/2);
}
strokeWeight(1);
for (var i = 0; i < smoothPath.length-1; i++) {
line(BLOCK_SIZE*smoothPath[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i][1]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i+1][0]+BLOCK_SIZE/2,
BLOCK_SIZE*smoothPath[i+1][1]+BLOCK_SIZE/2);
}
// Perform gradient descent
var update;
var diff = 0;
for (var i = 1; i < smoothPath.length-1; i++) {
update = [0, 0];
for (var j = 0; j < smoothPath[0].length; j++) {
update[j] += weightData * (path[i][j] - smoothPath[i][j]);
update[j] += weightSmooth * (smoothPath[(i+1)%smoothPath.length][j] + smoothPath[(i-1+smoothPath.length)%smoothPath.length][j] - 2*smoothPath[i][j]);
}
// Simulataneous update
for (var j = 0; j < smoothPath[0].length; j++) {
smoothPath[i][j] += update[j];
diff += abs(update[j]);
}
}
if (diff < 0.000001) {
robot = new Robot(BLOCK_SIZE/2, BLOCK_SIZE/2, BLOCK_SIZE/2);
mode = NAVIGATE;
}
}
else if (mode === CALIBRATE) {
var steps = 100;
var error = simulate(steps);
var dp = [1.0, 1.0, 1.0];
var params = [0, 0, 0];
if (error/steps > 0.04) {
for (var i = 0; i < dp.length; i++) {
params[i] += dp[i];
var newError = simulate(steps);
if (newError < error) {
error = newError;
dp[i] *= 1.1;
}
else {
params[i] -= 2*dp[i];
newError = simulate(steps);
if (newError < error) {
error = newError;
dp[i] *= -1.1;
}
else {
params[i] += dp[i];
dp[i] *= 0.9;
}
}
}
tP = params[0];
tD = params[1];
tI = params[2];
println(error/steps);
}
else {
println(params);
tP = params[0];
tD = params[1];
tI = params[2];
mode = NAVIGATE;
}
}
// Use PID control to follow the path
else if (mode === NAVIGATE) {
// Draw path
stroke(SMOOTH_PATH_COLOR);
strokeWeight(5);
for (var i = 0; i < smoothPath.length; i++) {
point(BLOCK_SIZE*smoothPath[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i][1]+BLOCK_SIZE/2);
}
strokeWeight(1);
for (var i = 0; i < smoothPath.length-1; i++) {
line(BLOCK_SIZE*smoothPath[i][0]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i][1]+BLOCK_SIZE/2, BLOCK_SIZE*smoothPath[i+1][0]+BLOCK_SIZE/2,
BLOCK_SIZE*smoothPath[i+1][1]+BLOCK_SIZE/2);
}
// Draw robot
robot.draw();
// Compute cte
var diff = -cte;
var x1 = smoothPath[index][0]*BLOCK_SIZE+BLOCK_SIZE/2;
var y1 = smoothPath[index][1]*BLOCK_SIZE+BLOCK_SIZE/2;
var x2 = smoothPath[index+1][0]*BLOCK_SIZE+BLOCK_SIZE/2;
var y2 = smoothPath[index+1][1]*BLOCK_SIZE+BLOCK_SIZE/2;
var dx = x2-x1;
var dy = y2-y1;
var d = sqrt(pow(dx, 2) + pow(dy, 2));
var u = ((robot.x-x1)*dx + (robot.y-y1)*dy)/(pow(dx, 2) + pow(dy, 2));
var Px = x1 + d*u*cos(atan2(dy, dx));
var Py = y1 + d*u*sin(atan2(dy, dx));
cte = ((robot.y-y1)*dx-(robot.x-x1)*dy)/(pow(dx, 2) + pow(dy, 2));
sigma += cte;
diff += cte;
if (u > 1) {
index++;
index = min(index, smoothPath.length-2);
}
// Update robot
robot.move(ROBOT_SPEED, -tP*cte - tD*diff - tI*sigma);
//println(index);
}
};
but all that happens is that a grey screen shows up

Custom gradient in javascript for canvas have some bugs

For a project I am attempting to make my own linear gradient code. And I have managed to come quite far, however, it is not perfect. Some places the gradient is not looking as it should.
See the following code where the colorStops are the stops in the gradient. It is supposed to show a gradient in between red, yellow and green as in a traffic light.
gradientSlider = {
colorStops: ["#b30000", "#ffff1a", "#00e600"],
minValue: 0,
maxValue: 100,
init: function(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d");
this.width = canvas.width;
this.height = canvas.height;
this.colors = this.calculateHexColorForStep();
this.draw();
},
draw: function() {
pixelWidth = this.width / this.maxValue;
for (i = 0; i < this.maxValue; i++) {
this.ctx.beginPath();
this.ctx.rect(i * pixelWidth, 0, pixelWidth, this.height);
this.ctx.fillStyle = this.colors[i];
this.ctx.fill();
}
},
calculateHexColorForStep: function() {
result = [];
stepsPerGradient = this.maxValue / (this.colorStops.length - 1);
for (i = 0; i < this.colorStops.length - 1; i++) {
percentIncrease = 100 / stepsPerGradient / 100;
firstColor = this.colorStops[i];
targetColor = this.colorStops[i + 1];
firstColorDecArray = this.tools.parseColor(firstColor);
targetColorDecArray = this.tools.parseColor(targetColor);
for (j = 0; j <= stepsPerGradient; j++) {
if (j == 0) {
result.push(firstColor);
} else if (j == stepsPerGradient) {
result.push(targetColor);
} else {
stepColorDecArray = [firstColorDecArray[0] + (percentIncrease * j) * (targetColorDecArray[0] - firstColorDecArray[0]),
firstColorDecArray[1] + (percentIncrease * j) * (targetColorDecArray[1] - firstColorDecArray[1]),
firstColorDecArray[2] + (percentIncrease * j) * (targetColorDecArray[2] - firstColorDecArray[2])
];
result.push(this.tools.decimalToHex(stepColorDecArray));
}
}
}
return result;
},
tools: {
parseColor: function(hexColorString) {
var m;
m = hexColorString.match(/^#([0-9a-f]{6})$/i)[1];
if (m) {
return [parseInt(m.substring(0, 2), 16), parseInt(m.substring(2, 4), 16), parseInt(m.substring(4, 6), 16)];
}
},
decimalToHex: function(decimalNumberArray) {
//TODO fikse tall under ti - alltid to plasser
var results = [];
results[1] = Math.round(decimalNumberArray[0]).toString(16);
results[2] = Math.round(decimalNumberArray[1]).toString(16);
results[3] = Math.round(decimalNumberArray[2]).toString(16);
for (var i = 1; i <= results.length; i++) {
if (!(isNaN(results[i]))) {
if (results[i] < 10) {
results[i] = "0" + results[i];
}
}
}
return "#" + results[1] + results[2] + results[3];
}
}
}
//get the canvas element
var canvasElm = document.querySelector("canvas");
//initialize the slider
gradientSlider.init(canvasElm);
<canvas id="gradient-slider" width="600" height="200" class="gradientslider"></canvas>
My solution is a fixed version of the function tools.decimalToHex. The error was that you treat the results array as an array of numbers, instead of an array of strings.
decimalToHex : function(decimalNumberArray){
var results = [];
// Maybe check if number is in range 0 - 255, before converting to string?
results[0] = Math.round(decimalNumberArray[0]).toString(16);
results[1] = Math.round(decimalNumberArray[1]).toString(16);
results[2] = Math.round(decimalNumberArray[2]).toString(16);
for (var i = 0; i<results.length; i++) {
if(results[i].length < 2) {
results[i] = "0" + results[i];
}
}
return "#" + results[0] + results[1] + results[2];
}
Corresponding jsFiddle: https://jsfiddle.net/8xr4zujj/4/

How to make a particle glow with three.js?

This is my codepen source, it's about particle stream.
I have already tried Three.js doc, couldn't found any about glow options...
demo resource link
this is javascript & stylesheet & html code:
var mContainer;
var mCamera, mRenderer;
var mControls;
var mScene;
var mParticleCount = 100000; // <-- change this number!
var mParticleSystem;
var mTime = 0.0;
var mTimeStep = (1 / 60);
var mDuration = 20;
window.onload = function() {
init();
};
function init() {
initTHREE();
initControls();
initParticleSystem();
requestAnimationFrame(tick);
window.addEventListener('resize', resize, false);
}
function initTHREE() {
mRenderer = new THREE.WebGLRenderer({
antialias: true
});
mRenderer.setSize(window.innerWidth, window.innerHeight);
mContainer = document.getElementById('three-container');
mContainer.appendChild(mRenderer.domElement);
mCamera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 5000);
mCamera.position.set(0, 600, 600);
mScene = new THREE.Scene();
var light;
light = new THREE.PointLight(0xffffff, 4, 1000, 2);
light.position.set(0, 400, 0);
mScene.add(light);
}
function initControls() {
mControls = new THREE.OrbitControls(mCamera, mRenderer.domElement);
}
function initParticleSystem() {
var material = new THREE.ShaderMaterial({
uniforms: {
color: {
type: "c",
value: new THREE.Color(0xffffff)
}
},
alphaTest: 0.9
});
var geometrys = new THREE.BufferGeometry();
var geometry = new THREE.Points(geometrys, material);
var prefabGeometry = new THREE.PlaneGeometry(2, 2);
var bufferGeometry = new THREE.BAS.PrefabBufferGeometry(prefabGeometry, mParticleCount);
bufferGeometry.computeVertexNormals();
// generate additional geometry data
var aOffset = bufferGeometry.createAttribute('aOffset', 1);
var aStartPosition = bufferGeometry.createAttribute('aStartPosition', 3);
var aControlPoint1 = bufferGeometry.createAttribute('aControlPoint1', 3);
var aControlPoint2 = bufferGeometry.createAttribute('aControlPoint2', 3);
var aEndPosition = bufferGeometry.createAttribute('aEndPosition', 3);
var aAxisAngle = bufferGeometry.createAttribute('aAxisAngle', 4);
var aColor = bufferGeometry.createAttribute('color', 3);
var i, j, offset;
// buffer time offset
var delay;
for (i = 0, offset = 0; i < mParticleCount; i++) {
delay = i / mParticleCount * mDuration;
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aOffset.array[offset++] = delay;
}
}
// buffer start positions
var x, y, z;
for (i = 0, offset = 0; i < mParticleCount; i++) {
x = -1000;
y = 0;
z = 0;
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aStartPosition.array[offset++] = x;
aStartPosition.array[offset++] = y;
aStartPosition.array[offset++] = z;
}
}
// buffer control points
for (i = 0, offset = 0; i < mParticleCount; i++) {
x = THREE.Math.randFloat(-400, 400);
y = THREE.Math.randFloat(400, 600);
z = THREE.Math.randFloat(-1200, -800);
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aControlPoint1.array[offset++] = x;
aControlPoint1.array[offset++] = y;
aControlPoint1.array[offset++] = z;
}
}
for (i = 0, offset = 0; i < mParticleCount; i++) {
x = THREE.Math.randFloat(-400, 400);
y = THREE.Math.randFloat(-600, -400);
z = THREE.Math.randFloat(800, 1200);
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aControlPoint2.array[offset++] = x;
aControlPoint2.array[offset++] = y;
aControlPoint2.array[offset++] = z;
}
}
// buffer end positions
for (i = 0, offset = 0; i < mParticleCount; i++) {
x = 1000;
y = 0;
z = 0;
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aEndPosition.array[offset++] = x;
aEndPosition.array[offset++] = y;
aEndPosition.array[offset++] = z;
}
}
// buffer axis angle
var axis = new THREE.Vector3();
var angle = 0;
for (i = 0, offset = 0; i < mParticleCount; i++) {
axis.x = THREE.Math.randFloatSpread(2);
axis.y = THREE.Math.randFloatSpread(2);
axis.z = THREE.Math.randFloatSpread(2);
axis.normalize();
angle = Math.PI * THREE.Math.randInt(16, 32);
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aAxisAngle.array[offset++] = axis.x;
aAxisAngle.array[offset++] = axis.y;
aAxisAngle.array[offset++] = axis.z;
aAxisAngle.array[offset++] = angle;
}
}
// buffer color
var color = new THREE.Color();
var h, s, l;
for (i = 0, offset = 0; i < mParticleCount; i++) {
h = i / mParticleCount;
s = THREE.Math.randFloat(0.4, 0.6);
l = THREE.Math.randFloat(0.4, 0.6);
color.setHSL(h, s, l);
for (j = 0; j < prefabGeometry.vertices.length; j++) {
aColor.array[offset++] = color.r;
aColor.array[offset++] = color.g;
aColor.array[offset++] = color.b;
}
}
var material = new THREE.BAS.PhongAnimationMaterial(
// custom parameters & THREE.MeshPhongMaterial parameters
{
vertexColors: THREE.VertexColors,
shading: THREE.FlatShading,
side: THREE.DoubleSide,
uniforms: {
uTime: {
type: 'f',
value: 0
},
uDuration: {
type: 'f',
value: mDuration
}
},
shaderFunctions: [
THREE.BAS.ShaderChunk['quaternion_rotation'],
THREE.BAS.ShaderChunk['cubic_bezier']
],
shaderParameters: [
'uniform float uTime;',
'uniform float uDuration;',
'attribute float aOffset;',
'attribute vec3 aStartPosition;',
'attribute vec3 aControlPoint1;',
'attribute vec3 aControlPoint2;',
'attribute vec3 aEndPosition;',
'attribute vec4 aAxisAngle;'
],
shaderVertexInit: [
'float tProgress = mod((uTime + aOffset), uDuration) / uDuration;',
'float angle = aAxisAngle.w * tProgress;',
'vec4 tQuat = quatFromAxisAngle(aAxisAngle.xyz, angle);'
],
shaderTransformNormal: [
'objectNormal = rotateVector(tQuat, objectNormal);'
],
shaderTransformPosition: [
'transformed = rotateVector(tQuat, transformed);',
'transformed += cubicBezier(aStartPosition, aControlPoint1, aControlPoint2, aEndPosition, tProgress);'
]
},
// THREE.MeshPhongMaterial uniforms
{
specular: 0xff0000,
shininess: 20
}
);
mParticleSystem = new THREE.Mesh(bufferGeometry, material);
// because the bounding box of the particle system does not reflect its on-screen size
// set this to false to prevent the whole thing from disappearing on certain angles
mParticleSystem.frustumCulled = false;
mScene.add(mParticleSystem);
}
function tick() {
update();
render();
mTime += mTimeStep;
mTime %= mDuration;
requestAnimationFrame(tick);
}
function update() {
mControls.update();
mParticleSystem.material.uniforms['uTime'].value = mTime;
}
function render() {
mRenderer.render(mScene, mCamera);
}
function resize() {
mCamera.aspect = window.innerWidth / window.innerHeight;
mCamera.updateProjectionMatrix();
mRenderer.setSize(window.innerWidth, window.innerHeight);
}
/////////////////////////////
// buffer animation system
/////////////////////////////
THREE.BAS = {};
THREE.BAS.ShaderChunk = {};
THREE.BAS.ShaderChunk["animation_time"] = "float tDelay = aAnimation.x;\nfloat tDuration = aAnimation.y;\nfloat tTime = clamp(uTime - tDelay, 0.0, tDuration);\nfloat tProgress = ease(tTime, 0.0, 1.0, tDuration);\n";
THREE.BAS.ShaderChunk["cubic_bezier"] = "vec3 cubicBezier(vec3 p0, vec3 c0, vec3 c1, vec3 p1, float t)\n{\n vec3 tp;\n float tn = 1.0 - t;\n\n tp.xyz = tn * tn * tn * p0.xyz + 3.0 * tn * tn * t * c0.xyz + 3.0 * tn * t * t * c1.xyz + t * t * t * p1.xyz;\n\n return tp;\n}\n";
THREE.BAS.ShaderChunk["ease_in_cubic"] = "float ease(float t, float b, float c, float d) {\n return c*(t/=d)*t*t + b;\n}\n";
THREE.BAS.ShaderChunk["ease_in_quad"] = "float ease(float t, float b, float c, float d) {\n return c*(t/=d)*t + b;\n}\n";
THREE.BAS.ShaderChunk["ease_out_cubic"] = "float ease(float t, float b, float c, float d) {\n return c*((t=t/d - 1.0)*t*t + 1.0) + b;\n}\n";
THREE.BAS.ShaderChunk["quaternion_rotation"] = "vec3 rotateVector(vec4 q, vec3 v)\n{\n return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);\n}\n\nvec4 quatFromAxisAngle(vec3 axis, float angle)\n{\n float halfAngle = angle * 0.5;\n return vec4(axis.xyz * sin(halfAngle), cos(halfAngle));\n}\n";
THREE.BAS.PrefabBufferGeometry = function(prefab, count) {
THREE.BufferGeometry.call(this);
this.prefabGeometry = prefab;
this.prefabCount = count;
this.prefabVertexCount = prefab.vertices.length;
this.bufferDefaults();
};
THREE.BAS.PrefabBufferGeometry.prototype = Object.create(THREE.BufferGeometry.prototype);
THREE.BAS.PrefabBufferGeometry.prototype.constructor = THREE.BAS.PrefabBufferGeometry;
THREE.BAS.PrefabBufferGeometry.prototype.bufferDefaults = function() {
var prefabFaceCount = this.prefabGeometry.faces.length;
var prefabIndexCount = this.prefabGeometry.faces.length * 3;
var prefabVertexCount = this.prefabVertexCount = this.prefabGeometry.vertices.length;
var prefabIndices = [];
//console.log('prefabCount', this.prefabCount);
//console.log('prefabFaceCount', prefabFaceCount);
//console.log('prefabIndexCount', prefabIndexCount);
//console.log('prefabVertexCount', prefabVertexCount);
//console.log('triangles', prefabFaceCount * this.prefabCount);
for (var h = 0; h < prefabFaceCount; h++) {
var face = this.prefabGeometry.faces[h];
prefabIndices.push(face.a, face.b, face.c);
}
var indexBuffer = new Uint32Array(this.prefabCount * prefabIndexCount);
var positionBuffer = new Float32Array(this.prefabCount * prefabVertexCount * 3);
this.setIndex(new THREE.BufferAttribute(indexBuffer, 1));
this.addAttribute('position', new THREE.BufferAttribute(positionBuffer, 3));
for (var i = 0, offset = 0; i < this.prefabCount; i++) {
for (var j = 0; j < prefabVertexCount; j++, offset += 3) {
var prefabVertex = this.prefabGeometry.vertices[j];
positionBuffer[offset] = prefabVertex.x;
positionBuffer[offset + 1] = prefabVertex.y;
positionBuffer[offset + 2] = prefabVertex.z;
}
for (var k = 0; k < prefabIndexCount; k++) {
indexBuffer[i * prefabIndexCount + k] = prefabIndices[k] + i * prefabVertexCount;
}
}
};
// todo test
THREE.BAS.PrefabBufferGeometry.prototype.bufferUvs = function() {
var prefabFaceCount = this.prefabGeometry.faces.length;
var prefabVertexCount = this.prefabVertexCount = this.prefabGeometry.vertices.length;
var prefabUvs = [];
for (var h = 0; h < prefabFaceCount; h++) {
var face = this.prefabGeometry.faces[h];
var uv = this.prefabGeometry.faceVertexUvs[0][h];
prefabUvs[face.a] = uv[0];
prefabUvs[face.b] = uv[1];
prefabUvs[face.c] = uv[2];
}
var uvBuffer = this.createAttribute('uv', 2);
for (var i = 0, offset = 0; i < this.prefabCount; i++) {
for (var j = 0; j < prefabVertexCount; j++, offset += 2) {
var prefabUv = prefabUvs[j];
uvBuffer.array[offset] = prefabUv.x;
uvBuffer.array[offset + 1] = prefabUv.y;
}
}
};
/**
* based on BufferGeometry.computeVertexNormals
* calculate vertex normals for a prefab, and repeat the data in the normal buffer
*/
THREE.BAS.PrefabBufferGeometry.prototype.computeVertexNormals = function() {
var index = this.index;
var attributes = this.attributes;
var positions = attributes.position.array;
if (attributes.normal === undefined) {
this.addAttribute('normal', new THREE.BufferAttribute(new Float32Array(positions.length), 3));
}
var normals = attributes.normal.array;
var vA, vB, vC,
pA = new THREE.Vector3(),
pB = new THREE.Vector3(),
pC = new THREE.Vector3(),
cb = new THREE.Vector3(),
ab = new THREE.Vector3();
var indices = index.array;
var prefabIndexCount = this.prefabGeometry.faces.length * 3;
for (var i = 0; i < prefabIndexCount; i += 3) {
vA = indices[i + 0] * 3;
vB = indices[i + 1] * 3;
vC = indices[i + 2] * 3;
pA.fromArray(positions, vA);
pB.fromArray(positions, vB);
pC.fromArray(positions, vC);
cb.subVectors(pC, pB);
ab.subVectors(pA, pB);
cb.cross(ab);
normals[vA] += cb.x;
normals[vA + 1] += cb.y;
normals[vA + 2] += cb.z;
normals[vB] += cb.x;
normals[vB + 1] += cb.y;
normals[vB + 2] += cb.z;
normals[vC] += cb.x;
normals[vC + 1] += cb.y;
normals[vC + 2] += cb.z;
}
for (var j = 1; j < this.prefabCount; j++) {
for (var k = 0; k < prefabIndexCount; k++) {
normals[j * prefabIndexCount + k] = normals[k];
}
}
this.normalizeNormals();
attributes.normal.needsUpdate = true;
};
THREE.BAS.PrefabBufferGeometry.prototype.createAttribute = function(name, itemSize) {
var buffer = new Float32Array(this.prefabCount * this.prefabVertexCount * itemSize);
var attribute = new THREE.BufferAttribute(buffer, itemSize);
this.addAttribute(name, attribute);
return attribute;
};
THREE.BAS.PrefabBufferGeometry.prototype.setAttribute4 = function(name, data) {
var offset = 0;
var array = this.geometry.attributes[name].array;
var i, j;
for (i = 0; i < data.length; i++) {
var v = data[i];
for (j = 0; j < this.prefabVertexCount; j++) {
array[offset++] = v.x;
array[offset++] = v.y;
array[offset++] = v.z;
array[offset++] = v.w;
}
}
this.geometry.attributes[name].needsUpdate = true;
};
THREE.BAS.PrefabBufferGeometry.prototype.setAttribute3 = function(name, data) {
var offset = 0;
var array = this.geometry.attributes[name].array;
var i, j;
for (i = 0; i < data.length; i++) {
var v = data[i];
for (j = 0; j < this.prefabVertexCount; j++) {
array[offset++] = v.x;
array[offset++] = v.y;
array[offset++] = v.z;
}
}
this.geometry.attributes[name].needsUpdate = true;
};
THREE.BAS.PrefabBufferGeometry.prototype.setAttribute2 = function(name, data) {
var offset = 0;
var array = this.geometry.attributes[name].array;
var i, j;
for (i = 0; i < this.prefabCount; i++) {
var v = data[i];
for (j = 0; j < this.prefabVertexCount; j++) {
array[offset++] = v.x;
array[offset++] = v.y;
}
}
this.geometry.attributes[name].needsUpdate = true;
};
THREE.BAS.BaseAnimationMaterial = function(parameters) {
THREE.ShaderMaterial.call(this);
this.shaderFunctions = [];
this.shaderParameters = [];
this.shaderVertexInit = [];
this.shaderTransformNormal = [];
this.shaderTransformPosition = [];
this.setValues(parameters);
};
THREE.BAS.BaseAnimationMaterial.prototype = Object.create(THREE.ShaderMaterial.prototype);
THREE.BAS.BaseAnimationMaterial.prototype.constructor = THREE.BAS.BaseAnimationMaterial;
// abstract
THREE.BAS.BaseAnimationMaterial.prototype._concatVertexShader = function() {
return '';
};
THREE.BAS.BaseAnimationMaterial.prototype._concatFunctions = function() {
return this.shaderFunctions.join('\n');
};
THREE.BAS.BaseAnimationMaterial.prototype._concatParameters = function() {
return this.shaderParameters.join('\n');
};
THREE.BAS.BaseAnimationMaterial.prototype._concatVertexInit = function() {
return this.shaderVertexInit.join('\n');
};
THREE.BAS.BaseAnimationMaterial.prototype._concatTransformNormal = function() {
return this.shaderTransformNormal.join('\n');
};
THREE.BAS.BaseAnimationMaterial.prototype._concatTransformPosition = function() {
return this.shaderTransformPosition.join('\n');
};
THREE.BAS.BaseAnimationMaterial.prototype.setUniformValues = function(values) {
for (var key in values) {
if (key in this.uniforms) {
var uniform = this.uniforms[key];
var value = values[key];
// todo add matrix uniform types
switch (uniform.type) {
case 'c': // color
uniform.value.set(value);
break;
case 'v2': // vectors
case 'v3':
case 'v4':
uniform.value.copy(value);
break;
case 'f': // float
case 't': // texture
uniform.value = value;
}
}
}
};
THREE.BAS.PhongAnimationMaterial = function(parameters, uniformValues) {
THREE.BAS.BaseAnimationMaterial.call(this, parameters);
var phongShader = THREE.ShaderLib['phong'];
this.uniforms = THREE.UniformsUtils.merge([phongShader.uniforms, this.uniforms]);
this.lights = true;
this.vertexShader = this._concatVertexShader();
this.fragmentShader = phongShader.fragmentShader;
// todo add missing default defines
uniformValues.map && (this.defines['USE_MAP'] = '');
uniformValues.normalMap && (this.defines['USE_NORMALMAP'] = '');
this.setUniformValues(uniformValues);
};
THREE.BAS.PhongAnimationMaterial.prototype = Object.create(THREE.BAS.BaseAnimationMaterial.prototype);
THREE.BAS.PhongAnimationMaterial.prototype.constructor = THREE.BAS.PhongAnimationMaterial;
THREE.BAS.PhongAnimationMaterial.prototype._concatVertexShader = function() {
// based on THREE.ShaderLib.phong
return [
"#define PHONG",
"varying vec3 vViewPosition;",
"#ifndef FLAT_SHADED",
" varying vec3 vNormal;",
"#endif",
THREE.ShaderChunk["common"],
THREE.ShaderChunk["uv_pars_vertex"],
THREE.ShaderChunk["uv2_pars_vertex"],
THREE.ShaderChunk["displacementmap_pars_vertex"],
THREE.ShaderChunk["envmap_pars_vertex"],
THREE.ShaderChunk["lights_phong_pars_vertex"],
THREE.ShaderChunk["color_pars_vertex"],
THREE.ShaderChunk["morphtarget_pars_vertex"],
THREE.ShaderChunk["skinning_pars_vertex"],
THREE.ShaderChunk["shadowmap_pars_vertex"],
THREE.ShaderChunk["logdepthbuf_pars_vertex"],
this._concatFunctions(),
this._concatParameters(),
"void main() {",
this._concatVertexInit(),
THREE.ShaderChunk["uv_vertex"],
THREE.ShaderChunk["uv2_vertex"],
THREE.ShaderChunk["color_vertex"],
THREE.ShaderChunk["beginnormal_vertex"],
this._concatTransformNormal(),
THREE.ShaderChunk["morphnormal_vertex"],
THREE.ShaderChunk["skinbase_vertex"],
THREE.ShaderChunk["skinnormal_vertex"],
THREE.ShaderChunk["defaultnormal_vertex"],
"#ifndef FLAT_SHADED", // Normal computed with derivatives when FLAT_SHADED
" vNormal = normalize( transformedNormal );",
"#endif",
THREE.ShaderChunk["begin_vertex"],
this._concatTransformPosition(),
THREE.ShaderChunk["displacementmap_vertex"],
THREE.ShaderChunk["morphtarget_vertex"],
THREE.ShaderChunk["skinning_vertex"],
THREE.ShaderChunk["project_vertex"],
THREE.ShaderChunk["logdepthbuf_vertex"],
" vViewPosition = - mvPosition.xyz;",
THREE.ShaderChunk["worldpos_vertex"],
THREE.ShaderChunk["envmap_vertex"],
THREE.ShaderChunk["lights_phong_vertex"],
THREE.ShaderChunk["shadowmap_vertex"],
"}"
].join("\n");
};
body {
margin: 0;
overflow: hidden;
cursor: move;
}
<div id="three-container"></div>
<script src="http://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/THREE.OrbitControls.js"></script>
Have a Look at THREEX.geometric glow

How to stop line colouring over transparent canvas?

I need some expert help. When I make canvas background transparent the line colors the whole canvas (shown in code below).
How do I stop/fix this? I want the line stay as a single line that doesn't color the canvas.
Math.clamp = function(x, min, max) {
return x < min ? min : (x > max ? max : x);
};
// canvas settings
var viewWidth = window.innerWidth,
viewHeight = window.innerHeight,
drawingCanvas = document.getElementById("drawing_canvas"),
ctx,
timeStep = (10 / 100),
time = 0;
var lineTension = 0.067,
lineDamping = 0.068,
waveSpreadFactor = 0.1;
var previousMousePosition = {
x: 0,
y: 0
},
currentMousePosition = {
x: 0,
y: 0
},
actualMousePosition = {
x: 0,
y: 0
};
var line,
lineSegmentCount = 64,
lineMaxForce = 32,
lineStrokeGradient;
var audioCtx,
nodeCount = 64,
oscillatorNodes = [],
gainNodes = [];
var segmentsPerNode = lineSegmentCount / nodeCount;
function initGui() {
}
function goBananas() {
lineTension = 0.2;
line.anchors[Math.floor(Math.random() * line.anchors.length)].
vel = lineMaxForce;
}
function resetLine() {
line.reset();
for (var i = 0; i < nodeCount; i++) {
oscillatorNodes[i].detune.value = 100;
gainNodes[i].gain.value = 0;
}
lineTension = 0.0025;
lineDamping = 0.05;
waveSpreadFactor = 0.1;
}
function initDrawingCanvas() {
drawingCanvas.width = viewWidth;
drawingCanvas.height = viewHeight;
window.addEventListener('resize', resizeHandler);
window.addEventListener('mousemove', mouseMoveHandler);
setInterval(updateMousePosition, (1000 / 30));
ctx = drawingCanvas.getContext('2d');
ctx.lineWidth = 5;
line = new Line(0, viewHeight * 0.5, viewWidth, lineSegmentCount);
// line.anchors[0].vel = viewHeight * 0.25;
lineStrokeGradient = ctx.createLinearGradient(0, 0, 0, viewHeight);
lineStrokeGradient.addColorStop(0, '#0ff');
}
function initWebAudio() {
audioCtx = new(window.AudioContext || window.webkitAudioContext)();
for (var i = 0; i < nodeCount; i++) {
var oscillatorNode = audioCtx.createOscillator();
var gainNode = audioCtx.createGain();
oscillatorNode.connect(gainNode);
gainNode.connect(audioCtx.destination);
gainNode.gain.value = 0;
oscillatorNode.type = 'saw';
oscillatorNode.detune.value = 200;
oscillatorNode.frequency.value = 1200 * (i / 60);
oscillatorNode.start();
oscillatorNodes[i] = oscillatorNode;
gainNodes[i] = gainNode;
}
}
function resizeHandler() {
drawingCanvas.width = viewWidth = window.innerWidth;
drawingCanvas.height = viewHeight = window.innerHeight;
if (ctx) {
ctx.lineWidth = 4;
line.resize(viewWidth, viewHeight * 0.5);
}
}
function mouseMoveHandler(event) {
actualMousePosition.x = Math.floor(event.clientX);
actualMousePosition.y = Math.floor(event.clientY);
}
function updateMousePosition() {
previousMousePosition.x = currentMousePosition.x;
previousMousePosition.y = currentMousePosition.y;
currentMousePosition.x = actualMousePosition.x;
currentMousePosition.y = actualMousePosition.y;
}
function update() {
var px = Math.min(previousMousePosition.x, currentMousePosition.x),
py = Math.min(previousMousePosition.y, currentMousePosition.y),
pw = Math.max(1, Math.abs(previousMousePosition.x - currentMousePosition.x)),
ph = Math.max(1, Math.abs(previousMousePosition.y - currentMousePosition.y)),
force = Math.clamp(currentMousePosition.y -
previousMousePosition.y, -lineMaxForce, lineMaxForce);
var pixels = ctx.getImageData(px, py, pw, ph),
data = pixels.data;
for (var i = 0; i < data.length; i += 3) {
var r = data[i + 0],
g = data[i + 1],
b = data[i + 2],
x = (i % ph) + px;
if (r + g + b > 0) {
line.ripple(x, force);
}
}
line.update();
for (var j = 0; j < gainNodes.length; j++) {
var anchor = line.anchors[j * segmentsPerNode],
gain = Math.clamp(Math.abs(anchor.vel) / viewHeight * 0.5, 0, 3),
detune = Math.clamp(anchor.pos / viewHeight * 100, 0, 300);
gainNodes[j].gain.value = gain;
oscillatorNodes[j].detune.value = detune;
}
}
function draw() {
ctx.strokeStyle = lineStrokeGradient;
line.draw();
}
window.onload = function() {
initDrawingCanvas();
initWebAudio();
initGui();
requestAnimationFrame(loop);
};
function loop() {
update();
draw();
time += timeStep;
requestAnimationFrame(loop);
}
Line = function(x, y, length, segments) {
this.x = x;
this.y = y;
this.length = length;
this.segments = segments;
this.segmentLength = this.length / this.segments;
this.anchors = [];
for (var i = 0; i <= this.segments; i++) {
this.anchors[i] = {
target: this.y,
pos: this.y,
vel: 0,
update: function() {
var dy = this.pos - this.target,
acc = -lineTension * dy - lineDamping * this.vel;
this.pos += this.vel;
this.vel += acc;
},
reset: function() {
this.pos = this.target;
this.vel = 0;
}
};
}
};
Line.prototype = {
resize: function(length, targetY) {
this.length = length;
this.segmentLength = this.length / this.segments;
for (var i = 0; i <= this.segments; i++) {
this.anchors[i].target = targetY;
}
},
reset: function() {
for (var i = 0; i <= this.segments; i++) {
this.anchors[i].reset();
}
},
ripple: function(origin, amplitude) {
var i = Math.floor((origin - this.x) / this.segmentLength);
if (i >= 0 && i <= this.segments) {
this.anchors[i].vel = amplitude;
}
},
update: function() {
var lDeltas = [],
rDeltas = [],
i;
for (i = 0; i <= this.segments; i++) {
this.anchors[i].update();
}
for (i = 0; i <= this.segments; i++) {
if (i > 0) {
lDeltas[i] = waveSpreadFactor * (this.anchors[i].pos - this.anchors[i - 1].pos);
this.anchors[i - 1].vel += lDeltas[i];
}
if (i < this.segments) {
rDeltas[i] = waveSpreadFactor * (this.anchors[i].pos - this.anchors[i + 1].pos);
this.anchors[i + 1].vel += rDeltas[i];
}
}
for (i = 0; i <= this.segments; i++) {
if (i > 0) {
this.anchors[i - 1].pos += lDeltas[i];
}
if (i < this.segments) {
this.anchors[i + 1].pos += rDeltas[i];
}
}
},
draw: function() {
ctx.beginPath();
for (var i = 0; i <= this.segments; i++) {
ctx.lineTo(
this.x + this.segmentLength * i,
this.anchors[i].pos
);
}
ctx.stroke();
}
};
From the code you posted, the problem seems to be a missing
ctx.clearRect(0, 0, viewWidth, viewHeight)
at the beginning of your "draw" function.
See a working example here

Categories