public static void WeightedSum(float w1, float w2, float w3, ref ObiPathFrame c1, ref ObiPathFrame c2, ref ObiPathFrame c3, ref ObiPathFrame sum)
        {
            sum.position.x = c1.position.x * w1 + c2.position.x * w2 + c3.position.x * w3;
            sum.position.y = c1.position.y * w1 + c2.position.y * w2 + c3.position.y * w3;
            sum.position.z = c1.position.z * w1 + c2.position.z * w2 + c3.position.z * w3;

            sum.tangent.x = c1.tangent.x * w1 + c2.tangent.x * w2 + c3.tangent.x * w3;
            sum.tangent.y = c1.tangent.y * w1 + c2.tangent.y * w2 + c3.tangent.y * w3;
            sum.tangent.z = c1.tangent.z * w1 + c2.tangent.z * w2 + c3.tangent.z * w3;

            sum.normal.x = c1.normal.x * w1 + c2.normal.x * w2 + c3.normal.x * w3;
            sum.normal.y = c1.normal.y * w1 + c2.normal.y * w2 + c3.normal.y * w3;
            sum.normal.z = c1.normal.z * w1 + c2.normal.z * w2 + c3.normal.z * w3;

            sum.binormal.x = c1.binormal.x * w1 + c2.binormal.x * w2 + c3.binormal.x * w3;
            sum.binormal.y = c1.binormal.y * w1 + c2.binormal.y * w2 + c3.binormal.y * w3;
            sum.binormal.z = c1.binormal.z * w1 + c2.binormal.z * w2 + c3.binormal.z * w3;

            sum.color.x = c1.color.x * w1 + c2.color.x * w2 + c3.color.x * w3;
            sum.color.y = c1.color.y * w1 + c2.color.y * w2 + c3.color.y * w3;
            sum.color.z = c1.color.z * w1 + c2.color.z * w2 + c3.color.z * w3;
            sum.color.w = c1.color.w * w1 + c2.color.w * w2 + c3.color.w * w3;

            sum.thickness = c1.thickness * w1 + c2.thickness * w2 + c3.thickness * w3;
        }
Beispiel #2
0
        public void LateUpdate()
        {
            ObiPathFrame section = generator.GetSectionAt(m);

            transform.position = generator.transform.TransformPoint(section.position);
            transform.rotation = generator.transform.rotation * (Quaternion.LookRotation(section.tangent, section.binormal));
        }
Beispiel #3
0
        public ObiPathFrame GetSectionAt(float mu)
        {
            float edgeMu    = smoothSections * Mathf.Clamp01(mu);
            int   index     = (int)edgeMu;
            float sectionMu = edgeMu - index;

            int counter      = 0;
            int chunkIndex   = -1;
            int indexInChunk = -1;

            for (int i = 0; i < smoothChunks.Count; ++i)
            {
                if (counter + smoothChunks[i].Count > index)
                {
                    chunkIndex   = i;
                    indexInChunk = index - counter;
                }
                counter += smoothChunks[i].Count;
            }

            ObiList <ObiPathFrame> chunk = smoothChunks[chunkIndex];
            ObiPathFrame           s1    = chunk[indexInChunk];
            ObiPathFrame           s2    = chunk[Mathf.Min(indexInChunk + 1, chunk.Count - 1)];

            return((1 - sectionMu) * s1 + sectionMu * s2);
        }
