暂存服务端渲染功能,材质渲染计划迁移至前端
This commit is contained in:
200
internal/service/skin_renderer/polygon.go
Normal file
200
internal/service/skin_renderer/polygon.go
Normal file
@@ -0,0 +1,200 @@
|
||||
package skin_renderer
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
// Polygon 表示一个四边形面片
|
||||
type Polygon struct {
|
||||
dots [4]*Point
|
||||
color color.RGBA
|
||||
isProjected bool
|
||||
face string // 面的方向: "x", "y", "z"
|
||||
faceDepth float64 // 面的深度
|
||||
}
|
||||
|
||||
// NewPolygon 创建一个新的多边形
|
||||
func NewPolygon(dots [4]*Point, c color.RGBA) *Polygon {
|
||||
p := &Polygon{
|
||||
dots: dots,
|
||||
color: c,
|
||||
}
|
||||
|
||||
// 确定面的方向
|
||||
x0, y0, z0 := dots[0].GetOriginCoord()
|
||||
x1, y1, z1 := dots[1].GetOriginCoord()
|
||||
x2, y2, z2 := dots[2].GetOriginCoord()
|
||||
|
||||
if x0 == x1 && x1 == x2 {
|
||||
p.face = "x"
|
||||
p.faceDepth = x0
|
||||
} else if y0 == y1 && y1 == y2 {
|
||||
p.face = "y"
|
||||
p.faceDepth = y0
|
||||
} else if z0 == z1 && z1 == z2 {
|
||||
p.face = "z"
|
||||
p.faceDepth = z0
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// Project 投影多边形的所有顶点
|
||||
func (p *Polygon) Project(cosAlpha, sinAlpha, cosOmega, sinOmega float64, minX, maxX, minY, maxY *float64) {
|
||||
for _, dot := range p.dots {
|
||||
if !dot.IsProjected() {
|
||||
dot.Project(cosAlpha, sinAlpha, cosOmega, sinOmega, minX, maxX, minY, maxY)
|
||||
}
|
||||
}
|
||||
p.isProjected = true
|
||||
}
|
||||
|
||||
// PreProject 预投影多边形的所有顶点
|
||||
func (p *Polygon) PreProject(dx, dy, dz, cosAlpha, sinAlpha, cosOmega, sinOmega float64) {
|
||||
for _, dot := range p.dots {
|
||||
dot.PreProject(dx, dy, dz, cosAlpha, sinAlpha, cosOmega, sinOmega)
|
||||
}
|
||||
}
|
||||
|
||||
// IsProjected 返回是否已投影
|
||||
func (p *Polygon) IsProjected() bool {
|
||||
return p.isProjected
|
||||
}
|
||||
|
||||
// AddToImage 将多边形绘制到图像上
|
||||
func (p *Polygon) AddToImage(img *image.RGBA, minX, minY, ratio float64) {
|
||||
// 检查透明度,完全透明则跳过
|
||||
if p.color.A == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取投影后的 2D 坐标
|
||||
points := make([][2]float64, 4)
|
||||
var coordX, coordY *float64
|
||||
|
||||
samePlanX := true
|
||||
samePlanY := true
|
||||
|
||||
for i, dot := range p.dots {
|
||||
x, y, _ := dot.GetDestCoord()
|
||||
points[i] = [2]float64{
|
||||
(x - minX) * ratio,
|
||||
(y - minY) * ratio,
|
||||
}
|
||||
|
||||
if coordX == nil {
|
||||
coordX = &x
|
||||
coordY = &y
|
||||
} else {
|
||||
if *coordX != x {
|
||||
samePlanX = false
|
||||
}
|
||||
if *coordY != y {
|
||||
samePlanY = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有点在同一平面(退化面),跳过
|
||||
if samePlanX || samePlanY {
|
||||
return
|
||||
}
|
||||
|
||||
// 使用扫描线算法填充多边形
|
||||
fillPolygon(img, points, p.color)
|
||||
}
|
||||
|
||||
// fillPolygon 使用扫描线算法填充四边形
|
||||
func fillPolygon(img *image.RGBA, points [][2]float64, c color.RGBA) {
|
||||
// 找到 Y 的范围
|
||||
minY := points[0][1]
|
||||
maxY := points[0][1]
|
||||
for _, pt := range points {
|
||||
if pt[1] < minY {
|
||||
minY = pt[1]
|
||||
}
|
||||
if pt[1] > maxY {
|
||||
maxY = pt[1]
|
||||
}
|
||||
}
|
||||
|
||||
bounds := img.Bounds()
|
||||
|
||||
// 扫描每一行
|
||||
for y := int(minY); y <= int(maxY); y++ {
|
||||
if y < bounds.Min.Y || y >= bounds.Max.Y {
|
||||
continue
|
||||
}
|
||||
|
||||
// 找到这一行与多边形边的交点
|
||||
var intersections []float64
|
||||
n := len(points)
|
||||
for i := 0; i < n; i++ {
|
||||
j := (i + 1) % n
|
||||
y1, y2 := points[i][1], points[j][1]
|
||||
x1, x2 := points[i][0], points[j][0]
|
||||
|
||||
// 检查这条边是否与当前扫描线相交
|
||||
if (y1 <= float64(y) && y2 > float64(y)) || (y2 <= float64(y) && y1 > float64(y)) {
|
||||
// 计算交点的 X 坐标
|
||||
t := (float64(y) - y1) / (y2 - y1)
|
||||
x := x1 + t*(x2-x1)
|
||||
intersections = append(intersections, x)
|
||||
}
|
||||
}
|
||||
|
||||
// 排序交点
|
||||
for i := 0; i < len(intersections)-1; i++ {
|
||||
for j := i + 1; j < len(intersections); j++ {
|
||||
if intersections[i] > intersections[j] {
|
||||
intersections[i], intersections[j] = intersections[j], intersections[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 填充交点之间的像素
|
||||
for i := 0; i+1 < len(intersections); i += 2 {
|
||||
xStart := int(intersections[i])
|
||||
xEnd := int(intersections[i+1])
|
||||
|
||||
for x := xStart; x <= xEnd; x++ {
|
||||
if x >= bounds.Min.X && x < bounds.Max.X {
|
||||
// Alpha 混合
|
||||
if c.A == 255 {
|
||||
img.SetRGBA(x, y, c)
|
||||
} else {
|
||||
existing := img.RGBAAt(x, y)
|
||||
blended := alphaBlend(existing, c)
|
||||
img.SetRGBA(x, y, blended)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// alphaBlend 执行 Alpha 混合
|
||||
func alphaBlend(dst, src color.RGBA) color.RGBA {
|
||||
if src.A == 0 {
|
||||
return dst
|
||||
}
|
||||
if src.A == 255 {
|
||||
return src
|
||||
}
|
||||
|
||||
srcA := float64(src.A) / 255.0
|
||||
dstA := float64(dst.A) / 255.0
|
||||
outA := srcA + dstA*(1-srcA)
|
||||
|
||||
if outA == 0 {
|
||||
return color.RGBA{}
|
||||
}
|
||||
|
||||
return color.RGBA{
|
||||
R: uint8((float64(src.R)*srcA + float64(dst.R)*dstA*(1-srcA)) / outA),
|
||||
G: uint8((float64(src.G)*srcA + float64(dst.G)*dstA*(1-srcA)) / outA),
|
||||
B: uint8((float64(src.B)*srcA + float64(dst.B)*dstA*(1-srcA)) / outA),
|
||||
A: uint8(outA * 255),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user