diff --git a/Components/Mesh/MengerSponge.cs b/Components/Mesh/MengerSponge.cs new file mode 100644 index 0000000..08f920f --- /dev/null +++ b/Components/Mesh/MengerSponge.cs @@ -0,0 +1,46 @@ +using Elements.Assets; +using Elements.Core; +using FrooxEngine; +using System; + +namespace Obsidian +{ + [Category(new string[] { "Obsidian/Assets/Procedural Meshes" })] + public class MengerSpongeMesh : ProceduralMesh + { + [Range(1, 4)] public readonly Sync Subdivisions; + private MengerSponge sponge; + private int _subdivisions; + + protected override void OnAwake() + { + base.OnAwake(); + Subdivisions.Value = 1; + } + + protected override void PrepareAssetUpdateData() + { + _subdivisions = Subdivisions.Value; + } + + protected override void ClearMeshData() + { + sponge = null; + } + + protected override void UpdateMeshData(MeshX meshx) + { + bool value = false; + if (sponge == null || sponge.Subdivisions != _subdivisions) + { + sponge?.Remove(); + sponge = new MengerSponge(meshx, _subdivisions); + value = true; + } + + sponge.Subdivisions = Subdivisions.Value; + sponge.Update(); + uploadHint[MeshUploadHint.Flag.Geometry] = value; + } + } +} \ No newline at end of file diff --git a/Components/Mesh/MengerSpongeMesh.cs b/Components/Mesh/MengerSpongeMesh.cs new file mode 100644 index 0000000..ef69183 --- /dev/null +++ b/Components/Mesh/MengerSpongeMesh.cs @@ -0,0 +1,103 @@ +using Elements.Assets; +using Elements.Core; +using System; +using System.Collections.Generic; + +namespace Obsidian +{ + public class MengerSponge : MeshXShape + { + public int Subdivisions; + + public MengerSponge(MeshX mesh, int subdivisions) : base(mesh) + { + Subdivisions = subdivisions; + mesh.Clear(); + GenerateSponge(mesh, Subdivisions, float3.Zero, 1); + } + + private void GenerateSponge(MeshX mesh, int level, float3 center, float size) + { + if (level == 0) + { + GenerateCube(mesh, center, size); + } + else + { + float newSize = size / 3; + int newLevel = level - 1; + + for (int x = -1; x <= 1; x++) + { + for (int y = -1; y <= 1; y++) + { + for (int z = -1; z <= 1; z++) + { + if (x == 0 && y == 0 || x == 0 && z == 0 || y == 0 && z == 0) + continue; + + + float3 newCenter = center + new float3(x * newSize, y * newSize, z * newSize); + GenerateSponge(mesh, newLevel, newCenter, newSize); + } + } + } + } + } + + private void GenerateCube(MeshX mesh, float3 center, float size) + { + float halfSize = size / 2; + var cubeVertices = new float3[] + { + center + new float3(-halfSize, -halfSize, -halfSize), + center + new float3(halfSize, -halfSize, -halfSize), + center + new float3(halfSize, -halfSize, halfSize), + center + new float3(-halfSize, -halfSize, halfSize), + center + new float3(-halfSize, halfSize, -halfSize), + center + new float3(halfSize, halfSize, -halfSize), + center + new float3(halfSize, halfSize, halfSize), + center + new float3(-halfSize, halfSize, halfSize), + }; + //fixed array + var cubeTriangles = new int[] + { + 0, 1, 2, 0, 2, 3, 2, 1, 6, 6, 1, 5, 0, 4, 5, 0, 5, 1, + 6, 7, 3, 6, 3, 2, 7, 6, 5, 7, 5, 4, 4, 0, 3, 4, 3, 7, + }; + + //leaving this here due to it can cause funky generation + + //var cubeTriangles = new int[] + //{ + // 0, 2, 1, 0, 3, 2, 2, 3, 6, 6, 3, 7, 0, 7, 3, 0, 4, 7, + // 6, 5, 2, 5, 1, 2, 1, 5, 0, 5, 4, 0, 4, 5, 7, 5, 6, 7, + //}; + + for (int i = 0; i < cubeTriangles.Length; i += 3) + { + int vertexCount = mesh.VertexCount; + mesh.SetVertexCount(vertexCount + 3); + + mesh.SetVertex(vertexCount, cubeVertices[cubeTriangles[i]]); + mesh.SetVertex(vertexCount + 1, cubeVertices[cubeTriangles[i + 1]]); + mesh.SetVertex(vertexCount + 2, cubeVertices[cubeTriangles[i + 2]]); + + float3 normal = MathX.Cross( + cubeVertices[cubeTriangles[i + 1]] - cubeVertices[cubeTriangles[i]], + cubeVertices[cubeTriangles[i + 2]] - cubeVertices[cubeTriangles[i]]).Normalized; + mesh.SetNormal(vertexCount, normal); + mesh.SetNormal(vertexCount + 1, normal); + mesh.SetNormal(vertexCount + 2, normal); + + mesh.AddTriangle(vertexCount, vertexCount + 1, vertexCount + 2); + } + } + + public override void Update() + { + Mesh.RecalculateNormals(AllTriangles); + Mesh.RecalculateTangents(AllTriangles); + } + } +} \ No newline at end of file diff --git a/Components/Mesh/SierpinskiPyramid.cs b/Components/Mesh/SierpinskiPyramid.cs new file mode 100644 index 0000000..45870cb --- /dev/null +++ b/Components/Mesh/SierpinskiPyramid.cs @@ -0,0 +1,115 @@ +using Elements.Assets; +using Elements.Core; +using System; +using System.Collections.Generic; + + +namespace Obsidian +{ + // https://answers.unity.com/questions/1477363/infinite-vector-3-generate-method-for-sierpinski-t.html + public class SierpinskiPyramid : MeshXShape + { + public int Subdivisions; + + public SierpinskiPyramid(MeshX mesh, int subdivisions) : base(mesh) + { + Subdivisions = subdivisions; + var sTet = new STetrahedron().SubdivideFirst(subdivisions); + mesh.Clear(); + mesh.SetVertexCount(sTet.centers.Count * 12); + + float s = sTet.Size; + int i = 0; + foreach (var c in sTet.centers) + { + var v0 = c + new float3(0, s, 0); + var v1 = c + new float3(-STetrahedron.s2_3 * s, -STetrahedron.f1_3 * s, -STetrahedron.s2_9 * s); + var v2 = c + new float3(STetrahedron.s2_3 * s, -STetrahedron.f1_3 * s, -STetrahedron.s2_9 * s); + var v3 = c + new float3(0, -STetrahedron.f1_3 * s, STetrahedron.s8_9 * s); + + var n = MathX.Cross(v2 - v0, v1 - v0).Normalized; + mesh.SetNormal(i, n); + mesh.SetNormal(i + 1, n); + mesh.SetNormal(i + 2, n); + mesh.AddTriangle(i, i + 1, i + 2); + mesh.SetVertex(i++, v0); + mesh.SetVertex(i++, v2); + mesh.SetVertex(i++, v1); + + n = MathX.Cross(v1 - v0, v3 - v0).Normalized; + mesh.SetNormal(i, n); + mesh.SetNormal(i + 1, n); + mesh.SetNormal(i + 2, n); + mesh.AddTriangle(i, i + 1, i + 2); + mesh.SetVertex(i++, v0); + mesh.SetVertex(i++, v1); + mesh.SetVertex(i++, v3); + + n = MathX.Cross(v3 - v0, v2 - v0).Normalized; + mesh.SetNormal(i, n); + mesh.SetNormal(i + 1, n); + mesh.SetNormal(i + 2, n); + mesh.AddTriangle(i, i + 1, i + 2); + mesh.SetVertex(i++, v0); + mesh.SetVertex(i++, v3); + mesh.SetVertex(i++, v2); + + n = float3.Down; + mesh.SetNormal(i, n); + mesh.SetNormal(i + 1, n); + mesh.SetNormal(i + 2, n); + mesh.AddTriangle(i, i + 1, i + 2); + mesh.SetVertex(i++, v1); + mesh.SetVertex(i++, v2); + mesh.SetVertex(i++, v3); + } + + // The following have no effects + // mesh.RecalculateNormals(); + // mesh.RecalculateTangents(); + // mesh.GetMergedDoubles(); + } + + public override void Update() + { + Mesh.RecalculateNormals(AllTriangles); + Mesh.RecalculateTangents(AllTriangles); + } + + public class STetrahedron + { + public static float s8_9 = MathX.Sqrt(8f / 9f); + public static float s2_9 = MathX.Sqrt(2f / 9f); + public static float s2_3 = MathX.Sqrt(2f / 3f); + public const float f1_3 = 1f / 3f; + public float Size = 1; + public List centers = new List(); + + private STetrahedron Subdivide() + { + var result = new STetrahedron(); + float s = result.Size = Size * 0.5f; + if (centers.Count == 0) + centers.Add(float3.Zero); + foreach (var c in centers) + { + result.centers.Add(c + new float3(0, s, 0)); + result.centers.Add(c + new float3(-s2_3 * s, -f1_3 * s, -s2_9 * s)); + result.centers.Add(c + new float3(s2_3 * s, -f1_3 * s, -s2_9 * s)); + result.centers.Add(c + new float3(0, -f1_3 * s, s8_9 * s)); + } + + return result; + } + + public STetrahedron SubdivideFirst(int aCount) + { + var res = this; + Size = aCount; + for (int i = 0; i < aCount; i++) + res = res.Subdivide(); + return res; + } + } + } +} \ No newline at end of file diff --git a/Components/Mesh/SierpinskiPyramidMesh.cs b/Components/Mesh/SierpinskiPyramidMesh.cs new file mode 100644 index 0000000..5227c90 --- /dev/null +++ b/Components/Mesh/SierpinskiPyramidMesh.cs @@ -0,0 +1,46 @@ +using Elements.Assets; +using Elements.Core; +using FrooxEngine; +using System; + +namespace Obsidian +{ + [Category(new string[] { "Obsidian/Assets/Procedural Meshes" })] + public class SierpinskiPyramidMesh : ProceduralMesh + { + [Range(1, 9)] public readonly Sync Subdivisions; + private SierpinskiPyramid pyramid; + private int _subdivisions; + + protected override void OnAwake() + { + base.OnAwake(); + Subdivisions.Value = 1; + } + + protected override void PrepareAssetUpdateData() + { + _subdivisions = Subdivisions.Value; + } + + protected override void ClearMeshData() + { + pyramid = null; + } + + protected override void UpdateMeshData(MeshX meshx) + { + bool value = false; + if (pyramid == null || pyramid.Subdivisions != _subdivisions) + { + pyramid?.Remove(); + pyramid = new SierpinskiPyramid(meshx, _subdivisions); + value = true; + } + + pyramid.Subdivisions = Subdivisions.Value; + pyramid.Update(); + uploadHint[MeshUploadHint.Flag.Geometry] = value; + } + } +} \ No newline at end of file diff --git a/Components/Transform/Drivers/MultiValueArithmeticDriver.cs b/Components/Transform/Drivers/MultiValueArithmeticDriver.cs new file mode 100644 index 0000000..ddf8e70 --- /dev/null +++ b/Components/Transform/Drivers/MultiValueArithmeticDriver.cs @@ -0,0 +1,70 @@ +using System.Linq; +using Elements.Assets; +using Elements.Core; +using FrooxEngine; + +namespace Obsidian; + +[Category(new string[] { "Obsidian/Transform/Drivers" })] +[GenericTypes(GenericTypesAttribute.Group.Primitives)] +public class MultiValueArithmeticDriver : Component +{ + public static bool IsValidGenericType => Coder.SupportsAddSub; + + public enum ArithmeticMode + { + Addition, + Subtraction, + Multiplication, + Division + } + + public readonly FieldDrive Target; + + public readonly Sync Mode; + + public readonly SyncList> Values; + + protected override void OnChanges() + { + if (!Target.IsLinkValid || Values.Count == 0) + { + return; + } + if (Values.Contains(Target.Target)) + { + // don't let the component drive itself, don't want a feedback loop + Target.ReleaseLink(); + return; + } + T value = Values[0].Value; + switch (Mode.Value) + { + case ArithmeticMode.Addition: + foreach (Sync sync in Values.Skip(1)) + { + value = Coder.Add(value, sync.Value); + } + break; + case ArithmeticMode.Subtraction: + foreach (Sync sync in Values.Skip(1)) + { + value = Coder.Sub(value, sync.Value); + } + break; + case ArithmeticMode.Multiplication: + foreach (Sync sync in Values.Skip(1)) + { + value = Coder.Mul(value, sync.Value); + } + break; + case ArithmeticMode.Division: + foreach (Sync sync in Values.Skip(1)) + { + value = Coder.Div(value, sync.Value); + } + break; + } + Target.Target.Value = value; + } +} \ No newline at end of file diff --git a/Components/Users/BoundingBoxUserTracker.cs b/Components/Users/BoundingBoxUserTracker.cs new file mode 100644 index 0000000..113eb81 --- /dev/null +++ b/Components/Users/BoundingBoxUserTracker.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using System.Linq; +using Elements.Assets; +using Elements.Core; + +namespace FrooxEngine +{ + [Category("Obsidian/Utility")] + public class BoundingBoxUserTracker : Component + { + public readonly Sync PositionSource; + + public readonly RawOutput IsLocalUserInside; + + public readonly RawOutput IsAnyUserInside; + + public readonly RawOutput NumberOfUsersInside; + + protected readonly SyncBag _usersInside; + + protected override void OnChanges() + { + base.OnChanges(); + _usersInside.RemoveAll((UserRef u) => u.Target == null); + IsAnyUserInside.Value = _usersInside.Count > 0; + NumberOfUsersInside.Value = _usersInside.Count; + IsLocalUserInside.Value = _usersInside.Any((KeyValuePair u) => u.Value.Target == this.LocalUser); + } + + public override void OnUserLeft(User user) + { + _usersInside.RemoveAll((UserRef u) => u.Target == user); + } + + protected override void OnAwake() + { + base.OnAwake(); + PositionSource.Value = UserRoot.UserNode.Root; + } + + protected override void OnCommonUpdate() + { + base.OnCommonUpdate(); + BoundingBox b = this.Slot.ComputeBoundingBox(); + if (MathX.IsBetween(this.LocalUser.Root.GetGlobalPosition(PositionSource.Value), b.min, b.max)) + { + if (!_usersInside.Any((KeyValuePair u) => u.Value.Target == this.LocalUser)) + { + _usersInside.Add().Target = this.LocalUser; + } + } + else + { + if (_usersInside.Any((KeyValuePair u) => u.Value.Target == this.LocalUser)) + { + _usersInside.RemoveAll((UserRef u) => u.Target == this.LocalUser); + } + } + } + } +} \ No newline at end of file diff --git a/ProjectObsidian.csproj b/ProjectObsidian.csproj index 9c1c632..49fecf8 100644 --- a/ProjectObsidian.csproj +++ b/ProjectObsidian.csproj @@ -37,5 +37,8 @@ $(ResonitePath)Resonite_Data/Managed/ProtoFluxBindings.dll + + $(ResonitePath)Resonite_Data/Managed/Elements.Assets.dll +