/* <copyright>
This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
(c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
</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;
var drawUtils		= require("js/helper-classes/3D/draw-utils").DrawUtils;
var vecUtils		= require("js/helper-classes/3D/vec-utils").VecUtils;

///////////////////////////////////////////////////////////////////////
// Class GLCircle
//      GL representation of a circle.
//      Derived from class GLGeomObj
//		The position and dimensions of the stroke, fill, and inner Radius should be in pixels
///////////////////////////////////////////////////////////////////////
exports.Circle = Object.create(GeomObj, {

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

    _radius: { value : 2.0, writable: true },
    _strokeWidth: { value : 0.25, writable: true },
    _innerRadius: { value : 0, writable: true },
    _ovalHeight: { value : 4.0, writable: true },
    _strokeStyle: { value : "Solid", writable: true },
    _aspectRatio: { value : 1.0, writable: true },

	init: {
        value: function(world, xOffset, yOffset, width, height, strokeSize, strokeColor, fillColor, innerRadius, strokeMaterial, fillMaterial, strokeStyle) {
            if(arguments.length > 0) {
                this._width = width;
                this._height = height;
                this._xOffset = xOffset;
                this._yOffset = yOffset;
                this._ovalHeight = 2.0 * this._radius;

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

                this._strokeStyle = strokeStyle;

                this._matrix = Matrix.I(4);
                //this._matrix[12] = xOffset;
                //this._matrix[13] = yOffset;
            }

            this.m_world = world;

            if(strokeMaterial) {
                this._strokeMaterial = strokeMaterial.dup();
            } else {
                this._strokeMaterial = MaterialsModel.getMaterial( MaterialsModel.getDefaultMaterialName() ).dup();
            }
			if (strokeColor && this._strokeMaterial.hasProperty( "color" ))  this._strokeMaterial.setProperty( "color",  this._strokeColor );

            if(fillMaterial) {
                this._fillMaterial = fillMaterial.dup();
            } else {
                this._fillMaterial = MaterialsModel.getMaterial(  MaterialsModel.getDefaultMaterialName() ).dup();
            }
			if (fillColor && this._fillMaterial.hasProperty( "color" ))  this._fillMaterial.setProperty( "color",  this._fillColor );
        }
    },

    ///////////////////////////////////////////////////////////////////////
    // 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;
        }
    },

    getRadius: {
        value: function() {
            return this._radius;
        }
    },

    setRadius: {
        value: function(r) {
            this._radius = r;
        }
    },

    getInnerRadius: {
        value: function() {
            return this._innerRadius;
        }
    },

    setInnerRadius: {
        value: function(r) {
            this._innerRadius = r;
        }
    },

    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_CIRCLE;
        }
    },

    ///////////////////////////////////////////////////////////////////////
	// 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;
//        }
//    },
    ///////////////////////////////////////////////////////////////////////

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

            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();

            // determine the number of triangles to generate
            var nTriangles = 60;        // yes, we will do better than this

            // 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,
                xRadNDC = this._width/vpw,  yRadNDC = this._height/vph,
                xStrokeNDC = 2*this._strokeWidth/vpw,  yStrokeNDC = 2*this._strokeWidth/vph,
                xInnRadNDC = this._innerRadius*xRadNDC,  yInnRadNDC = this._innerRadius*yRadNDC;

            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 radii
            var xRad = -z*(r-l)/(2.0*zn)*xRadNDC,
                yRad = -z*(t-b)/(2.0*zn)*yRadNDC;

            // save the overall dimensions to be used in the uv calculations
            this._ovalWidth = xRad;  this._ovalHeight = yRad;

            // get the x & y stroke size
            var xStroke = -z*(r-l)/(2.0*zn)*xStrokeNDC,
                yStroke = -z*(t-b)/(2.0*zn)*yStrokeNDC;

            // get the inner radius
            var xInnRad = -z*(r-l)/(2.0*zn)*xInnRadNDC,
                yInnRad = -z*(t-b)/(2.0*zn)*yInnRadNDC;

            // get a matrix to rotate a point around the circle
            var angle = 2.0 * Math.PI/Number(nTriangles);
            var mat = Matrix.RotationZ( angle );
            var reverseRotMat = Matrix.RotationZ( -angle );

            // calculate matrices to scale the circle and stroke to fit the bounds of the ellipse
            var strokeScaleMat = Matrix.I(4);
            strokeScaleMat[0] = xRad;
            strokeScaleMat[5] = yRad;

            var fillScaleMat = Matrix.I(4);
            fillScaleMat[0] = xRad - xStroke;
            fillScaleMat[5] = yRad - yStroke;

            var innerRadiusScaleMat = Matrix.I(4);
            innerRadiusScaleMat[0] = xInnRad;
            innerRadiusScaleMat[5] = yInnRad;

            var innerStrokeScaleMat = Matrix.I(4);
            innerStrokeScaleMat[0] = xInnRad - xStroke;
            innerStrokeScaleMat[5] = yInnRad - yStroke;

            var fillPrim,  strokePrim0,  strokePrim1;
            var fillMaterial,  strokeMaterial0,  strokeMaterial2;

            this._primArray = [];
            this._materialArray = [];
            this._materialTypeArray = [];
            this._materialNodeArray = [];

            /////////////////////////////////////////////////////////////
            // Strokes
            if(this._strokeWidth > 0) {
                var numStrokes = 1;
                if(this._innerRadius !== 0) {
					strokeMaterial0 = this.makeStrokeMaterial();
                    strokePrim0 = this.generateOvalRing(x, y, reverseRotMat, innerStrokeScaleMat, innerRadiusScaleMat, nTriangles,  strokeMaterial0);
                }

                strokeMaterial2 = this.makeStrokeMaterial();
                strokePrim1 = this.generateOvalRing(x, y, reverseRotMat, fillScaleMat, strokeScaleMat, nTriangles,  strokeMaterial2);
            }

            if (strokePrim0) {
				strokeMaterial0.fitToPrimitive( strokePrim0 );

                this._primArray.push( strokePrim0 );
                this._materialNodeArray.push( strokeMaterial0.getMaterialNode() );
            }

            if (strokePrim1) {
				strokeMaterial2.fitToPrimitive( strokePrim1 );

                this._primArray.push( strokePrim1 );
                this._materialNodeArray.push( strokeMaterial2.getMaterialNode() );
            }

            /////////////////////////////////////////////////////////////
            //  Fill
            fillMaterial = this.makeFillMaterial();
            if(this._innerRadius === 0) {
                fillPrim = this.generateOval(x, y, mat, fillScaleMat, nTriangles,  fillMaterial);
            } else {
                fillPrim = this.generateOvalRing(x, y, reverseRotMat, innerRadiusScaleMat, fillScaleMat, nTriangles,  fillMaterial);
            }

            if (fillPrim) {
				fillMaterial.fitToPrimitive( fillPrim );

                this._primArray.push( fillPrim );
                this._materialNodeArray.push( fillMaterial.getMaterialNode() );
            }

            world.updateObject(this);
        }
    },

    generateOval: {
        value: function(xOff, yOff, rotationMat, scaleMat, nTriangles,  material) {
            var pt = [1.0, 0.0, 0.0];
            //var pts = scaleMat.multiply(pt);
            var pts = glmat4.multiplyVec3( scaleMat, pt, []);
            var x = pts[0],  y = pts[1], z = 0;
            var xs = scaleMat[0], ys = scaleMat[4];

            var	vrts = [], nrms = [], uvs = [], indices = [];
            var index = 0;
            for (var i=0;  i<nTriangles;  i++) {
                //pt = rotationMat.multiply( pt );
                //pts = scaleMat.multiply(pt);
                glmat4.multiplyVec3( rotationMat, pt );
                glmat4.multiplyVec3( scaleMat, pt, pts );

                // push the 3 vertices for the next triangle
                vrts.push(pts[0]+xOff);
                vrts.push(pts[1]+yOff);
                vrts.push(z);

                vrts.push(x+xOff);
                vrts.push(y+yOff);
                vrts.push(z);

                vrts.push(xOff);
                vrts.push(yOff);
                vrts.push(z);

                // push a texture coordinate pair for each vertex
                uvs.push(0.5);
                uvs.push(0.5);
                uvs.push(x/(2.0 * xs) + 0.5,  y/(2.0 * ys) + 0.5);
                uvs.push(pts[0]/(2.0 * xs) + 0.5, pts[1]/(2.0 * ys) + 0.5);

                // push a normal for each vertex
                nrms.push(0.0);
                nrms.push(0.0);
                nrms.push(1);
                nrms.push(0.0);
                nrms.push(0.0);
                nrms.push(1);
                nrms.push(0.0);
                nrms.push(0.0);
                nrms.push(1);

                x = pts[0];  y = pts[1];

                indices[index] = index++;
                indices[index] = index++;
                indices[index] = index++;
            }

            this.recalcTexMapCoords( vrts, uvs );

			//refine the mesh for vertex deformations
			if (material) {
				if (material.hasVertexDeformation()) {
					var paramRange = material.getVertexDeformationRange();
					var tolerance = material.getVertexDeformationTolerance();
					ShapePrimitive.refineMesh( vrts, nrms, uvs, indices, vrts.length/3,  paramRange,  tolerance );
				}
			}

            return ShapePrimitive.create(vrts, nrms, uvs, indices, RDGE.globals.engine.getContext().renderer.TRIANGLES, index);
        }
    },

    generateOvalRing: {
        value: function(xOff, yOff, rotationMat, innerScaleMat, outerScaleMat, nTriangles,  material) {
            var pt = [1.0, 0.0, 0.0];

            var z = 0;
            var pt0s,  pt1s;
            //pt0s = innerScaleMat.multiply(pt);
            //pt1s = outerScaleMat.multiply(pt);
            pt0s = glmat4.multiplyVec3(innerScaleMat, pt, []);
            pt1s = glmat4.multiplyVec3(outerScaleMat, pt, []);

            var	vrts = [], nrms = [], uvs = [], indices = [];

            // normals
            var insideAngle  = -15.0*Math.PI/180.0,
                outsideAngle =  15.0*Math.PI/180.0;
            var cs1 = Math.cos(insideAngle),  sn0 = Math.sin(insideAngle),
                cs0 = Math.cos(outsideAngle), sn1 = Math.sin(outsideAngle);
            var nrm0 = [-sn0, 0,  cs0],
                nrm1 = [-sn1, 0,  cs1];

            var index = 0;
            vrts.push( pt0s[0]+xOff);  vrts.push(pt0s[1]+yOff);  vrts.push(z);
            vrts.push( pt1s[0]+xOff);  vrts.push(pt1s[1]+yOff);  vrts.push(z);
            uvs.push(0.5*pt0s[0] + 0.5);  uvs.push(0.5*pt0s[1] + 0.5);
            uvs.push(0.5*pt1s[0] + 0.5);  uvs.push(0.5*pt1s[1] + 0.5);
            nrms.push( nrm0[0] );  nrms.push(nrm0[1] );  nrms.push(nrm0[2] );
            nrms.push( nrm1[0] );  nrms.push(nrm1[1] );  nrms.push(nrm1[2] );
            indices[index] = index++;
            indices[index] = index++;

            for (var i=0;  i<nTriangles;  i++) {
                pt   = glmat4.multiplyVec3( rotationMat, pt );
                glmat4.multiplyVec3( innerScaleMat, pt, pt0s );
                glmat4.multiplyVec3( outerScaleMat, pt, pt1s );

                // vertices
                vrts.push( pt0s[0]+xOff);  vrts.push(pt0s[1]+yOff);  vrts.push(z);
                vrts.push( pt1s[0]+xOff);  vrts.push(pt1s[1]+yOff);  vrts.push(z);

                // textures
                uvs.push(0.5*pt0s[0] + 0.5);  uvs.push(0.5*pt0s[1] + 0.5);
                uvs.push(0.5*pt1s[0] + 0.5);  uvs.push(0.5*pt1s[1] + 0.5);

                // normals
                glmat4.multiplyVec3( rotationMat, nrm0 );
                glmat4.multiplyVec3( rotationMat, nrm1 );
                nrms.push( nrm0[0]);  nrms.push(nrm0[1]);  nrms.push(nrm0[2] );
                nrms.push( nrm1[0]);  nrms.push(nrm1[1]);  nrms.push(nrm1[2] );
                indices[index] = index++;
                indices[index] = index++;
            }

            this.recalcTexMapCoords( vrts, uvs );

			/*
			//refine the mesh for vertex deformations
			if (material) {
				if (material.hasVertexDeformation()) {
					var paramRange = material.getVertexDeformationRange();
					var tolerance = material.getVertexDeformationTolerance();
					ShapePrimitive.refineMesh( vrts, nrms, uvs, indices, indices.length,  paramRange,  tolerance );
				}
			}
			*/

            return ShapePrimitive.create(vrts, nrms, uvs, indices, RDGE.globals.engine.getContext().renderer.TRIANGLE_STRIP, indices.length);
        }
    },

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

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

            // declare some variables
            var p0, p1;
            var x0, y1,   x1, y1;

            // create the matrix
            var lineWidth = this._strokeWidth;
            var innerRad  = this.getInnerRadius();
            var xScale = 0.5*this._width - lineWidth,
                yScale = 0.5*this._height - lineWidth;

            // translate
            var xCtr = 0.5*world.getViewportWidth() + this._xOffset,
                yCtr = 0.5*world.getViewportHeight() + this._yOffset;
            //ctx.setTransform( xScale, 0.0,  0.0, yScale, xCtr, yCtr );
            var mat = Matrix.create( [
                                [ xScale,     0.0,  0.0,  xCtr],
                                [    0.0,  yScale,  0.0,  yCtr],
                                [    0.0,     0.0,  1.0,   0.0],
                                [    0.0,     0.0,  0.0,   1.0]
                            ] );

            // get a bezier representation of the circle
            var bezPts = MathUtils.circularArcToBezier( [0,0,0], [1,0,0], 2.0*Math.PI );
            if (bezPts) {
                var n = bezPts.length;
                var gradient,
                    colors,
                    len,
                    j,
                    position,
                    cs,
                    c;

                // set up the fill style
                ctx.beginPath();
                ctx.lineWidth = 0;
                if (this._fillColor) {
                    if(this._fillColor.gradientMode) {
                        if(this._fillColor.gradientMode === "radial") {
                            gradient = ctx.createRadialGradient(xCtr, yCtr, 0,
                                                                xCtr, yCtr, Math.max(this._width, this._height)/2 - lineWidth);
                        } else {
                            gradient = ctx.createLinearGradient(lineWidth, this._height/2, this._width-lineWidth, this._height/2);
                        }
                        colors = this._fillColor.color;

                        len = colors.length;

                        for(j=0; j<len; j++) {
                            position = colors[j].position/100;
                            cs = colors[j].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;
                    }
                    // draw the fill
    //				ctx.beginPath();
                    var p = MathUtils.transformPoint( bezPts[0],   mat );
                    ctx.moveTo( p[0],  p[1] );
                    var index = 1;
                    while (index < n) {
                        p0   = MathUtils.transformPoint( bezPts[index],  mat );
                        p1 = MathUtils.transformPoint( bezPts[index+1],  mat );

                        x0 = p0[0];  y0 = p0[1];
                        x1 = p1[0];  y1 = p1[1];
                        ctx.quadraticCurveTo( x0,  y0,  x1, y1 );
                        index += 2;
                    }

                    if (MathUtils.fpSign(innerRad) > 0) {
                        xScale = 0.5*innerRad*this._width;
                        yScale = 0.5*innerRad*this._height;
                        mat[0] = xScale;
                        mat[5] = yScale;

                        // get the bezier points
                        var bezPts = MathUtils.circularArcToBezier( [0,0,0], [1,0,0], -2.0*Math.PI );
                        if (bezPts) {
                            var n = bezPts.length;
                            p = MathUtils.transformPoint( bezPts[0],   mat );
                            ctx.moveTo( p[0],  p[1] );
                            index = 1;
                            while (index < n) {
                                p0 = MathUtils.transformPoint( bezPts[index],    mat );
                                p1 = MathUtils.transformPoint( bezPts[index+1],  mat );

                                var x0 = p0[0],  y0 = p0[1],
                                    x1 = p1[0],  y1 = p1[1];
                                ctx.quadraticCurveTo( x0,  y0,  x1, y1 );
                                index += 2;
                            }
                        }
                    }

                    // fill the path
                    ctx.fill();
                }

                // calculate the stroke matrix
                xScale = 0.5*this._width  - 0.5*lineWidth;
                yScale = 0.5*this._height - 0.5*lineWidth;
                mat[0] = xScale;
                mat[5] = yScale;

                // set up the stroke style
                ctx.beginPath();
                ctx.lineWidth	= lineWidth;
                if (this._strokeColor) {
                    if(this._strokeColor.gradientMode) {
                        if(this._strokeColor.gradientMode === "radial") {
                            gradient = ctx.createRadialGradient(xCtr, yCtr, 0,
                                                                xCtr, yCtr, 0.5*Math.max(this._height, this._width));
                        } else {
                            gradient = ctx.createLinearGradient(0, this._height/2, this._width, this._height/2);
                        }
                        colors = this._strokeColor.color;

                        len = colors.length;

                        for(j=0; j<len; j++) {
                            position = colors[j].position/100;
                            cs = colors[j].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;
                    }
                    // draw the stroke
                    p = MathUtils.transformPoint( bezPts[0],   mat );
                    ctx.moveTo( p[0],  p[1] );
                    index = 1;
                    while (index < n) {
                        var p0   = MathUtils.transformPoint( bezPts[index],  mat );
                        var p1 = MathUtils.transformPoint( bezPts[index+1],  mat );

                        var x0 = p0[0],  y0 = p0[1],
                            x1 = p1[0],  y1 = p1[1];
                        ctx.quadraticCurveTo( x0,  y0,  x1, y1 );
                        index += 2;
                    }

                    if (MathUtils.fpSign(innerRad) > 0) {
                        // calculate the stroke matrix
                        xScale = 0.5*innerRad*this._width  - 0.5*lineWidth;
                        yScale = 0.5*innerRad*this._height - 0.5*lineWidth;
                        mat[0] = xScale;
                        mat[5] = yScale;

                        // draw the stroke
                        p = MathUtils.transformPoint( bezPts[0],   mat );
                        ctx.moveTo( p[0],  p[1] );
                        index = 1;
                        while (index < n) {
                            var p0   = MathUtils.transformPoint( bezPts[index],  mat );
                            var p1 = MathUtils.transformPoint( bezPts[index+1],  mat );

                            var x0 = p0[0],  y0 = p0[1],
                                x1 = p1[0],  y1 = p1[1];
                            ctx.quadraticCurveTo( x0,  y0,  x1, y1 );
                            index += 2;
                        }
                    }

                    // render the stroke
                    ctx.stroke();
                }
            }
        }
    },

    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,
                '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._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 );
        }
    },

    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 gl coordinates
            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 angle = Math.atan2( planePtNDC[1] - yNDC, planePtNDC[0] - xNDC );
            var degrees = angle*180.0/Math.PI;
            var objPtNDC = [Math.cos(angle)*xRadNDC + xNDC, Math.sin(angle)*yRadNDC + yNDC, 0];

            var ctrNDC = [xNDC, yNDC];

            var distToBoundary = VecUtils.vecDist( 2, ctrNDC, objPtNDC ),
                distToPt		= VecUtils.vecDist( 2, ctrNDC, planePtNDC );

            return (MathUtils.fpCmp(distToPt,distToBoundary) <= 0);
        }
    },

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

            // the input point and direction are in GL space
            // project to the z == 0 plane
            var mat = this.getMatrix();
            var plane = [0,0,1,0];
            plane = MathUtils.transformPlane( plane, mat );
            var projPt = MathUtils.vecIntersectPlane ( pt, dir, plane );

            // get the center of the circle in GL space
            var ctr = this.getGLCenter();

            // transform the projected point to the plane of the circle
            var planePt = MathUtils.transformPoint( projPt, mat );

            // get a matrix mapping the circle to a 2D coordinate system
            var normal = [ mat[8], mat[9], mat[10] ];
            var planeMat = drawUtils.getPlaneToWorldMatrix(normal, ctr);
            var planeMatInv = glmat4.inverse( planeMat, [] );
            var planePt2D = MathUtils.transformPoint( planePt, planeMatInv );

            // get 2 points on the axes of the oval
            var wPt = this.preViewToGL( [this._xOffset + 0.5*this.getWidth(),   this._yOffset,  0] ),
                hPt = this.preViewToGL( [this._xOffset,  this._yOffset + 0.5*this.getHeight(),  0] );
            var w = vecUtils.vecDist( 2, wPt, ctr ),
                h = vecUtils.vecDist( 2, hPt, ctr );
            var aspect = w/h;

            // get the angle of the projected point relative to the circle
            var angle = Math.atan2( planePt2D[1], planePt2D[0]/aspect );
            var degrees = angle*180.0/Math.PI;

            // get the corresponding point on the object
            var pt = [ Math.cos(angle)*w,  Math.sin(angle)*h,  0 ];
            var glPt = MathUtils.transformPoint( pt, planeMat );

            return glPt;
        }
    },

	recalcTexMapCoords: {
		value: function(vrts, uvs) {
			var n = vrts.length/3;
			if (n === 0)  return;
			var ivrt = 0,  iuv = 0;
			var uMin = 1.e8,  uMax = -1.e8,
				vMin = 1.e8,  vMax = -1.e8;
			
			var i, index = 3;
			var xMin = vrts[0], xMax = vrts[0],
				yMin = vrts[1], yMax = vrts[1];
			for (i=1;  i<n;  i++)
			{
				if (vrts[index] < xMin)  xMin = vrts[index];
				else if (vrts[index] > xMax)  xMax = vrts[index];

				if (vrts[index+1] < yMin)  yMin = vrts[index+1];
				else if (vrts[index+1] > yMax)  yMax = vrts[index+1];

				index += 3;
			}
			var ovalWidth  = xMax - xMin,
				ovalHeight = yMax - yMin;
			for (i=0;  i<n;  i++) {
				uvs[iuv] = (vrts[ivrt]-xMin)/ovalWidth;
				if (uvs[iuv] < uMin)  uMin = uvs[iuv];
				if (uvs[iuv] > uMax)  uMax = uvs[iuv];

				iuv++;  ivrt++;
				uvs[iuv] = (vrts[ivrt]-yMin)/ovalHeight;
				if (uvs[iuv] < vMin)  vMin = uvs[iuv];
				if (uvs[iuv] > vMax)  vMax = uvs[iuv];
				iuv++;  ivrt += 2;
			}

			//console.log( "remap" );
			//console.log( "uRange: " + uMin + " => " + uMax );
			//console.log( "vRange: " + vMin + " => " + vMax );
		}
	}
});