618 lines
16 KiB
JavaScript
618 lines
16 KiB
JavaScript
/**
|
||
* 材质预设管理器
|
||
* 负责管理所有材质预设,包括内置预设和用户自定义预设
|
||
*/
|
||
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;
|