Setting Light Glow Angle

Started by ppumkin, September 09, 2016, 08:35:44 PM

Previous topic - Next topic

ppumkin

Is it possible to change the angle of light glowing. Currently all lights seem to be 360.
Even if it is in C# - Would it be possible to make a narrower angle? more directional...
'Sharing is caring'
- Unknown

Master Bucketsmith

Currently everything is defined by a radius.

If there would be a way to set different shapes, that would be fucking amazing!
Defining squares, rectangles, triangles, etc..
Hell it would already mean a lot if we could limit the radius to a certain minimum and maximum degrees from front!

ppumkin

OK :) thanks for reply.

Sorry for being a noob - But do you think it would be possible to write a mod in C# to change that "built in" behaviour?
I understand the engine runs on Unity, and there is the secret sauce code (i think??) Is it that that base code does not expose these properties (because unity for sure has to have a way to do it) or because nobody knows how to write the mod?

Just asking :) heheh

'Sharing is caring'
- Unknown

Master Bucketsmith

I've been poking at the source with ILSpy to see if I could understand why it only works in radius, or how it applies glow on tiles.
I'm not a C# dev so I didn't really see anything I clearly recognized. :P

It might be possible, but the question would be; how much of the code is not being decompiled and would you have to reverse engineer in a different way & how many bits of code are set to private/protected so that you can't reference that and have to go a step deeper?

ppumkin

The that is the problem. We have, what it looks like 3 layers here.

1) Unity Engine
2) Core Game Source - Implementation of Unity , Then,  Abstracts and Interfaces for Layer 3
3) XML files, like core and modding - Implementing and setting properties defined in Abstracts/Interface defined in 2.

So, if Layer 2 does not have any way of telling Layer 1 how to set the light radius, intensity (we have Radius though) or shape. Then I am not sure it will be possible until a Core (2) implementation is done. I think it would really require updating the core game code, or bypassing 2 directly into Unity... But I dont know anything about Unity - Although I have seen allot of .cs files in the games folder... Maybe there is something there? Not sure. WIll have a look.

Just finished creating my first mod just using XML. So I am going to off an see what I can do by using VisualSutdio.
'Sharing is caring'
- Unknown

Master Bucketsmith

I believe the following two classes represent how glow is drawn and how it connects to the Unity engine.
I see a bunch about a grid, which could mean that the radius is simply on the side of RW.
I'm afraid we'd still have to write something comprehensive to be able to mod non-circle glowers.

I'd really love to know because it would allow me to greatly improve my spotlights & ideas I had for better light sources.
(Though a lot of people are already attempting the latter.)

using System;
using System.Collections.Generic;
using UnityEngine;

namespace Verse
{
public sealed class GlowGrid
{
public const int AlphaOfNotOverlit = 0;

public const int AlphaOfOverlit = 1;

private const float MaxGameGlowFromNonOverlitGroundLights = 0.6f;

private const float GameGlowLitThreshold = 0.3f;

private const float GameGlowOverlitThreshold = 0.9f;

private const float GroundGameGlowFactorUp = 6f;

public Color32[] glowGrid;

private bool glowGridDirty;

private HashSet<CompGlower> litGlowers = new HashSet<CompGlower>();

private List<IntVec3> initialGlowerLocs = new List<IntVec3>();

public GlowGrid()
{
this.glowGrid = new Color32[CellIndices.NumGridCells];
}

public Color32 VisualGlowAt(IntVec3 c)
{
return this.glowGrid[CellIndices.CellToIndex(c)];
}

public float GameGlowAt(IntVec3 c)
{
float num = 0f;
if (!Find.RoofGrid.Roofed(c))
{
num += SkyManager.CurSkyGlow;
}
Color32 color = this.glowGrid[CellIndices.CellToIndex(c)];
if (color.a == 1)
{
return 1f;
}
int num2 = (int)color.r;
if ((int)color.g > num2)
{
num2 = (int)color.g;
}
if ((int)color.b > num2)
{
num2 = (int)color.b;
}
float num3 = (float)num2 / 255f * 0.6f;
num3 *= 6f;
num3 = Mathf.Min(0.6f, num3);
return Mathf.Max(num, num3);
}

public PsychGlow PsychGlowAt(IntVec3 c)
{
float glow = this.GameGlowAt(c);
return GlowGrid.PsychGlowAtGlow(glow);
}

public static PsychGlow PsychGlowAtGlow(float glow)
{
if (glow > 0.9f)
{
return PsychGlow.Overlit;
}
if (glow > 0.3f)
{
return PsychGlow.Lit;
}
return PsychGlow.Dark;
}

public void RegisterGlower(CompGlower newGlow)
{
this.litGlowers.Add(newGlow);
this.MarkGlowGridDirty(newGlow.parent.Position);
if (Current.ProgramState != ProgramState.MapPlaying)
{
this.initialGlowerLocs.Add(newGlow.parent.Position);
}
}

public void DeRegisterGlower(CompGlower oldGlow)
{
this.litGlowers.Remove(oldGlow);
this.MarkGlowGridDirty(oldGlow.parent.Position);
}

public void MarkGlowGridDirty(IntVec3 loc)
{
this.glowGridDirty = true;
Find.MapDrawer.MapMeshDirty(loc, MapMeshFlag.GroundGlow);
}

public void GlowGridUpdate_First()
{
if (this.glowGridDirty)
{
this.RecalculateAllGlow();
this.glowGridDirty = false;
}
}

private void RecalculateAllGlow()
{
if (Current.ProgramState != ProgramState.MapPlaying)
{
return;
}
if (this.initialGlowerLocs != null)
{
foreach (IntVec3 current in this.initialGlowerLocs)
{
this.MarkGlowGridDirty(current);
}
this.initialGlowerLocs = null;
}
for (int i = 0; i < CellIndices.NumGridCells; i++)
{
this.glowGrid[i] = new Color32(0, 0, 0, 0);
}
foreach (CompGlower current2 in this.litGlowers)
{
GlowFlooder.AddFloodGlowFor(current2);
}
}
}
}

