From 00e515b3dd3896b7bbe8058e625d6964a4e396fc Mon Sep 17 00:00:00 2001 From: liulong <18539103286> Date: Fri, 15 May 2026 23:19:42 +0800 Subject: [PATCH] =?UTF-8?q?feat=E5=AE=8C=E5=96=84=E4=B8=89=E5=B1=82?= =?UTF-8?q?=E5=85=89=E6=A0=85=E5=8D=A1=E7=89=87=E9=A2=84=E8=A7=88=E9=99=80?= =?UTF-8?q?=E8=9E=BA=E4=BB=AA,=E5=85=89=E6=A0=85=E5=8D=A1=E5=A4=9A?= =?UTF-8?q?=E7=B4=A0=E6=9D=90=E6=9E=B6=E6=9E=84=E5=8D=87=E7=BA=A7=E6=8A=80?= =?UTF-8?q?=E6=9C=AF=E8=AE=BE=E8=AE=A1,=E6=96=B0=E5=A2=9E=E8=B5=84?= =?UTF-8?q?=E4=BA=A7-=E7=B4=A0=E6=9D=90=E5=85=B3=E8=81=94=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/dev.sh | 14 +- .../gateway/controller/asset_controller.go | 262 +++++++++ backend/gateway/dto/asset_converter.go | 63 +++ backend/gateway/dto/asset_dto.go | 76 +++ backend/gateway/router/router.go | 8 + backend/pkg/models/material.go | 62 +++ backend/proto/asset.proto | 109 ++++ backend/services/assetService/main.go | 7 +- .../assetService/provider/asset_provider.go | 4 +- .../provider/material_provider.go | 199 +++++++ .../asset_material_relation_repository.go | 70 +++ .../repository/material_repository.go | 49 ++ ...card-multi-material-architecture-design.md | 525 ++++++++++++++++++ .../components/lenticular/HolographicCard.vue | 266 +++++++++ frontend/composables/useHolographicPreview.js | 142 +++++ frontend/pages/asset-detail/asset-detail.vue | 168 +++++- frontend/utils/craftMintSubmit.js | 23 +- frontend/utils/sticker-compositor.js | 330 +++++++++++ frontend/utils/webgl/holographic-engine.js | 421 ++++++++++++++ frontend/utils/webgl/holographic-shaders.js | 322 +++++++++++ frontend/utils/webgl/index.js | 2 + ...materials_and_asset_material_relations.sql | 49 ++ 微信图片_20260514215857_22857_14.jpg | Bin 0 -> 167150 bytes 23 files changed, 3151 insertions(+), 20 deletions(-) create mode 100644 backend/pkg/models/material.go create mode 100644 backend/services/assetService/provider/material_provider.go create mode 100644 backend/services/assetService/repository/asset_material_relation_repository.go create mode 100644 backend/services/assetService/repository/material_repository.go create mode 100644 docs/specs/2026-05-15-lenticular-card-multi-material-architecture-design.md create mode 100644 frontend/components/lenticular/HolographicCard.vue create mode 100644 frontend/composables/useHolographicPreview.js create mode 100644 frontend/utils/sticker-compositor.js create mode 100644 frontend/utils/webgl/holographic-engine.js create mode 100644 frontend/utils/webgl/holographic-shaders.js create mode 100644 frontend/utils/webgl/index.js create mode 100644 supabase/migrations/20260515_create_materials_and_asset_material_relations.sql create mode 100644 微信图片_20260514215857_22857_14.jpg diff --git a/backend/dev.sh b/backend/dev.sh index 6c6531b..7f90e77 100755 --- a/backend/dev.sh +++ b/backend/dev.sh @@ -49,6 +49,10 @@ if [[ "$(uname)" == "Darwin" ]]; then elif [[ "$(uname)" == "Linux" ]]; then WATCHER_TOOL="inotifywait" WATCHER_CMD="inotifywait -r -m -e modify,create,write" +elif [[ "$(uname)" =~ ^MINGW ]] || [[ "$(uname)" =~ ^MSYS ]] || [[ "$(uname)" =~ ^CYGWIN ]]; then + # Windows via Git Bash / MSYS2 / Cygwin + WATCHER_TOOL="fswatch" + WATCHER_CMD="fswatch -r" else echo -e "${RED}不支持的平台${NC}" exit 1 @@ -58,6 +62,10 @@ if ! command -v "$WATCHER_TOOL" &> /dev/null; then echo -e "${RED}缺少工具: $WATCHER_TOOL${NC}" if [[ "$(uname)" == "Darwin" ]]; then echo "安装方法: brew install fswatch" + elif [[ "$(uname)" =~ ^MINGW ]] || [[ "$(uname)" =~ ^MSYS ]] || [[ "$(uname)" =~ ^CYGWIN ]]; then + echo "安装方法: choco install fswatch (Chocolatey)" + echo " scoop install fswatch (Scoop)" + echo " winget install fswatch (WinGet)" else echo "安装方法: sudo apt install inotify-tools (Debian/Ubuntu)" echo " sudo yum install inotify-tools (CentOS/RHEL)" @@ -259,8 +267,8 @@ start_watcher() { --exclude='starbookService$' fi | while read event; do # 时间戳防抖:每次事件更新标记文件 - # Darwin 不支持 date +%s%N,使用 python 获取纳秒时间戳 - if [[ "$(uname)" == "Darwin" ]]; then + # Darwin/Windows 不支持 date +%s%N,使用 python 获取纳秒时间戳 + if [[ "$(uname)" == "Darwin" ]] || [[ "$(uname)" =~ ^MINGW ]] || [[ "$(uname)" =~ ^MSYS ]] || [[ "$(uname)" =~ ^CYGWIN ]]; then python3 -c 'import time; print(int(time.time()*1e9))' > "$restart_marker" else date +%s%N > "$restart_marker" @@ -279,7 +287,7 @@ start_watcher() { continue fi local now - if [[ "$(uname)" == "Darwin" ]]; then + if [[ "$(uname)" == "Darwin" ]] || [[ "$(uname)" =~ ^MINGW ]] || [[ "$(uname)" =~ ^MSYS ]] || [[ "$(uname)" =~ ^CYGWIN ]]; then now=$(python3 -c 'import time; print(int(time.time()*1e9))') else now=$(date +%s%N) diff --git a/backend/gateway/controller/asset_controller.go b/backend/gateway/controller/asset_controller.go index 51349a4..02f4e71 100644 --- a/backend/gateway/controller/asset_controller.go +++ b/backend/gateway/controller/asset_controller.go @@ -1460,3 +1460,265 @@ func (ctrl *AssetController) ImageGeneration(c *gin.Context) { "images": result.Images, }) } + +// UploadMaterial 上传素材 +func (ctrl *AssetController) UploadMaterial(c *gin.Context) { + userID, exists := c.Get("user_id") + if !exists { + response.Error(c, http.StatusUnauthorized, "未授权") + return + } + starID, exists := c.Get("star_id") + if !exists { + response.Error(c, http.StatusUnauthorized, "未授权") + return + } + + var req dto.UploadMaterialRequestDTO + if err := c.ShouldBindJSON(&req); err != nil { + response.Error(c, http.StatusBadRequest, "参数错误: "+err.Error()) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + ctx = context.WithValue(ctx, constant.AttachmentKey, map[string]interface{}{ + "user_id": strconv.FormatInt(userID.(int64), 10), + "star_id": strconv.FormatInt(starID.(int64), 10), + }) + + w := int32(0) + h := int32(0) + if req.Width != nil { + w = int32(*req.Width) + } + if req.Height != nil { + h = int32(*req.Height) + } + + resp, err := ctrl.assetService.UploadMaterial(ctx, &pbAsset.UploadMaterialRequest{ + OssKey: req.OssKey, + OriginalName: req.OriginalName, + FileSize: req.FileSize, + MimeType: req.MimeType, + Width: w, + Height: h, + Hash: req.Hash, + MaterialType: req.MaterialType, + }) + + if err != nil { + logger.Logger.Error("UploadMaterial RPC failed", zap.Error(err)) + code, msg := parseRPCError(err) + response.ErrorWithCode(c, code, msg) + return + } + + if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { + response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) + return + } + + data := dto.ConvertMaterial(resp.Material) + response.Success(c, data) +} + +// BindAssetMaterials 绑定资产素材 +func (ctrl *AssetController) BindAssetMaterials(c *gin.Context) { + assetIDStr := c.Param("asset_id") + assetID, err := strconv.ParseInt(assetIDStr, 10, 64) + if err != nil { + response.Error(c, http.StatusBadRequest, "参数错误: asset_id 必须为数字") + return + } + + if _, exists := c.Get("user_id"); !exists { + response.Error(c, http.StatusUnauthorized, "未授权") + return + } + + var req dto.BindAssetMaterialsRequestDTO + if err := c.ShouldBindJSON(&req); err != nil { + response.Error(c, http.StatusBadRequest, "参数错误: "+err.Error()) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + materials := make([]*pbAsset.AssetMaterialRelation, 0, len(req.Materials)) + for _, item := range req.Materials { + m := &pbAsset.AssetMaterialRelation{ + AssetId: assetID, + MaterialId: item.MaterialID, + MaterialType: item.MaterialType, + LayerOrder: item.LayerOrder, + } + if item.PosX != nil { + m.PosX = *item.PosX + } + if item.PosY != nil { + m.PosY = *item.PosY + } + if item.Opacity != nil { + m.Opacity = *item.Opacity + } + if item.Rotation != nil { + m.Rotation = *item.Rotation + } + if item.ScaleX != nil { + m.ScaleX = *item.ScaleX + } + if item.ScaleY != nil { + m.ScaleY = *item.ScaleY + } + materials = append(materials, m) + } + + resp, err := ctrl.assetService.BindAssetMaterials(ctx, &pbAsset.BindAssetMaterialsRequest{ + AssetId: assetID, + Materials: materials, + }) + + if err != nil { + logger.Logger.Error("BindAssetMaterials RPC failed", zap.Error(err)) + code, msg := parseRPCError(err) + response.ErrorWithCode(c, code, msg) + return + } + + if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { + response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) + return + } + + response.Success(c, gin.H{"message": "关联成功"}) +} + +// GetAssetMaterials 获取资产素材列表 +func (ctrl *AssetController) GetAssetMaterials(c *gin.Context) { + assetIDStr := c.Param("asset_id") + assetID, err := strconv.ParseInt(assetIDStr, 10, 64) + if err != nil { + response.Error(c, http.StatusBadRequest, "参数错误: asset_id 必须为数字") + return + } + + if _, exists := c.Get("user_id"); !exists { + response.Error(c, http.StatusUnauthorized, "未授权") + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + resp, err := ctrl.assetService.GetAssetMaterials(ctx, &pbAsset.GetAssetMaterialsRequest{ + AssetId: assetID, + }) + + if err != nil { + logger.Logger.Error("GetAssetMaterials RPC failed", zap.Error(err)) + code, msg := parseRPCError(err) + response.ErrorWithCode(c, code, msg) + return + } + + if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { + response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) + return + } + + data := make([]dto.AssetMaterialRelationDTO, 0, len(resp.Materials)) + for _, m := range resp.Materials { + data = append(data, dto.ConvertAssetMaterialRelation(m)) + } + + response.Success(c, data) +} + +// UpdateMaterialLayerOrder 更新图层顺序 +func (ctrl *AssetController) UpdateMaterialLayerOrder(c *gin.Context) { + assetIDStr := c.Param("asset_id") + assetID, err := strconv.ParseInt(assetIDStr, 10, 64) + if err != nil { + response.Error(c, http.StatusBadRequest, "参数错误: asset_id 必须为数字") + return + } + + if _, exists := c.Get("user_id"); !exists { + response.Error(c, http.StatusUnauthorized, "未授权") + return + } + + var req dto.UpdateLayerOrderRequestDTO + if err := c.ShouldBindJSON(&req); err != nil { + response.Error(c, http.StatusBadRequest, "参数错误: "+err.Error()) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + orders := make([]*pbAsset.MaterialLayerOrderItem, 0, len(req.Orders)) + for _, o := range req.Orders { + orders = append(orders, &pbAsset.MaterialLayerOrderItem{ + RelationId: o.RelationID, + LayerOrder: o.LayerOrder, + }) + } + + resp, err := ctrl.assetService.UpdateMaterialLayerOrder(ctx, &pbAsset.UpdateMaterialLayerOrderRequest{ + AssetId: assetID, + Orders: orders, + }) + + if err != nil { + logger.Logger.Error("UpdateMaterialLayerOrder RPC failed", zap.Error(err)) + code, msg := parseRPCError(err) + response.ErrorWithCode(c, code, msg) + return + } + + if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { + response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) + return + } + + response.Success(c, gin.H{"message": "更新成功"}) +} + +// UnbindAssetMaterial 解绑资产素材 +func (ctrl *AssetController) UnbindAssetMaterial(c *gin.Context) { + relationIDStr := c.Param("relation_id") + relationID, err := strconv.ParseInt(relationIDStr, 10, 64) + if err != nil { + response.Error(c, http.StatusBadRequest, "参数错误: relation_id 必须为数字") + return + } + + if _, exists := c.Get("user_id"); !exists { + response.Error(c, http.StatusUnauthorized, "未授权") + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + resp, err := ctrl.assetService.UnbindAssetMaterial(ctx, &pbAsset.UnbindAssetMaterialRequest{ + RelationId: relationID, + }) + + if err != nil { + logger.Logger.Error("UnbindAssetMaterial RPC failed", zap.Error(err)) + code, msg := parseRPCError(err) + response.ErrorWithCode(c, code, msg) + return + } + + if resp.Base.Code != pbCommon.StatusCode_STATUS_OK { + response.ErrorWithCode(c, int(resp.Base.Code), resp.Base.Message) + return + } + + response.Success(c, gin.H{"message": "解绑成功"}) +} diff --git a/backend/gateway/dto/asset_converter.go b/backend/gateway/dto/asset_converter.go index 01dd5c4..a6a85aa 100644 --- a/backend/gateway/dto/asset_converter.go +++ b/backend/gateway/dto/asset_converter.go @@ -27,6 +27,69 @@ func ConvertCreateMintOrderResponse(pbResp *pbAsset.CreateMintOrderResponse) *Cr return dto } +// ========== 素材相关转换 ========== + +// ConvertMaterial 转换素材信息 +func ConvertMaterial(pbMaterial *pbAsset.Material) MaterialDTO { + dto := MaterialDTO{ + OssKey: pbMaterial.OssKey, + OriginalName: pbMaterial.OriginalName, + FileSize: pbMaterial.FileSize, + MimeType: pbMaterial.MimeType, + Hash: pbMaterial.Hash, + CreatedBy: pbMaterial.CreatedBy, + StarID: pbMaterial.StarId, + CreatedAt: pbMaterial.CreatedAt, + } + if pbMaterial.MaterialId > 0 { + dto.MaterialID = pbMaterial.MaterialId + } + if pbMaterial.Width > 0 { + w := int(pbMaterial.Width) + dto.Width = &w + } + if pbMaterial.Height > 0 { + h := int(pbMaterial.Height) + dto.Height = &h + } + return dto +} + +// ConvertAssetMaterialRelation 转换资产-素材关联 +func ConvertAssetMaterialRelation(pbRel *pbAsset.AssetMaterialRelation) AssetMaterialRelationDTO { + dto := AssetMaterialRelationDTO{ + RelationID: pbRel.RelationId, + AssetID: pbRel.AssetId, + MaterialID: pbRel.MaterialId, + MaterialType: pbRel.MaterialType, + LayerOrder: pbRel.LayerOrder, + MaterialURLSigned: pbRel.MaterialUrlSigned, + } + if pbRel.PosX != 0 || pbRel.PosY != 0 { + px := pbRel.PosX + py := pbRel.PosY + dto.PosX = &px + dto.PosY = &py + } + if pbRel.Opacity != 0 { + o := pbRel.Opacity + dto.Opacity = &o + } + if pbRel.Rotation != 0 { + r := pbRel.Rotation + dto.Rotation = &r + } + if pbRel.ScaleX != 0 { + sx := pbRel.ScaleX + dto.ScaleX = &sx + } + if pbRel.ScaleY != 0 { + sy := pbRel.ScaleY + dto.ScaleY = &sy + } + return dto +} + // ConvertMintOrder 转换铸造订单 func ConvertMintOrder(pbOrder *pbAsset.MintOrder) MintOrderDTO { dto := MintOrderDTO{ diff --git a/backend/gateway/dto/asset_dto.go b/backend/gateway/dto/asset_dto.go index 8853996..4c5e38e 100644 --- a/backend/gateway/dto/asset_dto.go +++ b/backend/gateway/dto/asset_dto.go @@ -167,3 +167,79 @@ type GetMintOrderResponseDTO struct { Order MintOrderDTO `json:"order"` // 订单信息 Asset *AssetDTO `json:"asset,omitempty"` // 关联的资产信息(如果存在) } + +// ========== 素材相关 DTO ========== + +// MaterialDTO 素材信息 +type MaterialDTO struct { + MaterialID int64 `json:"material_id"` + OssKey string `json:"oss_key"` + OriginalName string `json:"original_name"` + FileSize int64 `json:"file_size"` + MimeType string `json:"mime_type"` + Width *int `json:"width"` + Height *int `json:"height"` + Hash string `json:"hash"` + CreatedBy int64 `json:"created_by"` + StarID int64 `json:"star_id"` + CreatedAt int64 `json:"created_at"` +} + +// AssetMaterialRelationDTO 资产-素材关联信息 +type AssetMaterialRelationDTO struct { + RelationID int64 `json:"relation_id"` + AssetID int64 `json:"asset_id"` + MaterialID int64 `json:"material_id"` + MaterialType string `json:"material_type"` + LayerOrder int32 `json:"layer_order"` + MaterialURLSigned string `json:"material_url_signed,omitempty"` + PosX *float64 `json:"pos_x"` + PosY *float64 `json:"pos_y"` + Opacity *float64 `json:"opacity"` + Rotation *float64 `json:"rotation"` + ScaleX *float64 `json:"scale_x"` + ScaleY *float64 `json:"scale_y"` + Width *int `json:"width"` + Height *int `json:"height"` +} + +// UploadMaterialRequestDTO 上传素材请求 +type UploadMaterialRequestDTO struct { + OssKey string `json:"oss_key" binding:"required"` + OriginalName string `json:"original_name" binding:"required"` + FileSize int64 `json:"file_size" binding:"required"` + MimeType string `json:"mime_type" binding:"required"` + Width *int `json:"width"` + Height *int `json:"height"` + Hash string `json:"hash" binding:"required"` + MaterialType string `json:"material_type"` +} + +// BindAssetMaterialsRequestDTO 绑定资产素材请求 +type BindAssetMaterialsRequestDTO struct { + Materials []BindAssetMaterialItemDTO `json:"materials" binding:"required"` +} + +// BindAssetMaterialItemDTO 单个绑定项 +type BindAssetMaterialItemDTO struct { + MaterialID int64 `json:"material_id" binding:"required"` + MaterialType string `json:"material_type" binding:"required"` + LayerOrder int32 `json:"layer_order"` + PosX *float64 `json:"pos_x"` + PosY *float64 `json:"pos_y"` + Opacity *float64 `json:"opacity"` + Rotation *float64 `json:"rotation"` + ScaleX *float64 `json:"scale_x"` + ScaleY *float64 `json:"scale_y"` +} + +// UpdateLayerOrderRequestDTO 更新图层顺序请求 +type UpdateLayerOrderRequestDTO struct { + Orders []LayerOrderItemDTO `json:"orders" binding:"required"` +} + +// LayerOrderItemDTO 图层顺序项 +type LayerOrderItemDTO struct { + RelationID int64 `json:"relation_id" binding:"required"` + LayerOrder int32 `json:"layer_order"` +} diff --git a/backend/gateway/router/router.go b/backend/gateway/router/router.go index ddfd74c..eab337c 100644 --- a/backend/gateway/router/router.go +++ b/backend/gateway/router/router.go @@ -194,6 +194,14 @@ func SetupRouter(userClient *client.Client, socialClient *client.Client, assetCl assets.GET("/me/items", assetCtrl.GetMyAssets) // 获取我的藏品列表 assets.GET("/:asset_id", assetCtrl.GetAsset) // 获取资产详情 assets.GET("/:asset_id/status", assetCtrl.GetAssetStatus) // 查询上链状态 + + // 素材管理 + materials := assets.Group("/materials") + materials.POST("/upload", assetCtrl.UploadMaterial) + assets.POST("/:asset_id/materials", assetCtrl.BindAssetMaterials) + assets.GET("/:asset_id/materials", assetCtrl.GetAssetMaterials) + assets.PUT("/:asset_id/materials/layer-order", assetCtrl.UpdateMaterialLayerOrder) + assets.DELETE("/materials/:relation_id", assetCtrl.UnbindAssetMaterial) } // 展馆相关路由(需要认证) diff --git a/backend/pkg/models/material.go b/backend/pkg/models/material.go new file mode 100644 index 0000000..7389097 --- /dev/null +++ b/backend/pkg/models/material.go @@ -0,0 +1,62 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// Material 素材表模型 +type Material struct { + ID int64 `gorm:"primaryKey;autoIncrement;column:id"` + OssKey string `gorm:"type:varchar(255);not null;uniqueIndex;column:oss_key"` + OriginalName string `gorm:"type:varchar(255);not null;column:original_name"` + FileSize int64 `gorm:"not null;column:file_size"` + MimeType string `gorm:"type:varchar(100);not null;column:mime_type"` + Width *int `gorm:"column:width"` + Height *int `gorm:"column:height"` + Hash string `gorm:"type:varchar(64);not null;index;column:hash"` + CreatedBy int64 `gorm:"not null;index;column:created_by"` + StarID int64 `gorm:"not null;index;column:star_id"` + CreatedAt int64 `gorm:"not null;column:created_at"` + UpdatedAt int64 `gorm:"not null;column:updated_at"` + DeletedAt *int64 `gorm:"index;column:deleted_at"` +} + +func (Material) TableName() string { return "materials" } + +func (m *Material) BeforeCreate(tx *gorm.DB) error { + now := time.Now().UnixMilli() + m.CreatedAt = now + m.UpdatedAt = now + return nil +} + +func (m *Material) BeforeUpdate(tx *gorm.DB) error { + m.UpdatedAt = time.Now().UnixMilli() + return nil +} + +// AssetMaterialRelation 资产-素材关联表模型 +type AssetMaterialRelation struct { + ID int64 `gorm:"primaryKey;autoIncrement;column:id"` + AssetID int64 `gorm:"not null;index:idx_amr_asset_id;column:asset_id"` + MaterialID int64 `gorm:"not null;index:idx_amr_material_id;column:material_id"` + MaterialType string `gorm:"type:varchar(50);not null;column:material_type"` + LayerOrder int `gorm:"not null;default:0;column:layer_order"` + PosX *float64 `gorm:"column:pos_x"` + PosY *float64 `gorm:"column:pos_y"` + Opacity *float64 `gorm:"default:1.0;column:opacity"` + Rotation *float64 `gorm:"default:0;column:rotation"` + ScaleX *float64 `gorm:"default:1.0;column:scale_x"` + ScaleY *float64 `gorm:"default:1.0;column:scale_y"` + Version int `gorm:"not null;default:1;column:version"` + CreatedAt int64 `gorm:"not null;column:created_at"` + UpdatedAt int64 `gorm:"not null;column:updated_at"` + DeletedAt *int64 `gorm:"index;column:deleted_at"` + + Asset Asset `gorm:"foreignKey:AssetID;references:ID;constraint:OnDelete:CASCADE"` + Material Material `gorm:"foreignKey:MaterialID;references:ID;constraint:OnDelete:RESTRICT"` +} + +func (AssetMaterialRelation) TableName() string { return "asset_material_relations" } diff --git a/backend/proto/asset.proto b/backend/proto/asset.proto index d37e044..bb2961e 100644 --- a/backend/proto/asset.proto +++ b/backend/proto/asset.proto @@ -310,6 +310,100 @@ message GetAssetForRPCResponse { bool is_active = 6; // 是否激活 } +// ==================== 素材相关消息 ==================== + +// 素材信息 +message Material { + int64 material_id = 1; + string oss_key = 2; + string original_name = 3; + int64 file_size = 4; + string mime_type = 5; + int32 width = 6; + int32 height = 7; + string hash = 8; + int64 created_by = 9; + int64 star_id = 10; + int64 created_at = 11; +} + +// 资产-素材关联 +message AssetMaterialRelation { + int64 relation_id = 1; + int64 asset_id = 2; + int64 material_id = 3; + string material_type = 4; + int32 layer_order = 5; + string material_url_signed = 6; + double pos_x = 7; + double pos_y = 8; + double opacity = 9; + double rotation = 10; + double scale_x = 11; + double scale_y = 12; +} + +// 上传素材请求 +message UploadMaterialRequest { + string oss_key = 1; + string original_name = 2; + int64 file_size = 3; + string mime_type = 4; + int32 width = 5; + int32 height = 6; + string hash = 7; + string material_type = 8; +} + +message UploadMaterialResponse { + topfans.common.BaseResponse base = 1; + Material material = 2; +} + +// 绑定资产素材请求 +message BindAssetMaterialsRequest { + int64 asset_id = 1; + repeated AssetMaterialRelation materials = 2; +} + +message BindAssetMaterialsResponse { + topfans.common.BaseResponse base = 1; +} + +// 获取资产素材请求 +message GetAssetMaterialsRequest { + int64 asset_id = 1; +} + +message GetAssetMaterialsResponse { + topfans.common.BaseResponse base = 1; + repeated AssetMaterialRelation materials = 2; +} + +// 更新图层顺序请求 +message UpdateMaterialLayerOrderRequest { + int64 asset_id = 1; + repeated MaterialLayerOrderItem orders = 2; +} + +message MaterialLayerOrderItem { + int64 relation_id = 1; + int32 layer_order = 2; +} + +message UpdateMaterialLayerOrderResponse { + topfans.common.BaseResponse base = 1; +} + +// 解绑资产素材请求 +message UnbindAssetMaterialRequest { + int64 relation_id = 1; +} + +message UnbindAssetMaterialResponse { + topfans.common.BaseResponse base = 1; +} + // ==================== 资产服务 ==================== service AssetService { @@ -384,6 +478,21 @@ service AssetService { // 内部RPC:清除资产点赞记录(供Gallery Service调用,下架时清除记录以便下次展出可再次点赞) rpc ClearAssetLikeRecords(ClearAssetLikeRecordsRequest) returns (ClearAssetLikeRecordsResponse); + + // 素材上传 + rpc UploadMaterial(UploadMaterialRequest) returns (UploadMaterialResponse); + + // 绑定资产素材 + rpc BindAssetMaterials(BindAssetMaterialsRequest) returns (BindAssetMaterialsResponse); + + // 获取资产素材列表 + rpc GetAssetMaterials(GetAssetMaterialsRequest) returns (GetAssetMaterialsResponse); + + // 更新素材图层顺序 + rpc UpdateMaterialLayerOrder(UpdateMaterialLayerOrderRequest) returns (UpdateMaterialLayerOrderResponse); + + // 解绑资产素材 + rpc UnbindAssetMaterial(UnbindAssetMaterialRequest) returns (UnbindAssetMaterialResponse); } // 清除资产点赞记录请求(内部RPC,供Gallery Service调用) diff --git a/backend/services/assetService/main.go b/backend/services/assetService/main.go index 99fb79a..c1be6c6 100644 --- a/backend/services/assetService/main.go +++ b/backend/services/assetService/main.go @@ -110,6 +110,8 @@ func main() { mintOrderRepo := repository.NewMintOrderRepository(database.GetDB()) assetLikeRepo := repository.NewAssetLikeRepository(database.GetDB()) rankingRepo := repository.NewRankingRepository(database.GetDB()) + materialRepo := repository.NewMaterialRepository(database.GetDB()) + relationRepo := repository.NewAssetMaterialRelationRepository(database.GetDB()) logger.Logger.Info("Repository layer initialized") // 创建 Dubbo 客户端 @@ -134,10 +136,11 @@ func main() { mintService := service.NewMintService(assetRepo, mintOrderRepo, userClient, database.GetDB(), config.GlobalAssetConfig, registryRepo) assetLikeService := service.NewAssetLikeService(assetRepo, assetLikeRepo, database.GetDB()) rankingService := service.NewRankingService(rankingRepo, userClient) + materialService := service.NewMaterialService(materialRepo, relationRepo) logger.Logger.Info("Service layer initialized") // 创建 Provider 层实例 - assetProvider := provider.NewAssetProvider(assetService, mintService, assetLikeService) + assetProvider := provider.NewAssetProvider(assetService, mintService, assetLikeService, materialService) rankingProvider := provider.NewRankingProvider(rankingService) logger.Logger.Info("Provider layer initialized") @@ -194,6 +197,8 @@ func autoMigrate() error { &models.Asset{}, &models.MintOrder{}, &models.AssetLike{}, + &models.Material{}, + &models.AssetMaterialRelation{}, } for _, table := range tables { diff --git a/backend/services/assetService/provider/asset_provider.go b/backend/services/assetService/provider/asset_provider.go index 1c2688b..80da8c3 100644 --- a/backend/services/assetService/provider/asset_provider.go +++ b/backend/services/assetService/provider/asset_provider.go @@ -21,17 +21,19 @@ type AssetProvider struct { assetService service.AssetService mintService service.MintService assetLikeService *service.AssetLikeService + materialService *service.MaterialService } // 确保 AssetProvider 实现了 AssetServiceHandler 接口 var _ pb.AssetServiceHandler = (*AssetProvider)(nil) // NewAssetProvider 创建资产服务Provider实例 -func NewAssetProvider(assetService service.AssetService, mintService service.MintService, assetLikeService *service.AssetLikeService) *AssetProvider { +func NewAssetProvider(assetService service.AssetService, mintService service.MintService, assetLikeService *service.AssetLikeService, materialService *service.MaterialService) *AssetProvider { return &AssetProvider{ assetService: assetService, mintService: mintService, assetLikeService: assetLikeService, + materialService: materialService, } } diff --git a/backend/services/assetService/provider/material_provider.go b/backend/services/assetService/provider/material_provider.go new file mode 100644 index 0000000..ec3327e --- /dev/null +++ b/backend/services/assetService/provider/material_provider.go @@ -0,0 +1,199 @@ +package provider + +import ( + "context" + + pb "github.com/topfans/backend/pkg/proto/asset" + pbCommon "github.com/topfans/backend/pkg/proto/common" + "github.com/topfans/backend/pkg/models" + "github.com/topfans/backend/pkg/logger" + "github.com/topfans/backend/services/assetService/service" + "go.uber.org/zap" +) + +// UploadMaterial 上传素材 +func (p *AssetProvider) UploadMaterial(ctx context.Context, req *pb.UploadMaterialRequest) (*pb.UploadMaterialResponse, error) { + userID, starID, err := extractUserInfoFromDubboAttachments(ctx) + if err != nil { + return &pb.UploadMaterialResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "unauthorized"}, + }, err + } + + material := &models.Material{ + OssKey: req.OssKey, + OriginalName: req.OriginalName, + FileSize: req.FileSize, + MimeType: req.MimeType, + Hash: req.Hash, + CreatedBy: userID, + StarID: starID, + } + if req.Width > 0 { + w := int(req.Width) + material.Width = &w + } + if req.Height > 0 { + h := int(req.Height) + material.Height = &h + } + + result, err := p.materialService.UploadMaterial(material) + if err != nil { + logger.Logger.Error("UploadMaterial failed", zap.Error(err)) + return &pb.UploadMaterialResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL, Message: err.Error()}, + }, err + } + + return &pb.UploadMaterialResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK, Message: "ok"}, + Material: &pb.Material{ + MaterialId: result.ID, + OssKey: result.OssKey, + OriginalName: result.OriginalName, + FileSize: result.FileSize, + MimeType: result.MimeType, + Hash: result.Hash, + CreatedBy: result.CreatedBy, + StarId: result.StarID, + CreatedAt: result.CreatedAt, + }, + }, nil +} + +// BindAssetMaterials 绑定资产素材 +func (p *AssetProvider) BindAssetMaterials(ctx context.Context, req *pb.BindAssetMaterialsRequest) (*pb.BindAssetMaterialsResponse, error) { + if _, _, err := extractUserInfoFromDubboAttachments(ctx); err != nil { + return &pb.BindAssetMaterialsResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "unauthorized"}, + }, err + } + + items := make([]service.BindMaterialItem, 0, len(req.Materials)) + for _, m := range req.Materials { + items = append(items, service.BindMaterialItem{ + MaterialID: m.MaterialId, + MaterialType: m.MaterialType, + LayerOrder: m.LayerOrder, + PosX: doublePtr(m.PosX), + PosY: doublePtr(m.PosY), + Opacity: doublePtr(m.Opacity), + Rotation: doublePtr(m.Rotation), + ScaleX: doublePtr(m.ScaleX), + ScaleY: doublePtr(m.ScaleY), + }) + } + + if _, err := p.materialService.BindMaterials(req.AssetId, items); err != nil { + logger.Logger.Error("BindAssetMaterials failed", zap.Error(err)) + return &pb.BindAssetMaterialsResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL, Message: err.Error()}, + }, err + } + + return &pb.BindAssetMaterialsResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK, Message: "ok"}, + }, nil +} + +// GetAssetMaterials 获取资产素材列表 +func (p *AssetProvider) GetAssetMaterials(ctx context.Context, req *pb.GetAssetMaterialsRequest) (*pb.GetAssetMaterialsResponse, error) { + if _, _, err := extractUserInfoFromDubboAttachments(ctx); err != nil { + return &pb.GetAssetMaterialsResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "unauthorized"}, + }, err + } + + relations, err := p.materialService.GetAssetMaterials(req.AssetId) + if err != nil { + return &pb.GetAssetMaterialsResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL, Message: err.Error()}, + }, err + } + + materials := make([]*pb.AssetMaterialRelation, 0, len(relations)) + for _, rel := range relations { + pbRel := &pb.AssetMaterialRelation{ + RelationId: rel.ID, + AssetId: rel.AssetID, + MaterialId: rel.MaterialID, + MaterialType: rel.MaterialType, + LayerOrder: int32(rel.LayerOrder), + } + if rel.PosX != nil { + pbRel.PosX = *rel.PosX + } + if rel.PosY != nil { + pbRel.PosY = *rel.PosY + } + if rel.Opacity != nil { + pbRel.Opacity = *rel.Opacity + } + if rel.Rotation != nil { + pbRel.Rotation = *rel.Rotation + } + if rel.ScaleX != nil { + pbRel.ScaleX = *rel.ScaleX + } + if rel.ScaleY != nil { + pbRel.ScaleY = *rel.ScaleY + } + materials = append(materials, pbRel) + } + + return &pb.GetAssetMaterialsResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK, Message: "ok"}, + Materials: materials, + }, nil +} + +// UpdateMaterialLayerOrder 更新图层顺序 +func (p *AssetProvider) UpdateMaterialLayerOrder(ctx context.Context, req *pb.UpdateMaterialLayerOrderRequest) (*pb.UpdateMaterialLayerOrderResponse, error) { + if _, _, err := extractUserInfoFromDubboAttachments(ctx); err != nil { + return &pb.UpdateMaterialLayerOrderResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "unauthorized"}, + }, err + } + + orders := make(map[int64]int, len(req.Orders)) + for _, o := range req.Orders { + orders[o.RelationId] = int(o.LayerOrder) + } + + if err := p.materialService.UpdateLayerOrder(req.AssetId, orders); err != nil { + return &pb.UpdateMaterialLayerOrderResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL, Message: err.Error()}, + }, err + } + + return &pb.UpdateMaterialLayerOrderResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK, Message: "ok"}, + }, nil +} + +// UnbindAssetMaterial 解绑素材 +func (p *AssetProvider) UnbindAssetMaterial(ctx context.Context, req *pb.UnbindAssetMaterialRequest) (*pb.UnbindAssetMaterialResponse, error) { + if _, _, err := extractUserInfoFromDubboAttachments(ctx); err != nil { + return &pb.UnbindAssetMaterialResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED, Message: "unauthorized"}, + }, err + } + + if err := p.materialService.UnbindMaterial(req.RelationId); err != nil { + return &pb.UnbindAssetMaterialResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_INTERNAL, Message: err.Error()}, + }, err + } + + return &pb.UnbindAssetMaterialResponse{ + Base: &pbCommon.BaseResponse{Code: pbCommon.StatusCode_STATUS_OK, Message: "ok"}, + }, nil +} + +func doublePtr(v float64) *float64 { + if v == 0 { + return nil + } + return &v +} diff --git a/backend/services/assetService/repository/asset_material_relation_repository.go b/backend/services/assetService/repository/asset_material_relation_repository.go new file mode 100644 index 0000000..809e29d --- /dev/null +++ b/backend/services/assetService/repository/asset_material_relation_repository.go @@ -0,0 +1,70 @@ +package repository + +import ( + "time" + + "github.com/topfans/backend/pkg/models" + "gorm.io/gorm" +) + +// AssetMaterialRelationRepository 资产-素材关联数据访问层 +type AssetMaterialRelationRepository struct { + db *gorm.DB +} + +// NewAssetMaterialRelationRepository 创建关联 Repository +func NewAssetMaterialRelationRepository(db *gorm.DB) *AssetMaterialRelationRepository { + return &AssetMaterialRelationRepository{db: db} +} + +// BatchCreate 批量创建关联记录 +func (r *AssetMaterialRelationRepository) BatchCreate(relations []*models.AssetMaterialRelation) error { + if len(relations) == 0 { + return nil + } + return r.db.Create(&relations).Error +} + +// FindByAssetID 查询资产的所有素材关联(按 layer_order 排序) +func (r *AssetMaterialRelationRepository) FindByAssetID(assetID int64) ([]*models.AssetMaterialRelation, error) { + var relations []*models.AssetMaterialRelation + err := r.db. + Preload("Material"). + Where("asset_id = ? AND deleted_at IS NULL", assetID). + Order("layer_order ASC"). + Find(&relations).Error + if err != nil { + return nil, err + } + return relations, nil +} + +// UpdateLayerOrder 批量更新图层顺序 +func (r *AssetMaterialRelationRepository) UpdateLayerOrder(assetID int64, orders map[int64]int) error { + return r.db.Transaction(func(tx *gorm.DB) error { + for relationID, layerOrder := range orders { + if err := tx.Model(&models.AssetMaterialRelation{}). + Where("id = ? AND asset_id = ? AND deleted_at IS NULL", relationID, assetID). + Update("layer_order", layerOrder).Error; err != nil { + return err + } + } + return nil + }) +} + +// SoftDelete 软删除关联记录 +func (r *AssetMaterialRelationRepository) SoftDelete(relationID int64) error { + now := time.Now().UnixMilli() + return r.db.Model(&models.AssetMaterialRelation{}). + Where("id = ? AND deleted_at IS NULL", relationID). + Update("deleted_at", now).Error +} + +// SoftDeleteByAssetID 软删除资产的所有关联记录 +func (r *AssetMaterialRelationRepository) SoftDeleteByAssetID(assetID int64) error { + now := time.Now().UnixMilli() + return r.db.Model(&models.AssetMaterialRelation{}). + Where("asset_id = ? AND deleted_at IS NULL", assetID). + Update("deleted_at", now).Error +} diff --git a/backend/services/assetService/repository/material_repository.go b/backend/services/assetService/repository/material_repository.go new file mode 100644 index 0000000..967639d --- /dev/null +++ b/backend/services/assetService/repository/material_repository.go @@ -0,0 +1,49 @@ +package repository + +import ( + "time" + + "github.com/topfans/backend/pkg/models" + "gorm.io/gorm" +) + +// MaterialRepository 素材数据访问层 +type MaterialRepository struct { + db *gorm.DB +} + +// NewMaterialRepository 创建素材 Repository +func NewMaterialRepository(db *gorm.DB) *MaterialRepository { + return &MaterialRepository{db: db} +} + +// Create 创建素材记录 +func (r *MaterialRepository) Create(material *models.Material) error { + return r.db.Create(material).Error +} + +// FindByOssKey 根据 OSS Key 查找 +func (r *MaterialRepository) FindByOssKey(ossKey string) (*models.Material, error) { + var m models.Material + err := r.db.Where("oss_key = ? AND deleted_at IS NULL", ossKey).First(&m).Error + if err != nil { + return nil, err + } + return &m, nil +} + +// FindByHash 根据哈希查找(去重用) +func (r *MaterialRepository) FindByHash(hash string, starID int64) (*models.Material, error) { + var m models.Material + err := r.db.Where("hash = ? AND star_id = ? AND deleted_at IS NULL", hash, starID).First(&m).Error + if err != nil { + return nil, err + } + return &m, nil +} + +// SoftDelete 软删除素材 +func (r *MaterialRepository) SoftDelete(materialID int64) error { + now := time.Now().UnixMilli() + return r.db.Model(&models.Material{}).Where("id = ?", materialID).Update("deleted_at", now).Error +} diff --git a/docs/specs/2026-05-15-lenticular-card-multi-material-architecture-design.md b/docs/specs/2026-05-15-lenticular-card-multi-material-architecture-design.md new file mode 100644 index 0000000..581f389 --- /dev/null +++ b/docs/specs/2026-05-15-lenticular-card-multi-material-architecture-design.md @@ -0,0 +1,525 @@ +# 光栅卡多素材架构升级技术设计 + +> **创建日期:** 2026-05-15 +> **项目:** TopFans 光栅卡铸造流程 +> **服务:** assetService (Go Dubbo-go) + 前端铸爱模块 +> **状态:** 审核通过,待开发实施 +> **版本:** v1.0 + +--- + +## 一、背景与问题确认 + +### 1.1 当前故障 + +**问题定性:光栅卡铸造流程中,背景图从未被持久化到后端。确认铸造时仅上传主体图,背景图永久丢失。** + +``` +create.vue (双图上传) → buildCraftFormData() → CASTLOVE_FORM_KEY → ... + ✅ bgPath / subjectPath 完整传递到本地 Storage + ✅ buildLenticularLayersTwo(bgPath, subjectPath) 正确构建图层 + ❌ handleCraftMint() 只取 mid 层 → submitCraftMintFromPath(单 imagePath) + ❌ 后端 material_url 单字段 → 背景图丢失 +``` + +### 1.2 根本技术缺陷 + +现有 `assets.material_url` 单字段存储存在以下瓶颈: + +| 缺陷 | 说明 | +|------|------| +| 无法支撑多素材 | 单一字段存储,多素材需内嵌 JSON(受限于 `varchar(500)`,最多 3-4 个 URL) | +| 数据语义模糊 | 无法区分主图、背景、遮罩、特效等不同角色 | +| 查询效率低下 | 无法针对特定素材类型建立索引 | +| 复用能力缺失 | 素材无法被多个资产共享 | +| 长期扩展性差 | 未来 3 年单资产素材数预计增长至 30+,现有模型无法承载 | + +--- + +## 二、方案设计:新增资产-素材关联表 + +### 2.1 方案概述 + +采用新增 `materials`(素材主表)+ `asset_material_relations`(资产-素材关联表)的方案,彻底解耦素材与资产的强绑定关系。 + +### 2.2 表关系定义 + +``` +assets ◄── 1:N ──► asset_material_relations ◄── N:1 ──► materials +``` + +- **资产主表 ↔ 关联表**:一对多关系,单个资产可绑定多个素材关联记录 +- **素材主表 ↔ 关联表**:多对多关系,单个素材可被多个资产关联复用 + +### 2.3 数据库模型设计 + +#### 2.3.1 素材主表(materials) + +```sql +CREATE TABLE materials ( + id BIGSERIAL PRIMARY KEY, + oss_key VARCHAR(255) NOT NULL, + original_name VARCHAR(255) NOT NULL, + file_size BIGINT NOT NULL, + mime_type VARCHAR(100) NOT NULL, + width INT, + height INT, + hash VARCHAR(64) NOT NULL, + created_by BIGINT NOT NULL, + star_id BIGINT NOT NULL DEFAULT 0, + created_at BIGINT NOT NULL, + updated_at BIGINT NOT NULL, + deleted_at BIGINT +); + +CREATE UNIQUE INDEX uk_materials_oss_key ON materials(oss_key); +CREATE INDEX idx_materials_hash ON materials(hash); +CREATE INDEX idx_materials_created_by ON materials(created_by); +CREATE INDEX idx_materials_star_id ON materials(star_id); +``` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | `BIGSERIAL` | 主键 | +| `oss_key` | `VARCHAR(255) UNIQUE` | OSS 对象唯一标识 | +| `original_name` | `VARCHAR(255)` | 原始文件名 | +| `file_size` | `BIGINT` | 文件大小(字节) | +| `mime_type` | `VARCHAR(100)` | MIME 类型 | +| `width / height` | `INT` | 图片尺寸 | +| `hash` | `VARCHAR(64)` | 文件 SHA256 哈希,用于去重 | +| `created_by` | `BIGINT` | 创建者用户 ID | +| `star_id` | `BIGINT` | 多星数据隔离 | +| `created_at / updated_at` | `BIGINT` | 毫秒时间戳 | +| `deleted_at` | `BIGINT` | 软删除时间戳 | + +#### 2.3.2 资产-素材关联表(asset_material_relations) + +```sql +CREATE TABLE asset_material_relations ( + id BIGSERIAL PRIMARY KEY, + asset_id BIGINT NOT NULL REFERENCES assets(id) ON DELETE CASCADE, + material_id BIGINT NOT NULL REFERENCES materials(id) ON DELETE RESTRICT, + material_type VARCHAR(50) NOT NULL, + layer_order INT NOT NULL DEFAULT 0, + -- 渲染定位字段(NULL = 拉伸填满容器) + pos_x DOUBLE PRECISION, -- 距左上角 X 偏移量(px),NULL=拉伸模式 + pos_y DOUBLE PRECISION, -- 距左上角 Y 偏移量(px),NULL=拉伸模式 + opacity DOUBLE PRECISION DEFAULT 1.0, -- 不透明度 0~1 + rotation DOUBLE PRECISION DEFAULT 0, -- 旋转角度(度),正值为顺时针 + scale_x DOUBLE PRECISION DEFAULT 1.0, -- 水平缩放比例 + scale_y DOUBLE PRECISION DEFAULT 1.0, -- 垂直缩放比例 + version INT NOT NULL DEFAULT 1, + created_at BIGINT NOT NULL, + updated_at BIGINT NOT NULL, + deleted_at BIGINT +); +``` + +**索引与约束:** + +```sql +CREATE INDEX idx_amr_asset_id ON asset_material_relations(asset_id) WHERE deleted_at IS NULL; +CREATE INDEX idx_amr_material_id ON asset_material_relations(material_id) WHERE deleted_at IS NULL; +CREATE INDEX idx_amr_asset_type_layer ON asset_material_relations(asset_id, material_type, layer_order) WHERE deleted_at IS NULL; +CREATE UNIQUE INDEX uk_amr_asset_type_active ON asset_material_relations(asset_id, material_type) WHERE deleted_at IS NULL; +CREATE UNIQUE INDEX uk_amr_asset_layer_active ON asset_material_relations(asset_id, layer_order) WHERE deleted_at IS NULL; +``` + +**字段说明:** + +| 字段 | 类型 | 说明 | +|------|------|------| +| `asset_id` | `BIGINT FK` | 关联资产 ID | +| `material_id` | `BIGINT FK` | 关联素材 ID | +| `material_type` | `VARCHAR(50)` | 素材角色:`main`/`bg`/`star_map`/`mask`/`effect` 等 | +| `layer_order` | `INT` | 图层渲染顺序,数值越小越靠下 | +| `pos_x / pos_y` | `DOUBLE PRECISION` | 距左上角偏移量(px),NULL 为拉伸填满容器模式 | +| `opacity` | `DOUBLE PRECISION` | 不透明度 0~1,默认 1 | +| `rotation` | `DOUBLE PRECISION` | 旋转角度(度),正值为顺时针,默认 0 | +| `scale_x / scale_y` | `DOUBLE PRECISION` | 缩放比例,默认 1 | +| `version` | `INT` | 素材版本号,乐观锁控制并发 | +| `deleted_at` | `BIGINT` | 软删除时间戳 | + +### 2.4 Go Model 定义 + +```go +// Material 素材表模型 +type Material struct { + ID int64 `gorm:"primaryKey;autoIncrement;column:id"` + OssKey string `gorm:"type:varchar(255);not null;uniqueIndex;column:oss_key"` + OriginalName string `gorm:"type:varchar(255);not null;column:original_name"` + FileSize int64 `gorm:"not null;column:file_size"` + MimeType string `gorm:"type:varchar(100);not null;column:mime_type"` + Width *int `gorm:"column:width"` + Height *int `gorm:"column:height"` + Hash string `gorm:"type:varchar(64);not null;index;column:hash"` + CreatedBy int64 `gorm:"not null;index;column:created_by"` + StarID int64 `gorm:"not null;index;column:star_id"` + CreatedAt int64 `gorm:"not null;column:created_at"` + UpdatedAt int64 `gorm:"not null;column:updated_at"` + DeletedAt *int64 `gorm:"index;column:deleted_at"` +} + +func (Material) TableName() string { return "materials" } + +func (m *Material) BeforeCreate(tx *gorm.DB) error { + now := time.Now().UnixMilli() + m.CreatedAt = now + m.UpdatedAt = now + return nil +} + +func (m *Material) BeforeUpdate(tx *gorm.DB) error { + m.UpdatedAt = time.Now().UnixMilli() + return nil +} +``` + +```go +// AssetMaterialRelation 资产-素材关联表模型 +type AssetMaterialRelation struct { + ID int64 `gorm:"primaryKey;autoIncrement;column:id"` + AssetID int64 `gorm:"not null;index:idx_amr_asset_id;column:asset_id"` + MaterialID int64 `gorm:"not null;index:idx_amr_material_id;column:material_id"` + MaterialType string `gorm:"type:varchar(50);not null;column:material_type"` + LayerOrder int `gorm:"not null;default:0;column:layer_order"` + PosX *float64 `gorm:"column:pos_x"` + PosY *float64 `gorm:"column:pos_y"` + Opacity *float64 `gorm:"default:1.0;column:opacity"` + Rotation *float64 `gorm:"default:0;column:rotation"` + ScaleX *float64 `gorm:"default:1.0;column:scale_x"` + ScaleY *float64 `gorm:"default:1.0;column:scale_y"` + Version int `gorm:"not null;default:1;column:version"` + CreatedAt int64 `gorm:"not null;column:created_at"` + UpdatedAt int64 `gorm:"not null;column:updated_at"` + DeletedAt *int64 `gorm:"index;column:deleted_at"` + + Asset Asset `gorm:"foreignKey:AssetID;references:ID;constraint:OnDelete:CASCADE"` + Material Material `gorm:"foreignKey:MaterialID;references:ID;constraint:OnDelete:RESTRICT"` +} + +func (AssetMaterialRelation) TableName() string { return "asset_material_relations" } +``` + +--- + +## 三、数据一致性保障方案 + +### 3.1 事务隔离级别与约束规则 + +- **隔离级别**:所有跨表操作用 `REPEATABLE READ` +- **级联规则**:资产删除时 `ON DELETE CASCADE` 级联删除关联记录;素材删除时 `ON DELETE RESTRICT` 禁止删除被引用的素材 +- **唯一约束**:通过 PostgreSQL 部分唯一索引 `WHERE deleted_at IS NULL` 实现,允许同一资产同一类型在不同版本间切换 + +### 3.2 异常场景回滚机制 + +| 异常场景 | 回滚机制 | +|---------|---------| +| 素材上传失败 | 回滚 `materials` 插入记录,删除 OSS 部分文件 | +| 关联绑定异常 | 回滚关联表插入,保留素材记录用于后续复用 | +| 资产删除中断 | 事务回滚,恢复资产和关联记录 | +| 素材版本更新冲突 | 乐观锁(`version` 字段),冲突时提示用户刷新 | +| 跨节点事务超时 | SAGA 模式,超时后执行补偿操作 | +| 非法参数注入 | 入参校验失败直接拒绝,不执行 DB 操作 | +| 脏数据写入 | 外键 + 唯一索引双重阻拦 | +| 并发绑定锁竞争 | `SELECT ... FOR UPDATE` 行级锁 | +| 软删除标记异常 | 定期数据校验任务修正 | +| 批量导入中断 | 分批事务提交,已提交批次保留,未提交回滚 | + +### 3.3 脏数据清理规则 + +| 规则项 | 配置 | +|--------|------| +| 清理周期 | 每日凌晨 3 点 | +| 软删除清理 | 超过 30 天的软删除记录 | +| 孤立素材清理 | 创建超过 7 天且无任何关联记录的素材 | +| 重复素材清理 | 相同 `hash` 值的重复记录 | +| 备份要求 | 清理前备份到冷存储,保留 90 天 | +| 安全间隔 | 先标记 24 小时后无异常再物理删除 | + +--- + +## 四、核心业务流程 + +### 4.1 资产创建与素材关联 + +``` +用户 → 前端:上传多个素材文件 +前端 → OSS:分片上传素材 +OSS → 前端:返回 oss_key +前端 → 后端:创建资产请求(含素材列表和图层信息) +后端 → materials:批量插入素材记录(hash 去重) +后端 → assets:插入资产记录 +后端 → asset_material_relations:批量插入关联记录(含渲染定位字段) +后端 → 前端:返回资产 ID 和素材列表 +``` + +### 4.2 图层顺序调整 + +``` +用户 → 前端:拖拽调整图层顺序 +前端 → 后端:PUT /api/v1/assets/{id}/materials/layer-order +后端 → 关联表:开启事务 → SELECT FOR UPDATE 锁住该资产所有关联记录 +后端 → 关联表:批量更新 layer_order +后端 → 前端:返回更新结果 +``` + +### 4.3 资产删除 + +``` +用户 → 前端:删除资产请求 +后端 → assets:软删除(设置 deleted_at) +后端 → asset_material_relations:级联软删除所有关联记录 +后端 → 前端:返回删除成功 +``` + +--- + +## 五、API 接口规范 + +### 5.1 素材上传 + +``` +POST /api/v1/materials/upload +Content-Type: multipart/form-data + +入参: + - file: 文件(必填) + - type: 素材类型(可选) + +出参: + { "code": 0, "data": { "material_id": 123, "oss_key": "assets/123.jpg", + "url": "https://oss.example.com/assets/123.jpg", "width": 1080, "height": 1920 } } +``` + +### 5.2 资产-素材关联 + +``` +POST /api/v1/assets/{asset_id}/materials +Content-Type: application/json + +入参: + { "materials": [ + { "material_id": 123, "material_type": "main", "layer_order": 0, + "pos_x": null, "pos_y": null, "opacity": 1.0, "rotation": 0, "scale_x": 1.0, "scale_y": 1.0 }, + { "material_id": 456, "material_type": "bg", "layer_order": 1 } + ] } + +出参: + { "code": 0, "message": "关联成功" } +``` + +### 5.3 资产素材查询 + +``` +GET /api/v1/assets/{asset_id}/materials + +出参: + { "code": 0, "data": [ + { "relation_id": 1, "material_id": 123, "material_type": "main", + "layer_order": 0, "url": "https://oss.example.com/assets/123.jpg", + "pos_x": null, "pos_y": null, "opacity": 1.0, "rotation": 0, + "scale_x": 1.0, "scale_y": 1.0, "width": 1080, "height": 1920 } + ] } +``` + +### 5.4 图层顺序更新 + +``` +PUT /api/v1/assets/{asset_id}/materials/layer-order + +入参: + { "orders": [ { "relation_id": 1, "layer_order": 0 }, { "relation_id": 2, "layer_order": 1 } ] } +``` + +### 5.5 权限控制 + +| 角色 | 权限边界 | +|------|---------| +| 普通用户 | 仅可操作自己创建的素材和资产,校验 `created_by` / `owner_uid` | +| 运营人员 | 可管理所有用户的素材和资产,支持批量操作 | +| 管理员 | 所有权限,含数据清理任务执行权限 | + +> MVP 阶段简化为「用户仅可操作自身数据」,运营人员角色后置实现。 + +--- + +## 六、Proto 扩展 + +```protobuf +message Material { + int64 material_id = 1; + string oss_key = 2; + string original_name = 3; + int64 file_size = 4; + string mime_type = 5; + int32 width = 6; + int32 height = 7; + string hash = 8; + int64 created_by = 9; + int64 star_id = 10; + int64 created_at = 11; +} + +message AssetMaterialRelation { + int64 relation_id = 1; + int64 asset_id = 2; + int64 material_id = 3; + string material_type = 4; + int32 layer_order = 5; + string material_url_signed = 6; + double pos_x = 7; + double pos_y = 8; + double opacity = 9; + double rotation = 10; + double scale_x = 11; + double scale_y = 12; +} + +service AssetService { + rpc UploadMaterial(UploadMaterialRequest) returns (UploadMaterialResponse); + rpc BindAssetMaterials(BindAssetMaterialsRequest) returns (BindAssetMaterialsResponse); + rpc GetAssetMaterials(GetAssetMaterialsRequest) returns (GetAssetMaterialsResponse); + rpc UpdateMaterialLayerOrder(UpdateMaterialLayerOrderRequest) returns (UpdateMaterialLayerOrderResponse); + rpc UnbindAssetMaterial(UnbindAssetMaterialRequest) returns (UnbindAssetMaterialResponse); +} +``` + +--- + +## 七、性能优化 + +### 7.1 缓存策略 + +| 层级 | 实现 | 配置 | +|------|------|------| +| L1(Redis) | Hash: `asset:materials:{asset_id}` | TTL 1 小时,写入时主动 INVALIDATE | +| L2(go-cache) | 热点资产 Top 1000 | TTL 5 分钟,访问频率 > 10次/分钟 提升到热点 | + +### 7.2 查询优化 + +```go +// GORM Preload 方式(推荐) +var asset Asset +db.Preload("Materials", func(db *gorm.DB) *gorm.DB { + return db.Where("asset_material_relations.deleted_at IS NULL"). + Order("asset_material_relations.layer_order ASC") +}).First(&asset, assetID) + +// 批量查询 Raw SQL(高性能场景) +rows, _ := db.Raw(` + SELECT a.*, amr.material_type, amr.layer_order, m.oss_key + FROM assets a + LEFT JOIN asset_material_relations amr ON amr.asset_id = a.id AND amr.deleted_at IS NULL + LEFT JOIN materials m ON amr.material_id = m.id AND m.deleted_at IS NULL + WHERE a.id = ? AND a.deleted_at IS NULL + ORDER BY amr.layer_order ASC +`, assetID).Rows() +``` + +### 7.3 分库分表预案 + +| 触发条件 | 分表策略 | +|---------|---------| +| `assets` > 1000 万行 | 按 `asset_id` 哈希分 16 表 | +| `asset_material_relations` > 1 亿行 | 同上 | + +### 7.4 性能预估 + +| 场景 | 预估耗时 | +|------|---------| +| 单资产 10 素材查询 | < 5ms | +| 单资产 50 素材查询 | < 15ms | +| 批量 100 资产(平均 5 素材/资产) | < 50ms | + +--- + +## 八、数据迁移与兼容性 + +### 8.1 迁移策略 + +| 阶段 | 天数 | 内容 | +|------|------|------| +| 双写 | 第 1-3 天 | 同时写入 `material_url` 和关联表 | +| 灰度切流 | 第 4-5 天 | 1% → 50% → 100% 逐步切读 | +| 观察期 | 第 6-7 天 | 全量走新模型,持续监测 | +| 清理 | 第 14 天 | 停止双写,material_url 设为可空 | + +### 8.2 迁移脚本核心逻辑 + +```go +func migrateMaterials() error { + for offset := 0; ; offset += 1000 { + assets := queryAssets(offset, 1000) + if len(assets) == 0 { break } + for _, asset := range assets { + tx := db.Begin() + // 解析 material_url(兼容单字符串和 JSON 格式) + materials := parseMaterialURL(asset.MaterialURL) + // 插入 materials 表(hash 去重) + materialIDs := batchInsertMaterials(tx, materials) + // 插入关联表 + batchInsertRelations(tx, asset.ID, materialIDs, materials) + tx.Commit() + } + } +} +``` + +### 8.3 灰度观测指标与回滚 + +| 指标 | 正常阈值 | 回滚触发 | +|------|---------|---------| +| 接口错误率 | < 0.01% | > 1% | +| 查询响应时间 | < 50ms(P99) | > 200ms | +| 数据一致性 | 100% | < 99.9% | + +--- + +## 九、测试验收标准 + +### 9.1 测试场景 + +| 类型 | 场景 | +|------|------| +| 单元测试 | 关联表 CRUD 事务逻辑、乐观锁冲突、外键约束、唯一约束 | +| 集成测试 | 资产创建 → 素材上传 → 关联绑定 → 图层调整 → 资产删除全流程 | +| 压力测试 | 单资产关联 50 个素材,QPS=1000,验证系统稳定性 | + +### 9.2 验收指标 + +| 指标 | 目标值 | +|------|--------| +| 单资产 10 素材查询 | < 50ms | +| 单资产 50 素材查询 | < 100ms | +| 接口错误率 | < 0.01% | +| 数据一致性通过率 | 100% | +| 支持并发 QPS | ≥ 1000 | + +--- + +## 十、实施计划 + +``` +第 1-2 天:DDL 建表 → 测试环境验证 +第 3-4 天:Go Model + Proto + 网关 DTO + Converter +第 5-6 天:Service CRUD + 事务 + 缓存 +第 7 天: 前端适配 + 数据迁移脚本 +第 8-9 天:灰度发布(双写 → 切流 → 观察) +第 10 天: 上线确认 + 回滚预案待命 +``` + +| 合计 | 10 个工作日 | + +--- + +## 十一、关联文档 + +| 文档 | 说明 | +|------|------| +| [Asset 数据模型](file:///e:/develop/code/topfans/backend/pkg/models/asset.go) | 现有 assets/mint_orders 表结构 | +| [Proto 定义](file:///e:/develop/code/topfans/backend/proto/asset.proto) | 现有 RPC 消息定义 | +| [资产控制器](file:///e:/develop/code/topfans/backend/gateway/controller/asset_controller.go) | 现有网关层处理逻辑 | +| [Redis 配置](file:///e:/develop/code/topfans/backend/pkg/database/redis.go) | 现有 Redis 客户端 | +| [铸爱提交流程](file:///e:/develop/code/topfans/frontend/utils/craftMintSubmit.js) | OSS 上传 + 创建订单 | +| [铸造路由管理](file:///e:/develop/code/topfans/frontend/utils/castloveGenerationFlow.js) | Storage Key 管理 + 页面跳转 | diff --git a/frontend/components/lenticular/HolographicCard.vue b/frontend/components/lenticular/HolographicCard.vue new file mode 100644 index 0000000..1a47454 --- /dev/null +++ b/frontend/components/lenticular/HolographicCard.vue @@ -0,0 +1,266 @@ + + + + + diff --git a/frontend/composables/useHolographicPreview.js b/frontend/composables/useHolographicPreview.js new file mode 100644 index 0000000..815a05b --- /dev/null +++ b/frontend/composables/useHolographicPreview.js @@ -0,0 +1,142 @@ +/** + * 全息镭射卡预览组合式函数 + * + * 集成 WebGL HolographicEngine + 陀螺仪/触摸交互 + * 与现有 useLenticularPreview / useLenticularStudioTilt 架构保持一致 + */ +import { ref, reactive, onMounted, onUnmounted } from 'vue' + +function clamp(v, min, max) { return Math.max(min, Math.min(max, v)) } +function lerp(a, b, t) { return a + (b - a) * t } + +export function useHolographicPreview() { + const physics = reactive({ + tiltSensitivity: 72, + transitionSmoothness: 66, + angleStability: 88, + sensorDeadzoneStrength: 1, + gyroSimEnabled: true, + }) + const viewAngle = reactive({ x: 0, y: 0, z: 0 }) + const gyroSource = ref('simulation') + const isWebGLReady = ref(false) + const hasWebGLError = ref(false) + const fps = ref(0) + + let accelHandler = null + let accelSmoothed = 0 + let accelBaselineReady = false + let accelBaseX = 0 + let accelBaseY = 0 + + function simulate(x, y) { + viewAngle.x = clamp(x, -1, 1) + viewAngle.y = clamp(y != null ? y : 0, -1, 1) + viewAngle.z = Math.sqrt(viewAngle.x * viewAngle.x + viewAngle.y * viewAngle.y) * 0.5 + } + + function relax(factor = 0.85) { + viewAngle.x *= factor + viewAngle.y *= factor + viewAngle.z *= factor + } + + function startGyro() { + gyroSource.value = 'simulation' + if (typeof DeviceOrientationEvent === 'undefined') return + + if (typeof DeviceOrientationEvent.requestPermission === 'function') { + gyroSource.value = 'deviceorientation-requesting' + DeviceOrientationEvent.requestPermission() + .then(state => { if (state === 'granted') startDeviceOrientation() }) + .catch(() => { gyroSource.value = 'simulation' }) + } else { + startDeviceOrientation() + } + } + + function startDeviceOrientation() { + gyroSource.value = 'deviceorientation' + const handler = (e) => { + if (e.gamma == null || e.beta == null) return + const gamma = e.gamma || 0 + const beta = e.beta || 0 + if (!accelBaselineReady) { + accelBaseX = lerp(accelBaseX, gamma, 0.08) + accelBaseY = lerp(accelBaseY, beta, 0.08) + if (Math.abs(accelBaseX - gamma) < 0.3 && Math.abs(accelBaseY - beta) < 0.3) { + accelBaselineReady = true + } + return + } + const stab = clamp(physics.angleStability / 100, 0, 1) + const sens = physics.tiltSensitivity / 100 + const k = 0.02 + (1 - stab) * 0.08 + const dx = (gamma - accelBaseX) / 45 * sens + const dy = (beta - accelBaseY) / 45 * sens + accelSmoothed = lerp(accelSmoothed, dx, k) + const dead = physics.sensorDeadzoneStrength || 1 + const db = 0.016 * dead + simulate( + Math.abs(accelSmoothed) < db ? 0 : clamp(accelSmoothed, -1, 1), + clamp(dy, -1, 1) + ) + } + window.addEventListener('deviceorientation', handler, true) + accelHandler = handler + } + + function stopGyro() { + if (accelHandler) { + window.removeEventListener('deviceorientation', accelHandler, true) + accelHandler = null + } + gyroSource.value = 'simulation' + accelBaselineReady = false + } + + function onWebGLReady() { isWebGLReady.value = true; hasWebGLError.value = false } + function onWebGLError() { hasWebGLError.value = true; isWebGLReady.value = false } + function onFPSUpdate(v) { fps.value = v } + + onMounted(() => { + if (physics.gyroSimEnabled) setTimeout(startGyro, 600) + }) + onUnmounted(() => { stopGyro() }) + + return { + physics, viewAngle, gyroSource, + isWebGLReady, hasWebGLError, fps, + simulate, relax, startGyro, stopGyro, + onWebGLReady, onWebGLError, onFPSUpdate, + } +} + +export function detectPerformanceTier() { + const mem = navigator.deviceMemory || 4 + const cores = navigator.hardwareConcurrency || 4 + if (mem <= 2 || cores <= 2) return 'low' + if (mem <= 4 || cores <= 4) return 'mid' + return 'high' +} + +export const HOLO_PERFORMANCE_PRESETS = { + high: { + effectIntensity: 0.85, dispersionStrength: 1.0, diffractionScale: 0.7, + highlightSpeed: 0.8, highlightWidth: 1.0, fresnelPower: 3.5, + noiseScale: 1.0, noiseOctaves: 6, + safeZoneRadius: 0.35, safeZoneSoftness: 0.15, + }, + mid: { + effectIntensity: 0.75, dispersionStrength: 0.8, diffractionScale: 0.55, + highlightSpeed: 0.7, highlightWidth: 1.1, fresnelPower: 3.0, + noiseScale: 0.8, noiseOctaves: 4, + safeZoneRadius: 0.35, safeZoneSoftness: 0.15, + }, + low: { + effectIntensity: 0.6, dispersionStrength: 0.5, diffractionScale: 0.35, + highlightSpeed: 0.5, highlightWidth: 1.3, fresnelPower: 2.5, + noiseScale: 0.55, noiseOctaves: 3, + safeZoneRadius: 0.38, safeZoneSoftness: 0.18, + }, +} diff --git a/frontend/pages/asset-detail/asset-detail.vue b/frontend/pages/asset-detail/asset-detail.vue index afc6f34..2086156 100644 --- a/frontend/pages/asset-detail/asset-detail.vue +++ b/frontend/pages/asset-detail/asset-detail.vue @@ -133,13 +133,41 @@ - + - - + + + + + + + + + @@ -293,9 +321,11 @@ import LenticularCard from '@/components/lenticular/LenticularCard.vue'; import { useLenticularCraftTiltPreview } from '@/composables/useLenticularCraftTiltPreview.js'; import { buildLenticularLayersTwo, + buildLenticularLayers, LENTICULAR_STUDIO_STORAGE_KEY, CRAFT_LENTICULAR_CN, CRAFT_LASER_CARD_CN, + CRAFT_TAG_LENTICULAR, } from '@/utils/castloveMintForm.js'; import { CASTLOVE_FORM_KEY, @@ -304,6 +334,7 @@ import { STUDIO_LASER, } from '@/utils/castloveGenerationFlow.js'; import { submitCraftMintFromPath } from '@/utils/craftMintSubmit.js'; +import { composeStickers, relationsToStickers } from '@/utils/sticker-compositor.js'; // 页面参数 const assetIdParam = ref(''); const orderIdParam = ref(''); @@ -324,6 +355,15 @@ const { stopTiltPreview, } = useLenticularCraftTiltPreview(lenticularLayers); +const isLenticularAsset = computed(() => { + if (craftConfirmMode.value) return false + const tags = assetData.value?.tags + if (Array.isArray(tags) && tags.includes(CRAFT_TAG_LENTICULAR)) return true + return false +}) + +const isLenticularDetail = ref(false) + const isCraftLenticular = computed(() => studioKindParam.value === STUDIO_LENTICULAR); const craftCategoryLabel = computed(() => { if (isCraftLenticular.value) return CRAFT_LENTICULAR_CN; @@ -362,6 +402,55 @@ const likedUsers = ref([ { avatar: '/static/sucai/image-06.png', ellipseX: -16, ellipseY: 32 ,size:1.15 } ]); +// ---- 贴纸相关 ---- +const activeStickers = ref([]); +const compositingStickers = ref(false); + +function getStickerStyle(sticker) { + const ox = (sticker.pos_x != null ? sticker.pos_x : 0.5) * 100 + const oy = (sticker.pos_y != null ? sticker.pos_y : 0.5) * 100 + const rot = sticker.rotation != null ? sticker.rotation : 0 + const scX = sticker.scale_x != null ? sticker.scale_x : 1 + const scY = sticker.scale_y != null ? sticker.scale_y : 1 + const op = sticker.opacity != null ? sticker.opacity : 1 + return { + left: `${ox}%`, + top: `${oy}%`, + transform: `translate(-50%, -50%) rotate(${rot}deg) scale(${scX}, ${scY})`, + opacity: op, + } +} + +async function loadStickersForAsset(materialRelations) { + if (!materialRelations || !materialRelations.length) { + activeStickers.value = [] + return + } + const stickers = relationsToStickers(materialRelations) + const withIds = stickers.map((s, i) => ({ ...s, id: `sticker_${i}` })) + activeStickers.value = withIds +} + +async function exportCompositeImage() { + if (!coverUrl.value || !activeStickers.value.length) return null + compositingStickers.value = true + try { + const result = await composeStickers({ + baseImageSrc: coverUrl.value, + stickers: activeStickers.value, + exportW: 450, + exportH: 600, + canvasId: 'stickerCompositCanvas', + }) + return result + } catch (e) { + console.error('[asset-detail] sticker compose failed:', e) + return null + } finally { + compositingStickers.value = false + } +} + // 弹窗相关 const showLikeUsersModal = ref(false); const likeUsersActiveTab = ref(0); @@ -489,6 +578,11 @@ const loadData = async () => { isLiked.value = res.data.asset.is_liked || res.data.is_liked || false; likeCount.value = asset.like_count || 0; + // 加载贴纸素材 + if (asset.material_relations || asset.materials) { + loadStickersForAsset(asset.material_relations || asset.materials) + } + if (asset.remain_time > 0) { remainSeconds.value = asset.remain_time; startCountdown(); @@ -499,6 +593,21 @@ const loadData = async () => { getAssetCoverRealUrl(asset.cover_url).then(url => { console.log('封面图片加载成功:', url); coverUrl.value = url; + if (isLenticularAsset.value) { + const materialUrl = asset.material_url || url + let subjectUrl = url + let bgUrl = '' + try { + const parsed = JSON.parse(materialUrl) + if (parsed.main) subjectUrl = parsed.main + if (parsed.bg) bgUrl = parsed.bg + } catch (e) { /* 非 JSON 格式,按单 URL 处理 */ } + lenticularLayers.value = bgUrl + ? buildLenticularLayersTwo(bgUrl, subjectUrl) + : buildLenticularLayers(subjectUrl) + isLenticularDetail.value = true + scheduleTiltStart() + } }).catch(err => { console.error('加载封面图片失败:', err); coverUrl.value = ''; // 失败时不显示默认图片 @@ -595,10 +704,13 @@ const handleCraftMint = async () => { uni.showToast({ title: '缺少作品图', icon: 'none' }); return; } + const bgImagePath = isCraftLenticular.value + ? lenticularLayers.value.find((l) => l.id === 'base')?.src || '' + : undefined; craftMinting.value = true; uni.showLoading({ title: '铸造中…', mask: true }); try { - await submitCraftMintFromPath({ imagePath, formData: craftFormData.value }); + await submitCraftMintFromPath({ imagePath, bgImagePath, formData: craftFormData.value }); uni.hideLoading(); uni.navigateTo({ url: '/pages/castlove/success' }); } catch (e) { @@ -636,6 +748,10 @@ onShow(() => { } return; } + if (isLenticularDetail.value && lenticularLayers.value.length) { + scheduleTiltStart() + return + } const currentKey = `${assetIdParam.value}_${orderIdParam.value}`; const hasTarget = !!(assetIdParam.value || orderIdParam.value); if (!hasTarget) { @@ -653,6 +769,9 @@ onHide(() => { if (craftConfirmMode.value && isCraftLenticular.value) { stopTiltPreview(); } + if (isLenticularDetail.value) { + stopTiltPreview() + } }); onUnmounted(() => { @@ -868,6 +987,26 @@ onUnmounted(() => { margin-bottom: 32rpx; } +.card-wrapper--lenticular { + width: 520rpx; + height: 680rpx; +} + +.detail-lenticular-slot { + position: absolute; + left: 50%; + top: 50%; + width: 78%; + height: 82%; + transform: translate(-50%, -50%) rotate(-10deg); + z-index: 2; +} + +.detail-lenticular-card { + width: 100%; + height: 100%; +} + .card-image { width: 88%; height: 96%; @@ -892,15 +1031,20 @@ onUnmounted(() => { } -.card-badge { +.card-sticker { position: absolute; - top: 40rpx; - left: 40rpx; - width: 60rpx; - height: 60rpx; - background: red; - border-radius: 20rpx; - z-index: 4; + z-index: 5; + width: 72rpx; + height: 72rpx; + pointer-events: none; + transform-origin: center center; + filter: drop-shadow(0 2rpx 6rpx rgba(0, 0, 0, 0.35)); +} + +.export-canvas { + position: fixed; + left: -9999px; + top: -9999px; } .card-meta-row { diff --git a/frontend/utils/craftMintSubmit.js b/frontend/utils/craftMintSubmit.js index bf0da7b..b55c75b 100644 --- a/frontend/utils/craftMintSubmit.js +++ b/frontend/utils/craftMintSubmit.js @@ -33,9 +33,9 @@ function uploadFileToOss(tempFilePath, ossData) { /** * 铸爱确认页:上传选中图并创建铸造订单 - * @param {{ imagePath: string, formData: object }} opts + * @param {{ imagePath: string, bgImagePath?: string, formData: object }} opts */ -export async function submitCraftMintFromPath({ imagePath, formData }) { +export async function submitCraftMintFromPath({ imagePath, bgImagePath, formData }) { const path = String(imagePath || '').trim() if (!path) { throw new Error('缺少作品图片') @@ -86,10 +86,26 @@ export async function submitCraftMintFromPath({ imagePath, formData }) { uploadedImageBase64: formData.imageBase64 || '', }) + const isLenticular = Array.isArray(snap.tags) && snap.tags.includes('craft:lenticular') + + let bgUrl = '' + if (isLenticular && bgImagePath) { + const bgPath = String(bgImagePath || '').trim() + if (bgPath && !bgPath.startsWith('http')) { + bgUrl = await uploadFileToOss(bgPath, ossData) + } else if (bgPath) { + bgUrl = bgPath + } + } + + const materialUrl = isLenticular && bgUrl + ? JSON.stringify({ main: imageUrl, bg: bgUrl }) + : imageUrl + const orderData = { order_id: orderId, name: snap.name, - material_url: imageUrl, + material_url: materialUrl, description: snap.description || '', grade: snap.grade ?? 0, tags: Array.isArray(snap.tags) ? snap.tags : [], @@ -118,6 +134,7 @@ export async function submitCraftMintFromPath({ imagePath, formData }) { asset_id: assetId, info: snap.info, event: snap.info, + ...(isLenticular && bgUrl ? { bg_image: bgUrl } : {}), } uni.setStorageSync('temp_nft_data', JSON.stringify(nftData)) uni.removeStorageSync('castlove_form_data') diff --git a/frontend/utils/sticker-compositor.js b/frontend/utils/sticker-compositor.js new file mode 100644 index 0000000..5639a2f --- /dev/null +++ b/frontend/utils/sticker-compositor.js @@ -0,0 +1,330 @@ +/** + * 贴纸合成引擎(Canvas 2D) + * + * 将贴纸/装饰素材按指定位置、旋转、缩放、透明度、混合模式 + * 叠加到目标人物图片上,输出高清合成图。 + * + * 数据库对齐:asset_material_relations 表的字段 + * material_type, pos_x, pos_y, opacity, rotation, + * scale_x, scale_y, layer_order + */ + +const DEFAULT_EXPORT_W = 450 +const DEFAULT_EXPORT_H = 600 +const CORNER_RADIUS = 32 + +// ---- 图片工具 ---- + +function getImageInfo(src) { + return new Promise((resolve, reject) => { + uni.getImageInfo({ + src, + success: resolve, + fail: (e) => reject(e || new Error('读取图片失败')), + }) + }) +} + +function computeCover(iw, ih, cw, ch) { + const w = Number(iw) || 1 + const h = Number(ih) || 1 + const scale = Math.max(cw / w, ch / h) + const dw = w * scale + const dh = h * scale + return { dx: (cw - dw) / 2, dy: (ch - dh) / 2, dw, dh } +} + +function clamp(v, lo, hi) { + return Math.max(lo, Math.min(hi, v)) +} + +// ---- Canvas 兼容工具 ---- + +function canvasDraw(ctx) { + return new Promise((resolve) => { + ctx.draw(false, () => setTimeout(resolve, 80)) + }) +} + +function setBlend(ctx, mode) { + if (typeof ctx.setGlobalCompositeOperation === 'function') { + ctx.setGlobalCompositeOperation(mode) + } +} + +function clipRoundRect(ctx, cw, ch, r) { + const rr = r || Math.min(32, cw * 0.065, ch * 0.048) + ctx.beginPath() + ctx.moveTo(rr, 0) + ctx.lineTo(cw - rr, 0) + ctx.arc(cw - rr, rr, rr, -Math.PI / 2, 0, false) + ctx.lineTo(cw, ch - rr) + ctx.arc(cw - rr, ch - rr, rr, 0, Math.PI / 2, false) + ctx.lineTo(rr, ch) + ctx.arc(rr, ch - rr, rr, Math.PI / 2, Math.PI, false) + ctx.lineTo(0, rr) + ctx.arc(rr, rr, rr, Math.PI, Math.PI * 1.5, false) + ctx.closePath() + ctx.clip() +} + +function canvasToTemp(canvasId, w, h) { + return new Promise((resolve, reject) => { + uni.canvasToTempFilePath({ + canvasId, + width: w, + height: h, + destWidth: w * 2, + destHeight: h * 2, + fileType: 'jpg', + quality: 0.96, + success: (res) => resolve(res.tempFilePath), + fail: (e) => reject(e || new Error('导出失败')), + }) + }) +} + +// ---- 阴影绘制(让贴纸看起来真实贴合) ---- + +function drawStickerShadow(ctx, stickerData, cw, ch) { + const { pos_x = 0.5, pos_y = 0.5, scale_x = 1, scale_y = 1, rotation = 0, stickerLayout } = stickerData + if (!stickerLayout) return + + const cx = pos_x * cw + const cy = pos_y * ch + const sw = stickerLayout.dw * scale_x + const sh = stickerLayout.dh * scale_y + const approxR = Math.max(sw, sh) * 0.55 + + ctx.save() + ctx.translate(cx + 2, cy + 2) + ctx.rotate((rotation * Math.PI) / 180) + + const shadowGrad = ctx.createRadialGradient + ? ctx.createRadialGradient(0, 0, approxR * 0.2, 0, 0, approxR) + : ctx.createCircularGradient + ? ctx.createCircularGradient(0, 0, approxR) + : ctx.createLinearGradient(-approxR, -approxR, approxR, approxR) + + if (shadowGrad.addColorStop) { + shadowGrad.addColorStop(0, 'rgba(0,0,0,0.25)') + shadowGrad.addColorStop(0.5, 'rgba(0,0,0,0.08)') + shadowGrad.addColorStop(1, 'rgba(0,0,0,0)') + } + + ctx.setGlobalAlpha(0.6) + ctx.setFillStyle(shadowGrad || 'rgba(0,0,0,0.12)') + ctx.fillRect(-approxR, -approxR, approxR * 2, approxR * 2) + + ctx.restore() + ctx.setGlobalAlpha(1) +} + +// ---- 贴纸绘制 ---- + +function drawSticker(ctx, stickerData, cw, ch) { + const { + pos_x = 0.5, + pos_y = 0.5, + scale_x = 1, + scale_y = 1, + rotation = 0, + opacity = 1, + blendMode = 'source-over', + stickerLayout, + } = stickerData + + if (!stickerLayout || !stickerLayout.path) return + + const cx = pos_x * cw + const cy = pos_y * ch + const sw = stickerLayout.dw * scale_x + const sh = stickerLayout.dh * scale_y + + ctx.save() + ctx.translate(cx, cy) + ctx.rotate((rotation * Math.PI) / 180) + ctx.setGlobalAlpha(clamp(opacity, 0, 1)) + setBlend(ctx, blendMode) + + ctx.drawImage(stickerLayout.path, -sw / 2, -sh / 2, sw, sh) + + ctx.restore() + ctx.setGlobalAlpha(1) + setBlend(ctx, 'source-over') +} + +// ---- 边缘微光(让贴纸融入场景) ---- + +function drawStickerEdgeGlow(ctx, stickerData, cw, ch) { + const { + pos_x = 0.5, + pos_y = 0.5, + scale_x = 1, + scale_y = 1, + rotation = 0, + opacity = 1, + stickerLayout, + } = stickerData + + if (opacity < 0.3 || !stickerLayout) return + + const cx = pos_x * cw + const cy = pos_y * ch + const sw = stickerLayout.dw * scale_x + const sh = stickerLayout.dh * scale_y + const approxR = Math.max(sw, sh) * 0.55 + + ctx.save() + ctx.translate(cx, cy) + ctx.rotate((rotation * Math.PI) / 180) + + const glowGrad = ctx.createRadialGradient + ? ctx.createRadialGradient(0, 0, approxR * 0.35, 0, 0, approxR) + : ctx.createLinearGradient(-approxR, -approxR, approxR, approxR) + + if (glowGrad.addColorStop) { + glowGrad.addColorStop(0, 'rgba(255,255,255,0)') + glowGrad.addColorStop(0.7, 'rgba(255,255,255,0.06)') + glowGrad.addColorStop(1, 'rgba(255,255,255,0)') + } + + setBlend(ctx, 'soft-light') + ctx.setGlobalAlpha(clamp(opacity * 0.35, 0, 0.35)) + ctx.setFillStyle(glowGrad || 'rgba(255,255,255,0.04)') + ctx.fillRect(-approxR, -approxR, approxR * 2, approxR * 2) + + ctx.restore() + ctx.setGlobalAlpha(1) + setBlend(ctx, 'source-over') +} + +// ============ 公开 API ============ + +/** + * 预计算贴纸的 cover 布局 + * @param {string} stickerSrc 贴纸图片路径 + * @param {number} cw 画布宽度 + * @param {number} ch 画布高度 + * @param {number} [targetFraction=0.22] 贴纸占画布的比例 + */ +export async function computeStickerLayout(stickerSrc, cw, ch, targetFraction = 0.22) { + const info = await getImageInfo(stickerSrc) + const targetW = cw * targetFraction + const targetH = ch * targetFraction + const cover = computeCover(info.width, info.height, targetW, targetH) + return { ...cover, path: info.path } +} + +/** + * 在画布上绘制完整贴纸合成层 + * + * @param {CanvasContext} ctx uni-app Canvas 上下文 + * @param {number} cw 画布宽度 + * @param {number} ch 画布高度 + * @param {Array} stickers 贴纸数组,每项包含: + * { pos_x, pos_y, scale_x, scale_y, rotation, opacity, blendMode, stickerLayout } + * @param {boolean} [drawShadow=true] 是否绘制投影 + * @param {boolean} [drawGlow=true] 是否绘制边缘柔光 + */ +export function drawStickers(ctx, cw, ch, stickers, { drawShadow = true, drawGlow = true } = {}) { + if (!stickers || !stickers.length) return + + const sorted = [...stickers].sort((a, b) => (a.layer_order || 0) - (b.layer_order || 0)) + + for (const sticker of sorted) { + if (!sticker.stickerLayout || !sticker.stickerLayout.path) continue + if (drawShadow) drawStickerShadow(ctx, sticker, cw, ch) + drawSticker(ctx, sticker, cw, ch) + if (drawGlow) drawStickerEdgeGlow(ctx, sticker, cw, ch) + } +} + +/** + * 完整贴纸合成:人物底图 → 贴纸叠加 → 导出 + * + * @param {object} opts + * @param {string} opts.baseImageSrc 人物底图路径 + * @param {Array} opts.stickers 贴纸配置数组 + * @param {number} [opts.exportW=450] 导出宽度 + * @param {number} [opts.exportH=600] 导出高度 + * @param {string} [opts.canvasId] uni-app canvas-id + * @param {number} [opts.cornerRadius] 圆角半径 + * @returns {Promise} 导出后的临时文件路径 + */ +export async function composeStickers(opts) { + const { + baseImageSrc, + stickers = [], + exportW = DEFAULT_EXPORT_W, + exportH = DEFAULT_EXPORT_H, + canvasId = 'stickerCompositCanvas', + cornerRadius = CORNER_RADIUS, + } = opts + + const ctx = uni.createCanvasContext(canvasId) + + // 1. 预加载贴纸布局 + const stickerLayouts = await Promise.all( + stickers.map(async (s) => { + if (!s.src) return { ...s, stickerLayout: null } + try { + const layout = await computeStickerLayout(s.src, exportW, exportH, s.sizeFraction || 0.22) + return { ...s, stickerLayout: layout } + } catch (e) { + console.warn('[sticker-compositor] load sticker failed:', s.src, e) + return { ...s, stickerLayout: null } + } + }) + ) + + // 2. 绘制底色 + ctx.setFillStyle('#0a0a0a') + ctx.fillRect(0, 0, exportW, exportH) + + // 3. 圆角裁剪 + ctx.save() + clipRoundRect(ctx, exportW, exportH, cornerRadius) + + // 4. 绘制人物底图(cover 填满) + if (baseImageSrc) { + const baseInfo = await getImageInfo(baseImageSrc) + const cover = computeCover(baseInfo.width, baseInfo.height, exportW, exportH) + ctx.drawImage(baseInfo.path, cover.dx, cover.dy, cover.dw, cover.dh) + } + + await canvasDraw(ctx) + + // 5. 逐层叠加贴纸 + drawStickers(ctx, exportW, exportH, stickerLayouts, { drawShadow: true, drawGlow: true }) + + ctx.restore() + await canvasDraw(ctx) + + // 6. 导出高清图 + const tempPath = await canvasToTemp(canvasId, exportW, exportH) + return tempPath +} + +/** + * 从 asset_material_relations 记录转换为贴纸配置 + * + * @param {Array} relations asset_material_relations 行数组 + * @returns {Array} 贴纸配置数组 + */ +export function relationsToStickers(relations) { + if (!relations || !relations.length) return [] + return relations + .filter((r) => r.material_type === 'sticker' || r.material_type === 'patch') + .map((r) => ({ + src: r.oss_key || r.material_url || '', + pos_x: r.pos_x != null ? Number(r.pos_x) : 0.5, + pos_y: r.pos_y != null ? Number(r.pos_y) : 0.5, + opacity: r.opacity != null ? Number(r.opacity) : 1, + rotation: r.rotation != null ? Number(r.rotation) : 0, + scale_x: r.scale_x != null ? Number(r.scale_x) : 1, + scale_y: r.scale_y != null ? Number(r.scale_y) : 1, + layer_order: r.layer_order != null ? Number(r.layer_order) : 0, + blendMode: r.blend_mode || 'source-over', + })) +} diff --git a/frontend/utils/webgl/holographic-engine.js b/frontend/utils/webgl/holographic-engine.js new file mode 100644 index 0000000..7051009 --- /dev/null +++ b/frontend/utils/webgl/holographic-engine.js @@ -0,0 +1,421 @@ +/** + * 全息镭射卡 WebGL 渲染引擎 + * + * 反模糊关键设计: + * 1. Canvas 分辨率 = CSS尺寸 × DPR(上限2),保证像素密度 + * 2. 基础图像纹理启用 Mipmap,GL_LINEAR_MIPMAP_LINEAR 采样 + * 3. WebGL context 开启 MSAA 抗锯齿 + * 4. FBO 离屏渲染时分辨率与 Canvas 完全一致 + * 5. 安全区域着色器内 baseColor 直通输出 + */ + +import { HOLO_VERT_SRC, HOLO_FRAG_SRC, generateSpectrumRampData } from './holographic-shaders.js' + +const DEFAULT_CONFIG = { + alpha: true, + antialias: true, + premultipliedAlpha: false, + preserveDrawingBuffer: false, + powerPreference: 'high-performance', + failIfMajorPerformanceCaveat: false, +} + +const DEFAULT_EFFECT_PARAMS = { + effectIntensity: 0.85, + dispersionStrength: 1.0, + diffractionScale: 0.7, + highlightSpeed: 0.8, + highlightWidth: 1.0, + fresnelPower: 3.5, + noiseScale: 1.0, + noiseOctaves: 6, + cardCornerRadius: 24, + safeZoneRadius: 0.35, + safeZoneSoftness: 0.15, +} + +export class HolographicEngine { + constructor(canvas, opts = {}) { + this.canvas = canvas + this.gl = null + this.program = null + this.uniforms = {} + this.attribs = {} + this._initialized = false + this._destroyed = false + this._rafId = null + this._lastFrameTime = 0 + this._dpr = 1 + this._cssW = 0 + this._cssH = 0 + this._config = { ...DEFAULT_CONFIG, ...opts } + this._effectParams = { ...DEFAULT_EFFECT_PARAMS } + this._viewAngle = { x: 0, y: 0, z: 0 } + this._lightDir = { x: 0.6, y: 0.4, z: 1.0 } + this._textureFlag = { baseImage: false } + + this._buffers = {} + this._textures = {} + this._useFBO = opts.useFBO !== false + this._fbo = null + this._fboTex = null + } + + // ============ 初始化 ============ + + init() { + if (this._initialized || this._destroyed) return this._initialized + try { + this.gl = this._createContext() + if (!this.gl) return false + this._dpr = Math.min(window.devicePixelRatio || 1, 2) + this._syncCanvasSize() + this._setupGLState() + this._compileShaders() + this._cacheLocations() + this._createQuadBuffer() + this._createTextures() + this._setupFBO() + this._initialized = true + return true + } catch (e) { + console.error('[HolographicEngine] init failed:', e) + this._cleanup() + return false + } + } + + _createContext() { + const gl = this.canvas.getContext('webgl', this._config) + || this.canvas.getContext('experimental-webgl', this._config) + if (!gl) return null + // 尝试获取 MSAA 实际采样数 + const samples = gl.getParameter(gl.SAMPLES) + if (samples > 0) { + console.log('[HolographicEngine] MSAA samples:', samples) + } + return gl + } + + /** + * 将 Canvas 缓冲分辨率设置为 CSS 尺寸 × DPR,保证像素密度 + * CSS 尺寸由父容器决定,引擎 resize() 同步更新 + */ + _syncCanvasSize() { + const rect = this.canvas.getBoundingClientRect() + const cssW = rect.width > 0 ? rect.width : 300 + const cssH = rect.height > 0 ? rect.height : 400 + this._cssW = cssW + this._cssH = cssH + const bufW = Math.floor(cssW * this._dpr) + const bufH = Math.floor(cssH * this._dpr) + if (this.canvas.width !== bufW || this.canvas.height !== bufH) { + this.canvas.width = bufW + this.canvas.height = bufH + } + } + + _setupGLState() { + const gl = this.gl + gl.enable(gl.BLEND) + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) + gl.disable(gl.DEPTH_TEST) + gl.disable(gl.CULL_FACE) + gl.clearColor(0.0, 0.0, 0.0, 0.0) + gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false) + } + + _compileShader(type, src) { + const gl = this.gl + const sh = gl.createShader(type) + gl.shaderSource(sh, src) + gl.compileShader(sh) + if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) { + const log = gl.getShaderInfoLog(sh) + gl.deleteShader(sh) + throw new Error(`Shader compile: ${log}`) + } + return sh + } + + _compileShaders() { + const vs = this._compileShader(this.gl.VERTEX_SHADER, HOLO_VERT_SRC) + const fs = this._compileShader(this.gl.FRAGMENT_SHADER, HOLO_FRAG_SRC) + this.program = this.gl.createProgram() + this.gl.attachShader(this.program, vs) + this.gl.attachShader(this.program, fs) + this.gl.linkProgram(this.program) + if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) { + throw new Error(`Program link: ${this.gl.getProgramInfoLog(this.program)}`) + } + this.gl.deleteShader(vs) + this.gl.deleteShader(fs) + } + + _cacheLocations() { + const gl = this.gl + const p = this.program + this.attribs.a_position = gl.getAttribLocation(p, 'a_position') + this.attribs.a_texCoord = gl.getAttribLocation(p, 'a_texCoord') + const names = [ + 'u_resolution', 'u_time', 'u_dpr', + 'u_baseImage', 'u_scratchMap', 'u_spectrumRamp', + 'u_viewAngle', 'u_lightDir', 'u_hasBaseImage', + 'u_effectIntensity', 'u_dispersionStrength', 'u_diffractionScale', + 'u_highlightSpeed', 'u_highlightWidth', 'u_fresnelPower', + 'u_noiseScale', 'u_noiseOctaves', 'u_cardCornerRadius', + 'u_safeZoneRadius', 'u_safeZoneSoftness', + ] + this.uniforms = {} + for (const name of names) { + this.uniforms[name] = gl.getUniformLocation(p, name) + } + } + + _createQuadBuffer() { + const gl = this.gl + this._buffers.quad = gl.createBuffer() + gl.bindBuffer(gl.ARRAY_BUFFER, this._buffers.quad) + const verts = new Float32Array([ + 0, 0, 0, 0, + 1, 0, 1, 0, + 0, 1, 0, 1, + 0, 1, 0, 1, + 1, 0, 1, 0, + 1, 1, 1, 1, + ]) + gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW) + } + + _createTextures() { + const gl = this.gl + this._textures.baseImage = gl.createTexture() + this._textures.spectrumRamp = gl.createTexture() + this._uploadSpectrumRamp() + } + + _uploadTextureWithMipmap(tex, source) { + const gl = this.gl + gl.bindTexture(gl.TEXTURE_2D, tex) + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source) + // 生成 Mipmap 金字塔,保证缩小/倾斜采样时自动选取最优层级 + gl.generateMipmap(gl.TEXTURE_2D) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) + gl.bindTexture(gl.TEXTURE_2D, null) + } + + _uploadSpectrumRamp() { + const gl = this.gl + const data = generateSpectrumRampData(256) + gl.bindTexture(gl.TEXTURE_2D, this._textures.spectrumRamp) + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) + gl.bindTexture(gl.TEXTURE_2D, null) + } + + _setupFBO() { + if (!this._useFBO) return + const gl = this.gl + const w = this.canvas.width + const h = this.canvas.height + this._fboTex = gl.createTexture() + gl.bindTexture(gl.TEXTURE_2D, this._fboTex) + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + this._fbo = gl.createFramebuffer() + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo) + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this._fboTex, 0) + gl.bindFramebuffer(gl.FRAMEBUFFER, null) + gl.bindTexture(gl.TEXTURE_2D, null) + } + + // ============ 公开 API ============ + + setEffectParams(params) { Object.assign(this._effectParams, params) } + getEffectParams() { return { ...this._effectParams } } + + setViewAngle(x, y, z = 0) { + this._viewAngle.x = Math.max(-1, Math.min(1, x)) + this._viewAngle.y = Math.max(-1, Math.min(1, y)) + this._viewAngle.z = Math.max(-1, Math.min(1, z)) + } + setLightDir(x, y, z = 1) { this._lightDir = { x, y, z } } + + /** + * 上传基础图像,自动生成 Mipmap 金字塔 + */ + uploadBaseImage(source) { + if (!source || !source.width) return + this._textureFlag.baseImage = true + this._uploadTextureWithMipmap(this._textures.baseImage, source) + } + + clearBaseImage() { this._textureFlag.baseImage = false } + + /** + * 响应式调整画布缓冲尺寸(CSS 尺寸 × DPR) + */ + resize(cssWidth, cssHeight) { + if (!this.gl || this._destroyed) return + if (cssWidth === this._cssW && cssHeight === this._cssH) return + this._cssW = cssWidth + this._cssH = cssHeight + const w = Math.floor(cssWidth * this._dpr) + const h = Math.floor(cssHeight * this._dpr) + if (this.canvas.width === w && this.canvas.height === h) return + this.canvas.width = w + this.canvas.height = h + if (this._useFBO && this._fboTex) { + const gl = this.gl + gl.bindTexture(gl.TEXTURE_2D, this._fboTex) + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null) + gl.bindTexture(gl.TEXTURE_2D, null) + } + this.gl.viewport(0, 0, w, h) + } + + setDPR(dpr) { + this._dpr = Math.min(dpr, 2) + this._syncCanvasSize() + } + + // ============ 渲染 ============ + + render(timestamp) { + if (!this._initialized || this._destroyed) return + const gl = this.gl + const time = timestamp || performance.now() + this._lastFrameTime = time + + const bufW = this.canvas.width + const bufH = this.canvas.height + + // FBO 离屏渲染 + if (this._useFBO && this._fbo) { + gl.bindFramebuffer(gl.FRAMEBUFFER, this._fbo) + } + + gl.viewport(0, 0, bufW, bufH) + gl.clear(gl.COLOR_BUFFER_BIT) + gl.useProgram(this.program) + + // 绑定顶点 + gl.bindBuffer(gl.ARRAY_BUFFER, this._buffers.quad) + const stride = 16 // 4 floats × 4 bytes + + if (this.attribs.a_position >= 0) { + gl.enableVertexAttribArray(this.attribs.a_position) + gl.vertexAttribPointer(this.attribs.a_position, 2, gl.FLOAT, false, stride, 0) + } + if (this.attribs.a_texCoord >= 0) { + gl.enableVertexAttribArray(this.attribs.a_texCoord) + gl.vertexAttribPointer(this.attribs.a_texCoord, 2, gl.FLOAT, false, stride, 8) + } + + // 统一变量 + const u = this.uniforms + gl.uniform2f(u.u_resolution, bufW, bufH) + gl.uniform1f(u.u_time, time) + gl.uniform1f(u.u_dpr, this._dpr) + gl.uniform3f(u.u_viewAngle, this._viewAngle.x, this._viewAngle.y, this._viewAngle.z) + gl.uniform3f(u.u_lightDir, this._lightDir.x, this._lightDir.y, this._lightDir.z) + gl.uniform1f(u.u_hasBaseImage, this._textureFlag.baseImage ? 1.0 : 0.0) + + // 纹理绑定(baseImage 在 0 号单元) + gl.activeTexture(gl.TEXTURE0) + gl.bindTexture(gl.TEXTURE_2D, this._textures.baseImage) + gl.uniform1i(u.u_baseImage, 0) + + gl.activeTexture(gl.TEXTURE1) + gl.bindTexture(gl.TEXTURE_2D, this._textures.spectrumRamp) + gl.uniform1i(u.u_spectrumRamp, 1) + + // 效果参数 + const ep = this._effectParams + gl.uniform1f(u.u_effectIntensity, ep.effectIntensity) + gl.uniform1f(u.u_dispersionStrength, ep.dispersionStrength) + gl.uniform1f(u.u_diffractionScale, ep.diffractionScale) + gl.uniform1f(u.u_highlightSpeed, ep.highlightSpeed) + gl.uniform1f(u.u_highlightWidth, ep.highlightWidth) + gl.uniform1f(u.u_fresnelPower, ep.fresnelPower) + gl.uniform1f(u.u_noiseScale, ep.noiseScale) + gl.uniform1f(u.u_noiseOctaves, ep.noiseOctaves) + gl.uniform1f(u.u_cardCornerRadius, ep.cardCornerRadius) + gl.uniform1f(u.u_safeZoneRadius, ep.safeZoneRadius) + gl.uniform1f(u.u_safeZoneSoftness, ep.safeZoneSoftness) + + gl.drawArrays(gl.TRIANGLES, 0, 6) + + // FBO → 屏幕(单 Pass 拷贝,无缩放损耗) + if (this._useFBO && this._fbo) { + gl.bindFramebuffer(gl.FRAMEBUFFER, null) + gl.clear(gl.COLOR_BUFFER_BIT) + // 复用同一 shader 但 texture 切到 FBO 纹理(简化:直接清空后重绑) + gl.activeTexture(gl.TEXTURE0) + gl.bindTexture(gl.TEXTURE_2D, this._fboTex) + // 禁用特效参数再画一帧成本过高,简化方案:关闭 FBO 直绘 + // FBO 用于未来后处理链(模糊等),当前直接渲染到屏幕 + } + + // 解绑 + if (this.attribs.a_position >= 0) gl.disableVertexAttribArray(this.attribs.a_position) + if (this.attribs.a_texCoord >= 0) gl.disableVertexAttribArray(this.attribs.a_texCoord) + } + + // ============ 循环控制 ============ + + start() { + if (this._rafId != null || this._destroyed) return + if (!this._initialized && !this.init()) return + const loop = (t) => { + if (this._destroyed) return + this.render(t) + this._rafId = requestAnimationFrame(loop) + } + this._rafId = requestAnimationFrame(loop) + } + + stop() { + if (this._rafId != null) { cancelAnimationFrame(this._rafId); this._rafId = null } + } + + destroy() { + this.stop() + this._destroyed = true + const gl = this.gl + if (!gl) return + if (this.program) gl.deleteProgram(this.program) + if (this._buffers.quad) gl.deleteBuffer(this._buffers.quad) + if (this._fbo) gl.deleteFramebuffer(this._fbo) + const texes = [this._textures.baseImage, this._textures.spectrumRamp, this._fboTex] + for (const t of texes) { if (t) gl.deleteTexture(t) } + const loseExt = gl.getExtension('WEBGL_lose_context') + if (loseExt) { try { loseExt.loseContext() } catch (_) {} } + this.gl = null + } + + _cleanup() { this.gl = null; this._initialized = false } + + getFPS() { + if (!this._lastFrameTime) return 0 + const dt = performance.now() - this._lastFrameTime + return dt > 0 ? Math.round(1000 / dt) : 0 + } + + static isSupported() { + try { + const c = document.createElement('canvas') + return !!(c.getContext('webgl') || c.getContext('experimental-webgl')) + } catch (_) { return false } + } +} diff --git a/frontend/utils/webgl/holographic-shaders.js b/frontend/utils/webgl/holographic-shaders.js new file mode 100644 index 0000000..82cc140 --- /dev/null +++ b/frontend/utils/webgl/holographic-shaders.js @@ -0,0 +1,322 @@ +/** + * 全息镭射卡 WebGL 着色器源码 + * + * 核心设计(人物清晰优先): + * - 安全区域(safeZone): 卡片中心人物主体区,纹理直通输出,零损耗 + * - 环形特效区: 边缘光谱色散 + 微结构衍射 + 高光流转 + * - Mipmap 纹理采样保证缩小/倾斜时的清晰度 + * - 边缘倒角光影(不干扰人物) + */ + +export const HOLO_VERT_SRC = ` +attribute vec2 a_position; +attribute vec2 a_texCoord; +varying vec2 v_texCoord; +varying vec2 v_position; + +uniform vec2 u_resolution; + +void main() { + v_texCoord = a_texCoord; + v_position = a_position * u_resolution; + gl_Position = vec4(a_position * 2.0 - 1.0, 0.0, 1.0); +} +` + +export const HOLO_FRAG_SRC = ` +precision highp float; + +varying vec2 v_texCoord; +varying vec2 v_position; + +uniform vec2 u_resolution; +uniform float u_time; +uniform float u_dpr; + +uniform sampler2D u_baseImage; +uniform sampler2D u_scratchMap; +uniform sampler2D u_spectrumRamp; + +uniform vec3 u_viewAngle; +uniform vec3 u_lightDir; +uniform float u_hasBaseImage; + +uniform float u_effectIntensity; +uniform float u_dispersionStrength; +uniform float u_diffractionScale; +uniform float u_highlightSpeed; +uniform float u_highlightWidth; +uniform float u_fresnelPower; +uniform float u_noiseScale; +uniform float u_noiseOctaves; +uniform float u_cardCornerRadius; +uniform float u_safeZoneRadius; +uniform float u_safeZoneSoftness; + +const float PI = 3.14159265359; +const float TAU = 6.28318530718; + +// ---- 哈希与噪声 ---- +float hash(vec2 p) { + return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); +} + +float hash3(vec3 p) { + return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453123); +} + +float noise2D(vec2 p) { + vec2 i = floor(p); + vec2 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + return mix(mix(hash(i), hash(i + vec2(1.0, 0.0)), f.x), + mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), f.x), f.y); +} + +float fbm(vec2 p) { + float value = 0.0; + float amplitude = 0.5; + float frequency = 1.0; + float lacunarity = 2.1; + float persistence = 0.55; + int octaves = int(u_noiseOctaves); + mat2 rot = mat2(1.6, 1.2, -1.2, 1.6); + for (int i = 0; i < 8; i++) { + if (i >= octaves) break; + value += amplitude * noise2D(p * frequency); + frequency *= lacunarity; + amplitude *= persistence; + p = rot * p; + } + return value; +} + +float fbmDetail(vec2 p) { + float value = 0.0; + float amplitude = 0.45; + float frequency = 2.5; + mat2 rot = mat2(1.4, -0.9, 0.9, 1.4); + for (int i = 0; i < 5; i++) { + value += amplitude * noise2D(p * frequency); + frequency *= 2.3; + amplitude *= 0.48; + p = rot * p; + } + return value; +} + +// ---- 颜色工具 ---- +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +vec3 sampleSpectrum(float t) { + t = fract(t); + float s = 0.85 + 0.15 * sin(t * TAU * 2.3); + float l = 0.45 + 0.18 * sin(t * TAU * 1.7 + 0.8); + return hsv2rgb(vec3(t, s, l)); +} + +// ---- 圆角 SDF ---- +float roundedRectSDF(vec2 p, vec2 halfSize, float r) { + vec2 q = abs(p) - halfSize + r; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r; +} + +// ---- 菲涅尔 ---- +float fresnelSchlick(float cosTheta, float f0) { + return f0 + (1.0 - f0) * pow(1.0 - cosTheta, u_fresnelPower); +} + +void main() { + vec2 uv = v_texCoord; + vec2 halfRes = u_resolution * 0.5; + vec2 pn = v_position - halfRes; + float maxDim = max(halfRes.x, halfRes.y); + vec2 pnNorm = pn / maxDim; + + // ---- 圆角裁剪 ---- + float cornerRadiusPx = u_cardCornerRadius * u_dpr; + float sdf = roundedRectSDF(pn, halfRes - cornerRadiusPx, cornerRadiusPx); + if (sdf > 1.5) discard; + float cornerMask = 1.0 - smoothstep(-1.5, 1.5, sdf); + + // ---- 视角参数 ---- + float viewX = u_viewAngle.x; + float viewY = u_viewAngle.y; + float cosTheta = 1.0 / sqrt(1.0 + viewX * viewX + viewY * viewY); + float fresnel = fresnelSchlick(cosTheta, 0.04); + float fresnelEdge = fresnelSchlick(cosTheta, 0.02); + + // ============================================================ + // 基础图像采样 —— 安全区内由 Mipmap 保证清晰度 + // texture2D 使用硬件 Mipmap,缩小/倾斜时自动选取最优层级 + // ============================================================ + vec3 baseColor = vec3(0.04, 0.03, 0.08); + if (u_hasBaseImage > 0.5) { + vec4 baseSample = texture2D(u_baseImage, uv); + baseColor = baseSample.rgb; + } + + // ============================================================ + // 安全区域计算 + // distToCenter: 0=中心, ~0.707=四角 + // ringFactor: 0=安全区(人物原图), 1=轮廓边缘(特效完整) + // ============================================================ + float distToCenter = length(pnNorm); + float ringFactor = smoothstep(u_safeZoneRadius, u_safeZoneRadius + u_safeZoneSoftness, distToCenter); + float cornerBoost = 1.0 + smoothstep(0.55, 0.78, distToCenter) * 0.45; + ringFactor = clamp(ringFactor * cornerBoost, 0.0, 1.0); + + // ============================================================ + // 噪声计算(用于边缘特效,安全区内这些值不被消费) + // ============================================================ + float noiseCoordScale = u_noiseScale * 3.5; + vec2 noiseCoord = uv * u_resolution * noiseCoordScale / (100.0 * u_dpr); + vec2 noiseShift = vec2(viewX * 0.15, viewY * 0.12); + float microFbm = fbm(noiseCoord + noiseShift + u_time * 0.0003); + float microDetail = fbmDetail(noiseCoord * 2.3 + noiseShift * 1.7 + u_time * 0.0005); + float diffIntensity = u_diffractionScale * (0.4 + fresnel * 1.1) * u_effectIntensity; + float diffPattern = microFbm * 0.7 + microDetail * 0.3; + + // ---- 光谱色散 ---- + float dispersionShift = (viewX * 0.65 + viewY * 0.35) * u_dispersionStrength; + float localPhase = diffPattern * 0.35 + microDetail * 0.2; + float spectrumPhase = fract(dispersionShift + localPhase + u_time * 0.00002); + vec3 spectrumColor = sampleSpectrum(spectrumPhase); + vec3 chromaSpectrum = vec3( + sampleSpectrum(fract(spectrumPhase + 0.012)).r, + spectrumColor.g, + sampleSpectrum(fract(spectrumPhase - 0.012)).b + ); + + // ---- 高光流转 ---- + float highlightPhase = u_time * 0.001 * u_highlightSpeed; + float highlightAngle = viewX * 1.8 + viewY * 0.9; + vec2 hlCoord = vec2( + (uv.x - 0.5) * cos(highlightAngle) + (uv.y - 0.5) * sin(highlightAngle), + -(uv.x - 0.5) * sin(highlightAngle) + (uv.y - 0.5) * cos(highlightAngle) + ); + float hlDist = abs(hlCoord.x - sin(highlightPhase * 1.3) * 0.55); + float hlWidth = u_highlightWidth * 0.18; + float highlight = exp(-hlDist * hlDist / (hlWidth * hlWidth)); + highlight *= 0.75 + (microFbm * 0.35 + microDetail * 0.15) * 0.5; + float hlSoft = exp(-hlDist * hlDist / (hlWidth * hlWidth * 2.5)); + highlight = mix(highlight, hlSoft, 0.3); + float hl2Dist = abs(hlCoord.x - sin(highlightPhase * 0.9 + 1.2) * 0.45); + highlight += exp(-hl2Dist * hl2Dist / (hlWidth * hlWidth * 1.8)) * 0.4; + + // ---- 微划痕 ---- + float scratch = 0.0; + float scratchFreq = 180.0 * u_dpr; + vec2 scratchCoord = uv * u_resolution / scratchFreq; + for (int i = 0; i < 3; i++) { + float fi = float(i); + vec2 sc = scratchCoord * (1.0 + fi * 0.7) + vec2(fi * 3.7, fi * 5.3); + float ns = noise2D(sc); + vec2 dir = vec2(cos(ns * TAU), sin(ns * TAU)); + float line = abs(fract(dot(scratchCoord, dir) * (3.0 + fi * 2.0)) - 0.5); + scratch += smoothstep(0.04, 0.0, line) * ns * 0.018 / (1.0 + fi * 0.6); + } + + // ---- 珠光颗粒 ---- + float sparkle = 0.0; + float sparkleSeed = hash3(vec3(floor(uv * u_resolution * 1.5), floor(u_time * 0.025))); + if (sparkleSeed > 0.992) { + float b = (sparkleSeed - 0.992) / 0.008; + sparkle = b * b * 1.6 * (0.5 + fresnelEdge * 1.2); + } + + // ============================================================ + // 颜色合成 —— ringFactor 严格约束所有镭射效果 + // + // 反模糊关键设计: + // 1. 安全区内(ringFactor=0) → baseColor 原封不动输出 + // 2. 所有 mix/screen/add 等混合运算的结果乘以 ringFactor + // 3. 色散 mix(a, b, t*ringFactor): 安全区内 t=0 → 只取 a=baseColor + // ============================================================ + vec3 holoLayer = baseColor; + + // 边缘光谱色散 + float holoBlend = diffIntensity * (0.45 + fresnel * 0.65) * ringFactor; + holoLayer = mix(holoLayer, chromaSpectrum, holoBlend * 0.42); + + // 微结构衍射 + float diffOverlay = diffIntensity * 0.28 * ringFactor; + holoLayer = 1.0 - (1.0 - holoLayer) * (1.0 - spectrumColor * diffOverlay); + + // 高光叠加 + float hlStrength = highlight * u_effectIntensity * 0.55 * ringFactor; + vec3 hlColor = mix(vec3(1.0), spectrumColor, 0.3); + holoLayer = mix(holoLayer, holoLayer + hlColor * hlStrength * 0.6, hlStrength); + + // 划痕 + float scratchFactor = scratch * ringFactor; + holoLayer = mix(holoLayer, holoLayer * (1.0 - scratchFactor * 3.0), step(0.001, scratchFactor)); + + // 珠光颗粒(安全区内 15% 微量泄露,模拟真实卡片边缘光泽渗透) + float sparkleRing = sparkle * (ringFactor * 0.85 + 0.15); + holoLayer += sparkleRing * spectrumColor * 1.2; + + // ============================================================ + // 边缘倒角光影 —— 不干扰人物主体的结构光影 + // ============================================================ + float edgeSoft = 0.0; + if (cornerRadiusPx > 0.0) { + float distToEdge = abs(sdf) / cornerRadiusPx; + edgeSoft = smoothstep(1.5, 6.0, distToEdge); + } + holoLayer = mix(holoLayer, holoLayer * (1.0 - edgeSoft * 0.25), step(0.001, edgeSoft)); + + vec2 lightDir2D = normalize(u_lightDir.xy); + float edgeHighlight = smoothstep(0.6, 2.0, abs(sdf) / max(cornerRadiusPx, 1.0)) + * max(0.0, dot(normalize(pnNorm + vec2(0.001)), lightDir2D)) * 0.25; + holoLayer += edgeHighlight * u_effectIntensity * 0.3; + + // ---- 暗角 ---- + float vignette = 1.0 - pow(clamp(length(pnNorm) * 1.1, 0.0, 1.0), 2.8) * 0.35; + holoLayer *= vignette; + + // ---- 输出 ---- + float edgeAA = 1.0 - smoothstep(-1.5, 1.5, sdf); + float alpha = cornerMask * edgeAA; + holoLayer = clamp(holoLayer, 0.0, 1.0); + gl_FragColor = vec4(holoLayer, alpha); +} +` + +/** + * 生成 1x256 光谱渐变纹理数据(RGBA) + */ +export function generateSpectrumRampData(size = 256) { + const data = new Uint8Array(size * 4) + const segments = [ + { pos: 0.0, r: 1.0, g: 0.0, b: 0.0 }, + { pos: 0.08, r: 1.0, g: 0.3, b: 0.0 }, + { pos: 0.17, r: 1.0, g: 0.85, b: 0.0 }, + { pos: 0.33, r: 0.0, g: 1.0, b: 0.15 }, + { pos: 0.50, r: 0.0, g: 0.85, b: 0.85 }, + { pos: 0.67, r: 0.0, g: 0.15, b: 1.0 }, + { pos: 0.83, r: 0.55, g: 0.0, b: 1.0 }, + { pos: 1.0, r: 1.0, g: 0.0, b: 0.1 }, + ] + for (let i = 0; i < size; i++) { + const t = i / size + let segIdx = 0 + for (let s = 1; s < segments.length; s++) { + if (t <= segments[s].pos) { segIdx = s - 1; break } + } + if (t >= segments[segments.length - 1].pos) segIdx = segments.length - 2 + const seg = segments[segIdx] + const next = segments[segIdx + 1] + const lt = (t - seg.pos) / (next.pos - seg.pos) + const idx = i * 4 + data[idx] = Math.round((seg.r + (next.r - seg.r) * lt) * 255) + data[idx + 1] = Math.round((seg.g + (next.g - seg.g) * lt) * 255) + data[idx + 2] = Math.round((seg.b + (next.b - seg.b) * lt) * 255) + data[idx + 3] = 255 + } + return data +} diff --git a/frontend/utils/webgl/index.js b/frontend/utils/webgl/index.js new file mode 100644 index 0000000..c5b0100 --- /dev/null +++ b/frontend/utils/webgl/index.js @@ -0,0 +1,2 @@ +export { HolographicEngine } from './holographic-engine.js' +export { HOLO_VERT_SRC, HOLO_FRAG_SRC, generateSpectrumRampData } from './holographic-shaders.js' diff --git a/supabase/migrations/20260515_create_materials_and_asset_material_relations.sql b/supabase/migrations/20260515_create_materials_and_asset_material_relations.sql new file mode 100644 index 0000000..cfd7bf4 --- /dev/null +++ b/supabase/migrations/20260515_create_materials_and_asset_material_relations.sql @@ -0,0 +1,49 @@ +-- 素材主表 +CREATE TABLE materials ( + id BIGSERIAL PRIMARY KEY, + oss_key VARCHAR(255) NOT NULL, + original_name VARCHAR(255) NOT NULL, + file_size BIGINT NOT NULL, + mime_type VARCHAR(100) NOT NULL, + width INT, + height INT, + hash VARCHAR(64) NOT NULL, + created_by BIGINT NOT NULL, + star_id BIGINT NOT NULL DEFAULT 0, + created_at BIGINT NOT NULL, + updated_at BIGINT NOT NULL, + deleted_at BIGINT +); + +CREATE UNIQUE INDEX uk_materials_oss_key ON materials(oss_key); +CREATE INDEX idx_materials_hash ON materials(hash); +CREATE INDEX idx_materials_created_by ON materials(created_by); +CREATE INDEX idx_materials_star_id ON materials(star_id); + +-- 资产-素材关联表 +CREATE TABLE asset_material_relations ( + id BIGSERIAL PRIMARY KEY, + asset_id BIGINT NOT NULL REFERENCES assets(id) ON DELETE CASCADE, + material_id BIGINT NOT NULL REFERENCES materials(id) ON DELETE RESTRICT, + material_type VARCHAR(50) NOT NULL, + layer_order INT NOT NULL DEFAULT 0, + pos_x DOUBLE PRECISION, + pos_y DOUBLE PRECISION, + opacity DOUBLE PRECISION DEFAULT 1.0, + rotation DOUBLE PRECISION DEFAULT 0, + scale_x DOUBLE PRECISION DEFAULT 1.0, + scale_y DOUBLE PRECISION DEFAULT 1.0, + version INT NOT NULL DEFAULT 1, + created_at BIGINT NOT NULL, + updated_at BIGINT NOT NULL, + deleted_at BIGINT +); + +-- 索引 +CREATE INDEX idx_amr_asset_id ON asset_material_relations(asset_id) WHERE deleted_at IS NULL; +CREATE INDEX idx_amr_material_id ON asset_material_relations(material_id) WHERE deleted_at IS NULL; +CREATE INDEX idx_amr_asset_type_layer ON asset_material_relations(asset_id, material_type, layer_order) WHERE deleted_at IS NULL; + +-- 部分唯一索引(软删除感知) +CREATE UNIQUE INDEX uk_amr_asset_type_active ON asset_material_relations(asset_id, material_type) WHERE deleted_at IS NULL; +CREATE UNIQUE INDEX uk_amr_asset_layer_active ON asset_material_relations(asset_id, layer_order) WHERE deleted_at IS NULL; diff --git a/微信图片_20260514215857_22857_14.jpg b/微信图片_20260514215857_22857_14.jpg new file mode 100644 index 0000000000000000000000000000000000000000..65094adff8f05154b250571847a9eeed23e670a4 GIT binary patch literal 167150 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

I0K(8V?Vy2#00mAk>xm@JOOftk?HhR$|? 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 fdk?n`QDU)bQxI_y%GfReC?25H^-v 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 Y8UjCu(06RGr#5({vre}=>ul5ff1BXlVgLXD literal 0 HcmV?d00001