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); }
internal void CalculateVelocity(Pathfinding.RVO.Simulator.WorkerContext context) { if (manuallyControlled) { return; } if (locked) { calculatedSpeed = 0; calculatedTargetPoint = position; return; } // Buffer which will be filled up with velocity obstacles (VOs) var vos = context.vos; vos.Clear(); GenerateObstacleVOs(vos); GenerateNeighbourAgentVOs(vos); bool insideAnyVO = BiasDesiredVelocity(vos, ref desiredVelocity, ref desiredTargetPointInVelocitySpace, simulator.symmetryBreakingBias); if (!insideAnyVO) { // Desired velocity can be used directly since it was not inside any velocity obstacle. // No need to run optimizer because this will be the global minima. // This is also a special case in which we can set the // calculated target point to the desired target point // instead of calculating a point based on a calculated velocity // which is an important difference when the agent is very close // to the target point // TODO: Not actually guaranteed to be global minima if desiredTargetPointInVelocitySpace.magnitude < desiredSpeed // maybe do something different here? calculatedTargetPoint = desiredTargetPointInVelocitySpace + position; calculatedSpeed = desiredSpeed; if (DebugDraw) { Draw.Debug.CrossXZ(FromXZ(calculatedTargetPoint), Color.white); } return; } Vector2 result = Vector2.zero; result = GradientDescent(vos, currentVelocity, desiredVelocity); if (DebugDraw) { Draw.Debug.CrossXZ(FromXZ(result + position), Color.white); } //Debug.DrawRay (To3D (position), To3D (result)); calculatedTargetPoint = position + result; calculatedSpeed = Mathf.Min(result.magnitude, maxSpeed); }
public float ConvertFrameToAnimationTime(int frame) { //return Mathf.Min(frame * SecondsPerFrame, ClipLength); float timeCheck = 0; for (int i = 0; i < FrameLengths.Length; i++) { if (!RenderFrames[i]) { continue; } if (i == frame) { return(timeCheck); } timeCheck += SecondsPerFrame * FrameLengths[i]; } return(Mathf.Min(timeCheck, ClipLength)); }
protected override void Inspector() { // Find all properties var points = FindProperty("points"); var legacyMode = FindProperty("legacyMode"); // Get a list of inspected components scripts = new GraphUpdateScene[targets.Length]; targets.CopyTo(scripts, 0); EditorGUI.BeginChangeCheck(); // Make sure no point arrays are null for (int i = 0; i < scripts.Length; i++) { scripts[i].points = scripts[i].points ?? new PF.Vector3[0]; } if (!points.hasMultipleDifferentValues && points.arraySize == 0) { if (scripts[0].GetComponent <PolygonCollider2D>() != null) { EditorGUILayout.HelpBox("Using polygon collider shape", MessageType.Info); } else if (scripts[0].GetComponent <Collider>() != null || scripts[0].GetComponent <Collider2D>() != null) { EditorGUILayout.HelpBox("No points, using collider.bounds", MessageType.Info); } else if (scripts[0].GetComponent <Renderer>() != null) { EditorGUILayout.HelpBox("No points, using renderer.bounds", MessageType.Info); } else { EditorGUILayout.HelpBox("No points and no collider or renderer attached, will not affect anything\nPoints can be added using the transform tool and holding shift", MessageType.Warning); } } DrawPointsField(); EditorGUI.indentLevel = 0; DrawPhysicsField(); PropertyField("updateErosion", null, "Recalculate erosion for grid graphs.\nSee online documentation for more info"); DrawConvexField(); // Minimum bounds height is not applied when using the bounds from a collider or renderer if (points.hasMultipleDifferentValues || points.arraySize > 0) { PropertyField("minBoundsHeight"); Clamp("minBoundsHeight", 0.1f); } PropertyField("applyOnStart"); PropertyField("applyOnScan"); DrawWalkableField(); DrawPenaltyField(); DrawTagField(); EditorGUILayout.Separator(); if (legacyMode.hasMultipleDifferentValues || legacyMode.boolValue) { EditorGUILayout.HelpBox("Legacy mode is enabled because you have upgraded from an earlier version of the A* Pathfinding Project. " + "Disabling legacy mode is recommended but you may have to tweak the point locations or object rotation in some cases", MessageType.Warning); if (GUILayout.Button("Disable Legacy Mode")) { for (int i = 0; i < scripts.Length; i++) { Undo.RecordObject(scripts[i], "Disable Legacy Mode"); scripts[i].DisableLegacyMode(); } } } if (scripts.Length == 1 && scripts[0].points.Length >= 3) { var size = scripts[0].GetBounds().size; if (Mathf.Min(Mathf.Min(Mathf.Abs(size.x), Mathf.Abs(size.y)), Mathf.Abs(size.z)) < 0.05f) { EditorGUILayout.HelpBox("The bounding box is very thin. Your shape might be oriented incorrectly. The shape will be projected down on the XZ plane in local space. Rotate this object " + "so that the local XZ plane corresponds to the plane in which you want to create your shape. For example if you want to create your shape in the XY plane then " + "this object should have the rotation (-90,0,0). You will need to recreate your shape after rotating this object.", MessageType.Warning); } } if (GUILayout.Button("Clear all points")) { for (int i = 0; i < scripts.Length; i++) { Undo.RecordObject(scripts[i], "Clear points"); scripts[i].points = new PF.Vector3[0]; scripts[i].RecalcConvex(); } } if (EditorGUI.EndChangeCheck()) { for (int i = 0; i < scripts.Length; i++) { EditorUtility.SetDirty(scripts[i]); } // Repaint the scene view if necessary if (!Application.isPlaying || EditorApplication.isPaused) { SceneView.RepaintAll(); } } }
public static IEnumerable <Progress> ScanAllTiles(this RecastGraph self) { self.transform = self.CalculateTransform(); self.InitializeTileInfo(); // If this is true, just fill the graph with empty tiles if (self.scanEmptyGraph) { self.FillWithEmptyTiles(); yield break; } // A walkableClimb higher than walkableHeight 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 navmesh without links) // The editor scripts also enforce this but we enforce it here too just to be sure self.walkableClimb = Mathf.Min(self.walkableClimb, self.walkableHeight); yield return(new Progress(0, "Finding Meshes")); var bounds = self.transform.Transform(new Bounds(self.forcedBoundsSize * 0.5f, self.forcedBoundsSize)); var meshes = self.CollectMeshes(bounds); var buckets = self.PutMeshesIntoTileBuckets(meshes); Queue <Int2> tileQueue = new Queue <Int2>(); // Put all tiles in the queue for (int z = 0; z < self.tileZCount; z++) { for (int x = 0; x < self.tileXCount; x++) { tileQueue.Enqueue(new Int2(x, z)); } } var workQueue = new ParallelWorkQueue <Int2>(tileQueue); // Create the voxelizers and set all settings (one for each thread) var voxelizers = new Voxelize[workQueue.threadCount]; for (int i = 0; i < voxelizers.Length; i++) { voxelizers[i] = new Voxelize(self.CellHeight, self.cellSize, self.walkableClimb, self.walkableHeight, self.maxSlope, self.maxEdgeLength); } workQueue.action = (tile, threadIndex) => { voxelizers[threadIndex].inputMeshes = buckets[tile.x + tile.y * self.tileXCount]; self.tiles[tile.x + tile.y * self.tileXCount] = self.BuildTileMesh(voxelizers[threadIndex], tile.x, tile.y, threadIndex); }; // Prioritize responsiveness while playing // but when not playing prioritize throughput // (the Unity progress bar is also pretty slow to update) int timeoutMillis = Application.isPlaying ? 1 : 200; // Scan all tiles in parallel foreach (var done in workQueue.Run(timeoutMillis)) { yield return(new Progress(Mathf.Lerp(0.1f, 0.9f, done / (float)self.tiles.Length), "Calculated Tiles: " + done + "/" + self.tiles.Length)); } yield return(new Progress(0.9f, "Assigning Graph Indices")); // Assign graph index to nodes uint graphIndex = (uint)AstarPath.active.data.GetGraphIndex(self); self.GetNodes(node => node.GraphIndex = graphIndex); // First connect all tiles with an EVEN coordinate sum // This would be the white squares on a chess board. // Then connect all tiles with an ODD coordinate sum (which would be all black squares on a chess board). // This will prevent the different threads that do all // this in parallel from conflicting with each other. // The directions are also done separately // first they are connected along the X direction and then along the Z direction. // Looping over 0 and then 1 for (int coordinateSum = 0; coordinateSum <= 1; coordinateSum++) { for (int direction = 0; direction <= 1; direction++) { for (int i = 0; i < self.tiles.Length; i++) { if ((self.tiles[i].x + self.tiles[i].z) % 2 == coordinateSum) { tileQueue.Enqueue(new Int2(self.tiles[i].x, self.tiles[i].z)); } } workQueue = new ParallelWorkQueue <Int2>(tileQueue); workQueue.action = (tile, threadIndex) => { // Connect with tile at (x+1,z) and (x,z+1) if (direction == 0 && tile.x < self.tileXCount - 1) { self.ConnectTiles(self.tiles[tile.x + tile.y * self.tileXCount], self.tiles[tile.x + 1 + tile.y * self.tileXCount]); } if (direction == 1 && tile.y < self.tileZCount - 1) { self.ConnectTiles(self.tiles[tile.x + tile.y * self.tileXCount], self.tiles[tile.x + (tile.y + 1) * self.tileXCount]); } }; var numTilesInQueue = tileQueue.Count; // Connect all tiles in parallel foreach (var done in workQueue.Run(timeoutMillis)) { yield return(new Progress(0.95f, "Connected Tiles " + (numTilesInQueue - done) + "/" + numTilesInQueue + " (Phase " + (direction + 1 + 2 * coordinateSum) + " of 4)")); } } } for (int i = 0; i < meshes.Count; i++) { meshes[i].Pool(); } ListPool <RasterizationMesh> .Release(ref meshes); // This may be used by the TileHandlerHelper script to update the tiles // while taking NavmeshCuts into account after the graph has been completely recalculated. if (self.OnRecalculatedTiles != null) { self.OnRecalculatedTiles(self.tiles.Clone() as NavmeshTile[]); } }
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); } } } }
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")); } }
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); } } }
// Returns a vector that is made from the smallest components of two vectors. public static Vector3 Min(Vector3 lhs, Vector3 rhs) { return(new Vector3(Mathf.Min(lhs.x, rhs.x), Mathf.Min(lhs.y, rhs.y), Mathf.Min(lhs.z, rhs.z))); }
/** * 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); }
void SubmitLines(RetainedGizmos gizmos, ulong hash) { // Unity only supports 65535 vertices per mesh. 65532 used because MaxLineEndPointsPerBatch needs to be even. const int MaxLineEndPointsPerBatch = 65532 / 2; int batches = (lines.Count + MaxLineEndPointsPerBatch - 1) / MaxLineEndPointsPerBatch; for (int batch = 0; batch < batches; batch++) { int startIndex = MaxLineEndPointsPerBatch * batch; int endIndex = Mathf.Min(startIndex + MaxLineEndPointsPerBatch, lines.Count); int lineEndPointCount = endIndex - startIndex; UnityEngine.Assertions.Assert.IsTrue(lineEndPointCount % 2 == 0); // Use pooled lists to avoid excessive allocations var vertices = ListPool <Vector3> .Claim(lineEndPointCount *2); var colors = ListPool <Color32> .Claim(lineEndPointCount *2); var normals = ListPool <Vector3> .Claim(lineEndPointCount *2); var uv = ListPool <Vector2> .Claim(lineEndPointCount *2); var tris = ListPool <int> .Claim(lineEndPointCount *3); // Loop through each endpoint of the lines // and add 2 vertices for each for (int j = startIndex; j < endIndex; j++) { var vertex = (Vector3)lines[j]; vertices.Add(vertex); vertices.Add(vertex); var color = (Color32)lineColors[j]; colors.Add(color); colors.Add(color); uv.Add(new Vector2(0, 0)); uv.Add(new Vector2(1, 0)); } // Loop through each line and add // one normal for each vertex for (int j = startIndex; j < endIndex; j += 2) { var lineDir = (Vector3)(lines[j + 1] - lines[j]); // Store the line direction in the normals. // A line consists of 4 vertices. The line direction will be used to // offset the vertices to create a line with a fixed pixel thickness normals.Add(lineDir); normals.Add(lineDir); normals.Add(lineDir); normals.Add(lineDir); } // Setup triangle indices // A triangle consists of 3 indices // A line (4 vertices) consists of 2 triangles, so 6 triangle indices for (int j = 0, v = 0; j < lineEndPointCount * 3; j += 6, v += 4) { // First triangle tris.Add(v + 0); tris.Add(v + 1); tris.Add(v + 2); // Second triangle tris.Add(v + 1); tris.Add(v + 3); tris.Add(v + 2); } var mesh = gizmos.GetMesh(); // Set all data on the mesh mesh.SetVertices(vertices); mesh.SetTriangles(tris, 0); mesh.SetColors(colors); mesh.SetNormals(normals); mesh.SetUVs(0, uv); // Upload all data and mark the mesh as unreadable mesh.UploadMeshData(true); // Release the lists back to the pool ListPool <Vector3> .Release(ref vertices); ListPool <Color32> .Release(ref colors); ListPool <Vector3> .Release(ref normals); ListPool <Vector2> .Release(ref uv); ListPool <int> .Release(ref tris); gizmos.meshes.Add(new MeshWithHash { hash = hash, mesh = mesh, lines = true }); gizmos.existingHashes.Add(hash); } }
// Returns a vector that is made from the smallest components of two vectors. public static Vector4 Min(Vector4 lhs, Vector4 rhs) { return(new Vector4(Mathf.Min(lhs.x, rhs.x), Mathf.Min(lhs.y, rhs.y), Mathf.Min(lhs.z, rhs.z), Mathf.Min(lhs.w, rhs.w))); }
public static Vector2 Min(Vector2 lhs, Vector2 rhs) { return(new Vector2(Mathf.Min(lhs.x, rhs.x), Mathf.Min(lhs.y, rhs.y))); }
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); }
/** 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); } } }
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 = ""; }; } }