using System;
using System.Collections.Generic;
using UnityEngine;

namespace Verse
{
public static class GlowFlooder
{
private struct GlowFloodCell
{
public int intDist;

public uint status;
}

private class CompareGlowFlooderLightSquares : IComparer<int>
{
private GlowFlooder.GlowFloodCell[] grid;

public CompareGlowFlooderLightSquares(GlowFlooder.GlowFloodCell[] grid)
{
this.grid = grid;
}

public int Compare(int a, int b)
{
if (this.grid[a].intDist > this.grid[b].intDist)
{
return 1;
}
if (this.grid[a].intDist < this.grid[b].intDist)
{
return -1;
}
return 0;
}
}

private static GlowFlooder.GlowFloodCell[] calcGrid;

private static FastPriorityQueue<int> openSet;

private static uint unseenVal = 0u;

private static uint openVal = 1u;

private static uint finalizedVal = 2u;

private static int mapSizePowTwo;

private static ushort gridSizeX;

private static ushort gridSizeY;

private static ushort gridSizeXMinus1;

private static ushort gridSizeZLog2;

private static CompGlower glower;

private static float attenLinearSlope;

private static int finalIdx;

private static Thing[] blockers = new Thing[8];

private static Color32[] glowGrid;

private static sbyte[,] Directions = new sbyte[,]
{
{
0,
-1
},
{
1,
0
},
{
0,
1
},
{
-1,
0
},
{
1,
-1
},
{
1,
1
},
{
-1,
1
},
{
-1,
-1
}
};

public static void Reinit()
{
GlowFlooder.calcGrid = null;
}

private static void InitializeWorkingData()
{
GlowFlooder.mapSizePowTwo = Find.Map.info.PowerOfTwoOverMapSize;
GlowFlooder.gridSizeX = (ushort)GlowFlooder.mapSizePowTwo;
GlowFlooder.gridSizeY = (ushort)GlowFlooder.mapSizePowTwo;
GlowFlooder.gridSizeXMinus1 = GlowFlooder.gridSizeX - 1;
GlowFlooder.gridSizeZLog2 = (ushort)Math.Log((double)GlowFlooder.gridSizeY, 2.0);
if (GlowFlooder.calcGrid == null || GlowFlooder.calcGrid.Length != (int)(GlowFlooder.gridSizeX * GlowFlooder.gridSizeY))
{
GlowFlooder.calcGrid = new GlowFlooder.GlowFloodCell[(int)(GlowFlooder.gridSizeX * GlowFlooder.gridSizeY)];
}
GlowFlooder.openSet = new FastPriorityQueue<int>(new GlowFlooder.CompareGlowFlooderLightSquares(GlowFlooder.calcGrid));
}

public static void AddFloodGlowFor(CompGlower theGlower)
{
if (GlowFlooder.calcGrid == null)
{
GlowFlooder.InitializeWorkingData();
}
GlowFlooder.glowGrid = Find.GlowGrid.glowGrid;
GlowFlooder.glower = theGlower;
Thing[] innerArray = Find.EdificeGrid.InnerArray;
GlowFlooder.unseenVal += 3u;
GlowFlooder.openVal += 3u;
GlowFlooder.finalizedVal += 3u;
IntVec3 position = GlowFlooder.glower.parent.Position;
GlowFlooder.attenLinearSlope = -1f / GlowFlooder.glower.Props.glowRadius;
int num = Mathf.RoundToInt(GlowFlooder.glower.Props.glowRadius * 100f);
IntVec3 intVec = default(IntVec3);
IntVec3 c = default(IntVec3);
int num2 = 0;
GlowFlooder.openSet.Clear();
int num3 = (position.z << (int)GlowFlooder.gridSizeZLog2) + position.x;
GlowFlooder.calcGrid[num3].intDist = 100;
GlowFlooder.openSet.Push(num3);
while (GlowFlooder.openSet.Count != 0)
{
int num4 = GlowFlooder.openSet.Pop();
intVec.x = (int)((ushort)(num4 & (int)GlowFlooder.gridSizeXMinus1));
intVec.z = (int)((ushort)(num4 >> (int)GlowFlooder.gridSizeZLog2));
GlowFlooder.calcGrid[num4].status = GlowFlooder.finalizedVal;
GlowFlooder.SetGlowGridFromDist(intVec);
if (UnityData.isDebugBuild && DebugViewSettings.drawGlow)
{
Find.DebugDrawer.FlashCell(intVec, (float)GlowFlooder.calcGrid[num4].intDist / 10f, GlowFlooder.calcGrid[num4].intDist.ToString("F2"));
num2++;
}
for (int i = 0; i < 8; i++)
{
c.x = (int)((ushort)(intVec.x + (int)GlowFlooder.Directions[i, 0]));
c.z = (int)((ushort)(intVec.z + (int)GlowFlooder.Directions[i, 1]));
int num5 = (c.z << (int)GlowFlooder.gridSizeZLog2) + c.x;
if (c.InBounds())
{
if (GlowFlooder.calcGrid[num5].status != GlowFlooder.finalizedVal)
{
GlowFlooder.blockers[i] = innerArray[CellIndices.CellToIndex(c)];
if (GlowFlooder.blockers[i] != null)
{
if (GlowFlooder.blockers[i].def.blockLight)
{
goto IL_41E;
}
GlowFlooder.blockers[i] = null;
}
int num6;
if (i < 4)
{
num6 = 100;
}
else
{
num6 = 141;
}
int num7 = GlowFlooder.calcGrid[num4].intDist + num6;
if (num7 <= num)
{
if (GlowFlooder.calcGrid[num5].status != GlowFlooder.finalizedVal)
{
if (i >= 4)
{
bool flag = false;
switch (i)
{
case 4:
if (GlowFlooder.blockers[0] != null && GlowFlooder.blockers[1] != null)
{
flag = true;
}
break;
case 5:
if (GlowFlooder.blockers[1] != null && GlowFlooder.blockers[2] != null)
{
flag = true;
}
break;
case 6:
if (GlowFlooder.blockers[2] != null && GlowFlooder.blockers[3] != null)
{
flag = true;
}
break;
case 7:
if (GlowFlooder.blockers[0] != null && GlowFlooder.blockers[3] != null)
{
flag = true;
}
break;
}
if (flag)
{
goto IL_41E;
}
}
if (GlowFlooder.calcGrid[num5].status <= GlowFlooder.unseenVal)
{
GlowFlooder.calcGrid[num5].intDist = 999999;
GlowFlooder.calcGrid[num5].status = GlowFlooder.openVal;
}
if (num7 < GlowFlooder.calcGrid[num5].intDist)
{
GlowFlooder.calcGrid[num5].intDist = num7;
GlowFlooder.calcGrid[num5].status = GlowFlooder.openVal;
GlowFlooder.openSet.Push(num5);
}
}
}
}
}
IL_41E:;
}
}
}

private static void SetGlowGridFromDist(IntVec3 c)
{
GlowFlooder.finalIdx = (c.z << (int)GlowFlooder.gridSizeZLog2) + c.x;
float num = (float)GlowFlooder.calcGrid[GlowFlooder.finalIdx].intDist / 100f;
ColorInt colB = default(ColorInt);
if (num <= GlowFlooder.glower.Props.glowRadius)
{
float b = 1f / (num * num);
float a = 1f + GlowFlooder.attenLinearSlope * num;
float b2 = Mathf.Lerp(a, b, 0.4f);
colB = GlowFlooder.glower.Props.glowColor * b2;
}
if (colB.r > 0 || colB.g > 0 || colB.b > 0)
{
colB.ClampToNonNegative();
int num2 = CellIndices.CellToIndex(c);
ColorInt colA = GlowFlooder.glowGrid[num2].AsColorInt();
colA += colB;
if (num < GlowFlooder.glower.Props.overlightRadius)
{
colA.a = 1;
}
Color32 toColor = colA.ToColor32;
GlowFlooder.glowGrid[num2] = toColor;
}
}
}
}

