增加摄像机

This commit is contained in:
2026-04-23 13:11:36 +08:00
parent a78b290920
commit 974946cee4
12 changed files with 1134 additions and 23 deletions

View File

@@ -33,6 +33,10 @@
namespace {
// 摄像机「输出」参考分辨率:视口框与预览缩放均按此换算,避免随窗口大小改变镜头覆盖的世界范围
constexpr double kCameraRefViewportW = 1600.0;
constexpr double kCameraRefViewportH = 900.0;
constexpr int kSamCropMargin = 32;
constexpr int kMinStrokePointsSam = 4;
constexpr int kMinStrokePointsManual = 8;
@@ -63,6 +67,13 @@ static QImage readImageTolerant(const QString& absPath) {
return reader.read();
}
QRectF cameraWorldViewportRect(const core::Project::Camera& cam) {
const double s = std::max(1e-9, cam.viewScale);
const double halfW = (kCameraRefViewportW * 0.5) / s;
const double halfH = (kCameraRefViewportH * 0.5) / s;
return QRectF(cam.centerWorld.x() - halfW, cam.centerWorld.y() - halfH, 2.0 * halfW, 2.0 * halfH);
}
QRectF clampRectTopLeftToBounds(const QRectF& rect, const QRectF& bounds) {
if (rect.isNull() || bounds.isNull()) {
return rect;
@@ -518,6 +529,26 @@ void EditorCanvas::dropEvent(QDropEvent* e) {
e->acceptProposedAction();
}
void EditorCanvas::setPreviewCameraViewLocked(bool on) {
m_previewCameraViewLocked = on;
}
void EditorCanvas::applyCameraViewport(const QPointF& centerWorld, double viewScale) {
// 与 cameraWorldViewportRect 一致viewScale 表示在 1600×900 参考视口下的像素/世界比;
// 实际画布用 min(宽/1600, 高/900) 将参考视口适配进当前控件,使可见世界宽高恒为 1600/s × 900/s。
const double pixelRatio =
std::min(static_cast<double>(std::max(1, width())) / kCameraRefViewportW,
static_cast<double>(std::max(1, height())) / kCameraRefViewportH);
const double eff = std::max(1e-9, static_cast<double>(viewScale)) * pixelRatio;
m_scale = std::clamp(static_cast<qreal>(eff), 0.05, 50.0);
m_pan = QPointF(width() / 2.0, height() / 2.0) - QPointF(centerWorld.x() * m_scale, centerWorld.y() * m_scale);
update();
}
QPointF EditorCanvas::viewCenterWorld() const {
return viewToWorld(QPointF(width() / 2.0, height() / 2.0));
}
void EditorCanvas::setPresentationPreviewMode(bool on) {
if (m_presentationPreviewMode == on) {
return;
@@ -715,12 +746,73 @@ void EditorCanvas::setTools(const QVector<core::Project::Tool>& tools, const QVe
update();
}
void EditorCanvas::setCameraOverlays(const QVector<core::Project::Camera>& cameras,
const QString& selectedId,
const QSet<QString>& tempHiddenCameraIds) {
m_cameraOverlays = cameras;
m_tempHiddenCameraIds = tempHiddenCameraIds;
m_selectedCameraId = selectedId;
m_selectedCameraIndex = -1;
if (!selectedId.isEmpty()) {
for (int i = 0; i < m_cameraOverlays.size(); ++i) {
if (m_cameraOverlays[i].id == selectedId) {
m_selectedCameraIndex = i;
break;
}
}
}
if (m_selectedCameraIndex < 0) {
m_selectedCameraId.clear();
m_draggingCamera = false;
}
update();
}
void EditorCanvas::selectCameraById(const QString& id) {
if (id.isEmpty()) {
clearCameraSelection();
return;
}
clearEntitySelection();
m_selectedTool = -1;
m_draggingTool = false;
emit selectedToolChanged(false, QString(), QPointF());
clearBlackholeSelection();
for (int i = 0; i < m_cameraOverlays.size(); ++i) {
if (m_cameraOverlays[i].id == id) {
m_selectedCameraIndex = i;
m_selectedCameraId = id;
const auto& c = m_cameraOverlays[i];
emit selectedCameraChanged(true, id, c.centerWorld, c.viewScale);
update();
return;
}
}
clearCameraSelection();
}
void EditorCanvas::clearCameraSelection() {
if (m_selectedCameraId.isEmpty() && m_selectedCameraIndex < 0 && !m_draggingCamera) {
return;
}
m_selectedCameraId.clear();
m_selectedCameraIndex = -1;
m_draggingCamera = false;
emit selectedCameraChanged(false, QString(), QPointF(), 1.0);
update();
}
void EditorCanvas::setTempHiddenIds(const QSet<QString>& entityIds, const QSet<QString>& toolIds) {
m_tempHiddenEntityIds = entityIds;
m_tempHiddenToolIds = toolIds;
update();
}
void EditorCanvas::setTempHiddenCameraIds(const QSet<QString>& cameraIds) {
m_tempHiddenCameraIds = cameraIds;
update();
}
void EditorCanvas::setCurrentFrame(int frame) {
if (m_currentFrame == frame) {
return;
@@ -879,8 +971,10 @@ void EditorCanvas::clearEntitySelection() {
void EditorCanvas::selectEntityById(const QString& id) {
if (id.isEmpty()) {
clearEntitySelection();
clearCameraSelection();
return;
}
clearCameraSelection();
for (int i = 0; i < m_entities.size(); ++i) {
if (m_entities[i].id != id) {
continue;
@@ -1608,6 +1702,30 @@ void EditorCanvas::paintEvent(QPaintEvent* e) {
}
}
// 摄像机视口框(编辑模式)
if (!m_presentationPreviewMode) {
const qreal handleRWorld = 10.0 / std::max(m_scale, 0.001);
for (int i = 0; i < m_cameraOverlays.size(); ++i) {
const auto& cam = m_cameraOverlays[i];
if (!cam.visible || cam.id.isEmpty() || m_tempHiddenCameraIds.contains(cam.id)) {
continue;
}
const QRectF camRect = cameraWorldViewportRect(cam);
QColor fill(80, 140, 255, 38);
QColor border(80, 140, 255, 170);
if (i == m_selectedCameraIndex) {
border = QColor(255, 170, 60, 230);
fill = QColor(255, 170, 60, 48);
}
p.setBrush(fill);
p.setPen(QPen(border, 1.5 / std::max(m_scale, 0.001)));
p.drawRect(camRect);
p.setBrush(QColor(255, 210, 120, 230));
p.setPen(QPen(QColor(30, 30, 30, 160), 1.0 / std::max(m_scale, 0.001)));
p.drawEllipse(cam.centerWorld, handleRWorld, handleRWorld);
}
}
// 创建实体手绘轨迹预览world 坐标)
if (!m_presentationPreviewMode && m_tool == Tool::CreateEntity && m_drawingEntity && m_strokeWorld.size() >= 2) {
QPen pen(QColor(255, 120, 0, 220), 2.0 / std::max<qreal>(m_scale, 0.001));
@@ -1840,11 +1958,13 @@ void EditorCanvas::mousePressEvent(QMouseEvent* e) {
emit presentationEntityIntroRequested(ent.id, anchorView);
return;
}
m_dragging = true;
m_presBgPanSession = true;
m_presBgDragDist = 0.0;
m_lastMouseView = e->position();
setCursor(Qt::ClosedHandCursor);
if (!m_previewCameraViewLocked) {
m_dragging = true;
m_presBgPanSession = true;
m_presBgDragDist = 0.0;
m_lastMouseView = e->position();
setCursor(Qt::ClosedHandCursor);
}
return;
}
if (e->button() == Qt::MiddleButton) {
@@ -1905,6 +2025,42 @@ void EditorCanvas::mousePressEvent(QMouseEvent* e) {
}
if (m_tool == Tool::Move && e->button() == Qt::LeftButton) {
// 摄像机:绘制在工具之上,命中优先于工具
if (!m_presentationPreviewMode) {
const qreal handleRWorld = 12.0 / std::max(m_scale, 0.001);
for (int idx = static_cast<int>(m_cameraOverlays.size()) - 1; idx >= 0; --idx) {
const auto& cam = m_cameraOverlays[idx];
if (!cam.visible || cam.id.isEmpty() || m_tempHiddenCameraIds.contains(cam.id)) {
continue;
}
const QRectF camRect = cameraWorldViewportRect(cam);
const double dist = QLineF(worldPos, cam.centerWorld).length();
const bool inHandle = dist <= handleRWorld;
const bool inRect = camRect.contains(worldPos);
if (!inHandle && !inRect) {
continue;
}
m_selectedCameraIndex = idx;
m_selectedCameraId = cam.id;
m_selectedEntity = -1;
m_selectedTool = -1;
m_draggingTool = false;
m_draggingEntity = false;
emit selectedEntityChanged(false, QString(), 0, QPointF());
emit selectedToolChanged(false, QString(), QPointF());
if (inHandle) {
m_draggingCamera = true;
m_cameraDragOffsetWorld = worldPos - cam.centerWorld;
m_cameraDragStartCenterWorld = cam.centerWorld;
} else {
m_draggingCamera = false;
}
emit selectedCameraChanged(true, cam.id, cam.centerWorld, cam.viewScale);
update();
return;
}
}
// 工具(气泡)优先命中:绘制在实体之后,交互也应优先
for (qsizetype i = m_tools.size(); i > 0; --i) {
const qsizetype idx = i - 1;
@@ -1914,6 +2070,7 @@ void EditorCanvas::mousePressEvent(QMouseEvent* e) {
if (tv.tool.type != core::Project::Tool::Type::Bubble) continue;
const QPainterPath path = bubblePathWorld(tv.tool);
if (path.contains(worldPos)) {
clearCameraSelection();
m_selectedTool = static_cast<int>(idx);
m_selectedEntity = -1;
m_draggingTool = true;
@@ -1940,6 +2097,7 @@ void EditorCanvas::mousePressEvent(QMouseEvent* e) {
const QPointF originView = worldToView(originWorld);
const GizmoHit gh = hitTestGizmo(e->position(), originView);
if (gh.mode == DragMode::AxisX || gh.mode == DragMode::AxisY) {
clearCameraSelection();
m_dragging = true;
m_draggingEntity = true;
m_dragMode = gh.mode;
@@ -1983,6 +2141,7 @@ void EditorCanvas::mousePressEvent(QMouseEvent* e) {
hitSelected = ent.rect.contains(worldPos);
}
if (hitSelected) {
clearCameraSelection();
m_draggingEntity = true;
m_dragMode = DragMode::Free;
emit entityDragActiveChanged(true);
@@ -2011,6 +2170,7 @@ void EditorCanvas::mousePressEvent(QMouseEvent* e) {
const int hit = hitTestEntity(worldPos);
if (hit >= 0) {
clearCameraSelection();
m_selectedEntity = hit;
m_selectedTool = -1;
m_draggingTool = false;
@@ -2047,6 +2207,7 @@ void EditorCanvas::mousePressEvent(QMouseEvent* e) {
m_selectedTool = -1;
m_draggingTool = false;
m_dragMode = DragMode::None;
clearCameraSelection();
emit selectedEntityChanged(false, QString(), 0, QPointF());
emit selectedToolChanged(false, QString(), QPointF());
update();
@@ -2144,6 +2305,17 @@ void EditorCanvas::mouseMoveEvent(QMouseEvent* e) {
return;
}
if (m_draggingCamera && m_selectedCameraIndex >= 0 && m_selectedCameraIndex < m_cameraOverlays.size()) {
const QPointF worldPos = viewToWorld(cur);
const QPointF newCenter = worldPos - m_cameraDragOffsetWorld;
QPointF delta = newCenter - m_cameraOverlays[m_selectedCameraIndex].centerWorld;
m_cameraOverlays[m_selectedCameraIndex].centerWorld += delta;
const auto& c = m_cameraOverlays[m_selectedCameraIndex];
emit selectedCameraChanged(true, c.id, c.centerWorld, c.viewScale);
update();
return;
}
if (m_draggingEntity && m_selectedEntity >= 0 && m_selectedEntity < m_entities.size()) {
const QPointF worldPos = viewToWorld(cur);
auto& ent = m_entities[m_selectedEntity];
@@ -2229,10 +2401,12 @@ void EditorCanvas::mouseMoveEvent(QMouseEvent* e) {
// 平移画布
if (m_tool == Tool::Move || (e->buttons() & Qt::MiddleButton) ||
(m_presentationPreviewMode && (e->buttons() & Qt::LeftButton))) {
if (m_presentationPreviewMode && m_presBgPanSession) {
m_presBgDragDist += std::abs(deltaView.x()) + std::abs(deltaView.y());
if (!(m_presentationPreviewMode && m_previewCameraViewLocked)) {
if (m_presentationPreviewMode && m_presBgPanSession) {
m_presBgDragDist += std::abs(deltaView.x()) + std::abs(deltaView.y());
}
m_pan += deltaView;
}
m_pan += deltaView;
update();
return;
}
@@ -2321,6 +2495,17 @@ void EditorCanvas::mouseReleaseEvent(QMouseEvent* e) {
}
}
if (m_draggingCamera && m_selectedCameraIndex >= 0 && m_selectedCameraIndex < m_cameraOverlays.size() &&
e->button() == Qt::LeftButton) {
const auto& cam = m_cameraOverlays[m_selectedCameraIndex];
const QPointF delta = cam.centerWorld - m_cameraDragStartCenterWorld;
if (!cam.id.isEmpty() && (!qFuzzyIsNull(delta.x()) || !qFuzzyIsNull(delta.y()))) {
emit requestMoveCamera(cam.id, delta);
} else if (!cam.id.isEmpty()) {
emit selectedCameraChanged(true, cam.id, cam.centerWorld, cam.viewScale);
}
}
m_dragging = false;
if (m_pendingDragging && e->button() == Qt::LeftButton) {
m_pendingDragging = false;
@@ -2332,6 +2517,7 @@ void EditorCanvas::mouseReleaseEvent(QMouseEvent* e) {
}
m_draggingEntity = false;
m_draggingTool = false;
m_draggingCamera = false;
m_dragPreviewActive = false;
m_dragMode = DragMode::None;
updateCursor();
@@ -2340,6 +2526,19 @@ void EditorCanvas::mouseReleaseEvent(QMouseEvent* e) {
}
void EditorCanvas::wheelEvent(QWheelEvent* e) {
if (m_presentationPreviewMode && m_previewCameraViewLocked) {
e->accept();
return;
}
if (!m_presentationPreviewMode && !m_selectedCameraId.isEmpty() && m_tool == Tool::Move) {
const qreal steps = e->angleDelta().y() / 120.0;
const qreal factor = std::pow(1.15, steps);
if (!qFuzzyCompare(factor, 1.0)) {
emit requestCameraViewScaleAdjust(m_selectedCameraId, factor);
}
e->accept();
return;
}
if (m_tool != Tool::Zoom && !(e->modifiers() & Qt::ControlModifier)) {
// 默认仍允许滚轮缩放:不强制用户切换工具
//(若你希望仅在 Zoom 工具下才缩放,可在此 return

View File

@@ -71,6 +71,13 @@ public:
// 预览呈现:完整背景 + 全部实体(忽略显隐开关),隐藏编辑辅助元素,仅可平移/缩放查看
void setPresentationPreviewMode(bool on);
bool presentationPreviewMode() const { return m_presentationPreviewMode; }
/// 预览展示由活动摄像机驱动时禁止画布平移/滚轮缩放,避免与镜头关键帧冲突
void setPreviewCameraViewLocked(bool on);
bool previewCameraViewLocked() const { return m_previewCameraViewLocked; }
/// 将视口对齐到摄像机viewScale 为相对参考分辨率 1600×900 的像素/世界比(见 Project::Camera
void applyCameraViewport(const QPointF& centerWorld, double viewScale);
QPointF viewCenterWorld() const;
double viewWorldScale() const { return m_scale; }
/// 退出「点击实体放大」状态并平滑回到进入前的视图(预览模式)
void clearPresentationEntityFocus();
@@ -78,7 +85,13 @@ public:
const QVector<double>& opacities01,
const QString& projectDirAbs);
void setTools(const QVector<core::Project::Tool>& tools, const QVector<double>& opacities01);
void setCameraOverlays(const QVector<core::Project::Camera>& cameras,
const QString& selectedId,
const QSet<QString>& tempHiddenCameraIds);
void selectCameraById(const QString& id);
void clearCameraSelection();
void setTempHiddenIds(const QSet<QString>& entityIds, const QSet<QString>& toolIds);
void setTempHiddenCameraIds(const QSet<QString>& cameraIds);
void setCurrentFrame(int frame);
int currentFrame() const { return m_currentFrame; }
@@ -122,6 +135,9 @@ signals:
void requestFinalizePendingEntity(const QVector<QPointF>& polyWorld);
void requestMoveEntity(const QString& id, const QPointF& delta);
void requestMoveTool(const QString& id, const QPointF& delta);
void requestMoveCamera(const QString& id, const QPointF& delta);
void requestCameraViewScaleAdjust(const QString& id, double factor);
void selectedCameraChanged(bool hasSelection, const QString& id, const QPointF& centerWorld, double viewScale);
void requestResolveBlackholeCopy(const QString& entityId, const QPoint& sourceOffsetPx);
void entityDragActiveChanged(bool on);
void selectedEntityPreviewChanged(const QString& id, int depth, const QPointF& originWorld);
@@ -255,6 +271,14 @@ private:
QVector<Entity> m_entities;
QVector<ToolView> m_tools;
QVector<core::Project::Camera> m_cameraOverlays;
QSet<QString> m_tempHiddenCameraIds;
QString m_selectedCameraId;
int m_selectedCameraIndex = -1;
bool m_draggingCamera = false;
QPointF m_cameraDragOffsetWorld;
QPointF m_cameraDragStartCenterWorld;
bool m_previewCameraViewLocked = false;
QSet<QString> m_tempHiddenEntityIds;
QSet<QString> m_tempHiddenToolIds;
QVector<QPointF> m_strokeWorld;