/* <copyright>
Copyright (c) 2012, Motorola Mobility, Inc
All Rights Reserved.
BSD License.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

  - Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer.
  - Redistributions in binary form must reproduce the above copyright
    notice, this list of conditions and the following disclaimer in the
    documentation and/or other materials provided with the distribution.
  - Neither the name of Motorola Mobility nor the names of its contributors
    may be used to endorse or promote products derived from this software
    without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
</copyright> */

var GeomObj = require("js/lib/geom/geom-obj").GeomObj;
var ShapePrimitive =    require("js/lib/geom/shape-primitive").ShapePrimitive;
var MaterialsModel = require("js/models/materials-model").MaterialsModel;

 ///////////////////////////////////////////////////////////////////////
// Class GLRectangle
//      GL representation of a rectangle.
//      Derived from class GeomObj
///////////////////////////////////////////////////////////////////////
exports.Rectangle = Object.create(GeomObj, {
	// CONSTANTS
	N_TRIANGLES: { value : 15, writable: false },       // TODO - This is not being used anywhere. Remove?

	//if (!MaterialsModel)
	//	MaterialsModel = require("js/models/materials-model").MaterialsModel;

	///////////////////////////////////////////////////////////////////////
	// Instance variables
	///////////////////////////////////////////////////////////////////////
    _width: { value : 2.0, writable: true },
    _height: { value : 2.0, writable: true },
    _xOffset: { value : 0, writable: true },
    _yOffset: { value : 0, writable: true },

    _tlRadius: { value : 0, writable: true },
    _trRadius: { value : 0, writable: true },
    _blRadius: { value : 0, writable: true },
    _brRadius: { value : 0, writable: true },

    _strokeWidth: { value : 0.25, writable: true },
    _strokeStyle: { value : "Solid", writable: true },

    init: {
        value: function(world, xOffset, yOffset, width, height, strokeSize, strokeColor, fillColor,
                      tlRadius, trRadius, blRadius, brRadius, strokeMaterial, fillMaterial, strokeStyle) {
            this.m_world = world;

            if (arguments.length > 0) {
                this._width = width;
                this._height = height;
                this._xOffset = xOffset;
                this._yOffset = yOffset;

                this._strokeWidth = strokeSize;
                this._strokeColor = strokeColor;
                this._fillColor = fillColor;

                this.setTLRadius(tlRadius);
                this.setTRRadius(trRadius);
                this.setBLRadius(blRadius);
                this.setBRRadius(brRadius);

                this._strokeStyle = strokeStyle;

			this._matrix = Matrix.I(4);
            }

            // the overall radius includes the fill and the stroke.  separate the two based on the stroke width
            //  this._fillRad = this._radius - this._strokeWidth;
            //    var err = 0.05;
            var err = 0;
            this._fillWidth = this._width - this._strokeWidth  + err;
            this._fillHeight = this._height - this._strokeWidth + err;

            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];

            if(strokeMaterial) {
                this._strokeMaterial = strokeMaterial.dup();
            } else {
                this._strokeMaterial = MaterialsModel.getMaterial( MaterialsModel.getDefaultMaterialName() ).dup();
            }

            if(strokeColor) {
                if(this._strokeMaterial.hasProperty("color")) {
                    this._strokeMaterial.setProperty( "color",  this._strokeColor );
                } else if (this._strokeMaterial && (this._strokeMaterial.gradientType === this._strokeColor.gradientMode)) {
                    this._strokeMaterial.setGradientData(this._strokeColor.color);
                }
            }

            if(fillMaterial) {
                this._fillMaterial = fillMaterial.dup();
            } else {
                this._fillMaterial = MaterialsModel.getMaterial( MaterialsModel.getDefaultMaterialName() ).dup();
            }

			if(fillColor) {
                if(this._fillMaterial.hasProperty("color")) {
                    this._fillMaterial.setProperty( "color",  this._fillColor );
                } else if (this._fillMaterial && (this._fillMaterial.gradientType === this._fillColor.gradientMode)) {
                    this._fillMaterial.setGradientData(this._fillColor.color);
                }
            }
        }
    },

	///////////////////////////////////////////////////////////////////////
    // Property Accessors
    ///////////////////////////////////////////////////////////////////////
    // TODO - Use getters/setters in the future
    getStrokeWidth: {
        value: function() {
            return this._strokeWidth;
        }
    },

    setStrokeWidth: {
        value: function(w) {
            this._strokeWidth = w;
        }
    },

    getStrokeMaterial: {
        value: function() {
            return this._strokeMaterial;
        }
    },

    setStrokeMaterial: {
        value: function(m) {
            this._strokeMaterial = m;
        }
    },

    getFillMaterial: {
        value: function() {
            return this._fillMaterial;
        }
    },

    setFillMaterial: {
        value: function(m) {
            this._fillMaterial = m;
        }
    },
	///////////////////////////////////////////////////////////////////////
    // update the "color of the material
    getFillColor: {
        value: function() {
            return this._fillColor;
        }
    },

//    setFillColor: {
//        value: function(c) {
//            this._fillColor = c;
//        }
//    },
    getStrokeColor: {
        value: function() {
            return this._strokeColor;
        }
    },

//    setStrokeColor: {
//        value: function(c) {
//            this._strokeColor = c;
//        }
//    },
    ///////////////////////////////////////////////////////////////////////
    getTLRadius: {
        value: function() {
            return this._tlRadius;
        }
    },

    setTLRadius: {
        value: function(r) {
            this._tlRadius = Math.min(r, (this._height - this._strokeWidth)/2, (this._width - this._strokeWidth)/2);
        }
    },

    getTRRadius: {
        value: function() {
            return this._trRadius;
        }
    },

    setTRRadius: {
        value: function(r) {
            this._trRadius = Math.min(r, (this._height - this._strokeWidth)/2, (this._width - this._strokeWidth)/2);
        }
    },

    getBLRadius: {
        value: function() {
            return this._blRadius;
        }
    },

    setBLRadius: {
        value: function(r) {
            this._blRadius = Math.min(r, (this._height - this._strokeWidth)/2, (this._width - this._strokeWidth)/2);
        }
    },

    getBRRadius: {
        value: function() {
            return this._brRadius;
        }
    },

    setBRRadius: {
        value: function(r) {
            this._brRadius = Math.min(r, (this._height - this._strokeWidth)/2, (this._width - this._strokeWidth)/2);
        }
    },

    getStrokeStyle: {
        value: function() {
            return this._strokeStyle;
        }
    },

    setStrokeStyle: {
        value: function(s) {
            this._strokeStyle = s;
        }
    },

    getWidth: {
        value: function() {
            return this._width;
        }
    },

    setWidth: {
        value: function(w) {
            this._width = w;
        }
    },

    getHeight: {
        value: function() {
            return this._height;
        }
    },

    setHeight: {
        value: function(h) {
            this._height = h;
        }
    },

    geomType: {
        value: function() {
            return this.GEOM_TYPE_RECTANGLE;
        }
    },

	///////////////////////////////////////////////////////////////////////
	// Methods
	///////////////////////////////////////////////////////////////////////
	// JSON export
    exportJSON: {
        value: function() {
            var jObj =
            {
                'type'			: this.geomType(),
                'xoff'			: this._xOffset,
                'yoff'			: this._yOffset,
                'width'			: this._width,
                'height'		: this._height,
                'strokeWidth'	: this._strokeWidth,
                'strokeColor'	: this._strokeColor,
                'fillColor'		: this._fillColor,
                'tlRadius'		: this._tlRadius,
                'trRadius'		: this._trRadius,
                'blRadius'		: this._blRadius,
                'brRadius'		: this._brRadius,
                'innerRadius'	: this._innerRadius,
                'strokeStyle'	: this._strokeStyle,
                'strokeMat'		: this._strokeMaterial ? this._strokeMaterial.getName() :  MaterialsModel.getDefaultMaterialName(),
                'fillMat'		: this._fillMaterial ?  this._fillMaterial.getName() :  MaterialsModel.getDefaultMaterialName(),
                'materials'		: this.exportMaterialsJSON()
            };

            return jObj;
        }
    },

    importJSON: {
        value: function(jObj) {
            this._xOffset			= jObj.xoff;
            this._yOffset			= jObj.yoff;
            this._width				= jObj.width;
            this._height			= jObj.height;
            this._strokeWidth		= jObj.strokeWidth;
            this._strokeColor		= jObj.strokeColor;
            this._fillColor			= jObj.fillColor;
            this._tlRadius			= jObj.tlRadius;
            this._trRadius			= jObj.trRadius;
            this._blRadius			= jObj.blRadius;
            this._brRadius			= jObj.brRadius;
            this._innerRadius		= jObj.innerRadius;
            this._strokeStyle		= jObj.strokeStyle;
            var strokeMaterialName	= jObj.strokeMat;
            var fillMaterialName	= jObj.fillMat;

            var strokeMat = MaterialsModel.getMaterial( strokeMaterialName ).dup();
            if (!strokeMat) {
                console.log( "object material not found in library: " + strokeMaterialName );
                strokeMat = MaterialsModel.getMaterial(  MaterialsModel.getDefaultMaterialName() ).dup();
            }
            this._strokeMaterial = strokeMat;
			if (this._strokeMaterial.hasProperty( 'color' ))
				this._strokeMaterial.setProperty( 'color', this._strokeColor );

            var fillMat = MaterialsModel.getMaterial( fillMaterialName ).dup();
            if (!fillMat) {
                console.log( "object material not found in library: " + fillMaterialName );
                fillMat = MaterialsModel.getMaterial(  MaterialsModel.getDefaultMaterialName() ).dup();
            }
            this._fillMaterial = fillMat;
			if (this._fillMaterial.hasProperty( 'color' ))
				this._fillMaterial.setProperty( 'color', this._fillColor );

            this.importMaterialsJSON( jObj.materials );
        }
    },

    buildBuffers: {
        value: function() {
            // get the world
            var world = this.getWorld();
            if (!world)  throw( "null world in buildBuffers" );
            //console.log( "GLRectangle.buildBuffers " + world._worldCount );
            if (!world._useWebGL)  return;

            // make sure RDGE has the correct context
            RDGE.globals.engine.setContext( world.getCanvas().rdgeid );

            // create the gl buffer
            var gl = world.getGLContext();

            var tlRadius = this._tlRadius; //top-left radius
            var trRadius = this._trRadius;
            var blRadius = this._blRadius;
            var brRadius = this._brRadius;

            // declare the arrays to hold the parts
            this._primArray = [];
            this._materialArray = [];
            this._materialTypeArray = [];
            this._materialNodeArray = [];

            // get the normalized device coordinates (NDC) for
            // all position and dimensions.
            var vpw = world.getViewportWidth(),  vph = world.getViewportHeight();
            var xNDC = 2*this._xOffset/vpw,  yNDC = 2*this._yOffset/vph,
                xFillNDC = this._width/vpw,  yFillNDC = this._height/vph,
                strokeSizeNDC = 2*this._strokeWidth/vpw,
                tlRadiusNDC = 2*tlRadius/vpw,  yTLRadiusNDC = 2*tlRadius/vph,
                trRadiusNDC = 2*trRadius/vpw,  yTRRadiusNDC = 2*trRadius/vph,
                blRadiusNDC = 2*blRadius/vpw,  yBLRadiusNDC = 2*blRadius/vph,
                brRadiusNDC = 2*brRadius/vpw,  yBRRadiusNDC = 2*brRadius/vph;

            var aspect = world.getAspect();
            var zn = world.getZNear(),  zf = world.getZFar();
            var t = zn * Math.tan(world.getFOV() * Math.PI / 360.0),
                b = -t,
                r = aspect*t,
                l = -r;

            // calculate the object coordinates from their NDC coordinates
            var z = -world.getViewDistance();

            // get the position of the origin
            var x = -z*(r-l)/(2.0*zn)*xNDC,
                y = -z*(t-b)/(2.0*zn)*yNDC;

            // get the x and y fill
            var xFill = -z*(r-l)/(2.0*zn)*xFillNDC,
                yFill = -z*(t-b)/(2.0*zn)*yFillNDC;

            // keep some variables giving the overall dimensions of the
            // rectangle. These values are used to calculate consistent
            // texture map coordinates across all pieces.
            this._rectWidth = xFill;  this._rectHeight = yFill;

            // get the stroke size
            var strokeSize = -z*(r-l)/(2.0*zn)*strokeSizeNDC;

            // get the absolute corner radii
            tlRadius = -z*(r-l)/(2.0*zn)*tlRadiusNDC,
            trRadius = -z*(r-l)/(2.0*zn)*trRadiusNDC,
            blRadius = -z*(r-l)/(2.0*zn)*blRadiusNDC,
            brRadius = -z*(r-l)/(2.0*zn)*brRadiusNDC;

            // stroke
            var strokeMaterial = this.makeStrokeMaterial();
            var strokePrim = this.createStroke([x,y],  2*xFill,  2*yFill,  strokeSize,  tlRadius, blRadius, brRadius, trRadius, strokeMaterial);
		strokeMaterial.fitToPrimitive( strokePrim );
            this._primArray.push( strokePrim );
            this._materialNodeArray.push( strokeMaterial.getMaterialNode() );

            // fill
            tlRadius -= strokeSize;  if (tlRadius < 0)  tlRadius = 0.0;
            blRadius -= strokeSize;  if (blRadius < 0)  blRadius = 0.0;
            brRadius -= strokeSize;  if (brRadius < 0)  brRadius = 0.0;
            trRadius -= strokeSize;  if (trRadius < 0)  trRadius = 0.0;
            xFill -= strokeSize;
            yFill -= strokeSize;
            var fillMaterial = this.makeFillMaterial();
            //console.log( "fillMaterial: " + fillMaterial.getName() );
            var fillPrim = this.createFill([x,y],  2*xFill,  2*yFill,  tlRadius, blRadius, brRadius, trRadius, fillMaterial);
		fillMaterial.fitToPrimitive( fillPrim );
            this._primArray.push( fillPrim );
            this._materialNodeArray.push( fillMaterial.getMaterialNode() );

            world.updateObject(this);
        }
    },

    renderQuadraticBezier: {
        value: function(bPts, ctx) {
            if (!bPts)  return;

            var nSegs = (bPts.length - 1)/2.0;
            if (nSegs <= 0)  return;

            var index = 1;
            for (var i=0;  i<nSegs;  i++) {
                ctx.quadraticCurveTo(  bPts[index][0],  bPts[index][1],    bPts[index+1][0], bPts[index+1][1] );
                index += 2;
            }
	    }
    },

    renderPath: {
        value: function(inset, ctx) {
            // various declarations
            var pt,  rad,  ctr,  startPt, bPts;
            var width  = Math.round(this.getWidth()),
                height = Math.round(this.getHeight()),
                hw = 0.5*width,
                hh = 0.5*height;

            pt = [inset, inset];	// top left corner

            var tlRad = this._tlRadius; //top-left radius
            var trRad = this._trRadius;
            var blRad = this._blRadius;
            var brRad = this._brRadius;
            // limit the radii to half the rectangle dimension
            var minDimen = hw < hh ? hw : hh;
            if (tlRad > minDimen)  tlRad = minDimen;
            if (blRad > minDimen)  blRad = minDimen;
            if (brRad > minDimen)  brRad = minDimen;
            if (trRad > minDimen)  trRad = minDimen;

		var viewUtils = require("js/helper-classes/3D/view-utils").ViewUtils;
		var world = this.getWorld();
		viewUtils.pushViewportObj( world.getCanvas() );
		var cop = viewUtils.getCenterOfProjection();
		viewUtils.popViewportObj();
		var xCtr = cop[0] + this._xOffset,					yCtr = cop[1] - this._yOffset;
		var xLeft = xCtr - 0.5*this.getWidth(),				yTop = yCtr - 0.5*this.getHeight();
		var xDist = cop[0] - xLeft,							yDist = cop[1] - yTop;
		var xOff = 0.5*world.getViewportWidth() - xDist,	yOff  = 0.5*world.getViewportHeight() - yDist;

            if ((tlRad <= 0) && (blRad <= 0) && (brRad <= 0) && (trRad <= 0)) {
			ctx.rect(pt[0]+xOff, pt[1]+yOff, width - 2*inset, height - 2*inset);
            } else {
                // get the top left point
                rad = tlRad - inset;
                if (rad < 0)  rad = 0;
                pt[1] += rad;
                if (MathUtils.fpSign(rad) == 0)  pt[1] = inset;
			ctx.moveTo( pt[0]+xOff,  pt[1]+yOff );

                // get the bottom left point
                pt = [inset, height - inset];
                rad = blRad - inset;
                if (rad < 0)  rad = 0;
                pt[1] -= rad;
			ctx.lineTo( pt[0]+xOff,  pt[1]+yOff );

                // get the bottom left curve
                if (MathUtils.fpSign(rad) > 0) {
				ctx.quadraticCurveTo( inset+xOff, height-inset+yOff,  inset+rad+xOff, height-inset+yOff );
                }

                // do the bottom of the rectangle
                pt = [width - inset,  height - inset];
                rad = brRad - inset;
                if (rad < 0)  rad = 0;
                pt[0] -= rad;
			ctx.lineTo( pt[0]+xOff, pt[1]+yOff );

                // get the bottom right arc
                if (MathUtils.fpSign(rad) > 0) {
				ctx.quadraticCurveTo( width-inset+xOff, height-inset+yOff,  width-inset+xOff, height-inset-rad+yOff );
                }

                // get the right of the rectangle
                pt = [width - inset,  inset];
                rad = trRad - inset;
                if (rad < 0)  rad = 0;
                pt[1] += rad;
			ctx.lineTo( pt[0]+xOff, pt[1]+yOff );

                // do the top right corner
                if (MathUtils.fpSign(rad) > 0) {
				ctx.quadraticCurveTo( width-inset+xOff, inset+yOff,  width-inset-rad+xOff, inset+yOff );
                }

                // do the top of the rectangle
                pt = [inset, inset];
                rad = tlRad - inset;
                if (rad < 0)  rad = 0;
                pt[0] += rad;
			ctx.lineTo( pt[0]+xOff, pt[1]+yOff );

                // do the top left corner
                if (MathUtils.fpSign(rad) > 0) {
				ctx.quadraticCurveTo( inset+xOff, inset+yOff, inset+xOff, inset+rad+yOff );
                } else {
				ctx.lineTo( inset+xOff, 2*inset+yOff );
                }
            }
        }
    },

	render: {
		value: function() {
			// get the world
			var world = this.getWorld();
			if (!world)  throw( "null world in rectangle render" );

			 // get the context
			var ctx = world.get2DContext();
			if (!ctx)  return;

			// get some dimensions
			var lw = this._strokeWidth;
			var	w = world.getViewportWidth(),
				h = world.getViewportHeight();

			var c,
				inset,
				gradient,
				colors,
				len,
				n,
				position,
				cs;
			// render the fill
			ctx.beginPath();
			if (this._fillColor) {
				inset = Math.ceil( lw ) - 0.5;

				if(this._fillColor.gradientMode) {
					if(this._fillColor.gradientMode === "radial") {
						var ww = w - 2*lw,  hh = h - 2*lw;
						gradient = ctx.createRadialGradient(w/2, h/2, 0, w/2, h/2, Math.max(ww, hh)/2);
					} else {
						gradient = ctx.createLinearGradient(inset, h/2, w-inset, h/2);
					}
					colors = this._fillColor.color;

					len = colors.length;

					for(n=0; n<len; n++) {
						position = colors[n].position/100;
						cs = colors[n].value;
						gradient.addColorStop(position, "rgba(" + cs.r + "," + cs.g + "," + cs.b + "," + cs.a + ")");
					}

					ctx.fillStyle = gradient;

				} else {
					c = "rgba(" + 255*this._fillColor[0] + "," + 255*this._fillColor[1] + "," + 255*this._fillColor[2] + "," + this._fillColor[3] + ")";
					ctx.fillStyle = c;
				}

				ctx.lineWidth	= lw;
				this.renderPath( inset, ctx );
				ctx.fill();
				ctx.closePath();
			}

			// render the stroke
			ctx.beginPath();
			if (this._strokeColor) {
				inset = Math.ceil( 0.5*lw ) - 0.5;

				if(this._strokeColor.gradientMode) {
					if(this._strokeColor.gradientMode === "radial")
						gradient = ctx.createRadialGradient(w/2, h/2, 0,  w/2, h/2, Math.max(h, w)/2);
					else
						gradient = ctx.createLinearGradient(0, h/2, w, h/2);
					colors = this._strokeColor.color;

					len = colors.length;

					for(n=0; n<len; n++) {
						position = colors[n].position/100;
						cs = colors[n].value;
						gradient.addColorStop(position, "rgba(" + cs.r + "," + cs.g + "," + cs.b + "," + cs.a + ")");
					}

					ctx.strokeStyle = gradient;

				} else {
					c = "rgba(" + 255*this._strokeColor[0] + "," + 255*this._strokeColor[1] + "," + 255*this._strokeColor[2] + "," + this._strokeColor[3] + ")";
					ctx.strokeStyle = c;
				}

				ctx.lineWidth	= lw;
				this.renderPath( inset, ctx );
				ctx.stroke();
				ctx.closePath();
			}
		}
	},

    createStroke: {
        value: function(ctr,  width,  height,  strokeWidth,  tlRad, blRad, brRad, trRad, material) {
            // create the geometry
            return RectangleStroke.create( ctr,  width, height, strokeWidth,  tlRad, blRad,  brRad, trRad, material);
	    }
    },

    createFill: {
        value: function(ctr,  width,  height,  tlRad, blRad, brRad, trRad, material) {
            // create the geometry
            // special the (common) case of no rounded corners
            var prim;

            if ((tlRad <= 0) && (blRad <= 0) && (brRad <= 0) && (trRad <= 0)) {
                prim = RectangleGeometry.create( ctr, width, height, material );
            } else {
                prim = RectangleFill.create( ctr,  width, height,  tlRad, blRad,  brRad, trRad, material);
            }

            return prim;
        }
    },

    collidesWithPoint: {
        value: function(x, y) {
            if(x < this._xOffset) return false;
            if(x > (this._xOffset + this._width)) return false;
            if(y < this._yOffset) return false;
            if(y > (this._yOffset + this._height)) return false;

            return true;
	    }
    },

    containsPoint: {
        value: function(pt, dir) {
            var world = this.getWorld();
            if (!world)  throw( "null world in containsPoint" );

            // get a point on the plane of the circle
            // the point is in NDC, as is the input parameters
            var mat = this.getMatrix();
            var plane = [0,0,1,0];
            plane = MathUtils.transformPlane( plane, mat );
            var projPt = MathUtils.vecIntersectPlane ( pt, dir, plane );

            // transform the projected point back to the XY plane
            //var invMat = mat.inverse();
            var invMat = glmat4.inverse( mat, [] );
            var planePt = MathUtils.transformPoint( projPt, invMat );

            // get the normalized device coordinates (NDC) for
            // the position and radii.
            var	vpw = world.getViewportWidth(),  vph = world.getViewportHeight();
            var	xNDC = 2*this._xOffset/vpw,  yNDC = 2*this._yOffset/vph,
                xRadNDC = this._width/vpw,  yRadNDC = this._height/vph;
            var projMat = world.makePerspectiveMatrix();
            var z = -world.getViewDistance();
            var planePtNDC = planePt.slice(0);
            planePtNDC[2] = z;
            planePtNDC = MathUtils.transformHomogeneousPoint( planePtNDC, projMat );
            planePtNDC = MathUtils.applyHomogeneousCoordinate( planePtNDC );

            // get the center and dimensions of the rect in NDC
            var vpw = world.getViewportWidth(),  vph = world.getViewportHeight();
            var xNDC = 2*this._xOffset/vpw,  yNDC = 2*this._yOffset/vph,
                hw = this._width/vpw,  hh = this._height/vph;

            var x = planePtNDC[0],  y = planePtNDC[1];
            if (x < (xNDC - hw))  return false;
            if (x > (xNDC + hw))  return false;
            if (y < (yNDC - hh))  return false;
            if (y > (yNDC + hh))  return false;

            return true;
        }
    },

    getNearVertex: {
        value: function(pt, dir) {
            var world = this.getWorld();
            if (!world)  throw( "null world in getNearPoint" );

            // get a point on the plane of the circle
            // the point is in NDC, as is the input parameters
            var mat = this.getMatrix();
            var plane = [0,0,1,0];
            plane = MathUtils.transformPlane( plane, mat );
            var projPt = MathUtils.vecIntersectPlane ( pt, dir, plane );

            // transform the projected point back to the XY plane
            //var invMat = mat.inverse();
            var invMat = glmat4.inverse(mat, []);
            var planePt = MathUtils.transformPoint( projPt, invMat );

            // get the normalized device coordinates (NDC) for
            // the position and radii.
            var	vpw = world.getViewportWidth(),  vph = world.getViewportHeight();
            var	xNDC = 2*this._xOffset/vpw,  yNDC = 2*this._yOffset/vph,
                hwNDC = this._width/vpw,  hhNDC = this._height/vph;
            var projMat = world.makePerspectiveMatrix();
            var z = -world.getViewDistance();
            var planePtNDC = planePt.slice(0);
            planePtNDC[2] = z;
            planePtNDC = MathUtils.transformHomogeneousPoint( planePtNDC, projMat );
            planePtNDC = MathUtils.applyHomogeneousCoordinate( planePtNDC );

            // get the near point in NDC
            var x = planePtNDC[0],  y = planePtNDC[1];
            var xMin = xNDC - hwNDC,  xMax = xNDC + hwNDC,
                yMin = yNDC - hhNDC,  yMax = yNDC + hhNDC;

            // compare the point against the 4 corners
                var pt, dist;
                pt = [xMin, yMin, 0];
                dist = VecUtils.vecDist(2, pt, planePtNDC);
                var minPt = pt,  minDist = dist;

                pt = [xMin, yMax, 0];
                dist = VecUtils.vecDist(2, pt, planePtNDC);
                if (dist < minDist) {
                    minDist = dist;
                    minPt = pt;
                }

                pt = [xMax, yMax, 0];
                dist = VecUtils.vecDist(2, pt, planePtNDC);
                if (dist < minDist) {
                    minDist = dist;
                    minPt = pt;
                }

                pt = [xMax, yMin, 0];
                dist = VecUtils.vecDist(2, pt, planePtNDC);
                if (dist < minDist) {
                    minDist = dist;
                    minPt = pt;
                }

            // convert to GL coordinates
            x = minPt[0];  y = minPt[1];
            var aspect = world.getAspect();
            var zn = world.getZNear(),  zf = world.getZFar();
            var	t = zn * Math.tan(world.getFOV() * Math.PI / 360.0),
                b = -t,
                r = aspect*t,
                l = -r;
            var objPt = [0,0,0];
            objPt[0] = -z*(r-l)/(2.0*zn)*x;
            objPt[1] = -z*(t-b)/(2.0*zn)*y;

            // re-apply the transform
            objPt = MathUtils.transformPoint( objPt, mat );

            return objPt;
        }
    },

    getNearPoint: {
        value: function(pt, dir) {
            var world = this.getWorld();
            if (!world)  throw( "null world in getNearPoint" );

            // get a point on the plane of the circle
            // the point is in NDC, as is the input parameters
            var mat = this.getMatrix();
            var plane = [0,0,1,0];
            plane = MathUtils.transformPlane( plane, mat );
            var projPt = MathUtils.vecIntersectPlane ( pt, dir, plane );

            // transform the projected point back to the XY plane
            var invMat = glmat4.inverse(mat, []);
            var planePt = MathUtils.transformPoint( projPt, invMat );

            // get the normalized device coordinates (NDC) for
            // the position and radii.
            var	vpw = world.getViewportWidth(),  vph = world.getViewportHeight();
            var	xNDC = 2*this._xOffset/vpw,  yNDC = 2*this._yOffset/vph,
                hwNDC = this._width/vpw,  hhNDC = this._height/vph;
            var projMat = world.makePerspectiveMatrix();
            var z = -world.getViewDistance();
            var planePtNDC = planePt.slice(0);
            planePtNDC[2] = z;
            planePtNDC = MathUtils.transformHomogeneousPoint( planePtNDC, projMat );
            planePtNDC = MathUtils.applyHomogeneousCoordinate( planePtNDC );

            // get the near point in NDC
            var x = planePtNDC[0],  y = planePtNDC[1];
            var xMin = xNDC - hwNDC,  xMax = xNDC + hwNDC,
                yMin = yNDC - hhNDC,  yMax = yNDC + hhNDC;

            // compare the point against the near point on the 4 sides
            var pt, dist;
            pt = [xMin, y, 0];
            if      (pt[1] < yMin)  pt[1] = yMin;
            else if (pt[1] > yMax)  pt[1] = yMax;
            dist = VecUtils.vecDist(2, pt, planePtNDC);
            var minPt = pt,  minDist = dist;

            pt = [x, yMax, 0];
            if      (pt[0] < xMin)  pt[0] = xMin;
            else if (pt[0] > xMax)  pt[0] = xMax;
            dist = VecUtils.vecDist(2, pt, planePtNDC);
            if (dist < minDist) {
                minDist = dist;
                minPt = pt;
            }

            pt = [xMax, y, 0];
            if      (pt[1] < yMin)  pt[1] = yMin;
            else if (pt[1] > yMax)  pt[1] = yMax;
            dist = VecUtils.vecDist(2, pt, planePtNDC);
            if (dist < minDist) {
                minDist = dist;
                minPt = pt;
            }

            pt = [x, yMin, 0];
            if      (pt[0] < xMin)  pt[0] = xMin;
            else if (pt[0] > xMax)  pt[0] = xMax;
            dist = VecUtils.vecDist(2, pt, planePtNDC);
            if (dist < minDist) {
                minDist = dist;
                minPt = pt;
            }

            // convert to GL coordinates
            x = minPt[0];  y = minPt[1];
            var aspect = world.getAspect();
            var zn = world.getZNear(),  zf = world.getZFar();
            var	t = zn * Math.tan(world.getFOV() * Math.PI / 360.0),
                b = -t,
                r = aspect*t,
                l = -r;
            var objPt = [0,0,0];
            objPt[0] = -z*(r-l)/(2.0*zn)*x;
            objPt[1] = -z*(t-b)/(2.0*zn)*y;

            // re-apply the transform
            objPt = MathUtils.transformPoint( objPt, mat );

            return objPt;
        }
    },

    recalcTexMapCoords: {
        value: function(vrts, uvs) {
            var n = vrts.length/3;
            var ivrt = 0,  iuv = 0;

            for (var i=0;  i<n;  i++) {
                uvs[iuv] = 0.5*(vrts[ivrt]/this._rectWidth + 1);
                iuv++;  ivrt++;
                uvs[iuv] = 0.5*(vrts[ivrt]/this._rectHeight + 1);
                iuv++;  ivrt += 2;
            }
        }
    }
});

