It has been 8 months I've been working on my metaball algorithm an the final technique I am trying out is writing magnets to which the sprite mesh edge vertices will update its position to, after finding the closest magnet point. The red lines will be magnet locations.
The problem is, after multiple hours of debugging and finally implementing an ECS structure that updates the positions of each gameobject sprite vertex to the closest magnet point, these are the ugly effects below:
May I ask for some advice on how to implement these color stretching metaballs for any game object sprite? It's close to a year I'm working on it now... Should making use of Lerp help??
Here is the ECS code that updates slime mesh vertex positions:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Jobs;
using Unity.Collections;
using HerminkasGams.SpriteSquash;
using System.Linq;
using UnityEditor;
using Unity.Burst;
using System.Runtime.Serialization;
using Unity.VisualScripting;
public partial class VertexUpdater : SystemBase
{
public GameObject ObjectManager;
public ObjectManagerController objectManagerController;
[HideInInspector] public List<SpriteMeshModifier> spriteMeshModifiers = new List<SpriteMeshModifier>();
// [HideInInspector] public List<Vertex> vertices = new List<Vertex>(); // List of vertices; each element of this variable is what I must make magnetic
private struct VertexCopy {
public Vector3 currentPos;
public Vector3 initialPos;
public Vector3 revertingPos;
public int _metaballEdgePointCount;
//_parentSpriteMeshModifier variables:
public Vector3 _parentSpriteMeshModifierPosition;
public Quaternion _parentSpriteMeshModifierRotation;
public Vector3 _parentSpriteMeshModifierLocalscale;
[HideInInspector]public int _index;
[HideInInspector]public Vector3 _closestMagnetPoint;
public VertexCopy(Vector3 pos, int index, int metaballEdgePointCount, Vector3 parentSpriteMeshModifierPosition, Quaternion parentSpriteMeshModifierRotation, Vector3 parentSpriteMeshModifierLocalscale, Vector3 closestMagnetPoint) {
currentPos = pos;
initialPos = pos;
revertingPos = pos;
_index = index;
_metaballEdgePointCount = metaballEdgePointCount;
//_parentSpriteMeshModifierPosition variables
_parentSpriteMeshModifierPosition = parentSpriteMeshModifierPosition;
_parentSpriteMeshModifierRotation = parentSpriteMeshModifierRotation;
_parentSpriteMeshModifierLocalscale = parentSpriteMeshModifierLocalscale;
_closestMagnetPoint = closestMagnetPoint;
}
public (Vector3, float) FindNearestMagnetPoint(NativeArray<Vector3> metaballVertexPositionArray) {
float minDistanceUnaltered = 99999;
//Compute the nearest metaball edge magnet point by iterating through them if metaballEdgePointVertexGOs is not empty
if (_metaballEdgePointCount > 0) {
int index = 0;
Vector3 scp = _parentSpriteMeshModifierRotation * currentPos;
Vector3 point = new Vector3(scp.x * _parentSpriteMeshModifierLocalscale.x, scp.y * _parentSpriteMeshModifierLocalscale.y, scp.z * _parentSpriteMeshModifierLocalscale.z) + _parentSpriteMeshModifierPosition;
float minDistance = 99999;
//Will iterate through metaballEdgePointVertexGOs to find closest
//Vector3 scpMagnet = new Vector3(0,0,0);
for (int i = 0; i < _metaballEdgePointCount; i++)
{
var dis = Vector2.Distance(point, metaballVertexPositionArray[i]);
if (dis < minDistance)
{
index = i;
minDistance = dis;
}
}
//Invert metaballVertexPositionArray[index] back to vertex coordinates
Vector3 vec1 = metaballVertexPositionArray[index] - _parentSpriteMeshModifierPosition;
Vector3 vec2 = _parentSpriteMeshModifierLocalscale;
Vector3 result = new Vector3(vec1.x / vec2.x, vec1.y / vec2.y, vec1.z / vec2.z);
Vector3 scpLocal = Quaternion.Inverse(_parentSpriteMeshModifierRotation) * (result);
// Debug.Log(currentPos);
return (scpLocal, minDistance);
}
return (new Vector3(0,0,-1), minDistanceUnaltered);
}
}
[BurstCompile]
private struct ComputeJob : IJobFor {
[NativeDisableParallelForRestriction]
public NativeArray<VertexCopy> vertexCopyArray;
[ReadOnly]
public NativeArray<Vector3> metaballVertexPositionArray;
public void Execute(int index) {
VertexCopy vertexCopy = this.vertexCopyArray[index];
float minDistance = 9999;
(vertexCopy._closestMagnetPoint, minDistance) = vertexCopy.FindNearestMagnetPoint(metaballVertexPositionArray);
//If closest distance is less than threshold
if (minDistance < 0.2) {
vertexCopy.currentPos = vertexCopy._closestMagnetPoint;
this.vertexCopyArray[index] = vertexCopy;
}
}
}
protected override void OnStartRunning()
{
ObjectManager = GameObject.Find("ObjectManager");
objectManagerController = ObjectManager.GetComponent<ObjectManagerController>();
// GameObject.Find("SpriteMeshModifier");
// EntityManager entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
// // Create an Entity "archetype" that will have MyComponent
// EntityArchetype spriteMeshModifierArchetype = entityManager.CreateArchetype(typeof(SpriteMeshModifier));
// // Create an entity with that archetype
// Entity spriteMeshModifierEntity = entityManager.CreateEntity(spriteMeshModifierArchetype);
List<GameObject> waterslimesDisplay = objectManagerController.colorSlimesDisplayGlobal;
foreach (GameObject waterslimeDisplay in waterslimesDisplay) {
spriteMeshModifiers.Add(waterslimeDisplay.GetComponent<SpriteMeshModifier>());
}
base.OnStartRunning();
}
protected override void OnUpdate()
{
CallJobs();
//throw new System.NotImplementedException();
}
private void CallJobs() {
foreach (SpriteMeshModifier spriteMeshModifier in spriteMeshModifiers) {
if(spriteMeshModifier.vertices.Count > 0 && spriteMeshModifier._isRegenerated && spriteMeshModifier.objectManagerController.metaballEdgePointVertices.Count > 0) {
NativeArray<VertexCopy> vertexCopies = new NativeArray<VertexCopy>(spriteMeshModifier.vertices.Count, Allocator.TempJob);
NativeArray<Vector3> metaballVertexPositions = new NativeArray<Vector3>(spriteMeshModifier.objectManagerController.metaballEdgePointVertices.Count, Allocator.TempJob);
CopyThingsTo(ref vertexCopies, ref metaballVertexPositions, spriteMeshModifier);
// Do heavy computation in a job
ComputeJob job = new ComputeJob() {
vertexCopyArray = vertexCopies,
metaballVertexPositionArray = metaballVertexPositions
};
job.ScheduleParallel(vertexCopies.Length, 64, default).Complete();
AssignComputedValues(vertexCopies, spriteMeshModifier);
UpdatePositions(vertexCopies, spriteMeshModifier);
// Don't forget to dispose
vertexCopies.Dispose();
metaballVertexPositions.Dispose();
}
}
}
private void CopyThingsTo(ref NativeArray<VertexCopy> copiesArray, ref NativeArray<Vector3> metaballVertexPositionsArray, SpriteMeshModifier spriteMeshModifier) {
for (int i = 0; i < spriteMeshModifier.vertices.Count; i++) {
Vertex vertex = spriteMeshModifier.vertices[i];
copiesArray[i] = new VertexCopy(vertex.currentPos, i, vertex._objectManagerController.metaballEdgePointVertices.Count, vertex._parentSpriteMeshModifier.transform.parent.gameObject.transform.position, vertex._parentSpriteMeshModifier.gameObject.transform.rotation, vertex._parentSpriteMeshModifier.gameObject.transform.localScale, vertex._closestMagnetPoint);
}
for (int i = 0; i < spriteMeshModifier.objectManagerController.metaballEdgePointVertices.Count; i++) {
MetaballEdgePointVertex metaballEdgePointVertex = spriteMeshModifier.objectManagerController.metaballEdgePointVertices[i];
metaballVertexPositionsArray[i] = metaballEdgePointVertex.currentPos;
}
}
private void AssignComputedValues(in NativeArray<VertexCopy> copiesArray, SpriteMeshModifier spriteMeshModifier) {
for (int i = 0; i < spriteMeshModifier.vertices.Count; ++i) {
spriteMeshModifier.vertices[i]._closestMagnetPoint = copiesArray[i]._closestMagnetPoint;
}
}
private void UpdatePositions(in NativeArray<VertexCopy> copiesArray, SpriteMeshModifier spriteMeshModifier) {
for (int i = 0; i < spriteMeshModifier.vertices.Count; ++i) {
if(copiesArray[i].currentPos == copiesArray[i]._closestMagnetPoint) {
//Should I lerp between copiesArray[i]._closestMagnetPoint and copiesArray[i].initialPos?
spriteMeshModifier.vertices[i].UpdateCurrentVertex(copiesArray[i]._closestMagnetPoint, copiesArray[i]._index);
}
else {
spriteMeshModifier.ResetPositionOfSelectedVertex(copiesArray[i]._index);
spriteMeshModifier.UpdateMesh();
}
}
}
}