简介:
最近上班需求不多空闲时间有些无聊,于是突然萌生一个想法能不能实现远程控制实现3d模型上开关门和开关灯的模拟操作,话不多说直接上效果。
remoteControl
核心流程步骤:
-》基于three.js加载glb的3D模型建筑物
-》通过mqtt协议实现订阅远程发送指令的接收和响应操作
-》mqtt服务端通过Node简单搭建一个
-》基于NodeRed实现远程发送控制命令
本地项目搭建环境
新建一个Demo文件夹,新建server.js/index.html文件,再加下载的glb模型文件放到同级目录下
环境初始化和运行命令
npm init -y # 初始化 Node.js 项目(如果尚未初始化)
npm install mqtt # 安装 MQTT.js 库
node server.js # 启动 MQTT 服务器
html直接点击打开会出现跨域问题所以
使用 VSCode Live Server 插件这个去插件市场搜索然后安装就可以了
右键 index.html,点击 “Open with Live Server”。
搭建mqtt服务端的代码
server.js
const mqtt = require('mqtt');
// 连接到 MQTT 服务器(本地 MQTT Broker,或者使用 test.mosquitto.org)
const brokerUrl = "mqtt://localhost"; // 如果使用在线服务器,可以换成 "mqtt://test.mosquitto.org"
const client = mqtt.connect(brokerUrl);
client.on("connect", () => {
console.log(" MQTT 服务器已连接");
client.subscribe("door/control"); // 监听门的开关指令
});
client.on("message", (topic, message) => {
console.log(` 收到消息: ${message.toString()}`);
// 在这里可以添加其他控制逻辑
});
console.log(` MQTT 服务器运行中: ${brokerUrl}`);
搭建3d场景的代码
index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D 教学楼门禁与灯光系统</title>
<!-- Three.js 核心库 -->
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
<!-- 动画库 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<!-- MQTT 连接 -->
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
.btn {
position: absolute;
bottom: 20px;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
#btnDoor {
left: 40%;
background: green;
color: white;
}
#btnLight {
left: 60%;
background: orange;
color: white;
}
</style>
</head>
<body>
<button id="btnDoor" class="btn">开门</button>
<button id="btnLight" class="btn">关灯</button>
<script>
let scene, camera, renderer, controls, door, light, ambientLight, mqttClient;
let isDoorOpen = false; // 默认门是关的
let isLightOn = true; // 默认灯是开的
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
scene.background = new THREE.Color(0xcccccc);
// 环境光 & 方向光
ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
light = new THREE.DirectionalLight(0xffffff, 1.5);
light.position.set(10, 20, 10);
scene.add(light);
// 轨道控制器
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
camera.position.set(0, 3, 10);
controls.update();
// 加载 3D 教学楼模型
const loader = new THREE.GLTFLoader();
loader.load('sunkehouse.glb', function (gltf) {
const model = gltf.scene;
model.position.set(0, 0, 0);
model.scale.set(0.2, 0.2, 0.2);
scene.add(model);
// 整体缩放
scene.scale.set(0.5, 0.5, 0.5);
// 查找门对象
door = model.getObjectByName("Object_6");
}, undefined, function (error) {
console.error("模型加载失败:", error);
});
animate();
// MQTT 连接
mqttClient = mqtt.connect("wss://test.mosquitto.org:8081/mqtt");
mqttClient.on("connect", () => {
console.log("已连接 MQTT");
mqttClient.subscribe("door/control");
mqttClient.subscribe("light/control");
});
mqttClient.on("message", (topic, message) => {
console.log(`收到 MQTT: ${message.toString()}`);
if (topic === "door/control") {
toggleDoor(message.toString() === "open");
}
if (topic === "light/control") {
toggleLight(message.toString() === "on");
}
});
// 绑定按钮事件
document.getElementById("btnDoor").addEventListener("click", toggleDoorButton);
document.getElementById("btnLight").addEventListener("click", toggleLightButton);
window.addEventListener("resize", onWindowResize);
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
function toggleDoor(open) {
if (!door) {
console.error(" 没有找到门对象!");
return;
}
const openRotation = Math.PI / 2;
const closeRotation = 0;
gsap.to(door.rotation, {
z: open ? openRotation : closeRotation,
duration: 3,
ease: "power4.inOut",
});
isDoorOpen = open;
document.getElementById("btnDoor").innerText = open ? "关门" : "开门";
}
function toggleDoorButton() {
toggleDoor(!isDoorOpen);
sendCommand("door/control", isDoorOpen ? "open" : "close");
}
function toggleLight(turnOn) {
isLightOn = turnOn;
gsap.to(light, { intensity: turnOn ? 1.5 : 0, duration: 1.5 });
gsap.to(ambientLight, { intensity: turnOn ? 0.6 : 0.1, duration: 1.5 });
document.getElementById("btnLight").innerText = turnOn ? "关灯" : "开灯";
}
function toggleLightButton() {
toggleLight(!isLightOn);
sendCommand("light/control", isLightOn ? "on" : "off");
}
function sendCommand(topic, command) {
if (mqttClient.connected) {
mqttClient.publish(topic, command);
} else {
console.error(" MQTT 未连接!");
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
init();
</script>
</body>
</html>
总结:
本次只是简单实现了一个远程控制的场景后续会思考将场景完善扩展,看后面有时间的话再研究,
感兴趣的一起可以交流https://t.zsxq.com/7AspS,平时也会分享一些公司碰到的问题和具体解决办法。