var RectangleFill = {};
RectangleFill.create = function( rectCtr,  width, height, tlRad, blRad,  brRad, trRad,  material) {
	var x = rectCtr[0],  y = rectCtr[1],  z = 0.0;
	var	hw = 0.5*width,  hh = 0.5*height;

	// limit the radii to half the rectangle dimension
	var minDimen = hw < hh ? hw : hh;
	if (tlRad > minDimen)  tlRad = minDimen;
	if (blRad > minDimen)  blRad = minDimen;
	if (brRad > minDimen)  brRad = minDimen;
	if (trRad > minDimen)  trRad = minDimen;

	// define some local variables
	this.vertices	= [];
	this.normals	= [];
	this.uvs		= [];
	this.indices	= [];

	// the center of the rectangle is the first vertex
	RectangleFill.pushVertex( x, y, z );

	// traverse the perimiter of the rectangle

	// push the starting point
	RectangleFill.pushVertex( x-hw, y+hh-tlRad,  z);

	// do the left side
	var ctr;
	if (blRad <= 0){
		RectangleFill.pushVertex( x-hw, y-hh, z);
    } else {
		ctr = [x - hw + blRad,  y - hh + blRad, z];
		RectangleFill.getRoundedCorner( ctr,  [x-hw, y-hh+blRad, z],  this.vertices );
	}

	// do the bottom
	if (brRad <= 0) {
		RectangleFill.pushVertex( x+hw, y-hh, z);
    } else {
		ctr = [x + hw - brRad,  y - hh + brRad, z];
		RectangleFill.getRoundedCorner( ctr,  [x+hw-brRad, y-hh, z],  this.vertices );
	}

	// do the right
	if (trRad <= 0) {
		RectangleFill.pushVertex( x+hw, y+hh, z);
    } else {
		ctr = [x + hw - trRad,  y + hh - trRad, z];
		RectangleFill.getRoundedCorner( ctr,  [x+hw, y+hh-trRad, z],  this.vertices );
	}

	// do the top
	if (tlRad <= 0) {
		RectangleFill.pushVertex( x-hw, y+hh, z);
    } else {
		ctr = [x - hw + tlRad,  y + hh - tlRad, z];
		RectangleFill.getRoundedCorner( ctr,  [x-hw+tlRad, y+hh, z],  this.vertices );
	}

	// get the normals and uvs
	var vrt, uv;
	var xMin = x - hw,
		yMin = y - hh;
	var n = [0, 0, 1];
	var nVertices = this.vertices.length / 3;
	for (var i=0;  i<nVertices;  i++) {
		vrt = RectangleFill.getVertex(i);
		RectangleFill.pushNormal( n );
		uv  = RectangleFill.getUV(vrt[0], vrt[1], xMin, width, yMin, height);
		RectangleFill.pushUV( uv );
	}

	// build the triangles
	var nTriangles = nVertices - 2;
	var i = 1,  j = 2;
	for (var iTri=0;  iTri<nTriangles;  iTri++) {
		RectangleFill.pushIndices( 0, j, i );
		i++;
		j++;
	}

	//refine the mesh for vertex deformations
	if (material) {
		if (material.hasVertexDeformation()) {
			var paramRange = material.getVertexDeformationRange();
			var tolerance = material.getVertexDeformationTolerance();
			nVertices = ShapePrimitive.refineMesh( this.vertices, this.normals, this.uvs, this.indices, nVertices,  paramRange,  tolerance );
		}
	}

	// create the RDGE primitive
	return ShapePrimitive.create(this.vertices, this.normals, this.uvs, this.indices, RDGE.globals.engine.getContext().renderer.TRIANGLES, nVertices);
};

