mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-07 08:30:06 +00:00
[3d] add output preview screen for load3d node
This commit is contained in:
@@ -25,7 +25,7 @@ app.registerExtension({
|
||||
container.id = `comfy-load-3d-${load3dNode.length}`
|
||||
container.classList.add('comfy-load-3d')
|
||||
|
||||
const load3d = new Load3d(container)
|
||||
const load3d = new Load3d(container, true)
|
||||
|
||||
containerToLoad3D.set(container.id, load3d)
|
||||
|
||||
@@ -129,40 +129,34 @@ app.registerExtension({
|
||||
|
||||
const material = node.widgets.find((w: IWidget) => w.name === 'material')
|
||||
|
||||
const lightIntensity = node.widgets.find(
|
||||
(w: IWidget) => w.name === 'light_intensity'
|
||||
)
|
||||
|
||||
const upDirection = node.widgets.find(
|
||||
(w: IWidget) => w.name === 'up_direction'
|
||||
)
|
||||
|
||||
const fov = node.widgets.find((w: IWidget) => w.name === 'fov')
|
||||
|
||||
let cameraState = node.properties['Camera Info']
|
||||
|
||||
const width = node.widgets.find((w: IWidget) => w.name === 'width')
|
||||
const height = node.widgets.find((w: IWidget) => w.name === 'height')
|
||||
|
||||
const config = new Load3DConfiguration(load3d)
|
||||
|
||||
config.configure(
|
||||
'input',
|
||||
modelWidget,
|
||||
material,
|
||||
lightIntensity,
|
||||
upDirection,
|
||||
fov,
|
||||
cameraState
|
||||
cameraState,
|
||||
width,
|
||||
height
|
||||
)
|
||||
|
||||
const w = node.widgets.find((w: IWidget) => w.name === 'width')
|
||||
const h = node.widgets.find((w: IWidget) => w.name === 'height')
|
||||
|
||||
// @ts-expect-error hacky override
|
||||
sceneWidget.serializeValue = async () => {
|
||||
node.properties['Camera Info'] = load3d.getCameraState()
|
||||
|
||||
const { scene: imageData, mask: maskData } = await load3d.captureScene(
|
||||
w.value,
|
||||
h.value
|
||||
width.value,
|
||||
height.value
|
||||
)
|
||||
|
||||
const [data, dataMask] = await Promise.all([
|
||||
@@ -194,7 +188,7 @@ app.registerExtension({
|
||||
container.id = `comfy-load-3d-animation-${load3dNode.length}`
|
||||
container.classList.add('comfy-load-3d-animation')
|
||||
|
||||
const load3d = new Load3dAnimation(container)
|
||||
const load3d = new Load3dAnimation(container, true)
|
||||
|
||||
containerToLoad3D.set(container.id, load3d)
|
||||
|
||||
@@ -298,33 +292,27 @@ app.registerExtension({
|
||||
|
||||
const material = node.widgets.find((w: IWidget) => w.name === 'material')
|
||||
|
||||
const lightIntensity = node.widgets.find(
|
||||
(w: IWidget) => w.name === 'light_intensity'
|
||||
)
|
||||
|
||||
const upDirection = node.widgets.find(
|
||||
(w: IWidget) => w.name === 'up_direction'
|
||||
)
|
||||
|
||||
const fov = node.widgets.find((w: IWidget) => w.name === 'fov')
|
||||
|
||||
let cameraState = node.properties['Camera Info']
|
||||
|
||||
const width = node.widgets.find((w: IWidget) => w.name === 'width')
|
||||
const height = node.widgets.find((w: IWidget) => w.name === 'height')
|
||||
|
||||
const config = new Load3DConfiguration(load3d)
|
||||
|
||||
config.configure(
|
||||
'input',
|
||||
modelWidget,
|
||||
material,
|
||||
lightIntensity,
|
||||
upDirection,
|
||||
fov,
|
||||
cameraState
|
||||
cameraState,
|
||||
width,
|
||||
height
|
||||
)
|
||||
|
||||
const w = node.widgets.find((w: IWidget) => w.name === 'width')
|
||||
const h = node.widgets.find((w: IWidget) => w.name === 'height')
|
||||
|
||||
// @ts-expect-error hacky override
|
||||
sceneWidget.serializeValue = async () => {
|
||||
node.properties['Camera Info'] = load3d.getCameraState()
|
||||
@@ -332,8 +320,8 @@ app.registerExtension({
|
||||
load3d.toggleAnimation(false)
|
||||
|
||||
const { scene: imageData, mask: maskData } = await load3d.captureScene(
|
||||
w.value,
|
||||
h.value
|
||||
width.value,
|
||||
height.value
|
||||
)
|
||||
|
||||
const [data, dataMask] = await Promise.all([
|
||||
@@ -432,16 +420,10 @@ app.registerExtension({
|
||||
|
||||
const material = node.widgets.find((w: IWidget) => w.name === 'material')
|
||||
|
||||
const lightIntensity = node.widgets.find(
|
||||
(w: IWidget) => w.name === 'light_intensity'
|
||||
)
|
||||
|
||||
const upDirection = node.widgets.find(
|
||||
(w: IWidget) => w.name === 'up_direction'
|
||||
)
|
||||
|
||||
const fov = node.widgets.find((w: IWidget) => w.name === 'fov')
|
||||
|
||||
const onExecuted = node.onExecuted
|
||||
|
||||
node.onExecuted = function (message: any) {
|
||||
@@ -461,14 +443,7 @@ app.registerExtension({
|
||||
|
||||
const config = new Load3DConfiguration(load3d)
|
||||
|
||||
config.configure(
|
||||
'output',
|
||||
modelWidget,
|
||||
material,
|
||||
lightIntensity,
|
||||
upDirection,
|
||||
fov
|
||||
)
|
||||
config.configure('output', modelWidget, material, upDirection)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -562,16 +537,10 @@ app.registerExtension({
|
||||
|
||||
const material = node.widgets.find((w: IWidget) => w.name === 'material')
|
||||
|
||||
const lightIntensity = node.widgets.find(
|
||||
(w: IWidget) => w.name === 'light_intensity'
|
||||
)
|
||||
|
||||
const upDirection = node.widgets.find(
|
||||
(w: IWidget) => w.name === 'up_direction'
|
||||
)
|
||||
|
||||
const fov = node.widgets.find((w: IWidget) => w.name === 'fov')
|
||||
|
||||
const onExecuted = node.onExecuted
|
||||
|
||||
node.onExecuted = function (message: any) {
|
||||
@@ -591,14 +560,7 @@ app.registerExtension({
|
||||
|
||||
const config = new Load3DConfiguration(load3d)
|
||||
|
||||
config.configure(
|
||||
'output',
|
||||
modelWidget,
|
||||
material,
|
||||
lightIntensity,
|
||||
upDirection,
|
||||
fov
|
||||
)
|
||||
config.configure('output', modelWidget, material, upDirection)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -11,10 +11,10 @@ class Load3DConfiguration {
|
||||
loadFolder: 'input' | 'output',
|
||||
modelWidget: IWidget,
|
||||
material: IWidget,
|
||||
lightIntensity: IWidget,
|
||||
upDirection: IWidget,
|
||||
fov: IWidget,
|
||||
cameraState?: any,
|
||||
width: IWidget | null = null,
|
||||
height: IWidget | null = null,
|
||||
postModelUpdateFunc?: (load3d: Load3d) => void
|
||||
) {
|
||||
this.setupModelHandling(
|
||||
@@ -24,9 +24,8 @@ class Load3DConfiguration {
|
||||
postModelUpdateFunc
|
||||
)
|
||||
this.setupMaterial(material)
|
||||
this.setupLighting(lightIntensity)
|
||||
this.setupDirection(upDirection)
|
||||
this.setupCamera(fov)
|
||||
this.setupTargetSize(width, height)
|
||||
this.setupDefaultProperties()
|
||||
}
|
||||
|
||||
@@ -56,13 +55,6 @@ class Load3DConfiguration {
|
||||
)
|
||||
}
|
||||
|
||||
private setupLighting(lightIntensity: IWidget) {
|
||||
lightIntensity.callback = (value: number) => {
|
||||
this.load3d.setLightIntensity(value)
|
||||
}
|
||||
this.load3d.setLightIntensity(lightIntensity.value as number)
|
||||
}
|
||||
|
||||
private setupDirection(upDirection: IWidget) {
|
||||
upDirection.callback = (
|
||||
value: 'original' | '-x' | '+x' | '-y' | '+y' | '-z' | '+z'
|
||||
@@ -74,11 +66,18 @@ class Load3DConfiguration {
|
||||
)
|
||||
}
|
||||
|
||||
private setupCamera(fov: IWidget) {
|
||||
fov.callback = (value: number) => {
|
||||
this.load3d.setFOV(value)
|
||||
private setupTargetSize(width: IWidget | null, height: IWidget | null) {
|
||||
if (width && height) {
|
||||
this.load3d.setTargetSize(width.value as number, height.value as number)
|
||||
|
||||
width.callback = (value: number) => {
|
||||
this.load3d.setTargetSize(value, height.value as number)
|
||||
}
|
||||
|
||||
height.callback = (value: number) => {
|
||||
this.load3d.setTargetSize(width.value as number, value)
|
||||
}
|
||||
}
|
||||
this.load3d.setFOV(fov.value as number)
|
||||
}
|
||||
|
||||
private setupDefaultProperties() {
|
||||
@@ -94,6 +93,21 @@ class Load3DConfiguration {
|
||||
const bgColor = this.load3d.loadNodeProperty('Background Color', '#282828')
|
||||
|
||||
this.load3d.setBackgroundColor(bgColor)
|
||||
|
||||
const fov = this.load3d.loadNodeProperty('FOV', '75')
|
||||
|
||||
this.load3d.setFOV(fov)
|
||||
|
||||
const lightIntensity = this.load3d.loadNodeProperty('Light Intensity', '5')
|
||||
|
||||
this.load3d.setLightIntensity(lightIntensity)
|
||||
|
||||
const previewVisible = this.load3d.loadNodeProperty(
|
||||
'Preview Visible',
|
||||
false
|
||||
)
|
||||
|
||||
this.load3d.setPreviewVisible(previewVisible)
|
||||
}
|
||||
|
||||
private createModelUpdateHandler(
|
||||
|
||||
@@ -45,8 +45,20 @@ class Load3d {
|
||||
gridSwitcherContainer: HTMLDivElement = {} as HTMLDivElement
|
||||
node: LGraphNode = {} as LGraphNode
|
||||
bgColorInput: HTMLInputElement = {} as HTMLInputElement
|
||||
fovSliderContainer: HTMLDivElement = {} as HTMLDivElement
|
||||
lightSliderContainer: HTMLDivElement = {} as HTMLDivElement
|
||||
previewRenderer: THREE.WebGLRenderer | null = null
|
||||
previewCamera: THREE.Camera | null = null
|
||||
previewContainer: HTMLDivElement = {} as HTMLDivElement
|
||||
targetWidth: number = 1024
|
||||
targetHeight: number = 1024
|
||||
previewToggleContainer: HTMLDivElement = {} as HTMLDivElement
|
||||
isPreviewVisible: boolean = true
|
||||
|
||||
constructor(container: Element | HTMLElement) {
|
||||
constructor(
|
||||
container: Element | HTMLElement,
|
||||
createPreview: boolean = false
|
||||
) {
|
||||
this.scene = new THREE.Scene()
|
||||
|
||||
this.perspectiveCamera = new THREE.PerspectiveCamera(75, 1, 0.1, 1000)
|
||||
@@ -123,12 +135,16 @@ class Load3d {
|
||||
this.standardMaterial = this.createSTLMaterial()
|
||||
|
||||
this.createViewHelper(container)
|
||||
|
||||
this.createGridSwitcher(container)
|
||||
|
||||
this.createCameraSwitcher(container)
|
||||
|
||||
this.createColorPicker(container)
|
||||
this.createFOVSlider(container)
|
||||
this.createLightIntensitySlider(container)
|
||||
|
||||
if (createPreview) {
|
||||
this.createPreviewToggle(container)
|
||||
this.createCapturePreview(container)
|
||||
}
|
||||
|
||||
this.handleResize()
|
||||
|
||||
@@ -156,6 +172,161 @@ class Load3d {
|
||||
return this.node.properties[name]
|
||||
}
|
||||
|
||||
createCapturePreview(container: Element | HTMLElement) {
|
||||
this.previewRenderer = new THREE.WebGLRenderer({
|
||||
alpha: true,
|
||||
antialias: true
|
||||
})
|
||||
this.previewRenderer.setSize(this.targetWidth, this.targetHeight)
|
||||
this.previewRenderer.setClearColor(0x282828)
|
||||
|
||||
this.previewContainer = document.createElement('div')
|
||||
this.previewContainer.style.cssText = `
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
display: block;
|
||||
`
|
||||
this.previewContainer.appendChild(this.previewRenderer.domElement)
|
||||
|
||||
this.previewContainer.style.display = this.isPreviewVisible
|
||||
? 'block'
|
||||
: 'none'
|
||||
|
||||
container.appendChild(this.previewContainer)
|
||||
}
|
||||
|
||||
updatePreviewRender() {
|
||||
if (!this.previewRenderer || !this.previewContainer) return
|
||||
|
||||
if (
|
||||
!this.previewCamera ||
|
||||
(this.activeCamera instanceof THREE.PerspectiveCamera &&
|
||||
!(this.previewCamera instanceof THREE.PerspectiveCamera)) ||
|
||||
(this.activeCamera instanceof THREE.OrthographicCamera &&
|
||||
!(this.previewCamera instanceof THREE.OrthographicCamera))
|
||||
) {
|
||||
this.previewCamera = this.activeCamera.clone()
|
||||
}
|
||||
|
||||
this.previewCamera.position.copy(this.activeCamera.position)
|
||||
this.previewCamera.rotation.copy(this.activeCamera.rotation)
|
||||
|
||||
const aspect = this.targetWidth / this.targetHeight
|
||||
|
||||
if (this.activeCamera instanceof THREE.OrthographicCamera) {
|
||||
const activeOrtho = this.activeCamera as THREE.OrthographicCamera
|
||||
const previewOrtho = this.previewCamera as THREE.OrthographicCamera
|
||||
|
||||
const frustumHeight =
|
||||
(activeOrtho.top - activeOrtho.bottom) / activeOrtho.zoom
|
||||
|
||||
const frustumWidth = frustumHeight * aspect
|
||||
|
||||
previewOrtho.top = frustumHeight / 2
|
||||
previewOrtho.left = -frustumWidth / 2
|
||||
previewOrtho.right = frustumWidth / 2
|
||||
previewOrtho.bottom = -frustumHeight / 2
|
||||
previewOrtho.zoom = 1
|
||||
|
||||
previewOrtho.updateProjectionMatrix()
|
||||
} else {
|
||||
;(this.previewCamera as THREE.PerspectiveCamera).aspect = aspect
|
||||
;(this.previewCamera as THREE.PerspectiveCamera).fov = (
|
||||
this.activeCamera as THREE.PerspectiveCamera
|
||||
).fov
|
||||
}
|
||||
|
||||
this.previewCamera.lookAt(this.controls.target)
|
||||
|
||||
const previewWidth = 120
|
||||
const previewHeight = (previewWidth * this.targetHeight) / this.targetWidth
|
||||
this.previewRenderer.setSize(previewWidth, previewHeight, false)
|
||||
this.previewRenderer.render(this.scene, this.previewCamera)
|
||||
}
|
||||
|
||||
createPreviewToggle(container: Element | HTMLElement) {
|
||||
this.previewToggleContainer = document.createElement('div')
|
||||
this.previewToggleContainer.style.position = 'absolute'
|
||||
this.previewToggleContainer.style.top = '128px'
|
||||
this.previewToggleContainer.style.left = '3px'
|
||||
this.previewToggleContainer.style.width = '20px'
|
||||
this.previewToggleContainer.style.height = '20px'
|
||||
this.previewToggleContainer.style.cursor = 'pointer'
|
||||
this.previewToggleContainer.style.display = 'flex'
|
||||
this.previewToggleContainer.style.alignItems = 'center'
|
||||
this.previewToggleContainer.style.justifyContent = 'center'
|
||||
this.previewToggleContainer.style.borderRadius = '2px'
|
||||
this.previewToggleContainer.title = 'Toggle Preview'
|
||||
|
||||
const eyeIcon = document.createElement('div')
|
||||
eyeIcon.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
|
||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
</svg>
|
||||
`
|
||||
|
||||
const updateButtonState = () => {
|
||||
if (this.isPreviewVisible) {
|
||||
this.previewToggleContainer.style.backgroundColor =
|
||||
'rgba(255, 255, 255, 0.2)'
|
||||
} else {
|
||||
this.previewToggleContainer.style.backgroundColor = 'transparent'
|
||||
}
|
||||
}
|
||||
|
||||
this.previewToggleContainer.addEventListener('mouseenter', () => {
|
||||
if (!this.isPreviewVisible) {
|
||||
this.previewToggleContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'
|
||||
}
|
||||
})
|
||||
|
||||
this.previewToggleContainer.addEventListener('mouseleave', () => {
|
||||
if (!this.isPreviewVisible) {
|
||||
this.previewToggleContainer.style.backgroundColor = 'transparent'
|
||||
}
|
||||
})
|
||||
|
||||
this.previewToggleContainer.addEventListener('click', (event) => {
|
||||
event.stopPropagation()
|
||||
|
||||
this.setPreviewVisible(!this.isPreviewVisible)
|
||||
|
||||
updateButtonState()
|
||||
})
|
||||
|
||||
this.previewToggleContainer.appendChild(eyeIcon)
|
||||
|
||||
container.appendChild(this.previewToggleContainer)
|
||||
|
||||
this.isPreviewVisible = this.loadNodeProperty('Preview Visible', true)
|
||||
|
||||
updateButtonState()
|
||||
}
|
||||
|
||||
setPreviewVisible(visible: boolean) {
|
||||
if (!this.previewContainer) return
|
||||
|
||||
this.isPreviewVisible = visible
|
||||
|
||||
this.previewContainer.style.display = this.isPreviewVisible
|
||||
? 'block'
|
||||
: 'none'
|
||||
|
||||
this.storeNodeProperty('Preview Visible', this.isPreviewVisible)
|
||||
}
|
||||
|
||||
updatePreviewSize() {
|
||||
if (!this.previewContainer) return
|
||||
|
||||
const previewWidth = 120
|
||||
const previewHeight = (previewWidth * this.targetHeight) / this.targetWidth
|
||||
|
||||
this.previewRenderer?.setSize(previewWidth, previewHeight, false)
|
||||
}
|
||||
|
||||
createViewHelper(container: Element | HTMLElement) {
|
||||
this.viewHelperContainer = document.createElement('div')
|
||||
|
||||
@@ -187,8 +358,8 @@ class Load3d {
|
||||
createGridSwitcher(container: Element | HTMLElement) {
|
||||
this.gridSwitcherContainer = document.createElement('div')
|
||||
this.gridSwitcherContainer.style.position = 'absolute'
|
||||
this.gridSwitcherContainer.style.top = '28px' // 修改这里,让按钮在相机按钮下方
|
||||
this.gridSwitcherContainer.style.left = '3px' // 与相机按钮左对齐
|
||||
this.gridSwitcherContainer.style.top = '28px'
|
||||
this.gridSwitcherContainer.style.left = '3px'
|
||||
this.gridSwitcherContainer.style.width = '20px'
|
||||
this.gridSwitcherContainer.style.height = '20px'
|
||||
this.gridSwitcherContainer.style.cursor = 'pointer'
|
||||
@@ -323,12 +494,257 @@ class Load3d {
|
||||
container.appendChild(colorPickerContainer)
|
||||
}
|
||||
|
||||
createFOVSlider(container: Element | HTMLElement) {
|
||||
this.fovSliderContainer = document.createElement('div')
|
||||
this.fovSliderContainer.style.position = 'absolute'
|
||||
this.fovSliderContainer.style.top = '78px'
|
||||
this.fovSliderContainer.style.left = '3px'
|
||||
this.fovSliderContainer.style.display = 'flex'
|
||||
this.fovSliderContainer.style.alignItems = 'center'
|
||||
this.fovSliderContainer.title = 'FOV (Perspective Camera Only)'
|
||||
|
||||
const wrapper = document.createElement('div')
|
||||
wrapper.style.position = 'relative'
|
||||
wrapper.style.display = 'flex'
|
||||
wrapper.style.alignItems = 'center'
|
||||
|
||||
const iconContainer = document.createElement('div')
|
||||
iconContainer.style.width = '20px'
|
||||
iconContainer.style.height = '20px'
|
||||
iconContainer.style.cursor = 'pointer'
|
||||
iconContainer.style.display = 'flex'
|
||||
iconContainer.style.alignItems = 'center'
|
||||
iconContainer.style.justifyContent = 'center'
|
||||
iconContainer.style.borderRadius = '2px'
|
||||
|
||||
const fovIcon = document.createElement('div')
|
||||
fovIcon.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
|
||||
<path d="M3 12h4"/>
|
||||
<path d="M17 12h4"/>
|
||||
<path d="M12 3v4"/>
|
||||
<path d="M12 17v4"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
</svg>
|
||||
`
|
||||
fovIcon.style.display = 'flex'
|
||||
fovIcon.style.alignItems = 'center'
|
||||
fovIcon.style.justifyContent = 'center'
|
||||
iconContainer.appendChild(fovIcon)
|
||||
|
||||
const sliderContainer = document.createElement('div')
|
||||
sliderContainer.style.position = 'absolute'
|
||||
sliderContainer.style.left = '25px'
|
||||
sliderContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'
|
||||
sliderContainer.style.padding = '5px'
|
||||
sliderContainer.style.borderRadius = '4px'
|
||||
sliderContainer.style.display = 'none'
|
||||
sliderContainer.style.width = '150px'
|
||||
sliderContainer.style.zIndex = '1000'
|
||||
|
||||
const slider = document.createElement('input')
|
||||
slider.type = 'range'
|
||||
slider.min = '10'
|
||||
slider.max = '150'
|
||||
slider.value = '75'
|
||||
slider.style.width = '100%'
|
||||
slider.style.height = '10px'
|
||||
|
||||
slider.addEventListener('input', (event) => {
|
||||
const value = parseInt((event.target as HTMLInputElement).value)
|
||||
this.setFOV(value)
|
||||
this.storeNodeProperty('FOV', value)
|
||||
})
|
||||
|
||||
let isHovered = false
|
||||
|
||||
const showSlider = () => {
|
||||
if (this.activeCamera === this.perspectiveCamera) {
|
||||
sliderContainer.style.display = 'block'
|
||||
iconContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'
|
||||
isHovered = true
|
||||
}
|
||||
}
|
||||
|
||||
const hideSlider = () => {
|
||||
isHovered = false
|
||||
setTimeout(() => {
|
||||
if (!isHovered) {
|
||||
sliderContainer.style.display = 'none'
|
||||
iconContainer.style.backgroundColor = 'transparent'
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
iconContainer.addEventListener('mouseenter', showSlider)
|
||||
iconContainer.addEventListener('mouseleave', hideSlider)
|
||||
sliderContainer.addEventListener('mouseenter', () => {
|
||||
isHovered = true
|
||||
})
|
||||
sliderContainer.addEventListener('mouseleave', hideSlider)
|
||||
|
||||
sliderContainer.appendChild(slider)
|
||||
wrapper.appendChild(iconContainer)
|
||||
wrapper.appendChild(sliderContainer)
|
||||
this.fovSliderContainer.appendChild(wrapper)
|
||||
container.appendChild(this.fovSliderContainer)
|
||||
|
||||
this.updateFOVSliderVisibility()
|
||||
}
|
||||
|
||||
updateFOVSliderVisibility() {
|
||||
if (this.activeCamera === this.perspectiveCamera) {
|
||||
this.fovSliderContainer.style.display = 'block'
|
||||
} else {
|
||||
this.fovSliderContainer.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
setTargetSize(width: number, height: number) {
|
||||
this.targetWidth = width
|
||||
this.targetHeight = height
|
||||
this.updatePreviewSize()
|
||||
if (this.previewRenderer && this.previewCamera) {
|
||||
if (this.previewCamera instanceof THREE.PerspectiveCamera) {
|
||||
this.previewCamera.aspect = width / height
|
||||
this.previewCamera.updateProjectionMatrix()
|
||||
} else if (this.previewCamera instanceof THREE.OrthographicCamera) {
|
||||
const frustumSize = 10
|
||||
const aspect = width / height
|
||||
this.previewCamera.left = (-frustumSize * aspect) / 2
|
||||
this.previewCamera.right = (frustumSize * aspect) / 2
|
||||
this.previewCamera.updateProjectionMatrix()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setFOV(fov: number) {
|
||||
if (this.activeCamera === this.perspectiveCamera) {
|
||||
this.perspectiveCamera.fov = fov
|
||||
this.perspectiveCamera.updateProjectionMatrix()
|
||||
this.renderer.render(this.scene, this.activeCamera)
|
||||
}
|
||||
|
||||
if (
|
||||
this.previewRenderer &&
|
||||
this.previewCamera instanceof THREE.PerspectiveCamera
|
||||
) {
|
||||
this.previewCamera.fov = fov
|
||||
this.previewCamera.updateProjectionMatrix()
|
||||
this.previewRenderer.render(this.scene, this.previewCamera)
|
||||
}
|
||||
}
|
||||
|
||||
createLightIntensitySlider(container: Element | HTMLElement) {
|
||||
this.lightSliderContainer = document.createElement('div')
|
||||
this.lightSliderContainer.style.position = 'absolute'
|
||||
this.lightSliderContainer.style.top = '103px'
|
||||
this.lightSliderContainer.style.left = '3px'
|
||||
this.lightSliderContainer.style.display = 'flex'
|
||||
this.lightSliderContainer.style.alignItems = 'center'
|
||||
this.lightSliderContainer.title = 'Light Intensity'
|
||||
|
||||
const wrapper = document.createElement('div')
|
||||
wrapper.style.position = 'relative'
|
||||
wrapper.style.display = 'flex'
|
||||
wrapper.style.alignItems = 'center'
|
||||
|
||||
const iconContainer = document.createElement('div')
|
||||
iconContainer.style.width = '20px'
|
||||
iconContainer.style.height = '20px'
|
||||
iconContainer.style.cursor = 'pointer'
|
||||
iconContainer.style.display = 'flex'
|
||||
iconContainer.style.alignItems = 'center'
|
||||
iconContainer.style.justifyContent = 'center'
|
||||
iconContainer.style.borderRadius = '2px'
|
||||
|
||||
const lightIcon = document.createElement('div')
|
||||
lightIcon.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="5"/>
|
||||
<line x1="12" y1="1" x2="12" y2="3"/>
|
||||
<line x1="12" y1="21" x2="12" y2="23"/>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
||||
<line x1="1" y1="12" x2="3" y2="12"/>
|
||||
<line x1="21" y1="12" x2="23" y2="12"/>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
||||
</svg>
|
||||
`
|
||||
lightIcon.style.display = 'flex'
|
||||
lightIcon.style.alignItems = 'center'
|
||||
lightIcon.style.justifyContent = 'center'
|
||||
iconContainer.appendChild(lightIcon)
|
||||
|
||||
const sliderContainer = document.createElement('div')
|
||||
sliderContainer.style.position = 'absolute'
|
||||
sliderContainer.style.left = '25px'
|
||||
sliderContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'
|
||||
sliderContainer.style.padding = '5px'
|
||||
sliderContainer.style.borderRadius = '4px'
|
||||
sliderContainer.style.display = 'none'
|
||||
sliderContainer.style.width = '150px'
|
||||
sliderContainer.style.zIndex = '1000'
|
||||
|
||||
const slider = document.createElement('input')
|
||||
slider.type = 'range'
|
||||
slider.min = '1'
|
||||
slider.max = '20'
|
||||
slider.step = '1'
|
||||
slider.value = '5'
|
||||
slider.style.width = '100%'
|
||||
slider.style.height = '10px'
|
||||
|
||||
slider.addEventListener('input', (event) => {
|
||||
const value = parseFloat((event.target as HTMLInputElement).value)
|
||||
this.setLightIntensity(value)
|
||||
this.storeNodeProperty('Light Intensity', value)
|
||||
})
|
||||
|
||||
let isHovered = false
|
||||
|
||||
const showSlider = () => {
|
||||
sliderContainer.style.display = 'block'
|
||||
iconContainer.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'
|
||||
isHovered = true
|
||||
}
|
||||
|
||||
const hideSlider = () => {
|
||||
isHovered = false
|
||||
setTimeout(() => {
|
||||
if (!isHovered) {
|
||||
sliderContainer.style.display = 'none'
|
||||
iconContainer.style.backgroundColor = 'transparent'
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
iconContainer.addEventListener('mouseenter', showSlider)
|
||||
iconContainer.addEventListener('mouseleave', hideSlider)
|
||||
sliderContainer.addEventListener('mouseenter', () => {
|
||||
isHovered = true
|
||||
})
|
||||
sliderContainer.addEventListener('mouseleave', hideSlider)
|
||||
|
||||
sliderContainer.appendChild(slider)
|
||||
wrapper.appendChild(iconContainer)
|
||||
wrapper.appendChild(sliderContainer)
|
||||
this.lightSliderContainer.appendChild(wrapper)
|
||||
container.appendChild(this.lightSliderContainer)
|
||||
|
||||
const savedIntensity = this.loadNodeProperty('Light Intensity', 5)
|
||||
slider.value = savedIntensity.toString()
|
||||
this.setLightIntensity(savedIntensity)
|
||||
this.updateLightIntensitySliderVisibility()
|
||||
}
|
||||
|
||||
updateLightIntensitySliderVisibility() {
|
||||
if (this.materialMode === 'original') {
|
||||
this.lightSliderContainer.style.display = 'block'
|
||||
} else {
|
||||
this.lightSliderContainer.style.display = 'none'
|
||||
}
|
||||
}
|
||||
|
||||
getCameraState() {
|
||||
@@ -417,6 +833,8 @@ class Load3d {
|
||||
setMaterialMode(mode: 'original' | 'normal' | 'wireframe' | 'depth') {
|
||||
this.materialMode = mode
|
||||
|
||||
this.updateLightIntensitySliderVisibility()
|
||||
|
||||
if (this.currentModel) {
|
||||
if (mode === 'depth') {
|
||||
this.renderer.outputColorSpace = THREE.LinearSRGBColorSpace
|
||||
@@ -567,6 +985,11 @@ class Load3d {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.previewCamera) {
|
||||
this.previewCamera = null
|
||||
}
|
||||
this.previewCamera = this.activeCamera.clone()
|
||||
|
||||
this.activeCamera.position.copy(position)
|
||||
this.activeCamera.rotation.copy(rotation)
|
||||
|
||||
@@ -585,8 +1008,11 @@ class Load3d {
|
||||
)
|
||||
this.viewHelper.center = this.controls.target
|
||||
|
||||
this.updateFOVSliderVisibility()
|
||||
|
||||
this.storeNodeProperty('Camera Type', this.getCurrentCameraType())
|
||||
this.handleResize()
|
||||
this.updatePreviewRender()
|
||||
}
|
||||
|
||||
getCurrentCameraType(): 'perspective' | 'orthographic' {
|
||||
@@ -624,6 +1050,11 @@ class Load3d {
|
||||
startAnimation() {
|
||||
const animate = () => {
|
||||
this.animationFrameId = requestAnimationFrame(animate)
|
||||
|
||||
if (this.isPreviewVisible) {
|
||||
this.updatePreviewRender()
|
||||
}
|
||||
|
||||
const delta = this.clock.getDelta()
|
||||
|
||||
if (this.viewHelper.animating) {
|
||||
@@ -725,6 +1156,7 @@ class Load3d {
|
||||
this.controls.dispose()
|
||||
this.viewHelper.dispose()
|
||||
this.renderer.dispose()
|
||||
this.fovSliderContainer.remove()
|
||||
this.renderer.domElement.remove()
|
||||
this.scene.clear()
|
||||
}
|
||||
@@ -921,6 +1353,7 @@ class Load3d {
|
||||
}
|
||||
|
||||
this.renderer.setSize(width, height)
|
||||
this.setTargetSize(this.targetWidth, this.targetHeight)
|
||||
}
|
||||
|
||||
animate = () => {
|
||||
@@ -936,6 +1369,7 @@ class Load3d {
|
||||
): Promise<{ scene: string; mask: string }> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
this.updatePreviewSize()
|
||||
const originalWidth = this.renderer.domElement.width
|
||||
const originalHeight = this.renderer.domElement.height
|
||||
const originalClearColor = this.renderer.getClearColor(
|
||||
|
||||
@@ -14,8 +14,11 @@ class Load3dAnimation extends Load3d {
|
||||
animationSelect: HTMLSelectElement = {} as HTMLSelectElement
|
||||
speedSelect: HTMLSelectElement = {} as HTMLSelectElement
|
||||
|
||||
constructor(container: Element | HTMLElement) {
|
||||
super(container)
|
||||
constructor(
|
||||
container: Element | HTMLElement,
|
||||
createPreview: boolean = false
|
||||
) {
|
||||
super(container, createPreview)
|
||||
this.createPlayPauseButton(container)
|
||||
this.createAnimationList(container)
|
||||
this.createSpeedSelect(container)
|
||||
@@ -200,12 +203,6 @@ class Load3dAnimation extends Load3d {
|
||||
}
|
||||
}
|
||||
|
||||
if (this.animationClips.length > 0) {
|
||||
this.playPauseContainer.style.display = 'block'
|
||||
} else {
|
||||
this.playPauseContainer.style.display = 'none'
|
||||
}
|
||||
|
||||
if (this.animationClips.length > 0) {
|
||||
this.playPauseContainer.style.display = 'block'
|
||||
this.animationSelect.style.display = 'block'
|
||||
@@ -330,6 +327,11 @@ class Load3dAnimation extends Load3d {
|
||||
startAnimation() {
|
||||
const animate = () => {
|
||||
this.animationFrameId = requestAnimationFrame(animate)
|
||||
|
||||
if (this.isPreviewVisible) {
|
||||
this.updatePreviewRender()
|
||||
}
|
||||
|
||||
const delta = this.clock.getDelta()
|
||||
|
||||
if (this.currentAnimation && this.isAnimationPlaying) {
|
||||
|
||||
Reference in New Issue
Block a user