当前位置: 首页 > news >正文

第41节:第三阶段总结:打造一个AR家具摆放应用

第41节:第三阶段总结:打造一个AR家具摆放应用

概述

本节将综合运用前面学习的3D模型加载、AR标记跟踪、交互控制等技术,构建一个完整的AR家具摆放应用。用户可以通过手机摄像头在真实环境中预览和摆放虚拟家具。

在这里插入图片描述

应用架构概览:

用户界面
AR核心系统
家具模型库
标记识别
姿态跟踪
场景渲染
模型加载
材质管理
虚拟物体锚定
交互系统
放置/移动/旋转
撤销/重做
场景保存

核心实现

AR家具摆放应用

<template><div class="ar-furniture-app"><!-- AR视图 --><div class="ar-viewport"><video ref="videoElement" class="camera-feed" autoplay playsinline></video><canvas ref="arCanvas" class="ar-overlay"></canvas><!-- UI叠加层 --><div class="ar-ui"><div class="status-indicator" :class="trackingStatus"><span class="status-icon">{{ statusIcon }}</span><span class="status-text">{{ statusText }}</span></div><div class="instruction" v-if="showInstruction">📱 扫描平面或标记来放置家具</div></div></div><!-- 底部控制栏 --><div class="control-bar"><!-- 家具选择 --><div class="furniture-picker"><div class="category-tabs"><button v-for="category in categories" :key="category.id":class="{ active: currentCategory === category.id }"@click="currentCategory = category.id"class="tab-button">{{ category.name }}</button></div><div class="furniture-grid"><div v-for="item in currentFurniture" :key="item.id":class="{ selected: selectedFurniture === item.id }"@click="selectFurniture(item)"class="furniture-item"><img :src="item.thumbnail" :alt="item.name" class="item-thumb"><span class="item-name">{{ item.name }}</span></div></div></div><!-- 操作控制 --><div class="action-controls"><button @click="setInteractionMode('place')" :class="{ active: interactionMode === 'place' }"class="action-button">📍 放置</button><button @click="setInteractionMode('move')":class="{ active: interactionMode === 'move' }"class="action-button">🚀 移动</button><button @click="setInteractionMode('rotate')":class="{ active: interactionMode === 'rotate' }"class="action-button">🔄 旋转</button><button @click="deleteSelected" class="action-button delete">🗑️ 删除</button><button @click="saveScene" class="action-button save"><span v-if="!isSaving">💾 保存</span><span v-else>⏳ 保存中...</span></button></div></div><!-- 变换控制面板 --><div v-if="selectedObject" class="transform-panel"><div class="transform-controls"><div class="control-group"><label>位置</label><div class="vector-controls"><input type="number" v-model.number="selectedObject.position.x" step="0.1" placeholder="X"><input type="number" v-model.number="selectedObject.position.y" step="0.1" placeholder="Y"><input type="number" v-model.number="selectedObject.position.z" step="0.1" placeholder="Z"></div></div><div class="control-group"><label>旋转</label><div class="vector-controls"><input type="number" v-model.number="selectedObject.rotation.x" step="0.1" placeholder="X"><input type="number" v-model.number="selectedObject.rotation.y" step="0.1" placeholder="Y"><input type="number" v-model.number="selectedObject.rotation.z" step="0.1" placeholder="Z"></div></div><div class="control-group"><label>缩放: {{ selectedObject.scale.x.toFixed(1) }}</label><input type="range" v-model.number="selectedObject.scale.x" min="0.1" max="3" step="0.1"@input="uniformScale"></div></div></div><!-- 加载状态 --><div v-if="isLoading" class="loading-overlay"><div class="loading-content"><div class="spinner"></div><h3>初始化AR场景...</h3><p>{{ loadingProgress }}%</p></div></div></div>
</template><script>
import { onMounted, onUnmounted, ref, reactive, computed } from 'vue';
import * as THREE from 'three';// AR场景管理器
class ARFurnitureScene {constructor(renderer, camera) {this.renderer = renderer;this.camera = camera;this.scene = new THREE.Scene();this.placedObjects = new Map();this.nextObjectId = 1;this.setupScene();}setupScene() {// 环境光照const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);this.scene.add(ambientLight);const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(5, 10, 7);this.scene.add(directionalLight);}// 添加家具到场景async addFurniture(modelType, position, rotation, scale = 1) {const geometry = this.createGeometry(modelType);const material = this.createMaterial(modelType);const mesh = new THREE.Mesh(geometry, material);mesh.position.copy(position);mesh.rotation.set(rotation.x, rotation.y, rotation.z);mesh.scale.setScalar(scale);const objectId = this.nextObjectId++;mesh.userData = {id: objectId,type: 'furniture',modelType: modelType};this.scene.add(mesh);this.placedObjects.set(objectId, mesh);return mesh;}// 创建几何体createGeometry(type) {switch (type) {case 'chair':return this.createChairGeometry();case 'table':return this.createTableGeometry();case 'sofa':return this.createSofaGeometry();case 'bed':return this.createBedGeometry();case 'lamp':return this.createLampGeometry();default:return new THREE.BoxGeometry(1, 1, 1);}}// 创建椅子几何体createChairGeometry() {const group = new THREE.Group();// 椅腿const legGeo = new THREE.CylinderGeometry(0.05, 0.05, 0.8, 8);const legMat = new THREE.MeshStandardMaterial({ color: 0x8B4513 });const positions = [[0.4, -0.4, 0.4], [-0.4, -0.4, 0.4],[0.4, -0.4, -0.4], [-0.4, -0.4, -0.4]];positions.forEach(pos => {const leg = new THREE.Mesh(legGeo, legMat);leg.position.set(pos[0], pos[1], pos[2]);group.add(leg);});// 椅面const seatGeo = new THREE.BoxGeometry(1, 0.1, 1);const seatMat = new THREE.MeshStandardMaterial({ color: 0xD2691E });const seat = new THREE.Mesh(seatGeo, seatMat);seat.position.y = 0;group.add(seat);// 椅背const backGeo = new THREE.BoxGeometry(1, 1, 0.1);const backMat = new THREE.MeshStandardMaterial({ color: 0xD2691E });const back = new THREE.Mesh(backGeo, backMat);back.position.set(0, 0.5, -0.45);group.add(back);return group;}// 创建桌子几何体createTableGeometry() {const group = new THREE.Group();// 桌腿const legGeo = new THREE.CylinderGeometry(0.08, 0.08, 1.2, 8);const legMat = new THREE.MeshStandardMaterial({ color: 0x8B4513 });const positions = [[0.8, -0.6, 0.8], [-0.8, -0.6, 0.8],[0.8, -0.6, -0.8], [-0.8, -0.6, -0.8]];positions.forEach(pos => {const leg = new THREE.Mesh(legGeo, legMat);leg.position.set(pos[0], pos[1], pos[2]);group.add(leg);});// 桌面const topGeo = new THREE.CylinderGeometry(1.2, 1.2, 0.1, 32);const topMat = new THREE.MeshStandardMaterial({ color: 0xD2691E });const top = new THREE.Mesh(topGeo, topMat);top.position.y = 0.05;group.add(top);return group;}// 创建沙发几何体createSofaGeometry() {const group = new THREE.Group();// 底座const baseGeo = new THREE.BoxGeometry(2, 0.5, 1);const baseMat = new THREE.MeshStandardMaterial({ color: 0x8B0000 });const base = new THREE.Mesh(baseGeo, baseMat);base.position.y = -0.25;group.add(base);// 靠背const backGeo = new THREE.BoxGeometry(2, 1, 0.1);const backMat = new THREE.MeshStandardMaterial({ color: 0x8B0000 });const back = new THREE.Mesh(backGeo, backMat);back.position.set(0, 0.25, -0.45);group.add(back);return group;}// 创建床几何体createBedGeometry() {const group = new THREE.Group();// 床垫const mattressGeo = new THREE.BoxGeometry(2, 0.3, 1.5);const mattressMat = new THREE.MeshStandardMaterial({ color: 0x87CEEB });const mattress = new THREE.Mesh(mattressGeo, mattressMat);group.add(mattress);// 床头板const headboardGeo = new THREE.BoxGeometry(2, 0.8, 0.1);const headboardMat = new THREE.MeshStandardMaterial({ color: 0x8B4513 });const headboard = new THREE.Mesh(headboardGeo, headboardMat);headboard.position.set(0, 0.25, -0.7);group.add(headboard);return group;}// 创建台灯几何体createLampGeometry() {const group = new THREE.Group();// 灯座const baseGeo = new THREE.CylinderGeometry(0.2, 0.2, 0.1, 32);const baseMat = new THREE.MeshStandardMaterial({ color: 0x333333 });const base = new THREE.Mesh(baseGeo, baseMat);base.position.y = -0.45;group.add(base);// 灯杆const poleGeo = new THREE.CylinderGeometry(0.03, 0.03, 1, 8);const poleMat = new THREE.MeshStandardMaterial({ color: 0x666666 });const pole = new THREE.Mesh(poleGeo, poleMat);group.add(pole);// 灯罩const shadeGeo = new THREE.ConeGeometry(0.3, 0.5, 32);const shadeMat = new THREE.MeshStandardMaterial({ color: 0xFFFFFF,transparent: true,opacity: 0.8});const shade = new THREE.Mesh(shadeGeo, shadeMat);shade.position.y = 0.25;group.add(shade);return group;}// 创建材质createMaterial(type) {const materials = {chair: new THREE.MeshStandardMaterial({ color: 0xD2691E }),table: new THREE.MeshStandardMaterial({ color: 0x8B4513 }),sofa: new THREE.MeshStandardMaterial({ color: 0x8B0000 }),bed: new THREE.MeshStandardMaterial({ color: 0x87CEEB }),lamp: new THREE.MeshStandardMaterial({ color: 0xFFFFFF })};return materials[type] || new THREE.MeshStandardMaterial({ color: 0x6495ED });}// 删除对象removeObject(objectId) {const object = this.placedObjects.get(objectId);if (object) {this.scene.remove(object);this.placedObjects.delete(objectId);}}// 获取所有对象getAllObjects() {return Array.from(this.placedObjects.values());}// 渲染场景render() {this.renderer.render(this.scene, this.camera);}
}// AR跟踪管理器
class ARTrackingManager {constructor(videoElement, canvasElement) {this.video = videoElement;this.canvas = canvasElement;this.ctx = canvasElement.getContext('2d');this.isTracking = false;this.markerDetector = new SimpleMarkerDetector();this.poseEstimator = new PoseEstimator();}async start() {this.isTracking = true;try {const stream = await navigator.mediaDevices.getUserMedia({video: {width: 1280,height: 720,facingMode: 'environment'}});this.video.srcObject = stream;this.startTrackingLoop();} catch (error) {console.error('无法访问相机:', error);throw error;}}stop() {this.isTracking = false;if (this.video.srcObject) {this.video.srcObject.getTracks().forEach(track => track.stop());}}startTrackingLoop() {const processFrame = async () => {if (!this.isTracking) return;this.ctx.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);try {const markers = await this.markerDetector.detectMarkers(this.video);if (this.onMarkersDetected) {this.onMarkersDetected(markers);}} catch (error) {console.warn('标记检测失败:', error);}requestAnimationFrame(processFrame);};processFrame();}// 估计放置位置(简化版平面检测)estimatePlacementPosition(screenX, screenY) {// 在实际应用中,这里应该使用ARCore/ARKit的平面检测// 简化实现:在相机前方固定距离放置const distance = 2;const fov = 60 * Math.PI / 180;const aspect = this.video.videoWidth / this.video.videoHeight;const x = (screenX / this.canvas.width - 0.5) * distance * Math.tan(fov/2) * aspect;const y = -(screenY / this.canvas.height - 0.5) * distance * Math.tan(fov/2);return new THREE.Vector3(x, y, -distance);}
}export default {name: 'ARFurnitureApp',setup() {// 响应式状态const videoElement = ref(null);const arCanvas = ref(null);const isLoading = ref(true);const loadingProgress = ref(0);const trackingStatus = ref('searching');const interactionMode = ref('place');const selectedFurniture = ref(null);const selectedObject = ref(null);const currentCategory = ref('living');const isSaving = ref(false);const showInstruction = ref(true);// 家具数据const categories = ref([{ id: 'living', name: '客厅' },{ id: 'bedroom', name: '卧室' },{ id: 'dining', name: '餐厅' },{ id: 'decor', name: '装饰' }]);const furnitureItems = ref({living: [{ id: 'sofa', name: '沙发', thumbnail: '/thumbnails/sofa.png' },{ id: 'chair', name: '椅子', thumbnail: '/thumbnails/chair.png' },{ id: 'table', name: '茶几', thumbnail: '/thumbnails/table.png' },{ id: 'lamp', name: '台灯', thumbnail: '/thumbnails/lamp.png' }],bedroom: [{ id: 'bed', name: '床', thumbnail: '/thumbnails/bed.png' },{ id: 'nightstand', name: '床头柜', thumbnail: '/thumbnails/nightstand.png' }],dining: [{ id: 'dining_table', name: '餐桌', thumbnail: '/thumbnails/dining_table.png' },{ id: 'dining_chair', name: '餐椅', thumbnail: '/thumbnails/dining_chair.png' }],decor: [{ id: 'plant', name: '植物', thumbnail: '/thumbnails/plant.png' },{ id: 'vase', name: '花瓶', thumbnail: '/thumbnails/vase.png' }]});// 计算属性const currentFurniture = computed(() => furnitureItems.value[currentCategory.value] || []);const statusText = computed(() => {const texts = {searching: '寻找平面...',tracking: '跟踪中',placing: '放置模式',moving: '移动模式',rotating: '旋转模式'};return texts[trackingStatus.value] || '未知状态';});const statusIcon = computed(() => {const icons = {searching: '🔍',tracking: '✅',placing: '📍',moving: '🚀',rotating: '🔄'};return icons[trackingStatus.value] || '❓';});// AR系统实例let arTracker, sceneManager, renderer, camera;let animationFrameId;// 初始化const init = async () => {try {await initARSystem();await initScene();await startARTracking();isLoading.value = false;// 3秒后隐藏指引setTimeout(() => {showInstruction.value = false;}, 3000);} catch (error) {console.error('初始化失败:', error);trackingStatus.value = 'searching';}};// 初始化AR系统const initARSystem = async () => {// 初始化渲染器renderer = new THREE.WebGLRenderer({canvas: arCanvas.value,alpha: true,antialias: true});renderer.setSize(window.innerWidth, window.innerHeight);renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));// 初始化相机camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);camera.position.set(0, 1.6, 3);// 初始化场景管理器sceneManager = new ARFurnitureScene(renderer, camera);loadingProgress.value = 50;// 初始化AR跟踪arTracker = new ARTrackingManager(videoElement.value, arCanvas.value);arTracker.onMarkersDetected = handleMarkersDetected;loadingProgress.value = 100;};// 初始化场景const initScene = async () => {// 可以预加载一些资源或设置初始场景return new Promise(resolve => setTimeout(resolve, 500));};// 开始AR跟踪const startARTracking = async () => {await arTracker.start();trackingStatus.value = 'tracking';startRenderLoop();};// 渲染循环const startRenderLoop = () => {const animate = () => {animationFrameId = requestAnimationFrame(animate);sceneManager.render();};animate();};// 处理标记检测const handleMarkersDetected = (markers) => {if (markers.length > 0) {trackingStatus.value = 'tracking';} else {trackingStatus.value = 'searching';}};// 选择家具const selectFurniture = (item) => {selectedFurniture.value = item.id;interactionMode.value = 'place';trackingStatus.value = 'placing';};// 设置交互模式const setInteractionMode = (mode) => {interactionMode.value = mode;trackingStatus.value = mode;if (mode !== 'place') {selectedFurniture.value = null;}};// 放置家具const placeFurniture = async (position) => {if (!selectedFurniture.value) return;const rotation = new THREE.Vector3(0, 0, 0);const object = await sceneManager.addFurniture(selectedFurniture.value,position,rotation,1);selectedObject.value = object;selectedFurniture.value = null;interactionMode.value = 'move';trackingStatus.value = 'moving';};// 统一缩放const uniformScale = (event) => {if (selectedObject.value) {const scale = parseFloat(event.target.value);selectedObject.value.scale.set(scale, scale, scale);}};// 删除选中对象const deleteSelected = () => {if (selectedObject.value) {sceneManager.removeObject(selectedObject.value.userData.id);selectedObject.value = null;}};// 保存场景const saveScene = async () => {isSaving.value = true;try {const sceneData = {objects: sceneManager.getAllObjects().map(obj => ({id: obj.userData.id,type: obj.userData.modelType,position: obj.position.toArray(),rotation: [obj.rotation.x, obj.rotation.y, obj.rotation.z],scale: obj.scale.toArray()})),timestamp: new Date().toISOString()};localStorage.setItem('arFurnitureScene', JSON.stringify(sceneData));// 显示保存成功提示setTimeout(() => {isSaving.value = false;}, 1000);} catch (error) {console.error('保存场景失败:', error);isSaving.value = false;}};// 点击放置家具const handleCanvasClick = (event) => {if (interactionMode.value === 'place' && selectedFurniture.value) {const rect = arCanvas.value.getBoundingClientRect();const x = event.clientX - rect.left;const y = event.clientY - rect.top;const position = arTracker.estimatePlacementPosition(x, y);placeFurniture(position);}};onMounted(() => {init();arCanvas.value.addEventListener('click', handleCanvasClick);});onUnmounted(() => {if (arTracker) {arTracker.stop();}if (animationFrameId) {cancelAnimationFrame(animationFrameId);}arCanvas.value.removeEventListener('click', handleCanvasClick);});return {// 模板引用videoElement,arCanvas,// 状态数据isLoading,loadingProgress,trackingStatus,interactionMode,selectedFurniture,selectedObject,currentCategory,isSaving,showInstruction,categories,furnitureItems,// 计算属性currentFurniture,statusText,statusIcon,// 方法selectFurniture,setInteractionMode,uniformScale,deleteSelected,saveScene};}
};
</script><style scoped>
.ar-furniture-app {width: 100%;height: 100vh;background: #000;overflow: hidden;position: relative;
}.ar-viewport {width: 100%;height: 100%;position: relative;
}.camera-feed {width: 100%;height: 100%;object-fit: cover;
}.ar-overlay {position: absolute;top: 0;left: 0;width: 100%;height: 100%;pointer-events: auto;
}.ar-ui {position: absolute;top: 0;left: 0;width: 100%;height: 100%;pointer-events: none;
}.status-indicator {position: absolute;top: 20px;left: 20px;padding: 12px 20px;background: rgba(0, 0, 0, 0.7);color: white;border-radius: 25px;font-size: 14px;display: flex;align-items: center;gap: 8px;backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.2);pointer-events: none;
}.status-indicator.searching {background: rgba(255, 165, 0, 0.7);
}.status-indicator.tracking {background: rgba(0, 255, 0, 0.7);
}.status-indicator.placing {background: rgba(0, 150, 255, 0.7);
}.instruction {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);background: rgba(0, 0, 0, 0.8);color: white;padding: 20px 30px;border-radius: 15px;font-size: 16px;text-align: center;backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.3);pointer-events: none;
}.control-bar {position: absolute;bottom: 0;left: 0;width: 100%;background: rgba(45, 45, 45, 0.95);backdrop-filter: blur(20px);border-top: 1px solid rgba(255, 255, 255, 0.1);padding: 15px;
}.furniture-picker {margin-bottom: 15px;
}.category-tabs {display: flex;gap: 5px;margin-bottom: 15px;overflow-x: auto;
}.tab-button {padding: 8px 16px;background: #444;color: #ccc;border: none;border-radius: 20px;font-size: 14px;white-space: nowrap;cursor: pointer;transition: all 0.3s ease;
}.tab-button.active {background: #00a8ff;color: white;
}.furniture-grid {display: grid;grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));gap: 10px;max-height: 120px;overflow-y: auto;
}.furniture-item {display: flex;flex-direction: column;align-items: center;padding: 8px;background: #333;border-radius: 10px;cursor: pointer;transition: all 0.3s ease;border: 2px solid transparent;
}.furniture-item:hover {background: #444;
}.furniture-item.selected {background: #00a8ff;border-color: #00ffff;
}.item-thumb {width: 40px;height: 40px;object-fit: cover;border-radius: 5px;margin-bottom: 5px;
}.item-name {color: white;font-size: 12px;text-align: center;
}.action-controls {display: grid;grid-template-columns: repeat(5, 1fr);gap: 8px;
}.action-button {padding: 12px 8px;background: #444;color: white;border: none;border-radius: 8px;font-size: 12px;cursor: pointer;transition: all 0.3s ease;display: flex;align-items: center;justify-content: center;gap: 4px;
}.action-button:hover {background: #555;
}.action-button.active {background: #00a8ff;
}.action-button.delete {background: #ff4757;
}.action-button.save {background: #2ed573;
}.transform-panel {position: absolute;top: 80px;right: 20px;background: rgba(45, 45, 45, 0.9);padding: 15px;border-radius: 10px;backdrop-filter: blur(10px);border: 1px solid rgba(255, 255, 255, 0.1);min-width: 200px;
}.transform-controls {display: flex;flex-direction: column;gap: 15px;
}.control-group {display: flex;flex-direction: column;gap: 8px;
}.control-group label {color: #00ffff;font-size: 14px;font-weight: bold;
}.vector-controls {display: grid;grid-template-columns: 1fr 1fr 1fr;gap: 5px;
}.vector-controls input {padding: 6px;background: #333;border: 1px solid #555;border-radius: 4px;color: white;font-size: 12px;text-align: center;
}.vector-controls input::placeholder {color: #888;
}.control-group input[type="range"] {width: 100%;
}.loading-overlay {position: absolute;top: 0;left: 0;width: 100%;height: 100%;background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);display: flex;justify-content: center;align-items: center;z-index: 1000;
}.loading-content {text-align: center;color: white;
}.spinner {width: 50px;height: 50px;border: 4px solid rgba(255, 255, 255, 0.3);border-top: 4px solid #00ffff;border-radius: 50%;animation: spin 1s linear infinite;margin: 0 auto 20px;
}.loading-content h3 {margin: 0 0 10px 0;color: white;
}.loading-content p {margin: 0;color: #00ffff;font-size: 18px;font-weight: bold;
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}/* 响应式设计 */
@media (max-width: 768px) {.action-controls {grid-template-columns: repeat(3, 1fr);}.action-button.save {grid-column: 1 / -1;}.transform-panel {position: relative;top: auto;right: auto;margin: 10px;width: calc(100% - 20px);}.furniture-grid {grid-template-columns: repeat(4, 1fr);}
}@media (max-width: 480px) {.category-tabs {font-size: 12px;}.tab-button {padding: 6px 12px;}.action-button {font-size: 11px;padding: 10px 6px;}.furniture-grid {grid-template-columns: repeat(3, 1fr);}
}
</style>

