├── .gitignore ├── .gitmodules ├── Content ├── Fonts │ └── default.xnb ├── HLSL │ ├── Common.fxh │ ├── Header.fxh │ ├── PostProcessEffect.fx │ ├── SkinnedEffect.fx │ ├── SpectrumEffect.fx │ ├── TerrainEffect.fx │ └── WaterEffect.fx └── Textures │ ├── blank.png │ └── missing.png ├── Entry.cs ├── Framework ├── Audio │ ├── AudioManager.cs │ ├── AudioPlayer.cs │ ├── Emitter.cs │ ├── MusicPlayer.cs │ └── SoundEffect.cs ├── Color.cs ├── Content │ ├── ContentHelper.cs │ ├── ContentParser.cs │ ├── EffectParser.cs │ ├── HeightmapParser.cs │ ├── ImageAsset.cs │ ├── ImageAssetParser.cs │ ├── InitDataParser.cs │ ├── MGCParser.cs │ ├── MemberContentAttribute.cs │ ├── ModelParsing │ │ ├── AnimationParser.cs │ │ ├── G3DJReader.cs │ │ ├── ModelParser.cs │ │ └── OBJReader.cs │ ├── Plugin.cs │ ├── SVGParser.cs │ ├── ScreenParser.cs │ ├── ScriptAsset.cs │ ├── ScriptParser.cs │ ├── Scripting │ │ └── CSScript.cs │ ├── SoundParser.cs │ └── Texture2DParser.cs ├── Context.cs ├── Debug │ ├── DebugPrinter.cs │ └── DebugTiming.cs ├── DefaultDict.cs ├── Entities │ ├── Component.cs │ ├── Entity.cs │ ├── EntityCollection.cs │ ├── EntityManager.cs │ ├── GameObject.cs │ ├── GameObject2D.cs │ ├── InitData.cs │ └── Interpolator.cs ├── FunctionalExtensions.cs ├── Graphics │ ├── Animation │ │ ├── AnimationClip.cs │ │ ├── AnimationData.cs │ │ ├── AnimationPlayer.cs │ │ ├── Bone.cs │ │ ├── Keyframe.cs │ │ └── SkinningData.cs │ ├── Batch3D.cs │ ├── Billboard.cs │ ├── Camera.cs │ ├── DrawablePart.cs │ ├── Effects │ │ ├── PostProcessEffect.cs │ │ ├── SkinnedEffect.cs │ │ ├── SpectrumEffect.cs │ │ ├── TerrainEffect.cs │ │ └── WaterEffect.cs │ ├── GraphicsEngine.cs │ ├── RenderTask.cs │ ├── Settings.cs │ ├── SpecModel.cs │ ├── VertexHelper.cs │ ├── VertexTypes.cs │ └── Water.cs ├── IDebug.cs ├── Input │ ├── Axis.cs │ ├── Gamepad.cs │ ├── InputLayout.cs │ ├── InputState.cs │ ├── KeyBinding.cs │ ├── PlayerInformation.cs │ ├── RawMouse.cs │ ├── SpectrumMouse.cs │ └── VR │ │ ├── VRBinding.cs │ │ ├── VRController.cs │ │ └── VRHmd.cs ├── JConvert.cs ├── JSON │ ├── JInitDataConverter.cs │ ├── JMatrixConverter.cs │ ├── JPointConverter.cs │ ├── JShapeConverter.cs │ ├── JVectorConverter.cs │ └── SimpleConverter.cs ├── LSystem │ ├── LNode.cs │ └── LTree.cs ├── LoadHelper.cs ├── Matrix.cs ├── Network │ ├── Connection.cs │ ├── Handshake.cs │ ├── MultiplayerReceiver.cs │ ├── MultiplayerSender.cs │ ├── MultiplayerService.cs │ ├── NetID.cs │ ├── NetMessage.cs │ ├── NetworkMutex.cs │ ├── ReplicationData.cs │ ├── ReplyWaiter.cs │ ├── Serialization.cs │ ├── SteamP2PReceiver.cs │ ├── Surrogates │ │ ├── EntitySurrogate.cs │ │ ├── FloatArraySurrogate.cs │ │ ├── JSONSurrogate.cs │ │ ├── ModelSurrogate.cs │ │ ├── PrimitiveSurrogate.cs │ │ └── StreamSurrogate.cs │ ├── UDPReceiver.cs │ └── UDPSender.cs ├── Physics │ ├── Collision │ │ ├── CollisionIsland.cs │ │ ├── CollisionSystem.cs │ │ ├── CollisionSystemPersistentSAP.cs │ │ ├── DynamicTree.cs │ │ ├── EPACollide.cs │ │ ├── GJKCollide.cs │ │ ├── GroundInfo.cs │ │ ├── ICollidable.cs │ │ ├── IslandManager.cs │ │ ├── Octree.cs │ │ └── Shapes │ │ │ ├── BoxShape.cs │ │ │ ├── ConeShape.cs │ │ │ ├── ConvexHullShape.cs │ │ │ ├── CylinderShape.cs │ │ │ ├── ISupportMappable.cs │ │ │ ├── ListMultishape.cs │ │ │ ├── Multishape.cs │ │ │ ├── Shape.cs │ │ │ ├── SphereShape.cs │ │ │ └── TerrainShape.cs │ ├── Dynamics │ │ ├── Arbiter.cs │ │ ├── ArbiterMap.cs │ │ ├── Constraint.cs │ │ ├── Contact.cs │ │ └── Material.cs │ ├── LinearMath │ │ ├── JBBox.cs │ │ └── JMath.cs │ ├── PhysicsEngine.cs │ └── ThreadManager.cs ├── Point.cs ├── Quaternion.cs ├── Ray.cs ├── Rectangle.cs ├── ResourcePool.cs ├── Schedule.cs ├── Screens │ ├── DialogScreen.cs │ ├── Element.cs │ ├── ElementSelector.cs │ ├── ElementSize.cs │ ├── ElementStyle.cs │ ├── GridLayout.cs │ ├── InGameScreen.cs │ ├── InputElements │ │ ├── Button.cs │ │ ├── Checkbox.cs │ │ ├── Dropdown.cs │ │ ├── InputElement.cs │ │ ├── ListOption.cs │ │ ├── ListSelector.cs │ │ ├── SelectionWheel.cs │ │ ├── Slider.cs │ │ ├── TextElement.cs │ │ └── TextInput.cs │ ├── LayoutManager.cs │ ├── LinearLayout.cs │ ├── MenuScreen.cs │ ├── RectOffset.cs │ ├── RootElement.cs │ └── SceneScreen.cs ├── SpectrumGame.cs ├── StateMachine.cs ├── Transform.cs ├── TypeHelper.cs ├── Utility │ ├── ArgParseHelper.cs │ ├── ExtensionMethods.cs │ ├── MathHelper.cs │ ├── MatrixHelper.cs │ ├── Point3.cs │ ├── RectangleExtensions.cs │ ├── SpriteBatchExtensions.cs │ ├── VectorExtensions.cs │ └── WinUtil.cs ├── VR │ ├── SpecVR.cs │ └── VRMenu.cs ├── Vector2.cs ├── Vector3.cs └── Vector4.cs ├── LICENSE ├── Spectrum.csproj ├── SpectrumTest.sln ├── Test ├── BenchmarkTests.cs ├── ElementTest.cs ├── InitDataTests.cs ├── JSONTests.cs ├── MathExtensionTests.cs ├── PhysicsTests.cs ├── SerializationTests.cs └── SpectrumTest.csproj ├── mgfx.targets.xml ├── openvr_api.cs └── openvr_api.dll /.gitignore: -------------------------------------------------------------------------------- 1 | *.cachefile 2 | *.zip 3 | *.mgfx 4 | *.vs 5 | bin 6 | obj -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Dependencies"] 2 | path = Dependencies 3 | url = https://github.com/alex-sherman/spectrumdependencies 4 | [submodule "Replicate"] 5 | path = Replicate 6 | url = https://github.com/alex-sherman/Replicate 7 | -------------------------------------------------------------------------------- /Content/Fonts/default.xnb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex-sherman/Spectrum/3030b61f5eb961815100b23dcceb99adfff22be9/Content/Fonts/default.xnb -------------------------------------------------------------------------------- /Content/HLSL/SkinnedEffect.fx: -------------------------------------------------------------------------------- 1 | #include "Common.fxh" 2 | 3 | #define SKINNED_EFFECT_MAX_BONES 64 4 | float4x3 Bones[SKINNED_EFFECT_MAX_BONES]; 5 | 6 | void Skin(inout CommonVSInput vin, float4 Indices, float4 Weights, uniform int boneCount) 7 | { 8 | float4x3 skinning = 0; 9 | [unroll] 10 | for (int i = 0; i < boneCount; i++) 11 | { 12 | skinning += Bones[Indices[i]] * Weights[i]; 13 | } 14 | 15 | vin.Position.xyz = mul(vin.Position, skinning); 16 | vin.normal = mul(vin.normal, (float3x3) skinning); 17 | vin.tangent = mul(vin.tangent, (float3x3) skinning); 18 | } 19 | 20 | CommonVSOut SkinnedVS(CommonVSInput input, float4 Indices : BLENDINDICES0, float4 Weights : BLENDWEIGHT0) 21 | { 22 | Skin(input, Indices, Weights, 4); 23 | return Transform(input); 24 | } 25 | 26 | CommonVSOut DepthTransform(CommonVSInput vin, float4 Indices : BLENDINDICES0, float4 Weights : BLENDWEIGHT0) 27 | { 28 | Skin(vin, Indices, Weights, 4); 29 | CommonVSOut Out = (CommonVSOut) 0; 30 | float4 worldPosition = CommonVS((CommonVSInput) vin, (CommonVSOut) Out); 31 | return Out; 32 | } 33 | float4 Depth(CommonVSOut vsout) : COLOR0 34 | { 35 | return float4(1 - vsout.Pos2DAsSeenByLight.z / vsout.Pos2DAsSeenByLight.w, 0, 0, 1); 36 | } 37 | technique Standard 38 | { 39 | pass P0 40 | { 41 | vertexShader = compile vs_4_0 SkinnedVS(); 42 | pixelShader = compile ps_4_0 ApplyTexture(); 43 | } 44 | } 45 | technique ShadowMap 46 | { 47 | pass P0 48 | { 49 | vertexShader = compile vs_4_0 DepthTransform(); 50 | pixelShader = compile ps_4_0 Depth(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Content/HLSL/SpectrumEffect.fx: -------------------------------------------------------------------------------- 1 | #include "Common.fxh" 2 | 3 | float4 Depth(CommonVSOut vsout) : COLOR0 { 4 | return float4(1 - vsout.Pos2DAsSeenByLight.z / vsout.Pos2DAsSeenByLight.w, 0, 0, 1); 5 | } 6 | 7 | technique Standard 8 | { 9 | pass P0 10 | { 11 | vertexShader = compile vs_4_0 Transform(); 12 | pixelShader = compile ps_4_0 ApplyTexture(); 13 | } 14 | } 15 | technique StandardInstance 16 | { 17 | pass P0 18 | { 19 | vertexShader = compile vs_4_0 InstanceTransform(); 20 | pixelShader = compile ps_4_0 ApplyTexture(); 21 | } 22 | } 23 | technique ShadowMap 24 | { 25 | pass P0 26 | { 27 | vertexShader = compile vs_4_0 Transform(); 28 | pixelShader = compile ps_4_0 Depth(); 29 | } 30 | } 31 | technique ShadowMapInstance 32 | { 33 | pass P0 34 | { 35 | vertexShader = compile vs_4_0 InstanceTransform(); 36 | pixelShader = compile ps_4_0 Depth(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Content/Textures/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex-sherman/Spectrum/3030b61f5eb961815100b23dcceb99adfff22be9/Content/Textures/blank.png -------------------------------------------------------------------------------- /Content/Textures/missing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex-sherman/Spectrum/3030b61f5eb961815100b23dcceb99adfff22be9/Content/Textures/missing.png -------------------------------------------------------------------------------- /Entry.cs: -------------------------------------------------------------------------------- 1 | using Spectrum.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum 9 | { 10 | /// 11 | /// The main class. 12 | /// 13 | public static class Entry where T : SpectrumGame, new() 14 | { 15 | /// 16 | /// The main entry point for the application. 17 | /// 18 | [STAThread] 19 | public static void Main(string[] args) 20 | { 21 | LoadHelper.SetMainAssembly(); 22 | #if !DEBUG 23 | try 24 | { 25 | #endif 26 | using (var game = new T()) 27 | game.Run(); 28 | #if !DEBUG 29 | } 30 | catch (Exception e) 31 | { 32 | DebugPrinter.Print(e.ToString()); 33 | } 34 | #endif 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Framework/Audio/AudioManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using SharpDX.Mathematics.Interop; 3 | using SharpDX.X3DAudio; 4 | using SharpDX.XAudio2; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace Spectrum.Framework.Audio 11 | { 12 | public class AudioManager 13 | { 14 | internal static RawVector3 V3ToV3(Vector3 vector) 15 | { 16 | return new RawVector3(vector.X, vector.Y, vector.Z); 17 | } 18 | internal static Vector3 V3ToV3(RawVector3 vector) 19 | { 20 | return new Vector3(vector.X, vector.Y, vector.Z); 21 | } 22 | internal static Listener Listener; 23 | internal static readonly XAudio2 _xaudio2 = new XAudio2(); 24 | internal static readonly X3DAudio X3DAudio = new X3DAudio(SharpDX.Multimedia.Speakers.Stereo); 25 | internal static MasteringVoice MasteringVoice = new MasteringVoice(_xaudio2); 26 | public static int DestinationChannels { get { return 2; } } 27 | 28 | public static Vector3 ListenerPosition 29 | { 30 | get => V3ToV3(Listener.Position); 31 | set => Listener.Position = V3ToV3(value); 32 | } 33 | public static Vector3 ListenerForward 34 | { 35 | get => V3ToV3(Listener.OrientFront); 36 | set => Listener.OrientFront = V3ToV3(value); 37 | } 38 | public static Vector3 ListenerUp 39 | { 40 | get => V3ToV3(Listener.OrientTop); 41 | set => Listener.OrientTop = V3ToV3(value); 42 | } 43 | 44 | public static void Init() 45 | { 46 | Listener = new Listener() 47 | { 48 | Position = new RawVector3(0, 0, 0), 49 | OrientFront = new RawVector3(0, 0, 1), 50 | OrientTop = new RawVector3(0, 1, 0), 51 | Velocity = new RawVector3(0, 0, 0) 52 | }; 53 | } 54 | 55 | public static void Shutdown() 56 | { 57 | _xaudio2.Dispose(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Framework/Audio/Emitter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using SharpDX.Mathematics.Interop; 3 | using SharpDX.X3DAudio; 4 | using Spectrum.Framework.Entities; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace Spectrum.Framework.Audio 11 | { 12 | public class SoundEmitter : AudioPlayer 13 | { 14 | private Emitter _emitter; 15 | 16 | public Vector3 Position 17 | { 18 | get { return AudioManager.V3ToV3(_emitter.Position); } 19 | set { _emitter.Position = AudioManager.V3ToV3(value); } 20 | } 21 | 22 | public Vector3 Forward 23 | { 24 | get { return AudioManager.V3ToV3(_emitter.OrientFront); } 25 | set { _emitter.OrientFront = AudioManager.V3ToV3(value); } 26 | } 27 | 28 | public Vector3 Up 29 | { 30 | get { return AudioManager.V3ToV3(_emitter.OrientTop); } 31 | set { _emitter.OrientTop = AudioManager.V3ToV3(value); } 32 | } 33 | 34 | public SoundEmitter() 35 | { 36 | _emitter = new Emitter() 37 | { 38 | ChannelCount = 1, 39 | CurveDistanceScaler = 20, 40 | OrientFront = new RawVector3(0, 0, 1), 41 | OrientTop = new RawVector3(0, 1, 0), 42 | Position = new RawVector3(0, 0, 0), 43 | Velocity = new RawVector3(0, 0, 0) 44 | }; 45 | } 46 | 47 | public void Update(GameObject emitted) 48 | { 49 | Position = emitted.position; 50 | Up = Vector3.Up; 51 | Forward = Vector3.Forward; 52 | if (voice != null) 53 | { 54 | DspSettings dspSettings = new DspSettings(1, AudioManager.DestinationChannels); 55 | AudioManager.X3DAudio.Calculate(AudioManager.Listener, _emitter, CalculateFlags.Matrix, dspSettings); 56 | voice.SetOutputMatrix(1, AudioManager.DestinationChannels, dspSettings.MatrixCoefficients); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Framework/Audio/MusicPlayer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Input; 3 | using Spectrum.Framework.Entities; 4 | using Spectrum.Framework.Input; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Spectrum.Framework.Audio 12 | { 13 | public class MusicPlayer : Entity 14 | { 15 | AudioPlayer player = new AudioPlayer(); 16 | public IReadOnlyList Tracks => tracks; 17 | private int trackIndex = 0; 18 | public SoundEffect CurrentTrack => Tracks.FirstOrDefault(); 19 | List tracks = new List(); 20 | public bool Repeat; 21 | 22 | public float Volume 23 | { 24 | get => player.Volume; 25 | set => player.Volume = value; 26 | } 27 | 28 | float fadeVolumeInitial; 29 | float fadeTimeInitial; 30 | float fadeTime; 31 | float fadeTarget; 32 | 33 | public MusicPlayer() 34 | { 35 | AllowReplicate = false; 36 | player.Loop = false; 37 | player.OnTrackEnd += Next; 38 | } 39 | public void SetTracks(IEnumerable tracks) 40 | { 41 | this.tracks = tracks.ToList(); 42 | player.SoundEffect = CurrentTrack; 43 | } 44 | public void Next() 45 | { 46 | trackIndex++; 47 | if (trackIndex >= tracks.Count) 48 | { 49 | if (Repeat) 50 | trackIndex = 0; 51 | else 52 | return; 53 | } 54 | player.SoundEffect = tracks[trackIndex]; 55 | player.State = AudioState.Playing; 56 | } 57 | public void Previous() 58 | { 59 | trackIndex--; 60 | if (trackIndex < 0) 61 | { 62 | if (Repeat) 63 | trackIndex = tracks.Count - 1; 64 | else 65 | trackIndex = 0; 66 | } 67 | player.SoundEffect = tracks[trackIndex]; 68 | } 69 | public void FadeTo(float volumeTarget, float time) 70 | { 71 | fadeTarget = volumeTarget; 72 | fadeVolumeInitial = Volume; 73 | fadeTimeInitial = fadeTime = time; 74 | } 75 | public AudioState State 76 | { 77 | get => player.State; 78 | set 79 | { 80 | player.Volume = Volume; 81 | player.State = value; 82 | } 83 | } 84 | public override void Update(float dt) 85 | { 86 | base.Update(dt); 87 | if (fadeTimeInitial != 0) 88 | { 89 | fadeTime -= dt; 90 | if (fadeTime > 0) 91 | { 92 | var w = fadeTime / fadeTimeInitial; 93 | Volume = fadeVolumeInitial * w + fadeTarget * (1 - w); 94 | } 95 | else 96 | { 97 | fadeTimeInitial = 0; 98 | Volume = fadeTarget; 99 | } 100 | } 101 | var track = tracks.FirstOrDefault(); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Framework/Audio/SoundEffect.cs: -------------------------------------------------------------------------------- 1 | using SharpDX; 2 | using SharpDX.MediaFoundation; 3 | using SharpDX.Multimedia; 4 | using SharpDX.XAudio2; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Text; 10 | 11 | namespace Spectrum.Framework.Audio 12 | { 13 | public class SoundEffect 14 | { 15 | public List Samples; 16 | public WaveFormat WaveFormat; 17 | 18 | public SoundEffect(Stream stream) 19 | { 20 | AudioDecoder audioDecoder = new AudioDecoder(stream); 21 | WaveFormat = audioDecoder.WaveFormat; 22 | // TODO: This is super slow but copying the stream/list doesn't seem to satisfy SharpDX 23 | Samples = audioDecoder.GetSamples().Select(sample => 24 | { 25 | var output = new DataPointer(SharpDX.Utilities.AllocateMemory(sample.Size), sample.Size); 26 | SharpDX.Utilities.CopyMemory(output.Pointer, sample.Pointer, sample.Size); 27 | return output; 28 | }).ToList(); 29 | } 30 | ~SoundEffect() 31 | { 32 | if (Samples != null) 33 | foreach (var sample in Samples) 34 | SharpDX.Utilities.FreeMemory(sample.Pointer); 35 | 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Framework/Color.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Spectrum.Framework 8 | { 9 | public struct Color 10 | { 11 | public byte R { get => (byte)(storage & 0x000000ff); set { storage = (storage & 0xffffff00) | value; } } 12 | public byte G { get => (byte)((storage & 0x0000ff00) >> 8); set { storage = (storage & 0xffff00ff) | (uint)value << 8; } } 13 | public byte B { get => (byte)((storage & 0x00ff0000) >> 16); set { storage = (storage & 0xff00ffff) | (uint)value << 16; } } 14 | public byte A { get => (byte)((storage & 0xff000000) >> 24); set { storage = (storage & 0x00ffffff) | (uint)value << 24; } } 15 | // ABGR storage 16 | // 0xAA BB GG RR 17 | private uint storage; 18 | public Color(byte r, byte g, byte b, byte a) { storage = 0; R = r; G = g; B = b; A = a; } 19 | public Color(Color c, byte a) { storage = 0; R = c.R; G = c.G; B = c.B; A = a; } 20 | public Color(Color c, float a) { storage = 0; R = c.R; G = c.G; B = c.B; A = (byte)(a * 255); } 21 | public Color(Vector4 v) : this((byte)(v.X * 255), (byte)(v.Y * 255), (byte)(v.Z * 255), (byte)(v.W * 255)) { } 22 | public Color(Vector3 v) : this((byte)(v.X * 255), (byte)(v.Y * 255), (byte)(v.Z * 255), 255) { } 23 | public Color(float r, float g, float b) : this((byte)(r * 255), (byte)(g * 255), (byte)(b * 255), 255) { } 24 | public Vector4 ToVector4() => new Vector4(R / 255f, G / 255f, B / 255f, A / 255f); 25 | public Vector3 ToVector3() => new Vector3(R / 255f, G / 255f, B / 255f); 26 | public static Color FromString(string value) 27 | { 28 | try 29 | { 30 | System.Drawing.Color color = System.Drawing.ColorTranslator.FromHtml(value); 31 | return new Color((byte)(color.R * color.A / 255), (byte)(color.G * color.A / 255), (byte)(color.B * color.A / 255), color.A); 32 | } 33 | catch (Exception) 34 | { 35 | return Black; 36 | } 37 | } 38 | public static implicit operator Microsoft.Xna.Framework.Color(Color color) => new Microsoft.Xna.Framework.Color(color.R, color.G, color.B, color.A); 39 | public static implicit operator Color(Microsoft.Xna.Framework.Color color) => new Color(color.R, color.G, color.B, color.A); 40 | public static implicit operator Color(string color) => FromString(color); 41 | public static Color Transparent => new Color(0, 0, 0, 0); 42 | public static Color Black => new Color(0, 0, 0, 255); 43 | public static Color Red => new Color(255, 0, 0, 255); 44 | public static Color Green => new Color(0, 255, 0, 255); 45 | public static Color Blue => new Color(0, 0, 255, 255); 46 | public static Color White => new Color(255, 255, 255, 255); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Framework/Content/EffectParser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using Spectrum.Framework.Graphics; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Spectrum.Framework.Content 11 | { 12 | class EffectParser : CachedContentParser 13 | { 14 | public EffectParser() : base("mgfx") 15 | { 16 | Prefix = "HLSL"; 17 | } 18 | protected override Effect LoadData(string path, string name) 19 | { 20 | using (var f = new FileStream(path, FileMode.Open, FileAccess.Read)) 21 | { 22 | byte[] output = new byte[f.Length]; 23 | f.Read(output, 0, (int)f.Length); 24 | return new Effect(SpectrumGame.Game.GraphicsDevice, output); 25 | } 26 | } 27 | 28 | protected override Effect SafeCopy(Effect toClone) 29 | { 30 | return toClone.Clone(); 31 | } 32 | } 33 | class SpectrumEffectParser : EffectParser 34 | { 35 | protected override Effect SafeCopy(Effect toClone) 36 | { 37 | return new SpectrumEffect(toClone); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Framework/Content/ImageAsset.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Svg; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Spectrum.Framework.Content 11 | { 12 | public class ImageAsset 13 | { 14 | public static readonly ImageAsset Blank; 15 | public static readonly ImageAsset Missing; 16 | static ImageAsset() 17 | { 18 | Blank = new ImageAsset(ContentHelper.Blank); 19 | Missing = new ImageAsset(ContentHelper.Missing); 20 | } 21 | public SvgDocument SVG = null; 22 | public Texture2D Texture = null; 23 | private Texture2D rasterized = null; 24 | // Marking as public requires a reference to SVG to resolve method 25 | public ImageAsset(SvgDocument svg) { SVG = svg; } 26 | public ImageAsset(Texture2D texture) { Texture = texture; } 27 | public static implicit operator ImageAsset(Texture2D texture) => new ImageAsset(texture); 28 | public static implicit operator ImageAsset(string path) 29 | { 30 | return ContentHelper.Load(path) ?? Missing; 31 | } 32 | public ImageAsset() { } 33 | public void Rasterize(int width, int height) 34 | { 35 | System.Drawing.Bitmap bitmap = SVG.Draw(width, height); 36 | rasterized?.Dispose(); 37 | rasterized = bitmap.GetTexture2DFromBitmap(SpectrumGame.Game.GraphicsDevice); 38 | } 39 | public Texture2D GetTexture(Rectangle rect) 40 | { 41 | if (SVG != null) 42 | { 43 | if (rasterized == null || rasterized.Bounds.Width != rect.Width || rasterized.Bounds.Height != rect.Height) 44 | { 45 | Rasterize(rect.Width, rect.Height); 46 | } 47 | return rasterized; 48 | } 49 | else if (Texture != null) 50 | { 51 | return Texture; 52 | } 53 | return null; 54 | } 55 | public ImageAsset Clone() 56 | { 57 | if (SVG != null) 58 | return new ImageAsset(SVG); 59 | return new ImageAsset(Texture); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Framework/Content/ImageAssetParser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using Svg; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Spectrum.Framework.Content 11 | { 12 | public class ImageAssetParser : CachedContentParser 13 | { 14 | private static Dictionary additionalAssets = new Dictionary(); 15 | public static void Add(string path, ImageAsset asset) => additionalAssets[path] = asset; 16 | public ImageAssetParser() : base("svg", "png", "jpg") 17 | { 18 | Prefix = "Textures"; 19 | } 20 | protected override string ResolvePath(string path, string name) 21 | { 22 | if (additionalAssets.ContainsKey(name)) return path; 23 | return base.ResolvePath(path, name); 24 | } 25 | protected override ImageAsset LoadData(string path, string name) 26 | { 27 | if (additionalAssets.TryGetValue(name, out var result)) return result; 28 | var extension = Path.GetExtension(path).Substring(1); 29 | if (extension == "svg") 30 | return new ImageAsset(SvgDocument.Open(path)); 31 | else 32 | return new ImageAsset(Texture2DParser.LoadFromPath(path)); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Framework/Content/InitDataParser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using Spectrum.Framework.Entities; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace Spectrum.Framework.Content 13 | { 14 | public class InitDataParser : CachedContentParser 15 | { 16 | public InitDataParser() : base("json") 17 | { 18 | Prefix = "InitData"; 19 | } 20 | protected override InitData LoadData(string path, string name) 21 | { 22 | using (var reader = new StreamReader(File.OpenRead(path))) 23 | { 24 | var output = JConvert.Deserialize(reader.ReadToEnd()).ToImmutable(); 25 | if (output.Name == null) output.Name = name; 26 | output.Path = name; 27 | output.FullPath = path; 28 | return output; 29 | } 30 | } 31 | 32 | protected override InitData SafeCopy(InitData data) 33 | { 34 | return data; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Framework/Content/MGCParser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Content; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework.Content 9 | { 10 | public class MGCParser : IContentParser 11 | { 12 | public string Prefix { get; set; } 13 | string Suffix; 14 | public MGCParser(string prefix, string suffix) 15 | { 16 | Prefix = prefix; 17 | Suffix = suffix; 18 | } 19 | public object Load(string path, string name, bool skipCache) 20 | { 21 | return SpectrumGame.Game.Content.Load(path + Suffix); 22 | } 23 | 24 | public void Clear() { } 25 | 26 | public IEnumerable FindAll(string directory, string glob = "*", bool recursive = true) 27 | { 28 | return Enumerable.Empty(); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Framework/Content/MemberContentAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography.X509Certificates; 5 | using System.Text; 6 | 7 | namespace Spectrum.Framework.Content 8 | { 9 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] 10 | public class MemberContentAttribute : Attribute 11 | { 12 | public string Path { get; private set; } 13 | public MemberContentAttribute(string path) 14 | { 15 | Path = path; 16 | } 17 | } 18 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] 19 | public class ClassContentAttribute : Attribute 20 | { 21 | public string Member { get; private set; } 22 | public string Path { get; private set; } 23 | public ClassContentAttribute(string member, string path) 24 | { 25 | Member = member; 26 | Path = path; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Framework/Content/ModelParsing/AnimationParser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using Spectrum.Framework.Graphics.Animation; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Text; 10 | 11 | namespace Spectrum.Framework.Content.ModelParsing 12 | { 13 | class AnimationParser : MultiContentParser 14 | { 15 | public AnimationParser() : base(new Dictionary() { 16 | { "g3dj", G3DJReader.LoadAnimation }, 17 | }) 18 | { 19 | Prefix = "Models"; 20 | } 21 | 22 | protected override AnimationData SafeCopy(AnimationData data) 23 | { 24 | return data; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Framework/Content/ModelParsing/ModelParser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | using Spectrum.Framework.Content.ModelParsing; 6 | using Spectrum.Framework.Graphics; 7 | using Spectrum.Framework.Graphics.Animation; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.IO; 11 | using System.Linq; 12 | using System.Text; 13 | using System.Text.RegularExpressions; 14 | 15 | namespace Spectrum.Framework.Content.ModelParsing 16 | { 17 | interface IModelReader 18 | { 19 | ModelParserCache LoadData(string path, string name); 20 | } 21 | 22 | class ModelParserCache 23 | { 24 | public SkinningData skinningData; 25 | public Dictionary parts = new Dictionary(); 26 | public Dictionary materials = new Dictionary(); 27 | public AnimationData animations = new AnimationData(); 28 | public List vertexAttributes = new List(); 29 | public string Directory; 30 | public string FileName; 31 | public string Name; 32 | 33 | public ModelParserCache(string name, string path) 34 | { 35 | Directory = Path.GetDirectoryName(path); 36 | FileName = path; 37 | Name = name; 38 | } 39 | } 40 | struct BoneWeight 41 | { 42 | public int Index; 43 | public float Weight; 44 | public BoneWeight(int index, float weight) 45 | { 46 | Index = index; 47 | Weight = weight; 48 | } 49 | } 50 | class ModelParser : MultiContentParser 51 | { 52 | public ModelParser() : base(new Dictionary() { 53 | { "g3dj", G3DJReader.LoadModel }, 54 | { "obj", OBJReader.LoadData }, 55 | }) 56 | { 57 | Prefix = "Models"; 58 | } 59 | 60 | protected override SpecModel SafeCopy(ModelParserCache data) 61 | { 62 | Dictionary parts = new Dictionary(); 63 | foreach (KeyValuePair part in data.parts) 64 | { 65 | parts[part.Key] = part.Value.CreateReference(); 66 | // TODO: If SkinnedEffect handled instancing correctly this could be avoided 67 | if (data.skinningData != null) 68 | parts[part.Key].effect = parts[part.Key].effect.Clone() as SpectrumEffect; 69 | } 70 | SpecModel model = new SpecModel(data.Name, data.FileName, parts, data.materials, data.skinningData?.Clone()) 71 | { 72 | Animations = data.animations 73 | }; 74 | return model; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Framework/Content/SVGParser.cs: -------------------------------------------------------------------------------- 1 | using Svg; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework.Content 9 | { 10 | class SVGParser : CachedContentParser 11 | { 12 | protected override SvgDocument LoadData(string path, string name) 13 | { 14 | return SvgDocument.Open(path); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Framework/Content/ScreenParser.cs: -------------------------------------------------------------------------------- 1 | using Spectrum.Framework.Screens; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace Spectrum.Framework.Content 10 | { 11 | //class ScreenParser : CachedContentParser 12 | //{ 13 | // protected override string LoadData(string path) 14 | // { 15 | // FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read); 16 | // return ""; 17 | // } 18 | 19 | // protected override GameScreen SafeCopy(string data) 20 | // { 21 | // Regex regex = new Regex(""); 22 | // Match match = regex.Match(data); 23 | // match. 24 | 25 | // return null; 26 | // } 27 | //} 28 | } 29 | -------------------------------------------------------------------------------- /Framework/Content/ScriptAsset.cs: -------------------------------------------------------------------------------- 1 | using Spectrum.Framework.Content.Scripting; 2 | using Spectrum.Framework.Entities; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Runtime.Remoting; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace Spectrum.Framework.Content 13 | { 14 | public class ScriptAsset 15 | { 16 | public readonly CSScript Script; 17 | public string Path { get; private set; } 18 | public ScriptAsset(string path, CSScript script) 19 | { 20 | Path = path; 21 | Script = script; 22 | } 23 | public static implicit operator Component(ScriptAsset script) 24 | { 25 | return script.Script.GetComponent(); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Framework/Content/ScriptParser.cs: -------------------------------------------------------------------------------- 1 | using Spectrum.Framework.Content.Scripting; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Spectrum.Framework.Content 10 | { 11 | public class ScriptParser : CachedContentParser 12 | { 13 | public ScriptParser() : base("cs") 14 | { 15 | Prefix = "Scripts"; 16 | } 17 | protected override ScriptAsset LoadData(string path, string name) 18 | { 19 | return new ScriptAsset(path, new CSScript(path)); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Framework/Content/Scripting/CSScript.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CSharp; 2 | using Spectrum.Framework.Entities; 3 | using System; 4 | using System.CodeDom.Compiler; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Reflection; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Spectrum.Framework.Content.Scripting 12 | { 13 | public class CSScript 14 | { 15 | Assembly assembly; 16 | ConstructorInfo constructor; 17 | public CSScript(params string[] filenames) 18 | { 19 | CSharpCodeProvider provider = new CSharpCodeProvider(); 20 | CompilerParameters parameters = new CompilerParameters() 21 | { 22 | GenerateInMemory = true, 23 | GenerateExecutable = false, 24 | }; 25 | parameters.ReferencedAssemblies.Add("Spectrum.dll"); 26 | parameters.ReferencedAssemblies.Add("System.dll"); 27 | parameters.ReferencedAssemblies.Add("System.Linq.dll"); 28 | parameters.ReferencedAssemblies.Add("MonoGame.Framework.dll"); 29 | parameters.IncludeDebugInformation = true; 30 | parameters.CompilerOptions = "/d:DEBUG"; 31 | var result = provider.CompileAssemblyFromFile(parameters, filenames); 32 | if (result.Errors.HasErrors) 33 | { 34 | var errors = result.Errors; 35 | var text = Enumerable.Range(0, errors.Count).Select(i => errors[i].ErrorText).ToList(); 36 | throw new Exception(string.Join("\n", text)); 37 | } 38 | assembly = result.CompiledAssembly; 39 | var candidateTypes = assembly.DefinedTypes 40 | .Where(typeof(Component).IsAssignableFrom).ToList(); 41 | var d = AppDomain.CurrentDomain; 42 | if (candidateTypes.Count != 1) 43 | throw new InvalidOperationException( 44 | $"C# scripts should contain exactly 1 Component type, found {candidateTypes.Count}"); 45 | constructor = candidateTypes.First().GetConstructor(new Type[] { }); 46 | } 47 | 48 | public Component GetComponent() 49 | { 50 | return constructor.Invoke(new object[] { }) as Component; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Framework/Content/SoundParser.cs: -------------------------------------------------------------------------------- 1 | using SharpDX.IO; 2 | using SharpDX.MediaFoundation; 3 | using SharpDX.Multimedia; 4 | using Spectrum.Framework.Audio; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Text; 10 | 11 | namespace Spectrum.Framework.Content 12 | { 13 | class SoundParser : CachedContentParser 14 | { 15 | public SoundParser() : base("wav", "m4a") 16 | { 17 | Prefix = "Sounds"; 18 | } 19 | 20 | protected override SoundEffect LoadData(string path, string name) 21 | { 22 | var nativefilestream = new NativeFileStream( 23 | path, 24 | NativeFileMode.Open, 25 | NativeFileAccess.Read, 26 | NativeFileShare.Read); 27 | return new SoundEffect(nativefilestream); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Framework/Content/Texture2DParser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace Spectrum.Framework.Content 10 | { 11 | public class Texture2DData 12 | { 13 | public bool HasAlpha = false; 14 | } 15 | public class Texture2DParser : CachedContentParser 16 | { 17 | public Texture2DParser() : base("jpg", "png") 18 | { 19 | Prefix = "Textures"; 20 | } 21 | static bool IsPowerOfTwo(int x) 22 | { 23 | return (x & (x - 1)) == 0; 24 | } 25 | public static Texture2D LoadFromPath(string full_path) 26 | { 27 | Texture2D loaded = Texture2D.FromStream(SpectrumGame.Game.GraphicsDevice, new FileStream(full_path, FileMode.Open, FileAccess.Read)); 28 | //Of course you have to generate your own mip maps when you import from a file 29 | //why not. Thanks Monogame. 30 | bool mipMap = IsPowerOfTwo(loaded.Width) && IsPowerOfTwo(loaded.Height); 31 | Texture2D output = new Texture2D(SpectrumGame.Game.GraphicsDevice, loaded.Width, loaded.Height, mipMap, loaded.Format); 32 | Texture2DData outputTag = new Texture2DData(); 33 | output.Tag = outputTag; 34 | Color[] data = new Color[loaded.Height * loaded.Width]; 35 | Color[] lastLevelData = data; 36 | loaded.GetData(data); 37 | for (int level = 0; level < output.LevelCount; level++) 38 | { 39 | int stride = 1 << level; 40 | int levelHeight = loaded.Height / stride; 41 | int levelWidth = loaded.Width / stride; 42 | Color[] levelData = level == 0 ? data : new Color[levelHeight * levelWidth]; 43 | if (level > 0) 44 | { 45 | for (int x = 0; x < levelWidth; x++) 46 | { 47 | for (int y = 0; y < levelHeight; y++) 48 | { 49 | Vector4 sum = new Vector4(); 50 | for (int ix = 0; ix < 2; ix++) 51 | { 52 | for (int iy = 0; iy < 2; iy++) 53 | { 54 | Color c = lastLevelData[(x * 2 + ix) + (y * 2 + iy) * levelWidth * 2]; 55 | if (c.A < 255) 56 | { 57 | outputTag.HasAlpha = true; 58 | } 59 | sum += c.ToVector4(); 60 | } 61 | } 62 | levelData[x + y * levelWidth] = new Color(sum / 4); 63 | } 64 | } 65 | } 66 | output.SetData(level, null, levelData, 0, levelHeight * levelWidth); 67 | lastLevelData = levelData; 68 | } 69 | return output; 70 | } 71 | protected override Texture2D LoadData(string path, string name) 72 | { 73 | return LoadFromPath(path); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Framework/Context.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework 9 | { 10 | public class Context : Context 11 | { 12 | private Context() { } 13 | private T Previous; 14 | public override void Dispose() 15 | { 16 | Current = Previous; 17 | base.Dispose(); 18 | } 19 | private static AsyncLocal current = new AsyncLocal(); 20 | public static T Current { get => current.Value; private set => current.Value = value; } 21 | public static Context Create(T value) 22 | { 23 | var result = new Context() { Previous = Current }; 24 | Current = value; 25 | return result; 26 | } 27 | } 28 | public class Context : IDisposable 29 | { 30 | private IDisposable next; 31 | public virtual void Dispose() 32 | { 33 | next?.Dispose(); 34 | } 35 | public static Context Create(U value) => Context.Create(value); 36 | public Context Inject(U value) 37 | { 38 | var result = Create(value); 39 | result.next = this; 40 | return result; 41 | } 42 | public IDisposable Include(IDisposable disposable) 43 | { 44 | if (next != null) throw new InvalidOperationException("Next is already set"); 45 | next = disposable; 46 | return this; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Framework/Entities/Component.cs: -------------------------------------------------------------------------------- 1 | using Replicate; 2 | using Spectrum.Framework.Input; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Spectrum.Framework.Entities 10 | { 11 | public interface IUpdate 12 | { 13 | void Update(float dt); 14 | } 15 | public interface IFullUpdate : IUpdate 16 | { 17 | void PreStep(float step); 18 | void PostStep(float step); 19 | } 20 | public class Component 21 | { 22 | [ReplicateIgnore] 23 | public Entity E { get; set; } 24 | public virtual void Initialize(Entity e) { E = e; } 25 | } 26 | public class Component : Component 27 | { 28 | [ReplicateIgnore] 29 | public T P { get; set; } 30 | public override void Initialize(Entity e) 31 | { 32 | base.Initialize(e); 33 | if (!(e is T p)) throw new InvalidCastException($"Cannot convert from {e.GetType()} to {typeof(T)}"); 34 | P = p; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Framework/Entities/EntityCollection.cs: -------------------------------------------------------------------------------- 1 | using Spectrum.Framework.Graphics; 2 | using Spectrum.Framework.Physics; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Collections; 8 | 9 | namespace Spectrum.Framework.Entities 10 | { 11 | public class EntityCollection 12 | { 13 | public readonly Dictionary Map = new Dictionary(); 14 | public readonly Dictionary Compacted = new Dictionary(); 15 | private List updatedSorted = new List(); 16 | private List drawSorted = new List(); 17 | public event Action OnEntityAdded; 18 | public event Action OnEntityRemoved; 19 | public IEnumerable UpdateSorted { get { lock (this) { return updatedSorted.Where(e => !e.Destroying); } } } 20 | public IEnumerable DrawSorted { get { lock (this) { return drawSorted.Where(e => !e.Destroying); } } } 21 | public IEnumerable Destroying { get { lock (this) { return Map.Values.Where(e => e.Destroying); } } } 22 | public IEnumerable All { get { lock (this) { return Map.Values; } } } 23 | 24 | public void Add(Entity entity) 25 | { 26 | lock (this) 27 | { 28 | if (Map.ContainsKey(entity.ID)) 29 | throw new InvalidOperationException("An Entity with that ID has already been added to the collection"); 30 | Map[entity.ID] = entity; 31 | int updateIndex = updatedSorted.TakeWhile(e => e.UpdateOrder < entity.UpdateOrder).Count(); 32 | updatedSorted.Insert(updateIndex, entity); 33 | int drawIndex = drawSorted.TakeWhile(e => e.DrawOrder < entity.DrawOrder).Count(); 34 | drawSorted.Insert(drawIndex, entity); 35 | OnEntityAdded?.Invoke(entity); 36 | } 37 | } 38 | public void Compact(Entity entity) 39 | { 40 | lock (this) 41 | { 42 | if (Map.ContainsKey(entity.ID)) 43 | RemoveFromMap(entity.ID); 44 | Compacted[entity.ID] = entity; 45 | } 46 | } 47 | public void Uncompact(Entity entity) 48 | { 49 | lock (this) 50 | { 51 | Compacted.Remove(entity.ID); 52 | Add(entity); 53 | } 54 | } 55 | private Entity RemoveFromMap(Guid entityID) 56 | { 57 | var entity = Map[entityID]; 58 | Map.Remove(entityID); 59 | updatedSorted.Remove(entity); 60 | drawSorted.Remove(entity); 61 | OnEntityRemoved?.Invoke(entity); 62 | return entity; 63 | } 64 | public Entity Remove(Guid entityID) 65 | { 66 | lock (this) 67 | { 68 | Entity entity = null; 69 | if (Compacted.ContainsKey(entityID)) 70 | { 71 | entity = Compacted[entityID]; 72 | Compacted.Remove(entityID); 73 | } 74 | else if (Map.ContainsKey(entityID)) 75 | entity = RemoveFromMap(entityID); 76 | return entity; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Framework/Entities/GameObject2D.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Replicate; 3 | using Spectrum.Framework.Content; 4 | using Spectrum.Framework.Graphics; 5 | using Spectrum.Framework.Screens; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace Spectrum.Framework.Entities 13 | { 14 | [ReplicateType] 15 | public class GameObject2D : Entity 16 | { 17 | [ReplicateIgnore] 18 | public static readonly DrawablePart GameObject2DPart; 19 | static GameObject2D() 20 | { 21 | GameObject2DPart = DrawablePart.From(new List() 22 | { 23 | new CommonTex(new Vector3(0, 1, 0), Vector3.UnitZ, new Vector2(0, 0)), 24 | new CommonTex(new Vector3(1, 1, 0), Vector3.UnitZ, new Vector2(1, 0)), 25 | new CommonTex(new Vector3(0, 0, 0), Vector3.UnitZ, new Vector2(0, 1)), 26 | new CommonTex(new Vector3(1, 0, 0), Vector3.UnitZ, new Vector2(1, 1)) 27 | }, new List() { 0, 1, 2, 1, 2, 3 }); 28 | GameObject2DPart.effect = new SpectrumEffect() { LightingEnabled = false }; 29 | } 30 | public ImageAsset Texture; 31 | public Color Color = Color.White; 32 | public Rectangle Bounds; 33 | public Vector2 Position; 34 | public float Layer; 35 | public Matrix World => Matrix.CreateRotationZ(Rotation) * Matrix.CreateTranslation(Position.X, Position.Y, Layer); 36 | public float Rotation = 0; 37 | public Quaternion Orientation => Quaternion.CreateFromAxisAngle(Vector3.Up, Rotation); 38 | public override void Draw(float gameTime) 39 | { 40 | base.Draw(gameTime); 41 | if (Texture != null) 42 | { 43 | Batch3D.Current.DrawPart(GameObject2DPart, CreateTexTransform(Bounds) * World, new MaterialData() { DiffuseTexture = Texture.GetTexture(Bounds), DiffuseColor = Color, DisableLighting = true }); 44 | } 45 | } 46 | 47 | public static Matrix CreateTexTransform(Rectangle rect) 48 | { 49 | return Matrix.CreateScale(rect.Width, rect.Height, 0) * Matrix.CreateTranslation(rect.X, rect.Y, 0); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Framework/Entities/Interpolator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Spectrum.Framework.Entities 8 | { 9 | public class Interpolator : Interpolator 10 | { 11 | Func interpolator; 12 | public Interpolator(Func interpolator) => this.interpolator = interpolator; 13 | public override object GetValue(float weight, object currentValue, object target) 14 | { 15 | return interpolator(weight, (T)currentValue, (T)target); 16 | } 17 | } 18 | public abstract class Interpolator 19 | { 20 | private float period; 21 | private float periodRemaining; 22 | private object target; 23 | public bool NeedsUpdate => periodRemaining > 0; 24 | public void BeginInterpolate(float period, object target) 25 | { 26 | this.target = target; 27 | this.period = period; 28 | periodRemaining = period; 29 | } 30 | public virtual object Update(float elapsed, object currentValue) 31 | { 32 | if (periodRemaining > 0) 33 | { 34 | periodRemaining -= elapsed; 35 | return GetValue(1 - periodRemaining / period, currentValue, target); 36 | } 37 | return null; 38 | } 39 | public abstract object GetValue(float weight, object currentValue, object target); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Framework/FunctionalExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework 9 | { 10 | public static class FunctionalExtensions 11 | { 12 | public static object GetConstantValue(this Expression exp) 13 | { 14 | return Expression.Lambda>(Expression.Convert(exp, typeof(object))).Compile()(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Framework/Graphics/Animation/AnimationClip.cs: -------------------------------------------------------------------------------- 1 | #region File Description 2 | //----------------------------------------------------------------------------- 3 | // AnimationClip.cs 4 | // 5 | // Microsoft XNA Community Game Platform 6 | // Copyright (C) Microsoft Corporation. All rights reserved. 7 | //----------------------------------------------------------------------------- 8 | #endregion 9 | 10 | #region Using Statements 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | #endregion 15 | 16 | namespace Spectrum.Framework.Graphics.Animation 17 | { 18 | /// 19 | /// An animation clip is the runtime equivalent of the 20 | /// Microsoft.Xna.Framework.Content.Pipeline.Graphics.AnimationContent type. 21 | /// It holds all the keyframes needed to describe a single animation. 22 | /// 23 | public class AnimationClip 24 | { 25 | /// 26 | /// Constructs a new animation clip object. 27 | /// 28 | public AnimationClip(string name, float duration, Dictionary> translations, Dictionary> rotations) 29 | { 30 | Name = name; 31 | Duration = duration; 32 | Translations = translations; 33 | Rotations = rotations; 34 | } 35 | 36 | 37 | /// 38 | /// Private constructor for use by the XNB deserializer. 39 | /// 40 | private AnimationClip() 41 | { 42 | } 43 | 44 | public string Name { get; private set; } 45 | 46 | /// 47 | /// Gets the total length of the animation. 48 | /// 49 | public float Duration { get; private set; } 50 | 51 | 52 | /// 53 | /// Gets a combined list containing all the keyframes for all bones, 54 | /// sorted by time. 55 | /// 56 | public Dictionary> Translations { get; private set; } 57 | public Dictionary> Rotations { get; private set; } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Framework/Graphics/Animation/AnimationData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Spectrum.Framework.Graphics.Animation 8 | { 9 | public class AnimationData : DefaultDict { } 10 | } 11 | -------------------------------------------------------------------------------- /Framework/Graphics/Animation/Bone.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Spectrum.Framework.Graphics.Animation 8 | { 9 | public class Bone : ITransform 10 | { 11 | public string Id; 12 | public List Children; 13 | public Bone Parent; 14 | public Quaternion DefaultRotation; 15 | public Vector3 DefaultTranslation; 16 | public Matrix BindPose; 17 | public Matrix InverseBindPose; 18 | private Quaternion rotation; 19 | public Quaternion Rotation 20 | { 21 | get => rotation; 22 | set { SetDirty(); rotation = value; } 23 | } 24 | private Vector3 translation; 25 | public Vector3 Translation 26 | { 27 | get => translation; 28 | set { SetDirty(); translation = value; } 29 | } 30 | Vector3 ITransform.Position { get { ResolveDirty(); return (withParentTransform).Translation; } } 31 | // TODO: This could be like the above and pull out a rotation from the matrix 32 | Quaternion ITransform.Orientation => Parent != null ? Rotation.Concat(((ITransform)Parent).Orientation) : Rotation; 33 | Vector3 ITransform.Scale => Vector3.One; 34 | private Matrix transform; 35 | 36 | private Matrix withParentTransform; 37 | private bool dirty = true; 38 | private void SetDirty() 39 | { 40 | dirty = true; 41 | foreach (var child in Children) 42 | child.SetDirty(); 43 | } 44 | private void ResolveDirty() 45 | { 46 | if (dirty) 47 | { 48 | // TODO: I think this can be combined into one operation 49 | transform = rotation.ToMatrix() * Matrix.CreateTranslation(translation); 50 | withParentTransform = Parent == null ? transform : transform * Parent.WithParentTransform; 51 | dirty = false; 52 | } 53 | } 54 | public Matrix WithParentTransform 55 | { 56 | get 57 | { 58 | ResolveDirty(); 59 | return withParentTransform; 60 | } 61 | } 62 | public Matrix DeltaTransform => InverseBindPose * WithParentTransform; 63 | 64 | public Bone(string id, Bone parent) 65 | { 66 | Parent = parent; 67 | Children = new List(); 68 | DefaultRotation = Quaternion.Identity; 69 | DefaultTranslation = Vector3.Zero; 70 | BindPose = Matrix.Identity; 71 | InverseBindPose = Matrix.Identity; 72 | Id = id; 73 | } 74 | 75 | public Bone Clone(Dictionary bones, Bone parent = null) 76 | { 77 | Bone output = new Bone(Id, parent); 78 | output.DefaultRotation = DefaultRotation; 79 | output.DefaultTranslation = DefaultTranslation; 80 | output.BindPose = BindPose; 81 | output.InverseBindPose = InverseBindPose; 82 | output.transform = transform; 83 | bones[Id] = output; 84 | foreach (var child in Children) 85 | { 86 | output.Children.Add(child.Clone(bones, output)); 87 | } 88 | return output; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Framework/Graphics/Animation/Keyframe.cs: -------------------------------------------------------------------------------- 1 | #region File Description 2 | //----------------------------------------------------------------------------- 3 | // Keyframe.cs 4 | // 5 | // Microsoft XNA Community Game Platform 6 | // Copyright (C) Microsoft Corporation. All rights reserved. 7 | //----------------------------------------------------------------------------- 8 | #endregion 9 | 10 | #region Using Statements 11 | using System; 12 | using Microsoft.Xna.Framework; 13 | #endregion 14 | 15 | namespace Spectrum.Framework.Graphics.Animation 16 | { 17 | /// 18 | /// Describes the position of a single bone at a single point in time. 19 | /// 20 | public class Keyframe 21 | { 22 | /// 23 | /// Constructs a new keyframe object. 24 | /// 25 | public Keyframe(float time, T transform) 26 | { 27 | Time = time; 28 | Value = transform; 29 | } 30 | 31 | 32 | /// 33 | /// Private constructor for use by the XNB deserializer. 34 | /// 35 | private Keyframe() 36 | { 37 | } 38 | 39 | /// 40 | /// Gets the time offset from the start of the animation to this keyframe. 41 | /// 42 | public float Time { get; private set; } 43 | 44 | 45 | /// 46 | /// Gets the bone transform for this keyframe. 47 | /// 48 | public T Value { get; private set; } 49 | public Keyframe Next; 50 | } 51 | public static class KeyframeExtensions 52 | { 53 | public static T LerpTo(this Keyframe head, float time, T start, Func lerp) 54 | { 55 | while (head.Next != null && time > head.Next.Time) 56 | head = head.Next; 57 | if (head?.Next != null) 58 | { 59 | float w = (time - head.Time) / (head.Next.Time - head.Time); 60 | return lerp(head.Value, head.Next.Value, w); 61 | } 62 | return start; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Framework/Graphics/Animation/SkinningData.cs: -------------------------------------------------------------------------------- 1 | #region File Description 2 | //----------------------------------------------------------------------------- 3 | // SkinningData.cs 4 | // 5 | // Microsoft XNA Community Game Platform 6 | // Copyright (C) Microsoft Corporation. All rights reserved. 7 | //----------------------------------------------------------------------------- 8 | #endregion 9 | 10 | #region Using Statements 11 | using System.Collections.Generic; 12 | using Spectrum.Framework.Graphics.Animation; 13 | using Microsoft.Xna.Framework; 14 | using Newtonsoft.Json.Linq; 15 | #endregion 16 | 17 | namespace Spectrum.Framework.Graphics.Animation 18 | { 19 | /// 20 | /// Combines all the data needed to render and animate a skinned object. 21 | /// This is typically stored in the Tag property of the Model being animated. 22 | /// 23 | public class SkinningData 24 | { 25 | /// 26 | /// Constructs a new skinning data object. 27 | /// 28 | public SkinningData(Bone root, Dictionary bones) 29 | { 30 | Bones = bones; 31 | Root = root; 32 | } 33 | 34 | public void ToDefault() 35 | { 36 | foreach (Bone bone in Bones.Values) 37 | { 38 | bone.Translation = bone.DefaultTranslation; 39 | bone.Rotation = bone.DefaultRotation; 40 | } 41 | } 42 | 43 | public Bone Root { get; private set; } 44 | 45 | public Dictionary Bones { get; private set; } 46 | 47 | public SkinningData Clone() 48 | { 49 | Dictionary outBones = new Dictionary(); 50 | return new SkinningData(Root.Clone(outBones), outBones); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Framework/Graphics/Billboard.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using SharpDX.Direct2D1.Effects; 4 | using Spectrum.Framework.Entities; 5 | using Spectrum.Framework.Input; 6 | using Spectrum.Framework.Physics.Collision.Shapes; 7 | using Spectrum.Framework.Screens; 8 | using System; 9 | using System.Collections.Generic; 10 | using System.Linq; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace Spectrum.Framework.Graphics 15 | { 16 | public static class Billboard 17 | { 18 | public static readonly DrawablePart BillboardPart; 19 | static Billboard() 20 | { 21 | BillboardPart = DrawablePart.From(new List() 22 | { 23 | new CommonTex(new Vector3(-0.5f, -0.5f, 0), Vector3.Backward, new Vector2(0,1)), 24 | new CommonTex(new Vector3(0.5f, -0.5f, 0), Vector3.Backward, new Vector2(1, 1)), 25 | new CommonTex(new Vector3(-0.5f, 0.5f, 0), Vector3.Backward, new Vector2(0, 0)), 26 | new CommonTex(new Vector3(0.5f, 0.5f, 0), Vector3.Backward, new Vector2(1, 0)) 27 | }); 28 | } 29 | public static void Draw(Matrix world, Vector2 size, MaterialData material) 30 | { 31 | Batch3D.Current.DrawPart( 32 | BillboardPart, 33 | Matrix.CreateScale(size.X, size.Y, 1) * world, 34 | material 35 | ); 36 | } 37 | public static void Draw(Vector3 position, Vector2 size, 38 | MaterialData material, Vector3 offset = default) 39 | { 40 | Batch3D.Current.DrawPart( 41 | BillboardPart, 42 | Matrix.Create(offset, new Vector3(size.X, size.Y, 1)), 43 | material, 44 | options: new Batch3D.DrawOptions() 45 | { 46 | DisableInstancing = true, 47 | DynamicDraw = (args) => 48 | { 49 | args.Group.Properties.Effect.World = args.World * 50 | Matrix.CreateRotationY(-args.Phase.View.ToQuaternion().Yaw()) * 51 | Matrix.CreateTranslation(position); 52 | } 53 | } 54 | ); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Framework/Graphics/Effects/SkinnedEffect.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using Spectrum.Framework.Content; 3 | using Spectrum.Framework.Graphics.Animation; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Spectrum.Framework.Graphics 11 | { 12 | public class SpectrumSkinnedEffect : SpectrumEffect 13 | { 14 | private string[] BoneNames; 15 | private Microsoft.Xna.Framework.Matrix[] BoneTransforms; 16 | public SpectrumSkinnedEffect() : base(ContentHelper.Load("SkinnedEffect")) { } 17 | private SpectrumSkinnedEffect(SpectrumSkinnedEffect clone) : base(clone) { } 18 | public override bool CanInstance 19 | { 20 | get 21 | { 22 | return false; 23 | } 24 | } 25 | protected override void OnApply() 26 | { 27 | if (BoneTransforms != null) 28 | Parameters["Bones"].SetValue(BoneTransforms); 29 | base.OnApply(); 30 | } 31 | public void SetBoneNames(params string[] boneNames) 32 | { 33 | BoneTransforms = new Microsoft.Xna.Framework.Matrix[boneNames.Count()]; 34 | for (int i = 0; i < boneNames.Count(); i++) 35 | { 36 | BoneTransforms[i] = Matrix.Identity; 37 | } 38 | BoneNames = boneNames; 39 | } 40 | public void UpdateBoneTransforms(SkinningData SkinningData) 41 | { 42 | for (int i = 0; i < BoneTransforms.Count(); i++) 43 | { 44 | BoneTransforms[i] = SkinningData.Bones[BoneNames[i]].DeltaTransform; 45 | } 46 | } 47 | public override Effect Clone() 48 | { 49 | var result = new SpectrumSkinnedEffect(this); 50 | result.SetBoneNames(BoneNames); 51 | return result; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Framework/Graphics/Effects/TerrainEffect.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework.Graphics; 2 | using Spectrum.Framework.Content; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Spectrum.Framework.Graphics 9 | { 10 | public class TerrainEffect : SpectrumEffect 11 | { 12 | public float VertexBlend 13 | { 14 | get { return Parameters["VertexBlend"].GetValueSingle(); } 15 | set { Parameters["VertexBlend"].SetValue(value); } 16 | } 17 | 18 | public TerrainEffect(string textureA, string textureB, string textureC, string textureD) 19 | : base(ContentHelper.Load("TerrainEffect")) 20 | { 21 | Parameters["UseTexture"].SetValue(true); 22 | Parameters["MultiTextureA"].SetValue(ContentHelper.Load(textureA)); 23 | Parameters["MultiTextureB"].SetValue(ContentHelper.Load(textureB)); 24 | Parameters["MultiTextureC"].SetValue(ContentHelper.Load(textureC)); 25 | Parameters["MultiTextureD"].SetValue(ContentHelper.Load(textureD)); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Framework/Graphics/Effects/WaterEffect.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Spectrum.Framework.Content; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace Spectrum.Framework.Graphics 10 | { 11 | class WaterEffect : SpectrumEffect 12 | { 13 | public static float WaterPerturbation = 1.4f; 14 | public static Matrix ReflectionView; 15 | public static Matrix ReflectionProj; 16 | public static float WaterTime; 17 | protected override void OnApply() 18 | { 19 | base.OnApply(); 20 | Parameters["waterPerturbCoef"].SetValue(WaterPerturbation); 21 | Parameters["reflView"].SetValue(ReflectionView); 22 | Parameters["reflProj"].SetValue(ReflectionProj); 23 | Parameters["waterTime"].SetValue(WaterTime); 24 | Texture2D faff = Parameters["Reflection"].GetValueTexture2D(); 25 | Texture2D test = Parameters["Refraction"].GetValueTexture2D(); 26 | Parameters["Reflection"].SetValue(Water.reflectionRenderTarget); 27 | Parameters["Refraction"].SetValue(Water.refractionRenderTarget); 28 | } 29 | public WaterEffect(string waterBumpMap1, string waterBumpMap2) 30 | : base(ContentHelper.Load("WaterEffect")) 31 | { 32 | 33 | Parameters["WaterBumpBase"].SetValue(ContentHelper.Load(waterBumpMap1)); 34 | Parameters["WaterBump"].SetValue(ContentHelper.Load(waterBumpMap2)); 35 | Vector2 windDirection = (new Vector2(1, 2)); 36 | windDirection.Normalize(); 37 | Parameters["windDirection"].SetValue(windDirection); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Framework/Graphics/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.Xna.Framework; 6 | using Microsoft.Xna.Framework.Graphics; 7 | using Microsoft.Xna.Framework.Content; 8 | using Spectrum.Framework.Content; 9 | 10 | namespace Spectrum.Framework.Graphics 11 | { 12 | public static class Settings 13 | { 14 | public static Matrix reflectionProjection; 15 | public static Matrix lightProjection; 16 | public static bool enableWater = false; 17 | public static int waterQuality = 0; 18 | public const string WaterBumpMapBase = "waterbump"; 19 | public const string WaterBumpMap = "waterbump1"; 20 | public static Vector2 ScreenSize; 21 | static Settings() 22 | { 23 | lightProjection = Matrix.CreatePerspectiveFOV( 24 | (float)Math.PI / 20f, 25 | 1, 26 | 100, 1100f); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Framework/Graphics/SpecModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Newtonsoft.Json.Linq; 4 | using Spectrum.Framework.Content; 5 | using Spectrum.Framework.Graphics.Animation; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Collections; 11 | using Spectrum.Framework.Physics.LinearMath; 12 | 13 | namespace Spectrum.Framework.Graphics 14 | { 15 | public class SpecModel 16 | { 17 | public string Path { get; private set; } 18 | public string Name { get; private set; } 19 | public AnimationData Animations { get; set; } 20 | public SkinningData SkinningData { get; protected set; } 21 | private static int _modelIndex = 0; 22 | private int _partIndex = 0; 23 | 24 | public Dictionary MeshParts { get; private set; } 25 | public Dictionary Materials { get; private set; } 26 | public static implicit operator SpecModel(string path) 27 | { 28 | return ContentHelper.Load(path); 29 | } 30 | public SpecModel() 31 | { 32 | Name = $"model_{_modelIndex++}"; 33 | Path = null; 34 | MeshParts = new Dictionary(); 35 | Materials = new Dictionary(); 36 | SkinningData = null; 37 | } 38 | public SpecModel(string name, string path, 39 | Dictionary meshParts, Dictionary materials, SkinningData skinningData) 40 | { 41 | Name = name; 42 | Path = path; 43 | MeshParts = meshParts; 44 | Materials = materials; 45 | SkinningData = skinningData; 46 | } 47 | public JBBox Bounds 48 | { 49 | get 50 | { 51 | JBBox output = new JBBox(Vector3.Zero, Vector3.Zero); 52 | foreach (var part in this) 53 | { 54 | output.AddPoint(part.Bounds.Min); 55 | output.AddPoint(part.Bounds.Max); 56 | } 57 | return output; 58 | } 59 | } 60 | /// 61 | /// Be careful using this because the GameObject's RenderTasks will not get updated 62 | /// 63 | /// 64 | public void Add(DrawablePart part) 65 | { 66 | MeshParts[$"part_{_partIndex++}"] = part; 67 | } 68 | public void Update(float dt) 69 | { 70 | if (SkinningData != null) 71 | foreach (var part in MeshParts.Values) 72 | (part.effect as SpectrumSkinnedEffect)?.UpdateBoneTransforms(SkinningData); 73 | } 74 | 75 | public IEnumerator GetEnumerator() 76 | { 77 | return MeshParts.Values.GetEnumerator(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Framework/Graphics/Water.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.Xna.Framework.Graphics; 6 | using Spectrum.Framework.Graphics; 7 | using Microsoft.Xna.Framework; 8 | using Spectrum.Framework; 9 | using Spectrum.Framework.Network; 10 | using Spectrum.Framework.Physics; 11 | using Spectrum.Framework.Entities; 12 | 13 | namespace Spectrum.Framework.Graphics 14 | { 15 | public class Water : GameObject 16 | { 17 | public const string waterBump1 = "waterbump"; 18 | public const string waterBump2 = "waterbump1"; 19 | public const float waterHeight = 0; 20 | public static RenderTarget2D refractionRenderTarget; 21 | public static RenderTarget2D reflectionRenderTarget; 22 | public VertexBuffer waterV; 23 | public int size; 24 | int numVertices = 32; 25 | public Water() 26 | { 27 | isStatic = true; 28 | AllowReplicate = false; 29 | } 30 | public override void Initialize() 31 | { 32 | Model = new SpecModel(); 33 | base.Initialize(); 34 | waterV = new VertexBuffer(SpectrumGame.Game.GraphicsDevice, VertexPositionTexture.VertexDeclaration, numVertices * numVertices, BufferUsage.WriteOnly); 35 | float[,] heights = new float[numVertices, numVertices]; 36 | VertexPositionTexture[] verts = new VertexPositionTexture[numVertices * numVertices]; 37 | for (int x = 0; x < numVertices; x++) 38 | { 39 | for (int y = 0; y < numVertices; y++) 40 | { 41 | verts[x + y * numVertices] = (VertexPositionTexture)VertexHelper.getVertex(x, y, heights, size * 1.0f / (numVertices - 1), Constructor); 42 | } 43 | } 44 | waterV.SetData(verts); 45 | IndexBuffer iBuffer = VertexHelper.MakeIndexBuffer(VertexHelper.getIndexList(numVertices, numVertices).ToList()); 46 | DrawablePart p = new DrawablePart(waterV, iBuffer); 47 | p.effect = new WaterEffect(waterBump1, waterBump2); 48 | Model.Add(p); 49 | } 50 | public static void ResetRenderTargets() 51 | { 52 | refractionRenderTarget?.Dispose(); 53 | refractionRenderTarget = new RenderTarget2D(SpectrumGame.Game.GraphicsDevice, (int)(2048.0 / Math.Pow(2, Settings.waterQuality)), 54 | (int)(2048.0 / Math.Pow(2, Settings.waterQuality)), false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8); 55 | reflectionRenderTarget?.Dispose(); 56 | reflectionRenderTarget = new RenderTarget2D(SpectrumGame.Game.GraphicsDevice, (int)(2048.0 / Math.Pow(2, Settings.waterQuality)), 57 | (int)(2048.0 / Math.Pow(2, Settings.waterQuality)), false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8); 58 | } 59 | private static VertexPositionTexture Constructor(VertexArgs args) 60 | { 61 | return new VertexPositionTexture(args.pos, args.texturePos); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Framework/IDebug.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Spectrum.Framework 9 | { 10 | public interface IDebug 11 | { 12 | string Debug(); 13 | void DebugDraw(float gameTime); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Framework/Input/Axis.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Input; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Spectrum.Framework.Input 9 | { 10 | public interface IAxis 11 | { 12 | float Value(InputState input, PlayerInformation player); 13 | } 14 | public struct KeyboardAxis : IAxis 15 | { 16 | public Keys Positive; 17 | public Keys Negative; 18 | public float Scalar; 19 | public KeyboardAxis(Keys positive, Keys negative, float scalar = 1) 20 | { 21 | Positive = positive; 22 | Negative = negative; 23 | Scalar = scalar; 24 | } 25 | 26 | public float Value(InputState input, PlayerInformation player) 27 | { 28 | if (!player.UsesKeyboard) return 0; 29 | float output = 0; 30 | if (input.IsKeyDown(Positive)) 31 | output += 1; 32 | if (input.IsKeyDown(Negative)) 33 | output -= 1; 34 | return output * Scalar; 35 | } 36 | } 37 | public struct GamepadAxis : IAxis 38 | { 39 | public GamepadAxisType Axis; 40 | public float Scalar; 41 | public GamepadAxis(GamepadAxisType axis, float scalar = 1) 42 | { 43 | Axis = axis; 44 | Scalar = scalar; 45 | } 46 | public float Value(InputState input, PlayerInformation player) 47 | { 48 | float output = 0; 49 | foreach (int gamepadIndex in player.UsedGamepads) 50 | { 51 | output += input.Gamepads[gamepadIndex].Axis(Axis); 52 | } 53 | return output * Scalar; 54 | } 55 | } 56 | public struct MouseAxis : IAxis 57 | { 58 | public bool horizontal; 59 | public float scalar; 60 | public MouseAxis(bool horizontal, float scalar = 0.001f) 61 | { 62 | this.horizontal = horizontal; 63 | this.scalar = scalar; 64 | } 65 | 66 | public float Value(InputState input, PlayerInformation player) 67 | { 68 | if (horizontal) 69 | return input.CursorState.DX * scalar; 70 | else 71 | return input.CursorState.DY * scalar; 72 | } 73 | } 74 | public struct Axis1 75 | { 76 | public List Axes; 77 | public Axis1(IAxis axis) 78 | { 79 | Axes = new List(); 80 | Axes.Add(axis); 81 | } 82 | public float Value(InputState input, PlayerInformation player) 83 | { 84 | float output = 0; 85 | foreach (IAxis axis in Axes) 86 | { 87 | output += axis.Value(input, player); 88 | } 89 | return Math.Min(Math.Max(output, -1), 1); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Framework/Input/InputLayout.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.Xna.Framework.Input; 6 | 7 | namespace Spectrum.Framework.Input 8 | { 9 | public class InputLayout 10 | { 11 | public static Dictionary Profiles = new Dictionary(); 12 | public static InputLayout Default; 13 | 14 | public DefaultDict KeyBindings = new DefaultDict(() => new KeyBinding(), true); 15 | public Dictionary Axes1 = new Dictionary(); 16 | 17 | public static void Init() 18 | { 19 | Default = new InputLayout("Default"); 20 | Default.Add("MenuLeft", new KeyBind(Keys.Left)); 21 | Default.Add("MenuRight", new KeyBind(Keys.Right)); 22 | Default.Add("MenuUp", new KeyBind(Keys.Up)); 23 | Default.Add("MenuDown", new KeyBind(Keys.Down)); 24 | Default.Add("MenuCycleF", new KeyBind(Keys.Tab)); 25 | Default.Add("MenuCycleB", new KeyBind(Keys.Tab, modifiers: new KeyBind(Keys.LeftShift))); 26 | Default.Add("GoBack", new KeyBind(Keys.Escape)); 27 | Default.Add("Continue", new KeyBind(Keys.Enter)); 28 | } 29 | 30 | public InputLayout(string name) 31 | { 32 | if (Profiles.ContainsKey(name)) throw new ArgumentException("An input profile with that name already exists"); 33 | Profiles[name] = this; 34 | } 35 | 36 | public void Add(string binding, KeyBind option) 37 | { 38 | KeyBindings[binding].Options.Add(option); 39 | } 40 | public static void AddBind(string binding, KeyBind option, string profile = "Default") 41 | { 42 | Profiles[profile].Add(binding, option); 43 | } 44 | 45 | public void AddBindClass(Type type) 46 | { 47 | foreach (var field in type.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static)) 48 | { 49 | if (field.FieldType != typeof(KeyBinding)) continue; 50 | KeyBindings[field.Name] = (KeyBinding)field.GetValue(null); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Framework/Input/PlayerInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Spectrum.Framework.Input 7 | { 8 | [Flags] 9 | public enum PlayerInputs 10 | { 11 | Keyboard = 1, 12 | Gamepad1 = 2, 13 | Gamepad2 = 4, 14 | Gamepad3 = 8, 15 | Gamepad4 = 16, 16 | Joystick1 = 32, 17 | Joystick2 = 64, 18 | Joystick3 = 128, 19 | Joystick4 = 256, 20 | } 21 | public class PlayerInformation 22 | { 23 | public static PlayerInformation Default = new PlayerInformation(PlayerInputs.Keyboard | PlayerInputs.Gamepad1, "Default"); 24 | 25 | public PlayerInputs PlayerType; 26 | public InputLayout Layout; 27 | 28 | public bool UsesKeyboard; 29 | public List UsedGamepads; 30 | public List UsedJoysticks; 31 | 32 | public PlayerInformation(PlayerInputs playerType, string inputLayout) 33 | { 34 | PlayerType = playerType; 35 | Layout = InputLayout.Profiles[inputLayout]; 36 | UsedGamepads = new List(); 37 | UsesKeyboard = false; 38 | UsedJoysticks = new List(); 39 | foreach (PlayerInputs input in Enum.GetValues(typeof(PlayerInputs))) 40 | { 41 | switch (input) 42 | { 43 | case PlayerInputs.Keyboard: 44 | UsesKeyboard = true; 45 | break; 46 | case PlayerInputs.Gamepad1: 47 | UsedGamepads.Add(0); 48 | break; 49 | case PlayerInputs.Gamepad2: 50 | UsedGamepads.Add(1); 51 | break; 52 | case PlayerInputs.Gamepad3: 53 | UsedGamepads.Add(2); 54 | break; 55 | case PlayerInputs.Gamepad4: 56 | UsedGamepads.Add(3); 57 | break; 58 | case PlayerInputs.Joystick1: 59 | UsedJoysticks.Add(0); 60 | break; 61 | case PlayerInputs.Joystick2: 62 | UsedJoysticks.Add(1); 63 | break; 64 | case PlayerInputs.Joystick3: 65 | UsedJoysticks.Add(2); 66 | break; 67 | case PlayerInputs.Joystick4: 68 | UsedJoysticks.Add(3); 69 | break; 70 | default: 71 | break; 72 | } 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Framework/Input/SpectrumMouse.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Windows.Forms; 8 | 9 | namespace Spectrum.Framework.Input 10 | { 11 | public class CursorState 12 | { 13 | public bool[] buttons; 14 | public Point P; 15 | public float DX; 16 | public float DY; 17 | public int ScrollX; 18 | public int ScrollY; 19 | public CursorState() 20 | { 21 | P = new Point { X = -1, Y = -1 }; 22 | buttons = new bool[16]; 23 | } 24 | } 25 | 26 | public class SpectrumMouse 27 | { 28 | public static bool UseRaw = true; 29 | Point mousePosition; 30 | public SpectrumMouse() 31 | { 32 | SpectrumGame.Game.WindowForm.MouseMove += WindowForm_MouseMove; 33 | } 34 | 35 | private void WindowForm_MouseMove(object sender, MouseEventArgs e) 36 | { 37 | mousePosition.X = e.X; 38 | mousePosition.Y = e.Y; 39 | } 40 | 41 | public CursorState GetCurrentState(CursorState previous = null) 42 | { 43 | bool[] buttons = new bool[16]; 44 | RawMouse.buttons.CopyTo(buttons, 0); 45 | return new CursorState() 46 | { 47 | buttons = buttons, 48 | P = mousePosition, 49 | DX = RawMouse.lastX / 2.0f, 50 | DY = RawMouse.lastY / 2.0f, 51 | ScrollY = RawMouse.lastZ, 52 | }; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Framework/Input/VR/VRBinding.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Spectrum.Framework.Input 8 | { 9 | public enum VRButton 10 | { 11 | System = 0, 12 | ApplicationMenu = 1, 13 | Grip = 2, 14 | DPad_Left = 3, 15 | DPad_Up = 4, 16 | DPad_Right = 5, 17 | DPad_Down = 6, 18 | A = 7, 19 | ProximitySensor = 31, 20 | Axis0 = 32, 21 | Axis1 = 33, 22 | Axis2 = 34, 23 | Axis3 = 35, 24 | Axis4 = 36, 25 | SteamVR_Touchpad = 32, 26 | SteamVR_Trigger = 33, 27 | Max = 64, 28 | } 29 | [Flags] 30 | public enum VRHand 31 | { 32 | Left = 1, 33 | Right = 2 34 | } 35 | [Flags] 36 | public enum VRPressType 37 | { 38 | Pressed = 1, 39 | Touched = 2 40 | } 41 | public struct VRBinding 42 | { 43 | public VRHand Hand; 44 | public VRButton Button; 45 | public VRPressType PressType; 46 | public VRBinding(VRButton button, VRHand hand = VRHand.Left | VRHand.Right, VRPressType pressType = VRPressType.Pressed) 47 | { 48 | Hand = hand; 49 | Button = button; 50 | PressType = pressType; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Framework/Input/VR/VRHmd.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Spectrum.Framework.VR; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Spectrum.Framework.Input 10 | { 11 | public struct VRHMD 12 | { 13 | public Vector3 Position; 14 | public Vector3 PositionDelta; 15 | public Vector3 Direction; 16 | public Quaternion Rotation; 17 | public Quaternion RotationDelta; 18 | public void Update() 19 | { 20 | var position = SpecVR.HeadPose.Translation; 21 | PositionDelta = position - Position; 22 | Position = position; 23 | var rotation = SpecVR.HeadPose.ToQuaternion(); 24 | RotationDelta = rotation * Rotation.Inverse(); 25 | Rotation = rotation; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Framework/JConvert.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using Replicate.MetaData; 5 | using Spectrum.Framework.Content; 6 | using Spectrum.Framework.Entities; 7 | using Spectrum.Framework.Graphics; 8 | using Spectrum.Framework.JSON; 9 | using Spectrum.Framework.Physics.Collision.Shapes; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.IO; 13 | using System.Linq; 14 | using System.Text; 15 | 16 | namespace Spectrum.Framework 17 | { 18 | public class JConvert 19 | { 20 | public static List Converters = new List(); 21 | static JsonSerializerSettings Settings { 22 | get 23 | { 24 | var settings = new JsonSerializerSettings(); 25 | foreach (var converter in Converters) 26 | { 27 | settings.Converters.Add(converter); 28 | } 29 | return settings; 30 | } 31 | } 32 | static JConvert() 33 | { 34 | Converters.Add(new JVectorConverter()); 35 | Converters.Add(new JPointConverter()); 36 | Converters.Add(new JMatrixConverter()); 37 | Converters.Add(new JShapeConverter()); 38 | Converters.Add(new SimpleConverter( 39 | model => model.Name, 40 | token => ContentHelper.Load((string)token))); 41 | Converters.Add(new JInitDataConverter()); 42 | //Converters.Add(new SimpleConverter( 43 | // typeData => typeData.TypeData.Name, 44 | // token => TypeHelper.Model.Types[(string)token]); 45 | } 46 | public static object Deserialize(JToken token, Type targetType) 47 | { 48 | return token?.ToObject(targetType, JsonSerializer.Create(Settings)); 49 | } 50 | public static T Deserialize(JToken token) 51 | { 52 | return (T)(Deserialize(token, typeof(T)) ?? default(T)); 53 | } 54 | public static T Deserialize(string json) 55 | { 56 | return (T)JsonConvert.DeserializeObject(json, typeof(T), Settings); 57 | } 58 | public static T DeserializeFile(string path) 59 | { 60 | using (var reader = new StreamReader(File.OpenRead(path))) 61 | return Deserialize(reader.ReadToEnd()); 62 | } 63 | public static string Serialize(object obj) 64 | { 65 | return JsonConvert.SerializeObject(obj, Formatting.Indented, Settings); 66 | } 67 | public static void SerializeFile(object obj, string path) 68 | { 69 | using (var f = File.Open(path, FileMode.OpenOrCreate)) 70 | { 71 | f.SetLength(0); 72 | using (var sw = new StreamWriter(f)) 73 | sw.Write(Serialize(obj)); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Framework/JSON/JMatrixConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Linq; 9 | 10 | namespace Spectrum.Framework.JSON 11 | { 12 | class JMatrixConverter : JsonConverter 13 | { 14 | public override bool CanConvert(Type objectType) 15 | { 16 | return objectType == typeof(Matrix); 17 | } 18 | 19 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 20 | { 21 | var token = JToken.ReadFrom(reader); 22 | if (token is JArray array) 23 | { 24 | return MatrixHelper.FromArray(array.Select(_token => (float)_token).ToArray()); 25 | } 26 | if (token is JObject obj) 27 | { 28 | Vector3 rotation = Vector3.Zero; 29 | Vector3 scale = Vector3.One; 30 | Vector3 translation = Vector3.Zero; 31 | if (obj["rotation"] != null) 32 | rotation = JConvert.Deserialize(obj["rotation"]) * (float)(Math.PI / 180); 33 | if (obj["scale"] != null) 34 | { 35 | if (obj["scale"].Type == JTokenType.Array) 36 | scale = JConvert.Deserialize(obj["scale"]); 37 | if (obj["scale"].Type == JTokenType.Float) 38 | scale = new Vector3((float)obj["scale"]); 39 | } 40 | if (obj["translation"] != null) 41 | translation = JConvert.Deserialize(obj["translation"]); 42 | return Matrix.CreateFromYawPitchRoll(rotation.Y, rotation.X, rotation.Z) * Matrix.CreateScale(scale) * Matrix.CreateTranslation(translation); 43 | } 44 | return null; 45 | } 46 | 47 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 48 | { 49 | var oldF = writer.Formatting; 50 | writer.Formatting = Formatting.None; 51 | serializer.Serialize(writer, ((Matrix)value).ToArray()); 52 | writer.Formatting = oldF; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Framework/JSON/JPointConverter.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using Replicate; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Spectrum.Framework.JSON 12 | { 13 | class JPointConverter : JsonConverter 14 | { 15 | public static bool IsNumeric(JToken token) 16 | { 17 | return token.Type == JTokenType.Float || token.Type == JTokenType.Integer; 18 | } 19 | 20 | public override bool CanConvert(Type objectType) 21 | { 22 | objectType = objectType.DeNullable(); 23 | return objectType == typeof(Point3) || objectType == typeof(Point); 24 | } 25 | 26 | public override object ReadJson(JsonReader reader, Type targetType, object existingValue, JsonSerializer serializer) 27 | { 28 | var token = JToken.ReadFrom(reader); 29 | targetType = targetType.DeNullable(); 30 | if (token is JArray array) 31 | { 32 | if (targetType == typeof(Point3) && array.Count == 3 && array.All(IsNumeric)) 33 | return new Point3((int)array[0], (int)array[1], (int)array[2]); 34 | if (targetType == typeof(Point) && array.Count == 2 && array.All(IsNumeric)) 35 | return new Point((int)array[0], (int)array[1]); 36 | } 37 | return null; 38 | } 39 | 40 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 41 | { 42 | JArray array = null; 43 | if (value is Point3 point3) 44 | array = new JArray(point3.X, point3.Y, point3.Z); 45 | if (value is Point point2) 46 | array = new JArray(point2.X, point2.Y); 47 | writer.Formatting = Formatting.None; 48 | array.WriteTo(writer); 49 | writer.Formatting = Formatting.Indented; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Framework/JSON/JShapeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Linq; 9 | using Spectrum.Framework.Physics.Collision.Shapes; 10 | 11 | namespace Spectrum.Framework.JSON 12 | { 13 | class JShapeConverter : JsonConverter 14 | { 15 | public override bool CanConvert(Type objectType) 16 | { 17 | return objectType == typeof(Shape) || objectType == typeof(BoxShape) || objectType == typeof(ListMultishape); 18 | } 19 | 20 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 21 | { 22 | var token = JToken.ReadFrom(reader); 23 | if (token is JObject obj) 24 | { 25 | var shapeType = (string)obj["type"]; 26 | switch (shapeType) 27 | { 28 | case "box": 29 | return new BoxShape(JConvert.Deserialize(obj["size"]), 30 | JConvert.Deserialize(obj["position"])); 31 | case "list": 32 | return new ListMultishape(((JArray)obj["shapes"]) 33 | .Select(shape => JConvert.Deserialize(shape)).ToList()); 34 | default: 35 | break; 36 | } 37 | } 38 | return null; 39 | } 40 | 41 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 42 | { 43 | if (value is BoxShape box) 44 | serializer.Serialize(writer, new 45 | { 46 | type = "box", 47 | size = box.Size, 48 | position = box.Position, 49 | }); 50 | else if (value is ListMultishape listShape) 51 | { 52 | serializer.Serialize(writer, new 53 | { 54 | type = "list", 55 | shapes = listShape.Shapes 56 | }); 57 | } 58 | else 59 | throw new NotImplementedException(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Framework/JSON/JVectorConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Linq; 9 | using Replicate; 10 | 11 | namespace Spectrum.Framework.JSON 12 | { 13 | class JVectorConverter : JsonConverter 14 | { 15 | public static bool IsNumeric(JToken token) 16 | { 17 | return token.Type == JTokenType.Float || token.Type == JTokenType.Integer; 18 | } 19 | 20 | public override bool CanConvert(Type objectType) 21 | { 22 | objectType = objectType.DeNullable(); 23 | return objectType == typeof(Vector2) || objectType == typeof(Vector3) || objectType == typeof(Vector4); 24 | } 25 | 26 | public override object ReadJson(JsonReader reader, Type targetType, object existingValue, JsonSerializer serializer) 27 | { 28 | var token = JToken.ReadFrom(reader); 29 | targetType = targetType.DeNullable(); 30 | if (token is JArray array) 31 | { 32 | if (targetType == typeof(Vector3) && array.Count == 3 && array.All(IsNumeric)) 33 | return new Vector3((float)array[0], (float)array[1], (float)array[2]); 34 | if (targetType == typeof(Vector2) && array.Count == 2 && array.All(IsNumeric)) 35 | return new Vector2((float)array[0], (float)array[1]); 36 | if (targetType == typeof(Vector4) && array.Count == 4 && array.All(IsNumeric)) 37 | return new Vector4((float)array[0], (float)array[1], (float)array[2], (float)array[3]); 38 | } 39 | return null; 40 | } 41 | 42 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 43 | { 44 | JArray array = null; 45 | if (value is Vector3 vector3) 46 | array = new JArray(vector3.X, vector3.Y, vector3.Z); 47 | if (value is Vector2 vector2) 48 | array = new JArray(vector2.X, vector2.Y); 49 | if (value is Vector4 vector4) 50 | array = new JArray(vector4.X, vector4.Y, vector4.Z, vector4.W); 51 | var oldF = writer.Formatting; 52 | writer.Formatting = Formatting.None; 53 | array.WriteTo(writer); 54 | writer.Formatting = oldF; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Framework/JSON/SimpleConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Linq; 8 | 9 | namespace Spectrum.Framework.JSON 10 | { 11 | class SimpleConverter : JsonConverter 12 | { 13 | readonly Func Writer; 14 | readonly Func Reader; 15 | public SimpleConverter(Func writer, Func reader) 16 | { 17 | Writer = writer; 18 | Reader = reader; 19 | } 20 | 21 | public override bool CanConvert(Type objectType) 22 | { 23 | return objectType == typeof(T); 24 | } 25 | 26 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 27 | { 28 | return Reader(JToken.ReadFrom(reader)); 29 | } 30 | 31 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 32 | { 33 | Writer((T)value).WriteTo(writer); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Framework/LSystem/LNode.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Spectrum.Framework.LSystem 8 | { 9 | public class LNode 10 | { 11 | public Matrix Transform { get { return Matrix.CreateTranslation(Vector3.Up) * Matrix.CreateFromAxisAngle(new Vector3((float)Math.Cos(R2), 0, (float)Math.Sin(R2)), R1) * Matrix.CreateScale(D) * root; } } 12 | public List Children = new List(); 13 | public float D; 14 | public float R1; 15 | public float R2; 16 | private Matrix root; 17 | public LNode(float D, float R1, float R2, Matrix root) 18 | { 19 | this.root = root; 20 | this.D = D; 21 | this.R1 = R1; 22 | this.R2 = R2; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Framework/Network/Handshake.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Spectrum.Framework.Network 7 | { 8 | public enum HandshakeStage 9 | { 10 | Wait = 0, 11 | Begin = 1, 12 | AckBegin = 2, 13 | PartialRequest = 3, 14 | PartialResponse = 4, 15 | Completed = 5 16 | } 17 | 18 | public struct HandshakeHandler 19 | { 20 | public Action Receive; 21 | public Action Write; 22 | public HandshakeHandler(Action write, Action receive) 23 | { 24 | Write = write; 25 | Receive = receive; 26 | } 27 | } 28 | 29 | public class Handshake 30 | { 31 | private static Dictionary> handshakeHandlers = new Dictionary>(); 32 | public static void RegisterHandshakeHandler(HandshakeStage stage, HandshakeHandler handler) 33 | { 34 | if (!handshakeHandlers.ContainsKey(stage)) { handshakeHandlers[stage] = new List(); } 35 | handshakeHandlers[stage].Add(handler); 36 | } 37 | public static void WriteHandshake(HandshakeStage stage, NetID peer, NetMessage message) 38 | { 39 | List handlers; 40 | if (!handshakeHandlers.TryGetValue(stage, out handlers)) { return; } 41 | foreach (HandshakeHandler handler in handlers) 42 | { 43 | handler.Write(peer, message); 44 | } 45 | } 46 | public static void ReadHandshake(HandshakeStage stage, NetID peer, NetMessage message) 47 | { 48 | List handlers; 49 | if (!handshakeHandlers.TryGetValue(stage, out handlers)) { return; } 50 | foreach (HandshakeHandler handler in handlers) 51 | { 52 | handler.Receive(peer, message); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Framework/Network/MultiplayerReceiver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Text; 9 | using System.Threading; 10 | 11 | namespace Spectrum.Framework.Network 12 | { 13 | public class MultiplayerReceiver 14 | { 15 | private NetworkStream NetStream; 16 | private TcpClient Client; 17 | private Connection Connection; 18 | public bool Running { get; private set; } 19 | public MultiplayerReceiver(Connection conn) 20 | { 21 | Connection = conn; 22 | Client = conn.Client; 23 | NetStream = Client.GetStream(); 24 | //new AsyncListenData(ListenForControlData).BeginInvoke(null, this); 25 | Thread receiverThread = new Thread(new ThreadStart(ListenForControlData)) 26 | { 27 | IsBackground = true 28 | }; 29 | Running = true; 30 | receiverThread.Start(); 31 | } 32 | public void ListenForControlData() 33 | { 34 | try 35 | { 36 | DateTime lastHeard = DateTime.Now; 37 | NetStream.ReadTimeout = 10000; 38 | byte[] guidBuffer = new byte[16]; 39 | while (Running && Client.Connected) 40 | { 41 | byte comType = (byte)NetStream.ReadByte(); 42 | NetMessage header = new NetMessage(NetStream); 43 | Connection.PeerID = header.Read(); 44 | NetMessage message = ReadFromControlStream(NetStream); 45 | switch (comType) 46 | { 47 | case 255: 48 | throw new InvalidDataException($"Bad data from host: {Connection.PeerID}"); 49 | default: 50 | Connection.MPService.ReceiveMessage(comType, Connection.PeerID, message); 51 | break; 52 | } 53 | lastHeard = DateTime.Now; 54 | } 55 | } 56 | catch (InvalidDataException) { } 57 | catch (IOException) { } 58 | catch (NullReferenceException) { } 59 | finally 60 | { 61 | Running = false; 62 | } 63 | } 64 | 65 | public NetMessage ReadFromControlStream(Stream netStream) 66 | { 67 | NetMessage messageOut = new NetMessage(netStream); 68 | byte testBytes = (byte)netStream.ReadByte(); 69 | if (testBytes != 255) 70 | { 71 | DebugPrinter.Print($"Bad data from host: {Connection.PeerID}"); 72 | Connection.Terminate(); 73 | } 74 | return messageOut; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Framework/Network/NetID.cs: -------------------------------------------------------------------------------- 1 | using ProtoBuf; 2 | using Replicate; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Spectrum.Framework.Network 9 | { 10 | [ReplicateType] 11 | public struct NetID : IComparable 12 | { 13 | public ulong? SteamID; 14 | public Guid? Guid; 15 | 16 | public static bool operator >(NetID lhs, NetID rhs) 17 | { 18 | return lhs.CompareTo(rhs) > 0; 19 | } 20 | public static bool operator <(NetID lhs, NetID rhs) 21 | { 22 | return lhs.CompareTo(rhs) < 0; 23 | } 24 | 25 | public static bool operator ==(NetID lhs, NetID rhs) 26 | { 27 | return lhs.Equals(rhs); 28 | } 29 | 30 | public static bool operator !=(NetID lhs, NetID rhs) 31 | { 32 | return !lhs.Equals(rhs); 33 | } 34 | 35 | public NetID(Guid guid) 36 | { 37 | this.Guid = guid; 38 | SteamID = null; 39 | } 40 | 41 | public NetID(ulong steamID) 42 | { 43 | SteamID = steamID; 44 | Guid = null; 45 | } 46 | 47 | public override bool Equals(object obj) 48 | { 49 | if(obj is NetID) 50 | { 51 | NetID other = (NetID)obj; 52 | return other.Guid == Guid && other.SteamID == SteamID; 53 | } 54 | return false; 55 | } 56 | 57 | public override int GetHashCode() => Guid?.GetHashCode() ?? SteamID.GetHashCode(); 58 | 59 | public int CompareTo(object obj) 60 | { 61 | if(obj is NetID) 62 | { 63 | NetID otherNetID = (NetID)obj; 64 | if (SteamID != null) 65 | { 66 | if (otherNetID.SteamID == null) 67 | return 1; 68 | return SteamID.Value.CompareTo(otherNetID.SteamID.Value); 69 | } 70 | else if(Guid != null) 71 | { 72 | if (otherNetID.Guid == null) 73 | return 1; 74 | return Guid.Value.CompareTo(otherNetID.Guid.Value); 75 | } 76 | return 0; 77 | } 78 | throw new ArgumentException("Cannot compare to anything but NetID"); 79 | } 80 | public override string ToString() 81 | { 82 | if (Guid.HasValue) return Guid.Value.ToString(); 83 | else if (SteamID.HasValue) return $"Steam: {SteamID.Value}"; 84 | return "null"; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Framework/Network/ReplicationData.cs: -------------------------------------------------------------------------------- 1 | using Replicate.MetaData; 2 | using Spectrum.Framework.Entities; 3 | using Spectrum.Framework.Network.Surrogates; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace Spectrum.Framework.Network 12 | { 13 | public interface IReplicatable 14 | { 15 | InitData InitData { get; set; } 16 | ReplicationData ReplicationData { get; set; } 17 | } 18 | public class ReplicationData 19 | { 20 | public const float DefaultReplicationPeriod = .2f; 21 | public TypeAccessor TypeData { get; private set; } 22 | public IReplicatable Replicated { get; private set; } 23 | public ReplicationData(TypeAccessor typeData, IReplicatable replicated) 24 | { 25 | TypeData = typeData; 26 | Replicated = replicated; 27 | } 28 | private Dictionary interpolators = new Dictionary(); 29 | 30 | public void SetInterpolator(string attributeName, Func interpolator) 31 | { 32 | interpolators[attributeName] = new Interpolator(interpolator); 33 | } 34 | 35 | public void HandleRPC(string name, object[] args) 36 | { 37 | if (TypeData.Methods.ContainsKey(name)) 38 | TypeData.Methods[name].Invoke(Replicated, args); 39 | } 40 | 41 | public virtual NetMessage WriteReplicationData(NetMessage output) 42 | { 43 | Primitive[] fields = TypeData.Members.Values.ToList().ConvertAll(x => new Primitive(x.GetValue(Replicated))).ToArray(); 44 | output.Write(fields); 45 | return output; 46 | } 47 | public virtual void ReadReplicationData(NetMessage input) 48 | { 49 | Primitive[] fields = input.Read(); 50 | var properties = TypeData.Members.Values.ToList(); 51 | for (int i = 0; i < fields.Count(); i++) 52 | { 53 | var replicate = properties[i]; 54 | if (interpolators.ContainsKey(replicate.Info.Name)) 55 | interpolators[replicate.Info.Name].BeginInterpolate(DefaultReplicationPeriod * 2, fields[i].Object); 56 | else 57 | replicate.SetValue(Replicated, fields[i].Object); 58 | } 59 | } 60 | public void Interpolate(float dt) 61 | { 62 | foreach (var interpolator in interpolators) 63 | { 64 | if (!interpolator.Value.NeedsUpdate) 65 | continue; 66 | var member = TypeData[interpolator.Key]; 67 | object value = interpolator.Value.Update(dt, member.GetValue(Replicated)); 68 | if (value != null) 69 | member.SetValue(Replicated, value); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Framework/Network/ReplyWaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace Spectrum.Framework.Network 8 | { 9 | /// 10 | /// Allows you to block and wait for all current peers to respond to your request 11 | /// 12 | public class ReplyWaiter 13 | { 14 | private bool noUpdateOnRemove; 15 | private volatile List replies = new List(); 16 | private List requiredReplies = new List(); 17 | private static List waiters = new List(); 18 | 19 | public ReplyWaiter(bool noUpdateOnRemove, params NetID[] requiredGuids) 20 | { 21 | this.noUpdateOnRemove = noUpdateOnRemove; 22 | requiredReplies = requiredGuids.ToList(); 23 | lock (waiters) { waiters.Add(this); } 24 | } 25 | 26 | public ReplyWaiter(params NetID[] requiredGuids) 27 | : this(false, requiredGuids) { } 28 | 29 | public void AddReply(NetID id) 30 | { 31 | lock (this) 32 | { 33 | if (!replies.Contains(id)) 34 | { 35 | replies.Add(id); 36 | Monitor.Pulse(this); 37 | } 38 | } 39 | } 40 | private bool allReplied 41 | { 42 | get 43 | { 44 | foreach (NetID peer in requiredReplies) 45 | { 46 | if (!replies.Contains(peer)) 47 | { 48 | return false; 49 | } 50 | } 51 | return true; 52 | } 53 | } 54 | public void WaitReplies() 55 | { 56 | lock (this) 57 | { 58 | while (!allReplied) 59 | { 60 | Monitor.Wait(this); 61 | } 62 | } 63 | lock (waiters) { waiters.Remove(this); } 64 | } 65 | public static void PeerRemoved(NetID removedPeer) 66 | { 67 | lock (waiters) 68 | { 69 | foreach (ReplyWaiter waiter in waiters) 70 | { 71 | lock (waiter) 72 | { 73 | if (waiter.noUpdateOnRemove) 74 | continue; 75 | waiter.requiredReplies.Remove(removedPeer); 76 | Monitor.Pulse(waiter); 77 | } 78 | } 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Framework/Network/Serialization.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Newtonsoft.Json.Linq; 3 | using ProtoBuf; 4 | using ProtoBuf.Meta; 5 | using Replicate; 6 | using Replicate.MetaData; 7 | using Replicate.Serialization; 8 | using Spectrum.Framework.Entities; 9 | using Spectrum.Framework.Graphics; 10 | using Spectrum.Framework.Network.Surrogates; 11 | using Spectrum.Framework.Physics.Collision.Shapes; 12 | using System; 13 | using System.Collections.Generic; 14 | using System.IO; 15 | using System.Linq; 16 | using System.Runtime.Serialization; 17 | using System.Runtime.Serialization.Formatters.Binary; 18 | using System.Text; 19 | using System.Threading.Tasks; 20 | 21 | namespace Spectrum.Framework.Network 22 | { 23 | public class Serialization 24 | { 25 | public static readonly BinarySerializer BinarySerializer = new BinarySerializer(TypeHelper.Model); 26 | private static bool Initialized = false; 27 | public static void Initialize() 28 | { 29 | if (Initialized) 30 | return; 31 | Initialized = true; 32 | TypeHelper.Model.Add(typeof(Vector2)).AddMember("X").AddMember("Y"); 33 | TypeHelper.Model.Add(typeof(Vector3)).AddMember("X").AddMember("Y").AddMember("Z"); 34 | TypeHelper.Model.Add(typeof(Rectangle)).AddMember("X").AddMember("Y").AddMember("Width").AddMember("Height"); 35 | TypeHelper.Model.Add(typeof(MemoryStream)).SetSurrogate(new Surrogate(typeof(StreamSurrogate))); 36 | TypeHelper.Model.Add(typeof(float[,])).SetSurrogate(new Surrogate(typeof(FloatArraySurrogate))); 37 | TypeHelper.Model.Add(typeof(Primitive)).SetSurrogate(typeof(PrimitiveSurrogate)); 38 | TypeHelper.Model.Add(typeof(SpecModel)).SetSurrogate(typeof(ModelSurrogate)); 39 | TypeHelper.Model.Add(typeof(Shape)); 40 | TypeHelper.Model.Add(typeof(BoxShape)); 41 | TypeHelper.Model.Add(typeof(CylinderShape)); 42 | } 43 | public static T Copy(T obj) where T : class, new() 44 | { 45 | if (obj == null) return null; 46 | var accessor = TypeHelper.Model.GetTypeAccessor(obj.GetType()); 47 | return TypeUtil.CopyToRaw(obj, accessor, null, accessor) as T; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Framework/Network/SteamP2PReceiver.cs: -------------------------------------------------------------------------------- 1 | using Steamworks; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Net.Sockets; 8 | using System.Text; 9 | using System.Threading; 10 | 11 | namespace Spectrum.Framework.Network 12 | { 13 | class SteamP2PReceiver 14 | { 15 | private MultiplayerService mpService; 16 | private volatile bool Running; 17 | 18 | public SteamP2PReceiver(MultiplayerService mpService) 19 | { 20 | this.mpService = mpService; 21 | Thread receiverThread = new Thread(new ThreadStart(ListenForData)); 22 | Running = true; 23 | receiverThread.IsBackground = true; 24 | receiverThread.Start(); 25 | } 26 | 27 | private void ListenForData() 28 | { 29 | uint messageSize; 30 | while (Running) 31 | { 32 | try 33 | { 34 | while (Steamworks.SteamNetworking.IsP2PPacketAvailable(out messageSize)) 35 | { 36 | byte[] buffer = new byte[messageSize]; 37 | CSteamID steamID; 38 | SteamNetworking.ReadP2PPacket(buffer, messageSize, out messageSize, out steamID); 39 | MemoryStream data = new MemoryStream(buffer); 40 | 41 | byte comType = (byte)data.ReadByte(); 42 | NetMessage header = new NetMessage(data); 43 | header.Read(); 44 | 45 | NetMessage messageOut = new NetMessage(data); 46 | byte testBytes = (byte)data.ReadByte(); 47 | if (testBytes != 255) 48 | { 49 | } 50 | 51 | mpService.ReceiveMessage(comType, new NetID(steamID.m_SteamID), messageOut); 52 | } 53 | Thread.Sleep(1); 54 | } 55 | catch (Exception e) 56 | { 57 | DebugPrinter.Print(e.Message); 58 | } 59 | } 60 | } 61 | public void Terminate() 62 | { 63 | Running = false; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Framework/Network/Surrogates/EntitySurrogate.cs: -------------------------------------------------------------------------------- 1 | using ProtoBuf; 2 | using Spectrum.Framework.Entities; 3 | using Spectrum.Framework.Network.Surrogates; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Spectrum.Framework.Network.Surrogates 11 | { 12 | [ProtoContract] 13 | public class EntitySurrogate where T : Entity 14 | { 15 | [ProtoMember(1)] 16 | Guid id; 17 | public static implicit operator EntitySurrogate(T entity) 18 | { 19 | if (entity == null) return null; 20 | return new EntitySurrogate() { id = entity.ID }; 21 | } 22 | public static implicit operator T(EntitySurrogate entity) 23 | { 24 | if (entity == null) return null; 25 | return SpectrumGame.Game.EntityManager.Find(entity.id) as T; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Framework/Network/Surrogates/FloatArraySurrogate.cs: -------------------------------------------------------------------------------- 1 | using ProtoBuf; 2 | using Replicate; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Spectrum.Framework.Network.Surrogates 10 | { 11 | [ProtoContract] 12 | [ReplicateType] 13 | public class FloatArraySurrogate 14 | { 15 | [Replicate] 16 | int width; 17 | [Replicate] 18 | int height; 19 | [Replicate] 20 | float[] buffer; 21 | private FloatArraySurrogate() { } 22 | public FloatArraySurrogate(int width, int height) 23 | { 24 | this.width = width; 25 | this.height = height; 26 | buffer = new float[width * height]; 27 | } 28 | public float this[int i1, int i2] 29 | { 30 | get { return buffer[i1 + width * i2]; } 31 | set { buffer[i1 + width * i2] = value; } 32 | } 33 | public static implicit operator FloatArraySurrogate(float[,] array) 34 | { 35 | if (array == null) return null; 36 | float[] buffer = new float[array.GetLength(0) * array.GetLength(1)]; 37 | for (int i = 0; i < array.GetLength(1); i++) 38 | { 39 | for (int j = 0; j < array.GetLength(0); j++) 40 | { 41 | buffer[j + i * array.GetLength(0)] = array[j, i]; 42 | } 43 | } 44 | return new FloatArraySurrogate(array.GetLength(0), array.GetLength(1)) { buffer = buffer }; 45 | } 46 | public static implicit operator float[,] (FloatArraySurrogate stream) 47 | { 48 | if (stream == null) return null; 49 | float[,] output = new float[stream.width, stream.height]; 50 | for (int i = 0; i < stream.height; i++) 51 | { 52 | for (int j = 0; j < stream.width; j++) 53 | { 54 | output[j, i] = stream.buffer[j + i * stream.width]; 55 | } 56 | } 57 | return output; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Framework/Network/Surrogates/JSONSurrogate.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using ProtoBuf; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Spectrum.Framework.Network.Surrogates 11 | { 12 | [ProtoContract] 13 | class JSONSurrogate 14 | { 15 | [ProtoMember(1)] 16 | string json; 17 | public static implicit operator JToken(JSONSurrogate surrogate) 18 | { 19 | return JToken.Parse(surrogate.json); 20 | } 21 | public static implicit operator JSONSurrogate(JToken token) 22 | { 23 | if (token == null) return null; 24 | return new JSONSurrogate() { json = token.ToString(Formatting.None) }; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Framework/Network/Surrogates/ModelSurrogate.cs: -------------------------------------------------------------------------------- 1 | using ProtoBuf; 2 | using Spectrum.Framework.Content; 3 | using Spectrum.Framework.Graphics; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Spectrum.Framework.Network.Surrogates 11 | { 12 | [ProtoContract] 13 | public class ModelSurrogate 14 | { 15 | [ProtoMember(1)] 16 | string ModelName; 17 | public static implicit operator SpecModel(ModelSurrogate surrogate) 18 | { 19 | if (surrogate.ModelName == null) return null; 20 | return ContentHelper.Load(surrogate.ModelName); 21 | } 22 | public static implicit operator ModelSurrogate(SpecModel model) 23 | { 24 | return new ModelSurrogate() { ModelName = model?.Name }; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Framework/Network/Surrogates/PrimitiveSurrogate.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using ProtoBuf; 3 | using Replicate; 4 | using Replicate.MetaData; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Runtime.Serialization.Formatters.Binary; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | 14 | namespace Spectrum.Framework.Network.Surrogates 15 | { 16 | public class Primitive 17 | { 18 | public object Object; 19 | public Primitive() { } 20 | public Primitive(object obj) 21 | { 22 | if (obj is Enum) 23 | obj = (int)obj; 24 | Object = obj; 25 | } 26 | } 27 | [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)] 28 | [ReplicateType] 29 | public class PrimitiveSurrogate 30 | { 31 | public TypeId type; 32 | public byte[] buffer; 33 | 34 | private object Object 35 | { 36 | get 37 | { 38 | if (type.Id.IsEmpty) return null; 39 | MemoryStream stream = new MemoryStream(buffer); 40 | Type objType = TypeHelper.Model.GetType(type); 41 | if (objType == typeof(JToken)) 42 | return (JToken)(JSONSurrogate)Serializer.NonGeneric.Deserialize(typeof(JSONSurrogate), stream); 43 | return Serialization.BinarySerializer.Deserialize(objType, stream); 44 | } 45 | } 46 | public static implicit operator PrimitiveSurrogate(Primitive obj) 47 | { 48 | if (obj == null) return null; 49 | if (obj.Object != null) 50 | { 51 | TypeId type = TypeHelper.Model.GetId(obj.Object.GetType()); 52 | MemoryStream buffer = new MemoryStream(); 53 | Serialize(buffer, obj.Object); 54 | return new PrimitiveSurrogate() { type = type, buffer = buffer.ToArray() }; 55 | } 56 | return new PrimitiveSurrogate(); 57 | } 58 | public static implicit operator Primitive(PrimitiveSurrogate obj) 59 | { 60 | if (obj == null) return null; 61 | return new Primitive(obj.Object); 62 | } 63 | public static MemoryStream Serialize(MemoryStream stream, object obj) 64 | { 65 | if (obj is JToken) 66 | obj = (JSONSurrogate)(JToken)obj; 67 | Serialization.BinarySerializer.Serialize(obj.GetType(), obj, stream); 68 | return stream; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Framework/Network/Surrogates/StreamSurrogate.cs: -------------------------------------------------------------------------------- 1 | using ProtoBuf; 2 | using Replicate; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Spectrum.Framework.Network.Surrogates 11 | { 12 | [ProtoContract] 13 | [ReplicateType] 14 | public class StreamSurrogate 15 | { 16 | [ProtoMember(1)] 17 | public byte[] buffer; 18 | public static implicit operator StreamSurrogate(MemoryStream stream) 19 | { 20 | if (stream == null) return null; 21 | return new StreamSurrogate() { buffer = stream.ToArray() }; 22 | } 23 | public static implicit operator MemoryStream(StreamSurrogate stream) 24 | { 25 | if (stream == null) return null; 26 | return new MemoryStream(stream.buffer); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Framework/Network/UDPReceiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading; 9 | 10 | namespace Spectrum.Framework.Network 11 | { 12 | class UDPReceiver 13 | { 14 | private MultiplayerService mpService; 15 | private UdpClient client; 16 | private IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 0); 17 | 18 | public UDPReceiver(MultiplayerService mpService, UdpClient client) 19 | { 20 | this.mpService = mpService; 21 | this.client = client; 22 | Thread receiverThread = new Thread(new ThreadStart(ListenForData)); 23 | receiverThread.IsBackground = true; 24 | receiverThread.Start(); 25 | } 26 | 27 | private void ListenForData() 28 | { 29 | while (true) 30 | { 31 | try 32 | { 33 | NetMessage data = new NetMessage(client.Receive(ref endpoint)); 34 | byte comType = data.Read(); 35 | NetID peerGuid = data.Read(); 36 | NetMessage userMessage = data.Read(); 37 | switch (comType) 38 | { 39 | case FrameworkMessages.KeepAlive: 40 | break; 41 | default: 42 | mpService.ReceiveMessage(comType, peerGuid, userMessage); 43 | break; 44 | } 45 | 46 | } 47 | catch (Exception e) 48 | { 49 | DebugPrinter.Print(e.Message); 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Framework/Network/UDPSender.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | using System.Threading; 9 | 10 | namespace Spectrum.Framework.Network 11 | { 12 | class UDPSender 13 | { 14 | struct QueueItem 15 | { 16 | public byte MessageType; 17 | public IPEndPoint dest; 18 | public NetMessage toWrite; 19 | } 20 | private Semaphore writeSem = new Semaphore(0, 10); 21 | private UdpClient client; 22 | private Queue dataQueue; 23 | private NetID mpID; 24 | 25 | public UDPSender(NetID mpID, UdpClient client) 26 | { 27 | this.mpID = mpID; 28 | dataQueue = new Queue(); 29 | this.client = client; 30 | new AsyncSendData(SendData).BeginInvoke(null, this); 31 | } 32 | private delegate void AsyncSendData(); 33 | private void SendData() 34 | { 35 | try 36 | { 37 | while (true) 38 | { 39 | if (!writeSem.WaitOne(100)) { continue; }; 40 | QueueItem item; 41 | lock (dataQueue) 42 | { 43 | if (dataQueue.Count == 0) { throw new Exception("Write semaphore was signaled without anything to dequeue"); } 44 | item = dataQueue.Dequeue(); 45 | } 46 | lock (client) 47 | { 48 | NetMessage data = new NetMessage(); 49 | data.Write(item.MessageType); 50 | data.Write(mpID); 51 | data.Write(item.toWrite); 52 | byte[] dataArray = data.stream.ToArray(); 53 | client.Send(dataArray, dataArray.Length, item.dest); 54 | } 55 | } 56 | } 57 | catch (Exception e) 58 | { 59 | DebugPrinter.Print(e.Message); 60 | } 61 | } 62 | public void SendMessage(IPEndPoint dest, byte messageType, NetMessage message) 63 | { 64 | WriteToStream(messageType, dest, message); 65 | } 66 | private void WriteToStream(byte EventType, IPEndPoint dest, NetMessage toWrite) 67 | { 68 | if (toWrite == null) 69 | { 70 | toWrite = new NetMessage(); 71 | } 72 | QueueItem item = new QueueItem(); 73 | item.MessageType = EventType; 74 | item.toWrite = toWrite; 75 | item.dest = dest; 76 | lock (dataQueue) 77 | { 78 | try 79 | { 80 | writeSem.Release(); 81 | dataQueue.Enqueue(item); 82 | } 83 | catch (SemaphoreFullException) 84 | { 85 | DebugPrinter.Print("Write queue is full"); 86 | } 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Framework/Physics/Collision/GroundInfo.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Spectrum.Framework.Entities; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Spectrum.Framework.Physics.Collision 9 | { 10 | public class CollisionInfo 11 | { 12 | public GameObject Object { get; private set; } 13 | public Vector3 Normal { get; private set; } 14 | public Vector3 Point { get; private set; } 15 | private CollisionInfo() { } 16 | public CollisionInfo(GameObject other, Vector3 point, Vector3 normal) 17 | { 18 | Point = point; 19 | Object = other; 20 | Normal = normal; 21 | } 22 | public void Update(Vector3 point, Vector3 normal) 23 | { 24 | Point = point; 25 | Normal = normal; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Framework/Physics/Collision/ICollidable.cs: -------------------------------------------------------------------------------- 1 | using Spectrum.Framework.Physics.Collision.Shapes; 2 | using Spectrum.Framework.Physics.LinearMath; 3 | using Microsoft.Xna.Framework; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace Spectrum.Framework.Physics.Collision 10 | { 11 | public interface ICollidable 12 | { 13 | Shape Shape { get; } 14 | Vector3 Position { get; } 15 | Vector3 Velocity { get; } 16 | Quaternion Orientation { get; } 17 | Quaternion InvOrientation { get; } 18 | JBBox BoundingBox { get; } 19 | } 20 | public static class ICollidableExtension 21 | { 22 | public static JBBox SweptBoundingBox(this ICollidable col) 23 | { 24 | JBBox box = col.BoundingBox; 25 | box.AddPoint(box.Max + col.Velocity); 26 | box.AddPoint(box.Min + col.Velocity); 27 | return box; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Framework/Physics/Collision/Shapes/ISupportMappable.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Spectrum.Framework.Physics.Collision.Shapes 8 | { 9 | /// 10 | /// The implementation of the ISupportMappable interface defines the form 11 | /// of a shape. 12 | /// 13 | public interface ISupportMappable 14 | { 15 | 16 | /// 17 | /// SupportMapping. Finds the point in the shape furthest away from the given direction. 18 | /// Imagine a plane with a normal in the search direction. Now move the plane along the normal 19 | /// until the plane does not intersect the shape. The last intersection point is the result. 20 | /// 21 | /// The direction. 22 | /// The result. 23 | /// Set to true when searching for the collision normal and penetration, 24 | /// useful to read in multishapes where sub shapes may have edges that produce wonky collision normals. 25 | void SupportMapping(ref Vector3 direction, out Vector3 result, bool retrievingInformation); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Framework/Physics/Collision/Shapes/ListMultishape.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using ProtoBuf; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace Spectrum.Framework.Physics.Collision.Shapes 10 | { 11 | [ProtoContract] 12 | public class ListMultishape : Multishape 13 | { 14 | [ProtoMember(1)] 15 | public List Shapes; 16 | private int currentShape; 17 | 18 | public ListMultishape() : this(new List()) { } 19 | 20 | public ListMultishape(List shapes) 21 | { 22 | if (shapes.Any((Shape shape) => (shape is Multishape))) 23 | { 24 | throw new ArgumentException("Cannot use Multishapes in a ListMultishape"); 25 | } 26 | Shapes = shapes; 27 | } 28 | 29 | public void Add(Shape shape) 30 | { 31 | if (shape == null) throw new ArgumentNullException(nameof(shape)); 32 | Shapes.Add(shape); 33 | UpdateShape(); 34 | } 35 | 36 | public void Remove(Shape shape) 37 | { 38 | Shapes.Remove(shape); 39 | UpdateShape(); 40 | } 41 | 42 | public override void SetCurrentShape(int index) 43 | { 44 | currentShape = index; 45 | } 46 | //TODO: These could be optimized 47 | public override int Prepare(ref LinearMath.JBBox box) 48 | { 49 | return Shapes.Count; 50 | } 51 | 52 | public override int Prepare(ref Vector3 rayOrigin, ref Vector3 rayDelta) 53 | { 54 | return Shapes.Count; 55 | } 56 | 57 | protected override Multishape CreateWorkingClone() 58 | { 59 | ListMultishape output = new ListMultishape(); 60 | output.Shapes = Shapes; 61 | output.currentShape = currentShape; 62 | 63 | return output; 64 | } 65 | 66 | public override void SupportMapping(ref Vector3 direction, out Vector3 result) 67 | { 68 | Shapes[currentShape].SupportMapping(ref direction, out result); 69 | } 70 | 71 | public override void SetScale(float scale) 72 | { 73 | foreach (var shape in Shapes) 74 | shape.SetScale(scale); 75 | UpdateShape(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Framework/Physics/Dynamics/Material.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Spectrum.Framework.Physics.Dynamics 6 | { 7 | 8 | // TODO: Check values, Documenation 9 | // Maybe some default materials, aka Material.Soft? 10 | public class Material 11 | { 12 | public float kineticFriction = 0.1f; 13 | public float staticFriction = 0.6f; 14 | public float restitution = 0.2f; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Framework/Point.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Spectrum.Framework 8 | { 9 | public struct Point : IEquatable 10 | { 11 | public int X { get; set; } 12 | public int Y { get; set; } 13 | public static Point operator +(Point a, Point b) => new Point(a.X + b.X, a.Y + b.Y); 14 | public static Point operator -(Point a, Point b) => new Point(a.X - b.X, a.Y - b.Y); 15 | public Point(int x, int y) { X = x; Y = y; } 16 | public static explicit operator Vector2(Point point) => new Vector2(point.X, point.Y); 17 | public static explicit operator Point(Vector2 vector) => new Point() { X = (int)Math.Floor(vector.X), Y = (int)Math.Floor(vector.Y) }; 18 | 19 | public override string ToString() 20 | { 21 | return $"{X},{Y}"; 22 | } 23 | 24 | #region Equality 25 | public static bool operator ==(Point left, Point right) 26 | { 27 | return left.Equals(right); 28 | } 29 | 30 | public static bool operator !=(Point left, Point right) 31 | { 32 | return !(left == right); 33 | } 34 | 35 | public override bool Equals(object obj) 36 | { 37 | return obj is Point point && Equals(point); 38 | } 39 | 40 | public bool Equals(Point other) 41 | { 42 | return X == other.X && 43 | Y == other.Y; 44 | } 45 | 46 | public override int GetHashCode() 47 | { 48 | int hashCode = 1861411795; 49 | hashCode = hashCode * -1521134295 + X.GetHashCode(); 50 | hashCode = hashCode * -1521134295 + Y.GetHashCode(); 51 | return hashCode; 52 | } 53 | #endregion 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Framework/Ray.cs: -------------------------------------------------------------------------------- 1 | using Spectrum.Framework.Physics.LinearMath; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework 9 | { 10 | public struct Ray 11 | { 12 | public Ray(Vector3 position, Vector3 direction) { Direction = direction; Position = position; } 13 | public Vector3 Direction { get; set; } 14 | public Vector3 Position { get; set; } 15 | public float? Intersects(JBBox box) 16 | { 17 | return box.RayIntersect(Position, Direction); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Framework/Rectangle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework 9 | { 10 | [DebuggerDisplay("[X:{X} Y:{Y} Width:{Width} Height:{Height}]")] 11 | public struct Rectangle 12 | { 13 | public Rectangle(int x, int y, int width, int height) { X = x; Y = y; Width = width; Height = height; } 14 | public int X { get; set; } 15 | public int Y { get; set; } 16 | public int Width { get; set; } 17 | public int Height { get; set; } 18 | public int Top => Y; 19 | public int YMax => Y + Height; 20 | public int YMin => Y; 21 | public int Right => X + Width; 22 | public int Bottom => Y + Height; 23 | public int Left => X; 24 | public Point TopLeft => new Point(X, Y); 25 | public Rectangle Clip(Rectangle other) 26 | { 27 | var x = Math.Max(X, other.X); 28 | var y = Math.Max(Y, other.Y); 29 | return new Rectangle(x, y, 30 | Math.Min(Right, other.Right) - x, Math.Min(Bottom, other.Bottom) - y); 31 | } 32 | public bool Intersects(Rectangle other) 33 | { 34 | return other.X < X + Width && other.X + other.Width > X 35 | && other.Y < Y + Height && other.Y + other.Height > Y; 36 | } 37 | public bool Contains(Point p) => Left <= p.X && p.X <= Right && Top <= p.Y && p.Y <= Bottom; 38 | public Rectangle Translate(Point p) => new Rectangle(X + p.X, Y + p.Y, Width, Height); 39 | /// 40 | /// Scales the source rectangle (and optionally centers it) to the destination rectangle while maintaining the original aspect ratio. 41 | /// The crop paremeter determines whether to scale up (overflowing and requiring a crop) or down (requiring no crop). 42 | /// 43 | /// 44 | /// 45 | /// If true will scale the source up, else down 46 | /// 47 | /// 48 | public Rectangle FitTo(Rectangle destination, bool crop = true, bool center = true) 49 | { 50 | bool scaleX = (!crop) ^ (destination.Width * 1.0 / Width * Height > destination.Height); 51 | double scale = scaleX ? destination.Width * 1.0 / Width : destination.Height * 1.0 / Height; 52 | var fittedX = X; 53 | var fittedY = Y; 54 | var fittedWidth = (int)(Width * scale); 55 | var fittedHeight = (int)(Height * scale); 56 | if (center) 57 | { 58 | fittedX = (destination.Width - fittedWidth) / 2; 59 | fittedY = (destination.Height - fittedHeight) / 2; 60 | } 61 | return new Rectangle(fittedX, fittedY, fittedWidth, fittedHeight); 62 | } 63 | public static implicit operator Microsoft.Xna.Framework.Rectangle(Rectangle r) => new Microsoft.Xna.Framework.Rectangle(r.X, r.Y, r.Width, r.Height); 64 | public static implicit operator Rectangle(Microsoft.Xna.Framework.Rectangle r) => new Rectangle(r.X, r.Y, r.Width, r.Height); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Framework/Schedule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework 9 | { 10 | public class Scheduler 11 | { 12 | public static Scheduler Current => Context.Current; 13 | public double Time = 0; 14 | SortedList> timers = new SortedList>(); 15 | const double BestEffortBudget = 0.001; 16 | ConcurrentQueue> bestEffort = new ConcurrentQueue>(); 17 | public void Wait(float scheduleAt, Action done) => timers.Add(Time + scheduleAt, done); 18 | // TODO: This could run immediate if `canBestEffort` 19 | public void RunBestEffort(Action done) => bestEffort.Enqueue(done); 20 | public void Update(float dt) 21 | { 22 | Time += dt; 23 | while (timers.Any() && timers.First().Key <= Time) 24 | { 25 | timers.First().Value(dt); 26 | timers.RemoveAt(0); 27 | } 28 | var start = DebugTiming.Now(); 29 | while (BestEffortBudget > (DebugTiming.Now() - start) && bestEffort.TryDequeue(out var action)) 30 | action(dt); 31 | } 32 | } 33 | public static class Schedule 34 | { 35 | public static void Wait(float scheduleAt, Action done) => Scheduler.Current.Wait(scheduleAt, done); 36 | public static void RunBestEffort(Action done) => Scheduler.Current.RunBestEffort(done); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Framework/Screens/DialogScreen.cs: -------------------------------------------------------------------------------- 1 | using Spectrum.Framework.Input; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework.Screens 9 | { 10 | public class DialogScreen : InGameScreen 11 | { 12 | public event Action OnClose; 13 | public KeyBind CloseKey; 14 | 15 | public override bool HandleInput(bool otherTookInput, InputState input) 16 | { 17 | return base.HandleInput(otherTookInput, input); 18 | } 19 | 20 | public override void Close() 21 | { 22 | OnClose?.Invoke(default(T)); 23 | base.Close(); 24 | } 25 | 26 | public void Close(T choice) 27 | { 28 | OnClose?.Invoke(choice); 29 | base.Close(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Framework/Screens/ElementSelector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Spectrum.Framework.Screens 8 | { 9 | public class Selector 10 | { 11 | // TODO: Implement properly? 12 | public static Selector Parse(string selector) 13 | { 14 | return new Selector(e => e?.HasTag(selector) ?? false); 15 | } 16 | private Func _selector; 17 | public Selector(Func selector) { _selector = selector; } 18 | public static implicit operator Selector(string selector) => Parse(selector); 19 | public static Selector operator &(Selector a, Selector b) => new Selector((e) => a.Matches(e) && b.Matches(e)); 20 | public static Selector operator |(Selector a, Selector b) => new Selector((e) => a.Matches(e) || b.Matches(e)); 21 | public static Selector operator !(Selector a) => new Selector((e) => !a.Matches(e)); 22 | public static Selector Parent(Selector selector, bool recursive = false) 23 | { 24 | bool parent(Element e) => selector.Matches(e.Parent) || (recursive && (e.Parent != null && parent(e.Parent))); 25 | return new Selector(parent); 26 | } 27 | public static Selector IsVR = Parent(Parse("vrmenu"), true); 28 | public virtual bool Matches(Element element) 29 | { 30 | return _selector(element); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Framework/Screens/ElementSize.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Spectrum.Framework.Screens 8 | { 9 | public enum SizeType 10 | { 11 | WrapContent = 0, 12 | Flat, 13 | MatchParent 14 | } 15 | public struct ElementSize 16 | { 17 | public bool WrapContent; 18 | public static ElementSize Zero = new ElementSize(0); 19 | public static ElementSize WrapFill = new ElementSize(0, 1, true); 20 | public static ElementSize Wrap = new ElementSize(0, 0, true); 21 | public double Relative; 22 | public int Flat; 23 | public ElementSize(int flat = 0, double relative = 0, bool wrapContent = false) 24 | { 25 | Flat = flat; 26 | Relative = relative; 27 | WrapContent = wrapContent; 28 | } 29 | public int Measure(int parent, int content = 0) 30 | { 31 | if (WrapContent) 32 | return Math.Max(content, (int)(parent * Relative) + Flat); 33 | else 34 | return (int)(parent * Relative) + Flat; 35 | } 36 | public static implicit operator ElementSize(int size) 37 | { 38 | return new ElementSize(size); 39 | } 40 | public static implicit operator ElementSize(double size) 41 | { 42 | return new ElementSize(relative: size); 43 | } 44 | #region Equality 45 | public static bool operator ==(ElementSize a, ElementSize b) 46 | { 47 | return a.Equals(b); 48 | } 49 | public static bool operator !=(ElementSize a, ElementSize b) 50 | { 51 | return !(a == b); 52 | } 53 | 54 | public override bool Equals(object obj) 55 | { 56 | if (!(obj is ElementSize)) 57 | { 58 | return false; 59 | } 60 | 61 | var size = (ElementSize)obj; 62 | return WrapContent == size.WrapContent && 63 | Flat == size.Flat && 64 | Relative == size.Relative; 65 | } 66 | 67 | public override int GetHashCode() 68 | { 69 | var hashCode = 76549531; 70 | hashCode = hashCode * -1521134295 + WrapContent.GetHashCode(); 71 | hashCode = hashCode * -1521134295 + Flat.GetHashCode(); 72 | hashCode = hashCode * -1521134295 + Relative.GetHashCode(); 73 | return hashCode; 74 | } 75 | #endregion 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Framework/Screens/ElementStyle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Replicate; 4 | using Replicate.MetaData; 5 | using Spectrum.Framework.Content; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace Spectrum.Framework.Screens 13 | { 14 | [ReplicateType] 15 | public struct ElementStyle 16 | { 17 | struct TagOverride 18 | { 19 | public Selector Selector; 20 | public ElementStyle Style; 21 | 22 | public TagOverride(Selector selector, ElementStyle style) 23 | { 24 | Selector = selector; 25 | Style = style; 26 | } 27 | } 28 | static TypeAccessor typeData; 29 | static ElementStyle() 30 | { 31 | typeData = TypeHelper.Model.GetTypeAccessor(typeof(ElementStyle)); 32 | } 33 | private static List TagOverrides = new List(); 34 | public static void OverrideTag(ElementStyle style) 35 | => TagOverrides.Add(new TagOverride(null, style)); 36 | public static void OverrideTag(string selector, ElementStyle style) 37 | => OverrideTag(Selector.Parse(selector), style); 38 | public static void OverrideTag(Selector selector, ElementStyle style) 39 | => TagOverrides.Add(new TagOverride(selector, style)); 40 | public static ElementStyle GetStyle(Element element) 41 | { 42 | ElementStyle output = new ElementStyle(); 43 | foreach (var overriden in TagOverrides) 44 | { 45 | if(overriden.Selector?.Matches(element) ?? true) 46 | output.Apply(overriden.Style); 47 | } 48 | return output; 49 | } 50 | public ImageAsset Texture; 51 | public Color? TextureColor; 52 | public ImageAsset Background; 53 | public Color? FontColor; 54 | public Color? BackgroundColor; 55 | public Color? FillColor; 56 | public SpriteFont Font; 57 | public ElementSize? Width; 58 | public ElementSize? Height; 59 | public RectOffset? Padding; 60 | public RectOffset? Margin; 61 | public static T Value(T? value, T? parent = null) where T : struct 62 | { 63 | return value ?? parent ?? default(T); 64 | } 65 | public static T Value(T value, T parent) where T : class 66 | { 67 | return value ?? parent ?? default(T); 68 | } 69 | public void Apply(ElementStyle newStyle) 70 | { 71 | this = TypeUtil.CopyTo(newStyle, this); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Framework/Screens/GridLayout.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework.Screens 9 | { 10 | public class GridLayout : Element 11 | { 12 | public GridLayout(int cols) 13 | { 14 | LayoutManager = new GridLayoutManager(cols); 15 | } 16 | } 17 | public class GridLayoutManager : LayoutManager 18 | { 19 | private int Cols; 20 | public GridLayoutManager(int cols) { Cols = cols; } 21 | public void OnLayout(Element element, Rectangle bounds) 22 | { 23 | int curRow = 0; 24 | int curCol = 0; 25 | foreach (var item in element.Children) 26 | { 27 | item.Layout(new Rectangle(colWidths.Where((h, i) => i < curCol).Sum(), rowHeights.Where((h, i) => i < curRow).Sum(), colWidths[curCol], rowHeights[curRow])); 28 | curCol += 1; 29 | if (curCol == Cols) 30 | { 31 | curRow += 1; 32 | curCol = 0; 33 | } 34 | } 35 | } 36 | private List rowHeights = new List(); 37 | private List colWidths = new List(); 38 | public void OnMeasure(Element element, int width, int height) 39 | { 40 | rowHeights.Clear(); 41 | colWidths.Clear(); 42 | colWidths.AddRange(Enumerable.Repeat(0, Cols)); 43 | int curRow = 0; 44 | int curCol = 0; 45 | int cellSize = element.PreChildWidth(width) / Cols; 46 | int curRowHeight = 0; 47 | int childHeight = height; 48 | foreach (var child in element.Children) 49 | { 50 | // Without passing in content dimensions children cannot have a parent percentage 51 | // that is affected by other childrens' dimensions. Might be fine for grids? 52 | child.Measure(cellSize, cellSize); 53 | curRowHeight = Math.Max(child.MeasuredHeight, curRowHeight); 54 | colWidths[curCol] = Math.Max(colWidths[curCol], child.MeasuredWidth); 55 | curCol += 1; 56 | if (curCol == Cols) 57 | { 58 | rowHeights.Add(curRowHeight); 59 | curRowHeight = 0; 60 | curRow += 1; 61 | curCol = 0; 62 | } 63 | } 64 | rowHeights.Add(curRowHeight); 65 | element.MeasuredHeight = element.MeasureHeight(height, rowHeights.DefaultIfEmpty(0).Sum()); 66 | element.MeasuredWidth = element.MeasureWidth(width, colWidths.DefaultIfEmpty(0).Sum()); 67 | } 68 | 69 | public int ContentWidth(Element element) 70 | { 71 | return colWidths.DefaultIfEmpty(0).Sum(); 72 | } 73 | 74 | public int ContentHeight(Element element) 75 | { 76 | return rowHeights.DefaultIfEmpty(0).Sum(); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Framework/Screens/InputElements/Button.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Microsoft.Xna.Framework.Input; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | 9 | namespace Spectrum.Framework.Screens.InputElements 10 | { 11 | public class Button : InputElement 12 | { 13 | public readonly TextElement TextElement; 14 | public string Text 15 | { 16 | get => TextElement.Text; 17 | set => TextElement.Text = value; 18 | } 19 | public Button(string text) 20 | { 21 | TextElement = AddElement(new TextElement(text) { Positioning = PositionType.Center }); 22 | } 23 | } 24 | public class ToggleButton : Button 25 | { 26 | public bool Value 27 | { 28 | get => HasTag("highlight"); 29 | set 30 | { 31 | if (value) 32 | AddTag("highlight"); 33 | else 34 | RemoveTag("highlight"); 35 | OnValueChanged?.Invoke(value); 36 | } 37 | } 38 | public event Action OnValueChanged; 39 | public ToggleButton(string text) : base(text) 40 | { 41 | OnClick += (_) => Value ^= true; 42 | } 43 | } 44 | public class CycleButton : Button 45 | { 46 | public readonly List> Options; 47 | private readonly List values; 48 | public T Value { get; private set; } 49 | public int Index { get; private set; } 50 | public CycleButton(List> options) : base(options[0].Item2) 51 | { 52 | Options = options; 53 | Value = options[0].Item1; 54 | values = options.Select(t => t.Item1).ToList(); 55 | OnClick += (_) => Cycle(); 56 | } 57 | public void Cycle(int amount = 1) 58 | { 59 | SetIndex((Index + amount) % Options.Count); 60 | } 61 | public void SetIndex(int index) 62 | { 63 | this.Index = index; 64 | Value = Options[index].Item1; 65 | Text = Options[index].Item2; 66 | } 67 | public bool SetValue(T value) 68 | { 69 | var i = values.IndexOf(value); 70 | if (i >= 0) 71 | { 72 | SetIndex(i); 73 | return true; 74 | } 75 | return false; 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Framework/Screens/InputElements/Checkbox.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework.Screens.InputElements 9 | { 10 | public delegate void OnToggleChanged(bool value); 11 | public class Checkbox : InputElement 12 | { 13 | Element valueIndicator; 14 | public bool Value 15 | { 16 | get => valueIndicator.Display; 17 | set => valueIndicator.Toggle(value); 18 | } 19 | public event OnToggleChanged OnValueChanged = null; 20 | public Checkbox() 21 | { 22 | Width = 20; Height = 20; 23 | } 24 | public bool ToggleValue(bool? value = null) 25 | { 26 | bool newValue = value ?? !Value; 27 | if (newValue != Value && OnValueChanged != null) 28 | OnValueChanged(newValue); 29 | Value = newValue; 30 | return Value; 31 | } 32 | public override void Initialize() 33 | { 34 | base.Initialize(); 35 | valueIndicator = new Element(); 36 | valueIndicator.Positioning = PositionType.Center; 37 | valueIndicator.Width = 6; 38 | valueIndicator.Height = 6; 39 | valueIndicator.AddTag("toggle-indicator"); 40 | Value = false; 41 | AddElement(valueIndicator); 42 | OnClick += (_) => ToggleValue(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Framework/Screens/InputElements/InputElement.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Microsoft.Xna.Framework.Input; 4 | using Spectrum.Framework.Input; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace Spectrum.Framework.Screens.InputElements 11 | { 12 | public class InputElement : Element 13 | { 14 | public event Action OnClick; 15 | public event Action OnRightClick; 16 | public object Data; 17 | public string HoverText; 18 | private TextElement hoverElement; 19 | public InputElement() 20 | { 21 | RegisterHandler(new KeyBind(0), (input) => 22 | { 23 | if (OnClick != null && MouseInside(input)) 24 | { 25 | OnClick(this); 26 | input.ConsumeInput(new KeyBind(0), true); 27 | return true; 28 | } 29 | return false; 30 | }); 31 | RegisterHandler(new KeyBind(1), (input) => 32 | { 33 | if (OnRightClick != null && MouseInside(input)) 34 | { 35 | OnRightClick(this); 36 | input.ConsumeInput(new KeyBind(1), true); 37 | return true; 38 | } 39 | return false; 40 | }); 41 | } 42 | private void ClearHoverText() { hoverElement?.Parent?.RemoveElement(hoverElement); hoverElement = null; } 43 | public override bool HandleInput(bool otherTookInput, InputState input) 44 | { 45 | if (Display && MouseInside(input)) 46 | { 47 | if (HoverText != null && hoverElement == null) 48 | { 49 | hoverElement = Root.AddElement(new TextElement(HoverText) 50 | { 51 | Positioning = PositionType.Absolute, 52 | BackgroundColor = Color.White, 53 | Z = 1000, 54 | ZDetach = true 55 | }); 56 | } 57 | } 58 | else if (hoverElement != null) { ClearHoverText(); } 59 | return base.HandleInput(otherTookInput, input); 60 | } 61 | public override void Draw(float gameTime, SpriteBatch spritebatch) 62 | { 63 | if (hoverElement != null) 64 | { 65 | hoverElement.X = InputState.Current.MousePosition.X; 66 | hoverElement.Y = InputState.Current.MousePosition.Y - hoverElement.MeasuredHeight; 67 | } 68 | base.Draw(gameTime, spritebatch); 69 | } 70 | public void Click() => OnClick?.Invoke(this); 71 | public void RightClick() => OnRightClick?.Invoke(this); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Framework/Screens/InputElements/ListOption.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Spectrum.Framework.Screens.InputElements 8 | { 9 | public class ListOption : InputElement 10 | { 11 | public int Id { get; set; } 12 | public T Option { get; set; } 13 | public string Text { get { return text?.Text; } set { text.Text = value; } } 14 | private TextElement text = new TextElement(""); 15 | 16 | public ListOption() : this(null, default(T)) { } 17 | public ListOption(string text, T option) : this(0, text, option) { } 18 | public ListOption(int id, string text, T tag) 19 | { 20 | Text = text; 21 | Id = id; 22 | Option = tag; 23 | } 24 | public override void Initialize() 25 | { 26 | base.Initialize(); 27 | AddElement(text); 28 | text.Center(); 29 | Width = ElementSize.WrapFill; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Framework/Screens/InputElements/ListSelector.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Microsoft.Xna.Framework.Input; 4 | using Spectrum.Framework.Input; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | 10 | namespace Spectrum.Framework.Screens.InputElements 11 | { 12 | public delegate void PickedEventHandler(int id, T picked); 13 | public class ListSelector : InputElement 14 | { 15 | public event PickedEventHandler OnPick; 16 | private List> options = new List>(); 17 | private int stringHeight; 18 | //The list selector's _rect is in absolute coordinates unlike other interface elements 19 | public ListSelector(Element parent, int x, int y, int width) 20 | { 21 | Positioning = PositionType.Absolute; 22 | X = x; 23 | Y = y; 24 | Width = new ElementSize() { Flat = width, WrapContent = true }; 25 | LayoutManager = new LinearLayoutManager(); 26 | } 27 | public override void Initialize() 28 | { 29 | base.Initialize(); 30 | stringHeight = (int)Font.LineSpacing; 31 | } 32 | public void AddOption(string text, T tag) 33 | { 34 | int id = Children.Select(ele => ele is ListOption ? (ele as ListOption).Id : 0).DefaultIfEmpty(0).Max() + 1; 35 | AddOption(id, text, tag); 36 | } 37 | public void AddOption(int id, string text, T tag = default(T)) 38 | { 39 | int optionHeight = stringHeight; 40 | ListOption option = new ListOption(id, text, tag); 41 | option.OnClick += optionClicked; 42 | options.Add(option); 43 | AddElement(option); 44 | } 45 | private void optionClicked(InputElement clicked) 46 | { 47 | if (OnPick != null) 48 | { 49 | var option = clicked as ListOption; 50 | OnPick(option.Id, option.Option); 51 | } 52 | Close(); 53 | } 54 | public override bool HandleInput(bool otherTookInput, InputState input) 55 | { 56 | if (!Display) 57 | return false; 58 | otherTookInput |= base.HandleInput(otherTookInput, input); 59 | if (otherTookInput || input.IsNewMousePress(0) && !Rect.Contains(input.MousePosition)) 60 | { 61 | Close(); 62 | } 63 | return true; 64 | } 65 | public void Close() 66 | { 67 | Parent?.RemoveElement(this); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Framework/Screens/InputElements/Slider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | using Spectrum.Framework.Input; 8 | 9 | namespace Spectrum.Framework.Screens.InputElements 10 | { 11 | public class Slider : InputElement 12 | { 13 | InputElement sliderPull; 14 | InputElement sliderTrack; 15 | bool dragging = false; 16 | float sliderValue = 0; 17 | public event Action OnValueChanged = null; 18 | public event Action OnSliderFinished = null; 19 | public float Value 20 | { 21 | get { return sliderValue; } 22 | set 23 | { 24 | sliderValue = Math.Min(1, Math.Max(0, value)); 25 | } 26 | } 27 | public Slider() 28 | { 29 | AddTag("slider"); 30 | Width = 100; 31 | Height = 20; 32 | } 33 | public override void Initialize() 34 | { 35 | base.Initialize(); 36 | sliderTrack = new InputElement(); 37 | sliderTrack.AddTag("slider-track"); 38 | sliderTrack.Height = 2; 39 | sliderTrack.Positioning = PositionType.Relative; 40 | sliderTrack.OnClick += (_) => dragging = true; 41 | AddElement(sliderTrack); 42 | 43 | sliderPull = new InputElement(); 44 | sliderPull.AddTag("slider-pull"); 45 | sliderPull.Height = 0.5f; 46 | sliderPull.Positioning = PositionType.Relative; 47 | sliderPull.OnClick += (_) => dragging = true; 48 | AddElement(sliderPull); 49 | } 50 | public override void OnMeasure(int width, int height) 51 | { 52 | base.OnMeasure(width, height); 53 | sliderPull.Width = sliderPull.MeasuredHeight; 54 | sliderTrack.Width = MeasuredWidth - Padding.WidthTotal(width) - sliderPull.MeasuredHeight; 55 | } 56 | public override void Layout(Rectangle bounds) 57 | { 58 | sliderTrack.Y = Rect.Height / 2 - sliderTrack.MeasuredHeight / 2; 59 | sliderTrack.X = Rect.Width / 2 - sliderTrack.MeasuredWidth / 2; 60 | sliderPull.X = (int)(sliderTrack.MeasuredWidth * sliderValue) + sliderTrack.X - sliderPull.MeasuredWidth / 2; 61 | sliderPull.Y = Rect.Height / 2 - sliderPull.MeasuredHeight / 2; 62 | base.Layout(bounds); 63 | } 64 | public override bool HandleInput(bool otherTookInput, InputState input) 65 | { 66 | if (dragging && !input.IsMouseDown(0)) 67 | { 68 | OnSliderFinished?.Invoke(Value); 69 | dragging = false; 70 | } 71 | otherTookInput |= base.HandleInput(otherTookInput, input); 72 | if (dragging) 73 | { 74 | otherTookInput = true; 75 | Value = (input.MousePosition.X - sliderTrack.Rect.Left) * 1.0f / sliderTrack.MeasuredWidth; 76 | OnValueChanged?.Invoke(Value); 77 | } 78 | return otherTookInput; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Framework/Screens/InputElements/TextElement.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Spectrum.Framework.Screens.InputElements 9 | { 10 | public class TextElement : Element 11 | { 12 | private int contentWidth; 13 | private int contentHeight; 14 | private bool _dirty = false; 15 | private string _text; 16 | public string Text 17 | { 18 | get => _text; 19 | set 20 | { 21 | if (_text != value) 22 | { 23 | _text = value; 24 | _dirty = true; 25 | } 26 | } 27 | } 28 | public Func TextSource; 29 | 30 | public TextElement(string text = null) { Text = text; } 31 | private void CacheDims() 32 | { 33 | if (_dirty) 34 | { 35 | _dirty = false; 36 | if (Text == null) 37 | { 38 | contentWidth = 0; 39 | contentHeight = (int)Font.MeasureString("a").Y; 40 | } 41 | else 42 | { 43 | contentWidth = (int)Font.MeasureString(Text).X; 44 | contentHeight = (int)Math.Max(Font.MeasureString("a").Y, Font.MeasureString(Text).Y); 45 | } 46 | } 47 | } 48 | public override int ContentWidth { get { CacheDims(); return contentWidth; } } 49 | public override int ContentHeight { get { CacheDims(); return contentHeight; } } 50 | 51 | public override void OnMeasure(int width, int height) 52 | { 53 | if (TextSource != null) 54 | Text = TextSource(); 55 | CacheDims(); 56 | MeasuredWidth = MeasureWidth(width, contentWidth); 57 | MeasuredHeight = MeasureHeight(height, contentHeight); 58 | } 59 | 60 | public override void Draw(float time, SpriteBatch spritebatch) 61 | { 62 | base.Draw(time, spritebatch); 63 | if (Text != null) 64 | spritebatch.DrawString(Font, Text, new Vector2(Rect.X, Rect.Y), FontColor, LayerDepth, Parent?.Clipped); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Framework/Screens/InputElements/TextInput.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Xna.Framework; 7 | using Microsoft.Xna.Framework.Graphics; 8 | using Spectrum.Framework.Input; 9 | using Spectrum.Framework.VR; 10 | 11 | namespace Spectrum.Framework.Screens.InputElements 12 | { 13 | public class TextInput : InputElement 14 | { 15 | public event Action OnFocusChanged; 16 | private bool _focused = false; 17 | public bool Focused 18 | { 19 | get => _focused; 20 | set 21 | { 22 | if (value != _focused) 23 | { 24 | _focused = value; 25 | OnFocusChanged?.Invoke(value); 26 | } 27 | } 28 | } 29 | public int CursorPosition = 0; 30 | private string text = ""; 31 | public string Text { get => text; set => text = value ?? ""; } 32 | private bool isVR = false; 33 | public TextInput() 34 | { 35 | Width = 250; 36 | OnClick += (_) => 37 | { 38 | if (isVR) 39 | if (!SpecVR.IsKeyboardVisible) 40 | SpecVR.ShowKeyboard(text); 41 | Focused = true; 42 | }; 43 | OnDisplayChanged += (display) => Focused &= display; 44 | } 45 | public override void Initialize() 46 | { 47 | isVR = Selector.IsVR.Matches(this); 48 | OnFocusChanged += (focus) => 49 | { 50 | if (!focus && isVR) 51 | SpecVR.HideKeyboard(); 52 | }; 53 | } 54 | public override bool HandleInput(bool otherTookInput, InputState input) 55 | { 56 | if (!Display) 57 | return false; 58 | otherTookInput = base.HandleInput(otherTookInput, input); 59 | if (Focused) 60 | { 61 | if (input.IsNewMousePress(0) && !MouseInside(input)) 62 | { 63 | Focused = false; 64 | return false; 65 | } 66 | if (isVR) 67 | text = SpecVR.GetKeyBoardText(); 68 | else 69 | input.TakeKeyboardInput(ref CursorPosition, ref text); 70 | return true; 71 | } 72 | return otherTookInput; 73 | } 74 | public override void OnMeasure(int width, int height) 75 | { 76 | MeasuredWidth = MeasureWidth(width, (int)Font.MeasureString(text).X); 77 | MeasuredHeight = MeasureHeight(height, (int)Math.Max(Font.MeasureString("a").Y, Font.MeasureString(text).Y)); 78 | } 79 | public override void Draw(float time, SpriteBatch spritebatch) 80 | { 81 | base.Draw(time, spritebatch); 82 | spritebatch.DrawString(Font, text, new Vector2(Rect.X, Rect.Y), FontColor, LayerDepth); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Framework/Screens/LayoutManager.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework.Screens 9 | { 10 | public interface LayoutManager 11 | { 12 | void OnMeasure(Element element, int width, int height); 13 | void OnLayout(Element element, Rectangle bounds); 14 | int ContentWidth(Element element); 15 | int ContentHeight(Element element); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Framework/Screens/MenuScreen.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Microsoft.Xna.Framework; 6 | using Microsoft.Xna.Framework.Input; 7 | using Microsoft.Xna.Framework.Graphics; 8 | using Spectrum.Framework.Screens.InputElements; 9 | using Spectrum.Framework.Input; 10 | 11 | namespace Spectrum.Framework.Screens 12 | { 13 | public class MenuScreen : Element 14 | { 15 | private string MenuTitle; 16 | public MenuScreen(string menuTitle) 17 | { 18 | MenuTitle = menuTitle; 19 | } 20 | public override void Initialize() 21 | { 22 | base.Initialize(); 23 | TextElement Title = new TextElement(MenuTitle); 24 | AddElement(Title); 25 | Title.Center(); 26 | } 27 | 28 | public override bool HandleInput(bool otherTookInput, InputState input) 29 | { 30 | otherTookInput |= base.HandleInput(otherTookInput, input); 31 | if (!otherTookInput) 32 | { 33 | if (input.IsNewKeyPress("GoBack")) 34 | { 35 | otherTookInput = true; 36 | Parent.RemoveElement(this); 37 | } 38 | } 39 | return true; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Framework/Screens/RectOffset.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Spectrum.Framework.Screens 7 | { 8 | public struct RectOffset 9 | { 10 | public ElementSize Left; 11 | public ElementSize Right; 12 | public ElementSize Top; 13 | public ElementSize Bottom; 14 | public int WidthTotal(int parentWidth) => Left.Measure(parentWidth) + Right.Measure(parentWidth); 15 | public int HeightTotal(int parentHeight) => Top.Measure(parentHeight) + Bottom.Measure(parentHeight); 16 | public static implicit operator RectOffset(int size) 17 | { 18 | return new RectOffset() 19 | { 20 | Left = size, 21 | Right = size, 22 | Top = size, 23 | Bottom = size, 24 | }; 25 | } 26 | public static implicit operator RectOffset(float size) 27 | { 28 | return new RectOffset() 29 | { 30 | Left = size, 31 | Right = size, 32 | Top = size, 33 | Bottom = size, 34 | }; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Framework/StateMachine.cs: -------------------------------------------------------------------------------- 1 | using Replicate; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework 9 | { 10 | [ReplicateType] 11 | public class StateMachine 12 | { 13 | public delegate int Handler(int current, object e); 14 | public int State { get; private set; } 15 | 16 | private Dictionary Defaults = new Dictionary(); 17 | private Dictionary<(int, Type), Handler> Handlers 18 | = new Dictionary<(int, Type), Handler>(); 19 | private StateMachine() { } 20 | public StateMachine(int initial) 21 | { 22 | State = initial; 23 | } 24 | public void Add(Func handler) 25 | { 26 | Defaults[typeof(TEvent)] = (s, e) => handler(s, (TEvent)e); 27 | } 28 | public void Add(int state, Func handler) 29 | { 30 | Handlers[(state, typeof(TEvent))] = (s, e) => handler((TEvent)e); 31 | } 32 | public int ProcessEvent(TEvent e) 33 | { 34 | if (Handlers.TryGetValue((State, typeof(TEvent)), out var handler) 35 | || Defaults.TryGetValue(typeof(TEvent), out handler)) 36 | return State = handler(State, e); 37 | return State; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Framework/Transform.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Spectrum.Framework 8 | { 9 | public interface ITransform 10 | { 11 | Vector3 Position { get; } 12 | Quaternion Orientation { get; } 13 | Vector3 Scale { get; } 14 | } 15 | public class Transform : ITransform 16 | { 17 | private ITransform source; 18 | public Transform() { } 19 | public Transform(Vector3 position) 20 | { 21 | Translation = position; 22 | } 23 | public Transform(Vector3 position, Quaternion rotation) 24 | { 25 | Translation = position; 26 | Rotation = rotation; 27 | } 28 | public Transform(ITransform source) { this.source = source; } 29 | public ITransform Parent; 30 | private Vector3 translation; 31 | public Vector3 Translation 32 | { 33 | get => source != null ? translation + source.Position : translation; 34 | set => translation = value; 35 | } 36 | public Vector3 Position 37 | { 38 | get 39 | { 40 | if (Parent != null) 41 | { 42 | return Parent.Apply(Translation); 43 | } 44 | return Translation; 45 | } 46 | } 47 | private Quaternion rotation = Quaternion.Identity; 48 | public Quaternion Rotation 49 | { 50 | get => source != null ? rotation.Concat(source.Orientation) : rotation; 51 | set => rotation = value; 52 | } 53 | public Vector3 Scale = Vector3.One; 54 | Vector3 ITransform.Scale => Scale; 55 | public Quaternion Orientation => Parent != null ? Rotation.Concat(Parent.Orientation) : Rotation; 56 | public static Transform Copy(ITransform transform) 57 | => new Transform(transform.Position, transform.Orientation); 58 | public Matrix LocalWorld => Matrix.CreateScale(Scale) * Rotation.ToMatrix() * Matrix.CreateTranslation(Translation); 59 | 60 | } 61 | public static class TransformExtension 62 | { 63 | public static Matrix World(this ITransform transform) 64 | => Matrix.CreateScale(transform.Scale) * transform.Orientation.ToMatrix() * Matrix.CreateTranslation(transform.Position); 65 | public static Vector3 Apply(this ITransform transform, Vector3 point) 66 | => transform.World() * point; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Framework/TypeHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Reflection; 6 | using System.IO; 7 | using Spectrum.Framework.Network; 8 | using Spectrum.Framework.Content; 9 | using Replicate.MetaData; 10 | using Replicate; 11 | using Spectrum.Framework.Entities; 12 | 13 | namespace Spectrum.Framework 14 | { 15 | public class TypeHelper 16 | { 17 | // TODO: Is using this model a good idea? 18 | public static ReplicationModel Model = TypeUtil.Model; 19 | private static DefaultDict plugins = new DefaultDict(); 20 | static ReplicateTypeAttribute MakeReplicateAttribute() => new ReplicateTypeAttribute() { AutoMembers = AutoAdd.None }; 21 | public static TypeData RegisterType(Type type, Plugin plugin) 22 | { 23 | // Laziness to avoid marking up every type with ReplicateType 24 | var typeData = Model.Add(type, type.GetCustomAttribute(false) ?? MakeReplicateAttribute()); 25 | // Reinitialize if it was added by another call and has no ReplicateType 26 | if (typeData.TypeAttribute == null) 27 | { 28 | typeData.TypeAttribute = MakeReplicateAttribute(); 29 | typeData.InitializeMembers(); 30 | Model.ClearTypeAccessorCache(); 31 | } 32 | plugins[type] = plugin; 33 | return typeData; 34 | } 35 | public static IEnumerable GetTypes(Type type) 36 | { 37 | return Model.Types.Values 38 | .Where(t => t.GenericTypeParameters == null && type.IsAssignableFrom(t.Type)) 39 | .Select(t => Model.GetTypeAccessor(t.Type)); 40 | } 41 | public static Plugin GetPlugin(Type type) 42 | { 43 | return plugins[type]; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Framework/Utility/ArgParseHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Spectrum.Framework.Utility 8 | { 9 | public class ArgParseResult 10 | { 11 | public List Positional = new List(); 12 | public HashSet Flags = new HashSet(); 13 | public Dictionary Arguments = new Dictionary(); 14 | public T Get(string key = null, int? position = null, T missing = default(T)) 15 | { 16 | if (key != null && Arguments.TryGetValue(key, out string value)) 17 | return (T)Convert.ChangeType(value, typeof(T)); 18 | if (position != null && Positional.Count > position.Value) 19 | return (T)Convert.ChangeType(Positional[position.Value], typeof(T)); 20 | return missing; 21 | } 22 | public bool HasFlag(string key) => Flags.Contains(key); 23 | } 24 | public class ArgParseHelper 25 | { 26 | public static ArgParseResult Parse(string[] args) 27 | { 28 | var result = new ArgParseResult(); 29 | for(int i = 0; i < args.Length; i++) 30 | { 31 | var arg = args[i]; 32 | if (arg.Substring(0, 2) == "--") 33 | { 34 | if (i + 1 >= args.Length || args[i + 1].Substring(0, 1) == "-") 35 | result.Flags.Add(arg.Substring(2)); 36 | else 37 | { 38 | result.Arguments.Add(arg.Substring(2), args[++i]); 39 | } 40 | } 41 | else if (arg.Substring(0, 1) == "-") 42 | result.Flags.Add(arg.Substring(1)); 43 | else 44 | result.Positional.Add(arg); 45 | } 46 | return result; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Framework/Utility/ExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Microsoft.Xna.Framework.Graphics; 3 | using Spectrum.Framework.Content; 4 | using Spectrum.Framework.Graphics; 5 | using Spectrum.Framework.Screens; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Runtime.InteropServices; 10 | using System.Text; 11 | 12 | namespace Spectrum.Framework 13 | { 14 | public static class ExtensionMethods 15 | { 16 | public static IEnumerable Union(this IEnumerable source, T item) 17 | { 18 | return source.Union(Enumerable.Repeat(item, 1)); 19 | } 20 | public static IEnumerable NotNull(this IEnumerable source) where T : struct 21 | { 22 | return source.Where(t => t.HasValue).Cast(); 23 | } 24 | public static IEnumerable NotNull(this IEnumerable source) where T : class 25 | { 26 | return source.Where(t => t != null).Cast(); 27 | } 28 | public static float DT(this GameTime time) 29 | { 30 | return (float)(time.ElapsedGameTime.TotalMilliseconds / 1000); 31 | } 32 | 33 | public static T Pop(this List list) 34 | { 35 | var ele = list[0]; 36 | list.RemoveAt(0); 37 | return ele; 38 | } 39 | public static float NextFloat(this Random r, float start, float end) 40 | => (float)(r.NextDouble() * (end - start) + start); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Framework/Utility/MathHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Spectrum.Framework 8 | { 9 | public static class MathHelper 10 | { 11 | public static float Lerp(float a, float b, float w) 12 | { 13 | return a * (1 - w) + b * w; 14 | } 15 | public static double Lerp(double a, double b, double w) 16 | { 17 | return a * (1 - w) + b * w; 18 | } 19 | public static float Clamp(float v, float min, float max) 20 | { 21 | return Math.Min(Math.Max(v, min), max); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Framework/Utility/MatrixHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace Spectrum.Framework 9 | { 10 | // TODO: Probably remove this 11 | public static class MatrixHelper 12 | { 13 | public static float[] ToArray(this Matrix matrix) 14 | { 15 | return new float[] 16 | { 17 | matrix.M11, matrix.M12, matrix.M13, matrix.M14, 18 | matrix.M21, matrix.M22, matrix.M23, matrix.M24, 19 | matrix.M31, matrix.M32, matrix.M33, matrix.M34, 20 | matrix.M41, matrix.M42, matrix.M43, matrix.M44, 21 | }; 22 | } 23 | public static Matrix FromArray(float[] array) 24 | { 25 | return new Matrix( 26 | array[0], array[1], array[2], array[3], 27 | array[4], array[5], array[6], array[7], 28 | array[8], array[9], array[10], array[11], 29 | array[12], array[13], array[14], array[15] 30 | ); 31 | } 32 | public static Matrix YUpToZUp() 33 | { 34 | var output = Matrix.Identity; 35 | output[1, 1] = 0; 36 | output[1, 2] = 1; 37 | output[2, 2] = 0; 38 | output[2, 1] = -1; 39 | return output; 40 | } 41 | public static Vector3 ToVector3(this JToken jobj) 42 | { 43 | return new Vector3( 44 | (float)jobj[0], 45 | (float)jobj[1], 46 | (float)jobj[2]); 47 | } 48 | public static Matrix ToTranslationMatrix(this JToken jobj) 49 | { 50 | return Matrix.CreateTranslation(jobj.ToVector3()); 51 | } 52 | public static Matrix ToRotationMatrix(this JToken jobj) 53 | { 54 | return jobj.ToQuaternion().ToMatrix(); 55 | } 56 | public static Quaternion ToQuaternion(this JToken jobj) 57 | { 58 | return new Quaternion( 59 | (float)jobj[0], 60 | (float)jobj[1], 61 | (float)jobj[2], 62 | (float)jobj[3]); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Framework/Utility/Point3.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework 9 | { 10 | public struct Point3 : IEquatable 11 | { 12 | public int X; 13 | public int Y; 14 | public int Z; 15 | 16 | public Point3(int x, int y, int z) 17 | { 18 | X = x; Y = y; Z = z; 19 | } 20 | 21 | public override bool Equals(object obj) 22 | { 23 | if (obj is Point3 point) 24 | return this.Equals(point); 25 | return false; 26 | } 27 | 28 | public override int GetHashCode() 29 | { 30 | var hashCode = -307843816; 31 | hashCode = hashCode * -1521134295 + X.GetHashCode(); 32 | hashCode = hashCode * -1521134295 + Y.GetHashCode(); 33 | hashCode = hashCode * -1521134295 + Z.GetHashCode(); 34 | return hashCode; 35 | } 36 | public static Point3 Zero => new Point3(); 37 | public static explicit operator Vector3(Point3 point) => new Vector3(point.X, point.Y, point.Z); 38 | public static explicit operator Point3(Vector3 vector) => new Point3() { X = (int)Math.Floor(vector.X), Y = (int)Math.Floor(vector.Y), Z = (int)Math.Floor(vector.Z) }; 39 | public static Point3 operator +(Point3 a, Point3 b) => new Point3() { X = a.X + b.X, Y = a.Y + b.Y, Z = a.Z + b.Z }; 40 | public static Point3 operator -(Point3 a, Point3 b) => new Point3() { X = a.X - b.X, Y = a.Y - b.Y, Z = a.Z - b.Z }; 41 | public static Point3 operator *(Point3 a, int s) => new Point3() { X = a.X * s, Y = a.Y * s, Z = a.Z * s }; 42 | public static bool operator ==(Point3 a, Point3 b) => a.Equals(b); 43 | public static bool operator !=(Point3 a, Point3 b) => !a.Equals(b); 44 | public static Point3 Round(Vector3 vector) => new Point3() { X = (int)Math.Round(vector.X), Y = (int)Math.Round(vector.Y), Z = (int)Math.Round(vector.Z) }; 45 | public override string ToString() 46 | { 47 | return $"{X},{Y},{Z}"; 48 | } 49 | 50 | public bool Equals(Point3 other) 51 | { 52 | return X == other.X && 53 | Y == other.Y && 54 | Z == other.Z; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Framework/Utility/RectangleExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework.Utility 9 | { 10 | using Rectangle = Microsoft.Xna.Framework.Rectangle; 11 | public static class RectangleExtensions 12 | { 13 | /// 14 | /// Scales the source rectangle (and optionally centers it) to the destination rectangle. 15 | /// The crop paremeter determines whether to scale up (overflowing and requiring a crop) or down (requiring no crop). 16 | /// 17 | /// 18 | /// 19 | /// If true will scale the source up, else down 20 | /// 21 | /// 22 | public static Rectangle FitTo(this Rectangle source, Rectangle destination, bool crop = true, bool center = true) 23 | { 24 | bool scaleX = (!crop) ^ (destination.Width * 1.0 / source.Width * source.Height > destination.Height); 25 | double scale = scaleX ? destination.Width * 1.0 / source.Width : destination.Height * 1.0 / source.Height; 26 | var fittedX = source.X; 27 | var fittedY = source.Y; 28 | var fittedWidth = (int)(source.Width * scale); 29 | var fittedHeight = (int)(source.Height * scale); 30 | if (center) 31 | { 32 | fittedX = (destination.Width - fittedWidth) / 2; 33 | fittedY = (destination.Height - fittedHeight) / 2; 34 | } 35 | return new Rectangle(fittedX, fittedY, fittedWidth, fittedHeight); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Framework/Utility/VectorExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework 9 | { 10 | public static class VectorExtensions 11 | { 12 | public static string FixedLenString(this Vector3 vector) 13 | { 14 | return "<" + vector.X.ToString("0.00") + ", " + vector.Y.ToString("0.00") + ", " + vector.Z.ToString("0.00") + ">"; 15 | } 16 | public static Vector3 Homogeneous(this Vector4 vector) 17 | { 18 | return new Vector3(vector.X, vector.Y, vector.Z) / vector.W; 19 | } 20 | public static bool IsInSameDirection(this Vector3 vector, Vector3 otherVector) 21 | { 22 | return vector.Dot(otherVector) > 0; 23 | } 24 | public static bool IsInOppositeDirection(this Vector3 vector, Vector3 otherVector) 25 | { 26 | return vector.Dot(otherVector) < 0; 27 | } 28 | public static Vector3 Project(this Vector3 source, Vector3 normal) 29 | { 30 | return source.Dot(normal) * normal; 31 | } 32 | public static Vector3 ProjectUnto(this Vector3 source, Vector3 planeNormal) 33 | { 34 | return source - source.Project(planeNormal); 35 | } 36 | public static float Roll(this Quaternion quaternion) 37 | { 38 | // yaw (z-axis rotation) 39 | double siny = +2.0 * (quaternion.W * quaternion.Z + quaternion.X * quaternion.Y); 40 | double cosy = +1.0 - 2.0 * (quaternion.X * quaternion.X + quaternion.Z * quaternion.Z); 41 | return (float)Math.Atan2(siny, cosy); 42 | } 43 | public static float Yaw(this Quaternion quaternion) 44 | { 45 | // roll (y-axis rotation) 46 | double sinr = +2.0 * (quaternion.W * quaternion.Y + quaternion.X * quaternion.Z); 47 | double cosr = +1.0 - 2.0 * (quaternion.X * quaternion.X + quaternion.Y * quaternion.Y); 48 | return (float)Math.Atan2(sinr, cosr); 49 | //// pitch (y-axis rotation) 50 | //double sinp = +2.0 * (quaternion.W * quaternion.Y - quaternion.Z * quaternion.X); 51 | //if (Math.Abs(sinp) >= 1) 52 | // return (float)(Math.Sign(sinp) * Math.PI / 2); // use 90 degrees if out of range 53 | //return (float)Math.Asin(sinp); 54 | } 55 | public static float Pitch(this Quaternion quaternion) 56 | { 57 | // pitch (x-axis rotation) 58 | double sinp = +2.0 * (quaternion.W * quaternion.X - quaternion.Z * quaternion.Y); 59 | if (Math.Abs(sinp) >= 1) 60 | return (float)(Math.Sign(sinp) * Math.PI / 2); // use 90 degrees if out of range 61 | return (float)Math.Asin(sinp); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Framework/Utility/WinUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework.Utility 9 | { 10 | public static class WinUtil 11 | { 12 | [DllImport("shcore.dll")] 13 | public static extern int SetProcessDpiAwareness(ProcessDPIAwareness value); 14 | 15 | public enum ProcessDPIAwareness 16 | { 17 | DPI_Unaware = 0, 18 | System_DPI_Aware = 1, 19 | Per_Monitor_DPI_Aware = 2 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Framework/Vector2.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Spectrum.Framework 9 | { 10 | public struct Vector2 : IEquatable 11 | { 12 | public static Vector2 Zero => new Vector2(); 13 | public static Vector2 UnitX => new Vector2(1, 0); 14 | public static Vector2 UnitY => new Vector2(0, 1); 15 | public static Vector2 One => new Vector2(1, 1); 16 | public Vector2(float x, float y) { X = x; Y = y; } 17 | public Vector2(float d) { X = d; Y = d; } 18 | public float X { get; set; } 19 | public float Y { get; set; } 20 | public void Normalize() 21 | { 22 | var length = Length; 23 | X /= length; Y /= length; 24 | } 25 | public Vector2 Normal() => this / Length; 26 | public Vector2 Transform(Matrix matrix) => new Vector2((X * matrix.M11) + (Y * matrix.M21) + matrix.M41, (X * matrix.M12) + (Y * matrix.M22) + matrix.M42); 27 | public Vector2 Rotate(float r) 28 | { 29 | var sin = Math.Sin(r); var cos = Math.Cos(r); 30 | return new Vector2((float)(X * cos - Y * sin), (float)(X * sin + Y * cos)); 31 | } 32 | [Obsolete] 33 | public static float Dot(Vector2 a, Vector2 b) => a.Dot(b); 34 | public float Dot(Vector2 b) => X * b.X + Y * b.Y; 35 | public override bool Equals(object obj) => obj is Vector2 vector && Equals(vector); 36 | public bool Equals(Vector2 other) => X == other.X && Y == other.Y; 37 | public override int GetHashCode() 38 | { 39 | var hashCode = 1861411795; 40 | hashCode = hashCode * -1521134295 + X.GetHashCode(); 41 | hashCode = hashCode * -1521134295 + Y.GetHashCode(); 42 | return hashCode; 43 | } 44 | public static Vector2 operator -(Vector2 a) => new Vector2(-a.X, -a.Y); 45 | public static Vector2 operator -(Vector2 a, Vector2 b) => new Vector2(a.X - b.X, a.Y - b.Y); 46 | public static Vector2 operator +(Vector2 a, Vector2 b) => new Vector2(a.X + b.X, a.Y + b.Y); 47 | public static Vector2 operator /(Vector2 a, int l) => new Vector2(a.X / l, a.Y / l); 48 | public static Vector2 operator /(Vector2 a, float l) => new Vector2(a.X / l, a.Y / l); 49 | public static Vector2 operator *(Vector2 a, int l) => new Vector2(a.X * l, a.Y * l); 50 | public static Vector2 operator *(Vector2 a, float l) => new Vector2(a.X * l, a.Y * l); 51 | public static bool operator ==(Vector2 left, Vector2 right) => left.Equals(right); 52 | public static bool operator !=(Vector2 left, Vector2 right) => !(left == right); 53 | public float Length => (float)Math.Pow(LengthSquared, 0.5); 54 | public float LengthSquared => (float)(Math.Pow(X, 2) + Math.Pow(Y, 2)); 55 | public static implicit operator Microsoft.Xna.Framework.Vector2(Vector2 vector) 56 | { 57 | return new Microsoft.Xna.Framework.Vector2(vector.X, vector.Y); 58 | } 59 | public static implicit operator Vector2(Microsoft.Xna.Framework.Vector2 vector) 60 | { 61 | return new Vector2(vector.X, vector.Y); 62 | } 63 | public override string ToString() 64 | { 65 | return $"{X:0.00},{Y:0.00}"; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Framework/Vector4.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Spectrum.Framework 8 | { 9 | public struct Vector4 10 | { 11 | public static Vector4 Zero => new Vector4(); 12 | public static Vector4 UnitX => new Vector4() { X = 1 }; 13 | public static Vector4 UnitY => new Vector4() { Y = 1 }; 14 | public static Vector4 UnitZ => new Vector4() { Z = 1 }; 15 | public static Vector4 UnitW => new Vector4() { W = 1 }; 16 | public static Vector4 One => new Vector4(1); 17 | public Vector4(float x, float y, float z, float w) { X = x; Y = y; Z = z; W = w; } 18 | public Vector4(float s) { X = s; Y = s; Z = s; W = s; } 19 | public float X { get; set; } 20 | public float Y { get; set; } 21 | public float Z { get; set; } 22 | public float W { get; set; } 23 | public static Vector4 operator -(Vector4 a) => new Vector4(-a.X, -a.Y, -a.Z, -a.W); 24 | public static Vector4 operator -(Vector4 a, Vector4 b) => new Vector4(a.X - b.X, a.Y - b.Y, a.Z - b.Z, a.W - b.W); 25 | public static Vector4 operator +(Vector4 a, Vector4 b) => new Vector4(a.X + b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W); 26 | public static Vector4 operator /(Vector4 a, int d) => new Vector4(a.X / d, a.Y / d, a.Z / d, a.W / d); 27 | public static Vector4 operator /(Vector4 a, float d) => new Vector4(a.X / d, a.Y / d, a.Z / d, a.W / d); 28 | public static Vector4 operator *(Vector4 a, int d) => new Vector4(a.X * d, a.Y * d, a.Z * d, a.W * d); 29 | public static Vector4 operator *(Vector4 a, float d) => new Vector4(a.X * d, a.Y * d, a.Z * d, a.W * d); 30 | public static Vector4 operator *(int d, Vector4 a) => new Vector4(a.X * d, a.Y * d, a.Z * d, a.W * d); 31 | public static Vector4 operator *(float d, Vector4 a) => new Vector4(a.X * d, a.Y * d, a.Z * d, a.W * d); 32 | public static implicit operator Microsoft.Xna.Framework.Vector4(Vector4 vector) => new Microsoft.Xna.Framework.Vector4(vector.X, vector.Y, vector.Z, vector.W); 33 | public static implicit operator Vector4(Microsoft.Xna.Framework.Vector4 vector) => new Vector4(vector.X, vector.Y, vector.Z, vector.W); 34 | public Vector4 Normal() => new Vector4(X / Length, Y / Length, Z / Length, W / Length); 35 | public float Length => (float)Math.Pow(LengthSquared, 0.5); 36 | public float LengthSquared => (float)(Math.Pow(X, 2) + Math.Pow(Y, 2) + Math.Pow(Z, 2) + Math.Pow(W, 2)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alex Sherman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Test/BenchmarkTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Spectrum.Framework.Entities; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace SpectrumTest 11 | { 12 | public struct TimingResult 13 | { 14 | public double T; 15 | public double N; 16 | public override string ToString() 17 | { 18 | 19 | if (T >= 1) 20 | return $"{T} s"; 21 | if (T >= 0.001) 22 | return $"{T * 1e3} ms"; 23 | return $"{T * 1e6} us"; 24 | } 25 | } 26 | [TestFixture] 27 | public class BenchmarkTests 28 | { 29 | public TimingResult Time(Action del, int n = 1_000, double t = 2) 30 | { 31 | Stopwatch s = new Stopwatch(); 32 | s.Start(); 33 | for (int i = 0; i < n; i++) 34 | { 35 | del(); 36 | } 37 | s.Stop(); 38 | return new TimingResult() { T = s.ElapsedTicks * 1.0 / Stopwatch.Frequency / n, N = n }; 39 | } 40 | [Test] 41 | public void LargeUpdateBatch() 42 | { 43 | var manager = new EntityManager(); 44 | for (int i = 0; i < 1e3; i++) 45 | { 46 | manager.AddEntity(new GameObject() { ID = Guid.NewGuid() }); 47 | } 48 | var time = Time(() => manager.Update(0.16666f)); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Test/ElementTest.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Spectrum.Framework; 3 | using Spectrum.Framework.Screens; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace SpectrumTest 11 | { 12 | [TestFixture] 13 | public class ElementTest 14 | { 15 | [Test] 16 | public void DefaultHorizontalLayout() 17 | { 18 | var root = new Element(); 19 | var ele1 = root.AddElement(new Element() { Width = 10, Height = 20 }); 20 | var ele2 = root.AddElement(new Element() { Width = 20, Height = 10 }); 21 | root.ClearMeasure(); 22 | root.Measure(100, 100); 23 | root.Layout(new Rectangle(0, 0, 100, 100)); 24 | Assert.AreEqual(ele1.Rect.Left, 0); 25 | Assert.AreEqual(ele1.Rect.Right, 10); 26 | Assert.AreEqual(ele1.Rect.Top, 0); 27 | Assert.AreEqual(ele1.Rect.Bottom, 20); 28 | Assert.AreEqual(ele2.Rect.Left, 10); 29 | Assert.AreEqual(ele2.Rect.Right, 30); 30 | Assert.AreEqual(ele2.Rect.Top, 0); 31 | Assert.AreEqual(ele2.Rect.Bottom, 10); 32 | } 33 | [Test] 34 | public void CenterHorizontally() 35 | { 36 | var root = new Element { Width = 100, Height = 100 }; 37 | var ele1 = root.AddElement(new Element() { Margin = new RectOffset() { Left = new ElementSize(-5, 0.5) }, Width = 10, Height = 20 }); 38 | root.ClearMeasure(); 39 | root.Measure(100, 100); 40 | root.Layout(new Rectangle(0, 0, 100, 100)); 41 | Assert.AreEqual(ele1.Rect.Left, 45); 42 | Assert.AreEqual(ele1.Rect.Right, 55); 43 | Assert.AreEqual(ele1.Rect.Top, 0); 44 | Assert.AreEqual(ele1.Rect.Bottom, 20); 45 | } 46 | [Test] 47 | public void GridLayoutRelativeChildren() 48 | { 49 | var root = new GridLayout(4) { Width = 100, Height = 100 }; 50 | var ele0 = root.AddElement(new Element() { Width = 1.0, Height = 1.0 }); 51 | var ele1 = root.AddElement(new Element() { Width = 1.0, Height = 1.0 }); 52 | var ele2 = root.AddElement(new Element() { Width = 1.0, Height = 1.0 }); 53 | var ele3 = root.AddElement(new Element() { Width = 1.0, Height = 1.0 }); 54 | var ele4 = root.AddElement(new Element() { Width = 1.0, Height = 1.0 }); 55 | root.ClearMeasure(); 56 | root.Measure(100, 100); 57 | root.Layout(new Rectangle(0, 0, 100, 100)); 58 | Assert.AreEqual(ele0.Rect.Left, 0); 59 | Assert.AreEqual(ele0.Rect.Right, 25); 60 | Assert.AreEqual(ele0.Rect.Top, 0); 61 | Assert.AreEqual(ele0.Rect.Bottom, 25); 62 | Assert.AreEqual(ele1.Rect.Left, 25); 63 | Assert.AreEqual(ele1.Rect.Right, 50); 64 | Assert.AreEqual(ele1.Rect.Top, 0); 65 | Assert.AreEqual(ele1.Rect.Bottom, 25); 66 | Assert.AreEqual(ele4.Rect.Left, 0); 67 | Assert.AreEqual(ele4.Rect.Right, 25); 68 | Assert.AreEqual(ele4.Rect.Top, 25); 69 | Assert.AreEqual(ele4.Rect.Bottom, 50); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Test/InitDataTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Spectrum.Framework; 3 | using Spectrum.Framework.Content; 4 | using Spectrum.Framework.Entities; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace SpectrumTest 13 | { 14 | [LoadableType] 15 | public class ClassWithArgument 16 | { 17 | public string Field; 18 | public int Property { get; set; } 19 | public float Derp { get; private set; } 20 | public ClassWithArgument(float derp) 21 | { 22 | Derp = derp; 23 | } 24 | } 25 | [TestFixture] 26 | public class InitDataTests 27 | { 28 | [SetUp] 29 | public void Initialize() 30 | { 31 | var plugin = Plugin.CreatePlugin("Main", null, LoadHelper.SpectrumAssembly); 32 | LoadHelper.RegisterTypes(plugin); 33 | } 34 | [Test] 35 | public void InitDataUpdateOnSet() 36 | { 37 | var idata = new InitData(); 38 | Assert.AreEqual(idata, idata.Set("Position", null)); 39 | } 40 | [Test] 41 | public void InitDataCopyOnSet() 42 | { 43 | InitData idata = new InitData().ToImmutable(); 44 | Assert.AreNotEqual(idata, idata.Set("ID", null)); 45 | } 46 | [Test] 47 | public void SetEntityDataValid() 48 | { 49 | Entity entity = new InitData().SetData("Test", "herp").Construct(); 50 | Assert.IsNotNull(entity); 51 | Assert.AreEqual(entity.Data("Test"), "herp"); 52 | } 53 | [Test] 54 | public void FunctionalInspection() 55 | { 56 | TypeHelper.RegisterType(typeof(ClassWithArgument), null); 57 | var obj = new InitData(() => new ClassWithArgument(1.5f) { Field = "Derp", Property = 3 }).Construct(); 58 | Assert.IsNotNull(obj); 59 | Assert.AreEqual(obj.Derp, 1.5f); 60 | Assert.AreEqual(obj.Field, "Derp"); 61 | Assert.AreEqual(obj.Property, 3); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Test/MathExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Xna.Framework; 2 | using NUnit.Framework; 3 | using Spectrum.Framework.Utility; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace SpectrumTest 11 | { 12 | [TestFixture] 13 | public class MathExtensionTests 14 | { 15 | [Test] 16 | public void RetangleFitCropX() 17 | { 18 | var fitted = new Rectangle(0, 0, 50, 50).FitTo(new Rectangle(0, 0, 200, 100), true, false); 19 | Assert.AreEqual(new Rectangle(0, 0, 200, 200), fitted); 20 | } 21 | [Test] 22 | public void RetangleFitCropY() 23 | { 24 | var fitted = new Rectangle(0, 0, 50, 50).FitTo(new Rectangle(0, 0, 100, 200), true, false); 25 | Assert.AreEqual(new Rectangle(0, 0, 200, 200), fitted); 26 | } 27 | [Test] 28 | public void RetangleFitNoCropX() 29 | { 30 | var fitted = new Rectangle(0, 0, 50, 50).FitTo(new Rectangle(0, 0, 100, 200), false, false); 31 | Assert.AreEqual(new Rectangle(0, 0, 100, 100), fitted); 32 | } 33 | [Test] 34 | public void RetangleFitNoCropY() 35 | { 36 | var fitted = new Rectangle(0, 0, 50, 50).FitTo(new Rectangle(0, 0, 200, 100), false, false); 37 | Assert.AreEqual(new Rectangle(0, 0, 100, 100), fitted); 38 | } 39 | 40 | //Centered 41 | [Test] 42 | public void RetangleFitCropXCentered() 43 | { 44 | var fitted = new Rectangle(0, 0, 50, 50).FitTo(new Rectangle(0, 0, 200, 100), true); 45 | Assert.AreEqual(new Rectangle(0, -50, 200, 200), fitted); 46 | } 47 | [Test] 48 | public void RetangleFitCropYCentered() 49 | { 50 | var fitted = new Rectangle(0, 0, 50, 50).FitTo(new Rectangle(0, 0, 100, 200), true); 51 | Assert.AreEqual(new Rectangle(-50, 0, 200, 200), fitted); 52 | } 53 | [Test] 54 | public void RetangleFitNoCropXCentered() 55 | { 56 | var fitted = new Rectangle(0, 0, 50, 50).FitTo(new Rectangle(0, 0, 100, 200), false); 57 | Assert.AreEqual(new Rectangle(0, 50, 100, 100), fitted); 58 | } 59 | [Test] 60 | public void RetangleFitNoCropYCentered() 61 | { 62 | var fitted = new Rectangle(0, 0, 50, 50).FitTo(new Rectangle(0, 0, 200, 100), false); 63 | Assert.AreEqual(new Rectangle(50, 0, 100, 100), fitted); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Test/SerializationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Spectrum.Framework.Physics.Collision.Shapes; 3 | using Spectrum.Framework.Physics.Collision; 4 | using Spectrum.Framework.Entities; 5 | using Spectrum; 6 | using Spectrum.Framework.Network; 7 | using Newtonsoft.Json.Linq; 8 | using Spectrum.Framework.Network.Surrogates; 9 | using NUnit.Framework; 10 | using Replicate.Serialization; 11 | using Spectrum.Framework; 12 | 13 | namespace SpectrumTest 14 | { 15 | [TestFixture] 16 | public class SerializationTests 17 | { 18 | [Test] 19 | public void TestJSONPrimitiveCopy() 20 | { 21 | Serialization.InitSurrogates(); 22 | var output = Serialization.Copy(new Primitive(JToken.Parse("{\"herp\": 3}"))); 23 | } 24 | [Test] 25 | public void TestInitDataTCopyable() 26 | { 27 | Serialization.InitSurrogates(); 28 | TypeHelper.Model.LoadTypes(typeof(GameObject).Assembly); 29 | var initData = new InitData(() => new GameObject() { Position = new Vector3(1, 2, 3) }); 30 | var stream = Serialization.BinarySerializer.Serialize(initData); 31 | stream.Position = 0; 32 | var result = Serialization.BinarySerializer.Deserialize>(stream); 33 | } 34 | [Test] 35 | public void TestInitDataCopyable() 36 | { 37 | Serialization.InitSurrogates(); 38 | TypeHelper.Model.LoadTypes(typeof(GameObject).Assembly); 39 | var initData = new InitData(() => new GameObject() { Position = new Vector3(1, 2, 3) }); 40 | var stream = Serialization.BinarySerializer.Serialize((InitData)initData); 41 | stream.Position = 0; 42 | var result = Serialization.BinarySerializer.Deserialize(stream); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Test/SpectrumTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net46 4 | x86 5 | 8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ..\Dependencies\MonoGame.Framework.dll 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /mgfx.targets.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dependencies\Utils\2MGFX 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | PreserveNewest 19 | 20 | 21 | 22 | 23 | 24 | BuildMGFXContent; 25 | $(AssignTargetPathsDependsOn); 26 | 27 | 28 | -------------------------------------------------------------------------------- /openvr_api.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alex-sherman/Spectrum/3030b61f5eb961815100b23dcceb99adfff22be9/openvr_api.dll --------------------------------------------------------------------------------