增加地面,增加放置区域筛选逻辑
This commit is contained in:
BIN
ScreenShot_2026-05-20_195151_973.png
Normal file
BIN
ScreenShot_2026-05-20_195151_973.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
BIN
ScreenShot_2026-05-21_094326_711.png
Normal file
BIN
ScreenShot_2026-05-21_094326_711.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 520 KiB |
69
index.html
69
index.html
@ -15,7 +15,7 @@
|
|||||||
body {
|
body {
|
||||||
font-family: Arial, sans-serif;
|
font-family: Arial, sans-serif;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
background-color: rgb(255, 255, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
@ -336,8 +336,10 @@
|
|||||||
<!-- 标签页容器 -->
|
<!-- 标签页容器 -->
|
||||||
<div class="tabs-container">
|
<div class="tabs-container">
|
||||||
<div class="tabs-header">
|
<div class="tabs-header">
|
||||||
<button class="tab-btn active" data-tab="size-1013">10x13 尺寸</button>
|
<button class="tab-btn active" data-tab="size-1013">10x13</button>
|
||||||
<button class="tab-btn" data-tab="size-1010">10x10 尺寸</button>
|
<button class="tab-btn" data-tab="size-1010">10x10</button>
|
||||||
|
<button class="tab-btn" data-tab="size-1020">10x20</button>
|
||||||
|
<button class="tab-btn" data-tab="size-1012">10x12</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 10x13 尺寸配置 -->
|
<!-- 10x13 尺寸配置 -->
|
||||||
@ -367,8 +369,6 @@
|
|||||||
<div class="option-group">
|
<div class="option-group">
|
||||||
<button class="option-btn" data-option="color-1">SPFPDS13FTW</button>
|
<button class="option-btn" data-option="color-1">SPFPDS13FTW</button>
|
||||||
<button class="option-btn" data-option="color-2">SPFPDS13FTC</button>
|
<button class="option-btn" data-option="color-2">SPFPDS13FTC</button>
|
||||||
<button class="option-btn" data-option="color-3">SPFPDS10FTW</button>
|
|
||||||
<button class="option-btn" data-option="color-4">SPFPDS10FTC</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -405,6 +405,65 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 10x20 尺寸配置 -->
|
||||||
|
<div class="tab-content" id="tab-size-1020">
|
||||||
|
<!-- 棚子尺寸 -->
|
||||||
|
<div class="config-category">
|
||||||
|
<div class="category-header active" data-category="size-1020">
|
||||||
|
<span class="category-title">棚子尺寸</span>
|
||||||
|
<span class="category-arrow expanded">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content expanded">
|
||||||
|
<div class="option-group">
|
||||||
|
<button class="option-btn" data-option="size-7">SPF111S1020C</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 百叶 -->
|
||||||
|
<div class="config-category">
|
||||||
|
<div class="category-header active" data-category="louver-1020">
|
||||||
|
<span class="category-title">百叶</span>
|
||||||
|
<span class="category-arrow expanded">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content expanded">
|
||||||
|
<div class="option-group">
|
||||||
|
<button class="option-btn" data-option="color-5">SPFPDS10FTW</button>
|
||||||
|
<button class="option-btn" data-option="color-6">SPFPDS10FTC</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 10x12 尺寸配置 -->
|
||||||
|
<div class="tab-content" id="tab-size-1012">
|
||||||
|
<!-- 棚子尺寸 -->
|
||||||
|
<div class="config-category">
|
||||||
|
<div class="category-header active" data-category="size-1012">
|
||||||
|
<span class="category-title">棚子尺寸</span>
|
||||||
|
<span class="category-arrow expanded">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content expanded">
|
||||||
|
<div class="option-group">
|
||||||
|
<button class="option-btn" data-option="size-10">SPF88S1012C</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 百叶 -->
|
||||||
|
<div class="config-category">
|
||||||
|
<div class="category-header active" data-category="louver-1012">
|
||||||
|
<span class="category-title">百叶</span>
|
||||||
|
<span class="category-arrow expanded">▼</span>
|
||||||
|
</div>
|
||||||
|
<div class="category-content expanded">
|
||||||
|
<div class="option-group">
|
||||||
|
<button class="option-btn" data-option="color-7">SPF80CS12FTC</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="hotspot-btn">生成热点</button>
|
<button id="hotspot-btn">生成热点</button>
|
||||||
|
|||||||
72
index.js
72
index.js
@ -22,6 +22,10 @@ export const initApp = (kernel) => {
|
|||||||
return kernelInstance;
|
return kernelInstance;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//全局唯一棚子sku
|
||||||
|
let pergolaSku = ""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前 kernel 实例
|
* 获取当前 kernel 实例
|
||||||
*/
|
*/
|
||||||
@ -64,8 +68,6 @@ export const getAutoLoadModelList = async () => {
|
|||||||
const kernel = getKernel();
|
const kernel = getKernel();
|
||||||
|
|
||||||
const url = apiConfig.getApiUrl('/api/models/auto-load/list')
|
const url = apiConfig.getApiUrl('/api/models/auto-load/list')
|
||||||
console.log('API URL:', url)
|
|
||||||
console.log('apiConfig:', apiConfig)
|
|
||||||
const response = await fetch(url)
|
const response = await fetch(url)
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
const models = data.data // 这就是模型列表
|
const models = data.data // 这就是模型列表
|
||||||
@ -74,6 +76,7 @@ export const getAutoLoadModelList = async () => {
|
|||||||
|
|
||||||
if (model.placement_zone) {
|
if (model.placement_zone) {
|
||||||
const { alpha, border_color, color, show_border, thickness, walls } = model.placement_zone
|
const { alpha, border_color, color, show_border, thickness, walls } = model.placement_zone
|
||||||
|
|
||||||
kernel.dropZone.setData({
|
kernel.dropZone.setData({
|
||||||
|
|
||||||
color: color,
|
color: color,
|
||||||
@ -98,7 +101,53 @@ export const getAutoLoadModelList = async () => {
|
|||||||
//获取放置区域
|
//获取放置区域
|
||||||
export const getPlacementZone = async (sku) => {
|
export const getPlacementZone = async (sku) => {
|
||||||
const kernel = getKernel();
|
const kernel = getKernel();
|
||||||
|
console.log(pergolaSku, sku);
|
||||||
|
let division_include = []
|
||||||
|
|
||||||
|
// 同时包含10和13
|
||||||
|
const only10_13 = /(?=.*10)(?=.*13)/.test(pergolaSku)
|
||||||
|
// 只包含10 无13 无12
|
||||||
|
const only10 = /(?=.*10)(?!.*13)(?!.*12)/.test(pergolaSku)
|
||||||
|
// 同时包含10和12
|
||||||
|
const only10_12 = /(?=.*10)(?=.*12)/.test(pergolaSku)
|
||||||
|
|
||||||
|
|
||||||
|
// 1. 只要字符串里包含 10,就返回 true
|
||||||
|
const has10 = /10/.test(sku);
|
||||||
|
|
||||||
|
// 2. 只要字符串里包含 13,就返回 true
|
||||||
|
const has13 = /13/.test(sku);
|
||||||
|
|
||||||
|
// 2. 只要字符串里包含 12,就返回 true
|
||||||
|
const has12 = /12/.test(sku);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//包含10,不包含13
|
||||||
|
if (only10 && has10) {
|
||||||
|
console.log('棚子包含10,不包含13 并且配件是10 说明是正方体 或者是10*20的');
|
||||||
|
division_include.push('前', '后', '左', '右', "前1", "后1", "前2", "后2")
|
||||||
|
}
|
||||||
|
//同时包含10和13
|
||||||
|
if (only10_13 && has10) {
|
||||||
|
console.log('棚子同时包10和13的并且含配件是10');
|
||||||
|
division_include.push('左', '右')
|
||||||
|
}
|
||||||
|
//同时包含10和13
|
||||||
|
if (only10_13 && has13) {
|
||||||
|
console.log('棚子同时包10和13的并且含配件是13');
|
||||||
|
division_include.push('前', '后')
|
||||||
|
}
|
||||||
|
if (only10_12 && has12) {
|
||||||
|
console.log('棚子同时包10和12的并且含配件是12');
|
||||||
|
division_include.push('前', '后')
|
||||||
|
}
|
||||||
|
if (only10_12 && has10) {
|
||||||
|
console.log('棚子同时包10和12的并且含配件是12');
|
||||||
|
division_include.push('左', '右')
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const response = await fetch(apiConfig.getApiUrl(`/api/product-configs/by-sku/${sku}`));
|
const response = await fetch(apiConfig.getApiUrl(`/api/product-configs/by-sku/${sku}`));
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
if (result.code === 200) {
|
if (result.code === 200) {
|
||||||
@ -107,10 +156,12 @@ export const getPlacementZone = async (sku) => {
|
|||||||
// const {position_x, position_y, position_z} = data;
|
// const {position_x, position_y, position_z} = data;
|
||||||
if (enable_placement_zone && wall_divisions != undefined) {
|
if (enable_placement_zone && wall_divisions != undefined) {
|
||||||
|
|
||||||
|
const filteredDivisions = wall_divisions.filter(item => division_include.includes(item.name))
|
||||||
|
console.log(filteredDivisions);
|
||||||
// 只清除旧的放置区域网格,不清除模型
|
// 只清除旧的放置区域网格,不清除模型
|
||||||
kernel.dropZone.clearZones();
|
kernel.dropZone.clearZones();
|
||||||
const divisions = wall_divisions.map(wall => ({
|
const divisions = filteredDivisions.map(wall => ({
|
||||||
name: wall.name, // 获取最后一个下划线后的部分
|
name: wall.name,
|
||||||
divisions: wall.divisions
|
divisions: wall.divisions
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -248,12 +299,12 @@ export const executeEvent2 = async (result, sku) => {
|
|||||||
kernel.model.removeAll();
|
kernel.model.removeAll();
|
||||||
// 清除所有 SKU 映射
|
// 清除所有 SKU 映射
|
||||||
clearAllSkuMappings();
|
clearAllSkuMappings();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先处理所有 change_model 事件
|
// 先处理所有 change_model 事件
|
||||||
for (const event of result.data.events) {
|
for (const event of result.data.events) {
|
||||||
console.log(event);
|
console.log(event);
|
||||||
|
|
||||||
if (event.event_type === 'change_model') {
|
if (event.event_type === 'change_model') {
|
||||||
const { target_data } = event;
|
const { target_data } = event;
|
||||||
console.log(event.target_data);
|
console.log(event.target_data);
|
||||||
@ -359,11 +410,18 @@ export const getProductConfig = async (sku) => {
|
|||||||
if (result.code === 200) {
|
if (result.code === 200) {
|
||||||
console.log(result.data);
|
console.log(result.data);
|
||||||
const { enable_placement_zone } = result.data;
|
const { enable_placement_zone } = result.data;
|
||||||
// await initPlacementZoneConfig();
|
//如果触发的是配件,需要显示放置区域
|
||||||
|
|
||||||
if (enable_placement_zone) {
|
if (enable_placement_zone) {
|
||||||
|
if (pergolaSku === "") {
|
||||||
|
console.error("请先加载棚子模型")
|
||||||
|
return;
|
||||||
|
}
|
||||||
getPlacementZone(sku)
|
getPlacementZone(sku)
|
||||||
}
|
}
|
||||||
|
//如果触发的是换棚子模型
|
||||||
else {
|
else {
|
||||||
|
pergolaSku = sku;
|
||||||
executeEvent2(result, sku)
|
executeEvent2(result, sku)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
package-lock.json
generated
10
package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babylonjs/core": "^9.3.1",
|
"@babylonjs/core": "^9.3.1",
|
||||||
"@babylonjs/loaders": "^9.3.1",
|
"@babylonjs/loaders": "^9.3.1",
|
||||||
|
"@babylonjs/materials": "^9.8.0",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"js-md5": "^0.8.3",
|
"js-md5": "^0.8.3",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
@ -38,6 +39,15 @@
|
|||||||
"babylonjs-gltf2interface": "^9.0.0"
|
"babylonjs-gltf2interface": "^9.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babylonjs/materials": {
|
||||||
|
"version": "9.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babylonjs/materials/-/materials-9.8.0.tgz",
|
||||||
|
"integrity": "sha512-BqAUtI5QwoN1a/UdcEy1p83rL1vG30uPDnPDRkqtclFl4tWS6O2SEYb770/v2ekgDv4v0BwQvCIgfl2QjRijfw==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@babylonjs/core": "^9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babylonjs/core": "^9.3.1",
|
"@babylonjs/core": "^9.3.1",
|
||||||
"@babylonjs/loaders": "^9.3.1",
|
"@babylonjs/loaders": "^9.3.1",
|
||||||
|
"@babylonjs/materials": "^9.8.0",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
"js-md5": "^0.8.3",
|
"js-md5": "^0.8.3",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
|
|||||||
@ -31,8 +31,8 @@ export class AppCamera extends Monobehiver {
|
|||||||
// 限制垂直角范围,实现上帝视角
|
// 限制垂直角范围,实现上帝视角
|
||||||
this.object.upperBetaLimit = Tools.ToRadians(90); // 最大垂直角(接近90度,避免万向锁)
|
this.object.upperBetaLimit = Tools.ToRadians(90); // 最大垂直角(接近90度,避免万向锁)
|
||||||
|
|
||||||
this.object.position = new Vector3(-0, 10, 0);
|
this.object.position = new Vector3(0, 10, 0);
|
||||||
this.setTarget(0, 2, 0);
|
this.setTarget(0, 0.5, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 设置相机目标点 */
|
/** 设置相机目标点 */
|
||||||
|
|||||||
@ -123,29 +123,19 @@ export class AppDropZone {
|
|||||||
'右': ['右', 'right', 'you']
|
'右': ['右', 'right', 'you']
|
||||||
};
|
};
|
||||||
|
|
||||||
// 匹配墙面名称(支持精确匹配和模糊匹配)
|
// 匹配墙面名称(精确匹配)
|
||||||
const matchWallName = (wallName: string): number | null => {
|
const matchWallName = (wallName: string): number | null => {
|
||||||
// 1. 优先精确匹配完整墙面名称
|
// 提取墙面名称的最后部分(最后一个下划线之后)
|
||||||
// 例如:wall.name = "80全铁3x6_前1",divisions 中有 "前1"
|
// 例如:"SW10000070_10x20星空篷_前1" → "前1"
|
||||||
for (const [configName, divisionValue] of Object.entries(divisionsMap)) {
|
const wallShortName = wallName.split('_').pop() || wallName;
|
||||||
if (wallName.includes(configName)) {
|
|
||||||
return divisionValue;
|
// 精确匹配提取出的简短名称
|
||||||
}
|
if (divisionsMap[wallShortName] !== undefined) {
|
||||||
}
|
console.log(`墙面 "${wallName}" 通过简短名称 "${wallShortName}" 精确匹配到分割数: ${divisionsMap[wallShortName]}`);
|
||||||
|
return divisionsMap[wallShortName];
|
||||||
// 2. 如果精确匹配失败,尝试方向模糊匹配(兼容旧版)
|
|
||||||
const lowerName = wallName.toLowerCase();
|
|
||||||
for (const [direction, keywords] of Object.entries(directionKeywords)) {
|
|
||||||
for (const keyword of keywords) {
|
|
||||||
if (lowerName.includes(keyword.toLowerCase()) || wallName.includes(keyword)) {
|
|
||||||
// 检查 divisionsMap 中是否有该方向的配置
|
|
||||||
if (divisionsMap[direction] !== undefined) {
|
|
||||||
return divisionsMap[direction];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`墙面 "${wallName}" 未匹配到任何分割数配置`);
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
220
src/babylonjs/AppGround.ts
Normal file
220
src/babylonjs/AppGround.ts
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
|
||||||
|
import { Mesh } from '@babylonjs/core/Meshes/mesh';
|
||||||
|
import { PBRMaterial } from '@babylonjs/core/Materials/PBR/pbrMaterial';
|
||||||
|
import { GridMaterial } from '@babylonjs/materials/grid/gridMaterial';
|
||||||
|
import { Texture } from '@babylonjs/core/Materials/Textures/texture';
|
||||||
|
import { Color3 } from '@babylonjs/core/Maths/math.color';
|
||||||
|
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
|
||||||
|
import { Monobehiver } from '../base/Monobehiver';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地面网格配置接口
|
||||||
|
*/
|
||||||
|
export interface GroundConfig {
|
||||||
|
/** 地面宽度 */
|
||||||
|
width?: number;
|
||||||
|
/** 地面深度 */
|
||||||
|
height?: number;
|
||||||
|
/** 网格细分数 */
|
||||||
|
subdivisions?: number;
|
||||||
|
/** 贴图URL */
|
||||||
|
textureUrl?: string;
|
||||||
|
/** 贴图平铺次数 (uScale, vScale) */
|
||||||
|
textureScale?: { u: number; v: number };
|
||||||
|
/** 地面颜色 (当没有贴图时使用) */
|
||||||
|
color?: Color3;
|
||||||
|
/** 是否接收阴影 */
|
||||||
|
receiveShadows?: boolean;
|
||||||
|
/** 地面位置 */
|
||||||
|
position?: Vector3;
|
||||||
|
/** 是否显示网格 */
|
||||||
|
showGrid?: boolean;
|
||||||
|
/** 网格线颜色 */
|
||||||
|
gridColor?: Color3;
|
||||||
|
/** 网格大小 */
|
||||||
|
gridRatio?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 地面网格管理类 - 负责创建和管理地面网格
|
||||||
|
*/
|
||||||
|
export class AppGround extends Monobehiver {
|
||||||
|
ground: Mesh | null;
|
||||||
|
material: PBRMaterial | null;
|
||||||
|
gridGround: Mesh | null;
|
||||||
|
gridMaterial: GridMaterial | null;
|
||||||
|
private config: GroundConfig;
|
||||||
|
|
||||||
|
constructor(mainApp: any, config: GroundConfig = {}) {
|
||||||
|
super(mainApp);
|
||||||
|
this.ground = null;
|
||||||
|
this.material = null;
|
||||||
|
this.gridGround = null;
|
||||||
|
this.gridMaterial = null;
|
||||||
|
this.config = {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
subdivisions: 10,
|
||||||
|
receiveShadows: true,
|
||||||
|
position: new Vector3(0, 0, 0),
|
||||||
|
textureScale: { u: 10, v: 10 },
|
||||||
|
color: new Color3(0.5, 0.5, 0.5),
|
||||||
|
textureUrl: "https://cdn.files.zguiy.com/zt/ground1.jpg", // 默认贴图
|
||||||
|
showGrid: true, // 默认显示网格
|
||||||
|
gridColor: new Color3(1,1,1),
|
||||||
|
gridRatio: 1.0,
|
||||||
|
...config
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化地面网格 */
|
||||||
|
Awake(): void {
|
||||||
|
this.createGround();
|
||||||
|
this.createMaterial();
|
||||||
|
if (this.config.showGrid) {
|
||||||
|
this.createGridGround();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建地面网格 */
|
||||||
|
private createGround(): void {
|
||||||
|
if (!this.mainApp.appScene.object) {
|
||||||
|
console.error('场景未初始化');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ground = MeshBuilder.CreateGround(
|
||||||
|
'ground',
|
||||||
|
{
|
||||||
|
width: this.config.width,
|
||||||
|
height: this.config.height,
|
||||||
|
subdivisions: this.config.subdivisions
|
||||||
|
},
|
||||||
|
this.mainApp.appScene.object
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.config.position) {
|
||||||
|
this.ground.position = this.config.position;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.config.receiveShadows) {
|
||||||
|
this.ground.receiveShadows = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建材质 */
|
||||||
|
private createMaterial(): void {
|
||||||
|
if (!this.ground || !this.mainApp.appScene.object) return;
|
||||||
|
|
||||||
|
this.material = new PBRMaterial('groundMaterial', this.mainApp.appScene.object);
|
||||||
|
|
||||||
|
// 如果有贴图URL,加载贴图
|
||||||
|
if (this.config.textureUrl) {
|
||||||
|
const texture = new Texture(
|
||||||
|
this.config.textureUrl,
|
||||||
|
this.mainApp.appScene.object
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置贴图平铺
|
||||||
|
if (this.config.textureScale) {
|
||||||
|
texture.uScale = this.config.textureScale.u;
|
||||||
|
texture.vScale = this.config.textureScale.v;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.material.albedoTexture = texture;
|
||||||
|
} else {
|
||||||
|
// 没有贴图时使用纯色
|
||||||
|
this.material.albedoColor = this.config.color || new Color3(1,1,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PBR 材质属性设置
|
||||||
|
this.material.metallic = 0.0; // 非金属
|
||||||
|
this.material.roughness = 1.0; // 粗糙表面
|
||||||
|
|
||||||
|
this.ground.material = this.material;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建网格地面 */
|
||||||
|
private createGridGround(): void {
|
||||||
|
if (!this.mainApp.appScene.object) return;
|
||||||
|
|
||||||
|
// 创建网格地面,位置稍微高一点避免z-fighting
|
||||||
|
this.gridGround = MeshBuilder.CreateGround(
|
||||||
|
'gridGround',
|
||||||
|
{
|
||||||
|
width: this.config.width,
|
||||||
|
height: this.config.height,
|
||||||
|
subdivisions: this.config.subdivisions
|
||||||
|
},
|
||||||
|
this.mainApp.appScene.object
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置位置,稍微高于贴图地面
|
||||||
|
const gridPosition = this.config.position ? this.config.position.clone() : new Vector3(0, 0, 0);
|
||||||
|
gridPosition.y += 0.01; // 抬高0.01单位避免z-fighting
|
||||||
|
this.gridGround.position = gridPosition;
|
||||||
|
|
||||||
|
// 创建网格材质
|
||||||
|
this.gridMaterial = new GridMaterial('gridMaterial', this.mainApp.appScene.object);
|
||||||
|
this.gridMaterial.mainColor = this.config.gridColor || new Color3(0.3, 0.3, 0.3);
|
||||||
|
this.gridMaterial.lineColor = this.config.gridColor || new Color3(0.3, 0.3, 0.3);
|
||||||
|
this.gridMaterial.gridRatio = this.config.gridRatio || 1.0;
|
||||||
|
this.gridMaterial.opacity = 0.8;
|
||||||
|
this.gridMaterial.backFaceCulling = false;
|
||||||
|
|
||||||
|
this.gridGround.material = this.gridMaterial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新贴图 */
|
||||||
|
setTexture(textureUrl: string, uScale: number = 10, vScale: number = 10): void {
|
||||||
|
if (!this.material || !this.mainApp.appScene.object) return;
|
||||||
|
|
||||||
|
const texture = new Texture(textureUrl, this.mainApp.appScene.object);
|
||||||
|
texture.uScale = uScale;
|
||||||
|
texture.vScale = vScale;
|
||||||
|
this.material.albedoTexture = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新地面颜色 */
|
||||||
|
setColor(color: Color3): void {
|
||||||
|
if (!this.material) return;
|
||||||
|
this.material.albedoColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 显示/隐藏地面 */
|
||||||
|
setVisible(visible: boolean): void {
|
||||||
|
if (this.ground) {
|
||||||
|
this.ground.isVisible = visible;
|
||||||
|
}
|
||||||
|
if (this.gridGround) {
|
||||||
|
this.gridGround.isVisible = visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 显示/隐藏网格 */
|
||||||
|
setGridVisible(visible: boolean): void {
|
||||||
|
if (this.gridGround) {
|
||||||
|
this.gridGround.isVisible = visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 销毁地面 */
|
||||||
|
dispose(): void {
|
||||||
|
if (this.ground) {
|
||||||
|
this.ground.dispose();
|
||||||
|
this.ground = null;
|
||||||
|
}
|
||||||
|
if (this.material) {
|
||||||
|
this.material.dispose();
|
||||||
|
this.material = null;
|
||||||
|
}
|
||||||
|
if (this.gridGround) {
|
||||||
|
this.gridGround.dispose();
|
||||||
|
this.gridGround = null;
|
||||||
|
}
|
||||||
|
if (this.gridMaterial) {
|
||||||
|
this.gridMaterial.dispose();
|
||||||
|
this.gridMaterial = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -19,6 +19,7 @@ import { AppSelectionOutline } from './AppSelectionOutline';
|
|||||||
import { AppPositionGizmo } from './AppPositionGizmo';
|
import { AppPositionGizmo } from './AppPositionGizmo';
|
||||||
import { AppModelDrag } from './AppModelDrag';
|
import { AppModelDrag } from './AppModelDrag';
|
||||||
import { AppDropZone } from './AppDropZone';
|
import { AppDropZone } from './AppDropZone';
|
||||||
|
import { AppGround } from './AppGround';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主应用类 - 3D场景的核心控制器
|
* 主应用类 - 3D场景的核心控制器
|
||||||
@ -38,6 +39,7 @@ export class MainApp {
|
|||||||
appPositionGizmo: AppPositionGizmo;
|
appPositionGizmo: AppPositionGizmo;
|
||||||
appModelDrag: AppModelDrag;
|
appModelDrag: AppModelDrag;
|
||||||
appDropZone: AppDropZone;
|
appDropZone: AppDropZone;
|
||||||
|
appGround: AppGround;
|
||||||
gameManager: GameManager;
|
gameManager: GameManager;
|
||||||
|
|
||||||
|
|
||||||
@ -54,6 +56,7 @@ export class MainApp {
|
|||||||
this.appSelectionOutline = new AppSelectionOutline(this);
|
this.appSelectionOutline = new AppSelectionOutline(this);
|
||||||
this.appPositionGizmo = new AppPositionGizmo(this);
|
this.appPositionGizmo = new AppPositionGizmo(this);
|
||||||
this.appModelDrag = new AppModelDrag(this);
|
this.appModelDrag = new AppModelDrag(this);
|
||||||
|
this.appGround = new AppGround(this);
|
||||||
this.gameManager = new GameManager(this);
|
this.gameManager = new GameManager(this);
|
||||||
|
|
||||||
window.addEventListener("resize", () => this.appEngin.handleResize());
|
window.addEventListener("resize", () => this.appEngin.handleResize());
|
||||||
@ -76,7 +79,6 @@ export class MainApp {
|
|||||||
async loadModel(): Promise<void> {
|
async loadModel(): Promise<void> {
|
||||||
await this.appModel.loadModel();
|
await this.appModel.loadModel();
|
||||||
await this.gameManager.Awake();
|
await this.gameManager.Awake();
|
||||||
console.log(1111111111111111111111);
|
|
||||||
EventBridge.allReady({ scene: this.appScene.object });
|
EventBridge.allReady({ scene: this.appScene.object });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +89,7 @@ export class MainApp {
|
|||||||
this.appCamera.Awake();
|
this.appCamera.Awake();
|
||||||
this.appLight.Awake();
|
this.appLight.Awake();
|
||||||
this.appEnv.Awake();
|
this.appEnv.Awake();
|
||||||
|
this.appGround.Awake();
|
||||||
this.appRay.Awake();
|
this.appRay.Awake();
|
||||||
this.appSelectionOutline.init();
|
this.appSelectionOutline.init();
|
||||||
this.appPositionGizmo.Awake();
|
this.appPositionGizmo.Awake();
|
||||||
@ -117,6 +120,7 @@ export class MainApp {
|
|||||||
this.appEnv?.clean();
|
this.appEnv?.clean();
|
||||||
this.appPositionGizmo?.dispose();
|
this.appPositionGizmo?.dispose();
|
||||||
this.appModelDrag?.dispose();
|
this.appModelDrag?.dispose();
|
||||||
|
this.appGround?.dispose();
|
||||||
// this.appHotspot?.clear();
|
// this.appHotspot?.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user