├── Shadow_Modules.rbxm ├── Shadows_TestPlaceFile.rbxl ├── ShadowTest_GloballLight_Demo.rbxl ├── README.md └── Shadow.lua /Shadow_Modules.rbxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razorboot/luau-2016-shadow-engine/HEAD/Shadow_Modules.rbxm -------------------------------------------------------------------------------- /Shadows_TestPlaceFile.rbxl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razorboot/luau-2016-shadow-engine/HEAD/Shadows_TestPlaceFile.rbxl -------------------------------------------------------------------------------- /ShadowTest_GloballLight_Demo.rbxl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Razorboot/luau-2016-shadow-engine/HEAD/ShadowTest_GloballLight_Demo.rbxl -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LuaU 2016-2022 Shadow Engine 2 | 3 | ## Description: 4 | Built off of Egomooses Shadow Silhouttes Tutorial: https://scriptinghelpers.org/guides/silhouettes-and-shadows 5 | * An attempt at an optimized raycasted shadow-polygon implementation. 6 | * This is a complex set of object oriented mini-classes and polygonal algorithms for: 7 | * accurate n-gon to triangle conversion, 8 | * clipped polygons, 9 | * multiple light sources, 10 | * optimized vertex grabbing, 11 | * ray to plane intersection, 12 | * rotated shadow occluders and canvases, 13 | * world position shadows to 2d space, clipped, triangle-based shadows, 14 | * surface-based real-time lighting calculations: 15 | an attempt to make the system appear more well-integrated with shadow engine. 16 | * Inspired by early 2000's Shadow Volumes in games like Thief: Deadly Shadows and Doom III. 17 | * Manifold data-structure system was inspired by collision manifolds in AABB physics. 18 | This implementation is classless and includes nested arrays as a form of orginization. 19 | 20 | ## New Changes as of 12/3/22: 21 | * I can confidently say that, in the realm of cube-shapes parts, the current engine is complete! 22 | * Complex multi-surface shadows supported. 23 | * Rotated parts (occluders and canvases) now supported. 24 | * A mini lighting engine for rendering part surfaces is now added and functional in real-time! 25 | * This supports multiple lighting options you now have control over in Shadow script! 26 | * Shadows are now Surface-Gui based for more optimal results. 27 | 28 | ## Diagram: a visual of a fully-rendered scene and it's corresponding parts 29 | * ![Group 1](https://user-images.githubusercontent.com/103084464/205473222-2a12b90c-f2e0-41b8-bf54-d5a7161b5eba.png) 30 | ## Demos: Some test-scenes I made to showcase a few features the engine has 31 | * ![ezgif com-gif-maker (2)](https://user-images.githubusercontent.com/103084464/205485358-7d54b807-034b-492c-b648-823228a8d3fa.gif) 32 | * ![ezgif com-gif-maker (3)](https://user-images.githubusercontent.com/103084464/205485469-d6303061-9256-471b-8908-d844c90755e0.gif) 33 | 34 | 35 | 36 | ## Getting Started: 37 | ### If you plan on using the Engine itself: 38 | 1. First, download the ``Shadow_Modules.rbxmx`` file. 39 | 2. If you plan on using the engine client-sided, right click on ``StarterGui``, select ``Insert From File``, and select the ``Shadow_Modules.rbxmx`` file. 40 | * Keep in mind FilteringEnabled will have to be enabled in order to use the client-sided engine. 41 | 3. If you plan on using the engine server-sided, right click on ``Workspace``, select ``Insert From File``, and select the ``Shadow_Modules.rbxmx`` file. 42 | 43 | ### If you plan on using the place file: 44 | 1. First, download the ``ShadowTest_GloballLight_Demo.rbxl`` file. 45 | 2. If you plan on using the engine client-sided, go into ``ServerScriptService`` --> ``Scripts`` --> ``Main``. 46 | 3. Cut the code from this script into a ``LocalScript`` and read the comments inside of the script to know what to uncomment. 47 | 4. Place the new script back into the ``Scripts`` folder. 48 | 5. Next, you can drag both ``Modules`` and ``Scripts`` into ``StarterGui``. 49 | 6. By default, the rbxl file is already set up to be used server-sided. 50 | 51 | ### Include the Shadow module: 52 | 1. For client-sided, insert a ``LocalScript`` in ``StarterGui``. 53 | 2. For server-sided, insert a new ``Script`` in ``Workspace``. 54 | 3. To finally include the module, type: 55 | ```lua 56 | --# Include 57 | local Modules = --Location of the inserted Shadow_Modules.rbxmx file 58 | local Shadow = require(Modules:WaitForChild("Shadow")) 59 | ``` 60 | 61 | ## Creating Light and Shadow Instances: 62 | ### Shadow Canvases: 63 | * Shadow Canvases are SurfaceGui's that are mapped onto parts! 64 | * They are the surfaces of a part that can have shadows projected onto them. 65 | * Shadow Canvases are flat planes that can be applied to any face of a Part. 66 | * Shadow Canvases can be added to a part or model using: 67 | ```lua 68 | -- Add canvas to all surfaces of a part 69 | Shadow.setModelProperty(Part, "isShadowCanvasAll", true) 70 | -- Add canvas to all surfaces of a model 71 | Shadow.setPartProperty(Part, "isShadowCanvasAll", true) 72 | ``` 73 | * Setting the final parameter to ``false`` will delete a pre-existing canvas. 74 | * ``"isShadowCanvasAll"`` is one out of 6 surfaces. You can replace ``All`` in ``isShadowCanvasAll`` with either Top, Bottom, Right, Left, Front, or Back in order to add a Shadow Canvas to a single surface. 75 | 76 | ### Occluders: 77 | * Occluders are the Parts that block light sources, and thus cast shadows onto shadow canvases. 78 | * All occluders are treated as cubes, so any other shaped-part will be treated as such. 79 | * I'm currently working on adding more Part-types, so this won't be permanent! 80 | * A part or model can be set to an Occluder using: 81 | ```lua 82 | -- Set a part to an Occluder 83 | Shadow.setModelProperty(Part, "isShadowOccluder", true) 84 | -- Set a model to an Occluder 85 | Shadow.setPartProperty(Part, "isShadowOccluder", true) 86 | ``` 87 | * Exactly as the previous function, setting the final parameter to ``false`` will ensure the part won't cast shadows. 88 | 89 | ### Lit Surfaces: 90 | * This is an optional category that isn't essential for shadow creation. 91 | * A Lit Surfaces Part allows the brightness of the surfaces of a part to be rendered more accurately to light sources. 92 | * This is my attempt at making part surfaces look less jarring when compared to dark shadows. 93 | * The settings that control the color of part surfaces can be found inside of the ``Shadow`` module inside of the inserted ``Shadow_Modules.rbxmx`` file. 94 | * You can make a part or model have Lit Surfaces using: 95 | ```lua 96 | -- Allow the surfaces of a part to be lit 97 | Shadow.setPartProperty(Part, "hasLitSurfaces", true) 98 | -- Allow the surfaces of a model to be lit 99 | Shadow.setModelProperty(Part, "hasLitSurfaces", true) 100 | ``` 101 | * Lit Surfaces are stored into containers called ``LitPartManifolds``, this allows the script to easily work with them: 102 | ```lua 103 | local litPartManifolds = Shadow.getLitPartManifolds() 104 | ``` 105 | * Lit Surfaces need to be updated each frame in order to account for changes in position relevant to Light Sources: 106 | ```lua 107 | litPartManifolds = Shadow.updateLitPartManifolds(litPartManifolds) 108 | ``` 109 | 110 | ### Light Sources: 111 | * Light Sources is a list of Lights in your scene that you want to cast shadows and influence Lit Surfaces. 112 | * You can insert all light sources in a container using: 113 | ```lua 114 | Shadow.getAllLightSources(workspace) 115 | ``` 116 | * You can insert and remove a specific light source into your scene using: 117 | ```lua 118 | Shadow.insertLightSource(light) 119 | Shadow.removeLightSource(light) 120 | ``` 121 | * Light Sources need to be updated to account for changes in position and range, you can also update all light sources using: 122 | ```lua 123 | Shadow.updateAllLightSources() 124 | ``` 125 | 126 | ### Root/Canvas Manifolds: 127 | * When creating shadows, each Shadow Canvas is passed into a function that generates arrays with relevant information for the Occluders and Light Sources that will be taken into account. 128 | * To create a root manifold for all Shadow Canvases, use: 129 | ```lua 130 | local rootManifolds = Shadow.getRootManifolds() 131 | ``` 132 | * Root manifolds need to be updated each frame in order to account for changes in Position of the Shadow Canvas itself, Occluders, and Light Sources: 133 | ```lua 134 | rootManifolds = Shadow.updateRootManifolds(rootManifolds) 135 | ``` 136 | 137 | ### Experimental Mode: 138 | * Inside of the ``Shadow`` module, you'll find an option called ``useExperimental``. 139 | * This simply adds the ability for shadows to be rendered when Light Source to vertex rays don't intersect with the Shadow Canvas plane. 140 | * This option is buggy, so I suggest use it with caution. 141 | -------------------------------------------------------------------------------- /Shadow.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Built off of: https://scriptinghelpers.org/guides/silhouettes-and-shadows 3 | Created By: Razorboot 4 | Last Modified: 12/3/22 5 | Description: 6 | - An attempt at an optimized raycasted shadow-polygon implementation. 7 | - This is a complex set of object oriented mini-classes and polygonal algorithms for: 8 | - accurate n-gon to triangle conversion, 9 | - clipped polygons, 10 | - multiple light sources, 11 | - optimized vertex grabbing, 12 | - ray to plane intersection, 13 | - rotated shadow occluders and canvases, 14 | - world position shadows to 2d space, clipped, triangle-based shadows, 15 | - surface-based real-time lighting calculations: 16 | - an attempt to make the system appear more well-integrated with shadow engine. 17 | - Inspired by early 2000's Shadow Volumes in games like Thief: Deadly Shadows and Doom III. 18 | - Manifold data-structure system was inspired by collision manifolds in AABB physics. 19 | - This implementation is classless and includes nested arrays as a form of orginization. 20 | 21 | New Changes: 22 | - I can confidently say that, in the realm of cube-shapes parts, the current engine is complete! 23 | - Complex multi-surface shadows supported. 24 | - Rotated parts (occluders and canvases) now supported. 25 | - A mini lighting engine for rendering part surfaces is now added and functional in real-time! 26 | - This supports multiple lighting options you now have control over in Shadow script! 27 | - Shadows are now Surface-Gui based for more optimal results. 28 | - Global Lighting is now supported! Local lighting can also be disabled/enabled in module settings. 29 | --]] 30 | 31 | 32 | --# Services 33 | local Lighting = game:GetService("Lighting") 34 | 35 | 36 | --# Include 37 | local Modules = script.Parent 38 | local BaseFuncs = require(Modules:WaitForChild("BaseFuncs")) -- My own library with a collection of modern functions supported for old Roblox. 39 | local Poly = require(script:WaitForChild("Polygon")) -- My own polygon library for clipping polygons :] 40 | local Hull = require(script:WaitForChild("GiftWrapHull")) -- Used to order polygon vertices 41 | local Tris = require(script:WaitForChild("Triangle")) -- Used to create Instances of polygons as ImageLabels 42 | local Deluany = require(script:WaitForChild("Deluany")) -- Used to create Instances of polygons as ImageLabels 43 | 44 | 45 | --# Math references (optimization) 46 | local vec3 = Vector3.new 47 | local vec2 = Vector2.new 48 | local cf = CFrame.new 49 | local cfa = CFrame.Angles 50 | local udim2 = UDim2.new 51 | local c3 = Color3.new 52 | 53 | 54 | --# Instance References 55 | local hasShadowColorInstance = script:WaitForChild("hasShadowColor") 56 | local hasAmbientInstance = script:WaitForChild("hasAmbient") 57 | local hasShadowBrightnessInstance = script:WaitForChild("hasShadowBrightness") 58 | local hasGlobalShadowBrightnessInstance = script:WaitForChild("hasGlobalShadowBrightness") 59 | local hasGlobalLightingInstance = script:WaitForChild("hasGlobalLighting") 60 | local hasLocalLightingInstance = script:WaitForChild("hasLocalLighting") 61 | local useLightingPropertiesInstance = script:WaitForChild("useLightingProperties") 62 | local useExperimentalInstance = script:WaitForChild("useExperimental") 63 | 64 | 65 | --# Point 66 | local Shadow = {} 67 | 68 | 69 | --# Storage components - helps code run faster (less GetDescendants) 70 | Shadow.AllLightSources = {} 71 | 72 | 73 | --# Misc Variables 74 | local transparency = 0.5 75 | local ambient = 0.25 76 | local useLightingProperties = useLightingPropertiesInstance.Value 77 | local shadowColor = hasShadowColorInstance.Value 78 | local shadowColorIsMemberOfLighting = BaseFuncs.hasProperty(Lighting, "ShadowColor") 79 | local globalLightingEnabled = hasGlobalLightingInstance.Value 80 | local localLightingEnabled = hasLocalLightingInstance.Value 81 | 82 | local prevSunDir = Lighting:GetSunDirection() 83 | local sunUpdateNeeded = false 84 | local localUpdateNeeded = false 85 | 86 | 87 | --# Local Functions 88 | local function updateLighting() 89 | useLightingProperties = useLightingPropertiesInstance.Value 90 | 91 | if hasAmbientInstance.Value >= 0 then ambient = hasAmbientInstance.Value end 92 | if hasShadowBrightnessInstance.Value >= 0 then transparency = hasShadowBrightnessInstance.Value end 93 | shadowColor = hasShadowColorInstance.Value 94 | 95 | if Lighting:GetSunDirection() ~= prevSunDir then 96 | sunUpdateNeeded = true 97 | else 98 | sunUpdateNeeded = false 99 | end 100 | 101 | if globalLightingEnabled ~= hasGlobalLightingInstance.Value then 102 | globalLightingEnabled = hasGlobalLightingInstance.Value 103 | sunUpdateNeeded = true 104 | end 105 | 106 | if hasLocalLightingInstance.Value ~= localLightingEnabled then 107 | localLightingEnabled = hasLocalLightingInstance.Value 108 | localUpdateNeeded = true 109 | else 110 | localUpdateNeeded = false 111 | end 112 | 113 | if useLightingProperties == true then 114 | ambient = vec3(Lighting.Ambient.r, Lighting.Ambient.g, Lighting.Ambient.b) + vec3(Lighting.OutdoorAmbient.r, Lighting.OutdoorAmbient.g, Lighting.OutdoorAmbient.b) 115 | transparency = math.min(math.min(Lighting.Brightness, 5), 1) 116 | if globalLightingEnabled ~= Lighting.GlobalShadows then 117 | globalLightingEnabled = Lighting.GlobalShadows 118 | sunUpdateNeeded = true 119 | end 120 | globalLightingEnabled = Lighting.GlobalShadows 121 | if shadowColorIsMemberOfLighting then 122 | shadowColor = Lighting.ShadowColor 123 | end 124 | end 125 | end 126 | 127 | updateLighting() 128 | 129 | 130 | -- Vertex Information for unique cases 131 | local sphereVertices = {} 132 | table.insert(sphereVertices, {-0.35355335474014, 0.35355335474014, 0} ) 133 | table.insert(sphereVertices, {-0.24999997019768, 0.35355335474014, 0.24999997019768} ) 134 | table.insert(sphereVertices, {0, 0.35355335474014, 0.35355335474014} ) 135 | table.insert(sphereVertices, {-0.35355338454247, 0, -0.35355338454247} ) 136 | table.insert(sphereVertices, {0, 0.49999997019768, 0} ) 137 | table.insert(sphereVertices, {-0.24999997019768, 0.35355335474014, -0.24999997019768} ) 138 | table.insert(sphereVertices, {0.24999997019768, 0.35355335474014, 0.24999997019768} ) 139 | table.insert(sphereVertices, {-0.5, 0, 0} ) 140 | table.insert(sphereVertices, {0, -0.35355335474014, 0.35355335474014} ) 141 | table.insert(sphereVertices, {0.24999997019768, -0.35355335474014, -0.24999997019768} ) 142 | table.insert(sphereVertices, {0, -0.49999997019768, 0} ) 143 | table.insert(sphereVertices, {-0.24999997019768, -0.35355335474014, 0.24999997019768} ) 144 | table.insert(sphereVertices, {0, 0, -0.5} ) 145 | table.insert(sphereVertices, {0.35355338454247, 0, -0.35355338454247} ) 146 | table.insert(sphereVertices, {-0.35355338454247, 0, 0.35355338454247} ) 147 | table.insert(sphereVertices, {0.24999997019768, 0.35355335474014, -0.24999997019768} ) 148 | table.insert(sphereVertices, {0, -0.35355335474014, -0.35355335474014} ) 149 | table.insert(sphereVertices, {0, 0, 0.5} ) 150 | table.insert(sphereVertices, {0, 0.35355335474014, -0.35355335474014} ) 151 | table.insert(sphereVertices, {-0.24999997019768, -0.35355335474014, -0.24999997019768} ) 152 | table.insert(sphereVertices, {0.5, 0, 0} ) 153 | table.insert(sphereVertices, {0.35355338454247, 0, 0.35355338454247} ) 154 | table.insert(sphereVertices, {0.24999997019768, -0.35355335474014, 0.24999997019768} ) 155 | table.insert(sphereVertices, {0.35355335474014, -0.35355335474014, 0} ) 156 | table.insert(sphereVertices, {0.35355335474014, 0.35355335474014, 0} ) 157 | table.insert(sphereVertices, {-0.35355335474014, -0.35355335474014, 0} ) 158 | 159 | 160 | --[[---------------------------------------------------------------------------------------------------- 161 | FUNCTIONS 162 | --]]---------------------------------------------------------------------------------------------------- 163 | 164 | 165 | --# Edge Calculations 166 | local lefts = { 167 | [Enum.NormalId.Top] = Vector3.FromNormalId(Enum.NormalId.Left); 168 | [Enum.NormalId.Back] = Vector3.FromNormalId(Enum.NormalId.Left); 169 | [Enum.NormalId.Right] = Vector3.FromNormalId(Enum.NormalId.Back); 170 | [Enum.NormalId.Bottom] = Vector3.FromNormalId(Enum.NormalId.Right); 171 | [Enum.NormalId.Front] = Vector3.FromNormalId(Enum.NormalId.Right); 172 | [Enum.NormalId.Left] = Vector3.FromNormalId(Enum.NormalId.Front); 173 | }; 174 | 175 | function getEdges(part) 176 | local connects = {} 177 | 178 | -- get the corners 179 | local size, corners = part.Size / 2, {} 180 | for x = -1, 1, 2 do 181 | for y = -1, 1, 2 do 182 | for z = -1, 1, 2 do 183 | table.insert(corners, (part.CFrame * cf(size * vec3(x, y, z))).p) 184 | end 185 | end 186 | end 187 | 188 | -- get each corner and the surface normals connected to it 189 | connects[1] = {} 190 | connects[1].corner = corners[1] 191 | table.insert(connects[1], {corners[1], corners[2]}) 192 | table.insert(connects[1], {corners[1], corners[3]}) 193 | table.insert(connects[1], {corners[1], corners[5]}) 194 | 195 | connects[2] = {} 196 | connects[2].corner = corners[2] 197 | table.insert(connects[2], {corners[2], corners[1]}) 198 | table.insert(connects[2], {corners[2], corners[4]}) 199 | table.insert(connects[2], {corners[2], corners[6]}) 200 | 201 | connects[3] = {} 202 | connects[3].corner = corners[3] 203 | table.insert(connects[3], {corners[3], corners[1]}) 204 | table.insert(connects[3], {corners[3], corners[4]}) 205 | table.insert(connects[3], {corners[3], corners[7]}) 206 | 207 | connects[4] = {} 208 | connects[4].corner = corners[4] 209 | table.insert(connects[4], {corners[4], corners[2]}) 210 | table.insert(connects[4], {corners[4], corners[3]}) 211 | table.insert(connects[4], {corners[4], corners[8]}) 212 | 213 | connects[5] = {} 214 | connects[5].corner = corners[5] 215 | table.insert(connects[5], {corners[5], corners[1]}) 216 | table.insert(connects[5], {corners[5], corners[6]}) 217 | table.insert(connects[5], {corners[5], corners[7]}) 218 | 219 | connects[6] = {} 220 | connects[6].corner = corners[6] 221 | table.insert(connects[6], {corners[6], corners[8]}) 222 | table.insert(connects[6], {corners[6], corners[5]}) 223 | table.insert(connects[6], {corners[6], corners[2]}) 224 | 225 | connects[7] = {} 226 | connects[7].corner = corners[7] 227 | table.insert(connects[7], {corners[7], corners[8]}) 228 | table.insert(connects[7], {corners[7], corners[5]}) 229 | table.insert(connects[7], {corners[7], corners[3]}) 230 | 231 | connects[8] = {} 232 | connects[8].corner = corners[8] 233 | table.insert(connects[8], {corners[8], corners[7]}) 234 | table.insert(connects[8], {corners[8], corners[6]}) 235 | table.insert(connects[8], {corners[8], corners[4]}) 236 | 237 | -- calculate the normal vectos 238 | for i, set in ipairs(connects) do 239 | for _, corners in ipairs(set) do 240 | corners.vector = (corners[1] - corners[2]).unit 241 | end 242 | end 243 | 244 | return connects 245 | end 246 | 247 | function getCorners(part, sourcePos, isGlobal) 248 | local lcorners = {} 249 | for k, set in next, getEdges(part) do 250 | local passCount = 0 251 | -- same calculation as the 2D one 252 | for i = 1, 3 do 253 | local lightVector 254 | if isGlobal == true then 255 | lightVector = sourcePos 256 | else 257 | lightVector = (sourcePos - set.corner).unit 258 | end 259 | 260 | local dot = set[i].vector:Dot(lightVector) 261 | if dot >= 0 then 262 | passCount = passCount + 1 263 | end 264 | end 265 | -- light can't shine on all 3 or none of the surfaces, must be inbetween 266 | if passCount > 0 and passCount < 3 then 267 | table.insert(lcorners, set.corner) 268 | end 269 | end 270 | return lcorners 271 | end 272 | 273 | function getEdgesSphere(part) 274 | -- get the corners 275 | local size, corners = part.Size, {} 276 | 277 | for i, vertex in pairs(sphereVertices) do 278 | local newCorner = {} 279 | local unMarkedPos = vec3(vertex[1], vertex[2], vertex[3]) 280 | newCorner.pos = part.Position + (unMarkedPos * (size)) 281 | newCorner.normal = unMarkedPos.unit 282 | table.insert(corners, newCorner) 283 | end 284 | 285 | return corners 286 | end 287 | 288 | function getCornersSphere(part, sourcePos, isGlobal) 289 | local lcorners = {} 290 | 291 | for _, corner in pairs(getEdgesSphere(part)) do 292 | local passCount = 0 293 | 294 | local lightVector 295 | if isGlobal == true then 296 | lightVector = sourcePos 297 | else 298 | lightVector = (sourcePos - corner.pos).unit 299 | end 300 | 301 | local dot = corner.normal:Dot(lightVector) 302 | if dot >= 0 then 303 | passCount = passCount + 1 304 | end 305 | 306 | -- light can't shine on all 3 or none of the surfaces, must be inbetween 307 | if passCount >= 0 then 308 | table.insert(lcorners, corner.pos) 309 | end 310 | end 311 | 312 | return lcorners 313 | end 314 | 315 | -- Light Source Functions 316 | local function isLightSourceIsNearby(light, point, offset) 317 | local lightRange = light:FindFirstChild("hasShadowRange") 318 | if lightRange then lightRange = lightRange.Value else lightRange = light.Range end 319 | 320 | if (light.Parent.Position - point).magnitude < (lightRange + offset) then 321 | return true 322 | end 323 | end 324 | 325 | function Shadow.insertLightSource(light) 326 | local rangeInstance = light:FindFirstChild("hasShadowRange") 327 | local lRange = light.Range 328 | if rangeInstance then 329 | lRange = rangeInstance.Value 330 | end 331 | 332 | table.insert(Shadow.AllLightSources, { 333 | instance = light, 334 | part = light.Parent, 335 | pos = light.Parent.Position, 336 | range = lRange, 337 | posChanged = false 338 | }) 339 | end 340 | 341 | function Shadow.removeLightSource(instance) 342 | for i, lightManifold in pairs(Shadow.AllLightSources) do 343 | if lightManifold.instance == instance then table.remove(Shadow.AllLightSources, i) return end 344 | end 345 | end 346 | 347 | function Shadow.getAllLightSources(location) 348 | for _, light in pairs(BaseFuncs.GetDescendants(workspace)) do 349 | if light:IsA("PointLight") or light:IsA("SpotLight") then 350 | Shadow.insertLightSource(light) 351 | end 352 | end 353 | end 354 | 355 | function Shadow.updateAllLightSources() 356 | for i, lightManifold in pairs(Shadow.AllLightSources) do 357 | -- Remove light source if the instance does no longer exist 358 | if lightManifold.instance == nil then 359 | table.remove(Shadow.AllLightSources, i) 360 | else 361 | -- Update the position of the light source if it's moved 362 | if lightManifold.part.Position ~= lightManifold.pos then 363 | lightManifold.posChanged = true 364 | else 365 | lightManifold.posChanged = false 366 | end 367 | 368 | lightManifold.pos = lightManifold.part.Position 369 | end 370 | end 371 | end 372 | 373 | local function getNearbyLightSources(point, offset) 374 | local lightSources = {} 375 | offset = offset or 0 376 | 377 | for i, lightManifold in pairs(Shadow.AllLightSources) do 378 | if (lightManifold.part.Position - point).magnitude < (lightManifold.range + offset) then 379 | table.insert(lightSources, i) 380 | end 381 | end 382 | 383 | return lightSources 384 | end 385 | 386 | 387 | --# Ray to Plane intersection 388 | function planeIntersectClipped(point, vector, origin, normal) 389 | local rpoint = point - origin; 390 | local vecDotNorm = vector:Dot(normal) 391 | local rDotNorm = rpoint:Dot(normal) 392 | 393 | local t = -rDotNorm / vecDotNorm; 394 | if (rDotNorm / vecDotNorm) <= -0.1 then 395 | return point + t * vector; 396 | end 397 | end 398 | 399 | --[[function planeIntersect(point, vector, origin, normal, overeach) 400 | local rpoint = point - origin; 401 | local vecDotNorm = vector:Dot(normal) 402 | local rDotNorm = rpoint:Dot(normal) 403 | 404 | local t = -rDotNorm / vecDotNorm; 405 | 406 | local hit1 = point + t * vector 407 | local hit2 = nil 408 | if overeach == true then 409 | hit2 = planeIntersectClipped(point + vector * -999999, normal, origin, normal) 410 | end 411 | if (rDotNorm / vecDotNorm) <= -0.1 then 412 | if hit2 == nil then hit1 = nil end 413 | end 414 | 415 | return hit1, hit2; 416 | end;]] 417 | 418 | function planeIntersect(point, vector, origin, normal, overeach) 419 | local rpoint = point - origin; 420 | local vecDotNorm = vector:Dot(normal) 421 | local rDotNorm = rpoint:Dot(normal) 422 | 423 | local t = -rDotNorm / vecDotNorm; 424 | 425 | local hit1 = point + t * vector 426 | 427 | if (rDotNorm / vecDotNorm) <= -0.1 then 428 | hit1 = nil 429 | end 430 | 431 | return hit1; 432 | end; 433 | 434 | function planeProject(point, v, orig, normal) 435 | local v = point - orig 436 | local dist = v * normal 437 | return point - dist * normal 438 | end 439 | 440 | 441 | --# Implementation Functions 442 | local function newRootManifold(SurfaceGui) 443 | local part = SurfaceGui.Parent 444 | 445 | -- Root canvas manifold 446 | local canvasManifold = {} 447 | canvasManifold.part = part 448 | canvasManifold.partCF = CFrame.new(part.CFrame:components()) 449 | canvasManifold.canvas = SurfaceGui 450 | 451 | -- Each occluder has a manifold that contains relevant light sources and the shadows they cast 452 | canvasManifold.occluderManifolds = {} 453 | 454 | -- Where shadow mesh instances are stored 455 | --[[canvasManifold.instanceStorage = Instance.new("Model", workspace) 456 | canvasManifold.instanceStorage.Name = "ShadowStorage_"..part.Name]] 457 | canvasManifold.instanceStorage = {} 458 | 459 | -- Occluder manifolds (nested inside base manifolds) 460 | for _, occluderPart in pairs(BaseFuncs.GetDescendants(workspace)) do 461 | if occluderPart:IsA("BasePart") and occluderPart:FindFirstChild("isShadowOccluder") and occluderPart ~= canvasManifold.part then 462 | -- Create occluder manifold 463 | local occluderManifold = {} 464 | occluderManifold.occluder = occluderPart 465 | occluderManifold.occluderCF = occluderPart.CFrame 466 | -- Create manifolds for relative lights and corresponding corners 467 | occluderManifold.shadowManifolds = {} 468 | local lightSources = getNearbyLightSources(occluderManifold.occluder.Position) 469 | 470 | for _, li in pairs(lightSources) do 471 | local shadowManifold = {} 472 | shadowManifold.li = li 473 | shadowManifold.brightness = 1 474 | if occluderManifold.occluder.Shape == Enum.PartType.Ball then 475 | shadowManifold.corners = getCornersSphere(occluderManifold.occluder, Shadow.AllLightSources[shadowManifold.li].part.Position) 476 | else 477 | shadowManifold.corners = getCorners(occluderManifold.occluder, Shadow.AllLightSources[shadowManifold.li].part.Position) 478 | end 479 | shadowManifold.instanceStorage = {} 480 | 481 | -- Apply current SM to shadowManifolds 482 | table.insert(occluderManifold.shadowManifolds, shadowManifold) 483 | end 484 | 485 | -- Global Lighting 486 | occluderManifold.globalShadowManifold = {} 487 | occluderManifold.globalShadowManifold.brightness = 1 488 | if occluderManifold.occluder.Shape == Enum.PartType.Ball then 489 | occluderManifold.globalShadowManifold.corners = getCornersSphere(occluderManifold.occluder, Lighting:GetSunDirection(), true) 490 | else 491 | occluderManifold.globalShadowManifold.corners = getCorners(occluderManifold.occluder, Lighting:GetSunDirection(), true) 492 | end 493 | occluderManifold.globalShadowManifold.instanceStorage = {} 494 | 495 | -- Apply to pre-existing parent manifold 496 | table.insert(canvasManifold.occluderManifolds, occluderManifold) 497 | end 498 | end 499 | 500 | -- Parent to root manifolds 501 | return canvasManifold 502 | end 503 | 504 | local function getRootManifolds() 505 | local rootManifolds = {} 506 | 507 | for _, instance in pairs(BaseFuncs.GetDescendants(workspace)) do 508 | if instance:IsA("SurfaceGui") and instance:FindFirstChild("isShadowCanvas") then 509 | if instance.Parent.Shape ~= Enum.PartType.Ball then 510 | local canvasManifold = newRootManifold(instance) 511 | 512 | -- Parent to root manifolds 513 | table.insert(rootManifolds, canvasManifold) 514 | end 515 | end 516 | end 517 | 518 | for _, instance in pairs(BaseFuncs.GetDescendants(workspace.CurrentCamera)) do 519 | if instance:IsA("SurfaceGui") and instance:FindFirstChild("isShadowCanvas") then 520 | if instance.Parent.Shape ~= Enum.PartType.Ball then 521 | local canvasManifold = newRootManifold(instance) 522 | 523 | -- Parent to root manifolds 524 | table.insert(rootManifolds, canvasManifold) 525 | end 526 | end 527 | end 528 | 529 | return rootManifolds 530 | end 531 | 532 | 533 | --# Custom shading on part surfaces 534 | local function newLitCanvases(part) 535 | -- Declare the manifold 536 | local partManifold = {} 537 | 538 | -- Attributes 539 | partManifold.canvasManifolds = {} 540 | partManifold.part = part 541 | partManifold.partCF = part.CFrame 542 | 543 | local lightSources = getNearbyLightSources(part.Position, part.Size.magnitude) 544 | partManifold.lightSources = {} 545 | 546 | for _, li in pairs(lightSources) do 547 | table.insert(partManifold.lightSources, li) 548 | end 549 | 550 | -- Create SurfaceGui for each Surface for lighting 551 | for ni, normalId in pairs(lefts) do 552 | local newCanvas = Instance.new("SurfaceGui") 553 | newCanvas.CanvasSize = vec2(1, 1) 554 | newCanvas.Face = ni 555 | newCanvas.Parent = part 556 | 557 | local frame = Instance.new("Frame") 558 | frame.BackgroundColor3 = shadowColor 559 | frame.Size = udim2(1, 0, 1, 0) 560 | frame.Parent = newCanvas 561 | frame.BorderSizePixel = 0 562 | 563 | local sid = newCanvas.Face 564 | local lnormal = Vector3.FromNormalId(sid) 565 | local normal = partManifold.part.CFrame:vectorToWorldSpace(lnormal) 566 | local origin = partManifold.part.Position + normal * (lnormal * partManifold.part.Size/2).magnitude 567 | 568 | table.insert(partManifold.canvasManifolds, { 569 | canvas = newCanvas, 570 | cover = frame, 571 | canvasWorldSpace = origin 572 | }) 573 | end 574 | 575 | -- Finalize 576 | return partManifold 577 | end 578 | 579 | local function getLitPartManifolds() 580 | -- Declare the manifold 581 | local litPartManifolds = {} 582 | 583 | -- Generate ALL manifolds 584 | for _, instance in pairs(BaseFuncs.GetDescendants(workspace)) do 585 | if instance:FindFirstChild("hasLitSurfaces") and instance.Shape ~= Enum.PartType.Ball then 586 | table.insert(litPartManifolds, newLitCanvases(instance)) 587 | end 588 | end 589 | 590 | for _, instance in pairs(BaseFuncs.GetDescendants(workspace.CurrentCamera)) do 591 | if instance:FindFirstChild("hasLitSurfaces") and instance.Shape ~= Enum.PartType.Ball then 592 | table.insert(litPartManifolds, newLitCanvases(instance)) 593 | end 594 | end 595 | 596 | -- Finalize 597 | return litPartManifolds 598 | end 599 | 600 | local function updateLitPartManifolds(litPartManifolds, onChange) 601 | for pi, partManifold in pairs(litPartManifolds) do 602 | -- Re-create world space pos if the part CFrame changes 603 | local posChanged = false 604 | if onChange == true then 605 | if partManifold.partCF ~= partManifold.part.CFrame then 606 | posChanged = true 607 | end 608 | else 609 | posChanged = true 610 | end 611 | 612 | if posChanged then 613 | partManifold.partCF = partManifold.part.CFrame 614 | --print("CF changed") 615 | for ci, canvasManifold in pairs(partManifold.canvasManifolds) do 616 | local sid = canvasManifold.canvas.Face 617 | local lnormal = Vector3.FromNormalId(sid) 618 | local normal = partManifold.part.CFrame:vectorToWorldSpace(lnormal) 619 | local origin = partManifold.part.Position + normal * (lnormal * partManifold.part.Size/2).magnitude 620 | 621 | litPartManifolds[pi].canvasManifolds[ci].normal = normal 622 | litPartManifolds[pi].canvasManifolds[ci].canvasWorldSpace = origin 623 | end 624 | end 625 | 626 | -- Detect and reset lights if the position between a lightsource and the part has changed 627 | local lightPosChanged = false 628 | 629 | for _, li in pairs(partManifold.lightSources) do 630 | if Shadow.AllLightSources[li].posChanged == true then lightPosChanged = true break end 631 | end 632 | 633 | if posChanged == true or lightPosChanged == true then 634 | litPartManifolds[pi].lightSources = {} 635 | for _, li in pairs(getNearbyLightSources(partManifold.part.Position, partManifold.part.Size.magnitude)) do 636 | table.insert(litPartManifolds[pi].lightSources, li) 637 | end 638 | 639 | end 640 | 641 | -- re-calculate all of the lighting 642 | for ci, canvasManifold in pairs(partManifold.canvasManifolds) do 643 | local accumulatedBrightness = 1 644 | 645 | if (sunUpdateNeeded == true) or posChanged == true then 646 | if globalLightingEnabled == true then 647 | local v = Lighting:GetSunDirection() 648 | local brightness = math.max(0, canvasManifold.normal:Dot(v)) 649 | 650 | accumulatedBrightness = (1 - accumulatedBrightness) + ambient + (brightness * (1 - (transparency) )) 651 | end 652 | end 653 | 654 | if (posChanged == true or lightPosChanged == true) and localLightingEnabled == true then 655 | local canvasWorldSpace = litPartManifolds[pi].canvasManifolds[ci].canvasWorldSpace 656 | 657 | for _, li in pairs(partManifold.lightSources) do 658 | local lightSource = Shadow.AllLightSources[li] 659 | 660 | local v = (lightSource.pos - canvasManifold.canvasWorldSpace).unit 661 | local brightness = math.max(0, canvasManifold.normal:Dot(v)) 662 | 663 | accumulatedBrightness = (1 - accumulatedBrightness) + ambient + (brightness * (1 - (transparency) )) 664 | end 665 | 666 | --print(accumulatedBrightness) 667 | litPartManifolds[pi].canvasManifolds[ci].cover.BackgroundTransparency = accumulatedBrightness 668 | else 669 | litPartManifolds[pi].canvasManifolds[ci].cover.BackgroundTransparency = 1 670 | end 671 | 672 | litPartManifolds[pi].canvasManifolds[ci].cover.BackgroundColor3 = shadowColor 673 | end 674 | 675 | end 676 | 677 | 678 | -- Finalize 679 | return litPartManifolds 680 | end 681 | 682 | 683 | --# ShadowMesh Creation 684 | function getTopLeft(hit, sid) 685 | local lnormal = Vector3.FromNormalId(sid) 686 | local cf = hit.CFrame + (hit.CFrame:vectorToWorldSpace(lnormal * (hit.Size/2))); 687 | local modi = (sid == Enum.NormalId.Top or sid == Enum.NormalId.Bottom) and -1 or 1; 688 | local left = lefts[sid]; 689 | local up = modi * left:Cross(lnormal); 690 | local tlcf = cf + hit.CFrame:vectorToWorldSpace((up + left) * hit.Size/2); 691 | return tlcf, Vector2.new((left * hit.Size).magnitude, (up * hit.Size).magnitude), 692 | hit.CFrame:vectorToWorldSpace(-left), 693 | hit.CFrame:vectorToWorldSpace(-up), modi; 694 | end; 695 | 696 | function isOvereached(normal, highestPoint, lowestPoint, point) 697 | if not highestPoint or not lowestPoint then return false end 698 | 699 | local projHighestPoint = highestPoint 700 | local projlowestPoint = lowestPoint 701 | local projLightPos = normal * point 702 | 703 | local xMet = false 704 | local yMet = false 705 | local zMet = false 706 | 707 | if (projLightPos.x >= projlowestPoint.x) and (projLightPos.x <= projHighestPoint.x) then xMet = true end 708 | if (projLightPos.y >= projlowestPoint.y) and (projLightPos.y <= projHighestPoint.y) then yMet = true end 709 | if (projLightPos.z >= projlowestPoint.z) and (projLightPos.z <= projHighestPoint.y) then zMet = true end 710 | --[[print(tostring("highest: ")..projlowestPoint.y) 711 | print(tostring("light: ")..projLightPos.y)]] 712 | --print((projLightPos.y >= projlowestPoint.y)) 713 | --print("---------------------") 714 | if (xMet == true and yMet == true and zMet == true) then return true else return false end 715 | end 716 | 717 | function createShadowMesh_Global(shadowManifold, canvasManifold) 718 | -- Clean up old Instances 719 | for _, tri in pairs(shadowManifold.instanceStorage) do 720 | tri:Destroy() 721 | end 722 | shadowManifold.instanceStorage = {} 723 | 724 | -- Variables 725 | local sid = canvasManifold.canvas.Face; 726 | local lnormal = Vector3.FromNormalId(sid); 727 | local normal = canvasManifold.part.CFrame:vectorToWorldSpace(lnormal); 728 | local origin = canvasManifold.part.Position + normal * (lnormal * canvasManifold.part.Size/2).magnitude; 729 | local tlc, size, right, down, modi = getTopLeft(canvasManifold.part, sid); 730 | local noPos = false 731 | --local lightPos = Shadow.AllLightSources[shadowManifold.li].part.Position 732 | 733 | local points = {} 734 | local pointCornerRef = {} 735 | 736 | for _, corner in next, shadowManifold.corners do 737 | local lightVector = Lighting:GetSunDirection() 738 | local dot = normal:Dot(lightVector) 739 | 740 | -- Only render shadows for surface if it can be seen from the light source 741 | if dot >= 0 then -- CSMG 742 | local pos = planeIntersect(corner, lightVector, origin, normal) 743 | 744 | --local ro = corner - (Lighting:GetSunDirection()*5) 745 | --local pos = planeIntersect(ro, lightVector, origin, normal) 746 | 747 | --[[local elevDist = ((corner) - (origin))*normal 748 | local pos = planeProject(corner + (lightVector * -elevDist.magnitude), lightVector, origin, normal)]] 749 | 750 | if pos then 751 | noPos = true 752 | 753 | local relative = pos - tlc.p; 754 | local x, y = right:Dot(relative)/size.x, down:Dot(relative)/size.y; 755 | x, y = modi < 1 and y or x, modi < 1 and x or y; 756 | 757 | local csize = canvasManifold.canvas.CanvasSize; 758 | local absPosition = Vector2.new(x * csize.x, y * csize.y); 759 | 760 | table.insert(points, absPosition); 761 | end 762 | 763 | table.insert(pointCornerRef, {corner, pos}) 764 | --[[else 765 | local elevDist = ((corner) - (origin))*normal 766 | local posProject = planeProject(corner + (lightVector * -elevDist.magnitude), lightVector, origin, normal) 767 | 768 | local relative = posProject - tlc.p; 769 | local x, y = right:Dot(relative)/size.x, down:Dot(relative)/size.y; 770 | x, y = modi < 1 and y or x, modi < 1 and x or y; 771 | 772 | local csize = canvasManifold.canvas.CanvasSize; 773 | local absPosition = Vector2.new(x * csize.x, y * csize.y); 774 | 775 | table.insert(points, absPosition);]] 776 | end 777 | end; 778 | 779 | if #points > 2 then 780 | local guiSize = canvasManifold.canvas.CanvasSize 781 | local clippingRect = { 782 | {guiSize.x, guiSize.y}, 783 | {guiSize.x, 0}, 784 | {0, 0}, 785 | {0, guiSize.y} 786 | } 787 | 788 | local newPoints = Hull.jarvis(points) 789 | for i, np in pairs(newPoints) do 790 | newPoints[i] = {np.x, np.y} 791 | end 792 | newPoints = Poly.clipAgainst(newPoints, clippingRect) 793 | 794 | local finalTris = {} 795 | 796 | if #newPoints > 0 then 797 | finalTris = Poly.triangulate(newPoints) 798 | 799 | for i, t in pairs(finalTris) do 800 | if t[1] and t[2] and t[3] then 801 | local ta, tb = Tris(canvasManifold.canvas, shadowColor, transparency, unpack(t)) 802 | table.insert(shadowManifold.instanceStorage, ta); 803 | table.insert(shadowManifold.instanceStorage, tb); 804 | end 805 | end 806 | end 807 | end 808 | 809 | return shadowManifold 810 | end 811 | 812 | function createShadowMesh(shadowManifold, canvasManifold) 813 | -- Clean up old Instances 814 | for _, tri in pairs(shadowManifold.instanceStorage) do 815 | tri:Destroy() 816 | end 817 | shadowManifold.instanceStorage = {} 818 | 819 | -- Variables 820 | local sid = canvasManifold.canvas.Face; 821 | local lnormal = Vector3.FromNormalId(sid); 822 | local normal = canvasManifold.part.CFrame:vectorToWorldSpace(lnormal); 823 | local origin = canvasManifold.part.Position + normal * (lnormal * canvasManifold.part.Size/2).magnitude; 824 | local tlc, size, right, down, modi = getTopLeft(canvasManifold.part, sid); 825 | local noPos = false 826 | local lightPos = Shadow.AllLightSources[shadowManifold.li].part.Position 827 | 828 | local points = {} 829 | local pointCornerRef = {} 830 | 831 | for _, corner in next, shadowManifold.corners do 832 | local lightVector = (lightPos - corner).unit 833 | local dot = normal:Dot(lightVector) 834 | 835 | -- Only render shadows for surface if it can be seen from the light source 836 | if dot >= 0 then 837 | local pos = planeIntersect(corner, lightVector, origin, normal) 838 | 839 | if pos then 840 | noPos = true 841 | 842 | local relative = pos - tlc.p; 843 | local x, y = right:Dot(relative)/size.x, down:Dot(relative)/size.y; 844 | x, y = modi < 1 and y or x, modi < 1 and x or y; 845 | 846 | local csize = canvasManifold.canvas.CanvasSize; 847 | local absPosition = Vector2.new(x * csize.x, y * csize.y); 848 | 849 | table.insert(points, absPosition); 850 | end 851 | 852 | table.insert(pointCornerRef, {corner, pos}) 853 | else 854 | local posProject = planeProject(corner + lightVector * -99999, lightVector, origin, normal) 855 | 856 | local relative = posProject - tlc.p; 857 | local x, y = right:Dot(relative)/size.x, down:Dot(relative)/size.y; 858 | x, y = modi < 1 and y or x, modi < 1 and x or y; 859 | 860 | local csize = canvasManifold.canvas.CanvasSize; 861 | local absPosition = Vector2.new(x * csize.x, y * csize.y); 862 | 863 | table.insert(points, absPosition); 864 | end 865 | end; 866 | 867 | if #points > 2 then 868 | local guiSize = canvasManifold.canvas.CanvasSize 869 | local clippingRect = { 870 | {guiSize.x, guiSize.y}, 871 | {guiSize.x, 0}, 872 | {0, 0}, 873 | {0, guiSize.y} 874 | } 875 | 876 | local newPoints = Hull.jarvis(points) 877 | for i, np in pairs(newPoints) do 878 | newPoints[i] = {np.x, np.y} 879 | end 880 | newPoints = Poly.clipAgainst(newPoints, clippingRect) 881 | 882 | local finalTris = {} 883 | 884 | if #newPoints > 0 then 885 | finalTris = Poly.triangulate(newPoints) 886 | 887 | for i, t in pairs(finalTris) do 888 | if t[1] and t[2] and t[3] then 889 | local ta, tb = Tris(canvasManifold.canvas, shadowColor, transparency, unpack(t)) 890 | table.insert(shadowManifold.instanceStorage, ta); 891 | table.insert(shadowManifold.instanceStorage, tb); 892 | end 893 | end 894 | end 895 | end 896 | 897 | return shadowManifold 898 | end 899 | 900 | 901 | --# Final collected functions 902 | function Shadow.getRootManifolds() 903 | return getRootManifolds() 904 | end 905 | 906 | function Shadow.createAllShadowMeshes(rootManifolds) 907 | for ci, canvasManifold in pairs(rootManifolds) do 908 | for oi, occluderManifold in pairs(canvasManifold.occluderManifolds) do 909 | for si, shadowManifold in pairs(occluderManifold.shadowManifolds) do 910 | if localLightingEnabled == true then 911 | shadowManifold = createShadowMesh(shadowManifold, canvasManifold) 912 | end 913 | end 914 | if globalLightingEnabled == true then 915 | createShadowMesh_Global(occluderManifold.globalShadowManifold, canvasManifold) 916 | end 917 | end 918 | end 919 | 920 | --return rootManifolds 921 | end 922 | local num1 = 0 923 | function Shadow.updateRootManifolds(rootManifolds) 924 | -- Update the lighting value settings 925 | updateLighting() 926 | 927 | -- Interate through manifolds 928 | for ci, canvasManifold in pairs(rootManifolds) do 929 | -- Re-compute ENTIRE shadow manifold if canvas position changes. 930 | --[[print(tostring(canvasManifold.part.CFrame.x)..", "..tostring(canvasManifold.part.CFrame.y)..", "..tostring(canvasManifold.part.CFrame.z)) 931 | print(tostring(canvasManifold.partCF.x)..", "..tostring(canvasManifold.partCF.y)..", "..tostring(canvasManifold.partCF.z)) 932 | print("------------------------------------------------------------------------------------------")]] 933 | 934 | -- Update ONLY if part CF changed 935 | if canvasManifold.part.CFrame ~= canvasManifold.partCF then 936 | -- Delete old instance storage 937 | for _, instance in pairs(canvasManifold.canvas:GetChildren()) do 938 | if (not instance:IsA("BoolValue")) and (not instance:IsA("StringValue")) and (not instance:IsA("Frame")) then 939 | instance:Destroy() 940 | end 941 | end 942 | 943 | -- Remake the entire canvas manifold. 944 | rootManifolds[ci] = newRootManifold(canvasManifold.canvas) 945 | 946 | -- Re-render shadows 947 | for oi, occluderManifold in pairs(canvasManifold.occluderManifolds) do 948 | if globalLightingEnabled == true then 949 | if rootManifolds[ci].occluderManifolds[oi].occluder.Shape == Enum.PartType.Ball then 950 | rootManifolds[ci].occluderManifolds[oi].globalShadowManifold.corners = getCornersSphere(rootManifolds[ci].occluderManifolds[oi].occluder, Lighting:GetSunDirection(), true) 951 | else 952 | rootManifolds[ci].occluderManifolds[oi].globalShadowManifold.corners = getCorners(rootManifolds[ci].occluderManifolds[oi].occluder, Lighting:GetSunDirection(), true) 953 | end 954 | 955 | rootManifolds[ci].occluderManifolds[oi].globalShadowManifold = createShadowMesh_Global(rootManifolds[ci].occluderManifolds[oi].globalShadowManifold, rootManifolds[ci]) 956 | end 957 | 958 | if localLightingEnabled == true then 959 | for si, shadowManifold in pairs(occluderManifold.shadowManifolds) do 960 | rootManifolds[ci].occluderManifolds[oi].shadowManifolds[si] = createShadowMesh(shadowManifold, canvasManifold) 961 | end 962 | end 963 | 964 | end 965 | else 966 | for oi, occluderManifold in pairs(canvasManifold.occluderManifolds) do 967 | local globalShadowMeshUpdated = false 968 | local localShadowMeshUpdated = false 969 | 970 | -- Re-compute ALL shadow manifolds if the occluder has changed position or rotation. 971 | if occluderManifold.occluder.CFrame ~= occluderManifold.occluderCF then 972 | occluderManifold.occluderCF = occluderManifold.occluder.CFrame 973 | 974 | -- Delete old instance storage 975 | for si, shadowManifold in pairs(occluderManifold.shadowManifolds) do 976 | for _, instance in pairs(shadowManifold.instanceStorage) do 977 | instance:Destroy() 978 | end 979 | shadowManifold.instanceStorage = {} 980 | end 981 | 982 | -- Actual re-computation of shadow manifolds 983 | occluderManifold.shadowManifolds = {} 984 | --[[local lightSources = getNearbyLightSources(occluderManifold.occluder.Position) 985 | 986 | for _, light in pairs(lightSources) do 987 | local lightPart = light.Parent 988 | local shadowManifold = {} 989 | shadowManifold.lightPart = lightPart 990 | shadowManifold.lightPos = lightPart.Position 991 | shadowManifold.lightNum = #lightSources 992 | shadowManifold.corners = getCorners(occluderManifold.occluder, lightPart.Position) 993 | shadowManifold.instanceStorage = {} 994 | table.insert(occluderManifold.shadowManifolds, shadowManifold) 995 | end]] 996 | 997 | if localLightingEnabled == true and localShadowMeshUpdated == false then 998 | localShadowMeshUpdated = true 999 | 1000 | local lightSources = getNearbyLightSources(occluderManifold.occluder.Position) 1001 | 1002 | for _, li in pairs(lightSources) do 1003 | local shadowManifold = {} 1004 | shadowManifold.li = li 1005 | shadowManifold.brightness = 1 1006 | if occluderManifold.occluder.Shape == Enum.PartType.Ball then 1007 | shadowManifold.corners = getCornersSphere(occluderManifold.occluder, Shadow.AllLightSources[shadowManifold.li].part.Position) 1008 | else 1009 | shadowManifold.corners = getCorners(occluderManifold.occluder, Shadow.AllLightSources[shadowManifold.li].part.Position) 1010 | end 1011 | shadowManifold.instanceStorage = {} 1012 | 1013 | -- Apply current SM to shadowManifolds 1014 | table.insert(occluderManifold.shadowManifolds, shadowManifold) 1015 | end 1016 | 1017 | -- Re-render shadows 1018 | for si, shadowManifold in pairs(occluderManifold.shadowManifolds) do 1019 | shadowManifold = createShadowMesh(shadowManifold, canvasManifold) 1020 | end 1021 | end 1022 | 1023 | -- Re-render global shadows 1024 | if globalShadowMeshUpdated == false then 1025 | globalShadowMeshUpdated = true 1026 | 1027 | if globalLightingEnabled == true then 1028 | if rootManifolds[ci].occluderManifolds[oi].occluder.Shape == Enum.PartType.Ball then 1029 | rootManifolds[ci].occluderManifolds[oi].globalShadowManifold.corners = getCornersSphere(rootManifolds[ci].occluderManifolds[oi].occluder, Lighting:GetSunDirection(), true) 1030 | else 1031 | rootManifolds[ci].occluderManifolds[oi].globalShadowManifold.corners = getCorners(rootManifolds[ci].occluderManifolds[oi].occluder, Lighting:GetSunDirection(), true) 1032 | end 1033 | 1034 | rootManifolds[ci].occluderManifolds[oi].globalShadowManifold = createShadowMesh_Global(rootManifolds[ci].occluderManifolds[oi].globalShadowManifold, canvasManifold) 1035 | else 1036 | for _, item in pairs(rootManifolds[ci].occluderManifolds[oi].globalShadowManifold.instanceStorage) do 1037 | if item then item:Destroy() end 1038 | end 1039 | rootManifolds[ci].occluderManifolds[oi].globalShadowManifold.instanceStorage = {} 1040 | end 1041 | end 1042 | end 1043 | 1044 | -- Re-compute SPECIFIC shadow manifold if light position changes 1045 | for si, shadowManifold in pairs(occluderManifold.shadowManifolds) do 1046 | --local lightPos = Shadow.AllLightSources[shadowManifold.li].pos 1047 | 1048 | if Shadow.AllLightSources[shadowManifold.li].posChanged == true then 1049 | 1050 | -- Delete old instance storage 1051 | for _, instance in pairs(shadowManifold.instanceStorage) do 1052 | instance:Destroy() 1053 | end 1054 | shadowManifold.instanceStorage = {} 1055 | 1056 | if localLightingEnabled == true and localShadowMeshUpdated == false then 1057 | localShadowMeshUpdated = true 1058 | --[[ Update shadow manifold variables 1059 | shadowManifold.lightPos = shadowManifold.lightPart.Position]] 1060 | --shadowManifold.corners = getCorners(occluderManifold.occluder, Shadow.AllLightSources[shadowManifold.li].pos) 1061 | if occluderManifold.occluder.Shape == Enum.PartType.Ball then 1062 | shadowManifold.corners = getCornersSphere(occluderManifold.occluder, Shadow.AllLightSources[shadowManifold.li].part.Position) 1063 | else 1064 | shadowManifold.corners = getCorners(occluderManifold.occluder, Shadow.AllLightSources[shadowManifold.li].part.Position) 1065 | end 1066 | shadowManifold.instanceStorage = {} 1067 | 1068 | -- Re-render shadow for specific manifold 1069 | shadowManifold = createShadowMesh(shadowManifold, canvasManifold) 1070 | 1071 | -- Apply shadow manifold 1072 | occluderManifold.shadowManifolds[si] = shadowManifold 1073 | end 1074 | end 1075 | 1076 | 1077 | -- Update shadows if light enabled/disabled 1078 | if localUpdateNeeded == true then 1079 | localShadowMeshUpdated = true 1080 | 1081 | if localLightingEnabled == true then 1082 | -- Delete old instance storage 1083 | for _, instance in pairs(shadowManifold.instanceStorage) do 1084 | instance:Destroy() 1085 | end 1086 | shadowManifold.instanceStorage = {} 1087 | 1088 | if occluderManifold.occluder.Shape == Enum.PartType.Ball then 1089 | shadowManifold.corners = getCornersSphere(occluderManifold.occluder, Shadow.AllLightSources[shadowManifold.li].part.Position) 1090 | else 1091 | shadowManifold.corners = getCorners(occluderManifold.occluder, Shadow.AllLightSources[shadowManifold.li].part.Position) 1092 | end 1093 | shadowManifold.instanceStorage = {} 1094 | 1095 | -- Re-render shadow for specific manifold 1096 | shadowManifold = createShadowMesh(shadowManifold, canvasManifold) 1097 | 1098 | -- Apply shadow manifold 1099 | occluderManifold.shadowManifolds[si] = shadowManifold 1100 | else 1101 | for _, instance in pairs(shadowManifold.instanceStorage) do 1102 | instance:Destroy() 1103 | end 1104 | shadowManifold.instanceStorage = {} 1105 | end 1106 | end 1107 | 1108 | end 1109 | 1110 | -- Check if sun dir changed or sun deleted 1111 | if sunUpdateNeeded == true then 1112 | if globalLightingEnabled == true then 1113 | if rootManifolds[ci].occluderManifolds[oi].occluder.Shape == Enum.PartType.Ball then 1114 | rootManifolds[ci].occluderManifolds[oi].globalShadowManifold.corners = getCornersSphere(rootManifolds[ci].occluderManifolds[oi].occluder, Lighting:GetSunDirection(), true) 1115 | else 1116 | rootManifolds[ci].occluderManifolds[oi].globalShadowManifold.corners = getCorners(rootManifolds[ci].occluderManifolds[oi].occluder, Lighting:GetSunDirection(), true) 1117 | end 1118 | 1119 | rootManifolds[ci].occluderManifolds[oi].globalShadowManifold = createShadowMesh_Global(rootManifolds[ci].occluderManifolds[oi].globalShadowManifold, canvasManifold) 1120 | else 1121 | for _, item in pairs(rootManifolds[ci].occluderManifolds[oi].globalShadowManifold.instanceStorage) do 1122 | if item then item:Destroy() end 1123 | end 1124 | rootManifolds[ci].occluderManifolds[oi].globalShadowManifold.instanceStorage = {} 1125 | end 1126 | end 1127 | 1128 | -- Apply global occluder manifold 1129 | rootManifolds[ci].occluderManifold = occluderManifold 1130 | end 1131 | end 1132 | end 1133 | 1134 | prevSunDir = Lighting:GetSunDirection() 1135 | 1136 | return rootManifolds 1137 | end 1138 | 1139 | function Shadow.getLitPartManifolds() 1140 | return getLitPartManifolds() 1141 | end 1142 | 1143 | function Shadow.updateLitPartManifolds(manifolds) 1144 | return updateLitPartManifolds(manifolds) 1145 | end 1146 | 1147 | function Shadow.lightPartManifolds(litPartManifolds) 1148 | return updateLitPartManifolds(litPartManifolds, false) 1149 | end 1150 | 1151 | 1152 | --# Utility functions - making parts occluders 1153 | function scanForSurfaceGuiSide(location, face) 1154 | for _, gui in pairs(location:GetChildren()) do 1155 | if gui:IsA("SurfaceGui") then 1156 | if gui.Face == face and gui:FindFirstChild("isShadowCanvas") then return true end 1157 | end 1158 | end 1159 | end 1160 | 1161 | function newFuncSurfaceGui(location, face, bool) 1162 | local surfaceGui = Instance.new("SurfaceGui", location) 1163 | surfaceGui.Name = "ShadowCanvasGui" 1164 | surfaceGui.CanvasSize = Vector2.new(10000, 10000) 1165 | surfaceGui.Face = face 1166 | local boolVal = Instance.new("BoolValue", surfaceGui) 1167 | boolVal.Name = "isShadowCanvas" 1168 | boolVal.Value = bool 1169 | end 1170 | 1171 | function Shadow.setPartProperty(part, property, bool) 1172 | if part:IsA("BasePart") then 1173 | if type(bool) ~= "table" then 1174 | if property ~= "isShadowCanvasAll" and property ~= "isShadowCanvasLeft" and property ~= "isShadowCanvasRight" and property ~= "isShadowCanvasTop" and property ~= "isShadowCanvasBottom" and property ~= "isShadowCanvasFront" and property ~= "isShadowCanvasBack" then 1175 | local propVal = part:FindFirstChild(property) 1176 | 1177 | if bool == false then 1178 | if propVal then 1179 | propVal:Destroy() 1180 | end 1181 | else 1182 | if not propVal then 1183 | propVal = Instance.new("BoolValue", part) 1184 | propVal.Name = property 1185 | end 1186 | end 1187 | 1188 | else 1189 | if property == "isShadowCanvasAll" then 1190 | for face, vec in pairs(lefts) do 1191 | local relativeGui = scanForSurfaceGuiSide(part, face) 1192 | if relativeGui then --[[relativeGui.Value = bool]] else newFuncSurfaceGui(part, face, bool) end 1193 | end 1194 | elseif property == "isShadowCanvasTop" then 1195 | local face = Enum.NormalId.Top 1196 | local relativeGui = scanForSurfaceGuiSide(part, face) 1197 | if relativeGui then --[[relativeGui.Value = bool]] else newFuncSurfaceGui(part, face, bool) end 1198 | elseif property == "isShadowCanvasBottom" then 1199 | local face = Enum.NormalId.Bottom 1200 | local relativeGui = scanForSurfaceGuiSide(part, face) 1201 | if relativeGui then --[[relativeGui.Value = bool]] else newFuncSurfaceGui(part, face, bool) end 1202 | elseif property == "isShadowCanvasRight" then 1203 | local face = Enum.NormalId.Right 1204 | local relativeGui = scanForSurfaceGuiSide(part, face) 1205 | if relativeGui then --[[relativeGui.Value = bool]] else newFuncSurfaceGui(part, face, bool) end 1206 | elseif property == "isShadowCanvasLeft" then 1207 | local face = Enum.NormalId.Left 1208 | local relativeGui = scanForSurfaceGuiSide(part, face) 1209 | if relativeGui then --[[relativeGui.Value = bool]] else newFuncSurfaceGui(part, face, bool) end 1210 | elseif property == "isShadowCanvasFront" then 1211 | local face = Enum.NormalId.Front 1212 | local relativeGui = scanForSurfaceGuiSide(part, face) 1213 | if relativeGui then --[[relativeGui.Value = bool]] else newFuncSurfaceGui(part, face, bool) end 1214 | elseif property == "isShadowCanvasBack" then 1215 | local face = Enum.NormalId.Back 1216 | local relativeGui = scanForSurfaceGuiSide(part, face) 1217 | if relativeGui then --[[relativeGui.Value = bool]] else newFuncSurfaceGui(part, face, bool) end 1218 | end 1219 | end 1220 | end 1221 | end 1222 | end 1223 | 1224 | function Shadow.setModelProperty(model, property, bool) 1225 | for _, part in pairs(BaseFuncs.GetDescendants(model)) do 1226 | Shadow.setPartProperty(part, property, bool) 1227 | end 1228 | end 1229 | 1230 | 1231 | --# Misc Functions 1232 | function Shadow.updateDescendants(location) 1233 | Shadow.allDescendants = {} 1234 | for _, part in pairs(BaseFuncs.GetDescendants(location)) do 1235 | if part:IsA("BasePart") then table.insert(Shadow.allDescendants, part) end 1236 | end 1237 | end 1238 | 1239 | 1240 | --# Finalize 1241 | return Shadow 1242 | --------------------------------------------------------------------------------