aboutsummaryrefslogtreecommitdiff
path: root/game/addons/zylann.hterrain/tools/globalmap_baker.gd
blob: 6faa0eb6d2092e2d5e45c6587940b33d96603f7a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168

# Bakes a global albedo map using the same shader the terrain uses,
# but renders top-down in orthographic mode.

@tool
extends Node

const HTerrain = preload("../hterrain.gd")
const HTerrainData = preload("../hterrain_data.gd")
const HTerrainMesher = preload("../hterrain_mesher.gd")

# Must be power of two
const DEFAULT_VIEWPORT_SIZE = 512

signal progress_notified(info)
signal permanent_change_performed(message)

var _terrain : HTerrain = null
var _viewport : SubViewport = null
var _viewport_size := DEFAULT_VIEWPORT_SIZE
var _plane : MeshInstance3D = null
var _camera : Camera3D = null
var _sectors := []
var _sector_index := 0


func _ready():
   set_process(false)


func bake(terrain: HTerrain):
   assert(terrain != null)
   var data := terrain.get_data()
   assert(data != null)
   _terrain = terrain

   var splatmap := data.get_texture(HTerrainData.CHANNEL_SPLAT)
   var colormap := data.get_texture(HTerrainData.CHANNEL_COLOR)

   var terrain_size := data.get_resolution()
   
   if _viewport == null:
      _setup_scene(terrain_size)
   
   var cw := terrain_size / _viewport_size
   var ch := terrain_size / _viewport_size
   for y in ch:
      for x in cw:
         _sectors.append(Vector2(x, y))
   
   var mat := _plane.material_override
   _terrain.setup_globalmap_material(mat)

   _sector_index = 0
   set_process(true)


func _setup_scene(terrain_size: int):
   assert(_viewport == null)
   
   _viewport_size = DEFAULT_VIEWPORT_SIZE
   while _viewport_size > terrain_size:
      _viewport_size /= 2

   _viewport = SubViewport.new()
   _viewport.size = Vector2(_viewport_size + 1, _viewport_size + 1)
   _viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
   _viewport.render_target_clear_mode = SubViewport.CLEAR_MODE_ALWAYS
   # _viewport.render_target_v_flip = true
   _viewport.world_3d = World3D.new()
   _viewport.own_world_3d = true
   _viewport.debug_draw = Viewport.DEBUG_DRAW_UNSHADED
   
   var mat := ShaderMaterial.new()
   
   _plane = MeshInstance3D.new()
   # Make a very small mesh, vertex precision isn't required
   var plane_res := 4
   _plane.mesh = \
      HTerrainMesher.make_flat_chunk(plane_res, plane_res, _viewport_size / plane_res, 0)
   _plane.material_override = mat
   _viewport.add_child(_plane)
   
   _camera = Camera3D.new()
   _camera.projection = Camera3D.PROJECTION_ORTHOGONAL
   _camera.size = _viewport.size.x
   _camera.near = 0.1
   _camera.far = 10.0
   _camera.current = true
   _camera.rotation = Vector3(deg_to_rad(-90), 0, 0)
   _viewport.add_child(_camera)
   
   add_child(_viewport)


func _cleanup_scene():
   _viewport.queue_free()
   _viewport = null
   _plane = null
   _camera = null


func _process(delta):
   if not is_processing():
      return

   if _sector_index > 0:
      _grab_image(_sectors[_sector_index - 1])

   if _sector_index >= len(_sectors):
      set_process(false)
      _finish()
      progress_notified.emit({ "finished": true })
   else:
      _setup_pass(_sectors[_sector_index])
      _report_progress()
      _sector_index += 1


func _report_progress():
   var sector = _sectors[_sector_index]
   progress_notified.emit({
      "progress": float(_sector_index) / len(_sectors),
      "message": "Calculating sector (" + str(sector.x) + ", " + str(sector.y) + ")"
   })


func _setup_pass(sector: Vector2):
   # Note: we implicitely take off-by-one pixels into account
   var origin := sector * _viewport_size
   var center := origin + 0.5 * Vector2(_viewport.size)

   # The heightmap is left empty, so will default to white, which is a height of 1.
   # The camera must be placed above the terrain to see it.
   _camera.position = Vector3(center.x, 2.0, center.y)
   _plane.position = Vector3(origin.x, 0.0, origin.y)


func _grab_image(sector: Vector2):
   var tex := _viewport.get_texture()
   var src := tex.get_image()
   
   assert(_terrain != null)
   var data := _terrain.get_data()
   assert(data != null)
   
   if data.get_map_count(HTerrainData.CHANNEL_GLOBAL_ALBEDO) == 0:
      data._edit_add_map(HTerrainData.CHANNEL_GLOBAL_ALBEDO)
   
   var dst := data.get_image(HTerrainData.CHANNEL_GLOBAL_ALBEDO)
   
   src.convert(dst.get_format())
   var origin = sector * _viewport_size
   dst.blit_rect(src, Rect2i(0, 0, src.get_width(), src.get_height()), origin)


func _finish():
   assert(_terrain != null)
   var data := _terrain.get_data() as HTerrainData
   assert(data != null)
   var dst := data.get_image(HTerrainData.CHANNEL_GLOBAL_ALBEDO)
   
   data.notify_region_change(Rect2(0, 0, dst.get_width(), dst.get_height()), 
      HTerrainData.CHANNEL_GLOBAL_ALBEDO)
   permanent_change_performed.emit("Bake globalmap")
   
   _cleanup_scene()
   _terrain = null