1495 lines
63 KiB
JavaScript
1495 lines
63 KiB
JavaScript
import { api } from '../../../scripts/api.js'
|
||
import { app } from '../../../scripts/app.js'
|
||
import { createRelightModal, modalStyles } from './config.js'
|
||
import { SceneUtils } from './scene_utils.js'
|
||
|
||
export class LightEditor {
|
||
constructor() {
|
||
const styleElement = document.createElement('style');
|
||
styleElement.textContent = modalStyles;
|
||
document.head.appendChild(styleElement);
|
||
this.modal = createRelightModal();
|
||
this.canvasContainer = this.modal.querySelector('.relight-canvas-container');
|
||
this.lightIndicator = this.modal.querySelector('.light-source-indicator');
|
||
this.isMovingLight = false;
|
||
this.currentNode = null;
|
||
this.isSceneSetup = false;
|
||
this.lightX = 0;
|
||
this.lightY = 0;
|
||
this.lightZ = 1;
|
||
this.zOffset = 0;
|
||
this.lightSources = [];
|
||
this.activeSourceIndex = -1;
|
||
this.bindEvents();
|
||
}
|
||
|
||
bindEvents() {
|
||
this.onCanvasMouseDownHandler = this.onCanvasMouseDown.bind(this);
|
||
this.onCanvasMouseMoveHandler = this.onCanvasMouseMove.bind(this);
|
||
this.onCanvasMouseUpHandler = this.onCanvasMouseUp.bind(this);
|
||
this.onSliderChangeHandler = this.onSliderChange.bind(this);
|
||
this.onLightTypeChangeHandler = this.onLightTypeChange.bind(this);
|
||
const cancelBtn = this.modal.querySelector('.relight-btn.cancel');
|
||
cancelBtn.addEventListener('click', () => this.cleanupAndClose(true));
|
||
const applyBtn = this.modal.querySelector('.relight-btn.apply');
|
||
applyBtn.addEventListener('click', () => this.applyChanges());
|
||
this.canvasContainer.addEventListener('mousedown', this.onCanvasMouseDownHandler);
|
||
const sliders = this.modal.querySelectorAll('.relight-slider');
|
||
sliders.forEach(slider => {
|
||
slider.addEventListener('input', this.onSliderChangeHandler);
|
||
});
|
||
|
||
// 添加光源类型切换事件监听
|
||
const lightTypeSelect = this.modal.querySelector('#lightType');
|
||
if (lightTypeSelect) {
|
||
lightTypeSelect.addEventListener('change', this.onLightTypeChangeHandler);
|
||
}
|
||
}
|
||
|
||
onLightTypeChange(event) {
|
||
const lightType = event.target.value;
|
||
const spotlightControls = this.modal.querySelectorAll('.spotlight-controls');
|
||
const pointlightControls = this.modal.querySelectorAll('.pointlight-controls');
|
||
|
||
// 显示或隐藏聚光灯控制项
|
||
spotlightControls.forEach(control => {
|
||
control.style.display = lightType === 'spot' ? 'block' : 'none';
|
||
});
|
||
|
||
// 显示或隐藏点光源控制项
|
||
pointlightControls.forEach(control => {
|
||
control.style.display = lightType === 'point' ? 'block' : 'none';
|
||
});
|
||
|
||
// 如果有活动光源,转换其类型
|
||
if (this.activeSourceIndex !== -1) {
|
||
const activeSource = this.lightSources[this.activeSourceIndex];
|
||
if (activeSource) {
|
||
this.convertLightType(activeSource, lightType);
|
||
}
|
||
}
|
||
}
|
||
|
||
convertLightType(source, newType) {
|
||
// 保存原始光源的属性
|
||
const position = source.position;
|
||
const intensity = source.intensity;
|
||
const color = source.light.color.getHex();
|
||
const visible = source.light.visible;
|
||
|
||
// 从场景中移除原始光源
|
||
this.scene.remove(source.light);
|
||
if (source.lightType === 'spot' && source.light.target) {
|
||
this.scene.remove(source.light.target);
|
||
}
|
||
|
||
// 创建新的光源
|
||
let newLight;
|
||
if (newType === 'spot') {
|
||
const spotlightAngleSlider = this.modal.querySelector('#spotlightAngle');
|
||
const spotlightPenumbraSlider = this.modal.querySelector('#spotlightPenumbra');
|
||
const angle = spotlightAngleSlider ? parseFloat(spotlightAngleSlider.value) * Math.PI : Math.PI / 3;
|
||
const penumbra = spotlightPenumbraSlider ? parseFloat(spotlightPenumbraSlider.value) : 0.2;
|
||
|
||
newLight = new THREE.SpotLight(color, intensity, 10, angle, penumbra);
|
||
|
||
// 设置目标点位置
|
||
let targetPosition;
|
||
if (source.targetPosition) {
|
||
targetPosition = source.targetPosition;
|
||
} else {
|
||
// 如果没有现成的目标点,默认设置在光源下方一些位置
|
||
targetPosition = {
|
||
x: position.x,
|
||
y: position.y - 1,
|
||
z: 0
|
||
};
|
||
}
|
||
newLight.target.position.set(targetPosition.x, targetPosition.y, targetPosition.z);
|
||
source.targetPosition = targetPosition;
|
||
|
||
this.scene.add(newLight.target);
|
||
|
||
// 为光源添加聚光灯特有属性
|
||
source.spotParams = {
|
||
angle: angle,
|
||
penumbra: penumbra
|
||
};
|
||
|
||
// 聚光灯指示器样式修改
|
||
if (source.indicator) {
|
||
source.indicator.style.clipPath = 'polygon(50% 0%, 0% 100%, 100% 100%)';
|
||
source.indicator.style.transform = 'translate(-50%, -20%)';
|
||
}
|
||
|
||
// 创建连接线
|
||
if (!source.connectionLine) {
|
||
source.connectionLine = document.createElement('div');
|
||
source.connectionLine.className = 'spotlight-connection-line';
|
||
this.canvasContainer.appendChild(source.connectionLine);
|
||
}
|
||
|
||
// 创建目标点指示器
|
||
if (!source.targetIndicator) {
|
||
source.targetIndicator = this.createTargetIndicator(
|
||
source.lightColor || '#ffffff'
|
||
);
|
||
source.targetIndicator.style.display = 'none'; // 默认隐藏
|
||
this.canvasContainer.appendChild(source.targetIndicator);
|
||
}
|
||
|
||
// 更新连接线位置
|
||
this.updateSpotlightLine(source);
|
||
} else {
|
||
const pointlightRadiusSlider = this.modal.querySelector('#pointlightRadius');
|
||
const radius = pointlightRadiusSlider ? parseFloat(pointlightRadiusSlider.value) : 10;
|
||
|
||
newLight = new THREE.PointLight(color, intensity, radius, 2);
|
||
|
||
// 点光源指示器样式恢复
|
||
if (source.indicator) {
|
||
source.indicator.style.clipPath = '';
|
||
source.indicator.style.borderRadius = '50%';
|
||
source.indicator.style.transform = 'translate(-50%, -50%)';
|
||
}
|
||
|
||
// 隐藏连接线
|
||
if (source.connectionLine) {
|
||
source.connectionLine.style.display = 'none';
|
||
}
|
||
|
||
// 隐藏目标点指示器
|
||
if (source.targetIndicator) {
|
||
source.targetIndicator.style.display = 'none';
|
||
}
|
||
|
||
// 移除聚光灯特有属性
|
||
if (source.spotParams) {
|
||
delete source.spotParams;
|
||
}
|
||
|
||
// 为点光源添加半径参数
|
||
source.pointParams = {
|
||
radius: radius
|
||
};
|
||
}
|
||
|
||
// 设置新光源的位置和可见性
|
||
newLight.position.set(position.x, position.y, position.z);
|
||
newLight.visible = visible;
|
||
|
||
// 更新光源对象
|
||
source.light = newLight;
|
||
source.lightType = newType;
|
||
|
||
// 将新光源添加到场景
|
||
this.scene.add(newLight);
|
||
|
||
// 更新渲染
|
||
this.render();
|
||
}
|
||
|
||
async cleanupAndClose(cancelled = false) {
|
||
if (cancelled && this.currentNode) {
|
||
try {
|
||
await api.fetchApi("/lg_relight_ultra/cancel", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({
|
||
node_id: this.currentNode.id
|
||
})
|
||
});
|
||
console.log('[RelightNode] 已发送取消信号Cancellation signal sent');
|
||
} catch (error) {
|
||
console.error('[RelightNode] 发送取消信号失败Failed to send cancel signal:', error);
|
||
}
|
||
}
|
||
document.removeEventListener('mousemove', this.onCanvasMouseMoveHandler);
|
||
document.removeEventListener('mouseup', this.onCanvasMouseUpHandler);
|
||
this.lightSources.forEach(source => {
|
||
if (source.indicator && source.indicator.parentNode) {
|
||
source.indicator.remove();
|
||
}
|
||
if (source.targetIndicator && source.targetIndicator.parentNode) {
|
||
source.targetIndicator.remove();
|
||
}
|
||
if (source.connectionLine && source.connectionLine.parentNode) {
|
||
source.connectionLine.remove();
|
||
}
|
||
});
|
||
if (this.scene) {
|
||
this.lightSources.forEach(source => {
|
||
this.scene.remove(source.light);
|
||
if (source.lightType === 'spot' && source.light.target) {
|
||
this.scene.remove(source.light.target);
|
||
}
|
||
});
|
||
this.lightSources = [];
|
||
this.activeSourceIndex = -1;
|
||
}
|
||
this.isMovingLight = false;
|
||
this.modal.close();
|
||
}
|
||
|
||
applyChanges() {
|
||
if (this.renderer && this.scene && this.camera && this.currentNode) {
|
||
this.renderer.render(this.scene, this.camera);
|
||
this.uploadCanvasResult(this.renderer.domElement, this.currentNode.id);
|
||
this.saveLightConfiguration(this.currentNode.id);
|
||
}
|
||
this.cleanupAndClose();
|
||
}
|
||
|
||
onCanvasMouseDown(event) {
|
||
if (event.target !== this.canvasContainer &&
|
||
event.target !== this.displayRenderer?.domElement) return;
|
||
|
||
const activeSource = this.activeSourceIndex !== -1 ? this.lightSources[this.activeSourceIndex] : null;
|
||
|
||
// 鼠标右键,且当前有活动的聚光灯
|
||
if (event.button === 2 && activeSource && activeSource.lightType === 'spot') {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
|
||
// 切换到目标点编辑模式
|
||
activeSource.editingTarget = true;
|
||
|
||
// 创建或显示目标点指示器
|
||
if (!activeSource.targetIndicator) {
|
||
activeSource.targetIndicator = this.createTargetIndicator(activeSource.lightColor || '#ffffff');
|
||
this.canvasContainer.appendChild(activeSource.targetIndicator);
|
||
} else {
|
||
activeSource.targetIndicator.style.display = 'block';
|
||
}
|
||
|
||
// 更新连接线
|
||
if (!activeSource.connectionLine) {
|
||
activeSource.connectionLine = document.createElement('div');
|
||
activeSource.connectionLine.className = 'spotlight-connection-line';
|
||
this.canvasContainer.appendChild(activeSource.connectionLine);
|
||
}
|
||
|
||
this.isMovingLight = true;
|
||
document.addEventListener('mousemove', this.onCanvasMouseMoveHandler);
|
||
document.addEventListener('mouseup', this.onCanvasMouseUpHandler);
|
||
this.updateLightFromMouseEvent(event);
|
||
|
||
return;
|
||
}
|
||
|
||
// 如果之前是在编辑目标点,现在切换回编辑光源位置
|
||
if (activeSource && activeSource.editingTarget) {
|
||
activeSource.editingTarget = false;
|
||
}
|
||
|
||
this.isMovingLight = true;
|
||
document.addEventListener('mousemove', this.onCanvasMouseMoveHandler);
|
||
document.addEventListener('mouseup', this.onCanvasMouseUpHandler);
|
||
this.updateLightFromMouseEvent(event);
|
||
event.stopPropagation();
|
||
}
|
||
|
||
onCanvasMouseMove(event) {
|
||
if (!this.isMovingLight) return;
|
||
this.updateLightFromMouseEvent(event);
|
||
event.stopPropagation();
|
||
}
|
||
|
||
onCanvasMouseUp(event) {
|
||
const activeSource = this.activeSourceIndex !== -1 ? this.lightSources[this.activeSourceIndex] : null;
|
||
|
||
// 如果处于目标点编辑模式,鼠标抬起后完成目标点的放置
|
||
if (activeSource && activeSource.editingTarget) {
|
||
activeSource.editingTarget = false;
|
||
|
||
// 隐藏目标点指示器,但保留连接线
|
||
if (activeSource.targetIndicator) {
|
||
activeSource.targetIndicator.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
this.isMovingLight = false;
|
||
document.removeEventListener('mousemove', this.onCanvasMouseMoveHandler);
|
||
document.removeEventListener('mouseup', this.onCanvasMouseUpHandler);
|
||
event.stopPropagation();
|
||
}
|
||
|
||
updateLightFromMouseEvent(event) {
|
||
if (!this.displayRenderer || !this.displayRenderer.domElement || this.activeSourceIndex === -1) return;
|
||
const activeSource = this.lightSources[this.activeSourceIndex];
|
||
if (!activeSource) return;
|
||
|
||
const mouseX = event.clientX;
|
||
const mouseY = event.clientY;
|
||
const rect = this.displayRenderer.domElement.getBoundingClientRect();
|
||
const x = Math.max(0, Math.min(1, (mouseX - rect.left) / rect.width));
|
||
const y = Math.max(0, Math.min(1, (mouseY - rect.top) / rect.height));
|
||
|
||
this.updateLightIndicatorExact(mouseX, mouseY, activeSource.indicator);
|
||
|
||
// 移除X轴计算中的负号
|
||
this.lightX = ((x * 2) - 1);
|
||
this.lightY = ((1 - y) * 2) - 1;
|
||
|
||
// 从深度图获取Z轴高度
|
||
const zValue = SceneUtils.getZValueFromDepthMap(this.depthMapTexture, x, y, this.zOffset);
|
||
|
||
const xValueEl = this.modal.querySelector('.light-x-value');
|
||
const yValueEl = this.modal.querySelector('.light-y-value');
|
||
const zValueEl = this.modal.querySelector('.light-z-value');
|
||
xValueEl.textContent = this.lightX.toFixed(2);
|
||
yValueEl.textContent = this.lightY.toFixed(2);
|
||
zValueEl.textContent = zValue.toFixed(2);
|
||
|
||
// 更新光源位置,包括从深度图获取的Z值
|
||
activeSource.position = { x: this.lightX, y: this.lightY, z: zValue };
|
||
activeSource.light.position.set(this.lightX, this.lightY, zValue);
|
||
|
||
// 如果当前正在编辑目标点而不是光源位置
|
||
if (activeSource.editingTarget && activeSource.lightType === 'spot') {
|
||
// 更新目标点位置
|
||
activeSource.targetPosition = { x: this.lightX, y: this.lightY, z: 0 };
|
||
activeSource.light.target.position.set(this.lightX, this.lightY, 0);
|
||
|
||
// 更新目标点指示器的位置
|
||
if (activeSource.targetIndicator) {
|
||
this.updateLightIndicatorExact(mouseX, mouseY, activeSource.targetIndicator);
|
||
}
|
||
|
||
// 更新连接线
|
||
this.updateSpotlightLine(activeSource);
|
||
} else if (activeSource.lightType === 'spot') {
|
||
// 如果没有设置过目标点,默认指向下方
|
||
if (!activeSource.targetPosition) {
|
||
activeSource.targetPosition = {
|
||
x: this.lightX,
|
||
y: this.lightY - 1,
|
||
z: 0
|
||
};
|
||
activeSource.light.target.position.set(
|
||
activeSource.targetPosition.x,
|
||
activeSource.targetPosition.y,
|
||
activeSource.targetPosition.z
|
||
);
|
||
}
|
||
|
||
// 更新连接线
|
||
this.updateSpotlightLine(activeSource);
|
||
}
|
||
|
||
this.render();
|
||
}
|
||
|
||
updateLightIndicatorExact(clientX, clientY, indicator) {
|
||
if (!indicator) return;
|
||
const rect = this.canvasContainer.getBoundingClientRect();
|
||
const offsetX = clientX - rect.left;
|
||
const offsetY = clientY - rect.top;
|
||
indicator.style.position = 'absolute';
|
||
indicator.style.left = `${offsetX}px`;
|
||
indicator.style.top = `${offsetY}px`;
|
||
indicator.style.display = 'block';
|
||
}
|
||
|
||
onSliderChange(event) {
|
||
const sliderId = event.target.id;
|
||
const value = parseFloat(event.target.value);
|
||
const activeSource = this.lightSources[this.activeSourceIndex];
|
||
if (!activeSource) return;
|
||
switch (sliderId) {
|
||
case 'zOffset':
|
||
this.zOffset = value;
|
||
// 如果有活动光源,需要更新它的z坐标(需要重新从深度图获取基础z值)
|
||
if (activeSource) {
|
||
// 从深度图获取当前位置的基础Z值
|
||
const rect = this.displayRenderer.domElement.getBoundingClientRect();
|
||
const indicator = activeSource.indicator;
|
||
const indicatorRect = indicator.getBoundingClientRect();
|
||
const x = Math.max(0, Math.min(1, (indicatorRect.left + indicatorRect.width/2 - rect.left) / rect.width));
|
||
const y = Math.max(0, Math.min(1, (indicatorRect.top + indicatorRect.height/2 - rect.top) / rect.height));
|
||
|
||
// 重新计算Z值并更新光源位置
|
||
const zValue = SceneUtils.getZValueFromDepthMap(this.depthMapTexture, x, y, this.zOffset);
|
||
activeSource.position.z = zValue;
|
||
activeSource.light.position.set(
|
||
activeSource.position.x,
|
||
activeSource.position.y,
|
||
zValue
|
||
);
|
||
|
||
// 更新显示的Z值
|
||
const zValueEl = this.modal.querySelector('.light-z-value');
|
||
if (zValueEl) {
|
||
zValueEl.textContent = zValue.toFixed(2);
|
||
}
|
||
}
|
||
break;
|
||
case 'lightIntensity':
|
||
activeSource.light.intensity = value;
|
||
activeSource.intensity = value;
|
||
break;
|
||
case 'ambientLight':
|
||
if (this.ambientLight) {
|
||
this.ambientLight.intensity = value;
|
||
}
|
||
break;
|
||
case 'normalStrength':
|
||
if (this.material) {
|
||
// 将法线强度应用到材质
|
||
this.material.normalScale.set(value, value);
|
||
// 如果有必要,可以存储该值以便保存配置
|
||
this.normalStrength = value;
|
||
}
|
||
break;
|
||
case 'spotlightAngle':
|
||
if (activeSource.lightType === 'spot') {
|
||
activeSource.light.angle = value * Math.PI;
|
||
activeSource.spotParams.angle = value * Math.PI;
|
||
}
|
||
break;
|
||
case 'spotlightPenumbra':
|
||
if (activeSource.lightType === 'spot') {
|
||
activeSource.light.penumbra = value;
|
||
activeSource.spotParams.penumbra = value;
|
||
}
|
||
break;
|
||
case 'pointlightRadius':
|
||
if (activeSource.lightType === 'point') {
|
||
activeSource.light.distance = value;
|
||
if (activeSource.pointParams) {
|
||
activeSource.pointParams.radius = value;
|
||
} else {
|
||
activeSource.pointParams = { radius: value };
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
this.render();
|
||
}
|
||
|
||
async setupScene(texture, depthMap, normalMap, maskTexture = null) {
|
||
try {
|
||
// 存储深度图纹理引用
|
||
this.depthMapTexture = depthMap;
|
||
|
||
if (!texture.image || !texture.image.complete) {
|
||
console.warn('[RelightNode] 纹理图像未完全加载Texture image not fully loaded');
|
||
await new Promise(resolve => setTimeout(resolve, 100));
|
||
}
|
||
const imageWidth = texture.image.width;
|
||
const imageHeight = texture.image.height;
|
||
const imageAspect = imageWidth / imageHeight;
|
||
|
||
const containerRect = this.canvasContainer.getBoundingClientRect();
|
||
const containerWidth = containerRect.width;
|
||
const containerHeight = containerRect.height;
|
||
const containerAspect = containerWidth / containerHeight;
|
||
|
||
let displayWidth, displayHeight;
|
||
if (imageAspect > containerAspect) {
|
||
displayWidth = containerWidth;
|
||
displayHeight = containerWidth / imageAspect;
|
||
} else {
|
||
displayHeight = containerHeight;
|
||
displayWidth = containerHeight * imageAspect;
|
||
}
|
||
|
||
const frustumHeight = 2;
|
||
const frustumWidth = frustumHeight * imageAspect;
|
||
|
||
if (!this.isSceneSetup) {
|
||
console.log('[RelightNode] 首次创建场景Creating a scene for the first time');
|
||
this.scene = new THREE.Scene();
|
||
this.camera = new THREE.OrthographicCamera(
|
||
frustumWidth / -2,
|
||
frustumWidth / 2,
|
||
frustumHeight / 2,
|
||
frustumHeight / -2,
|
||
0.1,
|
||
1000
|
||
);
|
||
this.camera.position.z = 5;
|
||
|
||
this.displayRenderer = new THREE.WebGLRenderer({
|
||
antialias: true,
|
||
alpha: true
|
||
});
|
||
|
||
this.renderer = new THREE.WebGLRenderer({
|
||
antialias: true,
|
||
preserveDrawingBuffer: true,
|
||
alpha: true
|
||
});
|
||
|
||
this.ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
|
||
this.scene.add(this.ambientLight);
|
||
this.isSceneSetup = true;
|
||
} else {
|
||
this.camera.left = frustumWidth / -2;
|
||
this.camera.right = frustumWidth / 2;
|
||
this.camera.top = frustumHeight / 2;
|
||
this.camera.bottom = frustumHeight / -2;
|
||
this.camera.updateProjectionMatrix();
|
||
}
|
||
|
||
// 设置显示渲染器的尺寸和样式
|
||
this.displayRenderer.setSize(displayWidth, displayHeight);
|
||
const displayCanvas = this.displayRenderer.domElement;
|
||
displayCanvas.style.maxWidth = '100%';
|
||
displayCanvas.style.maxHeight = '100%';
|
||
displayCanvas.style.margin = 'auto';
|
||
displayCanvas.style.position = 'absolute';
|
||
displayCanvas.style.left = '50%';
|
||
displayCanvas.style.top = '50%';
|
||
displayCanvas.style.transform = 'translate(-50%, -50%)';
|
||
|
||
if (!this.canvasContainer.contains(displayCanvas)) {
|
||
this.canvasContainer.appendChild(displayCanvas);
|
||
}
|
||
|
||
// 设置输出渲染器为原始图像尺寸
|
||
this.renderer.setSize(imageWidth, imageHeight);
|
||
|
||
const geometry = new THREE.PlaneGeometry(2 * imageAspect, 2, 32, 32);
|
||
|
||
// 获取法线强度值(如果已存在)
|
||
const normalStrengthSlider = this.modal.querySelector('#normalStrength');
|
||
this.normalStrength = normalStrengthSlider ? parseFloat(normalStrengthSlider.value) : 0;
|
||
|
||
let material;
|
||
if (maskTexture) {
|
||
material = SceneUtils.createMaskedMaterial(texture, depthMap, normalMap, maskTexture, this.normalStrength);
|
||
} else {
|
||
material = SceneUtils.createSimpleMaterial(texture, depthMap, normalMap, this.normalStrength);
|
||
}
|
||
if (this.mesh) {
|
||
this.mesh.geometry.dispose();
|
||
this.mesh.geometry = geometry;
|
||
this.mesh.material.dispose();
|
||
this.mesh.material = material;
|
||
} else {
|
||
this.mesh = new THREE.Mesh(geometry, material);
|
||
this.scene.add(this.mesh);
|
||
}
|
||
this.material = material;
|
||
this.renderer.setClearColor(0x000000, 0);
|
||
const canvas = this.renderer.domElement;
|
||
canvas.style.maxWidth = '100%';
|
||
canvas.style.maxHeight = '100%';
|
||
canvas.style.margin = 'auto';
|
||
canvas.style.position = 'absolute';
|
||
canvas.style.left = '50%';
|
||
canvas.style.top = '50%';
|
||
canvas.style.transform = 'translate(-50%, -50%)';
|
||
this.renderer.render(this.scene, this.camera);
|
||
|
||
// 更新渲染方法
|
||
this.render();
|
||
|
||
console.log('[RelightNode] 场景设置完成');
|
||
return true;
|
||
} catch (error) {
|
||
console.error('[RelightNode] 场景设置错误:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
uploadCanvasResult(canvas, nodeId) {
|
||
// 使用输出渲染器的画布而不是显示渲染器的画布
|
||
this.renderer.domElement.toBlob(async (blob) => {
|
||
try {
|
||
console.log('[RelightNode] 正在上传渲染结果...');
|
||
const formData = new FormData();
|
||
formData.append('node_id', nodeId);
|
||
formData.append('result_image', blob, 'result.png');
|
||
const response = await api.fetchApi('/lg_relight/upload_result', {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
const result = await response.json();
|
||
if (result.error) {
|
||
console.error('[RelightNode] 上传失败:', result.error);
|
||
} else {
|
||
console.log('[RelightNode] 上传成功');
|
||
}
|
||
} catch (error) {
|
||
console.error('[RelightNode] 上传失败:', error);
|
||
}
|
||
}, 'image/png', 1.0);
|
||
}
|
||
|
||
createLightSource() {
|
||
const lightIntensitySlider = this.modal.querySelector('#lightIntensity');
|
||
const lightTypeSelect = this.modal.querySelector('#lightType');
|
||
const lightType = lightTypeSelect ? lightTypeSelect.value : 'point';
|
||
|
||
let light;
|
||
let indicator;
|
||
const lightIntensity = parseFloat(lightIntensitySlider.value);
|
||
|
||
// 根据选择的光源类型创建对应的光源
|
||
if (lightType === 'spot') {
|
||
const spotlightAngleSlider = this.modal.querySelector('#spotlightAngle');
|
||
const spotlightPenumbraSlider = this.modal.querySelector('#spotlightPenumbra');
|
||
const angle = spotlightAngleSlider ? parseFloat(spotlightAngleSlider.value) * Math.PI : Math.PI / 3;
|
||
const penumbra = spotlightPenumbraSlider ? parseFloat(spotlightPenumbraSlider.value) : 0.2;
|
||
|
||
light = new THREE.SpotLight(0xffffff, lightIntensity, 10, angle, penumbra);
|
||
light.target.position.set(0, -1, 0); // 默认向下照射
|
||
this.scene.add(light.target);
|
||
|
||
indicator = this.createLightIndicator('#ffffff', 'spot');
|
||
} else {
|
||
const pointlightRadiusSlider = this.modal.querySelector('#pointlightRadius');
|
||
const radius = pointlightRadiusSlider ? parseFloat(pointlightRadiusSlider.value) : 10;
|
||
|
||
light = new THREE.PointLight(0xffffff, lightIntensity, radius, 2);
|
||
indicator = this.createLightIndicator('#ffffff', 'point');
|
||
}
|
||
|
||
const lightSource = {
|
||
id: Date.now(),
|
||
name: `光源 ${this.lightSources.length + 1}`,
|
||
light: light,
|
||
position: { x: 0, y: 0, z: 1.0 }, // 默认值,会在鼠标点击时更新
|
||
intensity: lightIntensity,
|
||
indicatorColor: '#ffffff',
|
||
lightColor: '#ffffff',
|
||
lightType: lightType,
|
||
indicator: indicator,
|
||
editingTarget: false // 是否正在编辑目标点
|
||
};
|
||
|
||
// 如果是聚光灯,添加额外的聚光灯参数
|
||
if (lightType === 'spot') {
|
||
lightSource.spotParams = {
|
||
angle: light.angle,
|
||
penumbra: light.penumbra
|
||
};
|
||
lightSource.targetPosition = { x: 0, y: -1, z: 0 };
|
||
|
||
// 创建连接线元素
|
||
lightSource.connectionLine = document.createElement('div');
|
||
lightSource.connectionLine.className = 'spotlight-connection-line';
|
||
this.canvasContainer.appendChild(lightSource.connectionLine);
|
||
|
||
// 创建目标点指示器
|
||
lightSource.targetIndicator = this.createTargetIndicator('#ffffff');
|
||
lightSource.targetIndicator.style.display = 'none'; // 默认隐藏
|
||
this.canvasContainer.appendChild(lightSource.targetIndicator);
|
||
} else if (lightType === 'point') {
|
||
// 为点光源添加半径参数
|
||
const pointlightRadiusSlider = this.modal.querySelector('#pointlightRadius');
|
||
const radius = pointlightRadiusSlider ? parseFloat(pointlightRadiusSlider.value) : 10;
|
||
lightSource.pointParams = {
|
||
radius: radius
|
||
};
|
||
}
|
||
|
||
// 直接设置光源位置
|
||
lightSource.light.position.set(0, 0, 1.0);
|
||
this.scene.add(lightSource.light);
|
||
this.lightSources.push(lightSource);
|
||
this.setActiveLight(this.lightSources.length - 1);
|
||
|
||
// 更新材质参数 - 固定为0
|
||
if (this.material) {
|
||
this.material.shininess = 0;
|
||
this.material.specular.setRGB(0, 0, 0);
|
||
|
||
// 从滑条获取法线强度值
|
||
const normalStrengthSlider = this.modal.querySelector('#normalStrength');
|
||
const normalStrength = normalStrengthSlider ? parseFloat(normalStrengthSlider.value) : 0;
|
||
this.material.normalScale.set(normalStrength, normalStrength);
|
||
this.normalStrength = normalStrength;
|
||
}
|
||
|
||
// 确保环境光强度与UI滑块一致
|
||
if (this.ambientLight) {
|
||
const ambientLightSlider = this.modal.querySelector('#ambientLight');
|
||
if (ambientLightSlider) {
|
||
this.ambientLight.intensity = parseFloat(ambientLightSlider.value);
|
||
}
|
||
}
|
||
|
||
return lightSource;
|
||
}
|
||
|
||
createLightIndicator(color, type = 'point') {
|
||
const indicator = document.createElement('div');
|
||
indicator.className = 'light-source-indicator';
|
||
indicator.style.backgroundColor = color;
|
||
indicator.style.boxShadow = `0 0 15px ${color}`;
|
||
|
||
// 根据光源类型设置指示器样式
|
||
if (type === 'spot') {
|
||
indicator.style.clipPath = 'polygon(50% 0%, 0% 100%, 100% 100%)';
|
||
indicator.style.transform = 'translate(-50%, -20%)';
|
||
}
|
||
|
||
// 添加选中状态的外圈
|
||
const selectionRing = document.createElement('div');
|
||
selectionRing.className = 'selection-ring';
|
||
selectionRing.style.display = 'none'; // 默认隐藏
|
||
selectionRing.style.borderColor = '#00ff00'; // 使用亮绿色
|
||
indicator.appendChild(selectionRing);
|
||
|
||
this.canvasContainer.appendChild(indicator);
|
||
return indicator;
|
||
}
|
||
|
||
updateLightSourcesList() {
|
||
const listContainer = this.modal.querySelector('.light-sources-list');
|
||
if (!listContainer) return;
|
||
listContainer.innerHTML = '';
|
||
this.lightSources.forEach((source, index) => {
|
||
const item = document.createElement('div');
|
||
item.className = `light-source-item ${index === this.activeSourceIndex ? 'active' : ''}`;
|
||
|
||
// 根据光源的可见状态选择眼睛图标
|
||
const visibilityIcon = source.light.visible ? '👁️' : '👁️🗨️';
|
||
|
||
// 添加光源类型图标
|
||
const typeIcon = source.lightType === 'spot' ? '🔦' : '💡';
|
||
|
||
item.innerHTML = `
|
||
<div class="light-source-header">
|
||
<div class="light-source-color" style="background-color: ${source.indicatorColor}"></div>
|
||
<span class="light-source-name">${typeIcon} ${source.name}</span>
|
||
<div class="light-source-controls">
|
||
<input type="color" class="light-color-picker" value="${source.lightColor || '#ffffff'}" title="选择光源颜色 Select the light source color">
|
||
<button class="light-source-visibility" title="${source.light.visible ? '隐藏' : '显示'}">${visibilityIcon}</button>
|
||
<button class="light-source-delete" title="删除 delete">🗑️</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
// 添加数据属性以识别索引
|
||
item.dataset.lightIndex = index;
|
||
item.addEventListener('click', (e) => {
|
||
// 添加点击时的日志输出
|
||
console.log(`[RelightNode] 点击了光源项 Click on the light source ${index}, 当前活动光源 Currently active light source: ${this.activeSourceIndex}`);
|
||
this.setActiveLight(index);
|
||
});
|
||
const colorPicker = item.querySelector('.light-color-picker');
|
||
colorPicker.addEventListener('input', (e) => {
|
||
e.stopPropagation();
|
||
this.updateLightColor(index, e.target.value);
|
||
});
|
||
const deleteBtn = item.querySelector('.light-source-delete');
|
||
if (deleteBtn) {
|
||
deleteBtn.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
this.deleteLight(index);
|
||
});
|
||
}
|
||
const visibilityBtn = item.querySelector('.light-source-visibility');
|
||
if (visibilityBtn) {
|
||
visibilityBtn.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
this.toggleLightVisibility(index);
|
||
// 更新按钮图标
|
||
visibilityBtn.textContent = source.light.visible ? '👁️' : '👁️🗨️';
|
||
visibilityBtn.title = source.light.visible ? '隐藏Hide' : '显示Show';
|
||
});
|
||
}
|
||
listContainer.appendChild(item);
|
||
});
|
||
}
|
||
|
||
setActiveLight(index) {
|
||
console.log(`[RelightNode] 设置活动光源Set active light source: ${index}, 当前光源数量Current number of light sources: ${this.lightSources.length}`);
|
||
|
||
// 先清除所有光源的选中状态
|
||
this.lightSources.forEach(source => {
|
||
if (source.indicator) {
|
||
const ring = source.indicator.querySelector('.selection-ring');
|
||
if (ring) {
|
||
ring.style.display = 'none';
|
||
}
|
||
}
|
||
});
|
||
|
||
// 设置新的选中光源
|
||
this.activeSourceIndex = index;
|
||
const source = this.lightSources[index];
|
||
if (source && source.indicator) {
|
||
const ring = source.indicator.querySelector('.selection-ring');
|
||
if (ring) {
|
||
console.log(`[RelightNode] 显示光源Display Light Source ${index} 的选择环Selection ring`);
|
||
ring.style.display = 'block';
|
||
} else {
|
||
console.warn(`[RelightNode] 光源 ${index} 没有选择环元素No ring element selected`);
|
||
}
|
||
} else {
|
||
console.warn(`[RelightNode] 光源light source ${index} 或其指示器不存在or its indicator does not exist`);
|
||
}
|
||
|
||
// 更新光源列表UI中的活动项
|
||
const listItems = this.modal.querySelectorAll('.light-source-item');
|
||
listItems.forEach(item => {
|
||
const itemIndex = parseInt(item.dataset.lightIndex);
|
||
if (itemIndex === index) {
|
||
item.classList.add('active');
|
||
} else {
|
||
item.classList.remove('active');
|
||
}
|
||
});
|
||
|
||
// 更新控制面板的值
|
||
if (source) {
|
||
const lightIntensitySlider = this.modal.querySelector('#lightIntensity');
|
||
if (lightIntensitySlider) {
|
||
lightIntensitySlider.value = source.light.intensity;
|
||
}
|
||
const xValueEl = this.modal.querySelector('.light-x-value');
|
||
const yValueEl = this.modal.querySelector('.light-y-value');
|
||
const zValueEl = this.modal.querySelector('.light-z-value');
|
||
if (xValueEl && yValueEl && zValueEl) {
|
||
xValueEl.textContent = source.position.x.toFixed(2);
|
||
yValueEl.textContent = source.position.y.toFixed(2);
|
||
zValueEl.textContent = source.position.z.toFixed(2);
|
||
}
|
||
|
||
// 更新光源类型选择器
|
||
const lightTypeSelect = this.modal.querySelector('#lightType');
|
||
if (lightTypeSelect) {
|
||
lightTypeSelect.value = source.lightType || 'point';
|
||
|
||
// 显示或隐藏聚光灯控制项
|
||
const spotlightControls = this.modal.querySelectorAll('.spotlight-controls');
|
||
spotlightControls.forEach(control => {
|
||
control.style.display = source.lightType === 'spot' ? 'block' : 'none';
|
||
});
|
||
|
||
// 显示或隐藏点光源控制项
|
||
const pointlightControls = this.modal.querySelectorAll('.pointlight-controls');
|
||
pointlightControls.forEach(control => {
|
||
control.style.display = source.lightType === 'point' ? 'block' : 'none';
|
||
});
|
||
|
||
// 更新聚光灯参数
|
||
if (source.lightType === 'spot' && source.spotParams) {
|
||
const spotlightAngleSlider = this.modal.querySelector('#spotlightAngle');
|
||
const spotlightPenumbraSlider = this.modal.querySelector('#spotlightPenumbra');
|
||
|
||
if (spotlightAngleSlider) {
|
||
spotlightAngleSlider.value = source.spotParams.angle / Math.PI;
|
||
}
|
||
|
||
if (spotlightPenumbraSlider) {
|
||
spotlightPenumbraSlider.value = source.spotParams.penumbra;
|
||
}
|
||
}
|
||
|
||
// 更新点光源参数
|
||
if (source.lightType === 'point' && source.pointParams) {
|
||
const pointlightRadiusSlider = this.modal.querySelector('#pointlightRadius');
|
||
|
||
if (pointlightRadiusSlider) {
|
||
pointlightRadiusSlider.value = source.pointParams.radius;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
deleteLight(index) {
|
||
const source = this.lightSources[index];
|
||
if (source) {
|
||
this.scene.remove(source.light);
|
||
if (source.lightType === 'spot' && source.light.target) {
|
||
this.scene.remove(source.light.target);
|
||
}
|
||
if (source.indicator && source.indicator.parentNode) {
|
||
source.indicator.remove();
|
||
}
|
||
if (source.connectionLine && source.connectionLine.parentNode) {
|
||
source.connectionLine.remove();
|
||
}
|
||
if (source.targetIndicator && source.targetIndicator.parentNode) {
|
||
source.targetIndicator.remove();
|
||
}
|
||
this.lightSources.splice(index, 1);
|
||
if (this.activeSourceIndex === index) {
|
||
this.activeSourceIndex = Math.max(-1, this.lightSources.length - 1);
|
||
} else if (this.activeSourceIndex > index) {
|
||
this.activeSourceIndex--;
|
||
}
|
||
if (this.lightSources.length === 0) {
|
||
this.activeSourceIndex = -1;
|
||
if (this.ambientLight) {
|
||
this.ambientLight.intensity = 1.0;
|
||
}
|
||
}
|
||
this.updateLightSourcesList();
|
||
this.render();
|
||
}
|
||
}
|
||
|
||
toggleLightVisibility(index) {
|
||
const source = this.lightSources[index];
|
||
if (source) {
|
||
source.light.visible = !source.light.visible;
|
||
source.indicator.style.opacity = source.light.visible ? '0.7' : '0.2';
|
||
this.render();
|
||
}
|
||
}
|
||
|
||
updateLightIntensity(index, value) {
|
||
const source = this.lightSources[index];
|
||
if (source) {
|
||
source.intensity = value;
|
||
source.light.intensity = value;
|
||
this.render();
|
||
}
|
||
}
|
||
|
||
render() {
|
||
if (this.displayRenderer && this.renderer && this.scene && this.camera) {
|
||
// 更新显示用的画布
|
||
this.displayRenderer.render(this.scene, this.camera);
|
||
// 同时更新用于输出的画布
|
||
this.renderer.render(this.scene, this.camera);
|
||
}
|
||
}
|
||
|
||
async show(nodeId, detail) {
|
||
try {
|
||
this.currentNode = app.graph.getNodeById(nodeId);
|
||
if (!this.currentNode) {
|
||
console.error('[RelightNode] 找不到节点Node not found:', nodeId);
|
||
return;
|
||
}
|
||
console.log('[RelightNode] 开始处理图像Start processing the image...');
|
||
const { bg_image, bg_depth_map, bg_normal_map, has_mask, mask } = detail;
|
||
this.hasMask = has_mask;
|
||
this.modal.showModal();
|
||
|
||
// 清理之前可能存在的光源
|
||
this.lightSources.forEach(source => {
|
||
if (this.scene) {
|
||
this.scene.remove(source.light);
|
||
if (source.lightType === 'spot' && source.light.target) {
|
||
this.scene.remove(source.light.target);
|
||
}
|
||
}
|
||
if (source.indicator && source.indicator.parentNode) {
|
||
source.indicator.remove();
|
||
}
|
||
if (source.targetIndicator && source.targetIndicator.parentNode) {
|
||
source.targetIndicator.remove();
|
||
}
|
||
if (source.connectionLine && source.connectionLine.parentNode) {
|
||
source.connectionLine.remove();
|
||
}
|
||
});
|
||
this.lightSources = [];
|
||
this.activeSourceIndex = -1;
|
||
|
||
const texturePromises = [
|
||
SceneUtils.base64ToTexture(bg_image),
|
||
SceneUtils.base64ToTexture(bg_depth_map),
|
||
SceneUtils.base64ToTexture(bg_normal_map)
|
||
];
|
||
if (has_mask && mask) {
|
||
texturePromises.push(SceneUtils.base64ToTexture(mask));
|
||
console.log('[RelightNode] 检测到遮罩数据,将加载遮罩纹理Mask data is detected and the mask texture will be loaded');
|
||
}
|
||
const loadedTextures = await Promise.all(texturePromises);
|
||
const texture = loadedTextures[0];
|
||
const depthMap = loadedTextures[1];
|
||
const normalMap = loadedTextures[2];
|
||
const maskTexture = has_mask ? loadedTextures[3] : null;
|
||
console.log('[RelightNode] 纹理加载完成,设置场景Texture loading complete, set up the scene...');
|
||
await this.setupScene(texture, depthMap, normalMap, maskTexture);
|
||
|
||
// 移除画布上的所有指示器元素
|
||
const existingIndicators = this.canvasContainer.querySelectorAll('.light-source-indicator, .spotlight-target-indicator, .spotlight-connection-line');
|
||
existingIndicators.forEach(indicator => indicator.remove());
|
||
|
||
const configRestored = await this.restoreLightConfiguration(nodeId);
|
||
if (!configRestored) {
|
||
console.log('[RelightNode] 没有找到已保存的配置,使用空白配置No saved configuration found, using a blank configuration');
|
||
// 没有恢复到配置,保持空白状态
|
||
}
|
||
|
||
if (this.renderer && this.scene && this.camera) {
|
||
this.renderer.render(this.scene, this.camera);
|
||
}
|
||
|
||
// 重新绑定添加光源按钮事件
|
||
const addLightBtn = this.modal.querySelector('.relight-btn.add-light');
|
||
if (addLightBtn) {
|
||
// 移除已有的事件监听器,避免重复添加
|
||
const newAddLightBtn = addLightBtn.cloneNode(true);
|
||
addLightBtn.parentNode.replaceChild(newAddLightBtn, addLightBtn);
|
||
|
||
// 添加新的事件监听器
|
||
newAddLightBtn.addEventListener('click', () => {
|
||
console.log('[RelightNode] 添加新光源Adding a New Light');
|
||
if (this.scene) {
|
||
const newSource = this.createLightSource();
|
||
console.log('[RelightNode] 新光源已创建New light source created,ID:', newSource.id);
|
||
this.updateLightSourcesList();
|
||
} else {
|
||
console.error('[RelightNode] 场景未初始化,无法添加光源The scene is not initialized, so light sources cannot be added.');
|
||
}
|
||
});
|
||
}
|
||
|
||
// 初始化光源列表
|
||
this.updateLightSourcesList();
|
||
|
||
console.log('[RelightNode] 编辑器显示成功,当前光源数量The editor displays success, the current number of light sources:', this.lightSources.length);
|
||
} catch (error) {
|
||
console.error('[RelightNode] 处理图像时出错Error processing image:', error);
|
||
}
|
||
}
|
||
|
||
updateLightColor(index, color) {
|
||
const source = this.lightSources[index];
|
||
if (source) {
|
||
// 更新光源颜色
|
||
source.lightColor = color;
|
||
source.indicatorColor = color; // 同时更新指示器颜色
|
||
const colorObj = new THREE.Color(color);
|
||
source.light.color = colorObj;
|
||
|
||
// 更新光源列表中的颜色
|
||
const listContainer = this.modal.querySelector('.light-sources-list');
|
||
if (listContainer) {
|
||
const lightItem = listContainer.children[index];
|
||
if (lightItem) {
|
||
// 更新色板颜色
|
||
const colorPicker = lightItem.querySelector('.light-color-picker');
|
||
if (colorPicker) {
|
||
colorPicker.value = color;
|
||
}
|
||
// 更新指示器颜色
|
||
const indicatorColor = lightItem.querySelector('.light-source-color');
|
||
if (indicatorColor) {
|
||
indicatorColor.style.backgroundColor = color;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 更新画布中的光源指示器颜色
|
||
if (source.indicator) {
|
||
source.indicator.style.backgroundColor = color;
|
||
source.indicator.style.boxShadow = `0 0 15px ${color}`;
|
||
}
|
||
|
||
this.render();
|
||
}
|
||
}
|
||
|
||
saveLightConfiguration(nodeId) {
|
||
const config = {
|
||
lights: this.lightSources.map(source => ({
|
||
screenX: source.indicator.offsetLeft,
|
||
screenY: source.indicator.offsetTop,
|
||
position: { ...source.position },
|
||
targetPosition: source.targetPosition ? { ...source.targetPosition } : null,
|
||
intensity: source.intensity,
|
||
color: source.lightColor,
|
||
visible: source.light.visible,
|
||
lightType: source.lightType || 'point',
|
||
spotParams: source.spotParams || null,
|
||
pointParams: source.pointParams || null
|
||
})),
|
||
ambientLight: {
|
||
intensity: this.ambientLight.intensity
|
||
},
|
||
material: {
|
||
normalScale: this.normalStrength || 0,
|
||
shininess: this.material.shininess,
|
||
specularStrength: this.material.specular.r
|
||
},
|
||
zOffset: this.zOffset
|
||
};
|
||
if (this.currentNode) {
|
||
this.currentNode.lightConfig = config;
|
||
}
|
||
}
|
||
|
||
async restoreLightConfiguration(nodeId) {
|
||
const node = app.graph.getNodeById(nodeId);
|
||
if (!node || !node.lightConfig) return false;
|
||
const config = node.lightConfig;
|
||
this.lightSources.forEach(source => {
|
||
this.scene.remove(source.light);
|
||
if (source.lightType === 'spot' && source.light.target) {
|
||
this.scene.remove(source.light.target);
|
||
}
|
||
if (source.indicator && source.indicator.parentNode) {
|
||
source.indicator.remove();
|
||
}
|
||
if (source.targetIndicator && source.targetIndicator.parentNode) {
|
||
source.targetIndicator.remove();
|
||
}
|
||
if (source.connectionLine && source.connectionLine.parentNode) {
|
||
source.connectionLine.remove();
|
||
}
|
||
});
|
||
this.lightSources = [];
|
||
|
||
// 设置光源类型选择器为默认值
|
||
const lightTypeSelect = this.modal.querySelector('#lightType');
|
||
if (lightTypeSelect) {
|
||
lightTypeSelect.value = 'point';
|
||
}
|
||
|
||
for (const lightConfig of config.lights) {
|
||
// 临时设置类型选择器的值,这样创建光源时会使用正确的类型
|
||
if (lightConfig.lightType && lightTypeSelect) {
|
||
lightTypeSelect.value = lightConfig.lightType;
|
||
}
|
||
|
||
const source = this.createLightSource();
|
||
source.position = { ...lightConfig.position };
|
||
source.light.position.set(
|
||
lightConfig.position.x,
|
||
lightConfig.position.y,
|
||
lightConfig.position.z
|
||
);
|
||
source.intensity = lightConfig.intensity;
|
||
source.light.intensity = lightConfig.intensity;
|
||
source.light.visible = lightConfig.visible;
|
||
source.lightType = lightConfig.lightType || 'point';
|
||
|
||
// 恢复聚光灯参数
|
||
if (source.lightType === 'spot' && lightConfig.spotParams) {
|
||
source.spotParams = { ...lightConfig.spotParams };
|
||
source.light.angle = lightConfig.spotParams.angle;
|
||
source.light.penumbra = lightConfig.spotParams.penumbra;
|
||
|
||
// 恢复目标点位置
|
||
if (lightConfig.targetPosition) {
|
||
source.targetPosition = { ...lightConfig.targetPosition };
|
||
source.light.target.position.set(
|
||
lightConfig.targetPosition.x,
|
||
lightConfig.targetPosition.y,
|
||
lightConfig.targetPosition.z
|
||
);
|
||
} else {
|
||
// 如果没有保存目标点,使用默认位置
|
||
source.targetPosition = {
|
||
x: lightConfig.position.x,
|
||
y: lightConfig.position.y - 1,
|
||
z: 0
|
||
};
|
||
source.light.target.position.set(
|
||
source.targetPosition.x,
|
||
source.targetPosition.y,
|
||
source.targetPosition.z
|
||
);
|
||
}
|
||
}
|
||
|
||
// 恢复点光源参数
|
||
if (source.lightType === 'point' && lightConfig.pointParams) {
|
||
source.pointParams = { ...lightConfig.pointParams };
|
||
source.light.distance = lightConfig.pointParams.radius;
|
||
}
|
||
|
||
if (lightConfig.color) {
|
||
this.updateLightColor(this.lightSources.length - 1, lightConfig.color);
|
||
}
|
||
|
||
if (this.canvasContainer && source.indicator) {
|
||
source.indicator.style.left = `${lightConfig.screenX}px`;
|
||
source.indicator.style.top = `${lightConfig.screenY}px`;
|
||
source.indicator.style.opacity = lightConfig.visible ? '0.7' : '0.2';
|
||
|
||
// 立即更新聚光灯连接线
|
||
if (source.lightType === 'spot') {
|
||
// 确保DOM元素已完全加载并计算好尺寸
|
||
setTimeout(() => {
|
||
this.updateSpotlightLine(source);
|
||
console.log('[RelightNode] 更新聚光灯连接线Update spotlight connector:', source.name);
|
||
}, 50);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (config.ambientLight && this.ambientLight) {
|
||
this.ambientLight.intensity = config.ambientLight.intensity;
|
||
const ambientLightSlider = this.modal.querySelector('#ambientLight');
|
||
if (ambientLightSlider) {
|
||
ambientLightSlider.value = config.ambientLight.intensity;
|
||
}
|
||
}
|
||
if (config.material && this.material) {
|
||
// 恢复法线强度
|
||
if (config.material.normalScale !== undefined) {
|
||
this.normalStrength = config.material.normalScale;
|
||
this.material.normalScale.set(this.normalStrength, this.normalStrength);
|
||
|
||
const normalStrengthSlider = this.modal.querySelector('#normalStrength');
|
||
if (normalStrengthSlider) {
|
||
normalStrengthSlider.value = this.normalStrength;
|
||
}
|
||
} else {
|
||
// 默认设置为0
|
||
this.material.normalScale.set(0, 0);
|
||
this.normalStrength = 0;
|
||
}
|
||
|
||
// 恢复其他材质参数,但固定为0
|
||
this.material.shininess = 0;
|
||
this.material.specular.setRGB(0, 0, 0);
|
||
}
|
||
if (config.zOffset !== undefined) {
|
||
this.zOffset = config.zOffset;
|
||
const zOffsetSlider = this.modal.querySelector('#zOffset');
|
||
if (zOffsetSlider) {
|
||
zOffsetSlider.value = config.zOffset;
|
||
}
|
||
}
|
||
|
||
// 确保所有聚光灯连接线都更新
|
||
setTimeout(() => {
|
||
this.lightSources.forEach(source => {
|
||
if (source.lightType === 'spot') {
|
||
this.updateSpotlightLine(source);
|
||
}
|
||
});
|
||
}, 100);
|
||
|
||
this.updateLightSourcesList();
|
||
this.render();
|
||
return true;
|
||
}
|
||
|
||
async processWithoutDialog(nodeId, detail) {
|
||
try {
|
||
this.currentNode = app.graph.getNodeById(nodeId);
|
||
if (!this.currentNode) {
|
||
console.error('[RelightNode] 找不到节点Node not found:', nodeId);
|
||
return;
|
||
}
|
||
|
||
console.log('[RelightNode] 开始无弹窗处理图像Start processing images without pop-up window...');
|
||
const { bg_image, bg_depth_map, bg_normal_map, has_mask, mask } = detail;
|
||
this.hasMask = has_mask;
|
||
|
||
// 加载纹理
|
||
const texturePromises = [
|
||
SceneUtils.base64ToTexture(bg_image),
|
||
SceneUtils.base64ToTexture(bg_depth_map),
|
||
SceneUtils.base64ToTexture(bg_normal_map)
|
||
];
|
||
|
||
if (has_mask && mask) {
|
||
texturePromises.push(SceneUtils.base64ToTexture(mask));
|
||
}
|
||
|
||
const loadedTextures = await Promise.all(texturePromises);
|
||
const texture = loadedTextures[0];
|
||
const depthMap = loadedTextures[1];
|
||
const normalMap = loadedTextures[2];
|
||
const maskTexture = has_mask ? loadedTextures[3] : null;
|
||
|
||
// 设置场景但不显示
|
||
if (!this.scene) {
|
||
// 首次创建场景
|
||
this.scene = new THREE.Scene();
|
||
const imageWidth = texture.image.width;
|
||
const imageHeight = texture.image.height;
|
||
const imageAspect = imageWidth / imageHeight;
|
||
|
||
const frustumHeight = 2;
|
||
const frustumWidth = frustumHeight * imageAspect;
|
||
|
||
this.camera = new THREE.OrthographicCamera(
|
||
frustumWidth / -2,
|
||
frustumWidth / 2,
|
||
frustumHeight / 2,
|
||
frustumHeight / -2,
|
||
0.1,
|
||
1000
|
||
);
|
||
this.camera.position.z = 5;
|
||
|
||
this.renderer = new THREE.WebGLRenderer({
|
||
antialias: true,
|
||
preserveDrawingBuffer: true,
|
||
alpha: true
|
||
});
|
||
|
||
this.ambientLight = new THREE.AmbientLight(0xffffff, 0.2);
|
||
this.scene.add(this.ambientLight);
|
||
this.isSceneSetup = true;
|
||
|
||
// 隐藏渲染器设置
|
||
this.renderer.setSize(imageWidth, imageHeight);
|
||
}
|
||
|
||
// 设置临时场景
|
||
await this.setupTemporaryScene(texture, depthMap, normalMap, maskTexture);
|
||
|
||
// 尝试恢复配置,如果没有则使用默认配置
|
||
const configRestored = await this.restoreLightConfiguration(nodeId);
|
||
if (!configRestored) {
|
||
// 没有现有配置,创建默认配置
|
||
this.createDefaultLight();
|
||
}
|
||
|
||
// 渲染场景
|
||
this.renderer.render(this.scene, this.camera);
|
||
|
||
// 上传结果
|
||
this.uploadCanvasResult(this.renderer.domElement, nodeId);
|
||
|
||
// 保存当前配置以供将来使用
|
||
this.saveLightConfiguration(nodeId);
|
||
|
||
console.log('[RelightNode] 无弹窗处理完成No pop-up window processing completed');
|
||
} catch (error) {
|
||
console.error('[RelightNode] 无弹窗处理错误No pop-up window processing error:', error);
|
||
}
|
||
}
|
||
|
||
async setupTemporaryScene(texture, depthMap, normalMap, maskTexture = null) {
|
||
// 类似setupScene但简化版本,仅用于无弹窗处理
|
||
try {
|
||
const imageWidth = texture.image.width;
|
||
const imageHeight = texture.image.height;
|
||
const imageAspect = imageWidth / imageHeight;
|
||
|
||
// 更新相机视锥体以匹配新图像的宽高比
|
||
const frustumHeight = 2;
|
||
const frustumWidth = frustumHeight * imageAspect;
|
||
|
||
this.camera.left = frustumWidth / -2;
|
||
this.camera.right = frustumWidth / 2;
|
||
this.camera.top = frustumHeight / 2;
|
||
this.camera.bottom = frustumHeight / -2;
|
||
this.camera.updateProjectionMatrix();
|
||
|
||
// 设置渲染器尺寸
|
||
this.renderer.setSize(imageWidth, imageHeight);
|
||
|
||
// 创建或更新几何体
|
||
const geometry = new THREE.PlaneGeometry(2 * imageAspect, 2, 32, 32);
|
||
|
||
// 获取法线强度值(如果已存在)
|
||
this.normalStrength = this.normalStrength || 0;
|
||
|
||
// 创建材质
|
||
let material;
|
||
if (maskTexture) {
|
||
material = SceneUtils.createMaskedMaterial(texture, depthMap, normalMap, maskTexture, this.normalStrength);
|
||
} else {
|
||
material = SceneUtils.createSimpleMaterial(texture, depthMap, normalMap, this.normalStrength);
|
||
}
|
||
|
||
// 更新或创建网格
|
||
if (this.mesh) {
|
||
this.mesh.geometry.dispose();
|
||
this.mesh.geometry = geometry;
|
||
this.mesh.material.dispose();
|
||
this.mesh.material = material;
|
||
} else {
|
||
this.mesh = new THREE.Mesh(geometry, material);
|
||
this.scene.add(this.mesh);
|
||
}
|
||
|
||
this.material = material;
|
||
this.renderer.setClearColor(0x000000, 0);
|
||
|
||
return true;
|
||
} catch (error) {
|
||
console.error('[RelightNode] 临时场景设置错误Temporary scene setting error:', error);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
createDefaultLight() {
|
||
this.lightSources.forEach(source => {
|
||
this.scene.remove(source.light);
|
||
if (source.lightType === 'spot' && source.light.target) {
|
||
this.scene.remove(source.light.target);
|
||
}
|
||
});
|
||
this.lightSources = [];
|
||
|
||
const defaultLight = {
|
||
id: Date.now(),
|
||
name: "默认光源",
|
||
// 创建默认点光源
|
||
light: new THREE.PointLight(0xffffff, 1.0, 10, 2),
|
||
position: { x: 0, y: 0, z: 1.0 },
|
||
intensity: 1.0,
|
||
lightColor: '#ffffff',
|
||
visible: true,
|
||
lightType: 'point',
|
||
pointParams: {
|
||
radius: 10
|
||
}
|
||
};
|
||
|
||
// 直接设置点光源位置
|
||
defaultLight.light.position.set(0, 0, 1);
|
||
this.scene.add(defaultLight.light);
|
||
this.lightSources.push(defaultLight);
|
||
|
||
if (this.ambientLight) {
|
||
this.ambientLight.intensity = 0.2;
|
||
}
|
||
|
||
return defaultLight;
|
||
}
|
||
|
||
createTargetIndicator(color) {
|
||
const indicator = document.createElement('div');
|
||
indicator.className = 'spotlight-target-indicator';
|
||
indicator.style.backgroundColor = color;
|
||
return indicator;
|
||
}
|
||
|
||
updateSpotlightLine(source) {
|
||
if (!source.connectionLine || !source.indicator || !source.targetPosition) return;
|
||
|
||
const lightRect = source.indicator.getBoundingClientRect();
|
||
const canvasRect = this.canvasContainer.getBoundingClientRect();
|
||
|
||
// 计算光源中心点
|
||
const lightX = lightRect.left + lightRect.width/2 - canvasRect.left;
|
||
const lightY = lightRect.top + lightRect.height/2 - canvasRect.top;
|
||
|
||
// 如果有目标点指示器,使用它的位置
|
||
let targetX, targetY;
|
||
if (source.targetIndicator && source.targetIndicator.style.display !== 'none') {
|
||
const targetRect = source.targetIndicator.getBoundingClientRect();
|
||
targetX = targetRect.left + targetRect.width/2 - canvasRect.left;
|
||
targetY = targetRect.top + targetRect.height/2 - canvasRect.top;
|
||
} else {
|
||
// 否则使用目标点在3D空间中的位置计算屏幕位置
|
||
// 这需要将3D空间点投影到屏幕空间
|
||
// 简化处理:用已有信息估算
|
||
const displayRect = this.displayRenderer.domElement.getBoundingClientRect();
|
||
const targetPosNormalized = {
|
||
x: (source.targetPosition.x + 1) / 2,
|
||
y: (1 - source.targetPosition.y) / 2
|
||
};
|
||
targetX = displayRect.left + displayRect.width * targetPosNormalized.x - canvasRect.left;
|
||
targetY = displayRect.top + displayRect.height * targetPosNormalized.y - canvasRect.top;
|
||
}
|
||
|
||
// 计算线段长度和角度
|
||
const length = Math.sqrt(Math.pow(targetX - lightX, 2) + Math.pow(targetY - lightY, 2));
|
||
const angle = Math.atan2(targetY - lightY, targetX - lightX) * 180 / Math.PI;
|
||
|
||
// 设置线段样式
|
||
source.connectionLine.style.width = `${length}px`;
|
||
source.connectionLine.style.left = `${lightX}px`;
|
||
source.connectionLine.style.top = `${lightY}px`;
|
||
source.connectionLine.style.transform = `rotate(${angle}deg)`;
|
||
source.connectionLine.style.transformOrigin = 'left center';
|
||
source.connectionLine.style.display = 'block';
|
||
}
|
||
}
|