RectangleFill.pushVertex = function( x, y, z ) {
	this.vertices.push( x );
	this.vertices.push( y );
	this.vertices.push( z );
};

RectangleFill.pushNormal = function( n ) {
	this.normals.push( n[0] );
	this.normals.push( n[1] );
	this.normals.push( n[2] );
};

RectangleFill.pushUV = function( uv ) {
	this.uvs.push( uv[0] );
	this.uvs.push( uv[1] );
};

RectangleFill.pushIndices = function( i, j, k ) {
	this.indices.push( i );
	this.indices.push( j );
	this.indices.push( k );
};

RectangleFill.getVertex = function( index ) {
	var i = 3*index;
	return [ this.vertices[i],  this.vertices[i+1],  this.vertices[i+2] ];
};

RectangleFill.getUV = function( x, y, xMin, w, yMin, h) {
	var u = (x - xMin)/w,
		v = (y - yMin)/h;

	var uv = [ u, v ];
	return uv;
};

RectangleFill.getRoundedCorner = function(ctr, startPt,  vertices) {
	var pt0 = startPt.slice();

	// create a matrix to rotate about the center
	var nSegs = 16;
	var angle = 0.5*Math.PI/nSegs;
	var ctrNeg = ctr.slice();
	VecUtils.vecNegate(3, ctrNeg);
	var tNeg  = Matrix.Translation( ctrNeg ),
		rot   = Matrix.RotationZ( angle ),
		trans = Matrix.Translation( ctr );
	var mat = glmat4.multiply( rot, tNeg, [] );
	glmat4.multiply(trans, mat, mat );

	RectangleFill.pushVertex(pt0[0], pt0[1], 0.0 );
	for (var i=0;  i<nSegs;  i++) {
		pt0 = MathUtils.transformPoint( pt0, mat );
		RectangleFill.pushVertex(pt0[0], pt0[1], 0.0 );
	}
};


