static Bounds GetBounds(PF.Vector3[] points, Vector3 right, Vector3 up, Vector3 forward, Vector3 origin, float minimumHeight) { if (points == null || points.Length == 0) { return(new Bounds()); } float miny = points[0].y, maxy = points[0].y; for (int i = 0; i < points.Length; i++) { miny = Mathf.Min(miny, points[i].y); maxy = Mathf.Max(maxy, points[i].y); } var extraHeight = Mathf.Max(minimumHeight - (maxy - miny), 0) * 0.5f; miny -= extraHeight; maxy += extraHeight; Vector3 min = right * points[0].x + up * points[0].y + forward * points[0].z; Vector3 max = min; for (int i = 0; i < points.Length; i++) { var p = right * points[i].x + forward * points[i].z; var p1 = p + up * miny; var p2 = p + up * maxy; min = Vector3.Min(min, p1); min = Vector3.Min(min, p2); max = Vector3.Max(max, p1); max = Vector3.Max(max, p2); } return(new Bounds((min + max) * 0.5F + origin, max - min)); }
/** Bias towards the right side of agents. * Rotate desiredVelocity at most [value] number of radians. 1 radian ≈ 57° * This breaks up symmetries. * * The desired velocity will only be rotated if it is inside a velocity obstacle (VO). * If it is inside one, it will not be rotated further than to the edge of it * * The targetPointInVelocitySpace will be rotated by the same amount as the desired velocity * * \returns True if the desired velocity was inside any VO */ static bool BiasDesiredVelocity(VOBuffer vos, ref Vector2 desiredVelocity, ref Vector2 targetPointInVelocitySpace, float maxBiasRadians) { var desiredVelocityMagn = desiredVelocity.magnitude; var maxValue = 0f; for (int i = 0; i < vos.length; i++) { float value; // The value is approximately the distance to the edge of the VO // so taking the maximum will give us the distance to the edge of the VO // which the desired velocity is furthest inside vos.buffer[i].Gradient(desiredVelocity, out value); maxValue = Mathf.Max(maxValue, value); } // Check if the agent was inside any VO var inside = maxValue > 0; // Avoid division by zero below if (desiredVelocityMagn < 0.001f) { return(inside); } // Rotate the desired velocity clockwise (to the right) at most maxBiasRadians number of radians // Assuming maxBiasRadians is small, we can just move it instead and it will give approximately the same effect // See https://en.wikipedia.org/wiki/Small-angle_approximation var angle = Mathf.Min(maxBiasRadians, maxValue / desiredVelocityMagn); desiredVelocity += new Vector2(desiredVelocity.y, -desiredVelocity.x) * angle; targetPointInVelocitySpace += new Vector2(targetPointInVelocitySpace.y, -targetPointInVelocitySpace.x) * angle; return(inside); }
#pragma warning restore 414 KMSelectable[] ProcessTwitchCommand(string command) { command = command.ToLowerInvariant().Trim(); if (Regex.IsMatch(command, @"^press +[0-9^, |&]+$")) { command = command.Substring(6).Trim(); var presses = command.Split(new[] { ',', ' ', '|', '&' }, StringSplitOptions.RemoveEmptyEntries); var pressList = new List <KMSelectable>(); for (var i = 0; i < presses.Length; i++) { if (Regex.IsMatch(presses[i], @"^[0-9]{1,2}$")) { pressList.Add(ModuleButtons[Math.Clamp(Math.Max(1, int.Parse(presses[i].ToString())) - 1, 0, ModuleButtons.Length - 1)]); } } return(pressList.ToArray()); } if (Regex.IsMatch(command, @"^(submit|sub|s)$")) { return new[] { SubmitButton } } ; return(null); } }
protected virtual void CalculateNextRotation(float slowdown, out Quaternion nextRotation) { if (lastDeltaTime > 0.00001f) { Vector2 desiredRotationDirection; if (rvoController != null && rvoController.enabled) { // When using local avoidance, use the actual velocity we are moving with if that velocity // is high enough, otherwise fall back to the velocity that we want to move with (velocity2D). // The local avoidance velocity can be very jittery when the character is close to standing still // as it constantly makes small corrections. We do not want the rotation of the character to be jittery. var actualVelocity = lastDeltaPosition / lastDeltaTime; desiredRotationDirection = Vector2.Lerp(velocity2D, actualVelocity, 4 * actualVelocity.magnitude / (maxSpeed + 0.0001f)); } else { desiredRotationDirection = velocity2D; } // Rotate towards the direction we are moving in. // Don't rotate when we are very close to the target. var currentRotationSpeed = rotationSpeed * Mathf.Max(0, (slowdown - 0.3f) / 0.7f); nextRotation = SimulateRotationTowards(desiredRotationDirection, currentRotationSpeed * lastDeltaTime); } else { // TODO: simulatedRotation nextRotation = rotation; } }
/** Finds the closest point on the current path and configures the #interpolator */ protected virtual void ConfigureNewPath() { var hadValidPath = interpolator.valid; var prevTangent = hadValidPath ? interpolator.tangent : Vector3.zero; interpolator.SetPath(path.vectorPath); interpolator.MoveToClosestPoint(GetFeetPosition()); if (interpolatePathSwitches && switchPathInterpolationSpeed > 0.01f && hadValidPath) { var correctionFactor = Mathf.Max(-Vector3.Dot(prevTangent.normalized, interpolator.tangent.normalized), 0); interpolator.distance -= speed * correctionFactor * (1f / switchPathInterpolationSpeed); } }
public void Validate() { if (surfaceDefinition == null) { surfaceDefinition = new ChiselSurfaceDefinition(); } curveSegments = Mathf.Max(curveSegments, 2); revolveSegments = Mathf.Max(revolveSegments, 1); totalAngle = Mathf.Clamp(totalAngle, 1, 360); // TODO: constants surfaceDefinition.EnsureSize(6); }
/** Traces the vector field constructed out of the velocity obstacles. * Returns the position which gives the minimum score (approximately). * * \see https://en.wikipedia.org/wiki/Gradient_descent */ Vector2 Trace(VOBuffer vos, Vector2 p, out float score) { // Pick a reasonable initial step size float stepSize = Mathf.Max(radius, 0.2f * desiredSpeed); float bestScore = float.PositiveInfinity; Vector2 bestP = p; // TODO: Add momentum to speed up convergence? const int MaxIterations = 50; for (int s = 0; s < MaxIterations; s++) { float step = 1.0f - (s / (float)MaxIterations); step = Sqr(step) * stepSize; float value; var gradient = EvaluateGradient(vos, p, out value); if (value < bestScore) { bestScore = value; bestP = p; } // TODO: Add cutoff for performance gradient.Normalize(); gradient *= step; Vector2 prev = p; p += gradient; if (DebugDraw) { Debug.DrawLine(FromXZ(prev + position), FromXZ(p + position), Rainbow(s * 0.1f) * new Color(1, 1, 1, 1f)); } } score = bestScore; return(bestP); }
void Tick() { if (Event.current.type == EventType.Repaint) { float deltaTime = Time.realtimeSinceStartup - lastUpdate; // Right at the start of a transition the deltaTime will // not be reliable, so use a very small value instead // until the next repaint if (value == 0f || value == 1f) { deltaTime = 0.001f; } deltaTime = Mathf.Clamp(deltaTime, 0.00001F, 0.1F); // Larger regions fade slightly slower deltaTime /= Mathf.Sqrt(Mathf.Max(lastRect.height, 100)); lastUpdate = Time.realtimeSinceStartup; float targetValue = open ? 1F : 0F; if (!Mathf.Approximately(targetValue, value)) { value += deltaTime * animationSpeed * Mathf.Sign(targetValue - value); value = Mathf.Clamp01(value); editor.Repaint(); if (!fancyEffects) { value = targetValue; } } else { value = targetValue; } } }
/** Calculates the bounds for this component. * This is a relatively expensive operation, it needs to go through all points and * run matrix multiplications. */ public Bounds GetBounds() { if (points == null || points.Length == 0) { Bounds bounds; var coll = GetComponent <Collider>(); var coll2D = GetComponent <Collider2D>(); var rend = GetComponent <Renderer>(); if (coll != null) { bounds = coll.bounds; } else if (coll2D != null) { bounds = coll2D.bounds; bounds.size = new Vector3(bounds.size.x, bounds.size.y, Mathf.Max(bounds.size.z, 1f)); } else if (rend != null) { bounds = rend.bounds; } else { return(new Bounds(Vector3.zero, Vector3.zero)); } if (legacyMode && bounds.size.y < minBoundsHeight) { bounds.size = new Vector3(bounds.size.x, minBoundsHeight, bounds.size.z); } return(bounds); } else { return(GraphUpdateShape.GetBounds(convex ? convexPoints : points, legacyMode && legacyUseWorldSpace ? Matrix4x4.identity : transform.localToWorldMatrix, minBoundsHeight)); } }
/** Checks if the character is grounded and prevents ground penetration. * \param position Position of the character in the world. * \param lastElevation Elevation coordinate before the agent was moved. This is along the 'up' axis of the #movementPlane. * * Sets #verticalVelocity to zero if the character is grounded. * * \returns The new position of the character. */ protected Vector3 RaycastPosition(Vector3 position, float lastElevation) { RaycastHit hit; float elevation; movementPlane.ToPlane(position, out elevation); float rayLength = centerOffset + Mathf.Max(0, lastElevation - elevation); Vector3 rayOffset = movementPlane.ToWorld(Vector2.zero.ToPFV2(), rayLength).ToUnityV3(); if (Physics.Raycast(position + rayOffset, -rayOffset, out hit, rayLength, groundMask, QueryTriggerInteraction.Ignore)) { // Grounded // Make the vertical velocity fall off exponentially. This is reasonable from a physical standpoint as characters // are not completely stiff and touching the ground will not immediately negate all velocity downwards. The AI will // stop moving completely due to the raycast penetration test but it will still *try* to move downwards. This helps // significantly when moving down along slopes as if the vertical velocity would be set to zero when the character // was grounded it would lead to a kind of 'bouncing' behavior (try it, it's hard to explain). Ideally this should // use a more physically correct formula but this is a good approximation and is much more performant. The constant // '5' in the expression below determines how quickly it converges but high values can lead to too much noise. verticalVelocity *= System.Math.Max(0, 1 - 5 * lastDeltaTime); return(hit.point); } return(position); }
private ItemEffect GenerateEffect(Item item) { Random rnd = new Random(); float itemPriceMax = 2 * PriceMax - item.Class.Price; float manaMax = item.Class.Material.MagicVolume * item.Class.Class.MagicVolume - item.ManaUsage; // note: this will not work correctly if SlotsWarrior or SlotsMage values for a slot are not sorted by ascending order. // parameters that appear first will "override" parameters with the same weight appearing later. int lastValue; Templates.TplModifier lastModifier = TemplateLoader.Templates.Modifiers[TemplateLoader.Templates.Modifiers.Count - 1]; if ((item.Class.Option.SuitableFor & 1) != 0) { lastValue = lastModifier.SlotsFighter[item.Class.Option.Slot - 1]; } else { lastValue = lastModifier.SlotsMage[item.Class.Option.Slot - 1]; } int randomValue = rnd.Next(0, lastValue) + 1; Templates.TplModifier matchingModifier = null; if (item.Class.Option.SuitableFor == 2 && item.Class.Option.Slot == 1) { matchingModifier = TemplateLoader.Templates.Modifiers[(int)ItemEffect.Effects.CastSpell]; } else { for (int i = 1; i < TemplateLoader.Templates.Modifiers.Count; i++) { Templates.TplModifier prevModifier = TemplateLoader.Templates.Modifiers[i - 1]; Templates.TplModifier modifier = TemplateLoader.Templates.Modifiers[i]; int prevValue; int value; if ((item.Class.Option.SuitableFor & 1) != 0) { prevValue = prevModifier.SlotsFighter[item.Class.Option.Slot - 1]; value = modifier.SlotsFighter[item.Class.Option.Slot - 1]; } else { prevValue = prevModifier.SlotsMage[item.Class.Option.Slot - 1]; value = modifier.SlotsMage[item.Class.Option.Slot - 1]; } if (prevValue < randomValue && randomValue <= value) { matchingModifier = modifier; break; } } } if (matchingModifier == null) { // parameter not found. weird, but happens. return(null); } if ((matchingModifier.UsableBy & item.Class.Option.SuitableFor) == 0) { // parameter for class not found in the item. return(null); } // parameter found. calculate max possible power ItemEffect effect = new ItemEffect(); effect.Type1 = (ItemEffect.Effects)matchingModifier.Index; float maxPower; float absoluteMax = manaMax / matchingModifier.ManaCost; if (matchingModifier.Index == (int)ItemEffect.Effects.CastSpell) { // select spell to cast. // if for fighter, choose between fighter-specific spells. // if for mage, choose between mage-specific spells. Spell.Spells[] spells; if ((item.Class.Option.SuitableFor & 1) != 0) { spells = new Spell.Spells[] { Spell.Spells.Stone_Curse, Spell.Spells.Drain_Life } } ; else { spells = new Spell.Spells[] { Spell.Spells.Fire_Arrow, Spell.Spells.Lightning, Spell.Spells.Prismatic_Spray, Spell.Spells.Stone_Curse, Spell.Spells.Drain_Life, Spell.Spells.Ice_Missile, Spell.Spells.Diamond_Dust } }; // choose random spell Spell.Spells spell = spells[rnd.Next(0, spells.Length)]; effect.Value1 = (int)spell; // calculate max power Templates.TplSpell spellTemplate = TemplateLoader.Templates.Spells[effect.Value1]; maxPower = Mathf.Log(itemPriceMax / (spellTemplate.ScrollCost * 10f)) / Mathf.Log(2); if (!float.IsNaN(maxPower) && maxPower > 0) { maxPower = (Mathf.Pow(1.2f, maxPower) - 1) * 30; } else { return(null); } maxPower = Mathf.Min(maxPower, absoluteMax); maxPower = Mathf.Min(maxPower, 100); } else { maxPower = Mathf.Log(itemPriceMax / (manaMax * 50) - 1) / Mathf.Log(1.5f) * 70f / matchingModifier.ManaCost; if (float.IsNaN(maxPower)) { return(null); } maxPower = Mathf.Min(maxPower, absoluteMax); maxPower = Mathf.Min(maxPower, matchingModifier.AffectMax); if (maxPower < matchingModifier.AffectMin) { return(null); } } if (maxPower <= 1) { // either some limit hit, or something else return(null); } // max parameter power found. randomize values switch (effect.Type1) { case ItemEffect.Effects.CastSpell: effect.Value2 = rnd.Next(1, (int)maxPower + 1); break; case ItemEffect.Effects.DamageFire: case ItemEffect.Effects.DamageWater: case ItemEffect.Effects.DamageAir: case ItemEffect.Effects.DamageEarth: case ItemEffect.Effects.DamageAstral: effect.Value1 = rnd.Next(1, (int)maxPower + 1); effect.Value2 = rnd.Next(1, (int)(maxPower / 2) + 1); break; default: effect.Value1 = (int)Mathf.Max(matchingModifier.AffectMin, rnd.Next(1, (int)maxPower + 1)); break; } return(effect); }
public static Vector2 Max(Vector2 lhs, Vector2 rhs) { return(new Vector2(Mathf.Max(lhs.x, rhs.x), Mathf.Max(lhs.y, rhs.y))); }
/** Will calculate a number of points around \a center which are on the graph and are separated by \a clearance from each other. * The maximum distance from \a center to any point will be \a radius. * Points will first be tried to be laid out as \a previousPoints and if that fails, random points will be selected. * This is great if you want to pick a number of target points for group movement. If you pass all current agent points from e.g the group's average position * this method will return target points so that the units move very little within the group, this is often aesthetically pleasing and reduces jitter if using * some kind of local avoidance. * * \param center The point to generate points around * \param g The graph to use for linecasting. If you are only using one graph, you can get this by AstarPath.active.graphs[0] as IRaycastableGraph. * Note that not all graphs are raycastable, recast, navmesh and grid graphs are raycastable. On recast and navmesh it works the best. * \param previousPoints The points to use for reference. Note that these should not be in world space. They are treated as relative to \a center. * The new points will overwrite the existing points in the list. The result will be in world space, not relative to \a center. * \param radius The final points will be at most this distance from \a center. * \param clearanceRadius The points will if possible be at least this distance from each other. * * \todo Write unit tests */ public static void GetPointsAroundPoint(Vector3 center, IRaycastableGraph g, List <Vector3> previousPoints, float radius, float clearanceRadius) { if (g == null) { throw new System.ArgumentNullException("g"); } var graph = g as NavGraph; if (graph == null) { throw new System.ArgumentException("g is not a NavGraph"); } NNInfoInternal nn = graph.GetNearestForce(center, NNConstraint.Default); center = nn.clampedPosition; if (nn.node == null) { // No valid point to start from return; } // Make sure the enclosing circle has a radius which can pack circles with packing density 0.5 radius = Mathf.Max(radius, 1.4142f * clearanceRadius * Mathf.Sqrt(previousPoints.Count)); //Mathf.Sqrt(previousPoints.Count*clearanceRadius*2)); clearanceRadius *= clearanceRadius; for (int i = 0; i < previousPoints.Count; i++) { Vector3 dir = previousPoints[i]; float magn = dir.magnitude; if (magn > 0) { dir /= magn; } float newMagn = radius; //magn > radius ? radius : magn; dir *= newMagn; GraphHitInfo hit; int tests = 0; while (true) { Vector3 pt = center + dir; if (g.Linecast(center, pt, nn.node, out hit)) { if (hit.point == PF.Vector3.zero) { // Oops, linecast actually failed completely // try again unless we have tried lots of times // then we just continue anyway tests++; if (tests > 8) { previousPoints[i] = pt; break; } } else { pt = hit.point; } } bool worked = false; for (float q = 0.1f; q <= 1.0f; q += 0.05f) { Vector3 qt = Vector3.Lerp(center, pt, q); worked = true; for (int j = 0; j < i; j++) { if ((previousPoints[j] - qt).sqrMagnitude < clearanceRadius) { worked = false; break; } } // Abort after 8 tests or when we have found a valid point if (worked || tests > 8) { worked = true; previousPoints[i] = qt; break; } } // Break out of nested loop if (worked) { break; } // If we could not find a valid point, reduce the clearance radius slightly to improve // the chances next time clearanceRadius *= 0.9f; // This will pick points in 2D closer to the edge of the circle with a higher probability dir = Random.onUnitSphere * Mathf.Lerp(newMagn, radius, tests / 5); dir.y = 0; tests++; } } }
private void Update() { if (!_initCompleted) { return; } if (SuperController.singleton.freezeAnimation) { return; } #if LFE_DEBUG if (Input.GetKey("up")) { CameraTarget.centerTarget.targetCamera.enabled = false; _detector.Detector.enabled = true; } else { CameraTarget.centerTarget.targetCamera.enabled = true; _detector.Detector.enabled = true; } #endif try { _brightnessChangeCountdown -= Time.deltaTime; // run the scheduled animation if (_currentAnimation != null) { _pupilMorph.morphValueAdjustLimits = Math.Clamp(_currentAnimation.Update(), -1.5f, 2.0f); if (_currentAnimation.IsFinished) { _currentAnimation = null; // schedule a new idle _idleCountDown = UnityEngine.Random.Range(0.01f, Math.Max(IdleMaxDelayStorable.val, 0.01f)); } } var brightness = CalculateBrightness(); var currentValue = _pupilMorph.morphValue; var targetValue = BrightnessToMorphValue(brightness); var duration = 0f; if (_lastBrightness == brightness) { // maybe schedule an idle animation - but do not interrupt an animation in progress just for idle if (_currentAnimation == null && _idleCountDown < 0) { duration = Math.Max(IdleAdjustSpeedStorable.val, 0.01f); _idleSign = _idleSign * -1; targetValue = targetValue + (_idleSign * UnityEngine.Random.Range(0.01f, IdleStrengthStorable.val)); _currentAnimation = new EyeDialationAnimation(currentValue, targetValue, duration, (p) => Easings.BackEaseOut(p)); } else { _idleCountDown -= Time.deltaTime; } } else { if (_brightnessChangeCountdown <= 0) { // schedule brightness adjustment animation - override any currently running animation for this one. light always wins duration = targetValue > currentValue ? DarkAdjustSpeedStorable.val : LightAdjustSpeedStorable.val; _currentAnimation = new EyeDialationAnimation(currentValue, targetValue, duration, (p) => Easings.ElasticEaseOut(p)); _brightnessChangeCountdown = _brightnessChangeThrottle; } } _lastBrightness = brightness; } catch (Exception ex) { SuperController.LogError(ex.ToString()); } }
public List <Vector3> SmoothSimple(List <Vector3> path) { if (path.Count < 2) { return(path); } List <Vector3> subdivided; if (uniformLength) { // Clamp to a small value to avoid the path being divided into a huge number of segments maxSegmentLength = Mathf.Max(maxSegmentLength, 0.005f); float pathLength = 0; for (int i = 0; i < path.Count - 1; i++) { pathLength += Vector3.Distance(path[i], path[i + 1]); } int estimatedNumberOfSegments = Mathf.FloorToInt(pathLength / maxSegmentLength); // Get a list with an initial capacity high enough so that we can add all points subdivided = ListPool <Vector3> .Claim(estimatedNumberOfSegments + 2); float distanceAlong = 0; // Sample points every [maxSegmentLength] world units along the path for (int i = 0; i < path.Count - 1; i++) { var start = path[i]; var end = path[i + 1]; float length = Vector3.Distance(start, end); while (distanceAlong < length) { subdivided.Add(Vector3.Lerp(start, end, distanceAlong / length)); distanceAlong += maxSegmentLength; } distanceAlong -= length; } // Make sure we get the exact position of the last point subdivided.Add(path[path.Count - 1]); } else { subdivisions = Mathf.Max(subdivisions, 0); if (subdivisions > 10) { Debug.LogWarning("Very large number of subdivisions. Cowardly refusing to subdivide every segment into more than " + (1 << subdivisions) + " subsegments"); subdivisions = 10; } int steps = 1 << subdivisions; subdivided = ListPool <Vector3> .Claim((path.Count - 1) *steps + 1); Polygon.Subdivide(path, subdivided, steps); } if (strength > 0) { for (int it = 0; it < iterations; it++) { Vector3 prev = subdivided[0]; for (int i = 1; i < subdivided.Count - 1; i++) { Vector3 tmp = subdivided[i]; // prev is at this point set to the value that subdivided[i-1] had before this loop started // Move the point closer to the average of the adjacent points subdivided[i] = Vector3.Lerp(tmp, (prev + subdivided[i + 1].ToUnityV3()) / 2F, strength); prev = tmp; } } } return(subdivided); }
public override void OnInspectorGUI(NavGraph target) { var graph = target as RecastGraph; bool preEnabled = GUI.enabled; System.Int64 estWidth = Mathf.RoundToInt(Mathf.Ceil(graph.forcedBoundsSize.x / graph.cellSize)); System.Int64 estDepth = Mathf.RoundToInt(Mathf.Ceil(graph.forcedBoundsSize.z / graph.cellSize)); // Show a warning if the number of voxels is too large if (estWidth * estDepth >= 1024 * 1024 || estDepth >= 1024 * 1024 || estWidth >= 1024 * 1024) { GUIStyle helpBox = GUI.skin.FindStyle("HelpBox") ?? GUI.skin.FindStyle("Box"); Color preColor = GUI.color; if (estWidth * estDepth >= 2048 * 2048 || estDepth >= 2048 * 2048 || estWidth >= 2048 * 2048) { GUI.color = Color.red; } else { GUI.color = Color.yellow; } GUILayout.Label("Warning : Might take some time to calculate", helpBox); GUI.color = preColor; } GUI.enabled = false; EditorGUILayout.LabelField(new GUIContent("Width (voxels)", "Based on the cell size and the bounding box"), new GUIContent(estWidth.ToString())); EditorGUILayout.LabelField(new GUIContent("Depth (voxels)", "Based on the cell size and the bounding box"), new GUIContent(estDepth.ToString())); GUI.enabled = preEnabled; graph.cellSize = EditorGUILayout.FloatField(new GUIContent("Cell Size", "Size of one voxel in world units"), graph.cellSize); if (graph.cellSize < 0.001F) { graph.cellSize = 0.001F; } graph.useTiles = (UseTiles)EditorGUILayout.EnumPopup("Use Tiles", graph.useTiles ? UseTiles.UseTiles : UseTiles.DontUseTiles) == UseTiles.UseTiles; if (graph.useTiles) { EditorGUI.indentLevel++; graph.editorTileSize = EditorGUILayout.IntField(new GUIContent("Tile Size", "Size in voxels of a single tile.\n" + "This is the width of the tile.\n" + "\n" + "A large tile size can be faster to initially scan (but beware of out of memory issues if you try with a too large tile size in a large world)\n" + "smaller tile sizes are (much) faster to update.\n" + "\n" + "Different tile sizes can affect the quality of paths. It is often good to split up huge open areas into several tiles for\n" + "better quality paths, but too small tiles can lead to effects looking like invisible obstacles."), graph.editorTileSize); EditorGUI.indentLevel--; } graph.minRegionSize = EditorGUILayout.FloatField(new GUIContent("Min Region Size", "Small regions will be removed. In square world units"), graph.minRegionSize); graph.walkableHeight = EditorGUILayout.FloatField(new GUIContent("Walkable Height", "Minimum distance to the roof for an area to be walkable"), graph.walkableHeight); graph.walkableHeight = Mathf.Max(graph.walkableHeight, 0); graph.walkableClimb = EditorGUILayout.FloatField(new GUIContent("Walkable Climb", "How high can the character climb"), graph.walkableClimb); // A walkableClimb higher than this can cause issues when generating the navmesh since then it can in some cases // Both be valid for a character to walk under an obstacle and climb up on top of it (and that cannot be handled with a navmesh without links) if (graph.walkableClimb >= graph.walkableHeight) { graph.walkableClimb = graph.walkableHeight; EditorGUILayout.HelpBox("Walkable climb should be less than walkable height. Clamping to " + graph.walkableHeight + ".", MessageType.Warning); } else if (graph.walkableClimb < 0) { graph.walkableClimb = 0; } graph.characterRadius = EditorGUILayout.FloatField(new GUIContent("Character Radius", "Radius of the character. It's good to add some margin.\nIn world units."), graph.characterRadius); graph.characterRadius = Mathf.Max(graph.characterRadius, 0); if (graph.characterRadius < graph.cellSize * 2) { EditorGUILayout.HelpBox("For best navmesh quality, it is recommended to keep the character radius at least 2 times as large as the cell size. Smaller cell sizes will give you higher quality navmeshes, but it will take more time to scan the graph.", MessageType.Warning); } graph.maxSlope = EditorGUILayout.Slider(new GUIContent("Max Slope", "Approximate maximum slope"), graph.maxSlope, 0F, 90F); graph.maxEdgeLength = EditorGUILayout.FloatField(new GUIContent("Max Border Edge Length", "Maximum length of one border edge in the completed navmesh before it is split. A lower value can often yield better quality graphs, but don't use so low values so that you get a lot of thin triangles."), graph.maxEdgeLength); graph.maxEdgeLength = graph.maxEdgeLength < graph.cellSize ? graph.cellSize : graph.maxEdgeLength; graph.contourMaxError = EditorGUILayout.FloatField(new GUIContent("Max Edge Error", "Amount of simplification to apply to edges.\nIn world units."), graph.contourMaxError); graph.rasterizeTerrain = EditorGUILayout.Toggle(new GUIContent("Rasterize Terrain", "Should a rasterized terrain be included"), graph.rasterizeTerrain); if (graph.rasterizeTerrain) { EditorGUI.indentLevel++; graph.rasterizeTrees = EditorGUILayout.Toggle(new GUIContent("Rasterize Trees", "Rasterize tree colliders on terrains. " + "If the tree prefab has a collider, that collider will be rasterized. " + "Otherwise a simple box collider will be used and the script will " + "try to adjust it to the tree's scale, it might not do a very good job though so " + "an attached collider is preferable."), graph.rasterizeTrees); if (graph.rasterizeTrees) { EditorGUI.indentLevel++; graph.colliderRasterizeDetail = EditorGUILayout.FloatField(new GUIContent("Collider Detail", "Controls the detail of the generated collider meshes. " + "Increasing does not necessarily yield better navmeshes, but lowering will speed up scan.\n" + "Spheres and capsule colliders will be converted to meshes in order to be able to rasterize them, a higher value will increase the number of triangles in those meshes."), graph.colliderRasterizeDetail); EditorGUI.indentLevel--; } graph.terrainSampleSize = EditorGUILayout.IntField(new GUIContent("Terrain Sample Size", "Size of terrain samples. A lower value is better, but slower"), graph.terrainSampleSize); graph.terrainSampleSize = graph.terrainSampleSize < 1 ? 1 : graph.terrainSampleSize; //Clamp to at least 1 EditorGUI.indentLevel--; } graph.rasterizeMeshes = EditorGUILayout.Toggle(new GUIContent("Rasterize Meshes", "Should meshes be rasterized and used for building the navmesh"), graph.rasterizeMeshes); graph.rasterizeColliders = EditorGUILayout.Toggle(new GUIContent("Rasterize Colliders", "Should colliders be rasterized and used for building the navmesh"), graph.rasterizeColliders); if (graph.rasterizeColliders) { EditorGUI.indentLevel++; graph.colliderRasterizeDetail = EditorGUILayout.FloatField(new GUIContent("Collider Detail", "Controls the detail of the generated collider meshes. " + "Increasing does not necessarily yield better navmeshes, but lowering will speed up scan.\n" + "Spheres and capsule colliders will be converted to meshes in order to be able to rasterize them, a higher value will increase the number of triangles in those meshes."), graph.colliderRasterizeDetail); EditorGUI.indentLevel--; } if (graph.rasterizeMeshes && graph.rasterizeColliders) { EditorGUILayout.HelpBox("You are rasterizing both meshes and colliders, this might just be duplicating the work that is done if the colliders and meshes are similar in shape. You can use the RecastMeshObj component" + " to always include some specific objects regardless of what the above settings are set to.", MessageType.Info); } Separator(); graph.forcedBoundsCenter = EditorGUILayout.Vector3Field("Center", graph.forcedBoundsCenter); graph.forcedBoundsSize = EditorGUILayout.Vector3Field("Size", graph.forcedBoundsSize); // Make sure the bounding box is not infinitely thin along any axis graph.forcedBoundsSize = Vector3.Max(graph.forcedBoundsSize, Vector3.one * 0.001f); graph.rotation = EditorGUILayout.Vector3Field("Rotation", graph.rotation); if (GUILayout.Button(new GUIContent("Snap bounds to scene", "Will snap the bounds of the graph to exactly contain all meshes that the bounds currently touches"))) { graph.SnapForceBoundsToScene(); GUI.changed = true; } Separator(); EditorGUILayout.HelpBox("Objects contained in any of these masks will be rasterized", MessageType.None); graph.mask = EditorGUILayoutx.LayerMaskField("Layer Mask", graph.mask); tagMaskFoldout = EditorGUILayoutx.UnityTagMaskList(new GUIContent("Tag Mask"), tagMaskFoldout, graph.tagMask); Separator(); GUILayout.BeginHorizontal(); GUILayout.Space(18); graph.showMeshSurface = GUILayout.Toggle(graph.showMeshSurface, new GUIContent("Show surface", "Toggles gizmos for drawing the surface of the mesh"), EditorStyles.miniButtonLeft); graph.showMeshOutline = GUILayout.Toggle(graph.showMeshOutline, new GUIContent("Show outline", "Toggles gizmos for drawing an outline of the nodes"), EditorStyles.miniButtonMid); graph.showNodeConnections = GUILayout.Toggle(graph.showNodeConnections, new GUIContent("Show connections", "Toggles gizmos for drawing node connections"), EditorStyles.miniButtonRight); GUILayout.EndHorizontal(); Separator(); GUILayout.Label(new GUIContent("Advanced"), EditorStyles.boldLabel); if (GUILayout.Button("Export to .obj file")) { ExportToFile(graph); } graph.relevantGraphSurfaceMode = (RecastGraph.RelevantGraphSurfaceMode)EditorGUILayout.EnumPopup(new GUIContent("Relevant Graph Surface Mode", "Require every region to have a RelevantGraphSurface component inside it.\n" + "A RelevantGraphSurface component placed in the scene specifies that\n" + "the navmesh region it is inside should be included in the navmesh.\n\n" + "If this is set to OnlyForCompletelyInsideTile\n" + "a navmesh region is included in the navmesh if it\n" + "has a RelevantGraphSurface inside it, or if it\n" + "is adjacent to a tile border. This can leave some small regions\n" + "which you didn't want to have included because they are adjacent\n" + "to tile borders, but it removes the need to place a component\n" + "in every single tile, which can be tedious (see below).\n\n" + "If this is set to RequireForAll\n" + "a navmesh region is included only if it has a RelevantGraphSurface\n" + "inside it. Note that even though the navmesh\n" + "looks continous between tiles, the tiles are computed individually\n" + "and therefore you need a RelevantGraphSurface component for each\n" + "region and for each tile."), graph.relevantGraphSurfaceMode); graph.nearestSearchOnlyXZ = EditorGUILayout.Toggle(new GUIContent("Nearest node queries in XZ space", "Recomended for single-layered environments.\nFaster but can be inacurate esp. in multilayered contexts."), graph.nearestSearchOnlyXZ); if (graph.nearestSearchOnlyXZ && (Mathf.Abs(graph.rotation.x) > 1 || Mathf.Abs(graph.rotation.z) > 1)) { EditorGUILayout.HelpBox("Nearest node queries in XZ space is not recommended for rotated graphs since XZ space no longer corresponds to the ground plane", MessageType.Warning); } }
/** Called during either Update or FixedUpdate depending on if rigidbodies are used for movement or not */ protected override void MovementUpdateInternal(float deltaTime, out Vector3 nextPosition, out Quaternion nextRotation) { float currentAcceleration = maxAcceleration; // If negative, calculate the acceleration from the max speed if (currentAcceleration < 0) { currentAcceleration *= -maxSpeed; } if (updatePosition) { // Get our current position. We read from transform.position as few times as possible as it is relatively slow // (at least compared to a local variable) simulatedPosition = tr.position; } if (updateRotation) { simulatedRotation = tr.rotation; } var currentPosition = simulatedPosition; // Update which point we are moving towards interpolator.MoveToCircleIntersection2D(currentPosition, pickNextWaypointDist, movementPlane); var dir = movementPlane.ToPlane(steeringTarget - currentPosition); // Calculate the distance to the end of the path float distanceToEnd = dir.magnitude + Mathf.Max(0, interpolator.remainingDistance); // Check if we have reached the target var prevTargetReached = reachedEndOfPath; reachedEndOfPath = distanceToEnd <= endReachedDistance && interpolator.valid; if (!prevTargetReached && reachedEndOfPath) { OnTargetReached(); } float slowdown; // Normalized direction of where the agent is looking var forwards = movementPlane.ToPlane(simulatedRotation * (rotationIn2D ? Vector3.up : Vector3.forward)); // Check if we have a valid path to follow and some other script has not stopped the character if (interpolator.valid && !isStopped) { // How fast to move depending on the distance to the destination. // Move slower as the character gets closer to the destination. // This is always a value between 0 and 1. slowdown = distanceToEnd < slowdownDistance?Mathf.Sqrt(distanceToEnd / slowdownDistance) : 1; if (reachedEndOfPath && whenCloseToDestination == CloseToDestinationMode.Stop) { // Slow down as quickly as possible velocity2D -= Vector2.ClampMagnitude(velocity2D, currentAcceleration * deltaTime); } else { velocity2D += MovementUtilities.CalculateAccelerationToReachPoint(dir.ToUnityV2(), dir.normalized * maxSpeed, velocity2D, currentAcceleration, rotationSpeed, maxSpeed, forwards) * deltaTime; } } else { slowdown = 1; // Slow down as quickly as possible velocity2D -= Vector2.ClampMagnitude(velocity2D, currentAcceleration * deltaTime); } velocity2D = MovementUtilities.ClampVelocity(velocity2D, maxSpeed, slowdown, slowWhenNotFacingTarget, forwards.ToUnityV2()); ApplyGravity(deltaTime); if (rvoController != null && rvoController.enabled) { // Send a message to the RVOController that we want to move // with this velocity. In the next simulation step, this // velocity will be processed and it will be fed back to the // rvo controller and finally it will be used by this script // when calling the CalculateMovementDelta method below // Make sure that we don't move further than to the end point // of the path. If the RVO simulation FPS is low and we did // not do this, the agent might overshoot the target a lot. var rvoTarget = currentPosition.ToPFV3() + movementPlane.ToWorld(Vector2.ClampMagnitude(velocity2D, distanceToEnd), 0f); rvoController.SetTarget(rvoTarget, velocity2D.magnitude, maxSpeed); } // Set how much the agent wants to move during this frame var delta2D = lastDeltaPosition = CalculateDeltaToMoveThisFrame(movementPlane.ToPlane(currentPosition), distanceToEnd, deltaTime); nextPosition = currentPosition + movementPlane.ToWorld(delta2D.ToPFV2(), verticalVelocity * lastDeltaTime).ToUnityV3(); CalculateNextRotation(slowdown, out nextRotation); }
void GenerateTerrainChunks(Terrain terrain, Bounds bounds, float desiredChunkSize, List <RasterizationMesh> result) { var terrainData = terrain.terrainData; if (terrainData == null) { throw new System.ArgumentException("Terrain contains no terrain data"); } Vector3 offset = terrain.GetPosition(); Vector3 center = offset + terrainData.size * 0.5F; // Figure out the bounds of the terrain in world space var terrainBounds = new Bounds(center, terrainData.size); // Only include terrains which intersects the graph if (!terrainBounds.Intersects(bounds)) { return; } // Original heightmap size int heightmapWidth = terrainData.heightmapWidth; int heightmapDepth = terrainData.heightmapHeight; // Sample the terrain heightmap float[, ] heights = terrainData.GetHeights(0, 0, heightmapWidth, heightmapDepth); Vector3 sampleSize = terrainData.heightmapScale; sampleSize.y = terrainData.size.y; // Make chunks at least 12 quads wide // since too small chunks just decreases performance due // to the overhead of checking for bounds and similar things const int MinChunkSize = 12; // Find the number of samples along each edge that corresponds to a world size of desiredChunkSize // Then round up to the nearest multiple of terrainSampleSize var chunkSizeAlongX = Mathf.CeilToInt(Mathf.Max(desiredChunkSize / (sampleSize.x * terrainSampleSize), MinChunkSize)) * terrainSampleSize; var chunkSizeAlongZ = Mathf.CeilToInt(Mathf.Max(desiredChunkSize / (sampleSize.z * terrainSampleSize), MinChunkSize)) * terrainSampleSize; for (int z = 0; z < heightmapDepth; z += chunkSizeAlongZ) { for (int x = 0; x < heightmapWidth; x += chunkSizeAlongX) { var width = Mathf.Min(chunkSizeAlongX, heightmapWidth - x); var depth = Mathf.Min(chunkSizeAlongZ, heightmapDepth - z); var chunkMin = offset + new Vector3(z * sampleSize.x, 0, x * sampleSize.z); var chunkMax = offset + new Vector3((z + depth) * sampleSize.x, sampleSize.y, (x + width) * sampleSize.z); var chunkBounds = new Bounds(); chunkBounds.SetMinMax(chunkMin, chunkMax); // Skip chunks that are not inside the desired bounds if (chunkBounds.Intersects(bounds)) { var chunk = GenerateHeightmapChunk(heights, sampleSize, offset, x, z, width, depth, terrainSampleSize); result.Add(chunk); } } } }
void Start() { moduleId = moduleIdCounter++; var serialNum = BombInfo.GetSerialNumber(); rnd = RuleSeedable.GetRNG(); Debug.LogFormat(@"[Colorful Madness #{0}] Using rule seed: {1}", moduleId, rnd.Seed); var grabSerial = new[] { 0, 2, 4 }; var firstColor = 0; var secondColor = 1; var modSteps = new[] { 4, 0, 5, 6, 7 }; var firstPattern = 4; var secondPattern = 3; var firstUnique = 1; var secondUnique = 1; if (rnd.Seed != 1) { firstColor = rnd.Next(5); secondColor = rnd.Next(firstColor + 1, 6); grabSerial = new[] { ChooseUnique(6), ChooseUnique(6), ChooseUnique(6) }; pickedValues.Clear(); modSteps = new[] { ChooseUnique(10), ChooseUnique(10), ChooseUnique(10), ChooseUnique(10), ChooseUnique(10) }; pickedValues.Clear(); firstPattern = ChooseUnique(7); secondPattern = ChooseUnique(7); pickedValues.Clear(); firstUnique = rnd.Next(1, 5); secondUnique = rnd.Next(1, 5); } var subCols = new[] { 0, 0, 1, 3, 6, 10 }; var initCol = (7 * ((5 * firstColor) - subCols[firstColor])) + (7 * (secondColor - (firstColor + 1))); ColScreen.SetActive(ColorblindMode.ColorblindModeActive); for (int i = 0; i < 3; i++) { digits[i] = serialNum[grabSerial[i]]; if (digits[i] >= 'A') { digits[i] -= 'A'; } else { digits[i] -= '0'; } } Debug.LogFormat(@"[Colorful Madness #{0}] The 3 characters of the serial number are: {1}", moduleId, Enumerable.Range(0, 3).Select(x => serialNum[grabSerial[x]]).Join(", ")); Debug.LogFormat(@"[Colorful Madness #{0}] The 3 main digits are: {1}", moduleId, digits.Join(", ")); var hasButtonCC = 0; var hasButtonA = 0; var hasButtonB = 0; for (int i = 0; i < 10; i++) { do { topHalfTextures[i] = Random.Range(0, 105); } while (bottomHalfTextures.Contains(topHalfTextures[i])); if (topHalfTextures[i] >= initCol && topHalfTextures[i] <= initCol + 6) { hasButtonCC++; } if ((topHalfTextures[i] % 7) == firstPattern) { hasButtonA++; } if ((topHalfTextures[i] % 7) == secondPattern) { hasButtonB++; } ModuleButtons[i].transform.GetChild(1).GetComponent <Renderer>().material.SetTexture("_MainTex", ColTexturesA[topHalfTextures[i]]); bottomHalfTextures[i] = topHalfTextures[i]; } for (int v = 0; v < bottomHalfTextures.Length; v++) { var tmp = bottomHalfTextures[v]; var r = Random.Range(v, bottomHalfTextures.Length); bottomHalfTextures[v] = bottomHalfTextures[r]; bottomHalfTextures[r] = tmp; } for (int i = 10; i < 20; i++) { ModuleButtons[i].transform.GetChild(1).GetComponent <Renderer>().material.SetTexture("_MainTex", ColTexturesB[bottomHalfTextures[i - 10]]); } var tempList = new List <int>(); tempList.AddRange(topHalfTextures); tempList.AddRange(bottomHalfTextures); moduleTextures = tempList.ToArray(); getCol = ((x, y, z) => ((7 * ((5 * x) - subCols[x])) + (7 * (y - (x + 1))) == (z - (z % 7)))); var serialDigits = BombInfo.GetSerialNumberNumbers().ToArray(); var stepList = new[] { hasButtonCC * 2, digits.Sum(), serialDigits.Sum(), int.Parse(serialNum[5].ToString()), BombInfo.GetBatteryCount(), BombInfo.GetPortCount(), BombInfo.GetBatteryHolderCount(), BombInfo.GetPortPlateCount(), Math.Min(serialDigits), Math.Max(serialDigits) }; var modValue = 0; if (hasButtonCC > 0) { modValue = stepList[modSteps[0]]; for (int i = 0; i < 3; i++) { digits[i] += modValue; } Debug.LogFormat(@"[Colorful Madness #{0}] There are {1} {2} and {3} button. Adding {4}: {5}", moduleId, hasButtonCC * 2, moduleColors[firstColor], moduleColors[secondColor], modValue, digits.Join(", ")); } stepList[0] = hasButtonA * 2; stepList[1] = digits.Sum(); if (hasButtonA > 0) { modValue = Mathf.Abs(stepList[modSteps[1]] - stepList[modSteps[2]]); for (int i = 0; i < 3; i++) { digits[i] = Mathf.Abs(digits[i] - modValue); } Debug.LogFormat(@"[Colorful Madness #{0}] There are {1} {2} buttons. Subtracting {3}: {4}", moduleId, hasButtonA * 2, moduleButtonNames[firstPattern], modValue, digits.Join(", ")); } stepList[0] = hasButtonB * 2; stepList[1] = digits.Sum(); if (hasButtonB > 0) { modValue = stepList[modSteps[3]] + stepList[modSteps[4]]; for (int i = 0; i < 3; i++) { digits[i] *= modValue; } Debug.LogFormat(@"[Colorful Madness #{0}] There are {1} {2} buttons. Multiplying by {3}: {4}", moduleId, hasButtonB * 2, moduleButtonNames[secondPattern], modValue, digits.Join(", ")); } for (int i = 0; i < 3; i++) { digits[i] = digits[i] % 10; } Debug.LogFormat(@"[Colorful Madness #{0}] Modulo 10: {1}", moduleId, digits.Join(", ")); while (digits[0] == digits[1] || digits[0] == digits[2]) { digits[0] = (digits[0] + firstUnique) % 10; } while (digits[1] == digits[0] || digits[1] == digits[2]) { digits[1] = (digits[1] + (10 - secondUnique)) % 10; } Debug.LogFormat(@"[Colorful Madness #{0}] Make unique: {1}", moduleId, digits.Join(", ")); Debug.LogFormat(@"[Colorful Madness #{0}] Added one: {1}", moduleId, digits.Select(x => x + 1).Join(", ")); var counterparts = digits.Select(digit => Array.IndexOf(bottomHalfTextures, topHalfTextures[digit]) + 10).ToArray(); Debug.LogFormat(@"[Colorful Madness #{0}] The correct counterpart buttons on the bottom half are: {1}", moduleId, counterparts.Select(x => x + 1).Join(", ")); for (int i = 0; i < ModuleButtons.Length; i++) { int j = i; ModuleButtons[i].OnInteract += delegate() { OnButtonPress(j); return(false); }; ModuleButtons[i].OnHighlight += delegate() { OnButtonHighlight(j); }; ModuleButtons[i].OnDeselect += delegate() { ColScreen.transform.GetChild(0).GetComponent <TextMesh>().text = ""; }; } }
/** Draws some gizmos */ void OnDrawGizmos(bool selected) { Color c = selected ? new Color(227 / 255f, 61 / 255f, 22 / 255f, 1.0f) : new Color(227 / 255f, 61 / 255f, 22 / 255f, 0.9f); if (selected) { Gizmos.color = Color.Lerp(c, new Color(1, 1, 1, 0.2f), 0.9f); Bounds b = GetBounds(); Gizmos.DrawCube(b.center, b.size); Gizmos.DrawWireCube(b.center, b.size); } if (points == null) { return; } if (convex) { c.a *= 0.5f; } Gizmos.color = c; Matrix4x4 matrix = legacyMode && legacyUseWorldSpace ? Matrix4x4.identity : transform.localToWorldMatrix; if (convex) { c.r -= 0.1f; c.g -= 0.2f; c.b -= 0.1f; Gizmos.color = c; } if (selected || !convex) { for (int i = 0; i < points.Length; i++) { Gizmos.DrawLine(matrix.MultiplyPoint3x4(points[i]), matrix.MultiplyPoint3x4(points[(i + 1) % points.Length])); } } if (convex) { if (convexPoints == null) { RecalcConvex(); } Gizmos.color = selected ? new Color(227 / 255f, 61 / 255f, 22 / 255f, 1.0f) : new Color(227 / 255f, 61 / 255f, 22 / 255f, 0.9f); for (int i = 0; i < convexPoints.Length; i++) { Gizmos.DrawLine(matrix.MultiplyPoint3x4(convexPoints[i]), matrix.MultiplyPoint3x4(convexPoints[(i + 1) % convexPoints.Length])); } } // Draw the full 3D shape var pts = convex ? convexPoints : points; if (selected && pts != null && pts.Length > 0) { Gizmos.color = new Color(1, 1, 1, 0.2f); float miny = pts[0].y, maxy = pts[0].y; for (int i = 0; i < pts.Length; i++) { miny = Mathf.Min(miny, pts[i].y); maxy = Mathf.Max(maxy, pts[i].y); } var extraHeight = Mathf.Max(minBoundsHeight - (maxy - miny), 0) * 0.5f; miny -= extraHeight; maxy += extraHeight; for (int i = 0; i < pts.Length; i++) { var next = (i + 1) % pts.Length; var p1 = matrix.MultiplyPoint3x4(pts[i] + PF.Vector3.up * (miny - pts[i].y)); var p2 = matrix.MultiplyPoint3x4(pts[i] + PF.Vector3.up * (maxy - pts[i].y)); var p1n = matrix.MultiplyPoint3x4(pts[next] + PF.Vector3.up * (miny - pts[next].y)); var p2n = matrix.MultiplyPoint3x4(pts[next] + PF.Vector3.up * (maxy - pts[next].y)); Gizmos.DrawLine(p1, p2); Gizmos.DrawLine(p1, p1n); Gizmos.DrawLine(p2, p2n); } } }
/** * Tweet cart is the whole script, fit in one tweet, i.e. code length must be <= 280 characters * * Tigra tweet cart shortcuts: * * Functions: * TIC() is called 60 times per second. * * Variables: * t: elapsed time in seconds * f: frame counter * * Aliases: * B: bool * F: float * I: int * M: UnityEngine.Mathf * R: UnityEngine.Random * S: System.String * V2: UnityEngine.Vector2 * V3: UnityEngine.Vector3 * Z: Tic80 * * Delegates: * CD: circ & circb delegate * RD: rect & rectb delegate * TD: tri & trib delegate * * Beautify/minify C# online tool: * https://codebeautify.org/csharpviewer */ class Tc3 : Z { void TIC() { cls(2); var m = mouse(); var x = m[0]; var y = m[1]; var p = m[2]; circ(60, 50, 40, 15); circ(180, 50, 40, 15); circ(M.Min(80, M.Max(40, x)), M.Min(70, M.Max(30, y)), 15, 0); circ(M.Min(200, M.Max(160, x)), M.Min(70, M.Max(30, y)), 15, 0); }
/** Creates a VO for avoiding another agent. * Note that the segment is directed, the agent will want to be on the left side of the segment. */ public static VO SegmentObstacle(Vector2 segmentStart, Vector2 segmentEnd, Vector2 offset, float radius, float inverseDt, float inverseDeltaTime) { var vo = new VO(); // Adjusted so that a parameter weightFactor of 1 will be the default ("natural") weight factor vo.weightFactor = 1; // Just higher than anything else vo.weightBonus = Mathf.Max(radius, 1) * 40; var closestOnSegment = VectorMath.ClosestPointOnSegment(segmentStart.ToPFV2(), segmentEnd.ToPFV2(), Vector2.zero.ToPFV2()); // Collision? if (closestOnSegment.magnitude <= radius) { vo.colliding = true; vo.line1 = closestOnSegment.normalized.ToUnityV3() * (closestOnSegment.magnitude - radius) * 0.3f * inverseDeltaTime; vo.dir1 = new Vector2(vo.line1.y, -vo.line1.x).normalized; vo.line1 += offset; vo.cutoffDir = Vector2.zero; vo.cutoffLine = Vector2.zero; vo.dir2 = Vector2.zero; vo.line2 = Vector2.zero; vo.radius = 0; vo.segmentStart = Vector2.zero; vo.segmentEnd = Vector2.zero; vo.segment = false; } else { vo.colliding = false; segmentStart *= inverseDt; segmentEnd *= inverseDt; radius *= inverseDt; var cutoffTangent = (segmentEnd - segmentStart).normalized; vo.cutoffDir = cutoffTangent; vo.cutoffLine = segmentStart + new Vector2(-cutoffTangent.y, cutoffTangent.x) * radius; vo.cutoffLine += offset; // See documentation for details // The call to Max is just to prevent floating point errors causing NaNs to appear var startSqrMagnitude = segmentStart.sqrMagnitude; var normal1 = -VectorMath.ComplexMultiply(segmentStart, new Vector2(radius, Mathf.Sqrt(Mathf.Max(0, startSqrMagnitude - radius * radius)))) / startSqrMagnitude; var endSqrMagnitude = segmentEnd.sqrMagnitude; var normal2 = -VectorMath.ComplexMultiply(segmentEnd, new Vector2(radius, -Mathf.Sqrt(Mathf.Max(0, endSqrMagnitude - radius * radius)))) / endSqrMagnitude; vo.line1 = segmentStart + normal1.ToUnityV2() * radius + offset; vo.line2 = segmentEnd + normal2.ToUnityV2() * radius + offset; // Note that the normals are already normalized vo.dir1 = new Vector2(normal1.y, -normal1.x); vo.dir2 = new Vector2(normal2.y, -normal2.x); vo.segmentStart = segmentStart; vo.segmentEnd = segmentEnd; vo.radius = radius; vo.segment = true; } return(vo); }
public static Vector4 Max(Vector4 lhs, Vector4 rhs) { return(new Vector4(Mathf.Max(lhs.x, rhs.x), Mathf.Max(lhs.y, rhs.y), Mathf.Max(lhs.z, rhs.z), Mathf.Max(lhs.w, rhs.w))); }
RasterizationMesh RasterizeCapsuleCollider(float radius, float height, Bounds bounds, Matrix4x4 localToWorldMatrix) { // Calculate the number of rows to use // grows as sqrt(x) to the radius of the sphere/capsule which I have found works quite well int rows = Mathf.Max(4, Mathf.RoundToInt(colliderRasterizeDetail * Mathf.Sqrt(localToWorldMatrix.MultiplyVector(Vector3.one).magnitude))); if (rows > 100) { Debug.LogWarning("Very large detail for some collider meshes. Consider decreasing Collider Rasterize Detail (RecastGraph)"); } int cols = rows; Vector3[] verts; int[] trisArr; // Check if we have already calculated a similar capsule CapsuleCache cached = null; for (int i = 0; i < capsuleCache.Count; i++) { CapsuleCache c = capsuleCache[i]; if (c.rows == rows && Mathf.Approximately(c.height, height)) { cached = c; } } if (cached == null) { // Generate a sphere/capsule mesh verts = new Vector3[(rows) * cols + 2]; var tris = new List <int>(); verts[verts.Length - 1] = Vector3.up; for (int r = 0; r < rows; r++) { for (int c = 0; c < cols; c++) { verts[c + r * cols] = new Vector3(Mathf.Cos(c * Mathf.PI * 2 / cols) * Mathf.Sin((r * Mathf.PI / (rows - 1))), Mathf.Cos((r * Mathf.PI / (rows - 1))) + (r < rows / 2 ? height : -height), Mathf.Sin(c * Mathf.PI * 2 / cols) * Mathf.Sin((r * Mathf.PI / (rows - 1)))); } } verts[verts.Length - 2] = Vector3.down; for (int i = 0, j = cols - 1; i < cols; j = i++) { tris.Add(verts.Length - 1); tris.Add(0 * cols + j); tris.Add(0 * cols + i); } for (int r = 1; r < rows; r++) { for (int i = 0, j = cols - 1; i < cols; j = i++) { tris.Add(r * cols + i); tris.Add(r * cols + j); tris.Add((r - 1) * cols + i); tris.Add((r - 1) * cols + j); tris.Add((r - 1) * cols + i); tris.Add(r * cols + j); } } for (int i = 0, j = cols - 1; i < cols; j = i++) { tris.Add(verts.Length - 2); tris.Add((rows - 1) * cols + j); tris.Add((rows - 1) * cols + i); } // Add calculated mesh to the cache cached = new CapsuleCache(); cached.rows = rows; cached.height = height; cached.verts = verts; cached.tris = tris.ToArray(); capsuleCache.Add(cached); } // Read from cache verts = cached.verts; trisArr = cached.tris; return(new RasterizationMesh(verts, trisArr, bounds, localToWorldMatrix)); }
public void LateUpdate() { if (!show || (!Application.isPlaying && !showInEditor)) { return; } if (Time.unscaledDeltaTime <= 0.0001f) { return; } int collCount = System.GC.CollectionCount(0); if (lastCollectNum != collCount) { lastCollectNum = collCount; delta = Time.realtimeSinceStartup - lastCollect; lastCollect = Time.realtimeSinceStartup; lastDeltaTime = Time.unscaledDeltaTime; collectAlloc = allocMem; } allocMem = (int)System.GC.GetTotalMemory(false); bool collectEvent = allocMem < peakAlloc; peakAlloc = !collectEvent ? allocMem : peakAlloc; if (Time.realtimeSinceStartup - lastAllocSet > 0.3F || !Application.isPlaying) { int diff = allocMem - lastAllocMemory; lastAllocMemory = allocMem; lastAllocSet = Time.realtimeSinceStartup; delayedDeltaTime = Time.unscaledDeltaTime; if (diff >= 0) { allocRate = diff; } } if (Application.isPlaying) { fpsDrops[Time.frameCount % fpsDrops.Length] = Time.unscaledDeltaTime > 0.00001f ? 1F / Time.unscaledDeltaTime : 0; int graphIndex = Time.frameCount % graph.Length; graph[graphIndex].fps = Time.unscaledDeltaTime < 0.00001f ? 1F / Time.unscaledDeltaTime : 0; graph[graphIndex].collectEvent = collectEvent; graph[graphIndex].memory = allocMem; } if (Application.isPlaying && cam != null && showGraph) { graphWidth = cam.pixelWidth * 0.8f; float minMem = float.PositiveInfinity, maxMem = 0, minFPS = float.PositiveInfinity, maxFPS = 0; for (int i = 0; i < graph.Length; i++) { minMem = Mathf.Min(graph[i].memory, minMem); maxMem = Mathf.Max(graph[i].memory, maxMem); minFPS = Mathf.Min(graph[i].fps, minFPS); maxFPS = Mathf.Max(graph[i].fps, maxFPS); } int currentGraphIndex = Time.frameCount % graph.Length; Matrix4x4 m = Matrix4x4.TRS(new Vector3((cam.pixelWidth - graphWidth) / 2f, graphOffset, 1), Quaternion.identity, new Vector3(graphWidth, graphHeight, 1)); for (int i = 0; i < graph.Length - 1; i++) { if (i == currentGraphIndex) { continue; } DrawGraphLine(i, m, i / (float)graph.Length, (i + 1) / (float)graph.Length, Mathf.InverseLerp(minMem, maxMem, graph[i].memory), Mathf.InverseLerp(minMem, maxMem, graph[i + 1].memory), Color.blue); DrawGraphLine(i, m, i / (float)graph.Length, (i + 1) / (float)graph.Length, Mathf.InverseLerp(minFPS, maxFPS, graph[i].fps), Mathf.InverseLerp(minFPS, maxFPS, graph[i + 1].fps), Color.green); } } }
public void BuildContours(float maxError, int maxEdgeLength, VoxelContourSet cset, int buildFlags) { AstarProfiler.StartProfile("Build Contours"); AstarProfiler.StartProfile("- Init"); int w = voxelArea.width; int d = voxelArea.depth; int wd = w * d; //cset.bounds = voxelArea.bounds; int maxContours = Mathf.Max(8 /*Max Regions*/, 8); //cset.conts = new VoxelContour[maxContours]; List <VoxelContour> contours = new List <VoxelContour>(maxContours); AstarProfiler.EndProfile("- Init"); AstarProfiler.StartProfile("- Mark Boundaries"); //cset.nconts = 0; //NOTE: This array may contain any data, but since we explicitly set all data in it before we use it, it's OK. ushort[] flags = voxelArea.tmpUShortArr; if (flags.Length < voxelArea.compactSpanCount) { flags = voxelArea.tmpUShortArr = new ushort[voxelArea.compactSpanCount]; } // Mark boundaries. (@?) for (int z = 0; z < wd; z += voxelArea.width) { for (int x = 0; x < voxelArea.width; x++) { CompactVoxelCell c = voxelArea.compactCells[x + z]; for (int i = (int)c.index, ci = (int)(c.index + c.count); i < ci; i++) { ushort res = 0; CompactVoxelSpan s = voxelArea.compactSpans[i]; if (s.reg == 0 || (s.reg & BorderReg) == BorderReg) { flags[i] = 0; continue; } for (int dir = 0; dir < 4; dir++) { int r = 0; if (s.GetConnection(dir) != NotConnected) { int nx = x + voxelArea.DirectionX[dir]; int nz = z + voxelArea.DirectionZ[dir]; int ni = (int)voxelArea.compactCells[nx + nz].index + s.GetConnection(dir); r = voxelArea.compactSpans[ni].reg; } //@TODO - Why isn't this inside the previous IF if (r == s.reg) { res |= (ushort)(1 << dir); } } //Inverse, mark non connected edges. flags[i] = (ushort)(res ^ 0xf); } } } AstarProfiler.EndProfile("- Mark Boundaries"); AstarProfiler.StartProfile("- Simplify Contours"); List <int> verts = ListPool <int> .Claim(256); //new List<int> (256); List <int> simplified = ListPool <int> .Claim(64); //new List<int> (64); for (int z = 0; z < wd; z += voxelArea.width) { for (int x = 0; x < voxelArea.width; x++) { CompactVoxelCell c = voxelArea.compactCells[x + z]; for (int i = (int)c.index, ci = (int)(c.index + c.count); i < ci; i++) { //CompactVoxelSpan s = voxelArea.compactSpans[i]; if (flags[i] == 0 || flags[i] == 0xf) { flags[i] = 0; continue; } int reg = voxelArea.compactSpans[i].reg; if (reg == 0 || (reg & BorderReg) == BorderReg) { continue; } int area = voxelArea.areaTypes[i]; verts.Clear(); simplified.Clear(); WalkContour(x, z, i, flags, verts); SimplifyContour(verts, simplified, maxError, maxEdgeLength, buildFlags); RemoveDegenerateSegments(simplified); VoxelContour contour = new VoxelContour(); contour.verts = PF.ArrayPool <int> .Claim(simplified.Count); //simplified.ToArray (); for (int j = 0; j < simplified.Count; j++) { contour.verts[j] = simplified[j]; } #if ASTAR_RECAST_INCLUDE_RAW_VERTEX_CONTOUR //Not used at the moment, just debug stuff contour.rverts = ClaimIntArr(verts.Count); for (int j = 0; j < verts.Count; j++) { contour.rverts[j] = verts[j]; } #endif contour.nverts = simplified.Count / 4; contour.reg = reg; contour.area = area; contours.Add(contour); #if ASTARDEBUG for (int q = 0, j = (simplified.Count / 4) - 1; q < (simplified.Count / 4); j = q, q++) { int i4 = q * 4; int j4 = j * 4; Vector3 p1 = Vector3.Scale( new Vector3( simplified[i4 + 0], simplified[i4 + 1], (simplified[i4 + 2] / (float)voxelArea.width) ), cellScale) + voxelOffset; Vector3 p2 = Vector3.Scale( new Vector3( simplified[j4 + 0], simplified[j4 + 1], (simplified[j4 + 2] / (float)voxelArea.width) ) , cellScale) + voxelOffset; if (CalcAreaOfPolygon2D(contour.verts, contour.nverts) > 0) { Debug.DrawLine(p1, p2, AstarMath.IntToColor(reg, 0.5F)); } else { Debug.DrawLine(p1, p2, Color.red); } } #endif } } } ListPool <int> .Release(ref verts); ListPool <int> .Release(ref simplified); AstarProfiler.EndProfile("- Simplify Contours"); AstarProfiler.StartProfile("- Fix Contours"); // Check and merge droppings. // Sometimes the previous algorithms can fail and create several contours // per area. This pass will try to merge the holes into the main region. for (int i = 0; i < contours.Count; i++) { VoxelContour cont = contours[i]; // Check if the contour is would backwards. if (CalcAreaOfPolygon2D(cont.verts, cont.nverts) < 0) { // Find another contour which has the same region ID. int mergeIdx = -1; for (int j = 0; j < contours.Count; j++) { if (i == j) { continue; } if (contours[j].nverts > 0 && contours[j].reg == cont.reg) { // Make sure the polygon is correctly oriented. if (CalcAreaOfPolygon2D(contours[j].verts, contours[j].nverts) > 0) { mergeIdx = j; break; } } } if (mergeIdx == -1) { Debug.LogError("rcBuildContours: Could not find merge target for bad contour " + i + "."); } else { // Debugging //Debug.LogWarning ("Fixing contour"); VoxelContour mcont = contours[mergeIdx]; // Merge by closest points. int ia = 0, ib = 0; GetClosestIndices(mcont.verts, mcont.nverts, cont.verts, cont.nverts, ref ia, ref ib); if (ia == -1 || ib == -1) { Debug.LogWarning("rcBuildContours: Failed to find merge points for " + i + " and " + mergeIdx + "."); continue; } #if ASTARDEBUG int p4 = ia * 4; int p42 = ib * 4; Vector3 p12 = Vector3.Scale( new Vector3( mcont.verts[p4 + 0], mcont.verts[p4 + 1], (mcont.verts[p4 + 2] / (float)voxelArea.width) ), cellScale) + voxelOffset; Vector3 p22 = Vector3.Scale( new Vector3( cont.verts[p42 + 0], cont.verts[p42 + 1], (cont.verts[p42 + 2] / (float)voxelArea.width) ) , cellScale) + voxelOffset; Debug.DrawLine(p12, p22, Color.green); #endif if (!MergeContours(ref mcont, ref cont, ia, ib)) { Debug.LogWarning("rcBuildContours: Failed to merge contours " + i + " and " + mergeIdx + "."); continue; } contours[mergeIdx] = mcont; contours[i] = cont; #if ASTARDEBUG Debug.Log(mcont.nverts); for (int q = 0, j = (mcont.nverts) - 1; q < (mcont.nverts); j = q, q++) { int i4 = q * 4; int j4 = j * 4; Vector3 p1 = Vector3.Scale( new Vector3( mcont.verts[i4 + 0], mcont.verts[i4 + 1], (mcont.verts[i4 + 2] / (float)voxelArea.width) ), cellScale) + voxelOffset; Vector3 p2 = Vector3.Scale( new Vector3( mcont.verts[j4 + 0], mcont.verts[j4 + 1], (mcont.verts[j4 + 2] / (float)voxelArea.width) ) , cellScale) + voxelOffset; Debug.DrawLine(p1, p2, Color.red); //} } #endif } } } cset.conts = contours; AstarProfiler.EndProfile("- Fix Contours"); AstarProfiler.EndProfile("Build Contours"); }
public void OnGUI() { if (!show || (!Application.isPlaying && !showInEditor)) { return; } if (style == null) { style = new GUIStyle(); style.normal.textColor = Color.white; style.padding = new RectOffset(5, 5, 5, 5); } if (Time.realtimeSinceStartup - lastUpdate > 0.5f || cachedText == null || !Application.isPlaying) { lastUpdate = Time.realtimeSinceStartup; boxRect = new Rect(5, yOffset, 310, 40); text.Length = 0; text.AppendLine("A* Pathfinding Project Debugger"); text.Append("A* Version: ").Append(AstarPath.Version.ToString()); if (showMemProfile) { boxRect.height += 200; text.AppendLine(); text.AppendLine(); text.Append("Currently allocated".PadRight(25)); text.Append((allocMem / 1000000F).ToString("0.0 MB")); text.AppendLine(); text.Append("Peak allocated".PadRight(25)); text.Append((peakAlloc / 1000000F).ToString("0.0 MB")).AppendLine(); text.Append("Last collect peak".PadRight(25)); text.Append((collectAlloc / 1000000F).ToString("0.0 MB")).AppendLine(); text.Append("Allocation rate".PadRight(25)); text.Append((allocRate / 1000000F).ToString("0.0 MB")).AppendLine(); text.Append("Collection frequency".PadRight(25)); text.Append(delta.ToString("0.00")); text.Append("s\n"); text.Append("Last collect fps".PadRight(25)); text.Append((1F / lastDeltaTime).ToString("0.0 fps")); text.Append(" ("); text.Append(lastDeltaTime.ToString("0.000 s")); text.Append(")"); } if (showFPS) { text.AppendLine(); text.AppendLine(); var delayedFPS = delayedDeltaTime > 0.00001f ? 1F / delayedDeltaTime : 0; text.Append("FPS".PadRight(25)).Append(delayedFPS.ToString("0.0 fps")); float minFps = Mathf.Infinity; for (int i = 0; i < fpsDrops.Length; i++) { if (fpsDrops[i] < minFps) { minFps = fpsDrops[i]; } } text.AppendLine(); text.Append(("Lowest fps (last " + fpsDrops.Length + ")").PadRight(25)).Append(minFps.ToString("0.0")); } if (showPathProfile) { AstarPath astar = AstarPath.active; text.AppendLine(); if (astar == null) { text.Append("\nNo AstarPath Object In The Scene"); } else { #if ProfileAstar double searchSpeed = (double)AstarPath.TotalSearchedNodes * 10000 / (double)AstarPath.TotalSearchTime; text.Append("\nSearch Speed (nodes/ms) ").Append(searchSpeed.ToString("0")).Append(" (" + AstarPath.TotalSearchedNodes + " / ").Append(((double)AstarPath.TotalSearchTime / 10000F).ToString("0") + ")"); #endif if (ListPool <Vector3> .GetSize() > maxVecPool) { maxVecPool = ListPool <Vector3> .GetSize(); } if (ListPool <GraphNode> .GetSize() > maxNodePool) { maxNodePool = ListPool <GraphNode> .GetSize(); } text.Append("\nPool Sizes (size/total created)"); for (int i = 0; i < debugTypes.Length; i++) { debugTypes[i].Print(text); } } } cachedText = text.ToString(); } if (font != null) { style.font = font; style.fontSize = fontSize; } boxRect.height = style.CalcHeight(new GUIContent(cachedText), boxRect.width); GUI.Box(boxRect, ""); GUI.Label(boxRect, cachedText, style); if (showGraph) { float minMem = float.PositiveInfinity, maxMem = 0, minFPS = float.PositiveInfinity, maxFPS = 0; for (int i = 0; i < graph.Length; i++) { minMem = Mathf.Min(graph[i].memory, minMem); maxMem = Mathf.Max(graph[i].memory, maxMem); minFPS = Mathf.Min(graph[i].fps, minFPS); maxFPS = Mathf.Max(graph[i].fps, maxFPS); } float line; GUI.color = Color.blue; // Round to nearest x.x MB line = Mathf.RoundToInt(maxMem / (100.0f * 1000)); GUI.Label(new Rect(5, Screen.height - AstarMath.MapTo(minMem, maxMem, 0 + graphOffset, graphHeight + graphOffset, line * 1000 * 100) - 10, 100, 20), (line / 10.0f).ToString("0.0 MB")); line = Mathf.Round(minMem / (100.0f * 1000)); GUI.Label(new Rect(5, Screen.height - AstarMath.MapTo(minMem, maxMem, 0 + graphOffset, graphHeight + graphOffset, line * 1000 * 100) - 10, 100, 20), (line / 10.0f).ToString("0.0 MB")); GUI.color = Color.green; // Round to nearest x.x MB line = Mathf.Round(maxFPS); GUI.Label(new Rect(55, Screen.height - AstarMath.MapTo(minFPS, maxFPS, 0 + graphOffset, graphHeight + graphOffset, line) - 10, 100, 20), line.ToString("0 FPS")); line = Mathf.Round(minFPS); GUI.Label(new Rect(55, Screen.height - AstarMath.MapTo(minFPS, maxFPS, 0 + graphOffset, graphHeight + graphOffset, line) - 10, 100, 20), line.ToString("0 FPS")); } }
#pragma warning restore 414 KMSelectable[] ProcessTwitchCommand(string command) { command = command.ToLowerInvariant().Trim(); if (Regex.IsMatch(command, @"^press +[0-9^, |&]+$")) { command = command.Substring(6).Trim(); var presses = command.Split(new[] { ',', ' ', '|', '&' }, StringSplitOptions.RemoveEmptyEntries); var pressList = new List <KMSelectable>(); for (int i = 0; i < presses.Length; i++) { if (Regex.IsMatch(presses[i], @"^[0-9]{1,2}$")) { pressList.Add(ModuleButtons[Math.Clamp(Math.Max(1, int.Parse(presses[i].ToString())) - 1, 0, ModuleButtons.Length - 1)]); } } return((pressList.Count > 0) ? pressList.ToArray() : null); } if (Regex.IsMatch(command, @"^show +([0-9^, |&]|all|top|bottom)+$")) { if (!ColScreen.activeSelf) { return(null); } command = command.Substring(5).Trim(); var showing = command.Split(new[] { ',', ' ', '|', '&' }, StringSplitOptions.RemoveEmptyEntries); var showList = new List <int>(); for (int i = 0; i < showing.Length; i++) { if (Regex.IsMatch(showing[i], @"^[0-9]{1,2}$")) { showList.Add(Math.Clamp(Math.Max(1, int.Parse(showing[i].ToString())) - 1, 0, ModuleButtons.Length - 1)); } else { showList.Clear(); for (int j = ((showing[i].Equals("bottom")) ? 10 : 0); j < 20 - (((showing[i].Equals("top")) ? 10 : 0)); j++) { showList.Add(j); } break; } } if (showList.Count == 0) { return(null); } StopAllCoroutines(); StartCoroutine(ShowScreen(showList)); return(new KMSelectable[0]); } if (Regex.IsMatch(command, @"^\s*(colorblind|cb)\s*$")) { ColScreen.SetActive(true); return(new KMSelectable[0]); } return(null); }