修改水

This commit is contained in:
2026-01-01 22:00:33 +08:00
parent 040a222bd6
commit 9ceffccd39
1800 changed files with 103929 additions and 139495 deletions

View File

@@ -1,4 +1,6 @@
using System.Collections.Generic;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Obi

View File

@@ -63,6 +63,7 @@ namespace Obi
{
throw new InvalidOperationException("Cannot get position in path because it has zero control points.");
}
}
/**

View File

@@ -12,9 +12,7 @@ namespace Obi
Z = 2
}
public static ObiPathFrame Identity => new ObiPathFrame(Vector3.zero, Vector3.forward, Vector3.up, Vector3.right, Color.white, 0);
public Vector3 position;
public Vector3 position;
public Vector3 tangent;
public Vector3 normal;
@@ -30,7 +28,7 @@ namespace Obi
this.binormal = binormal;
this.color = color;
this.thickness = thickness;
}
}
public void Reset()
{
@@ -52,7 +50,7 @@ namespace Obi
return new ObiPathFrame(c.position * f, c.tangent * f, c.normal * f, c.binormal * f,c.color * f, c.thickness * f);
}
public static void WeightedSum(float w1, float w2, float w3, in ObiPathFrame c1, in ObiPathFrame c2, in ObiPathFrame c3, ref ObiPathFrame sum)
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;

View File

@@ -1,12 +1,21 @@
using UnityEngine;
using Unity.Profiling;
using System;
using System.Collections;
using System.Collections.Generic;
namespace Obi
{
[ExecuteInEditMode]
[RequireComponent(typeof(ObiRopeBase))]
public class ObiPathSmoother : MonoBehaviour, ObiActorRenderer<ObiPathSmoother>
public class ObiPathSmoother : MonoBehaviour
{
static ProfilerMarker m_AllocateRawChunksPerfMarker = new ProfilerMarker("AllocateRawChunks");
static ProfilerMarker m_GenerateSmoothChunksPerfMarker = new ProfilerMarker("GenerateSmoothChunks");
private Matrix4x4 w2l;
private Quaternion w2lRotation;
[Range(0, 1)]
[Tooltip("Curvature threshold below which the path will be decimated. A value of 0 won't apply any decimation. As you increase the value, decimation will become more aggresive.")]
public float decimation = 0;
@@ -18,84 +27,353 @@ namespace Obi
[Tooltip("Twist in degrees applied to each sucessive path section.")]
public float twist = 0;
public ObiActor actor { get; private set; }
public event ObiActor.ActorCallback OnCurveGenerated;
[HideInInspector] public int indexInSystem = 0;
protected float smoothLength = 0;
protected int smoothSections = 0;
[HideInInspector] public ObiList<ObiList<ObiPathFrame>> rawChunks = new ObiList<ObiList<ObiPathFrame>>();
[HideInInspector] public ObiList<ObiList<ObiPathFrame>> smoothChunks = new ObiList<ObiList<ObiPathFrame>>();
private Stack<Vector2Int> stack = new Stack<Vector2Int>();
private BitArray decimateBitArray = new BitArray(0);
public float SmoothLength
{
get
{
if (actor.isLoaded)
{
var system = actor.solver.GetRenderSystem<ObiPathSmoother>() as ObiPathSmootherRenderSystem;
if (system != null)
return system.GetSmoothLength(indexInSystem);
}
return 0;
}
get { return smoothLength; }
}
public float SmoothSections
{
get {
if (actor.isLoaded)
{
var system = actor.solver.GetRenderSystem<ObiPathSmoother>() as ObiPathSmootherRenderSystem;
if (system != null)
return system.GetSmoothFrameCount(indexInSystem);
}
return 0;
}
get { return smoothSections; }
}
public void OnEnable()
private void OnEnable()
{
actor = GetComponent<ObiActor>();
((ObiActorRenderer<ObiPathSmoother>)this).EnableRenderer();
GetComponent<ObiRopeBase>().OnInterpolate += Actor_OnInterpolate;
}
private void OnDisable()
{
((ObiActorRenderer<ObiPathSmoother>)this).DisableRenderer();
GetComponent<ObiRopeBase>().OnInterpolate -= Actor_OnInterpolate;
}
private void OnValidate()
void Actor_OnInterpolate(ObiActor actor)
{
((ObiActorRenderer<ObiPathSmoother>)this).SetRendererDirty(Oni.RenderingSystemType.AllSmoothedRopes);
GenerateSmoothChunks(((ObiRopeBase)actor), smoothing);
if (OnCurveGenerated != null)
OnCurveGenerated(actor);
}
private void AllocateChunk(int sections)
{
if (sections > 1)
{
if (rawChunks.Data[rawChunks.Count] == null)
{
rawChunks.Data[rawChunks.Count] = new ObiList<ObiPathFrame>();
smoothChunks.Data[smoothChunks.Count] = new ObiList<ObiPathFrame>();
}
rawChunks.Data[rawChunks.Count].SetCount(sections);
rawChunks.SetCount(rawChunks.Count + 1);
smoothChunks.SetCount(smoothChunks.Count + 1);
}
}
private float CalculateChunkLength(ObiList<ObiPathFrame> chunk)
{
float length = 0;
for (int i = 1; i < chunk.Count; ++i)
length += Vector3.Distance(chunk[i].position, chunk[i - 1].position);
return length;
}
/**
* 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);
}
}
private void PathFrameFromParticle(ObiRopeBase actor, ref ObiPathFrame frame, int particleIndex, bool interpolateOrientation = true)
{
// 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 current = actor.GetParticleOrientation(particleIndex);
Quaternion previous = actor.GetParticleOrientation(Mathf.Max(0, particleIndex - 1));
Quaternion average = w2lRotation * (interpolateOrientation ? Quaternion.SlerpUnclamped(current, previous, 0.5f) : current);
frame.normal = average * Vector3.up;
frame.binormal = average * Vector3.right;
frame.tangent = average * Vector3.forward;
}
}
/**
* 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;
smoothLength += CalculateChunkLength(smoothChunks[i]);
}
}
}
public ObiPathFrame GetSectionAt(float mu)
{
if (actor.isLoaded)
{
var system = actor.solver.GetRenderSystem<ObiPathSmoother>() as ObiPathSmootherRenderSystem;
float edgeMu = smoothSections * Mathf.Clamp(mu,0,0.9999f);
int index = (int)edgeMu;
float sectionMu = edgeMu - index;
if (system != null)
return system.GetFrameAt(indexInSystem, mu);
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;
break;
}
counter += smoothChunks[i].Count;
}
return ObiPathFrame.Identity;
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;
}
RenderSystem<ObiPathSmoother> ObiRenderer<ObiPathSmoother>.CreateRenderSystem(ObiSolver solver)
/**
* Iterative version of the Ramer-Douglas-Peucker path decimation algorithm.
*/
private bool Decimate(ObiList<ObiPathFrame> input, ObiList<ObiPathFrame> output, float threshold)
{
switch (solver.backendType)
// 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));
decimateBitArray.Length = Mathf.Max(decimateBitArray.Length, input.Count);
decimateBitArray.SetAll(true);
while (stack.Count > 0)
{
var range = stack.Pop();
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
case ObiSolver.BackendType.Burst: return new BurstPathSmootherRenderSystem(solver);
#endif
case ObiSolver.BackendType.Compute:
default:
float dmax = 0;
int index = range.x;
float mu;
if (SystemInfo.supportsComputeShaders)
return new ComputePathSmootherRenderSystem(solver);
return null;
for (int i = index + 1; i < range.y; ++i)
{
if (decimateBitArray[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)
decimateBitArray[i] = false;
}
}
output.Clear();
for (int i = 0; i < input.Count; ++i)
if (decimateBitArray[i])
output.Add(input[i]);
return true;
}
/**
* 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];
}
}
}

View File

@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 8791eecf125744cbeadea65319c29d5a, type: 3}
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: