Download CommonLua/Classes/Particles/ParticleEmitter.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 21.1 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Classes/Particles/ParticleEmitter.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/Classes/Particles/ParticleEmitter.lua
-
curl -L -o ParticleEmitter.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Classes/Particles/ParticleEmitter.lua
21.1 kB
| config.ParticlesMaxBaseColorMapSize = 2048 | |
| config.ParticlesMaxNormalMapSize = 512 | |
| local function folder_fn(obj) | |
| return obj:GetTextureFolders() | |
| end | |
| local function filter_fn_texture(obj) | |
| return obj:GetTextureFilter() | |
| end | |
| local function filter_fn_normalmap(obj) | |
| return obj:GetNormalmapFilter() | |
| end | |
| DefineClass.ParticleEmitter = | |
| { | |
| __parents = { "ParticleBehavior" }, | |
| PropEditorCopy = true, | |
| EditorName = "Emitter", | |
| properties = { | |
| -- Base | |
| { id = "emit_detail_level", name = "Detail level category", category = "Base", default = ActionFXDetailLevelCombo()[1].value, editor = "combo", items = ActionFXDetailLevelCombo, help = "Determines the options detail levels at which the emitter is active. Essential will make it always active, Optional at high/medium setting, and EyeCandy at high setting only.", }, | |
| -- Emission | |
| { id = "enabled", name = "Enabled", category = "Emission Attributes", editor = "bool", dynamic = true, }, | |
| { id = "emit_fade", name = "Emit fading range (m)", category = "Emission Attributes", editor = "range", slider = true, min = 0, max = 15000, default = range( 0, 2000 ), help = "Camera distance interval in which particles will be emitted." }, | |
| { id = "max_live_count", name = "Max live count", category = "Emission Attributes", editor = "number", min = 1, dynamic = true, max = 5000 }, | |
| { id = "parts_per_sec", name = "Particles/sec", category = "Emission Attributes", editor = "number", scale = 100, dynamic = true, max = 12000000 }, | |
| { id = "parts_per_meter", name = "Particles/meter", category = "Emission Attributes", editor = "number", scale = 100, dynamic = true }, | |
| { id = "parts_per_meter_min_velocity", name = "Particles/meter treshold", category = "Emission Attributes", editor = "number", scale = 1000, default = 0, }, | |
| { id = "lifetime_min", name = "Lifetime min", category = "Emission Attributes", editor = "number", min = 0, scale = "sec", dynamic = true }, | |
| { id = "lifetime_max", name = "Lifetime max", category = "Emission Attributes", editor = "number", min = 0, scale = "sec", dynamic = true }, | |
| { id = "position", name = "Position", category = "Emission Attributes", editor = "point", scale = guim, dynamic = true }, | |
| { id = "angle", name = "Angle (degrees)", category = "Emission Attributes", editor = "range", slider = true, min = -360, max = 360 }, | |
| { id = "size_min", name = "Size min (m)", category = "Emission Attributes", editor = "number", min = 1, scale = 1000, dynamic = true }, | |
| { id = "size_max", name = "Size max (m)", category = "Emission Attributes", editor = "number", min = 1, scale = 1000, dynamic = true }, | |
| { id = "part_emit_modifier", name = "Emit Modifier", category = "Emission Attributes", editor = "number", min = 1, scale = 1000, dynamic = true }, | |
| -- Material | |
| { id = "geometry_building", name = "Geometry", category = "Material Attributes", editor = "combo", items = { "Particle", "Ribbon", "Decal" }, help = "What type of geometry is being emitted." }, | |
| { id = "decal_depth", name = "Decal Depth", category = "Material Attributes", editor = "number", scale = 1000, no_edit = function(self) return self.geometry_building ~= "Decal" end, dynamic = true }, | |
| { id = "decal_group", name = "Decal Group", category = "Material Attributes", editor = "choice", items = const.DecalGroups, no_edit = function(self) return self.geometry_building ~= "Decal" end, dynamic = true }, | |
| { id = "shader", name = "Shader", category = "Material Attributes", editor = "combo", items = { "Solid", "Blend", "Add", "Premultiplied", "Overlay", "Add Light", "Distortion", "Blend Exposed" } }, | |
| { id = "SourceTexture", name = "Texture", category = "Material Attributes", default = false, editor = "browse", folder = folder_fn, filter = filter_fn_texture, dont_save = true, }, | |
| { id = "texture", editor = "text", no_edit = true, }, | |
| { id = "TexturePreview", name = "Texture Preview", category = "Material Attributes", editor = "image", default = "", dont_save = true, | |
| img_size = 128, img_box = 1, img_comp = "rgba", img_back = RGB(0, 0, 0), | |
| img_polyline_color = RGB(128, 128, 128), img_polyline = function(self) return self.outlines end, img_polyline_closed = true }, | |
| { id = "filtering_bilinear", name = "Texture filtering: smooth", category = "Material Attributes", editor = "bool" }, | |
| { id = "normal_as_flow_map", name = "Normal map as Flow map", category = "Material Attributes", editor = "bool", default = false, no_edit = function(self) return self.shader == "Distortion" end, dynamic = true }, | |
| { id = "SourceNormalmap", name = "Normalmap", category = "Material Attributes", editor = "browse", folder = folder_fn, filter = filter_fn_normalmap, image_preview_size = 128, dont_save = true, default = false }, | |
| { id = "normalmap", editor = "text", no_edit = true, }, | |
| { id = "frames", name = "UV Frames", category = "Material Attributes", editor = "point2d", max = 8*8, help = "X * Y should be limited to 6x6" }, | |
| { id = "self_illum", name = "Self-Illumination", category = "Material Attributes", editor = "number", slider = true, min = 0, max = 200, no_edit = function(self) return self.shader == "Distortion" end, dynamic = true, help = "Shaded particles receive additional light based on the material color." }, | |
| { id = "light_softness", name = "NM Light Softness", category = "Material Attributes", editor = "number", slider = true, min = 0, max = 1000, no_edit = function(self) return self.shader == "Distortion" end, dynamic = true, help = "How soft should the light affecting this particle be" }, | |
| { id = "mat_roughness", name = "Material Roughness", category = "Material Attributes", editor = "number", min = 0, max = 100, slider = true, no_edit = function(self) return self.shader == "Distortion" end, dynamic = true, help = "Controls the roughness of the material surface. Value of 1 results in no specular reflection and an optimized shading path." }, | |
| { id = "mat_metallic", name = "Material Metallic", category = "Material Attributes", editor = "number", min = 0, max = 100, slider = true, no_edit = function(self) return self.shader == "Distortion" end, dynamic = true, help = "Controls the metallicness of the material surface." }, | |
| { id = "receive_shadow", name = "Receive Shadow", category = "Material Attributes", editor = "bool", default = false }, | |
| -- Flow map | |
| { id = "flow_speed", name = "Flow Speed", category = "Material Attributes", editor = "number", slider = true, min = 0, max = 1000, default = 0, help = "Speed of texture distortion", no_edit = function(self) return not self.normal_as_flow_map end, dynamic = true }, | |
| { id = "flow_scale", name = "Flow Scale", category = "Material Attributes", editor = "number", slider = true, min = 0, max = 1000, default = 0, help = "Scale of texture distortion", no_edit = function(self) return not self.normal_as_flow_map end, dynamic = true }, | |
| -- Transparency | |
| { id = "softness", name = "Softness (m)", no_edit = true, category = "Transparency Attributes", editor = "number", scale = 100, read_only = function(obj, prop_meta) return obj.ui end }, | |
| { id = "far_softness", name = "Terrain Distance Fade (m)", category = "Transparency Attributes", editor = "number", scale = 100, min = -1, read_only = function(obj, prop_meta) return obj.ui end }, | |
| { id = "near_softness", name = "Camera Distance Fade (m)", category = "Transparency Attributes", editor = "number", scale = 100, min = -1, read_only = function(obj, prop_meta) return obj.ui end }, | |
| { id = "far_softness_curve", name = "Far Softness %", category = "Transparency Attributes", editor = "curve4", fixedx = true, scale = 1000, min = 0, max = 1000, default = MakeLine(0, 1000), }, | |
| { id = "near_softness_curve", name = "Near Softness %", category = "Transparency Attributes", editor = "curve4", fixedx = true, scale = 1000, min = 0, max = 1000, default = MakeLine(0, 1000), }, | |
| { id = "viewangle_softness_curve", name = "View Angle Softness %", category = "Transparency Attributes", editor = "curve4", fixedx = true, scale = 1000, min = 0, max = 1000, default = MakeLine(1000, 1000), }, | |
| { id = "view_dependent_opacity", name = "View dependent opacity", category = "Transparency Attributes", editor = "number", slider = true, min = 0, max = 1000, default = 0, help = "Lowers particle opacity when viewed perpendicular to normal." }, | |
| { id = "alpha_test", name = "Alpha test (0-255)", category = "Transparency Attributes", editor = "number", slider = true, min = 0, max = 255, help = "Cut texture pixels with lesser alpha" }, | |
| { id = "alpha", name = "Alpha (0-255)", category = "Transparency Attributes", editor = "range", slider = true, min = 0, max = 255 }, | |
| { id = "interior", category = "Transparency Attributes", editor = "bool", default = true, help = "Render if inside buildings" }, | |
| { id = "exterior", category = "Transparency Attributes", editor = "bool", default = true, help = "Render if outside buildings"}, | |
| -- Distortion | |
| { id = "normal_to_distortion", name = "Normal map as Distortion map", category = "Material Attributes", editor = "bool", no_edit = function(self) return self.shader ~= "Distortion" end, dynamic = true }, | |
| { id = "distortion_mode", name = "Distortion mode", category = "Material Attributes", editor = "combo", items = { "Fixed", "Ramp", "Ping-Pong" }, help = "How does distortion change overt time", no_edit = function(self) return self.shader ~= "Distortion" end, dynamic = true }, | |
| { id = "distortion_start", name = "Distortion start", category = "Material Attributes", editor = "number", scale = "sec", help = "When does the animated distortion effect start", no_edit = function(self) return self.shader ~= "Distortion" end, dynamic = true }, | |
| { id = "distortion_end", name = "Distortion end", category = "Material Attributes", editor = "number", scale = "sec", help = "When does the animated distortion effect end", no_edit = function(self) return self.shader ~= "Distortion" end, dynamic = true }, | |
| { id = "distortion_scale", name = "Distortion scale (min)", category = "Material Attributes", editor = "number", min = -1000, max = 1000, slider = true, help = "The strength of the distortion effect", no_edit = function(self) return self.shader ~= "Distortion" end, dynamic = true }, | |
| { id = "distortion_scale_max", name = "Distortion scale (max)", category = "Material Attributes", editor = "number", min = -1000, max = 1000, slider = true, help = "The strength of the distortion effect", no_edit = function(self) return self.shader ~= "Distortion" end, dynamic = true }, | |
| -- Depth | |
| { id = "no_depth_test", name = "No depth test", category = "Depth Settings", editor = "bool", default = false, read_only = function(obj, prop_meta) return obj.ui end }, | |
| { id = "drawing_order", name = "Drawing order", category = "Depth Settings", editor = "number" }, | |
| { id = "depth_offset", name = "Depth offset", category = "Depth Settings", editor = "number", min = -10000, max = 10000, scale = "m", slider = true, help = "Camera relative offset of particles along the view direction" }, | |
| { id = "sort", name = "Sort particles", category = "Depth Settings", editor = "bool", help = "Should we sort the particles front to back to the camera or not (Blend mode only)!" }, | |
| -- no_edit | |
| { id = "probability", no_edit = true }, | |
| { id = "outlines", default = false, editor = "prop_table", no_edit = true }, | |
| { id = "texture_hash", default = false, editor = "number", no_edit = true }, | |
| { id = "ui", default = false, editor = "bool", no_edit = false }, | |
| { id = "mat_ice", name = "Material Ice", category = "Material Attributes", editor = "number", min = 0, max = 100, slider = true, no_edit = true, help = "Controls how much the material is affected by the Ice special effect." }, | |
| }, | |
| bins = set( "A" ), | |
| geometry_building = "Particle", | |
| time_start = 0, | |
| time_stop = -1000, | |
| time_period = 0, | |
| randomize_period = 0, | |
| enabled = true, | |
| max_live_count = 200, | |
| parts_per_sec = 5000, | |
| parts_per_meter = 0, | |
| lifetime_min = 1000, | |
| lifetime_max = 3000, | |
| texture = "", | |
| normalmap = "", | |
| self_illum = 0, | |
| light_softness = 800, | |
| decal_depth = 1000, | |
| decal_group = "Default", | |
| part_emit_modifier = 1000, | |
| frames = point(1, 1), | |
| drawing_order = 0, | |
| softness = 0, | |
| far_softness = -1, | |
| near_softness = -1, | |
| alpha_test = 0, | |
| shader = "Blend", | |
| position = point30, | |
| size_min = 1000, | |
| size_max = 2000, | |
| alpha = range( 255, 255 ), | |
| angle = range( 0, 0 ), | |
| filtering_bilinear = true, | |
| sort = true, | |
| normal_to_distortion = false, | |
| distortion_mode = "Fixed", | |
| distortion_scale = 0, | |
| distortion_scale_max = 0, | |
| distortion_start = 0, | |
| distortion_end = 0, | |
| velocity_min = 0, | |
| velocity_max = 0, | |
| mat_roughness = 100, | |
| mat_metallic = 0, | |
| mat_ice = 100, | |
| depth_offset = 0, | |
| } | |
| function ParticleEmitter:GetTextureFolders() | |
| return GetParentTable(self):GetTextureFolders() | |
| end | |
| function ParticleEmitter:GetTextureFilter() | |
| return "Texture (*.tga)|*.tga" | |
| end | |
| function ParticleEmitter:GetNormalmapFilter() | |
| return "Texture (*.norm.tga)|*.norm.tga" | |
| end | |
| function ParticleEmitter:Setframes(value) | |
| local x, y = value:xy() | |
| local max = table.find_value(self.properties, "id", "frames").max | |
| local frames = x * y | |
| if frames > max then | |
| x = max * x / frames | |
| y = max * y / frames | |
| assert(x * y <= max, "Frames not properly limit-scaled down") | |
| end | |
| self.frames = point(x, y) | |
| end | |
| function ParticleEmitter:GetColorForGed() | |
| return self.enabled and "32 128 32" or "169 18 23" | |
| end | |
| function ParticleEmitter:ShouldNormalizeTexturePath() | |
| return true | |
| end | |
| local function ConvertSlashes(path) | |
| --convert all '\' into '/' | |
| return string.gsub(path, "\\", "/") | |
| end | |
| local function EscapeMagicSymbols(path) | |
| --convert all gsub 'magic symbols' into escaped symbols | |
| return string.gsub(ConvertSlashes(path), "[%(%)%.%%%+%-%*%?%[%^%$]", "%%%1") | |
| end | |
| function ParticleEmitter:NormalizeTexturePath(texture_path) | |
| if not self:ShouldNormalizeTexturePath() then return texture_path end | |
| return texture_path | |
| end | |
| function ParticleEmitter:Settexture(texture) | |
| self.texture = self:NormalizeTexturePath(texture) | |
| end | |
| function ParticleEmitter:Setnormalmap(texture) | |
| self.normalmap = self:NormalizeTexturePath(texture) | |
| end | |
| function ParticleEmitter:GetAlphaPreview() | |
| return self.texture | |
| end | |
| function ParticleEmitter:GetTexturePreview() | |
| return self.texture | |
| end | |
| local function GetSegments(path) | |
| path = path:gsub("[\\/]+", '/') | |
| local segments = {} | |
| for segment in string.gmatch(path, '[^/]+') do | |
| table.insert(segments, segment) | |
| end | |
| return segments | |
| end | |
| local function GetRelativePath(path, base, game_path) | |
| if not path then return path end | |
| path = GetSegments(path) | |
| base = GetSegments(base) | |
| game_path = GetSegments(game_path) | |
| for key, value in ipairs(base) do | |
| if value ~= path[key] then | |
| return false | |
| end | |
| end | |
| local moved = table.move(path, #base + 1, #path, #game_path + 1, game_path) | |
| return table.concat(moved, '/') | |
| end | |
| function ParticleEmitter:GetTextureBasePath() | |
| return GetParentTable(self):GetTextureBasePath() | |
| end | |
| function ParticleEmitter:GetTextureTargetPath() | |
| return GetParentTable(self):GetTextureTargetPath() | |
| end | |
| function ParticleEmitter:GetTextureTargetGamePath() | |
| return GetParentTable(self):GetTextureTargetGamePath() | |
| end | |
| function ParticleEmitter:GetSourceTexture() | |
| if self.texture == "" or not self.texture then | |
| return "" | |
| end | |
| return self:GetTextureBasePath() .. self.texture | |
| end | |
| function ParticleEmitter:SetSourceTexture(value) | |
| self.texture = GetRelativePath(value, self:GetTextureBasePath() .. self:GetTextureTargetPath(), self:GetTextureTargetGamePath()) or "" | |
| end | |
| function ParticleEmitter:GetSourceNormalmap() | |
| if self.normalmap == "" or not self.normalmap then | |
| return "" | |
| end | |
| return self:GetTextureBasePath() .. self.normalmap | |
| end | |
| function ParticleEmitter:SetSourceNormalmap(value) | |
| self.normalmap = GetRelativePath(value, self:GetTextureBasePath() .. self:GetTextureTargetPath(), self:GetTextureTargetGamePath()) or "" | |
| end | |
| function ParticleEmitter:ScheduleGenerateOutlines() | |
| self.outlines = false | |
| CreateRealTimeThread(self.GenerateOutlines, self) | |
| end | |
| function ParticleEmitter:Setui(ui) | |
| self.ui = ui | |
| if ui then | |
| self.no_depth_test = true | |
| self.softness = 0 | |
| end | |
| end | |
| local outlines_cache = false | |
| local texture_to_hash = false | |
| function ClearOutlinesCache() | |
| outlines_cache = false | |
| texture_to_hash = false | |
| end | |
| function ParticleEmitter:GenerateOutlines(update_mode) | |
| if self.outlines and not update_mode then | |
| return | |
| end | |
| local texture = self.texture | |
| texture_to_hash = texture_to_hash or {} | |
| local texture_hash = texture_to_hash[texture] | |
| if texture_hash == nil then | |
| local err | |
| if texture ~= "" then | |
| err, texture_hash = AsyncFileToString(self:GetSourceTexture(), nil, nil, "hash") | |
| if err then | |
| print("Error", err, "while computing hash of", texture) | |
| end | |
| end | |
| texture_hash = texture_hash or false | |
| texture_to_hash[texture] = texture_hash | |
| end | |
| if update_mode == "update" and texture_hash == self.texture_hash then | |
| return | |
| end | |
| local outlines, generated | |
| if self.normal_to_distortion then | |
| outlines = {} | |
| else | |
| local param_hash = xxhash(texture, self.alpha_test, self.frames) | |
| outlines_cache = outlines_cache or {} | |
| outlines = outlines_cache[param_hash] | |
| if not outlines then | |
| generated = true | |
| local err | |
| err, outlines = TrimParticleTexture( texture, self.alpha_test, self.frames:x(), self.frames:y() ) | |
| outlines = outlines or {} | |
| if err then | |
| print("Outlines:", err) | |
| end | |
| outlines_cache[param_hash] = outlines | |
| local count = 0 | |
| for i=1,#outlines do | |
| count = count + #outlines[i] | |
| end | |
| if count > 256 then | |
| print("Too many outlines", count, "in", texture) | |
| end | |
| end | |
| end | |
| self.texture_hash = texture_hash | |
| self.outlines = outlines | |
| ObjModified(self) | |
| return true, generated | |
| end | |
| function ParticleEmitter:IsOutlineProp(prop_id) | |
| local outline_props = { | |
| normal_to_distortion = true, | |
| texture = true, | |
| SourceTexture = true, | |
| frames = true, | |
| alpha_test = true, | |
| } | |
| return outline_props[prop_id] | |
| end | |
| function ParticleEmitter:OnEditorSetProperty(prop_id, old_value, ged) | |
| local value = self:GetProperty(prop_id) | |
| if prop_id == "texture" then | |
| local w, h = UIL.MeasureImage(value) | |
| if (Max(w, h) > config.ParticlesMaxBaseColorMapSize) then | |
| ged:ShowMessage("Warning", "Diffuse texture is over the size limit " .. config.ParticlesMaxBaseColorMapSize .. "px. Please resize!") | |
| end | |
| elseif prop_id == "normalmap" then | |
| local w, h = UIL.MeasureImage(value) | |
| if (Max(w, h) > config.ParticlesMaxNormalMapSize) then | |
| ged:ShowMessage("Warning", "Normalmap texture is over the size limit " .. config.ParticlesMaxNormalMapSize .. "px. Please resize!") | |
| end | |
| elseif prop_id == "emit_detail_level" then | |
| if type(value) == "string" and tonumber(value) then | |
| self.emit_detail_level = tonumber(value) | |
| end | |
| end | |
| if self:IsOutlineProp(prop_id) then | |
| self:ScheduleGenerateOutlines() | |
| end | |
| end | |
| function ParticleEmitter:GetError() | |
| if self.emit_detail_level == ActionFXDetailLevelCombo()[1].value then | |
| return "Please set the 'Detail level category' property to specify at which detail levels this emitter should be active." | |
| end | |
| end | |
| function ParticleEmitter:OnEditorNew(preset, ged, is_paste) | |
| self:Setui(preset.ui) | |
| preset:OverrideEmitterFuncs(self) | |
| end | |
| function SaveParticleSystem(parsys) | |
| parsys:EnableDynamicToggles() | |
| parsys:Save() | |
| end | |
| function ReloadParticleTexture(filename) | |
| local parsyslist = GetParticleSystemList() | |
| for i=1,#parsyslist do | |
| local parsys = parsyslist[i] | |
| for j=1,#parsys do | |
| local behavior = parsys[j] | |
| if behavior:IsKindOf("ParticleEmitter") and behavior.texture == filename then | |
| DelayedCall( 500, SaveParticleSystem, parsys ) | |
| end | |
| end | |
| end | |
| end | |
| DefineClass.Rename = | |
| { | |
| __parents = { "ParticleBehavior" }, | |
| properties = | |
| { | |
| { id = "life_time", name = "Lifetime (sec)", editor = "number", min = 100, scale = 1000, dynamic = true }, | |
| { id = "remove_bins", name = "Remove Bins", editor = "set", items = { "A", "B", "C", "D", "E", "F", "G", "H" } }, | |
| { id = "add_bins", name = "Add Bins", editor = "set", items = { "A", "B", "C", "D", "E", "F", "G", "H" } }, | |
| { id = "reset_new_flag", name = "Reset New Flag", editor = "number" }, | |
| }, | |
| EditorName = "Rename", | |
| life_time = 1000, | |
| remove_bins = set(), | |
| add_bins = set(), | |
| reset_new_flag = 0, | |
| } | |
| if Platform.developer then | |
| function FindParticleMaxProperty(part_class, prop) | |
| local particles = GetParticleSystemList() | |
| local max | |
| for i = 1, #particles do | |
| local par_sys = particles[i] | |
| for k = 1, #par_sys do | |
| local par_beh = par_sys[k] | |
| if par_beh:IsKindOf(part_class) then | |
| --local dynamic = table.find_value(g_Classes[part_class].properties, "id", prop).dynamic | |
| --local value = dynamic and par_beh:GetParam(prop) or par_beh:GetProperty(prop) | |
| local value = par_beh:GetProperty(prop) | |
| value = type(value) == "number" and value or 0 | |
| if not max or value > max then | |
| max = value | |
| end | |
| end | |
| end | |
| end | |
| return max | |
| end | |
| function DumpPartStats() | |
| print("Max ParticleEmitter stats:") | |
| print("Frames: ", FindParticleMaxProperty("ParticleEmitter", "frames")) | |
| print("Max Live Count: ", FindParticleMaxProperty("ParticleEmitter", "max_live_count")) | |
| print("Particles/Sec: ", FindParticleMaxProperty("ParticleEmitter", "parts_per_sec")) | |
| end | |
| end |