@quenty/fakeskybox 11.15.0 → 11.16.0
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.
- package/CHANGELOG.md +12 -0
- package/deploy.nevermore.json +10 -0
- package/package.json +18 -5
- package/src/Client/FakeSkyboxServiceClient.lua +17 -0
- package/src/Client/Render/FakeSkybox.lua +548 -0
- package/src/Client/Render/FakeSkybox.spec.lua +27 -0
- package/src/Client/Render/FakeSkyboxRenderMethod.lua +15 -0
- package/src/Client/Render/SkyboxRenderPart.lua +258 -0
- package/src/Client/Render/SkyboxSide.lua +135 -0
- package/src/Server/FakeSkyboxService.lua +17 -0
- package/src/jest.config.lua +3 -0
- package/test/default.project.json +32 -0
- package/test/scripts/Client/ClientMain.client.lua +235 -0
- package/test/scripts/Server/ServerMain.server.lua +18 -0
- package/src/Client/FakeSkybox.lua +0 -167
- package/src/Client/FakeSkyboxSide.lua +0 -89
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [11.16.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/fakeskybox@11.15.0...@quenty/fakeskybox@11.16.0) (2026-04-23)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Add fake skybox support ([2c6a1ed](https://github.com/Quenty/NevermoreEngine/commit/2c6a1ed6ed6a4897aab6f9e04e4a3b55b246603d))
|
|
12
|
+
* Additional improvments ([44896ef](https://github.com/Quenty/NevermoreEngine/commit/44896efe8dd9506ad6002bc41f816b9b2b482ebc))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
6
18
|
# [11.15.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/fakeskybox@11.14.0...@quenty/fakeskybox@11.15.0) (2026-02-20)
|
|
7
19
|
|
|
8
20
|
**Note:** Version bump only for package @quenty/fakeskybox
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/fakeskybox",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.16.0",
|
|
4
4
|
"description": "Allow transitions between skyboxes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -29,12 +29,25 @@
|
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@quenty/acceltween": "2.7.0",
|
|
32
|
-
"@quenty/
|
|
33
|
-
"@quenty/
|
|
34
|
-
"@quenty/
|
|
32
|
+
"@quenty/baseobject": "10.13.0",
|
|
33
|
+
"@quenty/basicpane": "13.30.0",
|
|
34
|
+
"@quenty/blend": "12.34.0",
|
|
35
|
+
"@quenty/colorsequenceutils": "7.11.0",
|
|
36
|
+
"@quenty/enums": "1.3.0",
|
|
37
|
+
"@quenty/instanceutils": "13.29.0",
|
|
38
|
+
"@quenty/loader": "10.11.0",
|
|
39
|
+
"@quenty/maid": "3.9.0",
|
|
40
|
+
"@quenty/nevermore-test-runner": "1.4.0",
|
|
41
|
+
"@quenty/rx": "13.28.0",
|
|
42
|
+
"@quenty/servicebag": "11.17.0",
|
|
43
|
+
"@quenty/signal": "7.13.0",
|
|
44
|
+
"@quenty/statestack": "14.31.0",
|
|
45
|
+
"@quenty/sunpositionutils": "2.5.0",
|
|
46
|
+
"@quenty/valueobject": "13.30.0",
|
|
47
|
+
"@quentystudios/jest-lua": "3.10.0-quenty.2"
|
|
35
48
|
},
|
|
36
49
|
"publishConfig": {
|
|
37
50
|
"access": "public"
|
|
38
51
|
},
|
|
39
|
-
"gitHead": "
|
|
52
|
+
"gitHead": "9413391da8b6f9026762285b601fd1d37d385a54"
|
|
40
53
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
@class FakeSkyboxServiceClient
|
|
3
|
+
]=]
|
|
4
|
+
|
|
5
|
+
local require = require(script.Parent.loader).load(script)
|
|
6
|
+
|
|
7
|
+
local ServiceBag = require("ServiceBag")
|
|
8
|
+
|
|
9
|
+
local FakeSkyboxServiceClient = {}
|
|
10
|
+
FakeSkyboxServiceClient.ServiceName = "FakeSkyboxServiceClient"
|
|
11
|
+
|
|
12
|
+
function FakeSkyboxServiceClient:Init(serviceBag: ServiceBag.ServiceBag)
|
|
13
|
+
assert(not self._serviceBag, "Already initialized")
|
|
14
|
+
self._serviceBag = assert(serviceBag, "No serviceBag")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
return FakeSkyboxServiceClient
|
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[=[
|
|
3
|
+
Allow transitions between skyboxes
|
|
4
|
+
|
|
5
|
+
@class FakeSkybox
|
|
6
|
+
]=]
|
|
7
|
+
|
|
8
|
+
local require = require(script.Parent.loader).load(script)
|
|
9
|
+
|
|
10
|
+
local Lighting = game:GetService("Lighting")
|
|
11
|
+
local Workspace = game:GetService("Workspace")
|
|
12
|
+
|
|
13
|
+
local BasicPane = require("BasicPane")
|
|
14
|
+
local BasicPaneUtils = require("BasicPaneUtils")
|
|
15
|
+
local Blend = require("Blend")
|
|
16
|
+
local FakeSkyboxRenderMethod = require("FakeSkyboxRenderMethod")
|
|
17
|
+
local Observable = require("Observable")
|
|
18
|
+
local Rx = require("Rx")
|
|
19
|
+
local RxInstanceUtils = require("RxInstanceUtils")
|
|
20
|
+
local RxStateStackUtils = require("RxStateStackUtils")
|
|
21
|
+
local SkyboxRenderPart = require("SkyboxRenderPart")
|
|
22
|
+
local SkyboxSide = require("SkyboxSide")
|
|
23
|
+
local SpringObject = require("SpringObject")
|
|
24
|
+
local SunPositionUtils = require("SunPositionUtils")
|
|
25
|
+
local ValueObject = require("ValueObject")
|
|
26
|
+
|
|
27
|
+
local SKYBOX_PROPERTY_IMAGE_MAP = table.freeze({
|
|
28
|
+
[Enum.NormalId.Top] = "SkyboxUp",
|
|
29
|
+
[Enum.NormalId.Bottom] = "SkyboxDn",
|
|
30
|
+
|
|
31
|
+
-- Bind backwards
|
|
32
|
+
[Enum.NormalId.Right] = "SkyboxLf",
|
|
33
|
+
[Enum.NormalId.Left] = "SkyboxRt",
|
|
34
|
+
|
|
35
|
+
[Enum.NormalId.Front] = "SkyboxFt",
|
|
36
|
+
[Enum.NormalId.Back] = "SkyboxBk",
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
local DEFAULT_SKY_DATA = table.freeze({
|
|
40
|
+
SkyboxUp = "rbxasset://textures/sky/sky512_up.tex",
|
|
41
|
+
SkyboxDn = "rbxasset://textures/sky/sky512_dn.tex",
|
|
42
|
+
SkyboxLf = "rbxasset://textures/sky/sky512_lf.tex",
|
|
43
|
+
SkyboxRt = "rbxasset://textures/sky/sky512_rt.tex",
|
|
44
|
+
SkyboxFt = "rbxasset://textures/sky/sky512_ft.tex",
|
|
45
|
+
SkyboxBk = "rbxasset://textures/sky/sky512_bk.tex",
|
|
46
|
+
|
|
47
|
+
-- Defaults
|
|
48
|
+
SunTextureId = "rbxasset://sky/sun.jpg",
|
|
49
|
+
MoonTextureId = "rbxasset://sky/moon.jpg",
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
-- Can't really render the black image propery
|
|
53
|
+
local DECAL_REPLACEMENTS = {
|
|
54
|
+
["rbxasset://sky/sun.jpg"] = "rbxassetid://6196665106",
|
|
55
|
+
["rbxasset://sky/moon.jpg"] = "rbxassetid://6444320592",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
local CELESTRIAL_BODY_OFFSET_STUDS = 10
|
|
59
|
+
local CELESTRIAL_BODY_MAX_DISTANCE_RENDER_HACK = 1000
|
|
60
|
+
local CELESTRIAL_BODY_PART_DEPTH = 1
|
|
61
|
+
|
|
62
|
+
local FakeSkybox = setmetatable({}, BasicPane)
|
|
63
|
+
FakeSkybox.__index = FakeSkybox
|
|
64
|
+
FakeSkybox.ClassName = "FakeSkybox"
|
|
65
|
+
|
|
66
|
+
export type FakeSkybox =
|
|
67
|
+
typeof(setmetatable(
|
|
68
|
+
{} :: {
|
|
69
|
+
Gui: Folder,
|
|
70
|
+
_skyboxWidth: ValueObject.ValueObject<number>,
|
|
71
|
+
_skyboxZOffset: ValueObject.ValueObject<number>,
|
|
72
|
+
_percentVisible: SpringObject.SpringObject<number>,
|
|
73
|
+
_skyValue: ValueObject.ValueObject<Sky?>,
|
|
74
|
+
_atmosphereValue: ValueObject.ValueObject<Atmosphere?>,
|
|
75
|
+
_cameraValue: ValueObject.ValueObject<Camera?>,
|
|
76
|
+
_viewportLightingArgs: ValueObject.ValueObject<SunPositionUtils.ViewportLightingArgs>,
|
|
77
|
+
_renderMethod: ValueObject.ValueObject<FakeSkyboxRenderMethod.FakeSkyboxRenderMethod>,
|
|
78
|
+
|
|
79
|
+
-- Cache
|
|
80
|
+
_observeSkyboxCFrameCache: Observable.Observable<CFrame>?,
|
|
81
|
+
_observeSunPositionDataCache: Observable.Observable<SunPositionUtils.SunPositionData>?,
|
|
82
|
+
_observeSkyboxGradientCache: Observable.Observable<ColorSequence>?,
|
|
83
|
+
},
|
|
84
|
+
{} :: typeof({ __index = FakeSkybox })
|
|
85
|
+
))
|
|
86
|
+
& BasicPane.BasicPane
|
|
87
|
+
|
|
88
|
+
--[=[
|
|
89
|
+
Constructs a new skybox defaulting to the current lighting + camera.
|
|
90
|
+
]=]
|
|
91
|
+
function FakeSkybox.new(): FakeSkybox
|
|
92
|
+
local self: FakeSkybox = setmetatable(BasicPane.new() :: any, FakeSkybox)
|
|
93
|
+
|
|
94
|
+
-- State
|
|
95
|
+
self._skyValue = self._maid:Add(ValueObject.new(nil))
|
|
96
|
+
self._atmosphereValue = self._maid:Add(ValueObject.new(nil))
|
|
97
|
+
self._percentVisible = self._maid:Add(SpringObject.new(0, 30))
|
|
98
|
+
self._skyboxWidth = self._maid:Add(ValueObject.new(2000, "number")) -- We want 2,700 but we are limited by max size
|
|
99
|
+
self._skyboxZOffset = self._maid:Add(ValueObject.new(-700, "number")) -- This adds the extra 700
|
|
100
|
+
self._cameraValue = self._maid:Add(ValueObject.new(nil))
|
|
101
|
+
self._viewportLightingArgs =
|
|
102
|
+
self._maid:Add(ValueObject.fromObservable(SunPositionUtils.observeViewportLightingArgs()))
|
|
103
|
+
self._renderMethod =
|
|
104
|
+
self._maid:Add(ValueObject.new(FakeSkyboxRenderMethod.SURFACEGUI :: any, FakeSkyboxRenderMethod:GetInterface()))
|
|
105
|
+
|
|
106
|
+
self._maid:GiveTask(self:ObserveVisible():Subscribe(function(isVisible, doNotAnimate)
|
|
107
|
+
self._percentVisible:SetTarget(isVisible and 1 or 0, doNotAnimate)
|
|
108
|
+
end))
|
|
109
|
+
|
|
110
|
+
self._maid:GiveTask(self:_render():Subscribe(function(gui)
|
|
111
|
+
self.Gui = gui
|
|
112
|
+
end))
|
|
113
|
+
|
|
114
|
+
self:_renderSkyboxSides()
|
|
115
|
+
self:_renderSun()
|
|
116
|
+
self:_renderMoon()
|
|
117
|
+
|
|
118
|
+
-- Set defaults
|
|
119
|
+
self:SetSky(nil)
|
|
120
|
+
self:SetAtmosphere(nil)
|
|
121
|
+
self:SetCamera(nil)
|
|
122
|
+
|
|
123
|
+
return self
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
--[=[
|
|
127
|
+
Set the part size
|
|
128
|
+
]=]
|
|
129
|
+
function FakeSkybox.SetPartSize(self: FakeSkybox, skyboxWidth: ValueObject.Mountable<number>): ()
|
|
130
|
+
return self._skyboxWidth:Mount(skyboxWidth)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
--[=[
|
|
134
|
+
Sets the transition speed of the fake skybox
|
|
135
|
+
]=]
|
|
136
|
+
function FakeSkybox.SetSpeed(self: FakeSkybox, speed: number): ()
|
|
137
|
+
self._percentVisible:SetSpeed(speed)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
--[=[
|
|
141
|
+
Sets the render method for the skybox parts
|
|
142
|
+
]=]
|
|
143
|
+
function FakeSkybox.SetRenderMethod(
|
|
144
|
+
self: FakeSkybox,
|
|
145
|
+
renderMethod: ValueObject.Mountable<FakeSkyboxRenderMethod.FakeSkyboxRenderMethod>
|
|
146
|
+
): ()
|
|
147
|
+
return self._renderMethod:Mount(renderMethod)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
--[=[
|
|
151
|
+
Sets the skybox
|
|
152
|
+
]=]
|
|
153
|
+
function FakeSkybox.SetSky(self: FakeSkybox, sky: ValueObject.Mountable<Sky?>?): () -> ()
|
|
154
|
+
return self._skyValue:Mount(sky or self:_observeSkyFromLighting())
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
--[=[
|
|
158
|
+
Sets the atmosphere
|
|
159
|
+
]=]
|
|
160
|
+
function FakeSkybox.SetAtmosphere(self: FakeSkybox, atmosphere: ValueObject.Mountable<Atmosphere?>?): () -> ()
|
|
161
|
+
return self._atmosphereValue:Mount(atmosphere or self:_observeAtmosphereFromLighting())
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
function FakeSkybox._observeSkyFromLighting(_self: FakeSkybox): Observable.Observable<Sky?>
|
|
165
|
+
return RxInstanceUtils.observeChildrenOfClassBrio(Lighting, "Sky"):Pipe({
|
|
166
|
+
RxStateStackUtils.topOfStack(nil) :: any,
|
|
167
|
+
}) :: any
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
function FakeSkybox._observeAtmosphereFromLighting(_self: FakeSkybox): Observable.Observable<Atmosphere?>
|
|
171
|
+
return RxInstanceUtils.observeChildrenOfClassBrio(Lighting, "Atmosphere"):Pipe({
|
|
172
|
+
RxStateStackUtils.topOfStack(nil) :: any,
|
|
173
|
+
}) :: any
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
function FakeSkybox._observeCurrentCamera(_self: FakeSkybox): Observable.Observable<Camera?>
|
|
177
|
+
return RxInstanceUtils.observeProperty(Workspace, "CurrentCamera") :: any
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
--[=[
|
|
181
|
+
Sets the camera to track to
|
|
182
|
+
]=]
|
|
183
|
+
function FakeSkybox.SetCamera(self: FakeSkybox, camera: ValueObject.Mountable<Camera?>?): () -> ()
|
|
184
|
+
return self._cameraValue:Mount(camera or self:_observeCurrentCamera())
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
function FakeSkybox.SetViewportLightingArgs(
|
|
188
|
+
self: FakeSkybox,
|
|
189
|
+
viewportLightingArgs: ValueObject.Mountable<SunPositionUtils.ViewportLightingArgs>
|
|
190
|
+
): () -> ()
|
|
191
|
+
return self._viewportLightingArgs:Mount(viewportLightingArgs)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
function FakeSkybox._renderSkyboxSides(self: FakeSkybox): ()
|
|
195
|
+
for normalId, propertyName in SKYBOX_PROPERTY_IMAGE_MAP do
|
|
196
|
+
local skyboxSide = self._maid:Add(SkyboxSide.new(normalId))
|
|
197
|
+
self._maid:Add(skyboxSide:SetRenderMethod(self._renderMethod:Observe()))
|
|
198
|
+
self._maid:Add(skyboxSide:SetTransparency(self:_observeTransparency()))
|
|
199
|
+
self._maid:Add(skyboxSide:SetPartSize(self._skyboxWidth:Observe()))
|
|
200
|
+
self._maid:Add(skyboxSide:SetImage(self:_observeImage(propertyName)))
|
|
201
|
+
self._maid:Add(skyboxSide:SetSkyboxCFrame(self:_observeSkyboxCFrame()))
|
|
202
|
+
self._maid:Add(skyboxSide:SetZOffset(self._skyboxZOffset:Observe()))
|
|
203
|
+
-- self._maid:Add(skyboxSide:SetBrightness(self:_observeSkyImageBrightness()))
|
|
204
|
+
self._maid:Add(skyboxSide:SetSkyboxGradient(self:_observeSkyboxGradient()))
|
|
205
|
+
|
|
206
|
+
skyboxSide.Gui.Parent = self.Gui
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
function FakeSkybox._observeTransparency(self: FakeSkybox): Observable.Observable<number>
|
|
211
|
+
return BasicPaneUtils.toTransparency(self._percentVisible:Observe()) :: any
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
function FakeSkybox._renderSun(self: FakeSkybox): ()
|
|
215
|
+
local observeSunState = self:_observeCelestrialBodyState("SunAngularSize")
|
|
216
|
+
|
|
217
|
+
local sunRender = self._maid:Add(SkyboxRenderPart.new())
|
|
218
|
+
sunRender:SetRenderMethod(self._renderMethod:Observe())
|
|
219
|
+
sunRender:SetCanvasSize(Vector2.new(256, 256)) -- TODO: Be smart about this
|
|
220
|
+
self._maid:Add(sunRender:SetBrightness(self:_observeBodyBrightness(observeSunState, function(state)
|
|
221
|
+
return SunPositionUtils.getSunImageBrightness(state.sunPositionData) * 10
|
|
222
|
+
end)))
|
|
223
|
+
self._maid:Add(sunRender:SetTransparency(self:_observeTransparency()))
|
|
224
|
+
self._maid:Add(sunRender:SetImage(self:_observeImage("SunTextureId")))
|
|
225
|
+
self._maid:Add(sunRender:SetSize(self:_observeCelestialBodyPartSize(
|
|
226
|
+
observeSunState,
|
|
227
|
+
Rx.combineLatest({
|
|
228
|
+
atmosphereDensity = self._atmosphereValue:Observe():Pipe({
|
|
229
|
+
Rx.map(function(atmosphere): number?
|
|
230
|
+
if atmosphere then
|
|
231
|
+
return atmosphere.Density
|
|
232
|
+
else
|
|
233
|
+
return nil
|
|
234
|
+
end
|
|
235
|
+
end) :: any,
|
|
236
|
+
}),
|
|
237
|
+
viewportLightingArgs = self._viewportLightingArgs:Observe(),
|
|
238
|
+
}):Pipe({
|
|
239
|
+
Rx.map(function(state: any)
|
|
240
|
+
if not state.atmosphereDensity then
|
|
241
|
+
-- No atmosphere, no size adjustment
|
|
242
|
+
return 1
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
return 1 + (state.atmosphereDensity * state.viewportLightingArgs.environmentDiffuseScale) / 2
|
|
246
|
+
end) :: any,
|
|
247
|
+
}) :: any
|
|
248
|
+
)))
|
|
249
|
+
self._maid:Add(sunRender:SetCFrame(self:_observeCelestrialBodyCFrame(observeSunState, "sunPosition")))
|
|
250
|
+
|
|
251
|
+
sunRender.Gui.Name = "SunPart"
|
|
252
|
+
sunRender.Gui.Parent = self.Gui
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
function FakeSkybox._renderMoon(self: FakeSkybox): ()
|
|
256
|
+
local observeMoonState = self:_observeCelestrialBodyState("MoonAngularSize")
|
|
257
|
+
local observeMoonBrightness = self:_observeBodyBrightness(observeMoonState, function(_state)
|
|
258
|
+
-- local brightness = SunPositionUtils.getMoonImageBrightness(state.sunPositionData, state.environmentDiffuseScale)
|
|
259
|
+
-- return brightness
|
|
260
|
+
|
|
261
|
+
return 1
|
|
262
|
+
end)
|
|
263
|
+
|
|
264
|
+
local moonRender = self._maid:Add(SkyboxRenderPart.new())
|
|
265
|
+
moonRender:SetRenderMethod(self._renderMethod:Observe())
|
|
266
|
+
moonRender:SetCanvasSize(Vector2.new(256, 256)) -- TODO: Be smart about this
|
|
267
|
+
self._maid:Add(moonRender:SetBrightness(observeMoonBrightness))
|
|
268
|
+
self._maid:Add(moonRender:SetTransparency(self:_observeTransparency()))
|
|
269
|
+
self._maid:Add(moonRender:SetImage(self:_observeImage("MoonTextureId")))
|
|
270
|
+
self._maid:Add(moonRender:SetSize(self:_observeCelestialBodyPartSize(observeMoonState)))
|
|
271
|
+
self._maid:Add(moonRender:SetCFrame(self:_observeCelestrialBodyCFrame(observeMoonState, "moonPosition")))
|
|
272
|
+
|
|
273
|
+
moonRender.Gui.Name = "MoonPart"
|
|
274
|
+
moonRender.Gui.Parent = self.Gui
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
function FakeSkybox._observeSkyImageBrightness(self: FakeSkybox): Observable.Observable<number>
|
|
278
|
+
return self:_observeSunPositionData():Pipe({
|
|
279
|
+
Rx.map(function(sunPositionData)
|
|
280
|
+
return math.clamp(SunPositionUtils.getLightSourceBrightness(sunPositionData), 0.3, 1)
|
|
281
|
+
end) :: any,
|
|
282
|
+
}) :: any
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
function FakeSkybox._observeSkyboxGradient(self: FakeSkybox): Observable.Observable<ColorSequence>
|
|
286
|
+
if self._observeSkyboxGradientCache then
|
|
287
|
+
return self._observeSkyboxGradientCache
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
self._observeSkyboxGradientCache = self._viewportLightingArgs:Observe():Pipe({
|
|
291
|
+
Rx.map(function(state: SunPositionUtils.ViewportLightingArgs)
|
|
292
|
+
return SunPositionUtils.getSkyboxGradient(state.clockTime, state.environmentDiffuseScale)
|
|
293
|
+
end) :: any,
|
|
294
|
+
}) :: any
|
|
295
|
+
assert(self._observeSkyboxGradientCache, "Typechecking assertion")
|
|
296
|
+
|
|
297
|
+
return self._observeSkyboxGradientCache
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
function FakeSkybox._observeBodyBrightness(
|
|
301
|
+
_self: FakeSkybox,
|
|
302
|
+
observeCelestrialBodyState: Observable.Observable<CelestrialBodyState>,
|
|
303
|
+
compute: (state: CelestrialBodyState) -> number
|
|
304
|
+
): Observable.Observable<number>
|
|
305
|
+
return observeCelestrialBodyState:Pipe({
|
|
306
|
+
Rx.map(function(state: any): number
|
|
307
|
+
if not state.bodyAngularSizeDegrees or not state.celestrialBodiesShown then
|
|
308
|
+
return 0
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
return compute(state)
|
|
312
|
+
end) :: any,
|
|
313
|
+
}) :: any
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
type CelestrialBodyState = {
|
|
317
|
+
sunPositionData: SunPositionUtils.SunPositionData,
|
|
318
|
+
viewportLightingArgs: SunPositionUtils.ViewportLightingArgs,
|
|
319
|
+
bodyAngularSizeDegrees: number?,
|
|
320
|
+
skyboxWidth: number,
|
|
321
|
+
celestrialBodiesShown: boolean,
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function FakeSkybox._observeCelestrialBodyState(
|
|
325
|
+
self: FakeSkybox,
|
|
326
|
+
angularSizeProperty: string
|
|
327
|
+
): Observable.Observable<CelestrialBodyState>
|
|
328
|
+
return Rx.combineLatest({
|
|
329
|
+
bodyAngularSizeDegrees = self._skyValue:Observe():Pipe({
|
|
330
|
+
Rx.switchMap(function(sky): any
|
|
331
|
+
if sky then
|
|
332
|
+
return RxInstanceUtils.observeProperty(sky, angularSizeProperty)
|
|
333
|
+
else
|
|
334
|
+
return Rx.of(nil)
|
|
335
|
+
end
|
|
336
|
+
end) :: any,
|
|
337
|
+
}),
|
|
338
|
+
viewportLightingArgs = self._viewportLightingArgs:Observe(),
|
|
339
|
+
brightness = RxInstanceUtils.observeProperty(Lighting, "Brightness"),
|
|
340
|
+
skyboxWidth = self._skyboxWidth:Observe(),
|
|
341
|
+
celestrialBodiesShown = self._skyValue:Observe():Pipe({
|
|
342
|
+
Rx.switchMap(function(sky): any
|
|
343
|
+
if sky then
|
|
344
|
+
return RxInstanceUtils.observeProperty(sky, "CelestialBodiesShown")
|
|
345
|
+
else
|
|
346
|
+
return Rx.of(true)
|
|
347
|
+
end
|
|
348
|
+
end) :: any,
|
|
349
|
+
}),
|
|
350
|
+
environmentDiffuseScale = RxInstanceUtils.observeProperty(Lighting, "EnvironmentDiffuseScale"),
|
|
351
|
+
sunPositionData = self:_observeSunPositionData(),
|
|
352
|
+
}):Pipe({
|
|
353
|
+
Rx.cache() :: any,
|
|
354
|
+
}) :: any
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
function FakeSkybox._observeCelestialBodyPartSize(
|
|
358
|
+
self: FakeSkybox,
|
|
359
|
+
observeCelestrialBodyState: Observable.Observable<CelestrialBodyState>,
|
|
360
|
+
observeSizeModifier: Observable.Observable<number>?
|
|
361
|
+
): Observable.Observable<Vector3>
|
|
362
|
+
return Rx.combineLatest({
|
|
363
|
+
bodyState = observeCelestrialBodyState,
|
|
364
|
+
sizeModifier = observeSizeModifier or 1,
|
|
365
|
+
renderMethod = self._renderMethod:Observe(),
|
|
366
|
+
}):Pipe({
|
|
367
|
+
Rx.map(function(state: any): Vector3?
|
|
368
|
+
if not state.bodyState.bodyAngularSizeDegrees or not state.bodyState.celestrialBodiesShown then
|
|
369
|
+
return Vector3.zero
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
local celestrialBodiesRadius =
|
|
373
|
+
self:_getCelestrialBodiesRenderDistance(state.renderMethod, state.bodyState.skyboxWidth)
|
|
374
|
+
local celestrialBodyRadius = math.tan(math.rad(state.bodyState.bodyAngularSizeDegrees) / 2)
|
|
375
|
+
* celestrialBodiesRadius
|
|
376
|
+
local diameter = celestrialBodyRadius * 2
|
|
377
|
+
|
|
378
|
+
diameter *= state.sizeModifier
|
|
379
|
+
|
|
380
|
+
return Vector3.new(diameter, diameter, CELESTRIAL_BODY_PART_DEPTH)
|
|
381
|
+
end) :: any,
|
|
382
|
+
}) :: any
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
function FakeSkybox._getCelestrialBodiesRenderDistance(
|
|
386
|
+
_self: FakeSkybox,
|
|
387
|
+
renderMethod: FakeSkyboxRenderMethod.FakeSkyboxRenderMethod,
|
|
388
|
+
skyboxWidth: number
|
|
389
|
+
): number
|
|
390
|
+
assert(FakeSkyboxRenderMethod:IsValue(renderMethod))
|
|
391
|
+
|
|
392
|
+
local radius = ((skyboxWidth / 2) - CELESTRIAL_BODY_OFFSET_STUDS)
|
|
393
|
+
if renderMethod == FakeSkyboxRenderMethod.DECAL then
|
|
394
|
+
-- Decals are rendered at a max distance, so we need to clamp the radius to avoid them disappearing
|
|
395
|
+
radius = radius / 4
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
return math.clamp(radius, 0, CELESTRIAL_BODY_MAX_DISTANCE_RENDER_HACK)
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
function FakeSkybox.ObserveBrightness(self: FakeSkybox): Observable.Observable<number>
|
|
402
|
+
return self:_observeSkyImageBrightness()
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
function FakeSkybox._observeCelestrialBodyCFrame(
|
|
406
|
+
self: FakeSkybox,
|
|
407
|
+
observeCelestrialBodyState: Observable.Observable<CelestrialBodyState>,
|
|
408
|
+
positionType: "sunPosition" | "moonPosition"
|
|
409
|
+
): Observable.Observable<CFrame>
|
|
410
|
+
return Rx.combineLatest({
|
|
411
|
+
bodyState = observeCelestrialBodyState,
|
|
412
|
+
renderMethod = self._renderMethod:Observe(),
|
|
413
|
+
celestrialSkyboxCFrame = self:_observeCelestrialSkyCFrame(),
|
|
414
|
+
}):Pipe({
|
|
415
|
+
Rx.map(function(state: any): CFrame
|
|
416
|
+
if not state.bodyState.bodyAngularSizeDegrees or not state.bodyState.celestrialBodiesShown then
|
|
417
|
+
return CFrame.identity
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
local celestrialBodiesRadius =
|
|
421
|
+
self:_getCelestrialBodiesRenderDistance(state.renderMethod, state.bodyState.skyboxWidth)
|
|
422
|
+
local direction = state.bodyState.sunPositionData[positionType].Unit
|
|
423
|
+
|
|
424
|
+
local cframe: CFrame = state.celestrialSkyboxCFrame
|
|
425
|
+
* CFrame.lookAt(Vector3.zero, direction, Vector3.yAxis)
|
|
426
|
+
* CFrame.new(0, 0, -celestrialBodiesRadius - CELESTRIAL_BODY_PART_DEPTH / 2)
|
|
427
|
+
* CFrame.Angles(0, math.pi, 0)
|
|
428
|
+
|
|
429
|
+
return cframe
|
|
430
|
+
end) :: any,
|
|
431
|
+
}) :: any
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
function FakeSkybox._observeSunPositionData(self: FakeSkybox): Observable.Observable<SunPositionUtils.SunPositionData>
|
|
435
|
+
if self._observeSunPositionDataCache then
|
|
436
|
+
return self._observeSunPositionDataCache
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
self._observeSunPositionDataCache = Rx.combineLatest({
|
|
440
|
+
clockTime = RxInstanceUtils.observeProperty(Lighting, "ClockTime"),
|
|
441
|
+
geoLatitude = RxInstanceUtils.observeProperty(Lighting, "GeographicLatitude"),
|
|
442
|
+
}):Pipe({
|
|
443
|
+
Rx.map(function(state: any): SunPositionUtils.SunPositionData
|
|
444
|
+
return SunPositionUtils.getSunPositionData(state.clockTime, state.geoLatitude)
|
|
445
|
+
end) :: any,
|
|
446
|
+
Rx.cache() :: any,
|
|
447
|
+
}) :: any
|
|
448
|
+
assert(self._observeSunPositionDataCache, "Typechecking assertion")
|
|
449
|
+
|
|
450
|
+
return self._observeSunPositionDataCache
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
function FakeSkybox._observeImage(self: FakeSkybox, propertyName: string): Observable.Observable<string>
|
|
454
|
+
local default = DEFAULT_SKY_DATA[propertyName]
|
|
455
|
+
if not default then
|
|
456
|
+
error("[FakeSkybox] - No default for property: " .. propertyName)
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
return Rx.combineLatest({
|
|
460
|
+
renderMethod = self._renderMethod:Observe(),
|
|
461
|
+
imageValue = self._skyValue:Observe():Pipe({
|
|
462
|
+
Rx.switchMap(function(sky): any
|
|
463
|
+
if sky then
|
|
464
|
+
return RxInstanceUtils.observeProperty(sky, propertyName):Pipe({
|
|
465
|
+
Rx.map(function(value)
|
|
466
|
+
return value or default
|
|
467
|
+
end) :: any,
|
|
468
|
+
})
|
|
469
|
+
else
|
|
470
|
+
return Rx.of(default)
|
|
471
|
+
end
|
|
472
|
+
end) :: any,
|
|
473
|
+
}),
|
|
474
|
+
}):Pipe({
|
|
475
|
+
Rx.map(function(state)
|
|
476
|
+
if state.renderMethod == FakeSkyboxRenderMethod.DECAL and state.imageValue then
|
|
477
|
+
-- Decal mode doesn't support the sun/moon textures, so we replace them with a solid color texture
|
|
478
|
+
return DECAL_REPLACEMENTS[state.imageValue] or state.imageValue
|
|
479
|
+
else
|
|
480
|
+
return state.imageValue
|
|
481
|
+
end
|
|
482
|
+
end) :: any,
|
|
483
|
+
}) :: any
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
function FakeSkybox._observeCelestrialSkyCFrame(self: FakeSkybox): Observable.Observable<CFrame>
|
|
487
|
+
return self._cameraValue:Observe():Pipe({
|
|
488
|
+
Rx.switchMap(function(camera): any
|
|
489
|
+
if camera then
|
|
490
|
+
return RxInstanceUtils.observeProperty(camera, "CFrame")
|
|
491
|
+
else
|
|
492
|
+
return Rx.EMPTY
|
|
493
|
+
end
|
|
494
|
+
end) :: any,
|
|
495
|
+
Rx.map(function(cameraCFrame)
|
|
496
|
+
if cameraCFrame then
|
|
497
|
+
return CFrame.new(cameraCFrame.Position)
|
|
498
|
+
else
|
|
499
|
+
return CFrame.identity
|
|
500
|
+
end
|
|
501
|
+
end) :: any,
|
|
502
|
+
}) :: any
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
function FakeSkybox._observeSkyboxCFrame(self: FakeSkybox): Observable.Observable<CFrame>
|
|
506
|
+
if self._observeSkyboxCFrameCache then
|
|
507
|
+
return self._observeSkyboxCFrameCache
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
self._observeSkyboxCFrameCache = Rx.combineLatest({
|
|
511
|
+
cameraCFrame = self._cameraValue:Observe():Pipe({
|
|
512
|
+
Rx.switchMap(function(camera): any
|
|
513
|
+
if camera then
|
|
514
|
+
return RxInstanceUtils.observeProperty(camera, "CFrame")
|
|
515
|
+
else
|
|
516
|
+
return Rx.EMPTY
|
|
517
|
+
end
|
|
518
|
+
end) :: any,
|
|
519
|
+
}),
|
|
520
|
+
skyboxOrientation = self._skyValue:Observe():Pipe({
|
|
521
|
+
Rx.switchMap(function(sky): any
|
|
522
|
+
if sky then
|
|
523
|
+
return RxInstanceUtils.observeProperty(sky, "SkyboxOrientation")
|
|
524
|
+
else
|
|
525
|
+
return Rx.of(Vector3.zero)
|
|
526
|
+
end
|
|
527
|
+
end) :: any,
|
|
528
|
+
}),
|
|
529
|
+
}):Pipe({
|
|
530
|
+
Rx.map(function(state)
|
|
531
|
+
local orientation =
|
|
532
|
+
CFrame.Angles(state.skyboxOrientation.X, state.skyboxOrientation.Y, state.skyboxOrientation.Z)
|
|
533
|
+
return CFrame.new(state.cameraCFrame.Position) * orientation
|
|
534
|
+
end) :: any,
|
|
535
|
+
Rx.cache() :: any,
|
|
536
|
+
}) :: any
|
|
537
|
+
assert(self._observeSkyboxCFrameCache, "Typechecking assertion")
|
|
538
|
+
|
|
539
|
+
return self._observeSkyboxCFrameCache
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
function FakeSkybox:_render(): any
|
|
543
|
+
return Blend.New "Folder" {
|
|
544
|
+
Name = "Skybox",
|
|
545
|
+
}
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
return FakeSkybox
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
--!nonstrict
|
|
2
|
+
--[[
|
|
3
|
+
@class FakeSkybox.spec.lua
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local FakeSkybox = require("FakeSkybox")
|
|
9
|
+
local Jest = require("Jest")
|
|
10
|
+
|
|
11
|
+
local describe = Jest.Globals.describe
|
|
12
|
+
local expect = Jest.Globals.expect
|
|
13
|
+
local it = Jest.Globals.it
|
|
14
|
+
|
|
15
|
+
describe("FakeSkybox", function()
|
|
16
|
+
it("should be requireable", function()
|
|
17
|
+
expect(FakeSkybox).never.toBeNil()
|
|
18
|
+
end)
|
|
19
|
+
|
|
20
|
+
it("should have ClassName set to FakeSkybox", function()
|
|
21
|
+
expect(FakeSkybox.ClassName).toEqual("FakeSkybox")
|
|
22
|
+
end)
|
|
23
|
+
|
|
24
|
+
it("should have a new constructor", function()
|
|
25
|
+
expect(typeof(FakeSkybox.new)).toEqual("function")
|
|
26
|
+
end)
|
|
27
|
+
end)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[=[
|
|
3
|
+
@class FakeSkyboxRenderMethod
|
|
4
|
+
]=]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local SimpleEnum = require("SimpleEnum")
|
|
9
|
+
|
|
10
|
+
export type FakeSkyboxRenderMethod = "surfacegui" | "decal"
|
|
11
|
+
|
|
12
|
+
return SimpleEnum.new({
|
|
13
|
+
SURFACEGUI = "surfacegui" :: "surfacegui",
|
|
14
|
+
DECAL = "decal" :: "decal",
|
|
15
|
+
})
|