Beispiel #4
0
        /**
         * This method uses a variant of Chainkin's algorithm to produce a smooth curve from a set of control points. It is specially fast
         * because it directly calculates subdivision level k, instead of recursively calculating levels 1..k.
         */
        private void Chaikin(ObiList <ObiPathFrame> input, ObiList <ObiPathFrame> output, uint k)
        {
            // no subdivision levels, no work to do. just copy the input to the output:
            if (k == 0 || input.Count < 3)
            {
                output.SetCount(input.Count);
                for (int i = 0; i < input.Count; ++i)
                {
                    output[i] = input[i];
                }
                return;
            }

            // calculate amount of new points generated by each inner control point:
            int pCount = (int)Mathf.Pow(2, k);

            // precalculate some quantities:
            int   n0 = input.Count - 1;
            float twoRaisedToMinusKPlus1   = Mathf.Pow(2, -(k + 1));
            float twoRaisedToMinusK        = Mathf.Pow(2, -k);
            float twoRaisedToMinus2K       = Mathf.Pow(2, -2 * k);
            float twoRaisedToMinus2KMinus1 = Mathf.Pow(2, -2 * k - 1);

            // allocate ouput:
            output.SetCount((n0 - 1) * pCount + 2);

            // calculate initial curve points:
            output[0] = (0.5f + twoRaisedToMinusKPlus1) * input[0] + (0.5f - twoRaisedToMinusKPlus1) * input[1];
            output[pCount * n0 - pCount + 1] = (0.5f - twoRaisedToMinusKPlus1) * input[n0 - 1] + (0.5f + twoRaisedToMinusKPlus1) * input[n0];

            // calculate internal points:
            for (int j = 1; j <= pCount; ++j)
            {
                // precalculate coefficients:
                float F = 0.5f - twoRaisedToMinusKPlus1 - (j - 1) * (twoRaisedToMinusK - j * twoRaisedToMinus2KMinus1);
                float G = 0.5f + twoRaisedToMinusKPlus1 + (j - 1) * (twoRaisedToMinusK - j * twoRaisedToMinus2K);
                float H = (j - 1) * j * twoRaisedToMinus2KMinus1;

                for (int i = 1; i < n0; ++i)
                {
                    ObiPathFrame.WeightedSum(F, G, H,
                                             ref input.Data[i - 1],
                                             ref input.Data[i],
                                             ref input.Data[i + 1],
                                             ref output.Data[(i - 1) * pCount + j]);
                }
            }

            // make first and last curve points coincide with original points:
            output[0] = input[0];
            output[output.Count - 1] = input[input.Count - 1];
        }
        public void Transport(ObiPathFrame frame, float twist)
        {
            // Calculate delta rotation:
            Quaternion rotQ   = Quaternion.FromToRotation(tangent, frame.tangent);
            Quaternion twistQ = Quaternion.AngleAxis(twist, frame.tangent);
            Quaternion finalQ = twistQ * rotQ;

            // Rotate previous frame axes to obtain the new ones:
            normal    = finalQ * normal;
            binormal  = finalQ * binormal;
            tangent   = frame.tangent;
            position  = frame.position;
            thickness = frame.thickness;
            color     = frame.color;
        }
Beispiel #6
0
        private void PathFrameFromParticle(ObiRopeBase actor, ref ObiPathFrame frame, int particleIndex)
        {
            // Update current frame values from particles:
            frame.position  = w2l.MultiplyPoint3x4(actor.GetParticlePosition(particleIndex));
            frame.thickness = actor.GetParticleMaxRadius(particleIndex);
            frame.color     = actor.GetParticleColor(particleIndex);

            // Use particle orientation if possible:
            if (actor.usesOrientedParticles)
            {
                Quaternion orientation = w2lRotation * Quaternion.SlerpUnclamped(actor.GetParticleOrientation(particleIndex), actor.GetParticleOrientation(Mathf.Max(0, particleIndex - 1)), 0.5f);
                frame.normal   = orientation * Vector3.up;
                frame.binormal = orientation * Vector3.right;
                frame.tangent  = orientation * Vector3.forward;
            }
        }
        // Update is called once per frame
        void UpdatePlugs(ObiActor actor)
        {
            var rope = actor as ObiRopeBase;

            // cache the rope's transform matrix/quaternion:
            Matrix4x4  l2w    = rope.transform.localToWorldMatrix;
            Quaternion l2wRot = l2w.rotation;

            int instanceIndex = 0;

            // place prefabs at the start/end of each curve:
            for (int c = 0; c < smoother.smoothChunks.Count; ++c)
            {
                ObiList <ObiPathFrame> curve = smoother.smoothChunks[c];

                if ((plugTears && c > 0) ||
                    (plugStart && c == 0))
                {
                    var instance = GetOrCreatePrefabInstance(instanceIndex++);
                    instance.SetActive(true);

                    ObiPathFrame frame = curve[0];
                    instance.transform.position   = l2w.MultiplyPoint3x4(frame.position);
                    instance.transform.rotation   = l2wRot * (Quaternion.LookRotation(-frame.tangent, frame.binormal));
                    instance.transform.localScale = instanceScale;
                }

                if ((plugTears && c < smoother.smoothChunks.Count - 1) ||
                    (plugEnd && c == smoother.smoothChunks.Count - 1))
                {
                    var instance = GetOrCreatePrefabInstance(instanceIndex++);
                    instance.SetActive(true);

                    ObiPathFrame frame = curve[curve.Count - 1];
                    instance.transform.position   = l2w.MultiplyPoint3x4(frame.position);
                    instance.transform.rotation   = l2wRot * (Quaternion.LookRotation(frame.tangent, frame.binormal));
                    instance.transform.localScale = instanceScale;
                }
            }

            // deactivate remaining instances:
            for (int i = instanceIndex; i < instances.Count; ++i)
            {
                instances[i].SetActive(false);
            }
        }