var RectangleStroke = {};
RectangleStroke.create = function( rectCtr,  width, height, strokeWidth,  tlRad, blRad,  brRad, trRad, material) {
	var x = rectCtr[0],  y = rectCtr[1],  z = 0.0;
	var	hw = 0.5*width,  hh = 0.5*height,  sw = strokeWidth;

	// limit the radii to half the rectangle dimension
	var minDimen = hw < hh ? hw : hh;
	if (tlRad > minDimen)  tlRad = minDimen;
	if (blRad > minDimen)  blRad = minDimen;
	if (brRad > minDimen)  brRad = minDimen;
	if (trRad > minDimen)  trRad = minDimen;

	// define some local variables
	this.vertices	= [];
	this.normals	= [];
	this.uvs		= [];
	this.indices	= [];

	// get the starting points
	if (tlRad == 0) {
		RectangleStroke.pushVertex( x-hw+sw, y+hh-sw, z);
		RectangleStroke.pushVertex( x-hw,    y+hh,    z);
	} else {
		if (tlRad > sw) {
			RectangleStroke.pushVertex( x-hw+sw, y+hh-tlRad, z);
			RectangleStroke.pushVertex( x-hw,    y+hh-tlRad, z);
		} else {
			RectangleStroke.pushVertex( x-hw+tlRad, y+hh-tlRad, z);
			RectangleStroke.pushVertex( x-hw,       y+hh-tlRad, z);
			RectangleStroke.pushVertex( x-hw+sw,    y+hh-sw,    z);
			RectangleStroke.pushVertex( x-hw,       y+hh-sw,    z);
		}
	}

	// get the left side
	if (blRad == 0) {
		RectangleStroke.pushVertex( x-hw+sw, y-hh+sw, z);
		RectangleStroke.pushVertex( x-hw,    y-hh,    z);
	} else {
		if (blRad >= sw) {
			RectangleStroke.pushVertex( x-hw+sw, y-hh+blRad, z);
			RectangleStroke.pushVertex( x-hw,    y-hh+blRad, z);
			var ctr		 =	[x-hw+blRad,  y-hh+blRad, z],
				insidePt =	[x-hw+sw,     y-hh+blRad, z],
				outsidePt = [x-hw,        y-hh+blRad, z];
			RectangleStroke.getRoundedCorner( ctr, insidePt,  outsidePt,  this.vertices );
		} else {
			RectangleStroke.pushVertex( x-hw+sw,  y-hh+sw,    z);
			RectangleStroke.pushVertex( x-hw,     y-hh+blRad, z);
			var ctr		 =	[x-hw+blRad,  y-hh+blRad, z],
				insidePt =	[x-hw+blRad,  y-hh+blRad, z],
				outsidePt = [x-hw,        y-hh+blRad, z];
			RectangleStroke.getRoundedCorner( ctr, insidePt, outsidePt, this.vertices  );

			RectangleStroke.pushVertex( x-hw+sw,  y-hh+sw, z);
			RectangleStroke.pushVertex( x-hw+sw,  y-hh,    z);
		}
	}

	// get the bottom
	if (brRad == 0) {
		RectangleStroke.pushVertex( x+hw-sw, y-hh+sw, z);
		RectangleStroke.pushVertex( x+hw,    y-hh,    z);
	} else {
		RectangleStroke.pushVertex( x+hw-brRad,    y-hh+sw, z);
		RectangleStroke.pushVertex( x+hw-brRad,    y-hh,    z);
		if (brRad >= sw) {
			var ctr		 =	[x+hw-brRad,  y-hh+brRad, z],
				insidePt =	[x+hw-brRad,  y-hh+sw,    z],
				outsidePt = [x+hw-brRad,  y-hh,       z];
			RectangleStroke.getRoundedCorner( ctr, insidePt,  outsidePt,  this.vertices );
		} else {
			RectangleStroke.pushVertex( x+hw-sw,    y-hh+sw, z);
			RectangleStroke.pushVertex( x+hw-brRad,    y-hh, z);
			var ctr		 =	[x+hw-brRad,  y-hh+brRad, z],
				insidePt =	[x+hw-brRad,  y-hh+brRad, z],
				outsidePt = [x+hw-brRad,  y-hh,       z];
			RectangleStroke.getRoundedCorner( ctr, insidePt, outsidePt,  this.vertices );
			RectangleStroke.pushVertex( x+hw-sw,    y-hh+sw, z);
			RectangleStroke.pushVertex( x+hw,       y-hh+sw, z);
		}
	}

	// get the right
	if (trRad == 0) {
		RectangleStroke.pushVertex( x+hw-sw, y+hh-sw, z);
		RectangleStroke.pushVertex(    x+hw,    y+hh, z);
	} else {
		if (trRad >= sw) {
			RectangleStroke.pushVertex( x+hw-sw,  y+hh-trRad, z);
			RectangleStroke.pushVertex( x+hw,     y+hh-trRad, z);
			var ctr		 =	[x+hw-trRad,  y+hh-trRad, z],
				insidePt =	[x+hw-sw,     y+hh-trRad, z],
				outsidePt = [x+hw,        y+hh-trRad, z];
			RectangleStroke.getRoundedCorner( ctr, insidePt,  outsidePt,  this.vertices );
		} else {
			RectangleStroke.pushVertex( x+hw-sw,  y+hh-sw,    z);
			RectangleStroke.pushVertex( x+hw,     y+hh-trRad, z);
			var ctr		 =	[x+hw-trRad,  y+hh-trRad, z],
				insidePt =	[x+hw-trRad,  y+hh-trRad, z],
				outsidePt = [x+hw,        y+hh-trRad, z];
			RectangleStroke.getRoundedCorner( ctr, insidePt, outsidePt,  this.vertices );
			RectangleStroke.pushVertex( x+hw-sw,  y+hh-sw, z);
			RectangleStroke.pushVertex( x+hw-sw,  y+hh,    z);
		}
	}

	// get the top
	if (tlRad == 0) {
		RectangleStroke.pushVertex( x-hw+sw,  y+hh-sw, z);
		RectangleStroke.pushVertex( x-hw,     y+hh,    z);
	} else {
		if (tlRad >= sw) {
			RectangleStroke.pushVertex( x-hw+tlRad,  y+hh-sw, z);
			RectangleStroke.pushVertex( x-hw+tlRad,  y+hh,    z);
			var ctr		 =	[x-hw+tlRad,  y+hh-tlRad, z],
				insidePt =	[x-hw+tlRad,  y+hh-sw, z],
				outsidePt = [x-hw+tlRad,  y+hh, z];
			RectangleStroke.getRoundedCorner( ctr, insidePt,  outsidePt,  this.vertices );
		} else {
			RectangleStroke.pushVertex( x-hw+sw,     y+hh-sw, z);
			RectangleStroke.pushVertex( x-hw+tlRad,  y+hh,    z);
			var ctr		 =	[x-hw+tlRad,  y+hh-tlRad, z],
				insidePt =	[x-hw+tlRad,  y+hh-tlRad, z],
				outsidePt = [x-hw+tlRad,  y+hh, z];
			RectangleStroke.getRoundedCorner( ctr, insidePt, outsidePt,  this.vertices );
		}
	}

	// get the normals and uvs
	var vrt, uv;
	var xMin = x - hw,
		yMin = y - hh;
	var n = [0, 0, 1];
	var nVertices = this.vertices.length / 3;
	for (var i=0;  i<nVertices;  i++) {
		vrt = RectangleStroke.getVertex(i);
		RectangleStroke.pushNormal( n );
		uv  = RectangleStroke.getUV(vrt[0], vrt[1], xMin, width, yMin, height);
		RectangleStroke.pushUV( uv );
	}

	// build the triangles
	var nTriangles = nVertices - 2;
	var i = 0,  j = 1, k = 2;
	var reverse = false;
	for (var iTri=0;  iTri<nTriangles;  iTri++) {
		// we created a triangle strip, so each sequential triangle has the opposite orientation than its predecessor
		if (!reverse) {
			RectangleStroke.pushIndices( k, j, i );
        } else {
			RectangleStroke.pushIndices( i, j, k );
        }

		reverse = !reverse;

		i++;
		j++;
		k++;
	}

	//refine the mesh for vertex deformations
	if (material)
	{
		if (material.hasVertexDeformation())
		{
			var paramRange = material.getVertexDeformationRange();
			var tolerance = material.getVertexDeformationTolerance();
			nVertices = ShapePrimitive.refineMesh( this.vertices, this.normals, this.uvs, this.indices, nVertices,  paramRange,  tolerance );
		}
	}

	// create the RDGE primitive
	return ShapePrimitive.create(this.vertices, this.normals, this.uvs, this.indices, RDGE.globals.engine.getContext().renderer.TRIANGLES, nVertices);
};

