aboutsummaryrefslogtreecommitdiff
path: root/builtin/fstk/ui.lua
blob: 3ac0386ca44d577bebdbb7690ddb826526b6ea46 (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
--Minetest
--Copyright (C) 2014 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

ui = {}
ui.childlist = {}
ui.default = nil

--------------------------------------------------------------------------------
function ui.add(child)
	--TODO check child
	ui.childlist[child.name] = child

	return child.name
end

--------------------------------------------------------------------------------
function ui.delete(child)

	if ui.childlist[child.name] == nil then
		return false
	end

	ui.childlist[child.name] = nil
	return true
end

--------------------------------------------------------------------------------
function ui.set_default(name)
	ui.default = name
end

--------------------------------------------------------------------------------
function ui.find_by_name(name)
	return ui.childlist[name]
end

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- Internal functions not to be called from user
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

local function wordwrap_quickhack(str)
	local res = ""
	local ar = str:split("\n")
	for i = 1, #ar do
		local text = ar[i]
		-- Hack to add word wrapping.
		-- TODO: Add engine support for wrapping in formspecs
		while #text > 80 do
			if res ~= "" then
				res = res .. ","
			end
			res = res .. core.formspec_escape(string.sub(text, 1, 79))
			text = string.sub(text, 80, #text)
		end
		if res ~= "" then
			res = res .. ","
		end
		res = res .. core.formspec_escape(text)
	end
	return res
end

--------------------------------------------------------------------------------
function ui.update()
	local formspec = ""

	-- handle errors
	if gamedata ~= nil and gamedata.reconnect_requested then
		formspec = wordwrap_quickhack(gamedata.errormessage or "")
		formspec = "size[12,5]" ..
				"label[0.5,0;" .. fgettext("The server has requested a reconnect:") ..
				"]textlist[0.2,0.8;11.5,3.5;;" .. formspec ..
				"]button[6,4.6;3,0.5;btn_reconnect_no;" .. fgettext("Main menu") .. "]" ..
				"button[3,4.6;3,0.5;btn_reconnect_yes;" .. fgettext("Reconnect") .. "]"
	elseif gamedata ~= nil and gamedata.errormessage ~= nil then
		formspec = wordwrap_quickhack(gamedata.errormessage)
		local error_title
		if string.find(gamedata.errormessage, "ModError") then
			error_title = fgettext("An error occured in a Lua script, such as a mod:")
		else
			error_title = fgettext("An error occured:")
		end
		formspec = "size[12,5]" ..
				"label[0.5,0;" .. error_title ..
				"]textlist[0.2,0.8;11.5,3.5;;" .. formspec ..
				"]button[4.5,4.6;3,0.5;btn_error_confirm;" .. fgettext("Ok") .. "]"
	else
		local active_toplevel_ui_elements = 0
		for key,value in pairs(ui.childlist) do
			if (value.type == "toplevel") then
				local retval = value:get_formspec()

				if retval ~= nil and retval ~= "" then
					active_toplevel_ui_elements = active_toplevel_ui_elements +1
					formspec = formspec .. retval
				end
			end
		end

		-- no need to show addons if there ain't a toplevel element
		if (active_toplevel_ui_elements > 0) then
			for key,value in pairs(ui.childlist) do
				if (value.type == "addon") then
					local retval = value:get_formspec()

					if retval ~= nil and retval ~= "" then
						formspec = formspec .. retval
					end
				end
			end
		end

		if (active_toplevel_ui_elements > 1) then
			core.log("warning", "more than one active ui "..
				"element, self most likely isn't intended")
		end

		if (active_toplevel_ui_elements == 0) then
			core.log("warning", "no toplevel ui element "..
					"active; switching to default")
			ui.childlist[ui.default]:show()
			formspec = ui.childlist[ui.default]:get_formspec()
		end
	end
	core.update_formspec(formspec)
end

--------------------------------------------------------------------------------
function ui.handle_buttons(fields)
	for key,value in pairs(ui.childlist) do

		local retval = value:handle_buttons(fields)

		if retval then
			ui.update()
			return
		end
	end
end


--------------------------------------------------------------------------------
function ui.handle_events(event)

	for key,value in pairs(ui.childlist) do

		if value.handle_events ~= nil then
			local retval = value:handle_events(event)

			if retval then
				return retval
			end
		end
	end
end

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-- initialize callbacks
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
core.button_handler = function(fields)
	if fields["btn_reconnect_yes"] then
		gamedata.reconnect_requested = false
		gamedata.errormessage = nil
		gamedata.do_reconnect = true
		core.start()
		return
	elseif fields["btn_reconnect_no"] or fields["btn_error_confirm"] then
		gamedata.errormessage = nil
		gamedata.reconnect_requested = false
		ui.update()
		return
	end

	if ui.handle_buttons(fields) then
		ui.update()
	end
end

--------------------------------------------------------------------------------
core.event_handler = function(event)
	if ui.handle_events(event) then
		ui.update()
		return
	end

	if event == "Refresh" then
		ui.update()
		return
	end
end
, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include <cmath> #include "noise.h" #include <iostream> #include <cstring> // memset #include "debug.h" #include "util/numeric.h" #include "util/string.h" #include "exceptions.h" #define NOISE_MAGIC_X 1619 #define NOISE_MAGIC_Y 31337 #define NOISE_MAGIC_Z 52591 #define NOISE_MAGIC_SEED 1013 typedef float (*Interp2dFxn)( float v00, float v10, float v01, float v11, float x, float y); typedef float (*Interp3dFxn)( float v000, float v100, float v010, float v110, float v001, float v101, float v011, float v111, float x, float y, float z); FlagDesc flagdesc_noiseparams[] = { {"defaults", NOISE_FLAG_DEFAULTS}, {"eased", NOISE_FLAG_EASED}, {"absvalue", NOISE_FLAG_ABSVALUE}, {"pointbuffer", NOISE_FLAG_POINTBUFFER}, {"simplex", NOISE_FLAG_SIMPLEX}, {NULL, 0} }; /////////////////////////////////////////////////////////////////////////////// PcgRandom::PcgRandom(u64 state, u64 seq) { seed(state, seq); } void PcgRandom::seed(u64 state, u64 seq) { m_state = 0U; m_inc = (seq << 1u) | 1u; next(); m_state += state; next(); } u32 PcgRandom::next() { u64 oldstate = m_state; m_state = oldstate * 6364136223846793005ULL + m_inc; u32 xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; u32 rot = oldstate >> 59u; return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); } u32 PcgRandom::range(u32 bound) { // If the bound is 0, we cover the whole RNG's range if (bound == 0) return next(); /* This is an optimization of the expression: 0x100000000ull % bound since 64-bit modulo operations typically much slower than 32. */ u32 threshold = -bound % bound; u32 r; /* If the bound is not a multiple of the RNG's range, it may cause bias, e.g. a RNG has a range from 0 to 3 and we take want a number 0 to 2. Using rand() % 3, the number 0 would be twice as likely to appear. With a very large RNG range, the effect becomes less prevalent but still present. This can be solved by modifying the range of the RNG to become a multiple of bound by dropping values above the a threshold. In our example, threshold == 4 % 3 == 1, so reject values < 1 (that is, 0), thus making the range == 3 with no bias. This loop may look dangerous, but will always terminate due to the RNG's property of uniformity. */ while ((r = next()) < threshold) ; return r % bound; } s32 PcgRandom::range(s32 min, s32 max) { if (max < min) throw PrngException("Invalid range (max < min)"); // We have to cast to s64 because otherwise this could overflow, // and signed overflow is undefined behavior. u32 bound = (s64)max - (s64)min + 1; return range(bound) + min; } void PcgRandom::bytes(void *out, size_t len) { u8 *outb = (u8 *)out; int bytes_left = 0; u32 r; while (len--) { if (bytes_left == 0) { bytes_left = sizeof(u32); r = next(); } *outb = r & 0xFF; outb++; bytes_left--; r >>= CHAR_BIT; } } s32 PcgRandom::randNormalDist(s32 min, s32 max, int num_trials) { s32 accum = 0; for (int i = 0; i != num_trials; i++) accum += range(min, max); return myround((float)accum / num_trials); } /////////////////////////////////////////////////////////////////////////////// float noise2d(int x, int y, s32 seed) { unsigned int n = (NOISE_MAGIC_X * x + NOISE_MAGIC_Y * y + NOISE_MAGIC_SEED * seed) & 0x7fffffff; n = (n >> 13) ^ n; n = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7fffffff; return 1.f - (float)(int)n / 0x40000000; } float noise3d(int x, int y, int z, s32 seed) { unsigned int n = (NOISE_MAGIC_X * x + NOISE_MAGIC_Y * y + NOISE_MAGIC_Z * z + NOISE_MAGIC_SEED * seed) & 0x7fffffff; n = (n >> 13) ^ n; n = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7fffffff; return 1.f - (float)(int)n / 0x40000000; } inline float dotProduct(float vx, float vy, float wx, float wy) { return vx * wx + vy * wy; } inline float linearInterpolation(float v0, float v1, float t) { return v0 + (v1 - v0) * t; } inline float biLinearInterpolation( float v00, float v10, float v01, float v11, float x, float y) { float tx = easeCurve(x); float ty = easeCurve(y); float u = linearInterpolation(v00, v10, tx); float v = linearInterpolation(v01, v11, tx); return linearInterpolation(u, v, ty); } inline float biLinearInterpolationNoEase( float v00, float v10, float v01, float v11, float x, float y) { float u = linearInterpolation(v00, v10, x); float v = linearInterpolation(v01, v11, x); return linearInterpolation(u, v, y); } float triLinearInterpolation( float v000, float v100, float v010, float v110, float v001, float v101, float v011, float v111, float x, float y, float z) { float tx = easeCurve(x); float ty = easeCurve(y); float tz = easeCurve(z); float u = biLinearInterpolationNoEase(v000, v100, v010, v110, tx, ty); float v = biLinearInterpolationNoEase(v001, v101, v011, v111, tx, ty); return linearInterpolation(u, v, tz); } float triLinearInterpolationNoEase( float v000, float v100, float v010, float v110, float v001, float v101, float v011, float v111, float x, float y, float z) { float u = biLinearInterpolationNoEase(v000, v100, v010, v110, x, y); float v = biLinearInterpolationNoEase(v001, v101, v011, v111, x, y); return linearInterpolation(u, v, z); } float noise2d_gradient(float x, float y, s32 seed, bool eased) { // Calculate the integer coordinates int x0 = myfloor(x); int y0 = myfloor(y); // Calculate the remaining part of the coordinates float xl = x - (float)x0; float yl = y - (float)y0; // Get values for corners of square float v00 = noise2d(x0, y0, seed); float v10 = noise2d(x0+1, y0, seed); float v01 = noise2d(x0, y0+1, seed); float v11 = noise2d(x0+1, y0+1, seed); // Interpolate if (eased) return biLinearInterpolation(v00, v10, v01, v11, xl, yl); return biLinearInterpolationNoEase(v00, v10, v01, v11, xl, yl); } float noise3d_gradient(float x, float y, float z, s32 seed, bool eased) { // Calculate the integer coordinates int x0 = myfloor(x); int y0 = myfloor(y); int z0 = myfloor(z); // Calculate the remaining part of the coordinates float xl = x - (float)x0; float yl = y - (float)y0; float zl = z - (float)z0; // Get values for corners of cube float v000 = noise3d(x0, y0, z0, seed); float v100 = noise3d(x0 + 1, y0, z0, seed); float v010 = noise3d(x0, y0 + 1, z0, seed); float v110 = noise3d(x0 + 1, y0 + 1, z0, seed); float v001 = noise3d(x0, y0, z0 + 1, seed); float v101 = noise3d(x0 + 1, y0, z0 + 1, seed); float v011 = noise3d(x0, y0 + 1, z0 + 1, seed); float v111 = noise3d(x0 + 1, y0 + 1, z0 + 1, seed); // Interpolate if (eased) { return triLinearInterpolation( v000, v100, v010, v110, v001, v101, v011, v111, xl, yl, zl); } return triLinearInterpolationNoEase( v000, v100, v010, v110, v001, v101, v011, v111, xl, yl, zl); } float noise2d_perlin(float x, float y, s32 seed, int octaves, float persistence, bool eased) { float a = 0; float f = 1.0; float g = 1.0; for (int i = 0; i < octaves; i++) { a += g * noise2d_gradient(x * f, y * f, seed + i, eased); f *= 2.0; g *= persistence; } return a; } float noise2d_perlin_abs(float x, float y, s32 seed, int octaves, float persistence, bool eased) { float a = 0; float f = 1.0; float g = 1.0; for (int i = 0; i < octaves; i++) { a += g * std::fabs(noise2d_gradient(x * f, y * f, seed + i, eased)); f *= 2.0; g *= persistence; } return a; } float noise3d_perlin(float x, float y, float z, s32 seed, int octaves, float persistence, bool eased) { float a = 0; float f = 1.0; float g = 1.0; for (int i = 0; i < octaves; i++) { a += g * noise3d_gradient(x * f, y * f, z * f, seed + i, eased); f *= 2.0; g *= persistence; } return a; } float noise3d_perlin_abs(float x, float y, float z, s32 seed, int octaves, float persistence, bool eased) { float a = 0; float f = 1.0; float g = 1.0; for (int i = 0; i < octaves; i++) { a += g * std::fabs(noise3d_gradient(x * f, y * f, z * f, seed + i, eased)); f *= 2.0; g *= persistence; } return a; } float contour(float v) { v = std::fabs(v); if (v >= 1.0) return 0.0; return (1.0 - v); } ///////////////////////// [ New noise ] //////////////////////////// float NoisePerlin2D(NoiseParams *np, float x, float y, s32 seed) { float a = 0; float f = 1.0; float g = 1.0; x /= np->spread.X; y /= np->spread.Y; seed += np->seed; for (size_t i = 0; i < np->octaves; i++) { float noiseval = noise2d_gradient(x * f, y * f, seed + i, np->flags & (NOISE_FLAG_DEFAULTS | NOISE_FLAG_EASED)); if (np->flags & NOISE_FLAG_ABSVALUE) noiseval = std::fabs(noiseval); a += g * noiseval; f *= np->lacunarity; g *= np->persist; } return np->offset + a * np->scale; } float NoisePerlin3D(NoiseParams *np, float x, float y, float z, s32 seed) { float a = 0; float f = 1.0; float g = 1.0; x /= np->spread.X; y /= np->spread.Y; z /= np->spread.Z; seed += np->seed; for (size_t i = 0; i < np->octaves; i++) { float noiseval = noise3d_gradient(x * f, y * f, z * f, seed + i, np->flags & NOISE_FLAG_EASED); if (np->flags & NOISE_FLAG_ABSVALUE) noiseval = std::fabs(noiseval); a += g * noiseval; f *= np->lacunarity; g *= np->persist; } return np->offset + a * np->scale; } Noise::Noise(NoiseParams *np_, s32 seed, u32 sx, u32 sy, u32 sz) { np = *np_; this->seed = seed; this->sx = sx; this->sy = sy; this->sz = sz; allocBuffers(); } Noise::~Noise() { delete[] gradient_buf; delete[] persist_buf; delete[] noise_buf; delete[] result; } void Noise::allocBuffers() { if (sx < 1) sx = 1; if (sy < 1) sy = 1; if (sz < 1) sz = 1; this->noise_buf = NULL; resizeNoiseBuf(sz > 1); delete[] gradient_buf; delete[] persist_buf; delete[] result; try { size_t bufsize = sx * sy * sz; this->persist_buf = NULL; this->gradient_buf = new float[bufsize]; this->result = new float[bufsize]; } catch (std::bad_alloc &e) { throw InvalidNoiseParamsException(); } } void Noise::setSize(u32 sx, u32 sy, u32 sz) { this->sx = sx; this->sy = sy; this->sz = sz; allocBuffers(); } void Noise::setSpreadFactor(v3f spread) { this->np.spread = spread; resizeNoiseBuf(sz > 1); } void Noise::setOctaves(int octaves) { this->np.octaves = octaves; resizeNoiseBuf(sz > 1); } void Noise::resizeNoiseBuf(bool is3d) { // Maximum possible spread value factor float ofactor = (np.lacunarity > 1.0) ? pow(np.lacunarity, np.octaves - 1) : np.lacunarity; // Noise lattice point count // (int)(sz * spread * ofactor) is # of lattice points crossed due to length float num_noise_points_x = sx * ofactor / np.spread.X; float num_noise_points_y = sy * ofactor / np.spread.Y; float num_noise_points_z = sz * ofactor / np.spread.Z; // Protect against obviously invalid parameters if (num_noise_points_x > 1000000000.f || num_noise_points_y > 1000000000.f || num_noise_points_z > 1000000000.f) throw InvalidNoiseParamsException(); // Protect against an octave having a spread < 1, causing broken noise values if (np.spread.X / ofactor < 1.0f || np.spread.Y / ofactor < 1.0f || np.spread.Z / ofactor < 1.0f) { errorstream << "A noise parameter has too many octaves: " << np.octaves << " octaves" << std::endl; throw InvalidNoiseParamsException("A noise parameter has too many octaves"); } // + 2 for the two initial endpoints // + 1 for potentially crossing a boundary due to offset size_t nlx = (size_t)std::ceil(num_noise_points_x) + 3; size_t nly = (size_t)std::ceil(num_noise_points_y) + 3; size_t nlz = is3d ? (size_t)std::ceil(num_noise_points_z) + 3 : 1; delete[] noise_buf; try { noise_buf = new float[nlx * nly * nlz]; } catch (std::bad_alloc &e) { throw InvalidNoiseParamsException(); } } /* * NB: This algorithm is not optimal in terms of space complexity. The entire * integer lattice of noise points could be done as 2 lines instead, and for 3D, * 2 lines + 2 planes. * However, this would require the noise calls to be interposed with the * interpolation loops, which may trash the icache, leading to lower overall * performance. * Another optimization that could save half as many noise calls is to carry over * values from the previous noise lattice as midpoints in the new lattice for the * next octave. */ #define idx(x, y) ((y) * nlx + (x)) void Noise::gradientMap2D( float x, float y, float step_x, float step_y, s32 seed) { float v00, v01, v10, v11, u, v, orig_u;