267 lines
6.3 KiB
Go
267 lines
6.3 KiB
Go
package sprite
|
||
|
||
import (
|
||
"image"
|
||
"math"
|
||
)
|
||
|
||
type Sprite struct {
|
||
Name string
|
||
Position image.Point
|
||
Images []image.Image
|
||
CurrentFrame int
|
||
Index int
|
||
}
|
||
|
||
func (s *Sprite) SetImage(img *image.RGBA) {
|
||
if len(s.Images) == 0 {
|
||
s.Images = []image.Image{img}
|
||
s.CurrentFrame = 0
|
||
} else {
|
||
s.Images[s.CurrentFrame] = img
|
||
}
|
||
}
|
||
|
||
func (s *Sprite) Rotate(angle float64) {
|
||
// 创建旋转矩阵
|
||
sin, cos := math.Sin(angle), math.Cos(angle)
|
||
|
||
// 获取原始图像的边界
|
||
bounds := s.Images[s.CurrentFrame].Bounds()
|
||
width, height := bounds.Dx(), bounds.Dy()
|
||
centerX, centerY := width/2, height/2
|
||
|
||
// 计算旋转后图像的边界
|
||
corners := [4][2]int{
|
||
{0 - centerX, 0 - centerY},
|
||
{width - centerX, 0 - centerY},
|
||
{0 - centerX, height - centerY},
|
||
{width - centerX, height - centerY},
|
||
}
|
||
|
||
// 计算旋转后的四个角点位置
|
||
minX, minY, maxX, maxY := 0, 0, 0, 0
|
||
for i, p := range corners {
|
||
// 应用旋转
|
||
rotX := int(cos*float64(p[0]) - sin*float64(p[1]))
|
||
rotY := int(sin*float64(p[0]) + cos*float64(p[1]))
|
||
|
||
// 更新边界
|
||
if i == 0 || rotX < minX {
|
||
minX = rotX
|
||
}
|
||
if i == 0 || rotY < minY {
|
||
minY = rotY
|
||
}
|
||
if i == 0 || rotX > maxX {
|
||
maxX = rotX
|
||
}
|
||
if i == 0 || rotY > maxY {
|
||
maxY = rotY
|
||
}
|
||
}
|
||
|
||
// 创建新图像
|
||
newWidth, newHeight := maxX-minX+1, maxY-minY+1
|
||
newImage := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
|
||
newCenterX, newCenterY := newWidth/2, newHeight/2
|
||
|
||
// 预计算矩阵常量
|
||
invSin, invCos := math.Sin(-angle), math.Cos(-angle)
|
||
|
||
// 使用查找表存储sin/cos值,避免重复计算
|
||
srcXOrigin := make([]int, newWidth)
|
||
srcYOrigin := make([]int, newHeight)
|
||
|
||
// 预计算X坐标的变换
|
||
for x := 0; x < newWidth; x++ {
|
||
srcXOrigin[x] = centerX
|
||
}
|
||
|
||
// 预计算Y坐标的变换
|
||
for y := 0; y < newHeight; y++ {
|
||
srcYOrigin[y] = centerY
|
||
}
|
||
|
||
// 批量处理像素
|
||
for y := 0; y < newHeight; y++ {
|
||
srcY := y - newCenterY
|
||
|
||
for x := 0; x < newWidth; x++ {
|
||
srcX := x - newCenterX
|
||
|
||
// 应用逆向旋转矩阵
|
||
origX := int(invCos*float64(srcX)-invSin*float64(srcY)) + centerX
|
||
origY := int(invSin*float64(srcX)+invCos*float64(srcY)) + centerY
|
||
|
||
// 检查是否在原图范围内
|
||
if origX >= 0 && origX < width && origY >= 0 && origY < height {
|
||
newImage.Set(x, y, s.Images[s.CurrentFrame].At(origX, origY))
|
||
}
|
||
}
|
||
}
|
||
|
||
s.Images[s.CurrentFrame] = newImage
|
||
}
|
||
|
||
func (s *Sprite) Move(x, y int) {
|
||
s.Position.X += x
|
||
s.Position.Y += y
|
||
}
|
||
|
||
func (s *Sprite) Project(projectMatrix *ProjectMatrix) {
|
||
bounds := s.Images[s.CurrentFrame].Bounds()
|
||
|
||
// 计算四个角点投影后的位置
|
||
minX, minY, maxX, maxY := 0, 0, 0, 0
|
||
|
||
// 预先计算所有角点的投影,一次性确定边界
|
||
corners := [4]image.Point{
|
||
{bounds.Min.X, bounds.Min.Y},
|
||
{bounds.Max.X, bounds.Min.Y},
|
||
{bounds.Min.X, bounds.Max.Y},
|
||
{bounds.Max.X, bounds.Max.Y},
|
||
}
|
||
|
||
for i, corner := range corners {
|
||
projected := projectMatrix.ProjectPoint(corner)
|
||
if i == 0 {
|
||
minX, minY = projected.X, projected.Y
|
||
maxX, maxY = projected.X, projected.Y
|
||
} else {
|
||
if projected.X < minX {
|
||
minX = projected.X
|
||
}
|
||
if projected.Y < minY {
|
||
minY = projected.Y
|
||
}
|
||
if projected.X > maxX {
|
||
maxX = projected.X
|
||
}
|
||
if projected.Y > maxY {
|
||
maxY = projected.Y
|
||
}
|
||
}
|
||
}
|
||
|
||
// 创建新图像
|
||
newWidth, newHeight := maxX-minX+1, maxY-minY+1
|
||
newImage := image.NewRGBA(image.Rect(0, 0, newWidth, newHeight))
|
||
|
||
// 计算逆矩阵
|
||
det := projectMatrix.Matrix[0][0]*projectMatrix.Matrix[1][1] -
|
||
projectMatrix.Matrix[0][1]*projectMatrix.Matrix[1][0]
|
||
if math.Abs(det) < 1e-6 {
|
||
// 矩阵接近不可逆
|
||
return
|
||
}
|
||
|
||
invDet := 1.0 / det
|
||
invMatrix := [2][2]float64{
|
||
{projectMatrix.Matrix[1][1] * invDet, -projectMatrix.Matrix[0][1] * invDet},
|
||
{-projectMatrix.Matrix[1][0] * invDet, projectMatrix.Matrix[0][0] * invDet},
|
||
}
|
||
|
||
// 预计算变换常量
|
||
m00, m01 := invMatrix[0][0], invMatrix[0][1]
|
||
m10, m11 := invMatrix[1][0], invMatrix[1][1]
|
||
|
||
// 使用直接内存访问优化像素设置
|
||
dstRGBA := newImage.Pix
|
||
srcImg, srcRGBA, _ := getImageBytes(s.Images[s.CurrentFrame])
|
||
|
||
// 批量处理像素
|
||
for y := 0; y < newHeight; y++ {
|
||
srcY := float64(y + minY)
|
||
|
||
for x := 0; x < newWidth; x++ {
|
||
srcX := float64(x + minX)
|
||
|
||
// 使用逆矩阵计算
|
||
origX := int(m00*srcX + m01*srcY)
|
||
origY := int(m10*srcX + m11*srcY)
|
||
|
||
// 检查边界
|
||
if origX >= bounds.Min.X && origX < bounds.Max.X &&
|
||
origY >= bounds.Min.Y && origY < bounds.Max.Y {
|
||
// 设置像素 - 直接使用indexing会更快
|
||
if srcRGBA != nil {
|
||
// 如果源图像是RGBA格式,直接复制像素
|
||
dstIdx := (y*newImage.Stride + x*4)
|
||
srcIdx := ((origY-bounds.Min.Y)*srcImg.Stride + (origX-bounds.Min.X)*4)
|
||
|
||
dstRGBA[dstIdx] = srcRGBA[srcIdx] // R
|
||
dstRGBA[dstIdx+1] = srcRGBA[srcIdx+1] // G
|
||
dstRGBA[dstIdx+2] = srcRGBA[srcIdx+2] // B
|
||
dstRGBA[dstIdx+3] = srcRGBA[srcIdx+3] // A
|
||
} else {
|
||
// 否则使用通用方法
|
||
newImage.Set(x, y, s.Images[s.CurrentFrame].At(origX, origY))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
s.Images[s.CurrentFrame] = newImage
|
||
}
|
||
|
||
func (s *Sprite) DrawLine(line *Line) {
|
||
// 使用Line.ToSprite方法在当前精灵上绘制线条
|
||
line.AddToSprite(s)
|
||
}
|
||
|
||
// getImageBytes 尝试提取图像的原始字节数据以加速访问
|
||
func getImageBytes(img image.Image) (*image.RGBA, []uint8, bool) {
|
||
if rgba, ok := img.(*image.RGBA); ok {
|
||
return rgba, rgba.Pix, true
|
||
}
|
||
return nil, nil, false
|
||
}
|
||
|
||
// AddFrame 添加一个新的图像帧
|
||
func (s *Sprite) AddFrame(img image.Image) {
|
||
s.Images = append(s.Images, img)
|
||
}
|
||
|
||
// SetFrames 设置所有图像帧
|
||
func (s *Sprite) SetFrames(images []image.Image) {
|
||
s.Images = images
|
||
s.CurrentFrame = 0
|
||
}
|
||
|
||
// NextFrame 切换到下一帧
|
||
func (s *Sprite) NextFrame() {
|
||
if len(s.Images) > 1 {
|
||
s.CurrentFrame = (s.CurrentFrame + 1) % len(s.Images)
|
||
}
|
||
}
|
||
|
||
// SetFrame 设置当前帧
|
||
func (s *Sprite) SetFrame(frame int) {
|
||
if len(s.Images) > 0 && frame >= 0 && frame < len(s.Images) {
|
||
s.CurrentFrame = frame
|
||
}
|
||
}
|
||
|
||
// GetCurrentImage 获取当前帧图像
|
||
func (s *Sprite) GetCurrentImage() image.Image {
|
||
if len(s.Images) == 0 {
|
||
return nil
|
||
}
|
||
return s.Images[s.CurrentFrame]
|
||
}
|
||
|
||
func (s *Sprite) GetFrame(index int) image.Image {
|
||
if index < 0 {
|
||
return s.Images[0]
|
||
} else if index >= len(s.Images) {
|
||
return s.Images[len(s.Images)-1]
|
||
}
|
||
return s.Images[index]
|
||
}
|
||
|
||
// FrameCount 返回图像帧数量
|
||
func (s *Sprite) FrameCount() int {
|
||
return len(s.Images)
|
||
}
|