深度画布功能
This commit is contained in:
@@ -0,0 +1,617 @@
|
||||
/**
|
||||
* 材质预设管理器
|
||||
* 负责管理所有材质预设,包括内置预设和用户自定义预设
|
||||
*/
|
||||
export class TexturePresetManager {
|
||||
constructor() {
|
||||
// 内置材质预设
|
||||
this.builtInTextures = [];
|
||||
|
||||
// 用户自定义材质
|
||||
this.customTextures = [];
|
||||
|
||||
// 材质分类
|
||||
this.categories = new Map();
|
||||
|
||||
// 材质缓存
|
||||
this.textureCache = new Map();
|
||||
|
||||
// 事件监听器
|
||||
this.listeners = {
|
||||
textureAdded: [],
|
||||
textureRemoved: [],
|
||||
textureUpdated: [],
|
||||
};
|
||||
|
||||
// 初始化内置材质
|
||||
this._initBuiltInTextures();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化内置材质预设
|
||||
* @private
|
||||
*/
|
||||
_initBuiltInTextures() {
|
||||
// 基于项目中的texture文件夹内容创建预设
|
||||
const textureList = [
|
||||
// 基础纹理
|
||||
{
|
||||
id: "texture0",
|
||||
name: "纸质纹理",
|
||||
category: "基础纹理",
|
||||
path: "/src/assets/texture/texture0.webp",
|
||||
},
|
||||
{
|
||||
id: "texture1",
|
||||
name: "粗糙表面",
|
||||
category: "基础纹理",
|
||||
path: "/src/assets/texture/texture1.webp",
|
||||
},
|
||||
{
|
||||
id: "texture2",
|
||||
name: "细腻纹理",
|
||||
category: "基础纹理",
|
||||
path: "/src/assets/texture/texture2.webp",
|
||||
},
|
||||
{
|
||||
id: "texture3",
|
||||
name: "颗粒质感",
|
||||
category: "基础纹理",
|
||||
path: "/src/assets/texture/texture3.webp",
|
||||
},
|
||||
{
|
||||
id: "texture4",
|
||||
name: "布料纹理",
|
||||
category: "基础纹理",
|
||||
path: "/src/assets/texture/texture4.webp",
|
||||
},
|
||||
{
|
||||
id: "texture5",
|
||||
name: "木质纹理",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture5.webp",
|
||||
},
|
||||
{
|
||||
id: "texture6",
|
||||
name: "石材纹理",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture6.webp",
|
||||
},
|
||||
{
|
||||
id: "texture7",
|
||||
name: "金属质感",
|
||||
category: "金属纹理",
|
||||
path: "/src/assets/texture/texture7.webp",
|
||||
},
|
||||
{
|
||||
id: "texture8",
|
||||
name: "皮革纹理",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture8.webp",
|
||||
},
|
||||
{
|
||||
id: "texture9",
|
||||
name: "水彩纸质",
|
||||
category: "艺术纹理",
|
||||
path: "/src/assets/texture/texture9.webp",
|
||||
},
|
||||
{
|
||||
id: "texture10",
|
||||
name: "画布纹理",
|
||||
category: "艺术纹理",
|
||||
path: "/src/assets/texture/texture10.webp",
|
||||
},
|
||||
{
|
||||
id: "texture11",
|
||||
name: "沙砾质感",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture11.webp",
|
||||
},
|
||||
{
|
||||
id: "texture12",
|
||||
name: "水波纹理",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture12.webp",
|
||||
},
|
||||
{
|
||||
id: "texture13",
|
||||
name: "云朵纹理",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture13.webp",
|
||||
},
|
||||
{
|
||||
id: "texture14",
|
||||
name: "火焰纹理",
|
||||
category: "特效纹理",
|
||||
path: "/src/assets/texture/texture14.webp",
|
||||
},
|
||||
{
|
||||
id: "texture15",
|
||||
name: "烟雾效果",
|
||||
category: "特效纹理",
|
||||
path: "/src/assets/texture/texture15.webp",
|
||||
},
|
||||
{
|
||||
id: "texture16",
|
||||
name: "星空纹理",
|
||||
category: "特效纹理",
|
||||
path: "/src/assets/texture/texture16.webp",
|
||||
},
|
||||
{
|
||||
id: "texture17",
|
||||
name: "大理石纹",
|
||||
category: "石材纹理",
|
||||
path: "/src/assets/texture/texture17.webp",
|
||||
},
|
||||
{
|
||||
id: "texture18",
|
||||
name: "花岗岩纹",
|
||||
category: "石材纹理",
|
||||
path: "/src/assets/texture/texture18.webp",
|
||||
},
|
||||
{
|
||||
id: "texture19",
|
||||
name: "竹纹理",
|
||||
category: "自然纹理",
|
||||
path: "/src/assets/texture/texture19.webp",
|
||||
},
|
||||
{
|
||||
id: "texture20",
|
||||
name: "抽象图案",
|
||||
category: "艺术纹理",
|
||||
path: "/src/assets/texture/texture20.webp",
|
||||
},
|
||||
];
|
||||
|
||||
// 添加内置材质
|
||||
textureList.forEach((texture) => {
|
||||
this.builtInTextures.push({
|
||||
id: texture.id,
|
||||
name: texture.name,
|
||||
category: texture.category,
|
||||
path: texture.path,
|
||||
type: "builtin",
|
||||
preview: texture.path, // 使用原图作为预览
|
||||
description: `内置${texture.category} - ${texture.name}`,
|
||||
tags: [texture.category.replace("纹理", ""), "内置"],
|
||||
created: new Date().toISOString(),
|
||||
// 默认属性
|
||||
defaultSettings: {
|
||||
scale: 1,
|
||||
opacity: 1,
|
||||
repeat: "repeat",
|
||||
angle: 0,
|
||||
},
|
||||
});
|
||||
|
||||
// 添加到分类
|
||||
if (!this.categories.has(texture.category)) {
|
||||
this.categories.set(texture.category, []);
|
||||
}
|
||||
this.categories.get(texture.category).push(texture.id);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有材质(内置 + 自定义)
|
||||
* @returns {Array} 材质数组
|
||||
*/
|
||||
getAllTextures() {
|
||||
return [...this.builtInTextures, ...this.customTextures];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据ID获取材质
|
||||
* @param {String} textureId 材质ID
|
||||
* @returns {Object|null} 材质对象
|
||||
*/
|
||||
getTextureById(textureId) {
|
||||
return this.getAllTextures().find((texture) => texture.id === textureId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据分类获取材质
|
||||
* @param {String} category 分类名称
|
||||
* @returns {Array} 材质数组
|
||||
*/
|
||||
getTexturesByCategory(category) {
|
||||
return this.getAllTextures().filter((texture) => texture.category === category);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有分类
|
||||
* @returns {Array} 分类名称数组
|
||||
*/
|
||||
getCategories() {
|
||||
const categories = new Set();
|
||||
this.getAllTextures().forEach((texture) => {
|
||||
categories.add(texture.category);
|
||||
});
|
||||
return Array.from(categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义材质
|
||||
* @param {Object} textureData 材质数据
|
||||
* @returns {String} 材质ID
|
||||
*/
|
||||
addCustomTexture(textureData) {
|
||||
const textureId =
|
||||
textureData.id || `custom_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const texture = {
|
||||
id: textureId,
|
||||
name: textureData.name || "自定义材质",
|
||||
category: textureData.category || "自定义材质",
|
||||
path: textureData.path || textureData.dataUrl,
|
||||
type: "custom",
|
||||
preview: textureData.preview || textureData.path || textureData.dataUrl,
|
||||
description: textureData.description || "用户自定义材质",
|
||||
tags: textureData.tags || ["自定义"],
|
||||
created: new Date().toISOString(),
|
||||
defaultSettings: {
|
||||
scale: textureData.scale || 1,
|
||||
opacity: textureData.opacity || 1,
|
||||
repeat: textureData.repeat || "repeat",
|
||||
angle: textureData.angle || 0,
|
||||
...textureData.defaultSettings,
|
||||
},
|
||||
// 保存原始文件信息
|
||||
file: textureData.file || null,
|
||||
dataUrl: textureData.dataUrl || null,
|
||||
};
|
||||
|
||||
this.customTextures.push(texture);
|
||||
|
||||
// 添加到分类
|
||||
if (!this.categories.has(texture.category)) {
|
||||
this.categories.set(texture.category, []);
|
||||
}
|
||||
this.categories.get(texture.category).push(textureId);
|
||||
|
||||
// 触发事件
|
||||
this._triggerEvent("textureAdded", texture);
|
||||
|
||||
return textureId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除自定义材质
|
||||
* @param {String} textureId 材质ID
|
||||
* @returns {Boolean} 是否删除成功
|
||||
*/
|
||||
removeCustomTexture(textureId) {
|
||||
const index = this.customTextures.findIndex((texture) => texture.id === textureId);
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const texture = this.customTextures[index];
|
||||
|
||||
// 只能删除自定义材质
|
||||
if (texture.type !== "custom") {
|
||||
console.warn("不能删除内置材质");
|
||||
return false;
|
||||
}
|
||||
|
||||
this.customTextures.splice(index, 1);
|
||||
|
||||
// 从分类中移除
|
||||
if (this.categories.has(texture.category)) {
|
||||
const categoryTextures = this.categories.get(texture.category);
|
||||
const categoryIndex = categoryTextures.indexOf(textureId);
|
||||
if (categoryIndex !== -1) {
|
||||
categoryTextures.splice(categoryIndex, 1);
|
||||
}
|
||||
|
||||
// 如果分类为空且不是内置分类,删除分类
|
||||
if (categoryTextures.length === 0 && texture.category === "自定义材质") {
|
||||
this.categories.delete(texture.category);
|
||||
}
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
this.textureCache.delete(textureId);
|
||||
|
||||
// 触发事件
|
||||
this._triggerEvent("textureRemoved", texture);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新材质信息
|
||||
* @param {String} textureId 材质ID
|
||||
* @param {Object} updates 更新数据
|
||||
* @returns {Boolean} 是否更新成功
|
||||
*/
|
||||
updateTexture(textureId, updates) {
|
||||
const texture = this.getTextureById(textureId);
|
||||
if (!texture || texture.type === "builtin") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新材质属性
|
||||
Object.assign(texture, updates);
|
||||
|
||||
// 触发事件
|
||||
this._triggerEvent("textureUpdated", texture);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取材质预览URL
|
||||
* @param {Object} texture 材质对象
|
||||
* @returns {String} 预览URL
|
||||
*/
|
||||
getTexturePreviewUrl(texture) {
|
||||
if (!texture) return null;
|
||||
|
||||
// 如果有预览图,使用预览图
|
||||
if (texture.preview) {
|
||||
return texture.preview;
|
||||
}
|
||||
|
||||
// 否则使用原图
|
||||
return texture.path || texture.dataUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载材质图像
|
||||
* @param {String} textureId 材质ID
|
||||
* @returns {Promise<HTMLImageElement>} 图像对象
|
||||
*/
|
||||
loadTextureImage(textureId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 检查缓存
|
||||
if (this.textureCache.has(textureId)) {
|
||||
resolve(this.textureCache.get(textureId));
|
||||
return;
|
||||
}
|
||||
|
||||
const texture = this.getTextureById(textureId);
|
||||
if (!texture) {
|
||||
reject(new Error(`材质 ${textureId} 不存在`));
|
||||
return;
|
||||
}
|
||||
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
|
||||
img.onload = () => {
|
||||
// 缓存图像
|
||||
this.textureCache.set(textureId, img);
|
||||
resolve(img);
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
reject(new Error(`材质 ${textureId} 加载失败`));
|
||||
};
|
||||
|
||||
img.src = texture.path || texture.dataUrl;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索材质
|
||||
* @param {String} query 搜索关键词
|
||||
* @returns {Array} 匹配的材质数组
|
||||
*/
|
||||
searchTextures(query) {
|
||||
if (!query) return this.getAllTextures();
|
||||
|
||||
const searchTerm = query.toLowerCase();
|
||||
return this.getAllTextures().filter((texture) => {
|
||||
return (
|
||||
texture.name.toLowerCase().includes(searchTerm) ||
|
||||
texture.category.toLowerCase().includes(searchTerm) ||
|
||||
texture.description.toLowerCase().includes(searchTerm) ||
|
||||
texture.tags.some((tag) => tag.toLowerCase().includes(searchTerm))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存自定义材质到本地存储
|
||||
*/
|
||||
saveCustomTexturesToStorage() {
|
||||
try {
|
||||
const customTexturesData = this.customTextures.map((texture) => ({
|
||||
...texture,
|
||||
// 不保存file对象到localStorage
|
||||
file: null,
|
||||
}));
|
||||
|
||||
localStorage.setItem("canvasEditor_customTextures", JSON.stringify(customTexturesData));
|
||||
} catch (error) {
|
||||
console.error("保存自定义材质失败:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从本地存储加载自定义材质
|
||||
*/
|
||||
loadCustomTexturesFromStorage() {
|
||||
try {
|
||||
const stored = localStorage.getItem("canvasEditor_customTextures");
|
||||
if (stored) {
|
||||
const customTexturesData = JSON.parse(stored);
|
||||
this.customTextures = customTexturesData;
|
||||
|
||||
// 重建分类索引
|
||||
this.customTextures.forEach((texture) => {
|
||||
if (!this.categories.has(texture.category)) {
|
||||
this.categories.set(texture.category, []);
|
||||
}
|
||||
if (!this.categories.get(texture.category).includes(texture.id)) {
|
||||
this.categories.get(texture.category).push(texture.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("加载自定义材质失败:", error);
|
||||
this.customTextures = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建材质预设
|
||||
* @param {String} name 预设名称
|
||||
* @param {Object} settings 材质设置
|
||||
* @returns {String} 预设ID
|
||||
*/
|
||||
createTexturePreset(name, settings) {
|
||||
const presetId = `preset_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const preset = {
|
||||
id: presetId,
|
||||
name: name,
|
||||
type: "preset",
|
||||
category: "材质预设",
|
||||
created: new Date().toISOString(),
|
||||
settings: {
|
||||
textureId: settings.textureId,
|
||||
scale: settings.scale || 1,
|
||||
opacity: settings.opacity || 1,
|
||||
repeat: settings.repeat || "repeat",
|
||||
angle: settings.angle || 0,
|
||||
brushSize: settings.brushSize || 5,
|
||||
brushOpacity: settings.brushOpacity || 1,
|
||||
brushColor: settings.brushColor || "#000000",
|
||||
},
|
||||
};
|
||||
|
||||
this.customTextures.push(preset);
|
||||
this._triggerEvent("textureAdded", preset);
|
||||
|
||||
return presetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用材质预设
|
||||
* @param {String} presetId 预设ID
|
||||
* @returns {Object|null} 预设设置
|
||||
*/
|
||||
applyTexturePreset(presetId) {
|
||||
const preset = this.getTextureById(presetId);
|
||||
if (!preset || preset.type !== "preset") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return preset.settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
* @param {String} event 事件名称
|
||||
* @param {Function} callback 回调函数
|
||||
*/
|
||||
addEventListener(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].push(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
* @param {String} event 事件名称
|
||||
* @param {Function} callback 回调函数
|
||||
*/
|
||||
removeEventListener(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
const index = this.listeners[event].indexOf(callback);
|
||||
if (index !== -1) {
|
||||
this.listeners[event].splice(index, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
* @param {String} event 事件名称
|
||||
* @param {*} data 事件数据
|
||||
* @private
|
||||
*/
|
||||
_triggerEvent(event, data) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].forEach((callback) => {
|
||||
try {
|
||||
callback(data);
|
||||
} catch (error) {
|
||||
console.error(`执行 ${event} 事件监听器出错:`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有缓存
|
||||
*/
|
||||
clearCache() {
|
||||
this.textureCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取统计信息
|
||||
* @returns {Object} 统计信息
|
||||
*/
|
||||
getStats() {
|
||||
return {
|
||||
builtInCount: this.builtInTextures.length,
|
||||
customCount: this.customTextures.length,
|
||||
totalCount: this.getAllTextures().length,
|
||||
categoriesCount: this.getCategories().length,
|
||||
cacheSize: this.textureCache.size,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证纹理文件
|
||||
* @param {File} file 要验证的文件
|
||||
* @returns {Boolean} 是否为有效的纹理文件
|
||||
*/
|
||||
validateTextureFile(file) {
|
||||
if (!file) {
|
||||
console.warn("文件不存在");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查文件类型
|
||||
if (!file.type.startsWith("image/")) {
|
||||
console.warn("文件类型无效,必须是图片文件");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查文件大小(限制为 10MB)
|
||||
const maxSize = 10 * 1024 * 1024; // 10MB
|
||||
if (file.size > maxSize) {
|
||||
console.warn("文件大小超过限制(10MB)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查支持的图片格式
|
||||
const supportedTypes = [
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/svg+xml",
|
||||
"image/bmp",
|
||||
"image/gif",
|
||||
];
|
||||
|
||||
if (!supportedTypes.includes(file.type)) {
|
||||
console.warn("不支持的图片格式");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建单例实例
|
||||
const texturePresetManager = new TexturePresetManager();
|
||||
|
||||
// 导出单例
|
||||
export default texturePresetManager;
|
||||
Reference in New Issue
Block a user