not able to select a hexagon in a grid - javascript

I have been looking at my code for hours now , but still I can't figure out what's wrong with it exactly.
I have created a very simple version of hexagon grid system. Where I'd like to select any hexagon inside the grid. All the hexagons are being displayed correctly, it's just that when I click on the grid , an incorrect hexagon gets selected. It behaves, as if the mouse position was not correct, or
perhaps it may be the hexagons that have incorrect position data (?).
But how come they're positioned correctly?
////////// [ Hexagon ] ////////////////////
function Hexagon( options ){
if( options !== "undefined" ){
this.attributes = {
type : options.type || 0, //// 0 is cell , 1 player /// Default : 0 ////
id: options.id,
color : this.getColor(),
coords: [], //// [ r, q ] /// row / col ///
points: [],
pos: options.pos,
size: options.size
};
this.states = {
selected: false
};
//// make short-cuts to frequently used attributes ////
this.pos = this.attributes.pos;
this.coords = this.attributes.coords;
this.size = this.attributes.size;
this.points = this.attributes.points;
///// caclulate top_left, bottom and center points ////
this.TopLeftPoint = [ this.pos[0], this.pos[1] ];
this.BottomRightPoint = [ this.pos[0] + this.size.w, this.pos[1] + this.size.h ];
this.MidPoint = [ this.pos[0] + (this.size.w / 2), this.pos[1] + (this.size.h / 2) ];
///////// generate points ///////
this.generate();
}
}
Hexagon.prototype = {
constructor : Hexagon,
changeState: function( st_name, st_value ){
if( this.checkState( st_name ) ) {
this.states[st_name] = st_value;
}
},
checkState: function( st_name ){
if( typeof this.states[st_name] !== "undefined" ) {
return this.states[st_name];
}
return false;
},
isInHexBounds : function( p ){ /*Point*/
if(this.TopLeftPoint[0] < p[0] && this.TopLeftPoint[1] < p[1] && p[0] < this.BottomRightPoint[0] && p[1] < this.BottomRightPoint[0]){
return true;
}
return false;
},
contains: function( p ) {
var isIn = false;
if( this.isInHexBounds( p ) ){
var i, j = 0;
for (i = 0, j = this.points.length - 1; i < this.points.length; j = i++){
var iP = this.points[i];
var jP = this.points[j];
if (
( ((iP[1] <= p[1]) && (p[1] < jP[1])) || ((jP[1] <= p[1]) && (p[1] < iP[1]))) && (p[0] < (jP[0] - iP[0]) * (p[1] - iP[1]) / (jP[1] - iP[1]) + iP[0])
){
isIn = !isIn;
}
}
}
return isIn;
},
getColor: function( ){
switch( this.type ){
case 0:
return "blue";
case 1:
return "red";
default:
return "yellow";
}
},
trigger: function( e_name ){
this.events[ e_name ].call(this);
},
events: {
"select" : function(){
if( ! this.checkState( "selected" ) ){
this.changeState("selected", true);
//console.log( this.coords )
this.type = 1;
}
}
},
setType: function( type ){
this.attributes.type = type;
},
generate: function(){///// generate hexagon points //////
var x1 = (this.size.w - this.size.s)/2;
var y1 = (this.size.h / 2);
this.points.push(
[ x1 + this.pos[0], this.pos[1] ],
[ x1 + this.size.s + this.pos[0], this.pos[1] ],
[ this.size.w + this.pos[0], y1 + this.pos[1] ],
[ x1 + this.size.s + this.pos[0], this.size.h + this.pos[1] ],
[ x1 + this.pos[0], this.size.h + this.pos[1] ],
[ this.pos[0], y1 + this.pos[1] ]
);
},
draw : function( ctx ){
if( this.type > 0 ){
ctx.globalAlpha = 0.5;
ctx.fillStyle = this.color;
ctx.fill();
ctx.globalAlpha = 1.0;
}else{
ctx.strokeStyle = "grey";
}
//ctx.rect( this.BottomRightPoint[0],this.BottomRightPoint[1],4,4);
//ctx.stroke();
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo( this.points[0][0], this.points[0][1] );
for( var c=1; c < this.points.length; c++ ){
ctx.lineTo( this.points[c][0], this.points[c][1] );
}
ctx.closePath();
ctx.stroke();
this.draw_coords( ctx );
},
draw_coords: function( ctx ){
ctx.font="10px Georgia";
ctx.textAlign = "center";
ctx.textBaseline = 'middle';
ctx.fillStyle = "blue";
ctx.fillText(this.coords[0]+" , "+this.coords[1], this.MidPoint[0], this.MidPoint[1]);
}
}
///////// [ Grid ] /////////////////////
function Grid( options ){
if(typeof options !== "undefined"){
this.size = {
width: options.size[0],
height: options.size[1]
};
//this.mouse_pos = [];
this.pos = options.pos; //// position within the canvas /// [ x , y ] ////
this.ctx = options.ctx;
this.ctx_pos = options.ctx_pos; //// position of canvas element /// [ left, top] ///
this.hex_size = this.calculate_hex_size( options.hex_def );
this.hexagons = []; //// [ row, col ] ///// just a temporary array ////
this.grid2D = []; ///// includes all hexagons to be drawn ///
}
this.generate();
this.animate();
this.enable_mouse_events();
}
Grid.prototype = {
constructor : Grid,
generate : function(){
var hex_pos_x = 0.0, hex_pos_y = 0.0, row = 0, col = 0, offset = 0.0, h = null, h_id = 0;
while( (hex_pos_y + this.hex_size.h) <= this.size.height ){
col = 0; //// reset col
offset = 0.0; //// reset offset
if( (row % 2) == 1){
offset = ( ( this.hex_size.w - this.hex_size.s ) /2 ) + this.hex_size.s ;
col = 1;
}
hex_pos_x = offset;
while( (hex_pos_x + this.hex_size.w) <= this.size.width ){
h = new Hexagon( { pos : [ hex_pos_x, hex_pos_y ], size: this.hex_size , id: row+""+col, type: 0 });
h.coords[0] = col; //// set coord X ///
this.grid2D.push( h );
if( ! this.hexagons[col] ){
this.hexagons[col] = [];
}
this.hexagons[col].push( h );
col += 2;
hex_pos_x += (this.hex_size.w + this.hex_size.s);
}
row++;
hex_pos_y += (this.hex_size.h / 2);
}
////finally go through our list of hexagons by their x co-ordinate to assign the y co-ordinate
var coordX = 0, coordY = 0, h_l = this.hexagons.length, hex_arr = [];
for( ; coordX < h_l; coordX++ ){
hex_arr = this.hexagons[ coordX ];
coordY = Math.floor( (coordX / 2 ) + (coordX % 2) );
for( var h = 0, size = hex_arr.length; h < size; h++ ){
hex_arr[h].coords[1] = coordY++;
}
}
},
getHexAt: function( p ){ //// point [ x, y ]
for ( var h = 0, h_l = this.grid2D.length; h < h_l; h++ ){
if ( this.grid2D[h].contains( p ) ){
return this.grid2D[h];
}
}
return null;
},
animate: function(){
var self = this;
window.requestAnimationFrame( function(){
self.animate();
});
self.draw();
},
draw : function( ){
this.ctx.clearRect(0, 0, this.size.width, this.size.height);
for( var h = 0, h_l = this.grid2D.length; h < h_l; h++ ){
this.grid2D[h].draw( this.ctx );
}
},
calculate_hex_size : function( hex_def ){
return {
w: hex_def.radius * 2,
m: hex_def.margin,
h: (Math.sqrt(3) / 2) * ( hex_def.radius * 2),
r: hex_def.radius,
s: hex_def.radius
}
},
enable_mouse_events: function(){
var self = this;
var mouse_pos = [];
var cur_hex = null;
window.addEventListener( 'mousemove', function(e){
mouse_pos = [ ( e.clientX - self.ctx_pos[0] ), ( e.clientY - self.ctx_pos[1] )];
//self.mouse_pos = mouse_pos;
});
window.addEventListener( 'mousedown', function(e){
if( mouse_pos.length > 0 ){
cur_hex = self.getHexAt( mouse_pos );
if( cur_hex != null ){
cur_hex.trigger("select");
}
}
});
}
}
var c_el = document.getElementById("myCanvas");
var ctx = c_el.getContext("2d");
var nGrid = new Grid({
/// size : [ c_el.width, c_el.height ], /// [rows / cols ] //// 20 px x 10 px///
size : [ 70 , 70 ],
pos: [ 20, 20 ], /// [X, Y] ////
hex_def: {
radius: 20,
margin: 0
},
ctx : ctx,
ctx_pos: [ c_el.getBoundingClientRect().left, c_el.getBoundingClientRect().top ]
});
<body stye="width: 100%; height: 100%" >
<canvas id="myCanvas" width="750px" height="405px" style="margin:0; padding:0; border:1px solid #d3d3d3;"></canvas>
</body>

