Java tutorial
/* Copyright (C) 2006-2010 Joan Queralt Molina * (C) 2014 Sbastien Le Callonnec * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ package biogenesis; import biogenesis.event.*; import java.awt.*; import java.awt.image.*; import java.awt.geom.*; import biogenesis.music.*; import org.apache.commons.lang3.event.EventListenerSupport; /** * This class implements an organism. * The body of the organism is drawn inside the Rectangle from which it inherits. */ public class Organism extends Rectangle { private final EventListenerSupport<OrganismCreatedListener> actionListeners = EventListenerSupport .create(OrganismCreatedListener.class); private final EventListenerSupport<OrganismCollidedListener> actionCollidedListeners = EventListenerSupport .create(OrganismCollidedListener.class); /** * The version of this class */ private static final long serialVersionUID = Utils.FILE_VERSION; /** * A reference to the genetic code of this organism */ protected GeneticCode _geneticCode; /** * If this organism has been infected by a white segment, here we have the * genetic code that this organism will reproduce. */ protected GeneticCode _infectedGeneticCode = null; /** * Number of children that this organism will produce at once. This * is the number of yellow segments in its genetic code with a * maximum of 8 and a minimum of 1. */ protected int _nChildren; /** * Reference to the world where the organism lives. */ protected World _world; /** * Reference to the visual part of the world where the organism lives. */ transient protected VisibleWorld _visibleWorld; /** * Identification number of this organism's parent. */ protected int _parentID; /** * Identification number of this organism. */ protected int _ID; /** * Generation number */ protected int _generation; /** * Number of children it has produced. */ protected int _nTotalChildren = 0; /** * Number of organism that has killed */ protected int _nTotalKills = 0; /** * Number of organism that has infected */ protected int _nTotalInfected = 0; /** * X coordinates of the starting point of each organism's segments. */ protected int[] _startPointX; /** * Y coordinates of the starting point of each organism's segments. */ protected int[] _startPointY; /** * X coordinates of the ending point of each organism's segments. */ protected int[] _endPointX; /** * Y coordinates of the ending point of each organism's segments. */ protected int[] _endPointY; /** * Precalculated distance from the origin to the starting point of each segment. * Used to calculate rotations. */ protected double[] _m1; /** * Precalculated distance from the origin to the ending point of each segment. * Used to calculate rotations. */ protected double[] _m2; /** * Precalculated modulus of each segment. */ protected double[] _m; /** * X coordinate of this organim's center of gravity. */ protected int _centerX; /** * Y coordinate of this organim's center of gravity. */ protected int _centerY; /** * Like _centerX but with double precision to be able to make movements slower than a pixel. */ protected double _dCenterX; /** * Like _centerY but with double precision to be able to make movements slower than a pixel. */ protected double _dCenterY; /** * Effective segment colors, taken from the genetic code if alive or brown if dead. */ protected Color[] _segColor; /** * The total number of segments of the organism */ protected int _segments; /** * Growth ratio of the organism. Used to calculate segments when the organism is not * fully grown. */ protected int _growthRatio; /** * Total mass of this organism. The mass is calculated as the sum of all segment lengths. * Used to calculate the effect of collisions. */ protected double _mass = 0; /** * Moment of inertia of this organism, used to calculate the effect of collisions. */ protected double _I = 0; /** * Chemical energy stored by this organism */ protected double _energy; /** * Organism size independent on its position in the world. * Let p be a point in the organism. Then, p.x + x - _sizeRect.x is the x coordinate * of p representation in the world. */ protected Rectangle _sizeRect = new Rectangle(); /** * Rotation angle that this organism has at a given moment. */ protected double _theta; /** * Last frame angle, used to avoid calculating point rotations when the angle doesn't * change between two consecutive frames. */ protected double _lastTheta = -1; /** * Rotated segments of the last frame, to use when _theta == _lastTheta */ protected int x1[], y1[], x2[], y2[]; /** * Speed. Variation applied to organism coordinates at every frame. */ protected double dx = 0d, dy = 0d; /** * Angular speed. Organism angle variation at every frame. */ protected double dtheta = 0d; /** * Number of frames of life of this organism */ protected int _age = 0; /** * Color used to draw the organism when a collision occurs. We save the color that * should be shown and the number of frames that it should be shown. If the number * if frames is 0, each segment is shown in its color. */ protected Color _color; /** * Number of frames in which the organism will be drawn in _color. */ protected int _framesColor = 0; /** * Number of frame that need to pass between two reproductions, even they are not * successfully. */ protected int _timeToReproduce = 0; /** * Indicates if the organism has grown at the last frame. If it has grown it is * necessary to recalculate its segments. */ protected int hasGrown; /** * Indicates if it has moved at the last frame. If it has moved it is necessary * to repaint it. */ protected boolean hasMoved = true; /** * The place that this organism occupies at the last frame. If the organism * moves, this rectangle must be painted too. */ protected Rectangle lastFrame = new Rectangle(); /** * Indicates if the organism is alive. */ protected boolean alive = true; private static transient Vector2D v = new Vector2D(); /** * Returns true if this organism is alive, false otherwise. * * @return true if this organism is alive, false otherwise. */ public boolean isAlive() { return alive; } /** * Returns the amount of chemical energy stored by this organism. * * @return The amount of chemical energy stored by this organism. */ public double getEnergy() { return _energy; } /** * Returns the identification number of this organism. * * @return The identification number of this organism. */ public int getID() { return _ID; } /** * Returns the identification number of this organism's parent. * * @return The identification number of this organism's parent. */ public int getParentID() { return _parentID; } /** * Returns the generation number of this organism. * * @return The generation number of this organism. */ public int getGeneration() { return _generation; } /** * Returns the age of this organism. * * @return The age of this organism, in number of frames. */ public int getAge() { return _age; } /** * Returns the number of children that this organism produced. * * @return The number of children that this organism produced. */ public int getTotalChildren() { return _nTotalChildren; } /** * Returns the number of organisms killed by this organism. * * @return The number of organisms killed by this organism. */ public int getTotalKills() { return _nTotalKills; } /** * Returns the number of organisms infected by this organism. * * @return The number of organisms infected by this organism. */ public int getTotalInfected() { return _nTotalInfected; } /** * Returns a reference to this organism's genetic code. * * @return A reference to this organism's genetic code. */ public GeneticCode getGeneticCode() { return _geneticCode; } /** * Returns the total mass of this organism. * * @return The total mass of this organism calculated as the sum * of all its segments length. */ public double getMass() { return _mass; } /** * Basic constructor. Doesn't initialize it: use {@link randomCreate} * or {@link inherit} to do this. * * @param world A reference to the world where this organism is in. */ public Organism(World world) { _world = world; _visibleWorld = world._visibleWorld; _theta = Utils.random.nextDouble() * Math.PI * 2d; addListener((OrganismCreatedListener) new MusicPlayer()); addListener((OrganismCollidedListener) new MusicPlayer()); } /** * Construct an organism with a given genetic code. Doesn't initialize it: * use {@link pasteOrganism} to do it. Use {@link World.addOrganism} to add * it to the world. * * @param world A reference to the world where this organism is in. * @param geneticCode A reference to the genetic code of this organism. */ public Organism(World world, GeneticCode geneticCode) { _world = world; _visibleWorld = world._visibleWorld; _theta = Utils.random.nextDouble() * Math.PI * 2d; _geneticCode = geneticCode; addListener((OrganismCreatedListener) new MusicPlayer()); addListener((OrganismCollidedListener) new MusicPlayer()); } /** * Creates all data structures of this organism. Must be used after the organism * has a genetic code assigned. */ protected void create() { _segments = _geneticCode.getNGenes() * _geneticCode.getSymmetry(); _segColor = new Color[_segments]; for (int i = 0; i < _segments; i++) _segColor[i] = _geneticCode.getGene(i % _geneticCode.getNGenes()).getColor(); _startPointX = new int[_segments]; _startPointY = new int[_segments]; _endPointX = new int[_segments]; _endPointY = new int[_segments]; _m1 = new double[_segments]; _m2 = new double[_segments]; _m = new double[_segments]; x1 = new int[_segments]; y1 = new int[_segments]; x2 = new int[_segments]; y2 = new int[_segments]; OrganismCreatedEvent event = new OrganismCreatedEvent(); actionListeners.fire().perform(event); } /** * Initializes variables for a new random organism and finds a place * to put it in the world. * * @return true if it found a place for this organism or false otherwise. */ public boolean randomCreate() { // Generates a random genetic code _geneticCode = new GeneticCode(); // it has no parent _parentID = -1; _generation = 1; _growthRatio = 16; // initial energy _energy = Math.min(Utils.INITIAL_ENERGY, _world.getCO2()); _world.decreaseCO2(_energy); _world.addO2(_energy); // initialize create(); symmetric(); // put it in the world return placeRandom(); } /** * Initializes variables for a new organism born from an existing * organism. Generates a mutated genetic code based on the parent's one * and finds a place in the world to put it. * * @param parent The organism from which this organism is born. * @param first * @return true if it found a place for this organism or false otherwise. */ public boolean inherit(Organism parent, boolean first) { GeneticCode inheritGeneticCode; boolean ok = true; // Create the inherited genetic code if (parent._infectedGeneticCode != null) inheritGeneticCode = parent._infectedGeneticCode; else inheritGeneticCode = parent._geneticCode; _geneticCode = new GeneticCode(inheritGeneticCode); // Take a reference to the parent _parentID = parent.getID(); _generation = parent.getGeneration() + 1; _growthRatio = 16; // Initial energy: minimum energy required to reproduce is divided // between all children and the parent. _energy = Math.min((inheritGeneticCode._reproduceEnergy / (double) (parent._nChildren + 1)), parent._energy); if (first || parent._energy >= _energy + Utils.YELLOW_ENERGY_CONSUMPTION) { // Initialize create(); symmetric(); // Put it in the world, near its parent ok = placeNear(parent); if (ok && !first) parent.useEnergy(Utils.YELLOW_ENERGY_CONSUMPTION); } else ok = false; return ok; } /** * Places the organism at the specified position in the world and initializes its * variables. The organism must has an assigned genetic code. * * @param posx The x coordinate of the position in the world we want to put this organism. * @param posy The y coordinate of the position in the world we want to put this organism. * @return true if there were enough space to put the organism, false otherwise. */ public boolean pasteOrganism(int posx, int posy) { _parentID = -1; _generation = 1; _growthRatio = 16; create(); symmetric(); _dCenterX = _centerX = posx; _dCenterY = _centerY = posy; calculateBounds(true); // Check that the position is inside the world if (isInsideWorld()) { // Check that the organism will not overlap other organisms if (_world.fastCheckHit(this) == null) { // Generem identificador _ID = _world.getNewId(); _energy = Math.min(Utils.INITIAL_ENERGY, _world.getCO2()); _world.decreaseCO2(_energy); _world.addO2(_energy); return true; } } // It can't be placed return false; } /** * Translates the genetic code of this organism to its segments representation in the world. * Also, calculates some useful information like segments length, inertia, etc. * This method must be called when an organism is firstly displayed on the world and every * time it changes its size. * inherit, randomCreate and pasteOrganism are the standard ways to add an organism to a world * and they already call this method. */ public void symmetric() { int i, j, segment = 0; int symmetry = _geneticCode.getSymmetry(); int mirror = _geneticCode.getMirror(); int sequence = _segments / symmetry; int left = 0, right = 0, top = 0, bottom = 0; int centerX, centerY; double cx, cy; for (i = 0; i < symmetry; i++) { for (j = 0; j < sequence; j++, segment++) { // Here, we take the vector that forms the segment, scale it depending on // the relative size of the organism and rotate it depending on the // symmetry and mirroring. v.setModulus(_geneticCode.getGene(j).getLength() / Utils.scale[_growthRatio - 1]); if (j == 0) { _startPointX[segment] = 0; _startPointY[segment] = 0; if (mirror == 0 || i % 2 == 0) v.setTheta(_geneticCode.getGene(j).getTheta() + i * 2 * Math.PI / symmetry); else { v.setTheta(_geneticCode.getGene(j).getTheta() + (i - 1) * 2 * Math.PI / symmetry); v.invertX(); } } else { _startPointX[segment] = _endPointX[segment - 1]; _startPointY[segment] = _endPointY[segment - 1]; if (mirror == 0 || i % 2 == 0) v.addDegree(_geneticCode.getGene(j).getTheta()); else v.addDegree(-_geneticCode.getGene(j).getTheta()); } // Apply the vector to the starting point to get the ending point. _endPointX[segment] = (int) Math.round(v.getX() + _startPointX[segment]); _endPointY[segment] = (int) Math.round(v.getY() + _startPointY[segment]); // Calculate the bounding rectangle of this organism left = Math.min(left, _endPointX[segment]); right = Math.max(right, _endPointX[segment]); top = Math.min(top, _endPointY[segment]); bottom = Math.max(bottom, _endPointY[segment]); } } _sizeRect.setBounds(left, top, right - left + 1, bottom - top + 1); // image center centerX = (left + right) >> 1; centerY = (top + bottom) >> 1; _mass = 0; _I = 0; for (i = 0; i < _segments; i++) { // express points relative to the image center _startPointX[i] -= centerX; _startPointY[i] -= centerY; _endPointX[i] -= centerX; _endPointY[i] -= centerY; // calculate points distance of the origin and modulus _m1[i] = Math.sqrt(_startPointX[i] * _startPointX[i] + _startPointY[i] * _startPointY[i]); _m2[i] = Math.sqrt(_endPointX[i] * _endPointX[i] + _endPointY[i] * _endPointY[i]); _m[i] = Math.sqrt( Math.pow(_endPointX[i] - _startPointX[i], 2) + Math.pow(_endPointY[i] - _startPointY[i], 2)); _mass += _m[i]; // calculate inertia moment // the mass center of a segment is its middle point cx = (_startPointX[i] + _endPointX[i]) / 2d; cy = (_startPointY[i] + _endPointY[i]) / 2d; // add the effect of this segment, following the parallel axis theorem _I += Math.pow(_m[i], 3) / 12d + _m[i] * cx * cx + cy * cy;// mass * length^2 (center is at 0,0) } } /** * Given a vector, calculates the resulting vector after a rotation, a scalation and possibly * after mirroring it. * The rotation degree and the mirroring is found using the Utils.degree array, where parameter * mirror is the row and step is the column. The step represents the repetition of this vector * following the organism symmetry. * The scalation is calculated using the Utils.scale coefficients, using the organism's * _growthRatio to find the appropriate value. * * @param p The end point of the vector. The starting point is (0,0). * @param step The repetition of the vectors pattern we are calculating. * @param mirror If mirroring is applied to this organism 1, otherwise 0. * @return The translated vector. */ /* private Vector2D translate(Point p, int step, int mirror) { if (p.x == 0 && p.y == 0) return new Vector2D(); double px = p.x; double py = p.y; px /= Utils.scale[_growthRatio - 1]; py /= Utils.scale[_growthRatio - 1]; Vector2D v = new Vector2D(px,py); v.addDegree(Utils.degree[mirror][step]); if (Utils.invertX[mirror][step] != 0) v.invertX(); if (Utils.invertY[mirror][step] != 0) v.invertY(); return v; }*/ /** * Tries to find a spare place in the world for this organism and place it. * It also generates an identification number for the organism if it can be placed * somewhere. * * @return true if a suitable place has been found, false if not. */ private boolean placeRandom() { /* We try to place the organism in 12 different positions. If all of them * are occupied, we return false. */ for (int i = 12; i > 0; i--) { /* Get a random point for the top left corner of the organism * making sure it is inside the world. */ Point origin = new Point(Utils.random.nextInt(_world.getWidth() - _sizeRect.width), Utils.random.nextInt(_world.getHeight() - _sizeRect.height)); setBounds(origin.x, origin.y, _sizeRect.width, _sizeRect.height); _dCenterX = _centerX = origin.x + (_sizeRect.width >> 1); _dCenterY = _centerY = origin.y + (_sizeRect.height >> 1); // Check that the position is not occupied. if (_world.fastCheckHit(this) == null) { // Generate an identification _ID = _world.getNewId(); return true; } } // If we get here, we haven't find a place for this organism. return false; } /** * Tries to find a spare place near its parent for this organism and place it. * It also generates an identification number for the organism if it can be placed * somewhere and substracts its energy from its parent's energy. * * @return true if a suitable place has been found, false if not. */ private boolean placeNear(Organism parent) { int nPos = Utils.random.nextInt(8); // Try to put it in any possible position, starting from a randomly chosen one. for (int nSide = 0; nSide < 8; nSide++) { // Calculate candidate position _dCenterX = parent._dCenterX + (parent.width / 2 + width / 2 + 1) * Utils.side[nPos][0]; _dCenterY = parent._dCenterY + (parent.height / 2 + height / 2 + 1) * Utils.side[nPos][1]; _centerX = (int) _dCenterX; _centerY = (int) _dCenterY; calculateBounds(true); // Check this position is inside the world. if (isInsideWorld()) { // Check that it doesn't overlap with other organisms. if (_world.fastCheckHit(this) == null) { if (parent._geneticCode.getDisperseChildren()) { dx = Utils.side[nPos][0]; dy = Utils.side[nPos][1]; } else { dx = parent.dx; dy = parent.dy; } // Generate an identification _ID = _world.getNewId(); // Substract the energy from the parent parent._energy -= _energy; return true; } } nPos = (nPos + 1) % 8; } // It can't be placed. return false; } /** * Draws this organism to a graphics context. * The organism is drawn at its position in the world. * * @param g The graphics context to draw to. */ public void draw(Graphics g) { int i; if (_framesColor > 0) { // Draw all the organism in the same color g.setColor(_color); _framesColor--; for (i = 0; i < _segments; i++) g.drawLine(x1[i] + _centerX, y1[i] + _centerY, x2[i] + _centerX, y2[i] + _centerY); } else { if (alive) { for (i = 0; i < _segments; i++) { g.setColor(_segColor[i]); g.drawLine(x1[i] + _centerX, y1[i] + _centerY, x2[i] + _centerX, y2[i] + _centerY); } } else { g.setColor(Utils.ColorBROWN); for (i = 0; i < _segments; i++) { g.drawLine(x1[i] + _centerX, y1[i] + _centerY, x2[i] + _centerX, y2[i] + _centerY); } } } } /** * Calculates the position of all organism points in the world, depending on * its rotation. It also calculates the bounding rectangle of the organism. * This method must be called from outside this class only when doing * manual drawing. * * @param force To avoid calculations, segments position are only calculated * if the organism's rotation has changed in the last frame. If it is necessary * to calculate them even when the rotation hasn't changed, assign true to this * parameter. */ public void calculateBounds(boolean force) { double left = java.lang.Double.MAX_VALUE, right = java.lang.Double.MIN_VALUE, top = java.lang.Double.MAX_VALUE, bottom = java.lang.Double.MIN_VALUE; double theta; for (int i = _segments - 1; i >= 0; i--) { /* Save calculation: if rotation hasn't changed and it is not forced, * don't calculate points again. */ if (_lastTheta != _theta || force) { theta = _theta + Math.atan2(_startPointY[i], _startPointX[i]); x1[i] = (int) (_m1[i] * Math.cos(theta)); y1[i] = (int) (_m1[i] * Math.sin(theta)); theta = _theta + Math.atan2(_endPointY[i], _endPointX[i]); x2[i] = (int) (_m2[i] * Math.cos(theta)); y2[i] = (int) (_m2[i] * Math.sin(theta)); } // Finds the rectangle that comprises the organism left = Utils.min(left, x1[i] + _dCenterX, x2[i] + _dCenterX); right = Utils.max(right, x1[i] + _dCenterX, x2[i] + _dCenterX); top = Utils.min(top, y1[i] + _dCenterY, y2[i] + _dCenterY); bottom = Utils.max(bottom, y1[i] + _dCenterY, y2[i] + _dCenterY); } setBounds((int) left, (int) top, (int) (right - left + 1) + 1, (int) (bottom - top + 1) + 1); _lastTheta = _theta; } /** * If its the time for this organism to grow, calculates its new segments and speed. * An alive organism can grow once every 8 frames until it gets its maximum size. */ private void grow() { if (_growthRatio > 1 && (_age & 0x07) == 0x07 && alive && _energy >= _mass / 10) { _growthRatio--; double m = _mass; double I = _I; symmetric(); // Cynetic energy is constant. If mass changes, speed must also change. m = Math.sqrt(m / _mass); dx *= m; dy *= m; dtheta *= Math.sqrt(I / _I); hasGrown = 1; } else { if (_growthRatio < 15 && _energy < _mass / 12) { _growthRatio++; double m = _mass; double I = _I; symmetric(); // Cynetic energy is constant. If mass changes, speed must also change. m = Math.sqrt(m / _mass); dx *= m; dy *= m; dtheta *= Math.sqrt(I / _I); hasGrown = -1; } else hasGrown = 0; } } /** * Makes this organism reproduce. It tries to create at least one * child and at maximum 8 (depending on the number of yellow segments * of the organism) and put them in the world. */ public void reproduce() { Organism newOrg; for (int i = 0; i < Utils.between(_nChildren, 1, 8); i++) { newOrg = new Organism(_world); if (newOrg.inherit(this, i == 0)) { // It can be created _nTotalChildren++; _world.addOrganism(newOrg, this); _infectedGeneticCode = null; } _timeToReproduce = 20; } } /** * Executes the organism's movement for this frame. * This includes segments upkeep and activation, * movement, growth, collision detection, reproduction, * respiration and death. */ public boolean move() { boolean collision = false; hasMoved = false; lastFrame.setBounds(this); if (Math.abs(dx) < Utils.tol) dx = 0; if (Math.abs(dy) < Utils.tol) dy = 0; if (Math.abs(dtheta) < Utils.tol) dtheta = 0; // Apply segment effects for this frame. segmentsFrameEffects(); // Apply rubbing effects rubbingFramesEffects(); // Check if it can grow or shrink grow(); // Movement double dxbak = dx, dybak = dy, dthetabak = dtheta; offset(dx, dy, dtheta); calculateBounds(hasGrown != 0); if (hasGrown != 0 || dx != 0 || dy != 0 || dtheta != 0) { hasMoved = true; // Check it is inside the world collision = !isInsideWorld(); // Collision detection with biological corridors if (alive) { OutCorridor c = _world.checkHitCorridor(this); if (c != null && c.canSendOrganism()) { if (c.sendOrganism(this)) return false; } } // Collision detection with other organisms. if (_world.checkHit(this) != null) collision = true; // If there is a collision, undo movement. if (collision) { hasMoved = false; offset(-dxbak, -dybak, -dthetabak); if (hasGrown != 0) { _growthRatio += hasGrown; symmetric(); } calculateBounds(hasGrown != 0); } } // Substract one to the time needed to reproduce if (_timeToReproduce > 0) _timeToReproduce--; // Check if it can reproduce: it needs enough energy and to be adult if (_energy > _geneticCode.getReproduceEnergy() + Utils.YELLOW_ENERGY_CONSUMPTION * (_nChildren - 1) && _growthRatio == 1 && _timeToReproduce == 0 && alive) reproduce(); // Check that it don't exceed the maximum chemical energy if (_energy > 2 * _geneticCode.getReproduceEnergy()) useEnergy(_energy - 2 * _geneticCode.getReproduceEnergy()); // Maintenance breath(); // Check that the organism has energy after this frame return _energy > Utils.tol; } /** * Makes the organism spend an amount of energy using the * respiration process. * * @param q The quantity of energy to spend. * @return true if the organism has enough energy and there are * enough oxygen in the atmosphere, false otherwise. */ public boolean useEnergy(double q) { if (_energy < q) { return false; } double respiration = _world.respiration(q); _energy -= respiration; if (respiration < q) return false; return true; } /** * Realize the respiration process to maintain its structure. * Aging is applied here too. */ public void breath() { if (alive) { _age++; // Respiration process boolean canBreath = useEnergy(Math.min(_mass / Utils.SEGMENT_COST_DIVISOR, _energy)); if ((_age >> 8) > _geneticCode.getMaxAge() || !canBreath) { // It's dead, but still may have energy die(null); } else { if (_energy <= Utils.tol) { alive = false; _world.decreasePopulation(); _world.organismHasDied(this, null); } } } else { // The corpse slowly decays useEnergy(Math.min(_energy, Utils.DECAY_ENERGY)); } } /** * Kills the organism. Sets its segments to brown and tells the world * about the event. * * @param killingOrganism The organism that has killed this organism, * or null if it has died of natural causes. */ public void die(Organism killingOrganism) { alive = false; hasMoved = true; for (int i = 0; i < _segments; i++) { _segColor[i] = Utils.ColorBROWN; } _world.decreasePopulation(); if (killingOrganism != null) killingOrganism._nTotalKills++; _world.organismHasDied(this, killingOrganism); } /** * Infects this organism with a genetic code. * Tells the world about this event. * Not currently used. * * @param infectingCode The genetic code that infects this organism. */ public void infectedBy(GeneticCode infectingCode) { _infectedGeneticCode = infectingCode; _world.organismHasBeenInfected(this, null); } /** * Infects this organism with the genetic code of another organism. * Tells the world about this event. * * @param infectingOrganism The organism that is infecting this one. */ public void infectedBy(Organism infectingOrganism) { infectingOrganism._nTotalInfected++; _infectedGeneticCode = infectingOrganism.getGeneticCode(); _world.organismHasBeenInfected(this, infectingOrganism); } /** * Calculates the resulting speeds after a collision between two organisms, following * physical rules. * * @param org The other organism in the collision. * @param p Intersection point between the organisms. * @param l Line that has collided. Of the two lines, this is the one that collided * on the center, not on the vertex. * @param thisOrganism true if l is a line of this organism, false if l is a line of org. */ private void touchMove(Organism org, Point2D.Double p, Line2D l, boolean thisOrganism) { // Distance vector between centers of mass and p double rapx = p.x - _dCenterX; double rapy = p.y - _dCenterY; double rbpx = p.x - org._dCenterX; double rbpy = p.y - org._dCenterY; // Speeds of point p in the body A and B, before collision. double vap1x = dx - dtheta * rapy + hasGrown * rapx / 10d; double vap1y = dy + dtheta * rapx + hasGrown * rapy / 10d; double vbp1x = org.dx - org.dtheta * rbpy; double vbp1y = org.dy + org.dtheta * rbpx; // Relative speeds between the two collision points. double vab1x = vap1x - vbp1x; double vab1y = vap1y - vbp1y; // Normal vector to the impact line //First: perpendicular vector to the line double nx = l.getY1() - l.getY2(); double ny = l.getX2() - l.getX1(); //Second: normalize, modulus 1 double modn = Math.sqrt(nx * nx + ny * ny); nx /= modn; ny /= modn; /*Third: of the two possible normal vectors we need the one that points to the * outside; we choose the one that its final point is the nearest to the center * of the other line. */ if (thisOrganism) { if ((p.x + nx - org._dCenterX) * (p.x + nx - org._dCenterX) + (p.y + ny - org._dCenterY) * (p.y + ny - org._dCenterY) < (p.x - nx - org._dCenterX) * (p.x - nx - org._dCenterX) + (p.y - ny - org._dCenterY) * (p.y - ny - org._dCenterY)) { nx = -nx; ny = -ny; } } else { if ((p.x + nx - _dCenterX) * (p.x + nx - _dCenterX) + (p.y + ny - _dCenterY) * (p.y + ny - _dCenterY) > (p.x - nx - _dCenterX) * (p.x - nx - _dCenterX) + (p.y - ny - _dCenterY) * (p.y - ny - _dCenterY)) { nx = -nx; ny = -ny; } } // This is the j in the parallel axis theorem double j = (-(1 + Utils.ELASTICITY) * (vab1x * nx + vab1y * ny)) / (1 / _mass + 1 / org._mass + Math.pow(rapx * ny - rapy * nx, 2) / _I + Math.pow(rbpx * ny - rbpy * nx, 2) / org._I); // Final speed dx = Utils.between(dx + j * nx / _mass, -Utils.MAX_VEL, Utils.MAX_VEL); dy = Utils.between(dy + j * ny / _mass, -Utils.MAX_VEL, Utils.MAX_VEL); org.dx = Utils.between(org.dx - j * nx / org._mass, -Utils.MAX_VEL, Utils.MAX_VEL); org.dy = Utils.between(org.dy - j * ny / org._mass, -Utils.MAX_VEL, Utils.MAX_VEL); dtheta = Utils.between(dtheta + j * (rapx * ny - rapy * nx) / _I, -Utils.MAX_ROT, Utils.MAX_ROT); org.dtheta = Utils.between(org.dtheta - j * (rbpx * ny - rbpy * ny) / org._I, -Utils.MAX_ROT, Utils.MAX_ROT); } /** * Checks if the organism is inside the world. If it is not, calculates its * speed after the collision with the world border. * This calculation should be updated to follow the parallel axis theorem, just * like the collision between two organisms. * * @return true if the organism is inside the world, false otherwise. */ private boolean isInsideWorld() { // Check it is inside the world if (x < 0 || y < 0 || x + width >= _world.getWidth() || y + height >= _world.getHeight()) { // Adjust direction if (x < 0 || x + width >= _world.getWidth()) dx = -dx; if (y < 0 || y + height >= _world.getHeight()) dy = -dy; dtheta = 0; return false; } return true; } /** * Moves the organism and rotates it. * * @param offsetx displacement on the x axis. * @param offsety displacement on the y axis. * @param offsettheta rotation degree. */ private void offset(double offsetx, double offsety, double offsettheta) { _dCenterX += offsetx; _dCenterY += offsety; _theta += offsettheta; _centerX = (int) _dCenterX; _centerY = (int) _dCenterY; } /** * Finds if two organism are touching and if so applies the effects of the * collision. * * @param org The organism to check for collisions. * @return true if the two organisms are touching, false otherwise. */ public final boolean contact(Organism org) { int i, j; ExLine2DDouble line = new ExLine2DDouble(); ExLine2DDouble bline = new ExLine2DDouble(); // Check collisions for all segments for (i = _segments - 1; i >= 0; i--) { // Consider only segments with modulus greater than 1 if (_m[i] >= 1) { line.setLine(x1[i] + _centerX, y1[i] + _centerY, x2[i] + _centerX, y2[i] + _centerY); // First check if the line intersects the bounding box of the other organism if (org.intersectsLine(line)) { // Do the same for the other organism's segments. for (j = org._segments - 1; j >= 0; j--) { if (org._m[j] >= 1) { bline.setLine(org.x1[j] + org._centerX, org.y1[j] + org._centerY, org.x2[j] + org._centerX, org.y2[j] + org._centerY); if (intersectsLine(bline) && line.intersectsLine(bline)) { // If we found two intersecting segments, apply effects touchEffects(org, i, j, true); // Intersection point Point2D.Double intersec = line.getIntersection(bline); /* touchMove needs to know which is the line that collides from the middle (not * from a vertex). Try to guess it by finding the vertex nearest to the * intersection point. */ double dl1, dl2, dbl1, dbl2; dl1 = intersec.distanceSq(line.getP1()); dl2 = intersec.distanceSq(line.getP2()); dbl1 = intersec.distanceSq(bline.getP1()); dbl2 = intersec.distanceSq(bline.getP2()); // Use this to send the best choice to touchMove if (Math.min(dl1, dl2) < Math.min(dbl1, dbl2)) touchMove(org, intersec, bline, false); else touchMove(org, intersec, line, true); OrganismCollidedEvent event = new OrganismCollidedEvent(); actionCollidedListeners.fire().perform(event); // Find only one collision to speed up. return true; } } } } } } return false; } /** * Applies the effects produced by two touching segments. * * @param org The organism which is touching. * @param seg Index of this organism's segment. * @param oseg Index of the other organism's segment. * @param firstCall Indicates if this organism is the one that has detected the collision * or this method is called by this same method in the other organism. */ private void touchEffects(Organism org, int seg, int oseg, boolean firstCall) { if ((_parentID == org._ID || _ID == org._parentID) && org.alive) return; double takenEnergy = 0; switch (getTypeColor(_segColor[seg])) { case RED: // Red segment: try to get energy from the other organism // If the other segment is blue, it acts as a shield switch (getTypeColor(org._segColor[oseg])) { case BLUE: if (org.useEnergy(Utils.BLUE_ENERGY_CONSUMPTION)) { org.setColor(Color.BLUE); } else { // Doesn't have energy to use the shield if (useEnergy(Utils.RED_ENERGY_CONSUMPTION)) { // Get energy depending on segment length takenEnergy = Utils.between(_m[seg] * Utils.ORGANIC_OBTAINED_ENERGY, 0, org._energy); // The other organism will be shown in yellow org.setColor(Color.YELLOW); } } break; case RED: if (useEnergy(Utils.RED_ENERGY_CONSUMPTION)) { // Get energy depending on segment length takenEnergy = Utils.between(_m[seg] * Utils.ORGANIC_OBTAINED_ENERGY, 0, org._energy); // The other organism will be shown in red org.setColor(Color.RED); } break; default: if (useEnergy(Utils.RED_ENERGY_CONSUMPTION)) { // Get energy depending on segment length takenEnergy = Utils.between(_m[seg] * Utils.ORGANIC_OBTAINED_ENERGY, 0, org._energy); // The other organism will be shown in yellow org.setColor(Color.YELLOW); } } // energy interchange org._energy -= takenEnergy; _energy += takenEnergy; double CO2freed = takenEnergy * Utils.ORGANIC_SUBS_PRODUCED; useEnergy(CO2freed); // This organism will be shown in red setColor(Color.RED); break; case WHITE: // White segment: try to infect the other organism switch (getTypeColor(org._segColor[oseg])) { case BLUE: if (org.useEnergy(Utils.BLUE_ENERGY_CONSUMPTION)) { setColor(Color.WHITE); org.setColor(Color.BLUE); } else { if (org._infectedGeneticCode != _geneticCode) { if (useEnergy(Utils.WHITE_ENERGY_CONSUMPTION)) { org.infectedBy(this); org.setColor(Color.YELLOW); setColor(Color.WHITE); } } } break; case BROWN: break; default: if (org._infectedGeneticCode != _geneticCode) { if (useEnergy(Utils.WHITE_ENERGY_CONSUMPTION)) { org.infectedBy(this); org.setColor(Color.YELLOW); setColor(Color.WHITE); } } } break; case GRAY: switch (getTypeColor(org._segColor[oseg])) { case BLUE: if (org.useEnergy(Utils.BLUE_ENERGY_CONSUMPTION)) { org.setColor(Color.BLUE); setColor(Color.GRAY); } else { if (useEnergy(Utils.GRAY_ENERGY_CONSUMPTION)) { org.die(this); setColor(Color.GRAY); } } break; case BROWN: break; default: if (useEnergy(Utils.GRAY_ENERGY_CONSUMPTION)) { org.die(this); setColor(Color.GRAY); } } break; } // Check if the other organism has died if (org.isAlive() && org._energy < Utils.tol) { org.die(this); } if (firstCall) org.touchEffects(this, oseg, seg, false); } /* * Perd velocitat pel fregament. */ private void rubbingFramesEffects() { dx *= Utils.RUBBING; if (Math.abs(dx) < Utils.tol) dx = 0; dy *= Utils.RUBBING; if (Math.abs(dy) < Utils.tol) dy = 0; dtheta *= Utils.RUBBING; if (Math.abs(dtheta) < Utils.tol) dtheta = 0; } /* * Perd el cost de manteniment dels segments * Aplica l'efecte de cadascun dels segments */ private void segmentsFrameEffects() { if (alive) { int i; // Energy obtained through photosynthesis double photosynthesis = 0; _nChildren = 1; for (i = _segments - 1; i >= 0; i--) { // Manteniment switch (getTypeColor(_segColor[i])) { // Segments cilis case CYAN: if (Utils.random.nextInt(100) < 8 && useEnergy(Utils.CYAN_ENERGY_CONSUMPTION)) { dx = Utils.between(dx + 12d * (x2[i] - x1[i]) / _mass, -Utils.MAX_VEL, Utils.MAX_VEL); dy = Utils.between(dy + 12d * (y2[i] - y1[i]) / _mass, -Utils.MAX_VEL, Utils.MAX_VEL); dtheta = Utils.between(dtheta + Utils.randomSign() * _m[i] * Math.PI / _I, -Utils.MAX_ROT, Utils.MAX_ROT); } break; // Segments fotosinttics case GREEN: if (useEnergy(Utils.GREEN_ENERGY_CONSUMPTION)) photosynthesis += _m[i]; break; // Segments que obtenen energia de subs1 // Segments relacionats amb la fertilitat case YELLOW: _nChildren++; break; } } // Photosynthesis process //Get sun's energy _energy += _world.photosynthesis(photosynthesis); } } private static final int NOCOLOR = -1; private static final int GREEN = 0; private static final int RED = 1; private static final int CYAN = 2; private static final int BLUE = 3; private static final int MAGENTA = 4; private static final int PINK = 5; private static final int ORANGE = 6; private static final int WHITE = 7; private static final int GRAY = 8; private static final int YELLOW = 9; private static final int BROWN = 10; private static int getTypeColor(Color c) { if (c.equals(Color.RED) || c.equals(Utils.ColorDARK_RED)) return RED; if (c.equals(Color.GREEN) || c.equals(Utils.ColorDARK_GREEN)) return GREEN; if (c.equals(Color.CYAN) || c.equals(Utils.ColorDARK_CYAN)) return CYAN; if (c.equals(Color.BLUE) || c.equals(Utils.ColorDARK_BLUE)) return BLUE; if (c.equals(Color.MAGENTA) || c.equals(Utils.ColorDARK_MAGENTA)) return MAGENTA; if (c.equals(Color.PINK) || c.equals(Utils.ColorDARK_PINK)) return PINK; if (c.equals(Color.ORANGE) || c.equals(Utils.ColorDARK_ORANGE)) return ORANGE; if (c.equals(Color.WHITE) || c.equals(Utils.ColorDARK_WHITE)) return WHITE; if (c.equals(Color.GRAY) || c.equals(Utils.ColorDARK_GRAY)) return GRAY; if (c.equals(Color.YELLOW) || c.equals(Utils.ColorDARK_YELLOW)) return YELLOW; if (c.equals(Utils.ColorBROWN)) return BROWN; return NOCOLOR; } private void setColor(Color c) { _color = c; _framesColor = 10; } public BufferedImage getImage() { BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); g.setBackground(Color.BLACK); g.clearRect(0, 0, width, height); for (int i = _segments - 1; i >= 0; i--) { g.setColor(_segColor[i]); g.drawLine(x1[i] - x + _centerX, y1[i] - y + _centerY, x2[i] - x + _centerX, y2[i] - y + _centerY); } return image; } public void addListener(OrganismCreatedListener listener) { actionListeners.addListener(listener); } public void addListener(OrganismCollidedListener listener) { actionCollidedListeners.addListener(listener); } }