1 package net.sf.openrocket.rocketcomponent;
3 import net.sf.openrocket.util.BugException;
4 import net.sf.openrocket.util.ChangeSource;
5 import net.sf.openrocket.util.Coordinate;
6 import net.sf.openrocket.util.LineStyle;
7 import net.sf.openrocket.util.MathUtil;
9 import javax.swing.event.ChangeListener;
10 import java.awt.Color;
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.EmptyStackException;
15 import java.util.Iterator;
16 import java.util.List;
17 import java.util.NoSuchElementException;
18 import java.util.Stack;
19 import java.util.UUID;
22 public abstract class RocketComponent implements ChangeSource, Cloneable,
23 Iterable<RocketComponent> , Visitable<ComponentVisitor, RocketComponent> {
26 * Text is suitable to the form
27 * Position relative to: <title>
29 public enum Position {
30 /** Position relative to the top of the parent component. */
31 TOP("Top of the parent component"),
32 /** Position relative to the middle of the parent component. */
33 MIDDLE("Middle of the parent component"),
34 /** Position relative to the bottom of the parent component. */
35 BOTTOM("Bottom of the parent component"),
36 /** Position after the parent component (for body components). */
37 AFTER("After the parent component"),
38 /** Specify an absolute X-coordinate position. */
39 ABSOLUTE("Tip of the nose cone");
42 Position(String title) {
47 public String toString() {
52 //////// Parent/child trees
54 * Parent component of the current component, or null if none exists.
56 private RocketComponent parent = null;
59 * List of child components of this component.
61 private List<RocketComponent> children = new ArrayList<RocketComponent>();
64 //////// Parameters common to all components:
67 * Characteristic length of the component. This is used in calculating the coordinate
68 * transformations and positions of other components in reference to this component.
69 * This may and should be used as the "true" length of the component, where applicable.
70 * By default it is zero, i.e. no translation.
72 protected double length = 0;
75 * Positioning of this component relative to the parent component.
77 protected Position relativePosition;
80 * Offset of the position of this component relative to the normal position given by
81 * relativePosition. By default zero, i.e. no position change.
83 protected double position = 0;
86 // Color of the component, null means to use the default color
87 private Color color = null;
88 private LineStyle lineStyle = null;
92 private double overrideMass = 0;
93 private boolean massOverriden = false;
94 private double overrideCGX = 0;
95 private boolean cgOverriden = false;
97 private boolean overrideSubcomponents = false;
100 // User-given name of the component
101 private String name = null;
103 // User-specified comment
104 private String comment = "";
106 // Unique ID of the component
107 private String id = null;
109 //// NOTE !!! All fields must be copied in the method copyFrom()! ////
114 * Default constructor. Sets the name of the component to the component's static name
115 * and the relative position of the component.
117 public RocketComponent(Position relativePosition) {
118 // These must not fire any events, due to Rocket undo system initialization
119 this.name = getComponentName();
120 this.relativePosition = relativePosition;
121 this.id = UUID.randomUUID().toString();
124 //////////// Methods that must be implemented ////////////
128 * Static component name. The name may not vary of the parameters, it must be static.
130 public abstract String getComponentName(); // Static component type name
133 * Return the component mass (regardless of mass overriding).
135 public abstract double getComponentMass(); // Mass of non-overridden component
138 * Return the component CG and mass (regardless of CG or mass overriding).
140 public abstract Coordinate getComponentCG(); // CG of non-overridden component
144 * Return the longitudal (around the y- or z-axis) unitary moment of inertia.
145 * The unitary moment of inertia is the moment of inertia with the assumption that
146 * the mass of the component is one kilogram. The inertia is measured in
147 * respect to the non-overridden CG.
149 * @return the longitudal unitary moment of inertia of this component.
151 public abstract double getLongitudalUnitInertia();
155 * Return the rotational (around the x-axis) unitary moment of inertia.
156 * The unitary moment of inertia is the moment of inertia with the assumption that
157 * the mass of the component is one kilogram. The inertia is measured in
158 * respect to the non-overridden CG.
160 * @return the rotational unitary moment of inertia of this component.
162 public abstract double getRotationalUnitInertia();
168 * Test whether the given component type can be added to this component. This type safety
169 * is enforced by the <code>addChild()</code> methods. The return value of this method
170 * may change to reflect the current state of this component (e.g. two components of some
171 * type cannot be placed as children).
173 * @param type The RocketComponent class type to add.
174 * @return Whether such a component can be added.
176 public abstract boolean isCompatible(Class<? extends RocketComponent> type);
179 /* Non-abstract helper method */
181 * Test whether the given component can be added to this component. This is equivalent
182 * to calling <code>isCompatible(c.getClass())</code>.
184 * @param c Component to test.
185 * @return Whether the component can be added.
186 * @see #isCompatible(Class)
188 public final boolean isCompatible(RocketComponent c) {
189 return isCompatible(c.getClass());
195 * Return a collection of bounding coordinates. The coordinates must be such that
196 * the component is fully enclosed in their convex hull.
198 * @return a collection of coordinates that bound the component.
200 public abstract Collection<Coordinate> getComponentBounds();
203 * Return true if the component may have an aerodynamic effect on the rocket.
205 public abstract boolean isAerodynamic();
208 * Return true if the component may have an effect on the rocket's mass.
210 public abstract boolean isMassive();
216 //////////// Methods that may be overridden ////////////
220 * Shift the coordinates in the array corresponding to radial movement. A component
221 * that has a radial position must shift the coordinates in this array suitably.
222 * If the component is clustered, then a new array must be returned with a
223 * coordinate for each cluster.
225 * The default implementation simply returns the array, and thus produces no shift.
227 * @param c an array of coordinates to shift.
228 * @return an array of shifted coordinates. The method may modify the contents
229 * of the passed array and return the array itself.
231 public Coordinate[] shiftCoordinates(Coordinate[] c) {
237 * Called when any component in the tree fires a ComponentChangeEvent. This is by
238 * default a no-op, but subclasses may override this method to e.g. invalidate
239 * cached data. The overriding method *must* call
240 * <code>super.componentChanged(e)</code> at some point.
242 * @param e The event fired
244 protected void componentChanged(ComponentChangeEvent e) {
252 * Return a descriptive name of the component.
254 * The description may include extra information about the type of component,
255 * e.g. "Conical nose cone".
257 * @return A string describing the component.
260 public final String toString() {
262 return getComponentName();
268 public final void printStructure() {
269 System.out.println("Rocket structure from '"+this.toString()+"':");
273 private void printStructure(int level) {
276 for (int i=0; i < level; i++) {
279 s += this.toString() + " (" + this.getComponentName()+")";
280 System.out.println(s);
282 for (RocketComponent c: children) {
283 c.printStructure(level+1);
289 * Make a deep copy of the rocket component tree structure from this component
290 * downwards. This method does not fire any events.
292 * This method must be overridden by any component that refers to mutable objects,
293 * or if some fields should not be copied. This should be performed by
294 * <code>RocketComponent c = super.copy();</code> and then cloning/modifying the
295 * appropriate fields.
297 * This is not performed as serializing/deserializing for performance reasons.
299 * @return A deep copy of the structure.
301 public RocketComponent copy() {
302 RocketComponent clone;
304 clone = (RocketComponent)this.clone();
305 } catch (CloneNotSupportedException e) {
306 throw new BugException("CloneNotSupportedException encountered, " +
310 // Reset all parent/child information
312 clone.children = new ArrayList<RocketComponent>();
314 // Add copied children to the structure without firing events.
315 for (RocketComponent child: this.children) {
316 RocketComponent childCopy = child.copy();
317 // Don't use add method since it fires events
318 clone.children.add(childCopy);
319 childCopy.parent = clone;
327 * Accept a visitor to this RocketComponent in the component hierarchy.
329 * @param theVisitor the visitor that will be called back with a reference to this RocketComponent
332 public void accept (final ComponentVisitor theVisitor) {
333 theVisitor.visit(this);
336 ////////////// Methods that may not be overridden ////////////
340 ////////// Common parameter setting/getting //////////
343 * Return the color of the object to use in 2D figures, or <code>null</code>
344 * to use the default color.
346 public final Color getColor() {
351 * Set the color of the object to use in 2D figures.
353 public final void setColor(Color c) {
354 if ((color == null && c == null) ||
355 (color != null && color.equals(c)))
359 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
363 public final LineStyle getLineStyle() {
367 public final void setLineStyle(LineStyle style) {
368 if (this.lineStyle == style)
370 this.lineStyle = style;
371 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
378 * Get the current override mass. The mass is not necessarily in use
381 * @return the override mass
383 public final double getOverrideMass() {
388 * Set the current override mass. The mass is not set to use by this
391 * @param m the override mass
393 public final void setOverrideMass(double m) {
394 overrideMass = Math.max(m,0);
396 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
400 * Return whether mass override is active for this component. This does NOT
401 * take into account whether a parent component is overriding the mass.
403 * @return whether the mass is overridden
405 public final boolean isMassOverridden() {
406 return massOverriden;
410 * Set whether the mass is currently overridden.
412 * @param o whether the mass is overridden
414 public final void setMassOverridden(boolean o) {
415 if (massOverriden != o) {
417 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
426 * Return the current override CG. The CG is not necessarily overridden.
428 * @return the override CG
430 public final Coordinate getOverrideCG() {
431 return getComponentCG().setX(overrideCGX);
435 * Return the x-coordinate of the current override CG.
437 * @return the x-coordinate of the override CG.
439 public final double getOverrideCGX() {
444 * Set the current override CG to (x,0,0).
446 * @param x the x-coordinate of the override CG to set.
448 public final void setOverrideCGX(double x) {
449 if (MathUtil.equals(overrideCGX, x))
451 this.overrideCGX = x;
452 if (isCGOverridden())
453 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
455 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
459 * Return whether the CG is currently overridden.
461 * @return whether the CG is overridden
463 public final boolean isCGOverridden() {
468 * Set whether the CG is currently overridden.
470 * @param o whether the CG is overridden
472 public final void setCGOverridden(boolean o) {
473 if (cgOverriden != o) {
475 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
482 * Return whether the mass and/or CG override overrides all subcomponent values
483 * as well. The default implementation is a normal getter/setter implementation,
484 * however, subclasses are allowed to override this behavior if some subclass
485 * always or never overrides subcomponents. In this case the subclass should
486 * also override {@link #isOverrideSubcomponentsEnabled()} to return
487 * <code>false</code>.
489 * @return whether the current mass and/or CG override overrides subcomponents as well.
491 public boolean getOverrideSubcomponents() {
492 return overrideSubcomponents;
497 * Set whether the mass and/or CG override overrides all subcomponent values
498 * as well. See {@link #getOverrideSubcomponents()} for details.
500 * @param override whether the mass and/or CG override overrides all subcomponent.
502 public void setOverrideSubcomponents(boolean override) {
503 if (overrideSubcomponents != override) {
504 overrideSubcomponents = override;
505 fireComponentChangeEvent(ComponentChangeEvent.MASS_CHANGE);
510 * Return whether the option to override all subcomponents is enabled or not.
511 * The default implementation returns <code>false</code> if neither mass nor
512 * CG is overridden, <code>true</code> otherwise.
514 * This method may be overridden if the setting of overriding subcomponents
517 * @return whether the option to override subcomponents is currently enabled.
519 public boolean isOverrideSubcomponentsEnabled() {
520 return isCGOverridden() || isMassOverridden();
527 * Get the user-defined name of the component.
529 public final String getName() {
534 * Set the user-defined name of the component. If name==null, sets the name to
535 * the default name, currently the component name.
537 public final void setName(String name) {
538 // System.out.println("Set name called:"+name+" orig:"+this.name);
539 if (name==null || name.matches("^\\s*$"))
540 this.name = getComponentName();
543 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
548 * Return the comment of the component. The component may contain multiple lines
549 * using \n as a newline separator.
551 * @return the comment of the component.
553 public final String getComment() {
558 * Set the comment of the component.
560 * @param comment the comment of the component.
562 public final void setComment(String comment) {
563 if (this.comment.equals(comment))
568 this.comment = comment;
569 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
575 * Returns the unique ID of the component.
577 * @return the ID of the component.
579 public final String getID() {
585 * Set the unique ID of the component. If <code>id</code> in <code>null</code> then
586 * this method generates a new unique ID for the component.
588 * This method should be used only in special cases, such as when creating database
589 * entries with empty IDs.
591 * @param id the ID to set.
593 public final void setID(String id) {
595 this.id = UUID.randomUUID().toString();
599 fireComponentChangeEvent(ComponentChangeEvent.NONFUNCTIONAL_CHANGE);
606 * Get the characteristic length of the component, for example the length of a body tube
607 * of the length of the root chord of a fin. This is used in positioning the component
608 * relative to its parent.
610 * If the length of a component is settable, the class must define the setter method
613 public final double getLength() {
618 * Get the positioning of the component relative to its parent component.
619 * This is one of the enums of {@link Position}. A setter method is not provided,
620 * but can be provided by a subclass.
622 public final Position getRelativePosition() {
623 return relativePosition;
628 * Set the positioning of the component relative to its parent component.
629 * The actual position of the component is maintained to the best ability.
631 * The default implementation is of protected visibility, since many components
632 * do not support setting the relative position. A component that does support
633 * it should override this with a public method that simply calls this
634 * supermethod AND fire a suitable ComponentChangeEvent.
636 * @param position the relative positioning.
638 protected void setRelativePosition(RocketComponent.Position position) {
639 if (this.relativePosition == position)
642 // Update position so as not to move the component
643 if (this.parent != null) {
644 double thisPos = this.toRelative(Coordinate.NUL,this.parent)[0].x;
648 this.position = this.toAbsolute(Coordinate.NUL)[0].x;
652 this.position = thisPos;
656 this.position = thisPos - (this.parent.length - this.length)/2;
660 this.position = thisPos - (this.parent.length - this.length);
664 assert(false): "Should not occur";
668 this.relativePosition = position;
669 fireComponentChangeEvent(ComponentChangeEvent.BOTH_CHANGE);
676 * Get the position value of the component. The exact meaning of the value is
677 * dependent on the current relative positioning.
679 * @return the positional value.
681 public final double getPositionValue() {
687 * Set the position value of the component. The exact meaning of the value
688 * depends on the current relative positioning.
690 * The default implementation is of protected visibility, since many components
691 * do not support setting the relative position. A component that does support
692 * it should override this with a public method that simply calls this
693 * supermethod AND fire a suitable ComponentChangeEvent.
695 * @param value the position value of the component.
697 public void setPositionValue(double value) {
698 if (MathUtil.equals(this.position, value))
700 this.position = value;
705 /////////// Coordinate changes ///////////
708 * Returns coordinate c in absolute coordinates. Equivalent to toComponent(c,null).
710 public Coordinate[] toAbsolute(Coordinate c) {
711 return toRelative(c,null);
716 * Return coordinate <code>c</code> described in the coordinate system of
717 * <code>dest</code>. If <code>dest</code> is <code>null</code> returns
718 * absolute coordinates.
720 * This method returns an array of coordinates, each of which represents a
721 * position of the coordinate in clustered cases. The array is guaranteed
722 * to contain at least one element.
724 * The current implementation does not support rotating components.
726 * @param c Coordinate in the component's coordinate system.
727 * @param dest Destination component coordinate system.
728 * @return an array of coordinates describing <code>c</code> in coordinates
729 * relative to <code>dest</code>.
731 public final Coordinate[] toRelative(Coordinate c, RocketComponent dest) {
732 double absoluteX = Double.NaN;
733 RocketComponent search = dest;
734 Coordinate[] array = new Coordinate[1];
737 RocketComponent component = this;
738 while ((component != search) && (component.parent != null)) {
740 array = component.shiftCoordinates(array);
742 switch (component.relativePosition) {
744 for (int i=0; i < array.length; i++) {
745 array[i] = array[i].add(component.position,0,0);
750 for (int i=0; i < array.length; i++) {
751 array[i] = array[i].add(component.position +
752 (component.parent.length-component.length)/2,0,0);
757 for (int i=0; i < array.length; i++) {
758 array[i] = array[i].add(component.position +
759 (component.parent.length-component.length),0,0);
764 // Add length of all previous brother-components with POSITION_RELATIVE_AFTER
765 int index = component.parent.children.indexOf(component);
767 for (index--; index >= 0; index--) {
768 RocketComponent comp = component.parent.children.get(index);
769 double length = comp.getTotalLength();
770 for (int i=0; i < array.length; i++) {
771 array[i] = array[i].add(length,0,0);
774 for (int i=0; i < array.length; i++) {
775 array[i] = array[i].add(component.position + component.parent.length,0,0);
780 search = null; // Requires back-search if dest!=null
781 if (Double.isNaN(absoluteX)) {
782 absoluteX = component.position;
787 throw new BugException("Unknown relative positioning type of component"+
788 component+": "+component.relativePosition);
791 component = component.parent; // parent != null
794 if (!Double.isNaN(absoluteX)) {
795 for (int i=0; i < array.length; i++) {
796 array[i] = array[i].setX(absoluteX + c.x);
800 // Check whether destination has been found or whether to backtrack
801 // TODO: LOW: Backtracking into clustered components uses only one component
802 if ((dest != null) && (component != dest)) {
803 Coordinate[] origin = dest.toAbsolute(Coordinate.NUL);
804 for (int i=0; i < array.length; i++) {
805 array[i] = array[i].sub(origin[0]);
814 * Recursively sum the lengths of all subcomponents that have position
817 * @return Sum of the lengths.
819 private final double getTotalLength() {
821 if (relativePosition == Position.AFTER)
823 for (int i=0; i<children.size(); i++)
824 l += children.get(i).getTotalLength();
830 /////////// Total mass and CG calculation ////////////
833 * Return the (possibly overridden) mass of component.
835 * @return The mass of the component or the given override mass.
837 public final double getMass() {
840 return getComponentMass();
844 * Return the (possibly overridden) center of gravity and mass.
846 * Returns the CG with the weight of the coordinate set to the weight of the component.
847 * Both CG and mass may be separately overridden.
849 * @return The CG of the component or the given override CG.
851 public final Coordinate getCG() {
853 return getOverrideCG().setWeight(getMass());
856 return getComponentCG().setWeight(getMass());
858 return getComponentCG();
863 * Return the longitudal (around the y- or z-axis) moment of inertia of this component.
864 * The moment of inertia is scaled in reference to the (possibly overridden) mass
865 * and is relative to the non-overridden CG.
867 * @return the longitudal moment of inertia of this component.
869 public final double getLongitudalInertia() {
870 return getLongitudalUnitInertia() * getMass();
874 * Return the rotational (around the y- or z-axis) moment of inertia of this component.
875 * The moment of inertia is scaled in reference to the (possibly overridden) mass
876 * and is relative to the non-overridden CG.
878 * @return the rotational moment of inertia of this component.
880 public final double getRotationalInertia() {
881 return getRotationalUnitInertia() * getMass();
886 /////////// Children handling ///////////
890 * Adds a child to the rocket component tree. The component is added to the end
891 * of the component's child list. This is a helper method that calls
892 * {@link #addChild(RocketComponent,int)}.
894 * @param component The component to add.
895 * @throws IllegalArgumentException if the component is already part of some
897 * @see #addChild(RocketComponent,int)
899 public final void addChild(RocketComponent component) {
900 addChild(component,children.size());
905 * Adds a child to the rocket component tree. The component is added to
906 * the given position of the component's child list.
908 * This method may be overridden to enforce more strict component addition rules.
909 * The tests should be performed first and then this method called.
911 * @param component The component to add.
912 * @param position Position to add component to.
913 * @throws IllegalArgumentException If the component is already part of
914 * some component tree.
916 public void addChild(RocketComponent component, int position) {
917 if (component.parent != null) {
918 throw new IllegalArgumentException("component "+component.getComponentName()+
919 " is already in a tree");
921 if (!isCompatible(component)) {
922 throw new IllegalStateException("Component "+component.getComponentName()+
923 " not currently compatible with component "+getComponentName());
926 children.add(position,component);
927 component.parent = this;
929 fireAddRemoveEvent(component);
934 * Removes a child from the rocket component tree.
936 * @param n remove the n'th child.
937 * @throws IndexOutOfBoundsException if n is out of bounds
939 public final void removeChild(int n) {
940 RocketComponent component = children.remove(n);
941 component.parent = null;
942 fireAddRemoveEvent(component);
946 * Removes a child from the rocket component tree. Does nothing if the component
947 * is not present as a child.
949 * @param component the component to remove
951 public final void removeChild(RocketComponent component) {
952 if (children.remove(component)) {
953 component.parent = null;
955 fireAddRemoveEvent(component);
963 * Move a child to another position.
965 * @param component the component to move
966 * @param position the component's new position
967 * @throws IllegalArgumentException If an illegal placement was attempted.
969 public final void moveChild(RocketComponent component, int position) {
970 if (children.remove(component)) {
971 children.add(position, component);
972 fireAddRemoveEvent(component);
978 * Fires an AERODYNAMIC_CHANGE, MASS_CHANGE or OTHER_CHANGE event depending on the
979 * type of component removed.
981 private void fireAddRemoveEvent(RocketComponent component) {
982 Iterator<RocketComponent> iter = component.deepIterator(true);
983 int type = ComponentChangeEvent.TREE_CHANGE;
984 while (iter.hasNext()) {
985 RocketComponent c = iter.next();
986 if (c.isAerodynamic())
987 type |= ComponentChangeEvent.AERODYNAMIC_CHANGE;
989 type |= ComponentChangeEvent.MASS_CHANGE;
992 fireComponentChangeEvent(type);
996 public final int getChildCount() {
997 return children.size();
1000 public final RocketComponent getChild(int n) {
1001 return children.get(n);
1004 public final RocketComponent[] getChildren() {
1005 return children.toArray(new RocketComponent[0]);
1010 * Returns the position of the child in this components child list, or -1 if the
1011 * component is not a child of this component.
1013 * @param child The child to search for.
1014 * @return Position in the list or -1 if not found.
1016 public final int getChildPosition(RocketComponent child) {
1017 return children.indexOf(child);
1021 * Get the parent component of this component. Returns <code>null</code> if the component
1024 * @return The parent of this component or <code>null</code>.
1026 public final RocketComponent getParent() {
1031 * Get the root component of the component tree.
1033 * @return The root component of the component tree.
1035 public final RocketComponent getRoot() {
1036 RocketComponent gp = this;
1037 while (gp.parent != null)
1043 * Returns the root Rocket component of this component tree. Throws an
1044 * IllegalStateException if the root component is not a Rocket.
1046 * @return The root Rocket component of the component tree.
1047 * @throws IllegalStateException If the root component is not a Rocket.
1049 public final Rocket getRocket() {
1050 RocketComponent r = getRoot();
1051 if (r instanceof Rocket)
1053 throw new IllegalStateException("getRocket() called with root component "
1054 +r.getComponentName());
1059 * Return the Stage component that this component belongs to. Throws an
1060 * IllegalStateException if a Stage is not in the parentage of this component.
1062 * @return The Stage component this component belongs to.
1063 * @throws IllegalStateException if a Stage component is not in the parentage.
1065 public final Stage getStage() {
1066 RocketComponent c = this;
1068 if (c instanceof Stage)
1072 throw new IllegalStateException("getStage() called without Stage as a parent.");
1076 * Return the stage number of the stage this component belongs to. The stages
1077 * are numbered from zero upwards.
1079 * @return the stage number this component belongs to.
1081 public final int getStageNumber() {
1082 if (parent == null) {
1083 throw new IllegalArgumentException("getStageNumber() called for root component");
1086 RocketComponent stage = this;
1087 while (!(stage instanceof Stage)) {
1088 stage = stage.parent;
1089 if (stage == null || stage.parent == null) {
1090 throw new IllegalStateException("getStageNumber() could not find parent " +
1094 return stage.parent.getChildPosition(stage);
1099 * Find a component with the given ID. The component tree is searched from this component
1100 * down (including this component) for the ID and the corresponding component is returned,
1101 * or null if not found.
1103 * @param id ID to search for.
1104 * @return The component with the ID, or null if not found.
1106 public final RocketComponent findComponent(String id) {
1107 Iterator<RocketComponent> iter = this.deepIterator(true);
1108 while (iter.hasNext()) {
1109 RocketComponent c = iter.next();
1110 if (c.id.equals(id))
1117 public final RocketComponent getPreviousComponent() {
1120 int pos = parent.getChildPosition(this);
1122 StringBuffer sb = new StringBuffer();
1123 sb.append("Inconsistent internal state: ");
1124 sb.append("this=").append(this).append('[')
1125 .append(System.identityHashCode(this)).append(']');
1126 sb.append(" parent.children=[");
1127 for (int i=0; i < parent.children.size(); i++) {
1128 RocketComponent c = parent.children.get(i);
1129 sb.append(c).append('[').append(System.identityHashCode(c)).append(']');
1130 if (i < parent.children.size()-1)
1134 throw new IllegalStateException(sb.toString());
1139 RocketComponent c = parent.getChild(pos-1);
1140 while (c.getChildCount() > 0)
1141 c = c.getChild(c.getChildCount()-1);
1145 public final RocketComponent getNextComponent() {
1146 if (getChildCount() > 0)
1149 RocketComponent current = this;
1150 RocketComponent parent = this.parent;
1152 while (parent != null) {
1153 int pos = parent.getChildPosition(current);
1154 if (pos < parent.getChildCount()-1)
1155 return parent.getChild(pos+1);
1158 parent = current.parent;
1164 /////////// Event handling //////////
1166 // Listener lists are provided by the root Rocket component,
1167 // a single listener list for the whole rocket.
1171 * Adds a ComponentChangeListener to the rocket tree. The listener is added to the root
1172 * component, which must be of type Rocket (which overrides this method). Events of all
1173 * subcomponents are sent to all listeners.
1175 * @throws IllegalStateException - if the root component is not a Rocket
1177 public void addComponentChangeListener(ComponentChangeListener l) {
1178 getRocket().addComponentChangeListener(l);
1182 * Removes a ComponentChangeListener from the rocket tree. The listener is removed from
1183 * the root component, which must be of type Rocket (which overrides this method).
1184 * Does nothing if the root component is not a Rocket. (The asymmetry is so
1185 * that listeners can always be removed just in case.)
1187 * @param l Listener to remove
1189 public void removeComponentChangeListener(ComponentChangeListener l) {
1190 if (parent != null) {
1191 getRoot().removeComponentChangeListener(l);
1197 * Adds a <code>ChangeListener</code> to the rocket tree. This is identical to
1198 * <code>addComponentChangeListener()</code> except that it uses a
1199 * <code>ChangeListener</code>. The same events are dispatched to the
1200 * <code>ChangeListener</code>, as <code>ComponentChangeEvent</code> is a subclass
1201 * of <code>ChangeEvent</code>.
1203 * @throws IllegalStateException - if the root component is not a <code>Rocket</code>
1205 public void addChangeListener(ChangeListener l) {
1206 getRocket().addChangeListener(l);
1210 * Removes a ChangeListener from the rocket tree. This is identical to
1211 * removeComponentChangeListener() except it uses a ChangeListener.
1212 * Does nothing if the root component is not a Rocket. (The asymmetry is so
1213 * that listeners can always be removed just in case.)
1215 * @param l Listener to remove
1217 public void removeChangeListener(ChangeListener l) {
1218 if (this.parent != null) {
1219 getRoot().removeChangeListener(l);
1225 * Fires a ComponentChangeEvent on the rocket structure. The call is passed to the
1226 * root component, which must be of type Rocket (which overrides this method).
1227 * Events of all subcomponents are sent to all listeners.
1229 * If the component tree root is not a Rocket, the event is ignored. This is the
1230 * case when constructing components not in any Rocket tree. In this case it
1231 * would be impossible for the component to have listeners in any case.
1233 * @param e Event to send
1235 protected void fireComponentChangeEvent(ComponentChangeEvent e) {
1237 /* Ignore if root invalid. */
1240 getRoot().fireComponentChangeEvent(e);
1245 * Fires a ComponentChangeEvent of the given type. The source of the event is set to
1248 * @param type Type of event
1249 * @see #fireComponentChangeEvent(ComponentChangeEvent)
1251 protected void fireComponentChangeEvent(int type) {
1252 fireComponentChangeEvent(new ComponentChangeEvent(this,type));
1257 /////////// Iterator implementation //////////
1260 * Private inner class to implement the Iterator.
1262 * This iterator is fail-fast if the root of the structure is a Rocket.
1264 private class RocketComponentIterator implements Iterator<RocketComponent> {
1265 // Stack holds iterators which still have some components left.
1266 private final Stack<Iterator<RocketComponent>> iteratorstack =
1267 new Stack<Iterator<RocketComponent>>();
1269 private final Rocket root;
1270 private final int treeModID;
1272 private final RocketComponent original;
1273 private boolean returnSelf=false;
1275 // Construct iterator with component's child's iterator, if it has elements
1276 public RocketComponentIterator(RocketComponent c, boolean returnSelf) {
1278 RocketComponent gp = c.getRoot();
1279 if (gp instanceof Rocket) {
1281 treeModID = root.getTreeModID();
1287 Iterator<RocketComponent> i = c.children.iterator();
1289 iteratorstack.push(i);
1292 this.returnSelf = returnSelf;
1295 public boolean hasNext() {
1299 return !iteratorstack.empty(); // Elements remain if stack is not empty
1302 public RocketComponent next() {
1303 Iterator<RocketComponent> i;
1307 // Return original component first
1313 // Peek first iterator from stack, throw exception if empty
1315 i = iteratorstack.peek();
1316 } catch (EmptyStackException e) {
1317 throw new NoSuchElementException("No further elements in " +
1318 "RocketComponent iterator");
1321 // Retrieve next component of the iterator, remove iterator from stack if empty
1322 RocketComponent c = i.next();
1324 iteratorstack.pop();
1326 // Add iterator of component children to stack if it has children
1327 i = c.children.iterator();
1329 iteratorstack.push(i);
1334 private void checkID() {
1336 if (root.getTreeModID() != treeModID) {
1337 throw new IllegalStateException("Rocket modified while being iterated");
1342 public void remove() {
1343 throw new UnsupportedOperationException("remove() not supported by " +
1344 "RocketComponent iterator");
1349 * Returns an iterator that iterates over all children and sub-children.
1351 * The iterator iterates through all children below this object, including itself if
1352 * returnSelf is true. The order of the iteration is not specified
1353 * (it may be specified in the future).
1355 * If an iterator iterating over only the direct children of the component is required,
1356 * use component.getChildren().iterator()
1358 * @param returnSelf boolean value specifying whether the component itself should be
1360 * @return An iterator for the children and sub-children.
1362 public final Iterator<RocketComponent> deepIterator(boolean returnSelf) {
1363 return new RocketComponentIterator(this,returnSelf);
1367 * Returns an iterator that iterates over all children and sub-children.
1369 * The iterator does NOT return the component itself. It is thus equivalent to
1370 * deepIterator(false).
1373 * @return An iterator for the children and sub-children.
1375 public final Iterator<RocketComponent> deepIterator() {
1376 return new RocketComponentIterator(this,false);
1381 * Return an iterator that iterates of the children of the component. The iterator
1382 * does NOT recurse to sub-children nor return itself.
1384 * @return An iterator for the children.
1386 public final Iterator<RocketComponent> iterator() {
1387 return Collections.unmodifiableList(children).iterator();
1390 //////////// Helper methods for subclasses
1393 * Helper method to add rotationally symmetric bounds at the specified coordinates.
1394 * The X-axis value is <code>x</code> and the radius at the specified position is
1397 protected static final void addBound(Collection<Coordinate> bounds, double x, double r) {
1398 bounds.add(new Coordinate(x,-r,-r));
1399 bounds.add(new Coordinate(x, r,-r));
1400 bounds.add(new Coordinate(x, r, r));
1401 bounds.add(new Coordinate(x,-r, r));
1405 protected static final Coordinate ringCG(double outerRadius, double innerRadius,
1406 double x1, double x2, double density) {
1407 return new Coordinate((x1+x2)/2, 0, 0,
1408 ringMass(outerRadius, innerRadius, x2-x1, density));
1411 protected static final double ringMass(double outerRadius, double innerRadius,
1412 double length, double density) {
1413 return Math.PI*(MathUtil.pow2(outerRadius) - MathUtil.pow2(innerRadius)) *
1417 protected static final double ringLongitudalUnitInertia(double outerRadius,
1418 double innerRadius, double length) {
1419 // 1/12 * (3 * (r1^2 + r2^2) + h^2)
1420 return (3 * (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius)) +
1421 MathUtil.pow2(length)) / 12;
1424 protected static final double ringRotationalUnitInertia(double outerRadius,
1425 double innerRadius) {
1426 // 1/2 * (r1^2 + r2^2)
1427 return (MathUtil.pow2(innerRadius) + MathUtil.pow2(outerRadius))/2;
1436 * Loads the RocketComponent fields from the given component. This method is meant
1437 * for in-place replacement of a component. It is used with the undo/redo
1438 * mechanism and when converting a finset into a freeform fin set.
1439 * This component must not have a parent, otherwise this method will fail.
1441 * The fields are copied by reference, and the supplied component must not be used
1442 * after the call, as it is in an undefined state.
1444 * TODO: MEDIUM: Make general to copy all private/protected fields...
1446 protected void copyFrom(RocketComponent src) {
1448 if (this.parent != null) {
1449 throw new UnsupportedOperationException("copyFrom called for non-root component "
1453 // Set parents and children
1454 this.children = src.children;
1455 src.children = new ArrayList<RocketComponent>();
1457 for (RocketComponent c: this.children) {
1461 // Set all parameters
1462 this.length = src.length;
1463 this.relativePosition = src.relativePosition;
1464 this.position = src.position;
1465 this.color = src.color;
1466 this.lineStyle = src.lineStyle;
1467 this.overrideMass = src.overrideMass;
1468 this.massOverriden = src.massOverriden;
1469 this.overrideCGX = src.overrideCGX;
1470 this.cgOverriden = src.cgOverriden;
1471 this.overrideSubcomponents = src.overrideSubcomponents;
1472 this.name = src.name;
1473 this.comment = src.comment;