|
'use strict'; |
|
|
|
class PsycheCheckers{ |
|
|
|
constructor(opts){ |
|
if(!opts) opts = {}; |
|
opts = JSON.clone(opts); |
|
|
|
if(!Array.isArray(opts.seedPoints)){ |
|
/* |
|
seedPoints = [ |
|
[Math.random(), Math.random()], |
|
[Math.random(), Math.random()], |
|
]; |
|
*/ |
|
opts.seedPoints = [ |
|
[0.25, 1/3], |
|
[0.75, 2/3], |
|
]; |
|
} |
|
|
|
if(isNaN(opts.ticks)){ |
|
opts.ticks = 250; |
|
} |
|
opts.ticks = Math.round(opts.ticks); |
|
if(opts.ticks < 4) opts.ticks = 4; |
|
|
|
if(isNaN(opts.ratio)){ |
|
opts.ratio = 480/640; |
|
} |
|
opts.ticks = parseInt(opts.ticks,10); |
|
|
|
|
|
|
|
this.dirtyEvent(); |
|
|
|
this.opts = opts; |
|
this.innerPoints = JSON.clone(opts.seedPoints); |
|
this.ratio = opts.ratio; |
|
} |
|
|
|
get ratio(){ |
|
return this._ratio; |
|
} |
|
|
|
set ratio(newRatio){ |
|
if(isNaN(newRatio)) return; |
|
if(isNaN(this._ratio)) this._ratio = 0; |
|
newRatio = +newRatio; |
|
|
|
if(this._ratio === newRatio) return; |
|
|
|
this.dirtyEvent(); |
|
this._ratio = newRatio; |
|
} |
|
|
|
get outerPoints(){ |
|
if(this._outerPoints) return this._outerPoints; |
|
|
|
let p = []; |
|
|
|
let half = Math.ceil(this.opts.ticks / 2); |
|
let vertical = Math.round(half * this.ratio/(this.ratio + 1)); |
|
let horizontal = half - vertical; |
|
let dist = 0; |
|
|
|
dist = 1/horizontal; |
|
let tickCount = 0; |
|
for(let x=0; x < 1; x += dist){ |
|
p.push([x ,0 ,tickCount ]); |
|
p.push([1-x ,1 ,tickCount+half ]); |
|
tickCount ++; |
|
} |
|
dist = 1/vertical; |
|
for(let y=0; y < 1 ; y += dist){ |
|
p.push([0 , 1-y , tickCount+half ]); |
|
p.push([1 , y , tickCount ]); |
|
tickCount ++; |
|
} |
|
p.sort(function(a,b){ |
|
let diff = a[2] - b[2]; |
|
return diff; |
|
}); |
|
|
|
this._outerPoints = p; |
|
return this._outerPoints; |
|
} |
|
|
|
get lines(){ |
|
if(this._lines) return this._lines; |
|
|
|
let outer = this.outerPoints; |
|
this._lines = this.innerPoints.map(function(i){ |
|
let lines = outer |
|
.map(function(o){ |
|
return [i,o]; |
|
}); |
|
return lines; |
|
}); |
|
|
|
return this._lines; |
|
} |
|
|
|
|
|
/** |
|
* List of all planes formed on draw area |
|
* |
|
* |
|
*/ |
|
get planes(){ |
|
if(this._planes) return this._planes; |
|
|
|
let x = this.lines[0]; |
|
let y = this.lines[1]; |
|
|
|
function basePlaneParser(x,xpos){ |
|
let direction = -1; |
|
|
|
let testLine = new Segment([ |
|
innerPoint, |
|
outerPoints[lastX.segment[1][2]] |
|
]); |
|
moveLine('#cut',testLine.segment); |
|
if(testLine.findIntercept(x) && testLine.findIntercept(lastX)){ |
|
direction = -1; |
|
} |
|
else{ |
|
direction = +1; |
|
} |
|
|
|
let oldCutLine = [x.segment[1],lastX.segment[1]]; |
|
let radialPoint = Array(2).fill(lastX.segment[0]); |
|
let newCutLine = null; |
|
let o = x.segment[1][2]; |
|
if(direction < 0){ |
|
o=(direction+o)%outerPoints.length; |
|
} |
|
let ypos = -1; |
|
|
|
|
|
for(; newCutLine !== radialPoint; o=(o+direction)%outerPoints.length){ |
|
ypos ++; |
|
if(o < 0){ |
|
o += outerPoints.length; |
|
} |
|
testLine = new Segment([ |
|
innerPoint, |
|
outerPoints[o] |
|
]); |
|
moveLine('#oldCut',oldCutLine); |
|
moveLine('#cut',testLine.segment); |
|
newCutLine = [ |
|
testLine.findIntercept(x), |
|
testLine.findIntercept(lastX) |
|
]; |
|
if(newCutLine[0] === null || newCutLine[1] === null){ |
|
newCutLine = radialPoint; |
|
} |
|
let plane = [ |
|
oldCutLine[0], |
|
oldCutLine[1], |
|
newCutLine[1], |
|
newCutLine[0], |
|
oldCutLine[0], |
|
]; |
|
plane = plane.reduce(function(a,d){ |
|
d = JSON.stringify(d); |
|
if(a[0] !== d){ |
|
a.unshift(d); |
|
} |
|
return a; |
|
},[]) |
|
.map(function(d){ |
|
return JSON.parse(d); |
|
}); |
|
document.querySelectorAll('#debug > circle').forEach(function(d,i){ |
|
moveCircle(d,plane[i]); |
|
}); |
|
plane = { |
|
position:[xpos,o], |
|
intersection:[xpos,ypos], |
|
segments:plane, |
|
}; |
|
planes.push(plane); |
|
oldCutLine = newCutLine; |
|
} |
|
|
|
} |
|
|
|
// Because we are working in pairs of lines, we need to get two lines |
|
// for every cycle. The easiest way to acheive this is to just remember |
|
// the last line we used as we get the next. Unfortunately, that means |
|
// we need to do something special either the first or last item in |
|
// the list. |
|
// |
|
// In this case, we get the first item in the list and initialize our |
|
// trailing item tracker. Then we move it to the end of the list to be |
|
// reprocessed there. |
|
x = x.map(function(x){return new Segment(x)}); |
|
let firstX = x[x.length-1]; |
|
|
|
y = y.map(function(y){return new Segment(y)}); |
|
let firstY = y[y.length-1]; |
|
|
|
function specialPlaneParser(line1,line2,xpos){ |
|
|
|
let lastY = firstY; |
|
let A = line1; |
|
let B = line2; |
|
y.forEach(function(y,i){ |
|
moveLine("#oldCut",lastY.segment); |
|
moveLine("#cut",y.segment); |
|
moveLine("#X",B.segment); |
|
moveLine("#lastX",A.segment); |
|
|
|
// Sometimes, after many hours of attempting something clever |
|
// I give up, and apply brute force |
|
let plane =[]; |
|
let didAIntercept = false; |
|
let didBIntercept = false; |
|
let inter = y.findIntercept(A); |
|
if(inter){ |
|
didAIntercept = true; |
|
plane.push(inter); |
|
} |
|
else{ |
|
inter = y.findIntercept(B); |
|
if(inter){ |
|
didBIntercept = true; |
|
plane.push(inter); |
|
} |
|
} |
|
plane.push(innerPoints[1]); |
|
inter = lastY.findIntercept(A); |
|
if(inter){ |
|
didAIntercept = true; |
|
plane.push(inter); |
|
} |
|
else{ |
|
inter = lastY.findIntercept(B); |
|
if(inter){ |
|
didBIntercept = true; |
|
plane.push(inter); |
|
} |
|
} |
|
if(didAIntercept && didBIntercept){ |
|
let isouter = plane.reduce(function(a,segment){ |
|
a.push(segment[0]); |
|
a.push(segment[1]); |
|
return a; |
|
},[]) |
|
.some(function(axis){ |
|
let bool = (axis === 0 || axis === 1); |
|
return bool; |
|
}) |
|
; |
|
if(!isouter){ |
|
plane.push(innerPoints[0]); |
|
} |
|
else{ |
|
if(isInsideTriangle(plane,innerPoints[0])){ |
|
plane = []; |
|
} |
|
} |
|
} |
|
|
|
plane = plane.filter(function(d){return d !== null;}); |
|
Array.from(document.querySelectorAll('circle')).forEach(function(d){ |
|
d.setAttribute('opacity',0); |
|
}); |
|
plane.forEach(function(p,i){ |
|
let circle = document.querySelector('#intercept'+(i+1)); |
|
moveCircle(circle,p); |
|
}); |
|
|
|
lastY = y; |
|
plane = { |
|
position:[xpos,i], |
|
intersection:[xpos,i], |
|
segments:plane, |
|
}; |
|
planes.push(plane); |
|
}); |
|
} |
|
|
|
let innerPoints = this.innerPoints; |
|
let innerPoint = this.innerPoints[1]; |
|
let outerPoints = this.outerPoints; |
|
|
|
|
|
function isInsideTriangle(triangle, point){ |
|
triangle = JSON.clone(triangle); |
|
triangle.push(triangle[0]); |
|
let segments = []; |
|
for(let i = 0; i<3; i ++){ |
|
segments.push(new Segment([ |
|
new Point(triangle[i]), |
|
new Point(triangle[i+1]) |
|
])); |
|
} |
|
|
|
point = new Point(point); |
|
moveLine('#lastX',segments[0].segment); |
|
moveLine('#X',segments[1].segment); |
|
moveLine('#oldCut',segments[2].segment); |
|
let isOutside = segments.some((segment,i)=>{ |
|
let validation = new Segment([ |
|
segment.midPoint, |
|
point |
|
]); |
|
moveLine('#cut',validation.segment); |
|
let isInside = segments.every(function(edge){ |
|
if(edge === segment){ |
|
return true; |
|
} |
|
if(validation.findIntercept(edge)){ |
|
return false; |
|
} |
|
return true; |
|
|
|
}); |
|
return !isInside; |
|
}); |
|
return !isOutside; |
|
} |
|
let lastX = firstX; |
|
let planes = []; |
|
x.forEach(function(x,xpos){ |
|
moveLine('#lastX',lastX.segment); |
|
moveLine('#X',x.segment); |
|
|
|
let triangle = lastX.segment.slice(0); |
|
triangle.push(x.segment[1]); |
|
if(isInsideTriangle(triangle, innerPoints[1])){ |
|
specialPlaneParser(x,lastX); |
|
} |
|
else{ |
|
basePlaneParser(x,xpos); |
|
} |
|
lastX = x; |
|
}) |
|
; |
|
|
|
this._planes = planes.filter(function(plane){ |
|
return plane.segments.length > 2; |
|
}); |
|
|
|
return this._planes; |
|
} |
|
|
|
|
|
dirtyEvent(){ |
|
this._ratio = 1; |
|
this._outerPoints = null; |
|
this._lines = null; |
|
} |
|
|
|
} |
|
|