This commit is contained in:
2026-04-24 20:27:54 +08:00
parent c992660011
commit eee1b62bfb
11 changed files with 298399 additions and 103 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

297813
examples/index copy.js Normal file

File diff suppressed because one or more lines are too long

120
examples/index.js Normal file
View File

@ -0,0 +1,120 @@
import { kernel } from './index copy.js';
// import { kernel } from 'https://sdk.zguiy.com/zt/assets/index.js';
// const config = {
// container: document.querySelector('#renderDom'),
// modelUrlList: ['/assets/model.glb'],
// env: { envPath: '/assets/hdr.env', intensity: 1.2, rotationY: 0.3, background: false },
// };
const config = {
container: document.querySelector('#renderDom'),
modelUrlList: [],
env: { envPath: 'https://sdk.zguiy.com/resurces/hdr/hdr.env', intensity: 1.2, rotationY: 0.3, background: true },
};
kernel.init(config);
kernel.model.add("卷帘大", "https://sdk.zguiy.com/resurces/model/框架.glb")
kernel.model.add("卷帘大", "https://sdk.zguiy.com/resurces/model/卷帘大.glb")
kernel.model.add("卷帘小", "https://sdk.zguiy.com/resurces/model/卷帘小.glb")
kernel.model.add("小桌", "https://sdk.zguiy.com/resurces/model/小桌.glb")
kernel.on('model:load:progress', (data) => {
console.log('模型加载事件', data);
});
kernel.on('model:loaded', (data) => {
console.log('模型加载完成', data);
// 隐藏进度条
const progressContainer = document.getElementById('progress-container');
if (progressContainer) {
progressContainer.style.display = 'none';
}
});
kernel.on('all:ready', (data) => {
console.log('所有模块加载完,', data);
kernel.material.apply({
target: 'Material__2',
attribute: 'alpha',
value: 0.5,
});
kernel.hotspot.render([
{
id: "h1",
type: 'hotspot',
name: "卷帘门",
meshName: "Valve_01",
icon: "https://bpic.588ku.com/element_pic/20/06/30/d1046b01afc0b9586844350d131f4daf.jpg!/fw/253/quality/90/unsharp/true/compress/true",
offset: [25, 25, 0],
radius: 20,
color: "#21c7ff",
payload: { type: "valve", code: "A" },
},
]);
});
// 存储当前选中的材质名和网格
let currentMaterialName = '';
let currentPickedMesh = null;
kernel.on('model:click', (data) => {
console.log('模型点击事件', data);
console.log(data);
// DOM 2D转3D 示例:点击模型时显示信息框
if (data.pickedMesh && data.pickedPoint) {
const meshName = data.pickedMesh.name;
const position = data.pickedPoint; // 使用点击位置的坐标
currentMaterialName = data.materialName || ''; // 保存材质名
currentPickedMesh = data.pickedMesh; // 保存网格对象
console.log('点击位置的3D坐标:', position);
console.log('材质名:', currentMaterialName);
// 获取已创建的DOM元素
const infoDiv = document.getElementById('model-info-box');
// 更新信息内容
document.getElementById('info-name').textContent = `名称: ${meshName}`;
document.getElementById('info-position').textContent = `坐标: [${position.x.toFixed(2)}, ${position.y.toFixed(2)}, ${position.z.toFixed(2)}]`;
// 将DOM附加到点击的3D坐标会自动显示
kernel.domTo3D.attach('model-info', infoDiv, [position.x, position.y, position.z], { x: -2, y: -2 });
}
});
// 暴露到全局,供 index.html 使用
window.getCurrentMaterialName = () => currentMaterialName;
window.getCurrentPickedMesh = () => currentPickedMesh;
// 暴露 kernel 到全局,方便调试
kernel.on('hotspot:click', (data) => {
console.log('热点被点击:', data);
const { id, name } = data
if (name === "卷帘门") {
kernel.door.toggle({ upY: 28, downY: 0, speed: 12 });
// Y轴剖切只作用于卷帘门网格保留下方剖掉上方
const clipHeight = 28; // 调整这个值找到合适的剖切高度
console.log('设置剖切:', clipHeight);
kernel.clipping.setY(clipHeight, true, ['Box005.001', 'Box006.001']);
}
});
window.kernel = kernel;
// 添加模型到场景
// await kernel.model.add('https://sdk.zguiy.com/resurces/model/百叶1.glb');
// 销毁模型
// kernel.model.destroy('car');
// 替换模型
// await kernel.model.replace('car', '/models/new-car.glb');

View File

