/* * Copyright 2022 Peter Han * Permission is hereby granted, free of charge, to any person obtaining a copy of this software * and associated documentation files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using PeterHan.PLib.Core; using PeterHan.PLib.Detours; using System; using System.Collections.Generic; using BrightnessDict = System.Collections.Generic.IDictionary; using Octant = DiscreteShadowCaster.Octant; namespace PeterHan.PLib.Lighting { /// /// A builder class which creates default light patterns based on octants. /// public sealed class OctantBuilder { /// /// The delegate type called to run the default DiscreteShadowCaster.ScanOctant. /// private delegate void ScanOctantFunc(Vector2I cellPos, int range, int depth, Octant octant, double startSlope, double endSlope, List visiblePoints); /// /// The method to call to scan octants. /// private static readonly ScanOctantFunc OCTANT_SCAN; static OctantBuilder() { // Cache the method for faster execution OCTANT_SCAN = typeof(DiscreteShadowCaster).Detour("ScanOctant"); if (OCTANT_SCAN == null) PLightManager.LogLightingWarning("OctantBuilder cannot find default octant scanner!"); } /// /// The fallout to use when building the light. /// public float Falloff { get; set; } /// /// If false, uses the default game smoothing. If true, uses better smoothing. /// public bool SmoothLight { get; set; } /// /// The origin cell. /// public int SourceCell { get; } /// /// The location where light cells are added. /// private readonly BrightnessDict destination; /// /// Creates a new octant builder. /// /// The location where the lit cells will be placed. /// The origin cell of the light. public OctantBuilder(BrightnessDict destination, int sourceCell) { if (!Grid.IsValidCell(sourceCell)) throw new ArgumentOutOfRangeException(nameof(sourceCell)); this.destination = destination ?? throw new ArgumentNullException(nameof( destination)); destination[sourceCell] = 1.0f; Falloff = 0.5f; // Use the default game's light algorithm SmoothLight = false; SourceCell = sourceCell; } /// /// Adds an octant of light. /// /// The range of the light. /// The octant to scan. /// This object, for call chaining. public OctantBuilder AddOctant(int range, Octant octant) { var points = ListPool.Allocate(); OCTANT_SCAN?.Invoke(Grid.CellToXY(SourceCell), range, 1, octant, 1.0, 0.0, points); // Transfer to our array using: foreach (int cell in points) { float intensity; if (SmoothLight) // Better, not rounded falloff intensity = PLightManager.GetSmoothFalloff(Falloff, cell, SourceCell); else // Default falloff intensity = PLightManager.GetDefaultFalloff(Falloff, cell, SourceCell); destination[cell] = intensity; } points.Recycle(); return this; } public override string ToString() { return string.Format("OctantBuilder[Cell {0:D}, {1:D} lit]", SourceCell, destination.Count); } } }