From 84332ab81c1b445195f1d9be8bbeae0725c8e758 Mon Sep 17 00:00:00 2001 From: Valerio Virgillito Date: Tue, 6 Mar 2012 10:58:25 -0800 Subject: Squashed commit of preload-fix into Master - Requiring all the previously pre-loaded files - RDGE, Codemirror and gl-matrix are not included via a script tag. Signed-off-by: Valerio Virgillito --- js/lib/geom/sub-path.js | 1288 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1288 insertions(+) create mode 100755 js/lib/geom/sub-path.js (limited to 'js/lib/geom/sub-path.js') diff --git a/js/lib/geom/sub-path.js b/js/lib/geom/sub-path.js new file mode 100755 index 00000000..ab54d1e9 --- /dev/null +++ b/js/lib/geom/sub-path.js @@ -0,0 +1,1288 @@ +/* +This file contains proprietary software owned by Motorola Mobility, Inc.
+No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+(c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ + + +var VecUtils = require("js/helper-classes/3D/vec-utils").VecUtils; + +var GeomObj = require("js/lib/geom/geom-obj").GeomObj; +var AnchorPoint = require("js/lib/geom/anchor-point").AnchorPoint; +var MaterialsModel = require("js/models/materials-model").MaterialsModel; + +// TODO Those function do not seems to be used. We should remove them +function SubpathOffsetPoint(pos, mapPos) { + this.Pos = [pos[0],pos[1],pos[2]]; + this.CurveMapPos = [mapPos[0], mapPos[1], mapPos[2]]; +} + +function SubpathOffsetTriangle(v0, v1, v2) { + this.v0 = v0; + this.v1 = v1; + this.v2 = v2; + this.n = [0,0,1]; //replace with the actual cross product later +} + +function sortNumberAscending(a,b){ + return a-b; +} +function sortNumberDescending(a,b){ + return b-a; +} +function SegmentIntersections(){ + this.paramArray = []; +} + +/////////////////////////////////////////////////////////////////////// +// Class GLSubpath +// representation a sequence of cubic bezier curves. +// Derived from class GeomObj +/////////////////////////////////////////////////////////////////////// + +var GLSubpath = function GLSubpath() { + /////////////////////////////////////////////////// + // Instance variables + /////////////////////////////////////////////////// + this._Anchors = []; + this._BBoxMin = [0, 0, 0]; + this._BBoxMax = [0, 0, 0]; + this._isClosed = false; + + this._samples = []; //polyline representation of this curve + this._sampleParam = []; //parametric distance of samples, within [0, N], where N is # of Bezier curves (=# of anchor points if closed, =#anchor pts -1 if open) + this._anchorSampleIndex = []; //index within _samples corresponding to anchor points + + this._UnprojectedAnchors = []; + + //initially set the _dirty bit so we will construct samples + this._dirty = true; + + //whether or not to use the canvas drawing to stroke/fill + this._useCanvasDrawing = true; + + //the X and Y location of this subpath's canvas in stage world space of Ninja + this._canvasX = 0; + this._canvasY = 0; + + //stroke information + this._strokeWidth = 0.0; + this._strokeColor = [0.4, 0.4, 0.4, 1.0]; + this._strokeMaterial = null + this._strokeStyle = "Solid"; + this._materialAmbient = [0.2, 0.2, 0.2, 1.0]; + this._materialDiffuse = [0.4, 0.4, 0.4, 1.0]; + this._materialSpecular = [0.4, 0.4, 0.4, 1.0]; + this._fillColor = [0.4, 0.4, 0.4, 1.0]; + this._fillMaterial = null; + this._DISPLAY_ANCHOR_RADIUS = 5; + //drawing context + this._world = null; + + //tool that owns this subpath + this._drawingTool = null; + this._planeMat = null; + this._planeMatInv = null; + this._planeCenter = null; + + //used to query what the user selected, OR-able for future extensions + this.SEL_NONE = 0; //nothing was selected + this.SEL_ANCHOR = 1; //anchor point was selected + this.SEL_PREV = 2; //previous handle of anchor point was selected + this.SEL_NEXT = 4; //next handle of anchor point was selected + this.SEL_PATH = 8; //the path itself was selected + this._selectMode = this.SEL_NONE; + this._selectedAnchorIndex = -1; + + this._SAMPLING_EPSILON = 0.5; //epsilon used for sampling the curve + this._DEFAULT_STROKE_WIDTH = 20; //use only if stroke width not specified + this._MAX_OFFSET_ANGLE = 10; //max angle (in degrees) between consecutive vectors from curve to offset path + + // (current GeomObj complains if buildBuffers/render is added to GLSubpath prototype) + //buildBuffers + // Build the stroke vertices, normals, textures and colors + // Add that array data to the GPU using OpenGL data binding + this.buildBuffers = function () { + // return; //no need to do anything for now + }; + + //render + // specify how to render the subpath in Canvas2D + this.render = function () { + // get the world + var world = this.getWorld(); + if (!world) throw( "null world in subpath render" ); + + // get the context + var ctx = world.get2DContext(); + if (!ctx) throw ("null context in subpath render") + + var numAnchors = this.getNumAnchors(); + if (numAnchors === 0) { + return; //nothing to do for empty paths + } + + ctx.save(); + + this.createSamples(); //dirty bit checked in this function...will generate a polyline representation + var bboxMin = this.getBBoxMin(); + var bboxMax = this.getBBoxMax(); + var bboxWidth = bboxMax[0] - bboxMin[0]; + var bboxHeight = bboxMax[1] - bboxMin[1]; + var bboxMid = [0.5 * (bboxMax[0] + bboxMin[0]), 0.5 * (bboxMax[1] + bboxMin[1]), 0.5 * (bboxMax[2] + bboxMin[2])]; + + ctx.clearRect(0, 0, bboxWidth, bboxHeight); + + ctx.lineWidth = this._strokeWidth; + ctx.strokeStyle = "black"; + if (this._strokeColor) { + ctx.strokeStyle = MathUtils.colorToHex( this._strokeColor ); + } + + ctx.fillStyle = "white"; + if (this._fillColor){ + //ctx.fillStyle = MathUtils.colorToHex( this._fillColor ); + var fillColorStr = "rgba("+parseInt(255*this._fillColor[0])+","+parseInt(255*this._fillColor[1])+","+parseInt(255*this._fillColor[2])+","+this._fillColor[3]+")"; + ctx.fillStyle = fillColorStr; + } + var lineCap = ['butt','round','square']; + ctx.lineCap = lineCap[1]; + ctx.beginPath(); + + /* + commenting this out for now because of Chrome bug where coincident endpoints of bezier curve cause the curve to not be rendered + var prevAnchor = this.getAnchor(0); + ctx.moveTo(prevAnchor.getPosX()-bboxMin[0],prevAnchor.getPosY()-bboxMin[1]); + for (var i = 1; i < numAnchors; i++) { + var currAnchor = this.getAnchor(i); + ctx.bezierCurveTo(prevAnchor.getNextX()-bboxMin[0],prevAnchor.getNextY()-bboxMin[1], currAnchor.getPrevX()-bboxMin[0], currAnchor.getPrevY()-bboxMin[1], currAnchor.getPosX()-bboxMin[0], currAnchor.getPosY()-bboxMin[1]); + prevAnchor = currAnchor; + } + if (this._isClosed === true) { + var currAnchor = this.getAnchor(0); + ctx.bezierCurveTo(prevAnchor.getNextX()-bboxMin[0],prevAnchor.getNextY()-bboxMin[1], currAnchor.getPrevX()-bboxMin[0], currAnchor.getPrevY()-bboxMin[1], currAnchor.getPosX()-bboxMin[0], currAnchor.getPosY()-bboxMin[1]); + prevAnchor = currAnchor; + ctx.fill(); + } + */ + + + var numPoints = this._samples.length/3; + ctx.moveTo(this._samples[0]-bboxMin[0],this._samples[1]-bboxMin[1]); + for (var i=0;i=0;i--) { + var newAnchor = new AnchorPoint(); + var oldAnchor = this._Anchors[i]; + newAnchor.setPos(oldAnchor.getPosX(),oldAnchor.getPosY(),oldAnchor.getPosZ()); + newAnchor.setPrevPos(oldAnchor.getNextX(),oldAnchor.getNextY(),oldAnchor.getNextZ()); + newAnchor.setNextPos(oldAnchor.getPrevX(),oldAnchor.getPrevY(),oldAnchor.getPrevZ()); + revAnchors.push(newAnchor); + } + if (this._selectedAnchorIndex >= 0){ + this._selectedAnchorIndex = (numAnchors-1) - this._selectedAnchorIndex; + } + this._Anchors = revAnchors; + this._dirty=true; +}; + +//remove all the anchor points +GLSubpath.prototype.clearAllAnchors = function () { + this._Anchors = []; + this._isClosed = false; + this._dirty = true; +}; + +GLSubpath.prototype.insertAnchorAtParameter = function(index, param) { + if (index+1 >= this._Anchors.length && !this._isClosed) { + return; + } + //insert an anchor after the specified index using the parameter, using De Casteljau subdivision + var nextIndex = (index+1)%this._Anchors.length; + + //build the De Casteljau points + var P0P1 = VecUtils.vecInterpolate(3, this._Anchors[index].getPos(), this._Anchors[index].getNext(), param); + var P1P2 = VecUtils.vecInterpolate(3, this._Anchors[index].getNext(), this._Anchors[nextIndex].getPrev(), param); + var P2P3 = VecUtils.vecInterpolate(3, this._Anchors[nextIndex].getPrev(), this._Anchors[nextIndex].getPos(), param); + + var P0P1P2 = VecUtils.vecInterpolate(3, P0P1, P1P2, param); + var P1P2P3 = VecUtils.vecInterpolate(3, P1P2, P2P3, param); + var anchorPos = VecUtils.vecInterpolate(3, P0P1P2, P1P2P3, param); + + + //update the next of the anchor at index and prev of anchor at nextIndex + var isPrevCoincident = false; + var isNextCoincident = false; + if (VecUtils.vecDist( 3, P0P1, this._Anchors[index].getNext()) < this._SAMPLING_EPSILON) { + //no change to the next point + isPrevCoincident = true; + } else { + this._Anchors[index].setNextPos(P0P1[0], P0P1[1], P0P1[2]); + } + + if (VecUtils.vecDist( 3, P2P3, this._Anchors[nextIndex].getPrev()) < this._SAMPLING_EPSILON) { + //no change to the prev point + isNextCoincident = true; + } else { + this._Anchors[nextIndex].setPrevPos(P2P3[0], P2P3[1], P2P3[2]); + } + + //create a new anchor point + var newAnchor = new AnchorPoint(); + + if (isPrevCoincident && isNextCoincident){ + anchorPos[0]=P1P2[0];anchorPos[1]=P1P2[1];anchorPos[2]=P1P2[2]; + newAnchor.setPos(anchorPos[0],anchorPos[1],anchorPos[2]); + newAnchor.setPrevPos(anchorPos[0],anchorPos[1],anchorPos[2]); + newAnchor.setNextPos(anchorPos[0],anchorPos[1],anchorPos[2]); + } else { + newAnchor.setPrevPos(P0P1P2[0], P0P1P2[1], P0P1P2[2]); + newAnchor.setNextPos(P1P2P3[0], P1P2P3[1], P1P2P3[2]); + newAnchor.setPos(anchorPos[0], anchorPos[1], anchorPos[2]); + } + + //insert the new anchor point at the correct index and set it as the selected anchor + this._Anchors.splice(nextIndex, 0, newAnchor); + this._selectedAnchorIndex = nextIndex; + this._dirty = true; +}; + +GLSubpath.prototype._checkIntersectionWithSamples = function(startIndex, endIndex, point, radius){ + //check whether the point is within the radius distance from the curve represented as a polyline in _samples + //return the parametric distance along the curve if there is an intersection, else return null + //will assume that the BBox test is performed outside this function + if (endIndex bboxMax[d]){ + bboxMax[d] = controlPts[i][d]; + } + } + } + //check whether the bbox of the control points contains the point within the specified radius + for (var d=0;d<3;d++){ + if (point[d] < (bboxMin[d]-radius)){ + return null; + } + if (point[d] > (bboxMax[d]+radius)){ + return null; + } + } + + //check if the curve is already flat, and if so, check the distance from the segment C0C3 to the point + //measure distance of C1 and C2 to segment C0-C3 + var distC1 = MathUtils.distPointToSegment(controlPts[1], controlPts[0], controlPts[3]); + var distC2 = MathUtils.distPointToSegment(controlPts[2], controlPts[0], controlPts[3]); + var maxDist = Math.max(distC1, distC2); + var threshold = this._SAMPLING_EPSILON; //this should be set outside this function //TODO + if (maxDist < threshold) { //if the curve is flat + var distP = MathUtils.distPointToSegment(point, controlPts[0], controlPts[3]); //TODO we may need to neglect cases where the non-perpendicular distance is used... + if (distP>radius) { + return null; + } else { + var param = MathUtils.paramPointProjectionOnSegment(point, controlPts[0], controlPts[3]); //TODO this function is already called in distPointToSegment...optimize by removing redundant call + //var param = VecUtils.vecDist(3, point, controlPts[0])/VecUtils.vecDist(3, controlPts[3], controlPts[0]); + if (param<0) + param=0; + if (param>1) + param=1; + + return beginParam + (endParam-beginParam)*param; + } + } + + //subdivide this curve using De Casteljau interpolation + var C0_ = VecUtils.vecInterpolate(3, controlPts[0], controlPts[1], 0.5); + var C1_ = VecUtils.vecInterpolate(3, controlPts[1], controlPts[2], 0.5); + var C2_ = VecUtils.vecInterpolate(3, controlPts[2], controlPts[3], 0.5); + + var C0__ = VecUtils.vecInterpolate(3, C0_, C1_, 0.5); + var C1__ = VecUtils.vecInterpolate(3, C1_, C2_, 0.5); + + var C0___ = VecUtils.vecInterpolate(3, C0__, C1__, 0.5); + + //recursively sample the first half of the curve + var midParam = (endParam+beginParam)*0.5; + var param1 = this._checkIntersection([controlPts[0],C0_,C0__,C0___], beginParam, midParam, point, radius); + if (param1!==null){ + return param1; + } + + //recursively sample the second half of the curve + var param2 = this._checkIntersection([C0___,C1__,C2_,controlPts[3]], midParam, endParam, point, radius); + if (param2!==null){ + return param2; + } + + //no intersection, so return null + return null; +}; + +//whether the point lies within the bbox given by the four control points +GLSubpath.prototype._isWithinBoundingBox = function(point, ctrlPts, radius) { + var bboxMin = [Infinity, Infinity, Infinity]; + var bboxMax = [-Infinity,-Infinity,-Infinity]; + for (var i=0;i bboxMax[d]){ + bboxMax[d] = ctrlPts[i][d]; + } + } + } + //check whether the bbox of the control points contains the point within the specified radius + for (var d=0;d<3;d++){ + if (point[d] < (bboxMin[d]-radius)){ + return false; + } + if (point[d] > (bboxMax[d]+radius)){ + return false; + } + } + return true; +}; + +GLSubpath.prototype.pickAnchor = function (pickX, pickY, pickZ, radius) { + var numAnchors = this._Anchors.length; + var selAnchorIndex = -1; + var retCode = this.SEL_NONE; + var radSq = radius * radius; + var minDistance = Infinity; + //check if the clicked location is close to the currently selected anchor position + if (this._selectedAnchorIndex>=0 && this._selectedAnchorIndex=0 && this._selectedAnchorIndex=0 && selAnchorIndex === -1) { + var distSq = this._Anchors[this._selectedAnchorIndex].getPrevDistanceSq(pickX, pickY, pickZ); + if (distSq < minDistance && distSq < radSq){ + selAnchorIndex = this._selectedAnchorIndex; + minDistance = distSq; + retCode = retCode | this.SEL_PREV; + } else { + //check the next for this anchor point + distSq = this._Anchors[this._selectedAnchorIndex].getNextDistanceSq(pickX, pickY, pickZ); + if (distSq 1) { + //start with the first anchor position (since the Bezier curve start point is not added in the sample function below) + //this._samples.push(this._Anchors[0].getPosX()); + //this._samples.push(this._Anchors[0].getPosY()); + //this._samples.push(this._Anchors[0].getPosZ()); + + for (var i = 0; i < numAnchors - 1; i++) { + //get the control points + var C0X = this._Anchors[i].getPosX(); + var C0Y = this._Anchors[i].getPosY(); + var C0Z = this._Anchors[i].getPosZ(); + + var C1X = this._Anchors[i].getNextX(); + var C1Y = this._Anchors[i].getNextY(); + var C1Z = this._Anchors[i].getNextZ(); + + var C2X = this._Anchors[i + 1].getPrevX(); + var C2Y = this._Anchors[i + 1].getPrevY(); + var C2Z = this._Anchors[i + 1].getPrevZ(); + + var C3X = this._Anchors[i + 1].getPosX(); + var C3Y = this._Anchors[i + 1].getPosY(); + var C3Z = this._Anchors[i + 1].getPosZ(); + + var beginParam = i; + var endParam = i+1; + this._anchorSampleIndex.push(this._samples.length/3); //index of sample corresponding to anchor i + this._sampleCubicBezier(C0X, C0Y, C0Z, C1X, C1Y, C1Z, C2X, C2Y, C2Z, C3X, C3Y, C3Z, beginParam, endParam); + } //for every anchor point i, except last + + if (this._isClosed) { + var i = numAnchors - 1; + //get the control points + var C0X = this._Anchors[i].getPosX(); + var C0Y = this._Anchors[i].getPosY(); + var C0Z = this._Anchors[i].getPosZ(); + + var C1X = this._Anchors[i].getNextX(); + var C1Y = this._Anchors[i].getNextY(); + var C1Z = this._Anchors[i].getNextZ(); + + var C2X = this._Anchors[0].getPrevX(); + var C2Y = this._Anchors[0].getPrevY(); + var C2Z = this._Anchors[0].getPrevZ(); + + var C3X = this._Anchors[0].getPosX(); + var C3Y = this._Anchors[0].getPosY(); + var C3Z = this._Anchors[0].getPosZ(); + + var beginParam = i; + var endParam = i+1; + this._anchorSampleIndex.push(this._samples.length/3); //index of sample corresponding to anchor i + this._sampleCubicBezier(C0X, C0Y, C0Z, C1X, C1Y, C1Z, C2X, C2Y, C2Z, C3X, C3Y, C3Z, beginParam, endParam); + } else { + this._anchorSampleIndex.push((this._samples.length/3) - 1); //index of sample corresponding to last anchor + } + } //if (numAnchors >== 2) { + + //re-compute the bounding box (this also accounts for stroke width, so assume the stroke width is set) + this.computeBoundingBox(true); + + } //if (this._dirty) + this._dirty = false; +}; + +GLSubpath.prototype.computeBoundingBox = function(useSamples){ + this._BBoxMin = [Infinity, Infinity, Infinity]; + this._BBoxMax = [-Infinity, -Infinity, -Infinity]; + if (useSamples) { + var numPoints = this._samples.length/3; + if (numPoints === 0) { + this._BBoxMin = [0, 0, 0]; + this._BBoxMax = [0, 0, 0]; + } else { + for (var i=0;i pt[d]) { + this._BBoxMin[d] = pt[d]; + } + if (this._BBoxMax[d] < pt[d]) { + this._BBoxMax[d] = pt[d]; + } + }//for every dimension d from 0 to 2 + } + } + } + else{ + //build a bbox of the anchor points, not the path itself + var numAnchors = this._Anchors.length; + var anchorPts = [[0,0,0], [0,0,0], [0,0,0]]; + if (numAnchors === 0) { + this._BBoxMin = [0, 0, 0]; + this._BBoxMax = [0, 0, 0]; + } else { + for (var i = 0; i < numAnchors; i++) { + anchorPts[0] = ([this._Anchors[i].getPosX(),this._Anchors[i].getPosY(),this._Anchors[i].getPosZ()]); + anchorPts[1] = ([this._Anchors[i].getPrevX(),this._Anchors[i].getPrevY(),this._Anchors[i].getPrevZ()]); + anchorPts[2] = ([this._Anchors[i].getNextX(),this._Anchors[i].getNextY(),this._Anchors[i].getNextZ()]); + + for (var p=0;p<3;p++){ + for (var d = 0; d < 3; d++) { + if (this._BBoxMin[d] > anchorPts[p][d]) { + this._BBoxMin[d] = anchorPts[p][d]; + } + if (this._BBoxMax[d] < anchorPts[p][d]) { + this._BBoxMax[d] = anchorPts[p][d]; + } + }//for every dimension d from 0 to 2 + } //for every anchorPts p from 0 to 2 + } //for every anchor point i + } //else of if (numSamples === 0) { + }//else of if useSamples + + //increase the bbox given the stroke width + for (var d = 0; d < 3; d++) { + this._BBoxMin[d]-= this._strokeWidth/2; + this._BBoxMax[d]+= this._strokeWidth/2; + }//for every dimension d from 0 to 2 +}; + +//returns v such that it is in [min,max] +GLSubpath.prototype._clamp = function (v, min, max) { + if (v < min) { + return min; + } + + if (v > max) { + return max; + } + + return v; +}; + + //input: point sIn in stage-world space, planeMidPt in stage-world space, matrix planeMat that rotates plane into XY (parallel to view plane), inverse of planeMat +//returns: sIn 'unprojected' +GLSubpath.prototype.unprojectPoint = function ( sIn, planeMidPt, planeMat, planeMatInv) { + var s = sIn.slice(0); + s[0] -= planeMidPt[0]; s[1] -= planeMidPt[1]; //bring s to the center of the plane + + // unproject the point s + var i; + var viewZ = 1400; + if (MathUtils.fpCmp(viewZ,-s[2]) !== 0){ + z = s[2]*viewZ/(viewZ + s[2]); + var x = s[0]*(viewZ - z)/viewZ, + y = s[1]*(viewZ - z)/viewZ; + s[0] = x; s[1] = y; s[2] = z; + } + + // add the translation back in + s[0] += planeMidPt[0]; s[1] += planeMidPt[1]; + + return s; +}; + +GLSubpath.prototype.computeUnprojectedNDC = function (pos, bboxMid, bboxDim, r, l, t, b, z, zn) { + //unproject pos from stageworld to unprojected state + var ppos = this.unprojectPoint(pos, this._planeCenter, this._planeMat, this._planeMatInv); + + //make the coordinates lie in [-1,1] + var x = (ppos[0] - bboxMid[0]) / bboxDim[0]; + var y = -(ppos[1] - bboxMid[1]) / bboxDim[1]; + + //x and y should never be outside the [-1,1] range + x = this._clamp(x, -1, 1); + y = this._clamp(y, -1, 1); + + //apply the perspective transform + x *= -z * (r - l) / (2.0 * zn); + y *= -z * (t - b) / (2.0 * zn); + + ppos[0] = x; + ppos[1] = y; + ppos[2] = 0; //z; + return ppos; +}; + +GLSubpath.prototype.makeStrokeMaterial = function() +{ + var strokeMaterial; + if (this.getStrokeMaterial()) { + strokeMaterial = this.getStrokeMaterial().dup(); + } else { + strokeMaterial = new FlatMaterial(); + } + + if (strokeMaterial) { + strokeMaterial.init(); + //if(!this.getStrokeMaterial() && this._strokeColor) + if(this._strokeColor) { + strokeMaterial.setProperty("color", this._strokeColor); + } + } + + this._materialArray.push( strokeMaterial ); + this._materialTypeArray.push( "stroke" ); + + return strokeMaterial; +}; + +GLSubpath.prototype.makeFillMaterial = function() { + var fillMaterial; + if (this.getFillMaterial()) { + fillMaterial = this.getFillMaterial().dup(); + } else { + fillMaterial = new FlatMaterial(); + } + + if (fillMaterial) { + fillMaterial.init(); + //if(!this.getFillMaterial() && this._fillColor) + if (this._fillColor) { + fillMaterial.setProperty("color", this._fillColor); + } + } + + this._materialArray.push( fillMaterial ); + this._materialTypeArray.push( "fill" ); + + return fillMaterial; +}; + +GLSubpath.prototype.getNearVertex = function( eyePt, dir ){ + //get the parameters used for computing perspective transformation + var bboxDim = []; + var bboxMid = []; + bboxDim[0] = 0.5 * (this._BBoxMax[0] - this._BBoxMin[0]); + bboxMid[0] = 0.5 * (this._BBoxMax[0] + this._BBoxMin[0]); + bboxDim[1] = 0.5 * (this._BBoxMax[1] - this._BBoxMin[1]); + bboxMid[1] = 0.5 * (this._BBoxMax[1] + this._BBoxMin[1]); + bboxDim[2] = 0.5 * (this._BBoxMax[2] - this._BBoxMin[2]); + bboxMid[3] = 0.5 * (this._BBoxMax[2] + this._BBoxMin[2]); + + // convert the stroke vertices into normalized device coordinates + var world = this.getWorld(); + if (!world) return null; + var aspect = world.getAspect(); + var zn = world.getZNear(), zf = world.getZFar(); + var t = zn * Math.tan(world.getFOV() * Math.PI / 360.0), //top of the frustum + b = -t, //bottom + r = aspect * t, //right + l = -r; //left + + // calculate the object coordinates from their NDC coordinates + var z = -world.getViewDistance(); + + // the eyePt and dir are in WebGL space...we need to convert each anchor point into the WebGL space + var numAnchors = this._Anchors.length; + var selAnchorPpos = null; + var minDistance = Infinity; + for (var i = 0; i < numAnchors; i++) { + var anchorPos = [this._Anchors[i].getPosX(), this._Anchors[i].getPosY(), this._Anchors[i].getPosZ()]; + var ppos = this.computeUnprojectedNDC(anchorPos, bboxMid, bboxDim, r, l, t, b, z, zn); + var dist = MathUtils.distPointToRay(ppos, eyePt, dir); + if (dist < minDistance) { + selAnchorPpos = ppos; + minDistance = dist; + } + } + return selAnchorPpos; +}; + +GLSubpath.prototype.getNearPoint = function( eyePt, dir ){ + return null; +}; + +//returns true if P is left of line through l0 and l1 or on it +GLSubpath.prototype.isLeft = function(l0, l1, P){ + var signedArea = (l1[0]-l0[0])*(P[1] - l0[1]) - (P[0]-l0[0])*(l1[1]-l0[1]); + + if (signedArea>=0) { + return true; + } else { + return false; + } +}; + +//returns true if 2D point p is contained within 2D quad given by r0,r1,r2,r3 (need not be axis-aligned) +GLSubpath.prototype.isPointInQuad2D = function(r0,r1,r2,r3,p){ + //returns true if the point is on the same side of the segments r0r1, r1r2, r2r3, r3r0 + var isLeft0 = this.isLeft(r0,r1,p); + var isLeft1 = this.isLeft(r1,r2,p); + var isLeft2 = this.isLeft(r2,r3,p); + var isLeft3 = this.isLeft(r3,r0,p); + var andAll = isLeft0 & isLeft1 & isLeft2 & isLeft3; + if (andAll) + return true; + var orAll = isLeft0 | isLeft1 | isLeft2 | isLeft3; + if (!orAll) { + return true; + } + + return false; +}; + +GLSubpath.prototype.export = function() { + var rtnStr = "type: " + this.geomType() + "\n"; + + rtnStr += "strokeWidth: " + this._strokeWidth + "\n"; + rtnStr += "strokeStyle: " + this._strokeStyle + "\n"; + + rtnStr += "strokeMat: "; + if (this._strokeMaterial) + rtnStr += this._strokeMaterial.getName(); + else + rtnStr += "flatMaterial"; + rtnStr += "\n"; + + rtnStr += "fillMat: "; + if (this._fillMaterial) + rtnStr += this._fillMaterial.getName(); + else + rtnStr += "flatMaterial"; + rtnStr += "\n"; + + var isClosedStr = "false"; + if (this._isClosed) + isClosedStr = "true"; + rtnStr += "isClosed: " + isClosedStr + "\n"; + + //add the anchor points + var numAnchors = this._Anchors.length; + rtnStr += "numAnchors: " + numAnchors + "\n"; + for (var i=0;i this._BBoxMax[0]) return false; + if (y < this._BBoxMin[1]) return false; + if (y > this._BBoxMax[1]) return false; + if (z < this._BBoxMin[2]) return false; + if (z > this._BBoxMax[2]) return false; + + return true; +}; + +GLSubpath.prototype.collidesWithPoint = function (x, y) { + if (x < this._BBoxMin[0]) return false; + if (x > this._BBoxMax[0]) return false; + if (y < this._BBoxMin[1]) return false; + if (y > this._BBoxMax[1]) return false; + + return true; +}; + +//GLSubpath.prototype = new GeomObj(); + +if (typeof exports === "object") { + exports.SubPath = GLSubpath; +} + -- cgit v1.2.3