@ -21,6 +21,12 @@
#app { #app {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
display: flex;
position: relative;
}
#canvas-container {
flex: 1;
position: relative; position: relative;
} }
@ -30,6 +36,157 @@
display: block; display: block;
} }
#config-panel {
width: 320px;
background: rgba(30, 30, 45, 0.95);
backdrop-filter: blur(10px);
overflow-y: auto;
padding: 20px;
box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3);
}
#config-panel::-webkit-scrollbar {
width: 6px;
}
#config-panel::-webkit-scrollbar-track {
background: rgba(255, 255, 255, 0.1);
}
#config-panel::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.3);
border-radius: 3px;
}
.config-title {
color: #fff;
font-size: 18px;
font-weight: bold;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid rgba(255, 255, 255, 0.2);
}
.config-category {
margin-bottom: 15px;
border-radius: 8px;
overflow: hidden;
background: rgba(255, 255, 255, 0.05);
}
.category-header {
padding: 12px 15px;
background: rgba(255, 255, 255, 0.1);
color: #fff;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: background 0.3s;
user-select: none;
}
.category-header:hover {
background: rgba(255, 255, 255, 0.15);
}
.category-header.active {
background: rgba(76, 175, 80, 0.3);
}
.category-title {
font-size: 14px;
font-weight: 600;
}
.category-arrow {
transition: transform 0.3s;
font-size: 12px;
}
.category-arrow.expanded {
transform: rotate(180deg);
}
.category-content {
display: grid;
grid-template-rows: 0fr;
overflow: hidden;
transition: grid-template-rows 0.3s ease, padding 0.3s ease;
padding: 0 15px;
}
.category-content.expanded {
grid-template-rows: 1fr;
padding: 15px;
}
.category-content>* {
min-height: 0;
}
.option-item {
margin-bottom: 12px;
}
.option-label {
color: rgba(255, 255, 255, 0.8);
font-size: 13px;
margin-bottom: 8px;
display: block;
}
.option-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.option-btn {
padding: 8px 16px;
border: 1px solid rgba(255, 255, 255, 0.3);
background: rgba(255, 255, 255, 0.05);
color: #fff;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
transition: all 0.3s;
}
.option-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
.option-btn.selected {
background: rgba(76, 175, 80, 0.6);
border-color: #4CAF50;
}
.option-checkbox {
display: flex;
align-items: center;
padding: 8px;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
}
.option-checkbox:hover {
background: rgba(255, 255, 255, 0.05);
}
.option-checkbox input[type="checkbox"] {
margin-right: 8px;
cursor: pointer;
}
.option-checkbox label {
color: rgba(255, 255, 255, 0.8);
font-size: 12px;
cursor: pointer;
}
/* 进度条样式 */ /* 进度条样式 */
#progress-container { #progress-container {
position: absolute; position: absolute;
@ -63,123 +220,313 @@
<body> <body>
<div id="app"> <div id="app">
<canvas id="renderDom"></canvas> <!-- 画布区域 -->
<div id="progress-container" style="display: none;"> <div id="canvas-container">
<div id="progress-bar"></div> <canvas id="renderDom"></canvas>
<div id="progress-text">0%</div> <div id="progress-container" style="display: none;">
<div id="progress-bar"></div>
<div id="progress-text">0%</div>
</div>
</div>
<!-- 配置面板 -->
<div id="config-panel">
<div class="config-title">选装选配</div>
<!-- 棚子尺寸 -->
<div class="config-category">
<div class="category-header" data-category="size">
<span class="category-title">棚子尺寸</span>
<span class="category-arrow"></span>
</div>
<div class="category-content">
<div class="option-group">
<button class="option-btn" data-option="size-1">3x3米</button>
<button class="option-btn" data-option="size-2">4x4米</button>
<button class="option-btn" data-option="size-3">5x5米</button>
<button class="option-btn" data-option="size-4">6x6米</button>
</div>
</div>
</div>
<!-- 棚子类型 -->
<div class="config-category">
<div class="category-header" data-category="type">
<span class="category-title">棚子类型</span>
<span class="category-arrow"></span>
</div>
<div class="category-content">
<div class="option-group">
<button class="option-btn" data-option="type-1">平顶</button>
<button class="option-btn" data-option="type-2">尖顶</button>
<button class="option-btn" data-option="type-3">弧形</button>
<button class="option-btn" data-option="type-4">异形</button>
</div>
</div>
</div>
<!-- 百叶 (单选) -->
<div class="config-category">
<div class="category-header" data-category="louver">
<span class="category-title">百叶</span>
<span class="category-arrow"></span>
</div>
<div class="category-content">
<div class="option-group">
<button class="option-btn" data-option="louver-1">百叶1</button>
<button class="option-btn" data-option="louver-2">百叶2</button>
<button class="option-btn" data-option="louver-3">百叶3</button>
<button class="option-btn" data-option="louver-4">百叶4</button>
<button class="option-btn" data-option="louver-4">卷帘小</button>
</div>
</div>
</div>
<!-- 配色 -->
<div class="config-category">
<div class="category-header" data-category="color">
<span class="category-title">配色</span>
<span class="category-arrow"></span>
</div>
<div class="category-content">
<div class="option-group">
<button class="option-btn" data-option="color-1">白色</button>
<button class="option-btn" data-option="color-2">灰色</button>
<button class="option-btn" data-option="color-3">黑色</button>
<button class="option-btn" data-option="color-4">木色</button>
</div>
</div>
</div>
</div> </div>
</div> </div>
<!-- 模型信息框用于2D转3D显示 -->
<div id="model-info-box" style="display: none;">
<div style="
background: rgba(255, 255, 255, 0.95);
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
font-size: 14px;
color: #333;
min-width: 200px;
">
<div style="font-weight: bold; margin-bottom: 8px; color: #4CAF50;">模型信息</div>
<div id="info-name" style="margin-bottom: 5px;">名称: -</div>
<div id="info-position" style="margin-bottom: 10px; font-size: 12px; color: #666;">坐标: -</div>
<div style="display: flex; gap: 8px; margin-bottom: 8px;">
<button id="color-btn-1" style="
flex: 1;
padding: 8px;
background: #FFFFFF;
color: #333;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
">白色</button>
<button id="color-btn-2" style="
flex: 1;
padding: 8px;
background: #000000;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
">黑色</button>
</div>
<div style="display: flex; gap: 8px;">
<button id="remove-model-btn" style="
flex: 1;
padding: 8px;
background: #f44336;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
">移除</button>
<button id="close-info-btn" style="
flex: 1;
padding: 8px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
">关闭</button>
</div>
</div>
</div>
<script type="module" src="./index.js"></script>
<script type="module"> <script type="module">
// import { kernel } from './src/main.ts'; import { kernel } from './index copy.js';
import { kernel } from 'https://sdk.zguiy.com/zt/assets/index.js';
// const config = {
// container: document.querySelector('#renderDom'),
// modelUrlList: ['/assets/model.glb'],
// env: { envPath: '/assets/hdr.env', intensity: 1.2, rotationY: 0.3, background: false },
// };
const config = {
container: document.querySelector('#renderDom'),
modelUrlList: ['https://sdk.zguiy.com/resurces/model/model.glb'],
env: { envPath: 'https://sdk.zguiy.com/resurces/hdr/hdr.env', intensity: 1.2, rotationY: 0.3, background: true },
};
kernel.init(config);
// ========== UI 交互逻辑 ==========
kernel.on('model:load:progress', (data) => { // 折叠面板切换
console.log('模型加载事件', data); document.querySelectorAll('.category-header').forEach(header => {
header.addEventListener('click', function () {
const content = this.nextElementSibling;
const arrow = this.querySelector('.category-arrow');
// 切换展开/收起状态
content.classList.toggle('expanded');
const progress = data.progress || 0; arrow.classList.toggle('expanded');
const progressBar = document.getElementById('progress-bar'); this.classList.toggle('active');
const progressText = document.getElementById('progress-text');
const progressContainer = document.getElementById('progress-container');
if (progressContainer) {
progressContainer.style.display = 'block';
}
if (progressBar) {
progressBar.style.width = `${progress * 100}%`;
}
if (progressText) {
progressText.textContent = `${Math.round(progress * 100)}%`;
}
});
kernel.on('model:loaded', (data) => {
console.log('模型加载完成', data);
// 隐藏进度条
const progressContainer = document.getElementById('progress-container');
if (progressContainer) {
progressContainer.style.display = 'none';
}
});
kernel.on('all:ready', (data) => {
console.log('所有模块加载完,', data);
kernel.material.apply({
target: 'Material__2',
attribute: 'alpha',
value: 0.5,
}); });
kernel.hotspot.render([
{
id: "h1",
name: "卷帘门",
meshName: "Valve_01",
icon: "./btn_热点.png",
offset: [25, 25, 0],
radius: 20,
color: "#21c7ff",
payload: { type: "valve", code: "A" },
},
]);
}); });
// 单选按钮逻辑
document.querySelectorAll('.option-btn').forEach(btn => {
btn.addEventListener('click', async function () {
const optionGroup = this.parentElement;
const category = this.closest('.config-category');
const categoryName = category.querySelector('.category-header').dataset.category;
kernel.on('model:click', (data) => { // 同一组内取消其他选中状态
console.log('模型点击事件', data); optionGroup.querySelectorAll('.option-btn').forEach(b => {
console.log(data); b.classList.remove('selected');
});
// 选中当前按钮
this.classList.add('selected');
// 触发自定义事件
const event = new CustomEvent('config:change', {
detail: {
category: categoryName,
value: this.dataset.option,
text: this.textContent
}
});
document.dispatchEvent(event);
console.log('配置变更:', {
category: categoryName,
value: this.dataset.option,
text: this.textContent
});
// 百叶模型替换逻辑
if (categoryName === "louver") {
const currentText = this.textContent;
const modelUrl = `https://sdk.zguiy.com/resurces/model/${currentText}.glb`;
console.log('替换百叶模型:', modelUrl);
try {
await kernel.model.replace('卷帘小', modelUrl);
console.log(`百叶模型已替换为 ${currentText}`);
} catch (error) {
console.error(`百叶模型替换失败:`, error);
}
}
});
}); });
kernel.on('hotspot:click', (data) => { // 多选复选框逻辑
console.log('热点被点击:', data); document.querySelectorAll('.option-checkbox input[type="checkbox"]').forEach(checkbox => {
const { id, name } = data checkbox.addEventListener('change', async function () {
if (name === "卷帘门") { const category = this.closest('.config-category');
kernel.door.toggle({ upY: 28, downY: 0, speed: 12 }); const categoryName = category.querySelector('.category-header').dataset.category;
const optionGroup = this.closest('.option-group');
const checked = this.checked;
// Y轴剖切只作用于卷帘门网格保留下方剖掉上方 // 获取当前组所有选中的值
const clipHeight = 28; // 调整这个值找到合适的剖切高度 const selectedValues = Array.from(
console.log('设置剖切:', clipHeight); optionGroup.querySelectorAll('input[type="checkbox"]:checked')
kernel.clipping.setY(clipHeight, true, ['Box005.001', 'Box006.001']); ).map(cb => ({
value: cb.dataset.option,
text: cb.nextElementSibling.textContent
}));
// 触发自定义事件
const event = new CustomEvent('config:change', {
detail: {
category: categoryName,
values: selectedValues,
checked: this.checked,
currentValue: this.dataset.option
}
});
document.dispatchEvent(event);
console.log('配置变更(多选):', {
category: categoryName,
selectedValues: selectedValues,
checked: this.checked,
currentValue: this.dataset.option
});
});
});
// 监听配置变更事件(供外部使用)
document.addEventListener('config:change', function (e) {
// 这里可以根据配置变更来操作 3D 模型
// 例如:
// if (e.detail.category === 'size') {
// kernel.model.replace('shed', `/models/shed-${e.detail.value}.glb`);
// }
// if (e.detail.category === 'color') {
// kernel.material.apply({
// target: 'ShedMaterial',
// attribute: 'baseColor',
// value: getColorValue(e.detail.value)
// });
// }
});
// ========== 模型信息框按钮事件 ==========
// 关闭按钮事件
document.getElementById('close-info-btn').addEventListener('click', () => {
kernel.domTo3D.detach('model-info');
});
// 白色按钮事件
document.getElementById('color-btn-1').addEventListener('click', () => {
const materialName = window.getCurrentMaterialName();
if (materialName) {
console.log('切换为白色,材质名:', materialName);
kernel.material.color(materialName, '#FFFFFF');
} else {
console.log('没有选中材质');
} }
}); });
// 黑色按钮事件
document.getElementById('color-btn-2').addEventListener('click', () => {
const materialName = window.getCurrentMaterialName();
if (materialName) {
console.log('切换为黑色,材质名:', materialName);
kernel.material.color(materialName, '#000000');
} else {
console.log('没有选中材质');
}
});
// 添加模型到场景 // 移除按钮事件
await kernel.model.add('/models/car.glb'); document.getElementById('remove-model-btn').addEventListener('click', () => {
const pickedMesh = window.getCurrentPickedMesh();
// 销毁模型 if (pickedMesh) {
kernel.model.destroy('car'); const meshName = pickedMesh.name;
const success = kernel.model.remove(meshName);
// 替换模型 if (success) {
await kernel.model.replace('car', '/models/new-car.glb'); console.log('模型已移除');
// 关闭信息框
kernel.domTo3D.detach('model-info');
} else {
console.log('移除失败:未找到该网格所属的模型');
}
} else {
console.log('没有选中的网格');
}
});
</script> </script>
</body> </body>

