An EXILED plugin for SCP: Secret Laboratory that renders 3D meshes from OBJ files in the game world using Unity primitive toys (Quads, Spheres, Cylinders, Cubes).
- Plugin:
TriangleScpSlv5.0.0 - Author: Foibos
- Framework: net48 / EXILED 9.13.3
- License: CC-BY-SA 3.0 (required by EXILED)
- Build the project or grab the latest release.
- Copy
TriangleScpSl.dllintoEXILED/Plugins/. - Place your
.obj(and optional.mtl) files inEXILED/Plugins/BlenderModels/. - In Remote Admin, run the command to display or export your model.
The plugin converts OBJ mesh faces into Unity primitives that can be spawned in SCP:SL.
Any convex polygon can be decomposed into parallelograms. A parallelogram can be rendered using Unity Quads by exploiting the SetParent deformation trick: a child primitive inherits the non-uniform scale of its invisible parent, producing a sheared shape.
The plugin applies several tricks to minimize the number of primitives needed:
| Optimization | What it does | Savings |
|---|---|---|
| N-gon processing | Works with original polygon faces instead of triangulating first | Use of N parallelograms for one N-gon |
| Coplanar face merging | Merges adjacent same-color faces on the same plane into larger polygons | Fewer, bigger faces to decompose |
| Rectangle detection | When a parallelogram has equal-length diagonals, uses 1 Quad instead of 2 | Up to 50% on box-like geometry |
| Primitive shape detection | Detects spheres, cylinders, cubes in the mesh and replaces them with 1 native primitive | Use of 1 primitive instead of parallelogram decomposition |
| Hidden tail optimization | Hides parallelogram tails inside solid material, avoiding extra primitives | ~10-30% on complex models |
| Stretch clustering (V2) | Groups similarly-oriented parallelograms under shared stretch primitives | Significant on dense meshes, but works as well on smaller ones |
V1 (Exact): Each parallelogram = 2 Quads (base + visible child). Pixel-perfect.
V2 (Approximate): Groups parallelograms by angular similarity under shared "stretch" primitives. Fewer total primitives at the cost of tiny vertex error (configurable accuracy parameter).
This command applies the N-gon pipeline with V2 stretch clustering. Adjust optimization parameters (hidden tails, primitive detection, planar threshold, etc.) via
NGonConfig.TriangulateNGonV2 <model.obj> [planar threshold] [accuracy]For export, use
ExportSchematicNGonV2with the same pipeline.
Every command can be run again while building to cancel, or run again after the model is visible to destroy it.
| Command | Pipeline | Description |
|---|---|---|
TriangulateNGonV2 <file> [planar] [accuracy] |
N-gon + V2 | Best result. N-gon decomposition with stretch clustering. |
TriangulateNGon <file> [planar] |
N-gon + V1 | N-gon decomposition with exact parallelograms. |
TriangulateV2 <file> [accuracy] |
Triangle + V2 | Triangle-based with stretch clustering. Simpler, higher primitive count. |
Triangulate <file> |
Triangle + V1 | Simplest pipeline. Triangle-based, exact parallelograms. |
| Command | Pipeline | Description |
|---|---|---|
ExportSchematicNGonV2 <file> <output> [planar] [accuracy] |
N-gon + V2 | Best export. Same pipeline as TriangulateNGonV2. |
ExportSchematicNGon <file> <output> [planar] |
N-gon + V1 | Export N-gon V1 to ProjectMER schematic JSON. |
ExportSchematicV2 <file> <output> [accuracy] |
Triangle + V2 | Export triangle V2 to ProjectMER schematic JSON. |
ExportSchematic <file> <output> |
Triangle + V1 | Export triangle V1 to ProjectMER schematic JSON. |
| Command | Description |
|---|---|
NGonConfig [property] [value] |
View or change session config for the N-gon pipeline (see below). |
TriangleExample |
Spawns a random triangle with colored vertex markers. |
TestParallelograms [count] |
Visualizes V2 stretch clustering with random parallelograms. |
TestNGons <file> [stage] |
Visualizes N-gon pipeline stages (0=raw, 1=merged, 2=convex, 3=parallelograms). |
The NGon pipeline is configured through NGonModelConfig, a session-scoped config that persists until the server restarts. Use the NGonConfig RA command to view and change values at runtime. All subsequent NGon commands (TriangulateNGon, TriangulateNGonV2, and their export variants) will use
the updated defaults.
NGonConfig -- list all current values
NGonConfig PlanarThreshold -- get a single value
NGonConfig PlanarThreshold 0.01 -- set a value for this session
| Property | Type | Default | Effect on Quality and Primitive Count |
|---|---|---|---|
UseHiddenTailOptimization |
bool | True |
Hides parallelogram tail triangles inside solid geometry. Saves ~10-30% primitives on complex models. |
DetectPrimitives |
bool | True |
Detects spheres, cylinders, cubes and replaces them with 1 native primitive each. |
Accuracy |
float | 0.001 |
Max vertex error (world units) for V2 stretch clustering. Lower = more precise but more primitives. Only affects V2 commands. |
SmoothMaxAngle |
float | 0.32 |
Max dihedral angle (radians, ~18 deg) between adjacent faces to consider a surface smooth. Controls how aggressively primitive shapes are detected. Higher = more shape detection. |
SmoothMinFraction |
float | 0.7 |
Minimum fraction of smooth edges required for a surface cluster to qualify for primitive detection. Lower = more lenient shape detection. |
UseEdgeWalkSampling |
bool | False |
Enables edge-walk sampling during hidden-tail verification. Catches pits/gaps between discrete sample points, so pits and holes in model will not beaccidentaly covered by HidingParallelogramTail. More accurate but MUCH slower. |
HiddenTailPullIn |
float | 0.03 |
Pull-in distance along normal for hidden-tail solid checks. Larger values produce lower primitives at the cost of model look getting "spiky". |
AllowNonPlanarNGons |
bool | False |
When enabled, non-planar n-gons are decomposed into parallelograms as-is instead of being fan-triangulated into planar triangles first. Reduces primitive count on models with many non-planar faces at the cost of geometric inaccuracy since the resulting parallelograms won't lie perfectly on the original surface. |
PlanarThreshold |
float | 0 |
Maximum vertex displacement when merging coplanar faces. 0 = exact only. Higher values (e.g. 0.0001 or 0.001) merge more aggressively, producing fewer larger polygons and fewer primitives, but with increasing this parameter too high, it can actually increase the amount of primitives, as it deforms the original model too much |
DeduplicateVertexThreshold |
float | 1E-04 |
Vertex deduplication distance. Vertices closer than this are merged. Rarely needs changing. |
DeduplicatePlaneDistThreshold |
float | 1E-04 |
Plane distance deduplication threshold for coplanar detection. Rarely needs changing. |
MaxMsPerFrame |
float | 8 |
Maximum milliseconds per frame before yielding in coroutine mode. Higher = faster loading but more lag. Lower = smoother but slower loading. |
To reduce primitive count (at the cost of accuracy):
- Enable
UseHiddenTailOptimizationandDetectPrimitivesif disabled - Increase
Accuracy(e.g.0.01) to allow more stretch reuse - Increase
HiddenTailPullIn(e.g.0.1) to get less primitives at the cost of more visible spikes on the model - Increase
PlanarThreshold(e.g.0.0001-0.001) to merge more coplanar faces (not that much of optimization) - Increase
SmoothMaxAngle(e.g.0.5) for more aggressive shape detection - Enable
AllowNonPlanarNGonsto keep large n-gons intact instead of splitting them (may cause heavy geometric inaccuracy)
To increase visual accuracy (at the cost of more primitives):
- Set
PlanarThresholdto0 - Enable
UseEdgeWalkSamplingfor hidden tail optimization to catch small pits and holes in the model (warning: very slow) - Decrease
Accuracy(e.g.0.0001- should be enough for any model) - Decrease
SmoothMaxAngleto avoid replacing low-poly geometry with smooth shapes
To speed up loading (at the cost of gameplay smoothness):
- Increase
MaxMsPerFrame(e.g.16or32)
Each subfolder with significant logic has its own README.md with detailed algorithm descriptions.
TriangleScpSl/
Plugin.cs, Config.cs - EXILED plugin entry point
Commands/ - Remote Admin commands
Core/
FileToTriangles/ - Simple OBJ parser (triangle-based, legacy)
ModelFactory/ - Factory for loading OBJ and creating models
Models/
ModelBase.cs - Abstract base with shared fields and methods
ApproximateModel/ - V2 stretch-clustering renderer
ExactModel/ - V1 exact parallelogram renderer
NGons/ - N-gon decomposition pipeline
Detectors/ - Primitive shape detection
Triangulation/
Triangle/ - Triangle data and decomposition into parallelograms
Parallelogram/ - Parallelogram data and the 2-Quad shearing primitive
ProjectMerExport/ - ProjectMER schematic JSON export
Paths/ - File path utilities
Runtime/ - Coroutine host
For programmatic use, the key entry points are:
// Load an OBJ with the full N-gon pipeline (recommended, coroutine)
NGonModelConfig config = NGonModelConfig.CreateFromSession();
yield return NGonModelBuilder.LoadCoroutine("model.obj", Color.white, result =>
{
// result.Parallelograms, result.DetectedPrimitives, result.NormalizedFileName
}, config);
// Or synchronous (blocks the main thread)
NGonModelResult result = NGonModelBuilder.Load("model.obj", Color.white, config);
// Create models
var exact = ExactModel.Create(result.Parallelograms, result.DetectedPrimitives, position);
var approx = ApproximateModel.Create(result.Parallelograms, result.DetectedPrimitives, position);
// Or deferred (build in coroutine to avoid lag)
var model = ApproximateModel.CreateDeferred(result.Parallelograms, result.DetectedPrimitives, position);
yield return model.BuildTrianglesCoroutine(PrimitiveFlags.Visible, batchSize: 64);
// Control
model.Position = newPos;
model.Rotation = newRot;
model.Scale = Vector3.one * 2f;
model.Color = Color.red;
model.Destroy();See Core/Models/ApproximateModel/README.md and Core/Models/ExactModel/README.md for full API details.
- EXILED — Creative Commons Attribution-ShareAlike 3.0 Unported
- Mirror Networking — MIT
- Unity Engine — Unity Companion License