private static void CreateCellLoops(MeshData md, List <int[]> loops, ParachuteConfig cfg, ParachuteMeshConfig cf)
        {
            // Create the loops
            float spanStep = cfg.Span / cfg.NumCells / cf.CellSpanLoops;

            for (int i = 0; i < cfg.NumCells; i++)
            {
                for (int j = 0; j < cf.CellSpanLoops; j++)
                {
                    float z = spanStep * (i * cf.CellSpanLoops + j);
                    float s = cf.SpanCellCurvature.Evaluate(j / ((float)cf.CellSpanLoops));

                    var l = _loopPool.Take();
                    CreateCellEdgeLoop(md, l, cfg, cf, z, s);
                    loops.Add(l);
                }
            }

            // Create the last loop
            var ll = _loopPool.Take();

            CreateCellEdgeLoop(md, ll, cfg, cf, cfg.Span, cf.SpanCellCurvature.Evaluate(1f));
            loops.Add(ll);

            // Create the faces between the loops
            for (int i = 0; i < loops.Count - 1; i++)
            {
                CreateLoopFaces(loops[i], loops[i + 1], md);
            }
        }
        /// <summary>
        /// Get the midpoint for all the cells in the canopy definition. Useful for rotating the cells to
        /// achieve variable rigging angle in factory state.
        /// </summary>
        /// <param name="config"></param>
        /// <returns></returns>
        public static Vector3 GetCanopyCentroid(ParachuteConfig config)
        {
            Vector3 centroid = Vector3.zero;

            float circumference = config.RadiusHorizontal * Mathf.PI * 2f;
            float widthRatio    = config.Span / circumference;
            float phaseStep     = widthRatio * Mathf.PI * 2f / config.NumCells;
            float phase         = -widthRatio * Mathf.PI;

            for (int i = 0; i < config.NumCells; i++)
            {
                Vector3 p0 = new Vector3(
                    Mathf.Sin(phase) * config.RadiusHorizontal,
                    Mathf.Cos(phase) * config.RadiusVertical + config.HeightOffset - config.RadiusVertical);
                Vector3 p1 = new Vector3(
                    Mathf.Sin(phase + phaseStep) * config.RadiusHorizontal,
                    Mathf.Cos(phase + phaseStep) * config.RadiusVertical + config.HeightOffset - config.RadiusVertical);

                centroid += Vector3.Lerp(p0, p1, 0.5f);

                phase += phaseStep;
            }

            centroid /= (float)config.NumCells;

            return(centroid);
        }
        /// <summary>
        /// Get an immutable transform representing the spatial state of a single cell from a canopy
        /// in factory state
        /// </summary>
        /// <param name="config"></param>
        /// <param name="centroid"></param>
        /// <param name="cellIndex"></param>
        /// <returns></returns>
        public static ImmutableTransform GetCellTransform(ParachuteConfig config, Vector3 centroid, int cellIndex)
        {
            float circumference = config.RadiusHorizontal * Mathf.PI * 2f;
            float widthRatio    = config.Span / circumference;
            float phaseStep     = widthRatio * Mathf.PI * 2f / config.NumCells;
            float phase         = -widthRatio * Mathf.PI;

            phase += phaseStep * cellIndex;

            Vector3 p0 = new Vector3(
                Mathf.Sin(phase) * config.RadiusHorizontal,
                Mathf.Cos(phase) * config.RadiusVertical + config.HeightOffset - config.RadiusVertical);
            Vector3 p1 = new Vector3(
                Mathf.Sin(phase + phaseStep) * config.RadiusHorizontal,
                Mathf.Cos(phase + phaseStep) * config.RadiusVertical + config.HeightOffset - config.RadiusVertical);

            Vector3    pos   = Vector3.Lerp(p0, p1, 0.5f);
            Quaternion rot   = Quaternion.LookRotation(Vector3.forward, Vector3.Cross(Vector3.forward, p1 - p0));
            Vector3    scale = new Vector3(Vector3.Distance(p0, p1), config.Thickness, config.Chord); // todo: areaMultiplier

            // Apply rigging angle

            Quaternion riggingRotation = Quaternion.AngleAxis(config.RiggingAngle, Vector3.right);
            Vector3    centroidPos     = pos - centroid;

            var t = new ImmutableTransform(
                centroid + riggingRotation * centroidPos,
                rot * riggingRotation,
                scale);

            return(t);
        }
