Back to Home

ESO Lua File v101044

libraries/zo_particles/zo_particletypes.lua

[◄ back to folders ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
--Bent Arc Particle
local cos = math.cos
local sin = math.sin
local atan = math.atan
local atan2 = math.atan2
local zo_lerp = zo_lerp
local zo_floor = zo_floor
local zo_random = zo_random
function ZO_BentArcParticle_OnUpdate(self, timeS)
    local parameters = self.parameters
    local finalMagnitude = parameters["BentArcFinalMagnitude"]
    local velocity = parameters["BentArcVelocity"]
    local azimuthStartRadians = parameters["BentArcAzimuthStartRadians"]
    local elevationStartRadians = parameters["BentArcElevationStartRadians"]
    local azimuthChangeRadians = parameters["BentArcAzimuthChangeRadians"]
    local elevationChangeRadians = parameters["BentArcElevationChangeRadians"]
    local progress = self:GetProgress(timeS)
    local magnitude
    if finalMagnitude then
        local magnitudeProgress = progress
        local easing = parameters["BentArcEasing"]
        if easing then
            magnitudeProgress = easing(progress)
        end
        magnitude = zo_lerp(0, finalMagnitude, magnitudeProgress)
    elseif velocity then
        magnitude = self:GetElapsedTime(timeS) * velocity
    end
    local bendProgress = progress
    local easing = parameters["BentArcBendEasing"]
    if easing then
        bendProgress = easing(progress)
    end
    local azimuthRadians = zo_lerp(azimuthStartRadians, azimuthStartRadians + azimuthChangeRadians, bendProgress)
    local elevationRadians = zo_lerp(elevationStartRadians, elevationStartRadians + elevationChangeRadians, bendProgress)
    
    --Spherical coordinates to Cartesian
    local h = magnitude * cos(elevationRadians)
    local z = h * sin(azimuthRadians)
    local y = magnitude * sin(elevationRadians)
    local x = h * cos(azimuthRadians)
    --X is right, Y is up, Z is toward the screen
    return x, y, z
end
ZO_BentArcParticle_SceneGraph = ZO_SceneGraphParticle:Subclass()
function ZO_BentArcParticle_SceneGraph:OnUpdate(timeS)
    ZO_SceneGraphParticle.OnUpdate(self, timeS)
end
function ZO_BentArcParticle_SceneGraph:New(...)
    return ZO_SceneGraphParticle.New(self, ...)
end
ZO_BentArcParticle_Control =  ZO_ControlParticle:Subclass()
function ZO_BentArcParticle_Control:OnUpdate(timeS)
    ZO_ControlParticle.OnUpdate(self, timeS)
    local x, y, z = ZO_BentArcParticle_OnUpdate(self, timeS)
    --Control particles expect that Y is down
    y = -y
    self:SetPosition(x, y, z)
    local parameters = self.parameters
    if parameters["BentArcOrientWithMotion"] then
        --Numerical solution to orient along the spiral. We use the slope between the last two positions to compute the tangent to the spiral. The angle
        --to orient the texture's right-middle point to the spiral is the 2*PI - the tangent's angle, or alternately minus the tangent's angle.
        if self.lastX then
            local angle = atan2(y - self.lastY, x - self.lastX)
            local offsetRadians = parameters["BentArcOrientWithMotionTextureRotationRadians"] or 0
            self.textureControl:SetTextureRotation(-angle - offsetRadians)
        end
        self.lastX = x
        self.lastY = y
    end
end
function ZO_BentArcParticle_Control:New(...)
    return ZO_ControlParticle.New(self, ...)
end
-- Physics Particle
ZO_PhysicsParticle_Control =  ZO_ControlParticle:Subclass()
function ZO_PhysicsParticle_Control:New(...)
    return ZO_ControlParticle.New(self, ...)
end
local MAX_ACCELERATIONS = 5
local ACCELERATION_ELEVATION_RADIANS_PARAMETER_NAMES = {}
local ACCELERATION_MAGNITUDE_PARAMETER_NAMES = {}
for accelerationIndex = 1, MAX_ACCELERATIONS do
    table.insert(ACCELERATION_ELEVATION_RADIANS_PARAMETER_NAMES, "PhysicsAccelerationElevationRadians" .. accelerationIndex)
    table.insert(ACCELERATION_MAGNITUDE_PARAMETER_NAMES, "PhysicsAccelerationMagnitude" .. accelerationIndex)
end
function ZO_PhysicsParticle_Control:Start(...)
    ZO_ControlParticle.Start(self, ...)
    
    self.velocityX = 0
    self.velocityY = 0
    local parameters = self.parameters
    local initialVelocityElevationRadians = parameters["PhysicsInitialVelocityElevationRadians"]
    if initialVelocityElevationRadians then
        local initialVelocityMagnitude = parameters["PhysicsInitialVelocityMagnitude"]
        if initialVelocityMagnitude then
            self.velocityX = initialVelocityMagnitude * cos(initialVelocityElevationRadians)
            self.velocityY = initialVelocityMagnitude * sin(initialVelocityElevationRadians)
        end
    end
    local combinedAccelerationX = 0
    local combinedAccelerationY = 0
    for accelerationIndex = 1, MAX_ACCELERATIONS do
        local accelerationElevationRadians = parameters[ACCELERATION_ELEVATION_RADIANS_PARAMETER_NAMES[accelerationIndex]]
        if not accelerationElevationRadians then
            break
        end
        local accelerationMagnitude = parameters[ACCELERATION_MAGNITUDE_PARAMETER_NAMES[accelerationIndex]]
        if not accelerationMagnitude then
            break
        end
        local accelerationX = accelerationMagnitude * cos(accelerationElevationRadians)
        local accelerationY = accelerationMagnitude * sin(accelerationElevationRadians)
        combinedAccelerationX = combinedAccelerationX + accelerationX
        combinedAccelerationY = combinedAccelerationY + accelerationY
    end
    self.combinedAccelerationX = combinedAccelerationX
    self.combinedAccelerationY = combinedAccelerationY
end
--Numerical can do complex calculations like drag, but it does so by doing calculations in intervals, since it's not a simple equation of time
ZO_NumericalPhysicsParticle_Control =  ZO_PhysicsParticle_Control:Subclass()
function ZO_NumericalPhysicsParticle_Control:New(...)
    return ZO_PhysicsParticle_Control.New(self, ...)
end
do
    local DEFAULT_MIN_STEPS_PER_SECOND = 30
    local MAX_STEPS_FOR_UPDATE = 10
    function ZO_NumericalPhysicsParticle_Control:Start(...)
        ZO_PhysicsParticle_Control.Start(self, ...)
    
        self.lastUpdateTimeS = self.startTimeS
        self.displacementX = 0
        self.displacementY = 0
        local parameters = self.parameters
        local minStepsPerSecond = parameters["PhysicsMinStepsPerSecond"] or DEFAULT_MIN_STEPS_PER_SECOND
        self.minStepIntervalS = 1 / minStepsPerSecond
        self.maxTimeForMinStepIntervalsS = self.minStepIntervalS * MAX_STEPS_FOR_UPDATE
        self.dragMultiplier = parameters["PhysicsDragMultiplier"] or 0
    end
    function ZO_NumericalPhysicsParticle_Control:OnUpdate(timeS)
        ZO_PhysicsParticle_Control.OnUpdate(self, timeS)
        local lastUpdateTimeS = self.lastUpdateTimeS
        local timeSinceLastUpdateS = timeS - lastUpdateTimeS
    
        local stepsIntervalS = self.minStepIntervalS
        if timeSinceLastUpdateS < stepsIntervalS then
            stepsIntervalS = timeSinceLastUpdateS
        elseif timeSinceLastUpdateS > self.maxTimeForMinStepIntervalsS then
            -- If we've hit a long spike since the last update, split the delta into equal intervals looping up to MAX_STEPS_FOR_UPDATE times to catch up
            stepsIntervalS = timeSinceLastUpdateS / MAX_STEPS_FOR_UPDATE
        end
        local displacementX = self.displacementX
        local displacementY = self.displacementY
        local velocityX = self.velocityX
        local velocityY = self.velocityY
        local velocityChangePerStepX = self.combinedAccelerationX * stepsIntervalS
        local velocityChangePerStepY = self.combinedAccelerationY * stepsIntervalS
        local dragMultiplier = self.dragMultiplier
        repeat
            local dragVelocityChangeX = velocityX * dragMultiplier * stepsIntervalS
            local dragVelocityChangeY = velocityY * dragMultiplier * stepsIntervalS
            velocityX = velocityX + velocityChangePerStepX - dragVelocityChangeX
            velocityY = velocityY + velocityChangePerStepY - dragVelocityChangeY
            ----------------------------------------------------------------------
            displacementX = displacementX + (velocityX * stepsIntervalS)
            displacementY = displacementY + (velocityY * stepsIntervalS)
            lastUpdateTimeS = lastUpdateTimeS + stepsIntervalS
        until (lastUpdateTimeS + stepsIntervalS) > timeS
        self.lastUpdateTimeS = lastUpdateTimeS
        self.displacementX = displacementX
        self.displacementY = displacementY
        self.velocityX = velocityX
        self.velocityY = velocityY
        --Control particles expect that Y is down
        self:SetPosition(displacementX, -displacementY, 0)
    end
end
--Analytical calculates physics as a direct equation of elapsed time. Does not support complex functionality like drag
ZO_AnalyticalPhysicsParticle_Control =  ZO_PhysicsParticle_Control:Subclass()
function ZO_AnalyticalPhysicsParticle_Control:New(...)
    return ZO_PhysicsParticle_Control.New(self, ...)
end
function ZO_AnalyticalPhysicsParticle_Control:OnUpdate(timeS)
    ZO_PhysicsParticle_Control.OnUpdate(self, timeS)
    local elapsedTimeS = timeS - self.startTimeS
    local velocityChangeX = self.combinedAccelerationX * elapsedTimeS
    local velocityChangeY = self.combinedAccelerationY * elapsedTimeS
    local x = (self.velocityX + velocityChangeX) * elapsedTimeS
    local y = (self.velocityY + velocityChangeY) * elapsedTimeS
    --Control particles expect that Y is down
    self:SetPosition(x, -y, 0)
end
--Stationary Paricle
ZO_StationaryParticle_Control = ZO_ControlParticle:Subclass()
function ZO_StationaryParticle_Control:New(...)
    return ZO_ControlParticle.New(self, ...)
end
function ZO_StationaryParticle_Control:OnUpdate(timeS)
    ZO_ControlParticle.OnUpdate(self, timeS)
    -- Offset is handled in the base update, and SetPosition takes that into consideration
    self:SetPosition(0, 0, 0)
end
--Leaf Particle
--A leaf like motion to the right and down from the origin, based on following sections of a tangent curve. Specify the top and bottom (y-values) and the leaf will move through that
--section of the tangent curve. Descent is a factor on the tangent curve (0, infinity). Higher descents make a steeper curve, while lower descents mean a gentler curve. Fall height is the
--UI space height that the curve will be mapped to. The code that orients the leaf to the direction of travel assumes that the leaf point is a 0 degrees on the unit circle. If that is not true
--it can be corrected by rotating the leaf using LeafTextureRotationRadians.
ZO_LeafParticle_Control = ZO_ControlParticle:Subclass()
function ZO_LeafParticle_Control:New(...)
    return ZO_ControlParticle.New(self, ...)
end
--Inverse of y = -descent * tan(x)
local function ComputeSectionX(sectionY, descent)
    return atan(-sectionY / descent)
end
function ZO_LeafParticle_Control:Start(...)
    ZO_ControlParticle.Start(self, ...)
    local parameters = self.parameters
    self.sectionTopX = ComputeSectionX(parameters["LeafSectionTop"], parameters["LeafDescent"])
end
function ZO_LeafParticle_Control:OnUpdate(timeS)
    ZO_ControlParticle.OnUpdate(self, timeS)
    local progress = self:GetProgress(timeS)
    local parameters = self.parameters
    local easing = parameters["LeafEasing"]
    if easing then
        progress = easing(progress)
    end
    local sectionTopY = parameters["LeafSectionTop"]
    local sectionBottomY = parameters["LeafSectionBottom"]
    local descent = parameters["LeafDescent"]
    local sectionHeight = sectionTopY - sectionBottomY
    local fallHeight = parameters["LeafFallHeight"]
    local leafTextureRotationRadians = parameters["LeafTextureRotationRadians"]
    --We move linearly from 0 to fallHeight along the Y (This means that the more the curve travels in the X the faster it goes through that section. It is not constant velocity).
    local offsetY = fallHeight * progress
    --Figure out where we are vertically on the section of the tangent curve
    local sectionY = zo_lerp(sectionTopY, sectionBottomY, progress)
    --Find the X the corresponds to that Y
    local sectionX = ComputeSectionX(sectionY, descent)
    --Convert that to an offset from the starting X value
    local sectionOffsetX = sectionX - self.sectionTopX
    --Finally, scale this offset to UI coordinates using the correspondence between the section height and the fall height to determine the scaling factor
    local offsetX = (sectionOffsetX / sectionHeight) * fallHeight
    --The derivative of -descent * tan(x) is -descent/cos^2(x). Orient the texture 0 radian point to the direction of travel.
    local cosSectionX = cos(sectionX)
    local slope = -descent / (cosSectionX * cosSectionX)
    local tumbleRadians = parameters["LeafTumbleRadians"]
    self.textureControl:SetTextureRotation(math.atan(slope) + leafTextureRotationRadians + tumbleRadians * progress)
    self:SetPosition(offsetX, offsetY, 0)
end
--Flow Particle
--Sends the particle through a series of "posts" that describe a set of vertical ranges. For example:
--
--
-- * * * * * *
-- * * * * * * * * * *
-- * * * * * * * **
-- * * * * *
--
--Particles will be assigned a normalized vertical offset and travel from post to post across where that normalized offset would lie.
--For example, if a particle was assigned 50% it would travel through the middle of each post, interpolating from middle to middle inbetween the posts.
ZO_FlowParticle_Control = ZO_ControlParticle:Subclass()
function ZO_FlowParticle_Control:New(...)
    return ZO_ControlParticle.New(self, ...)
end
function ZO_FlowParticle_Control:Start(...)
    ZO_ControlParticle.Start(self, ...)
    self.normalizedY = zo_random()
end
function ZO_FlowParticle_Control:OnUpdate(timeS)
    ZO_ControlParticle.OnUpdate(self, timeS)
    local progress = self:GetProgress(timeS)
    local parameters = self.parameters
    local areaTop = parameters["FlowAreaTop"]
    local areaBottom = parameters["FlowAreaBottom"]
    local areaLeft = parameters["FlowAreaLeft"]
    local areaRight = parameters["FlowAreaRight"]
    local normalizedPosts = parameters["FlowNormalizedPosts"]
    local numPosts = #normalizedPosts
    local temp = progress * (numPosts - 1)
    local progressBetweenPosts = temp % 1
    local previousPostIndex = zo_floor(temp) + 1
    local previousPost = normalizedPosts[previousPostIndex]
    --the or handles the case where progress is exactly 1
    local nextPost = normalizedPosts[previousPostIndex + 1] or normalizedPosts[numPosts]
    --index 1 holds the top of the post and index 2 holds the bottom of the post
    local normalizedFlowTop = zo_lerp(previousPost[1], nextPost[1], progressBetweenPosts)
    local normalizedFlowBottom = zo_lerp(previousPost[2], nextPost[2], progressBetweenPosts)
    local normalizedY = zo_lerp(normalizedFlowTop, normalizedFlowBottom, self.normalizedY)
    local offsetY = zo_lerp(areaTop, areaBottom, normalizedY)
    local offsetX = zo_lerp(areaLeft, areaRight, progress)
    self:SetPosition(offsetX, offsetY, 0)
end