Skip to content
This repository was archived by the owner on Aug 21, 2024. It is now read-only.

Commit dc736f5

Browse files
authored
Improve portal editor and rebake all default scene portals (#9588)
* Improve portal editor and rebake all default scene portals * cleanup logs
1 parent f641394 commit dc736f5

26 files changed

+130
-191
lines changed

packages/client-core/i18n/en/editor.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -806,8 +806,8 @@
806806
"lbl-effectType": "Loading Effect",
807807
"lbl-previewType": "Preview Type",
808808
"lbl-previewImageURL": "Preview Image URL",
809-
"lbl-savedImageURL": "Saved Image URL",
810-
"lbl-previewImage": "Preview Image",
809+
"lbl-savedImageURL": "Saved Image",
810+
"lbl-generateImage": "Generate Image",
811811
"lbl-saveImage": "Save Image",
812812
"lbl-spawnPosition": "Spawn Position",
813813
"lbl-spawnRotation": "Spawn Rotation",

packages/editor/src/components/inputs/FileBrowserInput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export function FileBrowserInput({
5858
const onUpload = useUpload(uploadOptions)
5959

6060
// todo fix for invalid URLs
61-
const assetIsExternal = value && !value?.includes(config.client.fileServer)
61+
const assetIsExternal = value && !value?.includes(config.client.fileServer) && !value.includes('blob:https://')
6262
const uploadExternalAsset = () => {
6363
onUpload([
6464
{

packages/editor/src/components/properties/PortalNodeEditor.tsx

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@ All portions of the code written by the Ethereal Engine team are Copyright © 20
2323
Ethereal Engine. All Rights Reserved.
2424
*/
2525

26-
import { debounce } from 'lodash'
27-
import React, { useCallback, useEffect, useState } from 'react'
26+
import React, { useEffect } from 'react'
2827
import { useTranslation } from 'react-i18next'
29-
import { Euler, Quaternion } from 'three'
28+
import { Euler, Quaternion, Vector3 } from 'three'
3029

3130
import { API } from '@etherealengine/client-core/src/API'
3231
import { getComponent, useComponent } from '@etherealengine/engine/src/ecs/functions/ComponentFunctions'
@@ -42,7 +41,9 @@ import { TransformComponent } from '@etherealengine/engine/src/transform/compone
4241
import MeetingRoomIcon from '@mui/icons-material/MeetingRoom'
4342

4443
import { PortalType, portalPath } from '@etherealengine/common/src/schema.type.module'
45-
import { getPreviewBakeTexture, uploadCubemapBakeToServer } from '../../functions/uploadEnvMapBake'
44+
import { imageDataToBlob } from '@etherealengine/engine/src/scene/classes/ImageUtils'
45+
import { useHookstate } from '@hookstate/core'
46+
import { bakeEnvmapTexture, uploadCubemapBakeToServer } from '../../functions/uploadEnvMapBake'
4647
import BooleanInput from '../inputs/BooleanInput'
4748
import { Button } from '../inputs/Button'
4849
import EulerInput from '../inputs/EulerInput'
@@ -52,17 +53,11 @@ import SelectInput from '../inputs/SelectInput'
5253
import { ControlledStringInput } from '../inputs/StringInput'
5354
import Vector3Input from '../inputs/Vector3Input'
5455
import NodeEditor from './NodeEditor'
55-
import { EditorComponentType, commitProperties, commitProperty, updateProperties, updateProperty } from './Util'
56+
import { EditorComponentType, commitProperties, commitProperty, updateProperty } from './Util'
5657

5758
type PortalOptions = {
58-
name: string
59-
value: string
60-
}
61-
62-
type PortalFilterOption = {
6359
label: string
6460
value: string
65-
data: PortalOptions
6661
}
6762

6863
const rotation = new Quaternion()
@@ -73,8 +68,11 @@ const rotation = new Quaternion()
7368
* @type {class component}
7469
*/
7570
export const PortalNodeEditor: EditorComponentType = (props) => {
76-
const [portals, setPortals] = useState<Array<{ value: string; label: string }>>([])
77-
const [bufferUrl, setBufferUrl] = useState<string>('')
71+
const state = useHookstate({
72+
portals: [] as PortalOptions[],
73+
previewImageData: null as ImageData | null,
74+
previewImageURL: ''
75+
})
7876

7977
const { t } = useTranslation()
8078
const transformComponent = useComponent(props.entity, TransformComponent)
@@ -85,20 +83,14 @@ export const PortalNodeEditor: EditorComponentType = (props) => {
8583
}, [])
8684

8785
const updateCubeMapBake = async () => {
88-
const imageBlob = await getPreviewBakeTexture(transformComponent.value.position)
89-
const url = URL.createObjectURL(imageBlob)
90-
setBufferUrl(url)
86+
const imageData = await bakeEnvmapTexture(
87+
transformComponent.value.position.clone().add(new Vector3(0, 2, 0).multiply(transformComponent.scale.value))
88+
)
89+
const blob = await imageDataToBlob(imageData)
90+
state.previewImageData.set(imageData)
91+
state.previewImageURL.set(URL.createObjectURL(blob!))
9192
}
9293

93-
const updateCubeMapBakeDebounced = useCallback(debounce(updateCubeMapBake, 500), []) //ms
94-
95-
useEffect(() => {
96-
updateCubeMapBakeDebounced()
97-
return () => {
98-
updateCubeMapBakeDebounced.cancel()
99-
}
100-
}, [transformComponent.position])
101-
10294
const loadPortals = async () => {
10395
const portalsDetail: PortalType[] = []
10496
try {
@@ -109,7 +101,7 @@ export const PortalNodeEditor: EditorComponentType = (props) => {
109101
} catch (error) {
110102
throw new Error(error)
111103
}
112-
setPortals(
104+
state.portals.set(
113105
portalsDetail
114106
.filter((portal) => portal.portalEntityId !== getComponent(props.entity, UUIDComponent))
115107
.map(({ portalEntityId, portalEntityName, sceneName }) => {
@@ -118,19 +110,16 @@ export const PortalNodeEditor: EditorComponentType = (props) => {
118110
)
119111
}
120112

121-
const bakeCubemap = async () => {
122-
const url = await uploadCubemapBakeToServer(
123-
getComponent(props.entity, NameComponent),
124-
transformComponent.position.value
125-
)
126-
loadPortals()
127-
updateProperties(PortalComponent, { previewImageURL: url }, [props.entity])
113+
const uploadEnvmap = async () => {
114+
if (!state.previewImageData.value) return
115+
const url = await uploadCubemapBakeToServer(getComponent(props.entity, NameComponent), state.previewImageData.value)
116+
commitProperties(PortalComponent, { previewImageURL: url }, [props.entity])
128117
}
129118

130119
const changeSpawnRotation = (value: Euler) => {
131120
rotation.setFromEuler(value)
132121

133-
updateProperties(PortalComponent, { spawnRotation: rotation })
122+
commitProperties(PortalComponent, { spawnRotation: rotation })
134123
}
135124

136125
const changePreviewType = (val) => {
@@ -150,7 +139,7 @@ export const PortalNodeEditor: EditorComponentType = (props) => {
150139
<InputGroup name="Portal" label={t('editor:properties.portal.lbl-portal')}>
151140
<SelectInput
152141
key={props.entity}
153-
options={portals}
142+
options={state.portals.value}
154143
value={portalComponent.linkedPortalId.value}
155144
onChange={commitProperty(PortalComponent, 'linkedPortalId')}
156145
/>
@@ -185,14 +174,14 @@ export const PortalNodeEditor: EditorComponentType = (props) => {
185174
onRelease={commitProperty(PortalComponent, 'previewImageURL')}
186175
/>
187176
</InputGroup>
188-
<InputGroup name="Preview Image Bake" label={t('editor:properties.portal.lbl-previewImage')}>
177+
<InputGroup name="Preview Image Bake" label={t('editor:properties.portal.lbl-generateImage')}>
189178
<div style={{ display: 'flex', flexDirection: 'column' }}>
190179
<div style={{ width: 'auto', display: 'flex', flexDirection: 'row' }}>
191180
<Button
192181
style={{ width: '100%', fontSize: '11px', overflow: 'hidden' }}
193182
type="submit"
194183
onClick={() => {
195-
bakeCubemap()
184+
uploadEnvmap()
196185
}}
197186
>
198187
{t('editor:properties.portal.lbl-saveImage')}
@@ -204,10 +193,10 @@ export const PortalNodeEditor: EditorComponentType = (props) => {
204193
updateCubeMapBake()
205194
}}
206195
>
207-
{t('editor:properties.portal.lbl-previewImage')}
196+
{t('editor:properties.portal.lbl-generateImage')}
208197
</Button>
209198
</div>
210-
<ImagePreviewInput value={bufferUrl} />
199+
<ImagePreviewInput value={state.previewImageURL.value ?? portalComponent.previewImageURL.value} />
211200
</div>
212201
</InputGroup>
213202
<InputGroup name="Spawn Position" label={t('editor:properties.portal.lbl-spawnPosition')}>

packages/editor/src/functions/uploadEnvMapBake.ts

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import CubemapCapturer from '@etherealengine/engine/src/scene/classes/CubemapCap
3939
import {
4040
blurAndScaleImageData,
4141
convertCubemapToEquiImageData,
42-
convertCubemapToImageData,
4342
convertImageDataToKTX2Blob
4443
} from '@etherealengine/engine/src/scene/classes/ImageUtils'
4544
import { EnvMapBakeComponent } from '@etherealengine/engine/src/scene/components/EnvMapBakeComponent'
@@ -101,12 +100,12 @@ export const uploadBPCEMBakeToServer = async (entity: Entity) => {
101100

102101
if (isSceneEntity) Engine.instance.scene.environment = renderTarget.texture
103102

104-
const envmapImageData = convertCubemapToImageData(
103+
const envmapImageData = convertCubemapToEquiImageData(
105104
EngineRenderer.instance.renderer,
106105
renderTarget.texture,
107106
bakeComponent.resolution,
108107
bakeComponent.resolution
109-
)
108+
) as ImageData
110109

111110
const envmap = await convertImageDataToKTX2Blob(envmapImageData)
112111

@@ -142,12 +141,12 @@ export const uploadSceneBakeToServer = async () => {
142141

143142
Engine.instance.scene.environment = renderTarget.texture
144143

145-
const envmapImageData = convertCubemapToImageData(
144+
const envmapImageData = convertCubemapToEquiImageData(
146145
EngineRenderer.instance.renderer,
147146
renderTarget.texture,
148147
bakeComponent.resolution,
149148
bakeComponent.resolution
150-
)
149+
) as ImageData
151150

152151
const loadingScreenImageData = blurAndScaleImageData(
153152
envmapImageData,
@@ -182,27 +181,22 @@ export const uploadSceneBakeToServer = async () => {
182181

183182
const resolution = 1024
184183

185-
const previewCubemapCapturer = new CubemapCapturer(
186-
EngineRenderer.instance.renderer,
187-
Engine.instance.scene,
188-
resolution / 8
189-
)
184+
const previewCubemapCapturer = new CubemapCapturer(EngineRenderer.instance.renderer, Engine.instance.scene, resolution)
190185
/**
191186
* Generates a low res cubemap at a specific position in the world for preview.
192187
*
193188
* @param position
194189
* @returns
195190
*/
196-
export const getPreviewBakeTexture = async (position: Vector3) => {
191+
export const bakeEnvmapTexture = async (position: Vector3) => {
197192
const renderTarget = previewCubemapCapturer.update(position)
198-
const imageBlob = (await convertCubemapToEquiImageData(
193+
const bake = (await convertCubemapToEquiImageData(
199194
EngineRenderer.instance.renderer,
200195
renderTarget.texture,
201-
resolution / 4,
202-
resolution / 4,
203-
true
204-
)) as Blob
205-
return imageBlob
196+
resolution,
197+
resolution
198+
)) as ImageData
199+
return bake
206200
}
207201

208202
/**
@@ -211,12 +205,8 @@ export const getPreviewBakeTexture = async (position: Vector3) => {
211205
* @param position
212206
* @returns
213207
*/
214-
const saveCubemapCapturer = new CubemapCapturer(EngineRenderer.instance.renderer, Engine.instance.scene, resolution)
215-
export const uploadCubemapBakeToServer = async (name: string, position: Vector3, res: number = resolution) => {
216-
const renderTarget = saveCubemapCapturer.update(position)
217-
const bake = await convertCubemapToImageData(EngineRenderer.instance.renderer, renderTarget.texture, res, res)
218-
219-
const blob = await convertImageDataToKTX2Blob(bake)
208+
export const uploadCubemapBakeToServer = async (name: string, data: ImageData) => {
209+
const blob = await convertImageDataToKTX2Blob(data)
220210

221211
if (!blob) return null!
222212

packages/engine/src/scene/classes/CubemapCapturer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,8 @@ export default class CubemapCapturer {
7070
this.renderer.autoClear = autoClear
7171
return this.cubeRenderTarget
7272
}
73+
74+
dispose() {
75+
this.cubeRenderTarget.dispose()
76+
}
7377
}

packages/engine/src/scene/classes/ImageUtils.ts

Lines changed: 19 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -151,57 +151,6 @@ export const ScreenshotSettings = defineState({
151151

152152
const ktx2write = new KTX2Encoder()
153153

154-
export const convertCubemapToImageData = (
155-
renderer: WebGLRenderer,
156-
source: CubeTexture,
157-
width: number,
158-
height: number
159-
) => {
160-
const scene = new Scene()
161-
const material = new RawShaderMaterial({
162-
uniforms: {
163-
map: new Uniform(new CubeTexture())
164-
},
165-
vertexShader: vertexShader,
166-
fragmentShader: fragmentShader,
167-
side: DoubleSide,
168-
transparent: true
169-
})
170-
const quad = new Mesh(new PlaneGeometry(1, 1), material)
171-
scene.add(quad)
172-
const camera = new OrthographicCamera(1 / -2, 1 / 2, 1 / 2, 1 / -2, -10000, 10000)
173-
174-
quad.scale.set(width, height, 1)
175-
camera.left = width / 2
176-
camera.right = width / -2
177-
camera.top = height / -2
178-
camera.bottom = height / 2
179-
camera.updateProjectionMatrix()
180-
const renderTarget = new WebGLRenderTarget(width, height, {
181-
minFilter: LinearFilter,
182-
magFilter: LinearFilter,
183-
wrapS: ClampToEdgeWrapping,
184-
wrapT: ClampToEdgeWrapping,
185-
colorSpace: SRGBColorSpace,
186-
format: RGBAFormat,
187-
type: UnsignedByteType
188-
})
189-
190-
const originalColorSpace = renderer.outputColorSpace
191-
renderer.outputColorSpace = SRGBColorSpace
192-
renderer.setRenderTarget(renderTarget)
193-
quad.material.uniforms.map.value = source
194-
195-
renderer.render(scene, camera)
196-
const pixels = new Uint8Array(4 * width * height)
197-
renderer.readRenderTargetPixels(renderTarget, 0, 0, width, height, pixels)
198-
199-
renderer.setRenderTarget(null) // pass `null` to set canvas as render target
200-
renderer.outputColorSpace = originalColorSpace
201-
202-
return new ImageData(new Uint8ClampedArray(pixels), width, height)
203-
}
204-
205154
export const convertImageDataToKTX2Blob = async (imageData: ImageData) => {
206155
const ktx2texture = (await ktx2write.encode(imageData, getState(ScreenshotSettings).ktx2)) as ArrayBuffer
207156
return new Blob([ktx2texture])
@@ -260,47 +209,47 @@ export const convertCubemapToEquiImageData = (
260209
const camera = new OrthographicCamera(1 / -2, 1 / 2, 1 / 2, 1 / -2, -10000, 10000)
261210

262211
quad.scale.set(width, height, 1)
263-
camera.left = width / -2
264-
camera.right = width / 2
265-
camera.top = height / 2
266-
camera.bottom = height / -2
212+
camera.left = width / 2
213+
camera.right = width / -2
214+
camera.top = height / -2
215+
camera.bottom = height / 2
267216
camera.updateProjectionMatrix()
268217
const renderTarget = new WebGLRenderTarget(width, height, {
269218
minFilter: LinearFilter,
270219
magFilter: LinearFilter,
271220
wrapS: ClampToEdgeWrapping,
272221
wrapT: ClampToEdgeWrapping,
222+
colorSpace: SRGBColorSpace,
273223
format: RGBAFormat,
274224
type: UnsignedByteType
275225
})
276226

227+
const originalColorSpace = renderer.outputColorSpace
228+
renderer.outputColorSpace = SRGBColorSpace
277229
renderer.setRenderTarget(renderTarget)
278230
quad.material.uniforms.map.value = source
279231
renderer.render(scene, camera)
280232
const pixels = new Uint8Array(4 * width * height)
281233
renderer.readRenderTargetPixels(renderTarget, 0, 0, width, height, pixels)
282-
const imageData = new ImageData(new Uint8ClampedArray(pixels), width, height)
283234
renderer.setRenderTarget(null) // pass `null` to set canvas as render target
235+
renderer.outputColorSpace = originalColorSpace
284236

285-
if (returnAsBlob) {
286-
const canvas = document.createElement('canvas')
287-
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
288-
canvas.width = width
289-
canvas.height = height
290-
ctx.putImageData(imageData, 0, 0)
291-
return new Promise<Blob | null>((resolve) => canvas.toBlob(resolve))
292-
}
293-
294-
/* const writer = new GLTFWriter()
295-
writer.processSampler(source)
237+
const imageData = new ImageData(new Uint8ClampedArray(pixels), width, height)
296238

297-
const g = new GLTF
298-
writer.write(new Object3D())
299-
*/
239+
if (returnAsBlob) return imageDataToBlob(imageData)
300240

301241
return imageData
302242
}
303243

244+
export const imageDataToBlob = (imageData: ImageData): Promise<Blob | null> => {
245+
const canvas = document.createElement('canvas')
246+
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D
247+
canvas.width = imageData.width
248+
canvas.height = imageData.height
249+
ctx.putImageData(imageData, 0, 0)
250+
return new Promise<Blob | null>((resolve) => canvas.toBlob(resolve))
251+
}
252+
304253
//convert Equirectangular map to WebGlCubeRenderTarget
305254
export const convertEquiToCubemap = (renderer: WebGLRenderer, source: Texture, size: number): WebGLCubeRenderTarget => {
306255
const convertScene = new Scene()

0 commit comments

Comments
 (0)