Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/crossed-cloak/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Built zips are attached to GitHub Releases, not committed.
dist/
92 changes: 92 additions & 0 deletions examples/crossed-cloak/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Crossed Cloak — FSMP Pattern Example

**Version 0.1.0** · an FSMP (Faster HDT-SMP) **pattern** demonstration mod.

This is a worked example for the [FSMP modder guide](https://github.com/DaymareOn/FSMP-Validator/wiki): it rebuilds the
physics of one Cloaks of Skyrim cloak as a **crossed 2-D tissue** using the FSMP *pattern*
macro feature, instead of hand-writing every bone and constraint. It is meant to be read
alongside the two source files, which are heavily commented.

> ⚠️ **Requires an FSMP build with pattern support, which is not yet released.** Until then
> this mod only works with the test DLL handed to testers. It is published here as a
> versioned reference, not yet as a public Nexus release.

---

## What it demonstrates

The required Cloaks of Skyrim cloak NIF is a 3×6 grid of `CB` cloth bones (`CB 01 1` …
`CB 03 6`) wired as three independent vertical chains. This example keeps the **same real NIF
bones** but reorganises the physics:

- **`patterns/DaydreamingDay.xml`** — a shared pattern library. It defines one reusable
pattern, `DaydreamingDay.cloak`, that emits the whole cloth structure:
- the CB grid (parameterised by `cols`/`rows`),
- **warp** constraints chaining each column top-to-bottom (locked near the neck, lightly
limited lower down),
- **weft** constraints tying adjacent columns at the same row — a deliberately loose,
non-Hookean spring so the cloak drapes as a sheet without going rigid.
- The weft tuning is exposed as parameters (`weftStiffness`, `weftLinearLimit`, …) with
sensible defaults.
- **`cloak.xml`** — the consumer. It declares only what is specific to *this* NIF (the
collision shapes, the body skeleton bones those shapes skin to) and invokes the pattern
with one line, overriding two weft knobs:

```xml
<pattern name="DaydreamingDay.cloak" weftStiffness="6" weftLinearLimit="1.5"/>
```

At load time FSMP expands that single tag into 18 bones and 25 constraints. Dropping
`DaydreamingDay.xml` into the global `patterns/` folder makes the pattern available to every
physics file, so other cloaks can reuse the same structure with their own tuning.

The expanded document passes the FSMP `smp report` validator with **0 errors, 0 warnings**.

---

## Requirements

Install **all** of these first, in this order. This mod ships **only XML config** — no
meshes or textures — so the cloak itself comes entirely from the mods below.

1. **[SKSE64](http://skse.silverlock.org/)**
2. **Faster HDT-SMP (FSMP) with pattern support** — the test build (pattern support is
unreleased as of this version). A stock HDT-SMP/FSMP without pattern support will fail to
load this file.
3. **[Cloaks of Skyrim](https://www.nexusmods.com/skyrimspecialedition/mods/6369)** (Nikinoodles
& Nazenn) — the base cloak items, meshes and textures.
4. **[Artesian Cloaks of Skyrim](https://www.nexusmods.com/skyrimspecialedition/mods/17416)** —
the SMP-enabled cloak NIFs (`Meshes\Clothes\cloaksofskyrim\`) whose `CB` bones this config
binds to.

## Installation

Install with a mod manager (MO2/Vortex) **after** the cloak mods above, and let it
**overwrite**. This mod replaces the SMP cloak patch's `cloak.xml` with the pattern-based
version and adds the shared `patterns/DaydreamingDay.xml`:

```
SKSE/Plugins/hdtSkinnedMeshConfigs/cloak.xml (replaces the patch's)
SKSE/Plugins/hdtSkinnedMeshConfigs/patterns/DaydreamingDay.xml (new, shared library)
```

It only changes the **cloak** physics. The patch's `cape.xml` is left untouched.

---

## Credits

- **Cloaks of Skyrim** — Nikinoodles & Nazenn. Base cloak meshes, textures and items.
- **Artesian Cloaks of Skyrim** — Zeridian and Rosent. The SMP-enabled cloak NIFs and the
original `cloak.xml` this example is derived from.
- **HDT-SMP / Faster HDT-SMP** — HydrogensaysHDT and the FSMP maintainers. The physics engine
and the pattern feature.
- **DaydreamingDay** — the pattern, this reimplementation, and the example.

## Permissions

This mod contains only original XML configuration authored by DaydreamingDay, derived from
Artesian Cloaks of Skyrim's `cloak.xml`. It **redistributes no art assets** (no meshes, no
textures) — those remain with their original authors and are obtained by installing the
required mods above. Artesian Cloaks of Skyrim's permissions explicitly forbid redistributing
its art assets, which this example respects by depending on it rather than bundling it.
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="https://raw.githubusercontent.com/DaymareOn/FSMP-Validator/da42538f0442583349b3774b8c0fd9a6551bf78a/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.sch" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
<system xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="FSMP-Validator" xsi:schemaLocation="FSMP-Validator https://raw.githubusercontent.com/DaymareOn/FSMP-Validator/da42538f0442583349b3774b8c0fd9a6551bf78a/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.xsd">

<!--
Cloaks of Skyrim cloak as a CROSSED 2-D tissue (pattern example). The whole CB grid (3 columns x 6 rows),
its bone/constraint templates, and the warp+weft constraints all come from the shared
"DaydreamingDay.cloak" pattern (patterns/DaydreamingDay.xml) via the single <pattern> use below. The
pattern emits the cloth-simulation structure - the CB bones (which match this NIF's "CB 0c r" nodes)
and how they interlink; it is reusable across any cloak NIF using that naming and a cols x rows grid.
This file adds what is unique to THIS garment: the collision shapes, the body skeleton bones those
shapes skin to, and the tuning overrides. Same real CB bones as the required SMP cloak NIF, so it binds
in-game; needs FSMP with pattern support.

The weft (horizontal) spring is the part you tune. It is a pattern parameter, so retune by
overriding on the use, e.g. <pattern name="DaydreamingDay.cloak" weftStiffness="6"/> - no need to
touch the library. See the override on the pattern use below.
-->

<per-triangle-shape name="VirtualGround">
<margin>5</margin>
<tag>ground</tag>
</per-triangle-shape>

<!--
Body skeleton bones. These are NOT part of the cloak simulation: they carry no template, so
they default to mass 0 (kinematic, animation-driven, never simulated), and they appear in no
constraint. They are here because the body/feet collision meshes below (BaseShapeB -> body2,
FeetB -> Feet) are skinned to them in the NIF. A per-triangle-shape's geometry follows the bones
its vertices are weighted to, so each such bone must be declared as a <bone> or that part of the
collision shape has nothing to move with. They give the cloak a body to drape over and collide
against; remove one and the matching slice of body collision goes static.
-->
<bone name="NPC R ForearmA [RLar]"/>
<bone name="NPC L ForearmA [LLar]"/>
<bone name="CME R Forearm [RLar]"/>
<bone name="CME L Forearm [LLar]"/>
<bone name="SkirtBBone01"/>
<bone name="SkirtBBone02"/>
<bone name="SkirtBBone03"/>
<bone name="NPC L RearCalf [LrClf]"/>
<bone name="NPC R RearCalf [RrClf]"/>
<bone name="NPC R Breast"/>
<bone name="NPC R Breast01"/>
<bone name="NPC L Breast"/>
<bone name="NPC L Breast01"/>
<bone name="NPC L Calf [LClf]"/>
<bone name="NPC R Calf [RClf]"/>
<bone name="NPC L Clavicle [LClv]"/>
<bone name="NPC R Clavicle [RClv]"/>
<bone name="NPC R Pauldron"/>
<bone name="NPC L Pauldron"/>
<bone name="NPC Head [Head]"/>
<bone name="NPC Pelvis [Pelv]"/>
<bone name="NPC Spine [Spn0]"/>
<bone name="NPC Spine1 [Spn1]"/>
<bone name="NPC Spine2 [Spn2]"/>

<!--
The whole crossed cloak, from the shared library: cols=3, rows=6, plus its bone templates
(first, mass) and constraint templates (capeLoose, capeLimited, capeWeft). The default weft is
a soft, non-Hookean spring; here we override two of its knobs to make this cloak's lateral
cohesion a touch stiffer and roomier than the library default. Drop the attributes to fall back
to the defaults (weftStiffness=4, weftLinearLimit=1, ...), or add more (weftDamping,
weftAngularLimit, weftNonHookean) to retune further - all without editing patterns/DaydreamingDay.xml.
-->
<pattern name="DaydreamingDay.cloak" weftStiffness="6" weftLinearLimit="1.5"/>

<!--
Collision shapes stay HERE, in the consumer, not in the pattern - and deliberately so. A
per-triangle-shape binds to a named mesh in THIS NIF (BaseShapeB, FeetB, Cloak, and VirtualGround
above): the name must match a geometry the NIF actually contains, and its tag / no-collide-with-tag
wire up this garment's collision groups. The pattern already names NIF nodes too (the CB cloth
bones), but those follow a shared "CB 0c r" convention many cloaks reuse, so a grid pattern stays
portable. Collision mesh names do not - they are unique to each garment's NIF, so a library meant
for many cloaks cannot know them. That is why the cloth structure lives in the pattern while the
shapes (and the body bones they skin to) live here in the consumer.
-->
<per-triangle-shape name="BaseShapeB">
<margin>0</margin>
<prenetration>0</prenetration>
<tag>body2</tag>
<no-collide-with-tag>body</no-collide-with-tag>
</per-triangle-shape>

<per-triangle-shape name="FeetB">
<margin>0</margin>
<prenetration>0</prenetration>
<tag>Feet</tag>
<no-collide-with-tag>body</no-collide-with-tag>
</per-triangle-shape>

<per-triangle-shape name="Cloak">
<prenetration>0</prenetration>
<tag>cloth</tag>
</per-triangle-shape>

</system>
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="https://raw.githubusercontent.com/DaymareOn/FSMP-Validator/da42538f0442583349b3774b8c0fd9a6551bf78a/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.sch" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
<!--
Shared FSMP pattern library. Dropping this file in
Data/SKSE/Plugins/hdtSkinnedMeshConfigs/patterns/ makes the "DaydreamingDay.cloak" pattern available
to EVERY physics XML, so any file can build the crossed cloak with a single
<pattern name="DaydreamingDay.cloak"/>.

The pattern is self-contained: it emits its own bone templates (first, mass) and constraint templates
(capeLoose, capeLimited, capeWeft), then the CB grid that uses them. A consumer needs nothing but the
one-line use. The horizontal "weft" spring is the part authors actually tune, so its values are
exposed as parameters with sensible defaults; override any of them on the use, e.g.
<pattern name="DaydreamingDay.cloak" weftStiffness="6" weftLinearLimit="1.5"/>
-->
<patterns xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="FSMP-Validator" xsi:schemaLocation="FSMP-Validator https://raw.githubusercontent.com/DaymareOn/FSMP-Validator/da42538f0442583349b3774b8c0fd9a6551bf78a/skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.xsd">

<!--
The cloak as a 2-D "tissue": a CB grid (cols x rows) linked both ways. Vertical (warp) chains
each column top-to-bottom; horizontal (weft) ties adjacent columns at the same row. No diagonals.
The weft uses a deliberately loose, non-Hookean template (capeWeft) so the columns stay associated
without making a rigid sheet. Structure and tuning both live here now; only the weft is parameterised.
-->
<pattern-default name="cloak" author="DaydreamingDay">
<param name="cols" default="3"/>
<param name="rows" default="6"/>
<!-- Horizontal (weft) tuning knobs. Defaults reproduce the original starting values; override per consumer. -->
<param name="weftStiffness" default="4"/>
<param name="weftDamping" default="0.2"/>
<param name="weftLinearLimit" default="1"/>
<param name="weftAngularLimit" default="0.5"/>
<param name="weftNonHookean" default="0.8"/>
<body>
<!-- Bone templates. first = static anchor (row 1); mass = simulated cloth body (rows 2..rows). -->
<bone-default name="first">
<restitution>1</restitution>
</bone-default>
<bone-default name="mass">
<mass>0.02</mass>
<inertia x="1" y="1" z="1"/>
<linearDamping>0.8</linearDamping>
<angularDamping>0.8</angularDamping>
<friction>0.9</friction>
<restitution>1</restitution>
</bone-default>

<!-- Vertical (warp) templates. capeLoose = upper links, joint fully locked (linear + angular
limits 0/0); capeLimited = lower links, locked linearly with a small angular limit.
Default-valued spring/damping/equilibrium tags are omitted (the engine defaults them to 0).
<frameInA/> is intentionally empty but NOT removable: the engine's default frame type is
frameInB (pivot at body B). The empty tag selects frameInA (pivot at body A, identity
transform) - its basis/origin children would just be identity, so they are dropped. -->
<generic-constraint-default name="capeLoose">
<frameInA/>
<linearLowerLimit x="0" y="0" z="0"/>
<linearUpperLimit x="0" y="0" z="0"/>
<angularLowerLimit x="0" y="0" z="0"/>
<angularUpperLimit x="0" y="0" z="0"/>
</generic-constraint-default>

<generic-constraint-default name="capeLimited">
<frameInA/>
<linearLowerLimit x="0" y="0" z="0"/>
<linearUpperLimit x="0" y="0" z="0"/>
<angularLowerLimit x="-0.1" y="-0.2" z="-0.1"/>
<angularUpperLimit x="0.1" y="0.2" z="0.1"/>
</generic-constraint-default>

<!--
Horizontal (weft) template: lateral cohesion that must NOT make a rigid sheet.
- small linear range (+/-weftLinearLimit) lets adjacent columns separate a little;
- soft linear spring (weftStiffness) gently pulls them back;
- weftNonHookean softens that spring the further it stretches
(stiffness * (1 - distanceFactor * weftNonHookean)), so big draping stays loose;
- wide angular limits (+/-weftAngularLimit) keep rotation free.
All five are parameters; override on the pattern use to retune.
-->
<generic-constraint-default name="capeWeft">
<frameInA/>
<linearLowerLimit x="-${weftLinearLimit}" y="-${weftLinearLimit}" z="-${weftLinearLimit}"/>
<linearUpperLimit x="${weftLinearLimit}" y="${weftLinearLimit}" z="${weftLinearLimit}"/>
<angularLowerLimit x="-${weftAngularLimit}" y="-${weftAngularLimit}" z="-${weftAngularLimit}"/>
<angularUpperLimit x="${weftAngularLimit}" y="${weftAngularLimit}" z="${weftAngularLimit}"/>
<linearStiffness x="${weftStiffness}" y="${weftStiffness}" z="${weftStiffness}"/>
<linearDamping x="${weftDamping}" y="${weftDamping}" z="${weftDamping}"/>
<linearNonHookeanStiffness x="${weftNonHookean}" y="${weftNonHookean}" z="${weftNonHookean}"/>
<linearNonHookeanDamping x="${weftNonHookean}" y="${weftNonHookean}" z="${weftNonHookean}"/>
</generic-constraint-default>

<!-- bones: each column = anchor (row 1) + mass rows 2..rows -->
<repeat var="c" count="${cols}" from="1">
<bone name="CB 0${c} 1" template="first"/>
<repeat var="r" count="${rows-1}" from="2">
<bone name="CB 0${c} ${r}" template="mass"/>
</repeat>
</repeat>
<!-- vertical (warp): upper links loose, lower links limited, like the original cloak -->
<repeat var="c" count="${cols}" from="1">
<constraint-group>
<generic-constraint bodyA="CB 0${c} 2" bodyB="CB 0${c} 1" template="capeLoose"/>
<generic-constraint bodyA="CB 0${c} 3" bodyB="CB 0${c} 2" template="capeLoose"/>
</constraint-group>
<constraint-group>
<repeat var="r" count="${rows-3}" from="4">
<generic-constraint bodyA="CB 0${c} ${r}" bodyB="CB 0${c} ${r-1}" template="capeLimited"/>
</repeat>
</constraint-group>
</repeat>
<!-- horizontal (weft): adjacent columns at the same row; loose + non-Hookean -->
<repeat var="c" count="${cols-1}" from="1">
<constraint-group>
<repeat var="r" count="${rows-1}" from="2">
<generic-constraint bodyA="CB 0${c} ${r}" bodyB="CB 0${c+1} ${r}" template="capeWeft"/>
</repeat>
</constraint-group>
</repeat>
</body>
</pattern-default>
</patterns>
29 changes: 29 additions & 0 deletions examples/crossed-cloak/package.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Builds the installable, versioned zip for the "Crossed Cloak - FSMP Pattern Example" mod.
#
# The zip root mirrors a Skyrim Data install (SKSE/Plugins/...), so a mod manager can install
# it directly, plus the README at the root. Output goes to dist/ (git-ignored); attach the
# resulting zip to a GitHub Release as the versioned asset, then upload that same file to Nexus.
#
# Usage: pwsh ./package.ps1 [-Version 0.1.0]
param([string]$Version = "0.1.0")

$ErrorActionPreference = "Stop"
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$dist = Join-Path $here "dist"
New-Item -ItemType Directory -Force -Path $dist | Out-Null

$zip = Join-Path $dist ("FSMP-Crossed-Cloak-Pattern-Example-{0}.zip" -f $Version)
if (Test-Path $zip) { Remove-Item $zip -Force }

# Contents: the installable data tree + the readme. Nothing else (no art assets).
$items = @(
(Join-Path $here "SKSE"),
(Join-Path $here "README.md")
)
Compress-Archive -Path $items -DestinationPath $zip -CompressionLevel Optimal

$size = [math]::Round((Get-Item $zip).Length / 1KB, 1)
Write-Output ("Built {0} ({1} KB)" -f $zip, $size)
Write-Output "Contents:"
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::OpenRead($zip).Entries | ForEach-Object { Write-Output (" " + $_.FullName) }
31 changes: 31 additions & 0 deletions skse/plugins/hdtSkinnedMeshConfigs/hdtSMP64.sch
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,37 @@

</sch:pattern>

<sch:pattern id="pattern-macros">
<sch:title>Pattern macro authoring rules</sch:title>

<!-- FSMP expands <pattern>/<repeat>/<pattern-default> away before its own validation, so these
rules fire in an authoring editor (which sees the raw file) rather than in smp report. They
are written without XPath-2.0 regex so they never error in the runtime validator. -->

<!-- <repeat count> must be a non-negative integer or a ${...} placeholder -->
<sch:rule context="f:repeat[not(starts-with(@count,'$')) and not(number(@count) &gt;= 0 and floor(number(@count)) = number(@count))]">
<sch:assert test="false()" role="error">repeat count must be a non-negative integer or a ${...} placeholder.</sch:assert>
</sch:rule>

<!-- <repeat from> (optional) must be an integer or a ${...} placeholder -->
<sch:rule context="f:repeat[@from and not(starts-with(@from,'$')) and not(floor(number(@from)) = number(@from))]">
<sch:assert test="false()" role="error">repeat from must be an integer or a ${...} placeholder.</sch:assert>
</sch:rule>

<!-- a literal count of 0 emits nothing -->
<sch:rule context="f:repeat[@count = '0']">
<sch:assert test="false()" role="warning">repeat count is 0; this repeat emits nothing.</sch:assert>
</sch:rule>

<!-- param and pattern-default names must not contain spaces, so ${name} / name="..." can resolve -->
<sch:rule context="f:param[contains(@name,' ')]">
<sch:assert test="false()" role="error">param name must not contain spaces.</sch:assert>
</sch:rule>
<sch:rule context="f:pattern-default[contains(@name,' ')]">
<sch:assert test="false()" role="error">pattern-default name must not contain spaces.</sch:assert>
</sch:rule>
</sch:pattern>

<sch:pattern id="static-rigid-body">
<sch:title>Static rigid body constraints (mass=0)</sch:title>

Expand Down
Loading