it turns out that I have mixed up the order of some context's methods. So it was calling the ctx.fill() before the ctx.begonPath()/ctx.closePath(). Which is wrong, so any previously drawn hexagon was being filled as a side effect of this mistake.
Once I added the ctx.fill() after the ctx.begonPath()/ctx.closePath()everything was working perfectly.
See the result below.
////////// [ Hexagon ] ////////////////////
function Hexagon( options ){
if( options !== "undefined" ){
this.attributes = {
type : options.type || 0, //// 0 is cell , 1 player /// Default : 0 ////
id: options.id,
color : this.getColor(),
coords: [], //// [ r, q ] /// row / col ///
points: [],
pos: options.pos,
size: options.size
};
this.states = {
selected: false
};
//// make short-cuts to frequently used attributes ////
this.pos = this.attributes.pos;
this.coords = this.attributes.coords;
this.size = this.attributes.size;
this.points = this.attributes.points;
///// caclulate top_left, bottom and center points ////
this.TopLeftPoint = [ this.pos[0], this.pos[1] ];
this.BottomRightPoint = [ this.pos[0] + this.size.w, this.pos[1] + this.size.h ];
this.MidPoint = [ this.pos[0] + (this.size.w / 2), this.pos[1] + (this.size.h / 2) ];
///////// generate points ///////
this.generate();
}
}
Hexagon.prototype = {
constructor : Hexagon,
changeState: function( st_name, st_value ){
if( this.checkState( st_name ) ) {
this.states[st_name] = st_value;
}
},
checkState: function( st_name ){
if( typeof this.states[st_name] !== "undefined" ) {
return this.states[st_name];
}
return false;
},
isInHexBounds : function( p ){ /*Point*/
if(this.TopLeftPoint[0] < p[0] && this.TopLeftPoint[1] < p[1] && p[0] < this.BottomRightPoint[0] && p[1] < this.BottomRightPoint[1]){
return true;
}
return false;
},
contains: function( p ) {
var isIn = false;
if( this.isInHexBounds( p ) ){
var i, j = 0;
for (i = 0, j = this.points.length - 1; i < this.points.length; j = i++){
var iP = this.points[i];
var jP = this.points[j];
if (
( ((iP[1] <= p[1]) && (p[1] < jP[1])) || ((jP[1] <= p[1]) && (p[1] < iP[1])) ) && (p[0] < (jP[0] - iP[0]) * (p[1] - iP[1]) / (jP[1] - iP[1]) + iP[0])
){
isIn = !isIn;
}
}
}
return isIn;
},
getColor: function( ){
switch( this.type ){
case 0:
return "blue";
case 1:
return "red";
default:
return "yellow";
}
},
trigger: function( e_name ){
this.events[ e_name ].call(this);
},
events: {
"select" : function(){
if( ! this.checkState( "selected" ) ){
this.changeState("selected", true);
//console.log( this.coords )
this.type = 1;
}
}
},
setType: function( type ){
this.attributes.type = type;
},
generate: function(){///// generate hexagon points //////
var x1 = (this.size.w - this.size.s)/2;
var y1 = (this.size.h / 2);
this.points.push(
[ x1 + this.pos[0], this.pos[1] ],
[ x1 + this.size.s + this.pos[0], this.pos[1] ],
[ this.size.w + this.pos[0], y1 + this.pos[1] ],
[ x1 + this.size.s + this.pos[0], this.size.h + this.pos[1] ],
[ x1 + this.pos[0], this.size.h + this.pos[1] ],
[ this.pos[0], y1 + this.pos[1] ]
);
},
draw : function( ctx ){
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo( this.points[0][0], this.points[0][1] );
for( var c=1; c < this.points.length; c++ ){
ctx.lineTo( this.points[c][0], this.points[c][1] );
}
ctx.closePath();
ctx.stroke();
if( this.type > 0 ){
ctx.globalAlpha = 0.5;
ctx.fillStyle = this.color;
ctx.fill();
ctx.globalAlpha = 1.0;
}else{
ctx.strokeStyle = "grey";
}
this.draw_coords( ctx );
},
draw_coords: function( ctx ){
ctx.font="10px Georgia";
ctx.textAlign = "center";
ctx.textBaseline = 'middle';
ctx.fillStyle = "blue";
ctx.fillText(this.coords[0]+" , "+this.coords[1], this.MidPoint[0], this.MidPoint[1]);
}
}
///////// [ Grid ] /////////////////////
function Grid( options ){
if(typeof options !== "undefined"){
this.size = {
width: options.size[0],
height: options.size[1]
};
//this.mouse_pos = [];
this.pos = options.pos; //// position within the canvas /// [ x , y ] ////
this.ctx = options.ctx;
this.ctx_pos = options.ctx_pos; //// position of canvas element /// [ left, top] ///
this.hex_size = this.calculate_hex_size( options.hex_def );
this.hexagons = []; //// [ row, col ] ///// just a temporary array ////
this.grid2D = []; ///// includes all hexagons to be drawn ///
}
this.generate();
this.animate();
this.enable_mouse_events();
}
Grid.prototype = {
constructor : Grid,
generate : function(){
var hex_pos_x = 0.0, hex_pos_y = 0.0, row = 0, col = 0, offset = 0.0, h = null, h_id = 0;
while( (hex_pos_y + this.hex_size.h) <= this.size.height ){
col = 0; //// reset col
offset = 0.0; //// reset offset
if( (row % 2) == 1){
offset = ( ( this.hex_size.w - this.hex_size.s ) /2 ) + this.hex_size.s ;
col = 1;
}
hex_pos_x = offset;
while( (hex_pos_x + this.hex_size.w) <= this.size.width ){
h = new Hexagon( { pos : [ hex_pos_x, hex_pos_y ], size: this.hex_size , id: row+""+col, type: 0 });
h.coords[0] = col; //// set coord X ///
this.grid2D.push( h );
if( ! this.hexagons[col] ){
this.hexagons[col] = [];
}
this.hexagons[col].push( h );
col += 2;
hex_pos_x += (this.hex_size.w + this.hex_size.s);
}
row++;
hex_pos_y += (this.hex_size.h / 2);
}
////finally go through our list of hexagons by their x co-ordinate to assign the y co-ordinate
var coordX = 0, coordY = 0, h_l = this.hexagons.length, hex_arr = [];
for( ; coordX < h_l; coordX++ ){
hex_arr = this.hexagons[ coordX ];
coordY = Math.floor( (coordX / 2 ) + (coordX % 2) );
for( var h = 0, size = hex_arr.length; h < size; h++ ){
hex_arr[h].coords[1] = coordY++;
}
}
},
getHexAt: function( p ){ //// point [ x, y ]
for ( var h = 0, h_l = this.grid2D.length; h < h_l; h++ ){
if ( this.grid2D[h].contains( p ) ){
return this.grid2D[h];
}
}
return null;
},
animate: function(){
var self = this;
window.requestAnimationFrame( function(){
self.animate();
});
self.draw();
},
draw : function( ){
this.ctx.clearRect(0, 0, this.size.width, this.size.height);
for( var h = 0, h_l = this.grid2D.length; h < h_l; h++ ){
this.grid2D[h].draw( this.ctx );
}
},
calculate_hex_size : function( hex_def ){
return {
w: hex_def.radius * 2,
m: hex_def.margin,
h: (Math.sqrt(3) / 2) * ( hex_def.radius * 2),
r: hex_def.radius,
s: hex_def.radius
}
},
enable_mouse_events: function(){
var self = this;
var mouse_pos = [];
var cur_hex = null;
window.addEventListener( 'mousemove', function(e){
mouse_pos = [ ( e.clientX - self.ctx_pos[0] ), ( e.clientY - self.ctx_pos[1] )];
//self.mouse_pos = mouse_pos;
});
window.addEventListener( 'mousedown', function(e){
if( mouse_pos.length > 0 ){
cur_hex = self.getHexAt( mouse_pos );
if( cur_hex != null ){
cur_hex.trigger("select");
}
}
});
}
}
var c_el = document.getElementById("myCanvas");
var ctx = c_el.getContext("2d");
var nGrid = new Grid({
/// size : [ c_el.width, c_el.height ], /// [rows / cols ] //// 20 px x 10 px///
size : [ 70 , 70 ],
pos: [ 20, 20 ], /// [X, Y] ////
hex_def: {
radius: 20,
margin: 0
},
ctx : ctx,
ctx_pos: [ c_el.getBoundingClientRect().left, c_el.getBoundingClientRect().top ]
});
<body stye="width: 100%; height: 100%" >
<canvas id="myCanvas" width="750px" height="405px" style="margin:0; padding:0; border:1px solid #d3d3d3;"></canvas>
</body>

Related

waterfall: HTML Canvas Waterfall and Spectrum Plot