应用特性

核心功能

  1. 实时AR跟踪 - 基于标记或平面检测的稳定跟踪
  2. 家具模型库 - 分类整理的3D家具模型
  3. 直观放置 - 点击屏幕即可放置选中家具
  4. 精细控制 - 支持位置、旋转、缩放的精确调整
  5. 场景持久化 - 本地保存和恢复家具布局

交互体验

  • 智能状态提示 - 清晰显示当前AR跟踪状态
  • 视觉反馈 - 选中高亮和操作确认
  • 渐进式指引 - 新用户友好引导
  • 响应式布局 - 适配不同屏幕尺寸

技术亮点

  • 模块化架构 - 清晰的职责分离
  • 性能优化 - 高效的渲染和资源管理
  • 错误处理 - 健壮的异常处理机制
  • 扩展性强 - 易于添加新家具和功能

本节通过完整的AR家具摆放应用,展示了如何将前面学习的AR技术、3D渲染和交互设计整合到实用的商业应用中。这种类型的应用在家具零售、室内设计等领域具有广泛的应用前景。

http://www.dtcms.com/a/618196.html

相关文章:

  • 建设网站流程2022年最新新闻播报稿件
  • 网站地图的作用长沙网站开发设计
  • 【读代码】最新端侧TTS模型NeuTTS-Air
  • 做装修网站多少钱四川成都住建局官网
  • Microsoft 远程桌面app,支持挂机宝,云主机服务器
  • 基于MATLAB的粒子群优化(PSO)算法对25杆桁架结构进行优化设计
  • 智能驾驶:从感知到规控的自动驾驶系统全解析
  • 练习项目:基于 LangGraph 和 MCP 服务器的本地语音助手
  • 在 VMware 的 Ubuntu 22.04 虚拟机和 Windows 主机之间设置共享剪贴板
  • 淄博专业网站建设哪家专业公司装修设计工程
  • 金融网站的设计中和阗盛工程建设有限公司网站
  • 《JavaScript基础-Day.4》笔记总结
  • 关于C++中的预编译指令
  • 做网站的重要性深圳程序开发
  • 其他落地手册:facebook实现与音视频剖析
  • 建站方法移动课程播放网站建设多少钱
  • ZJUCTF2025(预赛+决赛)-我的writeup
  • 2025.11.16 AI快讯
  • Java分治算法题目练习(快速/归并排序)
  • Python 生信进阶:Biopython 库完全指南(序列处理 + 数据库交互)
  • 基于单片机的功率因数校正与无功补偿系统设计
  • 【计算机网络笔记】第六章 数据链路层
  • 网站开发工作前景电商哪个平台销量最好
  • 正规的网站建设官网动漫设计难不难
  • 运行,暂停,检查:探索如何使用LLDB进行有效调试
  • YOLOv8交通信号灯检测
  • asp.net企业网站管理系统工厂型企业做网站
  • linux gpib 驱动
  • 中壹建设工程有限公司官方网站搜索引擎实训心得体会
  • 公司做个网站学网站开发的书