support panoramic image in 3d node (#6638)

## Summary

Adds panoramic image support to the 3D node viewer, allowing users to
display equirectangular panoramic images as immersive backgrounds
alongside the existing tiled image mode.

## Changes

- Toggle between tiled and panorama rendering modes for background
images
- Field of view (FOV) control for panorama mode
- Refactored FOV slider into reusable PopupSlider component

## Screenshots


https://github.com/user-attachments/assets/8955d74b-b0e6-4b26-83ca-ccf902b43aa6

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6638-support-panoramic-image-in-3d-node-2a56d73d365081b98647f988130e312e)
by [Unito](https://www.unito.io)
This commit is contained in:
Terry Jia
2025-11-11 04:02:12 -05:00
committed by GitHub
parent c94cedf8ee
commit 879cb8f1a8
15 changed files with 310 additions and 84 deletions

View File

@@ -39,6 +39,8 @@
v-model:show-grid="sceneConfig!.showGrid"
v-model:background-color="sceneConfig!.backgroundColor"
v-model:background-image="sceneConfig!.backgroundImage"
v-model:background-render-mode="sceneConfig!.backgroundRenderMode"
v-model:fov="cameraConfig!.fov"
@update-background-image="handleBackgroundImageUpdate"
/>

View File

@@ -34,6 +34,8 @@
<SceneControls
v-model:background-color="viewer.backgroundColor.value"
v-model:show-grid="viewer.showGrid.value"
v-model:background-render-mode="viewer.backgroundRenderMode.value"
v-model:fov="viewer.fov.value"
:has-background-image="viewer.hasBackgroundImage.value"
@update-background-image="viewer.handleBackgroundImageUpdate"
/>

View File

@@ -6,65 +6,30 @@
value: $t('load3d.switchCamera'),
showDelay: 300
}"
:class="['pi', getCameraIcon, 'text-lg text-white']"
:class="['pi', 'pi-camera', 'text-lg text-white']"
/>
</Button>
<div v-if="showFOVButton" class="show-fov relative">
<Button class="p-button-rounded p-button-text" @click="toggleFOV">
<i
v-tooltip.right="{ value: $t('load3d.fov'), showDelay: 300 }"
class="pi pi-expand text-lg text-white"
/>
</Button>
<div
v-show="showFOV"
class="absolute top-0 left-12 rounded-lg bg-black/50 p-4 shadow-lg"
style="width: 150px"
>
<Slider v-model="fov" class="w-full" :min="10" :max="150" :step="1" />
</div>
</div>
<PopupSlider
v-if="showFOVButton"
v-model="fov"
:tooltip-text="$t('load3d.fov')"
/>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Slider from 'primevue/slider'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { computed } from 'vue'
import PopupSlider from '@/components/load3d/controls/PopupSlider.vue'
import type { CameraType } from '@/extensions/core/load3d/interfaces'
const showFOV = ref(false)
const cameraType = defineModel<CameraType>('cameraType')
const fov = defineModel<number>('fov')
const showFOVButton = computed(() => cameraType.value === 'perspective')
const getCameraIcon = computed(() => {
return cameraType.value === 'perspective' ? 'pi-camera' : 'pi-camera'
})
const toggleFOV = () => {
showFOV.value = !showFOV.value
}
const switchCamera = () => {
cameraType.value =
cameraType.value === 'perspective' ? 'orthographic' : 'perspective'
}
const closeCameraSlider = (e: MouseEvent) => {
const target = e.target as HTMLElement
if (!target.closest('.show-fov')) {
showFOV.value = false
}
}
onMounted(() => {
document.addEventListener('click', closeCameraSlider)
})
onUnmounted(() => {
document.removeEventListener('click', closeCameraSlider)
})
</script>

View File