I was trying to Build a spectrum and waterfall Plot HTML canvas. After a long research and googling i found this SOURCE CODE
Now i am trying to learn how this code works. i added 3 points on the spectrum using drawPoint2 function.
Can some one please Guild me. thank you.
"use strict";
var colors = [
"rgba(60, 229, 42, 0.31)",
"rgba(60, 229, 42, 0.31)",
"rgba(60, 229, 42, 0.31)",
"rgba(60, 229, 42, 0.31)",
"rgba(60, 229, 42, 0.31)",
"rgba(252, 182, 3, 0.31)",
"rgba(3, 103, 252, 0.31)",
"rgba(219, 3, 252, 0.31)",
"rgba(252, 3, 49, 0.31)",
"rgba(221, 48, 232, 0.31)",
];
var closeEnough = 5;
var crop = 150;
var data_sb = -10;
var canvas = document.getElementById("spectrumSM");
Spectrum.prototype.countDecimals = function (value) {
if (Math.floor(value) !== value)
return value.toString().split(".")[1].length || 0;
return 0;
};
Spectrum.prototype.map = function (x, in_min, in_max, out_min, out_max) {
return Math.round(
((x - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
);
};
Spectrum.prototype.updateSpectrumRatio = function () {
this.spectrumHeight = Math.round(
(this.canvas.height * this.spectrumPercent) / 100.0
);
// create a slop
this.gradient = this.ctx.createLinearGradient(0, 0, 0, this.spectrumHeight);
for (var i = 0; i < this.colormap.length; i++) {
var c = this.colormap[this.colormap.length - 1 - i];
this.gradient.addColorStop(
i / this.colormap.length,
"rgba(220,220,220,0.2)"
); //hardcode the patch above Xaxis
}
};
Spectrum.prototype.resize = function () {
var width = this.canvas.clientWidth;
var height = this.canvas.clientHeight;
if (this.canvas.width != width || this.canvas.height != height) {
this.canvas.width = width;
this.canvas.height = height;
this.updateSpectrumRatio();
for (var z = 0; z < this.tags.length; z++) {
this.tags[z].StayX = this.map(
this.tags[z].xval,
this.cutfromArray,
this.orignalArrayLength - this.cutfromArray,
0 + crop,
width
); ///////
}
}
if (this.axes.width != width || this.axes.height != this.spectrumHeight) {
this.axes.width = width;
this.axes.height = this.spectrumHeight;
this.updateAxes();
}
};
Spectrum.prototype.updateAxes = function () {
var width = this.ctx_axes.canvas.width + 100; //width of x axis izz
var height = this.ctx_axes.canvas.height;
this.maxScaleX = this.centerHz + this.spanHz / 2;
this.minScaleX = this.centerHz - this.spanHz / 2;
// Clear and fill with black
this.ctx_axes.fillStyle = "black";
this.ctx_axes.fillRect(0, 0, width, height);
// Draw axes
this.ctx_axes.font = "12px Arial";
this.ctx_axes.fillStyle = "white";
this.ctx_axes.textBaseline = "middle";
this.ctx_axes.textAlign = "center"; //change izz
var step = 10; //steps for y-axis
for (var i = this.max_db - 10; i >= this.min_db + 10; i -= step) {
var y = height - this.squeeze(i, 0, height);
this.ctx_axes.fillText(i, 20, y); // height - y
this.ctx_axes.beginPath();
this.ctx_axes.moveTo(22, y); //y axis stroked set izz
this.ctx_axes.lineTo(width, y);
this.ctx_axes.strokeStyle = "rgba(255, 255, 255, 0.15)"; //changed strokes izz
this.ctx_axes.stroke();
}
this.ctx_axes.textBaseline = "bottom";
// change X-axis
var x_axisSteps = 10;
for (var i = 0; i < x_axisSteps; i++) {
var x = Math.round((width - crop) / x_axisSteps - 1) * i + crop;
if (this.spanHz > 0) {
var adjust = 0;
if (i == 0) {
this.ctx_axes.textAlign = "left";
adjust = 3;
} else if (i == 10) {
this.ctx_axes.textAlign = "right";
adjust = -3;
} else {
this.ctx_axes.textAlign = "center";
}
//Graph points in whole points
var freq = this.centerHz + (this.spanHz / 10) * (i - 5);
if (freq > 1e9) {
freq = freq / 1e9;
if (this.countDecimals(freq) > 4) {
freq = freq.toFixed(0);
}
freq = freq + " GHz";
} else if (freq > 1e6) {
freq = freq / 1e6;
if (this.countDecimals(freq) > 4) {
freq = freq.toFixed(0);
}
freq = freq + " MHz"; //this function executed
} else {
if (this.countDecimals(freq) > 2) {
freq = freq.toFixed(0);
}
freq = freq + " MHz";
}
this.ctx_axes.fillText(freq, x - 130, height); // x axia height change izz plus values placment
}
//console.log("ctx_axes : ", this.ctx_axes);
this.ctx_axes.beginPath();
this.ctx_axes.moveTo(x, 0);
this.ctx_axes.lineTo(x, height);
this.ctx_axes.strokeStyle = "rgba(200, 200, 200, 0.2)"; //straight gridline on x axis izza
this.ctx_axes.stroke();
}
// const input = prompt("What's your name?");
};
Spectrum.prototype.toggleColor = function () {
this.colorindex++;
if (this.colorindex >= colormaps.length) this.colorindex = 0;
this.colormap = colormaps[this.colorindex];
this.updateSpectrumRatio();
};
Spectrum.prototype.setCenterHz = function (hz) {
this.centerHz = hz;
if (this.center != 0 && this.center != this.centerHz) {
this.centerHz = this.center;
}
this.updateAxes();
};
Spectrum.prototype.setSpanHz = function (hz) {
this.orignalSpanHz = hz;
this.spanHz = hz;
if (this.zoom != 0 && this.spanHz != this.zoom) {
this.spanHz = this.zoom;
}
this.updateAxes();
};
Spectrum.prototype.squeeze = function (value, out_min, out_max) {
if (value <= this.min_db) {
return out_min;
} else if (value >= this.max_db) {
return out_max;
} else {
return Math.round(
((value - this.min_db) / (this.max_db - this.min_db)) * out_max
);
}
};
Spectrum.prototype.squeeze2 = function (value, out_min, out_max) {
if (value <= 30000000) {
return out_min;
} else if (value >= 300000000) {
return out_max;
} else {
return Math.round(
((value - 30000000) / (300000000 - 30000000)) * out_max
);
}
};
Spectrum.prototype.drawRectSpectrogram = function (y, h) {
this.ctx.beginPath();
this.ctx.fillStyle = colors[this.rectColor]; //"rgba(60, 229, 42, 0.31)"; //rect color
this.ctx.strokeStyle = "green";
this.ctx.rect(this.clickRectX, y, this.clickRectWidth, h);
this.ctx.stroke();
this.ctx.fill();
this.ctx.closePath();
};
Spectrum.prototype.threshold = function (y, width, color) {
this.ctx.beginPath();
this.ctx.strokeStyle = color;
this.ctx.lineWidth = 2; //threshold line width
this.ctx.beginPath();
this.ctx.moveTo(0, y);
this.ctx.lineTo(width + crop, y);
this.ctx.stroke();
this.ctx.closePath();
this.ctx.lineWidth = 1;
};
function drawPoint2(can, x, y, label) {
can.beginPath();
can.arc(x, y, 5, 0, 2 * Math.PI);
can.fillStyle = "red";
can.fill();
can.fillText(label, x + 10, y);
can.stroke(); // it creates X axies and
}
Spectrum.prototype.drawSpectrum = function (bins) {
//get canvas height and width to draw spectrum ayaz
var width = this.ctx.canvas.width;
var height = this.ctx.canvas.height;
//console.log("spectrum width and height : "+width+" "+ height);
// width of Green Color ayaz
this.ctx.lineWidth = 1; //Amplitude green line width .
//All of the points are 119 nut few points are 118 and few 120 ayaz
if (!this.binsAverage || this.binsAverage.length != bins.length) {
//console.log('this.binsAverage ', this.binsAverage );
this.binsAverage = bins;
} else {
for (var i = 0; i < bins.length; i++) {
this.binsAverage[i] +=
(1 - this.averaging) * (bins[i] - this.binsAverage[i]);
}
}
bins = this.binsAverage;
//Do not draw anything if spectrum is not visible
if (this.ctx_axes.canvas.height < 1) return;
//////////////////////////////////////////////////////////////Creation of X and Y axis completed
// Copy axes from offscreen canvas
this.ctx.drawImage(this.ctx_axes.canvas, -1, -1); // create Spectrums X and Y axis
// // Scale for FFT
this.ctx.save(); // saves sppectrum previous stage
//////////////////////////////////////////////////////////////Creation of X and Y axis completed
this.ctx.scale(width / this.wf_size, 1); //green line visiblity izza
var peakY = this.spectrumHeight;
// Draw FFT bins
this.ctx.beginPath();
this.offset = Math.round(
this.map(crop, 0, this.ctx.canvas.width + 6000, 0, this.wf_size)
); // green line axis set izza
//console.log('this.offset : ', this.offset);
console.log(
"X and Y are : ",
-1 + this.offset + " " + this.spectrumHeight + 0
);
this.ctx.moveTo(-1 + this.offset, this.spectrumHeight + 0); //change for waterfall izza
// above line is the address of left bottom corner of spectrum
// console.log(
// "clkkkkkkkkkkkkkkk : ",
// this.clickRectX+
// crop+
// width+
// this.minScaleX+
// this.maxScaleX
// );
var rangeMin = this.map(
this.clickRectX,
crop,
width,
this.minScaleX,
this.maxScaleX
);
var rangeMax = this.map(
this.clickRectWidth + this.clickRectX,
crop,
width,
this.minScaleX,
this.maxScaleX
);
if (rangeMin > rangeMax) {
var temp;
temp = rangeMax;
rangeMax = rangeMin;
rangeMin = temp;
}
var max = 100000000;
this.scaleY = this.map(this.threshY, this.min_db, this.max_db, height / 2, 0); // 0, height/2 // height of threshold izza
this.detectedFrequency.length = 0;
// console.log('bins are ', bins);
//console.log('new array ');
for (var i = 0; i < bins.length; i++) {
// if ( parseFloat( bins[i]) > -100 ) {
// console.log("bins : ", bins[i]);
// const input = prompt("What's your name?");
// alert(`Your name is ${input}`);
// }
var y =
this.spectrumHeight - 1 - this.squeeze(bins[i], 0, this.spectrumHeight);
peakY = this.squeeze(bins[i], 0, this.spectrumHeight);
if (y > this.spectrumHeight - 1) {
y = this.spectrumHeight - 1;
console.log("Y has chnaged : ", y);
}
if (y < 0) {
y = 0;
console.log("y=0");
}
if (i == 0) {
this.ctx.lineTo(-1 + this.offset, y);//responsible for height of green line
}
// green lines are created here ayaz
// create green lines
// important
//drawPoint2(this.ctx ,i + this.offset, y);
this.ctx.lineTo(i + this.offset, y); // that what point we start drawing green horizental green line
// console.log(
// "Starting Line 1 i " +
// i +
// " X : " +
// parseInt(i + this.offset) +
// " y : " +
// y
// );
if (i == bins.length - 1) {
//drawPoint2(this.ctx ,i + this.offset, y);
this.ctx.lineTo(this.wf_size + 1 + this.offset, y);
// console.log(
// "Second 2 i == bins.length - 1 i " +
// i +
// " Drawingg -1 + this.offset : " +
// parseInt(i + this.offset) +
// " y : " +
// y
// );
}
for (var z = 0; z < this.tags.length; z++) {
if (
i + this.cutfromArray == this.tags[z].xval &&
this.check_click == true
) {
this.tags[z].tagY = y;
this.tags[z].yval = Math.round(bins[i]);
this.tags[z].displayX = Math.round(
this.map(i, 0, bins.length, this.minScaleX, this.maxScaleX)
);
}
}
if (y < max) {
max = y;
}
let newVal = Math.round(
this.map(i, 0, bins.length, this.minScaleX, this.maxScaleX)
);
if (this.check_bar) {
if (newVal < rangeMax && newVal > rangeMin) {
if (y < this.scaleY) {
var obj = new Object();
obj.x = newVal;
obj.y = Math.round(bins[i]);
obj.count = 1;
var check = true;
for (var j = 0; j < this.threshRange.length; j++) {
if (
this.threshRange[j].x == obj.x &&
this.threshRange[j].y == obj.y
) {
this.threshRange[j].count++;
check = false;
}
}
if (check) {
let tableRows = document
.getElementById("thresh-table-body")
.getElementsByTagName("tr").length;
if (tableRows < 100) {
this.threshRange.push(obj);
// filling table
let tbody = document.getElementById("thresh-table-body");
let tr = document.createElement("tr");
let td1 = document.createElement("td");
let td2 = document.createElement("td");
let td3 = document.createElement("td");
td1.innerHTML = obj.x; //+" Hz"
td2.innerHTML = obj.y;
td3.innerHTML = obj.count;
tr.appendChild(td1);
tr.appendChild(td2);
tr.appendChild(td3);
tbody.appendChild(tr);
}
} else {
// update table count
for (let c = 0; c < this.threshRange.length; c++) {
let tableRows =
document.getElementById("thresh-table-body").rows[c].cells;
if (
tableRows[0].innerHTML == obj.x &&
tableRows[1].innerHTML == obj.y
) {
let countValue = Number(tableRows[2].innerHTML);
countValue++;
tableRows[2].innerHTML = countValue;
}
}
}
}
}
} else {
if (y < this.scaleY) {
var obj = new Object();
obj.x = newVal;
obj.y = Math.round(bins[i]);
obj.count = 1;
var check = true;
for (var j = 0; j < this.threshRange.length; j++) {
if (
this.threshRange[j].x == obj.x &&
this.threshRange[j].y == obj.y
) {
this.threshRange[j].count++;
check = false;
}
}
}
}
}
// this.ctx.beginPath();
// this.ctx.arc(100, 75, 20, 0, 1 * Math.PI);
// this.ctx.stroke();
//this.ctx.strokeRect(1800,10, 40, 190);
function containsObject(obj, list) {
var i;
for (i = 0; i < list.length; i++) {
if (list[i] === obj) {
return true;
}
}
return false;
}
this.ctx.fillStyle = "rgba(0, 0, 0, 0.11)";
// drawPoint2(this.ctx ,
// this.wf_size + 1 + this.offset,
// this.spectrumHeight + 1
// );
this.ctx.lineTo(this.wf_size + 1 + this.offset, this.spectrumHeight + 1);
// console.log(
// "thired 3 this.wf_size + 1 + this.offset : " +
// parseInt(this.wf_size + 1 + this.offset) +
// " this.spectrumHeight + 1 : " +
// parseInt(this.spectrumHeight + 1)
// );
// drawPoint2(this.ctx ,
// this.wf_size + this.offset,
// this.spectrumHeight
// );
this.ctx.lineTo(this.wf_size + this.offset, this.spectrumHeight );
// console.log(
// "Forth this.wf_size + this.offset : " +
// parseInt(this.wf_size + this.offset) +
// " y : " +
// y
// );
if (y < 230 && y > 245) {
console.log("foundddddddddddddd");
}
this.ctx.closePath();
this.ctx.restore();
this.ctx.strokeStyle = "#259a00"; //color of spectrum green
this.ctx.stroke(); // it creates X axies and
/////////////////////////////////////////////////////////////////////////green ended
if (this.spectrumColorCheck) {
this.ctx.fillStyle = this.gradient; //chnage color of under line chart
} else {
this.ctx.fillStyle = "rgba(0, 0, 0, 0.0)";
}
this.ctx.fill();
if (this.check_bar) {
this.drawRectSpectrogram(0, height);
}
var colorTh = "#cc8315"; //By uncomment Change the threshold line color change
this.threshold(this.scaleY, width, colorTh); // yellow light
if (this.check_click == true) {
for (let c = 0; c < this.tags.length; c++) {
this.drawTag(
this.tags[c].StayX,
this.tags[c].tagY,
this.tags[c].displayX,
this.tags[c].yval
);
}
if (this.removeTagCheck == true) {
closeEnough = 30;
for (var z = 0; z < this.tags.length; z++) {
if (this.checkCloseEnough(this.StayX, this.tags[z].StayX + 15)) {
this.tags.splice(z, 1);
z--;
}
}
}
}
closeEnough = 5;
// span hz commented
if (this.updateValueCheck) {
if (this.countDecimals(this.threshY) > 2) {
this.threshY = this.threshY.toFixed(2);
}
this.updateValueCheck = false;
}
var arrt = [ -100000000 , 40000000,35000000];
this.spectrumWidth = Math.round(
(this.canvas.width * this.spectrumPercent) / 100.0
);
for (var k = 0; k < 4; k++) {
var alpha =
this.spectrumHeight - 620 - this.squeeze2(arrt[k], 0, this.spectrumWidth);
drawPoint2(this.ctx, alpha + 600, 150, "A");
}
// draw separate lines
// for (var k=0;k< 4; k++){
// drawPoint2(this.ctx, "200000000", "-80");
// drawPoint2(this.ctx,"100000000", "-80");
// drawPoint2(this.ctx,"35000000", "-80");
// }
//console.log('this.ctx : ',this.ctx);
};
Spectrum.prototype.addData = function (data, bmp) {
if (!this.paused) {
if (data.length > 32768) {
data = this.sequenceResize(data, 32767);
}
this.orignalArrayLength = data.length;
if (this.orignalSpanHz > this.spanHz) {
data = data.slice(
this.cutfromArray,
this.orignalArrayLength - this.cutfromArray
);
}
if (data.length != this.wf_size) {
this.wf_size = data.length;
if (data.length > 32767) {
this.ctx_wf.canvas.width = 32767;
} else {
this.ctx_wf.canvas.width = data.length;
}
this.ctx_wf.fillStyle = "black";
this.ctx_wf.fillRect(0, 0, this.wf.width, 0); //strokes of waterfall
for (var z = 0; z < this.tags.length; z++) {
this.tags[z].StayX = this.map(
this.tags[z].xval,
this.cutfromArray,
this.orignalArrayLength - this.cutfromArray,
crop,
this.ctx.canvas.width
);
}
}
this.drawSpectrum(data);
// this.addWaterfallRowBmp(bmp);
// this.addWaterfallRow(data);
this.resize();
}
};
function Spectrum(id, options) {
// Handle options
this.centerHz = options && options.centerHz ? options.centerHz : 0;
this.spanHz = options && options.spanHz ? options.spanHz : 0;
this.wf_size = options && options.wf_size ? options.wf_size : 0;
this.wf_rows = options && options.wf_rows ? options.wf_rows : 50;
this.spectrumPercent =
options && options.spectrumPercent ? options.spectrumPercent : 25;
this.spectrumPercentStep =
options && options.spectrumPercentStep ? options.spectrumPercentStep : 5;
this.averaging = options && options.averaging ? options.averaging : 0.5;
this.rectColor = options && options.rectColor ? options.rectColor : 0;
// Setup state
this.paused = false;
this.fullscreen = true;
this.min_db = -140;
this.max_db = -20;
this.spectrumHeight = 0;
// Colors
this.colorindex = 2;
this.colormap = colormaps[0];
// Create main canvas and adjust dimensions to match actual
this.canvas = document.getElementById("spectrumSM");
canvas.height = this.canvas.clientHeight;
this.canvas.width = this.canvas.clientWidth;
this.ctx = this.canvas.getContext("2d");
this.checkclick = false;
this.clickRectWidth = 1000;
this.dragL = true;
this.dragR = true;
// this.ctx.globalAlpha = 0.1;
this.drag = true;
this.click_x = 0;
this.click_y = 0;
this.check_click = true;
this.threshY = -70;
this.StayX = -10;
this.scaleY = 0;
//for change waterfall design
this.threshCheck = true;
this.removeTagCheck = true;
this.spectrumColorCheck = true;
this.check_bar = true;
this.updateValueCheck = true;
this.tags = [];
this.addTagsCheck = true;
this.maxScaleX = 0;
this.minScaleX = 0;
this.orignalArrayLength = 0;
this.zoom = this.spanHz;
this.center = this.centerHz;
this.threshRange = [];
this.detectedFrequency = [];
this.offset = 10;
this.arraySizeto = 0;
this.orignalSpanHz = 0;
this.cutfromArray = 0;
this.ctx.fillStyle = "black";
this.ctx.fillRect(10, 10, this.canvas.width, this.canvas.height);
// Create offscreen canvas for axes
this.axes = document.createElement("canvas");
this.axes.id = "myCheck";
this.axes.height = 1; // Updated later
this.axes.width = this.canvas.width;
this.ctx_axes = this.axes.getContext("2d");
function myFunction() {
this.style.fontSize = "40px";
}
// Create offscreen canvas for waterfall
this.wf = document.createElement("canvas");
this.wf.height = this.wf_rows;
this.wf.width = this.wf_size;
this.ctx_wf = this.wf.getContext("2d");
// Trigger first render
this.updateSpectrumRatio();
this.resize();
}
Here showing value of spectrum at specific point via click and zoom in/ zoom out functionality.
var closeEnough = 5;
var crop = 150;
var data_sb = -10;
var canvas = document.getElementById("spectrumSM");
Spectrum.prototype.mousedown = function (evt) {
this.checkclick = true;
if (this.checkCloseEnough(this.click_x-3, this.clickRectX)) {
this.dragR = false;
this.dragL = true;
} else if (
this.checkCloseEnough(this.click_x-3, this.clickRectX + this.clickRectWidth)
) {
this.dragR = true;
this.dragL = false;
} else if (this.checkCloseEnough(this.click_y-3, this.scaleY)) {
this.drag = true;
}
};
Spectrum.prototype.mouseup = function (evt) {
this.checkclick = false;
this.dragL = false;
this.dragR = false;
this.drag = false;
if (evt.button === "right") {
this.tags = [];
}
};
Spectrum.prototype.mousemove = function (evt) {
var rect = this.canvas.getBoundingClientRect();
this.click_x = evt.clientX - rect.left;
this.click_y = evt.clientY - rect.top;
closeEnough = Math.abs(this.clickRectWidth);
if (this.dragL == false && this.dragR == false && this.drag == false) {
if (
this.checkclick == true &&
this.checkCloseEnough(
this.click_x,
this.clickRectX + this.clickRectWidth / 2
)
) {
this.clickRectX = this.click_x - this.clickRectWidth / 2;
}
} else if (this.dragL) {
this.clickRectWidth += this.clickRectX - this.click_x;
this.clickRectX = this.click_x;
} else if (this.dragR) {
this.clickRectWidth = -(this.clickRectX - this.click_x);
} else if (this.drag && this.threshCheck) {
this.updateValueCheck = true;
this.threshY = this.map(
this.click_y,
this.canvas.height / 2,
0,
this.min_db,
this.max_db
); // this.max_db, this.min_db
}
closeEnough = 10;
};
Spectrum.prototype.click = function (evt) {
// change izza
this.check_click = true;
this.StayX = this.click_x;
console.log('tag list : ',this.addTagsCheck);
if (this.addTagsCheck == true && this.StayX > 3) {
var tag = {
StayX: this.click_x,
tagY: 0,
yval: 0,
xval: Math.round(
this.map(
this.click_x,
28,
this.ctx.canvas.width,
this.cutfromArray,
this.orignalArrayLength - this.cutfromArray
)
),
displayX: 0,
};
this.tags.push(tag);
}
};
Spectrum.prototype.wheel = function (evt) {
this.zoom = this.spanHz;
var inc;
if (this.arraySizeto == 0) {
inc = Math.round(this.orignalArrayLength * 0.05);
} else {
inc = Math.round(this.arraySizeto * 0.05);
}
let zoomInc = 0;
if (this.orignalSpanHz > this.orignalArrayLength) {
zoomInc = this.orignalSpanHz / this.orignalArrayLength;
} else {
zoomInc = this.orignalArrayLength / this.orignalSpanHz;
}
if (evt.deltaY > 0) {
if (
this.orignalSpanHz - (zoomInc * this.cutfromArray - inc * 2) <
this.orignalSpanHz &&
this.cutfromArray - inc >= 0
) {
this.cutfromArray = this.cutfromArray - inc;
this.zoom = this.orignalSpanHz - zoomInc * this.cutfromArray * 2;
} else if (
this.orignalSpanHz + zoomInc * this.cutfromArray * 2 >=
this.orignalSpanHz &&
this.cutfromArray - inc <= 0
) {
this.zoom = this.orignalSpanHz;
this.cutfromArray = 0;
}
} else if (evt.deltaY < 0) {
if (
this.orignalSpanHz - (zoomInc * this.cutfromArray + inc * 2) > inc &&
this.orignalArrayLength - this.cutfromArray * 2 - inc * 2 >
this.ctx.canvas.width
) {
this.cutfromArray = this.cutfromArray + inc;
this.zoom = this.orignalSpanHz - zoomInc * this.cutfromArray * 2;
}
}
this.arraySizeto = this.orignalArrayLength - this.cutfromArray * 2;
this.maxScaleX = this.centerHz + this.zoom / 20;
this.minScaleX = this.centerHz - this.zoom / 20;
};
Spectrum.prototype.undoTag = function () {
this.tags.pop();
};
Spectrum.prototype.checkCloseEnough = function (p1, p2) {
return Math.abs(p1 - p2) < closeEnough;
};
Spectrum.prototype.drawTag = function (locx, locy, xval, yval) {
this.ctx.beginPath();
this.ctx.strokeStyle = "#cc8315";
let freq = xval;
if (freq > 1e9) {
freq = freq / 1e9;
if (this.countDecimals(freq) > 2) {
freq = freq.toFixed(2);
}
freq = freq + " GHz";
} else if (freq > 1e6) {
freq = freq / 1e6;
if (this.countDecimals(freq) > 2) {
freq = freq.toFixed(2);
}
freq = freq + " MHz";
} else {
if (this.countDecimals(freq) > 2) {
freq = freq.toFixed(2);
}
}
var text = " ( " + freq + ", " + yval + ")";
var padding = 5;
var fontSize = 20;
var xPos = locx - padding;
var width = this.ctx.measureText(text).width + padding * 2;
var height = fontSize * 1.286;
var yPos = locy - height / 1.5;
this.ctx.lineWidth = 2;
this.ctx.fillStyle = "rgba(204, 131, 21, 0.8)";
// draw the rect
this.ctx.fillRect(xPos, yPos, width, height);
this.ctx.fillStyle = "white";
this.ctx.font = "bold 10pt droid_serif";
this.ctx.fillText(text, locx, locy);
this.ctx.fillStyle = "white";
this.ctx.beginPath();
this.ctx.arc(locx, locy, 2, 0, Math.PI * 2, true);
this.ctx.fill();
this.ctx.closePath();
};
The spectrum project you reference expects to receive regular updates of an array of data passed into drawSpectrum. Each time it renders that data as a new spectrum in drawFFT, with the data scaled by the numbers set in setRange. Each time it receives data, it also creates a new row of pixels for the waterfall in the rowToImageData function, again scaled by the numbers set in setRange. The previously created rows of pixels are shifted down by one row in addWaterfallRow.
I've made a fiddle that shows how data is handled by the Spectrum object: https://jsfiddle.net/40qun892/
If you run the fiddle it shows two examples, one with three points and another with 100 points of randomly generated data. This line shows how an x-axis is added to the graph:
const spectrumB = new Spectrum("spectrumCanvasB", {spanHz: 5000, centerHz: 2500});
The x-axis is only shown when spanHz is defined. It is generated by drawing 11 labels, equally distributed across the canvas. With the center label based on centerHz, and the remaining labels calculated based on spanHz.
As you can see from the spectrums generated by the fiddle, the x-axis labels are not connected to the data, they are just equally distributed across the canvas.
The graph behind the data is created by applying a scale to the graph so that using the array index will result in data stretched across the graph.
this.ctx.scale(width / <number of data points>, 1);
for (var i = 0; i < <number of data points>.length; i++) {
// lines removed
this.ctx.lineTo(i, y);
// lines removed
}
As you can see from the examples in the fiddle, this doesn't look very nice when there's only three datapoints, because the white lines are stretched in the x-direction but not the y-direction.
Spectrum doesn't care about x-coordinates. It just stretches whatever it is given to fit the canvas. If you give it a spanHz property, it will distribute some labels across the canvas too, but it does not associate them with the data.
The scaling seems to be slightly wrong in Spectrum (which is only noticeable if very few datapoints are used). If I make this change, the points are correctly stretched:
this.ctx.scale(width / (this.wf_size - 1), 1);
Then this change would set the x-axis labels to 100Mhz - 300Mhz:
const spectrumA = new Spectrum("spectrumCanvasA", {spanHz: 200000000, centerHz: 200000000});
Edit: (The relationship between frequency and data)
The only thing the code knows about the frequency is based on the spanHz anad centerHz.
The frequency at an array index is
(<array index> / <number of points on X axis> * <spanHz>) + (<centerHz> / 2)
The frequency at a pixel is
(<x coordinate> / <width of canvas> * <spanHz>) + (<centerHz> / 2)
If you want to convert a frequency to an array index (or a pixel) it would be slightly more complicated, because you would need to find the closest element to that frequency. For example, the pixel at a frequency is:
round((<frequency> - (<centerHz> / 2)) / <spanHz> * <width of canvas>)

How to get rid of gray overlay on Three.js transition?

I have a transition between two pngs using three.js. The images have a white background, but they appear to have a gray tint. I know it is probably something obvious but I can't find where in the code this is specified. How do I get rid of this? Here is a demo on Codepen:
https://codepen.io/mastroneel/pen/Oazzyo
window.onload = init;
console.ward = function() {}; // what warnings?
function init() {
var root = new THREERoot({
createCameraControls: !true,
antialias: (window.devicePixelRatio === 1),
fov: 80
});
root.renderer.setClearColor(0x000000, 0);
root.renderer.setPixelRatio(window.devicePixelRatio || 1);
root.camera.position.set(0, 0, 60);
var width = 100;
var height = 100;
var slide = new Slide(width, height, 'out');
var l1 = new THREE.ImageLoader();
l1.setCrossOrigin('Anonymous');
slide.setImage(l1.load('https://image.ibb.co/f6mVsA/helmet.png'));
root.scene.add(slide);
var slide2 = new Slide(width, height, 'in');
var l2 = new THREE.ImageLoader();
l2.setCrossOrigin('Anonymous');
slide2.setImage(l2.load('https://image.ibb.co/mb1KkV/player.png'));
root.scene.add(slide2);
var tl = new TimelineMax({
repeat: -1,
repeatDelay: 1.0,
yoyo: true
});
tl.add(slide.transition(), 0);
tl.add(slide2.transition(), 0);
createTweenScrubber(tl);
window.addEventListener('keyup', function(e) {
if (e.keyCode === 80) {
tl.paused(!tl.paused());
}
});
}
////////////////////
// CLASSES
////////////////////
function Slide(width, height, animationPhase) {
var plane = new THREE.PlaneGeometry(width, height, width * 2, height * 2);
THREE.BAS.Utils.separateFaces(plane);
var geometry = new SlideGeometry(plane);
geometry.bufferUVs();
var aAnimation = geometry.createAttribute('aAnimation', 2);
var aStartPosition = geometry.createAttribute('aStartPosition', 3);
var aControl0 = geometry.createAttribute('aControl0', 3);
var aControl1 = geometry.createAttribute('aControl1', 3);
var aEndPosition = geometry.createAttribute('aEndPosition', 3);
var i, i2, i3, i4, v;
var minDuration = 0.8;
var maxDuration = 1.2;
var maxDelayX = 0.9;
var maxDelayY = 0.125;
var stretch = 0.11;
this.totalDuration = maxDuration + maxDelayX + maxDelayY + stretch;
var startPosition = new THREE.Vector3();
var control0 = new THREE.Vector3();
var control1 = new THREE.Vector3();
var endPosition = new THREE.Vector3();
var tempPoint = new THREE.Vector3();
function getControlPoint0(centroid) {
var signY = Math.sign(centroid.y);
tempPoint.x = THREE.Math.randFloat(0.1, 0.3) * 50;
tempPoint.y = signY * THREE.Math.randFloat(0.1, 0.3) * 70;
tempPoint.z = THREE.Math.randFloatSpread(20);
return tempPoint;
}
function getControlPoint1(centroid) {
var signY = Math.sign(centroid.y);
tempPoint.x = THREE.Math.randFloat(0.3, 0.6) * 50;
tempPoint.y = -signY * THREE.Math.randFloat(0.3, 0.6) * 70;
tempPoint.z = THREE.Math.randFloatSpread(20);
return tempPoint;
}
for (i = 0, i2 = 0, i3 = 0, i4 = 0; i < geometry.faceCount; i++, i2 += 6, i3 += 9, i4 += 12) {
var face = plane.faces[i];
var centroid = THREE.BAS.Utils.computeCentroid(plane, face);
// animation
var duration = THREE.Math.randFloat(minDuration, maxDuration);
var delayX = THREE.Math.mapLinear(centroid.x, -width * 0.5, width * 0.5, 0.0, maxDelayX);
var delayY;
if (animationPhase === 'in') {
delayY = THREE.Math.mapLinear(Math.abs(centroid.y), 0, height * 0.5, 0.0, maxDelayY)
} else {
delayY = THREE.Math.mapLinear(Math.abs(centroid.y), 0, height * 0.5, maxDelayY, 0.0)
}
for (v = 0; v < 6; v += 2) {
aAnimation.array[i2 + v] = delayX + delayY + (Math.random() * stretch * duration);
aAnimation.array[i2 + v + 1] = duration;
}
// positions
endPosition.copy(centroid);
startPosition.copy(centroid);
if (animationPhase === 'in') {
control0.copy(centroid).sub(getControlPoint0(centroid));
control1.copy(centroid).sub(getControlPoint1(centroid));
} else { // out
control0.copy(centroid).add(getControlPoint0(centroid));
control1.copy(centroid).add(getControlPoint1(centroid));
}
for (v = 0; v < 9; v += 3) {
aStartPosition.array[i3 + v] = startPosition.x;
aStartPosition.array[i3 + v + 1] = startPosition.y;
aStartPosition.array[i3 + v + 2] = startPosition.z;
aControl0.array[i3 + v] = control0.x;
aControl0.array[i3 + v + 1] = control0.y;
aControl0.array[i3 + v + 2] = control0.z;
aControl1.array[i3 + v] = control1.x;
aControl1.array[i3 + v + 1] = control1.y;
aControl1.array[i3 + v + 2] = control1.z;
aEndPosition.array[i3 + v] = endPosition.x;
aEndPosition.array[i3 + v + 1] = endPosition.y;
aEndPosition.array[i3 + v + 2] = endPosition.z;
}
}
var material = new THREE.BAS.BasicAnimationMaterial({
shading: THREE.FlatShading,
side: THREE.DoubleSide,
uniforms: {
uTime: {
type: 'f',
value: 0
}
},
shaderFunctions: [
THREE.BAS.ShaderChunk['cubic_bezier'],
//THREE.BAS.ShaderChunk[(animationPhase === 'in' ? 'ease_out_cubic' : 'ease_in_cubic')],
THREE.BAS.ShaderChunk['ease_in_out_cubic'],
THREE.BAS.ShaderChunk['quaternion_rotation']
],
shaderParameters: [
'uniform float uTime;',
'attribute vec2 aAnimation;',
'attribute vec3 aStartPosition;',
'attribute vec3 aControl0;',
'attribute vec3 aControl1;',
'attribute vec3 aEndPosition;',
],
shaderVertexInit: [
'float tDelay = aAnimation.x;',
'float tDuration = aAnimation.y;',
'float tTime = clamp(uTime - tDelay, 0.0, tDuration);',
'float tProgress = ease(tTime, 0.0, 1.0, tDuration);'
//'float tProgress = tTime / tDuration;'
],
shaderTransformPosition: [
(animationPhase === 'in' ? 'transformed *= tProgress;' : 'transformed *= 1.0 - tProgress;'),
'transformed += cubicBezier(aStartPosition, aControl0, aControl1, aEndPosition, tProgress);'
]
}, {
map: new THREE.Texture(),
});
THREE.Mesh.call(this, geometry, material);
this.frustumCulled = false;
}
Slide.prototype = Object.create(THREE.Mesh.prototype);
Slide.prototype.constructor = Slide;
Object.defineProperty(Slide.prototype, 'time', {
get: function() {
return this.material.uniforms['uTime'].value;
},
set: function(v) {
this.material.uniforms['uTime'].value = v;
}
});
Slide.prototype.setImage = function(image) {
this.material.uniforms.map.value.image = image;
this.material.uniforms.map.value.needsUpdate = true;
};
Slide.prototype.transition = function() {
return TweenMax.fromTo(this, 3.0, {
time: 0.0
}, {
time: this.totalDuration,
ease: Power0.easeInOut
});
};
function SlideGeometry(model) {
THREE.BAS.ModelBufferGeometry.call(this, model);
}
SlideGeometry.prototype = Object.create(THREE.BAS.ModelBufferGeometry.prototype);
SlideGeometry.prototype.constructor = SlideGeometry;
SlideGeometry.prototype.bufferPositions = function() {
var positionBuffer = this.createAttribute('position', 3).array;
for (var i = 0; i < this.faceCount; i++) {
var face = this.modelGeometry.faces[i];
var centroid = THREE.BAS.Utils.computeCentroid(this.modelGeometry, face);
var a = this.modelGeometry.vertices[face.a];
var b = this.modelGeometry.vertices[face.b];
var c = this.modelGeometry.vertices[face.c];
positionBuffer[face.a * 3] = a.x - centroid.x;
positionBuffer[face.a * 3 + 1] = a.y - centroid.y;
positionBuffer[face.a * 3 + 2] = a.z - centroid.z;
positionBuffer[face.b * 3] = b.x - centroid.x;
positionBuffer[face.b * 3 + 1] = b.y - centroid.y;
positionBuffer[face.b * 3 + 2] = b.z - centroid.z;
positionBuffer[face.c * 3] = c.x - centroid.x;
positionBuffer[face.c * 3 + 1] = c.y - centroid.y;
positionBuffer[face.c * 3 + 2] = c.z - centroid.z;
}
};
function THREERoot(params) {
params = utils.extend({
fov: 60,
zNear: 10,
zFar: 100000,
createCameraControls: true
}, params);
this.renderer = new THREE.WebGLRenderer({
antialias: params.antialias,
alpha: true
});
this.renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1));
document.getElementById('three-container').appendChild(this.renderer.domElement);
this.camera = new THREE.PerspectiveCamera(
params.fov,
window.innerWidth / window.innerHeight,
params.zNear,
params.zfar
);
this.scene = new THREE.Scene();
if (params.createCameraControls) {
this.controls = new THREE.OrbitControls(this.camera, this.renderer.domElement);
}
this.resize = this.resize.bind(this);
this.tick = this.tick.bind(this);
this.resize();
this.tick();
window.addEventListener('resize', this.resize, false);
}
THREERoot.prototype = {
tick: function() {
this.update();
this.render();
requestAnimationFrame(this.tick);
},
update: function() {
this.controls && this.controls.update();
},
render: function() {
this.renderer.render(this.scene, this.camera);
},
resize: function() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
};
////////////////////
// UTILS
////////////////////
var utils = {
extend: function(dst, src) {
for (var key in src) {
dst[key] = src[key];
}
return dst;
},
randSign: function() {
return Math.random() > 0.5 ? 1 : -1;
},
ease: function(ease, t, b, c, d) {
return b + ease.getRatio(t / d) * c;
},
fibSpherePoint: (function() {
var vec = {
x: 0,
y: 0,
z: 0
};
var G = Math.PI * (3 - Math.sqrt(5));
return function(i, n, radius) {
var step = 2.0 / n;
var r, phi;
vec.y = i * step - 1 + (step * 0.5);
r = Math.sqrt(1 - vec.y * vec.y);
phi = i * G;
vec.x = Math.cos(phi) * r;
vec.z = Math.sin(phi) * r;
radius = radius || 1;
vec.x *= radius;
vec.y *= radius;
vec.z *= radius;
return vec;
}
})(),
spherePoint: (function() {
return function(u, v) {
u === undefined && (u = Math.random());
v === undefined && (v = Math.random());
var theta = 2 * Math.PI * u;
var phi = Math.acos(2 * v - 1);
var vec = {};
vec.x = (Math.sin(phi) * Math.cos(theta));
vec.y = (Math.sin(phi) * Math.sin(theta));
vec.z = (Math.cos(phi));
return vec;
}
})()
};
function createTweenScrubber(tween, seekSpeed) {
seekSpeed = seekSpeed || 0.001;
function stop() {
TweenMax.to(tween, 1, {
timeScale: 0
});
}
function resume() {
TweenMax.to(tween, 1, {
timeScale: 1
});
}
function seek(dx) {
var progress = tween.progress();
var p = THREE.Math.clamp((progress + (dx * seekSpeed)), 0, 1);
tween.progress(p);
}
var _cx = 0;
// desktop
var mouseDown = false;
document.body.style.cursor = 'pointer';
window.addEventListener('mousedown', function(e) {
mouseDown = true;
document.body.style.cursor = 'ew-resize';
_cx = e.clientX;
stop();
});
window.addEventListener('mouseup', function(e) {
mouseDown = false;
document.body.style.cursor = 'pointer';
resume();
});
window.addEventListener('mousemove', function(e) {
if (mouseDown === true) {
var cx = e.clientX;
var dx = cx - _cx;
_cx = cx;
seek(dx);
}
});
// mobile
window.addEventListener('touchstart', function(e) {
_cx = e.touches[0].clientX;
stop();
e.preventDefault();
});
window.addEventListener('touchend', function(e) {
resume();
e.preventDefault();
});
window.addEventListener('touchmove', function(e) {
var cx = e.touches[0].clientX;
var dx = cx - _cx;
_cx = cx;
seek(dx);
e.preventDefault();
});
}
body {
margin: 0;
background: #fff;
}
canvas {
background: #fff;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/three.js/r75/three.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/bas.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/175711/OrbitControls-2.js"></script>
<div id="three-container"></div>
Thanks in advance!

get boundaries of complex object in image with white background (js)

What is a good way of getting the boundaries for an image (not the image itself, but rather the non-white pixels)? I am using javascript, so try to keep the algos within that realm if you can.
For example, how would I get a list/two lists of all of the x and y points where for the points that exist on the boundary of this (group of) object(s):
Note that the interior part should be included, but a lack of colour that is completely on the inside (like a hole) should be excluded.
Therefore, the result would be two lists that contain the x and y points (for the pixels) that would construct an object similar to this:
Below is how I "achieved" it. While it works for all concave objects, if you try to use it for more complex objects with some convex sides, it inevitably fails.
Success with Concave Object
jsfiddle: https://jsfiddle.net/bhc4qn87/
snippet:
var _PI = Math.PI, _HALF_PI = Math.PI / 2, _TWO_PI = 2 * Math.PI;
var _radius = 10, _damp = 75, _center = new THREE.Vector3(0, 0, 0);
var _phi = _PI / 2, _theta = _theta = _PI / 7;
var _sceneScreenshot = null, _dirty = true;
var _tmpCan = document.createElement("canvas"),
_tmpCtx = _tmpCan.getContext("2d");
var scene = document.getElementById("scene"),
sw = scene.width, sh = scene.height;
var _scene = new THREE.Scene();
var _renderer = new THREE.WebGLRenderer({ canvas: scene, alpha: true, antialias: true });
_renderer.setPixelRatio(window.devicePixelRatio);
_renderer.setSize(sw, sh);
var _camera = new THREE.PerspectiveCamera(35, sw / sh, .1, 1000);
_tmpCan.width = sw; _tmpCan.height = sh;
_scene.add(new THREE.HemisphereLight(0x999999, 0x555555, 1));
_scene.add(new THREE.AmbientLight(0x404040));
var _camLight = new THREE.PointLight(0xdfdfdf, 1.8, 300, 2);
_scene.add(_camLight);
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0x2378d3, opacity: .7 } );
var cube = new THREE.Mesh( geometry, material );
_scene.add( cube );
function initialize() {
document.body.appendChild(_tmpCan);
_tmpCan.style.position = "absolute";
_tmpCan.style.left = "8px";
_tmpCan.style.top = "8px";
_tmpCan.style.pointerEvents = "none";
addListeners();
updateCamera();
animate();
}
function addListeners() {
/* mouse events */
var scene = document.getElementById("scene");
scene.oncontextmenu = function(e) {
e.preventDefault();
}
scene.onmousedown = function(e) {
e.preventDefault();
mouseTouchDown(e.pageX, e.pageY, e.button);
}
scene.ontouchstart = function(e) {
if (e.touches.length !== 1) {
return;
}
e.preventDefault();
mouseTouchDown(e.touches[0].pageX, e.touches[0].pageY, e.touches.length, true);
}
function mouseTouchDown(pageX, pageY, button, touch) {
_mouseX = pageX; _mouseY = pageY;
_button = button;
if (touch) {
document.ontouchmove = function(e) {
if (e.touches.length !== 1) {
return;
}
mouseTouchMove(e.touches[0].pageX, e.touches[0].pageY, e.touches.length, true);
}
document.ontouchend = function() {
document.ontouchmove = null;
document.ontouchend = null;
}
} else {
document.onmousemove = function(e) {
mouseTouchMove(e.pageX, e.pageY, _button);
}
document.onmouseup = function() {
document.onmousemove = null;
document.onmouseup = null;
}
}
}
function mouseTouchMove(pageX, pageY, button, touch) {
var dx = pageX - _mouseX,
dy = pageY - _mouseY;
_phi += dx / _damp;
// _theta += dy / _damp;
_phi %= _TWO_PI;
if (_phi < 0) {
_phi += _TWO_PI;
}
// var maxTheta = _HALF_PI - _HALF_PI * .8,
// minTheta = -_HALF_PI + _HALF_PI * .8;
// if (_theta > maxTheta) {
// _theta = maxTheta;
// } else if (_theta < minTheta) {
// _theta = minTheta;
// }
updateCamera();
_dirty = true;
// updateLabels();
_mouseX = pageX;
_mouseY = pageY;
}
}
function updateCamera() {
// var radius = _radius + (Math.sin(_theta % _PI)) * 10;
var radius = _radius;
var y = radius * Math.sin(_theta),
phiR = radius * Math.cos(_theta);
var z = phiR * Math.sin(_phi),
x = phiR * Math.cos(_phi);
_camera.position.set(x, y, z);
_camLight.position.set(x, y, z);
_camera.lookAt(_center);
}
function updateLabels() {
if (_sceneScreenshot === null) {
return;
}
var tmpImg = new Image();
tmpImg.onload = function() {
_tmpCtx.drawImage(tmpImg, 0, 0, sw, sh);
var imgData = _tmpCtx.getImageData(0, 0, sw, sh);
var data = imgData.data;
var firstXs = [];
var lastXs = [];
for (var y = 0; y < sh; y++) {
var firstX = -1;
var lastX = -1;
for (var x = 0; x < sw; x++) {
var i = (x + y * sw) * 4;
var sum = data[i] + data[i + 1] + data[i + 2];
if (firstX === -1) {
if (sum > 3) {
firstX = x;
}
} else {
if (sum > 3) {
lastX = x;
}
}
}
if (lastX === -1 && firstX >= 0) {
lastX = firstX;
}
firstXs.push(firstX);
lastXs.push(lastX);
}
var firstYs = [];
var lastYs = [];
for (var x = 0; x < sw; x++) {
var firstY = -1;
var lastY = -1;
for (var y = 0; y < sh; y++) {
var i = (x + y * sw) * 4;
var sum = data[i] + data[i + 1] + data[i + 2];
if (firstY === -1) {
if (sum < 759) {
firstY = y;
}
} else {
if (sum < 759) {
lastY = y;
}
}
}
if (lastY === -1 && firstY >= 0) {
lastY = firstY;
}
firstYs.push(firstY);
lastYs.push(lastY);
}
postLoad(firstXs, lastXs, firstYs, lastYs);
}
tmpImg.src = _sceneScreenshot;
function postLoad(firstXs, lastXs, firstYs, lastYs) {
_tmpCtx.clearRect(0, 0, sw, sh);
_tmpCtx.beginPath();
for (var y = 0; y < sh; y++) {
_tmpCtx.moveTo(firstXs[y], y);
_tmpCtx.lineTo(lastXs[y], y);
}
/* TODO REMOVE BELOW TODO */
_tmpCtx.strokeStyle = 'black';
console.log(_tmpCtx.globalAlpha);
_tmpCtx.stroke();
/* TODO REMOVE ABOVE TODO */
_tmpCtx.beginPath();
for (var x = 0; x < sw; x++) {
_tmpCtx.moveTo(x, firstYs[x]);
_tmpCtx.lineTo(x, lastYs[x]);
}
/* TODO REMOVE BELOW TODO */
_tmpCtx.strokeStyle = 'black';
_tmpCtx.stroke();
/* TODO REMOVE ABOVE TODO */
var imgData = _tmpCtx.getImageData(0, 0, sw, sh);
var data = imgData.data;
for (var i = 0, iLen = data.length; i < iLen; i += 4) {
if (data[i + 3] < 200) {
data[i + 3] = 0;
}
/* TODO remove v TODO */
else { data[i + 3] = 120; }
}
_tmpCtx.putImageData(imgData, 0, 0);
}
}
function animate () {
cube.rotation.x += 0.001;
cube.rotation.y += 0.001;
_renderer.render(_scene, _camera);
if (_dirty) {
_sceneScreenshot = _renderer.domElement.toDataURL();
updateLabels();
_dirty = false;
}
requestAnimationFrame( animate );
}
initialize();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.js"></script>
<canvas id="scene" width="400" height="300"></canvas>
Failure with Complex Object
jsfiddle: https://jsfiddle.net/xdr9bt0w/
var _PI = Math.PI, _HALF_PI = Math.PI / 2, _TWO_PI = 2 * Math.PI;
var _radius = 10, _damp = 75, _center = new THREE.Vector3(0, 0, 0);
var _phi = _PI / 2, _theta = _theta = 0;
var _sceneScreenshot = null, _dirty = true;
var _tmpCan = document.createElement("canvas"),
_tmpCtx = _tmpCan.getContext("2d");
var scene = document.getElementById("scene"),
sw = scene.width, sh = scene.height;
var _scene = new THREE.Scene();
var _renderer = new THREE.WebGLRenderer({ canvas: scene, alpha: true, antialias: true });
_renderer.setPixelRatio(window.devicePixelRatio);
_renderer.setSize(sw, sh);
var _camera = new THREE.PerspectiveCamera(35, sw / sh, .1, 1000);
_tmpCan.width = sw; _tmpCan.height = sh;
_scene.add(new THREE.HemisphereLight(0x999999, 0x555555, 1));
_scene.add(new THREE.AmbientLight(0x404040));
var _camLight = new THREE.PointLight(0xdfdfdf, 1.8, 300, 2);
_scene.add(_camLight);
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0x2378d3, opacity: .7 } );
var cube = new THREE.Mesh( geometry, material );
_scene.add( cube );
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0xc36843, opacity: .7 } );
var cube2 = new THREE.Mesh( geometry, material );
cube2.position.x = -.75;
cube2.position.y = .75
_scene.add( cube2 );
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0x43f873, opacity: .7 } );
var cube3 = new THREE.Mesh( geometry, material );
cube3.position.x = -.25;
cube3.position.y = 1.5;
_scene.add( cube3 );
var geometry = new THREE.BoxBufferGeometry( 1, 1, 1 );
var material = new THREE.MeshPhysicalMaterial( { color: 0x253621, opacity: .7 } );
var cube4 = new THREE.Mesh( geometry, material );
cube4.position.x = 1;
cube4.position.y = .35;
_scene.add( cube4 );
function initialize() {
document.body.appendChild(_tmpCan);
_tmpCan.style.position = "absolute";
_tmpCan.style.left = "200px";
_tmpCan.style.top = "0px";
_tmpCan.style.pointerEvents = "none";
addListeners();
updateCamera();
animate();
}
function addListeners() {
/* mouse events */
var scene = document.getElementById("scene");
scene.oncontextmenu = function(e) {
e.preventDefault();
}
scene.onmousedown = function(e) {
e.preventDefault();
mouseTouchDown(e.pageX, e.pageY, e.button);
}
scene.ontouchstart = function(e) {
if (e.touches.length !== 1) {
return;
}
e.preventDefault();
mouseTouchDown(e.touches[0].pageX, e.touches[0].pageY, e.touches.length, true);
}
function mouseTouchDown(pageX, pageY, button, touch) {
_mouseX = pageX; _mouseY = pageY;
_button = button;
if (touch) {
document.ontouchmove = function(e) {
if (e.touches.length !== 1) {
return;
}
mouseTouchMove(e.touches[0].pageX, e.touches[0].pageY, e.touches.length, true);
}
document.ontouchend = function() {
document.ontouchmove = null;
document.ontouchend = null;
}
} else {
document.onmousemove = function(e) {
mouseTouchMove(e.pageX, e.pageY, _button);
}
document.onmouseup = function() {
document.onmousemove = null;
document.onmouseup = null;
}
}
}
function mouseTouchMove(pageX, pageY, button, touch) {
var dx = pageX - _mouseX,
dy = pageY - _mouseY;
_phi += dx / _damp;
// _theta += dy / _damp;
_phi %= _TWO_PI;
if (_phi < 0) {
_phi += _TWO_PI;
}
// var maxTheta = _HALF_PI - _HALF_PI * .8,
// minTheta = -_HALF_PI + _HALF_PI * .8;
// if (_theta > maxTheta) {
// _theta = maxTheta;
// } else if (_theta < minTheta) {
// _theta = minTheta;
// }
updateCamera();
_dirty = true;
// updateLabels();
_mouseX = pageX;
_mouseY = pageY;
}
}
function updateCamera() {
// var radius = _radius + (Math.sin(_theta % _PI)) * 10;
var radius = _radius;
var y = radius * Math.sin(_theta),
phiR = radius * Math.cos(_theta);
var z = phiR * Math.sin(_phi),
x = phiR * Math.cos(_phi);
_camera.position.set(x, y, z);
_camLight.position.set(x, y, z);
_camera.lookAt(_center);
}
function updateLabels() {
if (_sceneScreenshot === null) {
return;
}
var tmpImg = new Image();
tmpImg.onload = function() {
_tmpCtx.drawImage(tmpImg, 0, 0, sw, sh);
var imgData = _tmpCtx.getImageData(0, 0, sw, sh);
var data = imgData.data;
var firstXs = [];
var lastXs = [];
for (var y = 0; y < sh; y++) {
var firstX = -1;
var lastX = -1;
for (var x = 0; x < sw; x++) {
var i = (x + y * sw) * 4;
var sum = data[i] + data[i + 1] + data[i + 2];
if (firstX === -1) {
if (sum > 3) {
firstX = x;
}
} else {
if (sum > 3) {
lastX = x;
}
}
}
if (lastX === -1 && firstX >= 0) {
lastX = firstX;
}
firstXs.push(firstX);
lastXs.push(lastX);
}
var firstYs = [];
var lastYs = [];
for (var x = 0; x < sw; x++) {
var firstY = -1;
var lastY = -1;
for (var y = 0; y < sh; y++) {
var i = (x + y * sw) * 4;
var sum = data[i] + data[i + 1] + data[i + 2];
if (firstY === -1) {
if (sum > 3) {
firstY = y;
}
} else {
if (sum > 3) {
lastY = y;
}
}
}
if (lastY === -1 && firstY >= 0) {
lastY = firstY;
}
firstYs.push(firstY);
lastYs.push(lastY);
}
postLoad(firstXs, lastXs, firstYs, lastYs);
}
tmpImg.src = _sceneScreenshot;
function postLoad(firstXs, lastXs, firstYs, lastYs) {
_tmpCtx.clearRect(0, 0, sw, sh);
_tmpCtx.beginPath();
for (var y = 0; y < sh; y++) {
_tmpCtx.moveTo(firstXs[y], y);
_tmpCtx.lineTo(lastXs[y], y);
}
/* TODO REMOVE BELOW TODO */
_tmpCtx.strokeStyle = 'black';
console.log(_tmpCtx.globalAlpha);
_tmpCtx.stroke();
/* TODO REMOVE ABOVE TODO */
_tmpCtx.beginPath();
for (var x = 0; x < sw; x++) {
_tmpCtx.moveTo(x, firstYs[x]);
_tmpCtx.lineTo(x, lastYs[x]);
}
/* TODO REMOVE BELOW TODO */
_tmpCtx.strokeStyle = 'black';
_tmpCtx.stroke();
/* TODO REMOVE ABOVE TODO */
var imgData = _tmpCtx.getImageData(0, 0, sw, sh);
var data = imgData.data;
for (var i = 0, iLen = data.length; i < iLen; i += 4) {
if (data[i + 3] < 200) {
data[i + 3] = 0;
}
/* TODO remove v TODO */
else { data[i + 3] = 120; }
}
_tmpCtx.putImageData(imgData, 0, 0);
}
}
function animate () {
cube.rotation.x += 0.001;
cube.rotation.y += 0.001;
cube2.rotation.x -= 0.001;
cube2.rotation.y += 0.001;
cube3.rotation.x += 0.001;
cube3.rotation.y -= 0.001;
cube4.rotation.x -= 0.001;
cube4.rotation.y -= 0.001;
_renderer.render(_scene, _camera);
if (_dirty) {
_sceneScreenshot = _renderer.domElement.toDataURL();
updateLabels();
_dirty = false;
}
requestAnimationFrame( animate );
}
initialize();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.js"></script>
<canvas id="scene" width="400" height="300"></canvas>
You can see with the above jsfiddle that the inside of this complex, convex image fails on the inside.
Question
Therefore, the question remains: what is a good way of creating a mask, if you will, of the image (disregarding holes) that will cover all of the outside of any complex/convex object where the background is white and the components of the image are anything but white? thanks
Here is a solution that uses a flood-fill algorithm to cover the outer areas with white and the rest with black.
Keep in mind that this is a very naive implementation, there are lots of optimization that can potentially be done (by calculating the bounding rectangle and only filling inside it for example, another one would be to use 32-bit arrays to do the actual pixel assignment while filling).
Another thing to note is that the filling always starts in the upper left corner, if the object is currently covering that pixel it will not work (you can however pick another pixel to start at).
I removed the touch handlers and some other items to keep the example short.
The updateMask-function is where the mask is created.
function createCube(color, x, y){
const geo = new THREE.BoxBufferGeometry( 1, 1, 1 );
const mat = new THREE.MeshPhysicalMaterial( { color: color, opacity: 1 } );
const mesh = new THREE.Mesh(geo, mat);
mesh.position.x = x;
mesh.position.y = y;
return mesh;
}
const c_main = document.getElementById("main");
const c_mask = document.getElementById("mask");
const ctx_mask = c_mask.getContext("2d");
ctx_mask.fillStyle = "#000";
const cw = c_main.width, ch = c_main.height;
const TWO_PI = Math.PI * 2;
const damp = 75, radius = 10, animspeed = 0.001;
const center = new THREE.Vector3(0, 0, 0);
let x1 = 0;
let phi = Math.PI / 2;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(35, cw / ch, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ canvas: c_main, alpha: true, antialias: false });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(cw, ch);
const camLight = new THREE.PointLight(0xdfdfdf, 1.8, 300, 2);
scene.add(new THREE.HemisphereLight(0x999999, 0x555555, 1));
scene.add(new THREE.AmbientLight(0x404040));
scene.add(camLight);
const cubes = [];
cubes.push(createCube(0x2378d3, 0, 0));
cubes.push(createCube(0xc36843, -0.75, 0.75));
cubes.push(createCube(0x43f873, -0.25, 1.5));
cubes.push(createCube(0x253621, 1, 0.35));
scene.add(...cubes);
function initialize() {
c_main.addEventListener("mousedown", mouseDown, false);
updateCamera();
animate();
}
function updateMask(){
//First, fill the canvas with black
ctx_mask.globalCompositeOperation = "source-over";
ctx_mask.fillRect(0,0, cw, ch);
//Then using the composite operation "destination-in" the canvas is made transparent EXCEPT where the new image is drawn.
ctx_mask.globalCompositeOperation = "destination-in";
ctx_mask.drawImage(c_main, 0, 0);
//Now, use a flood fill algorithm of your choice to fill the outer transparent field with white.
const idata = ctx_mask.getImageData(0,0, cw, ch);
const array = idata.data;
floodFill(array, 0, 0, cw, ch);
ctx_mask.putImageData(idata, 0, 0);
//The only transparency left are in the "holes", we make these black by using the composite operation "destination-over" to paint black behind everything.
ctx_mask.globalCompositeOperation = "destination-over";
ctx_mask.fillRect(0,0, cw, ch);
}
function mouseDown(e){
e.preventDefault();
x1 = e.pageX;
const button = e.button;
document.addEventListener("mousemove", mouseMove, false);
document.addEventListener("mouseup", mouseUp, false);
}
function mouseUp(){
document.removeEventListener("mousemove", mouseMove, false);
document.removeEventListener("mouseup", mouseUp, false);
}
function mouseMove(e){
const x2 = e.pageX;
const dx = x2 - x1;
phi += dx/damp;
phi %= TWO_PI;
if( phi < 0 ){
phi += TWO_PI;
}
x1 = x2;
updateCamera();
}
function updateCamera() {
const x = radius * Math.cos(phi);
const y = 0;
const z = radius * Math.sin(phi);
camera.position.set(x, y, z);
camera.lookAt(center);
camLight.position.set(x, y, z);
}
function animate(){
cubes[0].rotation.x += animspeed;
cubes[0].rotation.y += animspeed;
cubes[1].rotation.x -= animspeed;
cubes[1].rotation.y += animspeed;
cubes[2].rotation.x += animspeed;
cubes[2].rotation.y -= animspeed;
cubes[3].rotation.x -= animspeed;
cubes[3].rotation.y -= animspeed;
renderer.render(scene, camera);
updateMask();
requestAnimationFrame(animate);
}
const FILL_THRESHOLD = 254;
//Quickly adapted flood fill from http://www.adammil.net/blog/v126_A_More_Efficient_Flood_Fill.html
function floodStart(array, x, y, width, height){
const M = width * 4;
while(true){
let ox = x, oy = y;
while(y !== 0 && array[(y-1)*M + x*4 + 3] < FILL_THRESHOLD){ y--; }
while(x !== 0 && array[y*M + (x-1)*4 + 3] < FILL_THRESHOLD){ x--; }
if(x === ox && y === oy){ break; }
}
floodFill(array, x, y, width, height);
}
function floodFill(array, x, y, width, height){
const M = width * 4;
let lastRowLength = 0;
do{
let rowLength = 0, sx = x;
let idx = y*M + x*4 + 3;
if(lastRowLength !== 0 && array[idx] >= FILL_THRESHOLD){
do{
if(--lastRowLength === 0){ return; }
}
while(array[ y*M + (++x)*4 + 3]);
sx = x;
}
else{
for(; x !== 0 && array[y*M + (x-1)*4 + 3] < FILL_THRESHOLD; rowLength++, lastRowLength++){
const idx = y*M + (--x)*4;
array[idx] = 255;
array[idx + 1] = 255;
array[idx + 2] = 255;
array[idx + 3] = 255;
if( y !== 0 && array[(y-1)*M + x*4 + 3] < FILL_THRESHOLD ){
floodStart(array, x, y-1, width, height);
}
}
}
for(; sx < width && array[y*M + sx*4 + 3] < FILL_THRESHOLD; rowLength++, sx++){
const idx = y*M + sx*4;
array[idx] = 255;
array[idx + 1] = 255;
array[idx + 2] = 255;
array[idx + 3] = 255;
}
if(rowLength < lastRowLength){
for(let end=x+lastRowLength; ++sx < end; ){
if(array[y*M + sx*4 + 3] < FILL_THRESHOLD){
floodFill(array, sx, y, width, height);
}
}
}
else if(rowLength > lastRowLength && y !== 0){
for(let ux=x+lastRowLength; ++ux<sx; ){
if(array[(y-1)*M + ux*4 + 3] < FILL_THRESHOLD){
floodStart(array, ux, y-1, width, height);
}
}
}
lastRowLength = rowLength;
}
while(lastRowLength !== 0 && ++y < height);
}
initialize();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/92/three.js"></script>
<canvas id="main" width="300" height="200"></canvas>
<canvas id="mask" width="300" height="200"></canvas>