RectangleStroke.getRoundedCorner = function( ctr, insidePt, outsidePt ) {
	var pt0 = insidePt.slice(),
		pt1 = outsidePt.slice();

	// create a matrix to rotate about the center
	var nSegs = 16;
	var angle = 0.5*Math.PI/nSegs;
	var ctrNeg = ctr.slice();
	VecUtils.vecNegate(3, ctrNeg);
	var tNeg  = Matrix.Translation( ctrNeg ),
		rot   = Matrix.RotationZ( angle ),
		trans = Matrix.Translation( ctr );
	var mat = glmat4.multiply( rot, tNeg, [] );
	glmat4.multiply(trans, mat, mat );

	RectangleStroke.pushVertex(pt0[0], pt0[1], 0.0 );
	RectangleStroke.pushVertex(pt1[0], pt1[1], 0.0 );
	for (var i=0;  i<nSegs;  i++) {
		pt0 = MathUtils.transformPoint( pt0, mat );
		pt1 = MathUtils.transformPoint( pt1, mat );

		RectangleStroke.pushVertex(pt0[0], pt0[1], 0.0 );
		RectangleStroke.pushVertex(pt1[0], pt1[1], 0.0 );
	}
};

RectangleStroke.pushVertex	= RectangleFill.pushVertex;
RectangleStroke.pushNormal	= RectangleFill.pushNormal;
RectangleStroke.pushUV		= RectangleFill.pushUV;
RectangleStroke.pushIndices	= RectangleFill.pushIndices;
RectangleStroke.getVertex	= RectangleFill.getVertex;
RectangleStroke.getUV		= RectangleFill.getUV;

