npm package: @metav_xly/decal-editor
Description: Vue3 + Element UI 贴花编辑器组件，基于 Three.js
Author: Shanda
Latest version: 1.1.3

# @metav_xly/decal-editor

基于 Vue3 + Element UI + Three.js 的贴花编辑器组件

## 特性

- 🎨 基于 Three.js 的 3D 贴花编辑功能
- 🖱️ 鼠标点击添加贴花
- ⚙️ 实时编辑贴花属性（位置、旋转、缩放、纹理类型）
- 📋 贴花列表管理
- 💾 配置导入导出
- 🎯 支持多种纹理类型
- 📦 TypeScript 支持
- 🔧 可自定义材质构造器

## 安装

```bash
npm install @metav_xly/decal-editor
# 或
pnpm add @metav_xly/decal-editor
# 或
yarn add @metav_xly/decal-editor
```

## 使用方法

### 全局注册

```javascript
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import DecalEditor from '@metav_xly/decal-editor'
import App from './App.vue'

const app = createApp(App)
app.use(ElementPlus)
app.use(DecalEditor)
app.mount('#app')
```

### 局部使用

```vue
<template>
  <DecalEditorComponent
    :model-url="modelUrl"
    :textures="textures"
    @decal-added="onDecalAdded"
    @decal-selected="onDecalSelected"
    @decal-updated="onDecalUpdated"
    @decal-removed="onDecalRemoved"
    @decals-cleared="onDecalsCleared"
  />
</template>

<script setup>
import { ref } from 'vue'
import { DecalEditorComponent } from '@metav_xly/decal-editor'

// 准备纹理配置数据
const textures = ref([
  {
    label: '贴花类型1',
    diffuseUrl: '/textures/decal1-diffuse.png',
    normalUrl: '/textures/decal1-normal.jpg'
  },
  {
    label: '贴花类型2',
    diffuseUrl: '/textures/decal2-diffuse.png',
    normalUrl: '/textures/decal2-normal.jpg'
  },
  {
    label: '贴花类型3',
    diffuseUrl: '/textures/decal3-diffuse.png',
    normalUrl: '/textures/decal3-normal.jpg'
  }
])

const modelUrl = ref('/models/your-model.glb')

// 事件处理
const onDecalAdded = (decal, index) => {
  console.log('贴花已添加:', decal, index)
}

const onDecalSelected = (decal, index) => {
  console.log('贴花已选中:', decal, index)
}

const onDecalUpdated = (decal, index) => {
  console.log('贴花已更新:', decal, index)
}

const onDecalRemoved = (index) => {
  console.log('贴花已删除:', index)
}

const onDecalsCleared = () => {
  console.log('所有贴花已清空')
}
</script>
```

### 使用核心类

```javascript
import * as THREE from 'three'
import { DecalEditor, DecalLoader } from '@metav_xly/decal-editor'

// 创建 Three.js 场景
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()

// 准备纹理配置
const textures = [
  {
    label: '贴花类型1',
    diffuseUrl: '/textures/decal-diffuse.png',
    normalUrl: '/textures/decal-normal.jpg'
  },
  {
    label: '贴花类型2',
    diffuseUrl: '/textures/decal2-diffuse.png',
    normalUrl: '/textures/decal2-normal.jpg'
  }
]

// 创建贴花编辑器
const decalEditor = new DecalEditor(scene, camera, renderer, textures)

// 设置目标网格
decalEditor.setTargetMesh(yourMesh)

// 设置参数
decalEditor.setParams({
  minScale: 0.1,
  maxScale: 5.0,
  textureType: '0',
  rotate: false,
  helperSize: 20,
  normalLength: 20
})

// 监听事件
decalEditor.onDecalAdded = (decal, index) => {
  console.log('贴花已添加:', decal, index)
}

decalEditor.onDecalSelected = (decal, index) => {
  console.log('贴花已选中:', decal, index)
}

decalEditor.onDecalUpdated = (decal, index) => {
  console.log('贴花已更新:', decal, index)
}

decalEditor.onDecalRemoved = (index) => {
  console.log('贴花已删除:', index)
}

decalEditor.onDecalsCleared = () => {
  console.log('所有贴花已清空')
}

// 创建贴花加载器
const decalLoader = new DecalLoader(scene, textures)

// 加载配置
const config = {
  version: '1.0',
  decals: [
    {
      id: 0,
      position: { x: 0, y: 0, z: 0 },
      orientation: { x: 0, y: 0, z: 0 },
      scale: 10,
      textureType: '0',
      targetName: 'mesh'
    }
  ]
}

const loadedDecals = decalLoader.loadConfig(config, yourMesh)
```

