I have searched high and low, consulted several AI chatbots, yet I can’t seem to find a single suitable library, which is crazy to me considering how long SVGs have been around.
The closest I got was SvgPathProperties by zHaytam, but the documentation is inconsistent with the actual code. The methods used are either placed in different classes or missing entirely.
All I need is to pass in an SVG file, or a path string, and be able to generate Vector2 points along the paths.
In Unity I’m using the experimental/pre-release “VectorGraphics” package, and while I can use the import settings’ “generate physics shape”, that only generates the outermost path. I want to recursively go through every path and plot them individually.
I am on the brink of just writing out all the calculations for every vector command (M, m, L, l, C, c, etc.) myself, and asking here is honestly a last resort. Every single C# library I have found parses the SVG, it can do various calculations, return the path length, etc. But none actually let me plot points along the path.
The only ones I found that do exactly what I need, are written most often in Javascript or python.
This is a perfect example of what I need. Doesn’t really matter to me if the library will plot points a set distance from each other, or a set amount of points. I just need something to generate simplified versions of very complex SVG paths one by one.
If all else fails, .svg seems to be just based on XML and paths are stored there really trivially: image - Interpreting paths in SVG files - Stack Overflow
I’m an outsider in this field, but I thought it might be possible to use svg-net’s SvgPathBuilder to convert a string into an SvgPathSegmentList, assign it to SvgPath.PathData, obtain a System.Drawing.Drawing2D.GraphicsPath from SvgPath.Path, and then call GraphicsPath.Flatten.
svg-net and Drawing.Drawing2D are whole other beasts of their own it feels like. There’s so many dependencies, and just trying to import svg-net source into Unity throws up so many errors, some about C#9, and I try to remove what’s causing them then I get warnings seemingly from my own code saying things aren’t CLS-Complaint (which goes away if I don’t have svg-net at all.
I don’t think those particular System libraries play nice with Unity since there’s immediately a lot of overlapping names and Unity gets confused about what it should be doing with its own code when it compiles. Drawing.Drawing2D is usually used for Windows Forms, and other Windows specific GUI developing. And it definitely seems overkill having all that code just to pick out a needle of relevant svg path processing.
Of course, I’m making the suggestion based on the premise that the necessary parts of the code will need to be extracted and modified.
I suggested this simply because I thought it would be better than writing out all the calculations for every vector command yourself.
I tried Unity’s VectorGraphics, and it seems that the text of an SVG file can be loaded using SVGParser.ImportSVG, and the paths can be converted into an array of points with VectorUtils.TessellatePath.
using System.Collections.Generic;
using UnityEngine;
using Unity.VectorGraphics;
using System.IO;
public class SVGReader : MonoBehaviour
{
public string svgFileName = "donut-svgrepo-com_svg";
static Stroke defaultStroke = new();
Scene scene;
int debugColorIndex;
void Start()
{
scene = LoadSVGText(svgFileName);
}
Scene LoadSVGText(string fileName)
{
TextAsset svgFile = Resources.Load<TextAsset>(fileName);
if (svgFile == null)
{
Debug.LogError("SVG file not found: " + fileName);
return null;
}
return SVGParser.ImportSVG(new StringReader(svgFile.text)).Scene;
}
void OnDrawGizmos()
{
debugColorIndex = 0;
if (scene != null && scene.Root != null)
DrawContour(scene.Root);
}
void DrawContour(SceneNode sceneNode)
{
if (sceneNode.Shapes != null)
{
foreach (var shape in sceneNode.Shapes)
{
foreach (var contour in shape.Contours)
{
Gizmos.color = Color.HSVToRGB((debugColorIndex % 12) / 12f, 1f, 1f);
debugColorIndex++;
var tessOptions = new VectorUtils.TessellationOptions
{
StepDistance = 1.0f,
MaxCordDeviation = 0.5f,
MaxTanAngleDeviation = 0.1f,
SamplingStepSize = 0.01f
};
var pathProps = shape.PathProps;
if (pathProps.Stroke == null)
{
pathProps.Stroke = defaultStroke;
shape.PathProps = pathProps;
}
VectorUtils.TessellatePath(contour, pathProps, tessOptions, out var vertices, out var indices);
for (int i = 0; i < indices.Length - 1; i++)
{
var p0 = vertices[indices[i]];
var p1 = vertices[indices[i + 1]];
Gizmos.DrawLine(p0, p1);
}
}
}
}
if (sceneNode.Children != null)
foreach (var childNode in sceneNode.Children)
DrawContour(childNode);
}
}
1 Like
While this technically does the job, I failed to mention I need to be able to differentiate between the different paths, which I don’t think VectorGraphics lets you do. Currently the way I’m doing it is through the paths’ IDs/Labels, since I need to occasionally treat several specific paths as one with two shapes, but not initially (can’t just merge them in a vector graphics software) because I still need to know which coordinates belong to which shape (I know I REALLY should’ve mentioned this).
But thank you, I never knew the package had this capability. I just assumed it was locked away behind some inaccessible source code.
Can’t that be solved with NodeIDs?
const string svgIDTestText = @"
<svg width=""120"" height=""120"" viewBox=""0 0 120 120"" xmlns=""http://www.w3.org/2000/svg"">
<rect id=""smallRect"" x=""10"" y=""10"" width=""50"" height=""50"" />
<rect id=""bigRect"" x=""20"" y=""20"" width=""100"" height=""100"" />
</svg>";
void DumpNodeIDs()
{
SVGParser.SceneInfo sceneInfo = SVGParser.ImportSVG(new StringReader(svgIDTestText));
foreach ((string id, SceneNode node) in sceneInfo.NodeIDs)
{
Debug.Log($"NodeID: {id}, {node.GetHashCode()}");
}
}
// NodeID: smallRect, -1721818200
// NodeID: bigRect, -1003767024
I came to the realization that regardless of how I do it, I would still need to hold a separate text file, since the way Unity does things, you can’t just drag in the raw SVG file or reference it. Technically you could reference its file path, and do “File.ReadAllText” and pass in the file path, but in my case, this is just a lot of unnecessary extra steps to do what I already have working in my own weird way (and personally, if given the option, I will always choose not to rely on writing out file paths).
For now I’ll stick with the online tool, as I already have it working as needed, and it is downloadable so access in the future isn’t an issue. But thank you so so much everyone for helping out. <3
P.S. Your solution does work, it’s just a lot more extra steps in my specific case to make it process the paths as needed.
Yep, I have modified the SVG importer a fair bit to allow NodeID access, which you can also push to a vertex attribute on the generated mesh or sprite, which you can then access at runtime in code/shaders.
Additionally @Josiah_Ironclad you can change the generated objects of the SVG importer so you could build you own scriptable object that can then be referenced in engine, which contains these Vector2 points for each individual path.
You should be able to get the vecs from the geometry building in the vector graphics package, might need a little fiddling, but it would pretty much be what the package is already doing for strokes. Then just associate different NodeIDs with each generated geometry instance and pass that to your scriptable object.
Reading directly from the svg file is definitely an option but yeah as you say its a bit primitive and annoying, using the vector graphics package to generate exactly the output you want will give you much more serialized control in the inspector and will save on parsing the SVG data at runtime.
1 Like