aboutsummaryrefslogtreecommitdiff
path: root/assets/addons/voxel-core/classes/voxel.gd
blob: f9bc7bbeae49b1b3d7a04ffed2a07a2d2dab356f (plain)
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
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
tool
class_name Voxel, "res://addons/voxel-core/assets/classes/voxel.png"
extends Object
# Utility class containing various properties and methods to do with voxels.
#
# Voxel Schema:
# Every voxel is a Dictionary, not every Dictionary is a voxel, only by following
# the voxel scheme indicated below can wide varieties of voxels be produced. 
# Note that voxel dictionaries may have additions, but they must be done in such
# a way as to respect the original structure so as to avoid conflicts.
#
# {
#   name                 :   String           ~   Used in VoxelSets, should always be lowercase
#   color                :   Color            ~   Default color used for all voxel faces, if not present fallback is Transparent color
#   colors               :   Dictionary = {   ~   Color used on a per face bases, if not present fallback is voxel color
#      Vector3.UP        :   Color
#      Vector3.DOWN      :   Color
#      Vector3.RIGHT     :   Color
#      Vector3.LEFT      :   Color
#      Vector3.FORWARD   :   Color
#      Vector3.BACK      :   Color
#   }
#   uv                   :   Vector2          ~   Default uv position used for all voxel faces, if not present fallback is (-1.0, -1.0)
#   uvs                  :   Dictionary = {   ~   uv position used on a per face bases, if not present fallback to voxel uv
#      Vector3.UP        :   Vector2
#      Vector3.DOWN      :   Vector2
#      Vector3.RIGHT     :   Vector2
#      Vector3.LEFT      :   Vector2
#      Vector3.FORWARD   :   Vector2
#      Vector3.BACK      :   Vector2
#   }
#   metallic             :   float            ~   Metallic value used for all voxel face's material, must be between 0.0 and 1.0 and if not present fallback is 0.0
#   specular             :   float            ~   Specular value used for all voxel faces, must be between 0.0 and 1.0 and if not present fallback is 0.5
#   roughness            :   float            ~   Roughness value used for all voxel faces, must be between 0.0 and 1.0 and if not present fallback is 1.0
#   energy               :   float            ~   Emission energy value used for all voxel faces, must be between 0.0 and 16.0 and if not present fallback is 0.0
#   energy_color         :   Color            ~   Emission color used for all voxel faces, if not present fallback is white
#   material             :   int              ~   ID of the VoxelSet material used for this voxel, if not present fallback is -1
# }
#



## Constants
# Lists of all voxel faces, and their adjacent directions
const Faces := {
	Vector3.RIGHT: [ Vector3.FORWARD, Vector3.BACK, Vector3.DOWN, Vector3.UP ],
	Vector3.UP: [ Vector3.LEFT, Vector3.RIGHT, Vector3.FORWARD, Vector3.BACK ],
	Vector3.FORWARD: [ Vector3.LEFT, Vector3.RIGHT, Vector3.DOWN, Vector3.UP ],
	Vector3.LEFT: [ Vector3.FORWARD, Vector3.BACK, Vector3.DOWN, Vector3.UP ],
	Vector3.DOWN: [ Vector3.LEFT, Vector3.RIGHT, Vector3.FORWARD, Vector3.BACK ],
	Vector3.BACK: [ Vector3.LEFT, Vector3.RIGHT, Vector3.DOWN, Vector3.UP ],
}

# 0.5 means that voxels will have the dimensions of 0.5 x 0.5 x 0.5
const VoxelWorldSize := 0.5



## Public Methods
# Returns Dictionary representation of a colored voxel
# color    :   Color                        :   color to set
# colors   :   Dictionary<Vector3, Color>   :   face colors to set
# return   :   Dictionary<String, Variant>  :   Dictionary representing voxel
static func colored(color : Color, colors := {}) -> Dictionary:
	var voxel = {}
	voxel["color"] = color
	if not colors.empty():
		voxel["colors"] = colors.duplicate()
	return voxel


# Returns true if voxel has color defined
static func has_color(voxel : Dictionary) -> bool:
	return voxel.has("color")