示例#4
0
        public void Init(ParachuteConfig config)
        {
            _config = config;

            _root = gameObject.GetComponent <Transform>();

            _renderers = GetComponentsInChildren <Renderer>();

            if (_sections == null || _leftRiserSections == null || _rightRiserSections == null)
            {
                _sections            = new List <Section>();
                _leftToggleSections  = new List <Section>();
                _rightToggleSections = new List <Section>();
                _leftRiserSections   = new List <Section>();
                _rightRiserSections  = new List <Section>();
            }

            _sections.Clear();
            _leftToggleSections.Clear();
            _rightToggleSections.Clear();
            _leftRiserSections.Clear();
            _rightRiserSections.Clear();

            _p = new Perlin(1234);
        }
 /// <summary>
 /// Basic filter making sure no nonsensical parachutes get passed into the factory
 /// </summary>
 /// <param name="config"></param>
 public static void ValidateConfig(ParachuteConfig config)
 {
     if (config.NumCells % 2 == 0)
     {
         config.NumCells += 1; // Todo: respect min/max
     }
     config.NumToggleControlledCells = Mathf.Clamp(config.NumToggleControlledCells, 1, config.NumCells / 2);
 }
示例#6
0
        public ParachuteStorage(string path, ParachuteConfig configPrefab, AirfoilDefinition hardcodedAirfoilDefinition, bool isEditable)
        {
            Directory.CreateDirectory(path);

            _path         = path;
            _configPrefab = configPrefab;
            _hardcodedAirfoilDefinition = hardcodedAirfoilDefinition;
            _isEditable = isEditable;
        }
        private static Parachute CreateNewInstance(ParachuteConfig config, ImmutableTransform transform, string name)
        {
            GameObject root = new GameObject("Parachute");

            root.transform.Set(transform);
            root.name = name;

            Parachute p = root.AddComponent <Parachute>();

            p.Init(config);

            return(p);
        }
        public static Parachute Create(ParachuteConfig config, ImmutableTransform transform, string name)
        {
            ParachuteMaths.ValidateConfig(config);
            Parachute p = CreateNewInstance(config, transform, name);

            // Todo: Differentiate between game use and editor use

            CreateCells(p);
            CreateInterCellJoints(p);
            CreateRiggingLines(p);
            CreateControlGroups(p);
            p.CalculateCanopyBounds();
            AddSounds(p);

            return(p);
        }
        private static void CreateCellEdgeLoop(MeshData md, int[] loop, ParachuteConfig cfg, ParachuteMeshConfig cf, float spanPos, float scale)
        {
            // Note: top vertex row has the outermost chord tip verts. Bottom reuses those.

            int idx = 0;

            float zScale = (0.8f + scale * 0.2f);
            float zMin   = -0.5f * cfg.Chord * zScale;

            // Top
            for (int i = 0; i < cf.CellChordLoops; i++)
            {
                float chordLerp = i / (float)(cf.CellChordLoops - 1);
                float c         = cf.ChordLineCurvature.Evaluate(chordLerp);
                c += cf.ChordUpperThickness.Evaluate(chordLerp) * scale;
                Vector3 v = new Vector3(
                    spanPos,
                    c,
                    zMin + chordLerp * cfg.Chord * zScale
                    );
                md.Vertices.Add(v);
                BoneWeight w = GetBoneWeight(v, cfg);
                md.Weights.Add(w);
                loop[idx++] = md.Vertices.Count - 1;
            }

            // Bottom
            for (int i = 1; i < cf.CellChordLoops - 1; i++)
            {
                float chordLerp = 1f - i / (float)(cf.CellChordLoops - 1);
                float c         = cf.ChordLineCurvature.Evaluate(chordLerp);
                c += cf.ChordLowerThickness.Evaluate(chordLerp) * scale;
                Vector3 v = new Vector3(
                    spanPos,
                    c,
                    zMin + chordLerp * cfg.Chord * zScale
                    );
                md.Vertices.Add(v);
                BoneWeight w = GetBoneWeight(v, cfg);
                md.Weights.Add(w);
                loop[idx++] = md.Vertices.Count - 1;
            }
        }
