diff --git a/ScreenShot_2026-05-13_115939_146.png b/ScreenShot_2026-05-13_115939_146.png new file mode 100644 index 0000000..1d3fadb Binary files /dev/null and b/ScreenShot_2026-05-13_115939_146.png differ diff --git a/ScreenShot_2026-05-13_115950_310.png b/ScreenShot_2026-05-13_115950_310.png new file mode 100644 index 0000000..b73d13a Binary files /dev/null and b/ScreenShot_2026-05-13_115950_310.png differ diff --git a/ScreenShot_2026-05-13_120444_480.png b/ScreenShot_2026-05-13_120444_480.png new file mode 100644 index 0000000..6efd974 Binary files /dev/null and b/ScreenShot_2026-05-13_120444_480.png differ diff --git a/index.html b/index.html index 5ec68e8..4114dcc 100644 --- a/index.html +++ b/index.html @@ -355,7 +355,7 @@ - + @@ -513,8 +513,7 @@ document.querySelector('#hotspot-btn').addEventListener('click', async function () { - console.log(11111111111111); - + await hotspotRequest(); }) const skuToFunc = async (currentText) => { @@ -526,7 +525,7 @@ if (result.code === 200 && result.data) { console.log('SKU配置数据:', result.data); console.log('关联事件:', result.data.events); - + placementWall(1); // 使用 for...of 循环以支持 await for (const event of result.data.events) { if (event.event_type === 'change_model') { @@ -738,10 +737,10 @@ const materialName = window.getCurrentMaterialName(); if (materialName) { console.log('切换为白色,材质名:', materialName); - kernel.material.apply({ - target: materialName, - albedoColor: '#FFFFFF', - }); + kernel.material.apply({ + target: materialName, + albedoColor: '#FFFFFF', + }); } else { console.log('没有选中材质'); } @@ -752,10 +751,10 @@ const materialName = window.getCurrentMaterialName(); if (materialName) { console.log('切换为黑色,材质名:', materialName); - kernel.material.apply({ - target: materialName, - albedoColor: '#000000', - }); + kernel.material.apply({ + target: materialName, + albedoColor: '#000000', + }); } else { console.log('没有选中材质'); } @@ -821,51 +820,8 @@ let dropZoneVisible = false; document.getElementById('dropzone-btn').addEventListener('click', () => { if (!dropZoneVisible) { - // 先清除旧的放置区域 - kernel.dropZone.clearAll(); - // 生成新的放置区域(使用新的墙面参数化API) - kernel.dropZone.generate({ - walls: [ - { - name: 'front', - startPoint: [-1.4, 0, -1.4], - endPoint: [1.4, 0, -1.4], - height: 2.3, - divisions: 5 - }, - { - name: 'back', - startPoint: [50, 0, 50], - endPoint: [-50, 0, 50], - height: 30, - divisions: 5 - }, - { - name: 'left', - startPoint: [-50, 0, 50], - endPoint: [-50, 0, -50], - height: 30, - divisions: 4 - }, - { - name: 'right', - startPoint: [50, 0, -50], - endPoint: [50, 0, 50], - height: 30, - divisions: 4 - } - ], - color: "#21c7ff", - alpha: 0.3, - thickness: 2, - showBorder: true, - borderColor: "#ffffff" - }); - // 显示放置区域 - kernel.dropZone.showAll(); - dropZoneVisible = true; // 更新按钮文字 document.getElementById('dropzone-btn').textContent = '隐藏放置区域'; @@ -880,6 +836,76 @@ console.log('已隐藏放置区域'); } }); + + const placementWall = (divisions) => { + + // 先清除旧的放置区域 + kernel.dropZone.clearAll(); + + // 生成新的放置区域(使用新的墙面参数化API) + // 调整 baseY 来控制整体高度(正数向上,负数向下) + const baseY = 0.08; // 修改这个值来调整整体高度 + + // 调整 offset 来控制每个面向外或向内的偏移 + // 正数 = 向外移动,负数 = 向内移动 + const wallOffset = -0.07; // 修改这个值来调整墙面偏移 + + kernel.dropZone.generate({ + walls: [ + { + name: 'front', + startPoint: [-1.43, baseY, -1.4], + endPoint: [1.37, baseY, -1.4], + height: 2.2, + divisions: divisions, + offset: wallOffset // 向外或向内偏移 + }, + { + name: 'back', + startPoint: [1.37, baseY, 1.4], + endPoint: [-1.43, baseY, 1.4], + height: 2.2, + divisions: divisions, + offset: wallOffset + }, + { + name: 'left', + startPoint: [-1.43, baseY, 1.39], + endPoint: [-1.43, baseY, -1.43], + height: 2.2, + divisions: divisions, + offset: wallOffset + }, + { + name: 'right', + startPoint: [1.37, baseY, -1.43], + endPoint: [1.37, baseY, 1.4], + height: 2.2, + divisions: divisions, + offset: wallOffset + } + ], + color: "#21c7ff", + alpha: 0.3, + thickness: 2, + showBorder: true, + borderColor: "#ffffff" + }); + + // 显示放置区域 + kernel.dropZone.showAll(); + dropZoneVisible = true; + } + + + // 监听放置区域点击事件 + kernel.on('dropzone:click', (data) => { + console.log('点击了放置区域:', data); + console.log('中心点:', data.center); + console.log('宽度:', data.width); + console.log('高度:', data.height); + console.log('法线:', data.normal); + }); diff --git a/index.js b/index.js index 036f76b..a15345e 100644 --- a/index.js +++ b/index.js @@ -28,18 +28,18 @@ const config = { kernel.init(config); - const response = await fetch('http://localhost:3001/api/models/auto-load/list') - const data = await response.json() - const models = data.data // 这就是模型列表 - console.log(models); - - models.forEach(model => { +const response = await fetch('http://localhost:3001/api/models/auto-load/list') +const data = await response.json() +const models = data.data // 这就是模型列表 +console.log(models); + +models.forEach(model => { kernel.model.add({ - modelId: model.id, - modelUrl: model.file_url || `http://localhost:3001${model.file_path}`, - modelControlType: model.model_control_type, + modelId: model.id, + modelUrl: model.file_url || `http://localhost:3001${model.file_path}`, + modelControlType: model.model_control_type, }); - }) +}) // kernel.model.add({ // modelId: "框架", diff --git a/src/babylonjs/AppCamera.ts b/src/babylonjs/AppCamera.ts index f227a93..76ee402 100644 --- a/src/babylonjs/AppCamera.ts +++ b/src/babylonjs/AppCamera.ts @@ -25,7 +25,7 @@ export class AppCamera extends Monobehiver { this.object = new ArcRotateCamera('Camera', Tools.ToRadians(70), Tools.ToRadians(85), 5, new Vector3(0, 2, 0), scene); this.object.attachControl(canvas, true); this.object.minZ = 0.01; // 近裁剪面 - // this.object.wheelPrecision =999999; // 滚轮缩放精度 + this.object.wheelPrecision =200; // 滚轮缩放精度 this.object.panningSensibility = 0; // 限制垂直角范围,实现上帝视角 diff --git a/src/babylonjs/AppDropZone.ts b/src/babylonjs/AppDropZone.ts index b4bedf0..6c8f6c3 100644 --- a/src/babylonjs/AppDropZone.ts +++ b/src/babylonjs/AppDropZone.ts @@ -54,6 +54,13 @@ export class AppDropZone { return this.placementWall.getZone(wallName, index); } + /** + * 设置点击回调 + */ + setOnZoneClick(callback: (zoneInfo: PlacementZoneInfo) => void): void { + this.placementWall.setOnZoneClick(callback); + } + /** * 显示所有放置区域 */ diff --git a/src/babylonjs/AppPlacementWall.ts b/src/babylonjs/AppPlacementWall.ts index e8ebe3a..39fccaf 100644 --- a/src/babylonjs/AppPlacementWall.ts +++ b/src/babylonjs/AppPlacementWall.ts @@ -1,4 +1,4 @@ -import { Scene, Mesh, MeshBuilder, StandardMaterial, Color3, Vector3 } from '@babylonjs/core'; +import { Scene, Mesh, MeshBuilder, StandardMaterial, Color3, Vector3, ActionManager, ExecuteCodeAction } from '@babylonjs/core'; /** * 墙面配置 - 定义一个垂直面的起始和结束坐标 @@ -9,6 +9,7 @@ export interface WallConfig { endPoint: Vector3; // 结束点坐标(右下角) height: number; // 墙面高度 divisions: number; // 分割数(将这个面分成几块) + offset?: number; // 偏移量(正数向外,负数向内,默认0) } /** @@ -40,6 +41,7 @@ export class AppPlacementWall { private scene: Scene; private placementZones: PlacementZoneInfo[] = []; private borderLines: Mesh[] = []; + private onZoneClickCallback?: (zoneInfo: PlacementZoneInfo) => void; constructor(scene: Scene) { this.scene = scene; @@ -83,7 +85,7 @@ export class AppPlacementWall { material: StandardMaterial, thickness: number ): PlacementZoneInfo[] { - const { name, startPoint, endPoint, height, divisions } = wall; + const { name, startPoint, endPoint, height, divisions, offset = 0 } = wall; // 计算墙面的方向向量和长度 const direction = endPoint.subtract(startPoint); @@ -96,15 +98,18 @@ export class AppPlacementWall { // 计算墙面的法线方向(垂直于墙面,指向外侧) const normal = new Vector3(-normalizedDir.z, 0, normalizedDir.x); + // 应用偏移量(沿着法线方向) + const offsetVector = normal.scale(offset); + const zones: PlacementZoneInfo[] = []; for (let i = 0; i < divisions; i++) { // 计算当前块的中心位置 - const offset = blockWidth * (i + 0.5); - const centerX = startPoint.x + normalizedDir.x * offset; + const offset_i = blockWidth * (i + 0.5); + const centerX = startPoint.x + normalizedDir.x * offset_i; const centerY = startPoint.y + height / 2; - const centerZ = startPoint.z + normalizedDir.z * offset; - const center = new Vector3(centerX, centerY, centerZ); + const centerZ = startPoint.z + normalizedDir.z * offset_i; + const center = new Vector3(centerX, centerY, centerZ).add(offsetVector); // 创建放置区域平面 const plane = MeshBuilder.CreatePlane( @@ -127,6 +132,28 @@ export class AppPlacementWall { // 启用拾取 plane.isPickable = true; + // 添加点击事件 + plane.actionManager = new ActionManager(this.scene); + plane.actionManager.registerAction( + new ExecuteCodeAction( + ActionManager.OnPickTrigger, + () => { + const zoneInfo: PlacementZoneInfo = { + mesh: plane, + wallName: name, + index: i, + center: center.clone(), + width: blockWidth, + height: height, + normal: normal.clone() + }; + if (this.onZoneClickCallback) { + this.onZoneClickCallback(zoneInfo); + } + } + ) + ); + // 为每个块创建边框 this.createBlockBorder(name, i, center, blockWidth, height, normalizedDir, normal); @@ -204,13 +231,21 @@ export class AppPlacementWall { * 创建墙面边框 */ private createWallBorder(wall: WallConfig, color: string): void { - const { name, startPoint, endPoint, height } = wall; + const { name, startPoint, endPoint, height, offset = 0 } = wall; - // 计算四个角点 - const bottomLeft = startPoint.clone(); - const bottomRight = endPoint.clone(); - const topLeft = new Vector3(startPoint.x, startPoint.y + height, startPoint.z); - const topRight = new Vector3(endPoint.x, endPoint.y + height, endPoint.z); + // 计算法线方向 + const direction = endPoint.subtract(startPoint); + const normalizedDir = direction.normalize(); + const normal = new Vector3(-normalizedDir.z, 0, normalizedDir.x); + + // 应用偏移量 + const offsetVector = normal.scale(offset); + + // 计算四个角点(应用偏移) + const bottomLeft = startPoint.clone().add(offsetVector); + const bottomRight = endPoint.clone().add(offsetVector); + const topLeft = new Vector3(startPoint.x, startPoint.y + height, startPoint.z).add(offsetVector); + const topRight = new Vector3(endPoint.x, endPoint.y + height, endPoint.z).add(offsetVector); // 创建四条边 const edges = [ @@ -240,7 +275,8 @@ export class AppPlacementWall { private createMaterial(color: string, alpha: number): StandardMaterial { const material = new StandardMaterial('placementMaterial', this.scene); const rgb = this.hexToRgb(color); - material.diffuseColor = new Color3(rgb.r, rgb.g, rgb.b); + material.emissiveColor = new Color3(rgb.r, rgb.g, rgb.b); + material.disableLighting = true; material.alpha = alpha; material.backFaceCulling = false; return material; @@ -283,6 +319,13 @@ export class AppPlacementWall { ); } + /** + * 设置点击回调 + */ + setOnZoneClick(callback: (zoneInfo: PlacementZoneInfo) => void): void { + this.onZoneClickCallback = callback; + } + /** * 显示所有放置区域 */ diff --git a/src/babylonjs/AppRay.ts b/src/babylonjs/AppRay.ts index 30bb075..ecb9507 100644 --- a/src/babylonjs/AppRay.ts +++ b/src/babylonjs/AppRay.ts @@ -87,6 +87,24 @@ class AppRay extends Monobehiver { return; } + // 检查是否点击的是放置区域 + if (pickInfo.pickedMesh.name.startsWith('placement_')) { + const zones = this.mainApp.appDropZone.getPlacementZones(); + const clickedZone = zones.find(zone => zone.mesh === pickInfo.pickedMesh); + if (clickedZone) { + EventBridge.dropZoneClick({ + wallName: clickedZone.wallName, + index: clickedZone.index, + center: clickedZone.center, + width: clickedZone.width, + height: clickedZone.height, + normal: clickedZone.normal, + mesh: clickedZone.mesh + }); + return; + } + } + this.mainApp.appDomTo3D.hideAll() const materialName = pickInfo.pickedMesh.material?.name || ''; diff --git a/src/event/bridge.ts b/src/event/bridge.ts index fd91f69..5135df0 100644 --- a/src/event/bridge.ts +++ b/src/event/bridge.ts @@ -5,7 +5,8 @@ import { ModelLoadErrorPayload, ModelLoadProgressPayload, SceneReadyPayload, - HotspotClickPayload + HotspotClickPayload, + DropZoneClickPayload } from './types'; /** @@ -39,6 +40,10 @@ export class EventBridge { return emit("hotspot:click", payload); } + static dropZoneClick(payload: DropZoneClickPayload): Emitter { + return emit("dropzone:click", payload); + } + // Listeners static onModelLoadProgress(callback: (payload: ModelLoadProgressPayload) => void, context?: unknown): Emitter { return on("model:load:progress", callback, context); @@ -65,6 +70,11 @@ export class EventBridge { static onHotspotClick(callback: (payload: HotspotClickPayload) => void, context?: unknown): Emitter { return on("hotspot:click", callback, context); } + + static onDropZoneClick(callback: (payload: DropZoneClickPayload) => void, context?: unknown): Emitter { + return on("dropzone:click", callback, context); + } + static onceSceneReady(callback: (payload: SceneReadyPayload) => void, context?: unknown): Emitter { return once("scene:ready", callback, context); } diff --git a/src/event/types.ts b/src/event/types.ts index 1232285..fc3dd68 100644 --- a/src/event/types.ts +++ b/src/event/types.ts @@ -46,3 +46,13 @@ export type HotspotClickPayload = { meshName?: string; payload?: unknown; }; + +export type DropZoneClickPayload = { + wallName: string; + index: number; + center: any; + width: number; + height: number; + normal: any; + mesh: any; +};