增加摄像机
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user