示例#10
0
        // From: http://pureskydive.com/canopy-wing-loading-calculator/
        public static ExperienceLevel GetDifficulty(ParachuteConfig config)
        {
            var wingLoading = WingLoadingToImperial(GetWingLoading(config));

            if (wingLoading > 1.80f)
            {
                return(ExperienceLevel.Expert);
            }
            if (wingLoading > 1.35f)
            {
                return(ExperienceLevel.Advanced);
            }
            if (wingLoading > 1.05f)
            {
                return(ExperienceLevel.Intermediate);
            }

            return(ExperienceLevel.Basic);
        }
示例#11
0
        public Parachute Create(ParachuteConfig config, ImmutableTransform spawnpoint, string name = "Parachute")
        {
            Profiler.BeginSample("CreateParachute");

            config.AirfoilDefinition = _airfoilDefinition;

            Profiler.BeginSample("CreateSimObject");
            var parachute = UnityParachuteFactory.Create(config, spawnpoint, name);

            Profiler.EndSample();

            Profiler.BeginSample("CreateSkinnedMesh");
            UnityParachuteMeshFactory.CreateSkinnedMesh(parachute, _parachuteMeshconfig, _parachuteMaterial);
            Profiler.EndSample();

            parachute.Inject(_windManager, _fixedClock, _fixedScheduler);

            Profiler.EndSample();

            return(parachute);
        }
 public static void FromByteStream(ParachuteConfig config, NetBuffer reader)
 {
     config.Id                       = reader.ReadString();
     config.Name                     = reader.ReadString();
     config.RadiusHorizontal         = reader.ReadFloat();
     config.RadiusVertical           = reader.ReadFloat();
     config.HeightOffset             = reader.ReadFloat();
     config.Span                     = reader.ReadFloat();
     config.Chord                    = reader.ReadFloat();
     config.NumCells                 = reader.ReadInt32();
     config.Mass                     = reader.ReadFloat();
     config.RiggingAngle             = reader.ReadFloat();
     config.PressureMultiplier       = reader.ReadFloat();
     config.RigAttachPos             = reader.ReadVector3();
     config.NumToggleControlledCells = reader.ReadInt32();
     config.RearRiserPullMagnitude   = reader.ReadFloat();
     config.FrontRiserPullMagnitude  = reader.ReadFloat();
     config.WeightshiftMagnitude     = reader.ReadFloat();
     config.PilotWeight              = reader.ReadFloat();
     config.InputGamma               = reader.ReadFloat();
     config.InputSmoothing           = reader.ReadFloat();
 }
 public static void ToByteStream(ParachuteConfig config, NetBuffer writer)
 {
     writer.Write(config.Id);
     writer.Write(config.Name);
     writer.Write(config.RadiusHorizontal);
     writer.Write(config.RadiusVertical);
     writer.Write(config.HeightOffset);
     writer.Write(config.Span);
     writer.Write(config.Chord);
     writer.Write(config.NumCells);
     writer.Write(config.Mass);
     writer.Write(config.RiggingAngle);
     writer.Write(config.PressureMultiplier);
     writer.Write(config.RigAttachPos);
     writer.Write(config.NumToggleControlledCells);
     writer.Write(config.RearRiserPullMagnitude);
     writer.Write(config.FrontRiserPullMagnitude);
     writer.Write(config.WeightshiftMagnitude);
     writer.Write(config.PilotWeight);
     writer.Write(config.InputGamma);
     writer.Write(config.InputSmoothing);
 }
示例#14
0
        private static BoneWeight GetBoneWeight(Vector3 vertPos, ParachuteConfig c)
        {
            float cellSpan = c.Span / c.NumCells;
            float v        = -0.5f * cellSpan + vertPos.x; // vert.x in boneSpace

            int boneL = Mathf.FloorToInt(v / cellSpan);
            int boneR = boneL + 1;

            BoneWeight w = new BoneWeight();

            if (boneL >= 0)
            {
                float boneLPos = boneL * cellSpan;
                w.boneIndex0 = boneL;
                w.weight0    = cellSpan - (v - boneLPos);
            }

            if (boneR < c.NumCells)
            {
                float boneRPos = boneR * cellSpan;
                w.boneIndex1 = boneR;
                w.weight1    = cellSpan + (v - boneRPos);
            }

            if (w.weight0 < w.weight1)
            {
                int   iTemp = w.boneIndex0;
                float wTemp = w.weight0;
                w.boneIndex0 = w.boneIndex1;
                w.weight0    = w.weight1;
                w.boneIndex1 = iTemp;
                w.weight1    = wTemp;
            }

            w = Normalize(w);

            return(w);
        }
