private IEnumerator createMainChains(PrimaryStructureFrame frame)
        {
            int   interpolation = 5;
            int   resolution    = 5; // should be in config
            float radius        = 0.015f;
            int   currentIndex  = 0;

            foreach (Chain chain in primaryStructure.Chains())
            {
                if (chain.ResidueType != StandardResidue.AminoAcid)
                {
                    // UnityEngine.Debug.Log("Skipping main chain build. Non protein main chain not currently supported.");
                    continue;
                }

                List <Vector3> nodePositions = new List <Vector3>();

                foreach (Atom atom in chain.MainChainAtoms)
                {
                    // if no frame number use the base structure coordinates.
                    Vector3 position;
                    if (frame == null)
                    {
                        if (atom == null)
                        {
                            UnityEngine.Debug.Log("Main chain atom is null");
                        }

                        position = new Vector3(atom.Position.x, atom.Position.y, atom.Position.z);
                    }
                    else
                    {
                        position = new Vector3(frame.Coords[atom.Index * 3], frame.Coords[(atom.Index * 3) + 1], frame.Coords[(atom.Index * 3) + 2]);
                    }

                    // flip coord system for Unity
                    position.z *= -1;

                    nodePositions.Add(position);
                }

                List <DynamicMeshNode> nodes        = new List <DynamicMeshNode>();
                IEnumerable            splinePoints = Interpolate.NewCatmullRom(nodePositions.ToArray(), interpolation, false);
                Color32 chainColour = ChainColors[currentIndex % ChainColors.Length];

                foreach (Vector3 position in splinePoints)
                {
                    DynamicMeshNode node = new DynamicMeshNode();
                    node.Position    = position;
                    node.VertexColor = chainColour;
                    nodes.Add(node);
                }

                DynamicMesh mesh      = new DynamicMesh(nodes, radius, resolution, interpolation + 1);
                Mesh        chainMesh = mesh.Build(Settings.DebugFlag);

                GameObject chainStructure = (GameObject)Instantiate(ChainPrefab);
                chainStructure.GetComponent <MeshFilter>().sharedMesh = chainMesh;

                chainStructure.SetActive(false);
                chainStructure.transform.SetParent(ChainParent.transform, false);

                currentIndex++;
                yield return(null);
            }
        }
        /**
         * Returns sequence generator from the first node to the last node over
         * duration time using the points in-between the first and last node
         * as control points of a bezier curve used to generate the interpolated points
         * in the sequence. If there are no control points (ie. only two nodes, first
         * and last) then this behaves exactly the same as NewEase(). In other words
         * a zero-degree bezier spline curve is just the easing method. The sequence
         * is generated as it is accessed using the Time.deltaTime to calculate the
         * portion of duration that has elapsed.
         */
        public static IEnumerable <Vector3> NewBezier(Function ease, Transform[] nodes, float duration)
        {
            IEnumerable <float> timer = Interpolate.NewTimer(duration);

            return(NewBezier <Transform>(ease, nodes, TransformDotPosition, duration, timer));
        }
        /**
         * Returns sequence generator from start to end over duration using the
         * given easing function. The sequence is generated as it is accessed
         * using the Time.deltaTime to calculate the portion of duration that has
         * elapsed.
         */
        public static IEnumerator NewEase(Function ease, Vector3 start, Vector3 end, float duration)
        {
            IEnumerable <float> timer = Interpolate.NewTimer(duration);

            return(NewEase(ease, start, end, duration, timer));
        }
        /**
         * Instead of easing based on time, generate n interpolated points (slices)
         * between the start and end positions.
         */
        public static IEnumerator NewEase(Function ease, Vector3 start, Vector3 end, int slices)
        {
            IEnumerable <float> counter = Interpolate.NewCounter(0, slices + 1, 1);

            return(NewEase(ease, start, end, slices + 1, counter));
        }
        private Mesh BuildStructureMesh(MoleculeRenderSettings renderSettings, Chain chain, PrimaryStructureFrame frame, SecondaryStructure secondaryStructure)
        {
            Mesh  structureMesh = null;
            int   interpolation = 20;
            int   resolution    = 6; // should be in config
            float radius        = 0.015f;

            List <DynamicMeshNode> nodes = new List <DynamicMeshNode>();

            Vector3 lastPosition            = Vector3.zero;
            Vector3 lastNormal              = Vector3.zero;
            Vector3 averagedNormal          = Vector3.zero;
            SecondaryStructureType lastType = SecondaryStructureType.Coil;

            for (int i = 0; i < chain.MainChainResidues.Count; i++)
            {
                DynamicMeshNode node = new DynamicMeshNode();

                Residue residue = chain.MainChainResidues[i];

                // check if residue mainchain information is complete. Ignore if not
                if (residue.AlphaCarbon == null || residue.CarbonylCarbon == null || residue.CarbonylOxygen == null)
                {
                    continue;
                }

                // set position
                Atom atom = residue.AlphaCarbon;

                // if no frame number use the base structure coordinates.
                Vector3 position;
                if (frame == null)
                {
                    position = new Vector3(atom.Position.x, atom.Position.y, atom.Position.z);
                }
                else
                {
                    position = new Vector3(frame.Coords[atom.Index * 3], frame.Coords[(atom.Index * 3) + 1], frame.Coords[(atom.Index * 3) + 2]);
                }

                // flip coord system for Unity
                position.z *= -1;

                node.Position = position;

                SecondaryStructureInfomation structureInformation = secondaryStructure.GetStructureInformation(residue.Index);

                Residue nextResidue = null;
                if (i + 1 < chain.MainChainResidues.Count)
                {
                    nextResidue = chain.MainChainResidues[i + 1];
                }

                SecondaryStructureInfomation nextResidueStructureInfo = null;
                if (nextResidue != null)
                {
                    nextResidueStructureInfo = secondaryStructure.GetStructureInformation(nextResidue.Index);
                }

                // store the node type
                if (structureInformation != null)
                {
                    if (renderSettings.ShowHelices &&
                        (structureInformation.type == SecondaryStructureType.ThreeHelix ||
                         structureInformation.type == SecondaryStructureType.AlphaHelix ||
                         structureInformation.type == SecondaryStructureType.FiveHelix))
                    {
                        node.Type = DynamicMeshNodeType.SpiralRibbon;
                    }
                    else if (renderSettings.ShowSheets &&
                             structureInformation.type == SecondaryStructureType.BetaSheet)
                    {
                        if (nextResidue == null || (nextResidueStructureInfo != null && nextResidueStructureInfo.type != SecondaryStructureType.BetaSheet))
                        {
                            node.Type = DynamicMeshNodeType.RibbonHead;
                        }
                        else
                        {
                            node.Type = DynamicMeshNodeType.Ribbon;
                        }
                    }
                    else if (renderSettings.ShowTurns &&
                             structureInformation.type == SecondaryStructureType.Turn)
                    {
                        node.Type = DynamicMeshNodeType.LargeTube;
                    }
                    else
                    {
                        node.Type = DynamicMeshNodeType.Tube;
                    }


                    // calculate and store the node color

                    bool foundColour = false;

                    if (renderSettings.CustomResidueRenderSettings != null && renderSettings.CustomResidueRenderSettings.ContainsKey(residue.ID))
                    {
                        ResidueRenderSettings residueRenderSettings = renderSettings.CustomResidueRenderSettings[residue.ID];

                        if (residueRenderSettings != null && residueRenderSettings.ColourSecondaryStructure)
                        {
                            node.VertexColor = residueRenderSettings.ResidueColour;
                            foundColour      = true;
                        }
                    }

                    if (foundColour == false)
                    {
                        switch (structureInformation.type)
                        {
                        case SecondaryStructureType.ThreeHelix:
                            node.VertexColor = Settings.ThreeHelixColour;
                            break;

                        case SecondaryStructureType.AlphaHelix:
                            node.VertexColor = Settings.AlphaHelixColour;
                            break;

                        case SecondaryStructureType.FiveHelix:
                            node.VertexColor = Settings.FiveHelixColour;
                            break;

                        case SecondaryStructureType.Turn:
                            node.VertexColor = Settings.TurnColour;
                            break;

                        case SecondaryStructureType.BetaSheet:
                            node.VertexColor = Settings.BetaSheetColour;
                            break;

                        case SecondaryStructureType.BetaBridge:
                            node.VertexColor = Settings.BetaBridgeColour;
                            break;

                        case SecondaryStructureType.Bend:
                            node.VertexColor = Settings.BendColour;
                            break;

                        case SecondaryStructureType.Coil:
                            node.VertexColor = Settings.CoilColour;
                            break;

                        default:
                            node.VertexColor = ErrorColor;
                            break;
                        }
                    }
                }
                else
                {
                    Debug.Log("*** Structure info null: assigning defaults");
                    node.Type        = DynamicMeshNodeType.Tube;
                    node.VertexColor = ErrorColor;
                }

                // determine the node rotation
                // calculate the normal from the peptide plane and store as the node rotation
                Vector3 vertexA = residue.AlphaCarbon.Position;
                Vector3 vertexB = residue.CarbonylCarbon.Position;
                Vector3 vertexC = residue.CarbonylOxygen.Position;

                // flip coord system for Unity
                vertexA.z *= -1;
                vertexB.z *= -1;
                vertexC.z *= -1;

                //// create a triangle to show the peptide plane on the model for debugging purposes
                //GameObject residuePlane = createTriangle(vertexA, vertexB, vertexC);
                //residuePlane.name = "ResiduePlane";
                //AddMeshToModel(residuePlane, StructureParent);

                Vector3 direction = Vector3.Cross(vertexB - vertexA, vertexC - vertexA);
                Vector3 normal    = Vector3.Normalize(direction);

                if (structureInformation != null && structureInformation.type == SecondaryStructureType.BetaSheet || lastType == SecondaryStructureType.BetaSheet)
                {
                    if (Vector3.Dot(normal, lastNormal) < 0)
                    {
                        normal *= -1;
                    }

                    if (lastType != SecondaryStructureType.BetaSheet)
                    {
                        averagedNormal = normal;
                    }
                    else
                    {
                        averagedNormal += normal;
                        averagedNormal.Normalize();
                        normal = averagedNormal;
                    }
                }

                node.Rotation = normal;

                lastNormal = normal;
                if (structureInformation != null)
                {
                    lastType = structureInformation.type;
                }
                else
                {
                    lastType = SecondaryStructureType.Coil;
                }

                nodes.Add(node);
            }


            if (renderSettings.SmoothNodes)
            {
                nodes = smoothMeshNodes(nodes);
            }

            //// draw debug line from node points along rotation vector
            //for (int q = 0; q < nodePositions.Count; q++) {

            //    Vector3 fromPosition = nodePositions[q];
            //    Vector3 toPosition = fromPosition + nodeRotations[q] * 0.3f;
            //    GameObject line = createLine(fromPosition, toPosition, Color.white, Color.red);
            //    AddMeshToModel(line, StructureParent);
            //}

            List <Vector3> nodePositions = new List <Vector3>();

            foreach (DynamicMeshNode node in nodes)
            {
                nodePositions.Add(node.Position);
            }
            List <DynamicMeshNode> splineNodes  = new List <DynamicMeshNode>();
            IEnumerable            splinePoints = Interpolate.NewCatmullRom(nodePositions.ToArray(), interpolation, false);
            int j = 0;

            foreach (Vector3 position in splinePoints)
            {
                int nodeIndex = j / (interpolation + 1);
                int splinePointsSinceLastNode = j % (interpolation + 1);

                DynamicMeshNode node = new DynamicMeshNode();
                node.Position = position;

                //int colorIndex = nodeIndex % DebugColors.Count;
                //node.VertexColor = DebugColors[colorIndex];

                node.VertexColor = nodes[nodeIndex].VertexColor;
                node.Type        = nodes[nodeIndex].Type;

                // set the mesh rotations for the node
                // dont do rotations on tube structures
                switch (node.Type)
                {
                case DynamicMeshNodeType.Ribbon:
                case DynamicMeshNodeType.RibbonHead:
                case DynamicMeshNodeType.SpiralRibbon:

                    if (nodeIndex < nodes.Count - 1)
                    {
                        float percentThroughNode = (float)splinePointsSinceLastNode / ((float)interpolation + 1f);
                        node.Rotation = Vector3.Lerp((Vector3)nodes[nodeIndex].Rotation, (Vector3)nodes[nodeIndex + 1].Rotation, percentThroughNode);
                    }
                    else       // last node
                    {
                        node.Rotation = (Vector3)nodes[nodeIndex].Rotation;
                    }

                    break;
                }

                splineNodes.Add(node);
                j++;
            }

            DynamicMesh dynamicMesh = new DynamicMesh(splineNodes, radius, resolution, interpolation + 1);

            structureMesh = dynamicMesh.Build(Settings.DebugFlag);

            return(structureMesh);
        }