var RectangleGeometry = {};
RectangleGeometry.create = function( ctr,  width, height, material ) {
	var x = ctr[0],  y = ctr[1],  z = 0.0;
	var	hw = 0.5*width,  hh = 0.5*height;

	// define some local variables
	this.vertices	= [];
	this.normals	= [];
	this.uvs		= [];
	this.indices	= [];

	// create the 4 vertices
	var nVertices = 4;
	RectangleGeometry.pushVertex( x-hw, y+hh, z);
	RectangleGeometry.pushVertex( x-hw, y-hh, z);
	RectangleGeometry.pushVertex( x+hw, y-hh, z);
	RectangleGeometry.pushVertex( x+hw, y+hh, z);

	// create the uv values for each vertex
	RectangleGeometry.pushUV( [0, 0] );
	RectangleGeometry.pushUV( [0, 1] );
	RectangleGeometry.pushUV( [1, 1] );
	RectangleGeometry.pushUV( [1, 0] );


	// create the per-vertex normals
	var n = [0, 0, 1];
	RectangleGeometry.pushNormal( n );
	RectangleGeometry.pushNormal( n );
	RectangleGeometry.pushNormal( n );
	RectangleGeometry.pushNormal( n );

	// create the 2 triangles
//	RectangleGeometry.pushIndices( 0, 1, 2 );
//	RectangleGeometry.pushIndices( 2, 3, 0 );
	RectangleGeometry.pushIndices( 2, 1, 0 );
	RectangleGeometry.pushIndices( 0, 3, 2 );

	//refine the mesh for vertex deformations
	if (material)
	{
		if (material.hasVertexDeformation())
		{
			var paramRange = material.getVertexDeformationRange();
			var tolerance = material.getVertexDeformationTolerance();
			nVertices = ShapePrimitive.refineMesh( this.vertices, this.normals, this.uvs, this.indices, nVertices,  paramRange,  tolerance );
		}
	}

	// create the RDGE primitive
	return ShapePrimitive.create(this.vertices, this.normals, this.uvs, this.indices, RDGE.globals.engine.getContext().renderer.TRIANGLES, nVertices);
};