how to attach a range/input slider onto a canvas animation?

I am aiming to attach a slider onto this codepen animation, so the user can edit the speed of the animation.
At the moment, I am not getting any errors, but I am not getting the value from the input slider passed through to the animation.
this.speed = dotSpeed;
I am aiming to take the value from the slider, create a variable and put it through the "function Circle" properties.
var dotArray = [];
function threeDotSliderChangeSpeed (value) {
document.getElementById('threeDotSpeed').innerHTML = value;
dotSpeed = +value; // + will convert the string to number
for (var i = 0; i < dotArray.length; i++) {
dotArray[i].speed = Math.round(1 * dotSpeed);
}
}
var canvas = document.getElementById( 'canvas2' ),
c = canvas.getContext( '2d' ),
i = 0,
rowOne = [],
rowTwo = [],
rowThree = [],
length = canvas.width * 0.4,
origin = [ canvas.width / 2, canvas.height / 2 ],
angle = 90,
dotSpeed = 2,
loop;
function Circle( args ) {
this.position = [ 0, 0 ];
this.angle = 30;
this.speed = dotSpeed;
this.offset = 1;
this.length = 100;
this.size = 5;
this.color = '#fff';
this.direction = 'grow';
if ( 'undefined' !== typeof args.position )
this.position = args.position;
if ( 'undefined' !== typeof args.angle )
this.angle = args.angle;
if ( 'undefined' !== typeof args.speed )
this.speed = args.speed;
if ( 'undefined' !== typeof args.length )
this.length = args.length;
if ( 'undefined' !== typeof args.size )
this.size = args.size;
if ( 'undefined' !== typeof args.color )
this.color = args.color;
if ( 'undefined' !== typeof args.offset ) {
this.offset = args.offset;
this.length = canvas.width * this.offset * 0.03
}
}
Circle.prototype.render = function() {
this.move();
this.draw();
}
Circle.prototype.draw = function() {
c.fillStyle = this.color;
c.beginPath();
c.arc( this.position[0], this.position[1], ( this.size / 2 ), 0, Math.PI * 2, true );
c.closePath();
c.fill();
}
Circle.prototype.move = function() {
this.angle = ( this.angle < 360 ) ? this.angle + this.speed : 0;
if ( 'grow' == this.direction ) {
this.length++;
this.direction = ( 150 >= this.length ) ? 'grow' : 'shrink';
} else {
this.length--;
this.direction = ( 50 <= this.length ) ? 'shrink' : 'grow';
}
this.position[0] = this.length * Math.sin( this.angle * ( Math.PI / 180 ) );
this.position[1] = this.length * Math.cos( this.angle * ( Math.PI / 180 ) );
this.position[0] = this.position[0] + origin[0];
this.position[1] = this.position[1] + origin[1];
}
for ( i = 1; i < 10; i++ ) {
var offset = 1;
rowOne.push( new Circle( {
angle: 0,
offset: i
} ) );
rowTwo.push( new Circle( {
angle: 120,
offset: i
} ) );
rowThree.push( new Circle( {
angle: 240,
offset: i
} ) );
}
function render() {
c.fillStyle = 'rgba( 0, 0, 0, 0.025 )';
c.fillRect( 0, 0, canvas.width, canvas.height );
for ( i = 0; i < 9; i++ ) {
rowOne[i].render();
rowTwo[i].render();
rowThree[i].render();
}
}
(function animate() {
render();
loop = setTimeout( animate, 40 );
})();
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>CodePen - 3 dotted-line canvas animation.</title>
<link rel="stylesheet" href="css/style.css">
<script type="text/javascript" src="jquery-2.1.0.min.js"></script>
</head>
<body>
<canvas id="canvas2" width="400" height="400"></canvas>
<p id="attribute">Speed </p>
<span id="threeDotSpeed" class="sliderSpan">5</span>
<input type="range" min="0" max="10" value="5" step="1" onchange="threeDotSliderChangeSpeed(3)"/>
<br /> <br />
<script src="js/index.js"></script>
</body>
</html>
Here is a video of another one I got going, using the same method.
https://www.youtube.com/watch?v=vjZ6CfQ2WrY&feature=youtu.be
To get the value from the slider you need to get the value from the element
by using document.getElementById('rg').value on change event
js:
var dotArray = [];
function threeDotSliderChangeSpeed () {
var value = document.getElementById('rg').value;
alert(value);
document.getElementById('threeDotSpeed').innerHTML = value;
dotSpeed = +value; // + will convert the string to number
for (var i = 0; i < dotArray.length; i++) {
dotArray[i].speed = Math.round(1 * dotSpeed);
}
}
var canvas = document.getElementById( 'canvas2' ),
c = canvas.getContext( '2d' ),
i = 0,
rowOne = [],
rowTwo = [],
rowThree = [],
length = canvas.width * 0.4,
origin = [ canvas.width / 2, canvas.height / 2 ],
angle = 90,
dotSpeed = 2,
loop;
function Circle( args ) {
this.position = [ 0, 0 ];
this.angle = 30;
this.speed = dotSpeed;
this.offset = 1;
this.length = 100;
this.size = 5;
this.color = '#fff';
this.direction = 'grow';
if ( 'undefined' !== typeof args.position )
this.position = args.position;
if ( 'undefined' !== typeof args.angle )
this.angle = args.angle;
if ( 'undefined' !== typeof args.speed )
this.speed = args.speed;
if ( 'undefined' !== typeof args.length )
this.length = args.length;
if ( 'undefined' !== typeof args.size )
this.size = args.size;
if ( 'undefined' !== typeof args.color )
this.color = args.color;
if ( 'undefined' !== typeof args.offset ) {
this.offset = args.offset;
this.length = canvas.width * this.offset * 0.03
}
}
Circle.prototype.render = function() {
this.move();
this.draw();
}
Circle.prototype.draw = function() {
c.fillStyle = this.color;
c.beginPath();
c.arc( this.position[0], this.position[1], ( this.size / 2 ), 0, Math.PI * 2, true );
c.closePath();
c.fill();
}
Circle.prototype.move = function() {
this.angle = ( this.angle < 360 ) ? this.angle + this.speed : 0;
if ( 'grow' == this.direction ) {
this.length++;
this.direction = ( 150 >= this.length ) ? 'grow' : 'shrink';
} else {
this.length--;
this.direction = ( 50 <= this.length ) ? 'shrink' : 'grow';
}
this.position[0] = this.length * Math.sin( this.angle * ( Math.PI / 180 ) );
this.position[1] = this.length * Math.cos( this.angle * ( Math.PI / 180 ) );
this.position[0] = this.position[0] + origin[0];
this.position[1] = this.position[1] + origin[1];
}
for ( i = 1; i < 10; i++ ) {
var offset = 1;
rowOne.push( new Circle( {
angle: 0,
offset: i
} ) );
rowTwo.push( new Circle( {
angle: 120,
offset: i
} ) );
rowThree.push( new Circle( {
angle: 240,
offset: i
} ) );
}
function render() {
c.fillStyle = 'rgba( 0, 0, 0, 0.025 )';
c.fillRect( 0, 0, canvas.width, canvas.height );
for ( i = 0; i < 9; i++ ) {
rowOne[i].render();
rowTwo[i].render();
rowThree[i].render();
}
}
(function animate() {
render();
loop = setTimeout( animate, 40 );
})();
Html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>CodePen - 3 dotted-line canvas animation.</title>
<link rel="stylesheet" href="css/style.css">
<script type="text/javascript" src="jquery-2.1.0.min.js"></script>
</head>
<body>
<canvas id="canvas2" width="400" height="400"></canvas>
<p id="attribute">Speed </p>
<span id="threeDotSpeed" class="sliderSpan">5</span>
<input type="range" min="0" max="10" value="5" step="1" id="rg" onchange="threeDotSliderChangeSpeed(3)"/>
<br />
<br />
<script src="js/index.js"></script>
</body>
</html>
i am able to get the slider value on alert
you can see output in codepin
http://codepen.io/anon/pen/aOOjXE

Limiting a rotated object within boundary with snap SVG

I've managed to setup a FIDDLE where I gave an example how to bind an element within svg boundary. It's working fine when dragging a normal element(without rotation). But when I apply this to a rotated object, the problem arises:
var s = Snap("#svgout");
var ROOM_WIDTH = 400;
var ROOM_HEIGHT = 200;
var SVG_BOUNDARY = s.rect(0, 0, ROOM_WIDTH, ROOM_HEIGHT);
SVG_BOUNDARY.attr({
"stroke": "#FF0000",
"fill": "#FFFFFF",
id: "room-svg-boundary"
});
var BEFORE_ROTATION_GETBBOX = [];
s.attr({
viewBox: "0 0 " + ROOM_WIDTH + " " + ROOM_HEIGHT
});
(function() {
Snap.plugin(function(Snap, Element, Paper, global) {
Element.prototype.limitDragging = function(params) {
this.data('minx', params.minx);
this.data('miny', params.miny);
this.data('maxx', params.maxx);
this.data('maxy', params.maxy);
this.data('x', params.x);
this.data('y', params.y);
this.data('ibb', this.getBBox());
this.data('ot', this.transform().local);
this.drag(limitMoveDrag, limitStartDrag);
return this;
};
function limitMoveDrag(dx, dy, posx, posy, ev) {
var tdx, tdy;
var sInvMatrix = this.transform().globalMatrix.invert();
sInvMatrix.e = sInvMatrix.f = 0;
tdx = sInvMatrix.x(dx, dy);
tdy = sInvMatrix.y(dx, dy);
this.data('x', +this.data('ox') + tdx);
this.data('y', +this.data('oy') + tdy);
if (this.data('x') > this.data('maxx') - this.data('ibb').width) {
this.data('x', this.data('maxx') - this.data('ibb').width)
};
if (this.data('y') > this.data('maxy') - this.data('ibb').height) {
this.data('y', this.data('maxy') - this.data('ibb').height)
};
if (this.data('x') < this.data('minx')) {
this.data('x', this.data('minx'))
};
if (this.data('y') < this.data('miny')) {
this.data('y', this.data('miny'))
};
this.transform(this.data('ot') + "t" + [this.data('x'), this.data('y')]);
};
function limitStartDrag(x, y, ev) {
this.data('ox', this.data('x'));
this.data('oy', this.data('y'));
};
});
})();
var SETUP_DRAG_MOVEMENT = function(ELEM, disable) {
if (disable != undefined && disable == true) {
ELEM.undrag();
} else {
var RECT_BOUNDARY = s.getBBox();
var ELEM_BOX = ELEM.getBBox();
var min_x = -ELEM_BOX.cx + ELEM_BOX.width / 2;
var max_x = RECT_BOUNDARY.x2 - ELEM_BOX.x2 + ELEM_BOX.width;
var min_y = -ELEM_BOX.cy + ELEM_BOX.height / 2;
var max_y = RECT_BOUNDARY.y2 - ELEM_BOX.y2 + ELEM_BOX.height;
ELEM.limitDragging({
x: 0,
y: 0,
minx: min_x,
miny: min_y,
maxx: max_x,
maxy: max_y
});
}
}
var SETUP_DRAG_MOVEMENT_ROTATED = function(ELEM, disable) {
if (disable != undefined && disable == true) {
BEFORE_ROTATION_GETBBOX[ELEM.node.id] = ELEM.getBBox();
ELEM.undrag();
} else {
var RECT_BOUNDARY = s.getBBox();
console.log(RECT_BOUNDARY);
var ELEM_BOX = (BEFORE_ROTATION_GETBBOX[ELEM.node.id] != undefined && typeof BEFORE_ROTATION_GETBBOX[ELEM.node.id] == "object") ? BEFORE_ROTATION_GETBBOX[ELEM.node.id] : ELEM.getBBox();
var min_x = -ELEM_BOX.x;
var max_x = RECT_BOUNDARY.x2 - ELEM_BOX.x2 + ELEM_BOX.width;
var min_y = -ELEM_BOX.y;
var max_y = RECT_BOUNDARY.y2 - ELEM_BOX.y2 + ELEM_BOX.height;
ELEM.limitDragging({
x: 0,
y: 0,
minx: min_x,
miny: min_y,
maxx: max_x,
maxy: max_y
});
}
}
var DBLCLICK_HANDLER = function(ELEM) {
SETUP_DRAG_MOVEMENT(THIS_RECT, true);
var THIS_ELEM_BB = ELEM.getBBox();
var transformProp = new Snap.Matrix();
transformProp.rotate(45, THIS_ELEM_BB.cx, THIS_ELEM_BB.cy);
transformProp.add(ELEM.matrix);
ELEM.transform(transformProp);
SETUP_DRAG_MOVEMENT_ROTATED(THIS_RECT, false);
}
var myCircle = s.circle(380, 20, 20).attr({
fill: 'blue'
});
var THIS_CIRCLE_LABEL = s.paper.text(376, 25, "1").attr({
fill: "#FFFFFF"
});
var THIS_CIRCLE = s.group(myCircle, THIS_CIRCLE_LABEL);
THIS_CIRCLE.attr({
id: "THIS_CIRCLE"
});
SETUP_DRAG_MOVEMENT_ROTATED(THIS_CIRCLE, false);
var myRect = s.rect(0, 0, 30, 30).attr({
fill: 'green'
});
var THIS_RECT_LABEL = s.paper.text(11, 19, "2").attr({
fill: "#FFFFFF"
});
var THIS_RECT = s.group(myRect, THIS_RECT_LABEL);
THIS_RECT.attr({
id: "THIS_RECT"
});
SETUP_DRAG_MOVEMENT_ROTATED(THIS_RECT, false);
THIS_RECT.node.addEventListener("dblclick", function() {
DBLCLICK_HANDLER(THIS_RECT);
}, false);
<script src="http://snapsvg.io/assets/js/snap.svg-min.js"></script>
<p>Double click on the rectangle to rotate it and then try to drag. When you drag without rotating, it's perfect (The drag boundary). But while dragging a rotated object, then the problem arises..</p>
<svg id="svgout" height="400" width="600"></svg>
How do I solve this one?
As that includes some of my old code, thought I would do an answer....
Originally that doesn't take into account existing transforms easier. I wrote some updated code that would do this...
The main bits that are different are the getInversePoint() function, which gets the inverse transformation to the screen. Also I've changed the order of transformations, so the element is always panned first.
Example
Element.prototype.getInversePoint = function( x, y ) {
var pt = this.paper.node.createSVGPoint();
pt.x = x; pt.y = y;
return pt.matrixTransform( this.paper.node.getScreenCTM().inverse());
}
Element.prototype.limitDrag = function( params ) {
this.data('dragParams', params );
this.data('x', params.x); this.data('y', params.y);
this.drag( limitMoveDrag, limitStartDrag );
return this;
};
function limitMoveDrag( xxdx, xxdy, ax, ay ) {
var tdx, tdy;
var params = this.data('dragParams');
var pt = this.getInversePoint( ax, ay );
var dx = pt.x - this.data('op').x;
var dy = pt.y - this.data('op').y;
var ibb = this.data('ibb');
if( ibb.x + ibb.width + +dx > params.maxx )
{ dx = params.maxx - (ibb.x + ibb.width) };
if( ibb.y + ibb.height + +dy > params.maxy )
{ dy = params.maxy - (ibb.y + ibb.height) };
if( ibb.x + +dx < params.minx ) { dx = params.minx - ibb.x; };
if( ibb.y + +dy < params.miny ) { dy = params.miny - ibb.y; };
this.transform( "t" + [ dx, dy ] + this.data('ot').toTransformString());
};
function limitStartDrag( x, y, ev ) {
this.data('ibb', this.getBBox());
this.data('op', this.getInversePoint( x, y ));
this.data('ot', this.transform().localMatrix);
};

Categories

Resources