/// <summary> /// Use a value in the range 0 to 1.0 to interpolate between min and max rotation, translation and/or scale, /// then apply this transformation to the given frame's transformation matrix to animate the frame. /// Record the amount of the change for calculating reaction forces /// NOTE: Rotation range must be less than 180 degrees, or the slerp will interpolate through the shortest distance, /// which is the opposite way to the one intended. /// </summary> /// <param name="frame">The frame to animate</param> /// <param name="amount">A value from 0 (min) to 1.0 (max)</param> public void Transform(JointFrame frame, float amount) { Quaternion Rotation = Quaternion.Identity; Vector3 Translation = Vector3.Empty; Vector3 Scale = Vector3.Empty; // Cap the value into range if (amount>1.0f) amount = 1.0f; else if (amount < 0) amount = 0; // Record the translation and rotation values before the change, for calculating relative mvt later Vector3 oldTrans = Translation; Quaternion oldRot = Rotation; // Interpolate Translation & store Translation = (TranslationMax - TranslationMin) * amount + TranslationMin; // Interpolate scale & store //Scale = (ScaleMax - ScaleMin) * amount + new Vector3(1,1,1); Scale = (ScaleMax - ScaleMin) * amount + ScaleMin; // Interpolate rotation & store Rotation = Quaternion.Slerp(RotationMin,RotationMax,amount); Rotation.Invert(); // Do the transform frame.TransformationMatrix = Matrix.Scaling(Scale) * Matrix.RotationQuaternion(Rotation) * Matrix.Translation(Translation); }
/// <summary> /// Find the frame in this hierarchy with this BASE name /// E.g. if name = "JointHead", the first frame called "JointHead*" will be returned. /// This copes with the fact that TrueSpace and presumably other packages add a number to /// the end of frame names. So if I call a mesh "Eric", Eric's frame will be called "Eric-0" /// or some other number, depending on when it was created. /// In a skinned mesh the bones are called Bone-# by default. MUST rename them to prevent them all having /// a base name of "Bone"! /// This method is to allow a frame to be found (e.g. a joint) using a standardised name, e.g. from /// a Cell's X file. /// </summary> /// <param name="root">the root to start searching from</param> /// <param name="basename">the ROOT name of the frame</param> /// <returns>the frame of that name, or null if not found</returns> public static JointFrame FindBaseName(JointFrame root, string basename) { // convert string to a valid base name if necessary, then recurse through the hierarchy return RecursiveFindBaseName(root, BaseName(basename)); }
/// <summary> /// Read a transformation matrix and attach to the current frame /// </summary> /// <param name="node"></param> /// <param name="depth"></param> /// <param name="thisFrame"></param> private static void LoadTransformationMatrix(XFileData node, int depth, JointFrame thisFrame) { try { // Debug.WriteLine(Depth(depth) + "Creating Matrix for "+thisFrame.Name); GraphicsStream stream = node.Lock(); // access the data stream thisFrame.TransformationMatrix = MatrixFromXFile(stream); // read in the matrix node.Unlock(); } catch (Exception e) { Debug.WriteLine("Error reading transformation matrix: "+e.ToString()); throw; } }
/// <summary> /// Read an AnimationKey node and load its transformation into the given frame /// </summary> /// <param name="node"></param> /// <param name="frame"></param> private static void LoadKeyframes(XFileData node, JointFrame frame) { int keyType = 0, keyFrames = 0; int keyTime = 0, keyValues = 0; Quaternion q = new Quaternion(); Vector3 v = new Vector3(); GraphicsStream stream = node.Lock(); // Lock the node and obtain a stream keyType = (int)stream.Read(keyType.GetType()); // get the keyframe type (rotation, etc.) keyFrames = (int)stream.Read(keyFrames.GetType()); // get the number of keyframes (should be 2) if (keyFrames>2) throw new Exception("XFile "+filespec+" should have only two keyframes per animation"); for (int i=0; i<keyFrames; i++) { keyTime = (int)stream.Read(keyTime.GetType()); // time of key (ignored) keyValues = (int)stream.Read(keyValues.GetType()); // number of values in key (ignored) switch (keyType) { // Rotation case 0: q = ReadQuaternion(stream); // get the transformation if (rmin == false) // store it in min or max { motion.RotationMin = q; // (min first, then max if min is full) motion.RotationMax = q; // (fill max too in case no 2nd key) rmin = true; } else motion.RotationMax = q; break; // Translation case 1: v = ReadVector(stream); if (smin == false) { motion.ScaleMin = v; motion.ScaleMax = v; smin = true; } else motion.ScaleMax = v; break; // Scale case 2: v = ReadVector(stream); if (tmin == false) { motion.TranslationMin = v; motion.TranslationMax = v; tmin = true; } else motion.TranslationMax = v; break; } } node.Unlock(); // release the node }
/// <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> /// Set the visibility level of all meshes of a given type ("SKT", "EFF", etc.) /// for ALL cells in the system /// </summary> /// <param name="meshname">root part of the FRAME name used to designate a part as being an effector/socket/etc</param> /// <param name="vis">true to make parts of this type visible</param> public static void SetVisibility(JointFrame.FrameType type, bool vis) { for (int i=0; i<Cell.library.Count; i++) // apply to every cell in the library { Cell c = (Cell)library.Values[i]; c.RecursiveSetVisibility(c.rootFrame, type, vis); } }
/// <summary> /// Recursively draw the local chemical concentration (in channels with the right affinity) /// For the Core cell, show the global chemical concs in the func0 to func5 organelles /// </summary> /// <param name="frame">frame to draw</param> /// <param name="lod">level of detail</param> private void RecursiveDrawScannerMode(JointFrame frame, float lod) { int chem = 0; float brightness = 0; Color col = Color.White; try { // Render this frame's mesh Cytoplasm cytoplasm = (Cytoplasm)frame.MeshContainer; // get first Cytoplasm mesh in this frame while (cytoplasm != null) // for each mesh... { switch (frame.Type) { // If this is a channel, render it in a colour determined by its chemical preference // and concentration case JointFrame.FrameType.Channel: chem = channel[frame.Index].chemical; // the chemical number for this channel col = Chemistry.Colour[chem]; // the base colour of that chemical brightness = GetChannel(frame.Index); // modulate by signal in that channel if (brightness == 0) brightness = 0.01f; // can't use black, or we'll render using texture Debug.Assert(brightness >= 0f, "Chemical conc in cell " + fullname + " channel " + frame.Index + " is < 0"); Debug.Assert(brightness <= 1.0f, "Chemical conc in cell " + fullname + " channel " + frame.Index + " is > 1"); col = Color.FromArgb((int)(col.R * brightness), (int)(col.G * brightness), (int)(col.B * brightness)); // adjust intensity cytoplasm.Render(frame, lod, col, col); break; // If this is a functional block, render it in yellow // UNLESS this is the Core cell, in which case we should colour its six func# meshes // to show the levels of the global chemicals case JointFrame.FrameType.Function: if (name == "Core") { chem = frame.Index + Chemistry.NUMSIGNALS; // the global chemical number for this func block col = Chemistry.Colour[chem]; // the base colour of that chemical brightness = owner.chemistry.Read(chem); // modulate by signal in that channel if (brightness == 0) brightness = 0.01f; // can't use black, or we'll render using texture col = Color.FromArgb((int)(col.R * brightness), (int)(col.G * brightness), (int)(col.B * brightness)); // adjust intensity cytoplasm.Render(frame, lod, col, col); } // For non-Core cells, render the functional blocks in yellow else { cytoplasm.Render(frame, lod, Color.Yellow, Color.Yellow); } break; // Cell membrane is rendered in wireframe, so that we can see organelles case JointFrame.FrameType.General: case JointFrame.FrameType.Animating: Engine.Device.RenderState.FillMode = FillMode.WireFrame; cytoplasm.Render(frame, lod, Color.Gray, Color.Gray); Engine.Device.RenderState.FillMode = FillMode.Solid; break; } // and repeat for any other meshes in frame cytoplasm = (Cytoplasm)cytoplasm.NextContainer; } } catch (Exception e) { throw new SDKException("Error: Unable to render chemical concentrations for celltype " + fullname + " chemical# " + chem + " concentration " + brightness,e); } // Render any siblings if (frame.Sibling != null) { RecursiveDrawScannerMode(frame.Sibling, lod); } // Render children and their siblings if (frame.FirstChild != null) { RecursiveDrawScannerMode(frame.FirstChild, lod); } }
/// <summary> /// Recursively draw in channel-edit mode /// </summary> /// <param name="frame"></param> /// <param name="lod"></param> /// <param name="style"></param> private void RecursiveDrawChannelMode(JointFrame frame, float lod, Cytoplasm.DesignStyle style) { try { // Render this frame's mesh Cytoplasm cytoplasm = (Cytoplasm)frame.MeshContainer; // get first Cytoplasm mesh in this frame while (cytoplasm != null) // for each mesh... { switch (frame.Type) { // If this is a socket, render it in a colour determined by whether it is selected or not case JointFrame.FrameType.Socket: if (frame == Lab.SelectedSocket) cytoplasm.Render(frame, lod, Color.Red, Color.Red); // selected socket else if (style != Cytoplasm.DesignStyle.Other) cytoplasm.Render(frame, lod, Color.White, Color.White); // unselected sockets on selected organism break; // If this is a channel, render it in a colour determined by its chemical preference // And flash it if it is being edited case JointFrame.FrameType.Channel: int chem = channel[frame.Index].chemical; // the chemical number for this channel Color c = Chemistry.Colour[chem]; // the colour of that chemical if ((this == Lab.SelectedCell) && (Lab.SelectedChannel == frame.Index)) // if we are the channel selected for editing { float time = Scene.TotalElapsedTime % 0.3f; // get current fraction of a second if (time > 0.15f) // flash 1:1 { //c = Color.DarkGray; c = Color.FromArgb(c.R / 3, c.G / 3, c.B / 3); // flash between bright and dark versions } } cytoplasm.Render(frame, lod, Color.FromArgb(1,1,1), c); // Use only emissive for clarity. Diffuse is dark (mustn't be black - means ignore) break; // If this is a functional block, render it in yellow case JointFrame.FrameType.Function: cytoplasm.Render(frame, lod, Color.Yellow, Color.Yellow); break; // Cell membrane is rendered in wireframe on a selected creature, so that we can see organelles case JointFrame.FrameType.General: case JointFrame.FrameType.Animating: Engine.Device.RenderState.FillMode = FillMode.WireFrame; if (style == Cytoplasm.DesignStyle.Selected) cytoplasm.Render(frame, lod, Color.DarkRed, Color.DarkRed); else cytoplasm.Render(frame, lod, Color.DarkGreen, Color.DarkGreen); Engine.Device.RenderState.FillMode = FillMode.Solid; break; } // and repeat for any other meshes in frame cytoplasm = (Cytoplasm)cytoplasm.NextContainer; } } catch (Exception e) { throw new SDKException("ERROR: Unable to draw organelle " + frame.Name + " in cell " + name, e); } // Render any siblings if (frame.Sibling != null) { RecursiveDrawChannelMode(frame.Sibling, lod, style); } // Render children and their siblings if (frame.FirstChild != null) { RecursiveDrawChannelMode(frame.FirstChild, lod, style); } }
/// <summary> /// Render the contents of this object's mesh in DESIGN mode /// in which the material depends on the status of the cell (selected, part of selected org, etc.). /// Hotspots are also rendered differently depending on whether they are selected, part of the selected cell, etc. /// </summary> /// <param name="frame">the frame of reference</param> /// <param name="lod">level of detail (as dist from camera), for progressive meshes</param> /// <param name="diffuse">Overridden colour for mesh, or Color.Black to use the mesh's own colour</param> /// <param name="emissive">Overridden emissive colour for mesh</param> public void Render(JointFrame frame, float lod, Color diffuse, Color emissive) { // If the natural mesh colour is being overridden, set up a material of the given colour if (diffuse != Color.Black) { Material mat = new Material(); mat.Ambient = Color.Black; mat.Diffuse = diffuse; mat.Emissive = emissive; mat.Specular = Color.Black; Fx.SetMaterial(mat); } // set the LOD for the progressive mesh using the dist from camera to obj // (calculated in Map.RenderQuad()) const float MAXDIST = 180.0f; // use min LOD beyond this dist const float MINDIST = 40.0f; // use max LOD nearer than this if (lod > MAXDIST) // TODO: USE LARGE ENOUGH DIST TO ENSURE LAB IS FULLY RENDERED!!!! MeshData.ProgressiveMesh.NumberFaces = MeshData.ProgressiveMesh.MinFaces; else { int faces = (int)((float)MeshData.ProgressiveMesh.MaxFaces * (1 - ((lod - MINDIST) / (MAXDIST - MINDIST)))); MeshData.ProgressiveMesh.NumberFaces = faces; /////Engine.DebugHUD.WriteLine(faces.ToString()); } // Set up the world matrix Fx.SetWorldMatrix(frame.CombinedMatrix); // Render the subsets for (int i = 0; i < materials.Length; i++) { // If this is a selected cell or an unselected cell on the selected org, we already // have a special material set up. Otherwise, we should use the mesh subset's own material // so the object is rendered normally if (diffuse == Color.Black) { Fx.SetMaterial(materials[i]); } // Use mesh's texture Fx.SetTexture(textures[i]); Fx.DrawMeshSubset(MeshData.ProgressiveMesh, i); } }
/// <summary> /// Render the contents of this object's mesh in NORMAL textured mode /// </summary> /// <param name="frame">the frame of reference</param> /// <param name="lod">level of detail (as dist from camera), for progressive meshes</param> public void Render(JointFrame frame, float lod) { // This class of mesh may not currently be visible if (Visible == false) return; // set the LOD for the progressive mesh using the dist from camera to obj // (calculated in Map.RenderQuad()) const float MAXDIST = 180.0f; // use min LOD beyond this dist const float MINDIST = 15.0f; // use max LOD nearer than this if (lod > MAXDIST) MeshData.ProgressiveMesh.NumberFaces = MeshData.ProgressiveMesh.MinFaces; else { int faces = (int)((float)MeshData.ProgressiveMesh.MaxFaces * (1 - ((lod - MINDIST) / (MAXDIST - MINDIST)))); MeshData.ProgressiveMesh.NumberFaces = faces; /////Engine.DebugHUD.WriteLine(faces.ToString()); } // Set up the world matrix Fx.SetWorldMatrix(frame.CombinedMatrix); // Render the subsets for (int i = 0; i < materials.Length; i++) { Fx.SetMaterial(materials[i]); Fx.SetTexture(textures[i], bumps[i]); Fx.DrawMeshSubset(MeshData.ProgressiveMesh, i); } }
/// <summary> /// Helper for Cell.HitTest(). Test to see whether these two meshes currently intersect. /// Method: If all points on one OBB lie in front of any of the planes marking the faces of the other, /// then the two boxes don't overlap. /// </summary> /// <param name="ourFrame">The frame containing one mesh</param> /// <param name="hisFrame">Same data for other mesh</param> /// <returns>True if there will be a collision</returns> public static bool CollisionTest(JointFrame ourFrame, JointFrame hisFrame) { // Get the transformed corners for each mesh Vector3[] ourCorners = ((Cytoplasm)ourFrame.MeshContainer).GetTransformedOBB(ourFrame.CombinedMatrix); Vector3[] hisCorners = ((Cytoplasm)hisFrame.MeshContainer).GetTransformedOBB(hisFrame.CombinedMatrix); // Test our points against his planes. If we definitely don't intersect, return false if (CollisionTest2(ourCorners, hisCorners) == false) return false; // If we're still here, the boxes still might intersect, so test his points against our planes return CollisionTest2(hisCorners, ourCorners); }
/// <summary> /// Helper for mouse selection of 3D objects. Given a "finger" pointing into the screen, /// return the Cell that the finger points to (or null if no such cell). /// Called by Camera.MousePick() /// </summary> /// <param name="rayPosition">position of ray on screen</param> /// <param name="rayDirection">vector pointing into screen</param> /// <param name="socket">If a SOCKET on the cell was selected, its frame is returned here</param> /// <returns>the Cell that the finger points to (or null)</returns> public static Cell MousePick(Vector3 rayPosition, Vector3 rayDirection, out JointFrame socket) { float bestDist = float.MaxValue; Cell bestCell = null; socket = null; // For every organism on the map foreach (Organism org in root.organismList) { // Skip if this is the camera ship - we're inside that so we'd be certain to click on it! if (org == (Organism)CameraShip.CurrentShip) continue; // if finger points somewhere within bounds of this organism if (Geometry.SphereBoundProbe(org.AbsSphere.Centre, org.AbsSphere.Radius, rayPosition, rayDirection)) { // Ask the organism to check its cells Cell cell = org.MousePick(rayPosition, rayDirection, out socket); // if the ray intersects this cell and it is closer than any previous cell, store it if (cell != null) { float dist = Vector3.Length(cell.AbsSphere.Centre - Camera.Position); if (dist < bestDist) bestCell = cell; } } } // return best candidate cell or null if none found return bestCell; }
/// <summary> /// Recursive part of FindBaseName() /// </summary> /// <param name="root"></param> /// <param name="basename"></param> /// <returns></returns> private static JointFrame RecursiveFindBaseName(JointFrame root, string basename) { // get the current frame's base name. If we match then return if (root.Name!=null) { string rootbase = BaseName(root.Name); if (rootbase == basename) return root; } // else continue with rest of hierarchy if (root.Sibling!=null) { JointFrame result = RecursiveFindBaseName(root.Sibling,basename); if (result!=null) return result; } if (root.FirstChild!=null) { JointFrame result = RecursiveFindBaseName(root.FirstChild,basename); if (result!=null) return result; } return null; }
private static void RecurseFrameType(FrameType type, JointFrame[] array, JointFrame frame) { if (frame.type==type) // if frame is correct type { array[frame.index] = frame; // store a ref to it in correct index } if (frame.FirstChild!=null) // then recurse through children/siblings RecurseFrameType(type, array, frame.FirstChild); if (frame.Sibling!=null) RecurseFrameType(type, array, frame.Sibling); }
/// <summary> /// Search the given frame hierarchy for all frames of a given type (sockets, effectors, etc.). /// Return an array containing references to these frames, sorted in order, so that /// "skt0" is in array[0], etc. Used to give cells rapid access to their joints and effectors /// for physics & animation. /// Note: array will be no larger than is needed to contain all the named frames. Array may be of zero size but will never be null. /// If any indices are missing (e.g. anim0 and anim2 exist but not anim1) then the unused array entries will be null /// and a trace warning will be emitted. /// </summary> /// <param name="root">The root frame for the hierarchy</param> /// <param name="type">The type of frame to return</param> /// <returns>The array of JointFrames</returns> public static JointFrame[] IndexFrameType(JointFrame root, FrameType type) { int num = 0; JointFrame[] array = new JointFrame[50]; // A big enough array RecurseFrameType(type, array, root); // fill the elements for (num=array.Length; (num>0)&&(array[num-1]==null); num--); // find out how many entries were filled JointFrame[] array2 = new JointFrame[num]; // copy elements into a correctly-sized array Array.Copy(array,0,array2,0,num); // (Possibly zero-length) for (int i=0; i<num; i++) // scan the array for any missing elements and { // emit a trace warning - cell designer might have if (array2[i]==null) // made a mistake! { Trace.WriteLine("WARNING: Unable to find a frame of type "+type.ToString()+" with index "+i); } } return array2; // any unused entries will be null }
/// <summary> /// Draw a frame and all its child and sibling frames in NORMAL display mode /// </summary> /// <param name="frame">Frame to draw</param> /// <param name="lod"> dist from camera </param> private void RecursiveDraw(JointFrame frame, float lod) { // Render this frame's mesh Cytoplasm cytoplasm = (Cytoplasm)frame.MeshContainer; // get first Cytoplasm mesh in this frame while(cytoplasm != null) { // if this frame has colour animation, copy the instanced material from the frame to the cytoplasm if (frame.animColour != null) cytoplasm.materials = frame.animColour; cytoplasm.Render(frame, lod); // draw it (textured) cytoplasm = (Cytoplasm)cytoplasm.NextContainer; // and repeat for any other meshes in frame } // Render any siblings if (frame.Sibling != null) { RecursiveDraw(frame.Sibling,lod); } // Render children and their siblings if (frame.FirstChild != null) { RecursiveDraw(frame.FirstChild,lod); } }
/// <summary> /// Recursively draw in cell edit mode /// </summary> /// <param name="frame"></param> /// <param name="lod"></param> /// <param name="style"></param> private void RecursiveDrawCellMode(JointFrame frame, float lod, Cytoplasm.DesignStyle style) { // Render this frame's mesh Cytoplasm cytoplasm = (Cytoplasm)frame.MeshContainer; // get first Cytoplasm mesh in this frame while (cytoplasm != null) // for each mesh... { switch (frame.Type) { // If this is the cell membrane, render it in a colour determined by whether it is selected or not case JointFrame.FrameType.General: case JointFrame.FrameType.Animating: if (style == Cytoplasm.DesignStyle.Selected) cytoplasm.Render(frame, lod, Color.Blue, Color.Black); else cytoplasm.Render(frame, lod, Color.White, Color.Black); break; // If this is a socket, render it in a colour determined by whether it is selected or not case JointFrame.FrameType.Socket: if (frame == Lab.SelectedSocket) cytoplasm.Render(frame, lod, Color.Red, Color.Red); else cytoplasm.Render(frame, lod, Color.White, Color.White); break; // All other parts are unrendered } // and repeat for any other meshes in frame cytoplasm = (Cytoplasm)cytoplasm.NextContainer; } // Render any siblings if (frame.Sibling != null) { RecursiveDrawCellMode(frame.Sibling, lod, style); } // Render children and their siblings if (frame.FirstChild != null) { RecursiveDrawCellMode(frame.FirstChild, lod, style); } }
/// <summary> /// Given a mouse click, find the cell that has been clicked on /// </summary> /// <param name="screenX">the mouse cursor position when the click occurred</param> /// <param name="screenY"></param> /// <param name="socket">If a SOCKET on the cell was selected, its frame is returned here</param> /// <returns>The picked cell, or null if no cell at that point</returns> public static Cell MousePick(float screenX, float screenY, out JointFrame socket) { // Step 1: Convert the mouse position into an eyepoint in 3D Vector3 v = new Vector3(); screenX -= Camera.SceneViewport.X; // remove offset of viewport screenY -= Camera.SceneViewport.Y; v.X = (((2.0f * screenX) / Camera.SceneViewport.Width) - 1) / projMatrix.M11; v.Y = -(((2.0f * screenY) / Camera.SceneViewport.Height) - 1) / projMatrix.M22; v.Z = 1.0f; // Step 2: Create a ray emanating from the mouse cursor in the direction of the camera Matrix m = viewMatrix; m.Invert(); Vector3 rayDirection = new Vector3( v.X * m.M11 + v.Y * m.M21 + v.Z * m.M31, v.X * m.M12 + v.Y * m.M22 + v.Z * m.M32, v.X * m.M13 + v.Y * m.M23 + v.Z * m.M33); Vector3 rayPosition = new Vector3(m.M41, m.M42, m.M43); // Step 3: Iterate through the orgs and cells to find the nearest one to the camera that our ray intersects return Map.MousePick(rayPosition, rayDirection, out socket); }
/// <summary> /// Construct a new Cell FOR USE IN THE LIBRARY as an archetype /// - Use Get() to get instances for actual animation and rendering /// </summary> /// <param name="name">name of Cell type, in the form "DLLname:celltype.variant" (where .variant is optional)</param> private Cell(string fullname) { // Store the full name so that we can identify our archetype in the library (to remove it when all instances have been disposed) this.fullname = fullname; // Parse the group:name.variant into group, name & variant int colon = fullname.IndexOf(":"); // if the celltype has no : it is in the default DLL if (colon >= 0) { group = fullname.Substring(0, colon); // the first part is the DLL assembly name name = fullname.Substring(colon + 1); // the second is the class name, plus possible variant } else { group = "CellTypes"; name = fullname; } int dot = name.IndexOf("."); // find start of any variant if (dot >= 0) // If the name ends in a variant { variantName = name; name = name.Substring(0, dot); // remove it from name variantName = variantName.Substring(dot+1); // and store it in variant } else { variantName = "default"; // if no variant specified, use "default.x" } // Load the frame hierarchy from disk rootFrame = CellLoader.Load(group, name, variantName); // Compute the overall bounding sphere for the cell, now that the frame hierarchy and meshes exist ComputeBoundingSphere(); // Compute an approximate colour for this cell, for use in optical sensors ComputeColour(); // Add this Cell to the library (key is complete dll.type name) library.Add(fullname, this); }
/// <summary> /// Helper for mouse selection of 3D objects. Given a "finger" pointing into the screen, /// return the Cell that the finger points to (or null if no such cell). /// Also, if the finger is pointing specifically to a SOCKET on that cell, return the socket's frame in the /// socket variable /// Called by Map.MousePick() /// </summary> /// <param name="rayPosition">position of ray on screen</param> /// <param name="rayDirection">vector pointing into screen</param> /// <param name="socket">If a SOCKET on the cell was selected, its frame is returned here</param> /// <returns>the nearest selected cell or null</returns> public Cell MousePick(Vector3 rayPosition, Vector3 rayDirection, out JointFrame socket) { float bestDist = float.MaxValue; Cell bestCell = null; JointFrame bestFrame = null; foreach (Cell cell in partList) { // Unproject the ray and test against every triangle of every mesh // in the cell. The cost of this is that I have to retain the original Mesh for each Cytoplasm object, since // its ProgressiveMesh doesn't have an .Intersect() method. // Return the one (if any) closest to the camera JointFrame result = cell.MousePick(rayPosition, rayDirection); if (result != null) { float dist = Vector3.Length(cell.AbsSphere.Centre - Camera.Position); if (dist < bestDist) { bestCell = cell; bestFrame = result; } } } // If the mesh that was clicked on is a socket, pass the socket frame back to the caller // If it was a cell but not a socket, pass back a null, because when the cell changes // we must find a new socket to select socket = null; if (bestCell != null) { if (bestFrame.Type == JointFrame.FrameType.Socket) socket = bestFrame; } // Return the nearest selected cell or null return bestCell; }
private void RecursiveSetVisibility(JointFrame frame, JointFrame.FrameType type, bool vis) { if (frame.Type==type) // if we're the right type of frame if (frame.MeshContainer!=null) // and there's a mesh ((Cytoplasm)frame.MeshContainer).Visible = vis; // set its visibility if (frame.Sibling != null) // recurse through siblings RecursiveSetVisibility(frame.Sibling, type, vis); if (frame.FirstChild != null) // recurse through children RecursiveSetVisibility(frame.FirstChild, type, vis); }
private void ComputeBoundingSphere(JointFrame frame, Matrix parentMatrix) { // Give the meshes their proper relative positions within the cell Matrix combinedMatrix = frame.TransformationMatrix * parentMatrix; // If this frame contains a mesh, transform its bounding sphere and // combine it with the overall bounds for the cell if (frame.MeshContainer!=null) { Cytoplasm cyt = (Cytoplasm)frame.MeshContainer; float radius = cyt.BoundRadius; Vector3 centre = cyt.BoundCentre; // transform the sphere's centre centre.TransformCoordinate(combinedMatrix); // Transform the sphere's radius (to scale it - the original vertices are probably not at their final scale Vector3 radiusVector = new Vector3(radius, 0, 0); // create a vector of size radius radiusVector.TransformCoordinate(combinedMatrix); // transform it to rescale it radius = radiusVector.Length(); // scaled radius is the length of the transformed vector // Combine this sphere with the others in the cell RelSphere.CombineBounds(centre, radius); } // Now propagate the new combined matrix through to my siblings and children if (frame.Sibling != null) // recurse through siblings { ComputeBoundingSphere(frame.Sibling, parentMatrix); } if (frame.FirstChild != null) // recurse through children { ComputeBoundingSphere(frame.FirstChild, combinedMatrix); } }
/// <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> /// Recursively convert the frame hierarchy into a flat list of frames for /// easier or more powerful searching, e.g. during 3D picking. /// </summary> /// <param name="list"></param> /// <returns></returns> private List<JointFrame> FlattenFrames(JointFrame frame, List<JointFrame> list) { list.Add(frame); if (frame.Sibling!=null) FlattenFrames(frame.Sibling, list); if (frame.FirstChild!=null) FlattenFrames(frame.FirstChild, list); return list; }
/// <summary> /// Recursively process frame node(s) and their subnodes from an XFileData object /// </summary> /// <param name="node"></param> /// <param name="depth"></param> /// <param name="parentFrame"></param> private static void LoadFrame(XFileData node, int depth, JointFrame parentFrame) { JointFrame newFrame = new JointFrame(node.Name); // Create a new frame // Debug.Write(Depth(depth) + "Creating frame "+newFrame.Name+" ("+newFrame.Type.ToString()+":"+newFrame.Index+") "); if (parentFrame==null) // if there's no parent frame { // the new frame is the root // Debug.WriteLine("as root"); rootFrame = newFrame; } else if (parentFrame.FirstChild==null) // or, if we're the first child { // Debug.WriteLine("as child of "+parentFrame.Name); parentFrame.FirstChild = newFrame; // attach as first child of parent } else // else we're a sibling { JointFrame bigSister = parentFrame.FirstChild; while (bigSister.Sibling!=null) // so scan to the end of this row bigSister = bigSister.Sibling; // Debug.WriteLine("as sibling of "+bigSister.Name); bigSister.Sibling = newFrame; // attach as a sibling } // --------- Recurse through this node's children to construct frame's contents ---------- depth++; for (int i=0; i<node.NumberChildren; i++) { using (XFileData child = node.GetChild(i)) { // **** Frame nested inside this frame if (child.Type==XFileGuid.Frame) { LoadFrame(child, depth, newFrame); } // **** FrameTransformationMatrix else if (child.Type==XFileGuid.FrameTransformMatrix) { LoadTransformationMatrix(child, depth, newFrame); } // **** Mesh else if (child.Type==XFileGuid.Mesh) { string name = child.Name; // use mesh named in xfile if available if (name == "") name = node.Name; // or name it after the frame LoadMesh(child, name, depth, newFrame); } } } }
/// <summary> /// Recursively create a list of frames that contain meshes. /// Useful for e.g. collision detection. /// </summary> /// <param name="frame">Current frame in the recursion</param> /// <param name="list">Where to store the results</param> /// <param name="includeSpots">False if we only want major meshes, not hotspots, sockets or channels</param> /// <returns></returns> private List<JointFrame> GetMeshFrames(JointFrame frame, List<JointFrame> list, bool includeSpots) { if (frame.MeshContainer != null) // if we have a mesh { if ((includeSpots==true) ||(frame.Type==JointFrame.FrameType.General) ||(frame.Type==JointFrame.FrameType.Animating)) // optionally exclude hotspots & sockets { list.Add(frame); } } if (frame.Sibling != null) GetMeshFrames(frame.Sibling, list, includeSpots); if (frame.FirstChild != null) GetMeshFrames(frame.FirstChild, list, includeSpots); return list; }
/// <summary> /// Read a mesh and create a Cytoplasm object from it, then attach this to the current frame /// </summary> /// <param name="node"></param> /// <param name="depth"></param> /// <param name="thisFrame"></param> private static void LoadMesh(XFileData node, string name, int depth, JointFrame thisFrame) { // Debug.WriteLine(Depth(depth) + "Creating Mesh "+name+" for "+thisFrame.Name); Mesh mesh = null; ExtendedMaterial[] materials = null; GraphicsStream adjacency = null; EffectInstance[] effects = null; try { mesh = Mesh.FromX(node,MeshFlags.Managed,Engine.Device,out adjacency, out materials, out effects); //Debug.WriteLine(Depth(depth) + "Mesh "+node.Name+" has "+mesh.NumberVertices+" verts and "+materials.Length+" materials"); } catch (Direct3DXException e) { Debug.WriteLine("Error reading mesh: "+e.ToString()); throw; } // Create a Cytoplasm (meshcontainer) object from mesh, materials, etc. // Give it the right name (node.Name) // Link it into the tree of Cytoplasm objects // Link it to this mesh Cytoplasm cyt = new Cytoplasm(folder,name,mesh,materials,effects,adjacency,null); thisFrame.MeshContainer = cyt; }
// Helper for above private bool MousePick(JointFrame frame, Vector3 rayPosition, Vector3 rayDirection) { // Skip frames that don't have meshes if (frame.MeshContainer == null) return false; // Unproject the ray and its normal into model space Matrix unproject = Matrix.Invert(frame.CombinedMatrix); Vector3 localPosn = Vector3.TransformCoordinate(rayPosition, unproject); Vector3 localDirn = Vector3.TransformNormal(rayDirection, unproject); localDirn.Normalize(); // Do the intersection test return ((Cytoplasm)frame.MeshContainer).OriginalMesh.Intersect(localPosn, localDirn); }
/// <summary> /// Called by UpdateFrames() to recursively calculate all combined matrices. /// </summary> /// <param name="frame"></param> /// <param name="parentMatrix"></param> private void RecurseFrameMatrices(JointFrame frame, Matrix parentMatrix) { // My combined transformation matrix is computed by combining my frame orientation // with my parent's combined matrix frame.CombinedMatrix = frame.TransformationMatrix * parentMatrix; // combine my matrix with total so far // Now propagate the new combined matrix through to my siblings and children if (frame.Sibling != null) // recurse through siblings { RecurseFrameMatrices(frame.Sibling, parentMatrix); } if (frame.FirstChild != null) // recurse through children { RecurseFrameMatrices(frame.FirstChild, frame.CombinedMatrix); } }
/// <summary> /// Find the frame in this hierarchy with this EXACT name /// </summary> /// <param name="root">the root to start searching from</param> /// <param name="name">the EXACT name of the frame</param> /// <returns>the frame of that name, or null if not found</returns> public static JointFrame Find(JointFrame root, string name) { // get the current frame's name. If we match then return if (root.Name == name) { return root; } // else continue with rest of hierarchy if (root.Sibling!=null) { JointFrame result = Find(root.Sibling,name); if (result!=null) return result; } if (root.FirstChild!=null) { JointFrame result = Find(root.FirstChild,name); if (result!=null) return result; } return null; }