Initial commit
This commit is contained in:
commit
23aa1fb180
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
72
.gitignore
vendored
Normal file
72
.gitignore
vendored
Normal 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
21
LICENSE
Normal 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
7
LICENSE.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: def6304472883004fa1dc20959d55415
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
2
README.md
Normal file
2
README.md
Normal file
@ -0,0 +1,2 @@
|
||||
# Spline-Importer
|
||||
Import and export splines between Blender and Unity
|
7
README.md.meta
Normal file
7
README.md.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e3449f6187d6b64dbc1ec8bdf5151d1
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Runtime.meta
Normal file
8
Runtime.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d99f03d3767d06409c6524aa84af941
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
190
Runtime/SplineImporter.cs
Normal file
190
Runtime/SplineImporter.cs
Normal 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
|
11
Runtime/SplineImporter.cs.meta
Normal file
11
Runtime/SplineImporter.cs.meta
Normal 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
195
Runtime/SplinePlus.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Runtime/SplinePlus.cs.meta
Normal file
11
Runtime/SplinePlus.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41eeb6f8f41efdb4eb29d6db19d12b6c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
17
Runtime/josh.spline-importer.asmdef
Normal file
17
Runtime/josh.spline-importer.asmdef
Normal 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
|
||||
}
|
7
Runtime/josh.spline-importer.asmdef.meta
Normal file
7
Runtime/josh.spline-importer.asmdef.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa8af8f66b6c2db4fa61f07958e484b9
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Samples.meta
Normal file
8
Samples.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dad66e5ce81d3da46a6f24be534d3c90
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
21
Samples/README.txt
Normal file
21
Samples/README.txt
Normal 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
7
Samples/README.txt.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f23fad103e2d8548be04a5c255ce66b
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
BIN
Samples/Spline Debug.blend
Normal file
BIN
Samples/Spline Debug.blend
Normal file
Binary file not shown.
109
Samples/Spline Debug.blend.meta
Normal file
109
Samples/Spline Debug.blend.meta
Normal 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
BIN
Samples/Spline Debug.blend1
Normal file
Binary file not shown.
7
Samples/Spline Debug.blend1.meta
Normal file
7
Samples/Spline Debug.blend1.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3d151e577ee0984bbf49dac54834606
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1020
Samples/Spline Debug.unity
Normal file
1020
Samples/Spline Debug.unity
Normal file
File diff suppressed because it is too large
Load Diff
7
Samples/Spline Debug.unity.meta
Normal file
7
Samples/Spline Debug.unity.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44ea138cdbdb30745b195f3459bdf8c9
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
48
Samples/SplineEvaluateDebug.cs
Normal file
48
Samples/SplineEvaluateDebug.cs
Normal 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);
|
||||
}
|
||||
}
|
11
Samples/SplineEvaluateDebug.cs.meta
Normal file
11
Samples/SplineEvaluateDebug.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b96504a617d74094faa656840a73e7a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
17
Samples/SplineNearestPointDebug.cs
Normal file
17
Samples/SplineNearestPointDebug.cs
Normal 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);
|
||||
}
|
||||
}
|
11
Samples/SplineNearestPointDebug.cs.meta
Normal file
11
Samples/SplineNearestPointDebug.cs.meta
Normal 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
8
Samples/Splines.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b9cd3cbddbe4e44abf819fdd6da81ee
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
45
Samples/Splines/Deform.json
Normal file
45
Samples/Splines/Deform.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
7
Samples/Splines/Deform.json.meta
Normal file
7
Samples/Splines/Deform.json.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22decb6838ce8a34a883a7ad6c7c3335
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
45
Samples/Splines/Spline.json
Normal file
45
Samples/Splines/Spline.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
7
Samples/Splines/Spline.json.meta
Normal file
7
Samples/Splines/Spline.json.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3719dd04e0fe40438bb9578e9d53804
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
203
Spline Exporter.py
Normal file
203
Spline Exporter.py
Normal 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
7
Spline Exporter.py.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cdf15d49993c2d14f936f40c16815acc
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
15
package.json
Normal file
15
package.json
Normal 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
7
package.json.meta
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11ef2ad10fe215940a918a2a580788a0
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
x
Reference in New Issue
Block a user