I am implementing a simple genetic algorithm and everything works fine but I found that something is going wrong with the mutation function. I set the weights and biases of the agent's neural network to be identical to the fittest agent in the generation and then applied a mutation but all the agents are moving identically.
You can run my web app for yourself on this online p5 editor sketch: https://editor.p5js.org/aideveloper/sketches/Ot-SA1ulw
Can someone please help me understand what my mutation function is doing wrong?
agent.js:
class Agent {
constructor(args) {
this.x = args.x
this.y = args.y
this.color = args.color
this.weights = []
this.biases = []
this.lost = false
for(let i = 0; i < args.layers.length-1; i++)
this.weights.push([...new Array(args.layers[i+1])].map(() => [...new Array(args.layers[i])].map(() => random(-1, 1))))
for(let i = 0; i < args.layers.length-1; i++)
this.biases.push([...new Array(args.layers[i+1])].map(() => random(-1, 1)))
}
predict(x) {
let y = x
for(let i = 0; i < this.weights.length; i++) {
let hidden = [...new Array(this.weights[i].length)]
for(let j = 0; j < this.weights[i].length; j++) {
hidden[j] = 0
for(let k = 0; k < this.weights[i][j].length; k++)
hidden[j] += this.weights[i][j][k] * y[k]
hidden[j] += this.biases[i][j]
hidden[j] = 1 / (1 + Math.exp(-hidden[j]))
}
y = hidden
}
return y
}
mutate(rate=0.1) {
for(let i = 0; i < this.weights.length; i++) {
for(let j = 0; j < this.weights[i].length; j++) {
if(Math.random() < rate)
this.biases[i][j] += random(-1, 1)
for(let k = 0; k < this.weights[i][j].length; k++) {
if (Math.random() < rate)
this.weights[i][j][k] += random(-1, 1)
}
}
}
}
}
sketch.js:
const speed = 5
const n = 2000
let fittest_agent = null
let agents = []
let generation = 1
function setup() {
createCanvas(window.innerWidth, window.innerHeight)
for (let i = 0; i < n; i++) {
agents.push(new Agent({
x: 20,
y: window.innerHeight/2,
color: color(
Math.random() * 255,
Math.random() * 255,
Math.random() * 255
),
layers: [2, 10, 10, 1]
}))
}
fittest_agent = agents[0]
document.querySelector('#controls button').addEventListener('click', () => {
reproduce()
generation += 1
document.querySelector('#controls h1').textContent = `Generation: #${generation}`
})
}
function draw() {
noStroke()
background(255)
for (let i = 0; i < n; i++) {
fill(agents[i].color)
ellipse(agents[i].x, agents[i].y, 30, 30)
if(!agents[i].lost) {
let a = agents[i].predict([agents[i].x, agents[i].y])
agents[i].x += speed * cos(a * 100000)
agents[i].y += speed * sin(a * 100000)
}
fittest_agent = agents[i].x > fittest_agent.x ? agents[i] : fittest_agent
document.querySelector('#controls div').outerHTML = `<div id="fittest" style="background-color:rgb(${fittest_agent.color.levels[0]},${fittest_agent.color.levels[1]},${fittest_agent.color.levels[2]})"></div>`
document.querySelector('#controls span').textContent = Math.ceil(fittest_agent.x)
if(agents[i].x+15>window.innerWidth/4 && agents[i].x-15<window.innerWidth/4+30 && agents[i].y-15<window.innerHeight*2/3)
agents[i].lost = true
if(agents[i].x+15>window.innerWidth/2 && agents[i].x-15<window.innerWidth/2+30 && agents[i].y+15>window.innerHeight/3)
agents[i].lost = true
if(agents[i].x+15>window.innerWidth*3/4 && agents[i].x+15<window.innerWidth*3/4+30 && agents[i].y-15<window.innerHeight*2/3)
agents[i].lost = true
if(agents[i].x<15)
agents[i].lost = true
if(agents[i].y<15)
agents[i].lost = true
if(agents[i].x+15>window.innerWidth)
agents[i].lost = true
if(agents[i].y+15>window.innerHeight)
agents[i].lost = true
}
fill(135, 206, 235)
rect(window.innerWidth/4, 0, 30, window.innerHeight*2/3)
rect(window.innerWidth/2, window.innerHeight/3, 30, window.innerHeight*2/3)
rect(window.innerWidth*3/4, 0, 30, window.innerHeight*2/3)
}
function reproduce() {
agents.map(agent => {
agent.x = 20
agent.y = window.innerHeight/2
agent.lost = false
agent.weights = fittest_agent.weights
agent.biases = fittest_agent.biases
agent.color = color(random() * 255, random() * 255, random() * 255)
agent.mutate()
return agent
})
}
This is a visual representation of the issue:
The problem is in reproduce() function - you assign a reference to fittest agent's weights/biases array to new agent's weights/biases and all agents finally have same "brain". You should create a deep copy of fittest agent's weights/biases first and then assign it to agent.
function reproduce() {
agents.map(agent => {
// other code
agent.weights = deepCopy(fittest_agent.weights)
agent.biases = deepCopy(fittest_agent.biases)
// other code
}
}
function deepCopy(arr) {
// your implementation
}
You can use for ex. cloneDeep from lodash library.
The default audio sample rate is 48000. Is it possible to change it to other values like 44100?
I log the value of AudioSettings.outputSampleRate and it shows 48000. But it doesn't seem possible to change that value.
Here is the code to change sample rate of Unity's AudioClip:
Most simple, but very rough
Averaging approach, but channels are get mixed
Averaging approach for each channel (best quality)
public static AudioClip SetSampleRateSimple(AudioClip clip, int frequency)
{
if (clip.frequency == frequency) return clip;
var samples = new float[clip.samples * clip.channels];
clip.GetData(samples, 0);
var samplesLength = (int)(frequency * clip.length) * clip.channels;
var samplesNew = new float[samplesLength];
var clipNew = AudioClip.Create(clip.name + "_" + frequency, samplesLength, clip.channels, frequency, false);
for (var i = 0; i < samplesLength; i++)
{
var index = (int) ((float) i * samples.Length / samplesLength);
samplesNew[i] = samples[index];
}
clipNew.SetData(samplesNew, 0);
return clipNew;
}
public static AudioClip SetSampleRateAverage(AudioClip clip, int frequency)
{
if (clip.frequency == frequency) return clip;
var samples = new float[clip.samples * clip.channels];
clip.GetData(samples, 0);
var samplesNewLength = (int) (frequency * clip.length) * clip.channels;
var samplesNew = new float[samplesNewLength];
var clipNew = AudioClip.Create(clip.name + "_" + frequency, samplesNewLength, clip.channels, frequency, false);
var index = 0;
var sum = 0f;
var count = 0;
for (var i = 0; i < samples.Length; i++)
{
var index_ = (int)((float)i / samples.Length * samplesNewLength);
if (index_ == index)
{
sum += samples[i];
count++;
}
else
{
samplesNew[index] = sum / count;
index = index_;
sum = samples[i];
count = 1;
}
}
clipNew.SetData(samplesNew, 0);
return clipNew;
}
public static AudioClip SetSampleRate(AudioClip clip, int frequency)
{
if (clip.frequency == frequency) return clip;
if (clip.channels != 1 && clip.channels != 2) return clip;
var samples = new float[clip.samples * clip.channels];
clip.GetData(samples, 0);
var samplesNewLength = (int) (frequency * clip.length) * clip.channels;
var clipNew = AudioClip.Create(clip.name + "_" + frequency, samplesNewLength, clip.channels, frequency, false);
var channelsOriginal = new List<float[]>();
var channelsNew = new List<float[]>();
if (clip.channels == 1)
{
channelsOriginal.Add(samples);
channelsNew.Add(new float[(int) (frequency * clip.length)]);
}
else
{
channelsOriginal.Add(new float[clip.samples]);
channelsOriginal.Add(new float[clip.samples]);
channelsNew.Add(new float[(int) (frequency * clip.length)]);
channelsNew.Add(new float[(int) (frequency * clip.length)]);
for (var i = 0; i < samples.Length; i++)
{
channelsOriginal[i % 2][i / 2] = samples[i];
}
}
for (var c = 0; c < clip.channels; c++)
{
var index = 0;
var sum = 0f;
var count = 0;
var channelSamples = channelsOriginal[c];
for (var i = 0; i < channelSamples.Length; i++)
{
var index_ = (int) ((float) i / channelSamples.Length * channelsNew[c].Length);
if (index_ == index)
{
sum += channelSamples[i];
count++;
}
else
{
channelsNew[c][index] = sum / count;
index = index_;
sum = channelSamples[i];
count = 1;
}
}
}
float[] samplesNew;
if (clip.channels == 1)
{
samplesNew = channelsNew[0];
}
else
{
samplesNew = new float[channelsNew[0].Length + channelsNew[1].Length];
for (var i = 0; i < samplesNew.Length; i++)
{
samplesNew[i] = channelsNew[i % 2][i / 2];
}
}
clipNew.SetData(samplesNew, 0);
return clipNew;
}
Hi I need help finding coordinate or points offset from two endpoints of a line. In my program, I would like to specify the two points and the offset. Then I need to calculate the two offset coordinates.
I worked something out using trigonometry but it only works in some cases and when the line is in the positive quadrant.
Here is an image describing what I need to find:
Points on line
Ok so I need to find X3,Y3 and X4,Y4 coordinates.
My method I followed:
Calculate angle:
Ang = atan((Y2 - Y1)/(X2 - X1))
To find X3:
X3 = X1 + Offset * Cos(Ang)
The same concept for Y3
The issue is that if the line is in a different quadrant the point info is not correct... Any help, please.
This question is a clear case for using 2d vector math. The idea is that we subtract p1 from p2 to give us a vector that describes the length and direction of the line. We then normalize this vector, such that it has a length of 1. If you then multiply this normalized vector with the number of units you'd like to move away from the end and add the result to the end-point, you'll have a new point.
Consider an example walking along the x axis:
p1 = 0,0
p2 = 10,0
dif = p2 - p1 = (10,0)
length is 10, so it's 10 times too long - we divide it by 10 to get a vector 1 unit long.
If we then move 5 times (1,0), we end up at 5,0 - 5 units away, bewdy!
Here's a function that achieves the same thing:
function calcOffsetPoint(x1,y1, x2,y2, distTowardsP2fromP1)
{
var p1 = new vec2d(x1,y1);
var p2 = new vec2d(x2,y2);
var delta = p2.sub(p1);
var dirVec = delta.clone();
dirVec.normalize();
dirVec.timesEquals(distTowardsP2fromP1);
var resultPoint = p1.add(dirVec);
return resultPoint;
}
As you can see, this makes use of something I've called vec2d. There's a copy of it in the following snippet:
"use strict";
function byId(id){return document.getElemetById(id)}
function newEl(tag){return document.createElement(tag)}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
var end1 = new vec2d(0,0);
var end2 = new vec2d(10,0);
var midPoint = calcOffsetPoint(end1.x,end1.y, end2.x,end2.y, 5);
console.log( midPoint.toStringN(2) );
}
class vec2d
{
constructor(x=0, y=0)
{
this.mX = x;
this.mY = y;
}
get x(){return this.mX;}
set x(newX){this.mX = newX;}
get y(){return this.mY;}
set y(newY){this.mY = newY;}
add(other)
{
return new vec2d(this.x+other.x, this.y+other.y);
}
sub(other)
{
return new vec2d(this.x-other.x, this.y-other.y);
}
timesEquals(scalar)
{
this.x *= scalar;
this.y *= scalar;
return this;
}
divByEquals(scalar)
{
this.x /= scalar;
this.y /= scalar;
return this;
}
dotProd(other)
{
return this.x*other.x + this.y*other.y;
}
length()
{
return Math.hypot(this.x, this.y);
}
normalize()
{
this.divByEquals( this.length() );
return this;
}
perpendicular()
{
var tmp = this.x;
this.x = -this.y;
this.y = tmp;
return this;
}
clone()
{
return vec2d.clone(this);
}
static clone(other)
{
return new vec2d(other.x, other.y);
}
toString(){return `vec2d {x: ${this.x}, y: ${this.y}}`}
toStringN(n){return `vec2d {x: ${this.x.toFixed(n)}, y: ${this.y.toFixed(n)}}`}
}
function calcOffsetPoint(x1,y1, x2,y2, distTowardsP2fromP1)
{
var p1 = new vec2d(x1,y1);
var p2 = new vec2d(x2,y2);
var delta = p2.sub(p1);
var dirVec = delta.clone();
dirVec.normalize();
dirVec.timesEquals(distTowardsP2fromP1);
var resultPoint = p1.add(dirVec);
return resultPoint;
}
I had some spare time over the weekend, so put together a working demo of the image you posted. Have a play around. Make sure you run it in full-screen, so you can see the sliders that set the offsets for p3 and p4. Disregard the coordinate-system transformation stuff, that's just there to allow me to make an image the same dimensions as your image yet conveniently display it in a window with about 5% the area. The questions come from the exercise section of some old text-book I was reading over the weekend.
"use strict";
class vec2d
{
constructor(x=0,y=0)
{
this.x = x;
this.y = y;
}
abs()
{
this.x = Math.abs(this.x);
this.y = Math.abs(this.y);
return this;
}
add(vec1)
{
return new vec2d(this.x+vec1.x, this.y+vec1.y);
}
sub(vec1)
{
return new vec2d(this.x-vec1.x, this.y-vec1.y);
}
mul(scalar)
{
return new vec2d(this.x*scalar, this.y*scalar);
}
plusEquals(vec1)
{
this.x += vec1.x;
this.y += vec1.y;
return this;
}
minusEquals(vec1)
{
this.x -= vec1.x;
this.y -= vec1.y;
return this;
}
timesEquals(scalar)
{
this.x *= scalar;
this.y *= scalar;
return this;
}
divByEquals(scalar)
{
this.x /= scalar;
this.y /= scalar;
return this;
}
normalize()
{
var len = this.length;
this.x /= len;
this.y /= len;
return this;
}
get length()
{
//return Math.sqrt( (this.x*this.x)+(this.y*this.y) );
return Math.hypot( this.x, this.y );
}
set length(newLen)
{
var invLen = newLen / this.length;
this.timesEquals(invLen);
}
dotProd(vec1)
{
return this.x*vec1.x + this.y*vec1.y;
}
perp()
{
var tmp = this.x;
this.x = -this.y;
this.y = tmp;
return this;
}
wedge(other)
{ // computes an area for parallelograms
return this.x*other.y - this.y*other.x;
}
static clone(other)
{
var result = new vec2d(other.x, other.y);
return result;
}
clone() // clone self
{
return vec2d.clone(this);
}
setTo(other)
{
this.x = other.x;
this.y = other.y;
}
get(){ return {x:this.x, y:this.y}; }
toString(){ return `vec2d {x: ${this.x}, y: ${this.y}}` }
toStringN(n){ return `vec2d {x: ${this.x.toFixed(n)}, y: ${this.y.toFixed(n)}}` }
print(){console.log(this.toString())}
};
class mat3
{
static clone(other)
{
var result = new mat3();
other.elems.forEach(
function(el, index, collection)
{
result.elems[index] = el;
}
);
return result;
}
clone()
{
return mat3.clone(this);
}
constructor(a,b,c,d,e,f)
{
if (arguments.length < 6)
this.setIdentity();
else
this.elems = [a,b,0,c,d,0,e,f,1];
}
setIdentity()
{
this.elems = [1,0,0, 0,1,0, 0,0,1];
}
multiply(other, shouldPrepend)
{
var a, b, c = new mat3();
if (shouldPrepend === true)
{
a = other;
b = this;
}
else
{
a = this;
b = other;
}
c.elems[0] = a.elems[0]*b.elems[0] + a.elems[1]*b.elems[3] + a.elems[2]*b.elems[6];
c.elems[1] = a.elems[0]*b.elems[1] + a.elems[1]*b.elems[4] + a.elems[2]*b.elems[7];
c.elems[2] = a.elems[0]*b.elems[2] + a.elems[1]*b.elems[5] + a.elems[2]*b.elems[8];
// row 1
c.elems[3] = a.elems[3]*b.elems[0] + a.elems[4]*b.elems[3] + a.elems[5]*b.elems[6];
c.elems[4] = a.elems[3]*b.elems[1] + a.elems[4]*b.elems[4] + a.elems[5]*b.elems[7];
c.elems[5] = a.elems[3]*b.elems[2] + a.elems[4]*b.elems[5] + a.elems[5]*b.elems[8];
// row 2
c.elems[6] = a.elems[6]*b.elems[0] + a.elems[7]*b.elems[3] + a.elems[8]*b.elems[6];
c.elems[7] = a.elems[6]*b.elems[1] + a.elems[7]*b.elems[4] + a.elems[8]*b.elems[7];
c.elems[8] = a.elems[6]*b.elems[2] + a.elems[7]*b.elems[5] + a.elems[8]*b.elems[8];
for (var i=0; i<9; i++)
this.elems[i] = c.elems[i];
}
transformVec2s(pointList)
{
var i, n = pointList.length;
for (i=0; i<n; i++)
{
var x = pointList[i].x*this.elems[0] + pointList[i].y*this.elems[3] + this.elems[6];
var y = pointList[i].x*this.elems[1] + pointList[i].y*this.elems[4] + this.elems[7];
pointList[i].x = x;
pointList[i].y = y;
}
}
makeTransformedPoints(pointList)
{
var result = [];
for (var i=0,n=pointList.length;i<n;i++)
{
var x = pointList[i].x*this.elems[0] + pointList[i].y*this.elems[3] + this.elems[6];
var y = pointList[i].x*this.elems[1] + pointList[i].y*this.elems[4] + this.elems[7];
result.push( new vec2d(x,y) );
}
return result;
}
rotate(degrees, shouldPrepend)
{
var tmp = new mat3();
tmp.elems[0] = Math.cos( degrees/180.0 * Math.PI );
tmp.elems[1] = -Math.sin( degrees/180.0 * Math.PI );
tmp.elems[3] = -tmp.elems[1];
tmp.elems[4] = tmp.elems[0];
this.multiply(tmp, shouldPrepend);
}
scaleEach(scaleX, scaleY, shouldPrepend)
{
var tmp = new mat3();
tmp.elems[0] = scaleX;
tmp.elems[4] = scaleY;
this.multiply(tmp, shouldPrepend);
}
scaleBoth(scaleAmount, shouldPrepend)
{
var tmp = new mat3();
tmp.elems[0] = scaleAmount;
tmp.elems[4] = scaleAmount;
this.multiply(tmp, shouldPrepend);
}
translate(transX, transY, shouldPrepend)
{
var tmp = new mat3();
tmp.elems[6] = transX;
tmp.elems[7] = transY;
this.multiply(tmp, shouldPrepend);
}
determinant()
{
var result, a, b;
a = ( (this.elems[0]*this.elems[4]*this.elems[8])
+ (this.elems[1]*this.elems[5]*this.elems[6])
+ (this.elems[2]*this.elems[3]*this.elems[7]) );
b = ( (this.elems[2]*this.elems[4]+this.elems[6])
+ (this.elems[1]*this.elems[3]+this.elems[8])
+ (this.elems[0]*this.elems[5]+this.elems[7]) );
result = a - b;
return result;
}
isInvertible()
{
return (this.determinant() != 0);
}
invert()
{
var det = this.determinant();
if (det == 0)
return;
var a,b,c,d,e,f,g,h,i;
a = this.elems[0]; b = this.elems[1]; c = this.elems[2];
d = this.elems[3]; e = this.elems[4]; f = this.elems[5];
g = this.elems[6]; h = this.elems[7]; i = this.elems[8];
this.elems[0] = (e*i - f*h); this.elems[1] = -((b*i) - (c*h)); this.elems[2] = (b*f)-(c*e);
this.elems[3] = -(d*i - f*g); this.elems[4] = (a*i) - (c*g); this.elems[5] = -( (a*f) - (c*d) );
this.elems[6] = (d*h - e*g); this.elems[7] = -((a*h) - (b*g)); this.elems[8] = (a*e)-(b*d);
var detInv = 1.0 / det;
for (var i=0; i<9; i++)
this.elems[i] *= detInv;
return this;
}
reset()
{
this.setIdentity();
}
print()
{
var str = '';
for (var i=0; i<9; i++)
{
if (i && i%3==0)
str += "\n";
str += " " + this.elems[i].toFixed(5);
}
console.log(str);
}
}
function byId(id){return document.getElementById(id)}
function newEl(tag){return document.createElement(tag)}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt)
{
byId('output').addEventListener('mousemove', onMouseMove, false);
byId('slider1').addEventListener('input', onSliderInput, false);
byId('slider2').addEventListener('input', onSliderInput, false);
draw();
}
//(400-48)/400 = 0.88
var invMat, svgInvMat;
function onMouseMove(evt)
{
var mousePos = new vec2d(evt.offsetX,evt.offsetY);
var worldPos = mousePos.clone();
invMat.transformVec2s( [worldPos] );
byId('screenMouse').textContent = `screen: ${mousePos.x},${mousePos.y}`;
byId('worldMouse').textContent = `world: ${worldPos.x.toFixed(1)}, ${worldPos.y.toFixed(1)}`;
}
function onSliderInput(evt)
{
draw();
}
function updateSliderLabels()
{
byId('ofset1Output').textContent = byId('slider1').value;
byId('ofset2Output').textContent = byId('slider2').value;
}
function draw()
{
var can = byId('output');
var ctx = can.getContext('2d');
ctx.clearRect(0,0,can.width,can.height);
var orientMat = evaluateViewOrientationMatrix(0.06*can.width,can.height-24, 0,-1);
var scaleMat = computeWindowToViewPortMatrix(2052,1317, can.width,can.height);
var viewMat = scaleMat.clone();
viewMat.multiply(orientMat);
console.log('viewMat');
viewMat.print();
invMat = viewMat.clone().invert();
for (var i=0; i<9; i++)
invMat.elems[i] /= invMat.elems[8];
ctx.strokeStyle = '#fff';
var axisPts = [ new vec2d(0,1070), new vec2d(0,0), new vec2d(0.88*2052,0) ]; // xAxis line 88% of image width
var axis = viewMat.makeTransformedPoints(axisPts);
drawLine(axis[0].x,axis[0].y, axis[1].x,axis[1].y, ctx);
drawLine(axis[1].x,axis[1].y, axis[2].x,axis[2].y, ctx);
var lineEnds = [new vec2d(330,263), new vec2d(1455,809)];
var pts2 = viewMat.makeTransformedPoints(lineEnds);
drawCircle(pts2[0].x,pts2[0].y, 4, ctx);
drawCircle(pts2[1].x,pts2[1].y, 4, ctx);
drawLine(pts2[0].x,pts2[0].y, pts2[1].x,pts2[1].y, ctx);
var rawP3 = calcOffsetCoords(lineEnds[0].x,lineEnds[0].y, lineEnds[1].x,lineEnds[1].y, byId('slider1').value);
var rawP4 = calcOffsetCoords(lineEnds[1].x,lineEnds[1].y, lineEnds[0].x,lineEnds[0].y, byId('slider2').value);
var ofsPts = viewMat.makeTransformedPoints( [rawP3, rawP4] );
drawCircle(ofsPts[0].x,ofsPts[0].y, 4, ctx);
drawCircle(ofsPts[1].x,ofsPts[1].y, 4, ctx);
updateSliderLabels();
}
function calcOffsetCoords(x1,y1, x2,y2, offset)
{
var dx = x2 - x1;
var dy = y2 - y1;
var lineLen = Math.hypot(dx, dy);
var normDx=0, normDy=0;
if (lineLen != 0)
{
normDx = dx / lineLen;
normDy = dy / lineLen;
}
var resultX = x1 + (offset * normDx);
var resultY = y1 + (offset * normDy);
return {x:resultX,y:resultY};//new vec2d(resultX,resultY); //{x:resultX,y:resultY};
}
// Exercise 6-1:
// Write a procedure to implement the evaluateViewOrientationMatrix function that calculates the elements of the
// matrix for transforming world coordinates to viewing coordinates, given the viewing coordinate origin Porigin and
// the viewUp vector
function evalViewOrientMatrix(screenOriginX,screenOriginY, worldUpVectorX,worldUpVectorY)
{
var worldUp = {x: worldUpVectorX, y: worldUpVectorY};
var len = Math.hypot(worldUp.x, worldUp.y);
if (len != 0)
len = 1.0 / len;
worldUp.x *= len;
worldUp.y *= len;
var worldRight = {x: worldUp.y, y: -worldUp.x};
var rotMat = svg.createSVGMatrix();
rotMat.a = worldRight.x;
rotMat.b = worldRight.y;
rotMat.c = worldUp.x;
rotMat.d = worldUp.y;
var transMat = svg.createSVGMatrix();
transMat = transMat.translate(screenOriginX, screenOriginY);
var result = rotMat.multiply(transMat);
return result;
}
function evaluateViewOrientationMatrix(screenOriginX,screenOriginY, worldUpVectorX,worldUpVectorY)
{
var worldUp = new vec2d(worldUpVectorX, worldUpVectorY);
worldUp.normalize();
var worldRight = worldUp.clone().perp();
var rotMat = new mat3();
rotMat.elems[0] = worldRight.x; rotMat.elems[1] = worldRight.y;
rotMat.elems[3] = worldUp.x; rotMat.elems[4] = worldUp.y;
var transMat = new mat3();
transMat.translate(screenOriginX,screenOriginY);
var result = rotMat.clone();
result.multiply(transMat);
return result;
}
/*
0 1 2
3 4 5
6 7 8
translation
-----------
1 0 0
0 1 0
tX tY 1
scaling
---------
sX 0 0
0 sY 0
0 0 1
rotation
--------
cosX -sinX 0
sinX cosX 0
0 0 1
*/
// Exercise 6-2:
// Derive the window to viewport transformation equations 6-3 by first scaling the window to
// the size of the viewport and then translating the scaled window to the viewport position
function computeWindowToViewPortMatrix(windowWidth,windowHeight,viewPortWidth,viewPortHeight)
{
var result = new mat3();
result.scaleEach(viewPortWidth/windowWidth,viewPortHeight/windowHeight);
return result;
}
// returns an SVGMatrix
function compWnd2ViewMat(windowWidth,windowHeight,viewPortWidth,viewPortHeight)
{
var result = svg.createSVGMatrix();
return result.scaleNonUniform(viewPortWidth/windowWidth,viewPortHeight/windowHeight);
}
function drawLine(x1,y1,x2,y2,ctx)
{
ctx.beginPath();
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.stroke();
}
function drawCircle(x,y,radius,ctx)
{
ctx.beginPath();
ctx.arc(x, y, radius, 0, (Math.PI/180)*360, false);
ctx.stroke();
ctx.closePath();
}
canvas
{
background-color: black;
}
.container
{
display: inline-block;
background-color: #888;
border: solid 4px #555;
}
#screenMouse, #worldMouse, .control
{
display: inline-block;
width: calc(513px/2 - 2*8px);
margin-left: 8px;
}
<body>
<div class='container'>
<canvas id='output' width='513' height='329'></canvas><br>
<div id='screenMouse'></div><div id='worldMouse'></div>
<div>
<div class='control'>P2 ofs: <input id='slider1' type='range' min='0' max='500' value='301'><span id='ofset1Output'></span></div>
<div class='control'>P3 ofs: <input id='slider2' type='range' min='0' max='500' value='285'><span id='ofset2Output'></span></div>
</div>
</div>
</body>
I must be going crazy. Coffeescript is continualy returning '0' for the difference between two non-equal integers.
x = #location[0]
y = #start[0]
console.log "#{x} - #{y} = #{x-y}"
This is outputting:
350 - 322 = 0
250 - 278 = 0
... and so on
I'm completely confused!
Edit: Here's the source in both coffee and the compiled js
class Branch
constructor: (#length, #start, #angle, #generation, #parent) ->
return if #generation >= RECURSION_LIMIT
#angleVariation = pi/3
#ratio = 2/(1+root(5))
#maxChildren = 10
#parent ?= null
#unit = 2
#children = []
#location = #start
#childLocations = [Math.random()*#length*#ratio/2+(#length*#ratio) \
for n in [0...#maxChildren]]
grow: ->
gl.beginPath()
moveTo #location
#location[0] += #unit * cos(#angle)
#location[1] -= #unit * sin(#angle)
lineTo #location
gl.stroke()
console.log #getLength()
if #getLength() >= #childLocations[#children.length]
#birthChild()
getLength: ->
x = #location[1]
y = #start[1]
console.log "#{x} - #{y} = #{x-y}"
return 1 #root( (#location[0]-#start[0])**2 + (#location[1]-#start[1])**2 )
birthChild: ->
angle = #angle + (Math.random()*#angleVariation*2) - #angleVariation
child = new Branch #length * #ratio, #location, angle, #generation+1, this
And in js:
Branch = (function() {
function Branch(length, start, angle, generation, parent) {
var n, _ref;
this.length = length;
this.start = start;
this.angle = angle;
this.generation = generation;
this.parent = parent;
if (this.generation >= RECURSION_LIMIT) {
return;
}
this.angleVariation = pi / 3;
this.ratio = 2 / (1 + root(5));
this.maxChildren = 10;
if ((_ref = this.parent) == null) {
this.parent = null;
}
this.unit = 2;
this.children = [];
this.location = this.start;
this.childLocations = [
(function() {
var _i, _ref1, _results;
_results = [];
for (n = _i = 0, _ref1 = this.maxChildren; 0 <= _ref1 ? _i < _ref1 : _i > _ref1; n = 0 <= _ref1 ? ++_i : --_i) {
_results.push(Math.random() * this.length * this.ratio / 2 + (this.length * this.ratio));
}
return _results;
}).call(this)
];
}
Branch.prototype.grow = function() {
gl.beginPath();
moveTo(this.location);
this.location[0] += this.unit * cos(this.angle);
this.location[1] -= this.unit * sin(this.angle);
lineTo(this.location);
gl.stroke();
console.log(this.getLength());
if (this.getLength() >= this.childLocations[this.children.length]) {
return this.birthChild();
}
};
Branch.prototype.getLength = function() {
var x, y;
x = this.location[1];
y = this.start[1];
console.log("" + x + " - " + y + " = " + (x - y));
return 1;
};
Branch.prototype.birthChild = function() {
var angle, child;
angle = this.angle + (Math.random() * this.angleVariation * 2) - this.angleVariation;
return child = new Branch(this.length * this.ratio, this.location, angle, this.generation + 1, this);
};
return Branch;
})();
So the createImageRegion method ignores all pixels with a tiny bit of alpha/opacity.
How can you make this function so that also pixels with an opacity of .5 or something will be count for hitdetection?
I looked into KineticJS. A colorKey is added to a hitregion, but it transforms the key to a hex key with no alpha. I can't figure out a way how i could make this work.
Help is much appriciated!
this is where the magic happens. But i don't understand how i can include pixels whith any kind of transpanacy but 0
createImageHitRegion: function (callback) {
var canvas = new Kinetic.Canvas(this.attrs.width, this.attrs.height);
var context = canvas.getContext();
context.drawImage(this.attrs.image, 0, 0);
try {
var imageData = context.getImageData(0, 0, canvas.getWidth(), canvas.getHeight());
var data = imageData.data;
var rgbColorKey = Kinetic.Type._hexToRgb(this.colorKey);
// replace non transparent pixels with color key
for (var i = 0, n = data.length; i < n; i += 4) {
data[i] = rgbColorKey.r;
data[i + 1] = rgbColorKey.g;
data[i + 2] = rgbColorKey.b;
// i+3 is alpha (the fourth element)
}
var that = this;
Kinetic.Type._getImage(imageData, function (imageObj) {
that.imageHitRegion = imageObj;
if (callback) {
callback();
}
});
}
catch (e) {
Kinetic.Global.warn('Unable to create image hit region. ' + e.message);
}
}
When i include: data[i + 3] = 255 (rgbColorKey.a) does not excist, none of the imageevents are working anymore
I found the answer:
for (var i = 0, n = data.length; i < n; i += 4) {
data[i] = rgbColorKey.r;
data[i + 1] = rgbColorKey.g;
data[i + 2] = rgbColorKey.b;
if (!ignoreAlpha && data[i + 3] > 0) {
data[i + 3] = 255;
}
}
This way, every pixel which has a bit of transparancy left, it will get a full color. So if you prototype this to kinetixJS it should work:
Kinetic.Image.prototype.createImageHitRegion = function (callback, ignoreTransparantPixels) {
var canvas = new Kinetic.Canvas(this.attrs.width, this.attrs.height);
var context = canvas.getContext();
var _ignoreTransparantPixels;
if(typeof ignoreTransparantPixels == 'undefined'){
_ignoreTransparantPixels = false;
}
context.drawImage(this.attrs.image, 0, 0);
try {
var imageData = context.getImageData(0, 0, canvas.getWidth(), canvas.getHeight());
var data = imageData.data;
var rgbColorKey = Kinetic.Type._hexToRgb(this.colorKey);
// replace non transparent pixels with color key
for (var i = 0, n = data.length; i < n; i += 4) {
data[i] = rgbColorKey.r;
data[i + 1] = rgbColorKey.g;
data[i + 2] = rgbColorKey.b;
if (!_ignoreTransparantPixels && data[i + 3] > 0) {
data[i + 3] = 255;
}
//trace(data[i + 3]);
// i+3 is alpha (the fourth element)
}
var that = this;
Kinetic.Type._getImage(imageData, function (imageObj) {
that.imageHitRegion = imageObj;
if (callback) {
callback();
}
});
}
catch (e) {
Kinetic.Global.warn('Unable to create image hit region. ' + e.message);
}
}