mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 21:01:52 +00:00
712 lines
19 KiB
Beef
712 lines
19 KiB
Beef
using System;
|
|
using System.Collections;
|
|
using cgltf;
|
|
using GlitchyEngine.Math;
|
|
using GlitchyEngine.Renderer;
|
|
using GlitchyEngine.Renderer.Animation;
|
|
using GlitchyEngine.World;
|
|
using System.IO;
|
|
|
|
namespace GlitchyEngine.Content
|
|
{
|
|
public static class ModelLoader
|
|
{
|
|
static readonly Matrix RightToLeftHand = .Scaling(1, 1, -1);
|
|
|
|
public static Result<void> GetMeshNames(String filename, List<String> meshNames)
|
|
{
|
|
CGLTF.Options options = .();
|
|
CGLTF.Data* data;
|
|
CGLTF.Result result = CGLTF.ParseFile(options, filename, out data);
|
|
|
|
if (!(result case .Success))
|
|
return .Err;
|
|
|
|
for (var mesh in data.Meshes)
|
|
{
|
|
meshNames.Add(new String(mesh.Name));
|
|
}
|
|
|
|
CGLTF.Free(data);
|
|
|
|
return .Ok;
|
|
}
|
|
|
|
public static GeometryBinding LoadMesh(StringView fileName, StringView meshName, int primitiveIndex)
|
|
{
|
|
// TODO: add a context to remember which buffers were loaded before so that we don't load the same data multiple times.
|
|
|
|
char8* scopedFileName = fileName.ToScopeCStr!();
|
|
|
|
CGLTF.Options options = .();
|
|
CGLTF.Data* data;
|
|
CGLTF.Result result = CGLTF.ParseFile(options, scopedFileName, out data);
|
|
|
|
if (!(result case .Success))
|
|
return null;
|
|
|
|
result = CGLTF.LoadBuffers(options, data, scopedFileName);
|
|
|
|
GeometryBinding geoBinding = null;
|
|
|
|
for (var mesh in data.Meshes)
|
|
{
|
|
var name = StringView(mesh.Name);
|
|
|
|
if (name == meshName)
|
|
{
|
|
Log.EngineLogger.AssertDebug(primitiveIndex >= 0 && primitiveIndex < mesh.Primitives.Length);
|
|
|
|
geoBinding = PrimitiveToGeoBinding(mesh.Primitives[primitiveIndex]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
CGLTF.Free(data);
|
|
|
|
return geoBinding;
|
|
}
|
|
|
|
public static GeometryBinding LoadMesh(Stream data, StringView meshName, int primitiveIndex)
|
|
{
|
|
// TODO: add a context to remember which buffers were loaded before so that we don't load the same data multiple times.
|
|
|
|
uint8[] rawData = new:ScopedAlloc! uint8[data.Length];
|
|
|
|
var dataReadResult = data.TryRead(rawData);
|
|
|
|
if (dataReadResult case .Err(let err))
|
|
{
|
|
Log.EngineLogger.Error($"Failed to read data from stream. Error: {err}");
|
|
}
|
|
|
|
CGLTF.Options options = .();
|
|
CGLTF.Data* modelData;
|
|
CGLTF.Result result = CGLTF.Parse(options, (Span<uint8>)rawData, out modelData);
|
|
|
|
if (!(result case .Success))
|
|
return null;
|
|
|
|
// TODO: one buffer can be used by multiple primitives, the content manager could manage the buffers
|
|
|
|
// TODO: load with content manager
|
|
|
|
result = CGLTF.LoadBuffers(options, modelData, (char8*)null);
|
|
//result = LoadBuffersWithContentManager(options, modelData, meshName, Application.Get().ContentManager);
|
|
|
|
GeometryBinding geoBinding = null;
|
|
|
|
for (var mesh in modelData.Meshes)
|
|
{
|
|
var name = StringView(mesh.Name);
|
|
|
|
//if (name == meshName)
|
|
{
|
|
Log.EngineLogger.AssertDebug(primitiveIndex >= 0 && primitiveIndex < mesh.Primitives.Length);
|
|
|
|
geoBinding = PrimitiveToGeoBinding(mesh.Primitives[primitiveIndex]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
CGLTF.Free(modelData);
|
|
|
|
return geoBinding;
|
|
}
|
|
|
|
private static CGLTF.Result LoadBuffersWithContentManager(CGLTF.Options options, CGLTF.Data* data, StringView fileName, IContentManager contentManager)
|
|
{
|
|
if (data.Buffers.Length > 0 && data.Buffers[0].Data == null && data.Buffers[0].Uri == null && !data.Bin.IsEmpty)
|
|
{
|
|
if ((uint)data.Bin.Length < data.Buffers[0].Size)
|
|
return .DataTooShort;
|
|
|
|
data.Buffers[0].Data = data.Bin.Ptr;
|
|
data.Buffers[0].DataFreeMethod = .None;
|
|
}
|
|
|
|
for (ref CGLTF.Buffer buffer in ref data.Buffers)
|
|
{
|
|
if (buffer.Data != null)
|
|
continue;
|
|
|
|
if (buffer.Uri == null)
|
|
continue;
|
|
|
|
StringView uri = StringView(buffer.Uri);
|
|
|
|
if (uri.StartsWith("data:"))
|
|
{
|
|
int commaIndex = uri.IndexOf(',');
|
|
|
|
//char* comma = strchr(uri, ',');
|
|
|
|
if (commaIndex == -1 || commaIndex >= 7 || uri.StartsWith(";base64"))
|
|
return .UnknownFormat;
|
|
|
|
StringView dataView = uri.Substring(commaIndex + 1);
|
|
|
|
#unwarn
|
|
CGLTF.Result loadBufferResult = CGLTF.LoadBuffersBase64(&options, buffer.Size, dataView.Ptr, &buffer.Data);
|
|
buffer.DataFreeMethod = .MemoryFree;
|
|
|
|
return loadBufferResult;
|
|
}
|
|
else
|
|
{
|
|
Runtime.NotImplemented();
|
|
|
|
// TODO: Request Buffer from Content Manager
|
|
|
|
//int index = uri.IndexOf("://");
|
|
|
|
//if (index == -1)
|
|
// return .UnknownFormat;
|
|
|
|
// TODO: load buffer file...
|
|
//CGLTF.Result res = //cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data);
|
|
//buffer.DataFreeMethod = cgltf_data_free_method_file_release;
|
|
|
|
/*if (res != cgltf_result_success)
|
|
{
|
|
return res;
|
|
}*/
|
|
}
|
|
}
|
|
|
|
/*
|
|
|
|
for (cgltf_size i = 0; i < data->buffers_count; ++i)
|
|
{
|
|
if (data->buffers[i].data)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const char* uri = data->buffers[i].uri;
|
|
|
|
if (uri == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (strncmp(uri, "data:", 5) == 0)
|
|
{
|
|
const char* comma = strchr(uri, ',');
|
|
|
|
if (comma && comma - uri >= 7 && strncmp(comma - 7, ";base64", 7) == 0)
|
|
{
|
|
cgltf_result res = cgltf_load_buffer_base64(options, data->buffers[i].size, comma + 1, &data->buffers[i].data);
|
|
data->buffers[i].data_free_method = cgltf_data_free_method_memory_free;
|
|
|
|
if (res != cgltf_result_success)
|
|
{
|
|
return res;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return cgltf_result_unknown_format;
|
|
}
|
|
}
|
|
else if (strstr(uri, "://") == NULL && gltf_path)
|
|
{
|
|
cgltf_result res = cgltf_load_buffer_file(options, data->buffers[i].size, uri, gltf_path, &data->buffers[i].data);
|
|
data->buffers[i].data_free_method = cgltf_data_free_method_file_release;
|
|
|
|
if (res != cgltf_result_success)
|
|
{
|
|
return res;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return cgltf_result_unknown_format;
|
|
}
|
|
}
|
|
*/
|
|
|
|
return .Success;
|
|
}
|
|
|
|
public static GeometryBinding PrimitiveToGeoBinding(CGLTF.Primitive primitive)
|
|
{
|
|
GeometryBinding binding = new GeometryBinding();
|
|
|
|
// primitive topology
|
|
switch(primitive.Type)
|
|
{
|
|
case .Points:
|
|
binding.SetPrimitiveTopology(.PointList);
|
|
case .Lines:
|
|
binding.SetPrimitiveTopology(.LineList);
|
|
case .LineStrip:
|
|
binding.SetPrimitiveTopology(.LineStrip);
|
|
case .Triangles:
|
|
binding.SetPrimitiveTopology(.TriangleList);
|
|
case .TriangleStrip:
|
|
binding.SetPrimitiveTopology(.TriangleStrip);
|
|
default:
|
|
Log.EngineLogger.Assert(false, scope $"{primitive.Type} not supported.");
|
|
}
|
|
|
|
// indices
|
|
{
|
|
CGLTF.Accessor* indices = primitive.Indices;
|
|
|
|
if(indices != null)
|
|
{
|
|
bool is16bit = indices.ComponentType == .R_16u || indices.ComponentType == .R_16;
|
|
|
|
IndexBuffer ib = new IndexBuffer((.)indices.Count, .Immutable, .None, is16bit ? .Index16Bit : .Index32Bit);
|
|
|
|
uint8* bufferData = (uint8*)indices.BufferView.Buffer.Data;
|
|
bufferData += indices.BufferView.Offset;
|
|
|
|
// TODO: this is a mess
|
|
ib.SetData<uint8>(bufferData, (.)indices.BufferView.Size);
|
|
|
|
binding.SetIndexBuffer(ib);
|
|
|
|
ib.ReleaseRef();
|
|
}
|
|
}
|
|
|
|
bool hasNormals = false;
|
|
bool hasTangents = false;
|
|
|
|
// vertices
|
|
{
|
|
List<VertexElement> elements = scope .(primitive.Attributes.Length);
|
|
Dictionary<void*, VertexBuffer> buffers = scope .();
|
|
List<VertexBufferBinding> bindings = scope .();
|
|
|
|
for(var attribute in primitive.Attributes)
|
|
{
|
|
{
|
|
StringView attributeName = StringView(attribute.Name);
|
|
|
|
if(attributeName.Equals("NORMAL"))
|
|
{
|
|
hasNormals = true;
|
|
}
|
|
else if(attributeName.Equals("TANGENT"))
|
|
{
|
|
hasTangents = true;
|
|
}
|
|
}
|
|
|
|
// Get Input Element format
|
|
Format format = FormatFromVectorComponent(attribute.Data.Type, attribute.Data.ComponentType);
|
|
Log.EngineLogger.AssertDebug(format != .Unknown, "Vertex element format must not be \"Unknown.\"");
|
|
|
|
VertexBuffer vertexBuffer = null;
|
|
|
|
binding.VertexCount = (uint32)attribute.Data.Count;
|
|
|
|
// Get vertex buffer
|
|
{
|
|
CGLTF.BufferView* bufferView = attribute.Data.BufferView;
|
|
|
|
// if buffer doesn't exist -> create
|
|
if(!buffers.TryGetValue(bufferView, out vertexBuffer))
|
|
{
|
|
vertexBuffer = new VertexBuffer(1, (uint32)bufferView.Size, .Immutable)..ReleaseRefNoDelete();
|
|
|
|
uint8* bufferData = (uint8*)bufferView.Buffer.Data;
|
|
bufferData += bufferView.Offset;
|
|
|
|
vertexBuffer.SetData(bufferData, (.)bufferView.Size);
|
|
|
|
buffers.Add(attribute.Data.BufferView, vertexBuffer);
|
|
}
|
|
|
|
Log.EngineLogger.AssertDebug(vertexBuffer != null);
|
|
}
|
|
|
|
VertexBufferBinding bufferBinding = .(vertexBuffer, (.)attribute.Data.Stride, (.)attribute.Data.Offset);
|
|
|
|
// get slot of bufferBinding
|
|
int bindingSlot = bindings.IndexOf(bufferBinding);
|
|
|
|
// binding has no slot -> add to list
|
|
if(bindingSlot == -1)
|
|
{
|
|
bindingSlot = bindings.Count;
|
|
bindings.Add(bufferBinding);
|
|
|
|
binding.SetVertexBufferSlot(bufferBinding, (.)bindingSlot);
|
|
}
|
|
|
|
StringView strView = .(attribute.Name);
|
|
|
|
// Remove number from end of name
|
|
while((*(strView.EndPtr - 1)).IsDigit || (*(strView.EndPtr - 1)) == '_')
|
|
{
|
|
strView.Length--;
|
|
}
|
|
|
|
VertexElement element = .(format, new String(strView), true, (.)attribute.Index, (.)bindingSlot);
|
|
elements.Add(element);
|
|
}
|
|
|
|
// Generate normals if missing
|
|
if(!hasNormals)
|
|
{
|
|
CGLTF.Accessor* positions = null;
|
|
|
|
// Find position accessor
|
|
for(var attribute in primitive.Attributes)
|
|
{
|
|
if(StringView(attribute.Name).Equals("POSITION"))
|
|
{
|
|
positions = attribute.Data;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Log.EngineLogger.AssertDebug(positions != null, "The model appears to have no position data?!");
|
|
|
|
float3[] normals = new float3[positions.Count];
|
|
|
|
if(primitive.Indices != null)
|
|
GenerateNormals(primitive.Indices, positions, normals);
|
|
else
|
|
GenerateNormals(positions, normals);
|
|
|
|
VertexBuffer vertexBuffer = new VertexBuffer((uint32)sizeof(float3), (uint32)normals.Count, .Immutable);
|
|
vertexBuffer.SetData<float3>(normals);
|
|
|
|
bindings.Add(vertexBuffer.Binding);
|
|
|
|
uint32 bindingSlot = (.)bindings.Count - 1;
|
|
|
|
binding.SetVertexBufferSlot(vertexBuffer.Binding, (.)bindingSlot);
|
|
VertexElement element = .(.R32G32B32_Float, "NORMAL", false, 0, bindingSlot);
|
|
elements.Add(element);
|
|
}
|
|
|
|
// TODO: validate vertex layout somewhere else
|
|
|
|
VertexElement[] vertexElements = new VertexElement[elements.Count];
|
|
for(int i < elements.Count)
|
|
{
|
|
vertexElements[i] = elements[i];
|
|
}
|
|
|
|
VertexLayout layout = new VertexLayout(vertexElements, true);
|
|
binding.SetVertexLayout(layout..ReleaseRefNoDelete());
|
|
}
|
|
|
|
// TODO: calculate tangents if missing (MikkTSpace algorithm)
|
|
// Note: Bitangent = cross(normal, tangent.xyz) * tangent.w
|
|
|
|
return binding;
|
|
}
|
|
|
|
/// Generates the normals for one triangle
|
|
static mixin GenerateTriangleNormals(int index0, int index1, int index2, CGLTF.Accessor* positions, float3[] normals)
|
|
{
|
|
float3 position0 = GetEntry<float3>(positions, (.)index0);
|
|
float3 position1 = GetEntry<float3>(positions, (.)index1);
|
|
float3 position2 = GetEntry<float3>(positions, (.)index2);
|
|
|
|
ref float3 normal0 = ref normals[(.)index0];
|
|
ref float3 normal1 = ref normals[(.)index1];
|
|
ref float3 normal2 = ref normals[(.)index2];
|
|
|
|
float3 e0 = position1 - position0;
|
|
float3 e1 = position2 - position0;
|
|
|
|
float3 normal = cross(e0, e1);
|
|
|
|
normal0 += normal;
|
|
normal1 += normal;
|
|
normal2 += normal;
|
|
}
|
|
|
|
[Inline]
|
|
static void NormalizeNormals(float3[] normals)
|
|
{
|
|
for(int i < normals.Count)
|
|
{
|
|
normalize(normals[i]);
|
|
}
|
|
}
|
|
|
|
/// Generates normals for the given model using indexed geometry
|
|
static void GenerateNormals(CGLTF.Accessor* indices, CGLTF.Accessor* positions, float3[] normals)
|
|
{
|
|
/// Enumerate triangle-wise
|
|
for(uint t = 0; t < indices.Count; t += 3)
|
|
{
|
|
int index0 = (.)CGLTF.AccessorReadIndex(indices, t);
|
|
int index1 = (.)CGLTF.AccessorReadIndex(indices, t + 1);
|
|
int index2 = (.)CGLTF.AccessorReadIndex(indices, t + 2);
|
|
|
|
GenerateTriangleNormals!(index0, index1, index2, positions, normals);
|
|
}
|
|
|
|
NormalizeNormals(normals);
|
|
}
|
|
|
|
/// Generates normals for the given model using nonindexed geometry
|
|
static void GenerateNormals(CGLTF.Accessor* positions, float3[] normals)
|
|
{
|
|
/// Enumerate triangle-wise
|
|
for(int i = 0; i < (.)positions.Count; i += 3)
|
|
{
|
|
GenerateTriangleNormals!(i, i + 1, i + 2, positions, normals);
|
|
}
|
|
|
|
NormalizeNormals(normals);
|
|
}
|
|
|
|
static Skeleton ExtractSkeleton(CGLTF.Skin* skin)
|
|
{
|
|
Skeleton skeleton = new Skeleton();
|
|
skeleton.Joints = new Joint[skin.Joints.Length];
|
|
|
|
for(int i < skin.Joints.Length)
|
|
{
|
|
ref Joint joint = ref skeleton.Joints[i];
|
|
|
|
joint.InverseBindPose = GetEntry<Matrix>(skin.InverseBindMatrices, i);
|
|
|
|
if (skin.Joints[i].Name != null)
|
|
joint.Name = new String(skin.Joints[i].Name);
|
|
|
|
int parentId = skin.Joints.IndexOf(skin.Joints[i].Parent);
|
|
|
|
Log.EngineLogger.AssertDebug(parentId < uint8.MaxValue, scope $"A skeleton must not have more than {uint8.MaxValue - 1} bones.");
|
|
|
|
if(parentId == -1)
|
|
joint.ParentID = uint8.MaxValue;
|
|
else
|
|
joint.ParentID = (uint8)parentId;
|
|
}
|
|
|
|
return skeleton;
|
|
}
|
|
|
|
static bool AnimationBelongsToSkeleton(CGLTF.Animation animation, CGLTF.Skin* skin)
|
|
{
|
|
for(var channel in animation.Channels)
|
|
{
|
|
if(skin.Joints.IndexOf(channel.TargetNode) == -1)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void LoadAnimationClips(CGLTF.Data* data, CGLTF.Skin* skin, Skeleton skeleton, List<AnimationClip> clips)
|
|
{
|
|
for(var animation in data.Animations)
|
|
{
|
|
if(!AnimationBelongsToSkeleton(animation, skin))
|
|
continue;
|
|
|
|
AnimationClip clip = new AnimationClip(skeleton);
|
|
clips.Add(clip);
|
|
clip.IsLooping = true;
|
|
|
|
for(var channel in animation.Channels)
|
|
{
|
|
int nodeIndex = skin.Joints.IndexOf(channel.TargetNode);
|
|
|
|
Log.EngineLogger.AssertDebug(nodeIndex != -1);
|
|
|
|
ref JointAnimation jointAnimation = ref clip.JointAnimations[nodeIndex];
|
|
|
|
if(jointAnimation == null)
|
|
jointAnimation = new JointAnimation();
|
|
|
|
Log.EngineLogger.AssertDebug(channel.Sampler.Input.Count == channel.Sampler.Output.Count);
|
|
|
|
int samples = (int)channel.Sampler.Input.Count;
|
|
|
|
Log.EngineLogger.AssertDebug(channel.Sampler.Input.ComponentType == .R_32f);
|
|
Log.EngineLogger.AssertDebug(channel.Sampler.Input.Type == .Scalar);
|
|
|
|
InterpolationMode interpolationMode;
|
|
|
|
switch(channel.Sampler.Interpolation)
|
|
{
|
|
case .Step:
|
|
interpolationMode = .Step;
|
|
case .Linear:
|
|
interpolationMode = .Linear;
|
|
case .CubicSpline:
|
|
interpolationMode = .CubicSpline;
|
|
}
|
|
|
|
switch(channel.TargetPath)
|
|
{
|
|
case .Translation:
|
|
jointAnimation.TranslationChannel = new .(samples, interpolationMode);
|
|
|
|
Log.EngineLogger.AssertDebug(channel.Sampler.Output.ComponentType == .R_32f);
|
|
Log.EngineLogger.AssertDebug(channel.Sampler.Output.Type == .Vec3);
|
|
|
|
for(int i < samples)
|
|
{
|
|
float timeStamp = GetEntry<float>(channel.Sampler.Input, i);
|
|
float3 sample = GetEntry<float3>(channel.Sampler.Output, i);
|
|
|
|
jointAnimation.TranslationChannel.TimeStamps[i] = timeStamp;
|
|
jointAnimation.TranslationChannel.Values[i] = sample;
|
|
}
|
|
case .Rotation:
|
|
jointAnimation.RotationChannel = new .(samples, interpolationMode);
|
|
|
|
Log.EngineLogger.AssertDebug(channel.Sampler.Output.ComponentType == .R_32f);
|
|
Log.EngineLogger.AssertDebug(channel.Sampler.Output.Type == .Vec4);
|
|
|
|
for(int i < samples)
|
|
{
|
|
float timeStamp = GetEntry<float>(channel.Sampler.Input, i);
|
|
Quaternion sample = GetEntry<Quaternion>(channel.Sampler.Output, i);
|
|
|
|
jointAnimation.RotationChannel.TimeStamps[i] = timeStamp;
|
|
jointAnimation.RotationChannel.Values[i] = sample;
|
|
}
|
|
case .Scale:
|
|
jointAnimation.ScaleChannel = new .(samples, interpolationMode);
|
|
|
|
Log.EngineLogger.AssertDebug(channel.Sampler.Output.ComponentType == .R_32f);
|
|
Log.EngineLogger.AssertDebug(channel.Sampler.Output.Type == .Vec3);
|
|
|
|
for(int i < samples)
|
|
{
|
|
float timeStamp = GetEntry<float>(channel.Sampler.Input, i);
|
|
float3 sample = GetEntry<float3>(channel.Sampler.Output, i);
|
|
|
|
jointAnimation.ScaleChannel.TimeStamps[i] = timeStamp;
|
|
jointAnimation.ScaleChannel.Values[i] = sample;
|
|
}
|
|
default:
|
|
Log.EngineLogger.Error($"Unknown channel target path \"{channel.TargetPath}\"");
|
|
}
|
|
|
|
clip.Duration = Math.Max(clip.Duration, jointAnimation.Duration);
|
|
}
|
|
}
|
|
}
|
|
|
|
static T GetEntry<T>(CGLTF.Accessor* accessor, int index)
|
|
{
|
|
Log.EngineLogger.AssertDebug((uint)index < accessor.Count);
|
|
|
|
T result = ?;
|
|
|
|
switch(typeof(T))
|
|
{
|
|
case typeof(float):
|
|
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 1);
|
|
case typeof(float2):
|
|
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 2);
|
|
case typeof(float3):
|
|
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 3);
|
|
case typeof(float4), typeof(Quaternion):
|
|
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 4);
|
|
case typeof(Matrix):
|
|
CGLTF.AccessorReadFloat(accessor, (uint)index, (float*)&result, 16);
|
|
case typeof(uint16):
|
|
CGLTF.AccessorReadUint(accessor, (uint)index, (uint32*)&result, 2);
|
|
case default:
|
|
uint8* data = (uint8*)accessor.BufferView.Buffer.Data;
|
|
|
|
data += accessor.BufferView.Offset;
|
|
|
|
data += accessor.Offset;
|
|
|
|
data += accessor.Stride * (uint)index;
|
|
|
|
result = *(T*)data;
|
|
}
|
|
|
|
return result;
|
|
|
|
/*
|
|
uint8* data = (uint8*)accessor.BufferView.Buffer.Data;
|
|
|
|
data += accessor.BufferView.Offset;
|
|
|
|
data += accessor.Offset;
|
|
|
|
data += accessor.Stride * (uint)index;
|
|
|
|
return *(T*)data;
|
|
*/
|
|
}
|
|
|
|
/**
|
|
* Converts the vector and component type to the corresponding Format.
|
|
*/
|
|
static Format FormatFromVectorComponent(CGLTF.Type vectorType, CGLTF.ComponentType componentType)
|
|
{
|
|
switch((vectorType, componentType))
|
|
{
|
|
case (.Scalar, .R_8):
|
|
return .R8_SInt;
|
|
case (.Scalar, .R_8u):
|
|
return .R8_UInt;
|
|
case (.Scalar, .R_16):
|
|
return .R16_SInt;
|
|
case (.Scalar, .R_16u):
|
|
return .R16_UInt;
|
|
case (.Scalar, .R_32u):
|
|
return .R32_UInt;
|
|
case (.Scalar, .R_32f):
|
|
return .R32_Float;
|
|
|
|
case (.Vec2, .R_8):
|
|
return .R8G8_SInt;
|
|
case (.Vec2, .R_8u):
|
|
return .R8G8_UInt;
|
|
case (.Vec2, .R_16):
|
|
return .R16G16_SInt;
|
|
case (.Vec2, .R_16u):
|
|
return .R16G16_UInt;
|
|
case (.Vec2, .R_32u):
|
|
return .R32G32_UInt;
|
|
case (.Vec2, .R_32f):
|
|
return .R32G32_Float;
|
|
|
|
//case (.Vec3, .R_8):
|
|
//case (.Vec3, .R_8u):
|
|
//case (.Vec3, .R_16):
|
|
//case (.Vec3, .R_16u):
|
|
case (.Vec3, .R_32u):
|
|
return .R32G32B32_UInt;
|
|
case (.Vec3, .R_32f):
|
|
return .R32G32B32_Float;
|
|
|
|
case (.Vec4, .R_8):
|
|
return .R8G8B8A8_SInt;
|
|
case (.Vec4, .R_8u):
|
|
return .R8G8B8A8_UInt;
|
|
case (.Vec4, .R_16):
|
|
return .R16G16B16A16_SInt;
|
|
case (.Vec4, .R_16u):
|
|
return .R16G16B16A16_UInt;
|
|
case (.Vec4, .R_32u):
|
|
return .R32G32B32A32_UInt;
|
|
case (.Vec4, .R_32f):
|
|
return .R32G32B32A32_Float;
|
|
|
|
case (.Mat4, .R_32f):
|
|
return .R32G32B32A32_Float;
|
|
|
|
default:
|
|
Log.EngineLogger.Assert(false, scope $"Unhandled vector - componenttype combination. ({vectorType}, {componentType})");
|
|
}
|
|
|
|
return .Unknown;
|
|
}
|
|
}
|
|
}
|