1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-11-10 06:16:27 -05:00
OpenDiablo2/d2core/d2map/d2maprenderer/renderer.go
Ziemas 11f743aa42
Get and use draw order and animation speed for objects (#473)
* String2enum ObjectAnimationMode

* Render objects at their assigned layer

Gets the orderflag from the object record and assign it to the mapentity
so the renderer can get at it.

This adds another render pass that loops through the objects.

* Get object animation speed from their txt entry
2020-06-27 14:30:23 -04:00

393 lines
12 KiB
Go

package d2maprenderer
import (
"errors"
"image/color"
"log"
"math"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dat"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2term"
)
// The map renderer, used to render the map
type MapRenderer struct {
mapEngine *d2mapengine.MapEngine // The map engine that is being rendered
palette *d2dat.DATPalette // The palette used for this map
viewport *Viewport // The viewport for the map renderer (used for rendering offsets)
camera Camera // The camera for this map renderer (used to determine where on the map we are rendering)
debugVisLevel int // Debug visibility index (0=none, 1=tiles, 2=sub-tiles)
lastFrameTime float64 // The last time the map was rendered
currentFrame int // The current render frame (for animations)
}
// Creates an instance of the map renderer
func CreateMapRenderer(mapEngine *d2mapengine.MapEngine) *MapRenderer {
result := &MapRenderer{
mapEngine: mapEngine,
viewport: NewViewport(0, 0, 800, 600),
}
result.viewport.SetCamera(&result.camera)
d2term.BindAction("mapdebugvis", "set map debug visualization level", func(level int) {
result.debugVisLevel = level
})
if mapEngine.LevelType().Id != 0 {
result.generateTileCache()
}
return result
}
func (mr *MapRenderer) RegenerateTileCache() {
mr.generateTileCache()
}
func (mr *MapRenderer) SetMapEngine(mapEngine *d2mapengine.MapEngine) {
mr.mapEngine = mapEngine
mr.generateTileCache()
}
func (mr *MapRenderer) Render(target d2render.Surface) {
mapSize := mr.mapEngine.Size()
stxf, styf := mr.viewport.ScreenToWorld(400, -200)
etxf, etyf := mr.viewport.ScreenToWorld(400, 1050)
startX := int(math.Max(0, math.Floor(stxf)))
startY := int(math.Max(0, math.Floor(styf)))
endX := int(math.Min(float64(mapSize.Width), math.Ceil(etxf)))
endY := int(math.Min(float64(mapSize.Height), math.Ceil(etyf)))
mr.renderPass1(target, startX, startY, endX, endY)
if mr.debugVisLevel > 0 {
mr.renderDebug(mr.debugVisLevel, target, startX, startY, endX, endY)
}
mr.renderPass3(target, startX, startY, endX, endY)
mr.renderPass4(target, startX, startY, endX, endY)
}
func (mr *MapRenderer) MoveCameraTo(x, y float64) {
mr.camera.MoveTo(x, y)
}
func (mr *MapRenderer) MoveCameraBy(x, y float64) {
mr.camera.MoveBy(x, y)
}
func (mr *MapRenderer) ScreenToWorld(x, y int) (float64, float64) {
return mr.viewport.ScreenToWorld(x, y)
}
func (mr *MapRenderer) ScreenToOrtho(x, y int) (float64, float64) {
return mr.viewport.ScreenToOrtho(x, y)
}
func (mr *MapRenderer) WorldToOrtho(x, y float64) (float64, float64) {
return mr.viewport.WorldToOrtho(x, y)
}
// Lower wall tiles, tile shadews, floor tiles
func (mr *MapRenderer) renderPass1(target d2render.Surface, startX, startY, endX, endY int) {
for tileY := startY; tileY < endY; tileY++ {
for tileX := startX; tileX < endX; tileX++ {
tile := mr.mapEngine.TileAt(tileX, tileY)
mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY))
mr.renderTilePass1(tile, target)
mr.viewport.PopTranslation()
}
}
}
// Objects below walls
func (mr *MapRenderer) renderPass2(target d2render.Surface, startX, startY, endX, endY int) {
for tileY := startY; tileY < endY; tileY++ {
for tileX := startX; tileX < endX; tileX++ {
mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY))
// TODO: Do not loop over every entity every frame
for _, mapEntity := range *mr.mapEngine.Entities() {
entityX, entityY := mapEntity.GetPosition()
if (int(entityX) != tileX) || (int(entityY) != tileY) {
continue
}
if mapEntity.GetLayer() != 1 {
continue
}
target.PushTranslation(mr.viewport.GetTranslationScreen())
mapEntity.Render(target)
target.Pop()
}
mr.viewport.PopTranslation()
}
}
}
// Upper wall tiles, objects that are on top of walls
func (mr *MapRenderer) renderPass3(target d2render.Surface, startX, startY, endX, endY int) {
for tileY := startY; tileY < endY; tileY++ {
for tileX := startX; tileX < endX; tileX++ {
tile := mr.mapEngine.TileAt(tileX, tileY)
mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY))
mr.renderTilePass2(tile, target)
// TODO: Do not loop over every entity every frame
for _, mapEntity := range *mr.mapEngine.Entities() {
entityX, entityY := mapEntity.GetPosition()
if (int(entityX) != tileX) || (int(entityY) != tileY) {
continue
}
if mapEntity.GetLayer() == 1 {
continue
}
target.PushTranslation(mr.viewport.GetTranslationScreen())
mapEntity.Render(target)
target.Pop()
}
mr.viewport.PopTranslation()
}
}
}
// Roof tiles
func (mr *MapRenderer) renderPass4(target d2render.Surface, startX, startY, endX, endY int) {
for tileY := startY; tileY < endY; tileY++ {
for tileX := startX; tileX < endX; tileX++ {
tile := mr.mapEngine.TileAt(tileX, tileY)
mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY))
mr.renderTilePass3(tile, target)
mr.viewport.PopTranslation()
}
}
}
func (mr *MapRenderer) renderTilePass1(tile *d2ds1.TileRecord, target d2render.Surface) {
for _, wall := range tile.Walls {
if !wall.Hidden && wall.Prop1 != 0 && wall.Type.LowerWall() {
mr.renderWall(wall, mr.viewport, target)
}
}
for _, floor := range tile.Floors {
if !floor.Hidden && floor.Prop1 != 0 {
mr.renderFloor(floor, target)
}
}
for _, shadow := range tile.Shadows {
if !shadow.Hidden && shadow.Prop1 != 0 {
mr.renderShadow(shadow, target)
}
}
}
func (mr *MapRenderer) renderTilePass2(tile *d2ds1.TileRecord, target d2render.Surface) {
for _, wall := range tile.Walls {
if !wall.Hidden && wall.Type.UpperWall() {
mr.renderWall(wall, mr.viewport, target)
}
}
}
func (mr *MapRenderer) renderTilePass3(tile *d2ds1.TileRecord, target d2render.Surface) {
for _, wall := range tile.Walls {
if wall.Type == d2enum.Roof {
mr.renderWall(wall, mr.viewport, target)
}
}
}
func (mr *MapRenderer) renderFloor(tile d2ds1.FloorShadowRecord, target d2render.Surface) {
var img d2render.Surface
if !tile.Animated {
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex)
} else {
img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, byte(mr.currentFrame))
}
if img == nil {
log.Printf("Render called on uncached floor {%v,%v}", tile.Style, tile.Sequence)
return
}
mr.viewport.PushTranslationOrtho(-80, float64(tile.YAdjust))
defer mr.viewport.PopTranslation()
target.PushTranslation(mr.viewport.GetTranslationScreen())
defer target.Pop()
target.Render(img)
}
func (mr *MapRenderer) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target d2render.Surface) {
img := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex)
if img == nil {
log.Printf("Render called on uncached wall {%v,%v,%v}", tile.Style, tile.Sequence, tile.Type)
return
}
viewport.PushTranslationOrtho(-80, float64(tile.YAdjust)-8)
defer viewport.PopTranslation()
target.PushTranslation(viewport.GetTranslationScreen())
defer target.Pop()
target.Render(img)
}
func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadowRecord, target d2render.Surface) {
img := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex)
if img == nil {
log.Printf("Render called on uncached shadow {%v,%v}", tile.Style, tile.Sequence)
return
}
defer mr.viewport.PushTranslationOrtho(-80, float64(tile.YAdjust)).PopTranslation()
target.PushTranslation(mr.viewport.GetTranslationScreen())
target.PushColor(color.RGBA{R: 255, G: 255, B: 255, A: 160})
defer target.PopN(2)
target.Render(img)
}
func (mr *MapRenderer) renderDebug(debugVisLevel int, target d2render.Surface, startX, startY, endX, endY int) {
for tileY := startY; tileY < endY; tileY++ {
for tileX := startX; tileX < endX; tileX++ {
mr.viewport.PushTranslationWorld(float64(tileX), float64(tileY))
mr.renderTileDebug(tileX, tileY, debugVisLevel, target)
mr.viewport.PopTranslation()
}
}
}
func (mr *MapRenderer) WorldToScreen(x, y float64) (int, int) {
return mr.viewport.WorldToScreen(x, y)
}
func (mr *MapRenderer) WorldToScreenF(x, y float64) (float64, float64) {
return mr.viewport.WorldToScreenF(x, y)
}
func (mr *MapRenderer) renderTileDebug(ax, ay int, debugVisLevel int, target d2render.Surface) {
subTileColor := color.RGBA{R: 80, G: 80, B: 255, A: 50}
tileColor := color.RGBA{R: 255, G: 255, B: 255, A: 100}
tileCollisionColor := color.RGBA{R: 128, G: 0, B: 0, A: 100}
screenX1, screenY1 := mr.viewport.WorldToScreen(float64(ax), float64(ay))
screenX2, screenY2 := mr.viewport.WorldToScreen(float64(ax+1), float64(ay))
screenX3, screenY3 := mr.viewport.WorldToScreen(float64(ax), float64(ay+1))
target.PushTranslation(screenX1, screenY1)
defer target.Pop()
target.DrawLine(screenX2-screenX1, screenY2-screenY1, tileColor)
target.DrawLine(screenX3-screenX1, screenY3-screenY1, tileColor)
target.PushTranslation(-10, 10)
target.DrawText("%v, %v", ax, ay)
target.Pop()
if debugVisLevel > 1 {
for i := 1; i <= 4; i++ {
x2 := i * 16
y2 := i * 8
target.PushTranslation(-x2, y2)
target.DrawLine(80, 40, subTileColor)
target.Pop()
target.PushTranslation(x2, y2)
target.DrawLine(-80, 40, subTileColor)
target.Pop()
}
tile := mr.mapEngine.TileAt(ax, ay)
//for i, floor := range tile.Floors {
// target.PushTranslation(-20, 10+(i+1)*14)
// target.DrawText("f: %v-%v", floor.Style, floor.Sequence)
// target.Pop()
//}
for i, wall := range tile.Walls {
if wall.Type.Special() {
target.PushTranslation(-20, 10+(i+1)*14)
target.DrawText("s: %v-%v", wall.Style, wall.Sequence)
target.Pop()
}
}
for yy := 0; yy < 5; yy++ {
for xx := 0; xx < 5; xx++ {
isoX := (xx - yy) * 16
isoY := (xx + yy) * 8
var walkableArea = (*mr.mapEngine.WalkMesh())[((yy+(ay*5))*mr.mapEngine.Size().Width*5)+xx+(ax*5)]
if !walkableArea.Walkable {
target.PushTranslation(isoX-3, isoY+4)
target.DrawRect(5, 5, tileCollisionColor)
target.Pop()
}
}
}
}
}
func (mr *MapRenderer) Advance(elapsed float64) {
frameLength := 0.1
mr.lastFrameTime += elapsed
framesAdvanced := int(mr.lastFrameTime / frameLength)
mr.lastFrameTime -= float64(framesAdvanced) * frameLength
mr.currentFrame += framesAdvanced
if mr.currentFrame > 9 {
mr.currentFrame = 0
}
}
func loadPaletteForAct(levelType d2enum.RegionIdType) (*d2dat.DATPalette, error) {
var palettePath string
switch levelType {
case d2enum.RegionAct1Town, d2enum.RegionAct1Wilderness, d2enum.RegionAct1Cave, d2enum.RegionAct1Crypt,
d2enum.RegionAct1Monestary, d2enum.RegionAct1Courtyard, d2enum.RegionAct1Barracks,
d2enum.RegionAct1Jail, d2enum.RegionAct1Cathedral, d2enum.RegionAct1Catacombs, d2enum.RegionAct1Tristram:
palettePath = d2resource.PaletteAct1
case d2enum.RegionAct2Town, d2enum.RegionAct2Sewer, d2enum.RegionAct2Harem, d2enum.RegionAct2Basement,
d2enum.RegionAct2Desert, d2enum.RegionAct2Tomb, d2enum.RegionAct2Lair, d2enum.RegionAct2Arcane:
palettePath = d2resource.PaletteAct2
case d2enum.RegionAct3Town, d2enum.RegionAct3Jungle, d2enum.RegionAct3Kurast, d2enum.RegionAct3Spider,
d2enum.RegionAct3Dungeon, d2enum.RegionAct3Sewer:
palettePath = d2resource.PaletteAct3
case d2enum.RegionAct4Town, d2enum.RegionAct4Mesa, d2enum.RegionAct4Lava, d2enum.RegionAct5Lava:
palettePath = d2resource.PaletteAct4
case d2enum.RegonAct5Town, d2enum.RegionAct5Siege, d2enum.RegionAct5Barricade, d2enum.RegionAct5Temple,
d2enum.RegionAct5IceCaves, d2enum.RegionAct5Baal:
palettePath = d2resource.PaletteAct5
default:
return nil, errors.New("failed to find palette for region")
}
return d2asset.LoadPalette(palettePath)
}
func (mr *MapRenderer) ViewportToLeft() {
mr.viewport.toLeft()
}
func (mr *MapRenderer) ViewportToRight() {
mr.viewport.toRight()
}
func (mr *MapRenderer) ViewportDefault() {
mr.viewport.resetAlign()
}