201 lines
4.4 KiB
Go
201 lines
4.4 KiB
Go
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),
|
|
}
|
|
}
|