using CodeWalker.GameFiles; using SharpDX; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace CodeWalker.Project.Panels { public partial class GenerateLODLightsPanel : ProjectPanel { public ProjectForm ProjectForm { get; set; } public ProjectFile CurrentProjectFile { get; set; } public GenerateLODLightsPanel(ProjectForm projectForm) { ProjectForm = projectForm; InitializeComponent(); if (ProjectForm?.WorldForm == null) { //could happen in some other startup mode - world form is required for this.. GenerateButton.Enabled = false; UpdateStatus("Unable to generate - World View not available!"); } } public void SetProject(ProjectFile project) { CurrentProjectFile = project; } private void GenerateComplete() { try { if (InvokeRequired) { Invoke(new Action(() => { GenerateComplete(); })); } else { GenerateButton.Enabled = true; } } catch { } } private void UpdateStatus(string text) { try { if (InvokeRequired) { Invoke(new Action(() => { UpdateStatus(text); })); } else { StatusLabel.Text = text; } } catch { } } private void GenerateButton_Click(object sender, EventArgs e) { //var space = ProjectForm?.WorldForm?.Space; //if (space == null) return; var gameFileCache = ProjectForm?.WorldForm?.GameFileCache; if (gameFileCache == null) return; var path = ProjectForm.CurrentProjectFile.GetFullFilePath("lodlights") + "\\"; GenerateButton.Enabled = false; List projectYmaps = ProjectForm.CurrentProjectFile.YmapFiles; var pname = NameTextBox.Text; Task.Run(() => { var lights = new List(); var eemin = new Vector3(float.MaxValue); var eemax = new Vector3(float.MinValue); var semin = new Vector3(float.MaxValue); var semax = new Vector3(float.MinValue); //var rnd = new Random(); foreach (var ymap in projectYmaps) { if (ymap?.AllEntities == null) continue; foreach (var ent in ymap.AllEntities) { if (ent.Archetype == null) continue; bool waiting = false; var dwbl = gameFileCache.TryGetDrawable(ent.Archetype, out waiting); while (waiting) { dwbl = gameFileCache.TryGetDrawable(ent.Archetype, out waiting); UpdateStatus("Waiting for " + ent.Archetype.AssetName + " to load..."); Thread.Sleep(20); } UpdateStatus("Adding lights from " + ent.Archetype.Name + "..."); if (dwbl != null) { Drawable ddwbl = dwbl as Drawable; FragDrawable fdwbl = dwbl as FragDrawable; LightAttributes_s[] lightAttrs = null; if (ddwbl != null) { lightAttrs = ddwbl.LightAttributes?.data_items; } else if (fdwbl != null) { lightAttrs = fdwbl.OwnerFragment?.LightAttributes?.data_items; } if (lightAttrs != null) { eemin = Vector3.Min(eemin, ent.BBMin); eemax = Vector3.Max(eemax, ent.BBMax); semin = Vector3.Min(semin, ent.BBMin - ent._CEntityDef.lodDist); semax = Vector3.Max(semax, ent.BBMax + ent._CEntityDef.lodDist); for (int li = 0; li= 0) && (parentind < pinds.Length)) { Matrix ptrans = (parentind < modeltransforms.Length) ? modeltransforms[parentind] : Matrix.Identity; ptrans.Column4 = Vector4.UnitW; xform = Matrix.Multiply(ptrans, xform); parentind = ((pinds != null) && (parentind < pinds.Length)) ? pinds[parentind] : (short)-1; } } } } Vector3 lpos = new Vector3(la.PositionX, la.PositionY, la.PositionZ); Vector3 ldir = new Vector3(la.DirectionX, la.DirectionY, la.DirectionZ); Vector3 bpos = xform.Multiply(lpos); Vector3 bdir = xform.MultiplyRot(ldir); Vector3 epos = ent.Orientation.Multiply(bpos) + ent.Position; Vector3 edir = ent.Orientation.Multiply(bdir); uint r = la.ColorR; uint g = la.ColorG; uint b = la.ColorB; uint i = (byte)Math.Min(la.Intensity*4, 255); uint c = (i << 24) + (r << 16) + (g << 8) + b; uint h = GetLightHash(ent, li);// (uint)rnd.NextLong(); if (ent._CEntityDef.guid == 91259075) { } //h = 2324437992? should be:19112537 if (ent._CEntityDef.guid == 889043351) { } //h = 422028630 ? should be:4267224866 //any other way to know if it's a streetlight? //var name = ent.Archetype.Name; var flags = la.Flags; bool isStreetLight = (((flags >> 10) & 1u) == 1);// (name != null) && (name.Contains("street") || name.Contains("traffic")); isStreetLight = false; //TODO: fix this! //@Calcium: //1 = point //2 = spot //4 = capsule uint type = la.Type; uint unk = isStreetLight ? 1u : 0;//2 bits - isStreetLight low bit, unk high bit uint t = la.TimeFlags | (type << 26) | (unk << 24); var maxext = (byte)Math.Max(Math.Max(la.ExtentX, la.ExtentY), la.ExtentZ); var light = new Light(); light.position = new MetaVECTOR3(epos); light.colour = c; light.direction = new MetaVECTOR3(edir); light.falloff = la.Falloff; light.falloffExponent = la.FalloffExponent; light.timeAndStateFlags = t; light.hash = h; light.coneInnerAngle = (byte)la.ConeInnerAngle; light.coneOuterAngleOrCapExt = Math.Max((byte)la.ConeOuterAngle, maxext); light.coronaIntensity = (byte)(la.CoronaIntensity * 6); light.isStreetLight = isStreetLight; lights.Add(light); } } } } } if (lights.Count == 0) { MessageBox.Show("No lights found in project!"); return; } //final lights should be sorted by isStreetLight (1 first!) and then hash lights.Sort((a, b) => { if (a.isStreetLight != b.isStreetLight) return b.isStreetLight.CompareTo(a.isStreetLight); return a.hash.CompareTo(b.hash); }); var position = new List(); var colour = new List(); var direction = new List(); var falloff = new List(); var falloffExponent = new List(); var timeAndStateFlags = new List(); var hash = new List(); var coneInnerAngle = new List(); var coneOuterAngleOrCapExt = new List(); var coronaIntensity = new List(); ushort numStreetLights = 0; foreach (var light in lights) { position.Add(light.position); colour.Add(light.colour); direction.Add(light.direction); falloff.Add(light.falloff); falloffExponent.Add(light.falloffExponent); timeAndStateFlags.Add(light.timeAndStateFlags); hash.Add(light.hash); coneInnerAngle.Add(light.coneInnerAngle); coneOuterAngleOrCapExt.Add(light.coneOuterAngleOrCapExt); coronaIntensity.Add(light.coronaIntensity); if (light.isStreetLight) numStreetLights++; } UpdateStatus("Creating new ymap files..."); var lodymap = new YmapFile(); var distymap = new YmapFile(); var ll = new YmapLODLights(); var dl = new YmapDistantLODLights(); var cdl = new CDistantLODLight(); cdl.category = 1;//0=small, 1=med, 2=large cdl.numStreetLights = numStreetLights; dl.CDistantLODLight = cdl; dl.positions = position.ToArray(); dl.colours = colour.ToArray(); ll.direction = direction.ToArray(); ll.falloff = falloff.ToArray(); ll.falloffExponent = falloffExponent.ToArray(); ll.timeAndStateFlags = timeAndStateFlags.ToArray(); ll.hash = hash.ToArray(); ll.coneInnerAngle = coneInnerAngle.ToArray(); ll.coneOuterAngleOrCapExt = coneOuterAngleOrCapExt.ToArray(); ll.coronaIntensity = coronaIntensity.ToArray(); lodymap._CMapData.flags = 0; distymap._CMapData.flags = 2; lodymap._CMapData.contentFlags = 128; distymap._CMapData.contentFlags = 256; lodymap._CMapData.entitiesExtentsMin = eemin; lodymap._CMapData.entitiesExtentsMax = eemax; lodymap._CMapData.streamingExtentsMin = semin - 1000f; lodymap._CMapData.streamingExtentsMax = semax + 1000f; //vanilla = ~1km distymap._CMapData.entitiesExtentsMin = eemin; distymap._CMapData.entitiesExtentsMax = eemax; distymap._CMapData.streamingExtentsMin = semin - 5000f; //make it huge distymap._CMapData.streamingExtentsMax = semax + 5000f; //vanilla = ~3km lodymap.LODLights = ll; distymap.DistantLODLights = dl; var lodname = pname + "_lodlights"; var distname = pname + "_distantlights"; lodymap.Name = lodname; lodymap._CMapData.name = JenkHash.GenHash(lodname); lodymap.RpfFileEntry = new RpfResourceFileEntry(); lodymap.RpfFileEntry.Name = lodname + ".ymap"; lodymap.RpfFileEntry.NameLower = lodname + ".ymap"; distymap.Name = distname; distymap._CMapData.name = JenkHash.GenHash(distname); distymap.RpfFileEntry = new RpfResourceFileEntry(); distymap.RpfFileEntry.Name = distname + ".ymap"; distymap.RpfFileEntry.NameLower = distname + ".ymap"; lodymap._CMapData.parent = distymap._CMapData.name; UpdateStatus("Adding new ymap files to project..."); ProjectForm.Invoke((MethodInvoker)delegate { ProjectForm.AddYmapToProject(lodymap); ProjectForm.AddYmapToProject(distymap); }); var stats = ""; UpdateStatus("Process complete. " + stats); GenerateComplete(); }); } private uint GetLightHash(YmapEntityDef ent, int lightIndex) { unchecked { //var aabb1 = GetAABB(ent); var aabb = GetAABB2(ent); var hashData = new int[7]; hashData[0] = (int)(aabb.Min.X * 10.0f); hashData[1] = (int)(aabb.Min.Y * 10.0f); hashData[2] = (int)(aabb.Min.Z * 10.0f); hashData[3] = (int)(aabb.Max.X * 10.0f); hashData[4] = (int)(aabb.Max.Y * 10.0f); hashData[5] = (int)(aabb.Max.Z * 10.0f); hashData[6] = lightIndex; int v3 = 7; int v4 = 0;//=hashData index int v5 = (int)0xDEADBEEF + 28;// -559038709; int v6 = (int)0xDEADBEEF + 28; int v7 = (int)0xDEADBEEF + 28; uint v8 = 2; do { int v9 = hashData[v4 + 2] + v5; int v10 = hashData[v4 + 1] + v6; int v11 = hashData[v4 + 0] - v9; int v13 = v10 + v9; int v14 = (v7 + v11) ^ RotateLeft(v9, 4); int v17 = v13 + v14; int v18 = (v10 - v14) ^ RotateLeft(v14, 6); int v21 = v17 + v18; int v22 = (v13 - v18) ^ RotateLeft(v18, 8); int v25 = v21 + v22; int v26 = (v17 - v22) ^ RotateLeft(v22, 16); int v29 = (v21 - v26) ^ RotateRight(v26, 13); int v30 = v25 - v29; v7 = v25 + v26; v6 = v7 + v29; v5 = v30 ^ RotateLeft(v29, 4); v4 += 3; v3 -= 3; --v8; } while (v8 > 0); int v32 = v3 - 1; //should always be 0 if (v32 != 0) { } int v50 = v7 + hashData[v4]; int v34 = (v6 ^ v5) - RotateLeft(v6, 14); int v35 = (v34 ^ v50) - RotateLeft(v34, 11); int v36 = (v35 ^ v6) - RotateRight(v35, 7); int v37 = (v36 ^ v34) - RotateLeft(v36, 16); int v51 = (v35 ^ v37) - RotateLeft(v37, 4); int v38 = (v51 ^ v36) - RotateLeft(v51, 14); int v53 = (v38 ^ v37) - RotateRight(v38, 8); return (uint)v53; } } private AABB_s GetAABB(YmapEntityDef ent) { var arch = ent.Archetype; var ori = ent.Orientation; Vector3 bbmin = ent.Position - ent.BSRadius; //sphere Vector3 bbmax = ent.Position + ent.BSRadius; if (arch != null) { Vector3[] c = new Vector3[8]; Vector3 abmin = arch.BBMin * ent.Scale; //entity box Vector3 abmax = arch.BBMax * ent.Scale; c[0] = abmin; c[1] = new Vector3(abmin.X, abmin.Y, abmax.Z); c[2] = new Vector3(abmin.X, abmax.Y, abmin.Z); c[3] = new Vector3(abmin.X, abmax.Y, abmax.Z); c[4] = new Vector3(abmax.X, abmin.Y, abmin.Z); c[5] = new Vector3(abmax.X, abmin.Y, abmax.Z); c[6] = new Vector3(abmax.X, abmax.Y, abmin.Z); c[7] = abmax; bbmin = new Vector3(float.MaxValue); bbmax = new Vector3(float.MinValue); for (int j = 0; j < 8; j++) { Vector3 corn = ori.Multiply(c[j]) + ent.Position; bbmin = Vector3.Min(bbmin, corn); bbmax = Vector3.Max(bbmax, corn); } } AABB_s b = new AABB_s(); b.Min = new Vector4(bbmin, 0f); b.Max = new Vector4(bbmax, 0f); return b; } private AABB_s GetAABB2(YmapEntityDef ent) { var arch = ent.Archetype; var ori = ent.Orientation; var pos = ent.Position; var sca = ent.Scale; var mat = Matrix.Transformation(Vector3.Zero, Quaternion.Identity, sca, Vector3.Zero, ori, pos); var matabs = mat; matabs.Column1 = mat.Column1.Abs(); matabs.Column2 = mat.Column2.Abs(); matabs.Column3 = mat.Column3.Abs(); matabs.Column4 = mat.Column4.Abs(); Vector3 bbmin = pos - ent.BSRadius; //sphere Vector3 bbmax = pos + ent.BSRadius; if (arch != null) { var bbcenter = (arch.BBMax + arch.BBMin) * 0.5f; var bbextent = (arch.BBMax - arch.BBMin) * 0.5f; var ncenter = Vector3.TransformCoordinate(bbcenter, mat); var nextent = Vector3.TransformNormal(bbextent, matabs); bbmin = ncenter - nextent; bbmax = ncenter + nextent; } AABB_s b = new AABB_s(); b.Min = new Vector4(bbmin, 0f); b.Max = new Vector4(bbmax, 0f); return b; } private static uint RotateLeft(uint value, int count) { return (value << count) | (value >> (32 - count)); } private static uint RotateRight(uint value, int count) { return (value >> count) | (value << (32 - count)); } private static int RotateLeft(int value, int count) { return (int)RotateLeft((uint)value, count); } private static int RotateRight(int value, int count) { return (int)RotateRight((uint)value, count); } public class Light { public MetaVECTOR3 position { get; set; } public uint colour { get; set; } public MetaVECTOR3 direction { get; set; } public float falloff { get; set; } public float falloffExponent { get; set; } public uint timeAndStateFlags { get; set; } public uint hash { get; set; } public byte coneInnerAngle { get; set; } public byte coneOuterAngleOrCapExt { get; set; } public byte coronaIntensity { get; set; } public bool isStreetLight { get; set; } } } }