protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) { var map = _owner.eyeManager.CurrentMap; var worldHandle = (DrawingHandleWorld)handle; ShaderInstance?currentShader = null; var player = _playerManager.LocalPlayer?.ControlledEntity; foreach (var effect in _owner._Effects) { if (effect.AttachedEntity?.Transform.MapID != player?.Transform.MapID && _mapManager.GetGrid(effect.Coordinates.GetGridId(_entityManager)).ParentMapId != map) { continue; } var newShader = effect.Shaded ? null : _unshadedShader; if (newShader != currentShader) { worldHandle.UseShader(newShader); currentShader = newShader; } var effectSprite = effect.EffectSprite; var effectOrigin = effect.AttachedEntity?.Transform.MapPosition.Position + effect.AttachedOffset ?? effect.Coordinates.ToMapPos(_entityManager); var effectArea = Box2.CenteredAround(effectOrigin, effect.Size); var rotatedBox = new Box2Rotated(effectArea, effect.Rotation, effectOrigin); worldHandle.DrawTextureRect(effectSprite, rotatedBox, ToColor(effect.Color)); } }
public IList <IEntity> GetEntitiesUnderPosition(MapCoordinates coordinates) { // Find all the entities intersecting our click var entities = EntityManager.GetEntitiesIntersecting(coordinates.MapId, Box2.CenteredAround(coordinates.Position, (1, 1))); // Check the entities against whether or not we can click them var foundEntities = new List <(IEntity clicked, int drawDepth, uint renderOrder)>(); foreach (var entity in entities) { if (entity.TryGetComponent <ClickableComponent>(out var component) && entity.Transform.IsMapTransform && component.CheckClick(coordinates.Position, out var drawDepthClicked, out var renderOrder)) { foundEntities.Add((entity, drawDepthClicked, renderOrder)); } } if (foundEntities.Count == 0) { return(new List <IEntity>()); } foundEntities.Sort(new ClickableEntityComparer()); // 0 is the top element. foundEntities.Reverse(); return(foundEntities.Select(a => a.clicked).ToList()); }
protected bool TryGetContextEntities(IEntity player, MapCoordinates targetPos, [NotNullWhen(true)] out List <IEntity>?contextEntities, bool buffer = false) { contextEntities = null; var length = buffer ? 1.0f: 0.5f; var entities = EntityManager.GetEntitiesIntersecting(targetPos.MapId, Box2.CenteredAround(targetPos.Position, (length, length))).ToList(); if (entities.Count == 0) { return(false); } // Check if we have LOS to the clicked-location, otherwise no popup. var vectorDiff = player.Transform.MapPosition.Position - targetPos.Position; var distance = vectorDiff.Length + 0.01f; bool Ignored(IEntity entity) { return(entities.Contains(entity) || entity == player || !entity.TryGetComponent(out OccluderComponent? occluder) || !occluder.Enabled); } var result = player.InRangeUnobstructed(targetPos, distance, CollisionGroup.Opaque, Ignored); if (!result) { return(false); } contextEntities = entities; return(true); }
public override void Update(float frameTime) { AccumulatedFrameTime += frameTime; _updateCooldown = 1 / _configManager.GetCVar <float>("net.atmosdbgoverlaytickrate"); if (AccumulatedFrameTime < _updateCooldown) { return; } // This is the timer from GasTileOverlaySystem AccumulatedFrameTime -= _updateCooldown; var currentTick = _gameTiming.CurTick; // Now we'll go through each player, then through each chunk in range of that player checking if the player is still in range // If they are, check if they need the new data to send (i.e. if there's an overlay for the gas). // Afterwards we reset all the chunk data for the next time we tick. foreach (var session in PlayerObservers) { if (session.AttachedEntity == null) { continue; } var entity = session.AttachedEntity; var worldBounds = Box2.CenteredAround(entity.Transform.WorldPosition, new Vector2(LocalViewRange, LocalViewRange)); foreach (var grid in _mapManager.FindGridsIntersecting(entity.Transform.MapID, worldBounds)) { if (!_entityManager.TryGetEntity(grid.GridEntityId, out var gridEnt)) { continue; } if (!gridEnt.TryGetComponent <GridAtmosphereComponent>(out var gam)) { continue; } var entityTile = grid.GetTileRef(entity.Transform.Coordinates).GridIndices; var baseTile = new MapIndices(entityTile.X - (LocalViewRange / 2), entityTile.Y - (LocalViewRange / 2)); var debugOverlayContent = new AtmosDebugOverlayData[LocalViewRange * LocalViewRange]; var index = 0; for (var y = 0; y < LocalViewRange; y++) { for (var x = 0; x < LocalViewRange; x++) { var mapIndices = new MapIndices(baseTile.X + x, baseTile.Y + y); debugOverlayContent[index++] = ConvertTileToData(gam.GetTile(mapIndices)); } } RaiseNetworkEvent(new AtmosDebugOverlayMessage(grid.Index, baseTile, debugOverlayContent), session.ConnectedClient); } } }
protected override void Draw(DrawingHandleBase handle, OverlaySpace overlay) { var drawHandle = (DrawingHandleWorld)handle; var mapId = _eyeManager.CurrentMap; var eye = _eyeManager.CurrentEye; var worldBounds = Box2.CenteredAround(eye.Position.Position, _clyde.ScreenSize / (float)EyeManager.PixelsPerMeter * eye.Zoom); foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds)) { if (!_gasTileOverlaySystem.HasData(mapGrid.Index)) { continue; } var gridBounds = new Box2(mapGrid.WorldToLocal(worldBounds.BottomLeft), mapGrid.WorldToLocal(worldBounds.TopRight)); foreach (var tile in mapGrid.GetTilesIntersecting(gridBounds)) { foreach (var(texture, color) in _gasTileOverlaySystem.GetOverlays(mapGrid.Index, tile.GridIndices)) { drawHandle.DrawTexture(texture, mapGrid.LocalToWorld(new Vector2(tile.X, tile.Y)), color); } } } }
protected override void Draw(DrawingHandleBase handle, OverlaySpace currentSpace) { var map = _owner.eyeManager.CurrentMap; var worldHandle = (DrawingHandleWorld)handle; ShaderInstance?currentShader = null; foreach (var effect in _owner._Effects) { if (_mapManager.GetGrid(effect.Coordinates.GridID).ParentMapId != map) { continue; } var newShader = effect.Shaded ? null : _unshadedShader; if (newShader != currentShader) { worldHandle.UseShader(newShader); currentShader = newShader; } var effectSprite = effect.EffectSprite; var effectOrigin = effect.Coordinates.ToMapPos(_mapManager); var effectArea = Box2.CenteredAround(effectOrigin, effect.Size); var rotatedBox = new Box2Rotated(effectArea, effect.Rotation, effectOrigin); worldHandle.DrawTextureRect(effectSprite, rotatedBox, ToColor(effect.Color)); } }
/// <summary> /// Get all of the entities in an area for displaying on the context menu. /// </summary> /// <param name="buffer">Whether we should slightly extend the entity search area.</param> public bool TryGetContextEntities(IEntity player, MapCoordinates targetPos, [NotNullWhen(true)] out List <IEntity>?contextEntities, bool buffer = false, bool ignoreVisibility = false) { contextEntities = null; // Check if we have LOS to the clicked-location. if (!ignoreVisibility && !player.InRangeUnOccluded(targetPos, range: ExamineSystemShared.ExamineRange)) { return(false); } // Get entities var length = buffer ? 1.0f : 0.5f; var entities = _lookup.GetEntitiesIntersecting( targetPos.MapId, Box2.CenteredAround(targetPos.Position, (length, length))) .ToList(); if (entities.Count == 0) { return(false); } if (ignoreVisibility) { contextEntities = entities; return(true); } // perform visibility checks var playerPos = player.Transform.MapPosition; foreach (var entity in entities.ToList()) { if (entity.HasTag("HideContextMenu")) { entities.Remove(entity); continue; } if (!ExamineSystemShared.InRangeUnOccluded( playerPos, entity.Transform.MapPosition, ExamineSystemShared.ExamineRange, null)) { entities.Remove(entity); } } if (entities.Count == 0) { return(false); } contextEntities = entities; return(true); }
/// <summary> /// Look for grids in an area and returns them. Also selects a special grid that will be used to determine the /// orientation of an explosion in space. /// </summary> /// <remarks> /// Note that even though an explosion may start ON a grid, the explosion in space may still be orientated to /// match a separate grid. This is done so that if you have something like a tiny suicide-bomb shuttle exploding /// near a large station, the explosion will still orient to match the station, not the tiny shuttle. /// </remarks> public (List <EntityUid>, EntityUid?, float) GetLocalGrids(MapCoordinates epicenter, float totalIntensity, float slope, float maxIntensity) { // Get the explosion radius (approx radius if it were in open-space). Note that if the explosion is confined in // some directions but not in others, the actual explosion may reach further than this distance from the // epicenter. Conversely, it might go nowhere near as far. var radius = 0.5f + IntensityToRadius(totalIntensity, slope, maxIntensity); // to avoid a silly lookup for silly input numbers, cap the radius to half of the theoretical maximum (lookup area gets doubled later on). radius = Math.Min(radius, MaxIterations / 4); EntityUid?referenceGrid = null; float mass = 0; // First attempt to find a grid that is relatively close to the explosion's center. Instead of looking in a // diameter x diameter sized box, use a smaller box with radius sized sides: var box = Box2.CenteredAround(epicenter.Position, (radius, radius)); foreach (var grid in _mapManager.FindGridsIntersecting(epicenter.MapId, box)) { if (TryComp(grid.GridEntityId, out PhysicsComponent? physics) && physics.Mass > mass) { mass = physics.Mass; referenceGrid = grid.GridEntityId; } } // Next, we use a much larger lookup to determine all grids relevant to the explosion. This is used to determine // what grids should be included during the grid-edge transformation steps. This means that if a grid is not in // this set, the explosion can never propagate from space onto this grid. // As mentioned before, the `diameter` is only indicative, as an explosion that is obstructed (e.g., in a // tunnel) may travel further away from the epicenter. But this should be very rare for space-traversing // explosions. So instead of using the largest possible distance that an explosion could theoretically travel // and using that for the grid look-up, we will just arbitrarily fudge the lookup size to be twice the diameter. radius *= 4; box = Box2.CenteredAround(epicenter.Position, (radius, radius)); var mapGrids = _mapManager.FindGridsIntersecting(epicenter.MapId, box).ToList(); var grids = mapGrids.Select(x => x.GridEntityId).ToList(); if (referenceGrid != null) { return(grids, referenceGrid, radius); } // We still don't have are reference grid. So lets also look in the enlarged region foreach (var grid in mapGrids) { if (TryComp(grid.GridEntityId, out PhysicsComponent? physics) && physics.Mass > mass) { mass = physics.Mass; referenceGrid = grid.GridEntityId; } } return(grids, referenceGrid, radius); }
private Box2 GetEntityBox(IEntity entity) { // Need to clip the aabb as anything with an edge intersecting another tile might be picked up, such as walls. if (entity.TryGetComponent(out IPhysicsComponent? physics)) { return(new Box2(physics.WorldAABB.BottomLeft + 0.01f, physics.WorldAABB.TopRight - 0.01f)); } // Don't want to accidentally get neighboring tiles unless we're near an edge return(Box2.CenteredAround(entity.Transform.Coordinates.ToMapPos(EntityManager), Vector2.One / 2)); }
private List <GasOverlayChunk> GetChunksInRange(IEntity entity) { var inRange = new List <GasOverlayChunk>(); // This is the max in any direction that we can get a chunk (e.g. max 2 chunks away of data). var(maxXDiff, maxYDiff) = ((int)(_updateRange / ChunkSize) + 1, (int)(_updateRange / ChunkSize) + 1); var worldBounds = Box2.CenteredAround(entity.Transform.WorldPosition, new Vector2(_updateRange, _updateRange)); foreach (var grid in _mapManager.FindGridsIntersecting(entity.Transform.MapID, worldBounds)) { if (!_overlay.TryGetValue(grid.Index, out var chunks)) { continue; } var entityTile = grid.GetTileRef(entity.Transform.Coordinates).GridIndices; for (var x = -maxXDiff; x <= maxXDiff; x++) { for (var y = -maxYDiff; y <= maxYDiff; y++) { var chunkIndices = GetGasChunkIndices(new MapIndices(entityTile.X + x * ChunkSize, entityTile.Y + y * ChunkSize)); if (!chunks.TryGetValue(chunkIndices, out var chunk)) { continue; } // Now we'll check if it's in range and relevant for us // (e.g. if we're on the very edge of a chunk we may need more chunks). var(xDiff, yDiff) = (chunkIndices.X - entityTile.X, chunkIndices.Y - entityTile.Y); if (xDiff > 0 && xDiff > _updateRange || yDiff > 0 && yDiff > _updateRange || xDiff < 0 && Math.Abs(xDiff + ChunkSize) > _updateRange || yDiff < 0 && Math.Abs(yDiff + ChunkSize) > _updateRange) { continue; } inRange.Add(chunk); } } } return(inRange); }
public override void FrameUpdate(float frameTime) { var eye = _eyeManager.CurrentEye; // So we could calculate the correct size of the entities based on the contents of their sprite... // Or we can just assume that no entity is larger than 10x10 and get a stupid easy check. // TODO: Make this check more accurate. var worldBounds = Box2.CenteredAround(eye.Position.Position, _clyde.ScreenSize / EyeManager.PIXELSPERMETER * eye.Zoom).Enlarged(5); var mapEntity = _mapManager.GetMapEntityId(eye.Position.MapId); var parentMatrix = Matrix3.Identity; RunUpdatesRecurse(frameTime, worldBounds, EntityManager.GetEntity(mapEntity), ref parentMatrix); }
public Box2Rotated GetWorldBounds(TileRef tileRef, Matrix3?worldMatrix = null, Angle?angle = null) { var grid = _mapManager.GetGrid(tileRef.GridIndex); if (worldMatrix == null || angle == null) { var gridXform = EntityManager.GetComponent <TransformComponent>(grid.GridEntityId); var(_, wAng, wMat) = gridXform.GetWorldPositionRotationMatrix(); worldMatrix = wMat; angle = wAng; } var center = worldMatrix.Value.Transform((Vector2)tileRef.GridIndices + 0.5f) * grid.TileSize; var translatedBox = Box2.CenteredAround(center, (grid.TileSize, grid.TileSize)); return(new Box2Rotated(translatedBox, -angle.Value, center)); }
public override void FrameUpdate(float frameTime) { var eye = _eyeManager.CurrentEye; // So we could calculate the correct size of the entities based on the contents of their sprite... // Or we can just assume that no entity is larger than 10x10 and get a stupid easy check. // TODO: Make this check more accurate. var worldBounds = Box2.CenteredAround(eye.Position.Position, _clyde.ScreenSize / EyeManager.PIXELSPERMETER * eye.Zoom).Enlarged(5); foreach (var entity in EntityManager.GetEntities(EntityQuery)) { var transform = entity.Transform; if (!worldBounds.Contains(transform.WorldPosition)) { continue; } // TODO: Don't call this on components without RSIs loaded. // Serious performance benefit here. entity.GetComponent <ISpriteComponent>().FrameUpdate(frameTime); } }
private void _drawLights(Box2 worldBounds) { if (!_lightManager.Enabled) { return; } var map = _eyeManager.CurrentMap; GL.BindFramebuffer(FramebufferTarget.Framebuffer, LightRenderTarget.ObjectHandle.Handle); var converted = Color.FromSrgb(new Color(0.1f, 0.1f, 0.1f)); GL.ClearColor(converted.R, converted.G, converted.B, 1); GL.Clear(ClearBufferMask.ColorBufferBit); var(lightW, lightH) = _lightMapSize(); GL.Viewport(0, 0, lightW, lightH); _lightShader.Use(); GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One); var lastRange = float.NaN; var lastPower = float.NaN; var lastColor = new Color(float.NaN, float.NaN, float.NaN, float.NaN); Texture lastMask = null; foreach (var component in _componentManager.GetAllComponents <PointLightComponent>()) { if (!component.Enabled || component.Owner.Transform.MapID != map) { continue; } var transform = component.Owner.Transform; var lightPos = transform.WorldMatrix.Transform(component.Offset); var lightBounds = Box2.CenteredAround(lightPos, Vector2.One * component.Radius * 2); if (!lightBounds.Intersects(worldBounds)) { continue; } Texture mask = null; var rotation = Angle.Zero; if (component.Mask != null) { mask = component.Mask; rotation = component.Rotation; if (component.MaskAutoRotate) { rotation += transform.WorldRotation; } } var maskTexture = mask ?? Texture.White; if (lastMask != maskTexture) { var maskHandle = _loadedTextures[((ClydeTexture)maskTexture).TextureId].OpenGLObject; GL.ActiveTexture(TextureUnit.Texture0); GL.BindTexture(TextureTarget.Texture2D, maskHandle.Handle); lastMask = maskTexture; _lightShader.SetUniformTexture("lightMask", TextureUnit.Texture0); } if (!FloatMath.CloseTo(lastRange, component.Radius)) { lastRange = component.Radius; _lightShader.SetUniform("lightRange", lastRange); } if (!FloatMath.CloseTo(lastPower, component.Energy)) { lastPower = component.Energy; _lightShader.SetUniform("lightPower", lastPower); } if (lastColor != component.Color) { lastColor = component.Color; _lightShader.SetUniform("lightColor", lastColor); } _lightShader.SetUniform("lightCenter", lightPos); var offset = new Vector2(component.Radius, component.Radius); Matrix3 matrix; if (mask == null) { matrix = Matrix3.Identity; } else { // Only apply rotation if a mask is said, because else it doesn't matter. matrix = Matrix3.CreateRotation(rotation); } (matrix.R0C2, matrix.R1C2) = lightPos; _drawQuad(-offset, offset, ref matrix, _lightShader); } GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); GL.Viewport(0, 0, ScreenSize.X, ScreenSize.Y); _lightingReady = true; }
public void Render() { _debugStats.Reset(); // Basic pre-render busywork. // Clear screen to black. ClearFramebuffer(Color.Black); // Update shared UBOs. _updateUniformConstants(); _setSpace(CurrentSpace.ScreenSpace); // Short path to render only the splash. if (_drawingSplash) { _drawSplash(_renderHandle); _flushRenderHandle(_renderHandle); _window.SwapBuffers(); return; } void RenderOverlays(OverlaySpace space) { using (DebugGroup($"Overlays: {space}")) { foreach (var overlay in _overlayManager.AllOverlays .Where(o => o.Space == space) .OrderBy(o => o.ZIndex)) { overlay.ClydeRender(_renderHandle); } _flushRenderHandle(_renderHandle); } } if (!_lite) { RenderOverlays(OverlaySpace.ScreenSpaceBelowWorld); _setSpace(CurrentSpace.WorldSpace); // Calculate world-space AABB for camera, to cull off-screen things. var eye = _eyeManager.CurrentEye; var worldBounds = Box2.CenteredAround(eye.Position.Position, _screenSize / EyeManager.PIXELSPERMETER * eye.Zoom); using (DebugGroup("Lights")) { _drawLights(worldBounds); } using (DebugGroup("Grids")) { _drawGrids(worldBounds); } using (DebugGroup("Entities")) { _sortingSpritesList.Clear(); var map = _eyeManager.CurrentMap; // So we could calculate the correct size of the entities based on the contents of their sprite... // Or we can just assume that no entity is larger than 10x10 and get a stupid easy check. // TODO: Make this check more accurate. var widerBounds = worldBounds.Enlarged(5); foreach (var sprite in _componentManager.GetAllComponents <SpriteComponent>()) { var entity = sprite.Owner; if (!entity.Transform.IsMapTransform || entity.Transform.MapID != map || !widerBounds.Contains(entity.Transform.WorldPosition) || !sprite.Visible) { continue; } _sortingSpritesList.Add(sprite); } _sortingSpritesList.Sort((a, b) => { var cmp = ((int)a.DrawDepth).CompareTo((int)b.DrawDepth); if (cmp != 0) { return(cmp); } cmp = a.RenderOrder.CompareTo(b.RenderOrder); if (cmp != 0) { return(cmp); } return(a.Owner.Uid.CompareTo(b.Owner.Uid)); }); foreach (var sprite in _sortingSpritesList) { Vector2i roundedPos = default; if (sprite.PostShader != null) { _renderHandle.UseRenderTarget(EntityPostRenderTarget); _renderHandle.Clear(new Color()); // Calculate viewport so that the entity thinks it's drawing to the same position, // which is necessary for light application, // but it's ACTUALLY drawing into the center of the render target. var spritePos = sprite.Owner.Transform.WorldPosition; var screenPos = _eyeManager.WorldToScreen(spritePos); var(roundedX, roundedY) = roundedPos = (Vector2i)screenPos; var flippedPos = new Vector2i(roundedX, ScreenSize.Y - roundedY); flippedPos -= EntityPostRenderTarget.Size / 2; _renderHandle.Viewport(Box2i.FromDimensions(-flippedPos, ScreenSize)); } sprite.OpenGLRender(_renderHandle.DrawingHandleWorld); if (sprite.PostShader != null) { _renderHandle.UseRenderTarget(null); _renderHandle.Viewport(Box2i.FromDimensions(Vector2i.Zero, ScreenSize)); _renderHandle.UseShader(sprite.PostShader); _renderHandle.SetSpace(CurrentSpace.ScreenSpace); _renderHandle.SetModelTransform(Matrix3.Identity); var rounded = roundedPos - EntityPostRenderTarget.Size / 2; var box = UIBox2i.FromDimensions(rounded, EntityPostRenderTarget.Size); _renderHandle.DrawTexture(EntityPostRenderTarget.Texture, box.BottomLeft, box.TopRight, Color.White, null, 0); _renderHandle.SetSpace(CurrentSpace.WorldSpace); _renderHandle.UseShader(null); } } _flushRenderHandle(_renderHandle); } RenderOverlays(OverlaySpace.WorldSpace); _lightingReady = false; _setSpace(CurrentSpace.ScreenSpace); RenderOverlays(OverlaySpace.ScreenSpace); } using (DebugGroup("UI")) { _userInterfaceManager.Render(_renderHandle); _flushRenderHandle(_renderHandle); } // And finally, swap those buffers! _window.SwapBuffers(); }
public IList <EntityUid> GetEntitiesUnderPosition(MapCoordinates coordinates) { // Find all the entities intersecting our click var entities = IoCManager.Resolve <IEntityLookup>().GetEntitiesIntersecting(coordinates.MapId, Box2.CenteredAround(coordinates.Position, (1, 1))); var containerSystem = _entitySystemManager.GetEntitySystem <SharedContainerSystem>(); // Check the entities against whether or not we can click them var foundEntities = new List <(EntityUid clicked, int drawDepth, uint renderOrder)>(); foreach (var entity in entities) { if (_entityManager.TryGetComponent <ClickableComponent?>(entity, out var component) && !containerSystem.IsEntityInContainer(entity) && component.CheckClick(coordinates.Position, out var drawDepthClicked, out var renderOrder)) { foundEntities.Add((entity, drawDepthClicked, renderOrder)); } } if (foundEntities.Count == 0) { return(Array.Empty <EntityUid>()); } foundEntities.Sort(_comparer); // 0 is the top element. foundEntities.Reverse(); return(foundEntities.Select(a => a.clicked).ToList()); }
public GridEdgeData(Vector2i tile, EntityUid?grid, Vector2 center, Angle angle, float size) { Tile = tile; Grid = grid; Box = new(Box2.CenteredAround(center, (size, size)), angle, center); }
protected override void Draw(DrawingHandleBase handle, OverlaySpace overlay) { var drawHandle = (DrawingHandleWorld)handle; var mapId = _eyeManager.CurrentMap; var eye = _eyeManager.CurrentEye; var worldBounds = Box2.CenteredAround(eye.Position.Position, _clyde.ScreenSize / (float)EyeManager.PixelsPerMeter * eye.Zoom); // IF YOU ARE ABOUT TO INTRODUCE CHUNKING OR SOME OTHER OPTIMIZATION INTO THIS CODE: // -- THINK! -- // 1. "Is this going to make a critical atmos debugging tool harder to debug itself?" // 2. "Is this going to do anything that could cause the atmos debugging tool to use resources, server-side or client-side, when nobody's using it?" // 3. "Is this going to make it harder for atmos programmers to add data that may not be chunk-friendly into the atmos debugger?" // Nanotrasen needs YOU! to avoid premature optimization in critical debugging tools - 20kdc foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds)) { if (!_atmosDebugOverlaySystem.HasData(mapGrid.Index)) { continue; } var gridBounds = new Box2(mapGrid.WorldToLocal(worldBounds.BottomLeft), mapGrid.WorldToLocal(worldBounds.TopRight)); for (var pass = 0; pass < 2; pass++) { foreach (var tile in mapGrid.GetTilesIntersecting(gridBounds)) { var dataMaybeNull = _atmosDebugOverlaySystem.GetData(mapGrid.Index, tile.GridIndices); if (dataMaybeNull != null) { var data = (SharedAtmosDebugOverlaySystem.AtmosDebugOverlayData)dataMaybeNull !; if (pass == 0) { // -- Mole Count -- float total = 0; switch (_atmosDebugOverlaySystem.CfgMode) { case AtmosDebugOverlayMode.TotalMoles: foreach (float f in data.Moles) { total += f; } break; case AtmosDebugOverlayMode.GasMoles: total = data.Moles[_atmosDebugOverlaySystem.CfgSpecificGas]; break; case AtmosDebugOverlayMode.Temperature: total = data.Temperature; break; } var interp = ((total - _atmosDebugOverlaySystem.CfgBase) / _atmosDebugOverlaySystem.CfgScale); Color res; if (_atmosDebugOverlaySystem.CfgCBM) { // Greyscale interpolation res = Color.InterpolateBetween(Color.Black, Color.White, interp); } else { // Red-Green-Blue interpolation if (interp < 0.5f) { res = Color.InterpolateBetween(Color.Red, Color.Green, interp * 2); } else { res = Color.InterpolateBetween(Color.Green, Color.Blue, (interp - 0.5f) * 2); } } res = res.WithAlpha(0.75f); drawHandle.DrawRect(Box2.FromDimensions(mapGrid.LocalToWorld(new Vector2(tile.X, tile.Y)), new Vector2(1, 1)), res); } else if (pass == 1) { // -- Blocked Directions -- void CheckAndShowBlockDir(AtmosDirection dir) { if (data.BlockDirection.HasFlag(dir)) { // Account for South being 0. var atmosAngle = dir.ToAngle() - Angle.FromDegrees(90); var atmosAngleOfs = atmosAngle.ToVec() * 0.45f; var atmosAngleOfsR90 = new Vector2(atmosAngleOfs.Y, -atmosAngleOfs.X); var tileCentre = new Vector2(tile.X + 0.5f, tile.Y + 0.5f); var basisA = mapGrid.LocalToWorld(tileCentre + atmosAngleOfs - atmosAngleOfsR90); var basisB = mapGrid.LocalToWorld(tileCentre + atmosAngleOfs + atmosAngleOfsR90); drawHandle.DrawLine(basisA, basisB, Color.Azure); } } CheckAndShowBlockDir(AtmosDirection.North); CheckAndShowBlockDir(AtmosDirection.South); CheckAndShowBlockDir(AtmosDirection.East); CheckAndShowBlockDir(AtmosDirection.West); // -- Pressure Direction -- if (data.PressureDirection != AtmosDirection.Invalid) { // Account for South being 0. var atmosAngle = data.PressureDirection.ToAngle() - Angle.FromDegrees(90); var atmosAngleOfs = atmosAngle.ToVec() * 0.4f; var tileCentre = new Vector2(tile.X + 0.5f, tile.Y + 0.5f); var basisA = mapGrid.LocalToWorld(tileCentre); var basisB = mapGrid.LocalToWorld(tileCentre + atmosAngleOfs); drawHandle.DrawLine(basisA, basisB, Color.Blue); } // -- Excited Groups -- if (data.InExcitedGroup) { var tilePos = new Vector2(tile.X, tile.Y); var basisA = mapGrid.LocalToWorld(tilePos); var basisB = mapGrid.LocalToWorld(tilePos + new Vector2(1.0f, 1.0f)); var basisC = mapGrid.LocalToWorld(tilePos + new Vector2(0.0f, 1.0f)); var basisD = mapGrid.LocalToWorld(tilePos + new Vector2(1.0f, 0.0f)); drawHandle.DrawLine(basisA, basisB, Color.Cyan); drawHandle.DrawLine(basisC, basisD, Color.Cyan); } } } } } } }
protected override void Draw(DrawingHandleBase handle, OverlaySpace overlay) { var drawHandle = (DrawingHandleWorld)handle; var mapId = _eyeManager.CurrentMap; var eye = _eyeManager.CurrentEye; var worldBounds = Box2.CenteredAround(eye.Position.Position, _clyde.ScreenSize / (float)EyeManager.PixelsPerMeter * eye.Zoom); // IF YOU ARE ABOUT TO INTRODUCE CHUNKING OR SOME OTHER OPTIMIZATION INTO THIS CODE: // -- THINK! -- // 1. "Is this going to make a critical atmos debugging tool harder to debug itself?" // 2. "Is this going to do anything that could cause the atmos debugging tool to use resources, server-side or client-side, when nobody's using it?" // 3. "Is this going to make it harder for atmos programmers to add data that may not be chunk-friendly into the atmos debugger?" // Nanotrasen needs YOU! to avoid premature optimization in critical debugging tools - 20kdc foreach (var mapGrid in _mapManager.FindGridsIntersecting(mapId, worldBounds)) { if (!_atmosDebugOverlaySystem.HasData(mapGrid.Index)) { continue; } var gridBounds = new Box2(mapGrid.WorldToLocal(worldBounds.BottomLeft), mapGrid.WorldToLocal(worldBounds.TopRight)); for (var pass = 0; pass < 3; pass++) { foreach (var tile in mapGrid.GetTilesIntersecting(gridBounds)) { var dataMaybeNull = _atmosDebugOverlaySystem.GetData(mapGrid.Index, tile.GridIndices); if (dataMaybeNull != null) { var data = (SharedAtmosDebugOverlaySystem.AtmosDebugOverlayData)dataMaybeNull !; if (pass == 0) { float total = 0; foreach (float f in data.Moles) { total += f; } var interp = total / (Atmospherics.MolesCellStandard * 2); var res = Color.InterpolateBetween(Color.Red, Color.Green, interp).WithAlpha(0.75f); drawHandle.DrawRect(Box2.FromDimensions(mapGrid.LocalToWorld(new Vector2(tile.X, tile.Y)), new Vector2(1, 1)), res); } else if (pass == 1) { if (data.PressureDirection != AtmosDirection.Invalid) { var atmosAngle = data.PressureDirection.ToAngle(); var atmosAngleOfs = atmosAngle.ToVec() * 0.4f; var tileCentre = new Vector2(tile.X + 0.5f, tile.Y + 0.5f); var basisA = mapGrid.LocalToWorld(tileCentre); var basisB = mapGrid.LocalToWorld(tileCentre + atmosAngleOfs); drawHandle.DrawLine(basisA, basisB, Color.Blue); } } else if (pass == 2) { if (data.InExcitedGroup) { var tilePos = new Vector2(tile.X, tile.Y); var basisA = mapGrid.LocalToWorld(tilePos); var basisB = mapGrid.LocalToWorld(tilePos + new Vector2(1.0f, 1.0f)); var basisC = mapGrid.LocalToWorld(tilePos + new Vector2(0.0f, 1.0f)); var basisD = mapGrid.LocalToWorld(tilePos + new Vector2(1.0f, 0.0f)); drawHandle.DrawLine(basisA, basisB, Color.Cyan); drawHandle.DrawLine(basisC, basisD, Color.Cyan); } } } } } } }