/// <summary> /// Helper for Organism.CollisionTest(). Looks for a collision between two cells, /// starting at the cell level then descending to the mesh level if required /// </summary> /// <param name="our"></param> /// <param name="his"></param> /// <returns></returns> public static bool CollisionTest(Cell our, Cell his) { // Do our spheres overlap? If not, we can't possibly collide if (Vector3.LengthSq(his.Location - our.Location) > (his.AbsSphere.Radius + our.AbsSphere.Radius) * (his.AbsSphere.Radius + our.AbsSphere.Radius)) return false; // Our spheres WILL intersect. So, for each mesh in both cells, test to see if they will actually collide foreach (JointFrame ourMesh in our.collisionFrames) { foreach (JointFrame hisMesh in his.collisionFrames) { bool result = Cytoplasm.CollisionTest(ourMesh, hisMesh); if (result == true) return true; } } return false; }
/// <summary> /// Get a clone of this Cell from the library, loading it first if necessary /// </summary> /// <returns>A modifiable clone of the named Cell</returns> /// <param name="gene">The gene containing the cell's properties, orientation and wiring</param> /// <param name="owner">The organism that owns me</param> public static Cell Get(Gene gene, Organism owner) { Cell cell = null; // Get name of the cell from the gene string fullname = gene.XFile(); // if the library already contains a Cell of that name, that's what we'll clone from if (library.ContainsKey(fullname)) { cell = (Cell)library[fullname]; // Debug.WriteLine(" Cell: Created new Cell instance: "+name+":"+cell.instance); } // otherwise, create a new entry in the library by loading a whole X file else { cell = new Cell(fullname); // Debug.WriteLine(" Cell: Created new Cell archetype: "+name+":"+cell.instance); } // Return a cloned instance of this Cell Cell newCell = cell.Clone(gene, owner); newCell.CreateBoundMarker(); // temp: create marker to show bounding sphere return newCell; }
/// <summary> /// Return stats about the creature being edited, for display on the VDU /// </summary> public void Stats(out float radius, out float mass, out float bouyancy, out float resistance, out Vector3 balance, out string name) { // Ensure the data is accurate after recent edits Refresh(); // Make sure that the trees and lists are up-to-date CalculateCG(); // Temporarily recalculate the CofG cell RecursiveUpdateFrames(rootCell); // calculate cell positions ComputeBoundingSphere(); // update radius and centre radius = this.AbsSphere.Radius; // org radius name = genome.Name; // current name mass = 0; bouyancy = 0; resistance = 0; foreach (Cell c in partList) // sum physical properties { mass += c.Physiology.Mass * scale; bouyancy += c.Physiology.Buoyancy * scale; resistance += c.Physiology.Resistance * scale; } balance = CGCell.AbsSphere.Centre - AbsSphere.Centre; // distance of CG from geometric centre (on each WORLD axis) balance.Scale(1.0f / radius); // as a fraction of creature's size // Clean up CGCell = rootCell; // Set the CofG back to the root cell }
/// <summary> /// Be magnetically attracted to the clamp (tractor beam). /// </summary> /// <param name="clamp">The location of the clamp</param> /// <returns>True if the organism is close enough to the clamp to become attached to it</returns> public bool Tractor(Vector3 clamp, Quaternion clampOrientation) { // If we're close to the clamp, return true and we'll get attached Vector3 targetVector = clamp - location; if (targetVector.LengthSq() < 1f) return true; // Switch off physics (it will get switched on again when re-released from clamp) Dynamic = false; CGCell = rootCell; // If we're a long way from underneath lab, aim for a point below lab first, so we don't pass through ship's wall if ((Math.Abs(targetVector.X) > 10f) || (Math.Abs(targetVector.Z) > 10f)) targetVector.Add(new Vector3(0, -8, 0)); // Apply a movement to bring the creature into alignment with the clamp or lower target targetVector.Normalize(); MoveBy(Vector3.Scale(targetVector,Scene.ElapsedTime * 5f)); // Rotate by a fraction of the difference between current rotation and that of clamp RotateTo(Quaternion.Slerp(Quaternion.RotationMatrix(rootCell.GetPlugMatrix()), clampOrientation, Scene.ElapsedTime * 1f)); return false; }
/// <summary> /// PREVIEW button pressed - temporarily add a new cell or delete it again when btn released /// </summary> /// <param name="sender"></param> /// <param name="value"></param> public void btnPreviewChanged(Widget sender, Object value) { if (((bool)value == true) && (Lab.SelectedCell != null) && (Lab.SelectedSocket != null)) { owner.Add(); DrawCellData(); previewed = Lab.SelectedCell; } else if (((bool)value == false) && (Lab.SelectedCell != null) && (Lab.SelectedCell == previewed)) { Organism.DeleteCell(); // delete the selected cell DrawCellData(); // update the LCD previewed = null; } }
/// <summary> /// Recursively create a new flat list of cells from the hierarchical one. /// </summary> /// <param name="cell"></param> private void BuildPartList(List<Cell> list, Cell cell) { list.Add(cell); if (cell.Sibling != null) BuildPartList(list, cell.Sibling); if (cell.FirstChild != null) BuildPartList(list, cell.FirstChild); }
/// <summary> /// We've been asked to carry the camera. See if any cell in this organism will accept the role of camera mount. /// If so, store a reference to that cell and its hotspot for use by the camera. /// </summary> /// <returns>true if the camera was successfully assigned</returns> public bool AssignCamera() { foreach (Cell cell in partList) { int spot = cell.AssignCamera(cameraMount, cameraHotspot); if (spot!=-1) { cameraMount = cell; cameraHotspot = spot; return true; } } return false; }
/// <summary> /// Return the socket number to which this other cell is connected /// </summary> /// <param name="other">the cell we're looking for</param> /// <returns>-2 if not found; -1 if the cell is on our plug; 0-n if the cell is on one of our sockets</returns> public int FindSocketNumberOf(Cell other) { // Are we the other's parent? If so, return the skt# to which it is attached if (this == other.parent) return other.parentSocket.Index; // Is other our parent? If so, return -1 because it is attached to our plug if (this.parent == other) return -1; // other isn't connected to us at all return -2; }
/// <summary> /// Update all the cells' Combined transformation matrices (e.g. after relocating the cell). /// This ensures that the cells don't think they've been pushed with extreme force when being /// moved artificially! /// </summary> /// <param name="cell"></param> private void RecursiveUpdateFrames(Cell cell) { cell.UpdateFrames(); // Now propagate the new combined matrices through to my siblings and children if (cell.Sibling != null) // recurse through siblings RecursiveUpdateFrames(cell.Sibling); if (cell.FirstChild != null) // recurse through children RecursiveUpdateFrames(cell.FirstChild); }
private float totalMass = 0; // sum of all cell masses #endregion Fields #region Constructors /// <summary> /// Construct a Creature instance from a genome /// </summary> /// <param name="genotype">The name of the xml file defining this creature, or "" if the creature is being created in the editor</param> /// <param name="location">world location of creature (Y should be zero)</param> /// <param name="orientation">world orientation of creature</param> public Organism(string genotype, Vector3 location, Orientation orientation) { Debug.WriteLine("Creating new Creature from genome: "+genotype); // Set relevant Renderable.FlagBits Dynamic = true; // (Most) creatures respond to forces // Set basic members this.location = location; this.orientation = Quaternion.RotationYawPitchRoll(orientation.Yaw,orientation.Pitch,orientation.Roll); instance = instanceCount++; // get a unique instance number name = genotype+instance.ToString("000"); // give the creature a unique name (valid filename) // Set up other members if (genotype == "") this.genome = new Genome(); // if no genotype supplied, create a default genome (Core cell only, for putting on the clamp) else this.genome = new Genome(genotype); // otherwise, create our unique Genome object and store its ref // Load all the Cells from disk or the Cell Library, using the recipe in the Creature's genome // and establish their connection points partList = new Cell[1024]; // Create an initial partlist (will scrunch later) rootCell = GetCells(genome.Root, null); // read the parts from the genome to create both the tree and partlist Cell[] shorter = new Cell[numParts]; // scrunch the partlist to the right size Array.Copy(partList,shorter,numParts); partList = shorter; // Now that the whole cell tree exists, allow the cells to connect up their channels rootCell.ClearAllChannels(); rootCell.WireUpAllChannels(); // Give the cells an absolute position in space so that we can calculate CG, bounds, etc. rootCell.Locate(Matrix.Translation(location)); // locate root cell temporarily at 0,0,0 RecursiveUpdateFrames(rootCell); // update the combined frames // Find out which cell is to act as the centre of gravity for the system CalculateCG(); // Register the creature with the map and position it properly in space Map.Add(this); MoveTo(location); // Calculate the initial center and radius of the Creature's bounding sphere now it is located ComputeBoundingSphere(); CheckMaps(); // And check maps again, because this requires sphere! // Calculate any aggregate properties required for sensor requests etc. SetAggregateProperties(); // Set the SlowUpdate() timer to a random value, so that organisms do their slow updates // at different times SlowUpdateTimer = Rnd.Float(SLOWUPDATERATE); }
/// <summary> /// Update this organism's cells /// To be called for ALL objects, whether visible or not, before rendering /// </summary> /// <param name="cell">The cell to update</param> private void RecursiveUpdate(Cell cell) { // Update this cell cell.Update(); if (cell.Sibling != null) // recurse through siblings RecursiveUpdate(cell.Sibling); if (cell.FirstChild != null) // recurse through children RecursiveUpdate(cell.FirstChild); }
/// <summary> /// Recursively walk the gene tree, extract the Cell filenames, clone the Cell from the library and /// record their interconnections (root->joint) /// </summary> /// <param name="gene">the gene being expressed</param> /// <param name="parent">the Cell that is the parent of this new one</param> /// <returns>the Cell created by this gene</returns> private Cell GetCells(Gene gene, Cell parent) { // Get the X filename from the gene and either load or fetch that Cell from the library, Cell s = Cell.Get(gene, this); // Add it to the flat partlist partList[numParts++] = s; // Locate this cell's SOCKET by locating the frame with the correct name in the // parent (if any) if (parent!=null) s.Attach(parent, gene.Socket); // if the gene has any siblings, recursively attach their structures as siblings of this one if (gene.Sibling!=null) s.Sibling = GetCells(gene.Sibling, parent); // we share a parent // if the gene has a child, recursively attach this as the child of this one if (gene.FirstChild!=null) s.FirstChild = GetCells(gene.FirstChild, s); // I am the parent return s; // return the new Cell to the parent/sibling }
/// <summary> /// Find out which cell is closest to the organism's centre of gravity. /// All future rotations and force calculations occur around CG cell rather than the root. /// NOTE: Don't call this until the cells have been given absolute locations /// </summary> private void CalculateCG() { Vector3 centre = new Vector3(); Vector3 xyz = new Vector3(); int n = partList.Length; // First, find the geographical centre of the creature (mean of all cells' locations) foreach (Cell c in partList) centre += c.Location; centre.X /= n; centre.Y /= n; centre.Z /= n; // find the distribution of mass around the centre foreach (Cell c in partList) xyz += (c.Location - centre) * c.Physiology.Mass; xyz.X /= n; xyz.Y /= n; xyz.Z /= n; // Adjust the CG to the centre of mass centre += xyz; // Find the cell nearest to this point Cell best = rootCell; float bestdist = float.PositiveInfinity; foreach (Cell c in partList) { float dist = Vector3.LengthSq(c.Location - centre); if (dist<bestdist) { bestdist = dist; best = c; } } // This is our CG cell. All rotations & forces will act around this cell CGCell = best; }
/// <summary> /// The absolute location/orientation of this Cell is determined by its SOCKET on the parent Cell. /// This method locates the frame in the parent with the given (genetically defined) name /// and stores a reference to it in .socket /// Also stores a reference to the parent cell, to make it easier to walk up the tree /// </summary> /// <param name="parent">the Cell to which I'm connected</param> /// <param name="skt">the name of the socket to which I'm attached</param> public void Attach(Cell parent, string sktname) { try { parentSocket = JointFrame.FindBaseName(parent.RootFrame, sktname); this.parent = parent; } catch { } if (parentSocket == null) { throw new SDKException("Cell.FindSocket() was unable to connect cell [" + this.name + "] to parent socket [" + parent.name + "-" + sktname + "]"); } // Debug.WriteLine(" Connected cell ["+this.name+"] to parent socket ["+parent.name+"-"+sktname+"]"); }
/// <summary> /// Virtual method implemented by all Renderable objects. /// The given cell is in collision with our bounding sphere. Test to see if it actually collides with /// one of my parts. If so, return a Vector describing the force we exert on the offending cell /// (since we're an organism we'll receive a share of the force too, unlike scenery) /// </summary> /// <param name="cell">The cell that may have collided with us</param> /// <returns> Any force vector acting on the cell </returns> public override Vector3 CollisionTest(Cell otherCell) { Vector3 bounce = new Vector3(); // Run through our own cells looking for a collision foreach (Cell ourCell in partList) { // Test more deeply by looking at the cell spheres then the // bounding boxes of each mesh in the cells if (Cell.CollisionTest(ourCell, otherCell) == true) { // The bounce direction is a vector in the direction of our centre towards theirs // (in other words, the two cells are treated as spheres and therefore bounce back away from // their centres). bounce = otherCell.AbsSphere.Centre - ourCell.AbsSphere.Centre; bounce.Normalize(); // The length of the force vector is proportional to the objects' closing speed Vector3 ourMvt = ourCell.Location - ourCell.OldLocation; // how much we will move next frame Vector3 hisMvt = otherCell.Location - otherCell.OldLocation; // how much he will move next frame float speed = Vector3.Length(ourMvt + hisMvt); // combine to get closing movement bounce.Scale(2.0f + speed * 10.0f); // arbitrary scaling factor // Also, a force acts on OUR cell, in the reverse direction. // Distribute the two forces inversely proportional to mass Vector3 ourBounce = -bounce; // effect on us is the inverse of our effect on him float mass = otherCell.Owner.TotalMass; // get the masses of the two ORGANISMS (not cells) float ourMass = ourCell.Owner.TotalMass; bounce.Scale(ourMass / (ourMass + mass)); // distribute the forces proportionately ourBounce.Scale(mass / (ourMass + mass)); ourCell.propulsionForce += ourBounce; // apply our share as propulsion // Once we've found a collision, we needn't look at any more of our cells return bounce; } } return bounce; }
/// <summary> /// Overload of Attach() to attach a cell to its parent given the actual attachment socket, rather than its name /// </summary> /// <param name="parent">the Cell to which I'm connected</param> /// <param name="skt">the actual socket to which I'm attached</param> public void Attach(Cell parent, JointFrame skt) { parentSocket = skt; this.parent = parent; }
/// <summary> /// This organism has been selected for editing. Prepare it /// </summary> public void EditOn() { // Temporarily stop responding to physical forces Dynamic = false; // Force the CofG to the root cell, so that the organism sits properly atop the clamp CGCell = rootCell; // Select this organism, its root cell and first socket (if any) Lab.SelectedOrg = this; SelectFirst(); }
/// <summary> /// The given cell is in collision with our bounding sphere. Test to see if it actually collides with /// one of my parts. If so, return a Vector describing the force we exert on the offending cell. /// The default is to return nothing, but Scenery classes that are .Collidable will need to implement this. /// </summary> /// <param name="cell">The cell that may have collided with us</param> /// <returns> Any force vector acting on the cell </returns> public override Vector3 CollisionTest(Cell otherCell) { /// TODO: behaviour should depend on type return Vector3.Empty; }
/// <summary> /// Actually render this organism's cells (recursively) /// </summary> /// <param name="cell"></param> public void RecursiveRender(Cell cell) { // Compute combined matrices and render this cell cell.Render((float)Math.Sqrt(DistSq)); // absolute dist from camera to obj sets LOD // Now propagate the new combined matrices through to my siblings and children if (cell.Sibling != null) // recurse through siblings RecursiveRender(cell.Sibling); if (cell.FirstChild != null) // recurse through children RecursiveRender(cell.FirstChild); }
/// <summary> /// Given a bounding sphere (e.g. of a cell), calculate accurately whether this sphere intersects my surface. /// The tile being checked lies in the same quad as the cell, but the cell may not be over it. /// </summary> /// IF there's a collision, return the bounce vector. /// This determines the force acting on the cell that hit me. /// <param name="cell">The cell that may have collided with me</param> /// <returns>The bounce vector (0,0,0 if there's no collision)</returns> public override Vector3 CollisionTest(Cell cell) { Vector3 bounce = new Vector3(); Vector3 cellCentre = cell.AbsSphere.Centre; float cellRadius = cell.AbsSphere.Radius * 1.1f; // scale up because sphere lags behind reality // 1. Reject if the sphere lies horizontally outside this tile's bounding BOX // i.e. is beyond the opposite or adjacent sides of the two triangles if ((cellCentre.X + cellRadius) < mesh[0].Position.X) // too far west return bounce; if ((cellCentre.X - cellRadius) > mesh[1].Position.X) // too far east return bounce; if ((cellCentre.Z + cellRadius) < mesh[2].Position.Z) // too far south return bounce; if ((cellCentre.Z - cellRadius) > mesh[0].Position.Z) // too far north return bounce; // 2. Reject if the cell is more than one radius above the highest point on the tile float max = mesh[0].Position.Y; if (mesh[1].Position.Y > max) max = mesh[1].Position.Y; if (mesh[2].Position.Y > max) max = mesh[2].Position.Y; if (mesh[3].Position.Y > max) max = mesh[3].Position.Y; if (cellCentre.Y > max + cellRadius) return bounce; //// 3. Reject if the sphere lies outside the plane of both triangles float dist0 = plane[0].Dot(cellCentre); float dist1 = plane[1].Dot(cellCentre); if ((dist0 > cellRadius) && (dist1 > cellRadius)) return bounce; // 4. We're close enough to justify bounding-box checks for each mesh in the cell if (cell.CollisionTest(this.mesh) == false) return bounce; // If we get here, at least one point is in contact with the tile // Construct a bounce vector - the movement required to return us to the surface... //float dist0 = plane[0].Dot(cellCentre); //float dist1 = plane[1].Dot(cellCentre); // First, find which facet we've collided with. This will (presumably) be the one we're NEAREST // the surface of int facet = 0; if (dist1 < dist0) { facet = 1; } // Our closing speed with the surface will be the difference between out present distance and // our previous one float movement = (plane[facet].Dot(cell.OldLocation) - plane[facet].Dot(cell.Location))*10.0f; if (movement < 0) movement = 0; // The bounce vector will be the surface normal scaled by our closing speed movement = (movement * movement + 1.0f); bounce = Vector3.Scale(faceNormal[facet], movement); return bounce; }
/// <summary> /// The given cell is in collision with our bounding sphere. Test to see if it actually collides with /// one of my parts. If so, return a Vector describing the force we exert on the offending cell /// </summary> /// <param name="cell">The cell that may have collided with us</param> /// <returns> Any force vector acting on the cell </returns> public virtual Vector3 CollisionTest(Cell otherCell) { // default is no force return Vector3.Empty; }