mirror of
https://github.com/maxartz15/VertexAnimation.git
synced 2025-06-13 14:56:18 +02:00
ModelBaker Test
MeshCombiner, combine SkinnedMeshRenderers and MeshRenderers into one. AnimationBaker, bake SkinnedMeshRenderer with of animations into vertex animations. Test shader, flipped Y compared to previvious version, it now starts at 0,0 and goes into the positive direciton.
This commit is contained in:
8
Runtime/Scripts/ModelBaker.meta
Normal file
8
Runtime/Scripts/ModelBaker.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 657facf5ba53f664b95d329ffb58abb1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
189
Runtime/Scripts/ModelBaker/AnimationBaker.cs
Normal file
189
Runtime/Scripts/ModelBaker/AnimationBaker.cs
Normal file
@ -0,0 +1,189 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TAO.VertexAnimation
|
||||
{
|
||||
public static class AnimationBaker
|
||||
{
|
||||
[System.Serializable]
|
||||
public struct BakedData
|
||||
{
|
||||
public Mesh mesh;
|
||||
public List<Texture2D> positionMaps;
|
||||
|
||||
// Returns main position map.
|
||||
public Texture2D GetPositionMap
|
||||
{
|
||||
get
|
||||
{
|
||||
return positionMaps[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct AnimationInfo
|
||||
{
|
||||
public int rawFrameHeight;
|
||||
public int frameHeight;
|
||||
public int frameSpacing;
|
||||
public int frames;
|
||||
public int maxFrames;
|
||||
public int textureWidth;
|
||||
public int textureHeight;
|
||||
|
||||
// Create animation info and calculate values.
|
||||
public AnimationInfo(Mesh mesh, int frames, int textureWidth)
|
||||
{
|
||||
this.frames = frames;
|
||||
this.textureWidth = textureWidth;
|
||||
|
||||
rawFrameHeight = Mathf.CeilToInt((float)mesh.vertices.Length / this.textureWidth);
|
||||
frameHeight = Mathf.NextPowerOfTwo(rawFrameHeight);
|
||||
frameSpacing = (frameHeight - rawFrameHeight) + 1;
|
||||
|
||||
textureHeight = Mathf.NextPowerOfTwo(frameHeight * this.frames);
|
||||
|
||||
maxFrames = textureHeight / frameHeight;
|
||||
}
|
||||
}
|
||||
|
||||
public static BakedData Bake(this GameObject model, AnimationClip[] animationClips, int fps, int textureWidth)
|
||||
{
|
||||
BakedData bakedData = new BakedData()
|
||||
{
|
||||
mesh = null,
|
||||
positionMaps = new List<Texture2D>()
|
||||
};
|
||||
|
||||
// Calculate what our max frames/time is going to be.
|
||||
int maxFrames = 0;
|
||||
foreach (AnimationClip ac in animationClips)
|
||||
{
|
||||
int frames = Mathf.FloorToInt(fps * ac.length);
|
||||
|
||||
if (maxFrames < frames)
|
||||
{
|
||||
maxFrames = frames;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the target mesh to calculate the animation info.
|
||||
Mesh mesh = model.GetComponent<SkinnedMeshRenderer>().sharedMesh;
|
||||
|
||||
// Get the info for the biggest animation.
|
||||
AnimationInfo animationInfo = new AnimationInfo(mesh, maxFrames, textureWidth);
|
||||
|
||||
foreach (AnimationClip ac in animationClips)
|
||||
{
|
||||
// Set the frames for this animation.
|
||||
animationInfo.frames = Mathf.FloorToInt(fps * ac.length);
|
||||
|
||||
BakedData bd = Bake(model, ac, animationInfo);
|
||||
bakedData.mesh = bd.mesh;
|
||||
bakedData.positionMaps.AddRange(bd.positionMaps);
|
||||
}
|
||||
|
||||
return bakedData;
|
||||
}
|
||||
|
||||
public static BakedData Bake(this GameObject model, AnimationClip animationClip, AnimationInfo animationInfo)
|
||||
{
|
||||
Mesh mesh = new Mesh
|
||||
{
|
||||
name = string.Format("{0}", model.name)
|
||||
};
|
||||
|
||||
// Bake mesh for a copy and to apply the new UV's to.
|
||||
SkinnedMeshRenderer skinnedMeshRenderer = model.GetComponent<SkinnedMeshRenderer>();
|
||||
skinnedMeshRenderer.BakeMesh(mesh);
|
||||
mesh.RecalculateBounds();
|
||||
|
||||
mesh.uv3 = mesh.BakePositionUVs(animationInfo);
|
||||
|
||||
BakedData bakedData = new BakedData()
|
||||
{
|
||||
mesh = mesh,
|
||||
positionMaps = new List<Texture2D>() { BakePositionMap(model, animationClip, animationInfo) }
|
||||
};
|
||||
|
||||
return bakedData;
|
||||
}
|
||||
|
||||
public static Texture2D BakePositionMap(this GameObject model, AnimationClip animationClip, AnimationInfo animationInfo)
|
||||
{
|
||||
// Create positionMap Texture without MipMaps which is Linear and HDR to store values in a bigger range.
|
||||
Texture2D positionMap = new Texture2D(animationInfo.textureWidth, animationInfo.textureHeight, TextureFormat.RGBAHalf, false, true);
|
||||
|
||||
// Create instance to sample from.
|
||||
GameObject inst = GameObject.Instantiate(model);
|
||||
SkinnedMeshRenderer skinnedMeshRenderer = inst.GetComponent<SkinnedMeshRenderer>();
|
||||
|
||||
int y = 0;
|
||||
for (int f = 0; f < animationInfo.frames; f++)
|
||||
{
|
||||
animationClip.SampleAnimation(inst, (animationClip.length / animationInfo.frames) * f);
|
||||
|
||||
Mesh sampledMesh = new Mesh();
|
||||
skinnedMeshRenderer.BakeMesh(sampledMesh);
|
||||
sampledMesh.RecalculateBounds();
|
||||
|
||||
int x = 0;
|
||||
for (int v = 0; v < sampledMesh.vertices.Length; v++)
|
||||
{
|
||||
Vector3 vert = sampledMesh.vertices[v];
|
||||
Vector3 normal = sampledMesh.normals[v];
|
||||
|
||||
positionMap.SetPixel(x, y,
|
||||
new Color(vert.x, vert.y, vert.z,
|
||||
VectorUtils.Float3ToFloat(normal))
|
||||
);
|
||||
|
||||
x++;
|
||||
if (x >= animationInfo.textureWidth)
|
||||
{
|
||||
x = 0;
|
||||
y++;
|
||||
}
|
||||
}
|
||||
y += animationInfo.frameSpacing;
|
||||
}
|
||||
|
||||
GameObject.DestroyImmediate(inst);
|
||||
|
||||
positionMap.name = string.Format("VA_N-{0}_F-{1}_MF-{2}", animationClip.name, animationInfo.frames, animationInfo.maxFrames);
|
||||
positionMap.filterMode = FilterMode.Point;
|
||||
// TODO: Make no longer readable.
|
||||
positionMap.Apply(false, false);
|
||||
|
||||
return positionMap;
|
||||
}
|
||||
|
||||
public static Vector2[] BakePositionUVs(this Mesh mesh, AnimationInfo animationInfo)
|
||||
{
|
||||
Vector2[] uv3 = new Vector2[mesh.vertices.Length];
|
||||
|
||||
float xOffset = 1.0f / animationInfo.textureWidth;
|
||||
float yOffset = 1.0f / animationInfo.textureHeight;
|
||||
|
||||
float x = xOffset / 2.0f;
|
||||
float y = yOffset / 2.0f;
|
||||
|
||||
for (int v = 0; v < mesh.vertices.Length; v++)
|
||||
{
|
||||
uv3[v] = new Vector2(x, y);
|
||||
|
||||
x += xOffset;
|
||||
if (x >= 1.0f)
|
||||
{
|
||||
x = xOffset / 2.0f;
|
||||
y += yOffset;
|
||||
}
|
||||
}
|
||||
|
||||
mesh.uv3 = uv3;
|
||||
|
||||
return uv3;
|
||||
}
|
||||
}
|
||||
}
|
11
Runtime/Scripts/ModelBaker/AnimationBaker.cs.meta
Normal file
11
Runtime/Scripts/ModelBaker/AnimationBaker.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f73c308e347c32142b0f61e8b2550914
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
254
Runtime/Scripts/ModelBaker/MeshCombiner.cs
Normal file
254
Runtime/Scripts/ModelBaker/MeshCombiner.cs
Normal file
@ -0,0 +1,254 @@
|
||||
// References:
|
||||
// https://forum.unity.com/threads/help-combining-and-manipulating-skinned-mesh-renderers-imported-from-blender.505078/
|
||||
// http://wiki.unity3d.com/index.php/CombineSkinnedMeshes
|
||||
// http://wiki.unity3d.com/index.php/SkinnedMeshCombiner
|
||||
|
||||
// TODO:
|
||||
// ---Bake ALL the MeshRenderers/SkinnedMeshRenderers and merge them together.---
|
||||
// ---Bake multiple animations.---
|
||||
// ---Get the longest animation to calculate the texture height, so all the textures have the same height for the 3D texture.---
|
||||
// Add options and previews for texture size, animation phasing/fps.
|
||||
// Either merge with the animation books or generate them from this and maybe store them as child (and then don't destroy them on re-bake to keep the reference but replace it).
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TAO.VertexAnimation
|
||||
{
|
||||
public static class MeshCombiner
|
||||
{
|
||||
private struct MaterialMeshGroup
|
||||
{
|
||||
public List<SkinnedMeshRenderer> skinnedMeshes;
|
||||
public List<(MeshFilter mf, MeshRenderer mr)> meshes;
|
||||
public Material material;
|
||||
}
|
||||
|
||||
public static SkinnedMeshRenderer Combine(this SkinnedMeshRenderer target, List<SkinnedMeshRenderer> skinnedMeshes, List<(MeshFilter mf, MeshRenderer mr)> meshes)
|
||||
{
|
||||
List<MaterialMeshGroup> groups = new List<MaterialMeshGroup>();
|
||||
|
||||
// Group skinnedMeshes.
|
||||
foreach (var sm in skinnedMeshes)
|
||||
{
|
||||
bool hasGroup = false;
|
||||
foreach (var g in groups)
|
||||
{
|
||||
if (sm.sharedMaterial == g.material)
|
||||
{
|
||||
hasGroup = true;
|
||||
g.skinnedMeshes.Add(sm);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasGroup)
|
||||
{
|
||||
groups.Add(new MaterialMeshGroup()
|
||||
{
|
||||
skinnedMeshes = new List<SkinnedMeshRenderer>()
|
||||
{
|
||||
sm
|
||||
},
|
||||
meshes = new List<(MeshFilter mf, MeshRenderer mr)>(),
|
||||
material = sm.sharedMaterial
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Group Meshes.
|
||||
foreach (var m in meshes)
|
||||
{
|
||||
bool hasGroup = false;
|
||||
foreach (var g in groups)
|
||||
{
|
||||
if (m.mr.sharedMaterial == g.material)
|
||||
{
|
||||
hasGroup = true;
|
||||
g.meshes.Add(m);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasGroup)
|
||||
{
|
||||
groups.Add(new MaterialMeshGroup()
|
||||
{
|
||||
skinnedMeshes = new List<SkinnedMeshRenderer>(),
|
||||
meshes = new List<(MeshFilter mf, MeshRenderer mr)>()
|
||||
{
|
||||
m
|
||||
},
|
||||
material = m.mr.sharedMaterial
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
List<GameObject> tmp = new List<GameObject>();
|
||||
for (int i = 0; i < groups.Count; i++)
|
||||
{
|
||||
tmp.Add(new GameObject("tmpChild", typeof(SkinnedMeshRenderer)));
|
||||
tmp[i].transform.parent = target.transform;
|
||||
|
||||
MaterialMeshGroup mmg = groups[i];
|
||||
tmp[i].GetComponent<SkinnedMeshRenderer>().Combine(mmg.skinnedMeshes, mmg.meshes, mmg.material);
|
||||
}
|
||||
|
||||
// TODO: Merge materialMergedObjects.
|
||||
// TEMP: Remove when materialMergedObjects.
|
||||
SkinnedMeshRenderer newSkinnedMeshRenderer = tmp[0].GetComponent<SkinnedMeshRenderer>();
|
||||
target.sharedMesh = newSkinnedMeshRenderer.sharedMesh;
|
||||
target.sharedMaterial = newSkinnedMeshRenderer.sharedMaterial;
|
||||
target.bones = newSkinnedMeshRenderer.bones;
|
||||
|
||||
foreach (var go in tmp)
|
||||
{
|
||||
GameObject.DestroyImmediate(go);
|
||||
}
|
||||
|
||||
// Set a name to make it more clear.
|
||||
target.sharedMesh.name = target.transform.name.Replace("(Clone)", "");
|
||||
return target;
|
||||
}
|
||||
|
||||
public static SkinnedMeshRenderer Combine(this SkinnedMeshRenderer target, List<SkinnedMeshRenderer> skinnedMeshes, List<(MeshFilter mf, MeshRenderer mr)> meshes, Material mainMaterial)
|
||||
{
|
||||
List<Transform> bones = new List<Transform>();
|
||||
List<BoneWeight> boneWeights = new List<BoneWeight>();
|
||||
List<Matrix4x4> bindPoses = new List<Matrix4x4>();
|
||||
List<CombineInstance> combineInstances = new List<CombineInstance>();
|
||||
|
||||
// Combine SkinnedMeshes.
|
||||
int boneOffset = 0;
|
||||
for (int s = 0; s < skinnedMeshes.Count; s++)
|
||||
{
|
||||
SkinnedMeshRenderer smr = skinnedMeshes[s];
|
||||
|
||||
//if the skinned mesh renderer has a material other than the default
|
||||
//we assume it's a one-off face material and deal with it later
|
||||
if (smr.sharedMaterial != mainMaterial)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
BoneWeight[] meshBoneweight = smr.sharedMesh.boneWeights;
|
||||
|
||||
// May want to modify this if the renderer shares bones as unnecessary bones will get added.
|
||||
// We don't care since it is going to be converted into vertex animations later anyways.
|
||||
for (int i = 0; i < meshBoneweight.Length; ++i)
|
||||
{
|
||||
BoneWeight bWeight = meshBoneweight[i];
|
||||
|
||||
bWeight.boneIndex0 += boneOffset;
|
||||
bWeight.boneIndex1 += boneOffset;
|
||||
bWeight.boneIndex2 += boneOffset;
|
||||
bWeight.boneIndex3 += boneOffset;
|
||||
|
||||
boneWeights.Add(bWeight);
|
||||
}
|
||||
|
||||
boneOffset += smr.bones.Length;
|
||||
|
||||
Transform[] meshBones = smr.bones;
|
||||
for (int i = 0; i < meshBones.Length; ++i)
|
||||
{
|
||||
bones.Add(meshBones[i]);
|
||||
|
||||
//we take the old bind pose that mapped from our mesh to world to bone,
|
||||
//and take out our localToWorldMatrix, so now it's JUST the bone matrix
|
||||
//since our skinned mesh renderer is going to be on the root of our object that works
|
||||
bindPoses.Add(smr.sharedMesh.bindposes[i] * smr.transform.worldToLocalMatrix);
|
||||
}
|
||||
|
||||
CombineInstance ci = new CombineInstance
|
||||
{
|
||||
mesh = smr.sharedMesh,
|
||||
transform = smr.transform.localToWorldMatrix
|
||||
};
|
||||
combineInstances.Add(ci);
|
||||
|
||||
GameObject.DestroyImmediate(smr);
|
||||
}
|
||||
|
||||
// Combine Meshes.
|
||||
for (int s = 0; meshes != null && s < meshes.Count; s++)
|
||||
{
|
||||
MeshFilter filter = meshes[s].mf;
|
||||
MeshRenderer renderer = meshes[s].mr;
|
||||
|
||||
//if the skinned mesh renderer has a material other than the default
|
||||
//we assume it's a one-off face material and deal with it later
|
||||
if (renderer.sharedMaterial != mainMaterial)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// May want to modify this if the renderer shares bones as unnecessary bones will get added.
|
||||
// We don't care since it is going to be converted into vertex animations later anyways.
|
||||
int vertCount = filter.sharedMesh.vertexCount;
|
||||
for (int i = 0; i < vertCount; ++i)
|
||||
{
|
||||
BoneWeight bWeight = new BoneWeight
|
||||
{
|
||||
boneIndex0 = boneOffset,
|
||||
boneIndex1 = boneOffset,
|
||||
boneIndex2 = boneOffset,
|
||||
boneIndex3 = boneOffset,
|
||||
weight0 = 1
|
||||
};
|
||||
|
||||
boneWeights.Add(bWeight);
|
||||
}
|
||||
|
||||
boneOffset += 1;
|
||||
|
||||
bones.Add(filter.transform);
|
||||
|
||||
// TODO: figure out what this should be.
|
||||
bindPoses.Add(filter.transform.worldToLocalMatrix);
|
||||
|
||||
CombineInstance ci = new CombineInstance
|
||||
{
|
||||
mesh = filter.sharedMesh,
|
||||
transform = filter.transform.localToWorldMatrix
|
||||
};
|
||||
combineInstances.Add(ci);
|
||||
|
||||
GameObject.DestroyImmediate(filter);
|
||||
GameObject.DestroyImmediate(renderer);
|
||||
}
|
||||
|
||||
// Actually combine and recalculate mesh.
|
||||
Mesh skinnedMesh = new Mesh();
|
||||
skinnedMesh.CombineMeshes(combineInstances.ToArray(), true, true);
|
||||
skinnedMesh.RecalculateBounds();
|
||||
|
||||
// Copy settings to target.
|
||||
target.sharedMesh = skinnedMesh;
|
||||
target.sharedMaterial = mainMaterial;
|
||||
target.bones = bones.ToArray();
|
||||
target.sharedMesh.boneWeights = boneWeights.ToArray();
|
||||
target.sharedMesh.bindposes = bindPoses.ToArray();
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
public static void ConbineAndConvertGameObject(this GameObject gameObject)
|
||||
{
|
||||
// Get Skinned Meshes.
|
||||
List<SkinnedMeshRenderer> skinnedMeshes = gameObject.GetComponentsInChildren<SkinnedMeshRenderer>(true).ToList();
|
||||
// Get Meshes.
|
||||
List<(MeshFilter, MeshRenderer)> meshes = new List<(MeshFilter, MeshRenderer)>();
|
||||
foreach (var mf in gameObject.GetComponentsInChildren<MeshFilter>(true))
|
||||
{
|
||||
if (mf.TryGetComponent(out MeshRenderer mr))
|
||||
{
|
||||
meshes.Add((mf, mr));
|
||||
}
|
||||
}
|
||||
|
||||
// Add target mesh.
|
||||
SkinnedMeshRenderer target = gameObject.AddComponent<SkinnedMeshRenderer>();
|
||||
target.Combine(skinnedMeshes, meshes);
|
||||
}
|
||||
}
|
||||
}
|
11
Runtime/Scripts/ModelBaker/MeshCombiner.cs.meta
Normal file
11
Runtime/Scripts/ModelBaker/MeshCombiner.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f43224daff50a5042a182c6fb12440a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
37
Runtime/Scripts/ModelBaker/VA_ModelBaker.cs
Normal file
37
Runtime/Scripts/ModelBaker/VA_ModelBaker.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TAO.VertexAnimation
|
||||
{
|
||||
[CreateAssetMenu(fileName = "new ModelBaker", menuName = "VA_ModelBaker/ModelBaker", order = 400)]
|
||||
public class VA_ModelBaker : ScriptableObject
|
||||
{
|
||||
public GameObject model;
|
||||
public AnimationClip[] animationClips;
|
||||
public int fps = 24;
|
||||
public int textureWidth = 512;
|
||||
public bool saveBakedDataToAsset = true;
|
||||
public bool generateAnimationBook = false;
|
||||
|
||||
[SerializeField]
|
||||
private AnimationBaker.BakedData bakedData;
|
||||
public AnimationBaker.BakedData BakedData
|
||||
{
|
||||
get
|
||||
{
|
||||
return bakedData;
|
||||
}
|
||||
}
|
||||
|
||||
public void Bake()
|
||||
{
|
||||
var target = Instantiate(model);
|
||||
|
||||
target.ConbineAndConvertGameObject();
|
||||
bakedData = target.Bake(animationClips, fps, textureWidth);
|
||||
|
||||
DestroyImmediate(target);
|
||||
}
|
||||
}
|
||||
}
|
11
Runtime/Scripts/ModelBaker/VA_ModelBaker.cs.meta
Normal file
11
Runtime/Scripts/ModelBaker/VA_ModelBaker.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6752e365d065458469473b601e74e699
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
34
Runtime/Scripts/ModelBaker/VectorUtils.cs
Normal file
34
Runtime/Scripts/ModelBaker/VectorUtils.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace TAO.VertexAnimation
|
||||
{
|
||||
public static class VectorUtils
|
||||
{
|
||||
public static Vector2 Float3ToFloat2(this Vector3 f3)
|
||||
{
|
||||
Vector3 rotation = Vector3.Normalize(new Vector3(f3.x, 0, f3.z));
|
||||
|
||||
Vector2 f2 = new Vector2();
|
||||
f2.x = Mathf.Acos(Vector3.Dot(rotation, new Vector3(1, 0, 0))) * Mathf.Sign(f3.z);
|
||||
f2.x = ((f2.x / Mathf.PI) + 1) * 0.5f;
|
||||
|
||||
f2.y = Mathf.Acos(f3.y) / Mathf.PI;
|
||||
|
||||
f2 *= 15;
|
||||
f2.x = Mathf.Round(f2.x);
|
||||
f2.y = Mathf.Round(f2.y);
|
||||
|
||||
return f2;
|
||||
}
|
||||
|
||||
public static float Float2ToFloat(this Vector2 f2)
|
||||
{
|
||||
return (f2.x + (16 * f2.y)) / 255;
|
||||
}
|
||||
|
||||
public static float Float3ToFloat(this Vector3 f3)
|
||||
{
|
||||
return Float2ToFloat(Float3ToFloat2(f3));
|
||||
}
|
||||
}
|
||||
}
|
11
Runtime/Scripts/ModelBaker/VectorUtils.cs.meta
Normal file
11
Runtime/Scripts/ModelBaker/VectorUtils.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a4c82d2f184d3f4d8bb111e89009c3f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user