package Command; import java.io.IOException; import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.HashMap; import java.util.StringTokenizer; import java.util.TreeSet; import javax.media.opengl.GL2; import javax.swing.undo.UndoManager; import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiNilLoader.Array; 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 Connectivity.CollisionBox; import Connectivity.Connectivity; import Connectivity.ConnectivityManager; import Connectivity.GlobalConnectivityManager; import Connectivity.Hole; import Connectivity.ICustom2DField; import Connectivity.MatrixItem; import Connectivity.Stud; import LDraw.Files.LDrawContainer; import LDraw.Files.LDrawFile; import LDraw.Files.LDrawModel; import LDraw.Files.LDrawStep; import LDraw.Support.ConnectivityLibrary; import LDraw.Support.DispatchGroup; import LDraw.Support.ILDrawObservable; import LDraw.Support.ILDrawObserver; import LDraw.Support.LDrawDirective; import LDraw.Support.LDrawGlobalFlag; import LDraw.Support.LDrawUtilities; import LDraw.Support.LDrawVertices; import LDraw.Support.MatrixMath; import LDraw.Support.ModelManager; import LDraw.Support.NSLock; import LDraw.Support.PartLibrary; import LDraw.Support.PartReport; import LDraw.Support.Range; import LDraw.Support.TransformComponents; import LDraw.Support.type.CacheFlagsT; import LDraw.Support.type.LDrawGridTypeT; import LDraw.Support.type.MessageT; import Notification.ILDrawSubscriber; import Notification.INotificationMessage; import Notification.NotificationMessageT; import Renderer.ILDrawRenderer; import Renderer.LDrawRenderColorT; public class LDrawPart extends LDrawDrawableElement implements ILDrawObserver, ILDrawSubscriber { public static final float SHRINK_AMOUNT = 0.125f; // in LDU private boolean isConnectivityInfoExist = true; /** * @uml.property name="displayName" */ String displayName; /** * @uml.property name="referenceName" */ String referenceName; // lower-case version of display name /** * @uml.property name="part data exist" */ boolean isPartDataExist = true; /** * @uml.property name="glTransformation" */ float glTransformation[]; /** * @uml.property name="cacheDrawable" * @uml.associationEnd */ LDrawDirective cacheDrawable; // The drawable is the model we link to OR a // VBO that represents it from the part // library -- a drawable proxy. /** * @uml.property name="cacheModel" * @uml.associationEnd */ LDrawModel cacheModel; // The model is the real model we link to. /** * @uml.property name="cacheType" * @uml.associationEnd */ PartTypeT cacheType; /** * @uml.property name="drawLock" * @uml.associationEnd */ NSLock drawLock; /** * @uml.property name="cacheBounds" * @uml.associationEnd */ Box3 cacheBounds = null; // Cached bonuding box of resolved parts, in part's // coordinate (that is, _not_ in the coordinates // of the // underlying model. Vector3f[] cachedOOB; Box3 boundsInIndentityTransform = null; Vector3f verticesForBoundsInIndentityTransform[] = null; float boundingBoxSize = 0; ArrayList<Connectivity> connectivityList = null; ArrayList<MatrixItem> connectivityMatrixItemList = null; ArrayList<CollisionBox> collisionBoxList = null; private boolean isDragingPart = false; public LDrawPart() { init(); } // ========== defaultIconName // =================================================== // // Purpose: The default icon name for this class. // // ============================================================================== public static String defaultIconName() { return "Brick"; } // end defaultIconName // // #pragma mark - // #pragma mark INITIALIZATION // #pragma mark - // ========== init // ============================================================== // // Purpose: Creates an empty part. // // ============================================================================== public LDrawPart init() { super.init(); glTransformation = new float[16]; setDisplayName(""); setIconName("Brick"); setTransformComponents(TransformComponents.getIdentityComponents()); color = new LDrawColor(); // drawLock = [[NSLock alloc] init); return this; }// end init // ========== initWithLines:inRange:parentGroup: // ================================ // // Purpose: Returns the LDraw directive based on lineFromFile, a single line // of LDraw code from a file. // // Line format: // 1 colour x y z a b c d e f g h i part.dat // // Matrix format: // +- -+ // | a d g 0 | // | b e h 0 | // | c f i 0 | // | x y z 1 | // +- -+ // // ============================================================================== public LDrawPart initWithLines(ArrayList<String> lines, Range range, DispatchGroup parentGroup) throws Exception { String workingLine = lines.get(range.getLocation()); String parsedField = null; Matrix4 transformation = Matrix4.getIdentityMatrix4(); LDrawColor parsedColor = null; float[][] elements = transformation.getElement(); 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) == 1) { // Read in the color code. // (color) parsedField = strTokenizer.nextToken(); parsedColor = LDrawUtilities.parseColorFromField(parsedField); setLDrawColor(parsedColor); // Read position. // (x) parsedField = strTokenizer.nextToken(); elements[3][0] = MatrixMath .round(Float.parseFloat(parsedField)); // (y) parsedField = strTokenizer.nextToken(); elements[3][1] = MatrixMath .round(Float.parseFloat(parsedField)); // (z) parsedField = strTokenizer.nextToken(); elements[3][2] = MatrixMath .round(Float.parseFloat(parsedField)); // Read Transformation X. // (a) parsedField = strTokenizer.nextToken(); elements[0][0] = MatrixMath .round(Float.parseFloat(parsedField)); // (b) parsedField = strTokenizer.nextToken(); elements[1][0] = MatrixMath .round(Float.parseFloat(parsedField)); // (c) parsedField = strTokenizer.nextToken(); elements[2][0] = MatrixMath .round(Float.parseFloat(parsedField)); // Read Transformation Y. // (d) parsedField = strTokenizer.nextToken(); elements[0][1] = MatrixMath .round(Float.parseFloat(parsedField)); // (e) parsedField = strTokenizer.nextToken(); elements[1][1] = MatrixMath .round(Float.parseFloat(parsedField)); // (f) parsedField = strTokenizer.nextToken(); elements[2][1] = MatrixMath .round(Float.parseFloat(parsedField)); // Read Transformation Z. // (g) parsedField = strTokenizer.nextToken(); elements[0][2] = MatrixMath .round(Float.parseFloat(parsedField)); // (h) parsedField = strTokenizer.nextToken(); elements[1][2] = MatrixMath .round(Float.parseFloat(parsedField)); // (i) parsedField = strTokenizer.nextToken(); elements[2][2] = MatrixMath .round(Float.parseFloat(parsedField)); // finish off the corner of the matrix. elements[3][3] = 1; setTransformationMatrix(transformation); // Read Part Name // (part.dat) -- It can have spaces (for MPD models), so we just // use the whole // rest of the line. if (strTokenizer.hasMoreTokens()) { String partName = strTokenizer.nextToken(); while (strTokenizer.hasMoreTokens()) { partName += " " + strTokenizer.nextToken(); } setDisplayName(partName, true, parentGroup); } // Debug check: full part resolution isn't thread-safe so make // sure we haven't run it by accident here! assert (cacheType == PartTypeT.PartTypeUnresolved); } else throw new Exception("BricksmithParseException: " + "Bad part syntax"); } catch (IOException e) { System.out.println(String.format("the part %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: Parts draw by pushing the matrix and color instancing info they // contain into the renderer, then passing drawSelf to the model // backing the part, if it exists. // // ================================================================================ public void drawSelf(GL2 gl2, ILDrawRenderer renderer) { if (hidden == false) { boolean isPushWire = false; resolvePart(); if (cacheModel != null) { LDrawColorT colorCode = color.getColorCode(); if (colorCode != LDrawColorT.LDrawCurrentColor) { // Old rendering code did not actually support // pushing the edge color as the new current // color - and it's probably against spec. But // it's not really the place of drawSelf to go // slappign wrists, so pass it to the render, // which actually DOES know how to get this case // right. if (colorCode == LDrawColorT.LDrawEdgeColor) renderer.pushColor(LDrawRenderColorT.LDrawRenderComplimentColor .getValue()); else { float c[] = new float[4]; color.getColorRGBA(c); renderer.pushColor(c); } } if (isSelected() == true) { renderer.pushWireFrame(gl2); isPushWire = true; } renderer.pushMatrix(glTransformation); if (cacheModel != null) cacheModel.drawSelf(gl2, renderer); renderer.popMatrix(); if (LDrawGlobalFlag.SHRINK_SEAMS != false) renderer.popMatrix(); if (colorCode != LDrawColorT.LDrawCurrentColor) renderer.popColor(); if (isPushWire) renderer.popWireFrame(gl2); } } }// end drawSelf: public void getRange(Matrix4 transform, float[] range) { if (hidden == false) { Box3 bounds = boundingBox3(); if (bounds == null) return; compareRange(range, bounds.getMax()); compareRange(range, bounds.getMin()); // Matrix4 partTransform = transformationMatrix(); // Matrix4 combinedTransform = MatrixMath.Matrix4Multiply( // partTransform, transform); // // resolvePart(); // // If we are doing a bounds test, for now get the model, not the // VBO // // - VBO bounds test does not yet exist // // (which is not so good). For parent-color parts we HAVE to get // the // // model - there is no VBO! // LDrawDirective modelToDraw = (cacheDrawable == null) ? cacheModel // : cacheDrawable; // // if (modelToDraw != null) { // modelToDraw.getRange(combinedTransform, range); // } } } // ========== hitTest:transform:viewScale:creditObject:hits: ======= // // Purpose: Hit-test the geometry. // // ============================================================================== public void hitTest(Ray3 pickRay, Matrix4 transform, LDrawDirective creditObject, HashMap<LDrawDirective, Float> hits) { if (hidden == false) { Matrix4 partTransform = transformationMatrix(); Matrix4 combinedTransform = MatrixMath.Matrix4Multiply( partTransform, transform); Box3 bounds = boundingBox3(combinedTransform); if (bounds == null) return; // if (boundingBoxSize > 100) if (MatrixMath.V3RayIntersectsBox3(pickRay, bounds, null, null) == false) { // System.out.println(displayName + ": " + boundingBoxSize); return; } // Credit all subgeometry to ourselves (unless we are already a // child part) if (creditObject == null) { creditObject = this; } resolvePart(); // If we are doing a bounds test, for now get the model, not the VBO // - VBO bounds test does not yet exist // (which is not so good). For parent-color parts we HAVE to get the // model - there is no VBO! LDrawDirective modelToDraw = (cacheDrawable == null) ? cacheModel : cacheDrawable; if (modelToDraw != null) { modelToDraw.hitTest(pickRay, combinedTransform, creditObject, hits); } } }// end hitTest:transform:viewScale:creditObject:hits: // ========== boxTest:transform: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) { if (!MatrixMath.VolumeCanIntersectBox(boundingBox3(), transform, bounds)) { return false; } Matrix4 partTransform = transformationMatrix(); Matrix4 combinedTransform = MatrixMath.Matrix4Multiply( partTransform, transform); LDrawDirective modelToDraw = null; // Credit all subgeometry to ourselves (unless we are already a // child part) if (creditObject == null) { creditObject = this; } resolvePart(); modelToDraw = cacheModel; if (boundsOnly == false) { if (modelToDraw.boxTest(bounds, combinedTransform, false, creditObject, hits)) if (creditObject != null) return true; } else { // Hit test the bounding cube LDrawVertices unitCube = LDrawUtilities.boundingCube(); Box3 bounds_3d = modelToDraw.boundingBox3(); Vector3f extents = MatrixMath.V3Sub(bounds_3d.getMax(), bounds_3d.getMin()); Matrix4 boxTransform = Matrix4.getIdentityMatrix4(); // Expand and position the unit cube to match the model boxTransform = MatrixMath.Matrix4Scale(boxTransform, extents); boxTransform = MatrixMath.Matrix4Translate(boxTransform, bounds_3d.getMin()); combinedTransform = MatrixMath.Matrix4Multiply(boxTransform, combinedTransform); if (unitCube.boxTest(bounds, combinedTransform, false, creditObject, hits)) if (creditObject != null) return true; } } return false; }// end boxTest:transform:creditObject:hits: // ========== depthTest: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 testPt, Box2 bounds, Matrix4 transform, LDrawDirective creditObject, ArrayList<LDrawDirective> bestObject, FloatBuffer bestDepth) { if (hidden == false) { if (!MatrixMath.VolumeCanIntersectPoint(boundingBox3(), transform, bounds, bestDepth.get(0))) return; Matrix4 partTransform = transformationMatrix(); Matrix4 combinedTransform = MatrixMath.Matrix4Multiply( partTransform, transform); LDrawModel modelToDraw = null; // Credit all subgeometry to ourselves (unless we are already a // child part) if (creditObject == null) { creditObject = this; } resolvePart(); modelToDraw = cacheModel; modelToDraw.depthTest(testPt, bounds, combinedTransform, creditObject, bestObject, bestDepth); } }// end depthTest:transform:creditObject:bestObject:bestDepth: // ========== write // ============================================================= // // Purpose: Returns a line that can be written out to a file. // // Line format: // 1 colour x y z a b c d e f g h i part.dat // // Matrix format: // +- -+ // | a d g 0 | // | b e h 0 | // | c f i 0 | // | x y z 1 | // +- -+ // // ============================================================================== public String write() { Matrix4 transformation = transformationMatrix(); float[][] elements = transformation.getElement(); return String.format("1 %s %s %s %s %s %s %s %s %s %s %s %s %s %s", LDrawUtilities.outputStringForColor(color), LDrawUtilities.outputStringForFloat(elements[3][0]), // position.x, // (x) LDrawUtilities.outputStringForFloat(elements[3][1]), // position.y, // (y) LDrawUtilities.outputStringForFloat(elements[3][2]), // position.z, // (z) LDrawUtilities.outputStringForFloat(elements[0][0]), // transformationX.x, // (a) LDrawUtilities.outputStringForFloat(elements[1][0]), // transformationX.y, // (b) LDrawUtilities.outputStringForFloat(elements[2][0]), // transformationX.z, // (c) LDrawUtilities.outputStringForFloat(elements[0][1]), // transformationY.x, // (d) LDrawUtilities.outputStringForFloat(elements[1][1]), // transformationY.y, // (e) LDrawUtilities.outputStringForFloat(elements[2][1]), // transformationY.z, // (f) LDrawUtilities.outputStringForFloat(elements[0][2]), // transformationZ.x, // (g) LDrawUtilities.outputStringForFloat(elements[1][2]), // transformationZ.y, // (h) LDrawUtilities.outputStringForFloat(elements[2][2]), // transformationZ.z, // (i) displayName); }// end write // #pragma mark - // #pragma mark DISPLAY // #pragma mark - // ========== browsingDescription // =============================================== // // Purpose: Returns a representation of the directive as a short string // which can be presented to the user. // // Here we want the part name displayed. // // ============================================================================== public String browsingDescription() { return PartLibrary.sharedPartLibrary().descriptionForPart(this); }// end browsingDescription // ========== inspectorClassName // ================================================ // // Purpose: Returns the name of the class used to inspect this one. // // ============================================================================== public String inspectorClassName() { return "InspectionPart"; }// end inspectorClassName // #pragma mark - // #pragma mark ACCESSORS // #pragma mark - public Box3 boundingBox3(Matrix4 transform) { Box3 bounds = null; int counter; Vector3f vertices[] = new Vector3f[8]; if (verticesForBoundsInIndentityTransform == null) boundingBox3(); if (verticesForBoundsInIndentityTransform == null) return Box3.getInvalidBox(); for (counter = 0; counter < 8; counter++) { if (verticesForBoundsInIndentityTransform[counter] != null) vertices[counter] = transform .transformPoint(verticesForBoundsInIndentityTransform[counter]); else vertices[counter] = transform.transformPoint(new Vector3f()); bounds = MatrixMath.V3UnionBoxAndPoint(bounds, vertices[counter]); } return bounds; } public Box3 boundingBox3Copy() { try { return (Box3) boundingBox3().clone(); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public Vector3f[] getCachedOOB() { // resolvePart(); // if (revalCache(CacheFlagsT.CacheFlagBounds) == // CacheFlagsT.CacheFlagBounds) boundingBox3(); return cachedOOB; } // ========== boundingBox3 // ====================================================== // // Purpose: Returns the minimum and maximum points of the box which // perfectly contains this object. Returns InvalidBox if the part // cannot be found. // // ============================================================================== public synchronized Box3 boundingBox3() { resolvePart(); if (revalCache(CacheFlagsT.CacheFlagBounds) == CacheFlagsT.CacheFlagBounds) { // if (boundsInIndentityTransform == null) // boundsInIndentityTransform = // getBoundingBoxFromConnectivityFile(); if (boundsInIndentityTransform == null) boundsInIndentityTransform = getBoundingsFromModel(); if (boundsInIndentityTransform == null) boundsInIndentityTransform = Box3.getInvalidBox(); Box3 bounds = null; if (boundsInIndentityTransform != Box3.getInvalidBox()) { setPartDataExist(true); // Transform all the points of the bounding box to find // the // new // minimum and maximum. if (verticesForBoundsInIndentityTransform == null) { Vector3f min = boundsInIndentityTransform.getMin(); Vector3f max = boundsInIndentityTransform.getMax(); verticesForBoundsInIndentityTransform = new Vector3f[] { new Vector3f(new float[] { min.getX(), min.getY(), min.getZ() }), new Vector3f(new float[] { min.getX(), min.getY(), max.getZ() }), new Vector3f(new float[] { min.getX(), max.getY(), max.getZ() }), new Vector3f(new float[] { min.getX(), max.getY(), min.getZ() }), new Vector3f(new float[] { max.getX(), min.getY(), min.getZ() }), new Vector3f(new float[] { max.getX(), min.getY(), max.getZ() }), new Vector3f(new float[] { max.getX(), max.getY(), max.getZ() }), new Vector3f(new float[] { max.getX(), max.getY(), min.getZ() }), }; } int counter = 0; Matrix4 transformation = transformationMatrix(); Vector3f vertices[] = new Vector3f[8]; for (counter = 0; counter < 8; counter++) { vertices[counter] = transformation .transformPoint(verticesForBoundsInIndentityTransform[counter]); bounds = MatrixMath.V3UnionBoxAndPoint(bounds, vertices[counter]); } cachedOOB = vertices; cacheBounds = bounds; Vector3f temp = bounds.getMax().sub(bounds.getMin()); boundingBoxSize = temp.dot(temp); } else { setPartDataExist(false); Matrix4 transformation = transformationMatrix(); cacheBounds = new Box3(); cacheBounds.setMax(transformation.getDefaultTransformPos()); cacheBounds.setMin(transformation.getDefaultTransformPos()); if (cachedOOB == null) { cachedOOB = new Vector3f[8]; for (int i = 0; i < 8; i++) cachedOOB[i] = transformation.getDefaultTransformPos(); } for (int i = 0; i < 8; i++) cachedOOB[i] = transformation.getDefaultTransformPos(); } } return cacheBounds; }// end boundingBox3 private Box3 getBoundingsFromModel() { LDrawModel modelToDraw = cacheModel; Box3 bounds = Box3.getInvalidBox(); // We need to have an actual model here. Blithely calling // boundingBox3 will // result in most of our Box3 structure being garbage data! if (modelToDraw != null && hidden == false) { bounds = modelToDraw.boundingBox3(); } return bounds; } // // private Box3 getBoundingBoxFromConnectivityFile() { // Box3 retBox = new Box3(); // // retBox.setMin(new Vector3f(3.40282347E+38f, 3.40282347E+38f, // 3.40282347E+38f)); // retBox.setMax(new Vector3f(-3.40282347E+38f, -3.40282347E+38f, // -3.40282347E+38f)); // // ArrayList<CollisionBox> boxes = getCollisionBoxList(); // if (boxes == null || boxes.size() == 0) // return null; // // for (int j = 0; j < boxes.size(); j++) { // CollisionBox collisionBox = boxes.get(j); // // Vector3f[] boxPos = new Vector3f[8]; // boxPos[0] = new Vector3f(-collisionBox.getsX(), // -collisionBox.getsY(), -collisionBox.getsZ()); // boxPos[1] = new Vector3f(collisionBox.getsX(), // -collisionBox.getsY(), -collisionBox.getsZ()); // boxPos[2] = new Vector3f(collisionBox.getsX(), // -collisionBox.getsY(), collisionBox.getsZ()); // boxPos[3] = new Vector3f(-collisionBox.getsX(), // -collisionBox.getsY(), collisionBox.getsZ()); // boxPos[4] = new Vector3f(-collisionBox.getsX(), // collisionBox.getsY(), -collisionBox.getsZ()); // boxPos[5] = new Vector3f(collisionBox.getsX(), // collisionBox.getsY(), -collisionBox.getsZ()); // boxPos[6] = new Vector3f(collisionBox.getsX(), // collisionBox.getsY(), collisionBox.getsZ()); // boxPos[7] = new Vector3f(-collisionBox.getsX(), // collisionBox.getsY(), collisionBox.getsZ()); // // for (int k = 0; k < 8; k++) { // boxPos[k] = boxPos[k].scale(1.03f); // boxPos[k] = collisionBox.getTransformMatrix().transformPoint( // boxPos[k]); // // if (retBox.min.x > boxPos[k].x) // retBox.min.x = boxPos[k].x; // if (retBox.min.y > boxPos[k].y) // retBox.min.y = boxPos[k].y; // if (retBox.min.z > boxPos[k].z) // retBox.min.z = boxPos[k].z; // // if (retBox.max.x < boxPos[k].x) // retBox.max.x = boxPos[k].x; // if (retBox.max.y < boxPos[k].y) // retBox.max.y = boxPos[k].y; // if (retBox.max.z < boxPos[k].z) // retBox.max.z = boxPos[k].z; // } // } // // return retBox; // } // ========== displayName // ======================================================= // // Purpose: Returns the name of the part as the user typed it. This // maintains the user's upper- and lower-case usage. // // ============================================================================== public String displayName() { return displayName; }// end displayName public boolean isPartDataExist() { return isPartDataExist; } public boolean isDraggingPart() { return isDragingPart; } public void isDraggingPart(boolean isDragingPart) { this.isDragingPart = isDragingPart; } public boolean isConnectivityInfoExist() { if (isConnectivityInfoExist == true) if (connectivityList == null) { isConnectivityInfoExist = ConnectivityLibrary.getInstance() .hasConnectivity(displayName); } else if (connectivityList.isEmpty()) isConnectivityInfoExist = false; return isConnectivityInfoExist; } public void setPartDataExist(boolean exist) { isPartDataExist = exist; } // ========== position // ========================================================== // // Purpose: Returns the coordinates at which the part is drawn. // // Notes: This is purely a convenience method. The actual position is // encoded in the transformation matrix. If you wish to set the // position, you should set either the matrix or the Transformation // Components. // // ============================================================================== public Vector3f position() { TransformComponents components = transformComponents(); Vector3f position = components.getTranslate(); return position; }// end position /* * To work, this needs to multiply the modelViewGLMatrix by the part * transform. * * //========== projectedBoundingBoxWithModelView:projection:view: * ================ // // Purpose: Returns the 2D projection (ignore the z) * of the object's bounds. // * //============================================= * ================================= - (Box3) * projectedBoundingBoxWithModelView:(Matrix4)modelView * projection:(Matrix4)projection view:(Box2)viewport; { LDrawModel * *modelToDraw = PartLibrary.sharedPartLibrary().modelForPart:this); Box3 * projectedBounds = InvalidBox; * * projectedBounds = [modelToDraw * projectedBoundingBoxWithModelView:modelViewGLMatrix * projection:projectionGLMatrix view:viewport); * * return projectedBounds; * * }//end projectedBoundingBoxWithModelView:projection:view: */ // ========== referenceName // ===================================================== // // Purpose: Returns the name of the part. This is the filename where the // part is found. Since Macintosh computers are case-insensitive, // I have adopted lower-case as the standard for names. // // ============================================================================== public String referenceName() { return referenceName; }// end referenceName // ========== referencedMPDSubmodel // ============================================= // // Purpose: Returns the MPD model to which this part refers, or null if // there // is no submodel in this part's file which has the name this part // specifies. // // Note: This method is ONLY intended to be used for resolving MPD // references. If you want to resolve the general reference, you // should call -modelForPart: in the PartLibrary! // // ============================================================================== public LDrawModel referencedMPDSubmodel() { LDrawModel model = null; LDrawFile enclosingFile = enclosingFile(); if (enclosingFile != null) model = (LDrawModel) enclosingFile.modelWithName(referenceName); // No can do if we get a reference back to ourselves. That would be // an infinitely-recursing reference, which is bad! if (enclosingStep() != null) if (model == enclosingStep().enclosingModel()) model = null; return model; }// end referencedMPDSubmodel // ========== transformComponents // =============================================== // // Purpose: Returns the individual components of the transformation matrix // applied to this part. // // ============================================================================== public TransformComponents transformComponents() { Matrix4 transformation = transformationMatrix(); TransformComponents components = TransformComponents .getIdentityComponents(); // This is a pretty darn neat little function. I wish I could say I // wrote it. // It will extract all the user-friendly components out of this nasty // matrix. MatrixMath.Matrix4DecomposeTransformation(transformation, components); return components; }// end transformComponents // ========== transformationMatrix // ============================================== // // Purpose: Returns a two-dimensional (row matrix) representation of the // part's transformation matrix. // // +- -+ // +- -+ +- -+| a d g 0 | // |a d g 0 b e h c f i 0 x y z 1| --> |x y z 1|| b e h 0 | // +- -+ +- -+| c f i 0 | // | x y z 1 | // +- -+ // OpenGL Matrix Format LDraw Matrix // (flat column-major of transpose) Format // // ============================================================================== public Matrix4 transformationMatrix() { return MatrixMath.Matrix4CreateFromGLMatrix4(glTransformation); }// end transformationMatrix // #pragma mark - // ========== setEnclosingDirective: // ============================================ // ============================================================================== public void setEnclosingDirective(LDrawContainer newParent) { unresolvePart(); super.setEnclosingDirective(newParent); } // ========== setLDrawColor: // ==================================================== // // Purpose: Sets the color of this element. // // ============================================================================== public void setLDrawColor(LDrawColor newColor) { super.setLDrawColor(newColor); unresolvePart(); invalCache(CacheFlagsT.CacheFlagBounds); }// end setLDrawColor: // ========== setDisplayName: // =================================================== // // Purpose: Updates the name of the part and attempts to load it into the // part library. // // ============================================================================== public void setDisplayName(String newPartName) { setDisplayName(newPartName, true, new DispatchGroup()); } // ========== setDisplayName:parse:inGroup: // ===================================== // // Purpose: Updates the name of the part. This is the filename where the // part is found. // // If shouldParse istrue, pre-loads the referenced part if // possible. Pre-loading is very import in initial model loading, // because it enables structual optimizations to be performed prior // to OpenGL optimizations. It also results in a more honest load // progress bar. // // Notes: References to LDraw/parts and LDraw/p are simply encoded as the // file name. However, references to LDraw/parts/s are encoded as // "s\partname.dat". The part library, meanwhile, must properly // handle the s\ prefix. // // ============================================================================== /** * @param newPartName * @param shouldParse * @param parentGroup * @uml.property name="displayName" */ public void setDisplayName(String newPartName, boolean shouldParse, DispatchGroup parentGroup) { String newReferenceName = newPartName.toLowerCase(); DispatchGroup parseGroup = null; displayName = newPartName; referenceName = newReferenceName; assert (parentGroup == null || cacheType == PartTypeT.PartTypeUnresolved); unresolvePart(); // Force the part library to parse the model this part will display. // This // pushes all parsing into the same operation, which improves loading // time // predictability and allows better potential threading optimization. // // Ben says: we _have_ to call this, even on MPD and peer models. Since // we don't know what kind of thing we are, checking the cache type will // always return unresolved. But I don't think I want to force-resolve // here - resolving later prevents thrash. if (shouldParse == true && newPartName != null && newPartName.length() > 0) { parseGroup = new DispatchGroup(); parseGroup.extendsFromParent(parentGroup); if (transformationMatrix().getDet() < 0) parseGroup.setReversed(); referenceName = newPartName + (parseGroup.isCCW() ? "" : "_CW"); PartLibrary.sharedPartLibrary().loadModelForName(displayName, referenceName, parseGroup); } }// end setDisplayName: // ========== setTransformComponents: // =========================================== // // Purpose: Converts the given componets (rotation, scaling, etc.) into an // internal transformation matrix represenation. // // ============================================================================== public void setTransformComponents(TransformComponents newComponents) { Matrix4 transformation = MatrixMath .Matrix4CreateTransformation(newComponents); setTransformationMatrix(transformation); }// end setTransformComponents: // ========== setTransformationMatrix: // ========================================== // // Purpose: Converts the row-major row-vector matrix into a flat column- // major column-vector matrix understood by OpenGL. // // // +- -+ +- -++- -+ // +- -+| a d g 0 | | a b c x || x | // |x y z 1|| b e h 0 | | d e f y || y | +- -+ // +- -+| c f i 0 | --> | g h i z || z | --> |a d g 0 b e h c f i 0 x y z 1| // | x y z 1 | | 0 0 0 1 || 1 | +- -+ // +- -+ +- -++- -+ // LDraw Matrix Transpose OpenGL Matrix Format // Format (flat column-major of transpose) // (also Matrix4 format) // // ============================================================================== public void setTransformationMatrix(Matrix4 newMatrix) { invalCache(CacheFlagsT.CacheFlagBounds); MatrixMath.Matrix4GetGLMatrix4(newMatrix, glTransformation); sendMessageToObservers(MessageT.MessageObservedChanged); }// end setTransformationMatrix // ========== setSelected: // ====================================================== // // Purpose: Somebody make this a protocol method. // // ============================================================================== public void setSelected(boolean flag) { super.setSelected(flag); // would like LDrawContainer to be a protocol. In its absence... LDrawDirective enclosingDirective = enclosingDirective(); if (enclosingDirective != null) { if (LDrawContainer.class.isInstance(enclosingDirective)) ((LDrawContainer) enclosingDirective) .setSubdirectiveSelected(flag); } }// end setSelected: // #pragma mark - // #pragma mark MOVEMENT // #pragma mark - // ========== displacementForNudge: // ============================================= // // Purpose: Returns the amount by which the element wants to move, given a // "nudge" in the specified direction. A "nudge" is generated by // pressing the arrow keys. We scale this value so as to make // nudging go by plate-heights vertically and brick widths // horizontally. // // ============================================================================== public Vector3f displacementForNudge(Vector3f nudgeVector) { Matrix4 transformationMatrix = Matrix4.getIdentityMatrix4(); Matrix4 inverseMatrix = Matrix4.getIdentityMatrix4(); Vector4f worldNudge = new Vector4f(new float[] { 0f, 0f, 0f, 1f }); Vector4f brickNudge = new Vector4f(); // convert incoming 3D vector to 4D for our math: worldNudge.setX(nudgeVector.getX()); worldNudge.setY(nudgeVector.getY()); worldNudge.setZ(nudgeVector.getZ()); // Figure out which direction we're asking to move the part itthis. transformationMatrix = transformationMatrix(); inverseMatrix = MatrixMath.Matrix4Invert(transformationMatrix); float[][] elements = inverseMatrix.getElement(); elements[3][0] = 0; // zero out the translation part, leaving only // rotation etc. elements[3][1] = 0; elements[3][2] = 0; // See if this is a nudge along the brick's "up" direction. // If so, the nudge needs to be a different magnitude, to compensate // for the fact that Lego bricks are not square! brickNudge = MatrixMath.V4MulPointByMatrix(worldNudge, inverseMatrix); if (Math.abs(brickNudge.getY()) > Math.abs(brickNudge.getX()) && Math.abs(brickNudge.getY()) > Math.abs(brickNudge.getZ())) { // The trouble is, we need to do different things for different // scales. For instance, in medium mode, we probably want to // move 1/2 stud horizontally but 1/3 stud vertically. // // But in coarse mode, we want to move 1 stud horizontally and // vertically. These are different ratios! So I test for known // numbers, and only apply modifications if they are recognized. if (nudgeVector.getX() % 20 == 0) nudgeVector.setX(nudgeVector.getX() * 24.0f / 20.0f); else if (nudgeVector.getX() % 10 == 0) nudgeVector.setX(nudgeVector.getX() * 8.0f / 10.0f); if (nudgeVector.getY() % 20 == 0) nudgeVector.setY(nudgeVector.getY() * 24.0f / 20.0f); else if (nudgeVector.getY() % 10 == 0) nudgeVector.setY(nudgeVector.getY() * 8.0f / 10.0f); if (nudgeVector.getZ() % 20 == 0) nudgeVector.setZ(nudgeVector.getZ() * 24.0f / 20.0f); else if (nudgeVector.getZ() % 10 == 0) nudgeVector.setZ(nudgeVector.getZ() * 8.0f / 10.0f); } // we now have a nudge based on the correct size: plates or bricks. return nudgeVector; }// end displacementForNudge: // ========== componentsSnappedToGrid:minimumAngle: // ============================= // // Purpose: Returns a copy of the part's current components, but snapped to // the grid. Kinda a weird legacy API. // // ============================================================================== public TransformComponents componentsSnappedToGrid(float gridSpacing, float degrees) { TransformComponents components = transformComponents(); return components(components, gridSpacing, degrees); }// end componentsSnappedToGrid:minimumAngle: // ========== components:snappedToGrid:minimumAngle: // ============================ // // Purpose: Aligns the given components to an imaginary grid along lines // separated by a distance of gridSpacing. This is done // intelligently based on the current orientation of the receiver: // if gridSpacing == 20, that is assumed to mean "1 stud," so the // y-axis (up) of the part will be aligned along a grid spacing of // 24 (1 stud vertically). // // The part's rotation angles will be adjusted to multiples of the // minimum angle specified. // // Parameters: components - transform to adjust. // gridSpacing - the grid line interval along stud widths. // degrees - angle granularity. Pass 0 to leave angle // unchanged. // // ============================================================================== public TransformComponents components(TransformComponents components, float gridSpacing, float degrees) { float rotationRadians = (float) Math.toRadians(degrees); Matrix4 transformationMatrix = Matrix4.getIdentityMatrix4(); Vector4f yAxisOfPart = new Vector4f(new float[] { 0, 1, 0, 1 }); Vector4f worldY = new Vector4f(new float[] { 0, 0, 0, 1 }); // yAxisOfPart // converted // to world // coordinates Vector3f worldY3 = new Vector3f(new float[] { 0, 0, 0 }); float gridSpacingYAxis = 0.0f; float gridX = 0.0f; float gridY = 0.0f; float gridZ = 0.0f; // ---------- Adjust position to grid // --------------------------------------- // Figure out which direction the y-axis is facing in world coordinates: transformationMatrix = transformationMatrix(); float[][] elements = transformationMatrix.getElement(); elements[3][0] = 0; // zero out the translation part, leaving only // rotation etc. elements[3][1] = 0; elements[3][2] = 0; worldY = MatrixMath.V4MulPointByMatrix(yAxisOfPart, transformationMatrix()); worldY3 = MatrixMath.V3FromV4(worldY); worldY3 = MatrixMath.V3IsolateGreatestComponent(worldY3); worldY3 = MatrixMath.V3Normalize(worldY3); // Get the adjusted grid spacing along the y direction. Remember that // Lego // bricks are not cubical, so the grid along the brick's y-axis should // be // spaced differently from the grid along its other sides. gridSpacingYAxis = gridSpacing; if (gridSpacing % 20 == 0) gridSpacingYAxis *= 24.0 / 20.0; else if (gridSpacing % 10 == 0) gridSpacingYAxis *= 8.0 / 10.0; // The actual grid spacing, in world coordinates. We will adjust the // approrpiate // x, y, or z based on which one the part's y-axis is aligned. gridX = gridSpacing; gridY = gridSpacing; gridZ = gridSpacing; // Find the direction of the part's Y-axis, and change its grid. if (MatrixMath.compareFloat(worldY3.getX(), 0f) != 0) gridX = gridSpacingYAxis; if (MatrixMath.compareFloat(worldY3.getY(), 0f) != 0) gridY = gridSpacingYAxis; if (MatrixMath.compareFloat(worldY3.getZ(), 0f) != 0) gridZ = gridSpacingYAxis; // Snap to the Grid! // Figure the closest grid line and bump the part to it. // Logically, this is a rounding operation with a granularity of the // grid // size. So all we need to do is normalize, round, then expand back to // the // original size. components.getTranslate().setX( Math.round(components.getTranslate().getX() / gridX) * gridX); components.getTranslate().setX( Math.round(components.getTranslate().getY() / gridY) * gridY); components.getTranslate().setX( Math.round(components.getTranslate().getZ() / gridZ) * gridZ); // ---------- Snap angles // --------------------------------------------------- if (rotationRadians != 0) { components.getRotate().setX( Math.round(components.getRotate().getX() / rotationRadians) * rotationRadians); components.getRotate().setY( Math.round(components.getRotate().getY() / rotationRadians) * rotationRadians); components.getRotate().setZ( Math.round(components.getRotate().getZ() / rotationRadians) * rotationRadians); } // round-off errors here? Potential for trouble. return components; }// end components:snappedToGrid:minimumAngle: // ========== moveBy: // =========================================================== // // Purpose: Moves the receiver in the specified direction. // // ============================================================================== public boolean moveBy(Vector3f moveVector, LDrawGridTypeT type) { return moveBy(moveVector, type, true); }// end moveBy: public boolean moveBy(Vector3f moveVector, LDrawGridTypeT type, boolean useSnap) { if (useSnap) moveVector = LDrawGridTypeT.getSnappedPos(moveVector, type); Matrix4 transformationMatrix = transformationMatrix(); // I NEED to modify the matrix itself here. Some parts have funky, // fragile // rotation values, and getting the components really badly botches them // up. transformationMatrix = MatrixMath.Matrix4Translate( transformationMatrix, moveVector); setTransformationMatrix(transformationMatrix); sendMessageToObservers(MessageT.MessageObservedChanged); if (MatrixMath.V3EqualPoints(moveVector, new Vector3f(0, 0, 0))) return false; return true; }// end moveBy: public void moveTo(Vector3f moveVector, LDrawGridTypeT type) { Matrix4 transformationMatrix = transformationMatrix(); // I NEED to modify the matrix itself here. Some parts have funky, // fragile // rotation values, and getting the components really badly botches them // up. float[][] t = transformationMatrix.getElement(); t[3][0] = Math.round(moveVector.getX() / type.getXZValue()) * type.getXZValue(); t[3][1] = Math.round(moveVector.getY() / type.getYValue()) * type.getYValue(); t[3][2] = Math.round(moveVector.getZ() / type.getXZValue()) * type.getXZValue(); setTransformationMatrix(transformationMatrix); sendMessageToObservers(MessageT.MessageObservedChanged); }// end moveBy: // ========== position:snappedToGrid: // =========================================== // // Purpose: Orients position at discrete points separated by the given grid // spacing. // // Notes: This method may be overridden by subclasses to provide more // intelligent grid alignment. // // This method is provided mainly as a service to drag-and-drop. // In the case of LDrawParts, you should generally avoid this // method in favor of // -[LDrawPart components:snappedToGrid:minimumAngle:]. // // ============================================================================== public Vector3f position(Vector3f position, float gridSpacing) { TransformComponents components = TransformComponents .getIdentityComponents(); // copy the position into a transform components.setTranslate(position); // Snap to grid using intelligent LDrawPart logic components = components(components, gridSpacing, 0); // copy the new position back out of the components position = components.getTranslate(); return position; }// end position:snappedToGrid: public void rotateByDegrees(float angle, Vector3f rotationVector, Vector3f rotationCenter) { Matrix4 transform = transformationMatrix(); Vector3f displacement = rotationCenter; Vector3f negativeDisplacement = MatrixMath.V3Negate(rotationCenter); // Do the rotation around the specified centerpoint. transform = MatrixMath .Matrix4Translate(transform, negativeDisplacement); // translate transform.rotate((float) Math.toRadians(angle), rotationVector); // rotationCenter transform = MatrixMath.Matrix4Translate(transform, displacement); // translate // back // to // original // position setTransformationMatrix(transform); sendMessageToObservers(MessageT.MessageObservedChanged); }// end rotateByDegrees:centerPoint: // #pragma mark - // #pragma mark OBSERVER // #pragma mark - // ========== observableSaysGoodbyeCruelWorld: // ================================== // // Purpose: // // ============================================================================== public void observableSaysGoodbyeCruelWorld( ILDrawObservable doomedObservable) { if (cacheType == PartTypeT.PartTypeUnresolved || cacheType == PartTypeT.PartTypeNotFound) System.out .println("WARNING: LDraw part is receiving a notification that its observer is dying but it thinks it should have no observer.\n"); if (doomedObservable != cacheModel) System.out .println("WARNING: LDraw part is receiving a notification from an observer that is not its cached drawable.\n"); unresolvePart(); } // ========== statusInvalidated:who: // ============================================ // // Purpose: This message is sent to us when a directive we are observing is // invalidated. We invalidate ourselves. This is what makes our // bbox need recalculating when a sub-model changes. // // ============================================================================== public void statusInvalidated(CacheFlagsT flags, ILDrawObservable observable) { invalCache(CacheFlagsT.CacheFlagBounds); }// end statusInvalidated:who: // ========== receiveMessage:who: // =============================================== // // Purpose: // // ============================================================================== public void receiveMessage(MessageT msg, ILDrawObservable observable) { switch (msg) { case MessageNameChanged: case MessageScopeChanged: case MessageObservedChanged: unresolvePart(); break; } } // #pragma mark - // #pragma mark UTILITIES // #pragma mark - // ========== containsReferenceTo: // ============================================== // // Purpose: Returns if the part references a model with the given name. This // is used by containers to detect circular references. // // ============================================================================== public boolean containsReferenceTo(String name) { boolean isMatch = referenceName.equals(name); return isMatch; } // ========== partIsMissing // ===================================================== // // Purpose: Identifies whether the part cannot be found in any known places // to look for it. // // ============================================================================== public boolean partIsMissing() { resolvePart(); return cacheType == PartTypeT.PartTypeNotFound; } // #pragma mark - // ========== flattenIntoLines:triangles:quadrilaterals:other:currentColor: // ===== // // Purpose: Appends the directive into the appropriate container. // // ============================================================================== public void flattenIntoLines(ArrayList<LDrawLine> lines, ArrayList<LDrawTriangle> triangles, ArrayList<LDrawQuadrilateral> quadriaterals, ArrayList<LDrawDirective> everythingElse, LDrawColor parentColor, Matrix4 transform, Matrix3 normalTransform, boolean recursive) { LDrawModel modelToDraw = null; LDrawModel flatCopy = null; Matrix4 partTransform = transformationMatrix(); Matrix4 combinedTransform = null; // Nonrecursive flattenings are just trying to collect the primitives. // Parts // should be completely ignored. if (recursive == true) { super.flattenIntoLines(lines, triangles, quadriaterals, everythingElse, parentColor, transform, normalTransform, recursive); // Flattening involves applying the part's transform to copies of // all // referenced vertices. (We are forced to make copies because you // can't call // glMultMatrix inside a glBegin; the only way to draw all like // geometry at // once is to have a flat, transformed copy of it.) // Do not go through the regular part resolution scheme - it is not // thread safe. // Look up sub-model first, to avoid taking a lock on the shared // library catalog ONLY // to discover that we aren't in there. modelToDraw = referencedMPDSubmodel(); if (modelToDraw == null) modelToDraw = PartLibrary.sharedPartLibrary() .modelForName_threadSafe(referenceName); if (modelToDraw == null) return; try { flatCopy = (LDrawModel) modelToDraw.clone(); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // concatenate the transform and pass it down combinedTransform = MatrixMath.Matrix4Multiply(partTransform, transform); // Normals are actually transformed by a different matrix. normalTransform = MatrixMath .Matrix3MakeNormalTransformFromProjMatrix(combinedTransform); flatCopy.flattenIntoLines(lines, triangles, quadriaterals, everythingElse, getLDrawColor(), combinedTransform, normalTransform, recursive); flatCopy.clear(); flatCopy = null; } }// end flattenIntoLines:triangles:quadrilaterals:other:currentColor: // ========== collectPartReport: // ================================================ // // Purpose: Collects a report on this part. If this is really an MPD // reference, we want to get a report on the submodel and not this // actual part. // // ============================================================================== public void collectPartReport(PartReport report) { resolvePart(); if (cacheType == PartTypeT.PartTypeSubmodel || cacheType == PartTypeT.PartTypePeerFile) cacheModel.collectPartReport(report); else if (cacheType == PartTypeT.PartTypeLibrary) report.registerPart(this); // There's a bug here: -referencedMPDSubmodel doesn't necessarily tell // you if // this actually *is* a submodel reference. It may actually resolve to // something in the part library. In this case, we would draw the // library // part, but report the submodel! I'm going to let this ride, because // the // specification explicitly says the behavior in such a case is // undefined. }// end collectPartReport: // ========== 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:this] // setTransformComponents:transformComponents]); // [[undoManager prepareWithInvocationTarget:this] // setDisplayName:displayName]); // [[undoManager prepareWithInvocationTarget:this] optimizeOpenGL); // // [undoManager setActionName:NSLocalizedString(@"UndoAttributesPart", // null)); }// end registerUndoActions: // ========== addedMPDModel // ===================================================== // // Purpose: This message is sent when a model is added to a MPD file; if we // aren't fonud, we unresolve so that we can get a shot at // re-resolving to the newly added part. // // ============================================================================== public void addedMPDModel() { if (cacheType == PartTypeT.PartTypeNotFound) unresolvePart(); }// end addedMPDModel // ========== resolvePart // ======================================================= // // Purpose: Find the object this part references and record the way in which // it was found. // // ============================================================================== public void resolvePart() { if (cacheType == PartTypeT.PartTypeUnresolved) { LDrawModel mpdModel = referencedMPDSubmodel(); if (mpdModel != null) { cacheModel = mpdModel; cacheDrawable = mpdModel; cacheType = PartTypeT.PartTypeSubmodel; } else { // Try the part library first for speed - sub-paths will thrash // the modelmanager. cacheModel = PartLibrary.sharedPartLibrary().modelForName( referenceName); if (cacheModel != null) { // Intentional: do not observe library parts - they are // immutable so // we don't need observations, and messing with the lib // parts set is expensive. // [cacheModel addObserver:this); // WE DO falseT LOOK UP THE DRAWABLE VBO HERE!!! Do that in // -optimizeOpenGL // instead. cacheDrawable = null; invalCache(CacheFlagsT.CacheFlagBounds); cacheType = PartTypeT.PartTypeLibrary; } else { cacheModel = ModelManager.sharedModelManager() .requestModel(referenceName, enclosingFile()); if (cacheModel != null) { cacheType = PartTypeT.PartTypePeerFile; cacheDrawable = cacheModel; invalCache(CacheFlagsT.CacheFlagBounds); cacheModel.addObserver(this); } else { cacheType = PartTypeT.PartTypeNotFound; cacheDrawable = null; cacheModel = null; invalCache(CacheFlagsT.CacheFlagBounds); // If we are not found, listen to the "sub-model-added" // notification; ideally this would be on our enclosing // LDrawFile but for // now listen to all instances. // NotificationCenter.getInstance().addSubscriber(this, // NotificationMessageT.LDrawMPDSubModelAdded); } } } if (cacheModel != null) { verticesForBoundsInIndentityTransform = null; boundsInIndentityTransform = null; cacheBounds = null; connectivityList = null; connectivityMatrixItemList = null; collisionBoxList = null; invalCache(CacheFlagsT.CacheFlagBounds); cacheModel.addObserver(this); } } } // ========== unresolvePart // ===================================================== // // Purpose: This method is called when something potentially breaks the link // between a part and the underlying model that represents it. // Typical events include: renaming the part (new name, new model), // putting the part in a new container (new container, new MPD // peers) or deallocing a directive tree in use by the observer // (since parts have weak references to their models, this can in // theory happen). // // ============================================================================== public void unresolvePart() { if (cacheType != PartTypeT.PartTypeUnresolved) { if (cacheModel != null && (cacheType == PartTypeT.PartTypeSubmodel || cacheType == PartTypeT.PartTypePeerFile)) { // printf("Part %p telling observer/cache %p to forget us.\n",this,cacheModel); cacheModel.removeObserver(this); } if (cacheType == PartTypeT.PartTypeNotFound) { // todo // NotificationCenter.defaultCenter().removeObserver(this, // MessageT.LDrawMPDSubModelAdded, null); } cacheType = PartTypeT.PartTypeUnresolved; cacheDrawable = null; cacheModel = null; } }// end unresolvePart // ========== unresolvePartIfPartLibrary // ======================================== // // Purpose: Unresolve a part only if it is a library part. There are two // cases: actual library parts and parts that were not found (and // thus maybe they should be library parts but the library that was // loaded was incomplete. // // This is used by unresolveLibraryParts to reload the library. // // ============================================================================== public void unresolvePartIfPartLibrary() { if (cacheType == PartTypeT.PartTypeLibrary || cacheType == PartTypeT.PartTypeNotFound) unresolvePart(); }// end unresolvePartIfPartLibrary /** * @return * @uml.property name="referenceName" */ public String getReferenceName() { return referenceName; } /** * @param referenceName * @uml.property name="referenceName" */ public void setReferenceName(String referenceName) { this.referenceName = referenceName; } /** * @return * @uml.property name="cacheDrawable" */ public LDrawDirective getCacheDrawable() { return cacheDrawable; } /** * @param cacheDrawable * @uml.property name="cacheDrawable" */ public void setCacheDrawable(LDrawDirective cacheDrawable) { this.cacheDrawable = cacheDrawable; } /** * @return * @uml.property name="cacheModel" */ public LDrawModel getCacheModel() { return cacheModel; } /** * @param cacheModel * @uml.property name="cacheModel" */ public void setCacheModel(LDrawModel cacheModel) { this.cacheModel = cacheModel; } /** * @return * @uml.property name="cacheType" */ public PartTypeT getCacheType() { return cacheType; } /** * @param cacheType * @uml.property name="cacheType" */ public void setCacheType(PartTypeT cacheType) { this.cacheType = cacheType; } /** * @return * @uml.property name="drawLock" */ public NSLock getDrawLock() { return drawLock; } /** * @param drawLock * @uml.property name="drawLock" */ public void setDrawLock(NSLock drawLock) { this.drawLock = drawLock; } public static boolean getShrinkSeams() { return LDrawGlobalFlag.SHRINK_SEAMS; } public static float getShrinkAmount() { return SHRINK_AMOUNT; } /** * @return * @uml.property name="displayName" */ public String getDisplayName() { return displayName; } @Override public void receiveNotification(NotificationMessageT notificationType, INotificationMessage message) { switch (notificationType) { case MPDSubModelAdded: addedMPDModel(); break; default: break; } } public void initWithPartName(String partName, Vector3f origin) { ArrayList<String> lines = new ArrayList<String>(); lines.add("1 16 0 0 0 1 0 0 0 1 0 0 0 1 " + partName); initWithLines(lines, new Range(0, 1)); this.moveBy(origin, LDrawGridTypeT.Fine); } public void rotateByDegrees(float angle, Vector3f rotationVector, Vector3f rotationCenter, LDrawGridTypeT gridUnit) { angle = Math.round(angle / gridUnit.getRotationValue()) * gridUnit.getRotationValue(); rotateByDegrees(angle, rotationVector, rotationCenter); } public synchronized ArrayList<Connectivity> getConnectivityList() { getConnectivityList(true, true); return connectivityList; } public synchronized ArrayList<MatrixItem> getConnectivityMatrixItemList() { getConnectivityList(true, true); return connectivityMatrixItemList; } private synchronized ArrayList<Connectivity> getConnectivityList( boolean useConnExtractor, boolean useCache) { if (useCache == false) { isConnectivityInfoExist = true; if (connectivityList != null) connectivityList.clear(); if (connectivityMatrixItemList != null) connectivityMatrixItemList = null; connectivityList = null; connectivityMatrixItemList = null; } if (useConnExtractor == false) if (isConnectivityInfoExist == false) return null; if (connectivityList == null) { if (getCacheType() == PartTypeT.PartTypeUnresolved) resolvePart(); if (getCacheType() == PartTypeT.PartTypeSubmodel) { ConnectivityManager connectivityManager = new ConnectivityManager(); for (LDrawStep step : getCacheModel().steps()) { for (LDrawDirective directive : step.subdirectives()) { if (directive instanceof LDrawPart) { LDrawPart part = (LDrawPart) directive; for (Connectivity conn : part.getConnectivityList()) { Connectivity conn_copy = null; try { if (Stud.class.isInstance(conn)) { conn_copy = (Stud) ((Stud) conn) .clone(); } else if (Hole.class.isInstance(conn)) { conn_copy = (Hole) ((Hole) conn) .clone(); } else { conn_copy = (Connectivity) conn.clone(); } if (conn_copy instanceof ICustom2DField) { MatrixItem[][] matrix = ((ICustom2DField) conn_copy) .getMatrixItem(); for (int column = 0; column < matrix.length; column++) for (int row = 0; row < matrix[column].length; row++) matrix[column][row] .setParent(conn_copy); } conn_copy .setTransformMatrix(Matrix4.multiply( conn.getTransformMatrix(), part.transformationMatrix())); conn_copy.setParent(this); connectivityManager.addConn(conn_copy); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } // connectivityManager.addPart(part); } } } connectivityList = connectivityManager.getConnectivityList(); connectivityMatrixItemList = connectivityManager .getConnectivityMatrixItemList(); } else { connectivityList = ConnectivityLibrary.getInstance() .getConnectivity(displayName(), useConnExtractor, true); if (connectivityList != null) { for (Connectivity conn : connectivityList) { if (ICustom2DField.class.isInstance(conn)) { ICustom2DField custom2DField = (ICustom2DField) conn; MatrixItem[][] matrix = custom2DField .getMatrixItem(); if (connectivityMatrixItemList == null) connectivityMatrixItemList = new ArrayList<MatrixItem>(); for (int column = 0; column < matrix.length; column++) for (int row = 0; row < matrix[column].length; row++) { matrix[column][row].setParent(conn); matrix[column][row].setColumnIndex(column); matrix[column][row].setRowIndex(row); // if (matrix[column][row].getAltitude() == // 23 || matrix[column][row].getAltitude() // == 29) // continue; connectivityMatrixItemList .add(matrix[column][row]); } } conn.setParent(this); } } } } return connectivityList; } public Matrix4 getTransformMatrixForRotateByDegrees(float angle, Vector3f rotationVector, Vector3f center) { Matrix4 transform = transformationMatrix(); Vector3f displacement = center; Vector3f negativeDisplacement = MatrixMath.V3Negate(center); // Do the rotation around the specified centerpoint. transform = MatrixMath .Matrix4Translate(transform, negativeDisplacement); // translate transform.rotate((float) Math.toRadians(angle), rotationVector); // rotationCenter transform = MatrixMath.Matrix4Translate(transform, displacement); // translate return transform; } public Matrix4 getTransformMatrixForMoveBy(Vector3f moveVector, LDrawGridTypeT gridUnit, boolean useSnap) { if (useSnap) moveVector = LDrawGridTypeT.getSnappedPos(moveVector, gridUnit); Matrix4 transformationMatrix = transformationMatrix(); // I NEED to modify the matrix itself here. Some parts have funky, // fragile // rotation values, and getting the components really badly botches them // up. transformationMatrix = MatrixMath.Matrix4Translate( transformationMatrix, moveVector); return transformationMatrix; } public synchronized ArrayList<CollisionBox> getCollisionBoxList() { return getCollisionBoxList(true); } private synchronized ArrayList<CollisionBox> getCollisionBoxList( boolean useCache) { if (useCache == false) { if (collisionBoxList != null) collisionBoxList.clear(); collisionBoxList = null; } if (collisionBoxList == null) { if (getCacheType() == PartTypeT.PartTypeSubmodel) { collisionBoxList = new ArrayList<CollisionBox>(); for (LDrawStep step : getCacheModel().steps()) { for (LDrawDirective directive : step.subdirectives()) { if (directive instanceof LDrawPart) { LDrawPart part = (LDrawPart) directive; for (CollisionBox cbox : part.getCollisionBoxList()) { try { CollisionBox newBox = (CollisionBox) cbox .clone(); newBox.setTransformMatrix(Matrix4.multiply( cbox.getTransformMatrix(), part.transformationMatrix())); collisionBoxList.add(newBox); newBox.setParent(this); } catch (CloneNotSupportedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } // if (collisionBoxList.size() > 100) // collisionBoxList.clear(); } else { collisionBoxList = ConnectivityLibrary.getInstance() .getCollisionBox(displayName(), true); if (collisionBoxList != null) for (CollisionBox cBox : collisionBoxList) { cBox.setParent(this); cBox.updateConnectivityOrientationInfo(); } } } if (collisionBoxList == null) collisionBoxList = new ArrayList<CollisionBox>(); return collisionBoxList; } public Matrix4 getRotationMatrix() { Matrix4 rotationMatrix = transformationMatrix(); rotationMatrix.element[3][0] = rotationMatrix.element[3][1] = rotationMatrix.element[3][2] = 0; return rotationMatrix; } public Object clone() throws CloneNotSupportedException { LDrawPart part = new LDrawPart(); part.initWithPartName(displayName, position()); part.setDisplayName(displayName); part.setLDrawColor(getLDrawColor()); part.setTransformationMatrix(transformationMatrix()); return part; } public ArrayList<CollisionBox> getCollisionBoxList(Matrix4 transformMatrix, Box3 boundingBox) { if (getCacheType() != PartTypeT.PartTypeSubmodel) { if (LDrawUtilities.isIntersected(boundingBox, boundingBox3(Matrix4 .multiply(transformationMatrix(), transformMatrix)))) return getCollisionBoxList(); else return new ArrayList<CollisionBox>(); } ArrayList<CollisionBox> retList = new ArrayList<CollisionBox>(); Matrix4 newMatrix = Matrix4.multiply(transformationMatrix(), transformMatrix); for (LDrawPart subPart : LDrawUtilities.extractLDrawPartListModel( getCacheModel(), false)) { for (CollisionBox cbox : subPart.getCollisionBoxList(newMatrix, boundingBox)) { try { CollisionBox newBox = (CollisionBox) cbox.clone(); newBox.setTransformMatrix(Matrix4.multiply( cbox.getTransformMatrix(), subPart.transformationMatrix())); retList.add(newBox); newBox.setParent(this); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } } return retList; } }