Download CommonLua/Billboards.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 14.6 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Billboards.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/Billboards.lua
-
curl -L -o Billboards.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Billboards.lua
14.6 kB
| --- | |
| --- Sets up the rendering environment for capturing billboards. | |
| --- | |
| --- This function performs the following steps: | |
| --- - Changes the current map to an empty map | |
| --- - Waits for 5 frames | |
| --- - Activates the `cameraMax` camera and sets its viewport, field of view, and locks it | |
| --- - Changes the video mode to the billboard screenshot capture size | |
| --- - Configures various rendering settings for billboard capture, such as: | |
| --- - Setting the object LOD cap to 100 | |
| --- - Disabling terrain rendering | |
| --- - Disabling auto-exposure | |
| --- - Disabling subsurface scattering | |
| --- - Setting the resolution to 100% with SMAA upscaling | |
| --- - Disabling shadows | |
| --- - Setting the near and far planes to 100 and 100,000 respectively | |
| --- - Enabling orthographic projection with a Y scale of 1000 | |
| --- - Deletes the current map and waits for 3 frames | |
| --- | |
| function SetupBillboardRendering() | |
| ChangeMap("__Empty") | |
| WaitNextFrame(5) | |
| cameraMax.Activate(1) | |
| camera.SetViewport(box(0, 0, 1000000, 1000000)) | |
| camera.SetFovX(83 * 60) | |
| camera.Lock(1) | |
| ChangeVideoMode(hr.BillboardScreenshotCaptureSize, hr.BillboardScreenshotCaptureSize, 0, false, false) | |
| table.change(hr, "BillboardCapture", | |
| {ObjectLODCapMax=100, ObjectLODCapMin=100, RenderBillboards=0, RenderTerrain=0, AutoExposureMode=0, | |
| EnableSubsurfaceScattering=0, ResolutionPercent=100, ResolutionUpscale="smaa", MaxFps=0, Shadowmap=0, | |
| NearZ=100, FarZ=100000, Ortho=1, OrthoYScale=1000}) | |
| MapDelete("map", nil) | |
| WaitNextFrame(3) | |
| end | |
| --- | |
| --- Defines a class for billboard objects. | |
| --- | |
| --- This class inherits from `EntityClass` and has the following properties: | |
| --- | |
| --- - `flags`: A table of flags, including `efHasBillboard` which indicates that this object has a billboard. | |
| --- - `ignore_axis_error`: A boolean flag that determines whether to ignore errors related to the object's axis. | |
| --- | |
| DefineClass.BillboardObject = {__parents={"EntityClass"}, flags={efHasBillboard=true}, ignore_axis_error=false} | |
| --- | |
| --- Returns an error message if the billboard object has an invalid axis. | |
| --- | |
| --- If the object has a billboard (`efHasBillboard` flag is set) and `ignore_axis_error` is false, this function checks the object's visual axis. If the X or Y axis is non-zero, or the Z axis is non-positive, it returns an error message indicating that the billboard object should have the default axis. | |
| --- | |
| --- @return string|nil An error message if the billboard object has an invalid axis, or nil if the axis is valid or `ignore_axis_error` is true. | |
| function BillboardObject:GetError() | |
| end | |
| function BillboardObject:GetError() | |
| if self:GetEnumFlags(const.efHasBillboard) ~= 0 and not self.ignore_axis_error then | |
| local x, y, z = self:GetVisualAxisXYZ() | |
| if x ~= 0 or y ~= 0 or z <= 0 then | |
| return "Billboard objects should have default axis" | |
| end | |
| end | |
| end | |
| --- | |
| --- Returns a sorted list of all `BillboardObject` classes and their descendants. | |
| --- | |
| --- This function traverses the class hierarchy starting from the `BillboardObject` class, and collects all valid entities that are instances of `BillboardObject` or its descendants. The resulting list is sorted by the class name. | |
| --- | |
| --- @return table A table of `BillboardObject` class definitions, sorted by class name. | |
| function BillboardsTree() | |
| end | |
| function BillboardsTree() | |
| local billboard_classes = {} | |
| ClassDescendantsList("BillboardObject", function(name, classdef, billboard_classes) | |
| if IsValidEntity(classdef:GetEntity()) then | |
| table.insert(billboard_classes, classdef) | |
| end | |
| end, billboard_classes) | |
| table.sortby_field(billboard_classes, "class") | |
| return billboard_classes | |
| end | |
| --- | |
| --- Bakes a billboard for the selected object in the GED. | |
| --- | |
| --- This function resolves the selected object in the GED and then calls `BakeEntityBillboard` to generate a billboard for that object. | |
| --- | |
| --- @param ged The GED object. | |
| function GedBakeBillboard(ged) | |
| end | |
| function GedBakeBillboard(ged) | |
| local obj = ged:ResolveObj("SelectedObject") | |
| if not obj then | |
| return | |
| end | |
| BakeEntityBillboard(obj:GetEntity()) | |
| end | |
| --- | |
| --- Bakes a billboard for the specified entity. | |
| --- | |
| --- This function generates a billboard for the given entity by executing an external command. The command is constructed using the entity's name and executed asynchronously. If an error occurs during the billboard generation, it is printed to the console. | |
| --- | |
| --- @param entity string The name of the entity to generate a billboard for. | |
| function BakeEntityBillboard(entity) | |
| end | |
| function BakeEntityBillboard(entity) | |
| if not entity then | |
| return | |
| end | |
| local cmd = string.format("cmd /c Build GenerateBillboards --billboard_entity=%s", entity) | |
| local dir = ConvertToOSPath("svnProject/") | |
| local err = AsyncExec(cmd, dir, true, true) | |
| if err then | |
| print("Failed to create billboard for %s: %s", entity, err) | |
| end | |
| end | |
| --- | |
| --- Spawns a billboard object at the cursor position, with a random offset. | |
| --- | |
| --- This function resolves the selected object in the GED and then places multiple instances of that object in a grid pattern around the cursor position, with a random offset applied to each instance. | |
| --- | |
| --- @param ged The GED object. | |
| function GedSpawnBillboard(ged) | |
| end | |
| function GedSpawnBillboard(ged) | |
| local obj = ged:ResolveObj("SelectedObject") | |
| if not obj then | |
| return | |
| end | |
| local pos = GetTerrainCursorXY(UIL.GetScreenSize() / 2) | |
| local step = 20 * guim | |
| SuspendPassEdits("spawn billboards") | |
| for y = -50, 50 do | |
| for x = -50, 50 do | |
| local o = PlaceObject(obj.class) | |
| local curr_pos = pos + point(x * step + (AsyncRand(21) - 11) * guim, y * step + (AsyncRand(21) - 11) * guim) | |
| local real_pos = point(curr_pos:x(), curr_pos:y(), terrain.GetHeight(curr_pos:x(), curr_pos:y())) | |
| o:SetPos(curr_pos) | |
| end | |
| end | |
| ResumePassEdits("spawn billboards") | |
| end | |
| --- | |
| --- Spawns a grid of billboard objects around the cursor position, with a random offset. | |
| --- | |
| --- This function resolves the selected object in the GED and then places multiple instances of that object in a grid pattern around the cursor position, with a random offset applied to each instance. This can be used to debug billboard rendering. | |
| --- | |
| --- @param ged The GED object. | |
| function GedDebugBillboards(ged) | |
| end | |
| function GedDebugBillboards(ged) | |
| hr.BillboardDebug = 1 | |
| hr.BillboardDistanceModifier = 10000 | |
| hr.ObjectLODCapMax = 100 | |
| hr.ObjectLODCapMin = 100 | |
| local pos = GetTerrainCursorXY(UIL.GetScreenSize() / 2) | |
| local step = 12 * guim | |
| local billboard_entities = {} | |
| for k, v in ipairs(GetClassAndDescendantsEntities("BillboardObject")) do | |
| if IsValidEntity(v) then | |
| billboard_entities[#billboard_entities + 1] = v | |
| end | |
| end | |
| local i = 1 | |
| for y = -10, 10 do | |
| for x = -5, 5 do | |
| local entity = billboard_entities[i] | |
| if i == #billboard_entities then | |
| i = 0 | |
| end | |
| i = i + 1 | |
| local o = PlaceObject(entity) | |
| local curr_pos = pos + point(x * step * 2, y * step) | |
| local real_pos = point(curr_pos:x(), curr_pos:y(), terrain.GetHeight(curr_pos:x(), curr_pos:y())) | |
| o:SetPos(curr_pos) | |
| end | |
| end | |
| end | |
| --- | |
| --- Generates billboards for all billboard objects in the game. | |
| --- | |
| --- This function executes an external command to generate billboards for all billboard objects in the game. It uses the `GenerateBillboards` function to create the billboards. | |
| --- | |
| --- @param ged The GED object. | |
| function GedBakeAllBillboards(ged) | |
| end | |
| function GedBakeAllBillboards(ged) | |
| local cmd = string.format("cmd /c Build GenerateBillboards") | |
| local dir = ConvertToOSPath("svnProject/") | |
| local err = AsyncExec(cmd, dir, true, true) | |
| if err then | |
| print("Failed to create billboards!") | |
| end | |
| end | |
| --- | |
| --- Generates billboards for all billboard objects in the game. | |
| --- | |
| --- This function executes an external command to generate billboards for all billboard objects in the game. It uses the `GenerateBillboards` function to create the billboards. | |
| --- | |
| --- @param ged The GED object. | |
| function GedBakeAllBillboards(ged) | |
| end | |
| function GenerateBillboards(specific_entity) | |
| CreateRealTimeThread(function() | |
| SetupBillboardRendering() | |
| local billboard_entities = {} | |
| if specific_entity then | |
| billboard_entities[specific_entity] = true | |
| else | |
| ClassDescendantsList("BillboardObject", function(name, classdef, billboard_entities) | |
| local ent = classdef:GetEntity() | |
| if IsValidEntity(ent) then | |
| billboard_entities[ent] = true | |
| end | |
| end, billboard_entities) | |
| end | |
| local o = PlaceObject("Shapeshifter") | |
| o:SetPos(point(0, 0)) | |
| local OctahedronSize = hr.BillboardScreenshotGridWidth - 1 | |
| local screenshot_downsample = hr.BillboardScreenshotCaptureSize / hr.BillboardScreenshotSize | |
| local unneeded_lods | |
| local power = 1 | |
| for i = 0, 10 do | |
| if power == screenshot_downsample then | |
| unneeded_lods = i | |
| break | |
| end | |
| power = power * 2 | |
| end | |
| local dir = ConvertToOSPath("svnAssets/BuildCache/win32/Billboards/") | |
| AsyncCreatePath("svnAssets/BuildCache/win32/Billboards/") | |
| for ent, _ in pairs(billboard_entities) do | |
| hr.MipmapLodBias = unneeded_lods * 1000 | |
| o:ChangeEntity(ent) | |
| local bbox = o:GetEntityBBox() | |
| local bbox_center = bbox:Center() | |
| local camera_target = o:GetVisualPos() + bbox_center | |
| WaitNextFrame(5) | |
| local dlc_name = EntitySpecPresets[ent].save_in | |
| if dlc_name ~= "" then | |
| dlc_name = dlc_name .. "\\" | |
| end | |
| local curr_dir = dir .. dlc_name | |
| local err = AsyncCreatePath(curr_dir) | |
| assert(not err) | |
| local _, radius = o:GetBSphere() | |
| local draw_radius = (radius * 173) / 100 | |
| local max_range = radius * OctahedronSize | |
| local half_max = (max_range * 173) / 100 + (hr.BillboardScreenshotGridWidth % 2 == 0 and 1 or 0) | |
| local bc_atlas = curr_dir .. ent .. "_bc.tga" | |
| local nm_atlas = curr_dir .. ent .. "_nm.tga" | |
| local rt_atlas = curr_dir .. ent .. "_rt.tga" | |
| local siao_atlas = curr_dir .. ent .. "_siao.tga" | |
| local depth_atlas = curr_dir .. ent .. "_dep.tga" | |
| local borders = curr_dir .. ent .. "_bor.dds" | |
| local id = 0 | |
| hr.OrthoX = radius * 2 | |
| BeginCaptureBillboardEntity(bc_atlas, nm_atlas, rt_atlas, siao_atlas, depth_atlas, borders) | |
| for y = 0, OctahedronSize do | |
| for x = 0, OctahedronSize do | |
| local curr_x, curr_y, curr_z = BillboardMap(x, y, OctahedronSize, half_max) | |
| local pos = SetLen(point(curr_x, curr_y, curr_z), draw_radius) | |
| SetCamera(camera_target + pos, camera_target) | |
| WaitNextFrame(1) | |
| CaptureBillboardFrame(draw_radius, id) | |
| WaitNextFrame(1) | |
| id = id + 1 | |
| end | |
| end | |
| WaitNextFrame(1) | |
| end | |
| WaitNextFrame(100) | |
| quit() | |
| end) | |
| end | |
| --- | |
| --- Checks if the given object has a billboard associated with it. | |
| --- | |
| --- @param obj table The object to check for a billboard. | |
| --- @return boolean True if the object has a billboard, false otherwise. | |
| function HasBillboard(obj) | |
| return hr.BillboardEntities and IsValid(obj) and IsValidEntity(obj:GetEntity()) | |
| and not not table.find(hr.BillboardEntities, obj:GetEntity()) | |
| end | |
| --- | |
| --- Gets a list of all billboard entities in the game. | |
| --- | |
| --- @param err_print function An optional function to call if there are any errors finding billboard entities. | |
| --- @return table A table of all valid billboard entity names. | |
| function GetBillboardEntities(err_print) | |
| if hr.BillboardDirectory then | |
| hr.BillboardDirectory = "Textures/Billboards/" | |
| local suffix = Platform.playstation and "_bc.hgt" or "_bc.dds" | |
| local err, textures = AsyncListFiles("Textures/Billboards", "*" .. suffix, "relative") | |
| local billboard_entities = {} | |
| for _, entity in ipairs(GetClassAndDescendantsEntities("BillboardObject")) do | |
| local check_texture = not Platform.developer or Platform.console or table.find(textures, entity .. suffix) | |
| if not check_texture then | |
| err_print("Entity %s is marked as a billboard entity, but has no billboard textures!", entity) | |
| end | |
| if IsValidEntity(entity) and check_texture then | |
| billboard_entities[#billboard_entities + 1] = entity | |
| end | |
| end | |
| hr.BillboardEntities = billboard_entities | |
| end | |
| end | |
| --- | |
| --- Stress tests the billboards in the game by randomly placing and removing tree objects. | |
| --- | |
| --- This function creates a real-time thread that continuously places and removes tree objects | |
| --- at random positions within a certain radius. The function keeps track of the number of | |
| --- objects placed and removed, and sleeps for a short period of time after every 1000 iterations. | |
| --- | |
| --- This function is likely used for testing and debugging purposes to ensure the billboards | |
| --- are rendering correctly and efficiently. | |
| --- | |
| --- @function StressTestBillboards | |
| --- @return nil | |
| function StressTestBillboards() | |
| CreateRealTimeThread(function() | |
| local count = 0 | |
| while true do | |
| local pos = point((1000 + AsyncRand(4144)) * guim, (1000 + AsyncRand(4144)) * guim) | |
| local o = MapGetFirst(pos:x(), pos:y(), 100, "Tree_01") | |
| if o then | |
| DoneObject(o) | |
| local new = PlaceObject("Tree_01") | |
| local curr_pos = point((1000 + AsyncRand(4144)) * guim, (1000 + AsyncRand(4144)) * guim) | |
| local real_pos = point(curr_pos:x(), curr_pos:y(), terrain.GetHeight(curr_pos:x(), curr_pos:y())) | |
| new:SetPos(real_pos) | |
| end | |
| count = count + 1 | |
| if count == 1000 then | |
| count = 0 | |
| Sleep(100) | |
| end | |
| end | |
| end) | |
| end | |
| function OnMsg.ClassesPostprocess() | |
| CreateRealTimeThread(function() | |
| GetBillboardEntities(function(...) printf("once", ...) end) | |
| end) | |
| end |