package Command; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.StringTokenizer; import java.util.TreeSet; import javax.swing.undo.UndoManager; import Common.Box2; import Common.Box3; import Common.Matrix3; import Common.Matrix4; import Common.Ray3; import Common.Vector2f; import Common.Vector3f; import Common.Vector4f; import LDraw.Support.DispatchGroup; import LDraw.Support.GLMatrixMath; import LDraw.Support.ILDrawDragHandler; import LDraw.Support.LDrawDirective; import LDraw.Support.LDrawDragHandle; import LDraw.Support.LDrawUtilities; import LDraw.Support.MatrixMath; import LDraw.Support.Range; import LDraw.Support.SelT; import LDraw.Support.type.CacheFlagsT; import Renderer.ILDrawCollector; import Renderer.ILDrawRenderer; import Renderer.LDrawRenderColorT; public class LDrawQuadrilateral extends LDrawDrawableElement implements ILDrawDragHandler { /** * @uml.property name="vertex1" * @uml.associationEnd multiplicity="(1 1)" */ Vector3f vertex1 = Vector3f.getZeroVector3f(); /** * @uml.property name="vertex2" * @uml.associationEnd multiplicity="(1 1)" */ Vector3f vertex2 = Vector3f.getZeroVector3f(); /** * @uml.property name="vertex3" * @uml.associationEnd multiplicity="(1 1)" */ Vector3f vertex3 = Vector3f.getZeroVector3f(); /** * @uml.property name="vertex4" * @uml.associationEnd multiplicity="(1 1)" */ Vector3f vertex4 = Vector3f.getZeroVector3f(); /** * @uml.property name="normal" * @uml.associationEnd multiplicity="(1 1)" */ Vector3f normal = Vector3f.getZeroVector3f(); /** * @uml.property name="dragHandles" * @uml.associationEnd multiplicity="(0 -1)" * elementType="LDraw.Support.LDrawDragHandle" */ ArrayList<LDrawDragHandle> dragHandles; // ========== initWithLines:inRange:parentGroup: // ================================ // // Purpose: Returns a triangle initialized from line of LDraw code beginning // at the given range. // // directive should have the format: // // 3 colour x1 y1 z1 x2 y2 z2 x3 y3 z3 // // ============================================================================== public LDrawQuadrilateral initWithLines(ArrayList<String> lines, Range range, DispatchGroup parentGroup) { String workingLine = lines.get(range.getLocation()); String parsedField = null; Vector3f workingVertex = Vector3f.getZeroVector3f(); LDrawColor parsedColor = null; try { super.initWithLines(lines, range, parentGroup); } catch (Exception e1) { // TODO Auto-generated catch block e1.printStackTrace(); } // A malformed part could easily cause a string indexing error, which // would // raise an exception. We don't want this to happen here. try { // Read in the line code and advance past it. StringTokenizer strTokenizer = new StringTokenizer(workingLine); parsedField = strTokenizer.nextToken(); // Only attempt to create the part if this is a valid line. if (Integer.parseInt(parsedField) == 4) { // Read in the color code. // (color) parsedField = strTokenizer.nextToken(); parsedColor = LDrawUtilities.parseColorFromField(parsedField); setLDrawColor(parsedColor); // Read Vertex 1. // (x1) parsedField = strTokenizer.nextToken(); workingVertex.setX(Float.parseFloat(parsedField)); // (y1) parsedField = strTokenizer.nextToken(); workingVertex.setY(Float.parseFloat(parsedField)); // (z1) parsedField = strTokenizer.nextToken(); workingVertex.setZ(Float.parseFloat(parsedField)); setVertex1(workingVertex); // Read Vertex 2. // (x2) parsedField = strTokenizer.nextToken(); workingVertex.setX(Float.parseFloat(parsedField)); // (y2) parsedField = strTokenizer.nextToken(); workingVertex.setY(Float.parseFloat(parsedField)); // (z2) parsedField = strTokenizer.nextToken(); workingVertex.setZ(Float.parseFloat(parsedField)); setVertex2(workingVertex); // Read Vertex 3. // (x3) parsedField = strTokenizer.nextToken(); workingVertex.setX(Float.parseFloat(parsedField)); // (y3) parsedField = strTokenizer.nextToken(); workingVertex.setY(Float.parseFloat(parsedField)); // (z3) parsedField = strTokenizer.nextToken(); workingVertex.setZ(Float.parseFloat(parsedField)); setVertex3(workingVertex); // Read Vertex 4. // (x4) parsedField = strTokenizer.nextToken(); workingVertex.setX(Float.parseFloat(parsedField)); // (y4) parsedField = strTokenizer.nextToken(); workingVertex.setY(Float.parseFloat(parsedField)); // (z4) parsedField = strTokenizer.nextToken(); workingVertex.setZ(Float.parseFloat(parsedField)); setVertex4(workingVertex); fixBowtie(); if(parentGroup.isCCW()==false) setToCW(); } else throw new Exception("BricksmithParseException: " + "Bad line syntax"); } catch (Exception e) { System.out.println(String.format( "the quadrilateral primitive %s was fatally invalid", lines.get(range.getLocation()))); System.out.println(String.format(" raised exception %s", e.getMessage())); } return this; }// end initWithLines:inRange: // ========== drawSelf: // =========================================================== // // Purpose: Draw this directive and its subdirectives by calling APIs on // the passed in renderer, then calling drawSelf on children. // // Notes: Triangles use this message to get their drag handles drawn if // needed. They do not draw their actual GL primitive because that // has already been "collected" by some parent capable of // accumulating a mesh. // // ================================================================================ public void drawSelf(ILDrawRenderer renderer) { revalCache(CacheFlagsT.DisplayList); if (hidden == false) { if (dragHandles != null) { for (LDrawDragHandle handle : dragHandles) { handle.drawSelf(renderer); } } } }// end drawSelf: // ========== collectSelf: // ======================================================== // // Purpose: Collect this is called on each directive by its parents to // accumulate _mesh_ data into a display list for later drawing. // The collector protocol passed in is some object capable of // remembering the collectable data. // // Real GL primitives participate by passing their color and // geometry data to the collector. // // ================================================================================ public void collectSelf(ILDrawCollector renderer) { // We must mark our DL as valid - otherwise we will not invalidate our // DL when edited, and if we don't do that, we won't pass the message // to our parents that our DL is invalid. This passing the invalid DL up // is what PRIMES our parent model to rebuild DLs as needed. revalCache(CacheFlagsT.DisplayList); if(color==null)return; if (hidden == false) { float v[] = { vertex1.getX(), vertex1.getY(), vertex1.getZ(), vertex2.getX(), vertex2.getY(), vertex2.getZ(), vertex3.getX(), vertex3.getY(), vertex3.getZ(), vertex4.getX(), vertex4.getY(), vertex4.getZ() }; float n[] = { normal.getX(), normal.getY(), normal.getZ() }; if (color.getColorCode() == LDrawColorT.LDrawCurrentColor) renderer.drawQuad(v, n, LDrawRenderColorT.LDrawRenderCurrentColor.getValue()); else if (color.getColorCode() == LDrawColorT.LDrawEdgeColor) renderer.drawQuad(v, n, LDrawRenderColorT.LDrawRenderComplimentColor.getValue()); else { float rgba[] = new float[4]; color.getColorRGBA(rgba); renderer.drawQuad(v, n, rgba); } } }// end collectSelf: public void getRange( Matrix4 transform, float[] range ) { if (hidden == false) { Vector3f worldVertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1, transform); Vector3f worldVertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2, transform); Vector3f worldVertex3 = MatrixMath.V3MulPointByProjMatrix(vertex3, transform); Vector3f worldVertex4 = MatrixMath.V3MulPointByProjMatrix(vertex4, transform); compareRange( range, worldVertex1 ); compareRange( range, worldVertex2 ); compareRange( range, worldVertex3 ); compareRange( range, worldVertex4 ); if (dragHandles != null) { for (LDrawDragHandle handle : dragHandles) { handle.getRange(transform, range); } } } } // ========== hitTest:transform:viewScale:boundsOnly:creditObject:hits: // ======= // // Purpose: Tests the directive and any of its children for intersections // between the pickRay and the directive's drawn content. // // ============================================================================== public void hitTest(Ray3 pickRay, Matrix4 transform, LDrawDirective creditObject, HashMap<LDrawDirective, Float> hits) { if (hidden == false) { Vector3f worldVertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1, transform); Vector3f worldVertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2, transform); Vector3f worldVertex3 = MatrixMath.V3MulPointByProjMatrix(vertex3, transform); Vector3f worldVertex4 = MatrixMath.V3MulPointByProjMatrix(vertex4, transform); FloatBuffer intersectDepth = FloatBuffer.allocate(1); boolean intersects = false; intersects = MatrixMath.V3RayIntersectsTriangle(pickRay, worldVertex1, worldVertex2, worldVertex3, intersectDepth, null); if (intersects == false) { intersects = MatrixMath.V3RayIntersectsTriangle(pickRay, worldVertex3, worldVertex4, worldVertex1, intersectDepth, null); } if (intersects) { LDrawUtilities.registerHitForObject(this, intersectDepth, creditObject, hits); } if (dragHandles != null) { for (LDrawDragHandle handle : dragHandles) { handle.hitTest(pickRay, transform, null, hits); } } } }// end hitTest:transform:viewScale:boundsOnly:creditObject:hits: // ========== boxTest:transform:boundsOnly:creditObject:hits: // =================== // // Purpose: Check for intersections with screen-space geometry. // // ============================================================================== public boolean boxTest(Box2 bounds, Matrix4 transform, boolean boundsOnly, LDrawDirective creditObject, TreeSet<LDrawDirective> hits) { if (hidden == false) { Vector4f clipVertex1 = MatrixMath.V4MulPointByMatrix( MatrixMath.V4FromVector3f(vertex1), transform); Vector4f clipVertex2 = MatrixMath.V4MulPointByMatrix( MatrixMath.V4FromVector3f(vertex2), transform); Vector4f clipVertex3 = MatrixMath.V4MulPointByMatrix( MatrixMath.V4FromVector3f(vertex3), transform); Vector4f clipVertex4 = MatrixMath.V4MulPointByMatrix( MatrixMath.V4FromVector3f(vertex4), transform); float h_tri1[] = { clipVertex1.getX(), clipVertex1.getY(), clipVertex1.getZ(), clipVertex1.getW(), clipVertex2.getX(), clipVertex2.getY(), clipVertex2.getZ(), clipVertex2.getW(), clipVertex3.getX(), clipVertex3.getY(), clipVertex3.getZ(), clipVertex3.getW() }; float h_tri2[] = { clipVertex3.getX(), clipVertex3.getY(), clipVertex3.getZ(), clipVertex3.getW(), clipVertex4.getX(), clipVertex4.getY(), clipVertex4.getZ(), clipVertex4.getW(), clipVertex1.getX(), clipVertex1.getY(), clipVertex1.getZ(), clipVertex1.getW() }; float ndc_tris[] = new float[36]; int triCount = GLMatrixMath.clipTriangle(h_tri1, ndc_tris); triCount += GLMatrixMath.clipTriangle(h_tri2, 0, ndc_tris, 9 * triCount); int i; for (i = 0; i < triCount; ++i) { Vector2f tri[] = { MatrixMath.V2Make(ndc_tris[i * 9 + 0], ndc_tris[i * 9 + 1]), MatrixMath.V2Make(ndc_tris[i * 9 + 3], ndc_tris[i * 9 + 4]), MatrixMath.V2Make(ndc_tris[i * 9 + 6], ndc_tris[i * 9 + 7]) }; if (MatrixMath.V2BoxIntersectsPolygon(bounds, tri, 3)) { LDrawUtilities.registerHitForObject(this, creditObject, hits); if (creditObject != null) return true; } } } return false; }// end boxTest:transform:boundsOnly:creditObject:hits: // ========== // depthTest:inBox:transform:creditObject:bestObject:bestDepth:======= // // Purpose: depthTest finds the closest primitive (in screen space) // overlapping a given point, as well as its device coordinate // depth. // // ============================================================================== public void depthTest(Vector2f pt, Box2 bounds, Matrix4 transform, LDrawDirective creditObject, ArrayList<LDrawDirective> bestObject, FloatBuffer bestDepth) { if (hidden == false) { Vector4f clipVertex1 = MatrixMath.V4MulPointByMatrix( MatrixMath.V4FromVector3f(vertex1), transform); Vector4f clipVertex2 = MatrixMath.V4MulPointByMatrix( MatrixMath.V4FromVector3f(vertex2), transform); Vector4f clipVertex3 = MatrixMath.V4MulPointByMatrix( MatrixMath.V4FromVector3f(vertex3), transform); Vector4f clipVertex4 = MatrixMath.V4MulPointByMatrix( MatrixMath.V4FromVector3f(vertex4), transform); Vector3f probe = new Vector3f(pt.getX(), pt.getY(), bestDepth.get(0)); float h_tri1[] = { clipVertex1.getX(), clipVertex1.getY(), clipVertex1.getZ(), clipVertex1.getW(), clipVertex2.getX(), clipVertex2.getY(), clipVertex2.getZ(), clipVertex2.getW(), clipVertex3.getX(), clipVertex3.getY(), clipVertex3.getZ(), clipVertex3.getW() }; float h_tri2[] = { clipVertex3.getX(), clipVertex3.getY(), clipVertex3.getZ(), clipVertex3.getW(), clipVertex4.getX(), clipVertex4.getY(), clipVertex4.getZ(), clipVertex4.getW(), clipVertex1.getX(), clipVertex1.getY(), clipVertex1.getZ(), clipVertex1.getW() }; float ndc_tris[] = new float[36]; int triCount = GLMatrixMath.clipTriangle(h_tri1, ndc_tris); triCount += GLMatrixMath.clipTriangle(h_tri2, 0, ndc_tris, 9 * triCount); int i; for (i = 0; i < triCount; ++i) { Vector3f ndcVertex1 = MatrixMath.V3Make(ndc_tris[i * 9 + 0], ndc_tris[i * 9 + 1], ndc_tris[i * 9 + 2]); Vector3f ndcVertex2 = MatrixMath.V3Make(ndc_tris[i * 9 + 3], ndc_tris[i * 9 + 4], ndc_tris[i * 9 + 5]); Vector3f ndcVertex3 = MatrixMath.V3Make(ndc_tris[i * 9 + 6], ndc_tris[i * 9 + 7], ndc_tris[i * 9 + 8]); if (MatrixMath.DepthOnTriangle(ndcVertex1, ndcVertex2, ndcVertex3, probe)) { if (probe.getZ() <= bestDepth.get(0)) { bestDepth.put(0, probe.getZ()); bestObject .add((LDrawDirective) (creditObject != null ? creditObject : this)); } } } if (dragHandles != null) { for (LDrawDragHandle handle : dragHandles) { handle.depthTest(pt, bounds, transform, creditObject, bestObject, bestDepth); } } } }// end depthTest:inBox:transform:creditObject:bestObject:bestDepth: // ========== write // ============================================================= // // Purpose: Returns a line that can be written out to a file. // Line format: // 3 colour x1 y1 z1 x2 y2 z2 x3 y3 z3 // // ============================================================================== public String write() { return String.format("4 %s %s %s %s %s %s %s %s %s %s %s %s %s", LDrawUtilities.outputStringForColor(color), LDrawUtilities.outputStringForFloat(vertex1.getX()), LDrawUtilities.outputStringForFloat(vertex1.getY()), LDrawUtilities.outputStringForFloat(vertex1.getZ()), LDrawUtilities.outputStringForFloat(vertex2.getX()), LDrawUtilities.outputStringForFloat(vertex2.getY()), LDrawUtilities.outputStringForFloat(vertex2.getZ()), LDrawUtilities.outputStringForFloat(vertex3.getX()), LDrawUtilities.outputStringForFloat(vertex3.getY()), LDrawUtilities.outputStringForFloat(vertex3.getZ()), LDrawUtilities.outputStringForFloat(vertex4.getX()), LDrawUtilities.outputStringForFloat(vertex4.getY()), LDrawUtilities.outputStringForFloat(vertex4.getZ()) ); }// end write // ========== browsingDescription // =============================================== // // Purpose: Returns a representation of the directive as a short string // which can be presented to the user. // // ============================================================================== public String browsingDescription() { return "Quadrilateral"; }// end browsingDescription // ========== iconName // ========================================================== // // Purpose: Returns the name of image file used to display this kind of // object, or null if there is no icon. // // ============================================================================== public String iconName() { return "Quadrilateral"; }// end iconName // ========== inspectorClassName // ================================================ // // Purpose: Returns the name of the class used to inspect this one. // // ============================================================================== public String inspectorClassName() { return "InspectionQuadrilateral"; }// end inspectorClassName // #pragma mark - // #pragma mark ACCESSORS // #pragma mark - // ========== boundingBox3 // ====================================================== // // Purpose: Returns the minimum and maximum points of the box which // perfectly contains this object. // // ============================================================================== public Box3 boundingBox3() { // Raw directive doesn't cache - we just compute our bbox on the fly. // But // keep our parents "in sync". revalCache(CacheFlagsT.CacheFlagBounds); if (hidden == true) return Box3.getInvalidBox(); Box3 bounds; // Compare first two points. bounds = MatrixMath.V3BoundsFromPoints(vertex1, vertex2); // Now toss the third vertex into the mix. bounds = MatrixMath.V3UnionBoxAndPoint(bounds, vertex3); bounds = MatrixMath.V3UnionBoxAndPoint(bounds, vertex4); return bounds; }// end boundingBox3 // ========== position // ========================================================== // // Purpose: Returns some position for the element. This is used by // drag-and-drop. This is not necessarily human-usable information. // // ============================================================================== public Vector3f position() { return vertex1; }// end position // ========== vertex1 // =========================================================== // ============================================================================== public Vector3f vertex1() { return vertex1; }// end vertex1 // ========== vertex2 // =========================================================== // ============================================================================== public Vector3f vertex2() { return vertex2; }// end vertex2 // ========== vertex3 // =========================================================== // ============================================================================== public Vector3f vertex3() { return vertex3; }// end vertex3 // ========== vertex4 // =========================================================== // ============================================================================== public Vector3f vertex4() { return vertex4; }// end vertex3 // #pragma mark - // ========== setSelected: // ====================================================== // // Purpose: Somebody make this a protocol method. // // ============================================================================== public void setSelected( boolean flag) { super.setSelected(flag); if (flag == true) { LDrawDragHandle handle1 = new LDrawDragHandle(); handle1.initWithTag(1, vertex1); LDrawDragHandle handle2 = new LDrawDragHandle(); handle1.initWithTag(2, vertex2); LDrawDragHandle handle3 = new LDrawDragHandle(); handle1.initWithTag(3, vertex3); LDrawDragHandle handle4 = new LDrawDragHandle(); handle1.initWithTag(4, vertex4); handle1.setTarget(this); handle2.setTarget(this); handle3.setTarget(this); handle4.setTarget(this); handle1.setAction(SelT.DragHandleChanged); handle2.setAction(SelT.DragHandleChanged); handle3.setAction(SelT.DragHandleChanged); handle4.setAction(SelT.DragHandleChanged); dragHandles = new ArrayList<LDrawDragHandle>(); dragHandles.add(handle1); dragHandles.add(handle2); dragHandles.add(handle3); dragHandles.add(handle4); } else { dragHandles = null; } }// end setSelected: // ========== setVertex1: // ======================================================= // // Purpose: Sets the triangle's first vertex. // // ============================================================================== /** * @param newVertex * @uml.property name="vertex1" */ public void setVertex1(Vector3f newVertex) { vertex1.set(newVertex); recomputeNormal(); invalCache(CacheFlagsT.CacheFlagBounds); invalCache(CacheFlagsT.DisplayList); if (dragHandles != null) { dragHandles.get(0).setPosition(newVertex, false); } if (enclosingDirective() != null) enclosingDirective().setVertexesNeedRebuilding(); }// end setVertex1: // ========== setVertex2: // ======================================================= // // Purpose: Sets the triangle's second vertex. // // ============================================================================== /** * @param newVertex * @uml.property name="vertex2" */ public void setVertex2(Vector3f newVertex) { vertex2.set(newVertex); recomputeNormal(); invalCache(CacheFlagsT.CacheFlagBounds); invalCache(CacheFlagsT.DisplayList); if (dragHandles != null) { dragHandles.get(1).setPosition(newVertex, false); } if (enclosingDirective() != null) enclosingDirective().setVertexesNeedRebuilding(); }// end setVertex2: // ========== setVertex3: // ======================================================= // // Purpose: Sets the triangle's last vertex. // // ============================================================================== /** * @param newVertex * @uml.property name="vertex3" */ public void setVertex3(Vector3f newVertex) { vertex3.set(newVertex); recomputeNormal(); invalCache(CacheFlagsT.CacheFlagBounds); invalCache(CacheFlagsT.DisplayList); if (dragHandles != null) { dragHandles.get(2).setPosition(newVertex, false); } if (enclosingDirective() != null) enclosingDirective().setVertexesNeedRebuilding(); }// end setVertex3: // ========== setVertex4: // ======================================================= // // Purpose: Sets the triangle's last vertex. // // ============================================================================== /** * @param newVertex * @uml.property name="vertex4" */ public void setVertex4(Vector3f newVertex) { vertex4.set(newVertex); recomputeNormal(); invalCache(CacheFlagsT.CacheFlagBounds); invalCache(CacheFlagsT.DisplayList); if (dragHandles != null) { dragHandles.get(3).setPosition(newVertex, false); } if (enclosingDirective() != null) enclosingDirective().setVertexesNeedRebuilding(); }// end setVertex4: // // #pragma mark - // #pragma mark ACTIONS // #pragma mark - // ========== dragHandleChanged: // ================================================ // // Purpose: One of the drag handles on our vertexes has changed. // // ============================================================================== public void dragHandleChanged(LDrawDragHandle sender) { LDrawDragHandle handle = (LDrawDragHandle) sender; Vector3f newPosition = handle.position(); int vertexNumber = handle.tag(); switch (vertexNumber) { case 1: setVertex1(newPosition); break; case 2: setVertex2(newPosition); break; case 3: setVertex3(newPosition); break; case 4: setVertex4(newPosition); break; } }// end dragHandleChanged: // ========== moveBy: // =========================================================== // // Purpose: Moves the receiver in the specified direction. // // ============================================================================== public void moveBy(Vector3f moveVector) { Vector3f newVertex1 = MatrixMath.V3Add(vertex1, moveVector); Vector3f newVertex2 = MatrixMath.V3Add(vertex2, moveVector); Vector3f newVertex3 = MatrixMath.V3Add(vertex3, moveVector); Vector3f newVertex4 = MatrixMath.V3Add(vertex4, moveVector); setVertex1(newVertex1); setVertex2(newVertex2); setVertex3(newVertex3); setVertex4(newVertex4); }// end moveBy: // #pragma mark - // #pragma mark UTILITIES // #pragma mark - // ========== fixBowtie // ========================================================= // // Purpose: Four points in any order define a quadrilateral, but if you want // to draw one in OpenGL, you need to be able to trace around the // edges in order. If two vertices are out of order, you wind up // with a "bowtie" shape, which needs to be corrected back into a // quadrilateral. // // 4 3 3 4 4 2 // +------+ +------+ + + // | | \ / |\ /| // | | \ / | \ / | // | | \/ | \/ | // | | /\ | /\ | // | | / \ | / \ | // | | / \ |/ \| // +------+ +------+ + + // 1 2 1 2 1 3 // // correct case 1 case 2 // switch 3 & 4 switch 2 & 3 // // ============================================================================== public void fixBowtie() { // If correct, the crosses of these three pairs should all point up. Vector3f vector1_2, vector1_4; // 1 to 2, 1 to 4 Vector3f vector3_4, vector3_2; Vector3f vector4_1, vector4_3; Vector3f cross1, cross3, cross4; vector1_2 = MatrixMath.V3Sub(vertex2, vertex1); vector1_4 = MatrixMath.V3Sub(vertex4, vertex1); vector3_4 = MatrixMath.V3Sub(vertex4, vertex3); vector3_2 = MatrixMath.V3Sub(vertex2, vertex3); vector4_1 = MatrixMath.V3Sub(vertex1, vertex4); vector4_3 = MatrixMath.V3Sub(vertex3, vertex4); cross1 = MatrixMath.V3Cross(vector1_2, vector1_4); cross3 = MatrixMath.V3Cross(vector3_4, vector3_2); cross4 = MatrixMath.V3Cross(vector4_1, vector4_3); // When crosses point different directions, we have a bowtie. To test // this, // recall that cos x = (u ??v) / (||u|| ||v||) // cos(180) = -1 and cos(0) = 1. So if u?�v is negative, we have opposing // vectors (since the denominator is always positive, we can ignore it). // If 1 & 4 point opposite directions, we have a case 1 bowtie if (MatrixMath.V3Dot(cross1, cross4) < 0) { // vectors point in opposite directions Vector3f swapPoint = vertex3; vertex3 = vertex4; vertex4 = swapPoint; } // If 3 & 4 point opposite directions, we have a case 2 bowtie else if (MatrixMath.V3Dot(cross3, cross4) < 0) { Vector3f swapPoint = vertex2; vertex2 = vertex3; vertex3 = swapPoint; } }// end fixBowtie // ========== flattenIntoLines:triangles:quadrilaterals:other:currentColor: // ===== // // Purpose: Appends the directive into the appropriate container. // // ============================================================================== public void flattenIntoLines(ArrayList<LDrawLine> lines, ArrayList<LDrawTriangle> triangles, ArrayList<LDrawQuadrilateral> quadrilaterals, ArrayList<LDrawDirective> everythingElse, LDrawColor parentColor, Matrix4 transform, Matrix3 normalTransform, boolean recursive) { super.flattenIntoLines(lines, triangles, quadrilaterals, everythingElse, parentColor, transform, normalTransform, recursive); vertex1 = MatrixMath.V3MulPointByProjMatrix(vertex1, transform); vertex2 = MatrixMath.V3MulPointByProjMatrix(vertex2, transform); vertex3 = MatrixMath.V3MulPointByProjMatrix(vertex3, transform); vertex4 = MatrixMath.V3MulPointByProjMatrix(vertex4, transform); normal = MatrixMath.V3MulPointByMatrix(normal, normalTransform); quadrilaterals.add(this); }// end flattenIntoLines:triangles:quadrilaterals:other:currentColor: // ========== recomputeNormal // =================================================== // // Purpose: Finds the normal vector for this surface. // // ============================================================================== public void recomputeNormal() { Vector3f vector1, vector2; vector1 = MatrixMath.V3Sub(vertex2, vertex1); vector2 = MatrixMath.V3Sub(vertex4, vertex1); normal = MatrixMath.V3Cross(vector1, vector2); }// end recomputeNormal // ========== registerUndoActions // =============================================== // // Purpose: Registers the undo actions that are unique to this subclass, // not to any superclass. // // ============================================================================== public void registerUndoActions(UndoManager undoManager) { super.registerUndoActions(undoManager); // todo // [[undoManager prepareWithInvocationTarget:self] setVertex3:vertex3]); // [[undoManager prepareWithInvocationTarget:self] setVertex2:vertex2]); // [[undoManager prepareWithInvocationTarget:self] setVertex1:vertex1]); // [undoManager setActionName:NSLocalizedString(@"UndoAttributesLine", // null)); }// end registerUndoActions: public void setToCW() { Vector3f tempVector = vertex4; vertex4 = vertex1; vertex1 = tempVector; tempVector = vertex3; vertex3 = vertex2; vertex2 = tempVector; recomputeNormal(); } }