# Returns the defined color within given voxel if present, otherwise returns transparent color
static func get_color(voxel : Dictionary) -> Color:
	return voxel.get("color", Color.transparent)


# Sets the given color to the given voxel
static func set_color(voxel : Dictionary, color : Color) -> void:
	voxel["color"] = color


# Returns true if voxel has specified color at given face
static func has_face_color(voxel : Dictionary, face : Vector3) -> bool:
	return voxel.has("colors") and voxel["colors"].has(face)


# Returns the defined color at given face if present, otherwise returns color
static func get_face_color(voxel : Dictionary, face : Vector3) -> Color:
	return voxel["colors"].get(face, get_color(voxel)) if voxel.has("colors") else get_color(voxel)


# Sets the given color at the given face to the given voxel
static func set_face_color(voxel : Dictionary, face : Vector3, color : Color) -> void:
	if not voxel.has("colors"): voxel["colors"] = {}
	voxel["colors"][face] = color


# Removes color at given face from given voxel
static func remove_face_color(voxel : Dictionary, face : Vector3) -> void:
	if voxel.has("colors"):
		voxel["colors"].erase(face)
		if voxel["colors"].empty():
			voxel.erase("colors")


# Returns Dictionary representation of a uvd voxel
# uv         :   Vector2                        :   uv to set
# uvs        :   Dictionary<Vector3, Vector2>   :   face uv to set
# color      :   Color                          :   color to set
# colors     :   Dictionary<Vector3, Color>     :   face colors to set
# return     :   Dictionary<String, Variant>    :   Dictionary representing voxel
static func uvd(uv : Vector2, uvs := {}, color := Color.white, colors := {}) -> Dictionary:
	var voxel = colored(color, colors)
	voxel["uv"] = uv
	if not uvs.empty():
		voxel["uvs"] = uvs
	return voxel


# Returns true if voxel has uv defined
static func has_uv(voxel : Dictionary) -> bool:
	return voxel.has("uv")


# Returns the defined uv within given voxel if present, otherwise returns a negative vector
static func get_uv(voxel : Dictionary) -> Vector2:
	return voxel.get("uv", -Vector2.ONE)


# Sets the given uv to the given voxel
static func set_uv(voxel : Dictionary, uv : Vector2) -> void:
	voxel["uv"] = uv


# Removes uv from given voxel
static func remove_uv(voxel : Dictionary) -> void:
	voxel.erase("uv")


# Returns true if voxel has specified uv at given face
static func has_face_uv(voxel : Dictionary, face : Vector3) -> bool:
	return voxel.has("uvs") and voxel["uvs"].has(face)


# Returns the defined uv at given face if present, otherwise returns uv
static func get_face_uv(voxel : Dictionary, face : Vector3) -> Vector2:
	return voxel["uvs"].get(face, get_uv(voxel)) if voxel.has("uvs") else get_uv(voxel)


# Sets the given uv at the given face to the given voxel
static func set_face_uv(voxel : Dictionary, face : Vector3, uv : Vector2) -> void:
	if not voxel.has("uvs"):
		voxel["uvs"] = {}
	voxel["uvs"][face] = uv


# Removes uv at given face from given voxel
static func remove_face_uv(voxel : Dictionary, face : Vector3) -> void:
	if voxel.has("uvs"):
		voxel["uvs"].erase(face)
		if voxel["uvs"].empty():
			voxel.erase("uvs")


# Returns true if given name is valid
static func is_valid_name(name : String) -> bool:
	return not name.empty()


# Returns the defined name within given voxel if present, otherwise returns an empty string
static func get_name(voxel : Dictionary) -> String:
	return voxel.get("name", "")


# Sets the given name to the given voxel
static func set_name(voxel : Dictionary, name : String) -> void:
	voxel["name"] = name.to_lower()


# Removes name from given voxel
static func remove_name(voxel : Dictionary) -> void:
	voxel.erase("name")


# Returns the defined metallic within given voxel if present, otherwise returns 0
static func get_metallic(voxel : Dictionary) -> float:
	return voxel.get("metallic", 0.0)


