diff --git a/three.js/examples/aruco.html b/three.js/examples/aruco.html
new file mode 100644
index 0000000000000000000000000000000000000000..3e7328fb9b9eb4ecfa92febd54180a152c97a5fc
--- /dev/null
+++ b/three.js/examples/aruco.html
@@ -0,0 +1,181 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/three.js/examples/threex-aruco/Makefile b/three.js/examples/threex-aruco/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..31c6369058e58d3688d393b676bac29a7bb42864
--- /dev/null
+++ b/three.js/examples/threex-aruco/Makefile
@@ -0,0 +1,16 @@
+watch: build
+ fswatch -0 three.js/*.js | xargs -0 -n 1 -I {} make build
+
+.PHONY: build
+build:
+ cat vendor/js-aruco/src/aruco.js \
+ vendor/js-aruco/src/cv.js \
+ vendor/js-aruco/src/posit1.js \
+ vendor/js-aruco/src/svd.js \
+ threex-*.js > build/threex-aruco.js
+
+minify: build
+ uglifyjs build/threex-aruco.js > build/threex-aruco-min.js
+
+prepack: build
+ prepack build/threex-aruco.js --out build/threex-aruco-prepacked.js
diff --git a/three.js/examples/threex-aruco/README.md b/three.js/examples/threex-aruco/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..fa27265756eeaad976bde83dc9c20a68df432862
--- /dev/null
+++ b/three.js/examples/threex-aruco/README.md
@@ -0,0 +1 @@
+Trying to use js-aruco as ar.js backend
diff --git a/three.js/examples/threex-aruco/TODO.md b/three.js/examples/threex-aruco/TODO.md
new file mode 100644
index 0000000000000000000000000000000000000000..2164011b16a643d0ab753c1aa21b7cd754b648b1
--- /dev/null
+++ b/three.js/examples/threex-aruco/TODO.md
@@ -0,0 +1,30 @@
+- expose all the hardcoded parameters from AR.Detect in the debug,html
+ - thus you can tune them
+
+---
+
+# Performances
+- jsaruco use a kernel size of 2 in adaptative thresholding
+ - could i reduce the resolution of the source image and use a kernel size of 1 ?
+ - it would produce more fps. what the difference would be ? create errors ?
+- jsaruco - adaptiveThreshold is doing it on ALL bytes - so all channel ??? check if it is correct
+ - it use blackwhite image - it only needs 1 channel - 8 bits is already a lot to store blackwhite
+ - this mean 4 times more work than needed
+ - NOTES: unclear this is true - grayscale is packing it all in 1 channel. check it out ?
+- in posit1: image difference is computed by a manathan distance. not a euclidian distance... how come ?
+ - this seems to intrduce an error without need (sqrt is nothing in js)
+ - ```imageDifference += Math.abs(sopImagePoints[i].x - oldSopImagePoints[i].x);```
+ - ```imageDifference += Math.abs(sopImagePoints[i].y - oldSopImagePoints[i].y);```
+
+# How to include it in AR.js
+- see how to include it in ar.js
+ - so basically a context and a controls
+ - the source can remain the same without trouble
+- how to handle all the options between the various backend
+
+- how to handle the development ?
+ - do you copy the files ?
+ - do i modify the master files
+
+- seems to have only a few modifications
+ -
diff --git a/three.js/examples/threex-aruco/build/threex-aruco-min.js b/three.js/examples/threex-aruco/build/threex-aruco-min.js
new file mode 100644
index 0000000000000000000000000000000000000000..c3da40fb6040dc946f29685f35fd4f1536dd55a6
--- /dev/null
+++ b/three.js/examples/threex-aruco/build/threex-aruco-min.js
@@ -0,0 +1,2 @@
+var AR=AR||{};AR.Marker=function(id,corners){this.id=id;this.corners=corners};AR.Detector=function(){this.grey=new CV.Image;this.thres=new CV.Image;this.homography=new CV.Image;this.binary=[];this.contours=[];this.polys=[];this.candidates=[]};AR.Detector.prototype.detect=function(image){CV.grayscale(image,this.grey);CV.adaptiveThreshold(this.grey,this.thres,2,7);this.contours=CV.findContours(this.thres,this.binary);this.candidates=this.findCandidates(this.contours,image.width*.2,.05,10);this.candidates=this.clockwiseCorners(this.candidates);this.candidates=this.notTooNear(this.candidates,10);return this.findMarkers(this.grey,this.candidates,49)};AR.Detector.prototype.findCandidates=function(contours,minSize,epsilon,minLength){var candidates=[],len=contours.length,contour,poly,i;this.polys=[];for(i=0;i=minSize){poly=CV.approxPolyDP(contour,contour.length*epsilon);this.polys.push(poly);if(4===poly.length&&CV.isContourConvex(poly)){if(CV.minEdgeLength(poly)>=minLength){candidates.push(poly)}}}}return candidates};AR.Detector.prototype.clockwiseCorners=function(candidates){var len=candidates.length,dx1,dx2,dy1,dy2,swap,i;for(i=0;i>>0,minZero=width*width>>1,bits=[],rotations=[],distances=[],square,pair,inc,i,j;for(i=0;i<7;++i){inc=0===i||6===i?1:6;for(j=0;j<7;j+=inc){square={x:j*width,y:i*width,width:width,height:width};if(CV.countNonZero(imageSrc,square)>minZero){return null}}}for(i=0;i<5;++i){bits[i]=[];for(j=0;j<5;++j){square={x:(j+1)*width,y:(i+1)*width,width:width,height:width};bits[i][j]=CV.countNonZero(imageSrc,square)>minZero?1:0}}rotations[0]=bits;distances[0]=this.hammingDistance(rotations[0]);pair={first:distances[0],second:0};for(i=1;i<4;++i){rotations[i]=this.rotate(rotations[i-1]);distances[i]=this.hammingDistance(rotations[i]);if(distances[i]max){max=between;threshold=i}}}return threshold};CV.stackBoxBlurMult=[1,171,205,293,57,373,79,137,241,27,391,357,41,19,283,265];CV.stackBoxBlurShift=[0,9,10,11,9,12,10,11,12,9,13,13,10,9,13,13];CV.BlurStack=function(){this.color=0;this.next=null};CV.stackBoxBlur=function(imageSrc,imageDst,kernelSize){var src=imageSrc.data,dst=imageDst.data,height=imageSrc.height,width=imageSrc.width,heightMinus1=height-1,widthMinus1=width-1,size=kernelSize+kernelSize+1,radius=kernelSize+1,mult=CV.stackBoxBlurMult[kernelSize],shift=CV.stackBoxBlurShift[kernelSize],stack,stackStart,color,sum,pos,start,p,x,y,i;stack=stackStart=new CV.BlurStack;for(i=1;i>>shift;p=x+radius;p=start+(p>>shift;p=y+radius;p=x+(p>1,cur,value,i,j,k;for(i=0;i=width){cur=pos}}else{cur=pos+k*width;if(i+k<0){cur=pos}else if(i+k>=height){cur=pos}}value+=kernel[limit+k]*src[cur]}dst[pos++]=horizontal?value:value+.5&255}}return imageDst};CV.gaussianKernel=function(kernelSize){var tab=[[1],[.25,.5,.25],[.0625,.25,.375,.25,.0625],[.03125,.109375,.21875,.28125,.21875,.109375,.03125]],kernel=[],center,sigma,scale2X,sum,x,i;if(kernelSize<=7&&kernelSize%2===1){kernel=tab[kernelSize>>1]}else{center=(kernelSize-1)*.5;sigma=.8+.3*(center-1);scale2X=-.5/(sigma*sigma);sum=0;for(i=0;i=1&&0===src[pos+1]){hole=true}if(outer||hole){++nbd;contours.push(CV.borderFollowing(src,pos,nbd,{x:j,y:i},hole,deltas))}}}}return contours};CV.borderFollowing=function(src,pos,nbd,point,hole,deltas){var contour=[],pos1,pos3,pos4,s,s_end,s_prev;contour.hole=hole;s=s_end=hole?0:4;do{s=s-1&7;pos1=pos+deltas[s];if(src[pos1]!==0){break}}while(s!==s_end);if(s===s_end){src[pos]=-nbd;contour.push({x:point.x,y:point.y})}else{pos3=pos;s_prev=s^4;while(true){s_end=s;do{pos4=pos3+deltas[++s]}while(src[pos4]===0);s&=7;if(s-1>>>0>>0){src[pos3]=-nbd}else if(src[pos3]===1){src[pos3]=nbd}contour.push({x:point.x,y:point.y});s_prev=s;point.x+=CV.neighborhood[s][0];point.y+=CV.neighborhood[s][1];if(pos4===pos&&pos3===pos1){break}pos3=pos4;s=s+4&7}}return contour};CV.neighborhood=[[1,0],[1,-1],[0,-1],[-1,-1],[-1,0],[-1,1],[0,1],[1,1]];CV.neighborhoodDeltas=function(width){var deltas=[],len=CV.neighborhood.length,i=0;for(;imax_dist){max_dist=dist;right_slice.start_index=j}}}if(max_dist<=epsilon){poly.push({x:start_pt.x,y:start_pt.y})}else{slice.start_index=k;slice.end_index=right_slice.start_index+=slice.start_index;right_slice.start_index-=right_slice.start_index>=len?len:0;right_slice.end_index=slice.start_index;if(right_slice.end_indexmax_dist){max_dist=dist;right_slice.start_index=i}}le_eps=max_dist*max_dist<=epsilon*(dx*dx+dy*dy)}if(le_eps){poly.push({x:start_pt.x,y:start_pt.y})}else{right_slice.end_index=slice.end_index;slice.end_index=right_slice.start_index;stack.push({start_index:right_slice.start_index,end_index:right_slice.end_index});stack.push({start_index:slice.start_index,end_index:slice.end_index})}}return poly};CV.warp=function(imageSrc,imageDst,contour,warpSize){var src=imageSrc.data,dst=imageDst.data,width=imageSrc.width,height=imageSrc.height,pos=0,sx1,sx2,dx1,dx2,sy1,sy2,dy1,dy2,p1,p2,p3,p4,m,r,s,t,u,v,w,x,y,i,j;m=CV.getPerspectiveTransform(contour,warpSize-1);r=m[8];s=m[2];t=m[5];for(i=0;i>>0;sx2=sx1===width-1?sx1:sx1+1;dx1=x-sx1;dx2=1-dx1;sy1=y>>>0;sy2=sy1===height-1?sy1:sy1+1;dy1=y-sy1;dy2=1-dy1;p1=p2=sy1*width;p3=p4=sy2*width;dst[pos++]=dy2*(dx2*src[p1+sx1]+dx1*src[p2+sx2])+dy1*(dx2*src[p3+sx1]+dx1*src[p4+sx2])&255}}imageDst.width=warpSize;imageDst.height=warpSize;return imageDst};CV.getPerspectiveTransform=function(src,size){var rq=CV.square2quad(src);rq[0]/=size;rq[1]/=size;rq[3]/=size;rq[4]/=size;rq[6]/=size;rq[7]/=size;return rq};CV.square2quad=function(src){var sq=[],px,py,dx1,dx2,dy1,dy2,den;px=src[0].x-src[1].x+src[2].x-src[3].x;py=src[0].y-src[1].y+src[2].y-src[3].y;if(0===px&&0===py){sq[0]=src[1].x-src[0].x;sq[1]=src[2].x-src[1].x;sq[2]=src[0].x;sq[3]=src[1].y-src[0].y;sq[4]=src[2].y-src[1].y;sq[5]=src[0].y;sq[6]=0;sq[7]=0;sq[8]=1}else{dx1=src[1].x-src[2].x;dx2=src[3].x-src[2].x;dy1=src[1].y-src[2].y;dy2=src[3].y-src[2].y;den=dx1*dy2-dx2*dy1;sq[6]=(px*dy2-dx2*py)/den;sq[7]=(dx1*py-px*dy1)/den;sq[8]=1;sq[0]=src[1].x-src[0].x+sq[6]*src[1].x;sq[1]=src[3].x-src[0].x+sq[7]*src[3].x;sq[2]=src[0].x;sq[3]=src[1].y-src[0].y+sq[6]*src[1].y;sq[4]=src[3].y-src[0].y+sq[7]*src[3].y;sq[5]=src[0].y}return sq};CV.isContourConvex=function(contour){var orientation=0,convex=true,len=contour.length,i=0,j=0,cur_pt,prev_pt,dxdy0,dydx0,dx0,dy0,dx,dy;prev_pt=contour[len-1];cur_pt=contour[0];dx0=cur_pt.x-prev_pt.x;dy0=cur_pt.y-prev_pt.y;for(;idxdy0?1:dydx0=0){q=(j0j0-i0i0+Math.sqrt(delta))/2}else{q=(j0j0-i0i0-Math.sqrt(delta))/2}if(q>=0){lambda=Math.sqrt(q);if(0===lambda){mu=0}else{mu=-i0j0/lambda}}else{lambda=Math.sqrt(-(i0j0*i0j0)/q);if(0===lambda){mu=Math.sqrt(i0i0-j0j0)}else{mu=-i0j0/lambda}}for(i=0;i<3;++i){ivec[i]=i0[i]+lambda*this.objectNormal[i];jvec[i]=j0[i]+mu*this.objectNormal[i]}scale=Math.sqrt(ivec[0]*ivec[0]+ivec[1]*ivec[1]+ivec[2]*ivec[2]);for(i=0;i<3;++i){row1[i]=ivec[i]/scale;row2[i]=jvec[i]/scale}row3[0]=row1[1]*row2[2]-row1[2]*row2[1];row3[1]=row1[2]*row2[0]-row1[0]*row2[2];row3[2]=row1[0]*row2[1]-row1[1]*row2[0];for(i=0;i<3;++i){rotation1[0][i]=row1[i];rotation1[1][i]=row2[i];rotation1[2][i]=row3[i]}for(i=0;i<3;++i){ivec[i]=i0[i]-lambda*this.objectNormal[i];jvec[i]=j0[i]-mu*this.objectNormal[i]}for(i=0;i<3;++i){row1[i]=ivec[i]/scale;row2[i]=jvec[i]/scale}row3[0]=row1[1]*row2[2]-row1[2]*row2[1];row3[1]=row1[2]*row2[0]-row1[0]*row2[2];row3[2]=row1[0]*row2[1]-row1[1]*row2[0];for(i=0;i<3;++i){rotation2[0][i]=row1[i];rotation2[1][i]=row2[i];rotation2[2][i]=row3[i]}translation[0]=imagePoints[0].x/scale;translation[1]=imagePoints[0].y/scale;translation[2]=this.focalLength/scale};POS.Posit.prototype.isValid=function(rotation,translation){var np=this.objectPoints.length,zmin=Infinity,i=0,zi;for(;i=0};POS.Posit.prototype.iterate=function(imagePoints,posRotation,posTranslation,rotation,translation){var np=this.objectPoints.length,oldSopImagePoints=[],sopImagePoints=[],rotation1=[[],[],[]],rotation2=[[],[],[]],translation1=[],translation2=[],converged=false,iteration=0,oldImageDifference,imageDifference,factor,error,error1,error2,delta,i,j;for(i=0;i=0&&error2.euclidean>=0){if(error2.euclidean=0){error=error2;for(i=0;i<3;++i){for(j=0;j<3;++j){rotation[i][j]=rotation2[i][j]}}}if(error2.euclidean<0&&error1.euclidean>=0){error=error1;for(i=0;i<3;++i){for(j=0;j<3;++j){rotation[i][j]=rotation1[i][j]}}}for(i=0;imaximum){maximum=Math.abs(errorvec[i][0])}if(Math.abs(errorvec[i][1])>maximum){maximum=Math.abs(errorvec[i][1])}}return{euclidean:euclidean/np,pixels:pixels,maximum:maximum}};POS.pseudoInverse=function(a,n,b){var w=[],v=[[],[],[]],s=[[],[],[]],wmax=0,cn=0,i,j,k;SVD.svdcmp(a,n,3,w,v);for(i=0;i<3;++i){if(w[i]>wmax){wmax=w[i]}}wmax*=.01;for(i=0;i<3;++i){if(w[i]=0;--i){if(i=0;--i){l=i+1;g=w[i];for(j=l;j=0;--k){for(its=1;its<=30;++its){flag=true;for(l=k;l>=0;--l){nm=l-1;if(Math.abs(rv1[l])+anorm===anorm){flag=false;break}if(Math.abs(w[nm])+anorm===anorm){break}}if(flag){c=0;s=1;for(i=l;i<=k;++i){f=s*rv1[i];if(Math.abs(f)+anorm===anorm){break}g=w[i];h=SVD.pythag(f,g);w[i]=h;h=1/h;c=g*h;s=-f*h;for(j=1;j<=m;++j){y=a[j][nm];z=a[j][i];a[j][nm]=y*c+z*s;a[j][i]=z*c-y*s}}}z=w[k];if(l===k){if(z<0){w[k]=-z;for(j=0;jbt){ct=bt/at;return at*Math.sqrt(1+ct*ct)}if(0===bt){return 0}ct=at/bt;return bt*Math.sqrt(1+ct*ct)};SVD.sign=function(a,b){return b>=0?Math.abs(a):-Math.abs(a)};var THREEx=THREEx||{};THREEx.ArucoContext=function(markerSize){this.canvas=document.createElement("canvas");this.canvas.width=80*4;this.canvas.height=60*4;var imageSmoothingEnabled=false;var context=this.canvas.getContext("2d");context.mozImageSmoothingEnabled=imageSmoothingEnabled;context.webkitImageSmoothingEnabled=imageSmoothingEnabled;context.msImageSmoothingEnabled=imageSmoothingEnabled;context.imageSmoothingEnabled=imageSmoothingEnabled;this.detector=new AR.Detector;this.posit=new POS.Posit(markerSize,this.canvas.width)};THREEx.ArucoContext.prototype.detect=function(videoElement){var _this=this;var canvas=this.canvas;var context=canvas.getContext("2d");context.drawImage(videoElement,0,0,canvas.width,canvas.height);var imageData=context.getImageData(0,0,canvas.width,canvas.height);var detectedMarkers=this.detector.detect(imageData);detectedMarkers.forEach(function(detectedMarker){var markerCorners=detectedMarker.corners;var poseCorners=new Array(markerCorners.length);for(var i=0;i= minSize) {
+ poly = _$0.CV.approxPolyDP(contour, contour.length * epsilon);
+ this.polys.push(poly);
+
+ if (4 === poly.length && _$0.CV.isContourConvex(poly)) {
+ if (_$0.CV.minEdgeLength(poly) >= minLength) {
+ candidates.push(poly);
+ }
+ }
+ }
+ }
+
+ return candidates;
+ }
+
+ function _6(candidates) {
+ var len = candidates.length,
+ dx1,
+ dx2,
+ dy1,
+ dy2,
+ swap,
+ i;
+
+ for (i = 0; i < len; ++i) {
+ dx1 = candidates[i][1].x - candidates[i][0].x;
+ dy1 = candidates[i][1].y - candidates[i][0].y;
+ dx2 = candidates[i][2].x - candidates[i][0].x;
+ dy2 = candidates[i][2].y - candidates[i][0].y;
+
+ if (dx1 * dy2 - dy1 * dx2 < 0) {
+ swap = candidates[i][1];
+ candidates[i][1] = candidates[i][3];
+ candidates[i][3] = swap;
+ }
+ }
+
+ return candidates;
+ }
+
+ function _7(candidates, minDist) {
+ var notTooNear = [],
+ len = candidates.length,
+ dist,
+ dx,
+ dy,
+ i,
+ j,
+ k;
+
+ for (i = 0; i < len; ++i) {
+ for (j = i + 1; j < len; ++j) {
+ dist = 0;
+
+ for (k = 0; k < 4; ++k) {
+ dx = candidates[i][k].x - candidates[j][k].x;
+ dy = candidates[i][k].y - candidates[j][k].y;
+ dist += dx * dx + dy * dy;
+ }
+
+ if (dist / 4 < minDist * minDist) {
+ if (_$0.CV.perimeter(candidates[i]) < _$0.CV.perimeter(candidates[j])) {
+ candidates[i].tooNear = true;
+ } else {
+ candidates[j].tooNear = true;
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < len; ++i) {
+ if (!candidates[i].tooNear) {
+ notTooNear.push(candidates[i]);
+ }
+ }
+
+ return notTooNear;
+ }
+
+ function _8(imageSrc, candidates, warpSize) {
+ var markers = [],
+ len = candidates.length,
+ candidate,
+ marker,
+ i;
+
+ for (i = 0; i < len; ++i) {
+ candidate = candidates[i];
+
+ _$0.CV.warp(imageSrc, this.homography, candidate, warpSize);
+
+ _$0.CV.threshold(this.homography, this.homography, _$0.CV.otsu(this.homography));
+
+ marker = this.getMarker(this.homography, candidate);
+
+ if (marker) {
+ markers.push(marker);
+ }
+ }
+
+ return markers;
+ }
+
+ function _9(imageSrc, candidate) {
+ var width = imageSrc.width / 7 >>> 0,
+ minZero = width * width >> 1,
+ bits = [],
+ rotations = [],
+ distances = [],
+ square,
+ pair,
+ inc,
+ i,
+ j;
+
+ for (i = 0; i < 7; ++i) {
+ inc = 0 === i || 6 === i ? 1 : 6;
+
+ for (j = 0; j < 7; j += inc) {
+ square = {
+ x: j * width,
+ y: i * width,
+ width: width,
+ height: width
+ };
+
+ if (_$0.CV.countNonZero(imageSrc, square) > minZero) {
+ return null;
+ }
+ }
+ }
+
+ for (i = 0; i < 5; ++i) {
+ bits[i] = [];
+
+ for (j = 0; j < 5; ++j) {
+ square = {
+ x: (j + 1) * width,
+ y: (i + 1) * width,
+ width: width,
+ height: width
+ };
+ bits[i][j] = _$0.CV.countNonZero(imageSrc, square) > minZero ? 1 : 0;
+ }
+ }
+
+ rotations[0] = bits;
+ distances[0] = this.hammingDistance(rotations[0]);
+ pair = {
+ first: distances[0],
+ second: 0
+ };
+
+ for (i = 1; i < 4; ++i) {
+ rotations[i] = this.rotate(rotations[i - 1]);
+ distances[i] = this.hammingDistance(rotations[i]);
+
+ if (distances[i] < pair.first) {
+ pair.first = distances[i];
+ pair.second = i;
+ }
+ }
+
+ if (0 !== pair.first) {
+ return null;
+ }
+
+ return new _$0.AR.Marker(this.mat2id(rotations[pair.second]), this.rotate2(candidate, 4 - pair.second));
+ }
+
+ function _a(bits) {
+ var ids = [[1, 0, 0, 0, 0], [1, 0, 1, 1, 1], [0, 1, 0, 0, 1], [0, 1, 1, 1, 0]],
+ dist = 0,
+ sum,
+ minSum,
+ i,
+ j,
+ k;
+
+ for (i = 0; i < 5; ++i) {
+ minSum = _$0.Infinity;
+
+ for (j = 0; j < 4; ++j) {
+ sum = 0;
+
+ for (k = 0; k < 5; ++k) {
+ sum += bits[i][k] === ids[j][k] ? 0 : 1;
+ }
+
+ if (sum < minSum) {
+ minSum = sum;
+ }
+ }
+
+ dist += minSum;
+ }
+
+ return dist;
+ }
+
+ function _b(bits) {
+ var id = 0,
+ i;
+
+ for (i = 0; i < 5; ++i) {
+ id <<= 1;
+ id |= bits[i][1];
+ id <<= 1;
+ id |= bits[i][3];
+ }
+
+ return id;
+ }
+
+ function _c(src) {
+ var dst = [],
+ len = src.length,
+ i,
+ j;
+
+ for (i = 0; i < len; ++i) {
+ dst[i] = [];
+
+ for (j = 0; j < src[i].length; ++j) {
+ dst[i][j] = src[src[i].length - j - 1][i];
+ }
+ }
+
+ return dst;
+ }
+
+ function _d(src, rotation) {
+ var dst = [],
+ len = src.length,
+ i;
+
+ for (i = 0; i < len; ++i) {
+ dst[i] = src[(rotation + i) % len];
+ }
+
+ return dst;
+ }
+
+ function _f(width, height, data) {
+ this.width = width || 0;
+ this.height = height || 0;
+ this.data = data || [];
+ }
+
+ function _g(imageSrc, imageDst) {
+ var src = imageSrc.data,
+ dst = imageDst.data,
+ len = src.length,
+ i = 0,
+ j = 0;
+
+ for (; i < len; i += 4) {
+ dst[j++] = src[i] * 0.299 + src[i + 1] * 0.587 + src[i + 2] * 0.114 + 0.5 & 0xff;
+ }
+
+ imageDst.width = imageSrc.width;
+ imageDst.height = imageSrc.height;
+ return imageDst;
+ }
+
+ function _h(imageSrc, imageDst, threshold) {
+ var src = imageSrc.data,
+ dst = imageDst.data,
+ len = src.length,
+ tab = [],
+ i;
+
+ for (i = 0; i < 256; ++i) {
+ tab[i] = i <= threshold ? 0 : 255;
+ }
+
+ for (i = 0; i < len; ++i) {
+ dst[i] = tab[src[i]];
+ }
+
+ imageDst.width = imageSrc.width;
+ imageDst.height = imageSrc.height;
+ return imageDst;
+ }
+
+ function _i(imageSrc, imageDst, kernelSize, threshold) {
+ var src = imageSrc.data,
+ dst = imageDst.data,
+ len = src.length,
+ tab = [],
+ i;
+
+ _$0.CV.stackBoxBlur(imageSrc, imageDst, kernelSize);
+
+ for (i = 0; i < 768; ++i) {
+ tab[i] = i - 255 <= -threshold ? 255 : 0;
+ }
+
+ for (i = 0; i < len; ++i) {
+ dst[i] = tab[src[i] - dst[i] + 255];
+ }
+
+ imageDst.width = imageSrc.width;
+ imageDst.height = imageSrc.height;
+ return imageDst;
+ }
+
+ function _j(imageSrc) {
+ var src = imageSrc.data,
+ len = src.length,
+ hist = [],
+ threshold = 0,
+ sum = 0,
+ sumB = 0,
+ wB = 0,
+ wF = 0,
+ max = 0,
+ mu,
+ between,
+ i;
+
+ for (i = 0; i < 256; ++i) {
+ hist[i] = 0;
+ }
+
+ for (i = 0; i < len; ++i) {
+ hist[src[i]]++;
+ }
+
+ for (i = 0; i < 256; ++i) {
+ sum += hist[i] * i;
+ }
+
+ for (i = 0; i < 256; ++i) {
+ wB += hist[i];
+
+ if (0 !== wB) {
+ wF = len - wB;
+
+ if (0 === wF) {
+ break;
+ }
+
+ sumB += hist[i] * i;
+ mu = sumB / wB - (sum - sumB) / wF;
+ between = wB * wF * mu * mu;
+
+ if (between > max) {
+ max = between;
+ threshold = i;
+ }
+ }
+ }
+
+ return threshold;
+ }
+
+ function _m() {
+ this.color = 0;
+ this.next = null;
+ }
+
+ function _n(imageSrc, imageDst, kernelSize) {
+ var src = imageSrc.data,
+ dst = imageDst.data,
+ height = imageSrc.height,
+ width = imageSrc.width,
+ heightMinus1 = height - 1,
+ widthMinus1 = width - 1,
+ size = kernelSize + kernelSize + 1,
+ radius = kernelSize + 1,
+ mult = _$0.CV.stackBoxBlurMult[kernelSize],
+ shift = _$0.CV.stackBoxBlurShift[kernelSize],
+ stack,
+ stackStart,
+ color,
+ sum,
+ pos,
+ start,
+ p,
+ x,
+ y,
+ i;
+ stack = stackStart = new _$0.CV.BlurStack();
+
+ for (i = 1; i < size; ++i) {
+ stack = stack.next = new _$0.CV.BlurStack();
+ }
+
+ stack.next = stackStart;
+ pos = 0;
+
+ for (y = 0; y < height; ++y) {
+ start = pos;
+ color = src[pos];
+ sum = radius * color;
+ stack = stackStart;
+
+ for (i = 0; i < radius; ++i) {
+ stack.color = color;
+ stack = stack.next;
+ }
+
+ for (i = 1; i < radius; ++i) {
+ stack.color = src[pos + i];
+ sum += stack.color;
+ stack = stack.next;
+ }
+
+ stack = stackStart;
+
+ for (x = 0; x < width; ++x) {
+ dst[pos++] = sum * mult >>> shift;
+ p = x + radius;
+ p = start + (p < widthMinus1 ? p : widthMinus1);
+ sum -= stack.color - src[p];
+ stack.color = src[p];
+ stack = stack.next;
+ }
+ }
+
+ for (x = 0; x < width; ++x) {
+ pos = x;
+ start = pos + width;
+ color = dst[pos];
+ sum = radius * color;
+ stack = stackStart;
+
+ for (i = 0; i < radius; ++i) {
+ stack.color = color;
+ stack = stack.next;
+ }
+
+ for (i = 1; i < radius; ++i) {
+ stack.color = dst[start];
+ sum += stack.color;
+ stack = stack.next;
+ start += width;
+ }
+
+ stack = stackStart;
+
+ for (y = 0; y < height; ++y) {
+ dst[pos] = sum * mult >>> shift;
+ p = y + radius;
+ p = x + (p < heightMinus1 ? p : heightMinus1) * width;
+ sum -= stack.color - dst[p];
+ stack.color = dst[p];
+ stack = stack.next;
+ pos += width;
+ }
+ }
+
+ return imageDst;
+ }
+
+ function _o(imageSrc, imageDst, imageMean, kernelSize) {
+ var kernel = _$0.CV.gaussianKernel(kernelSize);
+
+ imageDst.width = imageSrc.width;
+ imageDst.height = imageSrc.height;
+ imageMean.width = imageSrc.width;
+ imageMean.height = imageSrc.height;
+
+ _$0.CV.gaussianBlurFilter(imageSrc, imageMean, kernel, true);
+
+ _$0.CV.gaussianBlurFilter(imageMean, imageDst, kernel, false);
+
+ return imageDst;
+ }
+
+ function _p(imageSrc, imageDst, kernel, horizontal) {
+ var src = imageSrc.data,
+ dst = imageDst.data,
+ height = imageSrc.height,
+ width = imageSrc.width,
+ pos = 0,
+ limit = kernel.length >> 1,
+ cur,
+ value,
+ i,
+ j,
+ k;
+
+ for (i = 0; i < height; ++i) {
+ for (j = 0; j < width; ++j) {
+ value = 0.0;
+
+ for (k = -limit; k <= limit; ++k) {
+ if (horizontal) {
+ cur = pos + k;
+
+ if (j + k < 0) {
+ cur = pos;
+ } else if (j + k >= width) {
+ cur = pos;
+ }
+ } else {
+ cur = pos + k * width;
+
+ if (i + k < 0) {
+ cur = pos;
+ } else if (i + k >= height) {
+ cur = pos;
+ }
+ }
+
+ value += kernel[limit + k] * src[cur];
+ }
+
+ dst[pos++] = horizontal ? value : value + 0.5 & 0xff;
+ }
+ }
+
+ return imageDst;
+ }
+
+ function _q(kernelSize) {
+ var tab = [[1], [0.25, 0.5, 0.25], [0.0625, 0.25, 0.375, 0.25, 0.0625], [0.03125, 0.109375, 0.21875, 0.28125, 0.21875, 0.109375, 0.03125]],
+ kernel = [],
+ center,
+ sigma,
+ scale2X,
+ sum,
+ x,
+ i;
+
+ if (kernelSize <= 7 && kernelSize % 2 === 1) {
+ kernel = tab[kernelSize >> 1];
+ } else {
+ center = (kernelSize - 1.0) * 0.5;
+ sigma = 0.8 + 0.3 * (center - 1.0);
+ scale2X = -0.5 / (sigma * sigma);
+ sum = 0.0;
+
+ for (i = 0; i < kernelSize; ++i) {
+ x = i - center;
+ sum += kernel[i] = _$0.Math.exp(scale2X * x * x);
+ }
+
+ sum = 1 / sum;
+
+ for (i = 0; i < kernelSize; ++i) {
+ kernel[i] *= sum;
+ }
+ }
+
+ return kernel;
+ }
+
+ function _r(imageSrc, binary) {
+ var width = imageSrc.width,
+ height = imageSrc.height,
+ contours = [],
+ src,
+ deltas,
+ pos,
+ pix,
+ nbd,
+ outer,
+ hole,
+ i,
+ j;
+ src = _$0.CV.binaryBorder(imageSrc, binary);
+ deltas = _$0.CV.neighborhoodDeltas(width + 2);
+ pos = width + 3;
+ nbd = 1;
+
+ for (i = 0; i < height; ++i, pos += 2) {
+ for (j = 0; j < width; ++j, ++pos) {
+ pix = src[pos];
+
+ if (0 !== pix) {
+ outer = hole = false;
+
+ if (1 === pix && 0 === src[pos - 1]) {
+ outer = true;
+ } else if (pix >= 1 && 0 === src[pos + 1]) {
+ hole = true;
+ }
+
+ if (outer || hole) {
+ ++nbd;
+ contours.push(_$0.CV.borderFollowing(src, pos, nbd, {
+ x: j,
+ y: i
+ }, hole, deltas));
+ }
+ }
+ }
+ }
+
+ return contours;
+ }
+
+ function _s(src, pos, nbd, point, hole, deltas) {
+ var contour = [],
+ pos1,
+ pos3,
+ pos4,
+ s,
+ s_end,
+ s_prev;
+ contour.hole = hole;
+ s = s_end = hole ? 0 : 4;
+
+ do {
+ s = s - 1 & 7;
+ pos1 = pos + deltas[s];
+
+ if (src[pos1] !== 0) {
+ break;
+ }
+ } while (s !== s_end);
+
+ if (s === s_end) {
+ src[pos] = -nbd;
+ contour.push({
+ x: point.x,
+ y: point.y
+ });
+ } else {
+ pos3 = pos;
+ s_prev = s ^ 4;
+
+ while (true) {
+ s_end = s;
+
+ do {
+ pos4 = pos3 + deltas[++s];
+ } while (src[pos4] === 0);
+
+ s &= 7;
+
+ if (s - 1 >>> 0 < s_end >>> 0) {
+ src[pos3] = -nbd;
+ } else if (src[pos3] === 1) {
+ src[pos3] = nbd;
+ }
+
+ contour.push({
+ x: point.x,
+ y: point.y
+ });
+ s_prev = s;
+ point.x += _$0.CV.neighborhood[s][0];
+ point.y += _$0.CV.neighborhood[s][1];
+
+ if (pos4 === pos && pos3 === pos1) {
+ break;
+ }
+
+ pos3 = pos4;
+ s = s + 4 & 7;
+ }
+ }
+
+ return contour;
+ }
+
+ function _C(width) {
+ var deltas = [],
+ len = _$0.CV.neighborhood.length,
+ i = 0;
+
+ for (; i < len; ++i) {
+ deltas[i] = _$0.CV.neighborhood[i][0] + _$0.CV.neighborhood[i][1] * width;
+ }
+
+ return deltas.concat(deltas);
+ }
+
+ function _D(contour, epsilon) {
+ var slice = {
+ start_index: 0,
+ end_index: 0
+ },
+ right_slice = {
+ start_index: 0,
+ end_index: 0
+ },
+ poly = [],
+ stack = [],
+ len = contour.length,
+ pt,
+ start_pt,
+ end_pt,
+ dist,
+ max_dist,
+ le_eps,
+ dx,
+ dy,
+ i,
+ j,
+ k;
+ epsilon *= epsilon;
+ k = 0;
+
+ for (i = 0; i < 3; ++i) {
+ max_dist = 0;
+ k = (k + right_slice.start_index) % len;
+ start_pt = contour[k];
+
+ if (++k === len) {
+ k = 0;
+ }
+
+ for (j = 1; j < len; ++j) {
+ pt = contour[k];
+
+ if (++k === len) {
+ k = 0;
+ }
+
+ dx = pt.x - start_pt.x;
+ dy = pt.y - start_pt.y;
+ dist = dx * dx + dy * dy;
+
+ if (dist > max_dist) {
+ max_dist = dist;
+ right_slice.start_index = j;
+ }
+ }
+ }
+
+ if (max_dist <= epsilon) {
+ poly.push({
+ x: start_pt.x,
+ y: start_pt.y
+ });
+ } else {
+ slice.start_index = k;
+ slice.end_index = right_slice.start_index += slice.start_index;
+ right_slice.start_index -= right_slice.start_index >= len ? len : 0;
+ right_slice.end_index = slice.start_index;
+
+ if (right_slice.end_index < right_slice.start_index) {
+ right_slice.end_index += len;
+ }
+
+ stack.push({
+ start_index: right_slice.start_index,
+ end_index: right_slice.end_index
+ });
+ stack.push({
+ start_index: slice.start_index,
+ end_index: slice.end_index
+ });
+ }
+
+ while (stack.length !== 0) {
+ slice = stack.pop();
+ end_pt = contour[slice.end_index % len];
+ start_pt = contour[k = slice.start_index % len];
+
+ if (++k === len) {
+ k = 0;
+ }
+
+ if (slice.end_index <= slice.start_index + 1) {
+ le_eps = true;
+ } else {
+ max_dist = 0;
+ dx = end_pt.x - start_pt.x;
+ dy = end_pt.y - start_pt.y;
+
+ for (i = slice.start_index + 1; i < slice.end_index; ++i) {
+ pt = contour[k];
+
+ if (++k === len) {
+ k = 0;
+ }
+
+ dist = _$0.Math.abs((pt.y - start_pt.y) * dx - (pt.x - start_pt.x) * dy);
+
+ if (dist > max_dist) {
+ max_dist = dist;
+ right_slice.start_index = i;
+ }
+ }
+
+ le_eps = max_dist * max_dist <= epsilon * (dx * dx + dy * dy);
+ }
+
+ if (le_eps) {
+ poly.push({
+ x: start_pt.x,
+ y: start_pt.y
+ });
+ } else {
+ right_slice.end_index = slice.end_index;
+ slice.end_index = right_slice.start_index;
+ stack.push({
+ start_index: right_slice.start_index,
+ end_index: right_slice.end_index
+ });
+ stack.push({
+ start_index: slice.start_index,
+ end_index: slice.end_index
+ });
+ }
+ }
+
+ return poly;
+ }
+
+ function _E(imageSrc, imageDst, contour, warpSize) {
+ var src = imageSrc.data,
+ dst = imageDst.data,
+ width = imageSrc.width,
+ height = imageSrc.height,
+ pos = 0,
+ sx1,
+ sx2,
+ dx1,
+ dx2,
+ sy1,
+ sy2,
+ dy1,
+ dy2,
+ p1,
+ p2,
+ p3,
+ p4,
+ m,
+ r,
+ s,
+ t,
+ u,
+ v,
+ w,
+ x,
+ y,
+ i,
+ j;
+ m = _$0.CV.getPerspectiveTransform(contour, warpSize - 1);
+ r = m[8];
+ s = m[2];
+ t = m[5];
+
+ for (i = 0; i < warpSize; ++i) {
+ r += m[7];
+ s += m[1];
+ t += m[4];
+ u = r;
+ v = s;
+ w = t;
+
+ for (j = 0; j < warpSize; ++j) {
+ u += m[6];
+ v += m[0];
+ w += m[3];
+ x = v / u;
+ y = w / u;
+ sx1 = x >>> 0;
+ sx2 = sx1 === width - 1 ? sx1 : sx1 + 1;
+ dx1 = x - sx1;
+ dx2 = 1.0 - dx1;
+ sy1 = y >>> 0;
+ sy2 = sy1 === height - 1 ? sy1 : sy1 + 1;
+ dy1 = y - sy1;
+ dy2 = 1.0 - dy1;
+ p1 = p2 = sy1 * width;
+ p3 = p4 = sy2 * width;
+ dst[pos++] = dy2 * (dx2 * src[p1 + sx1] + dx1 * src[p2 + sx2]) + dy1 * (dx2 * src[p3 + sx1] + dx1 * src[p4 + sx2]) & 0xff;
+ }
+ }
+
+ imageDst.width = warpSize;
+ imageDst.height = warpSize;
+ return imageDst;
+ }
+
+ function _F(src, size) {
+ var rq = _$0.CV.square2quad(src);
+
+ rq[0] /= size;
+ rq[1] /= size;
+ rq[3] /= size;
+ rq[4] /= size;
+ rq[6] /= size;
+ rq[7] /= size;
+ return rq;
+ }
+
+ function _G(src) {
+ var sq = [],
+ px,
+ py,
+ dx1,
+ dx2,
+ dy1,
+ dy2,
+ den;
+ px = src[0].x - src[1].x + src[2].x - src[3].x;
+ py = src[0].y - src[1].y + src[2].y - src[3].y;
+
+ if (0 === px && 0 === py) {
+ sq[0] = src[1].x - src[0].x;
+ sq[1] = src[2].x - src[1].x;
+ sq[2] = src[0].x;
+ sq[3] = src[1].y - src[0].y;
+ sq[4] = src[2].y - src[1].y;
+ sq[5] = src[0].y;
+ sq[6] = 0;
+ sq[7] = 0;
+ sq[8] = 1;
+ } else {
+ dx1 = src[1].x - src[2].x;
+ dx2 = src[3].x - src[2].x;
+ dy1 = src[1].y - src[2].y;
+ dy2 = src[3].y - src[2].y;
+ den = dx1 * dy2 - dx2 * dy1;
+ sq[6] = (px * dy2 - dx2 * py) / den;
+ sq[7] = (dx1 * py - px * dy1) / den;
+ sq[8] = 1;
+ sq[0] = src[1].x - src[0].x + sq[6] * src[1].x;
+ sq[1] = src[3].x - src[0].x + sq[7] * src[3].x;
+ sq[2] = src[0].x;
+ sq[3] = src[1].y - src[0].y + sq[6] * src[1].y;
+ sq[4] = src[3].y - src[0].y + sq[7] * src[3].y;
+ sq[5] = src[0].y;
+ }
+
+ return sq;
+ }
+
+ function _H(contour) {
+ var orientation = 0,
+ convex = true,
+ len = contour.length,
+ i = 0,
+ j = 0,
+ cur_pt,
+ prev_pt,
+ dxdy0,
+ dydx0,
+ dx0,
+ dy0,
+ dx,
+ dy;
+ prev_pt = contour[len - 1];
+ cur_pt = contour[0];
+ dx0 = cur_pt.x - prev_pt.x;
+ dy0 = cur_pt.y - prev_pt.y;
+
+ for (; i < len; ++i) {
+ if (++j === len) {
+ j = 0;
+ }
+
+ prev_pt = cur_pt;
+ cur_pt = contour[j];
+ dx = cur_pt.x - prev_pt.x;
+ dy = cur_pt.y - prev_pt.y;
+ dxdy0 = dx * dy0;
+ dydx0 = dy * dx0;
+ orientation |= dydx0 > dxdy0 ? 1 : dydx0 < dxdy0 ? 2 : 3;
+
+ if (3 === orientation) {
+ convex = false;
+ break;
+ }
+
+ dx0 = dx;
+ dy0 = dy;
+ }
+
+ return convex;
+ }
+
+ function _I(poly) {
+ var len = poly.length,
+ i = 0,
+ j = len - 1,
+ p = 0.0,
+ dx,
+ dy;
+
+ for (; i < len; j = i++) {
+ dx = poly[i].x - poly[j].x;
+ dy = poly[i].y - poly[j].y;
+ p += _$0.Math.sqrt(dx * dx + dy * dy);
+ }
+
+ return p;
+ }
+
+ function _J(poly) {
+ var len = poly.length,
+ i = 0,
+ j = len - 1,
+ min = _$0.Infinity,
+ d,
+ dx,
+ dy;
+
+ for (; i < len; j = i++) {
+ dx = poly[i].x - poly[j].x;
+ dy = poly[i].y - poly[j].y;
+ d = dx * dx + dy * dy;
+
+ if (d < min) {
+ min = d;
+ }
+ }
+
+ return _$0.Math.sqrt(min);
+ }
+
+ function _K(imageSrc, square) {
+ var src = imageSrc.data,
+ height = square.height,
+ width = square.width,
+ pos = square.x + square.y * imageSrc.width,
+ span = imageSrc.width - width,
+ nz = 0,
+ i,
+ j;
+
+ for (i = 0; i < height; ++i) {
+ for (j = 0; j < width; ++j) {
+ if (0 !== src[pos++]) {
+ ++nz;
+ }
+ }
+
+ pos += span;
+ }
+
+ return nz;
+ }
+
+ function _L(imageSrc, dst) {
+ var src = imageSrc.data,
+ height = imageSrc.height,
+ width = imageSrc.width,
+ posSrc = 0,
+ posDst = 0,
+ i,
+ j;
+
+ for (j = -2; j < width; ++j) {
+ dst[posDst++] = 0;
+ }
+
+ for (i = 0; i < height; ++i) {
+ dst[posDst++] = 0;
+
+ for (j = 0; j < width; ++j) {
+ dst[posDst++] = 0 === src[posSrc++] ? 0 : 1;
+ }
+
+ dst[posDst++] = 0;
+ }
+
+ for (j = -2; j < width; ++j) {
+ dst[posDst++] = 0;
+ }
+
+ return dst;
+ }
+
+ function _N(modelSize, focalLength) {
+ this.objectPoints = this.buildModel(modelSize);
+ this.focalLength = focalLength;
+ this.objectVectors = [];
+ this.objectNormal = [];
+ this.objectMatrix = [[], [], []];
+ this.init();
+ }
+
+ var _O = _N.prototype;
+
+ function _P(a, n, b) {
+ var w = [],
+ v = [[], [], []],
+ s = [[], [], []],
+ wmax = 0.0,
+ cn = 0,
+ i,
+ j,
+ k;
+
+ _$0.SVD.svdcmp(a, n, 3, w, v);
+
+ for (i = 0; i < 3; ++i) {
+ if (w[i] > wmax) {
+ wmax = w[i];
+ }
+ }
+
+ wmax *= 0.01;
+
+ for (i = 0; i < 3; ++i) {
+ if (w[i] < wmax) {
+ w[i] = 0.0;
+ }
+ }
+
+ for (j = 0; j < 3; ++j) {
+ if (0.0 === w[j]) {
+ ++cn;
+
+ for (k = j; k < 2; ++k) {
+ for (i = 0; i < n; ++i) {
+ a[i][k] = a[i][k + 1];
+ }
+
+ for (i = 0; i < 3; ++i) {
+ v[i][k] = v[i][k + 1];
+ }
+ }
+ }
+ }
+
+ for (j = 0; j < 2; ++j) {
+ if (0.0 === w[j]) {
+ w[j] = w[j + 1];
+ }
+ }
+
+ for (i = 0; i < 3; ++i) {
+ for (j = 0; j < 3 - cn; ++j) {
+ s[i][j] = v[i][j] / w[j];
+ }
+ }
+
+ for (i = 0; i < 3; ++i) {
+ for (j = 0; j < n; ++j) {
+ b[i][j] = 0.0;
+
+ for (k = 0; k < 3 - cn; ++k) {
+ b[i][j] += s[i][k] * a[j][k];
+ }
+ }
+ }
+ }
+
+ function _Q(error1, rotation1, translation1, error2, rotation2, translation2) {
+ this.bestError = error1;
+ this.bestRotation = rotation1;
+ this.bestTranslation = translation1;
+ this.alternativeError = error2;
+ this.alternativeRotation = rotation2;
+ this.alternativeTranslation = translation2;
+ }
+
+ function _R(modelSize) {
+ var half = modelSize / 2.0;
+ return [[-half, half, 0.0], [half, half, 0.0], [half, -half, 0.0], [-half, -half, 0.0]];
+ }
+
+ function _S() {
+ var np = this.objectPoints.length,
+ vectors = [],
+ n = [],
+ len = 0.0,
+ row = 2,
+ i;
+
+ for (i = 0; i < np; ++i) {
+ this.objectVectors[i] = [this.objectPoints[i][0] - this.objectPoints[0][0], this.objectPoints[i][1] - this.objectPoints[0][1], this.objectPoints[i][2] - this.objectPoints[0][2]];
+ vectors[i] = [this.objectVectors[i][0], this.objectVectors[i][1], this.objectVectors[i][2]];
+ }
+
+ while (0.0 === len) {
+ n[0] = this.objectVectors[1][1] * this.objectVectors[row][2] - this.objectVectors[1][2] * this.objectVectors[row][1];
+ n[1] = this.objectVectors[1][2] * this.objectVectors[row][0] - this.objectVectors[1][0] * this.objectVectors[row][2];
+ n[2] = this.objectVectors[1][0] * this.objectVectors[row][1] - this.objectVectors[1][1] * this.objectVectors[row][0];
+ len = _$0.Math.sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]);
+ ++row;
+ }
+
+ for (i = 0; i < 3; ++i) {
+ this.objectNormal[i] = n[i] / len;
+ }
+
+ _$0.POS.pseudoInverse(vectors, np, this.objectMatrix);
+ }
+
+ function _T(imagePoints) {
+ var posRotation1 = [[], [], []],
+ posRotation2 = [[], [], []],
+ posTranslation = [],
+ rotation1 = [[], [], []],
+ rotation2 = [[], [], []],
+ translation1 = [],
+ translation2 = [],
+ error1,
+ error2,
+ valid1,
+ valid2,
+ i,
+ j;
+ this.pos(imagePoints, posRotation1, posRotation2, posTranslation);
+ valid1 = this.isValid(posRotation1, posTranslation);
+
+ if (valid1) {
+ error1 = this.iterate(imagePoints, posRotation1, posTranslation, rotation1, translation1);
+ } else {
+ error1 = {
+ euclidean: -1.0,
+ pixels: -1,
+ maximum: -1.0
+ };
+ }
+
+ valid2 = this.isValid(posRotation2, posTranslation);
+
+ if (valid2) {
+ error2 = this.iterate(imagePoints, posRotation2, posTranslation, rotation2, translation2);
+ } else {
+ error2 = {
+ euclidean: -1.0,
+ pixels: -1,
+ maximum: -1.0
+ };
+ }
+
+ for (i = 0; i < 3; ++i) {
+ for (j = 0; j < 3; ++j) {
+ if (valid1) {
+ translation1[i] -= rotation1[i][j] * this.objectPoints[0][j];
+ }
+
+ if (valid2) {
+ translation2[i] -= rotation2[i][j] * this.objectPoints[0][j];
+ }
+ }
+ }
+
+ return error1.euclidean < error2.euclidean ? new _$0.POS.Pose(error1.pixels, rotation1, translation1, error2.pixels, rotation2, translation2) : new _$0.POS.Pose(error2.pixels, rotation2, translation2, error1.pixels, rotation1, translation1);
+ }
+
+ function _U(imagePoints, rotation1, rotation2, translation) {
+ var np = this.objectPoints.length,
+ imageVectors = [],
+ i0 = [],
+ j0 = [],
+ ivec = [],
+ jvec = [],
+ row1 = [],
+ row2 = [],
+ row3 = [],
+ i0i0,
+ j0j0,
+ i0j0,
+ delta,
+ q,
+ lambda,
+ mu,
+ scale,
+ i,
+ j;
+
+ for (i = 0; i < np; ++i) {
+ imageVectors[i] = [imagePoints[i].x - imagePoints[0].x, imagePoints[i].y - imagePoints[0].y];
+ } //i0 and j0
+
+
+ for (i = 0; i < 3; ++i) {
+ i0[i] = 0.0;
+ j0[i] = 0.0;
+
+ for (j = 0; j < np; ++j) {
+ i0[i] += this.objectMatrix[i][j] * imageVectors[j][0];
+ j0[i] += this.objectMatrix[i][j] * imageVectors[j][1];
+ }
+ }
+
+ i0i0 = i0[0] * i0[0] + i0[1] * i0[1] + i0[2] * i0[2];
+ j0j0 = j0[0] * j0[0] + j0[1] * j0[1] + j0[2] * j0[2];
+ i0j0 = i0[0] * j0[0] + i0[1] * j0[1] + i0[2] * j0[2]; //Lambda and mu
+
+ delta = (j0j0 - i0i0) * (j0j0 - i0i0) + 4.0 * (i0j0 * i0j0);
+
+ if (j0j0 - i0i0 >= 0.0) {
+ q = (j0j0 - i0i0 + _$0.Math.sqrt(delta)) / 2.0;
+ } else {
+ q = (j0j0 - i0i0 - _$0.Math.sqrt(delta)) / 2.0;
+ }
+
+ if (q >= 0.0) {
+ lambda = _$0.Math.sqrt(q);
+
+ if (0.0 === lambda) {
+ mu = 0.0;
+ } else {
+ mu = -i0j0 / lambda;
+ }
+ } else {
+ lambda = _$0.Math.sqrt(-(i0j0 * i0j0) / q);
+
+ if (0.0 === lambda) {
+ mu = _$0.Math.sqrt(i0i0 - j0j0);
+ } else {
+ mu = -i0j0 / lambda;
+ }
+ } //First rotation
+
+
+ for (i = 0; i < 3; ++i) {
+ ivec[i] = i0[i] + lambda * this.objectNormal[i];
+ jvec[i] = j0[i] + mu * this.objectNormal[i];
+ }
+
+ scale = _$0.Math.sqrt(ivec[0] * ivec[0] + ivec[1] * ivec[1] + ivec[2] * ivec[2]);
+
+ for (i = 0; i < 3; ++i) {
+ row1[i] = ivec[i] / scale;
+ row2[i] = jvec[i] / scale;
+ }
+
+ row3[0] = row1[1] * row2[2] - row1[2] * row2[1];
+ row3[1] = row1[2] * row2[0] - row1[0] * row2[2];
+ row3[2] = row1[0] * row2[1] - row1[1] * row2[0];
+
+ for (i = 0; i < 3; ++i) {
+ rotation1[0][i] = row1[i];
+ rotation1[1][i] = row2[i];
+ rotation1[2][i] = row3[i];
+ } //Second rotation
+
+
+ for (i = 0; i < 3; ++i) {
+ ivec[i] = i0[i] - lambda * this.objectNormal[i];
+ jvec[i] = j0[i] - mu * this.objectNormal[i];
+ }
+
+ for (i = 0; i < 3; ++i) {
+ row1[i] = ivec[i] / scale;
+ row2[i] = jvec[i] / scale;
+ }
+
+ row3[0] = row1[1] * row2[2] - row1[2] * row2[1];
+ row3[1] = row1[2] * row2[0] - row1[0] * row2[2];
+ row3[2] = row1[0] * row2[1] - row1[1] * row2[0];
+
+ for (i = 0; i < 3; ++i) {
+ rotation2[0][i] = row1[i];
+ rotation2[1][i] = row2[i];
+ rotation2[2][i] = row3[i];
+ } //Translation
+
+
+ translation[0] = imagePoints[0].x / scale;
+ translation[1] = imagePoints[0].y / scale;
+ translation[2] = this.focalLength / scale;
+ }
+
+ function _V(rotation, translation) {
+ var np = this.objectPoints.length,
+ zmin = _$0.Infinity,
+ i = 0,
+ zi;
+
+ for (; i < np; ++i) {
+ zi = translation[2] + (rotation[2][0] * this.objectVectors[i][0] + rotation[2][1] * this.objectVectors[i][1] + rotation[2][2] * this.objectVectors[i][2]);
+
+ if (zi < zmin) {
+ zmin = zi;
+ }
+ }
+
+ return zmin >= 0.0;
+ }
+
+ function _W(imagePoints, posRotation, posTranslation, rotation, translation) {
+ var np = this.objectPoints.length,
+ oldSopImagePoints = [],
+ sopImagePoints = [],
+ rotation1 = [[], [], []],
+ rotation2 = [[], [], []],
+ translation1 = [],
+ translation2 = [],
+ converged = false,
+ iteration = 0,
+ oldImageDifference,
+ imageDifference,
+ factor,
+ error,
+ error1,
+ error2,
+ delta,
+ i,
+ j;
+
+ for (i = 0; i < np; ++i) {
+ oldSopImagePoints[i] = {
+ x: imagePoints[i].x,
+ y: imagePoints[i].y
+ };
+ }
+
+ for (i = 0; i < 3; ++i) {
+ for (j = 0; j < 3; ++j) {
+ rotation[i][j] = posRotation[i][j];
+ }
+
+ translation[i] = posTranslation[i];
+ }
+
+ for (i = 0; i < np; ++i) {
+ factor = 0.0;
+
+ for (j = 0; j < 3; ++j) {
+ factor += this.objectVectors[i][j] * rotation[2][j] / translation[2];
+ }
+
+ sopImagePoints[i] = {
+ x: (1.0 + factor) * imagePoints[i].x,
+ y: (1.0 + factor) * imagePoints[i].y
+ };
+ }
+
+ imageDifference = 0.0;
+
+ for (i = 0; i < np; ++i) {
+ imageDifference += _$0.Math.abs(sopImagePoints[i].x - oldSopImagePoints[i].x);
+ imageDifference += _$0.Math.abs(sopImagePoints[i].y - oldSopImagePoints[i].y);
+ }
+
+ for (i = 0; i < 3; ++i) {
+ translation1[i] = translation[i] - (rotation[i][0] * this.objectPoints[0][0] + rotation[i][1] * this.objectPoints[0][1] + rotation[i][2] * this.objectPoints[0][2]);
+ }
+
+ error = error1 = this.error(imagePoints, rotation, translation1); //Convergence
+
+ converged = 0.0 === error1.pixels || imageDifference < 0.01;
+
+ while (iteration++ < 100 && !converged) {
+ for (i = 0; i < np; ++i) {
+ oldSopImagePoints[i].x = sopImagePoints[i].x;
+ oldSopImagePoints[i].y = sopImagePoints[i].y;
+ }
+
+ this.pos(sopImagePoints, rotation1, rotation2, translation);
+
+ for (i = 0; i < 3; ++i) {
+ translation1[i] = translation[i] - (rotation1[i][0] * this.objectPoints[0][0] + rotation1[i][1] * this.objectPoints[0][1] + rotation1[i][2] * this.objectPoints[0][2]);
+ translation2[i] = translation[i] - (rotation2[i][0] * this.objectPoints[0][0] + rotation2[i][1] * this.objectPoints[0][1] + rotation2[i][2] * this.objectPoints[0][2]);
+ }
+
+ error1 = this.error(imagePoints, rotation1, translation1);
+ error2 = this.error(imagePoints, rotation2, translation2);
+
+ if (error1.euclidean >= 0.0 && error2.euclidean >= 0.0) {
+ if (error2.euclidean < error1.euclidean) {
+ error = error2;
+
+ for (i = 0; i < 3; ++i) {
+ for (j = 0; j < 3; ++j) {
+ rotation[i][j] = rotation2[i][j];
+ }
+ }
+ } else {
+ error = error1;
+
+ for (i = 0; i < 3; ++i) {
+ for (j = 0; j < 3; ++j) {
+ rotation[i][j] = rotation1[i][j];
+ }
+ }
+ }
+ }
+
+ if (error1.euclidean < 0.0 && error2.euclidean >= 0.0) {
+ error = error2;
+
+ for (i = 0; i < 3; ++i) {
+ for (j = 0; j < 3; ++j) {
+ rotation[i][j] = rotation2[i][j];
+ }
+ }
+ }
+
+ if (error2.euclidean < 0.0 && error1.euclidean >= 0.0) {
+ error = error1;
+
+ for (i = 0; i < 3; ++i) {
+ for (j = 0; j < 3; ++j) {
+ rotation[i][j] = rotation1[i][j];
+ }
+ }
+ }
+
+ for (i = 0; i < np; ++i) {
+ factor = 0.0;
+
+ for (j = 0; j < 3; ++j) {
+ factor += this.objectVectors[i][j] * rotation[2][j] / translation[2];
+ }
+
+ sopImagePoints[i].x = (1.0 + factor) * imagePoints[i].x;
+ sopImagePoints[i].y = (1.0 + factor) * imagePoints[i].y;
+ }
+
+ oldImageDifference = imageDifference;
+ imageDifference = 0.0;
+
+ for (i = 0; i < np; ++i) {
+ imageDifference += _$0.Math.abs(sopImagePoints[i].x - oldSopImagePoints[i].x);
+ imageDifference += _$0.Math.abs(sopImagePoints[i].y - oldSopImagePoints[i].y);
+ }
+
+ delta = _$0.Math.abs(imageDifference - oldImageDifference);
+ converged = 0.0 === error.pixels || delta < 0.01;
+ }
+
+ return error;
+ }
+
+ function _X(imagePoints, rotation, translation) {
+ var np = this.objectPoints.length,
+ move = [],
+ projection = [],
+ errorvec = [],
+ euclidean = 0.0,
+ pixels = 0.0,
+ maximum = 0.0,
+ i,
+ j,
+ k;
+
+ if (!this.isValid(rotation, translation)) {
+ return {
+ euclidean: -1.0,
+ pixels: -1,
+ maximum: -1.0
+ };
+ }
+
+ for (i = 0; i < np; ++i) {
+ move[i] = [];
+
+ for (j = 0; j < 3; ++j) {
+ move[i][j] = translation[j];
+ }
+ }
+
+ for (i = 0; i < np; ++i) {
+ for (j = 0; j < 3; ++j) {
+ for (k = 0; k < 3; ++k) {
+ move[i][j] += rotation[j][k] * this.objectPoints[i][k];
+ }
+ }
+ }
+
+ for (i = 0; i < np; ++i) {
+ projection[i] = [];
+
+ for (j = 0; j < 2; ++j) {
+ projection[i][j] = this.focalLength * move[i][j] / move[i][2];
+ }
+ }
+
+ for (i = 0; i < np; ++i) {
+ errorvec[i] = [projection[i][0] - imagePoints[i].x, projection[i][1] - imagePoints[i].y];
+ }
+
+ for (i = 0; i < np; ++i) {
+ euclidean += _$0.Math.sqrt(errorvec[i][0] * errorvec[i][0] + errorvec[i][1] * errorvec[i][1]);
+ pixels += _$0.Math.abs(_$0.Math.round(projection[i][0]) - _$0.Math.round(imagePoints[i].x)) + _$0.Math.abs(_$0.Math.round(projection[i][1]) - _$0.Math.round(imagePoints[i].y));
+
+ if (_$0.Math.abs(errorvec[i][0]) > maximum) {
+ maximum = _$0.Math.abs(errorvec[i][0]);
+ }
+
+ if (_$0.Math.abs(errorvec[i][1]) > maximum) {
+ maximum = _$0.Math.abs(errorvec[i][1]);
+ }
+ }
+
+ return {
+ euclidean: euclidean / np,
+ pixels: pixels,
+ maximum: maximum
+ };
+ }
+
+ function _Z(a, m, n, w, v) {
+ var flag,
+ i,
+ its,
+ j,
+ jj,
+ k,
+ l,
+ nm,
+ anorm = 0.0,
+ c,
+ f,
+ g = 0.0,
+ h,
+ s,
+ scale = 0.0,
+ x,
+ y,
+ z,
+ rv1 = []; //Householder reduction to bidiagonal form
+
+ for (i = 0; i < n; ++i) {
+ l = i + 1;
+ rv1[i] = scale * g;
+ g = s = scale = 0.0;
+
+ if (i < m) {
+ for (k = i; k < m; ++k) {
+ scale += _$0.Math.abs(a[k][i]);
+ }
+
+ if (0.0 !== scale) {
+ for (k = i; k < m; ++k) {
+ a[k][i] /= scale;
+ s += a[k][i] * a[k][i];
+ }
+
+ f = a[i][i];
+ g = -_$0.SVD.sign(_$0.Math.sqrt(s), f);
+ h = f * g - s;
+ a[i][i] = f - g;
+
+ for (j = l; j < n; ++j) {
+ for (s = 0.0, k = i; k < m; ++k) {
+ s += a[k][i] * a[k][j];
+ }
+
+ f = s / h;
+
+ for (k = i; k < m; ++k) {
+ a[k][j] += f * a[k][i];
+ }
+ }
+
+ for (k = i; k < m; ++k) {
+ a[k][i] *= scale;
+ }
+ }
+ }
+
+ w[i] = scale * g;
+ g = s = scale = 0.0;
+
+ if (i < m && i !== n - 1) {
+ for (k = l; k < n; ++k) {
+ scale += _$0.Math.abs(a[i][k]);
+ }
+
+ if (0.0 !== scale) {
+ for (k = l; k < n; ++k) {
+ a[i][k] /= scale;
+ s += a[i][k] * a[i][k];
+ }
+
+ f = a[i][l];
+ g = -_$0.SVD.sign(_$0.Math.sqrt(s), f);
+ h = f * g - s;
+ a[i][l] = f - g;
+
+ for (k = l; k < n; ++k) {
+ rv1[k] = a[i][k] / h;
+ }
+
+ for (j = l; j < m; ++j) {
+ for (s = 0.0, k = l; k < n; ++k) {
+ s += a[j][k] * a[i][k];
+ }
+
+ for (k = l; k < n; ++k) {
+ a[j][k] += s * rv1[k];
+ }
+ }
+
+ for (k = l; k < n; ++k) {
+ a[i][k] *= scale;
+ }
+ }
+ }
+
+ anorm = _$0.Math.max(anorm, _$0.Math.abs(w[i]) + _$0.Math.abs(rv1[i]));
+ } //Acumulation of right-hand transformation
+
+
+ for (i = n - 1; i >= 0; --i) {
+ if (i < n - 1) {
+ if (0.0 !== g) {
+ for (j = l; j < n; ++j) {
+ v[j][i] = a[i][j] / a[i][l] / g;
+ }
+
+ for (j = l; j < n; ++j) {
+ for (s = 0.0, k = l; k < n; ++k) {
+ s += a[i][k] * v[k][j];
+ }
+
+ for (k = l; k < n; ++k) {
+ v[k][j] += s * v[k][i];
+ }
+ }
+ }
+
+ for (j = l; j < n; ++j) {
+ v[i][j] = v[j][i] = 0.0;
+ }
+ }
+
+ v[i][i] = 1.0;
+ g = rv1[i];
+ l = i;
+ } //Acumulation of left-hand transformation
+
+
+ for (i = _$0.Math.min(n, m) - 1; i >= 0; --i) {
+ l = i + 1;
+ g = w[i];
+
+ for (j = l; j < n; ++j) {
+ a[i][j] = 0.0;
+ }
+
+ if (0.0 !== g) {
+ g = 1.0 / g;
+
+ for (j = l; j < n; ++j) {
+ for (s = 0.0, k = l; k < m; ++k) {
+ s += a[k][i] * a[k][j];
+ }
+
+ f = s / a[i][i] * g;
+
+ for (k = i; k < m; ++k) {
+ a[k][j] += f * a[k][i];
+ }
+ }
+
+ for (j = i; j < m; ++j) {
+ a[j][i] *= g;
+ }
+ } else {
+ for (j = i; j < m; ++j) {
+ a[j][i] = 0.0;
+ }
+ }
+
+ ++a[i][i];
+ } //Diagonalization of the bidiagonal form
+
+
+ for (k = n - 1; k >= 0; --k) {
+ for (its = 1; its <= 30; ++its) {
+ flag = true;
+
+ for (l = k; l >= 0; --l) {
+ nm = l - 1;
+
+ if (_$0.Math.abs(rv1[l]) + anorm === anorm) {
+ flag = false;
+ break;
+ }
+
+ if (_$0.Math.abs(w[nm]) + anorm === anorm) {
+ break;
+ }
+ }
+
+ if (flag) {
+ c = 0.0;
+ s = 1.0;
+
+ for (i = l; i <= k; ++i) {
+ f = s * rv1[i];
+
+ if (_$0.Math.abs(f) + anorm === anorm) {
+ break;
+ }
+
+ g = w[i];
+ h = _$0.SVD.pythag(f, g);
+ w[i] = h;
+ h = 1.0 / h;
+ c = g * h;
+ s = -f * h;
+
+ for (j = 1; j <= m; ++j) {
+ y = a[j][nm];
+ z = a[j][i];
+ a[j][nm] = y * c + z * s;
+ a[j][i] = z * c - y * s;
+ }
+ }
+ } //Convergence
+
+
+ z = w[k];
+
+ if (l === k) {
+ if (z < 0.0) {
+ w[k] = -z;
+
+ for (j = 0; j < n; ++j) {
+ v[j][k] = -v[j][k];
+ }
+ }
+
+ break;
+ }
+
+ if (30 === its) {
+ return false;
+ } //Shift from bottom 2-by-2 minor
+
+
+ x = w[l];
+ nm = k - 1;
+ y = w[nm];
+ g = rv1[nm];
+ h = rv1[k];
+ f = ((y - z) * (y + z) + (g - h) * (g + h)) / (2.0 * h * y);
+ g = _$0.SVD.pythag(f, 1.0);
+ f = ((x - z) * (x + z) + h * (y / (f + _$0.SVD.sign(g, f)) - h)) / x; //Next QR transformation
+
+ c = s = 1.0;
+
+ for (j = l; j <= nm; ++j) {
+ i = j + 1;
+ g = rv1[i];
+ y = w[i];
+ h = s * g;
+ g = c * g;
+ z = _$0.SVD.pythag(f, h);
+ rv1[j] = z;
+ c = f / z;
+ s = h / z;
+ f = x * c + g * s;
+ g = g * c - x * s;
+ h = y * s;
+ y *= c;
+
+ for (jj = 0; jj < n; ++jj) {
+ x = v[jj][j];
+ z = v[jj][i];
+ v[jj][j] = x * c + z * s;
+ v[jj][i] = z * c - x * s;
+ }
+
+ z = _$0.SVD.pythag(f, h);
+ w[j] = z;
+
+ if (0.0 !== z) {
+ z = 1.0 / z;
+ c = f * z;
+ s = h * z;
+ }
+
+ f = c * g + s * y;
+ x = c * y - s * g;
+
+ for (jj = 0; jj < m; ++jj) {
+ y = a[jj][j];
+ z = a[jj][i];
+ a[jj][j] = y * c + z * s;
+ a[jj][i] = z * c - y * s;
+ }
+ }
+
+ rv1[l] = 0.0;
+ rv1[k] = f;
+ w[k] = x;
+ }
+ }
+
+ return true;
+ }
+
+ function _10(a, b) {
+ var at = _$0.Math.abs(a),
+ bt = _$0.Math.abs(b),
+ ct;
+
+ if (at > bt) {
+ ct = bt / at;
+ return at * _$0.Math.sqrt(1.0 + ct * ct);
+ }
+
+ if (0.0 === bt) {
+ return 0.0;
+ }
+
+ ct = at / bt;
+ return bt * _$0.Math.sqrt(1.0 + ct * ct);
+ }
+
+ function _11(a, b) {
+ return b >= 0.0 ? _$0.Math.abs(a) : -_$0.Math.abs(a);
+ }
+
+ function _13(markerSize) {
+ this.canvas = _$0.document.createElement('canvas');
+ this.canvas.width = 80 * 8;
+ this.canvas.height = 60 * 4; // experiment with imageSmoothingEnabled
+
+ var imageSmoothingEnabled = false;
+ var context = this.canvas.getContext('2d');
+ context.mozImageSmoothingEnabled = imageSmoothingEnabled;
+ context.webkitImageSmoothingEnabled = imageSmoothingEnabled;
+ context.msImageSmoothingEnabled = imageSmoothingEnabled;
+ context.imageSmoothingEnabled = imageSmoothingEnabled;
+ this.detector = new _$0.AR.Detector();
+ this.posit = new _$0.POS.Posit(markerSize, this.canvas.width);
+ }
+
+ var _15 = _13.prototype;
+
+ function _14(object3D, detectedMarker) {
+ var rotation = detectedMarker.pose.bestRotation;
+ var translation = detectedMarker.pose.bestTranslation;
+ object3D.position.x = translation[0];
+ object3D.position.y = translation[1];
+ object3D.position.z = -translation[2];
+ object3D.rotation.x = -_$0.Math.asin(-rotation[1][2]);
+ object3D.rotation.y = -_$0.Math.atan2(rotation[0][2], rotation[2][2]);
+ object3D.rotation.z = _$0.Math.atan2(rotation[1][0], rotation[1][1]);
+ object3D.scale.x = markerSize;
+ object3D.scale.y = markerSize;
+ object3D.scale.z = markerSize;
+ }
+
+ function _16(arucoContext) {
+ this.arucoContext = arucoContext;
+ this.canvasElement = _$0.document.createElement('canvas');
+ this.canvasElement.width = this.arucoContext.canvas.width;
+ this.canvasElement.height = this.arucoContext.canvas.height;
+ }
+
+ var _17 = _16.prototype;
+
+ function _18() {}
+
+ function _19(markerId, svgSize) {
+ var domElement = _$0.document.createElement('div');
+
+ domElement.innerHTML = new ArucoMarker(markerId).toSVG(svgSize);
+ return domElement;
+ }
+
+ function _1a(markerId, svgSize) {
+ // get the svgElement
+ var svgElement = _$0.THREEx.ArucoMarkerGenerator.createSVG(markerId, svgSize).firstChild; // build imageURL
+
+
+ var xml = new XMLSerializer().serializeToString(svgElement);
+ var imageURL = 'data:image/svg+xml;base64,' + btoa(xml); // create imageElement
+
+ var imageElement = _$0.document.createElement('img');
+
+ imageElement.src = imageURL; // return imageElement
+
+ return imageElement;
+ }
+
+ function _1b(videoElement) {
+ var _this = this;
+
+ var canvas = this.canvas; // get imageData from videoElement
+
+ var context = canvas.getContext('2d');
+ context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
+ var imageData = context.getImageData(0, 0, canvas.width, canvas.height); // detect markers in imageData
+
+ var detectedMarkers = this.detector.detect(imageData); // compute the pose for each detectedMarkers
+
+ detectedMarkers.forEach(function (detectedMarker) {
+ // debugger
+ var markerCorners = detectedMarker.corners; // convert the corners
+
+ var poseCorners = new _$0.Array(markerCorners.length);
+
+ for (var i = 0; i < markerCorners.length; ++i) {
+ var markerCorner = markerCorners[i];
+ poseCorners[i] = {
+ x: markerCorner.x - canvas.width / 2,
+ y: -markerCorner.y + canvas.height / 2
+ };
+ } // estimate pose from corners
+
+
+ detectedMarker.pose = _this.posit.pose(poseCorners);
+ });
+ return detectedMarkers;
+ }
+
+ function _1c() {
+ var canvas = this.canvasElement;
+ var context = canvas.getContext('2d');
+ context.clearRect(0, 0, canvas.width, canvas.height);
+ }
+
+ function _1d() {
+ var contours = this.arucoContext.detector.contours;
+ var canvas = this.canvasElement;
+ this.drawContours(contours, 0, 0, canvas.width, canvas.height, function (hole) {
+ return hole ? "magenta" : "blue";
+ });
+ }
+
+ function _1e() {
+ var contours = this.arucoContext.detector.polys;
+ var canvas = this.canvasElement;
+ this.drawContours(contours, 0, 0, canvas.width, canvas.height, function () {
+ return 'green';
+ });
+ }
+
+ function _1f() {
+ var contours = this.arucoContext.detector.candidates;
+ var canvas = this.canvasElement;
+ this.drawContours(contours, 0, 0, canvas.width, canvas.height, function () {
+ return 'red';
+ });
+ }
+
+ function _1g(contours, x, y, width, height, fn) {
+ var i = contours.length,
+ j,
+ contour,
+ point;
+ var canvas = this.canvasElement;
+ var context = canvas.getContext('2d');
+ context.save();
+
+ while (i--) {
+ contour = contours[i];
+ context.strokeStyle = fn(contour.hole);
+ context.beginPath();
+
+ for (j = 0; j < contour.length; ++j) {
+ point = contour[j];
+ context.moveTo(x + point.x, y + point.y);
+ point = contour[(j + 1) % contour.length];
+ context.lineTo(x + point.x, y + point.y);
+ }
+
+ context.stroke();
+ context.closePath();
+ }
+
+ context.restore();
+ }
+
+ function _1h() {
+ var cvImage = arucoContext.detector.grey;
+ this.drawCVImage(cvImage);
+ }
+
+ function _1i() {
+ var cvImage = arucoContext.detector.thres;
+ this.drawCVImage(cvImage);
+ }
+
+ function _1j(cvImage) {
+ var detector = this.arucoContext.detector;
+ var canvas = this.canvasElement;
+ var context = canvas.getContext('2d');
+ var imageData = context.createImageData(canvas.width, canvas.height);
+ copyImage(cvImage, imageData);
+ context.putImageData(imageData, 0, 0);
+ return;
+
+ function copyImage(src, dst) {
+ var i = src.data.length,
+ j = i * 4 + 3;
+
+ while (i--) {
+ dst.data[j -= 4] = 255;
+ dst.data[j - 1] = dst.data[j - 2] = dst.data[j - 3] = src.data[i];
+ }
+
+ return dst;
+ }
+
+ ;
+ }
+
+ function _1k(videoElement) {
+ var canvas = this.canvasElement;
+ var context = canvas.getContext('2d');
+ context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
+ }
+
+ function _1l(markers) {
+ var canvas = this.canvasElement;
+ var context = canvas.getContext('2d');
+ var corners, corner, x, y, i, j;
+ context.save();
+ context.strokeStyle = "blue";
+ context.lineWidth = 1;
+
+ for (i = 0; i !== markers.length; ++i) {
+ corners = markers[i].corners;
+ x = _$0.Infinity;
+ y = _$0.Infinity;
+
+ for (j = 0; j !== corners.length; ++j) {
+ corner = corners[j];
+ x = _$0.Math.min(x, corner.x);
+ y = _$0.Math.min(y, corner.y);
+ }
+
+ context.strokeText(markers[i].id, x, y);
+ }
+
+ context.restore();
+ }
+
+ function _1m(markers) {
+ var canvas = this.canvasElement;
+ var corners, corner, i, j;
+ var context = canvas.getContext('2d');
+ context.save();
+ context.lineWidth = 3;
+
+ for (i = 0; i < markers.length; ++i) {
+ corners = markers[i].corners;
+ context.strokeStyle = 'red';
+ context.beginPath();
+
+ for (j = 0; j < corners.length; ++j) {
+ corner = corners[j];
+ context.moveTo(corner.x, corner.y);
+ corner = corners[(j + 1) % corners.length];
+ context.lineTo(corner.x, corner.y);
+ }
+
+ context.stroke();
+ context.closePath();
+ context.strokeStyle = 'green';
+ context.strokeRect(corners[0].x - 2, corners[0].y - 2, 4, 4);
+ }
+
+ context.restore();
+ }
+
+ THREEx = undefined;
+ SVD = undefined;
+ POS = undefined;
+ CV = undefined;
+ AR = undefined;
+ _3.rotate2 = _d;
+ _3.rotate = _c;
+ _3.mat2id = _b;
+ _3.hammingDistance = _a;
+ _3.getMarker = _9;
+ _3.findMarkers = _8;
+ _3.notTooNear = _7;
+ _3.clockwiseCorners = _6;
+ _3.findCandidates = _5;
+ _3.detect = _4;
+ AR = {
+ Marker: _1,
+ Detector: _2
+ };
+ CV = {
+ Image: _f,
+ grayscale: _g,
+ threshold: _h,
+ adaptiveThreshold: _i,
+ otsu: _j,
+ stackBoxBlurMult: [1, 171, 205, 293, 57, 373, 79, 137, 241, 27, 391, 357, 41, 19, 283, 265],
+ stackBoxBlurShift: [0, 9, 10, 11, 9, 12, 10, 11, 12, 9, 13, 13, 10, 9, 13, 13],
+ BlurStack: _m,
+ stackBoxBlur: _n,
+ gaussianBlur: _o,
+ gaussianBlurFilter: _p,
+ gaussianKernel: _q,
+ findContours: _r,
+ borderFollowing: _s,
+ neighborhood: [[1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1]],
+ neighborhoodDeltas: _C,
+ approxPolyDP: _D,
+ warp: _E,
+ getPerspectiveTransform: _F,
+ square2quad: _G,
+ isContourConvex: _H,
+ perimeter: _I,
+ minEdgeLength: _J,
+ countNonZero: _K,
+ binaryBorder: _L
+ };
+ _O.error = _X;
+ _O.iterate = _W;
+ _O.isValid = _V;
+ _O.pos = _U;
+ _O.pose = _T;
+ _O.init = _S;
+ _O.buildModel = _R;
+ POS = {
+ Posit: _N,
+ pseudoInverse: _P,
+ Pose: _Q
+ };
+ SVD = {
+ svdcmp: _Z,
+ pythag: _10,
+ sign: _11
+ };
+ _13.updateObject3D = _14;
+ _18.createSVG = _19;
+ _18.createIMG = _1a;
+ _17.drawMarkerCorners = _1m;
+ _17.drawMarkerIDs = _1l;
+ _17.drawVideo = _1k;
+ _17.drawCVImage = _1j;
+ _17.drawDetectorThreshold = _1i;
+ _17.drawDetectorGrey = _1h;
+ _17.drawContours = _1g;
+ _17.drawContoursCandidates = _1f;
+ _17.drawContoursPolys = _1e;
+ _17.drawContoursContours = _1d;
+ _17.clear = _1c;
+ _15.detect = _1b;
+ THREEx = {
+ ArucoContext: _13,
+ ArucoDebug: _16,
+ ArucoMarkerGenerator: _18
+ };
+}).call(this);
\ No newline at end of file
diff --git a/three.js/examples/threex-aruco/build/threex-aruco.js b/three.js/examples/threex-aruco/build/threex-aruco.js
new file mode 100644
index 0000000000000000000000000000000000000000..e122125b323b1cc4fc4c76da8f54f646ce858d02
--- /dev/null
+++ b/three.js/examples/threex-aruco/build/threex-aruco.js
@@ -0,0 +1,2050 @@
+/*
+Copyright (c) 2011 Juan Mellado
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/*
+References:
+- "ArUco: a minimal library for Augmented Reality applications based on OpenCv"
+ http://www.uco.es/investiga/grupos/ava/node/26
+*/
+
+var AR = AR || {};
+
+AR.Marker = function(id, corners){
+ this.id = id;
+ this.corners = corners;
+};
+
+AR.Detector = function(){
+ this.grey = new CV.Image();
+ this.thres = new CV.Image();
+ this.homography = new CV.Image();
+ this.binary = [];
+ this.contours = [];
+ this.polys = [];
+ this.candidates = [];
+};
+
+AR.Detector.prototype.detect = function(image){
+ CV.grayscale(image, this.grey);
+ CV.adaptiveThreshold(this.grey, this.thres, 2, 7);
+
+ this.contours = CV.findContours(this.thres, this.binary);
+
+ this.candidates = this.findCandidates(this.contours, image.width * 0.20, 0.05, 10);
+ this.candidates = this.clockwiseCorners(this.candidates);
+ this.candidates = this.notTooNear(this.candidates, 10);
+
+ return this.findMarkers(this.grey, this.candidates, 49);
+};
+
+AR.Detector.prototype.findCandidates = function(contours, minSize, epsilon, minLength){
+ var candidates = [], len = contours.length, contour, poly, i;
+
+ this.polys = [];
+
+ for (i = 0; i < len; ++ i){
+ contour = contours[i];
+
+ if (contour.length >= minSize){
+ poly = CV.approxPolyDP(contour, contour.length * epsilon);
+
+ this.polys.push(poly);
+
+ if ( (4 === poly.length) && ( CV.isContourConvex(poly) ) ){
+
+ if ( CV.minEdgeLength(poly) >= minLength){
+ candidates.push(poly);
+ }
+ }
+ }
+ }
+
+ return candidates;
+};
+
+AR.Detector.prototype.clockwiseCorners = function(candidates){
+ var len = candidates.length, dx1, dx2, dy1, dy2, swap, i;
+
+ for (i = 0; i < len; ++ i){
+ dx1 = candidates[i][1].x - candidates[i][0].x;
+ dy1 = candidates[i][1].y - candidates[i][0].y;
+ dx2 = candidates[i][2].x - candidates[i][0].x;
+ dy2 = candidates[i][2].y - candidates[i][0].y;
+
+ if ( (dx1 * dy2 - dy1 * dx2) < 0){
+ swap = candidates[i][1];
+ candidates[i][1] = candidates[i][3];
+ candidates[i][3] = swap;
+ }
+ }
+
+ return candidates;
+};
+
+AR.Detector.prototype.notTooNear = function(candidates, minDist){
+ var notTooNear = [], len = candidates.length, dist, dx, dy, i, j, k;
+
+ for (i = 0; i < len; ++ i){
+
+ for (j = i + 1; j < len; ++ j){
+ dist = 0;
+
+ for (k = 0; k < 4; ++ k){
+ dx = candidates[i][k].x - candidates[j][k].x;
+ dy = candidates[i][k].y - candidates[j][k].y;
+
+ dist += dx * dx + dy * dy;
+ }
+
+ if ( (dist / 4) < (minDist * minDist) ){
+
+ if ( CV.perimeter( candidates[i] ) < CV.perimeter( candidates[j] ) ){
+ candidates[i].tooNear = true;
+ }else{
+ candidates[j].tooNear = true;
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < len; ++ i){
+ if ( !candidates[i].tooNear ){
+ notTooNear.push( candidates[i] );
+ }
+ }
+
+ return notTooNear;
+};
+
+AR.Detector.prototype.findMarkers = function(imageSrc, candidates, warpSize){
+ var markers = [], len = candidates.length, candidate, marker, i;
+
+ for (i = 0; i < len; ++ i){
+ candidate = candidates[i];
+
+ CV.warp(imageSrc, this.homography, candidate, warpSize);
+
+ CV.threshold(this.homography, this.homography, CV.otsu(this.homography) );
+
+ marker = this.getMarker(this.homography, candidate);
+ if (marker){
+ markers.push(marker);
+ }
+ }
+
+ return markers;
+};
+
+AR.Detector.prototype.getMarker = function(imageSrc, candidate){
+ var width = (imageSrc.width / 7) >>> 0,
+ minZero = (width * width) >> 1,
+ bits = [], rotations = [], distances = [],
+ square, pair, inc, i, j;
+
+ for (i = 0; i < 7; ++ i){
+ inc = (0 === i || 6 === i)? 1: 6;
+
+ for (j = 0; j < 7; j += inc){
+ square = {x: j * width, y: i * width, width: width, height: width};
+ if ( CV.countNonZero(imageSrc, square) > minZero){
+ return null;
+ }
+ }
+ }
+
+ for (i = 0; i < 5; ++ i){
+ bits[i] = [];
+
+ for (j = 0; j < 5; ++ j){
+ square = {x: (j + 1) * width, y: (i + 1) * width, width: width, height: width};
+
+ bits[i][j] = CV.countNonZero(imageSrc, square) > minZero? 1: 0;
+ }
+ }
+
+ rotations[0] = bits;
+ distances[0] = this.hammingDistance( rotations[0] );
+
+ pair = {first: distances[0], second: 0};
+
+ for (i = 1; i < 4; ++ i){
+ rotations[i] = this.rotate( rotations[i - 1] );
+ distances[i] = this.hammingDistance( rotations[i] );
+
+ if (distances[i] < pair.first){
+ pair.first = distances[i];
+ pair.second = i;
+ }
+ }
+
+ if (0 !== pair.first){
+ return null;
+ }
+
+ return new AR.Marker(
+ this.mat2id( rotations[pair.second] ),
+ this.rotate2(candidate, 4 - pair.second) );
+};
+
+AR.Detector.prototype.hammingDistance = function(bits){
+ var ids = [ [1,0,0,0,0], [1,0,1,1,1], [0,1,0,0,1], [0,1,1,1,0] ],
+ dist = 0, sum, minSum, i, j, k;
+
+ for (i = 0; i < 5; ++ i){
+ minSum = Infinity;
+
+ for (j = 0; j < 4; ++ j){
+ sum = 0;
+
+ for (k = 0; k < 5; ++ k){
+ sum += bits[i][k] === ids[j][k]? 0: 1;
+ }
+
+ if (sum < minSum){
+ minSum = sum;
+ }
+ }
+
+ dist += minSum;
+ }
+
+ return dist;
+};
+
+AR.Detector.prototype.mat2id = function(bits){
+ var id = 0, i;
+
+ for (i = 0; i < 5; ++ i){
+ id <<= 1;
+ id |= bits[i][1];
+ id <<= 1;
+ id |= bits[i][3];
+ }
+
+ return id;
+};
+
+AR.Detector.prototype.rotate = function(src){
+ var dst = [], len = src.length, i, j;
+
+ for (i = 0; i < len; ++ i){
+ dst[i] = [];
+ for (j = 0; j < src[i].length; ++ j){
+ dst[i][j] = src[src[i].length - j - 1][i];
+ }
+ }
+
+ return dst;
+};
+
+AR.Detector.prototype.rotate2 = function(src, rotation){
+ var dst = [], len = src.length, i;
+
+ for (i = 0; i < len; ++ i){
+ dst[i] = src[ (rotation + i) % len ];
+ }
+
+ return dst;
+};
+/*
+Copyright (c) 2011 Juan Mellado
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/*
+References:
+- "OpenCV: Open Computer Vision Library"
+ http://sourceforge.net/projects/opencvlibrary/
+- "Stack Blur: Fast But Goodlooking"
+ http://incubator.quasimondo.com/processing/fast_blur_deluxe.php
+*/
+
+var CV = CV || {};
+
+CV.Image = function(width, height, data){
+ this.width = width || 0;
+ this.height = height || 0;
+ this.data = data || [];
+};
+
+CV.grayscale = function(imageSrc, imageDst){
+ var src = imageSrc.data, dst = imageDst.data, len = src.length,
+ i = 0, j = 0;
+
+ for (; i < len; i += 4){
+ dst[j ++] =
+ (src[i] * 0.299 + src[i + 1] * 0.587 + src[i + 2] * 0.114 + 0.5) & 0xff;
+ }
+
+ imageDst.width = imageSrc.width;
+ imageDst.height = imageSrc.height;
+
+ return imageDst;
+};
+
+CV.threshold = function(imageSrc, imageDst, threshold){
+ var src = imageSrc.data, dst = imageDst.data,
+ len = src.length, tab = [], i;
+
+ for (i = 0; i < 256; ++ i){
+ tab[i] = i <= threshold? 0: 255;
+ }
+
+ for (i = 0; i < len; ++ i){
+ dst[i] = tab[ src[i] ];
+ }
+
+ imageDst.width = imageSrc.width;
+ imageDst.height = imageSrc.height;
+
+ return imageDst;
+};
+
+CV.adaptiveThreshold = function(imageSrc, imageDst, kernelSize, threshold){
+ var src = imageSrc.data, dst = imageDst.data, len = src.length, tab = [], i;
+
+ CV.stackBoxBlur(imageSrc, imageDst, kernelSize);
+
+ for (i = 0; i < 768; ++ i){
+ tab[i] = (i - 255 <= -threshold)? 255: 0;
+ }
+
+ for (i = 0; i < len; ++ i){
+ dst[i] = tab[ src[i] - dst[i] + 255 ];
+ }
+
+ imageDst.width = imageSrc.width;
+ imageDst.height = imageSrc.height;
+
+ return imageDst;
+};
+
+CV.otsu = function(imageSrc){
+ var src = imageSrc.data, len = src.length, hist = [],
+ threshold = 0, sum = 0, sumB = 0, wB = 0, wF = 0, max = 0,
+ mu, between, i;
+
+ for (i = 0; i < 256; ++ i){
+ hist[i] = 0;
+ }
+
+ for (i = 0; i < len; ++ i){
+ hist[ src[i] ] ++;
+ }
+
+ for (i = 0; i < 256; ++ i){
+ sum += hist[i] * i;
+ }
+
+ for (i = 0; i < 256; ++ i){
+ wB += hist[i];
+ if (0 !== wB){
+
+ wF = len - wB;
+ if (0 === wF){
+ break;
+ }
+
+ sumB += hist[i] * i;
+
+ mu = (sumB / wB) - ( (sum - sumB) / wF );
+
+ between = wB * wF * mu * mu;
+
+ if (between > max){
+ max = between;
+ threshold = i;
+ }
+ }
+ }
+
+ return threshold;
+};
+
+CV.stackBoxBlurMult =
+ [1, 171, 205, 293, 57, 373, 79, 137, 241, 27, 391, 357, 41, 19, 283, 265];
+
+CV.stackBoxBlurShift =
+ [0, 9, 10, 11, 9, 12, 10, 11, 12, 9, 13, 13, 10, 9, 13, 13];
+
+CV.BlurStack = function(){
+ this.color = 0;
+ this.next = null;
+};
+
+CV.stackBoxBlur = function(imageSrc, imageDst, kernelSize){
+ var src = imageSrc.data, dst = imageDst.data,
+ height = imageSrc.height, width = imageSrc.width,
+ heightMinus1 = height - 1, widthMinus1 = width - 1,
+ size = kernelSize + kernelSize + 1, radius = kernelSize + 1,
+ mult = CV.stackBoxBlurMult[kernelSize],
+ shift = CV.stackBoxBlurShift[kernelSize],
+ stack, stackStart, color, sum, pos, start, p, x, y, i;
+
+ stack = stackStart = new CV.BlurStack();
+ for (i = 1; i < size; ++ i){
+ stack = stack.next = new CV.BlurStack();
+ }
+ stack.next = stackStart;
+
+ pos = 0;
+
+ for (y = 0; y < height; ++ y){
+ start = pos;
+
+ color = src[pos];
+ sum = radius * color;
+
+ stack = stackStart;
+ for (i = 0; i < radius; ++ i){
+ stack.color = color;
+ stack = stack.next;
+ }
+ for (i = 1; i < radius; ++ i){
+ stack.color = src[pos + i];
+ sum += stack.color;
+ stack = stack.next;
+ }
+
+ stack = stackStart;
+ for (x = 0; x < width; ++ x){
+ dst[pos ++] = (sum * mult) >>> shift;
+
+ p = x + radius;
+ p = start + (p < widthMinus1? p: widthMinus1);
+ sum -= stack.color - src[p];
+
+ stack.color = src[p];
+ stack = stack.next;
+ }
+ }
+
+ for (x = 0; x < width; ++ x){
+ pos = x;
+ start = pos + width;
+
+ color = dst[pos];
+ sum = radius * color;
+
+ stack = stackStart;
+ for (i = 0; i < radius; ++ i){
+ stack.color = color;
+ stack = stack.next;
+ }
+ for (i = 1; i < radius; ++ i){
+ stack.color = dst[start];
+ sum += stack.color;
+ stack = stack.next;
+
+ start += width;
+ }
+
+ stack = stackStart;
+ for (y = 0; y < height; ++ y){
+ dst[pos] = (sum * mult) >>> shift;
+
+ p = y + radius;
+ p = x + ( (p < heightMinus1? p: heightMinus1) * width );
+ sum -= stack.color - dst[p];
+
+ stack.color = dst[p];
+ stack = stack.next;
+
+ pos += width;
+ }
+ }
+
+ return imageDst;
+};
+
+CV.gaussianBlur = function(imageSrc, imageDst, imageMean, kernelSize){
+ var kernel = CV.gaussianKernel(kernelSize);
+
+ imageDst.width = imageSrc.width;
+ imageDst.height = imageSrc.height;
+
+ imageMean.width = imageSrc.width;
+ imageMean.height = imageSrc.height;
+
+ CV.gaussianBlurFilter(imageSrc, imageMean, kernel, true);
+ CV.gaussianBlurFilter(imageMean, imageDst, kernel, false);
+
+ return imageDst;
+};
+
+CV.gaussianBlurFilter = function(imageSrc, imageDst, kernel, horizontal){
+ var src = imageSrc.data, dst = imageDst.data,
+ height = imageSrc.height, width = imageSrc.width,
+ pos = 0, limit = kernel.length >> 1,
+ cur, value, i, j, k;
+
+ for (i = 0; i < height; ++ i){
+
+ for (j = 0; j < width; ++ j){
+ value = 0.0;
+
+ for (k = -limit; k <= limit; ++ k){
+
+ if (horizontal){
+ cur = pos + k;
+ if (j + k < 0){
+ cur = pos;
+ }
+ else if (j + k >= width){
+ cur = pos;
+ }
+ }else{
+ cur = pos + (k * width);
+ if (i + k < 0){
+ cur = pos;
+ }
+ else if (i + k >= height){
+ cur = pos;
+ }
+ }
+
+ value += kernel[limit + k] * src[cur];
+ }
+
+ dst[pos ++] = horizontal? value: (value + 0.5) & 0xff;
+ }
+ }
+
+ return imageDst;
+};
+
+CV.gaussianKernel = function(kernelSize){
+ var tab =
+ [ [1],
+ [0.25, 0.5, 0.25],
+ [0.0625, 0.25, 0.375, 0.25, 0.0625],
+ [0.03125, 0.109375, 0.21875, 0.28125, 0.21875, 0.109375, 0.03125] ],
+ kernel = [], center, sigma, scale2X, sum, x, i;
+
+ if ( (kernelSize <= 7) && (kernelSize % 2 === 1) ){
+ kernel = tab[kernelSize >> 1];
+ }else{
+ center = (kernelSize - 1.0) * 0.5;
+ sigma = 0.8 + (0.3 * (center - 1.0) );
+ scale2X = -0.5 / (sigma * sigma);
+ sum = 0.0;
+ for (i = 0; i < kernelSize; ++ i){
+ x = i - center;
+ sum += kernel[i] = Math.exp(scale2X * x * x);
+ }
+ sum = 1 / sum;
+ for (i = 0; i < kernelSize; ++ i){
+ kernel[i] *= sum;
+ }
+ }
+
+ return kernel;
+};
+
+CV.findContours = function(imageSrc, binary){
+ var width = imageSrc.width, height = imageSrc.height, contours = [],
+ src, deltas, pos, pix, nbd, outer, hole, i, j;
+
+ src = CV.binaryBorder(imageSrc, binary);
+
+ deltas = CV.neighborhoodDeltas(width + 2);
+
+ pos = width + 3;
+ nbd = 1;
+
+ for (i = 0; i < height; ++ i, pos += 2){
+
+ for (j = 0; j < width; ++ j, ++ pos){
+ pix = src[pos];
+
+ if (0 !== pix){
+ outer = hole = false;
+
+ if (1 === pix && 0 === src[pos - 1]){
+ outer = true;
+ }
+ else if (pix >= 1 && 0 === src[pos + 1]){
+ hole = true;
+ }
+
+ if (outer || hole){
+ ++ nbd;
+
+ contours.push( CV.borderFollowing(src, pos, nbd, {x: j, y: i}, hole, deltas) );
+ }
+ }
+ }
+ }
+
+ return contours;
+};
+
+CV.borderFollowing = function(src, pos, nbd, point, hole, deltas){
+ var contour = [], pos1, pos3, pos4, s, s_end, s_prev;
+
+ contour.hole = hole;
+
+ s = s_end = hole? 0: 4;
+ do{
+ s = (s - 1) & 7;
+ pos1 = pos + deltas[s];
+ if (src[pos1] !== 0){
+ break;
+ }
+ }while(s !== s_end);
+
+ if (s === s_end){
+ src[pos] = -nbd;
+ contour.push( {x: point.x, y: point.y} );
+
+ }else{
+ pos3 = pos;
+ s_prev = s ^ 4;
+
+ while(true){
+ s_end = s;
+
+ do{
+ pos4 = pos3 + deltas[++ s];
+ }while(src[pos4] === 0);
+
+ s &= 7;
+
+ if ( ( (s - 1) >>> 0) < (s_end >>> 0) ){
+ src[pos3] = -nbd;
+ }
+ else if (src[pos3] === 1){
+ src[pos3] = nbd;
+ }
+
+ contour.push( {x: point.x, y: point.y} );
+
+ s_prev = s;
+
+ point.x += CV.neighborhood[s][0];
+ point.y += CV.neighborhood[s][1];
+
+ if ( (pos4 === pos) && (pos3 === pos1) ){
+ break;
+ }
+
+ pos3 = pos4;
+ s = (s + 4) & 7;
+ }
+ }
+
+ return contour;
+};
+
+CV.neighborhood =
+ [ [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1] ];
+
+CV.neighborhoodDeltas = function(width){
+ var deltas = [], len = CV.neighborhood.length, i = 0;
+
+ for (; i < len; ++ i){
+ deltas[i] = CV.neighborhood[i][0] + (CV.neighborhood[i][1] * width);
+ }
+
+ return deltas.concat(deltas);
+};
+
+CV.approxPolyDP = function(contour, epsilon){
+ var slice = {start_index: 0, end_index: 0},
+ right_slice = {start_index: 0, end_index: 0},
+ poly = [], stack = [], len = contour.length,
+ pt, start_pt, end_pt, dist, max_dist, le_eps,
+ dx, dy, i, j, k;
+
+ epsilon *= epsilon;
+
+ k = 0;
+
+ for (i = 0; i < 3; ++ i){
+ max_dist = 0;
+
+ k = (k + right_slice.start_index) % len;
+ start_pt = contour[k];
+ if (++ k === len) {k = 0;}
+
+ for (j = 1; j < len; ++ j){
+ pt = contour[k];
+ if (++ k === len) {k = 0;}
+
+ dx = pt.x - start_pt.x;
+ dy = pt.y - start_pt.y;
+ dist = dx * dx + dy * dy;
+
+ if (dist > max_dist){
+ max_dist = dist;
+ right_slice.start_index = j;
+ }
+ }
+ }
+
+ if (max_dist <= epsilon){
+ poly.push( {x: start_pt.x, y: start_pt.y} );
+
+ }else{
+ slice.start_index = k;
+ slice.end_index = (right_slice.start_index += slice.start_index);
+
+ right_slice.start_index -= right_slice.start_index >= len? len: 0;
+ right_slice.end_index = slice.start_index;
+ if (right_slice.end_index < right_slice.start_index){
+ right_slice.end_index += len;
+ }
+
+ stack.push( {start_index: right_slice.start_index, end_index: right_slice.end_index} );
+ stack.push( {start_index: slice.start_index, end_index: slice.end_index} );
+ }
+
+ while(stack.length !== 0){
+ slice = stack.pop();
+
+ end_pt = contour[slice.end_index % len];
+ start_pt = contour[k = slice.start_index % len];
+ if (++ k === len) {k = 0;}
+
+ if (slice.end_index <= slice.start_index + 1){
+ le_eps = true;
+
+ }else{
+ max_dist = 0;
+
+ dx = end_pt.x - start_pt.x;
+ dy = end_pt.y - start_pt.y;
+
+ for (i = slice.start_index + 1; i < slice.end_index; ++ i){
+ pt = contour[k];
+ if (++ k === len) {k = 0;}
+
+ dist = Math.abs( (pt.y - start_pt.y) * dx - (pt.x - start_pt.x) * dy);
+
+ if (dist > max_dist){
+ max_dist = dist;
+ right_slice.start_index = i;
+ }
+ }
+
+ le_eps = max_dist * max_dist <= epsilon * (dx * dx + dy * dy);
+ }
+
+ if (le_eps){
+ poly.push( {x: start_pt.x, y: start_pt.y} );
+
+ }else{
+ right_slice.end_index = slice.end_index;
+ slice.end_index = right_slice.start_index;
+
+ stack.push( {start_index: right_slice.start_index, end_index: right_slice.end_index} );
+ stack.push( {start_index: slice.start_index, end_index: slice.end_index} );
+ }
+ }
+
+ return poly;
+};
+
+CV.warp = function(imageSrc, imageDst, contour, warpSize){
+ var src = imageSrc.data, dst = imageDst.data,
+ width = imageSrc.width, height = imageSrc.height,
+ pos = 0,
+ sx1, sx2, dx1, dx2, sy1, sy2, dy1, dy2, p1, p2, p3, p4,
+ m, r, s, t, u, v, w, x, y, i, j;
+
+ m = CV.getPerspectiveTransform(contour, warpSize - 1);
+
+ r = m[8];
+ s = m[2];
+ t = m[5];
+
+ for (i = 0; i < warpSize; ++ i){
+ r += m[7];
+ s += m[1];
+ t += m[4];
+
+ u = r;
+ v = s;
+ w = t;
+
+ for (j = 0; j < warpSize; ++ j){
+ u += m[6];
+ v += m[0];
+ w += m[3];
+
+ x = v / u;
+ y = w / u;
+
+ sx1 = x >>> 0;
+ sx2 = (sx1 === width - 1)? sx1: sx1 + 1;
+ dx1 = x - sx1;
+ dx2 = 1.0 - dx1;
+
+ sy1 = y >>> 0;
+ sy2 = (sy1 === height - 1)? sy1: sy1 + 1;
+ dy1 = y - sy1;
+ dy2 = 1.0 - dy1;
+
+ p1 = p2 = sy1 * width;
+ p3 = p4 = sy2 * width;
+
+ dst[pos ++] =
+ (dy2 * (dx2 * src[p1 + sx1] + dx1 * src[p2 + sx2]) +
+ dy1 * (dx2 * src[p3 + sx1] + dx1 * src[p4 + sx2]) ) & 0xff;
+
+ }
+ }
+
+ imageDst.width = warpSize;
+ imageDst.height = warpSize;
+
+ return imageDst;
+};
+
+CV.getPerspectiveTransform = function(src, size){
+ var rq = CV.square2quad(src);
+
+ rq[0] /= size;
+ rq[1] /= size;
+ rq[3] /= size;
+ rq[4] /= size;
+ rq[6] /= size;
+ rq[7] /= size;
+
+ return rq;
+};
+
+CV.square2quad = function(src){
+ var sq = [], px, py, dx1, dx2, dy1, dy2, den;
+
+ px = src[0].x - src[1].x + src[2].x - src[3].x;
+ py = src[0].y - src[1].y + src[2].y - src[3].y;
+
+ if (0 === px && 0 === py){
+ sq[0] = src[1].x - src[0].x;
+ sq[1] = src[2].x - src[1].x;
+ sq[2] = src[0].x;
+ sq[3] = src[1].y - src[0].y;
+ sq[4] = src[2].y - src[1].y;
+ sq[5] = src[0].y;
+ sq[6] = 0;
+ sq[7] = 0;
+ sq[8] = 1;
+
+ }else{
+ dx1 = src[1].x - src[2].x;
+ dx2 = src[3].x - src[2].x;
+ dy1 = src[1].y - src[2].y;
+ dy2 = src[3].y - src[2].y;
+ den = dx1 * dy2 - dx2 * dy1;
+
+ sq[6] = (px * dy2 - dx2 * py) / den;
+ sq[7] = (dx1 * py - px * dy1) / den;
+ sq[8] = 1;
+ sq[0] = src[1].x - src[0].x + sq[6] * src[1].x;
+ sq[1] = src[3].x - src[0].x + sq[7] * src[3].x;
+ sq[2] = src[0].x;
+ sq[3] = src[1].y - src[0].y + sq[6] * src[1].y;
+ sq[4] = src[3].y - src[0].y + sq[7] * src[3].y;
+ sq[5] = src[0].y;
+ }
+
+ return sq;
+};
+
+CV.isContourConvex = function(contour){
+ var orientation = 0, convex = true,
+ len = contour.length, i = 0, j = 0,
+ cur_pt, prev_pt, dxdy0, dydx0, dx0, dy0, dx, dy;
+
+ prev_pt = contour[len - 1];
+ cur_pt = contour[0];
+
+ dx0 = cur_pt.x - prev_pt.x;
+ dy0 = cur_pt.y - prev_pt.y;
+
+ for (; i < len; ++ i){
+ if (++ j === len) {j = 0;}
+
+ prev_pt = cur_pt;
+ cur_pt = contour[j];
+
+ dx = cur_pt.x - prev_pt.x;
+ dy = cur_pt.y - prev_pt.y;
+ dxdy0 = dx * dy0;
+ dydx0 = dy * dx0;
+
+ orientation |= dydx0 > dxdy0? 1: (dydx0 < dxdy0? 2: 3);
+
+ if (3 === orientation){
+ convex = false;
+ break;
+ }
+
+ dx0 = dx;
+ dy0 = dy;
+ }
+
+ return convex;
+};
+
+CV.perimeter = function(poly){
+ var len = poly.length, i = 0, j = len - 1,
+ p = 0.0, dx, dy;
+
+ for (; i < len; j = i ++){
+ dx = poly[i].x - poly[j].x;
+ dy = poly[i].y - poly[j].y;
+
+ p += Math.sqrt(dx * dx + dy * dy) ;
+ }
+
+ return p;
+};
+
+CV.minEdgeLength = function(poly){
+ var len = poly.length, i = 0, j = len - 1,
+ min = Infinity, d, dx, dy;
+
+ for (; i < len; j = i ++){
+ dx = poly[i].x - poly[j].x;
+ dy = poly[i].y - poly[j].y;
+
+ d = dx * dx + dy * dy;
+
+ if (d < min){
+ min = d;
+ }
+ }
+
+ return Math.sqrt(min);
+};
+
+CV.countNonZero = function(imageSrc, square){
+ var src = imageSrc.data, height = square.height, width = square.width,
+ pos = square.x + (square.y * imageSrc.width),
+ span = imageSrc.width - width,
+ nz = 0, i, j;
+
+ for (i = 0; i < height; ++ i){
+
+ for (j = 0; j < width; ++ j){
+
+ if ( 0 !== src[pos ++] ){
+ ++ nz;
+ }
+ }
+
+ pos += span;
+ }
+
+ return nz;
+};
+
+CV.binaryBorder = function(imageSrc, dst){
+ var src = imageSrc.data, height = imageSrc.height, width = imageSrc.width,
+ posSrc = 0, posDst = 0, i, j;
+
+ for (j = -2; j < width; ++ j){
+ dst[posDst ++] = 0;
+ }
+
+ for (i = 0; i < height; ++ i){
+ dst[posDst ++] = 0;
+
+ for (j = 0; j < width; ++ j){
+ dst[posDst ++] = (0 === src[posSrc ++]? 0: 1);
+ }
+
+ dst[posDst ++] = 0;
+ }
+
+ for (j = -2; j < width; ++ j){
+ dst[posDst ++] = 0;
+ }
+
+ return dst;
+};
+/*
+Copyright (c) 2012 Juan Mellado
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/*
+References:
+- "Iterative Pose Estimation using Coplanar Feature Points"
+ Denis Oberkampf, Daniel F. DeMenthon, Larry S. Davis
+ http://www.cfar.umd.edu/~daniel/daniel_papersfordownload/CoplanarPts.pdf
+*/
+
+var POS = POS || {};
+
+POS.Posit = function(modelSize, focalLength){
+ this.objectPoints = this.buildModel(modelSize);
+ this.focalLength = focalLength;
+
+ this.objectVectors = [];
+ this.objectNormal = [];
+ this.objectMatrix = [[],[],[]];
+
+ this.init();
+};
+
+POS.Posit.prototype.buildModel = function(modelSize){
+ var half = modelSize / 2.0;
+
+ return [
+ [-half, half, 0.0],
+ [ half, half, 0.0],
+ [ half, -half, 0.0],
+ [-half, -half, 0.0] ];
+};
+
+POS.Posit.prototype.init = function(){
+ var np = this.objectPoints.length,
+ vectors = [], n = [], len = 0.0, row = 2, i;
+
+ for (i = 0; i < np; ++ i){
+ this.objectVectors[i] = [this.objectPoints[i][0] - this.objectPoints[0][0],
+ this.objectPoints[i][1] - this.objectPoints[0][1],
+ this.objectPoints[i][2] - this.objectPoints[0][2]];
+
+ vectors[i] = [this.objectVectors[i][0],
+ this.objectVectors[i][1],
+ this.objectVectors[i][2]];
+ }
+
+ while(0.0 === len){
+ n[0] = this.objectVectors[1][1] * this.objectVectors[row][2] -
+ this.objectVectors[1][2] * this.objectVectors[row][1];
+ n[1] = this.objectVectors[1][2] * this.objectVectors[row][0] -
+ this.objectVectors[1][0] * this.objectVectors[row][2];
+ n[2] = this.objectVectors[1][0] * this.objectVectors[row][1] -
+ this.objectVectors[1][1] * this.objectVectors[row][0];
+
+ len = Math.sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]);
+
+ ++ row;
+ }
+
+ for (i = 0; i < 3; ++ i){
+ this.objectNormal[i] = n[i] / len;
+ }
+
+ POS.pseudoInverse(vectors, np, this.objectMatrix);
+};
+
+POS.Posit.prototype.pose = function(imagePoints){
+ var posRotation1 = [[],[],[]], posRotation2 = [[],[],[]], posTranslation = [],
+ rotation1 = [[],[],[]], rotation2 = [[],[],[]], translation1 = [], translation2 = [],
+ error1, error2, valid1, valid2, i, j;
+
+ this.pos(imagePoints, posRotation1, posRotation2, posTranslation);
+
+ valid1 = this.isValid(posRotation1, posTranslation);
+ if (valid1){
+ error1 = this.iterate(imagePoints, posRotation1, posTranslation, rotation1, translation1);
+ }else{
+ error1 = {euclidean: -1.0, pixels: -1, maximum: -1.0};
+ }
+
+ valid2 = this.isValid(posRotation2, posTranslation);
+ if (valid2){
+ error2 = this.iterate(imagePoints, posRotation2, posTranslation, rotation2, translation2);
+ }else{
+ error2 = {euclidean: -1.0, pixels: -1, maximum: -1.0};
+ }
+
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3; ++ j){
+ if (valid1){
+ translation1[i] -= rotation1[i][j] * this.objectPoints[0][j];
+ }
+ if (valid2){
+ translation2[i] -= rotation2[i][j] * this.objectPoints[0][j];
+ }
+ }
+ }
+
+ return error1.euclidean < error2.euclidean?
+ new POS.Pose(error1.pixels, rotation1, translation1, error2.pixels, rotation2, translation2):
+ new POS.Pose(error2.pixels, rotation2, translation2, error1.pixels, rotation1, translation1);
+};
+
+POS.Posit.prototype.pos = function(imagePoints, rotation1, rotation2, translation){
+ var np = this.objectPoints.length, imageVectors = [],
+ i0 = [], j0 = [], ivec = [], jvec = [], row1 = [], row2 = [], row3 = [],
+ i0i0, j0j0, i0j0, delta, q, lambda, mu, scale, i, j;
+
+ for (i = 0; i < np; ++ i){
+ imageVectors[i] = [imagePoints[i].x - imagePoints[0].x,
+ imagePoints[i].y - imagePoints[0].y];
+ }
+
+ //i0 and j0
+ for (i = 0; i < 3; ++ i){
+ i0[i] = 0.0;
+ j0[i] = 0.0;
+ for (j = 0; j < np; ++ j){
+ i0[i] += this.objectMatrix[i][j] * imageVectors[j][0];
+ j0[i] += this.objectMatrix[i][j] * imageVectors[j][1];
+ }
+ }
+
+ i0i0 = i0[0] * i0[0] + i0[1] * i0[1] + i0[2] * i0[2];
+ j0j0 = j0[0] * j0[0] + j0[1] * j0[1] + j0[2] * j0[2];
+ i0j0 = i0[0] * j0[0] + i0[1] * j0[1] + i0[2] * j0[2];
+
+ //Lambda and mu
+ delta = (j0j0 - i0i0) * (j0j0 - i0i0) + 4.0 * (i0j0 * i0j0);
+
+ if (j0j0 - i0i0 >= 0.0){
+ q = (j0j0 - i0i0 + Math.sqrt(delta) ) / 2.0;
+ }else{
+ q = (j0j0 - i0i0 - Math.sqrt(delta) ) / 2.0;
+ }
+
+ if (q >= 0.0){
+ lambda = Math.sqrt(q);
+ if (0.0 === lambda){
+ mu = 0.0;
+ }else{
+ mu = -i0j0 / lambda;
+ }
+ }else{
+ lambda = Math.sqrt( -(i0j0 * i0j0) / q);
+ if (0.0 === lambda){
+ mu = Math.sqrt(i0i0 - j0j0);
+ }else{
+ mu = -i0j0 / lambda;
+ }
+ }
+
+ //First rotation
+ for (i = 0; i < 3; ++ i){
+ ivec[i] = i0[i] + lambda * this.objectNormal[i];
+ jvec[i] = j0[i] + mu * this.objectNormal[i];
+ }
+
+ scale = Math.sqrt(ivec[0] * ivec[0] + ivec[1] * ivec[1] + ivec[2] * ivec[2]);
+
+ for (i = 0; i < 3; ++ i){
+ row1[i] = ivec[i] / scale;
+ row2[i] = jvec[i] / scale;
+ }
+
+ row3[0] = row1[1] * row2[2] - row1[2] * row2[1];
+ row3[1] = row1[2] * row2[0] - row1[0] * row2[2];
+ row3[2] = row1[0] * row2[1] - row1[1] * row2[0];
+
+ for (i = 0; i < 3; ++ i){
+ rotation1[0][i] = row1[i];
+ rotation1[1][i] = row2[i];
+ rotation1[2][i] = row3[i];
+ }
+
+ //Second rotation
+ for (i = 0; i < 3; ++ i){
+ ivec[i] = i0[i] - lambda * this.objectNormal[i];
+ jvec[i] = j0[i] - mu * this.objectNormal[i];
+ }
+
+ for (i = 0; i < 3; ++ i){
+ row1[i] = ivec[i] / scale;
+ row2[i] = jvec[i] / scale;
+ }
+
+ row3[0] = row1[1] * row2[2] - row1[2] * row2[1];
+ row3[1] = row1[2] * row2[0] - row1[0] * row2[2];
+ row3[2] = row1[0] * row2[1] - row1[1] * row2[0];
+
+ for (i = 0; i < 3; ++ i){
+ rotation2[0][i] = row1[i];
+ rotation2[1][i] = row2[i];
+ rotation2[2][i] = row3[i];
+ }
+
+ //Translation
+ translation[0] = imagePoints[0].x / scale;
+ translation[1] = imagePoints[0].y / scale;
+ translation[2] = this.focalLength / scale;
+};
+
+POS.Posit.prototype.isValid = function(rotation, translation){
+ var np = this.objectPoints.length, zmin = Infinity, i = 0, zi;
+
+ for (; i < np; ++ i){
+ zi = translation[2] +
+ (rotation[2][0] * this.objectVectors[i][0] +
+ rotation[2][1] * this.objectVectors[i][1] +
+ rotation[2][2] * this.objectVectors[i][2]);
+ if (zi < zmin){
+ zmin = zi;
+ }
+ }
+
+ return zmin >= 0.0;
+};
+
+POS.Posit.prototype.iterate = function(imagePoints, posRotation, posTranslation, rotation, translation){
+ var np = this.objectPoints.length,
+ oldSopImagePoints = [], sopImagePoints = [],
+ rotation1 = [[],[],[]], rotation2 = [[],[],[]],
+ translation1 = [], translation2 = [],
+ converged = false, iteration = 0,
+ oldImageDifference, imageDifference, factor,
+ error, error1, error2, delta, i, j;
+
+ for (i = 0; i < np; ++ i){
+ oldSopImagePoints[i] = {x: imagePoints[i].x,
+ y: imagePoints[i].y};
+ }
+
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3; ++ j){
+ rotation[i][j] = posRotation[i][j];
+ }
+ translation[i] = posTranslation[i];
+ }
+
+ for (i = 0; i < np; ++ i){
+ factor = 0.0;
+ for (j = 0; j < 3; ++ j){
+ factor += this.objectVectors[i][j] * rotation[2][j] / translation[2];
+ }
+ sopImagePoints[i] = {x: (1.0 + factor) * imagePoints[i].x,
+ y: (1.0 + factor) * imagePoints[i].y};
+ }
+
+ imageDifference = 0.0;
+
+ for (i = 0; i < np; ++ i){
+ imageDifference += Math.abs(sopImagePoints[i].x - oldSopImagePoints[i].x);
+ imageDifference += Math.abs(sopImagePoints[i].y - oldSopImagePoints[i].y);
+ }
+
+ for (i = 0; i < 3; ++ i){
+ translation1[i] = translation[i] -
+ (rotation[i][0] * this.objectPoints[0][0] +
+ rotation[i][1] * this.objectPoints[0][1] +
+ rotation[i][2] * this.objectPoints[0][2]);
+ }
+
+ error = error1 = this.error(imagePoints, rotation, translation1);
+
+ //Convergence
+ converged = (0.0 === error1.pixels) || (imageDifference < 0.01);
+
+ while( iteration ++ < 100 && !converged ){
+
+ for (i = 0; i < np; ++ i){
+ oldSopImagePoints[i].x = sopImagePoints[i].x;
+ oldSopImagePoints[i].y = sopImagePoints[i].y;
+ }
+
+ this.pos(sopImagePoints, rotation1, rotation2, translation);
+
+ for (i = 0; i < 3; ++ i){
+ translation1[i] = translation[i] -
+ (rotation1[i][0] * this.objectPoints[0][0] +
+ rotation1[i][1] * this.objectPoints[0][1] +
+ rotation1[i][2] * this.objectPoints[0][2]);
+
+ translation2[i] = translation[i] -
+ (rotation2[i][0] * this.objectPoints[0][0] +
+ rotation2[i][1] * this.objectPoints[0][1] +
+ rotation2[i][2] * this.objectPoints[0][2]);
+ }
+
+ error1 = this.error(imagePoints, rotation1, translation1);
+ error2 = this.error(imagePoints, rotation2, translation2);
+
+ if ( (error1.euclidean >= 0.0) && (error2.euclidean >= 0.0) ){
+ if (error2.euclidean < error1.euclidean){
+ error = error2;
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3; ++ j){
+ rotation[i][j] = rotation2[i][j];
+ }
+ }
+ }else{
+ error = error1;
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3; ++ j){
+ rotation[i][j] = rotation1[i][j];
+ }
+ }
+ }
+ }
+
+ if ( (error1.euclidean < 0.0) && (error2.euclidean >= 0.0) ){
+ error = error2;
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3; ++ j){
+ rotation[i][j] = rotation2[i][j];
+ }
+ }
+ }
+
+ if ( (error2.euclidean < 0.0) && (error1.euclidean >= 0.0) ){
+ error = error1;
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3; ++ j){
+ rotation[i][j] = rotation1[i][j];
+ }
+ }
+ }
+
+ for (i = 0; i < np; ++ i){
+ factor = 0.0;
+ for (j = 0; j < 3; ++ j){
+ factor += this.objectVectors[i][j] * rotation[2][j] / translation[2];
+ }
+ sopImagePoints[i].x = (1.0 + factor) * imagePoints[i].x;
+ sopImagePoints[i].y = (1.0 + factor) * imagePoints[i].y;
+ }
+
+ oldImageDifference = imageDifference;
+ imageDifference = 0.0;
+
+ for (i = 0; i < np; ++ i){
+ imageDifference += Math.abs(sopImagePoints[i].x - oldSopImagePoints[i].x);
+ imageDifference += Math.abs(sopImagePoints[i].y - oldSopImagePoints[i].y);
+ }
+
+ delta = Math.abs(imageDifference - oldImageDifference);
+
+ converged = (0.0 === error.pixels) || (delta < 0.01);
+ }
+
+ return error;
+};
+
+POS.Posit.prototype.error = function(imagePoints, rotation, translation){
+ var np = this.objectPoints.length,
+ move = [], projection = [], errorvec = [],
+ euclidean = 0.0, pixels = 0.0, maximum = 0.0,
+ i, j, k;
+
+ if ( !this.isValid(rotation, translation) ){
+ return {euclidean: -1.0, pixels: -1, maximum: -1.0};
+ }
+
+ for (i = 0; i < np; ++ i){
+ move[i] = [];
+ for (j = 0; j < 3; ++ j){
+ move[i][j] = translation[j];
+ }
+ }
+
+ for (i = 0; i < np; ++ i){
+ for (j = 0; j < 3; ++ j){
+ for (k = 0; k < 3; ++ k){
+ move[i][j] += rotation[j][k] * this.objectPoints[i][k];
+ }
+ }
+ }
+
+ for (i = 0; i < np; ++ i){
+ projection[i] = [];
+ for (j = 0; j < 2; ++ j){
+ projection[i][j] = this.focalLength * move[i][j] / move[i][2];
+ }
+ }
+
+ for (i = 0; i < np; ++ i){
+ errorvec[i] = [projection[i][0] - imagePoints[i].x,
+ projection[i][1] - imagePoints[i].y];
+ }
+
+ for (i = 0; i < np; ++ i){
+ euclidean += Math.sqrt(errorvec[i][0] * errorvec[i][0] +
+ errorvec[i][1] * errorvec[i][1]);
+
+ pixels += Math.abs( Math.round(projection[i][0]) - Math.round(imagePoints[i].x) ) +
+ Math.abs( Math.round(projection[i][1]) - Math.round(imagePoints[i].y) );
+
+ if (Math.abs(errorvec[i][0]) > maximum){
+ maximum = Math.abs(errorvec[i][0]);
+ }
+ if (Math.abs(errorvec[i][1]) > maximum){
+ maximum = Math.abs(errorvec[i][1]);
+ }
+ }
+
+ return {euclidean: euclidean / np, pixels: pixels, maximum: maximum};
+};
+
+POS.pseudoInverse = function(a, n, b){
+ var w = [], v = [[],[],[]], s = [[],[],[]],
+ wmax = 0.0, cn = 0,
+ i, j, k;
+
+ SVD.svdcmp(a, n, 3, w, v);
+
+ for (i = 0; i < 3; ++ i){
+ if (w[i] > wmax){
+ wmax = w[i];
+ }
+ }
+
+ wmax *= 0.01;
+
+ for (i = 0; i < 3; ++ i){
+ if (w[i] < wmax){
+ w[i] = 0.0;
+ }
+ }
+
+ for (j = 0; j < 3; ++ j){
+ if (0.0 === w[j]){
+ ++ cn;
+ for (k = j; k < 2; ++ k){
+ for (i = 0; i < n; ++ i){
+ a[i][k] = a[i][k + 1];
+ }
+ for (i = 0; i < 3; ++ i){
+ v[i][k] = v[i][k + 1];
+ }
+ }
+ }
+ }
+
+ for (j = 0; j < 2; ++ j){
+ if (0.0 === w[j]){
+ w[j] = w[j + 1];
+ }
+ }
+
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3 - cn; ++ j){
+ s[i][j] = v[i][j] / w[j];
+ }
+ }
+
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < n; ++ j){
+ b[i][j] = 0.0;
+ for (k = 0; k < 3 - cn; ++ k){
+ b[i][j] += s[i][k] * a[j][k];
+ }
+ }
+ }
+};
+
+POS.Pose = function(error1, rotation1, translation1, error2, rotation2, translation2){
+ this.bestError = error1;
+ this.bestRotation = rotation1;
+ this.bestTranslation = translation1;
+ this.alternativeError = error2;
+ this.alternativeRotation = rotation2;
+ this.alternativeTranslation = translation2;
+};
+/*
+Copyright (c) 2012 Juan Mellado
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/*
+References:
+- "Numerical Recipes in C - Second Edition"
+ http://www.nr.com/
+*/
+
+var SVD = SVD || {};
+
+SVD.svdcmp = function(a, m, n, w, v){
+ var flag, i, its, j, jj, k, l, nm,
+ anorm = 0.0, c, f, g = 0.0, h, s, scale = 0.0, x, y, z, rv1 = [];
+
+ //Householder reduction to bidiagonal form
+ for (i = 0; i < n; ++ i){
+ l = i + 1;
+ rv1[i] = scale * g;
+ g = s = scale = 0.0;
+ if (i < m){
+ for (k = i; k < m; ++ k){
+ scale += Math.abs( a[k][i] );
+ }
+ if (0.0 !== scale){
+ for (k = i; k < m; ++ k){
+ a[k][i] /= scale;
+ s += a[k][i] * a[k][i];
+ }
+ f = a[i][i];
+ g = -SVD.sign( Math.sqrt(s), f );
+ h = f * g - s;
+ a[i][i] = f - g;
+ for (j = l; j < n; ++ j){
+ for (s = 0.0, k = i; k < m; ++ k){
+ s += a[k][i] * a[k][j];
+ }
+ f = s / h;
+ for (k = i; k < m; ++ k){
+ a[k][j] += f * a[k][i];
+ }
+ }
+ for (k = i; k < m; ++ k){
+ a[k][i] *= scale;
+ }
+ }
+ }
+ w[i] = scale * g;
+ g = s = scale = 0.0;
+ if ( (i < m) && (i !== n - 1) ){
+ for (k = l; k < n; ++ k){
+ scale += Math.abs( a[i][k] );
+ }
+ if (0.0 !== scale){
+ for (k = l; k < n; ++ k){
+ a[i][k] /= scale;
+ s += a[i][k] * a[i][k];
+ }
+ f = a[i][l];
+ g = -SVD.sign( Math.sqrt(s), f );
+ h = f * g - s;
+ a[i][l] = f - g;
+ for (k = l; k < n; ++ k){
+ rv1[k] = a[i][k] / h;
+ }
+ for (j = l; j < m; ++ j){
+ for (s = 0.0, k = l; k < n; ++ k){
+ s += a[j][k] * a[i][k];
+ }
+ for (k = l; k < n; ++ k){
+ a[j][k] += s * rv1[k];
+ }
+ }
+ for (k = l; k < n; ++ k){
+ a[i][k] *= scale;
+ }
+ }
+ }
+ anorm = Math.max(anorm, ( Math.abs( w[i] ) + Math.abs( rv1[i] ) ) );
+ }
+
+ //Acumulation of right-hand transformation
+ for (i = n - 1; i >= 0; -- i){
+ if (i < n - 1){
+ if (0.0 !== g){
+ for (j = l; j < n; ++ j){
+ v[j][i] = ( a[i][j] / a[i][l] ) / g;
+ }
+ for (j = l; j < n; ++ j){
+ for (s = 0.0, k = l; k < n; ++ k){
+ s += a[i][k] * v[k][j];
+ }
+ for (k = l; k < n; ++ k){
+ v[k][j] += s * v[k][i];
+ }
+ }
+ }
+ for (j = l; j < n; ++ j){
+ v[i][j] = v[j][i] = 0.0;
+ }
+ }
+ v[i][i] = 1.0;
+ g = rv1[i];
+ l = i;
+ }
+
+ //Acumulation of left-hand transformation
+ for (i = Math.min(n, m) - 1; i >= 0; -- i){
+ l = i + 1;
+ g = w[i];
+ for (j = l; j < n; ++ j){
+ a[i][j] = 0.0;
+ }
+ if (0.0 !== g){
+ g = 1.0 / g;
+ for (j = l; j < n; ++ j){
+ for (s = 0.0, k = l; k < m; ++ k){
+ s += a[k][i] * a[k][j];
+ }
+ f = (s / a[i][i]) * g;
+ for (k = i; k < m; ++ k){
+ a[k][j] += f * a[k][i];
+ }
+ }
+ for (j = i; j < m; ++ j){
+ a[j][i] *= g;
+ }
+ }else{
+ for (j = i; j < m; ++ j){
+ a[j][i] = 0.0;
+ }
+ }
+ ++ a[i][i];
+ }
+
+ //Diagonalization of the bidiagonal form
+ for (k = n - 1; k >= 0; -- k){
+ for (its = 1; its <= 30; ++ its){
+ flag = true;
+ for (l = k; l >= 0; -- l){
+ nm = l - 1;
+ if ( Math.abs( rv1[l] ) + anorm === anorm ){
+ flag = false;
+ break;
+ }
+ if ( Math.abs( w[nm] ) + anorm === anorm ){
+ break;
+ }
+ }
+ if (flag){
+ c = 0.0;
+ s = 1.0;
+ for (i = l; i <= k; ++ i){
+ f = s * rv1[i];
+ if ( Math.abs(f) + anorm === anorm ){
+ break;
+ }
+ g = w[i];
+ h = SVD.pythag(f, g);
+ w[i] = h;
+ h = 1.0 / h;
+ c = g * h;
+ s = -f * h;
+ for (j = 1; j <= m; ++ j){
+ y = a[j][nm];
+ z = a[j][i];
+ a[j][nm] = y * c + z * s;
+ a[j][i] = z * c - y * s;
+ }
+ }
+ }
+
+ //Convergence
+ z = w[k];
+ if (l === k){
+ if (z < 0.0){
+ w[k] = -z;
+ for (j = 0; j < n; ++ j){
+ v[j][k] = -v[j][k];
+ }
+ }
+ break;
+ }
+
+ if (30 === its){
+ return false;
+ }
+
+ //Shift from bottom 2-by-2 minor
+ x = w[l];
+ nm = k - 1;
+ y = w[nm];
+ g = rv1[nm];
+ h = rv1[k];
+ f = ( (y - z) * (y + z) + (g - h) * (g + h) ) / (2.0 * h * y);
+ g = SVD.pythag( f, 1.0 );
+ f = ( (x - z) * (x + z) + h * ( (y / (f + SVD.sign(g, f) ) ) - h) ) / x;
+
+ //Next QR transformation
+ c = s = 1.0;
+ for (j = l; j <= nm; ++ j){
+ i = j + 1;
+ g = rv1[i];
+ y = w[i];
+ h = s * g;
+ g = c * g;
+ z = SVD.pythag(f, h);
+ rv1[j] = z;
+ c = f / z;
+ s = h / z;
+ f = x * c + g * s;
+ g = g * c - x * s;
+ h = y * s;
+ y *= c;
+ for (jj = 0; jj < n; ++ jj){
+ x = v[jj][j];
+ z = v[jj][i];
+ v[jj][j] = x * c + z * s;
+ v[jj][i] = z * c - x * s;
+ }
+ z = SVD.pythag(f, h);
+ w[j] = z;
+ if (0.0 !== z){
+ z = 1.0 / z;
+ c = f * z;
+ s = h * z;
+ }
+ f = c * g + s * y;
+ x = c * y - s * g;
+ for (jj = 0; jj < m; ++ jj){
+ y = a[jj][j];
+ z = a[jj][i];
+ a[jj][j] = y * c + z * s;
+ a[jj][i] = z * c - y * s;
+ }
+ }
+ rv1[l] = 0.0;
+ rv1[k] = f;
+ w[k] = x;
+ }
+ }
+
+ return true;
+};
+
+SVD.pythag = function(a, b){
+ var at = Math.abs(a), bt = Math.abs(b), ct;
+
+ if (at > bt){
+ ct = bt / at;
+ return at * Math.sqrt(1.0 + ct * ct);
+ }
+
+ if (0.0 === bt){
+ return 0.0;
+ }
+
+ ct = at / bt;
+ return bt * Math.sqrt(1.0 + ct * ct);
+};
+
+SVD.sign = function(a, b){
+ return b >= 0.0? Math.abs(a): -Math.abs(a);
+};
+var THREEx = THREEx || {}
+
+THREEx.ArucoContext = function(markerSize){
+ this.canvas = document.createElement('canvas');
+ this.canvas.width = 80*4
+ this.canvas.height = 60*4
+
+ // experiment with imageSmoothingEnabled
+ var imageSmoothingEnabled = false
+ var context = this.canvas.getContext('2d');
+ context.mozImageSmoothingEnabled = imageSmoothingEnabled;
+ context.webkitImageSmoothingEnabled = imageSmoothingEnabled;
+ context.msImageSmoothingEnabled = imageSmoothingEnabled;
+ context.imageSmoothingEnabled = imageSmoothingEnabled;
+
+ this.detector = new AR.Detector();
+ this.posit = new POS.Posit(markerSize, this.canvas.width);
+}
+
+THREEx.ArucoContext.prototype.detect = function (videoElement) {
+ var _this = this
+ var canvas = this.canvas
+
+ // get imageData from videoElement
+ var context = canvas.getContext('2d');
+ context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
+ var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
+
+ // detect markers in imageData
+ var detectedMarkers = this.detector.detect(imageData);
+
+ // compute the pose for each detectedMarkers
+ detectedMarkers.forEach(function(detectedMarker){
+ // debugger
+ var markerCorners = detectedMarker.corners;
+
+ // convert the corners
+ var poseCorners = new Array(markerCorners.length)
+ for (var i = 0; i < markerCorners.length; ++ i){
+ var markerCorner = markerCorners[i];
+ poseCorners[i] = {
+ x: markerCorner.x - (canvas.width / 2),
+ y: -markerCorner.y + (canvas.height/ 2)
+ }
+ }
+
+ // estimate pose from corners
+ detectedMarker.pose = _this.posit.pose(poseCorners);
+ })
+
+ return detectedMarkers
+};
+
+THREEx.ArucoContext.updateObject3D = function(object3D, detectedMarker){
+ var rotation = detectedMarker.pose.bestRotation
+ var translation = detectedMarker.pose.bestTranslation
+
+ object3D.position.x = translation[0];
+ object3D.position.y = translation[1];
+ object3D.position.z = -translation[2];
+
+ object3D.rotation.x = -Math.asin(-rotation[1][2]);
+ object3D.rotation.y = -Math.atan2(rotation[0][2], rotation[2][2]);
+ object3D.rotation.z = Math.atan2(rotation[1][0], rotation[1][1]);
+
+ object3D.scale.x = markerSize;
+ object3D.scale.y = markerSize;
+ object3D.scale.z = markerSize;
+}
+var THREEx = THREEx || {}
+
+THREEx.ArucoDebug = function(arucoContext){
+ this.arucoContext = arucoContext
+
+ this.canvasElement = document.createElement('canvas');
+ this.canvasElement.width = this.arucoContext.canvas.width
+ this.canvasElement.height = this.arucoContext.canvas.height
+}
+
+THREEx.ArucoDebug.prototype.clear = function(){
+ var canvas = this.canvasElement
+ var context = canvas.getContext('2d');
+ context.clearRect(0,0,canvas.width, canvas.height)
+
+}
+
+THREEx.ArucoDebug.prototype.drawContoursContours = function(){
+ var contours = this.arucoContext.detector.contours
+ var canvas = this.canvasElement
+ this.drawContours(contours, 0, 0, canvas.width, canvas.height, function(hole){
+ return hole? "magenta": "blue"
+ })
+}
+
+THREEx.ArucoDebug.prototype.drawContoursPolys = function(){
+ var contours = this.arucoContext.detector.polys
+ var canvas = this.canvasElement
+ this.drawContours(contours, 0, 0, canvas.width, canvas.height, function(){
+ return 'green'
+ })
+}
+
+
+THREEx.ArucoDebug.prototype.drawContoursCandidates = function(){
+ var contours = this.arucoContext.detector.candidates
+ var canvas = this.canvasElement
+ this.drawContours(contours, 0, 0, canvas.width, canvas.height, function(){
+ return 'red'
+ })
+}
+
+THREEx.ArucoDebug.prototype.drawContours = function(contours, x, y, width, height, fn){
+ var i = contours.length, j, contour, point;
+ var canvas = this.canvasElement
+ var context = canvas.getContext('2d');
+
+ context.save();
+ while(i --){
+ contour = contours[i];
+ context.strokeStyle = fn(contour.hole);
+ context.beginPath();
+ for (j = 0; j < contour.length; ++ j){
+ point = contour[j];
+ context.moveTo(x + point.x, y + point.y);
+ point = contour[(j + 1) % contour.length];
+ context.lineTo(x + point.x, y + point.y);
+ }
+
+ context.stroke();
+ context.closePath();
+ }
+ context.restore();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Code Separator
+//////////////////////////////////////////////////////////////////////////////
+
+THREEx.ArucoDebug.prototype.drawDetectorGrey = function(){
+ var cvImage = arucoContext.detector.grey
+ this.drawCVImage( cvImage )
+}
+
+THREEx.ArucoDebug.prototype.drawDetectorThreshold = function(){
+ var cvImage = arucoContext.detector.thres
+ this.drawCVImage( cvImage )
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Code Separator
+//////////////////////////////////////////////////////////////////////////////
+THREEx.ArucoDebug.prototype.drawCVImage = function(cvImage){
+ var detector = this.arucoContext.detector
+
+ var canvas = this.canvasElement
+ var context = canvas.getContext('2d');
+
+ var imageData = context.createImageData(canvas.width, canvas.height);
+ copyImage(cvImage, imageData)
+ context.putImageData( imageData, 0, 0);
+
+ return
+ function copyImage(src, dst){
+ var i = src.data.length, j = (i * 4) + 3;
+
+ while(i --){
+ dst.data[j -= 4] = 255;
+ dst.data[j - 1] = dst.data[j - 2] = dst.data[j - 3] = src.data[i];
+ }
+
+ return dst;
+ };
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+// Code Separator
+//////////////////////////////////////////////////////////////////////////////
+THREEx.ArucoDebug.prototype.drawVideo = function(videoElement){
+ var canvas = this.canvasElement
+ var context = canvas.getContext('2d');
+ context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Code Separator
+//////////////////////////////////////////////////////////////////////////////
+THREEx.ArucoDebug.prototype.drawMarkerIDs = function(markers){
+ var canvas = this.canvasElement
+ var context = canvas.getContext('2d');
+ var corners, corner, x, y, i, j;
+
+ context.save();
+ context.strokeStyle = "blue";
+ context.lineWidth = 1;
+
+ for (i = 0; i !== markers.length; ++ i){
+ corners = markers[i].corners;
+
+ x = Infinity;
+ y = Infinity;
+
+ for (j = 0; j !== corners.length; ++ j){
+ corner = corners[j];
+
+ x = Math.min(x, corner.x);
+ y = Math.min(y, corner.y);
+ }
+ context.strokeText(markers[i].id, x, y)
+ }
+ context.restore();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Code Separator
+//////////////////////////////////////////////////////////////////////////////
+THREEx.ArucoDebug.prototype.drawMarkerCorners = function(markers){
+ var canvas = this.canvasElement
+ var corners, corner, i, j;
+ var context = canvas.getContext('2d');
+ context.save();
+ context.lineWidth = 3;
+
+ for (i = 0; i < markers.length; ++ i){
+ corners = markers[i].corners;
+
+ context.strokeStyle = 'red';
+ context.beginPath();
+
+ for (j = 0; j < corners.length; ++ j){
+ corner = corners[j];
+ context.moveTo(corner.x, corner.y);
+ corner = corners[(j + 1) % corners.length];
+ context.lineTo(corner.x, corner.y);
+ }
+
+ context.stroke();
+ context.closePath();
+
+ context.strokeStyle = 'green';
+ context.strokeRect(corners[0].x - 2, corners[0].y - 2, 4, 4);
+ }
+ context.restore();
+
+}
+var THREEx = THREEx || {}
+
+THREEx.ArucoMarkerGenerator = function(){
+
+}
+
+THREEx.ArucoMarkerGenerator.createSVG = function(markerId, svgSize){
+ var domElement = document.createElement('div');
+ domElement.innerHTML = new ArucoMarker(markerId).toSVG(svgSize);
+ return domElement
+}
+
+THREEx.ArucoMarkerGenerator.createIMG = function(markerId, svgSize){
+ // get the svgElement
+ var svgElement = THREEx.ArucoMarkerGenerator.createSVG(markerId, svgSize).firstChild
+
+ // build imageURL
+ var xml = new XMLSerializer().serializeToString(svgElement);
+ var imageURL = 'data:image/svg+xml;base64,' + btoa(xml)
+
+ // create imageElement
+ var imageElement = document.createElement('img');
+ imageElement.src = imageURL
+
+ // return imageElement
+ return imageElement;
+}
diff --git a/three.js/examples/threex-aruco/examples/debug.html b/three.js/examples/threex-aruco/examples/debug.html
new file mode 100644
index 0000000000000000000000000000000000000000..d287797f6b80c311fbb1ed77bfe3e2c0cbc61295
--- /dev/null
+++ b/three.js/examples/threex-aruco/examples/debug.html
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/three.js/examples/threex-aruco/examples/images/1001.png b/three.js/examples/threex-aruco/examples/images/1001.png
new file mode 100644
index 0000000000000000000000000000000000000000..da1bb06586a53b4d64cfff2349d6967c38bbc41f
Binary files /dev/null and b/three.js/examples/threex-aruco/examples/images/1001.png differ
diff --git a/three.js/examples/threex-aruco/examples/images/1002.png b/three.js/examples/threex-aruco/examples/images/1002.png
new file mode 100644
index 0000000000000000000000000000000000000000..f9766a48ede8c7312ae95bd638cccf973d6d6154
Binary files /dev/null and b/three.js/examples/threex-aruco/examples/images/1002.png differ
diff --git a/three.js/examples/threex-aruco/examples/images/1003.png b/three.js/examples/threex-aruco/examples/images/1003.png
new file mode 100644
index 0000000000000000000000000000000000000000..f84a24806383dabdec8bde998691493a0035cf0c
Binary files /dev/null and b/three.js/examples/threex-aruco/examples/images/1003.png differ
diff --git a/three.js/examples/threex-aruco/examples/images/1004.png b/three.js/examples/threex-aruco/examples/images/1004.png
new file mode 100644
index 0000000000000000000000000000000000000000..5d7ab6f59fefb3bd8739e1ea611ceb0583e3bc2a
Binary files /dev/null and b/three.js/examples/threex-aruco/examples/images/1004.png differ
diff --git a/three.js/examples/threex-aruco/examples/marker-generator.html b/three.js/examples/threex-aruco/examples/marker-generator.html
new file mode 100644
index 0000000000000000000000000000000000000000..61f7885ced87667388f3dc4b425f30502396c33f
--- /dev/null
+++ b/three.js/examples/threex-aruco/examples/marker-generator.html
@@ -0,0 +1,18 @@
+
+
+
diff --git a/three.js/examples/threex-aruco/examples/performance.html b/three.js/examples/threex-aruco/examples/performance.html
new file mode 100644
index 0000000000000000000000000000000000000000..435937a49f11c4a81e1bffd8e62d35cbb03c72ba
--- /dev/null
+++ b/three.js/examples/threex-aruco/examples/performance.html
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![](images/1001.png)
+
+
+
diff --git a/three.js/examples/threex-aruco/threex-arucocontext.js b/three.js/examples/threex-aruco/threex-arucocontext.js
new file mode 100644
index 0000000000000000000000000000000000000000..c40dd624444fc99883409b501f72c4c28ea0b8c1
--- /dev/null
+++ b/three.js/examples/threex-aruco/threex-arucocontext.js
@@ -0,0 +1,69 @@
+var THREEx = THREEx || {}
+
+THREEx.ArucoContext = function(markerSize){
+ this.canvas = document.createElement('canvas');
+ this.canvas.width = 80*4
+ this.canvas.height = 60*4
+
+ // experiment with imageSmoothingEnabled
+ var imageSmoothingEnabled = false
+ var context = this.canvas.getContext('2d');
+ context.mozImageSmoothingEnabled = imageSmoothingEnabled;
+ context.webkitImageSmoothingEnabled = imageSmoothingEnabled;
+ context.msImageSmoothingEnabled = imageSmoothingEnabled;
+ context.imageSmoothingEnabled = imageSmoothingEnabled;
+
+ this.detector = new AR.Detector();
+ this.posit = new POS.Posit(markerSize, this.canvas.width);
+}
+
+THREEx.ArucoContext.prototype.detect = function (videoElement) {
+ var _this = this
+ var canvas = this.canvas
+
+ // get imageData from videoElement
+ var context = canvas.getContext('2d');
+ context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
+ var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
+
+ // detect markers in imageData
+ var detectedMarkers = this.detector.detect(imageData);
+
+ // compute the pose for each detectedMarkers
+ // FIXME: i fix we should have one posit estimator per marker
+ detectedMarkers.forEach(function(detectedMarker){
+ var markerCorners = detectedMarker.corners;
+
+ // convert the corners
+ var poseCorners = new Array(markerCorners.length)
+ for (var i = 0; i < markerCorners.length; ++ i){
+ var markerCorner = markerCorners[i];
+ poseCorners[i] = {
+ x: markerCorner.x - (canvas.width / 2),
+ y: -markerCorner.y + (canvas.height/ 2)
+ }
+ }
+
+ // estimate pose from corners
+ detectedMarker.pose = _this.posit.pose(poseCorners);
+ })
+
+ return detectedMarkers
+};
+
+THREEx.ArucoContext.updateObject3D = function(object3D, detectedMarker){
+ var rotation = detectedMarker.pose.bestRotation
+ var translation = detectedMarker.pose.bestTranslation
+
+ object3D.position.x = translation[0];
+ object3D.position.y = translation[1];
+ object3D.position.z = -translation[2];
+
+ object3D.rotation.x = -Math.asin(-rotation[1][2]);
+ object3D.rotation.y = -Math.atan2(rotation[0][2], rotation[2][2]);
+ object3D.rotation.z = Math.atan2(rotation[1][0], rotation[1][1]);
+
+ object3D.scale.x = markerSize;
+ object3D.scale.y = markerSize;
+ object3D.scale.z = markerSize;
+}
diff --git a/three.js/examples/threex-aruco/threex-arucodebug.js b/three.js/examples/threex-aruco/threex-arucodebug.js
new file mode 100644
index 0000000000000000000000000000000000000000..82cee2a7a8a839844f15f90d95ae8a72cfaaeedb
--- /dev/null
+++ b/three.js/examples/threex-aruco/threex-arucodebug.js
@@ -0,0 +1,175 @@
+var THREEx = THREEx || {}
+
+THREEx.ArucoDebug = function(arucoContext){
+ this.arucoContext = arucoContext
+
+ this.canvasElement = document.createElement('canvas');
+ this.canvasElement.width = this.arucoContext.canvas.width
+ this.canvasElement.height = this.arucoContext.canvas.height
+}
+
+THREEx.ArucoDebug.prototype.clear = function(){
+ var canvas = this.canvasElement
+ var context = canvas.getContext('2d');
+ context.clearRect(0,0,canvas.width, canvas.height)
+
+}
+
+THREEx.ArucoDebug.prototype.drawContoursContours = function(){
+ var contours = this.arucoContext.detector.contours
+ var canvas = this.canvasElement
+ this.drawContours(contours, 0, 0, canvas.width, canvas.height, function(hole){
+ return hole? "magenta": "blue"
+ })
+}
+
+THREEx.ArucoDebug.prototype.drawContoursPolys = function(){
+ var contours = this.arucoContext.detector.polys
+ var canvas = this.canvasElement
+ this.drawContours(contours, 0, 0, canvas.width, canvas.height, function(){
+ return 'green'
+ })
+}
+
+
+THREEx.ArucoDebug.prototype.drawContoursCandidates = function(){
+ var contours = this.arucoContext.detector.candidates
+ var canvas = this.canvasElement
+ this.drawContours(contours, 0, 0, canvas.width, canvas.height, function(){
+ return 'red'
+ })
+}
+
+THREEx.ArucoDebug.prototype.drawContours = function(contours, x, y, width, height, fn){
+ var i = contours.length, j, contour, point;
+ var canvas = this.canvasElement
+ var context = canvas.getContext('2d');
+
+ context.save();
+ while(i --){
+ contour = contours[i];
+ context.strokeStyle = fn(contour.hole);
+ context.beginPath();
+ for (j = 0; j < contour.length; ++ j){
+ point = contour[j];
+ context.moveTo(x + point.x, y + point.y);
+ point = contour[(j + 1) % contour.length];
+ context.lineTo(x + point.x, y + point.y);
+ }
+
+ context.stroke();
+ context.closePath();
+ }
+ context.restore();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Code Separator
+//////////////////////////////////////////////////////////////////////////////
+
+THREEx.ArucoDebug.prototype.drawDetectorGrey = function(){
+ var cvImage = arucoContext.detector.grey
+ this.drawCVImage( cvImage )
+}
+
+THREEx.ArucoDebug.prototype.drawDetectorThreshold = function(){
+ var cvImage = arucoContext.detector.thres
+ this.drawCVImage( cvImage )
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Code Separator
+//////////////////////////////////////////////////////////////////////////////
+THREEx.ArucoDebug.prototype.drawCVImage = function(cvImage){
+ var detector = this.arucoContext.detector
+
+ var canvas = this.canvasElement
+ var context = canvas.getContext('2d');
+
+ var imageData = context.createImageData(canvas.width, canvas.height);
+ this.copyCVImage2ImageData(cvImage, imageData)
+ context.putImageData( imageData, 0, 0);
+}
+
+
+THREEx.ArucoDebug.prototype.copyCVImage2ImageData = function(cvImage, imageData){
+ var i = cvImage.data.length, j = (i * 4) + 3;
+
+ while(i --){
+ imageData.data[j -= 4] = 255;
+ imageData.data[j - 1] = imageData.data[j - 2] = imageData.data[j - 3] = cvImage.data[i];
+ }
+
+ return imageData;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// Code Separator
+//////////////////////////////////////////////////////////////////////////////
+THREEx.ArucoDebug.prototype.drawVideo = function(videoElement){
+ var canvas = this.canvasElement
+ var context = canvas.getContext('2d');
+ context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Code Separator
+//////////////////////////////////////////////////////////////////////////////
+THREEx.ArucoDebug.prototype.drawMarkerIDs = function(markers){
+ var canvas = this.canvasElement
+ var context = canvas.getContext('2d');
+ var corners, corner, x, y, i, j;
+
+ context.save();
+ context.strokeStyle = "blue";
+ context.lineWidth = 1;
+
+ for (i = 0; i !== markers.length; ++ i){
+ corners = markers[i].corners;
+
+ x = Infinity;
+ y = Infinity;
+
+ for (j = 0; j !== corners.length; ++ j){
+ corner = corners[j];
+
+ x = Math.min(x, corner.x);
+ y = Math.min(y, corner.y);
+ }
+ context.strokeText(markers[i].id, x, y)
+ }
+ context.restore();
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Code Separator
+//////////////////////////////////////////////////////////////////////////////
+THREEx.ArucoDebug.prototype.drawMarkerCorners = function(markers){
+ var canvas = this.canvasElement
+ var corners, corner, i, j;
+ var context = canvas.getContext('2d');
+ context.save();
+ context.lineWidth = 3;
+
+ for (i = 0; i < markers.length; ++ i){
+ corners = markers[i].corners;
+
+ context.strokeStyle = 'red';
+ context.beginPath();
+
+ for (j = 0; j < corners.length; ++ j){
+ corner = corners[j];
+ context.moveTo(corner.x, corner.y);
+ corner = corners[(j + 1) % corners.length];
+ context.lineTo(corner.x, corner.y);
+ }
+
+ context.stroke();
+ context.closePath();
+
+ context.strokeStyle = 'green';
+ context.strokeRect(corners[0].x - 2, corners[0].y - 2, 4, 4);
+ }
+ context.restore();
+
+}
diff --git a/three.js/examples/threex-aruco/threex-arucomarkergenerator.js b/three.js/examples/threex-aruco/threex-arucomarkergenerator.js
new file mode 100644
index 0000000000000000000000000000000000000000..b1e1a57a3c38e3b1fb7404d570ed5be6cc5d87b0
--- /dev/null
+++ b/three.js/examples/threex-aruco/threex-arucomarkergenerator.js
@@ -0,0 +1,59 @@
+var THREEx = THREEx || {}
+
+THREEx.ArucoMarkerGenerator = function(){
+
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Code Separator
+//////////////////////////////////////////////////////////////////////////////
+THREEx.ArucoMarkerGenerator.createSVG = function(markerId, svgSize){
+ var domElement = document.createElement('div');
+ domElement.innerHTML = new ArucoMarker(markerId).toSVG(svgSize);
+ return domElement
+}
+
+THREEx.ArucoMarkerGenerator.createImage = function(markerId, width){
+ // create canvas
+ var canvas = this.createCanvas(markerId, width)
+ var imageURL = canvas.toDataURL()
+
+ // create imageElement
+ var imageElement = document.createElement('img');
+ imageElement.src = imageURL
+
+ // return imageElement
+ return imageElement;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Code Separator
+//////////////////////////////////////////////////////////////////////////////
+THREEx.ArucoMarkerGenerator.createCanvas = function(markerId, width){
+ var canvas = document.createElement('canvas');
+ var context = canvas.getContext('2d')
+ canvas.width = width
+ canvas.height = width
+
+ var arucoMarker = new ArucoMarker(markerId)
+ var marker = arucoMarker.markerMatrix()
+
+ var margin = canvas.width*0.1
+ var innerW = width-margin*2
+ var squareW = innerW/7
+
+ context.fillStyle = 'white'
+ context.fillRect(0, 0, canvas.width, canvas.height)
+ context.fillStyle = 'black'
+ context.fillRect(margin, margin, canvas.width-margin*2, canvas.height-margin*2)
+
+ for(var y = 0; y < 5; y++){
+ for(var x = 0; x < 5; x++){
+ if (marker[x][y] !== 1) continue
+ context.fillStyle = 'white'
+ context.fillRect(margin+(x+1)*squareW, margin+(y+1)*squareW, squareW+1, squareW+1)
+ }
+ }
+
+ return canvas
+}
diff --git a/three.js/examples/threex-aruco/vendor/aruco-marker.js b/three.js/examples/threex-aruco/vendor/aruco-marker.js
new file mode 100644
index 0000000000000000000000000000000000000000..dcfe5bb8b6f0fe4bf25e821a15928101ae0cb6ce
--- /dev/null
+++ b/three.js/examples/threex-aruco/vendor/aruco-marker.js
@@ -0,0 +1,92 @@
+/*! aruco-marker 1.0.0 2014-06-19 - MIT Licensed, see http://github.com/bhollis/aruco-marker */
+// Export for use via AMD, Node.js, or a browser global.
+// See https://github.com/umdjs/umd/blob/master/returnExportsGlobal.js
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module.
+ define([], function () {
+ return (root.ArucoMarker = factory());
+ });
+ } else if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like enviroments that support module.exports,
+ // like Node.
+ module.exports = factory();
+ } else {
+ // Browser globals
+ root.ArucoMarker = factory();
+ }
+}(this, function () {
+
+ // Create an ArucoMarker object by its ID, which can then be used to generate images.
+ // The id must be in the range [0..1023]
+ // Based on https://github.com/rmsalinas/aruco/blob/master/trunk/src/arucofidmarkers.cpp
+ function ArucoMarker(id) {
+ if (id < 0 || id > 1023) {
+ throw new RangeError('Marker ID must be in the range [0..1023]');
+ }
+
+ this.id = id;
+ }
+
+ ArucoMarker.prototype = {
+ // Generate a marker as a 5x5 matrix of 0s and 1s.
+ markerMatrix: function() {
+ var ids = [16, 23, 9, 14];
+ var index, val, x, y;
+ var marker = [[0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0]];
+
+ for (y = 0; y < 5; y++) {
+ index = (this.id >> 2 * (4 - y)) & 3;
+ val = ids[index];
+ for (x = 0; x < 5; x++) {
+ if ((val >> (4 - x)) & 1) {
+ marker[x][y] = 1;
+ } else {
+ marker[x][y] = 0;
+ }
+ }
+ }
+
+ return marker;
+ },
+
+ // Create an SVG image of the marker, as a string.
+ // Optionally pass a size (in any SVG-compatible units) or leave it out to size it on your own.
+ toSVG: function(size) {
+ var x, y;
+ var marker = this.markerMatrix();
+ var image;
+
+ if (size) {
+ size = 'height="' + size + '" width="' + size + '"';
+ } else {
+ size = '';
+ }
+
+ image = '';
+
+ return image;
+ }
+ };
+
+ return ArucoMarker;
+}));
diff --git a/three.js/examples/threex-aruco/vendor/js-aruco/src/aruco.js b/three.js/examples/threex-aruco/vendor/js-aruco/src/aruco.js
new file mode 100644
index 0000000000000000000000000000000000000000..890eac7f212e7814b050ab262407ad6587699e92
--- /dev/null
+++ b/three.js/examples/threex-aruco/vendor/js-aruco/src/aruco.js
@@ -0,0 +1,268 @@
+/*
+Copyright (c) 2011 Juan Mellado
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/*
+References:
+- "ArUco: a minimal library for Augmented Reality applications based on OpenCv"
+http://www.uco.es/investiga/grupos/ava/node/26
+*/
+
+var AR = AR || {};
+
+AR.Marker = function(id, corners){
+ this.id = id;
+ this.corners = corners;
+};
+
+AR.Detector = function(){
+ this.grey = new CV.Image();
+ this.thres = new CV.Image();
+ this.homography = new CV.Image();
+ this.binary = [];
+ this.contours = [];
+ this.polys = [];
+ this.candidates = [];
+};
+
+AR.Detector.prototype.detect = function(image){
+ CV.grayscale(image, this.grey);
+ CV.adaptiveThreshold(this.grey, this.thres, 2, 7);
+
+ this.contours = CV.findContours(this.thres, this.binary);
+
+ this.candidates = this.findCandidates(this.contours, image.width * 0.20, 0.05, 10);
+ this.candidates = this.clockwiseCorners(this.candidates);
+ this.candidates = this.notTooNear(this.candidates, 10);
+
+ return this.findMarkers(this.grey, this.candidates, 49);
+};
+
+AR.Detector.prototype.findCandidates = function(contours, minSize, epsilon, minLength){
+ var candidates = [], len = contours.length, contour, poly, i;
+
+ this.polys = [];
+
+ for (i = 0; i < len; ++ i){
+ contour = contours[i];
+
+ if (contour.length >= minSize){
+ poly = CV.approxPolyDP(contour, contour.length * epsilon);
+
+ this.polys.push(poly);
+
+ if ( (4 === poly.length) && ( CV.isContourConvex(poly) ) ){
+
+ if ( CV.minEdgeLength(poly) >= minLength){
+ candidates.push(poly);
+ }
+ }
+ }
+ }
+
+ return candidates;
+};
+
+AR.Detector.prototype.clockwiseCorners = function(candidates){
+ var len = candidates.length, dx1, dx2, dy1, dy2, swap, i;
+
+ for (i = 0; i < len; ++ i){
+ dx1 = candidates[i][1].x - candidates[i][0].x;
+ dy1 = candidates[i][1].y - candidates[i][0].y;
+ dx2 = candidates[i][2].x - candidates[i][0].x;
+ dy2 = candidates[i][2].y - candidates[i][0].y;
+
+ if ( (dx1 * dy2 - dy1 * dx2) < 0){
+ swap = candidates[i][1];
+ candidates[i][1] = candidates[i][3];
+ candidates[i][3] = swap;
+ }
+ }
+
+ return candidates;
+};
+
+AR.Detector.prototype.notTooNear = function(candidates, minDist){
+ var notTooNear = [], len = candidates.length, dist, dx, dy, i, j, k;
+
+ for (i = 0; i < len; ++ i){
+
+ for (j = i + 1; j < len; ++ j){
+ dist = 0;
+
+ for (k = 0; k < 4; ++ k){
+ dx = candidates[i][k].x - candidates[j][k].x;
+ dy = candidates[i][k].y - candidates[j][k].y;
+
+ dist += dx * dx + dy * dy;
+ }
+
+ if ( (dist / 4) < (minDist * minDist) ){
+
+ if ( CV.perimeter( candidates[i] ) < CV.perimeter( candidates[j] ) ){
+ candidates[i].tooNear = true;
+ }else{
+ candidates[j].tooNear = true;
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < len; ++ i){
+ if ( !candidates[i].tooNear ){
+ notTooNear.push( candidates[i] );
+ }
+ }
+
+ return notTooNear;
+};
+
+AR.Detector.prototype.findMarkers = function(imageSrc, candidates, warpSize){
+ var markers = [], len = candidates.length, candidate, marker, i;
+
+ for (i = 0; i < len; ++ i){
+ candidate = candidates[i];
+
+ CV.warp(imageSrc, this.homography, candidate, warpSize);
+
+ CV.threshold(this.homography, this.homography, CV.otsu(this.homography) );
+
+ marker = this.getMarker(this.homography, candidate);
+ if (marker){
+ markers.push(marker);
+ }
+ }
+
+ return markers;
+};
+
+AR.Detector.prototype.getMarker = function(imageSrc, candidate){
+ var width = (imageSrc.width / 7) >>> 0,
+ minZero = (width * width) >> 1,
+ bits = [], rotations = [], distances = [],
+ square, pair, inc, i, j;
+
+ for (i = 0; i < 7; ++ i){
+ inc = (0 === i || 6 === i)? 1: 6;
+
+ for (j = 0; j < 7; j += inc){
+ square = {x: j * width, y: i * width, width: width, height: width};
+ if ( CV.countNonZero(imageSrc, square) > minZero){
+ return null;
+ }
+ }
+ }
+
+ for (i = 0; i < 5; ++ i){
+ bits[i] = [];
+
+ for (j = 0; j < 5; ++ j){
+ square = {x: (j + 1) * width, y: (i + 1) * width, width: width, height: width};
+
+ bits[i][j] = CV.countNonZero(imageSrc, square) > minZero? 1: 0;
+ }
+ }
+
+ rotations[0] = bits;
+ distances[0] = this.hammingDistance( rotations[0] );
+
+ pair = {first: distances[0], second: 0};
+
+ for (i = 1; i < 4; ++ i){
+ rotations[i] = this.rotate( rotations[i - 1] );
+ distances[i] = this.hammingDistance( rotations[i] );
+
+ if (distances[i] < pair.first){
+ pair.first = distances[i];
+ pair.second = i;
+ }
+ }
+
+ if (0 !== pair.first){
+ return null;
+ }
+
+ return new AR.Marker(
+ this.mat2id( rotations[pair.second] ),
+ this.rotate2(candidate, 4 - pair.second)
+ );
+};
+
+AR.Detector.prototype.hammingDistance = function(bits){
+ var ids = [ [1,0,0,0,0], [1,0,1,1,1], [0,1,0,0,1], [0,1,1,1,0] ],
+ dist = 0, sum, minSum, i, j, k;
+
+ for (i = 0; i < 5; ++ i){
+ minSum = Infinity;
+
+ for (j = 0; j < 4; ++ j){
+ sum = 0;
+
+ for (k = 0; k < 5; ++ k){
+ sum += bits[i][k] === ids[j][k]? 0: 1;
+ }
+
+ if (sum < minSum){
+ minSum = sum;
+ }
+ }
+
+ dist += minSum;
+ }
+
+ return dist;
+};
+
+AR.Detector.prototype.mat2id = function(bits){
+ var id = 0, i;
+
+ for (i = 0; i < 5; ++ i){
+ id <<= 1;
+ id |= bits[i][1];
+ id <<= 1;
+ id |= bits[i][3];
+ }
+
+ return id;
+};
+
+AR.Detector.prototype.rotate = function(src){
+ var dst = [], len = src.length, i, j;
+
+ for (i = 0; i < len; ++ i){
+ dst[i] = [];
+ for (j = 0; j < src[i].length; ++ j){
+ dst[i][j] = src[src[i].length - j - 1][i];
+ }
+ }
+
+ return dst;
+};
+
+AR.Detector.prototype.rotate2 = function(src, rotation){
+ var dst = [], len = src.length, i;
+
+ for (i = 0; i < len; ++ i){
+ dst[i] = src[ (rotation + i) % len ];
+ }
+
+ return dst;
+};
diff --git a/three.js/examples/threex-aruco/vendor/js-aruco/src/cv.js b/three.js/examples/threex-aruco/vendor/js-aruco/src/cv.js
new file mode 100644
index 0000000000000000000000000000000000000000..c8bf40bf48eba07a5173072ea77d29fa3d38f327
--- /dev/null
+++ b/three.js/examples/threex-aruco/vendor/js-aruco/src/cv.js
@@ -0,0 +1,736 @@
+/*
+Copyright (c) 2011 Juan Mellado
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/*
+References:
+- "OpenCV: Open Computer Vision Library"
+http://sourceforge.net/projects/opencvlibrary/
+- "Stack Blur: Fast But Goodlooking"
+http://incubator.quasimondo.com/processing/fast_blur_deluxe.php
+*/
+
+var CV = CV || {};
+
+CV.Image = function(width, height, data){
+ this.width = width || 0;
+ this.height = height || 0;
+ this.data = data || [];
+};
+
+CV.grayscale = function(imageSrc, imageDst){
+ var src = imageSrc.data, dst = imageDst.data, len = src.length,
+ i = 0, j = 0;
+
+ for (; i < len; i += 4){
+ dst[j ++] =
+ (src[i] * 0.299 + src[i + 1] * 0.587 + src[i + 2] * 0.114 + 0.5) & 0xff;
+ }
+
+ imageDst.width = imageSrc.width;
+ imageDst.height = imageSrc.height;
+
+ return imageDst;
+};
+
+CV.threshold = function(imageSrc, imageDst, threshold){
+ var src = imageSrc.data, dst = imageDst.data,
+ len = src.length, tab = [], i;
+
+ for (i = 0; i < 256; ++ i){
+ tab[i] = i <= threshold? 0: 255;
+ }
+
+ for (i = 0; i < len; ++ i){
+ dst[i] = tab[ src[i] ];
+ }
+
+ imageDst.width = imageSrc.width;
+ imageDst.height = imageSrc.height;
+
+ return imageDst;
+};
+
+CV.adaptiveThreshold = function(imageSrc, imageDst, kernelSize, threshold){
+ var src = imageSrc.data, dst = imageDst.data, len = src.length, tab = [], i;
+
+ CV.stackBoxBlur(imageSrc, imageDst, kernelSize);
+
+ for (i = 0; i < 768; ++ i){
+ tab[i] = (i - 255 <= -threshold)? 255: 0;
+ }
+
+ for (i = 0; i < len; ++ i){
+ dst[i] = tab[ src[i] - dst[i] + 255 ];
+ }
+
+ imageDst.width = imageSrc.width;
+ imageDst.height = imageSrc.height;
+
+ return imageDst;
+};
+
+CV.otsu = function(imageSrc){
+ var src = imageSrc.data, len = src.length, hist = [],
+ threshold = 0, sum = 0, sumB = 0, wB = 0, wF = 0, max = 0,
+ mu, between, i;
+
+ for (i = 0; i < 256; ++ i){
+ hist[i] = 0;
+ }
+
+ for (i = 0; i < len; ++ i){
+ hist[ src[i] ] ++;
+ }
+
+ for (i = 0; i < 256; ++ i){
+ sum += hist[i] * i;
+ }
+
+ for (i = 0; i < 256; ++ i){
+ wB += hist[i];
+ if (0 !== wB){
+
+ wF = len - wB;
+ if (0 === wF){
+ break;
+ }
+
+ sumB += hist[i] * i;
+
+ mu = (sumB / wB) - ( (sum - sumB) / wF );
+
+ between = wB * wF * mu * mu;
+
+ if (between > max){
+ max = between;
+ threshold = i;
+ }
+ }
+ }
+
+ return threshold;
+};
+
+CV.stackBoxBlurMult =
+[1, 171, 205, 293, 57, 373, 79, 137, 241, 27, 391, 357, 41, 19, 283, 265];
+
+CV.stackBoxBlurShift =
+[0, 9, 10, 11, 9, 12, 10, 11, 12, 9, 13, 13, 10, 9, 13, 13];
+
+CV.BlurStack = function(){
+ this.color = 0;
+ this.next = null;
+};
+
+CV.stackBoxBlur = function(imageSrc, imageDst, kernelSize){
+ var src = imageSrc.data, dst = imageDst.data,
+ height = imageSrc.height, width = imageSrc.width,
+ heightMinus1 = height - 1, widthMinus1 = width - 1,
+ size = kernelSize + kernelSize + 1, radius = kernelSize + 1,
+ mult = CV.stackBoxBlurMult[kernelSize],
+ shift = CV.stackBoxBlurShift[kernelSize],
+ stack, stackStart, color, sum, pos, start, p, x, y, i;
+
+ stack = stackStart = new CV.BlurStack();
+ for (i = 1; i < size; ++ i){
+ stack = stack.next = new CV.BlurStack();
+ }
+ stack.next = stackStart;
+
+ pos = 0;
+
+ for (y = 0; y < height; ++ y){
+ start = pos;
+
+ color = src[pos];
+ sum = radius * color;
+
+ stack = stackStart;
+ for (i = 0; i < radius; ++ i){
+ stack.color = color;
+ stack = stack.next;
+ }
+ for (i = 1; i < radius; ++ i){
+ stack.color = src[pos + i];
+ sum += stack.color;
+ stack = stack.next;
+ }
+
+ stack = stackStart;
+ for (x = 0; x < width; ++ x){
+ dst[pos ++] = (sum * mult) >>> shift;
+
+ p = x + radius;
+ p = start + (p < widthMinus1? p: widthMinus1);
+ sum -= stack.color - src[p];
+
+ stack.color = src[p];
+ stack = stack.next;
+ }
+ }
+
+ for (x = 0; x < width; ++ x){
+ pos = x;
+ start = pos + width;
+
+ color = dst[pos];
+ sum = radius * color;
+
+ stack = stackStart;
+ for (i = 0; i < radius; ++ i){
+ stack.color = color;
+ stack = stack.next;
+ }
+ for (i = 1; i < radius; ++ i){
+ stack.color = dst[start];
+ sum += stack.color;
+ stack = stack.next;
+
+ start += width;
+ }
+
+ stack = stackStart;
+ for (y = 0; y < height; ++ y){
+ dst[pos] = (sum * mult) >>> shift;
+
+ p = y + radius;
+ p = x + ( (p < heightMinus1? p: heightMinus1) * width );
+ sum -= stack.color - dst[p];
+
+ stack.color = dst[p];
+ stack = stack.next;
+
+ pos += width;
+ }
+ }
+
+ return imageDst;
+};
+
+CV.gaussianBlur = function(imageSrc, imageDst, imageMean, kernelSize){
+ var kernel = CV.gaussianKernel(kernelSize);
+
+ imageDst.width = imageSrc.width;
+ imageDst.height = imageSrc.height;
+
+ imageMean.width = imageSrc.width;
+ imageMean.height = imageSrc.height;
+
+ CV.gaussianBlurFilter(imageSrc, imageMean, kernel, true);
+ CV.gaussianBlurFilter(imageMean, imageDst, kernel, false);
+
+ return imageDst;
+};
+
+CV.gaussianBlurFilter = function(imageSrc, imageDst, kernel, horizontal){
+ var src = imageSrc.data, dst = imageDst.data,
+ height = imageSrc.height, width = imageSrc.width,
+ pos = 0, limit = kernel.length >> 1,
+ cur, value, i, j, k;
+
+ for (i = 0; i < height; ++ i){
+
+ for (j = 0; j < width; ++ j){
+ value = 0.0;
+
+ for (k = -limit; k <= limit; ++ k){
+
+ if (horizontal){
+ cur = pos + k;
+ if (j + k < 0){
+ cur = pos;
+ }
+ else if (j + k >= width){
+ cur = pos;
+ }
+ }else{
+ cur = pos + (k * width);
+ if (i + k < 0){
+ cur = pos;
+ }
+ else if (i + k >= height){
+ cur = pos;
+ }
+ }
+
+ value += kernel[limit + k] * src[cur];
+ }
+
+ dst[pos ++] = horizontal? value: (value + 0.5) & 0xff;
+ }
+ }
+
+ return imageDst;
+};
+
+CV.gaussianKernel = function(kernelSize){
+ var tab =
+ [ [1],
+ [0.25, 0.5, 0.25],
+ [0.0625, 0.25, 0.375, 0.25, 0.0625],
+ [0.03125, 0.109375, 0.21875, 0.28125, 0.21875, 0.109375, 0.03125] ],
+ kernel = [], center, sigma, scale2X, sum, x, i;
+
+ if ( (kernelSize <= 7) && (kernelSize % 2 === 1) ){
+ kernel = tab[kernelSize >> 1];
+ }else{
+ center = (kernelSize - 1.0) * 0.5;
+ sigma = 0.8 + (0.3 * (center - 1.0) );
+ scale2X = -0.5 / (sigma * sigma);
+ sum = 0.0;
+ for (i = 0; i < kernelSize; ++ i){
+ x = i - center;
+ sum += kernel[i] = Math.exp(scale2X * x * x);
+ }
+ sum = 1 / sum;
+ for (i = 0; i < kernelSize; ++ i){
+ kernel[i] *= sum;
+ }
+ }
+
+ return kernel;
+};
+
+CV.findContours = function(imageSrc, binary){
+ var width = imageSrc.width, height = imageSrc.height, contours = [],
+ src, deltas, pos, pix, nbd, outer, hole, i, j;
+
+ src = CV.binaryBorder(imageSrc, binary);
+
+ deltas = CV.neighborhoodDeltas(width + 2);
+
+ pos = width + 3;
+ nbd = 1;
+
+ for (i = 0; i < height; ++ i, pos += 2){
+
+ for (j = 0; j < width; ++ j, ++ pos){
+ pix = src[pos];
+
+ if (0 !== pix){
+ outer = hole = false;
+
+ if (1 === pix && 0 === src[pos - 1]){
+ outer = true;
+ }
+ else if (pix >= 1 && 0 === src[pos + 1]){
+ hole = true;
+ }
+
+ if (outer || hole){
+ ++ nbd;
+
+ contours.push( CV.borderFollowing(src, pos, nbd, {x: j, y: i}, hole, deltas) );
+ }
+ }
+ }
+ }
+
+ return contours;
+};
+
+CV.borderFollowing = function(src, pos, nbd, point, hole, deltas){
+ var contour = [], pos1, pos3, pos4, s, s_end, s_prev;
+
+ contour.hole = hole;
+
+ s = s_end = hole? 0: 4;
+ do{
+ s = (s - 1) & 7;
+ pos1 = pos + deltas[s];
+ if (src[pos1] !== 0){
+ break;
+ }
+ }while(s !== s_end);
+
+ if (s === s_end){
+ src[pos] = -nbd;
+ contour.push( {x: point.x, y: point.y} );
+
+ }else{
+ pos3 = pos;
+ s_prev = s ^ 4;
+
+ while(true){
+ s_end = s;
+
+ do{
+ pos4 = pos3 + deltas[++ s];
+ }while(src[pos4] === 0);
+
+ s &= 7;
+
+ if ( ( (s - 1) >>> 0) < (s_end >>> 0) ){
+ src[pos3] = -nbd;
+ }
+ else if (src[pos3] === 1){
+ src[pos3] = nbd;
+ }
+
+ contour.push( {x: point.x, y: point.y} );
+
+ s_prev = s;
+
+ point.x += CV.neighborhood[s][0];
+ point.y += CV.neighborhood[s][1];
+
+ if ( (pos4 === pos) && (pos3 === pos1) ){
+ break;
+ }
+
+ pos3 = pos4;
+ s = (s + 4) & 7;
+ }
+ }
+
+ return contour;
+};
+
+CV.neighborhood =
+[ [1, 0], [1, -1], [0, -1], [-1, -1], [-1, 0], [-1, 1], [0, 1], [1, 1] ];
+
+CV.neighborhoodDeltas = function(width){
+ var deltas = [], len = CV.neighborhood.length, i = 0;
+
+ for (; i < len; ++ i){
+ deltas[i] = CV.neighborhood[i][0] + (CV.neighborhood[i][1] * width);
+ }
+
+ return deltas.concat(deltas);
+};
+
+CV.approxPolyDP = function(contour, epsilon){
+ var slice = {start_index: 0, end_index: 0},
+ right_slice = {start_index: 0, end_index: 0},
+ poly = [], stack = [], len = contour.length,
+ pt, start_pt, end_pt, dist, max_dist, le_eps,
+ dx, dy, i, j, k;
+
+ epsilon *= epsilon;
+
+ k = 0;
+
+ for (i = 0; i < 3; ++ i){
+ max_dist = 0;
+
+ k = (k + right_slice.start_index) % len;
+ start_pt = contour[k];
+ if (++ k === len) {k = 0;}
+
+ for (j = 1; j < len; ++ j){
+ pt = contour[k];
+ if (++ k === len) {k = 0;}
+
+ dx = pt.x - start_pt.x;
+ dy = pt.y - start_pt.y;
+ dist = dx * dx + dy * dy;
+
+ if (dist > max_dist){
+ max_dist = dist;
+ right_slice.start_index = j;
+ }
+ }
+ }
+
+ if (max_dist <= epsilon){
+ poly.push( {x: start_pt.x, y: start_pt.y} );
+
+ }else{
+ slice.start_index = k;
+ slice.end_index = (right_slice.start_index += slice.start_index);
+
+ right_slice.start_index -= right_slice.start_index >= len? len: 0;
+ right_slice.end_index = slice.start_index;
+ if (right_slice.end_index < right_slice.start_index){
+ right_slice.end_index += len;
+ }
+
+ stack.push( {start_index: right_slice.start_index, end_index: right_slice.end_index} );
+ stack.push( {start_index: slice.start_index, end_index: slice.end_index} );
+ }
+
+ while(stack.length !== 0){
+ slice = stack.pop();
+
+ end_pt = contour[slice.end_index % len];
+ start_pt = contour[k = slice.start_index % len];
+ if (++ k === len) {k = 0;}
+
+ if (slice.end_index <= slice.start_index + 1){
+ le_eps = true;
+
+ }else{
+ max_dist = 0;
+
+ dx = end_pt.x - start_pt.x;
+ dy = end_pt.y - start_pt.y;
+
+ for (i = slice.start_index + 1; i < slice.end_index; ++ i){
+ pt = contour[k];
+ if (++ k === len) {k = 0;}
+
+ dist = Math.abs( (pt.y - start_pt.y) * dx - (pt.x - start_pt.x) * dy);
+
+ if (dist > max_dist){
+ max_dist = dist;
+ right_slice.start_index = i;
+ }
+ }
+
+ le_eps = max_dist * max_dist <= epsilon * (dx * dx + dy * dy);
+ }
+
+ if (le_eps){
+ poly.push( {x: start_pt.x, y: start_pt.y} );
+
+ }else{
+ right_slice.end_index = slice.end_index;
+ slice.end_index = right_slice.start_index;
+
+ stack.push( {start_index: right_slice.start_index, end_index: right_slice.end_index} );
+ stack.push( {start_index: slice.start_index, end_index: slice.end_index} );
+ }
+ }
+
+ return poly;
+};
+
+CV.warp = function(imageSrc, imageDst, contour, warpSize){
+ var src = imageSrc.data, dst = imageDst.data,
+ width = imageSrc.width, height = imageSrc.height,
+ pos = 0,
+ sx1, sx2, dx1, dx2, sy1, sy2, dy1, dy2, p1, p2, p3, p4,
+ m, r, s, t, u, v, w, x, y, i, j;
+
+ m = CV.getPerspectiveTransform(contour, warpSize - 1);
+
+ r = m[8];
+ s = m[2];
+ t = m[5];
+
+ for (i = 0; i < warpSize; ++ i){
+ r += m[7];
+ s += m[1];
+ t += m[4];
+
+ u = r;
+ v = s;
+ w = t;
+
+ for (j = 0; j < warpSize; ++ j){
+ u += m[6];
+ v += m[0];
+ w += m[3];
+
+ x = v / u;
+ y = w / u;
+
+ sx1 = x >>> 0;
+ sx2 = (sx1 === width - 1)? sx1: sx1 + 1;
+ dx1 = x - sx1;
+ dx2 = 1.0 - dx1;
+
+ sy1 = y >>> 0;
+ sy2 = (sy1 === height - 1)? sy1: sy1 + 1;
+ dy1 = y - sy1;
+ dy2 = 1.0 - dy1;
+
+ p1 = p2 = sy1 * width;
+ p3 = p4 = sy2 * width;
+
+ dst[pos ++] =
+ (dy2 * (dx2 * src[p1 + sx1] + dx1 * src[p2 + sx2]) +
+ dy1 * (dx2 * src[p3 + sx1] + dx1 * src[p4 + sx2]) ) & 0xff;
+
+ }
+ }
+
+ imageDst.width = warpSize;
+ imageDst.height = warpSize;
+
+ return imageDst;
+};
+
+CV.getPerspectiveTransform = function(src, size){
+ var rq = CV.square2quad(src);
+
+ rq[0] /= size;
+ rq[1] /= size;
+ rq[3] /= size;
+ rq[4] /= size;
+ rq[6] /= size;
+ rq[7] /= size;
+
+ return rq;
+};
+
+CV.square2quad = function(src){
+ var sq = [], px, py, dx1, dx2, dy1, dy2, den;
+
+ px = src[0].x - src[1].x + src[2].x - src[3].x;
+ py = src[0].y - src[1].y + src[2].y - src[3].y;
+
+ if (0 === px && 0 === py){
+ sq[0] = src[1].x - src[0].x;
+ sq[1] = src[2].x - src[1].x;
+ sq[2] = src[0].x;
+ sq[3] = src[1].y - src[0].y;
+ sq[4] = src[2].y - src[1].y;
+ sq[5] = src[0].y;
+ sq[6] = 0;
+ sq[7] = 0;
+ sq[8] = 1;
+
+ }else{
+ dx1 = src[1].x - src[2].x;
+ dx2 = src[3].x - src[2].x;
+ dy1 = src[1].y - src[2].y;
+ dy2 = src[3].y - src[2].y;
+ den = dx1 * dy2 - dx2 * dy1;
+
+ sq[6] = (px * dy2 - dx2 * py) / den;
+ sq[7] = (dx1 * py - px * dy1) / den;
+ sq[8] = 1;
+ sq[0] = src[1].x - src[0].x + sq[6] * src[1].x;
+ sq[1] = src[3].x - src[0].x + sq[7] * src[3].x;
+ sq[2] = src[0].x;
+ sq[3] = src[1].y - src[0].y + sq[6] * src[1].y;
+ sq[4] = src[3].y - src[0].y + sq[7] * src[3].y;
+ sq[5] = src[0].y;
+ }
+
+ return sq;
+};
+
+CV.isContourConvex = function(contour){
+ var orientation = 0, convex = true,
+ len = contour.length, i = 0, j = 0,
+ cur_pt, prev_pt, dxdy0, dydx0, dx0, dy0, dx, dy;
+
+ prev_pt = contour[len - 1];
+ cur_pt = contour[0];
+
+ dx0 = cur_pt.x - prev_pt.x;
+ dy0 = cur_pt.y - prev_pt.y;
+
+ for (; i < len; ++ i){
+ if (++ j === len) {j = 0;}
+
+ prev_pt = cur_pt;
+ cur_pt = contour[j];
+
+ dx = cur_pt.x - prev_pt.x;
+ dy = cur_pt.y - prev_pt.y;
+ dxdy0 = dx * dy0;
+ dydx0 = dy * dx0;
+
+ orientation |= dydx0 > dxdy0? 1: (dydx0 < dxdy0? 2: 3);
+
+ if (3 === orientation){
+ convex = false;
+ break;
+ }
+
+ dx0 = dx;
+ dy0 = dy;
+ }
+
+ return convex;
+};
+
+CV.perimeter = function(poly){
+ var len = poly.length, i = 0, j = len - 1,
+ p = 0.0, dx, dy;
+
+ for (; i < len; j = i ++){
+ dx = poly[i].x - poly[j].x;
+ dy = poly[i].y - poly[j].y;
+
+ p += Math.sqrt(dx * dx + dy * dy) ;
+ }
+
+ return p;
+};
+
+CV.minEdgeLength = function(poly){
+ var len = poly.length, i = 0, j = len - 1,
+ min = Infinity, d, dx, dy;
+
+ for (; i < len; j = i ++){
+ dx = poly[i].x - poly[j].x;
+ dy = poly[i].y - poly[j].y;
+
+ d = dx * dx + dy * dy;
+
+ if (d < min){
+ min = d;
+ }
+ }
+
+ return Math.sqrt(min);
+};
+
+CV.countNonZero = function(imageSrc, square){
+ var src = imageSrc.data, height = square.height, width = square.width,
+ pos = square.x + (square.y * imageSrc.width),
+ span = imageSrc.width - width,
+ nz = 0, i, j;
+
+ for (i = 0; i < height; ++ i){
+
+ for (j = 0; j < width; ++ j){
+
+ if ( 0 !== src[pos ++] ){
+ ++ nz;
+ }
+ }
+
+ pos += span;
+ }
+
+ return nz;
+};
+
+CV.binaryBorder = function(imageSrc, dst){
+ var src = imageSrc.data, height = imageSrc.height, width = imageSrc.width,
+ posSrc = 0, posDst = 0, i, j;
+
+ for (j = -2; j < width; ++ j){
+ dst[posDst ++] = 0;
+ }
+
+ for (i = 0; i < height; ++ i){
+ dst[posDst ++] = 0;
+
+ for (j = 0; j < width; ++ j){
+ dst[posDst ++] = (0 === src[posSrc ++]? 0: 1);
+ }
+
+ dst[posDst ++] = 0;
+ }
+
+ for (j = -2; j < width; ++ j){
+ dst[posDst ++] = 0;
+ }
+
+ return dst;
+};
diff --git a/three.js/examples/threex-aruco/vendor/js-aruco/src/posit1.js b/three.js/examples/threex-aruco/vendor/js-aruco/src/posit1.js
new file mode 100644
index 0000000000000000000000000000000000000000..676d724c0c21a25149a81daf4b87bbaa5472af5e
--- /dev/null
+++ b/three.js/examples/threex-aruco/vendor/js-aruco/src/posit1.js
@@ -0,0 +1,500 @@
+/*
+Copyright (c) 2012 Juan Mellado
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/*
+References:
+- "Iterative Pose Estimation using Coplanar Feature Points"
+Denis Oberkampf, Daniel F. DeMenthon, Larry S. Davis
+http://www.cfar.umd.edu/~daniel/daniel_papersfordownload/CoplanarPts.pdf
+*/
+
+var POS = POS || {};
+
+POS.Posit = function(modelSize, focalLength){
+ this.objectPoints = this.buildModel(modelSize);
+ this.focalLength = focalLength;
+
+ this.objectVectors = [];
+ this.objectNormal = [];
+ this.objectMatrix = [[],[],[]];
+
+ this.init();
+};
+
+POS.Posit.prototype.buildModel = function(modelSize){
+ var half = modelSize / 2.0;
+
+ return [
+ [-half, half, 0.0],
+ [ half, half, 0.0],
+ [ half, -half, 0.0],
+ [-half, -half, 0.0]
+ ];
+};
+
+POS.Posit.prototype.init = function(){
+ var np = this.objectPoints.length,
+ vectors = [], n = [], len = 0.0, row = 2, i;
+
+ for (i = 0; i < np; ++ i){
+ this.objectVectors[i] = [this.objectPoints[i][0] - this.objectPoints[0][0],
+ this.objectPoints[i][1] - this.objectPoints[0][1],
+ this.objectPoints[i][2] - this.objectPoints[0][2]];
+
+ vectors[i] = [this.objectVectors[i][0],
+ this.objectVectors[i][1],
+ this.objectVectors[i][2]];
+ }
+
+ while(0.0 === len){
+ n[0] = this.objectVectors[1][1] * this.objectVectors[row][2] -
+ this.objectVectors[1][2] * this.objectVectors[row][1];
+ n[1] = this.objectVectors[1][2] * this.objectVectors[row][0] -
+ this.objectVectors[1][0] * this.objectVectors[row][2];
+ n[2] = this.objectVectors[1][0] * this.objectVectors[row][1] -
+ this.objectVectors[1][1] * this.objectVectors[row][0];
+
+ len = Math.sqrt(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]);
+
+ ++ row;
+ }
+
+ for (i = 0; i < 3; ++ i){
+ this.objectNormal[i] = n[i] / len;
+ }
+
+ POS.pseudoInverse(vectors, np, this.objectMatrix);
+};
+
+POS.Posit.prototype.pose = function(imagePoints){
+ var posRotation1 = [[],[],[]], posRotation2 = [[],[],[]], posTranslation = [],
+ rotation1 = [[],[],[]], rotation2 = [[],[],[]], translation1 = [], translation2 = [],
+ error1, error2, valid1, valid2, i, j;
+
+ this.pos(imagePoints, posRotation1, posRotation2, posTranslation);
+
+ valid1 = this.isValid(posRotation1, posTranslation);
+ if (valid1){
+ error1 = this.iterate(imagePoints, posRotation1, posTranslation, rotation1, translation1);
+ }else{
+ error1 = {euclidean: -1.0, pixels: -1, maximum: -1.0};
+ }
+
+ valid2 = this.isValid(posRotation2, posTranslation);
+ if (valid2){
+ error2 = this.iterate(imagePoints, posRotation2, posTranslation, rotation2, translation2);
+ }else{
+ error2 = {euclidean: -1.0, pixels: -1, maximum: -1.0};
+ }
+
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3; ++ j){
+ if (valid1){
+ translation1[i] -= rotation1[i][j] * this.objectPoints[0][j];
+ }
+ if (valid2){
+ translation2[i] -= rotation2[i][j] * this.objectPoints[0][j];
+ }
+ }
+ }
+
+ return error1.euclidean < error2.euclidean?
+ new POS.Pose(error1.pixels, rotation1, translation1, error2.pixels, rotation2, translation2):
+ new POS.Pose(error2.pixels, rotation2, translation2, error1.pixels, rotation1, translation1);
+};
+
+POS.Posit.prototype.pos = function(imagePoints, rotation1, rotation2, translation){
+ var np = this.objectPoints.length, imageVectors = [],
+ i0 = [], j0 = [], ivec = [], jvec = [], row1 = [], row2 = [], row3 = [],
+ i0i0, j0j0, i0j0, delta, q, lambda, mu, scale, i, j;
+
+ for (i = 0; i < np; ++ i){
+ imageVectors[i] = [imagePoints[i].x - imagePoints[0].x,
+ imagePoints[i].y - imagePoints[0].y];
+ }
+
+ //i0 and j0
+ for (i = 0; i < 3; ++ i){
+ i0[i] = 0.0;
+ j0[i] = 0.0;
+ for (j = 0; j < np; ++ j){
+ i0[i] += this.objectMatrix[i][j] * imageVectors[j][0];
+ j0[i] += this.objectMatrix[i][j] * imageVectors[j][1];
+ }
+ }
+
+ i0i0 = i0[0] * i0[0] + i0[1] * i0[1] + i0[2] * i0[2];
+ j0j0 = j0[0] * j0[0] + j0[1] * j0[1] + j0[2] * j0[2];
+ i0j0 = i0[0] * j0[0] + i0[1] * j0[1] + i0[2] * j0[2];
+
+ //Lambda and mu
+ delta = (j0j0 - i0i0) * (j0j0 - i0i0) + 4.0 * (i0j0 * i0j0);
+
+ if (j0j0 - i0i0 >= 0.0){
+ q = (j0j0 - i0i0 + Math.sqrt(delta) ) / 2.0;
+ }else{
+ q = (j0j0 - i0i0 - Math.sqrt(delta) ) / 2.0;
+ }
+
+ if (q >= 0.0){
+ lambda = Math.sqrt(q);
+ if (0.0 === lambda){
+ mu = 0.0;
+ }else{
+ mu = -i0j0 / lambda;
+ }
+ }else{
+ lambda = Math.sqrt( -(i0j0 * i0j0) / q);
+ if (0.0 === lambda){
+ mu = Math.sqrt(i0i0 - j0j0);
+ }else{
+ mu = -i0j0 / lambda;
+ }
+ }
+
+ //First rotation
+ for (i = 0; i < 3; ++ i){
+ ivec[i] = i0[i] + lambda * this.objectNormal[i];
+ jvec[i] = j0[i] + mu * this.objectNormal[i];
+ }
+
+ scale = Math.sqrt(ivec[0] * ivec[0] + ivec[1] * ivec[1] + ivec[2] * ivec[2]);
+
+ for (i = 0; i < 3; ++ i){
+ row1[i] = ivec[i] / scale;
+ row2[i] = jvec[i] / scale;
+ }
+
+ row3[0] = row1[1] * row2[2] - row1[2] * row2[1];
+ row3[1] = row1[2] * row2[0] - row1[0] * row2[2];
+ row3[2] = row1[0] * row2[1] - row1[1] * row2[0];
+
+ for (i = 0; i < 3; ++ i){
+ rotation1[0][i] = row1[i];
+ rotation1[1][i] = row2[i];
+ rotation1[2][i] = row3[i];
+ }
+
+ //Second rotation
+ for (i = 0; i < 3; ++ i){
+ ivec[i] = i0[i] - lambda * this.objectNormal[i];
+ jvec[i] = j0[i] - mu * this.objectNormal[i];
+ }
+
+ for (i = 0; i < 3; ++ i){
+ row1[i] = ivec[i] / scale;
+ row2[i] = jvec[i] / scale;
+ }
+
+ row3[0] = row1[1] * row2[2] - row1[2] * row2[1];
+ row3[1] = row1[2] * row2[0] - row1[0] * row2[2];
+ row3[2] = row1[0] * row2[1] - row1[1] * row2[0];
+
+ for (i = 0; i < 3; ++ i){
+ rotation2[0][i] = row1[i];
+ rotation2[1][i] = row2[i];
+ rotation2[2][i] = row3[i];
+ }
+
+ //Translation
+ translation[0] = imagePoints[0].x / scale;
+ translation[1] = imagePoints[0].y / scale;
+ translation[2] = this.focalLength / scale;
+};
+
+POS.Posit.prototype.isValid = function(rotation, translation){
+ var np = this.objectPoints.length, zmin = Infinity, i = 0, zi;
+
+ for (; i < np; ++ i){
+ zi = translation[2] +
+ (rotation[2][0] * this.objectVectors[i][0] +
+ rotation[2][1] * this.objectVectors[i][1] +
+ rotation[2][2] * this.objectVectors[i][2]
+ );
+ if (zi < zmin){
+ zmin = zi;
+ }
+ }
+
+ return zmin >= 0.0;
+};
+
+POS.Posit.prototype.iterate = function(imagePoints, posRotation, posTranslation, rotation, translation){
+ var np = this.objectPoints.length,
+ oldSopImagePoints = [], sopImagePoints = [],
+ rotation1 = [[],[],[]], rotation2 = [[],[],[]],
+ translation1 = [], translation2 = [],
+ converged = false, iteration = 0,
+ oldImageDifference, imageDifference, factor,
+ error, error1, error2, delta, i, j;
+
+ for (i = 0; i < np; ++ i){
+ oldSopImagePoints[i] = {x: imagePoints[i].x,
+ y: imagePoints[i].y
+ };
+ }
+
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3; ++ j){
+ rotation[i][j] = posRotation[i][j];
+ }
+ translation[i] = posTranslation[i];
+ }
+
+ for (i = 0; i < np; ++ i){
+ factor = 0.0;
+ for (j = 0; j < 3; ++ j){
+ factor += this.objectVectors[i][j] * rotation[2][j] / translation[2];
+ }
+ sopImagePoints[i] = {x: (1.0 + factor) * imagePoints[i].x,
+ y: (1.0 + factor) * imagePoints[i].y
+ };
+ }
+
+ imageDifference = 0.0;
+
+ for (i = 0; i < np; ++ i){
+ imageDifference += Math.abs(sopImagePoints[i].x - oldSopImagePoints[i].x);
+ imageDifference += Math.abs(sopImagePoints[i].y - oldSopImagePoints[i].y);
+ }
+
+ for (i = 0; i < 3; ++ i){
+ translation1[i] = translation[i] -
+ (rotation[i][0] * this.objectPoints[0][0] +
+ rotation[i][1] * this.objectPoints[0][1] +
+ rotation[i][2] * this.objectPoints[0][2]
+ );
+ }
+
+ error = error1 = this.error(imagePoints, rotation, translation1);
+
+ //Convergence
+ converged = (0.0 === error1.pixels) || (imageDifference < 0.01);
+
+ while( iteration ++ < 100 && !converged ){
+
+ for (i = 0; i < np; ++ i){
+ oldSopImagePoints[i].x = sopImagePoints[i].x;
+ oldSopImagePoints[i].y = sopImagePoints[i].y;
+ }
+
+ this.pos(sopImagePoints, rotation1, rotation2, translation);
+
+ for (i = 0; i < 3; ++ i){
+ translation1[i] = translation[i] -
+ (rotation1[i][0] * this.objectPoints[0][0] +
+ rotation1[i][1] * this.objectPoints[0][1] +
+ rotation1[i][2] * this.objectPoints[0][2]
+ );
+
+ translation2[i] = translation[i] -
+ (rotation2[i][0] * this.objectPoints[0][0] +
+ rotation2[i][1] * this.objectPoints[0][1] +
+ rotation2[i][2] * this.objectPoints[0][2]
+ );
+ }
+
+ error1 = this.error(imagePoints, rotation1, translation1);
+ error2 = this.error(imagePoints, rotation2, translation2);
+
+ if ( (error1.euclidean >= 0.0) && (error2.euclidean >= 0.0) ){
+ if (error2.euclidean < error1.euclidean){
+ error = error2;
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3; ++ j){
+ rotation[i][j] = rotation2[i][j];
+ }
+ }
+ }else{
+ error = error1;
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3; ++ j){
+ rotation[i][j] = rotation1[i][j];
+ }
+ }
+ }
+ }
+
+ if ( (error1.euclidean < 0.0) && (error2.euclidean >= 0.0) ){
+ error = error2;
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3; ++ j){
+ rotation[i][j] = rotation2[i][j];
+ }
+ }
+ }
+
+ if ( (error2.euclidean < 0.0) && (error1.euclidean >= 0.0) ){
+ error = error1;
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3; ++ j){
+ rotation[i][j] = rotation1[i][j];
+ }
+ }
+ }
+
+ for (i = 0; i < np; ++ i){
+ factor = 0.0;
+ for (j = 0; j < 3; ++ j){
+ factor += this.objectVectors[i][j] * rotation[2][j] / translation[2];
+ }
+ sopImagePoints[i].x = (1.0 + factor) * imagePoints[i].x;
+ sopImagePoints[i].y = (1.0 + factor) * imagePoints[i].y;
+ }
+
+ oldImageDifference = imageDifference;
+ imageDifference = 0.0;
+
+ for (i = 0; i < np; ++ i){
+ imageDifference += Math.abs(sopImagePoints[i].x - oldSopImagePoints[i].x);
+ imageDifference += Math.abs(sopImagePoints[i].y - oldSopImagePoints[i].y);
+ }
+
+ delta = Math.abs(imageDifference - oldImageDifference);
+
+ converged = (0.0 === error.pixels) || (delta < 0.01);
+ }
+
+ return error;
+};
+
+POS.Posit.prototype.error = function(imagePoints, rotation, translation){
+ var np = this.objectPoints.length,
+ move = [], projection = [], errorvec = [],
+ euclidean = 0.0, pixels = 0.0, maximum = 0.0,
+ i, j, k;
+
+ if ( !this.isValid(rotation, translation) ){
+ return {euclidean: -1.0, pixels: -1, maximum: -1.0};
+ }
+
+ for (i = 0; i < np; ++ i){
+ move[i] = [];
+ for (j = 0; j < 3; ++ j){
+ move[i][j] = translation[j];
+ }
+ }
+
+ for (i = 0; i < np; ++ i){
+ for (j = 0; j < 3; ++ j){
+ for (k = 0; k < 3; ++ k){
+ move[i][j] += rotation[j][k] * this.objectPoints[i][k];
+ }
+ }
+ }
+
+ for (i = 0; i < np; ++ i){
+ projection[i] = [];
+ for (j = 0; j < 2; ++ j){
+ projection[i][j] = this.focalLength * move[i][j] / move[i][2];
+ }
+ }
+
+ for (i = 0; i < np; ++ i){
+ errorvec[i] = [projection[i][0] - imagePoints[i].x,
+ projection[i][1] - imagePoints[i].y];
+ }
+
+ for (i = 0; i < np; ++ i){
+ euclidean += Math.sqrt(errorvec[i][0] * errorvec[i][0] +
+ errorvec[i][1] * errorvec[i][1]
+ );
+
+ pixels += Math.abs( Math.round(projection[i][0]) - Math.round(imagePoints[i].x) ) +
+ Math.abs( Math.round(projection[i][1]) - Math.round(imagePoints[i].y) );
+
+ if (Math.abs(errorvec[i][0]) > maximum){
+ maximum = Math.abs(errorvec[i][0]);
+ }
+ if (Math.abs(errorvec[i][1]) > maximum){
+ maximum = Math.abs(errorvec[i][1]);
+ }
+ }
+
+ return {euclidean: euclidean / np, pixels: pixels, maximum: maximum};
+};
+
+POS.pseudoInverse = function(a, n, b){
+ var w = [], v = [[],[],[]], s = [[],[],[]],
+ wmax = 0.0, cn = 0,
+ i, j, k;
+
+ SVD.svdcmp(a, n, 3, w, v);
+
+ for (i = 0; i < 3; ++ i){
+ if (w[i] > wmax){
+ wmax = w[i];
+ }
+ }
+
+ wmax *= 0.01;
+
+ for (i = 0; i < 3; ++ i){
+ if (w[i] < wmax){
+ w[i] = 0.0;
+ }
+ }
+
+ for (j = 0; j < 3; ++ j){
+ if (0.0 === w[j]){
+ ++ cn;
+ for (k = j; k < 2; ++ k){
+ for (i = 0; i < n; ++ i){
+ a[i][k] = a[i][k + 1];
+ }
+ for (i = 0; i < 3; ++ i){
+ v[i][k] = v[i][k + 1];
+ }
+ }
+ }
+ }
+
+ for (j = 0; j < 2; ++ j){
+ if (0.0 === w[j]){
+ w[j] = w[j + 1];
+ }
+ }
+
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < 3 - cn; ++ j){
+ s[i][j] = v[i][j] / w[j];
+ }
+ }
+
+ for (i = 0; i < 3; ++ i){
+ for (j = 0; j < n; ++ j){
+ b[i][j] = 0.0;
+ for (k = 0; k < 3 - cn; ++ k){
+ b[i][j] += s[i][k] * a[j][k];
+ }
+ }
+ }
+};
+
+POS.Pose = function(error1, rotation1, translation1, error2, rotation2, translation2){
+ this.bestError = error1;
+ this.bestRotation = rotation1;
+ this.bestTranslation = translation1;
+ this.alternativeError = error2;
+ this.alternativeRotation = rotation2;
+ this.alternativeTranslation = translation2;
+};
diff --git a/three.js/examples/threex-aruco/vendor/js-aruco/src/posit2.js b/three.js/examples/threex-aruco/vendor/js-aruco/src/posit2.js
new file mode 100644
index 0000000000000000000000000000000000000000..45eb4d73bf21b832e088125a39ee20ee63090241
--- /dev/null
+++ b/three.js/examples/threex-aruco/vendor/js-aruco/src/posit2.js
@@ -0,0 +1,495 @@
+/*
+Copyright (c) 2012 Juan Mellado
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/*
+References:
+- "3D Pose Estimation"
+ Andrew Kirillow
+ http://www.aforgenet.com/articles/posit/
+*/
+
+var POS = POS || {};
+
+POS.Posit = function(modelSize, focalLength){
+ this.model = this.buildModel(modelSize);
+ this.focalLength = focalLength;
+
+ this.init();
+};
+
+POS.Posit.prototype.buildModel = function(modelSize){
+ var half = modelSize / 2.0;
+
+ return [
+ new Vec3(-half, half, 0.0),
+ new Vec3( half, half, 0.0),
+ new Vec3( half, -half, 0.0),
+ new Vec3(-half, -half, 0.0) ];
+};
+
+POS.Posit.prototype.init = function(){
+ var d = new Vec3(), v = new Mat3(), u;
+
+ this.modelVectors = Mat3.fromRows(
+ Vec3.sub(this.model[1], this.model[0]),
+ Vec3.sub(this.model[2], this.model[0]),
+ Vec3.sub(this.model[3], this.model[0]) );
+
+ u = Mat3.clone(this.modelVectors);
+
+ SVD.svdcmp(u.m, 3, 3, d.v, v.m);
+
+ this.modelPseudoInverse = Mat3.mult(
+ Mat3.mult(v, Mat3.fromDiagonal( Vec3.inverse(d) ) ), Mat3.transpose(u) );
+
+ this.modelNormal = v.column( d.minIndex() );
+};
+
+POS.Posit.prototype.pose = function(points){
+ var eps = new Vec3(1.0, 1.0, 1.0),
+ rotation1 = new Mat3(), rotation2 = new Mat3(),
+ translation1 = new Vec3(), translation2 = new Vec3(),
+ error1, error2;
+
+ this.pos(points, eps, rotation1, rotation2, translation1, translation2);
+
+ error1 = this.iterate(points, rotation1, translation1);
+ error2 = this.iterate(points, rotation2, translation2);
+
+ return error1 < error2?
+ new POS.Pose(error1, rotation1.m, translation1.v, error2, rotation2.m, translation2.v):
+ new POS.Pose(error2, rotation2.m, translation2.v, error1, rotation1.m, translation1.v);
+};
+
+POS.Posit.prototype.pos = function(points, eps, rotation1, rotation2, translation1, translation2){
+ var xi = new Vec3(points[1].x, points[2].x, points[3].x),
+ yi = new Vec3(points[1].y, points[2].y, points[3].y),
+ xs = Vec3.addScalar( Vec3.mult(xi, eps), -points[0].x),
+ ys = Vec3.addScalar( Vec3.mult(yi, eps), -points[0].y),
+ i0 = Mat3.multVector(this.modelPseudoInverse, xs),
+ j0 = Mat3.multVector(this.modelPseudoInverse, ys),
+ s = j0.square() - i0.square(),
+ ij = Vec3.dot(i0, j0),
+ r = 0.0, theta = 0.0,
+ i, j, k, inorm, jnorm, scale, temp, lambda, mu;
+
+ if (0.0 === s){
+ r = Math.sqrt( Math.abs(2.0 * ij) );
+ theta = (-Math.PI / 2.0) * (ij < 0.0? -1: (ij > 0.0? 1.0: 0.0) );
+ }else{
+ r = Math.sqrt( Math.sqrt(s * s + 4.0 * ij * ij) );
+ theta = Math.atan(-2.0 * ij / s);
+ if (s < 0.0){
+ theta += Math.PI;
+ }
+ theta /= 2.0;
+ }
+
+ lambda = r * Math.cos(theta);
+ mu = r * Math.sin(theta);
+
+ //First possible rotation/translation
+ i = Vec3.add(i0, Vec3.multScalar(this.modelNormal, lambda) );
+ j = Vec3.add(j0, Vec3.multScalar(this.modelNormal, mu) );
+ inorm = i.normalize();
+ jnorm = j.normalize();
+ k = Vec3.cross(i, j);
+ rotation1.copy( Mat3.fromRows(i, j, k) );
+
+ scale = (inorm + jnorm) / 2.0;
+ temp = Mat3.multVector(rotation1, this.model[0]);
+ translation1.v = [
+ points[0].x / scale - temp.v[0],
+ points[0].y / scale - temp.v[1],
+ this.focalLength / scale];
+
+ //Second possible rotation/translation
+ i = Vec3.sub(i0, Vec3.multScalar(this.modelNormal, lambda) );
+ j = Vec3.sub(j0, Vec3.multScalar(this.modelNormal, mu) );
+ inorm = i.normalize();
+ jnorm = j.normalize();
+ k = Vec3.cross(i, j);
+ rotation2.copy( Mat3.fromRows(i, j, k) );
+
+ scale = (inorm + jnorm) / 2.0;
+ temp = Mat3.multVector(rotation2, this.model[0]);
+ translation2.v = [
+ points[0].x / scale - temp.v[0],
+ points[0].y / scale - temp.v[1],
+ this.focalLength / scale];
+};
+
+POS.Posit.prototype.iterate = function(points, rotation, translation){
+ var prevError = Infinity,
+ rotation1 = new Mat3(), rotation2 = new Mat3(),
+ translation1 = new Vec3(), translation2 = new Vec3(),
+ i = 0, eps, error, error1, error2;
+
+ for (; i < 100; ++ i){
+ eps = Vec3.addScalar( Vec3.multScalar(
+ Mat3.multVector( this.modelVectors, rotation.row(2) ), 1.0 / translation.v[2]), 1.0);
+
+ this.pos(points, eps, rotation1, rotation2, translation1, translation2);
+
+ error1 = this.getError(points, rotation1, translation1);
+ error2 = this.getError(points, rotation2, translation2);
+
+ if (error1 < error2){
+ rotation.copy(rotation1);
+ translation.copy(translation1);
+ error = error1;
+ }else{
+ rotation.copy(rotation2);
+ translation.copy(translation2);
+ error = error2;
+ }
+
+ if ( (error <= 2.0) || (error > prevError) ){
+ break;
+ }
+
+ prevError = error;
+ }
+
+ return error;
+};
+
+POS.Posit.prototype.getError = function(points, rotation, translation){
+ var v1 = Vec3.add( Mat3.multVector(rotation, this.model[0]), translation),
+ v2 = Vec3.add( Mat3.multVector(rotation, this.model[1]), translation),
+ v3 = Vec3.add( Mat3.multVector(rotation, this.model[2]), translation),
+ v4 = Vec3.add( Mat3.multVector(rotation, this.model[3]), translation),
+ modeled, ia1, ia2, ia3, ia4, ma1, ma2, ma3, ma4;
+
+ v1 = v1.v; v2 = v2.v; v3 = v3.v; v4 = v4.v;
+
+ v1[0] *= this.focalLength / v1[2];
+ v1[1] *= this.focalLength / v1[2];
+ v2[0] *= this.focalLength / v2[2];
+ v2[1] *= this.focalLength / v2[2];
+ v3[0] *= this.focalLength / v3[2];
+ v3[1] *= this.focalLength / v3[2];
+ v4[0] *= this.focalLength / v4[2];
+ v4[1] *= this.focalLength / v4[2];
+
+ modeled = [
+ {x: v1[0], y: v1[1]},
+ {x: v2[0], y: v2[1]},
+ {x: v3[0], y: v3[1]},
+ {x: v4[0], y: v4[1]}
+ ];
+
+ ia1 = this.angle( points[0], points[1], points[3] );
+ ia2 = this.angle( points[1], points[2], points[0] );
+ ia3 = this.angle( points[2], points[3], points[1] );
+ ia4 = this.angle( points[3], points[0], points[2] );
+
+ ma1 = this.angle( modeled[0], modeled[1], modeled[3] );
+ ma2 = this.angle( modeled[1], modeled[2], modeled[0] );
+ ma3 = this.angle( modeled[2], modeled[3], modeled[1] );
+ ma4 = this.angle( modeled[3], modeled[0], modeled[2] );
+
+ return ( Math.abs(ia1 - ma1) +
+ Math.abs(ia2 - ma2) +
+ Math.abs(ia3 - ma3) +
+ Math.abs(ia4 - ma4) ) / 4.0;
+};
+
+POS.Posit.prototype.angle = function(a, b, c){
+ var x1 = b.x - a.x, y1 = b.y - a.y,
+ x2 = c.x - a.x, y2 = c.y - a.y;
+
+ return Math.acos( (x1 * x2 + y1 * y2) /
+ (Math.sqrt(x1 * x1 + y1 * y1) * Math.sqrt(x2 * x2 + y2 * y2) ) ) * 180.0 / Math.PI;
+};
+
+POS.Pose = function(error1, rotation1, translation1, error2, rotation2, translation2){
+ this.bestError = error1;
+ this.bestRotation = rotation1;
+ this.bestTranslation = translation1;
+ this.alternativeError = error2;
+ this.alternativeRotation = rotation2;
+ this.alternativeTranslation = translation2;
+};
+
+var Vec3 = function(x, y, z){
+ this.v = [x || 0.0, y || 0.0, z || 0.0];
+};
+
+Vec3.prototype.copy = function(a){
+ var v = this.v;
+
+ a = a.v;
+
+ v[0] = a[0];
+ v[1] = a[1];
+ v[2] = a[2];
+
+ return this;
+};
+
+Vec3.add = function(a, b){
+ var vector = new Vec3(), v = vector.v;
+
+ a = a.v; b = b.v;
+
+ v[0] = a[0] + b[0];
+ v[1] = a[1] + b[1];
+ v[2] = a[2] + b[2];
+
+ return vector;
+};
+
+Vec3.sub = function(a, b){
+ var vector = new Vec3(), v = vector.v;
+
+ a = a.v; b = b.v;
+
+ v[0] = a[0] - b[0];
+ v[1] = a[1] - b[1];
+ v[2] = a[2] - b[2];
+
+ return vector;
+};
+
+Vec3.mult = function(a, b){
+ var vector = new Vec3(), v = vector.v;
+
+ a = a.v; b = b.v;
+
+ v[0] = a[0] * b[0];
+ v[1] = a[1] * b[1];
+ v[2] = a[2] * b[2];
+
+ return vector;
+};
+
+Vec3.addScalar = function(a, b){
+ var vector = new Vec3(), v = vector.v;
+
+ a = a.v;
+
+ v[0] = a[0] + b;
+ v[1] = a[1] + b;
+ v[2] = a[2] + b;
+
+ return vector;
+};
+
+Vec3.multScalar = function(a, b){
+ var vector = new Vec3(), v = vector.v;
+
+ a = a.v;
+
+ v[0] = a[0] * b;
+ v[1] = a[1] * b;
+ v[2] = a[2] * b;
+
+ return vector;
+};
+
+Vec3.dot = function(a, b){
+ a = a.v; b = b.v;
+
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+};
+
+Vec3.cross = function(a, b){
+ a = a.v; b = b.v;
+
+ return new Vec3(
+ a[1] * b[2] - a[2] * b[1],
+ a[2] * b[0] - a[0] * b[2],
+ a[0] * b[1] - a[1] * b[0]);
+};
+
+Vec3.prototype.normalize = function(){
+ var v = this.v,
+ len = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
+
+ if (len > 0.0){
+ v[0] /= len;
+ v[1] /= len;
+ v[2] /= len;
+ }
+
+ return len;
+};
+
+Vec3.inverse = function(a){
+ var vector = new Vec3(), v = vector.v;
+
+ a = a.v;
+
+ if (a[0] !== 0.0){
+ v[0] = 1.0 / a[0];
+ }
+ if (a[1] !== 0.0){
+ v[1] = 1.0 / a[1];
+ }
+ if (a[2] !== 0.0){
+ v[2] = 1.0 / a[2];
+ }
+
+ return vector;
+};
+
+Vec3.prototype.square = function(){
+ var v = this.v;
+
+ return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
+};
+
+Vec3.prototype.minIndex = function(){
+ var v = this.v;
+
+ return v[0] < v[1]? (v[0] < v[2]? 0: 2): (v[1] < v[2]? 1: 2);
+};
+
+var Mat3 = function(){
+ this.m = [ [0.0, 0.0, 0.0],
+ [0.0, 0.0, 0.0],
+ [0.0, 0.0, 0.0] ];
+};
+
+Mat3.clone = function(a){
+ var matrix = new Mat3(), m = matrix.m;
+
+ a = a.m;
+
+ m[0][0] = a[0][0];
+ m[0][1] = a[0][1];
+ m[0][2] = a[0][2];
+ m[1][0] = a[1][0];
+ m[1][1] = a[1][1];
+ m[1][2] = a[1][2];
+ m[2][0] = a[2][0];
+ m[2][1] = a[2][1];
+ m[2][2] = a[2][2];
+
+ return matrix;
+};
+
+Mat3.prototype.copy = function(a){
+ var m = this.m;
+
+ a = a.m;
+
+ m[0][0] = a[0][0];
+ m[0][1] = a[0][1];
+ m[0][2] = a[0][2];
+ m[1][0] = a[1][0];
+ m[1][1] = a[1][1];
+ m[1][2] = a[1][2];
+ m[2][0] = a[2][0];
+ m[2][1] = a[2][1];
+ m[2][2] = a[2][2];
+
+ return this;
+};
+
+Mat3.fromRows = function(a, b, c){
+ var matrix = new Mat3(), m = matrix.m;
+
+ a = a.v; b = b.v; c = c.v;
+
+ m[0][0] = a[0];
+ m[0][1] = a[1];
+ m[0][2] = a[2];
+ m[1][0] = b[0];
+ m[1][1] = b[1];
+ m[1][2] = b[2];
+ m[2][0] = c[0];
+ m[2][1] = c[1];
+ m[2][2] = c[2];
+
+ return matrix;
+};
+
+Mat3.fromDiagonal = function(a){
+ var matrix = new Mat3(), m = matrix.m;
+
+ a = a.v;
+
+ m[0][0] = a[0];
+ m[1][1] = a[1];
+ m[2][2] = a[2];
+
+ return matrix;
+};
+
+Mat3.transpose = function(a){
+ var matrix = new Mat3(), m = matrix.m;
+
+ a = a.m;
+
+ m[0][0] = a[0][0];
+ m[0][1] = a[1][0];
+ m[0][2] = a[2][0];
+ m[1][0] = a[0][1];
+ m[1][1] = a[1][1];
+ m[1][2] = a[2][1];
+ m[2][0] = a[0][2];
+ m[2][1] = a[1][2];
+ m[2][2] = a[2][2];
+
+ return matrix;
+};
+
+Mat3.mult = function(a, b){
+ var matrix = new Mat3(), m = matrix.m;
+
+ a = a.m; b = b.m;
+
+ m[0][0] = a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0];
+ m[0][1] = a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1];
+ m[0][2] = a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2];
+ m[1][0] = a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0];
+ m[1][1] = a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1];
+ m[1][2] = a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2];
+ m[2][0] = a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0];
+ m[2][1] = a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1];
+ m[2][2] = a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2];
+
+ return matrix;
+};
+
+Mat3.multVector = function(m, a){
+ m = m.m; a = a.v;
+
+ return new Vec3(
+ m[0][0] * a[0] + m[0][1] * a[1] + m[0][2] * a[2],
+ m[1][0] * a[0] + m[1][1] * a[1] + m[1][2] * a[2],
+ m[2][0] * a[0] + m[2][1] * a[1] + m[2][2] * a[2]);
+};
+
+Mat3.prototype.column = function(index){
+ var m = this.m;
+
+ return new Vec3( m[0][index], m[1][index], m[2][index] );
+};
+
+Mat3.prototype.row = function(index){
+ var m = this.m;
+
+ return new Vec3( m[index][0], m[index][1], m[index][2] );
+};
diff --git a/three.js/examples/threex-aruco/vendor/js-aruco/src/svd.js b/three.js/examples/threex-aruco/vendor/js-aruco/src/svd.js
new file mode 100644
index 0000000000000000000000000000000000000000..72098d55d6ef2df401bc46b7194eaee46291960c
--- /dev/null
+++ b/three.js/examples/threex-aruco/vendor/js-aruco/src/svd.js
@@ -0,0 +1,283 @@
+/*
+Copyright (c) 2012 Juan Mellado
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+/*
+References:
+- "Numerical Recipes in C - Second Edition"
+ http://www.nr.com/
+*/
+
+var SVD = SVD || {};
+
+SVD.svdcmp = function(a, m, n, w, v){
+ var flag, i, its, j, jj, k, l, nm,
+ anorm = 0.0, c, f, g = 0.0, h, s, scale = 0.0, x, y, z, rv1 = [];
+
+ //Householder reduction to bidiagonal form
+ for (i = 0; i < n; ++ i){
+ l = i + 1;
+ rv1[i] = scale * g;
+ g = s = scale = 0.0;
+ if (i < m){
+ for (k = i; k < m; ++ k){
+ scale += Math.abs( a[k][i] );
+ }
+ if (0.0 !== scale){
+ for (k = i; k < m; ++ k){
+ a[k][i] /= scale;
+ s += a[k][i] * a[k][i];
+ }
+ f = a[i][i];
+ g = -SVD.sign( Math.sqrt(s), f );
+ h = f * g - s;
+ a[i][i] = f - g;
+ for (j = l; j < n; ++ j){
+ for (s = 0.0, k = i; k < m; ++ k){
+ s += a[k][i] * a[k][j];
+ }
+ f = s / h;
+ for (k = i; k < m; ++ k){
+ a[k][j] += f * a[k][i];
+ }
+ }
+ for (k = i; k < m; ++ k){
+ a[k][i] *= scale;
+ }
+ }
+ }
+ w[i] = scale * g;
+ g = s = scale = 0.0;
+ if ( (i < m) && (i !== n - 1) ){
+ for (k = l; k < n; ++ k){
+ scale += Math.abs( a[i][k] );
+ }
+ if (0.0 !== scale){
+ for (k = l; k < n; ++ k){
+ a[i][k] /= scale;
+ s += a[i][k] * a[i][k];
+ }
+ f = a[i][l];
+ g = -SVD.sign( Math.sqrt(s), f );
+ h = f * g - s;
+ a[i][l] = f - g;
+ for (k = l; k < n; ++ k){
+ rv1[k] = a[i][k] / h;
+ }
+ for (j = l; j < m; ++ j){
+ for (s = 0.0, k = l; k < n; ++ k){
+ s += a[j][k] * a[i][k];
+ }
+ for (k = l; k < n; ++ k){
+ a[j][k] += s * rv1[k];
+ }
+ }
+ for (k = l; k < n; ++ k){
+ a[i][k] *= scale;
+ }
+ }
+ }
+ anorm = Math.max(anorm, ( Math.abs( w[i] ) + Math.abs( rv1[i] ) ) );
+ }
+
+ //Acumulation of right-hand transformation
+ for (i = n - 1; i >= 0; -- i){
+ if (i < n - 1){
+ if (0.0 !== g){
+ for (j = l; j < n; ++ j){
+ v[j][i] = ( a[i][j] / a[i][l] ) / g;
+ }
+ for (j = l; j < n; ++ j){
+ for (s = 0.0, k = l; k < n; ++ k){
+ s += a[i][k] * v[k][j];
+ }
+ for (k = l; k < n; ++ k){
+ v[k][j] += s * v[k][i];
+ }
+ }
+ }
+ for (j = l; j < n; ++ j){
+ v[i][j] = v[j][i] = 0.0;
+ }
+ }
+ v[i][i] = 1.0;
+ g = rv1[i];
+ l = i;
+ }
+
+ //Acumulation of left-hand transformation
+ for (i = Math.min(n, m) - 1; i >= 0; -- i){
+ l = i + 1;
+ g = w[i];
+ for (j = l; j < n; ++ j){
+ a[i][j] = 0.0;
+ }
+ if (0.0 !== g){
+ g = 1.0 / g;
+ for (j = l; j < n; ++ j){
+ for (s = 0.0, k = l; k < m; ++ k){
+ s += a[k][i] * a[k][j];
+ }
+ f = (s / a[i][i]) * g;
+ for (k = i; k < m; ++ k){
+ a[k][j] += f * a[k][i];
+ }
+ }
+ for (j = i; j < m; ++ j){
+ a[j][i] *= g;
+ }
+ }else{
+ for (j = i; j < m; ++ j){
+ a[j][i] = 0.0;
+ }
+ }
+ ++ a[i][i];
+ }
+
+ //Diagonalization of the bidiagonal form
+ for (k = n - 1; k >= 0; -- k){
+ for (its = 1; its <= 30; ++ its){
+ flag = true;
+ for (l = k; l >= 0; -- l){
+ nm = l - 1;
+ if ( Math.abs( rv1[l] ) + anorm === anorm ){
+ flag = false;
+ break;
+ }
+ if ( Math.abs( w[nm] ) + anorm === anorm ){
+ break;
+ }
+ }
+ if (flag){
+ c = 0.0;
+ s = 1.0;
+ for (i = l; i <= k; ++ i){
+ f = s * rv1[i];
+ if ( Math.abs(f) + anorm === anorm ){
+ break;
+ }
+ g = w[i];
+ h = SVD.pythag(f, g);
+ w[i] = h;
+ h = 1.0 / h;
+ c = g * h;
+ s = -f * h;
+ for (j = 1; j <= m; ++ j){
+ y = a[j][nm];
+ z = a[j][i];
+ a[j][nm] = y * c + z * s;
+ a[j][i] = z * c - y * s;
+ }
+ }
+ }
+
+ //Convergence
+ z = w[k];
+ if (l === k){
+ if (z < 0.0){
+ w[k] = -z;
+ for (j = 0; j < n; ++ j){
+ v[j][k] = -v[j][k];
+ }
+ }
+ break;
+ }
+
+ if (30 === its){
+ return false;
+ }
+
+ //Shift from bottom 2-by-2 minor
+ x = w[l];
+ nm = k - 1;
+ y = w[nm];
+ g = rv1[nm];
+ h = rv1[k];
+ f = ( (y - z) * (y + z) + (g - h) * (g + h) ) / (2.0 * h * y);
+ g = SVD.pythag( f, 1.0 );
+ f = ( (x - z) * (x + z) + h * ( (y / (f + SVD.sign(g, f) ) ) - h) ) / x;
+
+ //Next QR transformation
+ c = s = 1.0;
+ for (j = l; j <= nm; ++ j){
+ i = j + 1;
+ g = rv1[i];
+ y = w[i];
+ h = s * g;
+ g = c * g;
+ z = SVD.pythag(f, h);
+ rv1[j] = z;
+ c = f / z;
+ s = h / z;
+ f = x * c + g * s;
+ g = g * c - x * s;
+ h = y * s;
+ y *= c;
+ for (jj = 0; jj < n; ++ jj){
+ x = v[jj][j];
+ z = v[jj][i];
+ v[jj][j] = x * c + z * s;
+ v[jj][i] = z * c - x * s;
+ }
+ z = SVD.pythag(f, h);
+ w[j] = z;
+ if (0.0 !== z){
+ z = 1.0 / z;
+ c = f * z;
+ s = h * z;
+ }
+ f = c * g + s * y;
+ x = c * y - s * g;
+ for (jj = 0; jj < m; ++ jj){
+ y = a[jj][j];
+ z = a[jj][i];
+ a[jj][j] = y * c + z * s;
+ a[jj][i] = z * c - y * s;
+ }
+ }
+ rv1[l] = 0.0;
+ rv1[k] = f;
+ w[k] = x;
+ }
+ }
+
+ return true;
+};
+
+SVD.pythag = function(a, b){
+ var at = Math.abs(a), bt = Math.abs(b), ct;
+
+ if (at > bt){
+ ct = bt / at;
+ return at * Math.sqrt(1.0 + ct * ct);
+ }
+
+ if (0.0 === bt){
+ return 0.0;
+ }
+
+ ct = at / bt;
+ return bt * Math.sqrt(1.0 + ct * ct);
+};
+
+SVD.sign = function(a, b){
+ return b >= 0.0? Math.abs(a): -Math.abs(a);
+};