public override void TransportFrame(ObiCurveFrame frame, ObiCurveSection section, float sectionTwist) { if (frame != null) { frame.Set(section); } }
public override void UpdateTearPrefab(ObiCurveFrame frame, ref int tearCount, bool reverseLookDirection) { if (tearPrefabPool != null && tearCount < tearPrefabPool.Length) { if (!tearPrefabPool[tearCount].activeSelf) { tearPrefabPool[tearCount].SetActive(true); } PlaceObjectAtCurveFrame(frame, tearPrefabPool[tearCount], Space.Self, reverseLookDirection); tearCount++; } }
public void PlaceObjectAtCurveFrame(ObiCurveFrame frame, GameObject obj, Space space, bool reverseLookDirection) { if (space == Space.Self) { Matrix4x4 l2w = transform.localToWorldMatrix; obj.transform.position = l2w.MultiplyPoint3x4(frame.position); if (frame.tangent != Vector3.zero) { obj.transform.rotation = Quaternion.LookRotation(l2w.MultiplyVector(reverseLookDirection ? frame.tangent:-frame.tangent), l2w.MultiplyVector(frame.normal)); } } else { obj.transform.position = frame.position; if (frame.tangent != Vector3.zero) { obj.transform.rotation = Quaternion.LookRotation(reverseLookDirection ? frame.tangent:-frame.tangent, frame.normal); } } }
/** * Generate a list of smooth curves using particles as control points. Will take into account cuts in the rope, * generating one curve for each continuous piece of rope. */ public void SmoothCurvesFromParticles() { curves.Clear(); curveSections = 0; curveLength = 0; // count amount of segments in each rope chunk: CountContinuousSegments(); Matrix4x4 w2l = transform.worldToLocalMatrix; Quaternion matrixRotation = Quaternion.LookRotation( w2l.GetColumn(2), w2l.GetColumn(1)); int firstSegment = 0; // generate curve for each rope chunk: for (int i = 0; i < rawCurves.Count; ++i) { int segments = rawCurves[i].Count - 1; // allocate memory for the curve: ObiList <ObiCurveSection> controlPoints = rawCurves[i]; // get control points position: int lastParticle = -1; int particle1 = -1, particle2 = -1; for (int m = 0; m < segments; ++m) { if (GetStructuralConstraintParticles(firstSegment + m, ref particle1, ref particle2)) { if (m == 0) { lastParticle = particle1; } // Find next and previous vectors: Vector3 nextV = GetParticlePosition(particle2) - GetParticlePosition(particle1); Vector3 prevV = GetParticlePosition(particle1) - GetParticlePosition(lastParticle); Vector3 pos = w2l.MultiplyPoint3x4(GetParticlePosition(particle1)); Quaternion orient = matrixRotation * Quaternion.SlerpUnclamped(GetParticleOrientation(lastParticle), GetParticleOrientation(particle1), 0.5f); Vector3 tangent = w2l.MultiplyVector(prevV + nextV).normalized; Color color = (this.colors != null && particle1 < this.colors.Length) ? this.colors[particle1] : Color.white; controlPoints[m] = new ObiCurveSection(new Vector4(pos.x, pos.y, pos.z, principalRadii[particle1][0]), tangent, orient * Vector3.up, color); lastParticle = particle1; } } // last segment adds its second particle too: if (segments > 0) { Vector3 pos = w2l.MultiplyPoint3x4(GetParticlePosition(particle2)); Quaternion orient = matrixRotation * GetParticleOrientation(particle1); Vector3 tangent = w2l.MultiplyVector(GetParticlePosition(particle2) - GetParticlePosition(particle1)).normalized; Color color = (this.colors != null && particle2 < this.colors.Length) ? this.colors[particle2] : Color.white; controlPoints[segments] = new ObiCurveSection(new Vector4(pos.x, pos.y, pos.z, principalRadii[particle2][0]), tangent, orient * Vector3.up, color); } firstSegment += segments; // get smooth curve points: ObiCurveFrame.Chaikin(controlPoints, curves[i], smoothing); // count total curve sections and total curve length: curveSections += curves[i].Count - 1; curveLength += CalculateCurveLength(curves[i]); } }
public abstract void TransportFrame(ObiCurveFrame frame, ObiCurveSection section, float sectionTwist);
public virtual void UpdateTearPrefab(ObiCurveFrame frame, ref int tearCount, bool reverseLookDirection) { return; }
/** * Generates the particle based physical representation of the rope. This is the initialization method for the rope object * and should not be called directly once the object has been created. */ protected override IEnumerator Initialize() { initialized = false; initializing = true; interParticleDistance = -1; RemoveFromSolver(null); if (ropePath == null) { Debug.LogError("Cannot initialize rope. There's no ropePath present. Please provide a spline to define the shape of the rope"); yield break; } ropePath.RecalculateSplineLenght(0.00001f, 7); closed = ropePath.closed; restLength = ropePath.Length; usedParticles = Mathf.CeilToInt(restLength / thickness * resolution) + (closed ? 0:1); totalParticles = usedParticles; active = new bool[totalParticles]; positions = new Vector3[totalParticles]; orientations = new Quaternion[totalParticles]; velocities = new Vector3[totalParticles]; angularVelocities = new Vector3[totalParticles]; invMasses = new float[totalParticles]; invRotationalMasses = new float[totalParticles]; principalRadii = new Vector3[totalParticles]; phases = new int[totalParticles]; restPositions = new Vector4[totalParticles]; restOrientations = new Quaternion[totalParticles]; colors = new Color[totalParticles]; int numSegments = usedParticles - (closed ? 0:1); if (numSegments > 0) { interParticleDistance = restLength / (float)numSegments; } else { interParticleDistance = 0; } float radius = interParticleDistance * resolution; for (int i = 0; i < usedParticles; i++) { active[i] = true; invMasses[i] = 1.0f / DEFAULT_PARTICLE_MASS; invRotationalMasses[i] = 1.0f / DEFAULT_PARTICLE_ROTATIONAL_MASS; float mu = ropePath.GetMuAtLenght(interParticleDistance * i); positions[i] = transform.InverseTransformPoint(ropePath.transform.TransformPoint(ropePath.GetPositionAt(mu))); principalRadii[i] = Vector3.one * radius; phases[i] = Oni.MakePhase(1, selfCollisions?Oni.ParticlePhase.SelfCollide:0); colors[i] = Color.white; if (i % 100 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiRod: generating particles...", i / (float)usedParticles)); } } StretchShearConstraints.Clear(); ObiStretchShearConstraintBatch stretchBatch = new ObiStretchShearConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS); StretchShearConstraints.AddBatch(stretchBatch); // rotation minimizing frame: ObiCurveFrame frame = new ObiCurveFrame(); frame.Reset(); for (int i = 0; i < numSegments; i++) { int next = (i + 1) % (ropePath.closed ? usedParticles:usedParticles + 1); float mu = ropePath.GetMuAtLenght(interParticleDistance * i); Vector3 normal = transform.InverseTransformVector(ropePath.transform.TransformVector(ropePath.GetNormalAt(mu))); frame.Transport(positions[i], (positions[next] - positions[i]).normalized, 0); orientations[i] = Quaternion.LookRotation(frame.tangent, normal); restOrientations[i] = orientations[i]; // Also set the orientation of the next particle. If it is not the last one, we will overwrite it. // This makes sure that open rods provide an orientation for their last particle (or rather, a phantom segment past the last particle). orientations[next] = orientations[i]; restOrientations[next] = orientations[i]; stretchBatch.AddConstraint(i, next, interParticleDistance, Quaternion.identity, Vector3.one); if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiRod: generating structural constraints...", i / (float)numSegments)); } } BendTwistConstraints.Clear(); ObiBendTwistConstraintBatch twistBatch = new ObiBendTwistConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS); BendTwistConstraints.AddBatch(twistBatch); // the last bend constraint couples the last segment and a phantom segment past the last particle. for (int i = 0; i < numSegments; i++) { int next = (i + 1) % (ropePath.closed ? usedParticles:usedParticles + 1); Quaternion darboux = keepInitialShape ? ObiUtils.RestDarboux(orientations[i], orientations[next]) : Quaternion.identity; twistBatch.AddConstraint(i, next, darboux, Vector3.one); if (i % 500 == 0) { yield return(new CoroutineJob.ProgressInfo("ObiRod: generating structural constraints...", i / (float)numSegments)); } } ChainConstraints.Clear(); ObiChainConstraintBatch chainBatch = new ObiChainConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS); ChainConstraints.AddBatch(chainBatch); int[] indices = new int[usedParticles + (closed ? 1:0)]; for (int i = 0; i < usedParticles; ++i) { indices[i] = i; } // Add the first particle as the last index of the chain, if closed. if (closed) { indices[usedParticles] = 0; } chainBatch.AddConstraint(indices, interParticleDistance, 1, 1); // Initialize tether constraints: TetherConstraints.Clear(); // Initialize pin constraints: PinConstraints.Clear(); ObiPinConstraintBatch pinBatch = new ObiPinConstraintBatch(false, false, MIN_YOUNG_MODULUS, MAX_YOUNG_MODULUS); PinConstraints.AddBatch(pinBatch); initializing = false; initialized = true; RegenerateRestPositions(); }
public override void UpdateRenderer(object sender, EventArgs e) { // In case there are no link prefabs to instantiate: if (linkPrefabs.Count == 0) { return; } // Regenerate instances if needed: if (linkInstances == null || linkInstances.Count < rope.TotalParticles) { CreateChainLinkInstances(); } int constraintCount = rope.GetStructuralConstraintCount(); // we will define and transport a reference frame along the curve using parallel transport method: if (frame == null) { frame = new ObiCurveFrame(); } frame.Reset(); frame.SetTwist(-sectionTwist * constraintCount * twistAnchor); int lastParticle = -1; int tearCount = 0; for (int i = 0; i < constraintCount; ++i) { int particle1 = -1, particle2 = -1; rope.GetStructuralConstraintParticles(i, ref particle1, ref particle2); Vector3 pos = rope.GetParticlePosition(particle1); Vector3 nextPos = rope.GetParticlePosition(particle2); Vector3 linkVector = nextPos - pos; Vector3 tangent = linkVector.normalized; // update tear prefab at the first side of tear: if (i > 0 && particle1 != lastParticle) { rope.UpdateTearPrefab(frame, ref tearCount, false); // reset frame at discontinuities: frame.Reset(); } // update frame: TODO: allow mesh rendering offset from torsion (twist) rope.TransportFrame(frame, new ObiCurveSection(nextPos, tangent, rope.GetParticleOrientation(particle1) * Vector3.up, Color.white), sectionTwist); // update tear prefab at the other side of the tear: if (i > 0 && particle1 != lastParticle) { frame.position = pos; rope.UpdateTearPrefab(frame, ref tearCount, false); } // update start/end prefabs: if (!rope.Closed) { if (i == 0 && rope.startPrefabInstance != null) { rope.PlaceObjectAtCurveFrame(frame, rope.startPrefabInstance, Space.World, false); } else if (i == constraintCount - 1 && rope.endPrefabInstance != null) { frame.position = nextPos; rope.PlaceObjectAtCurveFrame(frame, rope.endPrefabInstance, Space.World, true); } } if (linkInstances[i] != null) { linkInstances[i].SetActive(true); Transform linkTransform = linkInstances[i].transform; linkTransform.position = pos + linkVector * 0.5f; linkTransform.localScale = rope.thicknessFromParticles ? (rope.principalRadii[particle1][0] / rope.thickness) * linkScale : linkScale; linkTransform.rotation = Quaternion.LookRotation(tangent, frame.normal); } lastParticle = particle2; } for (int i = constraintCount; i < linkInstances.Count; ++i) { if (linkInstances[i] != null) { linkInstances[i].SetActive(false); } } }
public override void UpdateRenderer(object sender, EventArgs e) { if (section == null || extrudedMesh == null) { return; } rope.SmoothCurvesFromParticles(); CreateMeshIfNeeded(); ClearMeshData(); float actualToRestLengthRatio = rope.SmoothLength / rope.RestLength; int sectionSegments = section.Segments; int verticesPerSection = sectionSegments + 1; // the last vertex in each section must be duplicated, due to uv wraparound. float vCoord = -uvScale.y * rope.RestLength * uvAnchor; // v texture coordinate. int sectionIndex = 0; int tearCount = 0; // we will define and transport a reference frame along the curve using parallel transport method: if (frame == null) { frame = new ObiCurveFrame(); } frame.Reset(); frame.SetTwist(-sectionTwist * rope.SmoothSections * uvAnchor); // for closed curves, last frame of the last curve must be equal to first frame of first curve. Vector3 firstTangent = Vector3.forward; Vector4 texTangent = Vector4.zero; Vector2 uv = Vector2.zero; for (int c = 0; c < rope.curves.Count; ++c) { ObiList <ObiCurveSection> curve = rope.curves[c]; // Reinitialize frame for each curve. frame.Reset(); for (int i = 0; i < curve.Count; ++i) { // Calculate previous and next curve indices: //int nextIndex = Mathf.Min(i+1,curve.Count-1); int prevIndex = Mathf.Max(i - 1, 0); // Calculate current tangent as the vector between previous and next curve points: //Vector3 nextV; // The next tangent of the last segment of the last curve in a closed rope, is the first tangent again: /*if (rope.Closed && c == rope.curves.Count-1 && i == curve.Count-1 ) * nextV = firstTangent; * else * nextV = curve[nextIndex].positionAndRadius - curve[i].positionAndRadius;*/ //Vector3 prevV = curve[i].positionAndRadius - curve[prevIndex].positionAndRadius; //Vector3 tangent = nextV + prevV; // update frame: rope.TransportFrame(frame, curve[i], sectionTwist); //curve[i].positionAndRadius,tangent,rope.sectionTwist); // update tear prefabs (first segment of not first curve, last segment of not last curve) if (c > 0 && i == 0) { rope.UpdateTearPrefab(frame, ref tearCount, false); } if (c < rope.curves.Count - 1 && i == curve.Count - 1) { rope.UpdateTearPrefab(frame, ref tearCount, true); } // update start/end prefabs: if (c == 0 && i == 0) { // store first tangent of the first curve (for closed ropes): firstTangent = frame.tangent; if (rope.startPrefabInstance != null && !rope.Closed) { rope.PlaceObjectAtCurveFrame(frame, rope.startPrefabInstance, Space.Self, false); } } else if (c == rope.curves.Count - 1 && i == curve.Count - 1 && rope.endPrefabInstance != null && !rope.Closed) { rope.PlaceObjectAtCurveFrame(frame, rope.endPrefabInstance, Space.Self, true); } // advance v texcoord: vCoord += uvScale.y * (Vector3.Distance(curve[i].positionAndRadius, curve[prevIndex].positionAndRadius) / (normalizeV ? rope.SmoothLength : actualToRestLengthRatio)); // calculate section thickness (either constant, or particle radius based): float sectionThickness = (rope.thicknessFromParticles ? curve[i].positionAndRadius.w : rope.thickness) * sectionThicknessScale; // Loop around each segment: for (int j = 0; j <= sectionSegments; ++j) { vertices.Add(frame.position + (section.vertices[j].x * frame.normal + section.vertices[j].y * frame.binormal) * sectionThickness); normals.Add(vertices[vertices.Count - 1] - frame.position); texTangent = Vector3.Cross(normals[normals.Count - 1], frame.tangent); texTangent.w = -1; tangents.Add(texTangent); vertColors.Add(curve[i].color); uv.Set((j / (float)sectionSegments) * uvScale.x, vCoord); uvs.Add(uv); if (j < sectionSegments && i < curve.Count - 1) { tris.Add(sectionIndex * verticesPerSection + j); tris.Add((sectionIndex + 1) * verticesPerSection + j); tris.Add(sectionIndex * verticesPerSection + (j + 1)); tris.Add(sectionIndex * verticesPerSection + (j + 1)); tris.Add((sectionIndex + 1) * verticesPerSection + j); tris.Add((sectionIndex + 1) * verticesPerSection + (j + 1)); } } sectionIndex++; } } CommitMeshData(); }
public override void UpdateRenderer(object sender, EventArgs e) { if (mesh == null) { return; } rope.SmoothCurvesFromParticles(); if (rope.curves.Count == 0) { return; } ObiList <ObiCurveSection> curve = rope.curves[0]; if (curve.Count < 2) { return; } float actualToRestLengthRatio = stretchWithRope ? rope.SmoothLength / rope.RestLength : 1; // squashing factor, makes mesh thinner when stretched and thicker when compresssed. float squashing = Mathf.Clamp(1 + volumeScaling * (1 / Mathf.Max(actualToRestLengthRatio, 0.01f) - 1), 0.01f, 2); // Calculate scale along swept axis so that the mesh spans the entire lenght of the rope if required. Vector3 actualScale = scale; if (spanEntireLength) { actualScale[(int)axis] = rope.RestLength / meshSizeAlongAxis; } float previousVertexValue = 0; float meshLength = 0; int index = 0; int nextIndex = 1; int prevIndex = 0; Vector3 nextV = curve[nextIndex].positionAndRadius - curve[index].positionAndRadius; Vector3 prevV = curve[index].positionAndRadius - curve[prevIndex].positionAndRadius; Vector3 tangent = (nextV + prevV).normalized; float sectionMagnitude = nextV.magnitude; // we will define and transport a reference frame along the curve using parallel transport method: if (frame == null) { frame = new ObiCurveFrame(); } frame.Reset(); frame.SetTwistAndTangent(-sectionTwist * rope.SmoothSections * twistAnchor, tangent); // set frame's initial position: frame.position = curve[index].positionAndRadius; // basis matrix for deforming the mesh, also calculate column offsets based on swept axis: Matrix4x4 basis = new Matrix4x4(); int xo = ((int)axis) % 3 * 4; int yo = ((int)axis + 1) % 3 * 4; int zo = ((int)axis + 2) % 3 * 4; basis[xo] = frame.tangent[0]; basis[xo + 1] = frame.tangent[1]; basis[xo + 2] = frame.tangent[2]; basis[yo] = frame.binormal[0]; basis[yo + 1] = frame.binormal[1]; basis[yo + 2] = frame.binormal[2]; basis[zo] = frame.normal[0]; basis[zo + 1] = frame.normal[1]; basis[zo + 2] = frame.normal[2]; for (int i = 0; i < orderedVertices.Length; ++i) { int vIndex = orderedVertices[i]; float vertexValue = inputVertices[vIndex][(int)axis] * actualScale[(int)axis] + offset; // Calculate how much we've advanced in the sort axis since the last vertex: meshLength += (vertexValue - previousVertexValue) * actualToRestLengthRatio; previousVertexValue = vertexValue; // If we have advanced to the next section of the curve: while (meshLength > sectionMagnitude && sectionMagnitude > Mathf.Epsilon) { meshLength -= sectionMagnitude; index = Mathf.Min(index + 1, curve.Count - 1); // Calculate previous and next curve indices: nextIndex = Mathf.Min(index + 1, curve.Count - 1); prevIndex = Mathf.Max(index - 1, 0); // Calculate current tangent as the vector between previous and next curve points: nextV = curve[index].positionAndRadius - curve[nextIndex].positionAndRadius; /*prevV = curve[index].positionAndRadius - curve[prevIndex].positionAndRadius; * tangent = (nextV + prevV).normalized;*/ sectionMagnitude = nextV.magnitude; // TODO: revisar, ya que la w deberÃa ser cero, no debe influir el radio. // Transport frame: frame.Transport(curve[index], sectionTwist); // Update basis matrix: basis[xo] = frame.tangent[0]; basis[xo + 1] = frame.tangent[1]; basis[xo + 2] = frame.tangent[2]; basis[yo] = frame.binormal[0]; basis[yo + 1] = frame.binormal[1]; basis[yo + 2] = frame.binormal[2]; basis[zo] = frame.normal[0]; basis[zo + 1] = frame.normal[1]; basis[zo + 2] = frame.normal[2]; } float sectionThickness = rope.thicknessFromParticles ? curve[index].positionAndRadius.w : rope.thickness; // calculate deformed vertex position: Vector3 offsetFromCurve = Vector3.Scale(inputVertices[vIndex], actualScale * sectionThickness * squashing); offsetFromCurve[(int)axis] = meshLength; vertices[vIndex] = frame.position + basis.MultiplyVector(offsetFromCurve); normals[vIndex] = basis.MultiplyVector(inputNormals[vIndex]); tangents[vIndex] = basis * inputTangents[vIndex]; // avoids expensive implicit conversion from Vector4 to Vector3. tangents[vIndex].w = inputTangents[vIndex].w; } CommitMeshData(); }
public void UpdateRenderer(Camera camera) { if (camera == null || !rope.gameObject.activeInHierarchy) { return; } rope.SmoothCurvesFromParticles(); CreateMeshIfNeeded(); ClearMeshData(); float actualToRestLengthRatio = rope.SmoothLength / rope.RestLength; float vCoord = -uvScale.y * rope.RestLength * uvAnchor; // v texture coordinate. int sectionIndex = 0; int tearCount = 0; Vector3 localSpaceCamera = rope.transform.InverseTransformPoint(camera.transform.position); // we will define and transport a reference frame along the curve using parallel transport method: if (frame == null) { frame = new ObiCurveFrame(); } frame.Reset(); // for closed curves, last frame of the last curve must be equal to first frame of first curve. Vector3 firstTangent = Vector3.forward; Vector4 texTangent = Vector4.zero; Vector2 uv = Vector2.zero; for (int c = 0; c < rope.curves.Count; ++c) { ObiList <ObiCurveSection> curve = rope.curves[c]; // Reinitialize frame for each curve. frame.Reset(); for (int i = 0; i < curve.Count; ++i) { // Calculate previous and next curve indices: //int nextIndex = Mathf.Min(i+1,curve.Count-1); int prevIndex = Mathf.Max(i - 1, 0); // Calculate current tangent as the vector between previous and next curve points: /*Vector3 nextV; * * // The next tangent of the last segment of the last curve in a closed rope, is the first tangent again: * if (rope.Closed && c == rope.curves.Count-1 && i == curve.Count-1 ) * nextV = firstTangent; * else * nextV = curve[nextIndex].positionAndRadius - curve[i].positionAndRadius; * * Vector3 prevV = curve[i].positionAndRadius - curve[prevIndex].positionAndRadius; * Vector3 tangent = nextV + prevV;*/ // update frame: frame.Transport(curve[i], 0); // update tear prefabs: if (c > 0 && i == 0) { rope.UpdateTearPrefab(frame, ref tearCount, false); } if (c < rope.curves.Count - 1 && i == curve.Count - 1) { rope.UpdateTearPrefab(frame, ref tearCount, true); } // update start/end prefabs: if (c == 0 && i == 0) { // store first tangent of the first curve (for closed ropes): firstTangent = frame.tangent; if (rope.startPrefabInstance != null && !rope.Closed) { rope.PlaceObjectAtCurveFrame(frame, rope.startPrefabInstance, Space.Self, false); } } else if (c == rope.curves.Count - 1 && i == curve.Count - 1 && rope.endPrefabInstance != null && !rope.Closed) { rope.PlaceObjectAtCurveFrame(frame, rope.endPrefabInstance, Space.Self, true); } // advance v texcoord: vCoord += uvScale.y * (Vector3.Distance(curve[i].positionAndRadius, curve[prevIndex].positionAndRadius) / (normalizeV?rope.SmoothLength:actualToRestLengthRatio)); // calculate section thickness (either constant, or particle radius based): float sectionThickness = (rope.thicknessFromParticles ? curve[i].positionAndRadius.w : rope.thickness) * sectionThicknessScale; Vector3 normal = frame.position - localSpaceCamera; normal.Normalize(); Vector3 bitangent = Vector3.Cross(normal, frame.tangent); bitangent.Normalize(); vertices.Add(frame.position + bitangent * sectionThickness); vertices.Add(frame.position - bitangent * sectionThickness); normals.Add(-normal); normals.Add(-normal); texTangent = -bitangent; texTangent.w = 1; tangents.Add(texTangent); tangents.Add(texTangent); vertColors.Add(curve[i].color); vertColors.Add(curve[i].color); uv.Set(0, vCoord); uvs.Add(uv); uv.Set(1, vCoord); uvs.Add(uv); if (i < curve.Count - 1) { tris.Add(sectionIndex * 2); tris.Add((sectionIndex + 1) * 2); tris.Add(sectionIndex * 2 + 1); tris.Add(sectionIndex * 2 + 1); tris.Add((sectionIndex + 1) * 2); tris.Add((sectionIndex + 1) * 2 + 1); } sectionIndex++; } } CommitMeshData(); }