View File

@ -10,12 +10,12 @@ import { kernel } from './src/main.ts';
const config = { const config = {
container: document.querySelector('#renderDom'), container: document.querySelector('#renderDom'),
modelUrlList: ['https://sdk.zguiy.com/resurces/model/框架.glb'], modelUrlList: [],
env: { envPath: 'https://sdk.zguiy.com/resurces/hdr/hdr.env', intensity: 1.2, rotationY: 0.3, background: true }, env: { envPath: 'https://sdk.zguiy.com/resurces/hdr/hdr.env', intensity: 1.2, rotationY: 0.3, background: true },
}; };
kernel.init(config); kernel.init(config);
kernel.model.add("卷帘大", "https://sdk.zguiy.com/resurces/model/框架.glb")
kernel.model.add("卷帘大", "https://sdk.zguiy.com/resurces/model/卷帘大.glb") kernel.model.add("卷帘大", "https://sdk.zguiy.com/resurces/model/卷帘大.glb")
kernel.model.add("卷帘小", "https://sdk.zguiy.com/resurces/model/卷帘小.glb") kernel.model.add("卷帘小", "https://sdk.zguiy.com/resurces/model/卷帘小.glb")
kernel.model.add("小桌", "https://sdk.zguiy.com/resurces/model/小桌.glb") kernel.model.add("小桌", "https://sdk.zguiy.com/resurces/model/小桌.glb")