## API 文档

### DecalEditorComponent Props

| 属性 | 类型 | 默认值 | 说明 |
|------|------|--------|---------|
| modelUrl | string | '' | 3D 模型文件 URL |
| textures | TextureConfig[] | [] | 纹理配置数组 |

### DecalEditorComponent Events

| 事件名 | 参数 | 说明 |
|--------|------|------|
| decal-added | (decal: THREE.Mesh, index: number) | 贴花添加时触发 |
| decal-selected | (decal: THREE.Mesh, index: number) | 贴花选中时触发 |
| decal-updated | (decal: THREE.Mesh, index: number) | 贴花更新时触发 |
| decal-removed | (index: number) | 贴花删除时触发 |
| decals-cleared | () | 清空所有贴花时触发 |

### TextureConfig 接口

```typescript
interface TextureConfig {
  label: string        // 纹理标签
  diffuseUrl?: string  // 漫反射纹理 URL
  normalUrl?: string   // 法线纹理 URL
}
```

### TextureSet 接口

```typescript
interface TextureSet {
  diffuse?: THREE.Texture  // 漫反射纹理
  normal?: THREE.Texture   // 法线纹理
  label: string           // 纹理标签
}
```

### DecalParams 接口

```typescript
interface DecalParams {
  minScale: number      // 最小缩放值
  maxScale: number      // 最大缩放值
  textureType: string   // 纹理类型索引
  rotate: boolean       // 是否随机旋转
  helperSize: number    // 辅助器大小
  normalLength: number  // 法线长度
}
```

### DecalEditor 类方法

| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| setTargetMesh | (mesh: THREE.Object3D) | void | 设置目标网格 |
| setParams | (params: Partial<DecalParams>) | void | 设置参数 |
| createDecal | () | THREE.Mesh \| null | 创建贴花 |
| selectDecal | (index: number) | void | 选择贴花 |
| updateDecal | (index: number, properties: DecalProperties) | void | 更新贴花 |
| removeDecal | (index: number) | void | 删除贴花 |
| removeAllDecals | () | void | 删除所有贴花 |
| exportConfig | () | any | 导出配置 |
| getDecals | () | THREE.Mesh[] | 获取所有贴花 |
| getSelectedDecal | () | THREE.Mesh \| null | 获取选中的贴花 |
| getSelectedDecalIndex | () | number | 获取选中贴花的索引 |

### DecalLoader 类方法

| 方法 | 参数 | 返回值 | 说明 |
|------|------|--------|------|
| loadConfig | (config: DecalConfig, targetMesh: THREE.Object3D) | THREE.Mesh[] | 加载配置 |
| validateConfig | (config: any) | boolean | 验证配置格式 |

## 自定义材质构造器

```javascript
import * as THREE from 'three'
import { DecalEditor } from '@metav_xly/decal-editor'

const customMaterialConstructor = (textureSet, index) => {
  const material = new THREE.MeshPhongMaterial({
    transparent: true,
    depthTest: true,
    depthWrite: false,
    polygonOffset: true,
    polygonOffsetFactor: -4
  })
  
  if (textureSet.diffuse) {
    material.map = textureSet.diffuse
  }
  if (textureSet.normal) {
    material.normalMap = textureSet.normal
  }
  
  // 根据索引和标签自定义材质属性
  if (index === 0) {
    material.color.setHex(0xff0000)
  }
  
  // 根据纹理标签设置不同属性
  if (textureSet.label.includes('金属')) {
    material.shininess = 100
  }
  
  return material
}

// 准备纹理配置
const textures = [
  {
    label: '金属纹理',
    diffuseUrl: '/textures/metal-diffuse.png',
    normalUrl: '/textures/metal-normal.jpg'
  }
]

const decalEditor = new DecalEditor(
  scene, 
  camera, 
  renderer, 
  textures, 
  customMaterialConstructor
)
```

## 开发

```bash
# 安装依赖
pnpm install

# 开发模式
pnpm dev

# 构建库
pnpm build:lib

# 类型检查
pnpm type-check
```

## 许可证

MIT