Merge branch 'main' of https://bdgit.educoder.net/pp3xivymc/Software_Architecture into main
commit
9115b25a2e
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,131 @@
|
||||
# Air模块配置文件
|
||||
# RoboMaster TT (Tello TLW004) 无人机配置
|
||||
|
||||
# 无人机基本配置
|
||||
drone:
|
||||
type: "tello" # 无人机类型
|
||||
model: "TLW004" # 型号
|
||||
name: "RoboMaster TT" # 显示名称
|
||||
|
||||
# 网络连接配置
|
||||
connection:
|
||||
ip: "192.168.10.1" # 无人机IP地址
|
||||
cmd_port: 8889 # 命令端口
|
||||
state_port: 8890 # 状态端口
|
||||
video_port: 11111 # 视频端口
|
||||
timeout: 5 # 连接超时时间(秒)
|
||||
|
||||
# 视频流配置
|
||||
video:
|
||||
# 支持的视频流格式
|
||||
formats:
|
||||
udp: "udp://{ip}:{port}"
|
||||
rtsp: "rtsp://{ip}:554/live"
|
||||
http: "http://{ip}:8080/video"
|
||||
|
||||
# 默认视频流URL
|
||||
default_stream: "udp://192.168.10.1:11111"
|
||||
|
||||
# 视频参数
|
||||
resolution:
|
||||
width: 960
|
||||
height: 720
|
||||
fps: 30
|
||||
|
||||
# 缓冲设置
|
||||
buffer_size: 10
|
||||
timeout: 10
|
||||
|
||||
# 录制设置
|
||||
recording:
|
||||
enabled: false
|
||||
format: "mp4"
|
||||
quality: "high"
|
||||
|
||||
# 图像分析配置
|
||||
analysis:
|
||||
# 检测阈值
|
||||
confidence_threshold: 0.25
|
||||
part_confidence_threshold: 0.3
|
||||
|
||||
# 模型路径(相对于项目根目录)
|
||||
models:
|
||||
ship_detector: "models/best.pt"
|
||||
part_detector: "models/part_detectors/best.pt"
|
||||
classifier: "models/custom/best.pt"
|
||||
|
||||
# 检测类别
|
||||
ship_classes:
|
||||
- "航空母舰"
|
||||
- "驱逐舰"
|
||||
- "护卫舰"
|
||||
- "潜艇"
|
||||
- "商船"
|
||||
- "油轮"
|
||||
|
||||
# 部件类别
|
||||
part_classes:
|
||||
- "舰桥"
|
||||
- "雷达"
|
||||
- "舰炮"
|
||||
- "导弹发射器"
|
||||
- "直升机甲板"
|
||||
- "烟囱"
|
||||
|
||||
# Web界面配置
|
||||
web:
|
||||
host: "0.0.0.0"
|
||||
port: 5000
|
||||
debug: true
|
||||
|
||||
# 静态文件路径
|
||||
static_folder: "web/static"
|
||||
template_folder: "web/templates"
|
||||
|
||||
# 上传设置
|
||||
upload:
|
||||
max_file_size: "10MB"
|
||||
allowed_extensions: [".jpg", ".jpeg", ".png", ".mp4", ".avi"]
|
||||
save_path: "uploads/drone_captures"
|
||||
|
||||
# 安全设置
|
||||
safety:
|
||||
max_height: 100 # 最大飞行高度(米)
|
||||
max_distance: 500 # 最大飞行距离(米)
|
||||
min_battery: 15 # 最低电量百分比
|
||||
return_home_battery: 30 # 自动返航电量
|
||||
|
||||
# 飞行限制区域
|
||||
no_fly_zones: []
|
||||
|
||||
# 日志配置
|
||||
logging:
|
||||
level: "INFO"
|
||||
format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
|
||||
# 日志文件
|
||||
files:
|
||||
main: "logs/air_main.log"
|
||||
drone: "logs/drone.log"
|
||||
video: "logs/video.log"
|
||||
analysis: "logs/analysis.log"
|
||||
|
||||
# 性能配置
|
||||
performance:
|
||||
# GPU使用
|
||||
use_gpu: true
|
||||
gpu_memory_fraction: 0.7
|
||||
|
||||
# 多线程设置
|
||||
max_workers: 4
|
||||
|
||||
# 内存限制
|
||||
max_memory_usage: "2GB"
|
||||
|
||||
# 开发调试配置
|
||||
debug:
|
||||
enabled: false
|
||||
save_frames: false
|
||||
frame_save_path: "debug/frames"
|
||||
log_commands: true
|
||||
mock_drone: false # 是否使用模拟无人机
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,13 @@
|
||||
"""
|
||||
无人机接口子系统(DroneInterface)
|
||||
-------------
|
||||
与无人机建立通信连接
|
||||
接收无人机传回的视频流
|
||||
将视频流转发给图像分析子系统
|
||||
向无人机发送控制命令(如需要)
|
||||
"""
|
||||
|
||||
from .drone_manager import DroneManager
|
||||
from .video_receiver import VideoReceiver
|
||||
|
||||
__all__ = ['DroneManager', 'VideoReceiver']
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,12 @@
|
||||
"""
|
||||
图像分析子系统(ImageAnalyzer)
|
||||
-------------
|
||||
调用模型进行舰船检测、分类和部件识别
|
||||
处理图像预处理和后处理
|
||||
生成分析结果报告
|
||||
提供API接口供Web应用调用
|
||||
"""
|
||||
|
||||
from .analyzer import ImageAnalyzer
|
||||
|
||||
__all__ = ['ImageAnalyzer']
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,138 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}舰船识别系统{% endblock %} - ShipAI</title>
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Font Awesome 图标 -->
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" rel="stylesheet">
|
||||
<!-- 自定义CSS -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{{ url_for('index') }}">ShipAI - 智能舰船识别系统</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('index') }}">首页</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('gallery') }}">样本图库</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('drone_control') }}">无人机控制</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="analysisDropdown" role="button" data-bs-toggle="dropdown">
|
||||
分析工具
|
||||
</a>
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item" href="{{ url_for('image_analysis') }}">图像分析</a>
|
||||
<a class="dropdown-item" href="{{ url_for('analytics') }}">分析报告</a>
|
||||
<a class="dropdown-item" href="{{ url_for('data_storage') }}">数据存储</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="modelDropdown" role="button" data-bs-toggle="dropdown">
|
||||
模型管理
|
||||
</a>
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item" href="{{ url_for('model_settings') }}">模型设置</a>
|
||||
<a class="dropdown-item" href="{{ url_for('annotation_tool') }}">图像标注</a>
|
||||
<a class="dropdown-item" href="{{ url_for('train_model') }}">模型训练</a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.path == '/ship-database' %}active{% endif %}" href="/ship-database">舰船数据库</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="modelsDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
部件检测
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="modelsDropdown">
|
||||
<li><a class="dropdown-item" href="{{ url_for('part_detection') }}">部件库管理</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('annotation_tool', type='part') }}">部件标注工具</a></li>
|
||||
<li><a class="dropdown-item" href="{{ url_for('train_part_model') }}">部件模型训练</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item" id="nav-history">
|
||||
<a class="nav-link" href="{{ url_for('detection_history') }}">检测历史</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ url_for('about') }}">关于我们</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 消息提示 -->
|
||||
<div class="container mt-3">
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
{% for category, message in messages %}
|
||||
<div class="alert alert-{{ category if category != 'message' else 'info' }} alert-dismissible fade show">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<main class="py-4">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="bg-dark text-white py-4 mt-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>ShipAI - 智能舰船识别系统</h5>
|
||||
<p>基于深度学习的海上舰船自动识别与分析平台</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<p>© {{ current_year }} ShipAI 团队</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/modal_fix.js') }}"></script>
|
||||
<script>
|
||||
// 初始化所有模态框
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 使所有具有data-bs-toggle="modal"属性的元素正确工作
|
||||
var modalTriggers = document.querySelectorAll('[data-bs-toggle="modal"]');
|
||||
modalTriggers.forEach(function(trigger) {
|
||||
trigger.addEventListener('click', function() {
|
||||
var targetId = this.getAttribute('data-bs-target');
|
||||
if (targetId) {
|
||||
var modalElement = document.querySelector(targetId);
|
||||
if (modalElement) {
|
||||
var modal = new bootstrap.Modal(modalElement);
|
||||
modal.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% block scripts %}{% endblock %}
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,470 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🛤️ 无人机轨迹模拟测试</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Microsoft YaHei', sans-serif;
|
||||
background: linear-gradient(135deg, #1e3c72, #2a5298);
|
||||
color: white;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 20px;
|
||||
padding: 30px;
|
||||
backdrop-filter: blur(10px);
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 1.1em;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 15px;
|
||||
padding: 25px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.control-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.control-group h3 {
|
||||
color: #00aaff;
|
||||
margin-bottom: 15px;
|
||||
font-size: 1.2em;
|
||||
border-bottom: 2px solid rgba(0, 170, 255, 0.3);
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.button-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn-circle {
|
||||
background: linear-gradient(135deg, #9C27B0, #7B1FA2);
|
||||
}
|
||||
|
||||
.btn-line {
|
||||
background: linear-gradient(135deg, #FF9800, #F57C00);
|
||||
}
|
||||
|
||||
.btn-random {
|
||||
background: linear-gradient(135deg, #607D8B, #455A64);
|
||||
}
|
||||
|
||||
.btn-stop {
|
||||
background: linear-gradient(135deg, #F44336, #D32F2F);
|
||||
}
|
||||
|
||||
.btn-clear {
|
||||
background: linear-gradient(135deg, #FF5722, #D32F2F);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
min-width: 100px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.input-group input,
|
||||
.input-group select {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.input-group input::placeholder {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.status-panel {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.status-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
font-weight: bold;
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 12px 15px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.alert.success {
|
||||
background: rgba(76, 175, 80, 0.2);
|
||||
border-left: 4px solid #4CAF50;
|
||||
}
|
||||
|
||||
.alert.error {
|
||||
background: rgba(244, 67, 54, 0.2);
|
||||
border-left: 4px solid #F44336;
|
||||
}
|
||||
|
||||
.alert.info {
|
||||
background: rgba(33, 150, 243, 0.2);
|
||||
border-left: 4px solid #2196F3;
|
||||
}
|
||||
|
||||
.drone-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.drone-item {
|
||||
padding: 8px 10px;
|
||||
margin-bottom: 5px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.link-section {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.link-section a {
|
||||
color: #00aaff;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
padding: 10px 20px;
|
||||
background: rgba(0, 170, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.link-section a:hover {
|
||||
background: rgba(0, 170, 255, 0.4);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>🛤️ 无人机轨迹模拟测试</h1>
|
||||
<p>测试无人机轨迹记录和可视化功能</p>
|
||||
</div>
|
||||
|
||||
<div id="alertContainer"></div>
|
||||
|
||||
<!-- 快速启动面板 -->
|
||||
<div class="control-panel">
|
||||
<div class="control-group">
|
||||
<h3>🚀 快速启动模拟</h3>
|
||||
<div class="button-grid">
|
||||
<button class="btn btn-circle" onclick="startQuickSimulation('circle')">
|
||||
🔄 圆形轨迹
|
||||
</button>
|
||||
<button class="btn btn-line" onclick="startQuickSimulation('line')">
|
||||
↔️ 直线轨迹
|
||||
</button>
|
||||
<button class="btn btn-random" onclick="startQuickSimulation('random')">
|
||||
🎲 随机轨迹
|
||||
</button>
|
||||
<button class="btn btn-stop" onclick="stopSimulation()">
|
||||
⏹️ 停止模拟
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 高级配置面板 -->
|
||||
<div class="control-panel">
|
||||
<div class="control-group">
|
||||
<h3>⚙️ 高级配置</h3>
|
||||
<div class="input-group">
|
||||
<label>轨迹类型:</label>
|
||||
<select id="simulationType">
|
||||
<option value="circle">圆形轨迹</option>
|
||||
<option value="line">直线往返</option>
|
||||
<option value="random">随机游走</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>无人机数量:</label>
|
||||
<input type="number" id="droneCount" value="2" min="1" max="5" placeholder="1-5架">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>更新间隔:</label>
|
||||
<input type="number" id="updateSpeed" value="3" min="1" max="10" placeholder="秒">
|
||||
</div>
|
||||
<div class="button-grid">
|
||||
<button class="btn btn-circle" onclick="startCustomSimulation()">
|
||||
🛠️ 启动自定义模拟
|
||||
</button>
|
||||
<button class="btn btn-clear" onclick="clearAllTrajectories()">
|
||||
🗑️ 清除所有轨迹
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态显示面板 -->
|
||||
<div class="status-panel">
|
||||
<h3 style="color: #00aaff; margin-bottom: 15px;">📊 模拟状态</h3>
|
||||
<div class="status-item">
|
||||
<span>模拟状态:</span>
|
||||
<span class="status-value" id="simulationStatus">未启动</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span>轨迹类型:</span>
|
||||
<span class="status-value" id="simulationType">无</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span>无人机数量:</span>
|
||||
<span class="status-value" id="droneCountStatus">0</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span>轨迹记录:</span>
|
||||
<span class="status-value" id="trajectoryRecordingStatus">开启</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 无人机列表 -->
|
||||
<div class="control-panel">
|
||||
<div class="control-group">
|
||||
<h3>🚁 模拟无人机列表</h3>
|
||||
<div class="drone-list" id="droneList">
|
||||
<div style="text-align: center; color: #999;">暂无模拟无人机</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 链接到主系统 -->
|
||||
<div class="link-section">
|
||||
<a href="/" target="_blank">🎯 打开主系统查看轨迹</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 显示提示信息
|
||||
function showAlert(message, type = 'info') {
|
||||
const alertContainer = document.getElementById('alertContainer');
|
||||
const alert = document.createElement('div');
|
||||
alert.className = `alert ${type}`;
|
||||
alert.textContent = message;
|
||||
|
||||
alertContainer.appendChild(alert);
|
||||
alert.style.display = 'block';
|
||||
|
||||
setTimeout(() => {
|
||||
alert.style.display = 'none';
|
||||
alertContainer.removeChild(alert);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// 快速启动模拟
|
||||
async function startQuickSimulation(type) {
|
||||
await startSimulation(type, 2, 3);
|
||||
}
|
||||
|
||||
// 自定义启动模拟
|
||||
async function startCustomSimulation() {
|
||||
const type = document.getElementById('simulationType').value;
|
||||
const droneCount = parseInt(document.getElementById('droneCount').value) || 2;
|
||||
const speed = parseInt(document.getElementById('updateSpeed').value) || 3;
|
||||
|
||||
await startSimulation(type, droneCount, speed);
|
||||
}
|
||||
|
||||
// 启动轨迹模拟
|
||||
async function startSimulation(type, droneCount = 2, speed = 3) {
|
||||
try {
|
||||
showAlert(`🚀 正在启动${droneCount}架无人机的${type}轨迹模拟...`, 'info');
|
||||
|
||||
const response = await fetch('/api/test/start_simulation', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
type: type,
|
||||
drone_count: droneCount,
|
||||
speed: speed
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
showAlert(`✅ ${result.message}`, 'success');
|
||||
updateStatus();
|
||||
} else {
|
||||
showAlert(`❌ 启动模拟失败: ${result.message}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showAlert(`❌ 启动模拟失败: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 停止轨迹模拟
|
||||
async function stopSimulation() {
|
||||
try {
|
||||
showAlert('🛑 正在停止轨迹模拟...', 'info');
|
||||
|
||||
const response = await fetch('/api/test/stop_simulation', {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
if (result.status === 'success') {
|
||||
showAlert('✅ 轨迹模拟已停止', 'success');
|
||||
updateStatus();
|
||||
} else {
|
||||
showAlert(`❌ 停止模拟失败: ${result.message}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showAlert(`❌ 停止模拟失败: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 清除所有轨迹
|
||||
async function clearAllTrajectories() {
|
||||
try {
|
||||
// 这个功能需要在主页面中执行
|
||||
showAlert('🗑️ 请在主系统页面中点击"清除"按钮来清除轨迹', 'info');
|
||||
} catch (error) {
|
||||
showAlert(`❌ 清除轨迹失败: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新状态显示
|
||||
async function updateStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/test/simulation_status');
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
// 更新状态显示
|
||||
document.getElementById('simulationStatus').textContent =
|
||||
result.simulation_active ? '运行中' : '未启动';
|
||||
document.getElementById('simulationStatus').style.color =
|
||||
result.simulation_active ? '#4CAF50' : '#F44336';
|
||||
|
||||
document.getElementById('simulationType').textContent =
|
||||
result.simulation_type || '无';
|
||||
document.getElementById('droneCountStatus').textContent =
|
||||
result.simulated_drones.length || '0';
|
||||
|
||||
// 更新无人机列表
|
||||
updateDroneList(result.simulated_drones);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取状态失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新无人机列表
|
||||
function updateDroneList(drones) {
|
||||
const droneList = document.getElementById('droneList');
|
||||
|
||||
if (!drones || drones.length === 0) {
|
||||
droneList.innerHTML = '<div style="text-align: center; color: #999;">暂无模拟无人机</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
const html = drones.map((drone, index) => `
|
||||
<div class="drone-item">
|
||||
🚁 ${drone.name || `无人机-${index + 1}`}
|
||||
<br>
|
||||
<span style="color: #999;">
|
||||
ID: ${drone.device_id.substring(0, 12)}...
|
||||
</span>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
droneList.innerHTML = html;
|
||||
}
|
||||
|
||||
// 页面加载完成后的初始化
|
||||
window.addEventListener('load', function () {
|
||||
updateStatus();
|
||||
|
||||
// 定期更新状态
|
||||
setInterval(updateStatus, 3000);
|
||||
|
||||
showAlert('🎯 轨迹模拟测试页面已加载', 'success');
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,20 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
project(mediamodule LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(Qt5 REQUIRED COMPONENTS Core Widgets)
|
||||
find_package(OpenCV REQUIRED)
|
||||
|
||||
add_library(mediamodule
|
||||
camera_streamer.cpp
|
||||
)
|
||||
|
||||
target_include_directories(mediamodule PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${OpenCV_INCLUDE_DIRS})
|
||||
|
||||
target_link_libraries(mediamodule PUBLIC Qt5::Core Qt5::Widgets ${OpenCV_LIBS})
|
||||
|
||||
add_executable(example_viewer example_viewer.cpp)
|
||||
|
||||
target_link_libraries(example_viewer PRIVATE mediamodule)
|
@ -0,0 +1,39 @@
|
||||
#include <QApplication>
|
||||
#include <QLabel>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHostAddress>
|
||||
#include "camera_streamer.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// 主窗口
|
||||
QWidget window;
|
||||
window.setWindowTitle("Unitree Camera Viewer");
|
||||
auto *layout = new QVBoxLayout(&window);
|
||||
|
||||
QLabel *label = new QLabel(&window);
|
||||
label->setAlignment(Qt::AlignCenter);
|
||||
layout->addWidget(label);
|
||||
|
||||
CameraStreamer streamer;
|
||||
|
||||
QObject::connect(&streamer, &CameraStreamer::newFrame, &window, [label](const QImage &img){
|
||||
label->setPixmap(QPixmap::fromImage(img).scaled(label->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
});
|
||||
|
||||
// 根据实际情况修改远端用户名、IP、端口
|
||||
QString remoteUser = "unitree";
|
||||
QString remoteHost = "192.168.123.10"; // 狗端 IP
|
||||
streamer.startStreaming(remoteUser, remoteHost);
|
||||
|
||||
window.resize(960, 540);
|
||||
window.show();
|
||||
|
||||
int ret = app.exec();
|
||||
|
||||
streamer.stopStreaming();
|
||||
return ret;
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
|
||||
add_executable(example_getRawFrame ./example_getRawFrame.cc)
|
||||
target_link_libraries(example_getRawFrame ${SDKLIBS})
|
||||
|
||||
add_executable(example_getDepthFrame ./example_getDepthFrame.cc)
|
||||
target_link_libraries(example_getDepthFrame ${SDKLIBS})
|
||||
|
||||
add_executable(example_getRectFrame ./example_getRectFrame.cc)
|
||||
target_link_libraries(example_getRectFrame ${SDKLIBS})
|
||||
|
||||
add_executable(example_getCalibParamsFile ./example_getCalibParamsFile.cc)
|
||||
target_link_libraries(example_getCalibParamsFile ${SDKLIBS})
|
||||
|
||||
add_executable(example_putImagetrans ./example_putImagetrans.cc)
|
||||
target_link_libraries(example_putImagetrans ${SDKLIBS})
|
||||
|
||||
add_executable(example_getimagetrans ./example_getimagetrans.cc)
|
||||
target_link_libraries(example_getimagetrans ${SDKLIBS})
|
||||
|
||||
# add_executable(example_share ./example_share.cc)
|
||||
# target_link_libraries(example_share ${SDKLIBS})
|
||||
|
||||
find_package(OpenGL REQUIRED)
|
||||
if(OpenGL_FOUND)
|
||||
include_directories(${OPENGL_INCLUDE_DIR})
|
||||
message(STATUS ${OPENGL_INCLUDE_DIR})
|
||||
message(STATUS ${OPENGL_LIBRARIES})
|
||||
else()
|
||||
message(WARNING "OpenGL Library Not Found")
|
||||
endif()
|
||||
|
||||
find_package(GLUT REQUIRED)
|
||||
if(GLUT_FOUND)
|
||||
include_directories(${GLUT_INCLUDE_DIR})
|
||||
message(STATUS ${GLUT_INCLUDE_DIR})
|
||||
message(STATUS ${GLUT_LIBRARY})
|
||||
else()
|
||||
message(WARNING "GLUT Library Not Found")
|
||||
endif()
|
||||
|
||||
find_package(X11 REQUIRED)
|
||||
if(X11_FOUND)
|
||||
include_directories(${X11_INCLUDE_DIR})
|
||||
message(${X11_INCLUDE_DIR})
|
||||
message(${X11_LIBRARIES})
|
||||
else()
|
||||
message(WARNING "X11 Library Not Found")
|
||||
endif()
|
||||
|
||||
if(X11_FOUND AND OpenGL_FOUND AND GLUT_FOUND)
|
||||
set(ShowPointCloud true)
|
||||
message(STATUS "Point Cloud Example Enabled")
|
||||
else()
|
||||
set(ShowPointCloud false)
|
||||
message(WARNING "Point Cloud Example Disabled")
|
||||
endif()
|
||||
|
||||
if(${ShowPointCloud})
|
||||
add_executable(example_getPointCloud ./example_getPointCloud.cc ./glViewer/glwindow_x11.cpp ./glViewer/scenewindow.cpp)
|
||||
target_link_libraries(example_getPointCloud ${SDKLIBS} ${OPENGL_LIBRARIES} ${GLUT_LIBRARY} ${X11_LIBRARIES} )
|
||||
endif()
|
||||
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -pthread")
|
||||
|
||||
|
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* @file example_getCalibParamsFile.cc
|
||||
* @brief This file is part of UnitreeCameraSDK.
|
||||
* @details This example that how to get camera internal parameters
|
||||
* @author ZhangChunyang
|
||||
* @date 2021.07.31
|
||||
* @version 1.0.1
|
||||
* @copyright Copyright (c) 2020-2021, Hangzhou Yushu Technology Stock CO.LTD. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include <UnitreeCameraSDK.hpp>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
|
||||
UnitreeCamera cam("stereo_camera_config.yaml"); ///< init UnitreeCamera object by config file
|
||||
if(!cam.isOpened()) ///< get camera open state
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
cam.startCapture(); ///< disable image h264 encoding and share memory sharing
|
||||
usleep(100000); ///< wait parameters initialization finished
|
||||
cam.saveCalibParams("output_camCalibParams.yaml"); ///< save parameters to output_camCalibParams.yaml
|
||||
std::cout << cam.getSerialNumber() << " " << cam.getPosNumber() << std::endl;
|
||||
usleep(100000);
|
||||
cam.stopCapture(); ///< stop camera capturing
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @file example_getDepthFrame.cc
|
||||
* @brief This file is part of UnitreeCameraSDK.
|
||||
* @details This example that how to get depth frame
|
||||
* @author SunMingzhe
|
||||
* @date 2021.12.07
|
||||
* @version 1.1.0
|
||||
* @copyright Copyright (c) 2020-2021, Hangzhou Yushu Technology Stock CO.LTD. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include <UnitreeCameraSDK.hpp>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
|
||||
UnitreeCamera cam("stereo_camera_config.yaml"); ///< init UnitreeCamera object by config file
|
||||
if(!cam.isOpened()) ///< get camera open state
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
cam.startCapture(); ///< disable image h264 encoding and share memory sharing
|
||||
cam.startStereoCompute(); ///< start disparity computing
|
||||
|
||||
while(cam.isOpened()){
|
||||
cv::Mat depth;
|
||||
std::chrono::microseconds t;
|
||||
if(!cam.getDepthFrame(depth, true, t)){ ///< get stereo camera depth image
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
if(!depth.empty()){
|
||||
cv::imshow("UnitreeCamera-Depth", depth);
|
||||
}
|
||||
char key = cv::waitKey(10);
|
||||
if(key == 27) // press ESC key
|
||||
break;
|
||||
}
|
||||
|
||||
cam.stopStereoCompute(); ///< stop disparity computing
|
||||
cam.stopCapture(); ///< stop camera capturing
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/**
|
||||
* @file example_getPointCloud.cc
|
||||
* @brief This file is part of UnitreeCameraSDK.
|
||||
* @details This example that how to get camera point cloud.
|
||||
* @author ZhangChunyang
|
||||
* @date 2021.07.31
|
||||
* @version 1.0.1
|
||||
* @copyright Copyright (c) 2020-2021, Hangzhou Yushu Technology Stock CO.LTD. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include <GL/gl.h>
|
||||
#include <signal.h>
|
||||
#include <cerrno>
|
||||
#include <cfenv>
|
||||
#include <unistd.h>
|
||||
#include "glViewer/scenewindow.hpp"
|
||||
#include <UnitreeCameraSDK.hpp>
|
||||
|
||||
#define RGB_PCL true ///< Color Point Cloud Enable Flag
|
||||
|
||||
void DrawScene(const std::vector<PCLType>& pcl_vec) {
|
||||
glBegin(GL_POINTS);
|
||||
for (uint i = 0; i < pcl_vec.size(); ++i) {
|
||||
PCLType pcl = pcl_vec[i];
|
||||
glColor3ub(pcl.clr(2), pcl.clr(1), pcl.clr(0));
|
||||
glVertex3f(-pcl.pts(0), -pcl.pts(1), pcl.pts(2));
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
|
||||
void DrawScene(const std::vector<cv::Vec3f>& pcl_vec) {
|
||||
glBegin(GL_POINTS);
|
||||
for (uint i = 0; i < pcl_vec.size(); ++i) {
|
||||
cv::Vec3f pcl = pcl_vec[i];
|
||||
glColor3ub(255, 255, 0);
|
||||
glVertex3f(-pcl(0), -pcl(1), pcl(2));
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
|
||||
bool killSignalFlag = false;
|
||||
void ctrl_c_handler(int s){
|
||||
killSignalFlag = true;
|
||||
return ;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
|
||||
UnitreeCamera cam("stereo_camera_config.yaml");
|
||||
if(!cam.isOpened())
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
cam.startCapture();
|
||||
cam.startStereoCompute();
|
||||
|
||||
struct sigaction sigIntHandler;
|
||||
sigIntHandler.sa_handler = ctrl_c_handler;
|
||||
sigemptyset(&sigIntHandler.sa_mask);
|
||||
sigIntHandler.sa_flags = 0;
|
||||
sigaction(SIGINT, &sigIntHandler, NULL);
|
||||
|
||||
std::cout << cam.getSerialNumber() << " " << cam.getPosNumber() << std::endl;
|
||||
|
||||
glwindow::SceneWindow scene(960, 720, "Panorama 3D Scene");
|
||||
|
||||
while(cam.isOpened()){
|
||||
|
||||
if(killSignalFlag){
|
||||
break;
|
||||
}
|
||||
|
||||
std::chrono::microseconds t;
|
||||
#if RGB_PCL
|
||||
std::vector<PCLType> pcl_vec;
|
||||
if(!cam.getPointCloud(pcl_vec, t)){
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
#else
|
||||
std::vector<cv::Vec3f> pcl_vec;
|
||||
if(!cam.getPointCloud(pcl_vec, t)){
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
if (scene.win.alive()) {
|
||||
if (scene.start_draw()) {
|
||||
DrawScene(pcl_vec);
|
||||
scene.finish_draw();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cam.stopStereoCompute();
|
||||
cam.stopCapture();
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @file example_getRawFrame.cc
|
||||
* @brief This file is part of UnitreeCameraSDK.
|
||||
* @details This example that how to get camera raw frame.
|
||||
* @author ZhangChunyang
|
||||
* @date 2021.07.31
|
||||
* @version 1.0.1
|
||||
* @copyright Copyright (c) 2020-2021, Hangzhou Yushu Technology Stock CO.LTD. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include <UnitreeCameraSDK.hpp>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
|
||||
int deviceNode = 0; // default 0 -> /dev/video0
|
||||
cv::Size frameSize(1856, 800); // defalut image size: 1856 X 800
|
||||
int fps = 30;
|
||||
|
||||
if(argc >= 2){
|
||||
deviceNode = std::atoi(argv[1]);
|
||||
if(argc >= 4){
|
||||
frameSize = cv::Size(std::atoi(argv[2]), std::atoi(argv[3]));
|
||||
}
|
||||
if(argc >=5)
|
||||
fps = std::atoi(argv[4]);
|
||||
}
|
||||
|
||||
UnitreeCamera cam(deviceNode); ///< init camera by device node number
|
||||
if(!cam.isOpened())
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
cam.setRawFrameSize(frameSize); ///< set camera frame size
|
||||
cam.setRawFrameRate(fps); ///< set camera frame rate
|
||||
|
||||
std::cout << "Device Position Number:" << cam.getPosNumber() << std::endl;
|
||||
|
||||
cam.startCapture(); ///< start camera capturing
|
||||
|
||||
while(cam.isOpened())
|
||||
{
|
||||
|
||||
cv::Mat frame;
|
||||
std::chrono::microseconds t;
|
||||
if(!cam.getRawFrame(frame, t)){ ///< get camera raw image
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
cv::Mat left,right;
|
||||
frame(cv::Rect(0, 0, frame.size().width/2, frame.size().height)).copyTo(right);
|
||||
frame(cv::Rect(frame.size().width/2,0, frame.size().width/2, frame.size().height)).copyTo(left);
|
||||
cv::hconcat(left, right, frame);
|
||||
cv::imshow("UnitreeCamera_Left-Right", frame);
|
||||
char key = cv::waitKey(10);
|
||||
if(key == 27) // press ESC key
|
||||
break;
|
||||
}
|
||||
|
||||
cam.stopCapture(); ///< stop camera capturing
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* @file example_getRectFrame.cc
|
||||
* @brief This file is part of UnitreeCameraSDK.
|
||||
* @details This example that how to get depth frame
|
||||
* @author ZhangChunyang
|
||||
* @date 2021.07.31
|
||||
* @version 1.0.1
|
||||
* @copyright Copyright (c) 2020-2021, Hangzhou Yushu Technology Stock CO.LTD. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#include <UnitreeCameraSDK.hpp>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char *argv[]){
|
||||
|
||||
int deviceNode = 0; ///< default 0 -> /dev/video0
|
||||
cv::Size frameSize(1856, 800); ///< default frame size 1856x800
|
||||
int fps = 30; ///< default camera fps: 30
|
||||
|
||||
if(argc >= 2){
|
||||
deviceNode = std::atoi(argv[1]);
|
||||
if(argc >= 4){
|
||||
frameSize = cv::Size(std::atoi(argv[2]), std::atoi(argv[3]));
|
||||
}
|
||||
if(argc >=5)
|
||||
fps = std::atoi(argv[4]);
|
||||
}
|
||||
|
||||
UnitreeCamera cam("stereo_camera_config.yaml"); ///< init camera by device node number
|
||||
if(!cam.isOpened()) ///< get camera open state
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
cam.setRawFrameSize(frameSize); ///< set camera frame size
|
||||
cam.setRawFrameRate(fps); ///< set camera camera fps
|
||||
cam.setRectFrameSize(cv::Size(frameSize.width >> 2, frameSize.height >> 1)); ///< set camera rectify frame size
|
||||
cam.startCapture(); ///< disable image h264 encoding and share memory sharing
|
||||
|
||||
usleep(500000);
|
||||
while(cam.isOpened()){
|
||||
cv::Mat left,right;
|
||||
if(!cam.getRectStereoFrame(left,right)){ ///< get rectify left,right frame
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
cv::Mat stereo;
|
||||
// cv::flip(left,left, -1);
|
||||
// cv::flip(right,right, -1);
|
||||
cv::hconcat(left, right, stereo);
|
||||
cv::flip(stereo,stereo, -1);
|
||||
cv::imshow("Longlat_Rect", stereo);
|
||||
char key = cv::waitKey(10);
|
||||
if(key == 27) // press ESC key
|
||||
break;
|
||||
}
|
||||
|
||||
cam.stopCapture(); ///< stop camera capturing
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
#include <UnitreeCameraSDK.hpp>
|
||||
#include <unistd.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
|
||||
UnitreeCamera cam("trans_rect_config.yaml"); ///< init camera by device node number
|
||||
if(!cam.isOpened()) ///< get camera open state
|
||||
exit(EXIT_FAILURE);
|
||||
cam.startCapture(false, true);
|
||||
|
||||
usleep(500000);
|
||||
while(cam.isOpened())
|
||||
{
|
||||
cv::Mat left,right,feim;
|
||||
if(!cam.getRectStereoFrame(left,right))
|
||||
{
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
char key = cv::waitKey(10);
|
||||
if(key == 27) // press ESC key
|
||||
break;
|
||||
}
|
||||
|
||||
cam.stopCapture(); ///< stop camera capturing
|
||||
|
||||
return 0;
|
||||
}
|
@ -0,0 +1,124 @@
|
||||
// Copyright (c) Ethan Eade, https://bitbucket.org/ethaneade/glwindow
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace glwindow {
|
||||
|
||||
namespace ButtonEvent {
|
||||
enum Buttons {
|
||||
LEFT=1, MIDDLE=2, RIGHT=4, WHEEL=8,
|
||||
MODKEY_CTRL=16, MODKEY_SHIFT=32,
|
||||
};
|
||||
};
|
||||
|
||||
namespace KeyCode {
|
||||
enum Codes {
|
||||
BACKSPACE=0x8,
|
||||
TAB=0x9,
|
||||
ENTER=0xD,
|
||||
ESCAPE=0x1B,
|
||||
DEL=0x7F,
|
||||
|
||||
SHIFT=0xFF00,
|
||||
CTRL,
|
||||
ALT,
|
||||
SUPER,
|
||||
CAPSLOCK,
|
||||
LEFT,
|
||||
UP,
|
||||
RIGHT,
|
||||
DOWN,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
class GLWindow;
|
||||
|
||||
struct EventHandler
|
||||
{
|
||||
public:
|
||||
virtual ~EventHandler() {}
|
||||
virtual bool on_key_down(GLWindow& win, int key) { return false; }
|
||||
virtual bool on_key_up(GLWindow& win, int key) { return false; }
|
||||
virtual bool on_text(GLWindow& win, const char *text, int len) { return false; }
|
||||
virtual bool on_button_down(GLWindow& win, int btn, int state, int x, int y) { return false; }
|
||||
virtual bool on_button_up(GLWindow& win, int btn, int state, int x, int y) { return false; }
|
||||
virtual bool on_mouse_move(GLWindow& win, int state, int x, int y) { return false; }
|
||||
virtual bool on_mouse_wheel(GLWindow& win, int state, int x, int y, int dx, int dy) { return false; }
|
||||
virtual bool on_resize(GLWindow& win, int x, int y, int w, int h) { return false; }
|
||||
virtual bool on_close(GLWindow& win) { return false; }
|
||||
};
|
||||
|
||||
// Dispatches to each handler in reverse order until one returns true
|
||||
class EventDispatcher : public EventHandler
|
||||
{
|
||||
public:
|
||||
const std::vector<EventHandler*> &handlers;
|
||||
EventDispatcher(const std::vector<EventHandler*> &h) :
|
||||
handlers(h) {}
|
||||
bool on_key_down(GLWindow& win, int key);
|
||||
bool on_key_up(GLWindow& win, int key);
|
||||
bool on_text(GLWindow& win, const char *text, int len);
|
||||
bool on_button_down(GLWindow& win, int btn, int state, int x, int y);
|
||||
bool on_button_up(GLWindow& win, int btn, int state, int x, int y);
|
||||
bool on_mouse_move(GLWindow& win, int state, int x, int y);
|
||||
bool on_mouse_wheel(GLWindow& win, int state, int x, int y, int dx, int dy);
|
||||
bool on_resize(GLWindow& win, int x, int y, int w, int h);
|
||||
bool on_close(GLWindow& win);
|
||||
};
|
||||
|
||||
class GLWindow
|
||||
{
|
||||
public:
|
||||
GLWindow(int w=-1, int h=-1, const char *title=0);
|
||||
virtual ~GLWindow();
|
||||
|
||||
int width() const;
|
||||
int height() const;
|
||||
bool visible() const;
|
||||
bool alive() const;
|
||||
|
||||
bool make_current();
|
||||
bool push_context();
|
||||
void pop_context();
|
||||
|
||||
struct ScopedContext {
|
||||
GLWindow &win;
|
||||
ScopedContext(GLWindow &w) : win(w) {
|
||||
win.push_context();
|
||||
}
|
||||
~ScopedContext() {
|
||||
win.pop_context();
|
||||
}
|
||||
};
|
||||
|
||||
void swap_buffers();
|
||||
|
||||
void set_size(int w, int h);
|
||||
void set_position(int x, int y);
|
||||
|
||||
void set_title(const char* title);
|
||||
|
||||
void add_handler(EventHandler* handler);
|
||||
bool remove_handler(EventHandler *handler);
|
||||
void handle_events();
|
||||
static void handle_all_events();
|
||||
|
||||
void destroy();
|
||||
|
||||
void draw_text(double x, double y, const char *text, int xywh[4]=0);
|
||||
protected:
|
||||
struct SystemState;
|
||||
SystemState *sys_state;
|
||||
|
||||
std::vector<EventHandler*> handlers;
|
||||
GLWindow *prev_active;
|
||||
|
||||
static GLWindow *active_context;
|
||||
static std::vector<GLWindow*> all_windows;
|
||||
static void add_window(GLWindow *win);
|
||||
static bool remove_window(GLWindow *win);
|
||||
};
|
||||
}
|
@ -0,0 +1,518 @@
|
||||
// Copyright (c) Ethan Eade, https://bitbucket.org/ethaneade/glwindow
|
||||
|
||||
#include "glwindow.hpp"
|
||||
|
||||
#include <X11/Xlib.h>
|
||||
#include <X11/keysym.h>
|
||||
#include <GL/glx.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace glwindow;
|
||||
|
||||
std::vector<GLWindow*> GLWindow::all_windows;
|
||||
|
||||
void GLWindow::add_window(GLWindow *win)
|
||||
{
|
||||
all_windows.push_back(win);
|
||||
}
|
||||
|
||||
bool GLWindow::remove_window(GLWindow *win)
|
||||
{
|
||||
for (size_t i=0; i<all_windows.size(); ++i) {
|
||||
if (all_windows[i] == win) {
|
||||
all_windows[i] = all_windows.back();
|
||||
all_windows.pop_back();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLWindow::handle_all_events()
|
||||
{
|
||||
for (size_t i=0; i<all_windows.size(); ++i)
|
||||
all_windows[i]->handle_events();
|
||||
}
|
||||
|
||||
GLWindow *GLWindow::active_context = 0;
|
||||
|
||||
bool GLWindow::push_context()
|
||||
{
|
||||
prev_active = active_context;
|
||||
return make_current();
|
||||
}
|
||||
|
||||
void GLWindow::pop_context()
|
||||
{
|
||||
if (active_context != this)
|
||||
return;
|
||||
|
||||
if (prev_active) {
|
||||
prev_active->make_current();
|
||||
} else {
|
||||
active_context = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void GLWindow::add_handler(EventHandler* handler)
|
||||
{
|
||||
handlers.push_back(handler);
|
||||
}
|
||||
|
||||
bool GLWindow::remove_handler(EventHandler* handler)
|
||||
{
|
||||
std::vector<EventHandler*>::reverse_iterator it;
|
||||
for (it = handlers.rbegin(); it != handlers.rend(); ++it) {
|
||||
if (*it == handler) {
|
||||
handlers.erase(it.base());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventDispatcher::on_key_down(GLWindow& win, int key) {
|
||||
for (int i=handlers.size()-1; i>=0; --i)
|
||||
if (handlers[i]->on_key_down(win, key))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
bool EventDispatcher::on_key_up(GLWindow& win, int key) {
|
||||
for (int i=handlers.size()-1; i>=0; --i)
|
||||
if (handlers[i]->on_key_up(win, key))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
bool EventDispatcher::on_text(GLWindow& win, const char *text, int len) {
|
||||
for (int i=handlers.size()-1; i>=0; --i)
|
||||
if (handlers[i]->on_text(win, text, len))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventDispatcher::on_button_down(GLWindow& win, int btn, int state, int x, int y) {
|
||||
for (int i=handlers.size()-1; i>=0; --i)
|
||||
if (handlers[i]->on_button_down(win, btn, state, x, y))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventDispatcher::on_button_up(GLWindow& win, int btn, int state, int x, int y) {
|
||||
for (int i=handlers.size()-1; i>=0; --i)
|
||||
if (handlers[i]->on_button_up(win, btn, state, x, y))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventDispatcher::on_mouse_move(GLWindow& win, int state, int x, int y) {
|
||||
for (int i=handlers.size()-1; i>=0; --i)
|
||||
if (handlers[i]->on_mouse_move(win, state, x, y))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventDispatcher::on_mouse_wheel(GLWindow& win, int state, int x, int y, int dx, int dy) {
|
||||
for (int i=handlers.size()-1; i>=0; --i)
|
||||
if (handlers[i]->on_mouse_wheel(win, state, x, y, dx, dy))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventDispatcher::on_resize(GLWindow &win, int x, int y, int w, int h) {
|
||||
for (int i=handlers.size()-1; i>=0; --i)
|
||||
if (handlers[i]->on_resize(win, x, y, w, h))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EventDispatcher::on_close(GLWindow &win) {
|
||||
for (int i=handlers.size()-1; i>=0; --i)
|
||||
if (handlers[i]->on_close(win))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static XVisualInfo *makeVisualInfo(Display *display)
|
||||
{
|
||||
int visualAttributes[] = {
|
||||
GLX_RED_SIZE, 8,
|
||||
GLX_GREEN_SIZE, 8,
|
||||
GLX_BLUE_SIZE, 8,
|
||||
GLX_DEPTH_SIZE, 16,
|
||||
GLX_STENCIL_SIZE, 8,
|
||||
GLX_RGBA,
|
||||
GLX_DOUBLEBUFFER,
|
||||
None
|
||||
};
|
||||
XVisualInfo *vi = glXChooseVisual(display, DefaultScreen(display), visualAttributes);
|
||||
return vi;
|
||||
}
|
||||
|
||||
static Window makeWindow(Display *display, XVisualInfo *vi, int width, int height)
|
||||
{
|
||||
Window rootWindow = RootWindow(display, vi->screen);
|
||||
|
||||
XSetWindowAttributes attributes;
|
||||
attributes.border_pixel = 0;
|
||||
attributes.colormap = XCreateColormap(display, rootWindow, vi->visual, AllocNone);
|
||||
attributes.event_mask = (KeyPressMask | KeyReleaseMask |
|
||||
ButtonPressMask | ButtonReleaseMask |
|
||||
PointerMotionMask |
|
||||
VisibilityChangeMask |
|
||||
StructureNotifyMask |
|
||||
ExposureMask);
|
||||
|
||||
Window window = XCreateWindow(display,
|
||||
rootWindow,
|
||||
0, 0, width, height,
|
||||
0, vi->depth,
|
||||
InputOutput,
|
||||
vi->visual,
|
||||
CWBorderPixel | CWColormap | CWEventMask,
|
||||
&attributes);
|
||||
return window;
|
||||
}
|
||||
|
||||
struct GLWindow::SystemState
|
||||
{
|
||||
Display* display;
|
||||
Window window;
|
||||
GLXContext context;
|
||||
Atom delete_atom;
|
||||
Cursor cursor;
|
||||
|
||||
int width, height;
|
||||
bool visible;
|
||||
|
||||
SystemState() {
|
||||
display = 0;
|
||||
window = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
visible = false;
|
||||
}
|
||||
~SystemState() {
|
||||
if (!display)
|
||||
return;
|
||||
|
||||
if (context) {
|
||||
destroy();
|
||||
|
||||
glXMakeCurrent(display, None, 0);
|
||||
glXDestroyContext(display, context);
|
||||
}
|
||||
|
||||
XCloseDisplay(display);
|
||||
}
|
||||
|
||||
bool init(int w, int h, const char *title)
|
||||
{
|
||||
display = XOpenDisplay(0);
|
||||
if (!display)
|
||||
return false;
|
||||
XVisualInfo *vi = makeVisualInfo(display);
|
||||
if (!vi)
|
||||
return false;
|
||||
|
||||
context = glXCreateContext(display, vi, 0, True);
|
||||
if (!context)
|
||||
return false;
|
||||
|
||||
width = w;
|
||||
height = h;
|
||||
window = makeWindow(display, vi, width, height);
|
||||
if (!window)
|
||||
return false;
|
||||
|
||||
XStoreName(display, window, title);
|
||||
|
||||
{
|
||||
XClassHint classHint;
|
||||
classHint.res_name = const_cast<char*>(title);
|
||||
char classname[] = "glwindow";
|
||||
classHint.res_class = classname;
|
||||
XSetClassHint(display, window, &classHint);
|
||||
XMapWindow(display, window);
|
||||
}
|
||||
|
||||
XEvent ev;
|
||||
do {
|
||||
XNextEvent(display, &ev);
|
||||
} while (ev.type != MapNotify);
|
||||
|
||||
visible = true;
|
||||
delete_atom = XInternAtom(display, "WM_DELETE_WINDOW", True);
|
||||
XSetWMProtocols(display, window, &delete_atom, 1);
|
||||
|
||||
cursor = XCreateFontCursor(display, ' ');
|
||||
return true;
|
||||
}
|
||||
|
||||
void destroy()
|
||||
{
|
||||
if (window) {
|
||||
XUnmapWindow(display, window);
|
||||
XDestroyWindow(display, window);
|
||||
window = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void swap_buffers()
|
||||
{
|
||||
if (window) {
|
||||
glXSwapBuffers(display, window);
|
||||
}
|
||||
}
|
||||
|
||||
void set_title(const char *title)
|
||||
{
|
||||
if (window) {
|
||||
XStoreName(display, window, title);
|
||||
}
|
||||
}
|
||||
|
||||
bool make_current()
|
||||
{
|
||||
if (!window)
|
||||
return false;
|
||||
glXMakeCurrent(display, window, context);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
GLWindow::GLWindow(int w, int h, const char *title)
|
||||
{
|
||||
sys_state = new SystemState();
|
||||
sys_state->init(w, h, title);
|
||||
all_windows.push_back(this);
|
||||
}
|
||||
|
||||
GLWindow::~GLWindow()
|
||||
{
|
||||
for (size_t i=0; i<all_windows.size(); ++i) {
|
||||
if (all_windows[i] == this) {
|
||||
all_windows[i] = all_windows.back();
|
||||
all_windows.pop_back();
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete sys_state;
|
||||
}
|
||||
|
||||
int GLWindow::width() const
|
||||
{
|
||||
return sys_state->width;
|
||||
}
|
||||
|
||||
int GLWindow::height() const
|
||||
{
|
||||
return sys_state->height;
|
||||
}
|
||||
|
||||
bool GLWindow::visible() const
|
||||
{
|
||||
return sys_state->visible;
|
||||
}
|
||||
|
||||
bool GLWindow::alive() const
|
||||
{
|
||||
return 0 != sys_state->window;
|
||||
}
|
||||
|
||||
bool GLWindow::make_current()
|
||||
{
|
||||
if (!sys_state->make_current())
|
||||
return false;
|
||||
|
||||
active_context = this;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLWindow::swap_buffers()
|
||||
{
|
||||
sys_state->swap_buffers();
|
||||
}
|
||||
|
||||
void GLWindow::set_size(int w, int h)
|
||||
{
|
||||
if (!alive())
|
||||
return;
|
||||
|
||||
XWindowChanges c;
|
||||
c.width = w;
|
||||
c.height = h;
|
||||
XConfigureWindow(sys_state->display,
|
||||
sys_state->window,
|
||||
CWWidth | CWHeight,
|
||||
&c);
|
||||
}
|
||||
|
||||
void GLWindow::set_position(int x, int y)
|
||||
{
|
||||
if (!alive())
|
||||
return;
|
||||
|
||||
XWindowChanges c;
|
||||
c.x = x;
|
||||
c.y = y;
|
||||
XConfigureWindow(sys_state->display,
|
||||
sys_state->window,
|
||||
CWX | CWY,
|
||||
&c);
|
||||
}
|
||||
|
||||
void GLWindow::set_title(const char* title)
|
||||
{
|
||||
if (!alive())
|
||||
return;
|
||||
|
||||
sys_state->set_title(title);
|
||||
}
|
||||
|
||||
static int convert_button_state(unsigned int state)
|
||||
{
|
||||
int s = 0;
|
||||
if (state & Button1Mask) s |= ButtonEvent::LEFT;
|
||||
if (state & Button2Mask) s |= ButtonEvent::MIDDLE;
|
||||
if (state & Button3Mask) s |= ButtonEvent::RIGHT;
|
||||
if (state & ControlMask) s |= ButtonEvent::MODKEY_CTRL;
|
||||
if (state & ShiftMask) s |= ButtonEvent::MODKEY_SHIFT;
|
||||
return s;
|
||||
}
|
||||
|
||||
static int convert_button(int button)
|
||||
{
|
||||
switch (button) {
|
||||
case Button1: return ButtonEvent::LEFT;
|
||||
case Button2: return ButtonEvent::MIDDLE;
|
||||
case Button3: return ButtonEvent::RIGHT;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int convert_keycode(int key)
|
||||
{
|
||||
switch (key) {
|
||||
case XK_BackSpace: return KeyCode::BACKSPACE;
|
||||
case XK_Tab: return KeyCode::TAB;
|
||||
case XK_Return: return KeyCode::ENTER;
|
||||
case XK_Shift_L: return KeyCode::SHIFT;
|
||||
case XK_Shift_R: return KeyCode::SHIFT;
|
||||
case XK_Control_L: return KeyCode::CTRL;
|
||||
case XK_Control_R: return KeyCode::CTRL;
|
||||
case XK_Alt_L: return KeyCode::ALT;
|
||||
case XK_Alt_R: return KeyCode::ALT;
|
||||
case XK_Super_L: return KeyCode::SUPER;
|
||||
case XK_Super_R: return KeyCode::SUPER;
|
||||
case XK_Caps_Lock: return KeyCode::CAPSLOCK;
|
||||
case XK_Delete: return KeyCode::DEL;
|
||||
case XK_Escape: return KeyCode::ESCAPE;
|
||||
case XK_Left: return KeyCode::LEFT;
|
||||
case XK_Up: return KeyCode::UP;
|
||||
case XK_Right: return KeyCode::RIGHT;
|
||||
case XK_Down: return KeyCode::DOWN;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
void GLWindow::handle_events()
|
||||
{
|
||||
if (!alive())
|
||||
return;
|
||||
|
||||
XEvent event;
|
||||
KeySym key;
|
||||
const int text_size = 64;
|
||||
char text[text_size];
|
||||
int len;
|
||||
EventDispatcher dispatcher(handlers);
|
||||
|
||||
int btn, state;
|
||||
|
||||
while (XPending(sys_state->display))
|
||||
{
|
||||
XNextEvent(sys_state->display, &event);
|
||||
//std::cerr << "event " << event.type << std::endl;
|
||||
switch (event.type) {
|
||||
case ButtonPress:
|
||||
state = convert_button_state(event.xbutton.state);
|
||||
if (event.xbutton.button == Button4) {
|
||||
// MouseWheel down
|
||||
dispatcher.on_mouse_wheel(*this, state, event.xbutton.x, event.xbutton.y, 0, 1);
|
||||
} else if (event.xbutton.button == Button5) {
|
||||
// MouseWheel up
|
||||
dispatcher.on_mouse_wheel(*this, state, event.xbutton.x, event.xbutton.y, 0, -1);
|
||||
} else {
|
||||
btn = convert_button(event.xbutton.button);
|
||||
dispatcher.on_button_down(*this, btn, state, event.xbutton.x, event.xbutton.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case ButtonRelease:
|
||||
if (event.xbutton.button == Button4 ||
|
||||
event.xbutton.button == Button5)
|
||||
break;
|
||||
btn = convert_button(event.xbutton.button);
|
||||
state = convert_button_state(event.xbutton.state);
|
||||
dispatcher.on_button_up(*this, btn, state, event.xbutton.x, event.xbutton.y);
|
||||
break;
|
||||
|
||||
case MotionNotify:
|
||||
state = convert_button_state(event.xbutton.state);
|
||||
dispatcher.on_mouse_move(*this, state, event.xmotion.x, event.xmotion.y);
|
||||
break;
|
||||
|
||||
case KeyPress:
|
||||
len = XLookupString(&event.xkey, text, text_size-1, &key, 0);
|
||||
dispatcher.on_key_down(*this, convert_keycode(key));
|
||||
if (len > 0) {
|
||||
text[len] = 0;
|
||||
dispatcher.on_text(*this, text, len);
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyRelease:
|
||||
XLookupString(&event.xkey, 0, 0, &key, 0);
|
||||
dispatcher.on_key_up(*this, convert_keycode(key));
|
||||
break;
|
||||
|
||||
case ConfigureNotify:
|
||||
sys_state->width = event.xconfigure.width;
|
||||
sys_state->height = event.xconfigure.height;
|
||||
dispatcher.on_resize(*this, event.xconfigure.x, event.xconfigure.y,
|
||||
sys_state->width, sys_state->height);
|
||||
break;
|
||||
|
||||
case VisibilityNotify:
|
||||
if (event.xvisibility.state == VisibilityFullyObscured)
|
||||
sys_state->visible = false;
|
||||
else
|
||||
sys_state->visible = true;
|
||||
break;
|
||||
|
||||
case DestroyNotify:
|
||||
//std::cerr << "DestroyNotify" << std::endl;
|
||||
//sys_state->window = 0;
|
||||
break;
|
||||
|
||||
case Expose:
|
||||
//std::cerr << "Expose" << std::endl;
|
||||
break;
|
||||
|
||||
case ClientMessage:
|
||||
if (event.xclient.data.l[0] == (int)sys_state->delete_atom) {
|
||||
if (!dispatcher.on_close(*this))
|
||||
destroy();
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLWindow::destroy()
|
||||
{
|
||||
sys_state->destroy();
|
||||
}
|
@ -0,0 +1,181 @@
|
||||
// Copyright (c) Ethan Eade, https://bitbucket.org/ethaneade/glwindow
|
||||
|
||||
#include "scenewindow.hpp"
|
||||
#include <GL/gl.h>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
using namespace glwindow;
|
||||
|
||||
SceneWindow::SceneWindow(int width, int height, const char *title)
|
||||
: win(width, height, title)
|
||||
{
|
||||
dragging = false;
|
||||
drawing = false;
|
||||
win.add_handler(this);
|
||||
}
|
||||
|
||||
SceneWindow::~SceneWindow()
|
||||
{
|
||||
}
|
||||
|
||||
void SceneWindow::update()
|
||||
{
|
||||
win.handle_events();
|
||||
}
|
||||
|
||||
SceneWindow::Viewpoint::Viewpoint()
|
||||
{
|
||||
target[0] = -0.05;
|
||||
target[1] = -0.75;
|
||||
target[2] = 0.;
|
||||
azimuth = 0.;
|
||||
elevation = 0.0;
|
||||
distance = 8.0;
|
||||
}
|
||||
|
||||
static void set_viewpoint(const SceneWindow::Viewpoint &vp)
|
||||
{
|
||||
const double RAD_TO_DEG = 180.0 / 3.141592653589793;
|
||||
glTranslated(0,0,vp.distance);
|
||||
glRotated(vp.elevation * RAD_TO_DEG, 1, 0, 0);
|
||||
glRotated(vp.azimuth * RAD_TO_DEG, 0, 1, 0);
|
||||
glTranslated(-vp.target[0], -vp.target[1], -vp.target[2]);
|
||||
}
|
||||
|
||||
bool SceneWindow::start_draw()
|
||||
{
|
||||
if (!win.alive() || drawing)
|
||||
return false;
|
||||
|
||||
drawing = true;
|
||||
|
||||
win.push_context();
|
||||
glPushAttrib(GL_COLOR_BUFFER_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT);
|
||||
|
||||
glViewport(0, 0, win.width(), win.height());
|
||||
double aspect = (double)win.width() / (double)std::max(win.height(),1);
|
||||
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
double znear = 0.01;
|
||||
double zfar = 100.0;
|
||||
double fy = 0.6 * znear;
|
||||
double fx = aspect * fy;
|
||||
glFrustum(-fx,fx,-fy,fy, znear, zfar);
|
||||
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
glScaled(1, -1, -1);
|
||||
|
||||
set_viewpoint(viewpoint);
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
glDisable(GL_LIGHTING);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void SceneWindow::finish_draw()
|
||||
{
|
||||
if (!drawing)
|
||||
return;
|
||||
|
||||
glPopAttrib();
|
||||
glFlush();
|
||||
win.swap_buffers();
|
||||
win.handle_events();
|
||||
|
||||
win.pop_context();
|
||||
drawing = false;
|
||||
}
|
||||
|
||||
bool SceneWindow::on_key_down(GLWindow& win, int key)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneWindow::on_button_down(GLWindow& win, int btn, int state, int x, int y)
|
||||
{
|
||||
if (dragging)
|
||||
return false;
|
||||
|
||||
//std::cerr << "down " << btn << std::endl;
|
||||
drag_btn = btn;
|
||||
x0 = x;
|
||||
y0 = y;
|
||||
vp0 = viewpoint;
|
||||
inv_w0 = 1.0 / win.width();
|
||||
inv_h0 = 1.0 / win.height();
|
||||
dragging = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneWindow::on_button_up(GLWindow& win, int btn, int state, int x, int y)
|
||||
{
|
||||
//std::cerr << "up " << btn << std::endl;
|
||||
dragging = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneWindow::on_mouse_move(GLWindow& win, int state, int x, int y)
|
||||
{
|
||||
int idx = x - x0;
|
||||
int idy = y - y0;
|
||||
double dx = idx * inv_w0;
|
||||
double dy = idy * inv_w0;
|
||||
|
||||
//std::cerr << dx << ", " << dy << std::endl;
|
||||
|
||||
if (!dragging)
|
||||
return false;
|
||||
|
||||
if (drag_btn == ButtonEvent::LEFT) {
|
||||
viewpoint.azimuth = vp0.azimuth - dx * 4.0;
|
||||
viewpoint.elevation = vp0.elevation + dy * 4.0;
|
||||
return true;
|
||||
} else if (drag_btn == ButtonEvent::RIGHT) {
|
||||
viewpoint.distance = ::exp(dy * 4.0) * vp0.distance;
|
||||
return true;
|
||||
} else if (drag_btn == ButtonEvent::MIDDLE) {
|
||||
double sa = ::sin(-vp0.azimuth);
|
||||
double ca = ::cos(-vp0.azimuth);
|
||||
double se = ::sin(vp0.elevation);
|
||||
double ce = ::cos(vp0.elevation);
|
||||
|
||||
double tx = -idx * 0.003;
|
||||
double ty = -idy * 0.003;
|
||||
|
||||
double dtx = ca * tx - se*sa*ty;
|
||||
double dty = ce * ty;
|
||||
double dtz = -sa*tx - se*ca*ty;
|
||||
double r = vp0.distance;
|
||||
|
||||
viewpoint.target[0] = vp0.target[0] + r*dtx;
|
||||
viewpoint.target[1] = vp0.target[1] + r*dty;
|
||||
viewpoint.target[2] = vp0.target[2] + r*dtz;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SceneWindow::on_mouse_wheel(GLWindow& win, int state, int x, int y, int dx, int dy)
|
||||
{
|
||||
viewpoint.distance *= ::exp(dy * -0.08);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SceneWindow::on_resize(GLWindow& win, int x, int y, int w, int h)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
// Copyright (c) Ethan Eade, https://bitbucket.org/ethaneade/glwindow
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "glwindow.hpp"
|
||||
|
||||
namespace glwindow
|
||||
{
|
||||
|
||||
class SceneWindow : public EventHandler
|
||||
{
|
||||
public:
|
||||
|
||||
struct Viewpoint
|
||||
{
|
||||
double target[3];
|
||||
double azimuth, elevation, distance;
|
||||
Viewpoint();
|
||||
};
|
||||
|
||||
SceneWindow(int width, int height, const char *title);
|
||||
virtual ~SceneWindow();
|
||||
|
||||
void update();
|
||||
|
||||
bool start_draw();
|
||||
void finish_draw();
|
||||
|
||||
GLWindow win;
|
||||
Viewpoint viewpoint;
|
||||
|
||||
protected:
|
||||
bool on_key_down(GLWindow& win, int key);
|
||||
bool on_button_down(GLWindow& win, int btn, int state, int x, int y);
|
||||
bool on_button_up(GLWindow& win, int btn, int state, int x, int y);
|
||||
bool on_mouse_move(GLWindow& win, int state, int x, int y);
|
||||
bool on_mouse_wheel(GLWindow& win, int state, int x, int y, int dx, int dy);
|
||||
bool on_resize(GLWindow& win, int x, int y, int w, int h);
|
||||
|
||||
bool dragging;
|
||||
int drag_btn;
|
||||
int x0, y0;
|
||||
double inv_w0, inv_h0;
|
||||
Viewpoint vp0;
|
||||
|
||||
bool drawing;
|
||||
};
|
||||
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue