更新obi到7.1

This commit is contained in:
Bob.Song
2025-11-03 11:53:45 +08:00
parent d12e1bc495
commit 7cf7f545bc
1161 changed files with 158924 additions and 37802 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3595afed529f346669fbe538323cd7d2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,62 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Burst;
using UnityEngine;
namespace Obi
{
[BurstCompile]
struct BuildParticleMeshDataJob : IJobParallelFor
{
[ReadOnly] public NativeArray<int> particleIndices;
[ReadOnly] public NativeArray<int> rendererIndices;
[ReadOnly] public NativeArray<ParticleRendererData> rendererData;
[ReadOnly] public NativeArray<float4> renderablePositions;
[ReadOnly] public NativeArray<quaternion> renderableOrientations;
[ReadOnly] public NativeArray<float4> renderableRadii;
[ReadOnly] public NativeArray<float4> colors;
[NativeDisableParallelForRestriction] public NativeArray<ParticleVertex> vertices;
[NativeDisableParallelForRestriction] public NativeArray<int> indices;
[ReadOnly] public int firstParticle;
public void Execute(int i)
{
int p = particleIndices[firstParticle + i];
int r = rendererIndices[firstParticle + i];
ParticleVertex v = new ParticleVertex();
v.pos = new float4(renderablePositions[p].xyz, 1);
v.color = colors[p] * (Vector4)rendererData[r].color;
v.b1 = new float4(math.mul(renderableOrientations[p], new float3(1, 0, 0)), renderableRadii[p][0] * renderableRadii[p][3] * rendererData[r].radiusScale);
v.b2 = new float4(math.mul(renderableOrientations[p], new float3(0, 1, 0)), renderableRadii[p][1] * renderableRadii[p][3] * rendererData[r].radiusScale);
v.b3 = new float4(math.mul(renderableOrientations[p], new float3(0, 0, 1)), renderableRadii[p][2] * renderableRadii[p][3] * rendererData[r].radiusScale);
v.offset = new float3(1, 1, 0);
vertices[i * 4] = v;
v.offset = new float3(-1, 1, 0);
vertices[i * 4 + 1] = v;
v.offset = new float3(-1, -1, 0);
vertices[i * 4 + 2] = v;
v.offset = new float3(1, -1, 0);
vertices[i * 4 + 3] = v;
indices[i * 6] = (i * 4 + 2);
indices[i * 6 + 1] = (i * 4 + 1);
indices[i * 6 + 2] = (i * 4);
indices[i * 6 + 3] = (i * 4 + 3);
indices[i * 6 + 4] = (i * 4 + 2);
indices[i * 6 + 5] = (i * 4);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cacb4a3597ee245a6b40358728c300e9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,247 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using UnityEngine;
using UnityEngine.Rendering;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using System.Collections.Generic;
#if (SRP_UNIVERSAL)
using UnityEngine.Rendering.Universal;
#endif
namespace Obi
{
public class BurstFoamRenderSystem : ObiFoamRenderSystem
{
protected NativeArray<float2> sortHandles;
protected struct SortHandleComparer : IComparer<float2>
{
public int Compare(float2 a, float2 b)
{
return b.y.CompareTo(a.y);
}
}
protected SortHandleComparer comparer = new SortHandleComparer();
public BurstFoamRenderSystem(ObiSolver solver) : base(solver)
{
#if (SRP_UNIVERSAL)
if (GraphicsSettings.currentRenderPipeline is UniversalRenderPipelineAsset)
renderBatch = new ProceduralRenderBatch<DiffuseParticleVertex>(0, Resources.Load<Material>("ObiMaterials/URP/Fluid/FoamParticlesURP"), new RenderBatchParams(true));
else
#endif
renderBatch = new ProceduralRenderBatch<DiffuseParticleVertex>(0, Resources.Load<Material>("ObiMaterials/Fluid/FoamParticles"), new RenderBatchParams(true));
ReallocateRenderBatch();
}
public override void Dispose()
{
base.Dispose();
if (sortHandles.IsCreated)
sortHandles.Dispose();
}
private void ReallocateRenderBatch()
{
// in case the amount of particles allocated does not match
// the amount requested by the solver, reallocate
if (!sortHandles.IsCreated || m_Solver.foamPositions.count * 4 != renderBatch.vertexCount)
{
renderBatch.Dispose();
renderBatch.vertexCount = m_Solver.foamPositions.count * 4;
renderBatch.triangleCount = m_Solver.foamPositions.count * 2;
renderBatch.Initialize(layout);
if (sortHandles.IsCreated)
sortHandles.Dispose();
sortHandles = new NativeArray<float2>(m_Solver.foamPositions.count, Allocator.Persistent);
}
}
public override void Setup()
{
}
public override void Step()
{
}
public override unsafe void Render()
{
if (!Application.isPlaying)
return;
var solver = m_Solver.implementation as BurstSolverImpl;
ReallocateRenderBatch();
foreach (Camera camera in cameras)
{
if (camera == null)
continue;
JobHandle inputDeps = new JobHandle();
var sortJob = sortHandles.Slice(0, m_Solver.foamCount[3]).SortJob(comparer);
//Clear all triangle indices to zero:
UnsafeUtility.MemClear(
NativeArrayUnsafeUtility.GetUnsafeBufferPointerWithoutChecks(renderBatch.triangles),
UnsafeUtility.SizeOf<int>() * renderBatch.triangles.Length);
var projectJob = new ProjectOnSortAxisJob
{
inputPositions = solver.abstraction.foamPositions.AsNativeArray<float4>(),
sortHandles = sortHandles,
sortAxis = solver.abstraction.transform.InverseTransformDirection(camera.transform.forward)
};
inputDeps = projectJob.Schedule(m_Solver.foamCount[3], 256, inputDeps);
inputDeps = sortJob.Schedule(inputDeps);
var sortParticlesJob = new SortParticles
{
sortHandles = sortHandles,
inputPositions = solver.abstraction.foamPositions.AsNativeArray<float4>(),
inputVelocities = solver.abstraction.foamVelocities.AsNativeArray<float4>(),
inputColors = solver.abstraction.foamColors.AsNativeArray<float4>(),
inputAttributes = solver.abstraction.foamAttributes.AsNativeArray<float4>(),
outputPositions = solver.auxPositions,
outputVelocities = solver.auxVelocities,
outputColors = solver.auxColors,
outputAttributes = solver.auxAttributes
};
inputDeps = sortParticlesJob.Schedule(m_Solver.foamCount[3], 256, inputDeps);
var meshJob = new BuildFoamMeshDataJob
{
inputPositions = solver.auxPositions,
inputVelocities = solver.auxVelocities,
inputColors = solver.auxColors,
inputAttributes = solver.auxAttributes,
vertices = renderBatch.vertices,
indices = renderBatch.triangles,
};
inputDeps = meshJob.Schedule(m_Solver.foamCount[3], 128, inputDeps);
inputDeps.Complete();
renderBatch.mesh.SetVertexBufferData(renderBatch.vertices, 0, 0, renderBatch.vertexCount, 0, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontResetBoneBounds | MeshUpdateFlags.DontNotifyMeshUsers);
renderBatch.mesh.SetIndexBufferData(renderBatch.triangles, 0, 0, renderBatch.triangleCount * 3, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices);
matProps.SetFloat("_FadeDepth", 0);
matProps.SetFloat("_VelocityStretching", m_Solver.maxFoamVelocityStretch);
matProps.SetFloat("_RadiusScale", m_Solver.foamRadiusScale);
matProps.SetFloat("_FadeIn", m_Solver.foamFade.x);
matProps.SetFloat("_FadeOut", m_Solver.foamFade.y);
matProps.SetFloat("_ScatterDensity", m_Solver.foamVolumeDensity);
matProps.SetFloat("_AmbientDensity", m_Solver.foamAmbientDensity);
matProps.SetColor("_ScatterColor", m_Solver.foamScatterColor);
matProps.SetColor("_AmbientColor", m_Solver.foamAmbientColor);
var rp = renderBatch.renderParams;
rp.worldBounds = m_Solver.bounds;
rp.camera = camera;
rp.matProps = matProps;
Graphics.RenderMesh(rp, renderBatch.mesh, 0, m_Solver.transform.localToWorldMatrix, m_Solver.transform.localToWorldMatrix);
}
}
[BurstCompile]
unsafe struct ProjectOnSortAxisJob : IJobParallelFor
{
[ReadOnly] public NativeArray<float4> inputPositions;
[NativeDisableParallelForRestriction] public NativeArray<float2> sortHandles;
public float3 sortAxis;
public void Execute(int i)
{
sortHandles[i] = new float2(i, math.dot(inputPositions[i].xyz, sortAxis));
}
}
[BurstCompile]
unsafe struct SortParticles : IJobParallelFor
{
[ReadOnly] public NativeArray<float2> sortHandles;
[ReadOnly] public NativeArray<float4> inputPositions;
[ReadOnly] public NativeArray<float4> inputVelocities;
[ReadOnly] public NativeArray<float4> inputColors;
[ReadOnly] public NativeArray<float4> inputAttributes;
[NativeDisableParallelForRestriction] public NativeArray<float4> outputPositions;
[NativeDisableParallelForRestriction] public NativeArray<float4> outputVelocities;
[NativeDisableParallelForRestriction] public NativeArray<float4> outputColors;
[NativeDisableParallelForRestriction] public NativeArray<float4> outputAttributes;
public void Execute(int i)
{
int o = (int)sortHandles[i].x;
outputPositions[i] = inputPositions[o];
outputVelocities[i] = inputVelocities[o];
outputColors[i] = inputColors[o];
outputAttributes[i] = inputAttributes[o];
}
}
[BurstCompile]
struct BuildFoamMeshDataJob : IJobParallelFor
{
[ReadOnly] public NativeArray<float4> inputPositions;
[ReadOnly] public NativeArray<float4> inputVelocities;
[ReadOnly] public NativeArray<float4> inputColors;
[ReadOnly] public NativeArray<float4> inputAttributes;
[NativeDisableParallelForRestriction] public NativeArray<DiffuseParticleVertex> vertices;
[NativeDisableParallelForRestriction] public NativeArray<int> indices;
public void Execute(int i)
{
DiffuseParticleVertex v = new DiffuseParticleVertex();
v.pos = new float4(inputPositions[i].xyz, 1);
v.color = inputColors[i];
v.velocity = inputVelocities[i];
v.attributes = inputAttributes[i];
v.offset = new float3(1, 1, 0);
vertices[i * 4] = v;
v.offset = new float3(-1, 1, 0);
vertices[i * 4 + 1] = v;
v.offset = new float3(-1, -1, 0);
vertices[i * 4 + 2] = v;
v.offset = new float3(1, -1, 0);
vertices[i * 4 + 3] = v;
indices[i * 6] = (i * 4 + 2);
indices[i * 6 + 1] = (i * 4 + 1);
indices[i * 6 + 2] = (i * 4);
indices[i * 6 + 3] = (i * 4 + 3);
indices[i * 6 + 4] = (i * 4 + 2);
indices[i * 6 + 5] = (i * 4);
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 555e1cc2122984a93afc0f6f31fa173b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,100 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Burst;
namespace Obi
{
public class BurstInstancedParticleRenderSystem : ObiInstancedParticleRenderSystem
{
public BurstInstancedParticleRenderSystem(ObiSolver solver) : base(solver)
{
}
public override void Render()
{
using (m_RenderMarker.Auto())
{
var instanceTransformsJob = new InstancedParticleTransforms
{
activeParticles = activeParticles.AsNativeArray<int>(),
rendererData = rendererData.AsNativeArray<ParticleRendererData>(),
rendererIndex = rendererIndex.AsNativeArray<int>(),
instanceTransforms = instanceTransforms.AsNativeArray<float4x4>(),
instanceColors = instanceColors.AsNativeArray<float4>(),
renderablePositions = m_Solver.renderablePositions.AsNativeArray<float4>(),
renderableOrientations = m_Solver.renderableOrientations.AsNativeArray<quaternion>(),
renderableRadii = m_Solver.renderableRadii.AsNativeArray<float4>(),
colors = m_Solver.colors.AsNativeArray<float4>(),
solverToWorld = m_Solver.transform.localToWorldMatrix
};
instanceTransformsJob.Schedule(activeParticles.count, 32).Complete();
var mpb = new MaterialPropertyBlock();
//Draw instances:
for (int i = 0; i < batchList.Count; i++)
{
var batch = batchList[i];
if (batch.instanceCount > 0)
{
// workaround for RenderMeshInstanced bug
// (https://forum.unity.com/threads/gpu-instanced-custom-properties-dont-take-unity_baseinstanceid-into-account.1520602/)
// also, no NativeArray<> overload :(
mpb.SetVectorArray("_Colors", instanceColors.AsNativeArray<Vector4>().Slice(batch.firstInstance, batch.instanceCount).ToArray());
var rp = batch.renderParams;
rp.material = batch.material;
rp.worldBounds = m_Solver.bounds;
rp.matProps = mpb;
// TODO: use generic overload to pass matrix + instance color.
Graphics.RenderMeshInstanced(rp, batch.mesh, 0, instanceTransforms.AsNativeArray<Matrix4x4>(), batch.instanceCount, batch.firstInstance);
}
}
}
}
[BurstCompile]
struct InstancedParticleTransforms : IJobParallelFor
{
[ReadOnly] public NativeArray<int> activeParticles;
[ReadOnly] public NativeArray<ParticleRendererData> rendererData;
[ReadOnly] public NativeArray<int> rendererIndex;
[ReadOnly] public NativeArray<float4> renderablePositions;
[ReadOnly] public NativeArray<quaternion> renderableOrientations;
[ReadOnly] public NativeArray<float4> renderableRadii;
[ReadOnly] public NativeArray<float4> colors;
[ReadOnly] public float4x4 solverToWorld;
[NativeDisableParallelForRestriction] public NativeArray<float4x4> instanceTransforms;
[NativeDisableParallelForRestriction] public NativeArray<float4> instanceColors;
public void Execute(int i)
{
int p = activeParticles[i];
Matrix4x4 tfrm = float4x4.TRS(renderablePositions[p].xyz,
renderableOrientations[p],
renderableRadii[p].xyz * renderableRadii[p][3] * rendererData[rendererIndex[i]].radiusScale);
instanceTransforms[i] = math.mul(solverToWorld, tfrm);
instanceColors[i] = colors[p] * (Vector4)rendererData[rendererIndex[i]].color;
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b3896a2253db543bcaa92126d83a7ba0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,58 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using UnityEngine;
using UnityEngine.Rendering;
using Unity.Jobs;
using Unity.Mathematics;
namespace Obi
{
public class BurstParticleRenderSystem : ObiParticleRenderSystem
{
public BurstParticleRenderSystem(ObiSolver solver) : base(solver)
{
m_Solver = solver;
}
public override void Render()
{
using (m_RenderMarker.Auto())
{
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
var buildArraysJob = new BuildParticleMeshDataJob
{
particleIndices = activeParticles.AsNativeArray<int>(),
rendererIndices = rendererIndex.AsNativeArray<int>(),
rendererData = rendererData.AsNativeArray<ParticleRendererData>(),
renderablePositions = m_Solver.renderablePositions.AsNativeArray<float4>(),
renderableOrientations = m_Solver.renderableOrientations.AsNativeArray<quaternion>(),
renderableRadii = m_Solver.renderableRadii.AsNativeArray<float4>(),
colors = m_Solver.colors.AsNativeArray<float4>(),
vertices = batch.vertices,
indices = batch.triangles,
firstParticle = batch.firstParticle,
};
buildArraysJob.Schedule(batch.vertexCount / 4, 32).Complete();
batch.mesh.SetVertexBufferData(batch.vertices, 0, 0, batch.vertexCount, 0, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontResetBoneBounds | MeshUpdateFlags.DontNotifyMeshUsers);
batch.mesh.SetIndexBufferData(batch.triangles, 0, 0, batch.triangleCount * 3, MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontValidateIndices);
var rp = batch.renderParams;
rp.worldBounds = m_Solver.bounds;
Graphics.RenderMesh(rp, batch.mesh, 0, m_Solver.transform.localToWorldMatrix, m_Solver.transform.localToWorldMatrix);
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d1d1ddc20ab7148e3ac806e3559ba2b8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 68fb3be53914247609d4e6da4f249e52
labels:
- ObiRope
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,139 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
namespace Obi
{
public class BurstChainRopeRenderSystem : ObiChainRopeRenderSystem
{
protected Matrix4x4[] transformsArray = new Matrix4x4[1023];
public BurstChainRopeRenderSystem(ObiSolver solver) : base(solver)
{
}
public override void Setup()
{
base.Setup();
}
public override void Render()
{
using (m_RenderMarker.Auto())
{
// generate raw frames using parallel transport
var instanceTransformsJob = new InstanceTransforms()
{
rendererData = rendererData.AsNativeArray<ChainRendererData>(),
chunkData = chunkData.AsNativeArray<ChunkData>(),
modifiers = modifiers.AsNativeArray<ObiRopeChainRenderer.LinkModifier>(),
elements = elements.AsNativeArray<int2>(),
instanceTransforms = instanceTransforms.AsNativeArray<float4x4>(),
instanceColors = instanceColors.AsNativeArray<float4>(),
renderablePositions = m_Solver.renderablePositions.AsNativeArray<float4>(),
renderableOrientations = m_Solver.renderableOrientations.AsNativeArray<quaternion>(),
principalRadii = m_Solver.principalRadii.AsNativeArray<float4>(),
colors = m_Solver.colors.AsNativeArray<float4>(),
solverToWorld = m_Solver.transform.localToWorldMatrix,
};
instanceTransformsJob.Schedule(chunkData.count, 8).Complete();
//Draw instances:
for (int i = 0; i < batchList.Count; i++)
{
var batch = batchList[i];
var rp = batch.renderParams;
rp.material = batch.material;
rp.worldBounds = m_Solver.bounds;
Graphics.RenderMeshInstanced(rp, batch.mesh, 0, instanceTransforms.AsNativeArray<Matrix4x4>(), batch.instanceCount, batch.firstInstance);
}
}
}
[BurstCompile]
struct InstanceTransforms : IJobParallelFor
{
[ReadOnly] public NativeArray<ChainRendererData> rendererData;
[ReadOnly] public NativeArray<ChunkData> chunkData;
[ReadOnly] public NativeArray<ObiRopeChainRenderer.LinkModifier> modifiers;
[ReadOnly] public NativeArray<int2> elements;
[ReadOnly] public NativeArray<float4> renderablePositions;
[ReadOnly] public NativeArray<quaternion> renderableOrientations;
[ReadOnly] public NativeArray<float4> principalRadii;
[ReadOnly] public NativeArray<float4> colors;
[ReadOnly] public float4x4 solverToWorld;
[NativeDisableParallelForRestriction] public NativeArray<float4x4> instanceTransforms;
[NativeDisableParallelForRestriction] public NativeArray<float4> instanceColors;
public void Execute(int i)
{
int firstIndex = i > 0 ? chunkData[i - 1].offset : 0;
int elementCount = chunkData[i].offset - firstIndex;
var rendererIndex = chunkData[i].rendererIndex;
var renderer = rendererData[rendererIndex];
float3 rendScale = ((float4)renderer.scale).xyz;
int firstModifier = rendererIndex > 0 ? rendererData[rendererIndex - 1].modifierOffset : 0;
int modifierCount = renderer.modifierOffset - firstModifier;
var modifier = new ObiRopeChainRenderer.LinkModifier();
modifier.Clear();
BurstPathFrame frame = new BurstPathFrame();
frame.Reset();
float twist = -renderer.twist * elementCount * renderer.twistAnchor;
frame.SetTwist(twist);
// parallel transport:
for (int m = 0; m < elementCount; ++m)
{
if (modifierCount > 0)
modifier = modifiers[firstModifier + m % modifierCount];
int index = firstIndex + m;
float4 pos = renderablePositions[elements[index].x];
float4 nextPos = renderablePositions[elements[index].y];
float4 vector = nextPos - pos;
float3 tangent = math.normalizesafe(vector.xyz);
if (renderer.usesOrientedParticles == 1)
{
frame.Transport(nextPos.xyz, tangent, math.rotate(renderableOrientations[elements[index].x], new float3(0, 1, 0)), twist);
twist += renderer.twist;
}
else
frame.Transport(nextPos.xyz, tangent, renderer.twist);
var rotation = quaternion.LookRotationSafe(frame.tangent, frame.normal);
var position = (pos + vector * 0.5f).xyz + math.mul(rotation, modifier.translation);
var scale = principalRadii[elements[index].x].x * 2 * rendScale * modifier.scale;
rotation = math.mul(rotation, quaternion.Euler(math.radians(modifier.rotation)));
instanceTransforms[index] = math.mul(solverToWorld,float4x4.TRS(position,rotation,scale));
instanceColors[index] = (colors[elements[index].x] + colors[elements[index].y]) * 0.5f;
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3c63ccd17bfc14b40a403c1c9a0be2c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,215 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using UnityEngine;
using UnityEngine.Rendering;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Burst;
namespace Obi
{
public class BurstExtrudedRopeRenderSystem : ObiExtrudedRopeRenderSystem
{
public BurstExtrudedRopeRenderSystem(ObiSolver solver) : base(solver)
{
}
public override void Setup()
{
base.Setup();
// Initialize each batch:
for (int i = 0; i < batchList.Count; ++i)
batchList[i].Initialize(layout, false);
}
public override void Render()
{
if (pathSmootherSystem == null)
return;
using (m_RenderMarker.Auto())
{
var handles = new NativeArray<JobHandle>(batchList.Count, Allocator.Temp);
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
var meshJob = new BuildExtrudedMesh
{
pathSmootherIndices = pathSmootherIndices.AsNativeArray<int>(),
chunkOffsets = pathSmootherSystem.chunkOffsets.AsNativeArray<int>(),
frames = pathSmootherSystem.smoothFrames.AsNativeArray<BurstPathFrame>(),
frameOffsets = pathSmootherSystem.smoothFrameOffsets.AsNativeArray<int>(),
frameCounts = pathSmootherSystem.smoothFrameCounts.AsNativeArray<int>(),
sectionData = sectionData.AsNativeArray<float2>(),
sectionOffsets = sectionOffsets.AsNativeArray<int>(),
sectionIndices = sectionIndices.AsNativeArray<int>(),
vertexOffsets = vertexOffsets.AsNativeArray<int>(),
triangleOffsets = triangleOffsets.AsNativeArray<int>(),
triangleCounts = triangleCounts.AsNativeArray<int>(),
pathData = pathSmootherSystem.pathData.AsNativeArray<BurstPathSmootherData>(),
rendererData = rendererData.AsNativeArray<BurstExtrudedMeshData>(),
vertices = batch.vertices,
tris = batch.triangles,
firstRenderer = batch.firstRenderer
};
handles[i] = meshJob.Schedule(batch.rendererCount, 1);
}
JobHandle.CombineDependencies(handles).Complete();
handles.Dispose();
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
batch.mesh.SetVertexBufferData(batch.vertices, 0, 0, batch.vertexCount, 0, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontRecalculateBounds);
batch.mesh.SetIndexBufferData(batch.triangles, 0, 0, batch.triangleCount * 3, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontRecalculateBounds);
var rp = batch.renderParams;
rp.worldBounds = m_Solver.bounds;
Graphics.RenderMesh(rp, batch.mesh, 0, m_Solver.transform.localToWorldMatrix, m_Solver.transform.localToWorldMatrix);
}
}
}
[BurstCompile]
struct BuildExtrudedMesh : IJobParallelFor
{
[ReadOnly] public NativeArray<int> pathSmootherIndices;
[ReadOnly] public NativeArray<int> chunkOffsets;
[ReadOnly] public NativeArray<BurstPathFrame> frames;
[ReadOnly] public NativeArray<int> frameOffsets;
[ReadOnly] public NativeArray<int> frameCounts;
[ReadOnly] public NativeArray<float2> sectionData;
[ReadOnly] public NativeArray<int> sectionOffsets;
[ReadOnly] public NativeArray<int> sectionIndices;
[ReadOnly] public NativeArray<int> vertexOffsets;
[ReadOnly] public NativeArray<int> triangleOffsets;
[ReadOnly] public NativeArray<int> triangleCounts;
[ReadOnly] public NativeArray<BurstExtrudedMeshData> rendererData;
[ReadOnly] public NativeArray<BurstPathSmootherData> pathData;
[NativeDisableParallelForRestriction] public NativeArray<ProceduralRopeVertex> vertices;
[NativeDisableParallelForRestriction] public NativeArray<int> tris;
[ReadOnly] public int firstRenderer;
public void Execute(int u)
{
int k = firstRenderer + u;
int s = pathSmootherIndices[k];
float3 vertex = float3.zero;
float3 normal = float3.zero;
float4 texTangent = float4.zero;
int tri = 0;
int sectionIndex = 0;
int sectionStart = sectionOffsets[sectionIndices[k]];
int sectionSegments = (sectionOffsets[sectionIndices[k] + 1] - sectionStart) - 1;
int verticesPerSection = sectionSegments + 1; // the last vertex in each section must be duplicated, due to uv wraparound.
float smoothLength = 0;
for (int i = chunkOffsets[s]; i < chunkOffsets[s + 1]; ++i)
smoothLength += pathData[i].smoothLength;
float vCoord = -rendererData[k].uvScale.y * pathData[chunkOffsets[s]].restLength * rendererData[k].uvAnchor;
float actualToRestLengthRatio = smoothLength / pathData[chunkOffsets[s]].restLength;
int firstVertex = vertexOffsets[k];
int firstTriangle = triangleOffsets[k];
// clear out triangle indices for this rope:
for (int i = firstTriangle; i < firstTriangle + triangleCounts[k]; ++i)
{
int offset = i * 3;
tris[offset] = 0;
tris[offset+1] = 0;
tris[offset+2] = 0;
}
// for each chunk in the rope:
for (int i = chunkOffsets[s]; i < chunkOffsets[s + 1]; ++i)
{
int firstFrame = frameOffsets[i];
int frameCount = frameCounts[i];
for (int f = 0; f < frameCount; ++f)
{
// Calculate previous and next curve indices:
int prevIndex = firstFrame + math.max(f - 1, 0);
int index = firstFrame + f;
// advance v texcoord:
vCoord += rendererData[k].uvScale.y * (math.distance(frames[index].position, frames[prevIndex].position) /
(rendererData[k].normalizeV == 1 ? smoothLength : actualToRestLengthRatio));
// calculate section thickness and scale the basis vectors by it:
float sectionThickness = frames[index].thickness * rendererData[k].thicknessScale;
// Loop around each segment:
int nextSectionIndex = sectionIndex + 1;
for (int j = 0; j <= sectionSegments; ++j)
{
// make just one copy of the section vertex:
float2 sectionVertex = sectionData[sectionStart + j];
// calculate normal using section vertex, curve normal and binormal:
normal.x = (sectionVertex.x * frames[index].normal.x + sectionVertex.y * frames[index].binormal.x) * sectionThickness;
normal.y = (sectionVertex.x * frames[index].normal.y + sectionVertex.y * frames[index].binormal.y) * sectionThickness;
normal.z = (sectionVertex.x * frames[index].normal.z + sectionVertex.y * frames[index].binormal.z) * sectionThickness;
// offset curve position by normal:
vertex.x = frames[index].position.x + normal.x;
vertex.y = frames[index].position.y + normal.y;
vertex.z = frames[index].position.z + normal.z;
// cross(normal, curve tangent)
texTangent.xyz = math.cross(normal, frames[index].tangent);
texTangent.w = -1;
vertices[firstVertex + sectionIndex * verticesPerSection + j] = new ProceduralRopeVertex
{
pos = vertex,
normal = normal,
tangent = texTangent,
color = frames[index].color,
uv = new float2(j / (float)sectionSegments * rendererData[k].uvScale.x, vCoord)
};
if (j < sectionSegments && f < frameCount - 1)
{
int offset = firstTriangle * 3;
tris[offset + tri++] = (firstVertex + sectionIndex * verticesPerSection + j);
tris[offset + tri++] = (firstVertex + nextSectionIndex * verticesPerSection + j);
tris[offset + tri++] = (firstVertex + sectionIndex * verticesPerSection + (j + 1));
tris[offset + tri++] = (firstVertex + sectionIndex * verticesPerSection + (j + 1));
tris[offset + tri++] = (firstVertex + nextSectionIndex * verticesPerSection + j);
tris[offset + tri++] = (firstVertex + nextSectionIndex * verticesPerSection + (j + 1));
}
}
sectionIndex++;
}
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 10b89fddf68724bc9ad3305774d86ee1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,215 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using UnityEngine;
using UnityEngine.Rendering;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Burst;
namespace Obi
{
public class BurstLineRopeRenderSystem : ObiLineRopeRenderSystem
{
public BurstLineRopeRenderSystem(ObiSolver solver) : base(solver)
{
}
public override void Setup()
{
base.Setup();
// Initialize each batch:
for (int i = 0; i < batchList.Count; ++i)
batchList[i].Initialize(layout, false);
}
public override void Render(){}
public override void RenderFromCamera(Camera camera)
{
if (pathSmootherSystem == null)
return;
using (m_RenderMarker.Auto())
{
var handles = new NativeArray<JobHandle>(batchList.Count, Allocator.Temp);
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
var meshJob = new BuildLineMesh
{
pathSmootherIndices = pathSmootherIndices.AsNativeArray<int>(),
chunkOffsets = pathSmootherSystem.chunkOffsets.AsNativeArray<int>(),
frames = pathSmootherSystem.smoothFrames.AsNativeArray<BurstPathFrame>(),
frameOffsets = pathSmootherSystem.smoothFrameOffsets.AsNativeArray<int>(),
frameCounts = pathSmootherSystem.smoothFrameCounts.AsNativeArray<int>(),
vertexOffsets = vertexOffsets.AsNativeArray<int>(),
triangleOffsets = triangleOffsets.AsNativeArray<int>(),
triangleCounts = triangleCounts.AsNativeArray<int>(),
pathData = pathSmootherSystem.pathData.AsNativeArray<BurstPathSmootherData>(),
rendererData = rendererData.AsNativeArray<BurstLineMeshData>(),
vertices = batch.vertices,
tris = batch.triangles,
firstRenderer = batch.firstRenderer,
localSpaceCamera = m_Solver.transform.InverseTransformPoint(camera.transform.position)
};
handles[i] = meshJob.Schedule(batch.rendererCount, 1);
}
JobHandle.CombineDependencies(handles).Complete();
handles.Dispose();
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
batch.mesh.SetVertexBufferData(batch.vertices, 0, 0, batch.vertexCount, 0, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontRecalculateBounds);
batch.mesh.SetIndexBufferData(batch.triangles, 0, 0, batch.triangleCount * 3, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontRecalculateBounds);
var rp = batch.renderParams;
rp.worldBounds = m_Solver.bounds;
Graphics.RenderMesh(rp, batch.mesh, 0, m_Solver.transform.localToWorldMatrix, m_Solver.transform.localToWorldMatrix);
}
}
}
[BurstCompile]
struct BuildLineMesh : IJobParallelFor
{
[ReadOnly] public NativeArray<int> pathSmootherIndices;
[ReadOnly] public NativeArray<int> chunkOffsets;
[ReadOnly] public NativeArray<BurstPathFrame> frames;
[ReadOnly] public NativeArray<int> frameOffsets;
[ReadOnly] public NativeArray<int> frameCounts;
[ReadOnly] public NativeArray<int> vertexOffsets;
[ReadOnly] public NativeArray<int> triangleOffsets;
[ReadOnly] public NativeArray<int> triangleCounts;
[ReadOnly] public NativeArray<BurstLineMeshData> rendererData;
[ReadOnly] public NativeArray<BurstPathSmootherData> pathData;
[NativeDisableParallelForRestriction] public NativeArray<ProceduralRopeVertex> vertices;
[NativeDisableParallelForRestriction] public NativeArray<int> tris;
[ReadOnly] public int firstRenderer;
[ReadOnly] public float3 localSpaceCamera;
public void Execute(int u)
{
int k = firstRenderer + u;
int s = pathSmootherIndices[k];
float3 vertex = float3.zero;
float3 normal = float3.zero;
float4 bitangent = float4.zero;
int tri = 0;
int sectionIndex = 0;
int firstVertex = vertexOffsets[k];
int firstTriangle = triangleOffsets[k];
float smoothLength = 0;
for (int i = chunkOffsets[s]; i < chunkOffsets[s + 1]; ++i)
smoothLength += pathData[i].smoothLength;
float vCoord = -rendererData[k].uvScale.y * pathData[chunkOffsets[s]].restLength * rendererData[k].uvAnchor;
float actualToRestLengthRatio = smoothLength / pathData[chunkOffsets[s]].restLength;
// clear out triangle indices for this rope:
for (int i = firstTriangle; i < firstTriangle + triangleCounts[k]; ++i)
{
int offset = i * 3;
tris[offset] = 0;
tris[offset+1] = 0;
tris[offset+2] = 0;
}
// for each chunk in the rope:
for (int i = chunkOffsets[s]; i < chunkOffsets[s + 1]; ++i)
{
int firstFrame = frameOffsets[i];
int frameCount = frameCounts[i];
for (int f = 0; f < frameCount; ++f)
{
// Calculate previous and next curve indices:
int prevIndex = firstFrame + math.max(f - 1, 0);
int index = firstFrame + f;
// advance v texcoord:
vCoord += rendererData[k].uvScale.y * (math.distance(frames[index].position, frames[prevIndex].position) /
(rendererData[k].normalizeV == 1 ? smoothLength : actualToRestLengthRatio));
// calculate section thickness and scale the basis vectors by it:
float sectionThickness = frames[index].thickness * rendererData[k].thicknessScale;
normal.x = frames[index].position.x - localSpaceCamera.x;
normal.y = frames[index].position.y - localSpaceCamera.y;
normal.z = frames[index].position.z - localSpaceCamera.z;
normal = math.normalize(normal);
bitangent.x = -(normal.y * frames[index].tangent.z - normal.z * frames[index].tangent.y);
bitangent.y = -(normal.z * frames[index].tangent.x - normal.x * frames[index].tangent.z);
bitangent.z = -(normal.x * frames[index].tangent.y - normal.y * frames[index].tangent.x);
bitangent.xyz = math.normalize(bitangent.xyz);
bitangent.w = 1;
vertex.x = frames[index].position.x - bitangent.x * sectionThickness;
vertex.y = frames[index].position.y - bitangent.y * sectionThickness;
vertex.z = frames[index].position.z - bitangent.z * sectionThickness;
vertices[firstVertex + sectionIndex * 2] = new ProceduralRopeVertex
{
pos = vertex,
normal = -normal,
tangent = bitangent,
color = frames[index].color,
uv = new float2(0, vCoord)
};
vertex.x = frames[index].position.x + bitangent.x * sectionThickness;
vertex.y = frames[index].position.y + bitangent.y * sectionThickness;
vertex.z = frames[index].position.z + bitangent.z * sectionThickness;
vertices[firstVertex + sectionIndex * 2 + 1] = new ProceduralRopeVertex
{
pos = vertex,
normal = -normal,
tangent = bitangent,
color = frames[index].color,
uv = new float2(1, vCoord)
};
if (f < frameCount - 1)
{
int offset = firstTriangle * 3;
tris[offset + tri++] = firstVertex + sectionIndex * 2;
tris[offset + tri++] = firstVertex + (sectionIndex + 1) * 2;
tris[offset + tri++] = firstVertex + sectionIndex * 2 + 1;
tris[offset + tri++] = firstVertex + sectionIndex * 2 + 1;
tris[offset + tri++] = firstVertex + (sectionIndex + 1) * 2;
tris[offset + tri++] = firstVertex + (sectionIndex + 1) * 2 + 1;
}
sectionIndex++;
}
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5f950cc46a13e4273856e2ec96c59eb9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,272 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using UnityEngine;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
using Unity.Burst;
using UnityEngine.Rendering;
namespace Obi
{
public class BurstMeshRopeRenderSystem : ObiMeshRopeRenderSystem
{
public BurstMeshRopeRenderSystem(ObiSolver solver) : base(solver)
{
}
protected override void CloseBatches()
{
for (int i = 0; i < batchList.Count; ++i)
batchList[i].Initialize(sortedRenderers, meshData, meshIndices, layout, false);
base.CloseBatches();
}
public override void Render()
{
if (pathSmootherSystem == null)
return;
using (m_RenderMarker.Auto())
{
var handles = new NativeArray<JobHandle>(batchList.Count, Allocator.Temp);
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
var meshJob = new BuildRopeMeshJob
{
chunkOffsets = pathSmootherSystem.chunkOffsets.AsNativeArray<int>(),
pathSmootherIndices = pathSmootherIndices.AsNativeArray<int>(),
frames = pathSmootherSystem.smoothFrames.AsNativeArray<BurstPathFrame>(),
frameOffsets = pathSmootherSystem.smoothFrameOffsets.AsNativeArray<int>(),
frameCounts = pathSmootherSystem.smoothFrameCounts.AsNativeArray<int>(),
vertexOffsets = vertexOffsets.AsNativeArray<int>(),
meshIndices = meshIndices.AsNativeArray<int>(),
meshData = meshData.meshData.AsNativeArray<MeshDataBatch.MeshData>(),
rendererData = rendererData.AsNativeArray<BurstMeshData>(),
pathData = pathSmootherSystem.pathData.AsNativeArray<BurstPathSmootherData>(),
sortedIndices = sortedIndices.AsNativeArray<int>(),
sortedOffsets = sortedOffsets.AsNativeArray<int>(),
positions = meshData.restPositions.AsNativeArray<float3>(),
normals = meshData.restNormals.AsNativeArray<float3>(),
tangents = meshData.restTangents.AsNativeArray<float4>(),
colors = meshData.restColors.AsNativeArray<float4>(),
vertices = batch.dynamicVertexData.AsNativeArray<RopeMeshVertex>(),
firstRenderer = batch.firstRenderer
};
handles[i] = meshJob.Schedule(batch.rendererCount, 1);
}
JobHandle.CombineDependencies(handles).Complete();
handles.Dispose();
for (int i = 0; i < batchList.Count; ++i)
{
var batch = batchList[i];
batch.mesh.SetVertexBufferData(batch.dynamicVertexData.AsNativeArray<DynamicBatchVertex>(), 0, 0, batch.vertexCount, 0, MeshUpdateFlags.DontValidateIndices | MeshUpdateFlags.DontRecalculateBounds | MeshUpdateFlags.DontResetBoneBounds | MeshUpdateFlags.DontNotifyMeshUsers);
var rp = batch.renderParams;
rp.worldBounds = m_Solver.bounds;
for (int m = 0; m < batch.materials.Length; ++m)
{
rp.material = batch.materials[m];
Graphics.RenderMesh(rp, batch.mesh, m, m_Solver.transform.localToWorldMatrix, m_Solver.transform.localToWorldMatrix);
// Unity bug: Graphics.RenderMesh consistently crashes when existing play mode (seems fixed in 2021.3.4f1)
// https://issuetracker.unity3d.com/issues/the-editor-crashes-on-exit-when-using-graphics-dot-rendermesh
//renderParams.material = batch.materials[m];
//renderParams.camera = null;
//Graphics.RenderMesh(renderParams, batch.mesh, m, m_Solver.transform.localToWorldMatrix);
}
}
}
}
[BurstCompile]
struct BuildRopeMeshJob : IJobParallelFor
{
[ReadOnly] public NativeArray<int> pathSmootherIndices;
[ReadOnly] public NativeArray<int> chunkOffsets;
[ReadOnly] public NativeArray<BurstPathFrame> frames;
[ReadOnly] public NativeArray<int> frameOffsets;
[ReadOnly] public NativeArray<int> frameCounts;
[ReadOnly] public NativeArray<int> vertexOffsets;
[ReadOnly] public NativeArray<int> meshIndices;
[ReadOnly] public NativeArray<MeshDataBatch.MeshData> meshData;
[ReadOnly] public NativeArray<BurstMeshData> rendererData;
[ReadOnly] public NativeArray<BurstPathSmootherData> pathData;
[ReadOnly] public NativeArray<int> sortedIndices;
[ReadOnly] public NativeArray<int> sortedOffsets;
[ReadOnly] public NativeArray<float3> positions;
[ReadOnly] public NativeArray<float3> normals;
[ReadOnly] public NativeArray<float4> tangents;
[ReadOnly] public NativeArray<float4> colors;
[NativeDisableParallelForRestriction] public NativeArray<RopeMeshVertex> vertices;
[ReadOnly] public int firstRenderer;
public void Execute(int i)
{
int rendererIndex = firstRenderer + i;
int pathIndex = pathSmootherIndices[rendererIndex];
var renderer = rendererData[rendererIndex];
// get mesh data:
var mesh = meshData[meshIndices[rendererIndex]];
var sortedOffset = sortedOffsets[rendererIndex];
// get index of first output vertex:
int firstOutputVertex = vertexOffsets[rendererIndex];
// get index of first chunk, ignore others (no support for tearing):
int chunkIndex = chunkOffsets[pathIndex];
// get first frame and frame count:
int firstFrame = frameOffsets[chunkIndex];
int lastFrame = firstFrame + frameCounts[chunkIndex] - 1;
// get mesh deform axis:
int axis = (int)renderer.axis;
// initialize scale vector:
float3 actualScale = (Vector3)renderer.scale;
// calculate stretch ratio:
float stretchRatio = renderer.stretchWithRope == 1 ? pathData[chunkIndex].smoothLength / pathData[chunkIndex].restLength : 1;
// squashing factor, makes mesh thinner when stretched and thicker when compresssed.
float squashing = math.clamp(1 + renderer.volumeScaling * (1 / math.max(stretchRatio, 0.01f) - 1), 0.01f, 2);
// calculate scale along swept axis so that the mesh spans the entire lenght of the rope if required.
if (renderer.spanEntireLength == 1)
{
float totalMeshLength = renderer.meshSizeAlongAxis * renderer.instances;
float totalSpacing = renderer.instanceSpacing * (renderer.instances - 1);
actualScale[axis] = pathData[chunkIndex].restLength / (totalMeshLength + totalSpacing);
}
// adjust axis lenght by stretch ratio:
actualScale[axis] *= stretchRatio;
// init loop variables:
float lengthAlongAxis = renderer.offset;
int index = firstFrame;
int nextIndex = firstFrame + 1;
int prevIndex = firstFrame;
float nextMagnitude = math.distance(frames[index].position, frames[nextIndex].position);
float prevMagnitude = nextMagnitude;
for (int k = 0; k < renderer.instances; ++k)
{
for (int j = 0; j < mesh.vertexCount; ++j)
{
int currVIndex = mesh.firstVertex + sortedIndices[sortedOffset + j];
int prevVIndex = mesh.firstVertex + sortedIndices[sortedOffset + math.max(0,j - 1)];
// calculate how much we've advanced in the sort axis since the last vertex:
lengthAlongAxis += (positions[currVIndex][axis] - positions[prevVIndex][axis]) * actualScale[axis];
// check if we have moved to a new section of the curve:
BurstPathFrame frame;
if (lengthAlongAxis < 0)
{
while (-lengthAlongAxis > prevMagnitude && index > firstFrame)
{
lengthAlongAxis += prevMagnitude;
index = math.max(index - 1, firstFrame);
nextIndex = math.min(index + 1, lastFrame);
prevIndex = math.max(index - 1, firstFrame);
nextMagnitude = math.distance(frames[index].position, frames[nextIndex].position);
prevMagnitude = math.distance(frames[index].position, frames[prevIndex].position);
}
var offset = float3.zero;
if (index == prevIndex)
{
offset = frames[index].position - frames[nextIndex].position;
prevMagnitude = math.length(offset);
}
frame = InterpolateFrames(frames[index], frames[prevIndex], offset, -lengthAlongAxis / prevMagnitude);
}
else
{
while (lengthAlongAxis > nextMagnitude && index < lastFrame)
{
lengthAlongAxis -= nextMagnitude;
index = math.min(index + 1, lastFrame);
nextIndex = math.min(index + 1, lastFrame);
prevIndex = math.max(index - 1, firstFrame);
nextMagnitude = math.distance(frames[index].position, frames[nextIndex].position);
prevMagnitude = math.distance(frames[index].position, frames[prevIndex].position);
}
var offset = float3.zero;
if (index == nextIndex)
{
offset = frames[index].position - frames[prevIndex].position;
nextMagnitude = math.length(offset);
}
frame = InterpolateFrames(frames[index], frames[nextIndex], offset, lengthAlongAxis / nextMagnitude);
}
// update basis matrix:
var basis = frame.ToMatrix(axis);
// calculate vertex offset from curve:
float3 offsetFromCurve = positions[currVIndex] * actualScale * frame.thickness * squashing;
offsetFromCurve[axis] = 0;
// write modified vertex data:
vertices[firstOutputVertex + sortedIndices[sortedOffset + j]] = new RopeMeshVertex
{
pos = frame.position + math.mul(basis, offsetFromCurve),
normal = math.mul(basis, normals[currVIndex]),
tangent = new float4(math.mul(basis, tangents[currVIndex].xyz), tangents[currVIndex].w),
color = colors[currVIndex] * frame.color,
};
}
firstOutputVertex += mesh.vertexCount;
lengthAlongAxis += renderer.instanceSpacing * actualScale[axis];
}
}
BurstPathFrame InterpolateFrames(BurstPathFrame a, BurstPathFrame b, float3 bOffset, float t)
{
// this offset is used to displace a copy of the first and last frames of the path,
// to ensure meshes extrude correctly prior to the first or past the last frame.
b.position += bOffset;
var interp = (1 - t) * a + t * b;
// (no need to renormalize tangent, since offsetFromCurve[axis] = 0)
interp.normal = math.normalize(interp.normal);
interp.binormal = math.normalize(interp.binormal);
return interp;
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1578f081848d045dba76cd147f835e7c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,170 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using System.Runtime.InteropServices;
using UnityEngine;
using Unity.Mathematics;
namespace Obi
{
[StructLayout(LayoutKind.Sequential)]
public struct BurstPathFrame
{
public enum Axis
{
X = 0,
Y = 1,
Z = 2
}
public float3 position;
public float3 tangent;
public float3 normal;
public float3 binormal;
public float4 color;
public float thickness;
public BurstPathFrame(float3 position, float3 tangent, float3 normal, float3 binormal, float4 color, float thickness)
{
this.position = position;
this.normal = normal;
this.tangent = tangent;
this.binormal = binormal;
this.color = color;
this.thickness = thickness;
}
public void Reset()
{
position = float3.zero;
tangent = new float3(0, 0, 1);
normal = new float3(0, 1, 0);
binormal = new float3(1, 0, 0);
color = new float4(1, 1, 1, 1);
thickness = 0;
}
public static BurstPathFrame operator +(BurstPathFrame c1, BurstPathFrame c2)
{
return new BurstPathFrame(c1.position + c2.position, c1.tangent + c2.tangent, c1.normal + c2.normal, c1.binormal + c2.binormal, c1.color + c2.color, c1.thickness + c2.thickness);
}
public static BurstPathFrame operator *(float f, BurstPathFrame c)
{
return new BurstPathFrame(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 BurstPathFrame c1, in BurstPathFrame c2, in BurstPathFrame c3, ref BurstPathFrame 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;
}
public void SetTwist(float twist)
{
quaternion twistQ = quaternion.AxisAngle(tangent, math.radians(twist));
normal = math.mul(twistQ, normal);
binormal = math.mul(twistQ, binormal);
}
public static quaternion FromToRotation(float3 aFrom, float3 aTo)
{
float3 axis = math.cross(aFrom, aTo);
float angle = math.acos(math.clamp(math.dot(math.normalize(aFrom), math.normalize(aTo)), -1f, 1f));
return quaternion.AxisAngle(math.normalize(axis), angle);
}
public void SetTwistAndTangent(float twist, float3 tangent)
{
this.tangent = tangent;
normal = math.normalize(new float3(tangent.y, tangent.x, 0));
binormal = math.cross(normal, tangent);
quaternion twistQ = quaternion.AxisAngle(tangent, math.radians(twist));
normal = math.mul(twistQ, normal);
binormal = math.mul(twistQ, binormal);
}
public void Transport(in BurstPathFrame frame, float twist)
{
// Calculate delta rotation:
quaternion rotQ = Quaternion.FromToRotation(tangent, frame.tangent);
quaternion twistQ = quaternion.AxisAngle(frame.tangent, math.radians(twist));
quaternion finalQ = math.mul(twistQ, rotQ);
// Rotate previous frame axes to obtain the new ones:
normal = math.mul(finalQ, normal);
binormal = math.mul(finalQ, binormal);
tangent = frame.tangent;
position = frame.position;
thickness = frame.thickness;
color = frame.color;
}
public void Transport(float3 newPosition, float3 newTangent, float twist)
{
// Calculate delta rotation:
quaternion rotQ = Quaternion.FromToRotation(tangent, newTangent);
quaternion twistQ = quaternion.AxisAngle(newTangent, math.radians(twist));
quaternion finalQ = math.mul(twistQ, rotQ);
// Rotate previous frame axes to obtain the new ones:
normal = math.mul(finalQ, normal);
binormal = math.mul(finalQ, binormal);
tangent = newTangent;
position = newPosition;
}
// Transport, hinting the normal.
public void Transport(float3 newPosition, float3 newTangent, float3 newNormal, float twist)
{
normal = math.mul(quaternion.AxisAngle(newTangent, math.radians(twist)), newNormal);
tangent = newTangent;
binormal = math.cross(normal, tangent);
position = newPosition;
}
public float3x3 ToMatrix(int mainAxis)
{
float3x3 basis = new float3x3();
int xo = (mainAxis) % 3;
int yo = (mainAxis + 1) % 3;
int zo = (mainAxis + 2) % 3;
basis[xo] = tangent;
basis[yo] = binormal;
basis[zo] = normal;
return basis;
}
public void DebugDraw(float size)
{
Debug.DrawRay(position, binormal * size, Color.red);
Debug.DrawRay(position, normal * size, Color.green);
Debug.DrawRay(position, tangent * size, Color.blue);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4827bd4bd4feb46bfbe458174871b8ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,63 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using Unity.Jobs;
using Unity.Mathematics;
namespace Obi
{
public class BurstPathSmootherRenderSystem : ObiPathSmootherRenderSystem
{
public BurstPathSmootherRenderSystem(ObiSolver solver) : base(solver)
{
}
public override void Render()
{
using (m_RenderMarker.Auto())
{
base.Render();
// generate raw frames using parallel transport
var parallelTransportJob = new ParallelTransportJob
{
pathFrames = rawFrames.AsNativeArray<BurstPathFrame>(),
frameOffsets = rawFrameOffsets.AsNativeArray<int>(),
particleIndices = particleIndices.AsNativeArray<int>(),
renderablePositions = m_Solver.renderablePositions.AsNativeArray<float4>(),
renderableOrientations = m_Solver.renderableOrientations.AsNativeArray<quaternion>(),
principalRadii = m_Solver.principalRadii.AsNativeArray<float4>(),
colors = m_Solver.colors.AsNativeArray<float4>(),
pathData = pathData.AsNativeArray<BurstPathSmootherData>()
};
var handle = parallelTransportJob.Schedule(rawFrameOffsets.count, 4);
// throw away unneeded frames using decimation
var decimationJob = new DecimateChunksJob
{
inputFrames = rawFrames.AsNativeArray<BurstPathFrame>(),
inputFrameOffsets = rawFrameOffsets.AsNativeArray<int>(),
outputFrameCounts = decimatedFrameCounts.AsNativeArray<int>(),
pathData = pathData.AsNativeArray<BurstPathSmootherData>()
};
handle = decimationJob.Schedule(rawFrameOffsets.count, 4, handle);
// smooth chunks:
var chaikinJob = new ChaikinSmoothChunksJob()
{
inputFrames = rawFrames.AsNativeArray<BurstPathFrame>(),
inputFrameOffsets = rawFrameOffsets.AsNativeArray<int>(),
inputFrameCounts = decimatedFrameCounts.AsNativeArray<int>(),
outputFrames = smoothFrames.AsNativeArray<BurstPathFrame>(),
outputFrameOffsets = smoothFrameOffsets.AsNativeArray<int>(),
outputFrameCounts = smoothFrameCounts.AsNativeArray<int>(),
pathData = pathData.AsNativeArray<BurstPathSmootherData>()
};
chaikinJob.Schedule(rawFrameOffsets.count, 4, handle).Complete();
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be61f37ff31ff43ebb9bc8a55bd3edc7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,93 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using Unity.Burst;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Jobs;
using Unity.Mathematics;
namespace Obi
{
[BurstCompile]
struct ChaikinSmoothChunksJob : IJobParallelFor
{
[NativeDisableParallelForRestriction] public NativeArray<BurstPathFrame> inputFrames;
[ReadOnly] public NativeArray<int> inputFrameOffsets;
[ReadOnly] public NativeArray<int> inputFrameCounts;
[NativeDisableParallelForRestriction] public NativeArray<BurstPathFrame> outputFrames;
[ReadOnly] public NativeArray<int> outputFrameOffsets;
[NativeDisableParallelForRestriction] public NativeArray<int> outputFrameCounts;
[NativeDisableParallelForRestriction] public NativeArray<BurstPathSmootherData> pathData;
public void Execute(int i)
{
int firstInputIndex = i > 0 ? inputFrameOffsets[i - 1] : 0;
int inputFrameCount = inputFrameCounts[i];
int firstOutputIndex = outputFrameOffsets[i];
int k = (int)pathData[i].smoothing;
// No work to do. just copy the input to the output:
if (k == 0)
{
outputFrameCounts[i] = inputFrameCount;
for (int j = 0; j < inputFrameCount; ++j)
outputFrames[firstOutputIndex + j] = inputFrames[firstInputIndex + j];
}
else
{
// precalculate some quantities:
int pCount = (int)math.pow(2, k);
int n0 = inputFrameCount - 1;
float twoRaisedToMinusKPlus1 = math.pow(2, -(k + 1));
float twoRaisedToMinusK = math.pow(2, -k);
float twoRaisedToMinus2K = math.pow(2, -2 * k);
float twoRaisedToMinus2KMinus1 = math.pow(2, -2 * k - 1);
outputFrameCounts[i] = (inputFrameCount - 2) * pCount + 2;
// calculate initial curve points:
outputFrames[firstOutputIndex] = (0.5f + twoRaisedToMinusKPlus1) * inputFrames[firstInputIndex] + (0.5f - twoRaisedToMinusKPlus1) * inputFrames[firstInputIndex + 1];
outputFrames[firstOutputIndex + pCount * n0 - pCount + 1] = (0.5f - twoRaisedToMinusKPlus1) * inputFrames[firstInputIndex + n0 - 1] + (0.5f + twoRaisedToMinusKPlus1) * inputFrames[firstInputIndex + 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 l = 1; l < n0; ++l)
{
BurstPathFrame.WeightedSum(F, G, H,
in GetElementAsRef(inputFrames, firstInputIndex + l - 1),
in GetElementAsRef(inputFrames, firstInputIndex + l),
in GetElementAsRef(inputFrames, firstInputIndex + l + 1),
ref GetElementAsRef(outputFrames, firstOutputIndex + (l - 1) * pCount + j));
}
}
// make first and last curve points coincide with original points:
outputFrames[firstOutputIndex] = inputFrames[firstInputIndex];
outputFrames[firstOutputIndex + outputFrameCounts[i] - 1] = inputFrames[firstInputIndex + inputFrameCount - 1];
}
var data = pathData[i];
data.smoothLength = 0;
for (int j = firstOutputIndex + 1; j < firstOutputIndex + outputFrameCounts[i]; ++j)
data.smoothLength += math.distance(outputFrames[j - 1].position, outputFrames[j].position);
pathData[i] = data;
}
private static unsafe ref T GetElementAsRef<T>(NativeArray<T> array, int index) where T : unmanaged
{
return ref UnsafeUtility.ArrayElementAsRef<T>(array.GetUnsafePtr(), index);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0b479f3e1bd184363a9259c4d737b611
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,79 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
namespace Obi
{
[BurstCompile]
struct DecimateChunksJob : IJobParallelFor
{
[ReadOnly] public NativeArray<int> inputFrameOffsets;
[NativeDisableParallelForRestriction] public NativeArray<BurstPathFrame> inputFrames;
[NativeDisableParallelForRestriction] public NativeArray<int> outputFrameCounts;
[ReadOnly] public NativeArray<BurstPathSmootherData> pathData;
public void Execute(int i)
{
int firstInputIndex = i > 0 ? inputFrameOffsets[i - 1] : 0;
int inputFrameCount = inputFrameOffsets[i] - firstInputIndex;
// no decimation, no work to do, just return:
if (pathData[i].decimation < 0.00001f || inputFrameCount < 3)
{
outputFrameCounts[i] = inputFrameCount;
return;
}
float scaledThreshold = pathData[i].decimation * pathData[i].decimation * 0.01f;
int start = 0;
int end = inputFrameCount - 1;
outputFrameCounts[i] = 0;
while (start < end)
{
// add starting point:
inputFrames[firstInputIndex + outputFrameCounts[i]++] = inputFrames[firstInputIndex + start];
var newEnd = end;
while (true)
{
int maxDistanceIndex = 0;
float maxDistance = 0;
// find the point that's furthest away from the current segment:
for (int k = start + 1; k < newEnd; k++)
{
var nearest = BurstMath.NearestPointOnEdge(inputFrames[firstInputIndex + start].position,
inputFrames[firstInputIndex + newEnd].position,
inputFrames[firstInputIndex + k].position, out _);
float d = math.lengthsq(nearest - inputFrames[firstInputIndex + k].position);
if (d > maxDistance)
{
maxDistanceIndex = k;
maxDistance = d;
}
}
if (maxDistance <= scaledThreshold)
break;
newEnd = maxDistanceIndex;
}
start = newEnd;
}
// add the last point:
inputFrames[firstInputIndex + outputFrameCounts[i]++] = inputFrames[firstInputIndex + end];
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6e3177e6c6643442db096e939f5d7109
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,89 @@
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
using Unity.Burst;
using Unity.Collections;
using Unity.Jobs;
using Unity.Mathematics;
namespace Obi
{
[BurstCompile]
struct ParallelTransportJob : IJobParallelFor
{
[NativeDisableParallelForRestriction] public NativeArray<BurstPathFrame> pathFrames;
[ReadOnly] public NativeArray<int> frameOffsets;
[ReadOnly] public NativeArray<int> particleIndices;
[ReadOnly] public NativeArray<float4> renderablePositions;
[ReadOnly] public NativeArray<quaternion> renderableOrientations;
[ReadOnly] public NativeArray<float4> principalRadii;
[ReadOnly] public NativeArray<float4> colors;
[ReadOnly] public NativeArray<BurstPathSmootherData> pathData;
public void Execute(int i)
{
BurstPathFrame nextFrame = new BurstPathFrame();
BurstPathFrame currFrame = new BurstPathFrame();
BurstPathFrame prevFrame = new BurstPathFrame();
nextFrame.Reset();
currFrame.Reset();
prevFrame.Reset();
int firstIndex = i > 0 ? frameOffsets[i - 1] : 0;
int frameCount = frameOffsets[i] - firstIndex;
// initialize current and previous frame:
PathFrameFromParticle(ref currFrame, particleIndices[firstIndex], pathData[i].usesOrientedParticles == 1, false);
prevFrame = currFrame;
// parallel transport:
for (int m = 1; m <= frameCount; ++m)
{
int index = firstIndex + math.min(m, frameCount - 1);
int pIndex = particleIndices[index];
// generate curve frame from particle:
PathFrameFromParticle(ref nextFrame, pIndex, pathData[i].usesOrientedParticles == 1);
if (pathData[i].usesOrientedParticles == 1)
{
// copy frame directly.
prevFrame = currFrame;
}
else
{
// perform parallel transport, using forward / backward average to calculate tangent.
// if the average is too small, reuse the previous frame tangent.
currFrame.tangent = math.normalizesafe((currFrame.position - prevFrame.position) +
(nextFrame.position - currFrame.position), prevFrame.tangent);
prevFrame.Transport(currFrame, pathData[i].twist);
}
// advance current frame:
currFrame = nextFrame;
pathFrames[firstIndex + m - 1] = prevFrame;
}
}
private void PathFrameFromParticle(ref BurstPathFrame frame, int particleIndex, bool useOrientedParticles, bool interpolateOrientation = false)
{
// Update current frame values from particles:
frame.position = renderablePositions[particleIndex].xyz;
frame.thickness = principalRadii[particleIndex][0];
frame.color = colors[particleIndex];
// Use particle orientation if possible:
if (useOrientedParticles)
{
quaternion current = renderableOrientations[particleIndex];
quaternion previous = renderableOrientations[math.max(0, particleIndex - 1)];
float4x4 average = (interpolateOrientation ? math.slerp(current, previous, 0.5f) : current).toMatrix();
frame.normal = average.c1.xyz;
frame.binormal = average.c0.xyz;
frame.tangent = average.c2.xyz;
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7c4018fb5d53646be9ef6c91f07c7eb6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: