@rbxts/vfx-forge 2.2.2-ts.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +82 -0
  2. package/README.md +39 -0
  3. package/out/forge-vfx/effects/beam.luau +312 -0
  4. package/out/forge-vfx/effects/bezier.luau +392 -0
  5. package/out/forge-vfx/effects/camera_shake.luau +200 -0
  6. package/out/forge-vfx/effects/lightning.luau +1183 -0
  7. package/out/forge-vfx/effects/mesh.luau +466 -0
  8. package/out/forge-vfx/effects/particle.luau +64 -0
  9. package/out/forge-vfx/effects/randomizer.luau +110 -0
  10. package/out/forge-vfx/effects/screen.luau +61 -0
  11. package/out/forge-vfx/effects/shockwave_debris.luau +277 -0
  12. package/out/forge-vfx/effects/shockwave_line.luau +356 -0
  13. package/out/forge-vfx/effects/shockwave_ring.luau +252 -0
  14. package/out/forge-vfx/effects/sound.luau +311 -0
  15. package/out/forge-vfx/effects/spin.luau +88 -0
  16. package/out/forge-vfx/effects/tweener.luau +122 -0
  17. package/out/forge-vfx/emitters.luau +387 -0
  18. package/out/forge-vfx/index.d.ts +356 -0
  19. package/out/forge-vfx/init.luau +279 -0
  20. package/out/forge-vfx/mod/attributes.luau +227 -0
  21. package/out/forge-vfx/mod/color/Oklab.luau +93 -0
  22. package/out/forge-vfx/mod/color/sRGB.luau +71 -0
  23. package/out/forge-vfx/mod/common/bezier.luau +372 -0
  24. package/out/forge-vfx/mod/common/flipbook.luau +102 -0
  25. package/out/forge-vfx/mod/lerp.luau +210 -0
  26. package/out/forge-vfx/mod/logger.luau +20 -0
  27. package/out/forge-vfx/mod/shape.luau +207 -0
  28. package/out/forge-vfx/mod/tween.luau +161 -0
  29. package/out/forge-vfx/mod/utility.luau +707 -0
  30. package/out/forge-vfx/obj/Bezier.luau +268 -0
  31. package/out/forge-vfx/obj/ObjectCache.luau +289 -0
  32. package/out/forge-vfx/services/caches.luau +62 -0
  33. package/out/forge-vfx/services/effects.luau +234 -0
  34. package/out/forge-vfx/services/enabled_effects.luau +120 -0
  35. package/out/forge-vfx/services/texture_loader.luau +174 -0
  36. package/out/forge-vfx/types.luau +43 -0
  37. package/out/index.d.ts +3 -0
  38. package/out/init.luau +5 -0
  39. package/out/shake.d.ts +2 -0
  40. package/out/shake.luau +6 -0
  41. package/out/tsconfig.tsbuildinfo +1 -0
  42. package/package.json +63 -0
@@ -0,0 +1,268 @@
1
+ --!native
2
+ --!nocheck
3
+ --!optimize 2
4
+ type n = vector | number
5
+
6
+ local EASE_LUT_SIZE = 64
7
+
8
+ local NEWTON_MIN_TOL = 0.001
9
+ local NEWTON_MAX_ITER = 5
10
+
11
+ local function cubicBezier(p0: n, p1: n, p2: n, p3: n, t: n): n
12
+ -- stylua: ignore
13
+ return (1-t)^3*p0+3*(1-t)^2*t*p1+3*(1-t)*t^2*p2+t^3*p3
14
+ end
15
+
16
+ local function cubicBezierDerivative(p0: n, p1: n, p2: n, p3: n, t: n): n
17
+ -- stylua: ignore
18
+ return 3*(1-t)^2*(p1-p0)+6*(1-t)*t*(p2-p1)+3*t^2*(p3-p2)
19
+ end
20
+
21
+ local bezier = {}
22
+ bezier.__index = bezier
23
+
24
+ export type Bezier = typeof(bezier) & {
25
+ points: { vector },
26
+ cumulative_lengths: { number },
27
+ ease_lut: { vector }?,
28
+
29
+ length: number,
30
+ accuracy: number,
31
+ point_count: number,
32
+ }
33
+
34
+ function bezier.new(points: { vector }, samplingAccuracy: number?)
35
+ local self = setmetatable({}, bezier)
36
+
37
+ self.points = points
38
+ self.accuracy = samplingAccuracy or 20
39
+
40
+ self.point_count = 0
41
+ self.cumulative_lengths = {}
42
+
43
+ self:_recalculate()
44
+
45
+ return self
46
+ end
47
+
48
+ function bezier.setPoints(self: Bezier, points: { vector })
49
+ self.points = points
50
+ self.ease_lut = nil
51
+
52
+ self:_recalculate()
53
+ end
54
+
55
+ function bezier.getSegmentPoints(self: Bezier, index: number)
56
+ if index < 1 or index > self.point_count - 1 then
57
+ if index <= 0 then
58
+ return vector.zero, vector.zero, vector.zero, vector.zero
59
+ end
60
+
61
+ error("attempt to get a non-existent segment at index " .. index)
62
+ end
63
+
64
+ local offset = (index - 1) * 4 - math.max(index - 2, 0)
65
+ local start = math.max(offset, 1)
66
+
67
+ local p0 = self.points[start]
68
+ local p1 = self.points[start + 1]
69
+ local p2 = self.points[start + 2]
70
+ local p3 = self.points[start + 3]
71
+
72
+ return p0, p1, p2, p3
73
+ end
74
+
75
+ function bezier.forSample(self: Bezier, size: number, callback: (pos: vector, i: number) -> ())
76
+ local amount = self.length // size
77
+
78
+ if amount == 0 then
79
+ return
80
+ end
81
+
82
+ for i = 0, amount do
83
+ local t = i / amount
84
+ callback(self:getPositionArcSpace(t), i)
85
+ end
86
+ end
87
+
88
+ function bezier.getPosition(self: Bezier, t: number)
89
+ local i, u = self:getSegmentIndex(t)
90
+ local p0, p1, p2, p3 = self:getSegmentPoints(i)
91
+
92
+ return cubicBezier(p0, p1, p2, p3, u)
93
+ end
94
+
95
+ function bezier.getSegmentIndex(self: Bezier, t: number)
96
+ t = math.clamp(t, 0, 1)
97
+
98
+ local m = self.point_count - 1
99
+ local s = t * m
100
+
101
+ local i = math.min(math.floor(s) + 1, m)
102
+ local u = s - math.floor(s)
103
+
104
+ if t == 1 then
105
+ u = 1
106
+ end
107
+
108
+ return i, u
109
+ end
110
+
111
+ -- only works with beziers whose input is the X axis
112
+ function bezier.getEasedSegmentIndex(self: Bezier, t: number)
113
+ local i = self.point_count - 1
114
+ local u = 1
115
+
116
+ for j = 1, i do
117
+ local p0, _, _, p3 = self:getSegmentPoints(j)
118
+
119
+ local start = p0.x
120
+ local stop = p3.x
121
+
122
+ if t >= start and t < stop then
123
+ i = j
124
+ u = (t - start) / (stop - start)
125
+
126
+ break
127
+ end
128
+ end
129
+
130
+ return i, u
131
+ end
132
+
133
+ function bezier._getEaseRaw(self: Bezier, t: number)
134
+ local i, s = self:getEasedSegmentIndex(t)
135
+ local p0, p1, p2, p3 = self:getSegmentPoints(i)
136
+
137
+ for i = 1, NEWTON_MAX_ITER do
138
+ local x = cubicBezier(p0.x, p1.x, p2.x, p3.x, s)
139
+ local dx = cubicBezierDerivative(p0.x, p1.x, p2.x, p3.x, s)
140
+
141
+ if dx == 0 then
142
+ break
143
+ end
144
+
145
+ local ds = (x - t) / dx
146
+ s = s - ds
147
+
148
+ if s < 0 then
149
+ s = 0
150
+ elseif s > 1 then
151
+ s = 1
152
+ end
153
+
154
+ if math.abs(ds) < NEWTON_MIN_TOL then
155
+ break
156
+ end
157
+ end
158
+
159
+ return cubicBezier(p0, p1, p2, p3, s)
160
+ end
161
+
162
+ function bezier._buildEaseLUT(self: Bezier)
163
+ local lut = table.create(EASE_LUT_SIZE + 1)
164
+
165
+ for i = 0, EASE_LUT_SIZE do
166
+ local t = i / EASE_LUT_SIZE
167
+ lut[i] = self:_getEaseRaw(t)
168
+ end
169
+
170
+ self.ease_lut = lut
171
+ end
172
+
173
+ function bezier.getEase(self: Bezier, t: number)
174
+ local lut = self.ease_lut
175
+
176
+ if not lut then
177
+ self:_buildEaseLUT()
178
+ lut = self.ease_lut
179
+ end
180
+
181
+ t = math.clamp(t, 0, 1)
182
+
183
+ local idx = t * EASE_LUT_SIZE
184
+
185
+ local lo = math.floor(idx)
186
+ local hi = math.min(lo + 1, EASE_LUT_SIZE)
187
+
188
+ local frac = idx - lo
189
+
190
+ local loVal = lut[lo]
191
+ local hiVal = lut[hi]
192
+
193
+ if not loVal or not hiVal then
194
+ return self:_getEaseRaw(t)
195
+ end
196
+
197
+ return loVal:Lerp(hiVal, frac)
198
+ end
199
+
200
+ function bezier.getPositionArcSpace(self: Bezier, t: number)
201
+ if self.length <= 0 then
202
+ return self.points[1] or vector.zero
203
+ end
204
+
205
+ t = math.clamp(t, 0, 1)
206
+
207
+ local targetLength = t * self.cumulative_lengths[self.accuracy + 1]
208
+ local low, high, index = 1,(self.accuracy + 1)
209
+
210
+ while low < high do
211
+ index = low + (high - low) // 2
212
+
213
+ if self.cumulative_lengths[index] < targetLength then
214
+ low = index + 1
215
+ else
216
+ high = index
217
+ end
218
+ end
219
+
220
+ if self.cumulative_lengths[index] > targetLength and index > 1 then
221
+ index -= 1
222
+ end
223
+
224
+ local lengthBefore = self.cumulative_lengths[index]
225
+
226
+ if lengthBefore == targetLength then
227
+ return self:getPosition((index - 1) / self.accuracy)
228
+ else
229
+ return self:getPosition(
230
+ ((index - 1) + (targetLength - lengthBefore) / (self.cumulative_lengths[index + 1] - lengthBefore))
231
+ / self.accuracy
232
+ )
233
+ end
234
+ end
235
+
236
+ function bezier._recalculate(self: Bezier)
237
+ table.clear(self.cumulative_lengths)
238
+ table.insert(self.cumulative_lengths, 0)
239
+
240
+ local count = math.ceil(#self.points / 3)
241
+
242
+ self.point_count = count
243
+
244
+ local total = 0
245
+
246
+ local prev: vector
247
+
248
+ for j = 1, self.accuracy do
249
+ local p = self:getPosition(j / self.accuracy)
250
+
251
+ if not prev then
252
+ prev = self:getPosition(0)
253
+ end
254
+
255
+ total += vector.magnitude(p - prev)
256
+ prev = p
257
+
258
+ table.insert(self.cumulative_lengths, total)
259
+ end
260
+
261
+ self.length = total
262
+
263
+ for i, len in self.cumulative_lengths do
264
+ self.cumulative_lengths[i] = len / total
265
+ end
266
+ end
267
+
268
+ return bezier
@@ -0,0 +1,289 @@
1
+ local utility = require(script.Parent.Parent.mod.utility)
2
+
3
+ local RECYCLE_INTERVAL = 15
4
+
5
+ local cache = {}
6
+ cache.__index = cache
7
+
8
+ type item = {
9
+ key: any,
10
+ value: Instance,
11
+
12
+ added: number,
13
+ dependents: number,
14
+ }
15
+
16
+ type params = {
17
+ size: number?,
18
+ excess_lifetime: number?,
19
+ on_free: ((item) -> ())?,
20
+ }
21
+
22
+ export type ObjectCache = typeof(cache) & {
23
+ ref: Instance,
24
+ parent: Instance?,
25
+
26
+ params: params,
27
+
28
+ amount: number,
29
+ restore_amount: number,
30
+
31
+ part_mode: boolean,
32
+
33
+ item_map: { [any]: item },
34
+ unused: { item },
35
+
36
+ scope: { any },
37
+ }
38
+
39
+ local function reconcile(a, b)
40
+ for k, v in b do
41
+ if a[k] == nil then
42
+ a[k] = v
43
+ end
44
+ end
45
+
46
+ return a
47
+ end
48
+
49
+ local moveParts: { BasePart } = table.create(10_000)
50
+ local movePartCFrames: { CFrame } = table.create(10_000)
51
+
52
+ local partUpdateScheduled = false
53
+
54
+ local bulkPartMoveThread = coroutine.create(function()
55
+ while true do
56
+ workspace:BulkMoveTo(moveParts, movePartCFrames, Enum.BulkMoveMode.FireCFrameChanged)
57
+
58
+ table.clear(moveParts)
59
+ table.clear(movePartCFrames)
60
+
61
+ partUpdateScheduled = false
62
+
63
+ coroutine.yield()
64
+ end
65
+ end)
66
+
67
+ function cache.new(ref: Instance, parent: Instance?, params: params?)
68
+ local self = setmetatable({}, cache)
69
+
70
+ self.ref = ref
71
+ self.parent = parent
72
+
73
+ self.amount = 0
74
+ self.restore_amount = 0
75
+
76
+ self.params = reconcile(params or {}, {
77
+ size = 100,
78
+ excess_lifetime = 30,
79
+ })
80
+
81
+ self.scope = { ref, parent }
82
+ self.unused = table.create(self.params.size)
83
+ self.item_map = {}
84
+
85
+ self.part_mode = ref:IsA("BasePart")
86
+
87
+ for _ = 1, self.params.size do
88
+ self:_add()
89
+ end
90
+
91
+ table.insert(
92
+ self.scope,
93
+ task.spawn(function()
94
+ while task.wait(RECYCLE_INTERVAL) do
95
+ if self.restore_amount > 0 then
96
+ for _ = 1, self.restore_amount do
97
+ self:_add()
98
+ self.restore_amount -= 1
99
+ end
100
+ end
101
+
102
+ if self.amount <= self.params.size then
103
+ continue
104
+ end
105
+
106
+ local offset = 0
107
+
108
+ for i = 1, #self.unused do
109
+ if self.amount <= self.params.size then
110
+ break
111
+ end
112
+
113
+ i -= offset
114
+
115
+ local item = self.unused[i]
116
+
117
+ if item.dependents ~= 0 or (os.clock() - item.added) > self.params.excess_lifetime then
118
+ continue
119
+ end
120
+
121
+ item:destroy()
122
+
123
+ table.remove(self.unused, i)
124
+
125
+ self.item_map[item.key] = nil
126
+ self.amount -= 1
127
+
128
+ offset += 1
129
+ end
130
+ end
131
+ end)
132
+ )
133
+
134
+ return self
135
+ end
136
+
137
+ function cache._add(self: ObjectCache, key: any?, excess: boolean?)
138
+ local obj = self.ref:Clone()
139
+ obj.Archivable = false
140
+ obj.Parent = self.parent
141
+
142
+ local item = {
143
+ key = key,
144
+ value = obj,
145
+
146
+ added = os.clock(),
147
+ dependents = 1,
148
+ }
149
+
150
+ local ignoreDestroy = false
151
+
152
+ function item:destroy()
153
+ ignoreDestroy = true
154
+ obj:Destroy()
155
+ end
156
+
157
+ self.amount += 1
158
+
159
+ obj.Destroying:Connect(function()
160
+ if ignoreDestroy then
161
+ return
162
+ end
163
+
164
+ local index = table.find(self.unused, item)
165
+
166
+ if index then
167
+ table.remove(self.unused, index)
168
+ end
169
+
170
+ if item.key then
171
+ self.item_map[item.key] = nil
172
+ end
173
+
174
+ self.amount -= 1
175
+ self.restore_amount += 1
176
+ end)
177
+
178
+ if key then
179
+ self.item_map[key] = item
180
+ end
181
+
182
+ if not excess then
183
+ table.insert(self.unused, item)
184
+ end
185
+
186
+ return item
187
+ end
188
+
189
+ function cache.has(self: ObjectCache, key: any)
190
+ return if self.item_map[key] then true else false
191
+ end
192
+
193
+ function cache.peek(self: ObjectCache, key: any)
194
+ return self.item_map[key]
195
+ end
196
+
197
+ function cache.get(self: ObjectCache, key: any)
198
+ if self:has(key) then
199
+ local item = self:peek(key)
200
+
201
+ if item then
202
+ item.dependents += 1
203
+ end
204
+
205
+ return item.value
206
+ end
207
+
208
+ local item = table.remove(self.unused)
209
+
210
+ if item then
211
+ item.key = key
212
+ item.added = os.clock()
213
+
214
+ self.item_map[key] = item
215
+ else
216
+ item = self:_add(key, true)
217
+ end
218
+
219
+ if self.part_mode then
220
+ local meta = {}
221
+
222
+ function meta:__newindex(key, value)
223
+ if key == "CFrame" then
224
+ table.insert(moveParts, item.value)
225
+ table.insert(movePartCFrames, value)
226
+
227
+ if not partUpdateScheduled then
228
+ partUpdateScheduled = true
229
+ task.defer(bulkPartMoveThread)
230
+ end
231
+ else
232
+ item.value[key] = value
233
+ end
234
+ end
235
+
236
+ function meta:__index(key)
237
+ local v = item.value[key]
238
+
239
+ if typeof(v) == "function" then
240
+ return function(_, ...)
241
+ return v(item.value, ...)
242
+ end
243
+ else
244
+ return v
245
+ end
246
+ end
247
+
248
+ return setmetatable({
249
+ _getReal = function()
250
+ return item.value
251
+ end,
252
+ }, meta)
253
+ else
254
+ return item.value
255
+ end
256
+ end
257
+
258
+ function cache.free(self: ObjectCache, key: any)
259
+ local item = self.item_map[key]
260
+
261
+ if not item then
262
+ return
263
+ end
264
+
265
+ item.dependents = math.max(item.dependents - 1, 0)
266
+
267
+ if item.dependents == 0 then
268
+ item.added = os.clock()
269
+
270
+ if self.params.on_free then
271
+ task.spawn(self.params.on_free, item)
272
+ end
273
+
274
+ table.insert(self.unused, item)
275
+ end
276
+ end
277
+
278
+ function cache.destroy(self: ObjectCache)
279
+ utility.cleanupScope(self.scope)
280
+
281
+ for _, item in self.item_map do
282
+ item.value:Destroy()
283
+ end
284
+
285
+ table.clear(self.unused)
286
+ table.clear(self.item_map)
287
+ end
288
+
289
+ return cache
@@ -0,0 +1,62 @@
1
+ local ObjectCache = require(script.Parent.Parent.obj.ObjectCache)
2
+
3
+ local utility = require(script.Parent.Parent.mod.utility)
4
+
5
+ local caches = {}
6
+
7
+ function caches.init(scope: { any })
8
+ local shared_part_cache
9
+
10
+ do
11
+ local template = Instance.new("Part")
12
+ template.Transparency = 1
13
+ template.Anchored = true
14
+ template.CanCollide = false
15
+ template.CanQuery = false
16
+ template.Locked = true
17
+
18
+ local parent = Instance.new("Folder")
19
+ parent.Name = "DO_NOT_REMOVE_ForgeSharedPartCache"
20
+ parent.Archivable = false
21
+ parent.Parent = workspace.Terrain
22
+
23
+ utility.protectParent(scope, parent)
24
+
25
+ shared_part_cache = ObjectCache.new(template, parent, {
26
+ size = 150,
27
+ on_free = function(item)
28
+ local part = item.value
29
+
30
+ part.Transparency = 1
31
+
32
+ part.Anchored = true
33
+ part.CanQuery = false
34
+ part.CanCollide = false
35
+
36
+ part.CollisionGroup = "ForgeMouseIgnore"
37
+
38
+ part.AssemblyLinearVelocity = Vector3.zero
39
+ part.AssemblyAngularVelocity = Vector3.zero
40
+
41
+ part.Parent = parent
42
+
43
+ -- this is probably not very good for performance lol
44
+ -- but I don't think there's currently a more reasonable
45
+ -- way of recycling parts
46
+ part:ClearAllChildren()
47
+ end,
48
+ })
49
+ end
50
+
51
+ local result = {
52
+ shared_part = shared_part_cache,
53
+ }
54
+
55
+ table.insert(scope, function()
56
+ shared_part_cache:destroy()
57
+ end)
58
+
59
+ return result
60
+ end
61
+
62
+ return caches