View File

@ -208,7 +208,7 @@ export class AppModel extends Monobehiver {
}); });
if (result.success && result.meshes) { if (result.success && result.meshes) {
this.cloneMaterials(result.meshes, modelName); // this.cloneMaterials(result.meshes, modelName);
this.modelDic.Set(modelName, result.meshes); this.modelDic.Set(modelName, result.meshes);
// 更新 GameManager 的字典 // 更新 GameManager 的字典

View File

@ -745,12 +745,13 @@ export class GameManager extends Monobehiver {
applyMaterial(target: string, attribute: string, value: number | string): void { applyMaterial(target: string, attribute: string, value: number | string): void {
// 这里需要根据实际的材质管理逻辑实现 // 这里需要根据实际的材质管理逻辑实现
console.log(`Applying attribute ${attribute} to ${value}`); console.log(`Applying attribute ${attribute} to ${value}`);
console.log(this.materialDic);
// 示例实现:根据目标和材质路径应用材质 // 示例实现:根据目标和材质路径应用材质
// 1. 查找目标网格 // 1. 查找目标网格
const targetMaterials: PBRMaterial[] = []; const targetMaterials: PBRMaterial[] = [];
this.materialDic.Values().forEach(material => { this.materialDic.Values().forEach(material => {
if (material.name.includes(target)) { if (material.name === target) {
console.log(`${this.materialDic.Get(material.name)}`, material); console.log(`${this.materialDic.Get(material.name)}`, material);
targetMaterials.push(material); targetMaterials.push(material);
} }
@ -769,8 +770,23 @@ export class GameManager extends Monobehiver {
targetMaterials.forEach(material => { targetMaterials.forEach(material => {
if (attribute === 'baseColor' && typeof value === 'string') { if (attribute === 'baseColor' && typeof value === 'string') {
// 如果是 baseColor 且值是字符串16进制颜色转换为 Color3 // 如果是 baseColor 且值是字符串16进制颜色转换为 Color3
material.albedoColor = Color3.FromHexString(value); const color = Color3.FromHexString(value);
console.log(`Applying baseColor ${value} to material: ${material.name}`);
console.log(`Before: albedoColor =`, material.albedoColor);
console.log(`Before: albedoTexture =`, material.albedoTexture);
material.albedoColor = color;
// 如果有纹理,颜色会作为纹理的乘法因子
if (material.albedoTexture) {
console.log(`Material ${material.name} has albedoTexture, color will tint the texture`);
}
// 强制刷新材质
material.markDirty();
console.log(`After: albedoColor =`, material.albedoColor);
console.log(`Applying baseColor ${value} to material: ${material.name}`, color);
} else if (material[attribute]) { } else if (material[attribute]) {
material[attribute] = value; material[attribute] = value;
console.log(`Applying attribute ${attribute} to ${value} to mesh: ${material.name}`); console.log(`Applying attribute ${attribute} to ${value} to mesh: ${material.name}`);