@@ -0,0 +1,64 @@
<template>
<div class="relative show-slider">
<Button class="p-button-rounded p-button-text" @click="toggleSlider">
<i
v-tooltip.right="{ value: tooltipText, showDelay: 300 }"
:class="['pi', icon, 'text-lg text-white']"
/>
</Button>
<div
v-show="showSlider"
class="absolute top-0 left-12 rounded-lg bg-black/50 p-4 shadow-lg w-[150px]"
>
<Slider
v-model="value"
class="w-full"
:min="min"
:max="max"
:step="step"
/>
</div>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import Slider from 'primevue/slider'
import { onMounted, onUnmounted, ref } from 'vue'
const {
icon = 'pi-expand',
min = 10,
max = 150,
step = 1
} = defineProps<{
icon?: string
tooltipText: string
min?: number
max?: number
step?: number
}>()
const value = defineModel<number>()
const showSlider = ref(false)
const toggleSlider = () => {
showSlider.value = !showSlider.value
}
const closeSlider = (e: MouseEvent) => {
const target = e.target as HTMLElement
if (!target.closest('.show-slider')) {
showSlider.value = false
}
}
onMounted(() => {
document.addEventListener('click', closeSlider)
})
onUnmounted(() => {
document.removeEventListener('click', closeSlider)
})
</script>

View File