示例#15
0
 public Parachute Create(ParachuteConfig config, string name = "Parachute")
 {
     return(Create(config, ImmutableTransform.Identity, name));
 }
示例#16
0
        public void Initialize(ITypedDataCursor <ParachuteStorageViewState> storage, bool isVrActive)
        {
            _openParachuteFolder.gameObject.SetActive(!isVrActive);
            _addParachute.gameObject.SetActive(!isVrActive);

            _backToFlight.onClick.AddListener(() => {
                if (BackToFlight != null)
                {
                    BackToFlight();
                }
            });

            var editorState     = storage.To(s => s.EditorState);
            var availableChutes = storage.To(s => s.AvailableParachutes);

            var             parachuteName       = editorState.To(s => s.Config).To(c => c.Name);
            Action <string> updateParachuteName = value => {
                parachuteName.Set(value.Limit(_parachuteNameCharLimit, ""));
            };

            _parachuteNameEditor.Limit        = _parachuteNameCharLimit;
            _parachuteNameEditor.TextChanged += updateParachuteName;

            _parachuteThumbnails = new ParachuteThumbnail[_maxParachutes];
            for (int i = 0; i < _maxParachutes; i++)
            {
                var parachuteThumbnail = GameObject.Instantiate(_parachuteThumbnailPrefab).GetComponent <ParachuteThumbnail>();
                parachuteThumbnail.transform.SetParent(_thumbnailRoot.transform);
                parachuteThumbnail.transform.localPosition = Vector3.zero;
                parachuteThumbnail.transform.localRotation = Quaternion.identity;
                parachuteThumbnail.transform.localScale    = Vector3.one;
                parachuteThumbnail.gameObject.SetActive(false);
                _parachuteThumbnails[i] = parachuteThumbnail;
            }

            var storageDir = storage.To(s => s.StorageDir);

            _openParachuteFolder.onClick.AddListener(() => {
                UnityFileBrowserUtil.Open(storageDir.Get());
            });

            Action <string> selectChute = parachuteId => {
                var state = storage.Get();
                if (state.EditorState.Config.Id != parachuteId)
                {
                    for (int i = 0; i < state.AvailableParachutes.Count; i++)
                    {
                        var parachute = state.AvailableParachutes[i];
                        if (parachute.Id == parachuteId)
                        {
                            editorState.Set(new ParachuteEditor.EditorState(parachute));
                        }
                    }
                }
            };

            Action <string> deleteChute = parachuteId => {
                var state = storage.Get();
                int selectedParachuteIndex = 0;
                for (int i = state.AvailableParachutes.Count - 1; i >= 0; i--)
                {
                    var parachute = state.AvailableParachutes[i];
                    if (parachute.Id == parachuteId)
                    {
                        state.AvailableParachutes.RemoveAt(i);
                        selectedParachuteIndex = Math.Max(i - 1, 0);
                        break;
                    }
                }
                if (state.EditorState.Config.Id == parachuteId)
                {
                    var defaultChute = state.AvailableParachutes[selectedParachuteIndex];
                    editorState.Set(new ParachuteEditor.EditorState(defaultChute));
                }
                availableChutes.Set(state.AvailableParachutes);
            };

            Action <string> cloneChute = parachuteId => {
                var state = storage.Get();
                if (state.AvailableParachutes.Count < _maxParachutes)
                {
                    ParachuteConfig existingParachute = null;
                    for (int i = 0; i < state.AvailableParachutes.Count; i++)
                    {
                        var parachute = state.AvailableParachutes[i];
                        if (parachute.Id == parachuteId)
                        {
                            existingParachute = parachute;
                        }
                    }

                    var newParachute = ParachuteConfig.CreateNew(existingParachute);
                    newParachute.Name = (existingParachute.Name + " Copy").Limit(_parachuteNameCharLimit, "");
                    state.AvailableParachutes.Add(newParachute);
                    availableChutes.Set(state.AvailableParachutes);
                    selectChute(newParachute.Id);
                }
            };

            var isEditingState = storage.To(s => s.EditorState).To(s => s.IsEditing);

            Action <string> editChute = parachuteId => {
                var    state = storage.Get();
                string currentlyEditingParachute = state.EditorState.IsEditing ? state.EditorState.Config.Id : null;
                if (currentlyEditingParachute == parachuteId)
                {
                    // Switch to read-only mode
                    isEditingState.Set(false);
                }
                else
                {
                    // Enable editing
                    // TODO Do selection and edit switching in one transformation
                    selectChute(parachuteId);
                    isEditingState.Set(true);
                }
            };

            _addParachute.onClick.AddListener(() => {
                var state = storage.Get();
                if (state.AvailableParachutes.Count < _maxParachutes)
                {
                    var newParachute  = ParachuteConfig.CreateNew(_parachuteConfigTemplate);
                    newParachute.Name = "New " + _parachuteConfigTemplate.Name;
                    state.AvailableParachutes.Add(newParachute);
                    availableChutes.Set(state.AvailableParachutes);
                    editorState.Set(new ParachuteEditor.EditorState(newParachute));
                }
            });

            storage.OnUpdate.Subscribe(state => {
                for (int i = 0; i < _parachuteThumbnails.Length; i++)
                {
                    var thumbnail       = _parachuteThumbnails[i];
                    thumbnail.OnSelect -= selectChute;
                    thumbnail.OnDelete -= deleteChute;
                    thumbnail.OnClone  -= cloneChute;
                    thumbnail.OnEdit   -= editChute;
                }

                _parachuteNameEditor.IsEditable = state.EditorState.IsEditing;
                _parachuteNameEditor.Text       = state.EditorState.Config.Name;

                for (int i = 0; i < _parachuteThumbnails.Length; i++)
                {
                    var thumbnail = _parachuteThumbnails[i];

                    if (i < state.AvailableParachutes.Count)
                    {
                        var parachute             = state.AvailableParachutes[i];
                        var isSelected            = state.EditorState.Config.Id == parachute.Id;
                        var parachuteControlState = new ParachuteThumbnail.ParachuteControlState(
                            parachute.Id,
                            parachute.Name,
                            isEditorAvailable: !isVrActive,
                            isSelected: state.EditorState.Config.Id == parachute.Id,
                            isEditable: parachute.IsEditable,
                            isDeletable: state.AvailableParachutes.Count > 1 && parachute.IsEditable,
                            isEditing: state.EditorState.IsEditing && isSelected);
                        thumbnail.SetState(parachuteControlState);
                        if (parachuteControlState.IsDeletable)
                        {
                            thumbnail.OnDelete += deleteChute;
                        }
                        if (parachuteControlState.IsEditable)
                        {
                            thumbnail.OnEdit += editChute;
                        }
                        thumbnail.OnSelect += selectChute;
                        thumbnail.OnClone  += cloneChute;
                        thumbnail.gameObject.SetActive(true);

                        if (parachuteControlState.IsSelected)
                        {
                            FirstObject = thumbnail.gameObject;
                        }
                    }
                    else
                    {
                        thumbnail.gameObject.SetActive(false);
                    }
                }
            });
        }
