592 lines
17 KiB
Go
592 lines
17 KiB
Go
|
|
package skin_renderer
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"bytes"
|
|||
|
|
"image"
|
|||
|
|
"image/color"
|
|||
|
|
"image/png"
|
|||
|
|
"math"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// SkinRenderer 皮肤渲染器
|
|||
|
|
type SkinRenderer struct {
|
|||
|
|
playerSkin image.Image
|
|||
|
|
isNewSkinType bool
|
|||
|
|
isAlex bool
|
|||
|
|
hdRatio int
|
|||
|
|
|
|||
|
|
// 旋转参数
|
|||
|
|
ratio float64
|
|||
|
|
headOnly bool
|
|||
|
|
hR float64 // 水平旋转角度
|
|||
|
|
vR float64 // 垂直旋转角度
|
|||
|
|
hrh float64 // 头部水平旋转
|
|||
|
|
vrll float64 // 左腿垂直旋转
|
|||
|
|
vrrl float64 // 右腿垂直旋转
|
|||
|
|
vrla float64 // 左臂垂直旋转
|
|||
|
|
vrra float64 // 右臂垂直旋转
|
|||
|
|
layers bool // 是否渲染第二层
|
|||
|
|
|
|||
|
|
// 计算后的三角函数值
|
|||
|
|
cosAlpha, sinAlpha float64
|
|||
|
|
cosOmega, sinOmega float64
|
|||
|
|
|
|||
|
|
// 边界
|
|||
|
|
minX, maxX, minY, maxY float64
|
|||
|
|
|
|||
|
|
// 各部件的旋转角度
|
|||
|
|
membersAngles map[string]angleSet
|
|||
|
|
|
|||
|
|
// 可见面
|
|||
|
|
visibleFaces map[string]faceVisibility
|
|||
|
|
frontFaces []string
|
|||
|
|
backFaces []string
|
|||
|
|
|
|||
|
|
// 多边形
|
|||
|
|
polygons map[string]map[string][]*Polygon
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type angleSet struct {
|
|||
|
|
cosAlpha, sinAlpha float64
|
|||
|
|
cosOmega, sinOmega float64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type faceVisibility struct {
|
|||
|
|
front []string
|
|||
|
|
back []string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var allFaces = []string{"back", "right", "top", "front", "left", "bottom"}
|
|||
|
|
|
|||
|
|
// NewSkinRenderer 创建皮肤渲染器
|
|||
|
|
func NewSkinRenderer(ratio float64, headOnly bool, horizontalRotation, verticalRotation float64) *SkinRenderer {
|
|||
|
|
return &SkinRenderer{
|
|||
|
|
ratio: ratio,
|
|||
|
|
headOnly: headOnly,
|
|||
|
|
hR: horizontalRotation,
|
|||
|
|
vR: verticalRotation,
|
|||
|
|
hrh: 0,
|
|||
|
|
vrll: 0,
|
|||
|
|
vrrl: 0,
|
|||
|
|
vrla: 0,
|
|||
|
|
vrra: 0,
|
|||
|
|
layers: true,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// NewSkinRendererFull 创建带完整参数的皮肤渲染器
|
|||
|
|
func NewSkinRendererFull(ratio float64, headOnly bool, hR, vR, hrh, vrll, vrrl, vrla, vrra float64, layers bool) *SkinRenderer {
|
|||
|
|
return &SkinRenderer{
|
|||
|
|
ratio: ratio,
|
|||
|
|
headOnly: headOnly,
|
|||
|
|
hR: hR,
|
|||
|
|
vR: vR,
|
|||
|
|
hrh: hrh,
|
|||
|
|
vrll: vrll,
|
|||
|
|
vrrl: vrrl,
|
|||
|
|
vrla: vrla,
|
|||
|
|
vrra: vrra,
|
|||
|
|
layers: layers,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Render 渲染皮肤
|
|||
|
|
func (r *SkinRenderer) Render(skinData []byte, isAlex bool) (image.Image, error) {
|
|||
|
|
// 解码皮肤图像
|
|||
|
|
img, err := png.Decode(bytes.NewReader(skinData))
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
r.playerSkin = img
|
|||
|
|
r.isAlex = isAlex
|
|||
|
|
|
|||
|
|
// 计算 HD 比例
|
|||
|
|
sourceWidth := img.Bounds().Dx()
|
|||
|
|
sourceHeight := img.Bounds().Dy()
|
|||
|
|
|
|||
|
|
// 防止内存溢出,限制最大尺寸
|
|||
|
|
if sourceWidth > 256 {
|
|||
|
|
r.playerSkin = resizeImage(img, 256, sourceHeight*256/sourceWidth)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
r.hdRatio = r.playerSkin.Bounds().Dx() / 64
|
|||
|
|
|
|||
|
|
// 检查是否为新版皮肤格式(64x64)
|
|||
|
|
if r.playerSkin.Bounds().Dx() == r.playerSkin.Bounds().Dy() {
|
|||
|
|
r.isNewSkinType = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 转换为 RGBA
|
|||
|
|
r.playerSkin = convertToRGBA(r.playerSkin)
|
|||
|
|
|
|||
|
|
// 处理背景透明
|
|||
|
|
r.makeBackgroundTransparent()
|
|||
|
|
|
|||
|
|
// 计算角度
|
|||
|
|
r.calculateAngles()
|
|||
|
|
|
|||
|
|
// 确定可见面
|
|||
|
|
r.facesDetermination()
|
|||
|
|
|
|||
|
|
// 生成多边形
|
|||
|
|
r.generatePolygons()
|
|||
|
|
|
|||
|
|
// 部件旋转
|
|||
|
|
r.memberRotation()
|
|||
|
|
|
|||
|
|
// 创建投影
|
|||
|
|
r.createProjectionPlan()
|
|||
|
|
|
|||
|
|
// 渲染图像
|
|||
|
|
return r.displayImage(), nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// makeBackgroundTransparent 处理背景透明
|
|||
|
|
func (r *SkinRenderer) makeBackgroundTransparent() {
|
|||
|
|
rgba, ok := r.playerSkin.(*image.RGBA)
|
|||
|
|
if !ok {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查左上角 8x8 区域是否为纯色
|
|||
|
|
var tempColor color.RGBA
|
|||
|
|
needRemove := true
|
|||
|
|
first := true
|
|||
|
|
|
|||
|
|
for y := 0; y < 8; y++ {
|
|||
|
|
for x := 0; x < 8; x++ {
|
|||
|
|
c := rgba.RGBAAt(x, y)
|
|||
|
|
|
|||
|
|
// 如果已有透明度,不需要处理
|
|||
|
|
if c.A < 128 {
|
|||
|
|
needRemove = false
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if first {
|
|||
|
|
tempColor = c
|
|||
|
|
first = false
|
|||
|
|
} else if c != tempColor {
|
|||
|
|
needRemove = false
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if !needRemove {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if !needRemove {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 将该颜色设为透明
|
|||
|
|
bounds := rgba.Bounds()
|
|||
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|||
|
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|||
|
|
c := rgba.RGBAAt(x, y)
|
|||
|
|
if c.R == tempColor.R && c.G == tempColor.G && c.B == tempColor.B {
|
|||
|
|
rgba.SetRGBA(x, y, color.RGBA{0, 0, 0, 0})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// calculateAngles 计算旋转角度
|
|||
|
|
func (r *SkinRenderer) calculateAngles() {
|
|||
|
|
// 转换为弧度
|
|||
|
|
alpha := r.vR * math.Pi / 180
|
|||
|
|
omega := r.hR * math.Pi / 180
|
|||
|
|
|
|||
|
|
r.cosAlpha = math.Cos(alpha)
|
|||
|
|
r.sinAlpha = math.Sin(alpha)
|
|||
|
|
r.cosOmega = math.Cos(omega)
|
|||
|
|
r.sinOmega = math.Sin(omega)
|
|||
|
|
|
|||
|
|
r.membersAngles = make(map[string]angleSet)
|
|||
|
|
|
|||
|
|
// 躯干不旋转
|
|||
|
|
r.membersAngles["torso"] = angleSet{
|
|||
|
|
cosAlpha: 1, sinAlpha: 0,
|
|||
|
|
cosOmega: 1, sinOmega: 0,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 头部旋转
|
|||
|
|
omegaHead := r.hrh * math.Pi / 180
|
|||
|
|
r.membersAngles["head"] = angleSet{
|
|||
|
|
cosAlpha: 1, sinAlpha: 0,
|
|||
|
|
cosOmega: math.Cos(omegaHead), sinOmega: math.Sin(omegaHead),
|
|||
|
|
}
|
|||
|
|
r.membersAngles["helmet"] = r.membersAngles["head"]
|
|||
|
|
|
|||
|
|
// 右臂旋转
|
|||
|
|
alphaRightArm := r.vrra * math.Pi / 180
|
|||
|
|
r.membersAngles["rightArm"] = angleSet{
|
|||
|
|
cosAlpha: math.Cos(alphaRightArm), sinAlpha: math.Sin(alphaRightArm),
|
|||
|
|
cosOmega: 1, sinOmega: 0,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 左臂旋转
|
|||
|
|
alphaLeftArm := r.vrla * math.Pi / 180
|
|||
|
|
r.membersAngles["leftArm"] = angleSet{
|
|||
|
|
cosAlpha: math.Cos(alphaLeftArm), sinAlpha: math.Sin(alphaLeftArm),
|
|||
|
|
cosOmega: 1, sinOmega: 0,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 右腿旋转
|
|||
|
|
alphaRightLeg := r.vrrl * math.Pi / 180
|
|||
|
|
r.membersAngles["rightLeg"] = angleSet{
|
|||
|
|
cosAlpha: math.Cos(alphaRightLeg), sinAlpha: math.Sin(alphaRightLeg),
|
|||
|
|
cosOmega: 1, sinOmega: 0,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 左腿旋转
|
|||
|
|
alphaLeftLeg := r.vrll * math.Pi / 180
|
|||
|
|
r.membersAngles["leftLeg"] = angleSet{
|
|||
|
|
cosAlpha: math.Cos(alphaLeftLeg), sinAlpha: math.Sin(alphaLeftLeg),
|
|||
|
|
cosOmega: 1, sinOmega: 0,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
r.minX, r.maxX = 0, 0
|
|||
|
|
r.minY, r.maxY = 0, 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// facesDetermination 确定可见面
|
|||
|
|
func (r *SkinRenderer) facesDetermination() {
|
|||
|
|
r.visibleFaces = make(map[string]faceVisibility)
|
|||
|
|
|
|||
|
|
parts := []string{"head", "torso", "rightArm", "leftArm", "rightLeg", "leftLeg"}
|
|||
|
|
|
|||
|
|
for _, part := range parts {
|
|||
|
|
angles := r.membersAngles[part]
|
|||
|
|
|
|||
|
|
// 创建测试立方体点
|
|||
|
|
cubePoints := r.createCubePoints()
|
|||
|
|
|
|||
|
|
var maxDepthPoint *Point
|
|||
|
|
var maxDepthFaces []string
|
|||
|
|
|
|||
|
|
for _, cp := range cubePoints {
|
|||
|
|
point := cp.point
|
|||
|
|
point.PreProject(0, 0, 0, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
|
|||
|
|
point.Project(r.cosAlpha, r.sinAlpha, r.cosOmega, r.sinOmega, &r.minX, &r.maxX, &r.minY, &r.maxY)
|
|||
|
|
|
|||
|
|
if maxDepthPoint == nil {
|
|||
|
|
maxDepthPoint = point
|
|||
|
|
maxDepthFaces = cp.faces
|
|||
|
|
} else {
|
|||
|
|
_, _, z1 := maxDepthPoint.GetDestCoord()
|
|||
|
|
_, _, z2 := point.GetDestCoord()
|
|||
|
|
if z1 > z2 {
|
|||
|
|
maxDepthPoint = point
|
|||
|
|
maxDepthFaces = cp.faces
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
r.visibleFaces[part] = faceVisibility{
|
|||
|
|
back: maxDepthFaces,
|
|||
|
|
front: diffFaces(allFaces, maxDepthFaces),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 确定全局前后面
|
|||
|
|
cubePoints := r.createCubePoints()
|
|||
|
|
var maxDepthPoint *Point
|
|||
|
|
var maxDepthFaces []string
|
|||
|
|
|
|||
|
|
for _, cp := range cubePoints {
|
|||
|
|
point := cp.point
|
|||
|
|
point.Project(r.cosAlpha, r.sinAlpha, r.cosOmega, r.sinOmega, &r.minX, &r.maxX, &r.minY, &r.maxY)
|
|||
|
|
|
|||
|
|
if maxDepthPoint == nil {
|
|||
|
|
maxDepthPoint = point
|
|||
|
|
maxDepthFaces = cp.faces
|
|||
|
|
} else {
|
|||
|
|
_, _, z1 := maxDepthPoint.GetDestCoord()
|
|||
|
|
_, _, z2 := point.GetDestCoord()
|
|||
|
|
if z1 > z2 {
|
|||
|
|
maxDepthPoint = point
|
|||
|
|
maxDepthFaces = cp.faces
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
r.backFaces = maxDepthFaces
|
|||
|
|
r.frontFaces = diffFaces(allFaces, maxDepthFaces)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type cubePoint struct {
|
|||
|
|
point *Point
|
|||
|
|
faces []string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (r *SkinRenderer) createCubePoints() []cubePoint {
|
|||
|
|
return []cubePoint{
|
|||
|
|
{NewPoint(0, 0, 0), []string{"back", "right", "top"}},
|
|||
|
|
{NewPoint(0, 0, 1), []string{"front", "right", "top"}},
|
|||
|
|
{NewPoint(0, 1, 0), []string{"back", "right", "bottom"}},
|
|||
|
|
{NewPoint(0, 1, 1), []string{"front", "right", "bottom"}},
|
|||
|
|
{NewPoint(1, 0, 0), []string{"back", "left", "top"}},
|
|||
|
|
{NewPoint(1, 0, 1), []string{"front", "left", "top"}},
|
|||
|
|
{NewPoint(1, 1, 0), []string{"back", "left", "bottom"}},
|
|||
|
|
{NewPoint(1, 1, 1), []string{"front", "left", "bottom"}},
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func diffFaces(all, exclude []string) []string {
|
|||
|
|
excludeMap := make(map[string]bool)
|
|||
|
|
for _, f := range exclude {
|
|||
|
|
excludeMap[f] = true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var result []string
|
|||
|
|
for _, f := range all {
|
|||
|
|
if !excludeMap[f] {
|
|||
|
|
result = append(result, f)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func contains(slice []string, item string) bool {
|
|||
|
|
for _, s := range slice {
|
|||
|
|
if s == item {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// memberRotation 部件旋转
|
|||
|
|
func (r *SkinRenderer) memberRotation() {
|
|||
|
|
hd := float64(r.hdRatio)
|
|||
|
|
|
|||
|
|
// 头部和头盔旋转
|
|||
|
|
angles := r.membersAngles["head"]
|
|||
|
|
for _, face := range r.polygons["head"] {
|
|||
|
|
for _, poly := range face {
|
|||
|
|
poly.PreProject(4*hd, 8*hd, 2*hd, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
for _, face := range r.polygons["helmet"] {
|
|||
|
|
for _, poly := range face {
|
|||
|
|
poly.PreProject(4*hd, 8*hd, 2*hd, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if r.headOnly {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 右臂旋转
|
|||
|
|
angles = r.membersAngles["rightArm"]
|
|||
|
|
for _, face := range r.polygons["rightArm"] {
|
|||
|
|
for _, poly := range face {
|
|||
|
|
poly.PreProject(-2*hd, 8*hd, 2*hd, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 左臂旋转
|
|||
|
|
angles = r.membersAngles["leftArm"]
|
|||
|
|
for _, face := range r.polygons["leftArm"] {
|
|||
|
|
for _, poly := range face {
|
|||
|
|
poly.PreProject(10*hd, 8*hd, 2*hd, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 右腿旋转
|
|||
|
|
angles = r.membersAngles["rightLeg"]
|
|||
|
|
zOffset := 4 * hd
|
|||
|
|
if angles.sinAlpha < 0 {
|
|||
|
|
zOffset = 0
|
|||
|
|
}
|
|||
|
|
for _, face := range r.polygons["rightLeg"] {
|
|||
|
|
for _, poly := range face {
|
|||
|
|
poly.PreProject(2*hd, 20*hd, zOffset, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 左腿旋转
|
|||
|
|
angles = r.membersAngles["leftLeg"]
|
|||
|
|
zOffset = 4 * hd
|
|||
|
|
if angles.sinAlpha < 0 {
|
|||
|
|
zOffset = 0
|
|||
|
|
}
|
|||
|
|
for _, face := range r.polygons["leftLeg"] {
|
|||
|
|
for _, poly := range face {
|
|||
|
|
poly.PreProject(6*hd, 20*hd, zOffset, angles.cosAlpha, angles.sinAlpha, angles.cosOmega, angles.sinOmega)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// createProjectionPlan 创建投影
|
|||
|
|
func (r *SkinRenderer) createProjectionPlan() {
|
|||
|
|
for _, piece := range r.polygons {
|
|||
|
|
for _, face := range piece {
|
|||
|
|
for _, poly := range face {
|
|||
|
|
if !poly.IsProjected() {
|
|||
|
|
poly.Project(r.cosAlpha, r.sinAlpha, r.cosOmega, r.sinOmega, &r.minX, &r.maxX, &r.minY, &r.maxY)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// displayImage 渲染最终图像
|
|||
|
|
func (r *SkinRenderer) displayImage() image.Image {
|
|||
|
|
width := r.maxX - r.minX
|
|||
|
|
height := r.maxY - r.minY
|
|||
|
|
ratio := r.ratio * 2
|
|||
|
|
|
|||
|
|
srcWidth := int(ratio*width) + 1
|
|||
|
|
srcHeight := int(ratio*height) + 1
|
|||
|
|
|
|||
|
|
img := image.NewRGBA(image.Rect(0, 0, srcWidth, srcHeight))
|
|||
|
|
|
|||
|
|
// 按深度顺序绘制
|
|||
|
|
displayOrder := r.getDisplayOrder()
|
|||
|
|
|
|||
|
|
for _, order := range displayOrder {
|
|||
|
|
for piece, faces := range order {
|
|||
|
|
for _, face := range faces {
|
|||
|
|
if polys, ok := r.polygons[piece][face]; ok {
|
|||
|
|
for _, poly := range polys {
|
|||
|
|
poly.AddToImage(img, r.minX, r.minY, ratio)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 抗锯齿:2x 渲染后缩小
|
|||
|
|
realWidth := srcWidth / 2
|
|||
|
|
realHeight := srcHeight / 2
|
|||
|
|
destImg := resizeImage(img, realWidth, realHeight)
|
|||
|
|
|
|||
|
|
return destImg
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// getDisplayOrder 获取绘制顺序
|
|||
|
|
func (r *SkinRenderer) getDisplayOrder() []map[string][]string {
|
|||
|
|
var displayOrder []map[string][]string
|
|||
|
|
|
|||
|
|
if contains(r.frontFaces, "top") {
|
|||
|
|
if contains(r.frontFaces, "right") {
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.visibleFaces["leftLeg"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.visibleFaces["rightLeg"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.visibleFaces["leftArm"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"torso": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"torso": r.visibleFaces["torso"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.visibleFaces["rightArm"].front})
|
|||
|
|
} else {
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.visibleFaces["rightLeg"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.visibleFaces["leftLeg"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.visibleFaces["rightArm"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"torso": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"torso": r.visibleFaces["torso"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.visibleFaces["leftArm"].front})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"helmet": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"head": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"head": r.visibleFaces["head"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"helmet": r.visibleFaces["head"].front})
|
|||
|
|
} else {
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"helmet": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"head": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"head": r.visibleFaces["head"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"helmet": r.visibleFaces["head"].front})
|
|||
|
|
|
|||
|
|
if contains(r.frontFaces, "right") {
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.visibleFaces["leftArm"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"torso": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"torso": r.visibleFaces["torso"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.visibleFaces["rightArm"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.visibleFaces["leftLeg"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.visibleFaces["rightLeg"].front})
|
|||
|
|
} else {
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightArm": r.visibleFaces["rightArm"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"torso": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"torso": r.visibleFaces["torso"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftArm": r.visibleFaces["leftArm"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"rightLeg": r.visibleFaces["rightLeg"].front})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.backFaces})
|
|||
|
|
displayOrder = append(displayOrder, map[string][]string{"leftLeg": r.visibleFaces["leftLeg"].front})
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return displayOrder
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 辅助函数
|
|||
|
|
|
|||
|
|
func convertToRGBA(img image.Image) *image.RGBA {
|
|||
|
|
if rgba, ok := img.(*image.RGBA); ok {
|
|||
|
|
return rgba
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
bounds := img.Bounds()
|
|||
|
|
rgba := image.NewRGBA(bounds)
|
|||
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|||
|
|
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|||
|
|
rgba.Set(x, y, img.At(x, y))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return rgba
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func resizeImage(img image.Image, width, height int) *image.RGBA {
|
|||
|
|
bounds := img.Bounds()
|
|||
|
|
srcW := bounds.Dx()
|
|||
|
|
srcH := bounds.Dy()
|
|||
|
|
|
|||
|
|
dst := image.NewRGBA(image.Rect(0, 0, width, height))
|
|||
|
|
|
|||
|
|
for y := 0; y < height; y++ {
|
|||
|
|
for x := 0; x < width; x++ {
|
|||
|
|
srcX := bounds.Min.X + x*srcW/width
|
|||
|
|
srcY := bounds.Min.Y + y*srcH/height
|
|||
|
|
dst.Set(x, y, img.At(srcX, srcY))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return dst
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// getPixelColor 从皮肤图像获取像素颜色
|
|||
|
|
func (r *SkinRenderer) getPixelColor(x, y int) color.RGBA {
|
|||
|
|
if x < 0 || y < 0 {
|
|||
|
|
return color.RGBA{}
|
|||
|
|
}
|
|||
|
|
bounds := r.playerSkin.Bounds()
|
|||
|
|
if x >= bounds.Dx() || y >= bounds.Dy() {
|
|||
|
|
return color.RGBA{}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
c := r.playerSkin.At(bounds.Min.X+x, bounds.Min.Y+y)
|
|||
|
|
r32, g32, b32, a32 := c.RGBA()
|
|||
|
|
return color.RGBA{
|
|||
|
|
R: uint8(r32 >> 8),
|
|||
|
|
G: uint8(g32 >> 8),
|
|||
|
|
B: uint8(b32 >> 8),
|
|||
|
|
A: uint8(a32 >> 8),
|
|||
|
|
}
|
|||
|
|
}
|