@@ -51,6 +51,28 @@
</Button>
</div>
<div v-if="hasBackgroundImage">
<Button
class="p-button-rounded p-button-text"
:class="{ 'p-button-outlined': backgroundRenderMode === 'panorama' }"
@click="toggleBackgroundRenderMode"
>
<i
v-tooltip.right="{
value: $t('load3d.panoramaMode'),
showDelay: 300
}"
class="pi pi-globe text-lg text-white"
/>
</Button>
</div>
<PopupSlider
v-if="hasBackgroundImage && backgroundRenderMode === 'panorama'"
v-model="fov"
:tooltip-text="$t('load3d.fov')"
/>
<div v-if="hasBackgroundImage">
<Button
class="p-button-rounded p-button-text"
@@ -72,6 +94,9 @@
import Button from 'primevue/button'
import { computed, ref } from 'vue'
import PopupSlider from '@/components/load3d/controls/PopupSlider.vue'
import type { BackgroundRenderModeType } from '@/extensions/core/load3d/interfaces'
const emit = defineEmits<{
(e: 'updateBackgroundImage', file: File | null): void
}>()
@@ -79,6 +104,11 @@ const emit = defineEmits<{
const showGrid = defineModel<boolean>('showGrid')
const backgroundColor = defineModel<string>('backgroundColor')
const backgroundImage = defineModel<string>('backgroundImage')
const backgroundRenderMode = defineModel<BackgroundRenderModeType>(
'backgroundRenderMode',
{ default: 'tiled' }
)
const fov = defineModel<number>('fov')
const hasBackgroundImage = computed(
() => backgroundImage.value && backgroundImage.value !== ''
)
@@ -113,4 +143,9 @@ const uploadBackgroundImage = (event: Event) => {
const removeBackgroundImage = () => {
emit('updateBackgroundImage', null)
}
const toggleBackgroundRenderMode = () => {
backgroundRenderMode.value =
backgroundRenderMode.value === 'panorama' ? 'tiled' : 'panorama'
}
</script>

View File

@@ -32,6 +32,24 @@
</div>
<div v-if="hasBackgroundImage" class="space-y-2">
<div class="flex gap-2">
<Button
:severity="backgroundRenderMode === 'tiled' ? 'primary' : 'secondary'"
:label="$t('load3d.tiledMode')"
icon="pi pi-th-large"
class="flex-1"
@click="setBackgroundRenderMode('tiled')"
/>
<Button
:severity="
backgroundRenderMode === 'panorama' ? 'primary' : 'secondary'
"
:label="$t('load3d.panoramaMode')"
icon="pi pi-globe"
class="flex-1"
@click="setBackgroundRenderMode('panorama')"
/>
</div>
<Button
severity="secondary"
:label="$t('load3d.removeBackgroundImage')"
@@ -50,6 +68,9 @@ import { ref } from 'vue'
const backgroundColor = defineModel<string>('backgroundColor')
const showGrid = defineModel<boolean>('showGrid')
const backgroundRenderMode = defineModel<'tiled' | 'panorama'>(
'backgroundRenderMode'
)
defineProps<{
hasBackgroundImage?: boolean
@@ -77,4 +98,8 @@ const handleImageUpload = (event: Event) => {
const removeBackgroundImage = () => {
emit('updateBackgroundImage', null)
}
const setBackgroundRenderMode = (mode: 'tiled' | 'panorama') => {
backgroundRenderMode.value = mode
}
</script>

View File

@@ -31,7 +31,8 @@ export const useLoad3d = (nodeOrRef: MaybeRef<LGraphNode | null>) => {
const sceneConfig = ref<SceneConfig>({
showGrid: true,
backgroundColor: '#000000',
backgroundImage: ''
backgroundImage: '',
backgroundRenderMode: 'tiled'
})
const modelConfig = ref<ModelConfig>({
@@ -131,7 +132,11 @@ export const useLoad3d = (nodeOrRef: MaybeRef<LGraphNode | null>) => {
// Restore configs - watchers will handle applying them to the Three.js scene
const savedSceneConfig = node.properties['Scene Config'] as SceneConfig
if (savedSceneConfig) {
sceneConfig.value = savedSceneConfig
sceneConfig.value = {
...sceneConfig.value,
...savedSceneConfig,
backgroundRenderMode: savedSceneConfig.backgroundRenderMode || 'tiled'
}
}
const savedModelConfig = node.properties['Model Config'] as ModelConfig
@@ -221,12 +226,17 @@ export const useLoad3d = (nodeOrRef: MaybeRef<LGraphNode | null>) => {
watch(
sceneConfig,
(newValue) => {
async (newValue) => {
if (load3d && nodeRef.value) {
nodeRef.value.properties['Scene Config'] = newValue
load3d.toggleGrid(newValue.showGrid)
load3d.setBackgroundColor(newValue.backgroundColor)
void load3d.setBackgroundImage(newValue.backgroundImage || '')
await load3d.setBackgroundImage(newValue.backgroundImage || '')
if (newValue.backgroundRenderMode) {
load3d.setBackgroundRenderMode(newValue.backgroundRenderMode)
}
}
},
{ deep: true }
@@ -424,6 +434,9 @@ export const useLoad3d = (nodeOrRef: MaybeRef<LGraphNode | null>) => {
backgroundColorChange: (value: string) => {
sceneConfig.value.backgroundColor = value
},
backgroundRenderModeChange: (value: string) => {
sceneConfig.value.backgroundRenderMode = value as 'tiled' | 'panorama'
},
lightIntensityChange: (value: number) => {
lightConfig.value.intensity = value
},

View File

@@ -3,6 +3,7 @@ import { ref, toRaw, watch } from 'vue'
import Load3d from '@/extensions/core/load3d/Load3d'
import Load3dUtils from '@/extensions/core/load3d/Load3dUtils'
import type {
BackgroundRenderModeType,
CameraType,
MaterialMode,
UpDirection
@@ -21,6 +22,7 @@ interface Load3dViewerState {
lightIntensity: number
cameraState: any
backgroundImage: string
backgroundRenderMode: BackgroundRenderModeType
upDirection: UpDirection
materialMode: MaterialMode
}
@@ -33,6 +35,7 @@ export const useLoad3dViewer = (node: LGraphNode) => {
const lightIntensity = ref(1)
const backgroundImage = ref('')
const hasBackgroundImage = ref(false)
const backgroundRenderMode = ref<BackgroundRenderModeType>('tiled')
const upDirection = ref<UpDirection>('original')
const materialMode = ref<MaterialMode>('original')
const needApplyChanges = ref(true)
@@ -49,6 +52,7 @@ export const useLoad3dViewer = (node: LGraphNode) => {
lightIntensity: 1,
cameraState: null,
backgroundImage: '',
backgroundRenderMode: 'tiled',
upDirection: 'original',
materialMode: 'original'
})
@@ -124,6 +128,20 @@ export const useLoad3dViewer = (node: LGraphNode) => {
}
})
watch(backgroundRenderMode, (newValue) => {
if (!load3d) return
try {
load3d.setBackgroundRenderMode(newValue)
} catch (error) {
console.error('Error updating background render mode:', error)
useToastStore().addAlert(
t('toastMessages.failedToUpdateBackgroundRenderMode', {
mode: newValue
})
)
}
})
watch(upDirection, (newValue) => {
if (!load3d) return
try {
@@ -180,6 +198,10 @@ export const useLoad3dViewer = (node: LGraphNode) => {
source.sceneManager.currentBackgroundColor
showGrid.value =
sceneConfig.showGrid ?? source.sceneManager.gridHelper.visible
backgroundRenderMode.value =
sceneConfig.backgroundRenderMode ||
source.sceneManager.backgroundRenderMode ||
'tiled'
const backgroundInfo = source.sceneManager.getCurrentBackgroundInfo()
if (backgroundInfo.type === 'image' && sceneConfig.backgroundImage) {
@@ -219,6 +241,7 @@ export const useLoad3dViewer = (node: LGraphNode) => {
lightIntensity: lightIntensity.value,
cameraState: sourceCameraState,
backgroundImage: backgroundImage.value,
backgroundRenderMode: backgroundRenderMode.value,
upDirection: upDirection.value,
materialMode: materialMode.value
}
@@ -274,7 +297,8 @@ export const useLoad3dViewer = (node: LGraphNode) => {
nodeValue.properties['Scene Config'] = {
showGrid: initialState.value.showGrid,
backgroundColor: initialState.value.backgroundColor,
backgroundImage: initialState.value.backgroundImage
backgroundImage: initialState.value.backgroundImage,
backgroundRenderMode: initialState.value.backgroundRenderMode
}
nodeValue.properties['Camera Config'] = {
@@ -309,7 +333,8 @@ export const useLoad3dViewer = (node: LGraphNode) => {
nodeValue.properties['Scene Config'] = {
showGrid: showGrid.value,
backgroundColor: backgroundColor.value,
backgroundImage: backgroundImage.value
backgroundImage: backgroundImage.value,
backgroundRenderMode: backgroundRenderMode.value
}
nodeValue.properties['Camera Config'] = {
@@ -331,6 +356,7 @@ export const useLoad3dViewer = (node: LGraphNode) => {
await useLoad3dService().copyLoad3dState(load3d, sourceLoad3d)
await sourceLoad3d.setBackgroundImage(backgroundImage.value)
sourceLoad3d.setBackgroundRenderMode(backgroundRenderMode.value)
sourceLoad3d.forceRender()
@@ -429,6 +455,7 @@ export const useLoad3dViewer = (node: LGraphNode) => {
lightIntensity,
backgroundImage,
hasBackgroundImage,
backgroundRenderMode,
upDirection,
materialMode,
needApplyChanges,

View File

@@ -162,7 +162,11 @@ class Load3DConfiguration {
return
}
this.load3d.setBackgroundImage(config.backgroundImage)
void this.load3d.setBackgroundImage(config.backgroundImage)
if (config.backgroundRenderMode) {
this.load3d.setBackgroundRenderMode(config.backgroundRenderMode)
}
}
}

View File

@@ -510,6 +510,11 @@ class Load3d {
this.forceRender()
}
setBackgroundRenderMode(mode: 'tiled' | 'panorama'): void {
this.sceneManager.setBackgroundRenderMode(mode)
this.forceRender()
}
toggleCamera(cameraType?: 'perspective' | 'orthographic'): void {
this.cameraManager.toggleCamera(cameraType)

View File

@@ -3,6 +3,7 @@ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import Load3dUtils from './Load3dUtils'
import {
type BackgroundRenderModeType,
type EventManagerInterface,
type SceneManagerInterface
} from './interfaces'
@@ -16,6 +17,8 @@ export class SceneManager implements SceneManagerInterface {
backgroundMesh: THREE.Mesh | null = null
backgroundTexture: THREE.Texture | null = null
backgroundRenderMode: 'tiled' | 'panorama' = 'tiled'
backgroundColorMaterial: THREE.MeshBasicMaterial | null = null
currentBackgroundType: 'color' | 'image' = 'color'
currentBackgroundColor: string = '#282828'
@@ -89,6 +92,10 @@ export class SceneManager implements SceneManagerInterface {
}
}
if (this.scene.background) {
this.scene.background = null
}
this.scene.clear()
}
@@ -104,6 +111,15 @@ export class SceneManager implements SceneManagerInterface {
this.currentBackgroundColor = color
this.currentBackgroundType = 'color'
if (this.scene.background instanceof THREE.Texture) {
this.scene.background = null
}
if (this.backgroundRenderMode === 'panorama') {
this.backgroundRenderMode = 'tiled'
this.eventManager.emitEvent('backgroundRenderModeChange', 'tiled')
}
if (!this.backgroundMesh || !this.backgroundColorMaterial) {
this.initBackgroundScene()
}
@@ -168,36 +184,41 @@ export class SceneManager implements SceneManagerInterface {
this.backgroundTexture = texture
this.currentBackgroundType = 'image'
if (!this.backgroundMesh) {
this.initBackgroundScene()
}
const imageMaterial = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
depthWrite: false,
depthTest: false,
side: THREE.DoubleSide
})
if (this.backgroundMesh) {
if (
this.backgroundMesh.material !== this.backgroundColorMaterial &&
this.backgroundMesh.material instanceof THREE.Material
) {
this.backgroundMesh.material.dispose()
if (this.backgroundRenderMode === 'panorama') {
texture.mapping = THREE.EquirectangularReflectionMapping
this.scene.background = texture
} else {
if (!this.backgroundMesh) {
this.initBackgroundScene()
}
this.backgroundMesh.material = imageMaterial
this.backgroundMesh.position.set(0, 0, 0)
}
const imageMaterial = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
depthWrite: false,
depthTest: false,
side: THREE.DoubleSide
})
this.updateBackgroundSize(
this.backgroundTexture,
this.backgroundMesh,
this.renderer.domElement.clientWidth,
this.renderer.domElement.clientHeight
)
if (this.backgroundMesh) {
if (
this.backgroundMesh.material !== this.backgroundColorMaterial &&
this.backgroundMesh.material instanceof THREE.Material
) {
this.backgroundMesh.material.dispose()
}
this.backgroundMesh.material = imageMaterial
this.backgroundMesh.position.set(0, 0, 0)
}
this.updateBackgroundSize(
this.backgroundTexture,
this.backgroundMesh,
this.renderer.domElement.clientWidth,
this.renderer.domElement.clientHeight
)
}
this.eventManager.emitEvent('backgroundImageChange', uploadPath)
this.eventManager.emitEvent('backgroundImageLoadingEnd', null)
@@ -213,6 +234,35 @@ export class SceneManager implements SceneManagerInterface {
this.eventManager.emitEvent('backgroundImageLoadingEnd', null)
}
setBackgroundRenderMode(mode: BackgroundRenderModeType): void {
if (this.backgroundRenderMode === mode) return
this.backgroundRenderMode = mode
if (this.currentBackgroundType === 'image' && this.backgroundTexture) {
try {
if (mode === 'panorama') {
this.backgroundTexture.mapping =
THREE.EquirectangularReflectionMapping
this.scene.background = this.backgroundTexture
} else {
this.scene.background = null
if (
this.backgroundMesh &&
this.backgroundMesh.material instanceof THREE.MeshBasicMaterial
) {
this.backgroundMesh.material.map = this.backgroundTexture
this.backgroundMesh.material.needsUpdate = true
}
}
} catch (error) {
console.error('Error set background render mode:', error)
}
}
this.eventManager.emitEvent('backgroundRenderModeChange', mode)
}
updateBackgroundSize(
backgroundTexture: THREE.Texture | null,
backgroundMesh: THREE.Mesh | null,
@@ -254,7 +304,11 @@ export class SceneManager implements SceneManagerInterface {
}
renderBackground(): void {
if (this.backgroundMesh) {
if (
(this.backgroundRenderMode === 'tiled' ||
this.currentBackgroundType === 'color') &&
this.backgroundMesh
) {
const currentToneMapping = this.renderer.toneMapping
const currentExposure = this.renderer.toneMappingExposure

View File

@@ -13,6 +13,7 @@ import { type CustomInputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
export type MaterialMode = 'original' | 'normal' | 'wireframe' | 'depth'
export type UpDirection = 'original' | '-x' | '+x' | '-y' | '+y' | '-z' | '+z'
export type CameraType = 'perspective' | 'orthographic'
export type BackgroundRenderModeType = 'tiled' | 'panorama'
export interface CameraState {
position: THREE.Vector3
@@ -25,6 +26,7 @@ export interface SceneConfig {
showGrid: boolean
backgroundColor: string
backgroundImage?: string
backgroundRenderMode?: BackgroundRenderModeType
}
export interface ModelConfig {
@@ -77,6 +79,7 @@ export interface SceneManagerInterface extends BaseManager {
setBackgroundColor(color: string): void
setBackgroundImage(uploadPath: string): Promise<void>
removeBackgroundImage(): void
setBackgroundRenderMode(mode: BackgroundRenderModeType): void
handleResize(width: number, height: number): void
captureScene(width: number, height: number): Promise<CaptureResult>
}

View File

@@ -1737,6 +1737,8 @@
"previewOutput": "Preview Output",
"uploadBackgroundImage": "Upload Background Image",
"removeBackgroundImage": "Remove Background Image",
"tiledMode": "Tiled",
"panoramaMode": "Panorama",
"loadingModel": "Loading 3D Model...",
"upDirection": "Up Direction",
"materialMode": "Material Mode",
@@ -1833,7 +1835,8 @@
"failedToInitializeLoad3dViewer": "Failed to initialize 3D Viewer",
"failedToLoadBackgroundImage": "Failed to load background image",
"failedToLoadModel": "Failed to load 3D model",
"modelLoadedSuccessfully": "3D model loaded successfully"
"modelLoadedSuccessfully": "3D model loaded successfully",
"failedToUpdateBackgroundRenderMode": "Failed to update background render mode to {mode}"
},
"auth": {
"apiKey": {

View File

@@ -47,7 +47,8 @@ describe('useLoad3d', () => {
'Scene Config': {
showGrid: true,
backgroundColor: '#000000',
backgroundImage: ''
backgroundImage: '',
backgroundRenderMode: 'tiled'
},
'Model Config': {
upDirection: 'original',
@@ -81,6 +82,7 @@ describe('useLoad3d', () => {
toggleGrid: vi.fn(),
setBackgroundColor: vi.fn(),
setBackgroundImage: vi.fn().mockResolvedValue(undefined),
setBackgroundRenderMode: vi.fn(),
setUpDirection: vi.fn(),
setMaterialMode: vi.fn(),
toggleCamera: vi.fn(),
@@ -130,7 +132,8 @@ describe('useLoad3d', () => {
expect(composable.sceneConfig.value).toEqual({
showGrid: true,
backgroundColor: '#000000',
backgroundImage: ''
backgroundImage: '',
backgroundRenderMode: 'tiled'
})
expect(composable.modelConfig.value).toEqual({
upDirection: 'original',
@@ -165,9 +168,11 @@ describe('useLoad3d', () => {
const containerRef = document.createElement('div')
await composable.initializeLoad3d(containerRef)
await nextTick()
expect(mockLoad3d.toggleGrid).toHaveBeenCalledWith(true)
expect(mockLoad3d.setBackgroundColor).toHaveBeenCalledWith('#000000')
expect(mockLoad3d.setBackgroundRenderMode).toHaveBeenCalledWith('tiled')
expect(mockLoad3d.setUpDirection).toHaveBeenCalledWith('original')
expect(mockLoad3d.setMaterialMode).toHaveBeenCalledWith('original')
expect(mockLoad3d.toggleCamera).toHaveBeenCalledWith('perspective')
@@ -356,17 +361,22 @@ describe('useLoad3d', () => {
composable.sceneConfig.value = {
showGrid: false,
backgroundColor: '#ffffff',
backgroundImage: 'test.jpg'
backgroundImage: 'test.jpg',
backgroundRenderMode: 'panorama'
}
await nextTick()
expect(mockLoad3d.toggleGrid).toHaveBeenCalledWith(false)
expect(mockLoad3d.setBackgroundColor).toHaveBeenCalledWith('#ffffff')
expect(mockLoad3d.setBackgroundImage).toHaveBeenCalledWith('test.jpg')
expect(mockLoad3d.setBackgroundRenderMode).toHaveBeenCalledWith(
'panorama'
)
expect(mockNode.properties['Scene Config']).toEqual({
showGrid: false,
backgroundColor: '#ffffff',
backgroundImage: 'test.jpg'
backgroundImage: 'test.jpg',
backgroundRenderMode: 'panorama'
})
})
@@ -375,6 +385,10 @@ describe('useLoad3d', () => {
const containerRef = document.createElement('div')
await composable.initializeLoad3d(containerRef)
await nextTick()
mockLoad3d.setUpDirection.mockClear()
mockLoad3d.setMaterialMode.mockClear()
composable.modelConfig.value.upDirection = '+y'
composable.modelConfig.value.materialMode = 'wireframe'
@@ -393,6 +407,10 @@ describe('useLoad3d', () => {
const containerRef = document.createElement('div')
await composable.initializeLoad3d(containerRef)
await nextTick()
mockLoad3d.toggleCamera.mockClear()
mockLoad3d.setFOV.mockClear()
composable.cameraConfig.value.cameraType = 'orthographic'
composable.cameraConfig.value.fov = 90
@@ -412,6 +430,9 @@ describe('useLoad3d', () => {
const containerRef = document.createElement('div')
await composable.initializeLoad3d(containerRef)
await nextTick()
mockLoad3d.setLightIntensity.mockClear()
composable.lightConfig.value.intensity = 10
await nextTick()
@@ -652,6 +673,7 @@ describe('useLoad3d', () => {
const expectedEvents = [
'materialModeChange',
'backgroundColorChange',
'backgroundRenderModeChange',
'lightIntensityChange',
'fovChange',
'cameraTypeChange',

View File

@@ -44,7 +44,8 @@ describe('useLoad3dViewer', () => {
'Scene Config': {
backgroundColor: '#282828',
showGrid: true,
backgroundImage: ''
backgroundImage: '',
backgroundRenderMode: 'tiled'
},
'Camera Config': {
cameraType: 'perspective',
@@ -115,6 +116,7 @@ describe('useLoad3dViewer', () => {
materialMode: 'original'
},
setBackgroundImage: vi.fn().mockResolvedValue(undefined),
setBackgroundRenderMode: vi.fn(),
forceRender: vi.fn()
}