mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-02 03:30:04 +00:00
438 lines
12 KiB
TypeScript
438 lines
12 KiB
TypeScript
import * as THREE from 'three'
|
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
|
|
|
|
import Load3dUtils from './Load3dUtils'
|
|
import {
|
|
type EventManagerInterface,
|
|
type SceneManagerInterface
|
|
} from './interfaces'
|
|
|
|
export class SceneManager implements SceneManagerInterface {
|
|
scene: THREE.Scene
|
|
gridHelper: THREE.GridHelper
|
|
|
|
backgroundScene: THREE.Scene
|
|
backgroundCamera: THREE.OrthographicCamera
|
|
backgroundMesh: THREE.Mesh | null = null
|
|
backgroundTexture: THREE.Texture | null = null
|
|
|
|
backgroundColorMaterial: THREE.MeshBasicMaterial | null = null
|
|
currentBackgroundType: 'color' | 'image' = 'color'
|
|
currentBackgroundColor: string = '#282828'
|
|
|
|
private eventManager: EventManagerInterface
|
|
private renderer: THREE.WebGLRenderer
|
|
|
|
private getActiveCamera: () => THREE.Camera
|
|
// @ts-expect-error unused variable
|
|
private getControls: () => OrbitControls
|
|
|
|
constructor(
|
|
renderer: THREE.WebGLRenderer,
|
|
getActiveCamera: () => THREE.Camera,
|
|
getControls: () => OrbitControls,
|
|
eventManager: EventManagerInterface
|
|
) {
|
|
this.renderer = renderer
|
|
this.eventManager = eventManager
|
|
this.scene = new THREE.Scene()
|
|
|
|
this.getActiveCamera = getActiveCamera
|
|
this.getControls = getControls
|
|
|
|
this.gridHelper = new THREE.GridHelper(20, 20)
|
|
this.gridHelper.position.set(0, 0, 0)
|
|
this.scene.add(this.gridHelper)
|
|
|
|
this.backgroundScene = new THREE.Scene()
|
|
this.backgroundCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, -1, 1)
|
|
|
|
this.initBackgroundScene()
|
|
}
|
|
|
|
private initBackgroundScene(): void {
|
|
const planeGeometry = new THREE.PlaneGeometry(2, 2)
|
|
|
|
this.backgroundColorMaterial = new THREE.MeshBasicMaterial({
|
|
color: new THREE.Color(this.currentBackgroundColor),
|
|
transparent: false,
|
|
depthWrite: false,
|
|
depthTest: false,
|
|
side: THREE.DoubleSide
|
|
})
|
|
|
|
this.backgroundMesh = new THREE.Mesh(
|
|
planeGeometry,
|
|
this.backgroundColorMaterial
|
|
)
|
|
this.backgroundMesh.position.set(0, 0, 0)
|
|
this.backgroundScene.add(this.backgroundMesh)
|
|
|
|
this.renderer.setClearColor(0x000000, 0)
|
|
}
|
|
|
|
init(): void {}
|
|
|
|
dispose(): void {
|
|
if (this.backgroundTexture) {
|
|
this.backgroundTexture.dispose()
|
|
}
|
|
|
|
if (this.backgroundColorMaterial) {
|
|
this.backgroundColorMaterial.dispose()
|
|
}
|
|
|
|
if (this.backgroundMesh) {
|
|
this.backgroundMesh.geometry.dispose()
|
|
if (this.backgroundMesh.material instanceof THREE.Material) {
|
|
this.backgroundMesh.material.dispose()
|
|
}
|
|
}
|
|
|
|
this.scene.clear()
|
|
}
|
|
|
|
toggleGrid(showGrid: boolean): void {
|
|
if (this.gridHelper) {
|
|
this.gridHelper.visible = showGrid
|
|
}
|
|
|
|
this.eventManager.emitEvent('showGridChange', showGrid)
|
|
}
|
|
|
|
setBackgroundColor(color: string): void {
|
|
this.currentBackgroundColor = color
|
|
this.currentBackgroundType = 'color'
|
|
|
|
if (!this.backgroundMesh || !this.backgroundColorMaterial) {
|
|
this.initBackgroundScene()
|
|
}
|
|
|
|
this.backgroundColorMaterial!.color.set(color)
|
|
this.backgroundColorMaterial!.map = null
|
|
this.backgroundColorMaterial!.transparent = false
|
|
this.backgroundColorMaterial!.needsUpdate = true
|
|
|
|
if (this.backgroundMesh) {
|
|
this.backgroundMesh.material = this.backgroundColorMaterial!
|
|
}
|
|
|
|
if (this.backgroundTexture) {
|
|
this.backgroundTexture.dispose()
|
|
this.backgroundTexture = null
|
|
}
|
|
|
|
this.eventManager.emitEvent('backgroundColorChange', color)
|
|
}
|
|
|
|
async setBackgroundImage(uploadPath: string): Promise<void> {
|
|
if (uploadPath === '') {
|
|
this.setBackgroundColor(this.currentBackgroundColor)
|
|
|
|
return
|
|
}
|
|
|
|
this.eventManager.emitEvent('backgroundImageLoadingStart', null)
|
|
|
|
let imageUrl = Load3dUtils.getResourceURL(
|
|
...Load3dUtils.splitFilePath(uploadPath)
|
|
)
|
|
|
|
if (!imageUrl.startsWith('/api')) {
|
|
imageUrl = '/api' + imageUrl
|
|
}
|
|
|
|
try {
|
|
const textureLoader = new THREE.TextureLoader()
|
|
const texture = await new Promise<THREE.Texture>((resolve, reject) => {
|
|
textureLoader.load(imageUrl, resolve, undefined, reject)
|
|
})
|
|
|
|
if (this.backgroundTexture) {
|
|
this.backgroundTexture.dispose()
|
|
}
|
|
|
|
texture.colorSpace = THREE.SRGBColorSpace
|
|
|
|
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()
|
|
}
|
|
|
|
this.backgroundMesh.material = imageMaterial
|
|
this.backgroundMesh.position.set(0, 0, 0)
|
|
}
|
|
|
|
this.updateBackgroundSize(
|
|
this.backgroundTexture,
|
|
this.backgroundMesh,
|
|
this.renderer.domElement.width,
|
|
this.renderer.domElement.height
|
|
)
|
|
|
|
this.eventManager.emitEvent('backgroundImageChange', uploadPath)
|
|
this.eventManager.emitEvent('backgroundImageLoadingEnd', null)
|
|
} catch (error) {
|
|
this.eventManager.emitEvent('backgroundImageLoadingEnd', null)
|
|
console.error('Error loading background image:', error)
|
|
this.setBackgroundColor(this.currentBackgroundColor)
|
|
}
|
|
}
|
|
|
|
removeBackgroundImage(): void {
|
|
this.setBackgroundColor(this.currentBackgroundColor)
|
|
this.eventManager.emitEvent('backgroundImageLoadingEnd', null)
|
|
}
|
|
|
|
updateBackgroundSize(
|
|
backgroundTexture: THREE.Texture | null,
|
|
backgroundMesh: THREE.Mesh | null,
|
|
targetWidth: number,
|
|
targetHeight: number
|
|
): void {
|
|
if (!backgroundTexture || !backgroundMesh) return
|
|
|
|
const material = backgroundMesh.material as THREE.MeshBasicMaterial
|
|
|
|
if (!material.map) return
|
|
|
|
const imageAspect =
|
|
backgroundTexture.image.width / backgroundTexture.image.height
|
|
const targetAspect = targetWidth / targetHeight
|
|
|
|
if (imageAspect > targetAspect) {
|
|
backgroundMesh.scale.set(imageAspect / targetAspect, 1, 1)
|
|
} else {
|
|
backgroundMesh.scale.set(1, targetAspect / imageAspect, 1)
|
|
}
|
|
|
|
material.needsUpdate = true
|
|
}
|
|
|
|
handleResize(width: number, height: number): void {
|
|
if (
|
|
this.backgroundTexture &&
|
|
this.backgroundMesh &&
|
|
this.currentBackgroundType === 'image'
|
|
) {
|
|
this.updateBackgroundSize(
|
|
this.backgroundTexture,
|
|
this.backgroundMesh,
|
|
width,
|
|
height
|
|
)
|
|
}
|
|
}
|
|
|
|
renderBackground(): void {
|
|
if (this.backgroundMesh) {
|
|
const currentToneMapping = this.renderer.toneMapping
|
|
const currentExposure = this.renderer.toneMappingExposure
|
|
|
|
this.renderer.toneMapping = THREE.NoToneMapping
|
|
this.renderer.render(this.backgroundScene, this.backgroundCamera)
|
|
|
|
this.renderer.toneMapping = currentToneMapping
|
|
this.renderer.toneMappingExposure = currentExposure
|
|
}
|
|
}
|
|
|
|
getCurrentBackgroundInfo(): { type: 'color' | 'image'; value: string } {
|
|
return {
|
|
type: this.currentBackgroundType,
|
|
value:
|
|
this.currentBackgroundType === 'color'
|
|
? this.currentBackgroundColor
|
|
: ''
|
|
}
|
|
}
|
|
|
|
captureScene(
|
|
width: number,
|
|
height: number
|
|
): Promise<{ scene: string; mask: string; normal: string; lineart: string }> {
|
|
return new Promise(async (resolve, reject) => {
|
|
try {
|
|
const originalWidth = this.renderer.domElement.width
|
|
const originalHeight = this.renderer.domElement.height
|
|
const originalClearColor = this.renderer.getClearColor(
|
|
new THREE.Color()
|
|
)
|
|
const originalClearAlpha = this.renderer.getClearAlpha()
|
|
const originalOutputColorSpace = this.renderer.outputColorSpace
|
|
|
|
this.renderer.setSize(width, height)
|
|
|
|
if (this.getActiveCamera() instanceof THREE.PerspectiveCamera) {
|
|
const perspectiveCamera =
|
|
this.getActiveCamera() as THREE.PerspectiveCamera
|
|
|
|
perspectiveCamera.aspect = width / height
|
|
perspectiveCamera.updateProjectionMatrix()
|
|
} else {
|
|
const orthographicCamera =
|
|
this.getActiveCamera() as THREE.OrthographicCamera
|
|
|
|
const frustumSize = 10
|
|
const aspect = width / height
|
|
|
|
orthographicCamera.left = (-frustumSize * aspect) / 2
|
|
orthographicCamera.right = (frustumSize * aspect) / 2
|
|
orthographicCamera.top = frustumSize / 2
|
|
orthographicCamera.bottom = -frustumSize / 2
|
|
|
|
orthographicCamera.updateProjectionMatrix()
|
|
}
|
|
|
|
if (
|
|
this.backgroundTexture &&
|
|
this.backgroundMesh &&
|
|
this.currentBackgroundType === 'image'
|
|
) {
|
|
this.updateBackgroundSize(
|
|
this.backgroundTexture,
|
|
this.backgroundMesh,
|
|
width,
|
|
height
|
|
)
|
|
}
|
|
|
|
const originalMaterials = new Map<
|
|
THREE.Mesh,
|
|
THREE.Material | THREE.Material[]
|
|
>()
|
|
|
|
this.renderer.clear()
|
|
this.renderBackground()
|
|
this.renderer.render(this.scene, this.getActiveCamera())
|
|
const sceneData = this.renderer.domElement.toDataURL('image/png')
|
|
|
|
this.renderer.setClearColor(0x000000, 0)
|
|
this.renderer.clear()
|
|
this.renderer.render(this.scene, this.getActiveCamera())
|
|
const maskData = this.renderer.domElement.toDataURL('image/png')
|
|
|
|
this.scene.traverse((child) => {
|
|
if (child instanceof THREE.Mesh) {
|
|
originalMaterials.set(child, child.material)
|
|
|
|
child.material = new THREE.MeshNormalMaterial({
|
|
flatShading: false,
|
|
side: THREE.DoubleSide,
|
|
normalScale: new THREE.Vector2(1, 1)
|
|
})
|
|
}
|
|
})
|
|
|
|
const gridVisible = this.gridHelper.visible
|
|
this.gridHelper.visible = false
|
|
|
|
this.renderer.setClearColor(0x000000, 1)
|
|
this.renderer.clear()
|
|
this.renderer.render(this.scene, this.getActiveCamera())
|
|
const normalData = this.renderer.domElement.toDataURL('image/png')
|
|
|
|
this.scene.traverse((child) => {
|
|
if (child instanceof THREE.Mesh) {
|
|
const originalMaterial = originalMaterials.get(child)
|
|
if (originalMaterial) {
|
|
child.material = originalMaterial
|
|
}
|
|
}
|
|
})
|
|
|
|
let lineartModel: THREE.Group | null = null
|
|
|
|
const originalSceneVisible: Map<THREE.Object3D, boolean> = new Map()
|
|
|
|
this.scene.traverse((child) => {
|
|
if (child instanceof THREE.Group && child.name === 'lineartModel') {
|
|
lineartModel = child as THREE.Group
|
|
}
|
|
|
|
if (
|
|
child instanceof THREE.Mesh &&
|
|
!(child.parent?.name === 'lineartModel')
|
|
) {
|
|
originalSceneVisible.set(child, child.visible)
|
|
|
|
child.visible = false
|
|
}
|
|
})
|
|
|
|
this.renderer.setClearColor(0xffffff, 1)
|
|
this.renderer.clear()
|
|
|
|
if (lineartModel !== null) {
|
|
lineartModel = lineartModel as THREE.Group
|
|
|
|
const originalLineartVisibleMap: Map<THREE.Object3D, boolean> =
|
|
new Map()
|
|
|
|
lineartModel.traverse((child: THREE.Object3D) => {
|
|
if (child instanceof THREE.Mesh) {
|
|
originalLineartVisibleMap.set(child, child.visible)
|
|
|
|
child.visible = true
|
|
}
|
|
})
|
|
|
|
const originalLineartVisible = lineartModel.visible
|
|
lineartModel.visible = true
|
|
|
|
this.renderer.render(this.scene, this.getActiveCamera())
|
|
|
|
lineartModel.visible = originalLineartVisible
|
|
|
|
originalLineartVisibleMap.forEach((visible, object) => {
|
|
object.visible = visible
|
|
})
|
|
}
|
|
|
|
const lineartData = this.renderer.domElement.toDataURL('image/png')
|
|
|
|
originalSceneVisible.forEach((visible, object) => {
|
|
object.visible = visible
|
|
})
|
|
|
|
this.gridHelper.visible = gridVisible
|
|
|
|
this.renderer.setClearColor(originalClearColor, originalClearAlpha)
|
|
this.renderer.setSize(originalWidth, originalHeight)
|
|
this.renderer.outputColorSpace = originalOutputColorSpace
|
|
|
|
this.handleResize(originalWidth, originalHeight)
|
|
|
|
resolve({
|
|
scene: sceneData,
|
|
mask: maskData,
|
|
normal: normalData,
|
|
lineart: lineartData
|
|
})
|
|
} catch (error) {
|
|
reject(error)
|
|
}
|
|
})
|
|
}
|
|
|
|
reset(): void {}
|
|
}
|