# Sets the given metallic to the given voxel
static func set_metallic(voxel : Dictionary, metallic : float) -> void:
	voxel["metallic"] = metallic


# Removes metallic from given voxel
static func remove_metallic(voxel : Dictionary) -> void:
	voxel.erase("metallic")


# Returns the defined specular within given voxel if present, otherwise returns 0.5
static func get_specular(voxel : Dictionary) -> float:
	return voxel.get("specular", 0.5)


# Sets the given specular to the given voxel
static func set_specular(voxel : Dictionary, specular : float) -> void:
	voxel["specular"] = specular


# Removes specular from given voxel
static func remove_specular(voxel : Dictionary) -> void:
	voxel.erase("specular")


# Returns the defined roughness within given voxel if present, otherwise returns 1
static func get_roughness(voxel : Dictionary) -> float:
	return voxel.get("roughness", 1.0)


# Sets the given roughness to the given voxel
static func set_roughness(voxel : Dictionary, roughness : float) -> void:
	voxel["roughness"] = roughness


# Removes roughness from given voxel
static func remove_roughness(voxel : Dictionary) -> void:
	voxel.erase("roughness")


# Returns the defined energy within given voxel if present, otherwise returns 0
static func get_energy(voxel : Dictionary) -> float:
	return voxel.get("energy", 0.0)


# Sets the given energy to the given voxel
static func set_energy(voxel : Dictionary, energy : float) -> void:
	voxel["energy"] = energy


# Removes energy from given voxel
static func remove_energy(voxel : Dictionary) -> void:
	voxel.erase("energy")


# Returns the defined energy_color within given voxel if present, otherwise returns Color.white
static func get_energy_color(voxel : Dictionary) -> Color:
	return voxel.get("energy_color", Color.white)


# Sets the given energy_color to the given voxel
static func set_energy_color(voxel : Dictionary, energy_color : Color) -> void:
	voxel["energy_color"] = energy_color


# Removes energy_color from given voxel
static func remove_energy_color(voxel : Dictionary) -> void:
	voxel.erase("energy_color")


# Returns the defined material within given voxel if present, otherwise returns -1
static func get_material(voxel : Dictionary) -> int:
	return voxel.get("material", -1)


# Sets the given material to the given voxel
static func set_material(voxel : Dictionary, material : int) -> void:
	voxel["material"] = material


# Removes material from given voxel
static func remove_material(voxel : Dictionary) -> void:
	voxel.erase("material")


# Removes unnecessary properties of given voxel in accordance to Voxel schema
static func clean(voxel : Dictionary) -> void:
	if not is_valid_name(get_name(voxel)):
		remove_name(voxel)
	
	if get_uv(voxel) == get_uv({}):
		remove_uv(voxel)
	
	for face in Faces:
		if get_face_color(voxel, face) == get_color({}):
			remove_face_color(voxel, face)
		if get_face_uv(voxel, face) == get_uv({}):
			remove_face_uv(voxel, face)
	
	if get_material(voxel) == get_material({}):
		remove_material(voxel)
	
	if get_metallic(voxel) == get_metallic({}):
		remove_metallic(voxel)
	
	if get_specular(voxel) == get_specular({}):
		remove_specular(voxel)
	
	if get_roughness(voxel) == get_roughness({}):
		remove_roughness(voxel)
	
	if get_energy(voxel) == get_energy({}):
		remove_energy(voxel)
	
	if get_energy_color(voxel) == get_energy_color({}):
		remove_energy_color(voxel)


# Returns the world position as snapped world position
static func world_to_snapped(world : Vector3) -> Vector3:
	return (world / VoxelWorldSize).floor() * VoxelWorldSize


# Returns the snapped world position as voxel grid position
static func snapped_to_grid(snapped : Vector3) -> Vector3:
	return snapped / VoxelWorldSize


# Returns world position as voxel grid position
static func world_to_grid(world : Vector3) -> Vector3:
	return snapped_to_grid(world_to_snapped(world))


# Returns voxel grid position as snapped world position
static func grid_to_snapped(grid : Vector3) -> Vector3:
	return grid * VoxelWorldSize