Beispiel #8
0
        /**
         * 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]);
                }
            }
        }
        protected virtual IEnumerator CreateStretchShearConstraints(List <Vector3> particleNormals)
        {
            stretchShearConstraintsData = new ObiStretchShearConstraintsData();

            stretchShearConstraintsData.AddBatch(new ObiStretchShearConstraintsBatch());
            stretchShearConstraintsData.AddBatch(new ObiStretchShearConstraintsBatch());

            // rotation minimizing frame:
            ObiPathFrame frame = new ObiPathFrame();

            frame.Reset();

            for (int i = 0; i < totalParticles - 1; i++)
            {
                var batch = stretchShearConstraintsData.batches[i % 2] as ObiStretchShearConstraintsBatch;

                Vector2Int indices = new Vector2Int(i, i + 1);
                Vector3    d       = positions[indices.y] - positions[indices.x];
                restLengths[i] = d.magnitude;

                frame.Transport(positions[indices.x], d.normalized, 0);

                orientations[i]     = Quaternion.LookRotation(frame.tangent, particleNormals[indices.x]);
                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[indices.y]     = orientations[i];
                restOrientations[indices.y] = orientations[i];

                batch.AddConstraint(indices, indices.x, restLengths[i], Quaternion.identity);
                batch.activeConstraintCount++;

                if (i % 500 == 0)
                {
                    yield return(new CoroutineJob.ProgressInfo("ObiRod: generating structural constraints...", i / (float)(totalParticles - 1)));
                }
            }

            // if the path is closed, add the last, loop closing constraint to a new batch to avoid sharing particles.
            if (path.Closed)
            {
                var loopClosingBatch = new ObiStretchShearConstraintsBatch();
                stretchShearConstraintsData.AddBatch(loopClosingBatch);

                Vector2Int indices = new Vector2Int(m_ActiveParticleCount - 1, 0);
                Vector3    d       = positions[indices.y] - positions[indices.x];
                restLengths[m_ActiveParticleCount - 2] = d.magnitude;

                frame.Transport(positions[indices.x], d.normalized, 0);

                orientations[m_ActiveParticleCount - 1]     = Quaternion.LookRotation(frame.tangent, particleNormals[indices.x]);
                restOrientations[m_ActiveParticleCount - 1] = orientations[m_ActiveParticleCount - 1];

                loopClosingBatch.AddConstraint(indices, indices.x, restLengths[m_ActiveParticleCount - 2], Quaternion.identity);
                loopClosingBatch.activeConstraintCount++;
            }

            // Recalculate rest length:
            m_RestLength = 0;
            foreach (float length in restLengths)
            {
                m_RestLength += length;
            }
        }