/* <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> */ /////////////////////////////////////////////////////////////////////// // Class GLRectangle // GL representation of a rectangle. // Derived from class GLGeomObj /////////////////////////////////////////////////////////////////////// function GLRectangle() { // CONSTANTS this.N_TRIANGLES = 15; // initialize the inherited members this.inheritedFrom = GLGeomObj; this.inheritedFrom(); /////////////////////////////////////////////////////////////////////// // Instance variables /////////////////////////////////////////////////////////////////////// this._width = 2.0; this._height = 2.0; this._xOffset = 0; this._yOffset = 0; this._tlRadius = 0; this._trRadius = 0; this._blRadius = 0; this._brRadius = 0; this._strokeWidth = 0.25; this._strokeStyle = "Solid"; this.init = 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; } // the overall radius includes the fill and the stroke. separate the two based onthe 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; else this._strokeMaterial = new FlatMaterial(); if(fillMaterial) this._fillMaterial = fillMaterial; else this._fillMaterial = new FlatMaterial(); } /////////////////////////////////////////////////////////////////////// // Property Accessors /////////////////////////////////////////////////////////////////////// this.getStrokeWidth = function() { return this._strokeWidth; } this.setStrokeWidth = function(w) { this._strokeWidth = w; } this.getStrokeMaterial = function() { return this._strokeMaterial; } this.setStrokeMaterial = function(m) { this._strokeMaterial = m; } this.getFillMaterial = function() { return this._fillMaterial; } this.setFillMaterial = function(m) { this._fillMaterial = m; } this.getStrokeColor = function() { return this._strokeColor; } //this.setStrokeColor = function(c) { this._strokeColor = c; } this.getFillColor = function() { return this._fillColor; } //this.setFillColor = function(c) { this._fillColor = c.slice(0); } this.getTLRadius = function() { return this._tlRadius; } this.setTLRadius = function(r) { this._tlRadius = Math.min(r, (this._height - this._strokeWidth)/2, (this._width - this._strokeWidth)/2); } this.getTRRadius = function() { return this._trRadius; } this.setTRRadius = function(r) { this._trRadius = Math.min(r, (this._height - this._strokeWidth)/2, (this._width - this._strokeWidth)/2); } this.getBLRadius = function() { return this._blRadius; } this.setBLRadius = function(r) { this._blRadius = Math.min(r, (this._height - this._strokeWidth)/2, (this._width - this._strokeWidth)/2); } this.getBRRadius = function() { return this._brRadius; } this.setBRRadius = function(r) { this._brRadius = Math.min(r, (this._height - this._strokeWidth)/2, (this._width - this._strokeWidth)/2); } this.getStrokeStyle = function() { return this._strokeStyle; } this.setStrokeStyle = function(s) { this._strokeStyle = s; } this.getWidth = function() { return this._width; } this.setWidth = function(w) { this._width = w; } this.getHeight = function() { return this._height; } this.setHeight = function(h) { this._height = h; } this.geomType = function() { return this.GEOM_TYPE_RECTANGLE; } /////////////////////////////////////////////////////////////////////// // Methods /////////////////////////////////////////////////////////////////////// this.export = function() { var rtnStr = "type: " + this.geomType() + "\n"; ///////////////////////////////////////////////////////////////////////// // // world, xOffset, yOffset, width, height, strokeSize, strokeColor, fillColor, // tlRadius, trRadius, blRadius, brRadius, strokeMaterial, fillMaterial, strokeStyle // ///////////////////////////////////////////////////////////////////////////// rtnStr += "xoff: " + this._xOffset + "\n"; rtnStr += "yoff: " + this._yOffset + "\n"; rtnStr += "width: " + this._width + "\n"; rtnStr += "height: " + this._height + "\n"; rtnStr += "strokeWidth: " + this._strokeWidth + "\n"; rtnStr += "strokeColor: " + String(this._strokeColor) + "\n"; rtnStr += "fillColor: " + String(this._fillColor) + "\n"; rtnStr += "tlRadius: " + this._tlRadius + "\n"; rtnStr += "trRadius: " + this._trRadius + "\n"; rtnStr += "blRadius: " + this._blRadius + "\n"; rtnStr += "brRadius: " + this._brRadius + "\n"; rtnStr += "innerRadius: " + this._innerRadius + "\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"; return rtnStr; } this.import = function( importStr ) { this._xOffset = Number( this.getPropertyFromString( "xoff: ", importStr ) ); this._yOffset = Number( this.getPropertyFromString( "yoff: ", importStr ) ); this._width = Number( this.getPropertyFromString( "width: ", importStr ) ); this._height = Number( this.getPropertyFromString( "height: ", importStr ) ); this._strokeWidth = Number( this.getPropertyFromString( "strokeWidth: ", importStr ) ); this._innerRadius = Number( this.getPropertyFromString( "innerRadius: ", importStr ) ); this._strokeStyle = Number( this.getPropertyFromString( "strokeStyle: ", importStr ) ); var strokeMaterialName = this.getPropertyFromString( "strokeMat: ", importStr ); var fillMaterialName = this.getPropertyFromString( "fillMat: ", importStr ); this._strokeStyle = this.getPropertyFromString( "strokeStyle: ", importStr ); this._fillColor = eval( "[" + this.getPropertyFromString( "fillColor: ", importStr ) + "]" ); this._strokeColor = eval( "[" + this.getPropertyFromString( "strokeColor: ", importStr ) + "]" ); this._tlRadius = Number( this.getPropertyFromString( "tlRadius: ", importStr ) ); this._trRadius = Number( this.getPropertyFromString( "trRadius: ", importStr ) ); this._blRadius = Number( this.getPropertyFromString( "blRadius: ", importStr ) ); this._brRadius = Number( this.getPropertyFromString( "brRadius: ", importStr ) ); var strokeMat = MaterialsLibrary.getMaterial( strokeMaterialName ); if (!strokeMat) { console.log( "object material not found in library: " + strokeMaterialName ); strokeMat = new FlatMaterial(); } this._strokeMaterial = strokeMat; var fillMat = MaterialsLibrary.getMaterial( fillMaterialName ); if (!fillMat) { console.log( "object material not found in library: " + fillMaterialName ); fillMat = new FlatMaterial(); } this._fillMaterial = fillMat; } this.buildBuffers = 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 g_Engine.setContext( world.getCanvas().uuid ); // 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); 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); this._primArray.push( fillPrim ); this._materialNodeArray.push( fillMaterial.getMaterialNode() ); world.updateObject(this); } this.renderQuadraticBezier = 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; } } this.renderPath = function( inset, ctx ) { // various declarations var pt, rad, ctr, startPt, bPts; var width = Math.round(this.getWidth()), height = Math.round(this.getHeight()); 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; if ((tlRad <= 0) && (blRad <= 0) && (brRad <= 0) && (trRad <= 0)) { ctx.rect(pt[0], pt[1], 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], pt[1] ); // get the bottom left point pt = [inset, height - inset]; rad = blRad - inset; if (rad < 0) rad = 0; pt[1] -= rad; ctx.lineTo( pt[0], pt[1] ); // get the bottom left curve if (MathUtils.fpSign(rad) > 0) ctx.quadraticCurveTo( inset, height-inset, inset+rad, height-inset ); // 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], pt[1] ); // get the bottom right arc if (MathUtils.fpSign(rad) > 0) ctx.quadraticCurveTo( width-inset, height-inset, width-inset, height-inset-rad ); // 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], pt[1] ); // do the top right corner if (MathUtils.fpSign(rad) > 0) ctx.quadraticCurveTo( width-inset, inset, width-inset-rad, inset ); // do the top of the rectangle pt = [inset, inset] rad = tlRad - inset; if (rad < 0) rad = 0; pt[0] += rad; ctx.lineTo( pt[0], pt[1] ); // do the top left corner if (MathUtils.fpSign(rad) > 0) ctx.quadraticCurveTo( inset, inset, inset, inset+rad ); else ctx.lineTo( inset, 2*inset ); } } this.render = 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(); // set the fill ctx.beginPath(); ctx.fillStyle = "#990000"; if (this._fillColor) ctx.fillStyle = MathUtils.colorToHex( this._fillColor ); // set the stroke ctx.strokeStyle = "#0000ff"; if (this._strokeColor) ctx.strokeStyle = MathUtils.colorToHex( this._strokeColor ); ctx.lineWidth = lw; var inset = Math.ceil( 0.5*lw ) + 0.5; this.renderPath( inset, ctx ); ctx.fill(); ctx.stroke(); ctx.closePath(); } this.createStroke = function(ctr, width, height, strokeWidth, tlRad, blRad, brRad, trRad, material) { // create the geometry var prim = RectangleStroke.create( ctr, width, height, strokeWidth, tlRad, blRad, brRad, trRad, material) return prim; } this.createFill = 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; } this.collidesWithPoint = 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; } this.containsPoint = 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 = Vector.create([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; } this.getNearVertex = 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 = Vector.create([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 = Vector.create( [xMin, yMin, 0] ); dist = VecUtils.vecDist(2, pt, planePtNDC); var minPt = pt, minDist = dist; pt = Vector.create( [xMin, yMax, 0] ); dist = VecUtils.vecDist(2, pt, planePtNDC); if (dist < minDist) { minDist = dist; minPt = pt; } pt = Vector.create( [xMax, yMax, 0] ); dist = VecUtils.vecDist(2, pt, planePtNDC); if (dist < minDist) { minDist = dist; minPt = pt; } pt = Vector.create( [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 = Vector.create([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; } this.getNearPoint = 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 = Vector.create([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 near point on the 4 sides var pt, dist; pt = Vector.create( [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 = Vector.create( [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 = Vector.create( [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 = Vector.create( [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 = Vector.create([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; } this.recalcTexMapCoords = 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; } //console.log( "remap: " + uvs ); } } 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 var prim = ShapePrimitive.create(this.vertices, this.normals, this.uvs, this.indices, g_Engine.getContext().renderer.TRIANGLES, nVertices); return prim; } 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 ); } } 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 var prim = ShapePrimitive.create(this.vertices, this.normals, this.uvs, this.indices, g_Engine.getContext().renderer.TRIANGLES, nVertices); //var prim = ShapePrimitive.create(this.vertices, this.normals, this.uvs, this.indices, g_Engine.getContext().renderer.LINES, nVertices); return prim; } 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; // Helper function for generating Three.js geometry 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 var prim = ShapePrimitive.create(this.vertices, this.normals, this.uvs, this.indices, g_Engine.getContext().renderer.TRIANGLES, nVertices); //var prim = ShapePrimitive.create(this.vertices, this.normals, this.uvs, this.indices, g_Engine.getContext().renderer.LINES, nVertices); return prim; } RectangleGeometry.pushVertex = RectangleFill.pushVertex; RectangleGeometry.pushNormal = RectangleFill.pushNormal; RectangleGeometry.pushUV = RectangleFill.pushUV; RectangleGeometry.pushIndices = RectangleFill.pushIndices; RectangleGeometry.getVertex = RectangleFill.getVertex; RectangleGeometry.getUV = RectangleFill.getUV; // Helper function for generating a RDGE primitive ShapePrimitive = {}; ShapePrimitive.create = function(coords, normals, uvs, indices, primType, vertexCount) { var renderer = g_Engine.getContext().renderer; // to setup a primitive you must define it // create a new primitive definition here to then fill out var prim = new rdgePrimitiveDefinition(); // the vertex definition declares how the data will be delivered to the shader // the position of an element in array determines which attribute in a shader the // data is bound to prim.vertexDefinition= { // this shows two ways to map this data to an attribute "vert":{'type':renderer.VS_ELEMENT_POS, 'bufferIndex':0, 'bufferUsage': renderer.BUFFER_STATIC}, "a_pos":{'type':renderer.VS_ELEMENT_POS, 'bufferIndex':0, 'bufferUsage': renderer.BUFFER_STATIC}, "normal":{'type':renderer.VS_ELEMENT_FLOAT3, 'bufferIndex':1, 'bufferUsage': renderer.BUFFER_STATIC}, "a_nrm":{'type':renderer.VS_ELEMENT_FLOAT3, 'bufferIndex':1, 'bufferUsage': renderer.BUFFER_STATIC}, "a_normal":{'type':renderer.VS_ELEMENT_FLOAT3, 'bufferIndex':1, 'bufferUsage': renderer.BUFFER_STATIC}, "texcoord":{'type':renderer.VS_ELEMENT_FLOAT2, 'bufferIndex':2, 'bufferUsage': renderer.BUFFER_STATIC}, "a_texcoord":{'type':renderer.VS_ELEMENT_FLOAT2, 'bufferIndex':2, 'bufferUsage': renderer.BUFFER_STATIC} }; // the actual data that correlates to the vertex definition prim.bufferStreams= [ coords, normals, uvs ]; // what type of buffers the data resides in, static is the most common case prim.streamUsage= [ renderer.BUFFER_STATIC, renderer.BUFFER_STATIC, renderer.BUFFER_STATIC ]; // this tells the renderer to draw the primitive as a list of triangles prim.type = primType; prim.indexUsage = renderer.BUFFER_STREAM; prim.indexBuffer = indices; // finally the primitive is created, buffers are generated and the system determines // the data it needs to draw this primitive according to the previous definition renderer.createPrimitive(prim, vertexCount); return prim; } ShapePrimitive.refineMesh = function( verts, norms, uvs, indices, nVertices, paramRange, tolerance ) { // get the param range var pUMin = paramRange[0], pVMin = paramRange[1], pUMax = paramRange[2], pVMax = paramRange[3]; var iTriangle = 0; var nTriangles = indices.length/3; var index = 0; while (iTriangle < nTriangles) { // get the indices of the 3 vertices var i0 = indices[index], i1 = indices[index+1], i2 = indices[index+2]; // get the uv values //var vrtIndex = 3*iTriangle; var iuv0 = 2 * i0, iuv1 = 2 * i1, iuv2 = 2 * i2; var u0 = uvs[iuv0], v0 = uvs[iuv0+1], u1 = uvs[iuv1], v1 = uvs[iuv1+1], u2 = uvs[iuv2], v2 = uvs[iuv2+1]; // find the u and v range var uMin = u0, vMin = v0; if (u1 < uMin) uMin = u1; if (v1 < vMin) vMin = v1; if (u2 < uMin) uMin = u2; if (v2 < vMin) vMin = v2; var uMax = u0, vMax = v0; if (u1 > uMax) uMax = u1; if (v1 > vMax) vMax = v1; if (u2 > uMax) uMax = u2; if (v2 > vMax) vMax = v2; // if the parameter range of the triangle is outside the // desired parameter range, advance to the next polygon and continue if ((uMin > pUMax) || (uMax < pUMin) || (vMin > pVMax) || (vMax < pVMin)) { // go to the next triangle iTriangle++; index += 3; } else { // check thesize of the triangle in uv space. If small enough, advance // to the next triangle. If not small enough, split the triangle into 3; var du = uMax - uMin, dv = vMax - vMin; if ((du < tolerance) && (dv < tolerance)) { iTriangle++; index += 3; } else // split the triangle into 4 parts { //calculate the position of the new vertex var iPt0 = 3 * i0, iPt1 = 3 * i1, iPt2 = 3 * i2; var x0 = verts[iPt0], y0 = verts[iPt0+1], z0 = verts[iPt0+2], x1 = verts[iPt1], y1 = verts[iPt1+1], z1 = verts[iPt1+2], x2 = verts[iPt2], y2 = verts[iPt2+1], z2 = verts[iPt2+2]; // calculate the midpoints of the edges var xA = (x0 + x1)/2.0, yA = (y0 + y1)/2.0, zA = (z0 + z1)/2.0, xB = (x1 + x2)/2.0, yB = (y1 + y2)/2.0, zB = (z1 + z2)/2.0, xC = (x2 + x0)/2.0, yC = (y2 + y0)/2.0, zC = (z2 + z0)/2.0; // calculate the uv values of the new coordinates var uA = (u0 + u1)/2.0, vA = (v0 + v1)/2.0, uB = (u1 + u2)/2.0, vB = (v1 + v2)/2.0, uC = (u2 + u0)/2.0, vC = (v2 + v0)/2.0; // calculate the normals for the new points var nx0 = norms[iPt0], ny0 = norms[iPt0+1], nz0 = norms[iPt0+2], nx1 = norms[iPt1], ny1 = norms[iPt1+1], nz1 = norms[iPt1+2], nx2 = norms[iPt2], ny2 = norms[iPt2+1], nz2 = norms[iPt2+2]; var nxA = (nx0 + nx1), nyA = (ny0 + ny1), nzA = (nz0 + nz1); var nrmA = VecUtils.vecNormalize(3, [nxA, nyA, nzA], 1.0 ), nxB = (nx1 + nx2), nyB = (ny1 + ny2), nzB = (nz1 + nz2); var nrmB = VecUtils.vecNormalize(3, [nxB, nyB, nzB], 1.0 ), nxC = (nx2 + nx0), nyC = (ny2 + ny0), nzC = (nz2 + nz0); var nrmC = VecUtils.vecNormalize(3, [nxC, nyC, nzC], 1.0 ); // push everything verts.push(xA); verts.push(yA); verts.push(zA); verts.push(xB); verts.push(yB); verts.push(zB); verts.push(xC); verts.push(yC); verts.push(zC); uvs.push(uA), uvs.push(vA); uvs.push(uB), uvs.push(vB); uvs.push(uC), uvs.push(vC); norms.push(nrmA[0]); norms.push(nrmA[1]); norms.push(nrmA[2]); norms.push(nrmB[0]); norms.push(nrmB[1]); norms.push(nrmB[2]); norms.push(nrmC[0]); norms.push(nrmC[1]); norms.push(nrmC[2]); // split the current triangle into 4 indices[index+1] = nVertices; indices[index+2] = nVertices+2; indices.push(nVertices); indices.push(i1); indices.push(nVertices+1); nTriangles++; indices.push(nVertices+1); indices.push(i2); indices.push(nVertices+2); nTriangles++; indices.push(nVertices); indices.push(nVertices+1); indices.push(nVertices+2); nTriangles++; nVertices += 3; // by not advancing 'index', we examine the first of the 3 triangles generated above } } } return nVertices; }