ppumkin

Thanks for the pointer!

Yea, its all about the lights isn't it  8) I am also looking at how to tweak the angles.

From what I can see, by just scratching the surface of the Rimworld/Verse source code - All that is doing is setting up the objects and implementing the code. That must be injected into Unity and executed at another level. This where I lack experience though, in Unity.

But gonna dive into what you provided there. Thanks again
'Sharing is caring'
- Unknown

ppumkin

Just quickly looking over that- It looks like the glowGrid is responsible for setting up the area somehow. But the issue is that the class is sealed... So cant override it or change it :( Maybe its possible to make another Type of GlowGrid and register that Type with

public void RegisterGlower(CompGlower newGlow)

Lets see what can be done
'Sharing is caring'
- Unknown

ppumkin

#8
Just a thought.

Since we cannot override or inherit the Glower classes. I made my own static GlowFloodTest class, by copying the GlowFlood source code and replacing all the calls to GlowFlood to GlowFloodTest. Basically made my own implementation of that GlowFlood by reusing the source we can see.

Now I have a hunch, in order to use our GlowFlood, we need to pass it into SpawnSetup(), way down in the base.base... implementation. Which is overridable. Just a hunch, as it looks like each "Thing" has its own things set like, drawerType,Grpahics,Tooltip, CoverGrid... etc.

Then it seems like we need to use Find (To find the GlowGrid or GlowFlooder) instance of the thing and bodge it in there??

Too late for me now though. TIme for bed
'Sharing is caring'
- Unknown

Master Bucketsmith

That's how I was able to customize the turret code for my spotlights and remove the firing mechanism. :)

I'm afraid I'm not much help in regards to your last reply. I don't know enough about C# to make suggestions like that. :D

Though, I think the GlowGrid/GlowFlood looks like it can handle other shapes than a radius from center? I don't see anything that suggests it only works with a full circle.
So, I'm guessing it's more of a matter of writing the code that makes the game understand it's a different shape. (And how customizable could we make that?)

ppumkin

The light radius, is not actually a radius :)