示例#17
0
 public EditorState(ParachuteConfig config)
 {
     SelectedWidget = null;
     IsEditing      = false;
     Config         = config;
 }
示例#18
0
 public ParachuteStorageViewState(ParachuteConfig selectedConfig, IList <ParachuteConfig> availableChutes, string storageDir)
 {
     StorageDir          = storageDir;
     EditorState         = new ParachuteEditor.EditorState(selectedConfig);
     AvailableParachutes = availableChutes;
 }
示例#19
0
 // Returned units: kg/m²
 public static float GetWingLoading(ParachuteConfig config)
 {
     return(config.PilotWeight / GetRectArea(config));
 }
示例#20
0
 public static float GetRectArea(ParachuteConfig config)
 {
     // Todo: elliptical/tapered shape
     return(config.Chord * config.Span);
 }
示例#21
0
        /* ======= Parachute Analysis & Evaluation ====== */

        public static float GetAspectRatio(ParachuteConfig config)
        {
            // Todo: span^2/measured-area
            return(config.Span / config.Chord);
        }
示例#22
0
        public void StoreParachute(ParachuteConfig config, string json)
        {
            var filePath = Path.Combine(_path, config.Name + "-" + config.Id + ".json");

            File.WriteAllText(filePath, json, Encoding.UTF8);
        }