RectangleGeometry.pushVertex	= RectangleFill.pushVertex;
RectangleGeometry.pushNormal	= RectangleFill.pushNormal;
RectangleGeometry.pushUV		= RectangleFill.pushUV;
RectangleGeometry.pushIndices	= RectangleFill.pushIndices;
RectangleGeometry.getVertex		= RectangleFill.getVertex;
RectangleGeometry.getUV			= RectangleFill.getUV;

RectangleGeometry.init = function()
{
	this.vertices	= [];
	this.normals	= [];
	this.uvs		= [];
	this.indices	= [];
}

RectangleGeometry.addQuad = function( verts,  normals, uvs )
{
	var offset = this.vertices.length/3;
	for (var i=0;  i<4;  i++)
	{
		RectangleGeometry.pushVertex( verts[i][0], verts[i][1], verts[i][2]);
		RectangleGeometry.pushNormal( normals[i] );
		RectangleGeometry.pushUV( uvs[i] );
	}

	RectangleGeometry.pushIndices( 0+offset, 1+offset, 2+offset );
	RectangleGeometry.pushIndices( 2+offset, 3+offset, 0+offset );
}

RectangleGeometry.buildPrimitive = function()
{
	var nVertices = this.vertices.length/3;
	return ShapePrimitive.create(this.vertices, this.normals, this.uvs, this.indices, RDGE.globals.engine.getContext().renderer.TRIANGLES, nVertices);
}



	exports.RectangleGeometry = RectangleGeometry;