It looks like an mathematical algorithm, that produces a circular grid with, by what it must be decreasing intensity from centre of thing.

I will try and alter it somehow later today
'Sharing is caring'
- Unknown

Master Bucketsmith

Within the scope of the game or scope of the engine?
In the end all of it is math and logic, isn't it.
I meant that as far as I could tell, within the game, almost everything is done with a radius.
A notable exception is the TV and it's area of effect. :)

It would be fantastic if you found a way to implement customizable shapes for glowers! :O

ppumkin

#12
Since I first posted this topic, I have gone high and low, cover to my ears in compiling and debugging this.


But I am proud to present angled light.

When I started I had no idea about anything in the Rimworld DLL or Unity. For a few days I was upset that I could not modify the GlowGrid or GlowFlooder in any moddable way. Searching high and low, using deduction and trying some crazy ass code and hacks out I eventually landed on on Rawcodes IL jump patch, linked from Stackoverflow.  Since then I have been practically glued to Visual Studio , ILSpy and Rimworld starting 200 times an hour totalling over 40 man hours.

Thanks for your support Master Bucketsmith
All credits are given in the mod where necessary too.

Yea, I did run into CCL along the way but I did not find any sign of making your own Glowflooders there so I pushed on made my own code and my own Extendable GlowFlooder. I do not break the vanilla compGlowers but I did a framework for other modders to be able to write thier own GlowFlooders, without any flickering on the screen, no lag (obviously your glowFlooder has to be optimised for speed) but I can place 1000 lights on the map and maybe have a tiny, insy winsy lag. Meh... It was all worth it.

Making the glowflooder was easy, the difficult part after that was collision detection but I have managed to optimise is as best as I know.

http://steamcommunity.com/sharedfiles/filedetails/?id=760900903

[attachment deleted by admin - too old]
'Sharing is caring'
- Unknown

EldVarg

Cool :). Would it be possible to make it fade out further and further away?

ppumkin

Yes. It just needs some logic applying. At the moment I just managed to sort out the "angles" and collision detection which took me a while to get working well enough.
I will be releasing this on GitHub soon so everybody will the opportunity to suggest code improvements or make their own implementations
'Sharing is caring'
- Unknown