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); }
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); }
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; } }
// 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); }
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); }
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); }
public Parachute Create(ParachuteConfig config, string name = "Parachute") { return(Create(config, ImmutableTransform.Identity, name)); }
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); } } }); }
public EditorState(ParachuteConfig config) { SelectedWidget = null; IsEditing = false; Config = config; }
public ParachuteStorageViewState(ParachuteConfig selectedConfig, IList <ParachuteConfig> availableChutes, string storageDir) { StorageDir = storageDir; EditorState = new ParachuteEditor.EditorState(selectedConfig); AvailableParachutes = availableChutes; }
// Returned units: kg/m² public static float GetWingLoading(ParachuteConfig config) { return(config.PilotWeight / GetRectArea(config)); }
public static float GetRectArea(ParachuteConfig config) { // Todo: elliptical/tapered shape return(config.Chord * config.Span); }
/* ======= Parachute Analysis & Evaluation ====== */ public static float GetAspectRatio(ParachuteConfig config) { // Todo: span^2/measured-area return(config.Span / config.Chord); }
public void StoreParachute(ParachuteConfig config, string json) { var filePath = Path.Combine(_path, config.Name + "-" + config.Id + ".json"); File.WriteAllText(filePath, json, Encoding.UTF8); }
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(); }