/** * Iterative version of the Ramer-Douglas-Peucker path decimation algorithm. */ private bool Decimate(ObiList <ObiPathFrame> input, ObiList <ObiPathFrame> output, float threshold) { // no decimation, no work to do, just return: if (threshold < 0.00001f || input.Count < 3) { return(false); } float scaledThreshold = threshold * threshold * 0.01f; stack.Push(new Vector2Int(0, input.Count - 1)); var bitArray = new BitArray(input.Count, true); while (stack.Count > 0) { var range = stack.Pop(); float dmax = 0; int index = range.x; float mu = 0; for (int i = index + 1; i < range.y; ++i) { if (bitArray[i]) { float d = Vector3.SqrMagnitude(ObiUtils.ProjectPointLine(input[i].position, input[range.x].position, input[range.y].position, out mu) - input[i].position); if (d > dmax) { index = i; dmax = d; } } } if (dmax > scaledThreshold) { stack.Push(new Vector2Int(range.x, index)); stack.Push(new Vector2Int(index, range.y)); } else { for (int i = range.x + 1; i < range.y; ++i) { bitArray[i] = false; } } } output.Clear(); for (int i = 0; i < bitArray.Count; ++i) { if (bitArray[i]) { output.Add(input[i]); } } return(true); }
/** * Generates raw curve chunks from the rope description. */ private void AllocateRawChunks(ObiRopeBase actor) { using (m_AllocateRawChunksPerfMarker.Auto()) { rawChunks.Clear(); if (actor.path == null) { return; } // Count particles for each chunk. int particles = 0; for (int i = 0; i < actor.elements.Count; ++i) { particles++; // At discontinuities, start a new chunk. if (i < actor.elements.Count - 1 && actor.elements[i].particle2 != actor.elements[i + 1].particle1) { AllocateChunk(++particles); particles = 0; } } AllocateChunk(++particles); } }
/** * Counts the amount of continuous sections in each chunk of rope. */ protected void CountContinuousSegments() { rawCurves.Clear(); int segmentCount = 0; int lastParticle = -1; // Iterate trough all distance constraints in order. If we find a discontinuity, reset segment count: int constraintCount = GetStructuralConstraintCount(); for (int i = 0; i < constraintCount; ++i) { int particle1 = -1, particle2 = -1; if (GetStructuralConstraintParticles(i, ref particle1, ref particle2)) { // start new curve at discontinuities: if (particle1 != lastParticle && segmentCount > 0) { // add a new curve with the correct amount of sections: AddCurve(segmentCount + 1); segmentCount = 0; } lastParticle = particle2; segmentCount++; } } // add the last curve: AddCurve(segmentCount + 1); }
/** * Counts the amount of continuous sections in each chunk of rope. */ protected void CountContinuousSegments() { rawCurves.Clear(); ObiDistanceConstraintBatch distanceBatch = DistanceConstraints.GetFirstBatch(); int segmentCount = 0; int lastParticle = -1; // Iterate trough all distance constraints in order. If we find a discontinuity, reset segment count: for (int i = 0; i < distanceBatch.ConstraintCount; ++i) { int particle1 = distanceBatch.springIndices[i * 2]; int particle2 = distanceBatch.springIndices[i * 2 + 1]; // start new curve at discontinuities: if (particle1 != lastParticle && segmentCount > 0) { // add a new curve with the correct amount of sections: AddCurve(segmentCount + 1); segmentCount = 0; } lastParticle = particle2; segmentCount++; } // add the last curve: AddCurve(segmentCount + 1); }
/** * 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(); ObiDistanceConstraintBatch distanceBatch = DistanceConstraints.GetFirstBatch(); Matrix4x4 w2l = transform.worldToLocalMatrix; 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 <CurveSection> controlPoints = rawCurves[i]; // get control points position: for (int m = 0; m < segments; ++m) { int particleIndex = distanceBatch.springIndices[(firstSegment + m) * 2]; controlPoints[m] = new CurveSection(w2l.MultiplyPoint3x4(GetParticlePosition(particleIndex)), solidRadii[particleIndex], (this.colors != null && particleIndex < this.colors.Length) ? this.colors[particleIndex] : Color.white); // last segment adds its second particle too: if (m == segments - 1) { particleIndex = distanceBatch.springIndices[(firstSegment + m) * 2 + 1]; controlPoints[m + 1] = new CurveSection(w2l.MultiplyPoint3x4(GetParticlePosition(particleIndex)), solidRadii[particleIndex], (this.colors != null && particleIndex < this.colors.Length) ? this.colors[particleIndex] : Color.white); } } firstSegment += segments; // get smooth curve points: ChaikinSmoothing(controlPoints, curves[i], smoothing); // count total curve sections and total curve length: curveSections += curves[i].Count - 1; curveLength += CalculateCurveLength(curves[i]); } }
/** * Generates smooth curve chunks. */ public void GenerateSmoothChunks(ObiRopeBase actor, uint smoothingLevels) { using (m_GenerateSmoothChunksPerfMarker.Auto()) { smoothChunks.Clear(); smoothSections = 0; smoothLength = 0; if (!Application.isPlaying) { actor.RebuildElementsFromConstraints(); } AllocateRawChunks(actor); w2l = actor.transform.worldToLocalMatrix; w2lRotation = w2l.rotation; // keep track of the first element of each chunk int chunkStart = 0; ObiPathFrame frame_0 = new ObiPathFrame(); // "next" frame ObiPathFrame frame_1 = new ObiPathFrame(); // current frame ObiPathFrame frame_2 = new ObiPathFrame(); // previous frame // generate curve for each rope chunk: for (int i = 0; i < rawChunks.Count; ++i) { int elementCount = rawChunks[i].Count - 1; // Initialize frames: frame_0.Reset(); frame_1.Reset(); frame_2.Reset(); PathFrameFromParticle(actor, ref frame_1, actor.elements[chunkStart].particle1, false); frame_2 = frame_1; for (int m = 1; m <= rawChunks[i].Count; ++m) { int index; if (m >= elementCount) { // second particle of last element in the chunk. index = actor.elements[chunkStart + elementCount - 1].particle2; } else { //first particle of current element. index = actor.elements[chunkStart + m].particle1; } // generate curve frame from particle: PathFrameFromParticle(actor, ref frame_0, index); if (actor.usesOrientedParticles) { // copy frame directly. frame_2 = frame_1; } else { // perform parallel transport, using forward / backward average to calculate tangent. frame_1.tangent = ((frame_1.position - frame_2.position) + (frame_0.position - frame_1.position)).normalized; frame_2.Transport(frame_1, twist); } // in case we wrapped around the rope, average first and last frames: if (chunkStart + m > actor.activeParticleCount) { frame_2 = rawChunks[0][0] = 0.5f * frame_2 + 0.5f * rawChunks[0][0]; } frame_1 = frame_0; rawChunks[i][m - 1] = frame_2; } // increment chunkStart by the amount of elements in this chunk: chunkStart += elementCount; // adaptive curvature-based decimation: if (Decimate(rawChunks[i], smoothChunks[i], decimation)) { // if any decimation took place, swap raw and smooth chunks: var aux = rawChunks[i]; rawChunks[i] = smoothChunks[i]; smoothChunks[i] = aux; } // get smooth curve points: Chaikin(rawChunks[i], smoothChunks[i], smoothingLevels); // count total curve sections and total curve length: smoothSections += smoothChunks[i].Count - 1; smoothLength += CalculateChunkLength(smoothChunks[i]); } } }
/** * 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]); } }