Generates mesh from SVG path in realtime for Unity.
This is a port of https://github.com/mattdesl/svg-mesh-3d
Copy Assets/SVGMeshUnity
directory to your Assets directory.
Add a SVGMesh
component to a GameObject that has MeshFilter
and MeshRenderer
.
var mesh = GetComponent<SVGMesh>();
var svg = new SVGData();
svg.Path("M17.316,6.246c0.008,0.162,0.011,... and so on");
mesh.Fill(svg);
Simply create an instance of SVGData
and set SVG path data string by calling SVGData.Path()
.
Then call Mesh.Fill()
, a mesh will be generated.
Instead of use SVG path data, you can directly make path data in your code.
void Update()
{
SVG.Clear();
var resolution = 5;
var radius = 3f;
SVG.Move(NoisedR(radius, 0), 0f);
for (var i = 0; i < resolution; ++i)
{
var i0 = i;
var i1 = (i + 1) % resolution;
var angle0 = Mathf.PI * 2f * ((float) i0 / resolution);
var angle1 = Mathf.PI * 2f * ((float) i1 / resolution);
var r0 = NoisedR(radius, i0);
var r1 = NoisedR(radius, i1);
var x0 = Mathf.Cos(angle0) * r0;
var y0 = Mathf.Sin(angle0) * r0;
var x1 = Mathf.Cos(angle1) * r1;
var y1 = Mathf.Sin(angle1) * r1;
var cx = x0 + (x1 - x0) * 0.5f;
var cy = y0 + (y1 - y0) * 0.5f;
var ca = Mathf.Atan2(cy, cx);
var cr = 0.3f + (Mathf.PerlinNoise(Time.time, i * -100f) - 0.5f) * 1.15f;
cx += Mathf.Cos(ca) * cr;
cy += Mathf.Sin(ca) * cr;
SVG.Curve(cx, cy, cx, cy, x1, y1);
}
Mesh.Fill(SVG);
}
private float NoisedR(float r, float randomize)
{
return r + (Mathf.PerlinNoise(Time.time, randomize * 10f) - 0.5f) * 0.5f;
}
public class Move : MonoBehaviour
{
[SerializeField] private float R = 0.5f;
[SerializeField] private float IntervalRate = 1f;
[SerializeField] private SVGMesh HeadMesh;
[SerializeField] private SVGMesh TailMesh;
[SerializeField] private SVGMesh BodyMesh;
private SVGData HeadSVG;
private SVGData TailSVG;
private SVGData BodySVG;
private Vector2 Head;
private Vector2 Tail;
private Vector2 To;
private float HeadR;
private float TailR;
private float Interval;
private float FollowTime;
void Start()
{
HeadSVG = new SVGData();
TailSVG = new SVGData();
BodySVG = new SVGData();
Head = RandomField();
Tail = Head;
}
void Update()
{
HeadSVG.Clear();
TailSVG.Clear();
BodySVG.Clear();
Update(Time.deltaTime);
var v = Mathf.Max(0.7f, 1.3f - Mathf.Clamp01((Head - Tail).magnitude / 3f));
Circle(HeadSVG, Head, HeadR);
Circle(TailSVG, Tail, TailR);
Metaball(BodySVG, Head, HeadR, Tail, TailR, v);
HeadMesh.Fill(HeadSVG);
TailMesh.Fill(TailSVG);
BodyMesh.Fill(BodySVG);
}
private void Update(float dt)
{
Interval -= dt;
FollowTime -= dt;
if (Interval <= 0f)
{
To = RandomField();
To = Head + Vector2.ClampMagnitude(To - Head, 2f);
HeadR = R * 0.15f;
TailR = R;
Interval = Random.Range(1.5f, 2.6f) * IntervalRate;
FollowTime = Random.Range(0.15f, 0.25f);
}
Head = Vector2.Lerp(To, Head, Mathf.Exp(-5f * dt));
if (FollowTime <= 0f)
{
Tail = Vector2.Lerp(Head, Tail, Mathf.Exp(-5f * dt));
if (FollowTime <= -0.4f)
{
TailR = Mathf.Lerp(R, TailR, Mathf.Exp(-4f * dt));
}
else
{
TailR = Mathf.Lerp(R * 0.05f, TailR, Mathf.Exp(-6f * dt));
}
HeadR = Mathf.Lerp(R, HeadR, Mathf.Exp(-3f * dt));
}
}
private Vector2 RandomField()
{
return new Vector2(Random.Range(-4f, 4f), Random.Range(-4f, 4f));
}
private void Circle(SVGData svg, Vector2 c, float r)
{
for (var i = 0; i < 4; ++i)
{
var angle0 = Mathf.PI * 0.5f * (i + 0);
var angle1 = Mathf.PI * 0.5f * (i + 1);
var x0 = c.x + Mathf.Cos(angle0) * r;
var y0 = c.y - Mathf.Sin(angle0) * r;
var x1 = c.x + Mathf.Cos(angle1) * r;
var y1 = c.y - Mathf.Sin(angle1) * r;
var a = r * (4f / 3f) * Mathf.Tan((angle1 - angle0) / 4f);
var inAngle = angle0 + Mathf.PI * 0.5f;
var inX = x0 + Mathf.Cos(inAngle) * a;
var inY = y0 - Mathf.Sin(inAngle) * a;
var outAngle = angle1 - Mathf.PI * 0.5f;
var outX = x1 + Mathf.Cos(outAngle) * a;
var outY = y1 - Mathf.Sin(outAngle) * a;
if (i == 0)
{
svg.Move(x0, y0);
}
svg.Curve(inX, inY, outX, outY, x1, y1);
}
}
// http://shspage.com/aijs/
private void Metaball(SVGData svg, Vector2 c1, float r1, Vector2 c2, float r2, float v)
{
if (r1 == 0f || r2 == 0f)
{
return;
}
var pi2 = Mathf.PI / 2f;
var d = (c2 - c1).magnitude;
var u1 = 0f;
var u2 = 0f;
if (d <= Mathf.Abs(r1 - r2))
{
return;
}
else if (d < r1 + r2)
{
// case circles are overlapping
u1 = Mathf.Acos((r1 * r1 + d * d - r2 * r2) / (2 * r1 * d));
u2 = Mathf.Acos((r2 * r2 + d * d - r1 * r1) / (2 * r2 * d));
}
var t1 = Mathf.Atan2(c2.y - c1.y, c2.x - c1.x);
var t2 = Mathf.Acos((r1 - r2) / d);
var t1a = t1 + u1 + (t2 - u1) * v;
var t1b = t1 - u1 - (t2 - u1) * v;
var t2a = t1 + Mathf.PI - u2 - (Mathf.PI - u2 - t2) * v;
var t2b = t1 - Mathf.PI + u2 + (Mathf.PI - u2 - t2) * v;
var p1a = PointOnCircle(c1, t1a, r1);
var p1b = PointOnCircle(c1, t1b, r1);
var p2a = PointOnCircle(c2, t2a, r2);
var p2b = PointOnCircle(c2, t2b, r2);
// define handle length by the distance between both ends of the curve to draw
var handle_len_rate = 2;
var d2 = Mathf.Min(v * handle_len_rate, (p2a - p1a).magnitude / (r1 + r2));
d2 *= Mathf.Min(1, d * 2 / (r1 + r2)); // case circles are overlapping
r1 *= d2;
r2 *= d2;
svg.Move(p1a);
svg.Curve(PointOnCircle(p1a, t1a - pi2, r1), PointOnCircle(p2a, t2a + pi2, r2), p2a);
svg.Line(p2b);
svg.Curve(PointOnCircle(p2b, t2b - pi2, r2), PointOnCircle(p1b, t1b + pi2, r1), p1b);
svg.Line(p1a);
}
private Vector2 PointOnCircle(Vector2 c, float angle, float r)
{
return c + new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * r;
}
}
Use SVGData.Move
, SVGData.Line
, SVGData.Curve
, ... and so on. Create realtime path as you like.
The following options are provided in SVGMesh component.
Delaunay
(defaultfalse
)- whether to use Delaunay triangulation
- Delaunay triangulation is slower, but looks better
Scale
(default1
)- a positive number, the scale at which to approximate the curves from the SVG paths
- higher number leads to smoother corners, but slower triangulation
Interior
if set, only return interior faces. See note. (Defaulttrue
)Exterior
if set, only return exterior faces. See note. (Defaultfalse
)Infinity
if set, then the triangulation is augmented with a point at infinity represented by the index-1
. (Defaultfalse
)
MIT