diff --git a/Editor/Services/WorldService.cs b/Editor/Services/WorldService.cs index 74644e05..50e3e394 100644 --- a/Editor/Services/WorldService.cs +++ b/Editor/Services/WorldService.cs @@ -38,6 +38,7 @@ public async void ParseWorld(string yaml) .WithTypeConverter(new ObservableListConverter( new IYamlTypeConverter[] { + new RotationYamlConverter(), new Vec4YamlConverter(), new Vec3YamlConverter(), new Vec2YamlConverter(), diff --git a/Editor/Utility/EngineTypes.cs b/Editor/Utility/EngineTypes.cs index b80a02ca..656fa1d6 100644 --- a/Editor/Utility/EngineTypes.cs +++ b/Editor/Utility/EngineTypes.cs @@ -10,6 +10,7 @@ using System.Globalization; using YamlDotNet.Serialization.NamingConventions; using SailorEngine; +using System.Xml.Linq; namespace SailorEngine { @@ -22,6 +23,11 @@ public class Property : PropertyBase }; public class FloatProperty : Property { } + public class RotationProperty : Property + { + public RotationProperty(Quat defaultValue) => DefaultValue = new Rotation(defaultValue); + } + public class Vec4Property : Property { } public class Vec3Property : Property { } public class Vec2Property : Property { } @@ -103,48 +109,57 @@ public static EngineTypes FromYaml(string yamlContent) .IgnoreUnmatchedProperties() .Build(); - var rootNode = deserializer.Deserialize(yamlContent); - var res = new EngineTypes(); - foreach (var component in rootNode.EngineTypes) + try { - var newComponent = new ComponentType - { - Name = component.Typename - }; + var rootNode = deserializer.Deserialize(yamlContent); - foreach (var property in component.Properties) + foreach (var component in rootNode.EngineTypes) { - PropertyBase newProperty = property.Value switch + var newComponent = new ComponentType { - "struct glm::vec<2,float,0>" => new Vec2Property(), - "struct glm::vec<3,float,0>" => new Vec3Property(), - "struct glm::vec<4,float,0>" => new Vec4Property(), - "float" => new FloatProperty(), - var value when value.StartsWith("class Sailor::TObjectPtr") => new ObjectPtrProperty(), - var value when value.Contains("TObjectPtr") => new InstanceIdProperty(), - var value when value.StartsWith("enum") => new EnumProperty() { Typename = value }, - _ => throw new InvalidOperationException($"Unexpected property type: {property.Value}") + Name = component.Typename }; - newComponent.Properties[property.Key] = newProperty; + foreach (var property in component.Properties) + { + PropertyBase newProperty = property.Value switch + { + "struct glm::qua" => new RotationProperty(rootNode.Cdos.Find((a) => a.Typename == component.Typename).DefaultValues[property.Key] as Quat ?? default), + "struct glm::vec<2,float,0>" => new Vec2Property(), + "struct glm::vec<3,float,0>" => new Vec3Property(), + "struct glm::vec<4,float,0>" => new Vec4Property(), + "float" => new FloatProperty(), + var value when value.StartsWith("class Sailor::TObjectPtr") => new ObjectPtrProperty(), + var value when value.Contains("TObjectPtr") => new InstanceIdProperty(), + var value when value.StartsWith("enum") => new EnumProperty() { Typename = value }, + _ => throw new InvalidOperationException($"Unexpected property type: {property.Value}") + }; + + + newComponent.Properties[property.Key] = newProperty; + } + + newComponent.Properties["fileId"] = new FileIdProperty() { DefaultValue = "NullFileId" }; + newComponent.Properties["instanceId"] = new InstanceIdProperty(); + + res.Components[component.Typename] = newComponent; } - newComponent.Properties["fileId"] = new FileIdProperty() { DefaultValue = "NullFileId" }; - newComponent.Properties["instanceId"] = new InstanceIdProperty(); + foreach (var enumNode in rootNode.Enums) + { + foreach (var enumEntry in enumNode) + { + res.Enums[enumEntry.Key] = enumEntry.Value; + } + } - res.Components[component.Typename] = newComponent; } - - foreach (var enumNode in rootNode.Enums) + catch (Exception ex) { - foreach (var enumEntry in enumNode) - { - res.Enums[enumEntry.Key] = enumEntry.Value; - } + Console.WriteLine(ex.Message); } - return res; } @@ -164,13 +179,195 @@ public class EngineTypeNode public class ComponentDefaultValuesNode { public string Typename { get; set; } - public Dictionary DefaultValues { get; set; } + public Dictionary DefaultValues { get; set; } } }; } namespace SailorEditor { + public partial class Rotation : ObservableObject, ICloneable, IComparable + { + public Rotation() { } + public Rotation(Quat value) => Quat = value; + + public Quat Quat + { + get => quat; + set + { + if (quat != value && SetProperty(ref quat, value)) + { + UpdateYawPitchRoll(); + OnPropertyChanged(nameof(Yaw)); + OnPropertyChanged(nameof(Pitch)); + OnPropertyChanged(nameof(Roll)); + } + } + } + + public float Yaw + { + get => yaw; + set + { + if (yaw != value) + { + yaw = value; + UpdateQuat(); + OnPropertyChanged(); + } + } + } + + public float Pitch + { + get => pitch; + set + { + if (pitch != value) + { + pitch = value; + UpdateQuat(); + OnPropertyChanged(); + } + } + } + + public float Roll + { + get => roll; + set + { + if (roll != value) + { + roll = value; + UpdateQuat(); + OnPropertyChanged(); + } + } + } + + public static implicit operator Rotation(Quat value) => new() { Quat = value }; + public static implicit operator Quat(Rotation uniform) => new(uniform.Quat); + public object Clone() => new Rotation { Quat = Quat }; + + public override string ToString() => $""; + + public int CompareTo(Rotation other) => Quat.CompareTo(other.Quat); + + private void UpdateQuat() => quat = Quat.FromYawPitchRoll(yaw, pitch, roll); + private void UpdateYawPitchRoll() => (yaw, pitch, roll) = quat.ToYawPitchRoll(); + + float yaw = 0.0f; + float pitch = 0.0f; + float roll = 0.0f; + + Quat quat; + } + + public partial class Quat : ObservableObject, ICloneable, IComparable + { + public Quat() { } + public Quat(Quaternion value) + { + X = value.X; + Y = value.Y; + Z = value.Z; + W = value.W; + } + + public Quat(float x, float y, float z, float w) + { + X = x; + Y = y; + Z = z; + W = w; + } + + public static implicit operator Quat(Quaternion value) => new() { X = value.X, Y = value.Y, Z = value.Z, W = value.W }; + public static implicit operator Quaternion(Quat uniform) => new(uniform.X, uniform.Y, uniform.Z, uniform.W); + public object Clone() => new Quat { X = X, Y = Y, Z = Z, W = W }; + public override string ToString() => $"<{X} {Y} {Z} {W}>"; + + public int CompareTo(Quat other) + { + if (other == null) return 1; + + int result = X.CompareTo(other.X); + if (result != 0) return result; + + result = Y.CompareTo(other.Y); + if (result != 0) return result; + + result = Z.CompareTo(other.Z); + if (result != 0) return result; + + return W.CompareTo(other.W); + } + + public (float yaw, float pitch, float roll) ToYawPitchRoll() + { + // Convert quaternion to yaw, pitch, roll + float ysqr = Y * Y; + + // Roll (x-axis rotation) + float t0 = +2.0f * (W * X + Y * Z); + float t1 = +1.0f - 2.0f * (X * X + ysqr); + float roll = MathF.Atan2(t0, t1); + + // Pitch (y-axis rotation) + float t2 = +2.0f * (W * Y - Z * X); + t2 = t2 > 1.0f ? 1.0f : t2; + t2 = t2 < -1.0f ? -1.0f : t2; + float pitch = MathF.Asin(t2); + + // Yaw (z-axis rotation) + float t3 = +2.0f * (W * Z + X * Y); + float t4 = +1.0f - 2.0f * (ysqr + Z * Z); + float yaw = MathF.Atan2(t3, t4); + + // Convert from radians to degrees + return (Sailor.Mathf.ToDegrees(yaw), Sailor.Mathf.ToDegrees(pitch), Sailor.Mathf.ToDegrees(roll)); + } + + public static Quat FromYawPitchRoll(float yaw, float pitch, float roll) + { + float yawRad = Sailor.Mathf.ToRadians(yaw); + float pitchRad = Sailor.Mathf.ToRadians(pitch); + float rollRad = Sailor.Mathf.ToRadians(roll); + + float cy = Sailor.Mathf.Cos(yawRad * 0.5f); + float sy = Sailor.Mathf.Sin(yawRad * 0.5f); + float cp = Sailor.Mathf.Cos(pitchRad * 0.5f); + float sp = Sailor.Mathf.Sin(pitchRad * 0.5f); + float cr = Sailor.Mathf.Cos(rollRad * 0.5f); + float sr = Sailor.Mathf.Sin(rollRad * 0.5f); + + Quat quat = new Quat + { + W = cr * cp * cy + sr * sp * sy, + X = sr * cp * cy - cr * sp * sy, + Y = cr * sp * cy + sr * cp * sy, + Z = cr * cp * sy - sr * sp * cy + }; + + return quat; + } + + [ObservableProperty] + float x = 0.0f; + + [ObservableProperty] + float y = 0.0f; + + [ObservableProperty] + float z = 0.0f; + + [ObservableProperty] + float w = 0.0f; + } + public partial class Vec4 : ObservableObject, ICloneable, IComparable { public Vec4() { } @@ -387,6 +584,72 @@ public void WriteYaml(IEmitter emitter, object value, Type type) } } + public class QuatYamlConverter : IYamlTypeConverter + { + public bool Accepts(Type type) => type == typeof(Quat); + + public object ReadYaml(IParser parser, Type type) + { + var list = new List(); + parser.Consume(); + while (parser.Current is SequenceEnd == false) + { + if (parser.Current is Scalar scalar) + { + list.Add(float.Parse(scalar.Value, CultureInfo.InvariantCulture.NumberFormat)); + } + parser.MoveNext(); + } + parser.Consume(); + + return new Quat { X = list[0], Y = list[1], Z = list[2], W = list[3] }; + } + + public void WriteYaml(IEmitter emitter, object value, Type type) + { + var vec = (Quat)value; + emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Block)); + emitter.Emit(new Scalar(null, vec.X.ToString())); + emitter.Emit(new Scalar(null, vec.Y.ToString())); + emitter.Emit(new Scalar(null, vec.Z.ToString())); + emitter.Emit(new Scalar(null, vec.W.ToString())); + emitter.Emit(new SequenceEnd()); + } + } + + public class RotationYamlConverter : IYamlTypeConverter + { + public bool Accepts(Type type) => type == typeof(Rotation); + + public object ReadYaml(IParser parser, Type type) + { + var list = new List(); + parser.Consume(); + while (parser.Current is SequenceEnd == false) + { + if (parser.Current is Scalar scalar) + { + list.Add(float.Parse(scalar.Value, CultureInfo.InvariantCulture.NumberFormat)); + } + parser.MoveNext(); + } + parser.Consume(); + + return new Rotation(new Quat { X = list[0], Y = list[1], Z = list[2], W = list[3] }); + } + + public void WriteYaml(IEmitter emitter, object value, Type type) + { + var quat = ((Rotation)value).Quat; + emitter.Emit(new SequenceStart(null, null, false, SequenceStyle.Block)); + emitter.Emit(new Scalar(null, quat.X.ToString())); + emitter.Emit(new Scalar(null, quat.Y.ToString())); + emitter.Emit(new Scalar(null, quat.Z.ToString())); + emitter.Emit(new Scalar(null, quat.W.ToString())); + emitter.Emit(new SequenceEnd()); + } + } + public class ComponentTypeYamlConverter : IYamlTypeConverter { public bool Accepts(Type type) => type == typeof(SailorEngine.ComponentType); diff --git a/Editor/Utility/Math.cs b/Editor/Utility/Math.cs new file mode 100644 index 00000000..5064a85f --- /dev/null +++ b/Editor/Utility/Math.cs @@ -0,0 +1,61 @@ +using System; + +namespace Sailor +{ + public static class Mathf + { + public const float PI = 3.14159265358979323846f; + + public static float Acos(float d) => (float)Math.Acos(d); + + public static float Asin(float d) => (float)Math.Asin(d); + + public static float Atan(float d) => (float)Math.Atan(d); + + public static float Atan2(float y, float x) => (float)Math.Atan2(y, x); + + public static float Ceiling(float a) => (float)Math.Ceiling(a); + + public static float Cos(float d) => (float)Math.Cos(d); + + public static float Cosh(float value) => (float)Math.Cosh(value); + + public static float Floor(float d) => (float)Math.Floor(d); + + public static float Sin(float a) => (float)Math.Sin(a); + + public static float Tan(float a) => (float)Math.Tan(a); + + public static float Sinh(float value) => (float)Math.Sinh(value); + + public static float Tanh(float value) => (float)Math.Tanh(value); + + public static float Round(float a) => (float)Math.Round(a); + + public static float Truncate(float d) => (float)Math.Truncate(d); + + public static float Sqrt(float d) => (float)Math.Sqrt(d); + + public static float Log(float d) => (float)Math.Log(d); + + public static float Log10(float d) => (float)Math.Log10(d); + + public static float Exp(float d) => (float)Math.Exp(d); + + public static float Pow(float x, float y) => (float)Math.Pow(x, y); + + public static float IEEERemainder(float x, float y) => (float)Math.IEEERemainder(x, y); + + public static float Abs(float value) => (float)Math.Abs(value); + + public static float Max(float val1, float val2) => (float)Math.Max(val1, val2); + + public static float Min(float val1, float val2) => (float)Math.Min(val1, val2); + + public static float Log(float a, float newBase) => (float)Math.Log(a, newBase); + + public static float ToRadians(float degrees) => degrees * (PI / 180.0f); + + public static float ToDegrees(float radians) => radians * (180.0f / PI); + } +} diff --git a/Editor/Utility/Templates.cs b/Editor/Utility/Templates.cs index 28214fcd..10f4c0ba 100644 --- a/Editor/Utility/Templates.cs +++ b/Editor/Utility/Templates.cs @@ -244,6 +244,35 @@ public static View Vec3Editor(Func conve return stackLayout; } + public static View RotationEditor(Func convert) + { + var valueXEntry = new Entry(); + valueXEntry.Bind(Entry.TextProperty, + getter: (TBindingContext vm) => convert(vm).Yaw, + setter: (TBindingContext vm, float value) => convert(vm).Yaw = value, + mode: BindingMode.TwoWay, + converter: new FloatValueConverter()); + + var valueYEntry = new Entry(); + valueYEntry.Bind(Entry.TextProperty, + getter: (TBindingContext vm) => convert(vm).Pitch, + setter: (TBindingContext vm, float value) => convert(vm).Pitch = value, + mode: BindingMode.TwoWay, + converter: new FloatValueConverter()); + + var valueZEntry = new Entry(); + valueZEntry.Bind(Entry.TextProperty, + getter: (TBindingContext vm) => convert(vm).Roll, + setter: (TBindingContext vm, float value) => convert(vm).Roll = value, + mode: BindingMode.TwoWay, + converter: new FloatValueConverter()); + + var stackLayout = new HorizontalStackLayout(); + stackLayout.Children.Add(new HorizontalStackLayout { Children = { valueXEntry, valueYEntry, valueZEntry } }); + + return stackLayout; + } + public static View Vec4Editor(Func convert) { var valueXEntry = new Entry(); diff --git a/Editor/ViewModels/Component.cs b/Editor/ViewModels/Component.cs index 47d9e1ea..da8e6fbe 100644 --- a/Editor/ViewModels/Component.cs +++ b/Editor/ViewModels/Component.cs @@ -49,6 +49,7 @@ public object ReadYaml(IParser parser, Type type) .WithTypeConverter(new ObservableDictionaryConverter()) .WithTypeConverter(new ComponentTypeYamlConverter()) .WithTypeConverter(new FileIdYamlConverter()) + .WithTypeConverter(new RotationYamlConverter()) .WithTypeConverter(new Vec4YamlConverter()) .WithTypeConverter(new Vec3YamlConverter()) .WithTypeConverter(new Vec2YamlConverter()) @@ -82,6 +83,7 @@ public object ReadYaml(IParser parser, Type type) ObservableObject value = propType switch { + RotationProperty => deserializer.Deserialize(parser), Vec4Property => deserializer.Deserialize(parser), Vec3Property => deserializer.Deserialize(parser), Vec2Property => deserializer.Deserialize(parser), @@ -116,6 +118,7 @@ public void WriteYaml(IEmitter emitter, object value, Type type) .WithTypeConverter(new ObservableDictionaryConverter()) .WithTypeConverter(new ComponentTypeYamlConverter()) .WithTypeConverter(new FileIdYamlConverter()) + .WithTypeConverter(new RotationYamlConverter()) .WithTypeConverter(new Vec4YamlConverter()) .WithTypeConverter(new Vec3YamlConverter()) .WithTypeConverter(new Vec2YamlConverter()) diff --git a/Editor/ViewModels/GameObject.cs b/Editor/ViewModels/GameObject.cs index 9fbfb530..d17e0cac 100644 --- a/Editor/ViewModels/GameObject.cs +++ b/Editor/ViewModels/GameObject.cs @@ -34,7 +34,7 @@ public GameObject() { Name = Name + "(Clone)", Position = new Vec4(Position), - Rotation = new Vec4(Rotation), + Rotation = new Rotation(Rotation), Scale = new Vec4(Scale), ParentIndex = ParentIndex, InstanceId = InstanceId, @@ -54,7 +54,7 @@ void OnClearComponents() { } Vec4 position = new(); [ObservableProperty] - Vec4 rotation = new(); + Rotation rotation = new(); [ObservableProperty] Vec4 scale = new(); diff --git a/Editor/Views/InspectorView/ComponentTemplate.cs b/Editor/Views/InspectorView/ComponentTemplate.cs index d76b53bb..200c2093 100644 --- a/Editor/Views/InspectorView/ComponentTemplate.cs +++ b/Editor/Views/InspectorView/ComponentTemplate.cs @@ -47,6 +47,7 @@ public ComponentTemplate() propertyEditor = property.Value switch { Observable observableFloat => Templates.FloatEditor((Component vm) => observableFloat.Value, (vm, value) => observableFloat.Value = value), + Rotation quat => Templates.RotationEditor((Component vm) => quat), Vec4 vec4 => Templates.Vec4Editor((Component vm) => vec4), Vec3 vec3 => Templates.Vec3Editor((Component vm) => vec3), Vec2 vec2 => Templates.Vec2Editor((Component vm) => vec2), diff --git a/Editor/Views/InspectorView/GameObjectTemplate.xaml b/Editor/Views/InspectorView/GameObjectTemplate.xaml index a15cec8d..075a1154 100644 --- a/Editor/Views/InspectorView/GameObjectTemplate.xaml +++ b/Editor/Views/InspectorView/GameObjectTemplate.xaml @@ -48,9 +48,9 @@