public RenderableDistantLODLights GetRenderableDistantLODLights(YmapDistantLODLights lights) { return(distlodlights.Get(lights)); }
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 <YmapFile> projectYmaps = ProjectForm.CurrentProjectFile.YmapFiles; var pname = NameTextBox.Text; Task.Run(() => { var lights = new List <Light>(); 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 < lightAttrs.Length; li++) { var la = lightAttrs[li]; //transform this light with the entity position and orientation //generate lights data from it! //gotta transform the light position by the given bone! annoying Bone bone = null; Matrix xform = Matrix.Identity; int boneidx = 0; var skeleton = dwbl.Skeleton; if (skeleton?.Bones?.Data != null) { for (int j = 0; j < skeleton.Bones.Data.Count; j++) { var tbone = skeleton.Bones.Data[j]; if (tbone.Tag == la.BoneId) { boneidx = j; bone = tbone; break; } } if (bone != null) { var modeltransforms = skeleton.Transformations; var fragtransforms = fdwbl?.OwnerFragmentPhys?.OwnerFragPhysLod?.FragTransforms?.Data; var fragtransformid = fdwbl?.OwnerFragmentPhys?.OwnerFragPhysIndex ?? 0; var fragoffset = fdwbl?.OwnerFragmentPhys?.OwnerFragPhysLod.Unknown_30h ?? Vector4.Zero; fragoffset.W = 0.0f; if ((fragtransforms != null) && (fragtransformid < fragtransforms.Length)) { xform = fragtransforms[fragtransformid]; xform.Row4 += fragoffset; } else { //when using the skeleton's matrices, they need to be transformed by parent xform = modeltransforms[boneidx]; xform.Column4 = Vector4.UnitW; //xform = Matrix.Identity; short[] pinds = skeleton.ParentIndices; short parentind = ((pinds != null) && (boneidx < pinds.Length)) ? pinds[boneidx] : (short)-1; while ((parentind >= 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 = la.Position; Vector3 ldir = la.Direction; 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 = (uint)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.Extent.X, la.Extent.Y), la.Extent.Z); 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 <MetaVECTOR3>(); var colour = new List <uint>(); var direction = new List <MetaVECTOR3>(); var falloff = new List <float>(); var falloffExponent = new List <float>(); var timeAndStateFlags = new List <uint>(); var hash = new List <uint>(); var coneInnerAngle = new List <byte>(); var coneOuterAngleOrCapExt = new List <byte>(); var coronaIntensity = new List <byte>(); 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 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 <YmapFile> projectYmaps = ProjectForm.CurrentProjectFile.YmapFiles; var pname = NameTextBox.Text; Task.Run(() => { var lights = new List <Light>(); 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) { var fphys = (dwbl as FragDrawable)?.OwnerFragmentPhys; ent.EnsureLights(dwbl); var elights = ent.Lights; if (elights != null) { for (int li = 0; li < elights.Length; li++) { var elight = elights[li]; var la = elight.Attributes; uint r = la.ColorR; uint g = la.ColorG; uint b = la.ColorB; uint i = (byte)Math.Max(Math.Min(Math.Round(la.Intensity * 5.3125f), 255), 0);//5.1=255/48 uint c = (i << 24) + (r << 16) + (g << 8) + b; uint h = elight.Hash; //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 = (uint)la.Type; uint unk = isStreetLight ? 1u : 0;//2 bits - isStreetLight low bit, unk high bit uint t = la.TimeFlags | (type << 26) | (unk << 24); var inner = (byte)Math.Round(la.ConeInnerAngle * 1.4117647f); var outer = (byte)Math.Round(la.ConeOuterAngle * 1.4117647f); if (type == 4) { outer = (byte)Math.Max(Math.Max(la.Extent.X, la.Extent.Y), la.Extent.Z); } var light = new Light(); light.position = new MetaVECTOR3(elight.Position); light.colour = c; light.direction = new MetaVECTOR3(elight.Direction); light.falloff = la.Falloff; light.falloffExponent = la.FalloffExponent; light.timeAndStateFlags = t; light.hash = h; light.coneInnerAngle = inner; light.coneOuterAngleOrCapExt = outer; 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 <MetaVECTOR3>(); var colour = new List <uint>(); var direction = new List <MetaVECTOR3>(); var falloff = new List <float>(); var falloffExponent = new List <float>(); var timeAndStateFlags = new List <uint>(); var hash = new List <uint>(); var coneInnerAngle = new List <byte>(); var coneOuterAngleOrCapExt = new List <byte>(); var coronaIntensity = new List <byte>(); 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(); distymap.DistantLODLights = dl; lodymap.LODLights = ll; lodymap.Parent = distymap; cdl.category = 1;//0=small, 1=med, 2=large cdl.numStreetLights = numStreetLights; dl.CDistantLODLight = cdl; dl.positions = position.ToArray(); dl.colours = colour.ToArray(); dl.Ymap = distymap; dl.CalcBB(); 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(); ll.Ymap = lodymap; ll.BuildLodLights(dl); ll.CalcBB(); ll.BuildBVH(); lodymap.CalcFlags(); lodymap.CalcExtents(); distymap.CalcFlags(); distymap.CalcExtents(); 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; lodymap.Loaded = true; distymap.Loaded = true; 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 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("navmeshes") + "\\"; GenerateButton.Enabled = false; List <YmapFile> projectYmaps = ProjectForm.CurrentProjectFile.YmapFiles; var pname = NameTextBox.Text; Task.Run(() => { var position = new List <MetaVECTOR3>(); var colour = new List <uint>(); var direction = new List <MetaVECTOR3>(); var falloff = new List <float>(); var falloffExponent = new List <float>(); var timeAndStateFlags = new List <uint>(); var hash = new List <uint>(); var coneInnerAngle = new List <byte>(); var coneOuterAngleOrCapExt = new List <byte>(); var coronaIntensity = new List <byte>(); var eemin = new Vector3(float.MaxValue); var eemax = new Vector3(float.MinValue); var semin = new Vector3(float.MaxValue); var semax = new Vector3(float.MinValue); foreach (var ymap in projectYmaps) { 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; } else if (fdwbl != null) { lightAttrs = fdwbl.OwnerFragment?.LightAttributes; } 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); foreach (var la in lightAttrs) { //transform this light with the entity position and orientation //generate lights data from it! //gotta transform the light position by the given bone! annoying Bone bone = null; Matrix xform = Matrix.Identity; int boneidx = 0; var skeleton = dwbl.Skeleton; if (skeleton?.Bones?.Data != null) { for (int j = 0; j < skeleton.Bones.Data.Count; j++) { var tbone = skeleton.Bones.Data[j]; if (tbone.Id == la.BoneId) { boneidx = j; bone = tbone; break; } } if (bone != null) { var modeltransforms = skeleton.Transformations; var fragtransforms = fdwbl?.OwnerFragmentPhys?.OwnerFragPhysLod?.FragTransforms?.Data; var fragtransformid = fdwbl?.OwnerFragmentPhys?.OwnerFragPhysIndex ?? 0; var fragoffset = fdwbl?.OwnerFragmentPhys?.OwnerFragPhysLod.Unknown_30h ?? Vector4.Zero; fragoffset.W = 0.0f; if ((fragtransforms != null) && (fragtransformid < fragtransforms.Length)) { xform = fragtransforms[fragtransformid]; xform.Row4 += fragoffset; } else { //when using the skeleton's matrices, they need to be transformed by parent xform = modeltransforms[boneidx]; xform.Column4 = Vector4.UnitW; //xform = Matrix.Identity; ushort[] pinds = skeleton.ParentIndices; ushort parentind = ((pinds != null) && (boneidx < pinds.Length)) ? pinds[boneidx] : (ushort)65535; while (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] : (ushort)65535; } } } } 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 = 123456; //TODO: what hash to use??? //@Calcium: //1 = point //2 = spot //4 = capsule uint type = 1; uint t = la.TimeFlags + (type << 26); var maxext = (byte)Math.Max(Math.Max(la.ExtentX, la.ExtentY), la.ExtentZ); position.Add(new MetaVECTOR3(epos)); colour.Add(c); direction.Add(new MetaVECTOR3(edir)); falloff.Add(la.Falloff); falloffExponent.Add(la.FalloffExponent); timeAndStateFlags.Add(t); hash.Add(h); coneInnerAngle.Add((byte)la.ConeInnerAngle); coneOuterAngleOrCapExt.Add(Math.Max((byte)la.ConeOuterAngle, maxext)); coronaIntensity.Add((byte)la.CoronaIntensity); } } } } } if (position.Count == 0) { MessageBox.Show("No lights found in project!"); return; } var lodymap = new YmapFile(); var distymap = new YmapFile(); var ll = new YmapLODLights(); var dl = new YmapDistantLODLights(); var cdl = new CDistantLODLight(); cdl.category = 1; 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; distymap._CMapData.entitiesExtentsMin = eemin; distymap._CMapData.entitiesExtentsMax = eemax; distymap._CMapData.streamingExtentsMin = semin - 5000f; //make it huge distymap._CMapData.streamingExtentsMax = semax + 5000f; 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; ProjectForm.Invoke((MethodInvoker) delegate { ProjectForm.AddYmapToProject(lodymap); ProjectForm.AddYmapToProject(distymap); }); var stats = ""; UpdateStatus("Process complete. " + stats); GenerateComplete(); }); }