public override void TransportFrame(CurveFrame frame, CurveSection section, float sectionTwist) { if (frame != null) { frame.Transport(section, sectionTwist); } }
/** * Updates mesh for one trail segment: */ private void UpdateSegmentMesh(List <Point> input, int start, int end, Vector3 localCamPosition) { // Get a list of the actual points to render: either the original, unsmoothed points or the smoothed curve. List <Point> trail = GetRenderablePoints(input, start, end); if (trail.Count > 1) { float lenght = Mathf.Max(GetLenght(trail), epsilon); float partialLenght = 0; float vCoord = textureMode == TextureMode.Stretch ? 0 : -uvFactor * lenght * tileAnchor; Vector4 texTangent = Vector4.zero; Vector2 uv = Vector2.zero; Color vertexColor; bool hqCorners = highQualityCorners && alignment != TrailAlignment.Local; // Initialize curve frame using the first two points to calculate the first tangent vector: CurveFrame frame = InitializeCurveFrame(trail[trail.Count - 1].position, trail[trail.Count - 2].position); int va = 1; int vb = 0; for (int i = trail.Count - 1; i >= 0; --i) { // Calculate next and previous point indices: int nextIndex = Mathf.Max(i - 1, 0); int prevIndex = Mathf.Min(i + 1, trail.Count - 1); // Calculate next and previous trail vectors: Vector3 nextV = trail[nextIndex].position - trail[i].position; Vector3 prevV = trail[i].position - trail[prevIndex].position; float sectionLength = nextV.magnitude; nextV.Normalize(); prevV.Normalize(); // Calculate tangent vector: Vector3 tangent = alignment == TrailAlignment.Local ? trail[i].tangent : (nextV + prevV); tangent.Normalize(); // Calculate normal vector: Vector3 normal = trail[i].normal; if (alignment != TrailAlignment.Local) { normal = alignment == TrailAlignment.View ? localCamPosition - trail[i].position: frame.Transport(tangent, trail[i].position); } normal.Normalize(); // Calculate bitangent vector: Vector3 bitangent = alignment == TrailAlignment.Velocity ? frame.bitangent : Vector3.Cross(tangent, normal); bitangent.Normalize(); // Calculate this point's normalized (0,1) lenght and life. float normalizedLength = partialLenght / lenght; float normalizedLife = Mathf.Clamp01(1 - trail[i].life / time); partialLenght += sectionLength; // Calulate vertex color: vertexColor = trail[i].color * colorOverTime.Evaluate(normalizedLife) * colorOverLenght.Evaluate(normalizedLength); // Update vcoord: vCoord += uvFactor * (textureMode == TextureMode.Stretch ? sectionLength / lenght : sectionLength); // Calulate final thickness: float sectionThickness = thickness * trail[i].thickness * thicknessOverTime.Evaluate(normalizedLife) * thicknessOverLenght.Evaluate(normalizedLength); Quaternion q = Quaternion.identity; Vector3 corner = Vector3.zero; float curvatureSign = 0; float correctedThickness = sectionThickness; Vector3 prevSectionBitangent = bitangent; // High-quality corners: if (hqCorners) { Vector3 nextSectionBitangent = i == 0 ? bitangent : Vector3.Cross(nextV, Vector3.Cross(bitangent, tangent)).normalized; // If round corners are enabled: if (cornerRoundness > 0) { prevSectionBitangent = i == trail.Count - 1 ? -bitangent : Vector3.Cross(prevV, Vector3.Cross(bitangent, tangent)).normalized; // Calculate "elbow" angle: curvatureSign = (i == 0 || i == trail.Count - 1) ? 1 : Mathf.Sign(Vector3.Dot(nextV, -prevSectionBitangent)); float angle = (i == 0 || i == trail.Count - 1) ? Mathf.PI : Mathf.Acos(Mathf.Clamp(Vector3.Dot(nextSectionBitangent, prevSectionBitangent), -1, 1)); // Prepare a quaternion for incremental rotation of the corner vector: q = Quaternion.AngleAxis(Mathf.Rad2Deg * angle / cornerRoundness, normal * curvatureSign); corner = prevSectionBitangent * sectionThickness * curvatureSign; } // Calculate correct thickness by projecting corner bitangent onto the next section bitangent. This prevents "squeezing" if (nextSectionBitangent.sqrMagnitude > 0.1f) { correctedThickness = sectionThickness / Mathf.Max(Vector3.Dot(bitangent, nextSectionBitangent), 0.15f); } } // Append straight section mesh data: if (hqCorners && cornerRoundness > 0) { // bitangents are slightly asymmetrical in case of high-quality round or sharp corners: if (curvatureSign > 0) { vertices.Add(trail[i].position + prevSectionBitangent * sectionThickness); vertices.Add(trail[i].position - bitangent * correctedThickness); } else { vertices.Add(trail[i].position + bitangent * correctedThickness); vertices.Add(trail[i].position - prevSectionBitangent * sectionThickness); } } else { vertices.Add(trail[i].position + bitangent * correctedThickness); vertices.Add(trail[i].position - bitangent * correctedThickness); } normals.Add(-normal); normals.Add(-normal); texTangent = -bitangent; texTangent.w = 1; tangents.Add(texTangent); tangents.Add(texTangent); vertColors.Add(vertexColor); vertColors.Add(vertexColor); uv.Set(vCoord, 0); uvs.Add(uv); uv.Set(vCoord, 1); uvs.Add(uv); if (i < trail.Count - 1) { int vc = vertices.Count - 1; tris.Add(vc); tris.Add(va); tris.Add(vb); tris.Add(vb); tris.Add(vc - 1); tris.Add(vc); } va = vertices.Count - 1; vb = vertices.Count - 2; // Append smooth corner mesh data: if (hqCorners && cornerRoundness > 0) { for (int p = 0; p <= cornerRoundness; ++p) { vertices.Add(trail[i].position + corner); normals.Add(-normal); tangents.Add(texTangent); vertColors.Add(vertexColor); uv.Set(vCoord, curvatureSign > 0?0:1); uvs.Add(uv); int vc = vertices.Count - 1; tris.Add(vc); tris.Add(va); tris.Add(vb); if (curvatureSign > 0) { vb = vc; } else { va = vc; } // rotate corner point: corner = q * corner; } } } } }
public void LateUpdate() { ClearMeshData(); if (section == null) { return; } IList <List <Vector3> > segments = cable.sampledCable.Segments; 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 * cable.RestLength; // v texture coordinate. int sectionIndex = 0; float strain = cable.sampledCable.Length / cable.RestLength; // we will define and transport a reference frame along the curve using parallel transport method: if (frame == null) { frame = new CurveFrame(); } Vector4 texTangent = Vector4.zero; Vector2 uv = Vector2.zero; Matrix4x4 w2l = transform.worldToLocalMatrix; for (int k = 0; k < segments.Count; ++k) { List <Vector3> samples = segments[k]; // Reinitialize frame for each segment. frame.Reset(); for (int i = 0; i < samples.Count; ++i) { // Calculate previous and next curve indices: int nextIndex = Mathf.Min(i + 1, samples.Count - 1); int prevIndex = Mathf.Max(i - 1, 0); Vector3 point = w2l.MultiplyPoint3x4(samples[i]); Vector3 prevPoint = w2l.MultiplyPoint3x4(samples[prevIndex]); Vector3 nextPoint = w2l.MultiplyPoint3x4(samples[nextIndex]); Vector3 nextV = (nextPoint - point).normalized; Vector3 prevV = (point - prevPoint).normalized; Vector3 tangent = nextV + prevV; // update frame: frame.Transport(point, tangent, 0); // advance v texcoord: vCoord += uvScale.y * (Vector3.Distance(point, prevPoint) / strain); // 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) * thickness); normals.Add(vertices[vertices.Count - 1] - frame.position); texTangent = -Vector3.Cross(normals[normals.Count - 1], frame.tangent); texTangent.w = 1; tangents.Add(texTangent); uv.Set((j / (float)sectionSegments) * uvScale.x, vCoord); uvs.Add(uv); if (j < sectionSegments && i < samples.Count - 1) { tris.Add(sectionIndex * verticesPerSection + j); tris.Add(sectionIndex * verticesPerSection + (j + 1)); tris.Add((sectionIndex + 1) * verticesPerSection + j); tris.Add(sectionIndex * verticesPerSection + (j + 1)); tris.Add((sectionIndex + 1) * verticesPerSection + (j + 1)); tris.Add((sectionIndex + 1) * verticesPerSection + j); } } sectionIndex++; } } CommitMeshData(); }
/** * 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: CurveFrame frame = new CurveFrame(); 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, 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(); }