/// <summary> /// This needs to be called immediately before writing to json, /// but immediately after preprocessing and buffer setup, so the model can be correctly validated. /// </summary> /// <param name="model">The model to validate.</param> private void _ValidateBeforeWriting(MODEL model) { if (_NoCloneWatchdog) { return; } if (this.Validation == SharpGLTF.Validation.ValidationMode.Skip) { return; } var vcontext = new Validation.ValidationResult(model, this.Validation); model.ValidateReferences(vcontext.GetContext()); var ex = vcontext.Errors.FirstOrDefault(); if (ex != null) { throw ex; } model.ValidateContent(vcontext.GetContext()); ex = vcontext.Errors.FirstOrDefault(); if (ex != null) { throw ex; } }
/// <summary> /// Writes <paramref name="model"/> to this context. /// </summary> /// <param name="baseName">The base name to use for asset files, without extension.</param> /// <param name="model">The <see cref="MODEL"/> to write.</param> public void WriteBinarySchema2(string baseName, MODEL model) { Guard.NotNullOrEmpty(baseName, nameof(baseName)); Guard.NotNull(model, nameof(model)); model = this._PreprocessSchema2(model, this.ImageWriting == ResourceWriteMode.BufferView, true, int.MaxValue); Guard.NotNull(model, nameof(model)); var ex = _BinarySerialization.IsBinaryCompatible(model); if (ex != null) { throw ex; } model._PrepareBuffersForInternalWriting(); model._PrepareImagesForWriting(this, baseName, ResourceWriteMode.Embedded); _ValidateBeforeWriting(model); using (var m = new MemoryStream()) { using (var w = new BinaryWriter(m)) { _BinarySerialization.WriteBinaryModel(w, model); } WriteAllBytesToEnd($"{baseName}.glb", m.ToArraySegment()); } model._AfterWriting(); }
public Mesh Load(Game game, string name) { unsafe { ModelRoot root; using (var stream = game.GetResourceStream(name)) root = ModelRoot.ReadGLB(stream, new()); var primitive = root.LogicalMeshes[0].Primitives[0]; var indices = primitive.IndexAccessor; var vertices = primitive.VertexAccessors["POSITION"]; var normals = primitive.VertexAccessors["NORMAL"]; var vertexArray = VertexArray.Gen(); using (vertexArray.Bind()) { Buffer.CreateFromData(indices.SourceBufferView.Content, BufferTargetARB.ElementArrayBuffer); Buffer.CreateFromData(vertices.SourceBufferView.Content); Gfx.Gl.EnableVertexAttribArray(PositionAttribIndex); Gfx.Gl.VertexAttribPointer(PositionAttribIndex, 3, (VertexAttribPointerType)vertices.Encoding, vertices.Normalized, (uint)vertices.SourceBufferView.ByteStride, (void *)vertices.ByteOffset); Buffer.CreateFromData(normals.SourceBufferView.Content); Gfx.Gl.EnableVertexAttribArray(NormalAttribIndex); Gfx.Gl.VertexAttribPointer(NormalAttribIndex, 3, (VertexAttribPointerType)vertices.Encoding, vertices.Normalized, (uint)vertices.SourceBufferView.ByteStride, (void *)vertices.ByteOffset); } var numTriangles = primitive.IndexAccessor.Count / 3; return(new(vertexArray, numTriangles)); } }
public Mesh Load(Game game, string name) { unsafe { ModelRoot root; using (var stream = game.GetResourceStream(name)) root = ModelRoot.ReadGLB(stream, new SharpGLTF.Schema2.ReadSettings()); var primitive = root.LogicalMeshes[0].Primitives[0]; var indices = primitive.IndexAccessor; var vertices = primitive.VertexAccessors["POSITION"]; var normals = primitive.VertexAccessors["NORMAL"]; var vertexArray = VertexArray.Gen(); using (vertexArray.Bind()) { Buffer.CreateFromData(indices.SourceBufferView.Content, BufferTargetARB.ElementArrayBuffer); Buffer.CreateFromData(vertices.SourceBufferView.Content); GFX.GL.EnableVertexAttribArray(POSITION_ATTRIB_INDEX); GFX.GL.VertexAttribPointer(POSITION_ATTRIB_INDEX, 3, (GLEnum)vertices.Encoding, vertices.Normalized, (uint)vertices.SourceBufferView.ByteStride, (void *)vertices.ByteOffset); Buffer.CreateFromData(normals.SourceBufferView.Content); GFX.GL.EnableVertexAttribArray(NORMAL_ATTRIB_INDEX); GFX.GL.VertexAttribPointer(NORMAL_ATTRIB_INDEX, 3, (GLEnum)vertices.Encoding, vertices.Normalized, (uint)vertices.SourceBufferView.ByteStride, (void *)vertices.ByteOffset); } var numVertices = vertices.Count; var numTriangles = primitive.IndexAccessor.Count / 3; return(new Mesh(vertexArray, numTriangles)); } }
/// <summary> /// Writes <paramref name="model"/> to this context using the glTF json container. /// </summary> /// <param name="baseName">The base name to use for asset files, without extension.</param> /// <param name="model">The <see cref="MODEL"/> to write.</param> /// <remarks> /// If the model has associated resources like binary assets and textures,<br/> /// these additional resources will be also written as associated files using the pattern:<br/> /// <br/> /// "<paramref name="baseName"/>.{Number}.bin|png|jpg|dds" /// </remarks> public void WriteTextSchema2(string baseName, MODEL model) { Guard.NotNullOrEmpty(baseName, nameof(baseName)); Guard.NotNull(model, nameof(model)); // merge images when explicitly requested. var mergeImages = this.ImageWriting == ResourceWriteMode.BufferView; model = this._PreprocessSchema2(model, mergeImages, this.MergeBuffers, this.BuffersMaxSize); Guard.NotNull(model, nameof(model)); model._PrepareBuffersForSatelliteWriting(this, baseName); model._PrepareImagesForWriting(this, baseName, false, ResourceWriteMode.SatelliteFile); _ValidateBeforeWriting(model); using (var m = new MemoryStream()) { model._WriteJSON(m, this.JsonOptions, this.JsonPostprocessor); WriteAllBytesToEnd($"{baseName}.gltf", m.ToArraySegment()); } model._AfterWriting(); }
public static List <Material> LoadMaterials(SharpGLTF.Schema2.ModelRoot root, GraphicsDevice device) { var result = new List <Material>(); foreach (var mat in root.LogicalMaterials) { var material = new MaterialDescriptor { Attributes = new MaterialAttributes() }; foreach (var chan in mat.Channels) { if (chan.Texture != null) { var imgBuf = chan.Texture.PrimaryImage.Content.Content.ToArray(); var imgPtr = new DataPointer(GCHandle.Alloc(imgBuf, GCHandleType.Pinned).AddrOfPinnedObject(), imgBuf.Length); var image = Stride.Graphics.Image.Load(imgPtr); var texture = Texture.New(device, image, TextureFlags.None); switch (chan.Key) { case "BaseColor": var vt = new ComputeTextureColor(texture) { AddressModeU = TextureAddressMode.Wrap, AddressModeV = TextureAddressMode.Wrap, TexcoordIndex = TextureCoordinate.Texcoord0 }; material.Attributes.Diffuse = new MaterialDiffuseMapFeature(vt); material.Attributes.DiffuseModel = new MaterialDiffuseLambertModelFeature(); break; case "MetallicRoughness": material.Attributes.MicroSurface = new MaterialGlossinessMapFeature(new ComputeTextureScalar(texture, TextureCoordinate.Texcoord0, Vector2.One, Vector2.Zero)); break; case "Normal": material.Attributes.Surface = new MaterialNormalMapFeature(new ComputeTextureColor(texture)); break; case "Occlusion": material.Attributes.Occlusion = new MaterialOcclusionMapFeature(); break; case "Emissive": material.Attributes.Emissive = new MaterialEmissiveMapFeature(new ComputeTextureColor(texture)); break; } } } material.Attributes.CullMode = CullMode.Back; result.Add(Material.New(device, material)); } return(result); }
/// <summary> /// Prepares the model for writing with the appropiate settings, creating a defensive copy if neccesary. /// </summary> /// <param name="model">The source <see cref="MODEL"/> instance.</param> /// <param name="imagesAsBufferViews">true if images should be stored as buffer views.</param> /// <param name="mergeBuffers">true if it's required the model must have a single buffer.</param> /// <param name="buffersMaxSize">When merging buffers, the max buffer size</param> /// <returns>The source <see cref="MODEL"/> instance, or a cloned and modified instance if current settings required it.</returns> private MODEL _PreprocessSchema2(MODEL model, bool imagesAsBufferViews, bool mergeBuffers, int buffersMaxSize) { Guard.NotNull(model, nameof(model)); foreach (var img in model.LogicalImages) { if (!img._HasContent) { throw new Validation.DataException(img, "Image Content is missing."); } } // check if we need to modify the model before saving it, // in order to create a defensive copy. if (model.LogicalImages.Count == 0) { imagesAsBufferViews = false; } if (model.LogicalBuffers.Count <= 1 && !imagesAsBufferViews) { mergeBuffers = false; } if (mergeBuffers | imagesAsBufferViews) { // cloning check is done to prevent cloning from entering in an infinite loop where each clone attempt triggers another clone request. if (_NoCloneWatchdog) { throw new InvalidOperationException($"Current settings require creating a densive copy before model modification, but calling {nameof(MODEL.DeepClone)} is not allowed with the current settings."); } model = model.DeepClone(); } if (imagesAsBufferViews) { model.MergeImages(); } if (mergeBuffers) { if (buffersMaxSize == int.MaxValue) { model.MergeBuffers(); } else { model.MergeBuffers(buffersMaxSize); } } if (this._UpdateSupportedExtensions) { model.UpdateExtensionsSupport(); } return(model); }
/// <summary> /// Writes a <see cref="MODEL"/> instance into a <see cref="BinaryWriter"/>. /// </summary> /// <param name="binaryWriter">The destination <see cref="BinaryWriter"/> stream.</param> /// <param name="model">The source <see cref="MODEL"/> instance.</param> public static void WriteBinaryModel(this BinaryWriter binaryWriter, MODEL model) { var ex = IsBinaryCompatible(model); if (ex != null) { throw ex; } var jsonText = model.GetJSON(false); var jsonChunk = Encoding.UTF8.GetBytes(jsonText); var jsonPadding = jsonChunk.Length & 3; if (jsonPadding != 0) { jsonPadding = 4 - jsonPadding; } var buffer = model.LogicalBuffers.Count > 0 ? model.LogicalBuffers[0].Content : null; if (buffer != null && buffer.Length == 0) { buffer = null; } var binPadding = buffer == null ? 0 : buffer.Length & 3; if (binPadding != 0) { binPadding = 4 - binPadding; } int fullLength = 4 + 4 + 4; fullLength += 8 + jsonChunk.Length + jsonPadding; if (buffer != null) { fullLength += 8 + buffer.Length + binPadding; } binaryWriter.Write(GLTFHEADER); binaryWriter.Write(GLTFVERSION2); binaryWriter.Write(fullLength); binaryWriter.Write(jsonChunk.Length + jsonPadding); binaryWriter.Write(CHUNKJSON); binaryWriter.Write(jsonChunk); for (int i = 0; i < jsonPadding; ++i) { binaryWriter.Write((Byte)0x20); } if (buffer != null) { binaryWriter.Write(buffer.Length + binPadding); binaryWriter.Write(CHUNKBIN); binaryWriter.Write(buffer); for (int i = 0; i < binPadding; ++i) { binaryWriter.Write((Byte)0); } } }
public DeviceModelCollection ConvertToDevice(SharpGLTF.Schema2.ModelRoot srcModel, DeviceMeshFactory meshFactory) { if (meshFactory == null) { throw new ArgumentNullException(); } var content = ConvertToContent(srcModel); return(DeviceModelCollection.CreateFrom(content, meshFactory.CreateMeshCollection)); }
public override void Start() { //Entity.Transform.Position = Vector3.UnitY; Log.ActivateLog(LogMessageType.Info); // var fox_glb = SharpGLTF.Schema2.ModelRoot.Load("D:/Downloads/glTF/cube/AnimatedCube.gltf"); //fox_glb = SharpGLTF.Schema2.ModelRoot.Load("D:/Downloads/glTF/fox/Fox.gltf"); // var fox_glb = SharpGLTF.Schema2.ModelRoot.Load("D:/Downloads/glTF/icosphere/icosphere.gltf"); // var fox_glb = SharpGLTF.Schema2.ModelRoot.Load("D:/Downloads/glTF/torus/torus.gltf"); //fox_glb = SharpGLTF.Schema2.ModelRoot.Load("C:/Users/kafia/Documents/blender try/SimpleCube.gltf"); //fox_glb = SharpGLTF.Schema2.ModelRoot.ReadGLB(File.OpenRead("D:/codeProj/xenproj/GltfImport/GltfImport/Resources/Fox.glb")); fox_glb = SharpGLTF.Schema2.ModelRoot.Load("D:/codeProj/xenproj/GltfImport/GltfImport/Resources/FoxEmbedded.gltf"); }
public static Model LoadFirstModel(GraphicsDevice device, SharpGLTF.Schema2.ModelRoot root) { var result = new Model { Meshes = root.LogicalMeshes.First().Primitives .Select(x => LoadMesh(x, device)) .ToList() }; LoadMaterials(root, device).ForEach(x => result.Add(x)); //result.Add(RedMaterial(device)); //result.Meshes.ForEach(x => x.MaterialIndex = 0); result.Skeleton = ConvertSkeleton(root); return(result); }
/// <summary> /// Tells if a given model can be stored as Binary format. /// </summary> /// <param name="model">the model to test</param> /// <returns>null if it can be stored as binary, or an exception object if it can't</returns> /// <remarks> /// Due to the limitations of Binary Format, not all models can be saved as Binary. /// </remarks> public static Exception IsBinaryCompatible(MODEL model) { try { Guard.NotNull(model, nameof(model)); Guard.IsTrue(model.LogicalBuffers.Count <= 1, nameof(model), $"GLB format only supports one binary buffer, {model.LogicalBuffers.Count} found. It can be solved by calling {nameof(ModelRoot.MergeImages)} and {nameof(ModelRoot.MergeBuffers)}"); } catch (ArgumentException ex) { return(ex); } // todo: buffer[0].Uri must be null return(null); }
public ModelCollectionContent ConvertToContent(SharpGLTF.Schema2.ModelRoot srcModel) { // process materials and meshes. var materialsBuilder = new GLTFMaterialsFactory(TagConverter); var meshesDecoders = srcModel.LogicalMeshes.ToXnaDecoders(materialsBuilder, TagConverter); var meshesContent = MeshCollectionBuilder.CreateContent(meshesDecoders); var materialsContent = materialsBuilder.CreateContent(); // build the armatures and models var armatures = new List <ArmatureContent>(); var models = new List <ModelContent>(); foreach (var scene in srcModel.LogicalScenes) { var armatureFactory = new GLTFArmatureFactory(scene, TagConverter); for (int i = 0; i < srcModel.LogicalAnimations.Count; ++i) { var track = srcModel.LogicalAnimations[i]; armatureFactory.SetAnimationTrack(i, track.Name, TagConverter?.Invoke(track), track.Duration); } // TODO: check if we can share armatures var armature = armatureFactory.CreateArmature(); armatures.Add(armature); var model = armatureFactory.CreateModelContent(scene, armatures.Count - 1); model.Name = scene.Name; model.Tag = TagConverter?.Invoke(scene); models.Add(model); } // coalesce all resources into a container class: var content = new ModelCollectionContent(materialsContent, meshesContent, armatures.ToArray(), models.ToArray(), srcModel.DefaultScene.LogicalIndex); content = PostProcessor.Postprocess(content); return(content); }
public static Skeleton ConvertSkeleton(SharpGLTF.Schema2.ModelRoot root) { Skeleton result = new Skeleton(); var skin = root.LogicalNodes.First(x => x.Mesh == root.LogicalMeshes.First()).Skin; var jointList = Enumerable.Range(0, skin.JointsCount).Select(x => skin.GetJoint(x).Joint).ToList(); var mnd = jointList .Select( x => new ModelNodeDefinition { Name = x.Name, Flags = ModelNodeFlags.Default, ParentIndex = jointList.IndexOf(x.VisualParent) + 1, Transform = new TransformTRS { Position = ConvertNumerics(x.LocalTransform.Translation), Rotation = ConvertNumerics(x.LocalTransform.Rotation), Scale = ConvertNumerics(x.LocalTransform.Scale) } } ) .ToList(); mnd.Insert( 0, new ModelNodeDefinition { Name = "Armature", Flags = ModelNodeFlags.EnableRender, ParentIndex = -1, Transform = new TransformTRS { Position = Vector3.Zero, Rotation = Quaternion.Identity, Scale = Vector3.Zero } }); result.Nodes = mnd.ToArray(); return(result); }
public static MeshSkinningDefinition ConvertInverseBindMatrices(SharpGLTF.Schema2.ModelRoot root) { var skin = root.LogicalNodes.First(x => x.Mesh == root.LogicalMeshes.First()).Skin; var jointList = Enumerable.Range(0, skin.JointsCount).Select(skin.GetJoint); var mnt = new MeshSkinningDefinition { Bones = jointList .Select((x, i) => new MeshBoneDefinition { NodeIndex = i + 1, LinkToMeshMatrix = ConvertNumerics(x.InverseBindMatrix) } ) .ToArray() }; return(mnt); }
/// <summary> /// Writes <paramref name="model"/> to this context. /// </summary> /// <param name="baseName">The base name to use for asset files, without extension.</param> /// <param name="model">The <see cref="MODEL"/> to write.</param> public void WriteTextSchema2(string baseName, MODEL model) { Guard.NotNullOrEmpty(baseName, nameof(baseName)); Guard.NotNull(model, nameof(model)); model = this._PreprocessSchema2(model, this.ImageWriting == ResourceWriteMode.BufferView, this.MergeBuffers); Guard.NotNull(model, nameof(model)); model._PrepareBuffersForSatelliteWriting(this, baseName); model._PrepareImagesForWriting(this, baseName, ResourceWriteMode.SatelliteFile); _ValidateBeforeWriting(model); using (var m = new MemoryStream()) { model._WriteJSON(m, this.JsonIndented); WriteAllBytesToEnd($"{baseName}.gltf", m.ToArraySegment()); } model._AfterWriting(); }
public static Dictionary <string, AnimationClip> ConvertAnimations(SharpGLTF.Schema2.ModelRoot root) { var animations = root.LogicalAnimations; var clips = animations .Select(x => { //Create animation clip with var clip = new AnimationClip { Duration = TimeSpan.FromSeconds(x.Duration) }; clip.RepeatMode = AnimationRepeatMode.LoopInfinite; // Add Curve ConvertCurves(x.Channels, root).ToList().ForEach(x => clip.AddCurve(x.Key, x.Value)); return(x.Name, clip); } ) .ToList() .ToDictionary(x => x.Name, x => x.clip); return(clips); }
private (MODEL Model, Validation.ValidationResult Validation) _Read(ReadOnlyMemory <Byte> jsonUtf8Bytes) { var root = new MODEL(); var vcontext = new Validation.ValidationResult(root, this.Validation); #if !SUPRESSTRYCATCH try { #endif if (jsonUtf8Bytes.IsEmpty) { throw new System.Text.Json.JsonException("JSon is empty."); } var reader = new Utf8JsonReader(jsonUtf8Bytes.Span); if (!reader.Read()) { vcontext.SetError(new Validation.SchemaException(root, "Json is empty")); return(null, vcontext); } root.Deserialize(ref reader); root.OnDeserializationCompleted(); // binary chunk check foreach (var b in root.LogicalBuffers) { b.OnValidateBinaryChunk(vcontext.GetContext(), this._BinaryChunk); } // schema validation if (this._CheckSupportedExtensions) { root._ValidateExtensions(vcontext.GetContext()); var ex = vcontext.Errors.FirstOrDefault(); if (ex != null) { return(null, vcontext); } } // we must do a basic validation before resolving external dependencies if (true) { root.ValidateReferences(vcontext.GetContext()); var ex = vcontext.Errors.FirstOrDefault(); if (ex != null) { return(null, vcontext); } } // resolve external dependencies root._ResolveSatelliteDependencies(this); // full validation if (this.Validation != VALIDATIONMODE.Skip) { root.ValidateContent(vcontext.GetContext()); var ex = vcontext.Errors.FirstOrDefault(); if (ex != null) { return(null, vcontext); } } #if !SUPRESSTRYCATCH } catch (JsonException rex) { vcontext.SetError(new Validation.SchemaException(root, rex)); return(null, vcontext); } catch (System.FormatException fex) { vcontext.SetError(new Validation.ModelException(null, fex)); return(null, vcontext); } catch (ArgumentException aex) { vcontext.SetError(new Validation.ModelException(root, aex)); return(null, vcontext); } catch (Validation.ModelException mex) { vcontext.SetError(mex); return(null, vcontext); } #endif return(root, vcontext); }
public static Dictionary <string, AnimationCurve> ConvertCurves(IReadOnlyList <SharpGLTF.Schema2.AnimationChannel> channels, SharpGLTF.Schema2.ModelRoot root) { var result = new Dictionary <string, AnimationCurve>(); string baseString = "[ModelComponent.Key].Skeleton.NodeTransformations[index].Transform.type"; var skin = root.LogicalNodes.First(x => x.Mesh == root.LogicalMeshes.First()).Skin; var jointList = Enumerable.Range(0, skin.JointsCount).Select(x => skin.GetJoint(x).Joint).ToList(); foreach (var chan in channels) { var index = jointList.IndexOf(chan.TargetNode) + 1; switch (chan.TargetNodePath) { case SharpGLTF.Schema2.PropertyPath.translation: result.Add(baseString.Replace("index", $"{index}").Replace("type", "Position"), ConvertCurve(chan.GetTranslationSampler())); break; case SharpGLTF.Schema2.PropertyPath.rotation: result.Add(baseString.Replace("index", $"{index}").Replace("type", "Rotation"), ConvertCurve(chan.GetRotationSampler())); break; case SharpGLTF.Schema2.PropertyPath.scale: result.Add(baseString.Replace("index", $"{index}").Replace("type", "Scale"), ConvertCurve(chan.GetScaleSampler())); break; } ; } return(result); }
public DeviceModelCollection ReadModel(SharpGLTF.Schema2.ModelRoot model) { var factory = UseBasicEffects ? (DeviceMeshFactory) new ClassicMeshFactory(_Device) : new PBRMeshFactory(_Device); return(ConvertToDevice(model, factory)); }