4
4
*/
5
5
6
6
import React , { useState , useLayoutEffect , useRef } from 'react' ;
7
- import { useThree , createRoot } from '@react-three/fiber' ;
8
7
import * as THREE from 'three' ;
9
8
10
- import { withLightScene , materialIsSupported } from './lightScene' ;
9
+ import { materialIsSupported } from './lightScene' ;
11
10
import {
12
- initializeWorkbench ,
13
11
traverseSceneItems ,
14
- Workbench ,
15
12
WorkbenchSettings ,
16
13
LIGHTMAP_READONLY_FLAG ,
17
14
LIGHTMAP_IGNORE_FLAG
18
15
} from './workbench' ;
19
- import { runBakingPasses } from './bake' ;
20
16
import { computeAutoUV2Layout } from './AutoUV2' ;
21
- import { createWorkManager } from './WorkManager ' ;
17
+ import { useOffscreenWorkflow , Debug } from './offscreenWorkflow ' ;
22
18
23
19
// prevent lightmap and UV2 generation for content
24
20
// (but still allow contribution to lightmap, for e.g. emissive objects, large occluders, etc)
@@ -97,106 +93,6 @@ function updateFinalSceneMaterials(
97
93
}
98
94
}
99
95
100
- const WorkSceneWrapper : React . FC < {
101
- onReady : ( gl : THREE . WebGLRenderer , scene : THREE . Scene ) => void ;
102
- children : React . ReactNode ;
103
- } > = ( props ) => {
104
- const { gl } = useThree ( ) ; // @todo use state selector
105
-
106
- // track latest reference to onReady callback
107
- const onReadyRef = useRef ( props . onReady ) ;
108
- onReadyRef . current = props . onReady ;
109
-
110
- const sceneRef = useRef < THREE . Scene > ( null ) ;
111
- useLayoutEffect ( ( ) => {
112
- // kick off the asynchronous workflow process in the parent
113
- // (this runs when scene content is loaded and suspensions are finished)
114
- const scene = sceneRef . current ;
115
- if ( ! scene ) {
116
- throw new Error ( 'expecting lightmap scene' ) ;
117
- }
118
-
119
- onReadyRef . current ( gl , scene ) ;
120
- } , [ gl ] ) ;
121
-
122
- // main baking scene container
123
- return (
124
- < scene name = "Lightmap Baking Scene" ref = { sceneRef } >
125
- { props . children }
126
- </ scene >
127
- ) ;
128
- } ;
129
-
130
- type OffscreenSettings = WorkbenchSettings & {
131
- workPerFrame ?: number ; // @todo allow fractions, dynamic value
132
- } ;
133
-
134
- // main async workflow, allows early cancellation via abortPromise
135
- async function runOffscreenWorkflow (
136
- content : React . ReactNode ,
137
- settings : OffscreenSettings ,
138
- abortPromise : Promise < void > ,
139
- debugListeners : {
140
- onAtlasMap : ( atlasMap : Workbench [ 'atlasMap' ] ) => void ;
141
- onPassComplete : ( data : Float32Array , width : number , height : number ) => void ;
142
- }
143
- ) {
144
- // render hidden canvas with the given content, wait for suspense to finish loading inside it
145
- const scenePromise = await new Promise < {
146
- gl : THREE . WebGLRenderer ;
147
- scene : THREE . Scene ;
148
- } > ( ( resolve ) => {
149
- // just sensible small canvas, not actually used for direct output
150
- const canvas = document . createElement ( 'canvas' ) ;
151
- canvas . width = 64 ;
152
- canvas . height = 64 ;
153
-
154
- const root = createRoot ( canvas ) . configure ( {
155
- frameloop : 'never' , // framebuffer target rendering is inside own RAF loop
156
- shadows : true // @todo use the original GL context settings
157
- } ) ;
158
-
159
- root . render (
160
- < React . Suspense fallback = { null } >
161
- < WorkSceneWrapper
162
- onReady = { ( gl , scene ) => {
163
- resolve ( { gl, scene } ) ;
164
- } }
165
- >
166
- { content }
167
- </ WorkSceneWrapper >
168
- </ React . Suspense >
169
- ) ;
170
- } ) ;
171
-
172
- // preempt any further logic if already aborted
173
- const { gl, scene } = await Promise . race ( [
174
- scenePromise ,
175
- abortPromise . then ( ( ) => {
176
- throw new Error ( 'aborted before scene is complete' ) ;
177
- } )
178
- ] ) ;
179
-
180
- // our own work manager (which is aware of the abort signal promise)
181
- const requestWork = createWorkManager (
182
- gl ,
183
- abortPromise ,
184
- settings . workPerFrame
185
- ) ;
186
-
187
- const workbench = await initializeWorkbench ( scene , settings , requestWork ) ;
188
- debugListeners . onAtlasMap ( workbench . atlasMap ) ; // expose atlas map for debugging
189
-
190
- await withLightScene ( workbench , async ( ) => {
191
- await runBakingPasses ( workbench , requestWork , ( data , width , height ) => {
192
- // expose current pass output for debugging
193
- debugListeners . onPassComplete ( data , width , height ) ;
194
- } ) ;
195
- } ) ;
196
-
197
- return workbench ;
198
- }
199
-
200
96
export type LightmapProps = WorkbenchSettings & {
201
97
workPerFrame ?: number ; // @todo allow fractions, dynamic value
202
98
disabled ?: boolean ;
@@ -206,93 +102,64 @@ export type LightmapProps = WorkbenchSettings & {
206
102
const Lightmap : React . FC < React . PropsWithChildren < LightmapProps > > = ( {
207
103
disabled,
208
104
onComplete,
209
- ...props
105
+ children,
106
+ ...settings
210
107
} ) => {
211
- const initialPropsRef = useRef ( props ) ;
212
-
213
108
// track latest reference to onComplete callback
214
109
const onCompleteRef = useRef ( onComplete ) ;
215
110
onCompleteRef . current = onComplete ;
216
111
217
- // track one-time flip from disabled to non-disabled
218
- // (i.e. once allowStart is true, keep it true)
219
- const disabledStartRef = useRef ( true ) ;
220
- disabledStartRef . current = disabledStartRef . current && ! ! disabled ;
221
- const allowStart = ! disabledStartRef . current ;
222
-
223
- const [ result , setResult ] = useState < Workbench | null > ( null ) ;
112
+ // debug helper
224
113
const [ debugInfo , setDebugInfo ] = useState < DebugInfo | null > ( null ) ;
114
+ const debug : Debug = {
115
+ onAtlasMap ( atlasMap ) {
116
+ // initialize debug display of atlas texture as well as blank placeholder for output
117
+ const atlasTexture = new THREE . DataTexture (
118
+ atlasMap . data ,
119
+ atlasMap . width ,
120
+ atlasMap . height ,
121
+ THREE . RGBAFormat ,
122
+ THREE . FloatType
123
+ ) ;
225
124
226
- useLayoutEffect ( ( ) => {
227
- // @todo check if this runs multiple times on some React versions???
228
- const { children, ...settings } = initialPropsRef . current ;
229
-
230
- // set up abort signal promise
231
- let abortResolver = ( ) => undefined as void ;
232
- const abortPromise = new Promise < void > ( ( resolve ) => {
233
- abortResolver = resolve ;
234
- } ) ;
235
-
236
- // run main logic with the abort signal promise
237
- if ( allowStart ) {
238
- const workflowResult = runOffscreenWorkflow (
239
- children ,
240
- settings ,
241
- abortPromise ,
242
- {
243
- onAtlasMap ( atlasMap ) {
244
- // initialize debug display of atlas texture as well as blank placeholder for output
245
- const atlasTexture = new THREE . DataTexture (
246
- atlasMap . data ,
247
- atlasMap . width ,
248
- atlasMap . height ,
249
- THREE . RGBAFormat ,
250
- THREE . FloatType
251
- ) ;
125
+ const outputTexture = new THREE . DataTexture (
126
+ new Float32Array ( atlasMap . width * atlasMap . height * 4 ) ,
127
+ atlasMap . width ,
128
+ atlasMap . height ,
129
+ THREE . RGBAFormat ,
130
+ THREE . FloatType
131
+ ) ;
252
132
253
- const outputTexture = new THREE . DataTexture (
254
- new Float32Array ( atlasMap . width * atlasMap . height * 4 ) ,
255
- atlasMap . width ,
256
- atlasMap . height ,
133
+ setDebugInfo ( {
134
+ atlasTexture,
135
+ outputTexture
136
+ } ) ;
137
+ } ,
138
+ onPassComplete ( data , width , height ) {
139
+ setDebugInfo (
140
+ ( prev ) =>
141
+ prev && {
142
+ ...prev ,
143
+
144
+ // replace with a new texture with copied source buffer data
145
+ outputTexture : new THREE . DataTexture (
146
+ new Float32Array ( data ) ,
147
+ width ,
148
+ height ,
257
149
THREE . RGBAFormat ,
258
150
THREE . FloatType
259
- ) ;
260
-
261
- setDebugInfo ( {
262
- atlasTexture,
263
- outputTexture
264
- } ) ;
265
- } ,
266
- onPassComplete ( data , width , height ) {
267
- setDebugInfo (
268
- ( prev ) =>
269
- prev && {
270
- ...prev ,
271
-
272
- // replace with a new texture with copied source buffer data
273
- outputTexture : new THREE . DataTexture (
274
- new Float32Array ( data ) ,
275
- width ,
276
- height ,
277
- THREE . RGBAFormat ,
278
- THREE . FloatType
279
- )
280
- }
281
- ) ;
151
+ )
282
152
}
283
- }
284
153
) ;
285
-
286
- workflowResult . then ( ( result ) => {
287
- setResult ( result ) ;
288
- } ) ;
289
154
}
155
+ } ;
290
156
291
- // on early unmount, resolve the abort signal promise
292
- return ( ) => {
293
- abortResolver ( ) ;
294
- } ;
295
- } , [ allowStart ] ) ;
157
+ // main offscreen workflow state
158
+ const result = useOffscreenWorkflow (
159
+ disabled ? null : children ,
160
+ settings ,
161
+ debug
162
+ ) ;
296
163
297
164
const sceneRef = useRef < THREE . Scene > ( null ) ;
298
165
@@ -304,7 +171,7 @@ const Lightmap: React.FC<React.PropsWithChildren<LightmapProps>> = ({
304
171
// create UV2 coordinates for the final scene meshes
305
172
// @todo somehow reuse ones from the baker?
306
173
computeAutoUV2Layout (
307
- initialPropsRef . current . lightMapSize ,
174
+ [ result . atlasMap . width , result . atlasMap . height ] ,
308
175
traverseSceneItems ( sceneRef . current , true ) ,
309
176
{
310
177
texelsPerUnit : result . texelsPerUnit
@@ -314,11 +181,7 @@ const Lightmap: React.FC<React.PropsWithChildren<LightmapProps>> = ({
314
181
// copy texture data since this is coming from a foreign canvas
315
182
const texture = result . createOutputTexture ( ) ;
316
183
317
- updateFinalSceneMaterials (
318
- sceneRef . current ,
319
- texture ,
320
- ! ! initialPropsRef . current . ao
321
- ) ;
184
+ updateFinalSceneMaterials ( sceneRef . current , texture , result . aoMode ) ;
322
185
323
186
// notify listener and pass the texture instance intended for parent GL context
324
187
if ( onCompleteRef . current ) {
@@ -332,7 +195,7 @@ const Lightmap: React.FC<React.PropsWithChildren<LightmapProps>> = ({
332
195
< DebugContext . Provider value = { debugInfo } >
333
196
{ result ? (
334
197
< scene name = "Lightmap Result Scene" ref = { sceneRef } >
335
- { props . children }
198
+ { children }
336
199
</ scene >
337
200
) : null }
338
201
</ DebugContext . Provider >
0 commit comments