示例#23
0
        private void UpdateEditorState <TMeasureSystem>(
            ParachuteConfig config,
            ParachuteProperties <TMeasureSystem> props)
            where TMeasureSystem : MeasureSystem
        {
            var editorOrigin        = transform.MakeImmutable();
            var canopyCentroidWorld = editorOrigin.TranslateLocally(ParachuteMaths.GetCanopyCentroid(config));

            _heightOffsetWidget.transform.Set(editorOrigin.TranslateLocally(y: 1f));
            _heightOffsetText.transform.Set(editorOrigin.TranslateLocally(y: config.HeightOffset + 1f, z: -0.5f));
            _heightOffsetText.Text.Clear();
            props.HeightOffset.FormatTo(_heightOffsetText.Text, precision: 2);

            _riggingAngleGizmo.transform.Set(canopyCentroidWorld);

            _cellColorPicker.CurrentColor = config.Color;

            _summaryText.Text.Clear();
            _summaryText.Text
            .Append("Difficulty level: ")
            .Append(ParachuteMaths.GetDifficulty(config).Stringify())
            .Append("\n")
            .Append(config.NumCells)
            .Append(" cells (")
            .Append(config.NumToggleControlledCells)
            .Append(" braked), ");
            props.CanopyMass.FormatTo(_summaryText.Text, precision: 1);
            _summaryText.Text.Append("\n");
            props.Span.FormatTo(_summaryText.Text, precision: 1);
            _summaryText.Text.Append(" × ");
            props.Chord.FormatTo(_summaryText.Text, precision: 1);
            _summaryText.Text.Append(" = ");
            props.Area.FormatTo(_summaryText.Text, precision: 0);
            _summaryText.transform.Set(canopyCentroidWorld.TranslateLocally(new Vector3(-3f, 2.5f, 0f)));

            // TODO Use mutable strings
            _riggingAngle.transform.Set(canopyCentroidWorld);
            _riggingAngle.Text.Clear();
            props.RiggingAngle.FormatTo(_riggingAngle.Text, precision: 0);

//            _rigAttachPositionText.text = string.Format("{0:0.##}, {1:0.##}, {2:0.##}",
//                props.RigAttachPosition.Value.x,
//                props.RigAttachPosition.Value.y,
//                props.RigAttachPosition.Value.z);
//            _rigAttachPositionText.transform.Set(rigAttachPositionTransform);

            var pilotWeightTransform = editorOrigin.TranslateLocally(y: -0.8f);

            _pilotWeight.transform.Set(pilotWeightTransform.TranslateLocally(y: -0.4f));
            _pilotWeightGizmo.transform.Set(pilotWeightTransform);

            _pilotWeight.Text.Clear();
            props.PilotWeight.FormatTo(_pilotWeight.Text, precision: 0);
            _pilotWeight.Text.Append(" (");
            props.WingLoading.FormatTo(_pilotWeight.Text, precision: 2);
            _pilotWeight.Text.Append(")");

            var pilotWeightShiftTransform = editorOrigin;

            _weightShiftMagnitudeGizmo.transform.Set(pilotWeightShiftTransform);
            _pilotWeightShiftMagnitude.transform.Set(pilotWeightShiftTransform.TranslateLocally(x: -1.1f));
            _weightShiftVisualizer.transform.position = pilotWeightShiftTransform.Position;
            _weightShiftVisualizer.Radius             = config.WeightshiftMagnitude;

            _pilotWeightShiftMagnitude.Text.Clear();
            props.WeightShiftMagnitude.FormatTo(_pilotWeightShiftMagnitude.Text, precision: 2);

            _radiusGizmo.UpdateState();
            _heightOffsetWidget.UpdateState();
            _rigAttachPosition.UpdateState();
            _pilotWeightGizmo.UpdateState();
            _weightShiftMagnitudeGizmo.UpdateState();
        }