Initial commit

This commit is contained in:
Josh4359 2023-09-16 18:53:33 -07:00
commit 23aa1fb180
35 changed files with 2160 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

72
.gitignore vendored Normal file
View File

@ -0,0 +1,72 @@
# This .gitignore file should be placed at the root of your Unity project directory
#
# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore
#
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/[Ll]ogs/
/[Uu]ser[Ss]ettings/
# MemoryCaptures can get excessive in size.
# They also could contain extremely sensitive data
/[Mm]emoryCaptures/
# Recordings can get excessive in size
/[Rr]ecordings/
# Uncomment this line if you wish to ignore the asset store tools plugin
# /[Aa]ssets/AssetStoreTools*
# Autogenerated Jetbrains Rider plugin
/[Aa]ssets/Plugins/Editor/JetBrains*
# Visual Studio cache directory
.vs/
# Gradle cache directory
.gradle/
# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.mdb
*.opendb
*.VC.db
# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
*.mdb.meta
# Unity3D generated file on crash reports
sysinfo.txt
# Builds
*.apk
*.aab
*.unitypackage
*.app
# Crashlytics generated file
crashlytics-build.properties
# Packed Addressables
/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
# Temporary auto-generated Android Assets
/[Aa]ssets/[Ss]treamingAssets/aa.meta
/[Aa]ssets/[Ss]treamingAssets/aa/*

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Josh4359
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7
LICENSE.meta Normal file
View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: def6304472883004fa1dc20959d55415
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# Spline-Importer
Import and export splines between Blender and Unity

7
README.md.meta Normal file
View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8e3449f6187d6b64dbc1ec8bdf5151d1
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

8
Runtime.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5d99f03d3767d06409c6524aa84af941
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

190
Runtime/SplineImporter.cs Normal file
View File

@ -0,0 +1,190 @@
#if UNITY_EDITOR
using System;
using UnityEditor;
using UnityEngine;
using UnityEngine.Splines;
using static SplineImporter;
using static SplineData;
using System.IO;
using System.Text;
using Unity.Mathematics;
using System.Collections.Generic;
[CustomEditor(typeof(SplineImporter))]
public class SplineImporterEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
if (GUILayout.Button("Import Spline"))
{
SplineImporter splineImporter = target as SplineImporter;
splineImporter.name = splineImporter.splineData.name;
SplineData splineData = JsonUtility.FromJson<SplineData>(splineImporter.splineData.text);
SplineContainer splineContainer = splineImporter.GetComponent<SplineContainer>();
foreach (UnityEngine.Splines.Spline thisSpline in splineContainer.Splines)
splineContainer.RemoveSpline(thisSpline);
foreach (SplineData.Spline thisDataSpline in splineData.splines)
{
UnityEngine.Splines.Spline thisSpline = splineContainer.AddSpline();
thisSpline.Closed = thisDataSpline.closed;
foreach (ControlPoint thisControlPoint in thisDataSpline.controlPoints)
{
Vector3 position = PositionToVector(thisControlPoint.position);
Vector3 handleL = PositionToVector(thisControlPoint.handleL);
Vector3 handleR = PositionToVector(thisControlPoint.handleR);
Quaternion rotation = Quaternion.LookRotation(handleR - position, Vector3.up) * Quaternion.AngleAxis(-thisControlPoint.tilt, Vector3.forward);
float3x3 rotationMatrix = new float3x3(rotation);
thisSpline.Add(new()
{
Position = position * splineImporter.scale,
Rotation = rotation,
TangentIn = ((Vector3)math.mul(handleL - position, rotationMatrix)) * splineImporter.scale,
TangentOut = ((Vector3)math.mul(handleR - position, rotationMatrix)) * splineImporter.scale
},
TangentMode.Broken);
}
}
}
if (GUILayout.Button("Export Spline"))
{
SplineImporter splineImporter = target as SplineImporter;
if (!splineImporter.splineData)
{
string path = "Assets" + EditorUtility.SaveFilePanel("Save .JSON", "", "New Spline.json", "json").Substring(Application.dataPath.Length);
if (path.Length > 0)
{
File.WriteAllBytes(path, Encoding.ASCII.GetBytes(""));
AssetDatabase.Refresh();
TextAsset textAsset = AssetDatabase.LoadAssetAtPath(path, typeof(TextAsset)) as TextAsset;
splineImporter.splineData = textAsset;
}
else return;
}
SplineContainer splineContainer = splineImporter.GetComponent<SplineContainer>();
SplineData splineData = new();
splineData.splines = new SplineData.Spline[splineContainer.Splines.Count];
List<SplineData.Spline> dataSplines = new();
foreach (UnityEngine.Splines.Spline thisSpline in splineContainer.Splines)
{
List<ControlPoint> controlPoints = new();
foreach (BezierKnot thisBezierKnot in thisSpline.Knots)
{
Position position = VectorToPosition(thisBezierKnot.Position);
float3x3 rotationMatrix = new float3x3(Quaternion.Inverse(thisBezierKnot.Rotation));
Position handleL = VectorToPosition(math.mul(thisBezierKnot.TangentIn, rotationMatrix) + thisBezierKnot.Position);
Position handleR = VectorToPosition(math.mul(thisBezierKnot.TangentOut, rotationMatrix) + thisBezierKnot.Position);
controlPoints.Add(new()
{
position = position,
handleL = handleL,
handleR = handleR
});
}
dataSplines.Add(new()
{
controlPoints = controlPoints.ToArray(),
closed = thisSpline.Closed
});
}
splineData.splines = dataSplines.ToArray();
File.WriteAllText(AssetDatabase.GetAssetPath(splineImporter.splineData), JsonUtility.ToJson(splineData, true));
AssetDatabase.Refresh();
}
}
}
[RequireComponent(typeof(SplineContainer))]
public class SplineImporter : MonoBehaviour
{
public TextAsset splineData;
public float scale = 1;
public static Vector3 PositionToVector(Position position)
{
return new(position.x, position.z, position.y);
}
public static Position VectorToPosition(Vector3 vector)
{
return new()
{
x = vector.x,
y = vector.z,
z = vector.y
};
}
}
[Serializable]
public class SplineData
{
[Serializable]
public struct Position
{
public float x;
public float y;
public float z;
}
[Serializable]
public struct ControlPoint
{
public Position position;
public Position handleL;
public Position handleR;
public float tilt;
}
[Serializable]
public struct Spline
{
public ControlPoint[] controlPoints;
public bool closed;
}
public Spline[] splines = new Spline[0];
}
#endif

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d91b840f8503aaf409d992f7c89e93ed
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

195
Runtime/SplinePlus.cs Normal file
View File

@ -0,0 +1,195 @@
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
public class SplinePlus : MonoBehaviour
{
public SplineContainer splineContainer;
public SplineContainer deformContainer;
public int resolution;
public void Evaluate(int splineIndex, float anchor, float distance, out Vector3 position, out Quaternion rotation)
{
float t = anchor + (distance / splineContainer.Spline.GetLength());
if (deformContainer)
DeformSpline(splineIndex, t, out position, out rotation);
else
EvaluateSpline(splineIndex, t, out position, out rotation);
}
public void Evaluate(float anchor, float distance, out Vector3 position, out Quaternion rotation)
{
float t = anchor + (distance / splineContainer.Spline.GetLength());
if (deformContainer)
DeformSpline(t, out position, out rotation);
else
EvaluateSpline(t, out position, out rotation);
}
public void GetNearestPoint(Vector3 point, out Vector3 position, out Quaternion rotation)
{
position = Vector3.zero;
rotation = Quaternion.identity;
float nearestDistance = Mathf.Infinity;
for (int i = 0; i < splineContainer.Splines.Count; i++)
{
int resolutionScale = Mathf.CeilToInt(splineContainer.Splines[i].GetLength()) * resolution;
for (float j = 0; j <= resolutionScale; j++)
{
Evaluate(i, j / resolutionScale, 0, out Vector3 thisPosition, out Quaternion thisRotation);
float thisDistance = Vector3.Distance(point, thisPosition);
if (thisDistance < nearestDistance)
{
position = thisPosition;
rotation = thisRotation;
nearestDistance = thisDistance;
}
}
}
}
void EvaluateSpline(int splineIndex, float t, out Vector3 position, out Quaternion rotation)
{
ScaledEvaluate(splineContainer, splineIndex, t, out float3 position1, out float3 tangent, out float3 upVector);
position = position1;
rotation = Quaternion.LookRotation(tangent, upVector);
}
void EvaluateSpline(float t, out Vector3 position, out Quaternion rotation)
{
splineContainer.Evaluate(t, out float3 position1, out float3 tangent, out float3 upVector);
position = position1;
rotation = Quaternion.LookRotation(tangent, upVector);
}
void DeformSpline(int splineIndex, float t, out Vector3 position, out Quaternion rotation)
{
int resolutionScale = Mathf.CeilToInt(splineContainer.Splines[splineIndex].GetLength()) * resolution;
position = EvaluatePoint(splineIndex, t, out float3 upVector);
float t1 = Mathf.Clamp(t, 0, 1 - (1 / (float)resolutionScale));
Vector3 position0 = EvaluatePoint(splineIndex, t1, out _);
Vector3 position1 = EvaluatePoint(splineIndex, t1 + (1 / (float)resolutionScale), out _);
Vector3 difference = position1 - position0;
rotation = Vector3.Dot(difference, upVector) > 0
? Quaternion.LookRotation(difference, upVector)
: Quaternion.FromToRotation(Vector3.forward, difference);
}
void DeformSpline(float t, out Vector3 position, out Quaternion rotation)
{
int resolutionScale = Mathf.CeilToInt(splineContainer.CalculateLength()) * resolution;
position = EvaluatePoint(t, out float3 upVector);
float t1 = Mathf.Clamp(t, 0, 1 - (1 / (float)resolutionScale));
Vector3 position0 = EvaluatePoint(t1, out _);
Vector3 position1 = EvaluatePoint(t1 + (1 / (float)resolutionScale), out _);
Vector3 difference = position1 - position0;
rotation = Vector3.Dot(difference, upVector) > 0
? Quaternion.LookRotation(difference, upVector)
: Quaternion.FromToRotation(Vector3.forward, difference);
}
Vector3 EvaluatePoint(int splineIndex, float t, out float3 upVector)
{
ScaledEvaluate(splineContainer, splineIndex, t, out float3 position, out _, out upVector);
ScaledEvaluate(deformContainer, 0, position.x / deformContainer.Spline.GetLength(), out float3 deformPosition, out float3 deformTangent, out float3 deformUpVector);
float3x3 deformMatrix = new()
{
c0 = (float3)Vector3.Normalize(Vector3.Cross(deformTangent, deformUpVector)),
c1 = (float3)Vector3.Normalize(deformUpVector),
c2 = (float3)Vector3.Normalize(deformTangent)
};
return deformPosition + (deformMatrix.c0 * position.z) + (deformMatrix.c1 * position.y);
}
Vector3 EvaluatePoint(float t, out float3 upVector)
{
splineContainer.Evaluate(t, out float3 position, out _, out upVector);
ScaledEvaluate(deformContainer, 0, position.x / deformContainer.Spline.GetLength(), out float3 deformPosition, out float3 deformTangent, out float3 deformUpVector);
float3x3 deformMatrix = new()
{
c0 = (float3)Vector3.Normalize(Vector3.Cross(deformTangent, deformUpVector)),
c1 = (float3)Vector3.Normalize(deformUpVector),
c2 = (float3)Vector3.Normalize(deformTangent)
};
return deformPosition + (deformMatrix.c0 * position.z) + (deformMatrix.c1 * position.y);
}
void ScaledEvaluate(SplineContainer splineContainer, int splineIndex, float t, out float3 position, out float3 tangent, out float3 upVector)
{
Spline spline = splineContainer.Splines[splineIndex];
if (spline == null)
{
splineContainer.Evaluate(t, out position, out tangent, out upVector);
return;
}
SplineUtility.Evaluate(splineContainer.Splines[splineIndex], t, out position, out tangent, out upVector);
position = splineContainer.transform.TransformPoint(position);
tangent = splineContainer.transform.TransformVector(tangent);
upVector = splineContainer.transform.TransformDirection(upVector);
}
void OnDrawGizmos()
{
if (!splineContainer || !deformContainer) return;
Gizmos.color = Color.green;
for (int i = 0; i < splineContainer.Splines.Count; i++)
{
Evaluate(i, 0, 0, out Vector3 position, out _);
Vector3 oldPosition = position;
int gizmoResolution = Mathf.CeilToInt(splineContainer.Splines[i].GetLength());
for (float j = 1; j <= gizmoResolution; j++)
{
Evaluate(i, j / gizmoResolution, 0, out position, out _);
Gizmos.DrawLine(oldPosition, position);
oldPosition = position;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 41eeb6f8f41efdb4eb29d6db19d12b6c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,17 @@
{
"name": "josh.spline-importer",
"rootNamespace": "",
"references": [
"Unity.Splines",
"Unity.Mathematics"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: aa8af8f66b6c2db4fa61f07958e484b9
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

8
Samples.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dad66e5ce81d3da46a6f24be534d3c90
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

21
Samples/README.txt Normal file
View File

@ -0,0 +1,21 @@
In the Spline Debug scene, there are 6 important objects:
1. Spline
- A curved spline going from (0, 0, 0) to (10, 0, -5)
2. Deform
- A curved spline used to deform Spline
3. Spline Plus
- An object with the SplinePlus component
- This object is used to deform spline Spline along spline Deform
- The resulting spline is rendered in green with Gizmos enabled
4. Evaluate
- Renders a cube gizmo along each of the above splines at a given distance from a given anchor point
5. Nearest Point
- Renders a cube gizmo at the nearest point along the deformed spline from the Spline Plus object
6. Spline Debug
- An instantiated Blender file including a tube warped around the deformed spline using Blender's Curve modifier

7
Samples/README.txt.meta Normal file
View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4f23fad103e2d8548be04a5c255ce66b
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

BIN
Samples/Spline Debug.blend Normal file

Binary file not shown.

View File

@ -0,0 +1,109 @@
fileFormatVersion: 2
guid: b93c3e7374b43344ca6fa176d935f2bd
ModelImporter:
serializedVersion: 22200
internalIDToNameTable: []
externalObjects: {}
materials:
materialImportMode: 2
materialName: 0
materialSearch: 1
materialLocation: 1
animations:
legacyGenerateAnimations: 4
bakeSimulation: 0
resampleCurves: 1
optimizeGameObjects: 0
removeConstantScaleCurves: 0
motionNodeName:
rigImportErrors:
rigImportWarnings:
animationImportErrors:
animationImportWarnings:
animationRetargetingWarnings:
animationDoRetargetingWarnings: 0
importAnimatedCustomProperties: 0
importConstraints: 0
animationCompression: 1
animationRotationError: 0.5
animationPositionError: 0.5
animationScaleError: 0.5
animationWrapMode: 0
extraExposedTransformPaths: []
extraUserProperties: []
clipAnimations: []
isReadable: 0
meshes:
lODScreenPercentages: []
globalScale: 1
meshCompression: 0
addColliders: 0
useSRGBMaterialColor: 1
sortHierarchyByName: 1
importPhysicalCameras: 1
importVisibility: 1
importBlendShapes: 1
importCameras: 1
importLights: 1
nodeNameCollisionStrategy: 1
fileIdsGeneration: 2
swapUVChannels: 0
generateSecondaryUV: 0
useFileUnits: 1
keepQuads: 0
weldVertices: 1
bakeAxisConversion: 1
preserveHierarchy: 0
skinWeightsMode: 0
maxBonesPerVertex: 4
minBoneWeight: 0.001
optimizeBones: 1
meshOptimizationFlags: -1
indexFormat: 0
secondaryUVAngleDistortion: 8
secondaryUVAreaDistortion: 15.000001
secondaryUVHardAngle: 88
secondaryUVMarginMethod: 1
secondaryUVMinLightmapResolution: 40
secondaryUVMinObjectScale: 1
secondaryUVPackMargin: 4
useFileScale: 1
strictVertexDataChecks: 0
tangentSpace:
normalSmoothAngle: 60
normalImportMode: 0
tangentImportMode: 3
normalCalculationMode: 4
legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0
blendShapeNormalImportMode: 1
normalSmoothingSource: 0
referencedClips: []
importAnimation: 1
humanDescription:
serializedVersion: 3
human: []
skeleton: []
armTwist: 0.5
foreArmTwist: 0.5
upperLegTwist: 0.5
legTwist: 0.5
armStretch: 0.05
legStretch: 0.05
feetSpacing: 0
globalScale: 1
rootMotionBoneName:
hasTranslationDoF: 0
hasExtraRoot: 0
skeletonHasParents: 1
lastHumanDescriptionAvatarSource: {instanceID: 0}
autoGenerateAvatarMappingIfUnspecified: 1
animationType: 2
humanoidOversampling: 1
avatarSetup: 0
addHumanoidExtraRootOnlyWhenUsingAvatar: 1
importBlendShapeDeformPercent: 1
remapMaterialsIfMaterialImportModeIsNone: 0
additionalBone: 0
userData:
assetBundleName:
assetBundleVariant:

BIN
Samples/Spline Debug.blend1 Normal file

Binary file not shown.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d3d151e577ee0984bbf49dac54834606
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

1020
Samples/Spline Debug.unity Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 44ea138cdbdb30745b195f3459bdf8c9
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,48 @@
using Unity.Mathematics;
using UnityEngine;
public class SplineEvaluateDebug : MonoBehaviour
{
[SerializeField] SplinePlus splinePlus;
[SerializeField] float anchor;
[SerializeField] float distance;
[SerializeField] float cubeSize;
[SerializeField] float matrixSize;
void OnDrawGizmos()
{
if (!splinePlus) return;
splinePlus.Evaluate(anchor, distance, out Vector3 position, out Quaternion rotation);
transform.position = position;
transform.rotation = rotation;
Gizmos.DrawCube(position, Vector3.one * cubeSize);
splinePlus.splineContainer.Evaluate(anchor + (distance / splinePlus.splineContainer.Spline.GetLength()), out float3 position1, out _, out _);
Gizmos.DrawCube(position1, Vector3.one * cubeSize);
splinePlus.deformContainer.Evaluate(position1.x / splinePlus.deformContainer.Spline.GetLength(), out float3 deformPosition, out _, out _);
Gizmos.DrawCube(deformPosition, Vector3.one * cubeSize);
Gizmos.color = Color.green;
Gizmos.DrawRay(position, transform.up * matrixSize);
Gizmos.color = Color.red;
Gizmos.DrawRay(position, transform.right * matrixSize);
Gizmos.color = Color.blue;
Gizmos.DrawRay(position, transform.forward * matrixSize);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b96504a617d74094faa656840a73e7a9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,17 @@
using UnityEngine;
public class SplineNearestPointDebug : MonoBehaviour
{
[SerializeField] SplinePlus splinePlus;
[SerializeField] float cubeSize;
void OnDrawGizmos()
{
if (!splinePlus) return;
splinePlus.GetNearestPoint(transform.position, out Vector3 position, out _);
Gizmos.DrawCube(position, Vector3.one * cubeSize);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0760258fbb4f0b5418d9a88014403d0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

8
Samples/Splines.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8b9cd3cbddbe4e44abf819fdd6da81ee
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,45 @@
{
"splines": [
{
"controlPoints": [
{
"position": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"handleL": {
"x": -9.999999046325684,
"y": -1.5099578831723193e-06,
"z": 0.0
},
"handleR": {
"x": 9.999999046325684,
"y": 1.5099578831723193e-06,
"z": 0.0
},
"tilt": 0.0
},
{
"position": {
"x": 10.000001907348633,
"y": -9.999998092651367,
"z": 0.0
},
"handleL": {
"x": 1.9073486328125e-06,
"y": -10.000001907348633,
"z": 0.0
},
"handleR": {
"x": 20.000001907348633,
"y": -9.999994277954102,
"z": 0.0
},
"tilt": 0.0
}
],
"closed": false
}
]
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 22decb6838ce8a34a883a7ad6c7c3335
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,45 @@
{
"splines": [
{
"controlPoints": [
{
"position": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"handleL": {
"x": -1.0,
"y": -8.742277657347586e-08,
"z": 0.0
},
"handleR": {
"x": 1.0,
"y": 8.742277657347586e-08,
"z": 0.0
},
"tilt": -0.0
},
{
"position": {
"x": 10.0,
"y": -5.0,
"z": 0.0
},
"handleL": {
"x": 9.0,
"y": -5.0,
"z": 0.0
},
"handleR": {
"x": 11.0,
"y": -5.0,
"z": 0.0
},
"tilt": -0.0
}
],
"closed": false
}
]
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f3719dd04e0fe40438bb9578e9d53804
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

203
Spline Exporter.py Normal file
View File

@ -0,0 +1,203 @@
bl_info = {
"name": "Spline Exporter",
"author": "Josh",
"version": (1, 0),
"blender": (2, 80, 0),
"location": "File > Import-Export",
"description": "Export spline control point data as .JSON",
"category": "Import-Export"
}
import json
import bpy
import math
from mathutils import *
from bpy_extras.io_utils import ExportHelper
from bpy_extras.io_utils import ImportHelper
from bpy.props import StringProperty
def ExportSpline():
curve = bpy.context.active_object
splines = {"splines": []}
for thisSpline in curve.data.splines:
points = {
"controlPoints": [],
"closed": thisSpline.use_cyclic_u
}
for thisPoint in thisSpline.bezier_points:
co = thisPoint.co
hl = thisPoint.handle_left
hr = thisPoint.handle_right
points["controlPoints"].append(
{
"position": {
"x": co.x,
"y": co.y,
"z": co.z
},
"handleL": {
"x": hl.x,
"y": hl.y,
"z": hl.z
},
"handleR": {
"x": hr.x,
"y": hr.y,
"z": hr.z
},
"tilt": thisPoint.tilt * (180 / math.pi)
})
splines["splines"].append(points)
class SaveJSON(bpy.types.Operator, ExportHelper):
bl_idname = "object.save_json"
bl_label = "Save JSON"
filename_ext = ".json"
filepath: StringProperty(subtype="FILE_PATH")
def execute(self, context):
with open(self.filepath, "w") as f:
json.dump(splines, f, indent = 4)
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {"RUNNING_MODAL"}
bpy.utils.register_class(SaveJSON)
bpy.ops.object.save_json("INVOKE_DEFAULT")
def ImportSpline():
class LoadJSON(bpy.types.Operator, ImportHelper):
bl_idname = "object.load_json"
bl_label = "Load JSON"
filename_ext = ".json"
filter_glob: bpy.props.StringProperty(
default = "*.json",
options = {"HIDDEN"},
maxlen = 255
)
def execute(self, context):
with open(self.filepath, "r") as f:
splines = json.load(f)
curve = bpy.data.curves.new("BezierCurve", "CURVE")
curve.dimensions = "3D"
curve.twist_mode = "Z_UP"
for thisSpline in splines["splines"]:
i = 0
spline = curve.splines.new("BEZIER")
for thisPoint in thisSpline["controlPoints"]:
if i > 0:
spline.bezier_points.add(1)
bezierPoint = spline.bezier_points[i]
position = thisPoint["position"]
handleL = thisPoint["handleL"]
handleR = thisPoint["handleR"]
bezierPoint.co = Vector((position["x"], position["y"], position["z"]))
bezierPoint.handle_left = Vector((handleL["x"], handleL["y"], handleL["z"]))
bezierPoint.handle_right = Vector((handleR["x"], handleR["y"], handleR["z"]))
i += 1
spline.use_cyclic_u = thisSpline["closed"]
bpy.context.scene.collection.objects.link(bpy.data.objects.new("BezierCurve", curve))
return {"FINISHED"}
bpy.utils.register_class(LoadJSON)
bpy.ops.object.load_json("INVOKE_DEFAULT")
class SplineExportOperator(bpy.types.Operator):
bl_idname = "object.spline_export_operator"
bl_label = "Export Spline"
def execute(self, context):
ExportSpline()
return {"FINISHED"}
class SplineImportOperator(bpy.types.Operator):
bl_idname = "object.spline_import_operator"
bl_label = "Import Spline"
def execute(self, context):
ImportSpline()
return {"FINISHED"}
class SplineExporterPanel(bpy.types.Panel):
bl_label = "Spline Exporter"
bl_idname = "SCENE_PT_SPLINE_EXPORTER"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "scene"
def draw(self, context):
layout = self.layout
scene = context.scene
layout.label(text="Export Spline")
row = layout.row()
row.scale_y = 1
row.operator("object.spline_export_operator")
row.operator("object.spline_import_operator")
def register():
bpy.utils.register_class(SplineImportOperator)
bpy.utils.register_class(SplineExportOperator)
bpy.utils.register_class(SplineExporterPanel)
def unregister():
bpy.utils.unregister_class(SplineImportOperator)
bpy.utils.unregister_class(SplineExportOperator)
bpy.utils.unregister_class(SplineExporterPanel)
if __name__ == "__main__":
register()

7
Spline Exporter.py.meta Normal file
View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: cdf15d49993c2d14f936f40c16815acc
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

15
package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "com.josh.spline-importer",
"version": "1.0.0",
"displayName": "Spline Importer",
"description": "Import and export splines between Blender and Unity",
"unity": "2022.1",
"dependencies": {
"com.unity.mathematics": "1.0.0",
"com.unity.splines": "1.0.0"
},
"author": {
"name": "Josh",
"url": "https://github.com/Josh4359"
}
}

7
package.json.meta Normal file
View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 11ef2ad10fe215940a918a2a580788a0
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: