@Override public String evaluate(final Collection<? extends Atom> atoms, final LevelMessages messages) { for (final Atom atom : extremities) { if (atom.getBonds().size() != 1) { return messages.getError(1); } } for (final Atom atom : otherAtoms) { if (atom.getBonds().size() != 2) { return messages.getError(1); } } return null; }
@Override public void setup(final Collection<? extends Atom> atoms) { for (final Atom atom : atoms) { if (atom.getType() != BasicType.B && atom.getType() != BasicType.C) { continue; } if (atom.getBonds().size() == 2) { extremities.add(atom); continue; } otherAtoms.add(atom); } }
// straight Euler method computation of spring forces per timestep // uses a maximum speed limiter to prevent numerical problems // doesn't lose overall speed however, since force computation typically // overestimates anyway // but reliable and quick // disadvantage: bonded atom groups lose group momentum as speed limiter // kicks in // some possible alternative physics: // - a hard-sphere type physics, where instead of a constant timestep we // search for future // collisions between the spheres and run the sim forward to that point, and // recompute their // velocities as a result of the collision. might be promising to explore // for OB - but how to include // bonds (and dragging)? // - a lattice-based physics can run very fast indeed but doesn't look as // satisfying public void doTimeStep(final DraggingPoint draggingPoint, final List<Reaction> reactions) { // boolean is_dragging,int which_being_dragged, int mouse_x,int mouse_y // COMPUTE AND REACT // we shuffle the reactions list in order to prevent any reaction // artefacts, since // reactions are applied as they are found, and conflicting reactions // would only ever have // the first one applied, eg. with a1c1->a2c2 and x1c1->x4c4, only the // first version would apply // to any a1c1 pairs. Now each reaction has an equal chance of being // chosen. Collections.shuffle(reactions); // starting over for this iteration reactedAtoms.clear(); // TODO the collider should not be responsible for the mangement of // reactions final float R = Atom.getAtomSize(); final float diam = 2.0f * R; final float diam2 = diam * diam; for (int i = 0; i < atoms.size(); i++) { Atom a = atoms.get(i); // bounce off the walls if (a.getPhysicalPoint().getPositionX() < R) { a.getPhysicalPoint() .setSpeedX( a.getPhysicalPoint().getSpeedX() + getForce(R - a.getPhysicalPoint().getPositionX())); } if (a.getPhysicalPoint().getPositionY() < R) { a.getPhysicalPoint() .setSpeedY( a.getPhysicalPoint().getSpeedY() + getForce(R - a.getPhysicalPoint().getPositionY())); } if (a.getPhysicalPoint().getPositionX() > width - R) { a.getPhysicalPoint() .setSpeedX( a.getPhysicalPoint().getSpeedX() - getForce(a.getPhysicalPoint().getPositionX() - (width - R))); } if (a.getPhysicalPoint().getPositionY() > height - R) { a.getPhysicalPoint() .setSpeedY( a.getPhysicalPoint().getSpeedY() - getForce(a.getPhysicalPoint().getPositionY() - (height - R))); } // bounce off other atoms that are within 2R distance of this one // what square radius must we search for neighbours? final int rx = (int) Math.ceil(diam / bucket_width); final int ry = (int) Math.ceil(diam / bucket_height); // what bucket is the atom in? final int wx = whichBucketX(a.getPhysicalPoint().getPositionX()); final int wy = whichBucketY(a.getPhysicalPoint().getPositionY()); // accumulate the list of any atoms in this square radius (clamped // to the valid area) for (int x = Math.max(0, wx - rx); x <= Math.min(n_buckets_x - 1, wx + rx); x++) { for (int y = Math.max(0, wy - ry); y <= Math.min(n_buckets_y - 1, wy + ry); y++) { // add each atom that is in this bucket final Iterator<Integer> it = buckets.get(x).get(y).listIterator(); while (it.hasNext()) { final int iOther = it.next().intValue(); if (iOther <= i) { continue; // using Newton's "action&reaction" as a } // shortcut Atom b = atoms.get(iOther); if (new Point2D.Float( a.getPhysicalPoint().getPositionX(), a.getPhysicalPoint().getPositionY()) .distanceSq( new Point2D.Float( b.getPhysicalPoint().getPositionX(), b.getPhysicalPoint().getPositionY())) < diam2) { // this is a collision - can any reactions apply to // these two atoms? if (!a.isKiller() && !b.isKiller()) { for (int twice = 0; twice < 2 && !reactedAtoms.contains(a) && !reactedAtoms.contains(b); twice++) { // try each reaction in turn final Iterator<Reaction> iterator = reactions.listIterator(); while (iterator.hasNext() && !reactedAtoms.contains(a) && !reactedAtoms.contains(b)) { if (iterator.next().tryOn(a, b)) { reactedAtoms.add(a); reactedAtoms.add(b); } } // now swap a and b and try again final Atom temp = a; a = b; b = temp; } } else { // the killer atom breaks the other atoms bonds // (unless other is an 'a' atom) if (a.isKiller()) { if (b.getType() != BasicType.A) { b.breakAllBonds(); } } else { if (a.getType() != BasicType.A) { a.breakAllBonds(); } } } // atoms bounce off other atoms final float sep = (float) new Point2D.Float( a.getPhysicalPoint().getPositionX(), a.getPhysicalPoint().getPositionY()) .distance( new Point2D.Float( b.getPhysicalPoint().getPositionX(), b.getPhysicalPoint().getPositionY())); final float force = getForce(diam - sep); // push from the other atom final float dx = force * (a.getPhysicalPoint().getPositionX() - b.getPhysicalPoint().getPositionX()) / sep; final float dy = force * (a.getPhysicalPoint().getPositionY() - b.getPhysicalPoint().getPositionY()) / sep; a.getPhysicalPoint().setSpeedX(a.getPhysicalPoint().getSpeedX() + dx); a.getPhysicalPoint().setSpeedY(a.getPhysicalPoint().getSpeedY() + dy); b.getPhysicalPoint().setSpeedX(b.getPhysicalPoint().getSpeedX() - dx); // using // Newton's // "action&reaction" // as // a // shortcut b.getPhysicalPoint().setSpeedY(b.getPhysicalPoint().getSpeedY() - dy); } } } } // bonds act like springs final Iterator<Atom> it = a.getBonds().iterator(); while (it.hasNext()) { final Atom other = it.next(); final float sep = (float) new Point2D.Float( a.getPhysicalPoint().getPositionX(), a.getPhysicalPoint().getPositionY()) .distance( new Point2D.Float( other.getPhysicalPoint().getPositionX(), other.getPhysicalPoint().getPositionY())); final float force = getForce(sep - diam) / 4.0f; // this // determines // the bond spring // stiffness // pull towards the other atom final float dx = force * (other.getPhysicalPoint().getPositionX() - a.getPhysicalPoint().getPositionX()) / sep; final float dy = force * (other.getPhysicalPoint().getPositionY() - a.getPhysicalPoint().getPositionY()) / sep; a.getPhysicalPoint().setSpeedX(a.getPhysicalPoint().getSpeedX() + dx); a.getPhysicalPoint().setSpeedY(a.getPhysicalPoint().getSpeedY() + dy); } // the user can pull atoms about using the mouse if (draggingPoint != null && draggingPoint.getWhichBeingDragging() == i) { // normalise the pull vector float pullX = draggingPoint.getX() - a.getPhysicalPoint().getPositionX(); float pullY = draggingPoint.getY() - a.getPhysicalPoint().getPositionY(); final float dist = (float) Math.sqrt(pullX * pullX + pullY * pullY); pullX /= dist; pullY /= dist; a.getPhysicalPoint().setSpeedX(a.getPhysicalPoint().getSpeedX() + 2.0f * pullX); a.getPhysicalPoint().setSpeedY(a.getPhysicalPoint().getSpeedY() + 2.0f * pullY); } // limit the velocity of each atom to prevent numerical problems final float speed = (float) Math.sqrt( a.getPhysicalPoint().getSpeedX() * a.getPhysicalPoint().getSpeedX() + a.getPhysicalPoint().getSpeedY() * a.getPhysicalPoint().getSpeedY()); if (speed > MAX_SPEED) { a.getPhysicalPoint().setSpeedX(a.getPhysicalPoint().getSpeedX() * MAX_SPEED / speed); a.getPhysicalPoint().setSpeedY(a.getPhysicalPoint().getSpeedY() * MAX_SPEED / speed); } } // MOVE ATOMS for (int i = 0; i < atoms.size(); i++) { final Atom a = atoms.get(i); if (a.isStuck()) { continue; // special atoms that don't move } int current_bucket_x, current_bucket_y; current_bucket_x = whichBucketX(a.getPhysicalPoint().getPositionX()); current_bucket_y = whichBucketY(a.getPhysicalPoint().getPositionY()); a.getPhysicalPoint() .setPositionX( a.getPhysicalPoint().getPositionX() + atoms.get(i).getPhysicalPoint().getSpeedX()); a.getPhysicalPoint() .setPositionY( a.getPhysicalPoint().getPositionY() + atoms.get(i).getPhysicalPoint().getSpeedY()); int new_bucket_x, new_bucket_y; new_bucket_x = whichBucketX(a.getPhysicalPoint().getPositionX()); new_bucket_y = whichBucketY(a.getPhysicalPoint().getPositionY()); // do we need to move the atom to a new bucket? if (new_bucket_x != current_bucket_x || new_bucket_y != current_bucket_y) { // remove the atom index from the list final List<Integer> list = buckets.get(current_bucket_x).get(current_bucket_y); final Iterator<Integer> it = list.listIterator(0); while (it.hasNext()) { if (it.next().intValue() == i) { it.remove(); } } buckets.get(new_bucket_x).get(new_bucket_y).add(new Integer(i)); } } }