summaryrefslogtreecommitdiff
path: root/src/camera.h
blob: b8de3b1db3246623c3e83f16d0f9bf876ee7769e (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
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>

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.
*/

#pragma once

#include "irrlichttypes_extrabloated.h"
#include "inventory.h"
#include "mesh.h"
#include "client/tile.h"
#include <ICameraSceneNode.h>
#include <ISceneNode.h>
#include <list>

class LocalPlayer;
struct MapDrawControl;
class Client;
class WieldMeshSceneNode;

struct Nametag {
	Nametag(scene::ISceneNode *a_parent_node,
			const std::string &a_nametag_text,
			const video::SColor &a_nametag_color,
			const v3f a_nametag_pos):
		parent_node(a_parent_node),
		nametag_text(a_nametag_text),
		nametag_color(a_nametag_color),
		nametag_pos(a_nametag_pos)
	{
	}
	scene::ISceneNode *parent_node;
	std::string nametag_text;
	video::SColor nametag_color;
	v3f nametag_pos;
};

enum CameraMode {CAMERA_MODE_FIRST, CAMERA_MODE_THIRD, CAMERA_MODE_THIRD_FRONT};

/*
	Client camera class, manages the player and camera scene nodes, the viewing distance
	and performs view bobbing etc. It also displays the wielded tool in front of the
	first-person camera.
*/
class Camera
{
public:
	Camera(MapDrawControl &draw_control, Client *client);
	~Camera();

	// Get camera scene node.
	// It has the eye transformation, pitch and view bobbing applied.
	inline scene::ICameraSceneNode* getCameraNode() const
	{
		return m_cameranode;
	}

	// Get the camera position (in absolute scene coordinates).
	// This has view bobbing applied.
	inline v3f getPosition() const
	{
		return m_camera_position;
	}

	// Get the camera direction (in absolute camera coordinates).
	// This has view bobbing applied.
	inline v3f getDirection() const
	{
		return m_camera_direction;
	}

	// Get the camera offset
	inline v3s16 getOffset() const
	{
		return m_camera_offset;
	}

	// Horizontal field of view
	inline f32 getFovX() const
	{
		return m_fov_x;
	}

	// Vertical field of view
	inline f32 getFovY() const
	{
		return m_fov_y;
	}

	// Get maximum of getFovX() and getFovY()
	inline f32 getFovMax() const
	{
		return MYMAX(m_fov_x, m_fov_y);
	}

	// Checks if the constructor was able to create the scene nodes
	bool successfullyCreated(std::string &error_message);

	// Step the camera: updates the viewing range and view bobbing.
	void step(f32 dtime);

	// Update the camera from the local player's position.
	// busytime is used to adjust the viewing range.
	void update(LocalPlayer* player, f32 frametime, f32 busytime,
			f32 tool_reload_ratio);

	// Update render distance
	void updateViewingRange();

	// Start digging animation
	// Pass 0 for left click, 1 for right click
	void setDigging(s32 button);

	// Replace the wielded item mesh
	void wield(const ItemStack &item);

	// Draw the wielded tool.
	// This has to happen *after* the main scene is drawn.
	// Warning: This clears the Z buffer.
	void drawWieldedTool(irr::core::matrix4* translation=NULL);

	// Toggle the current camera mode
	void toggleCameraMode() {
		if (m_camera_mode == CAMERA_MODE_FIRST)
			m_camera_mode = CAMERA_MODE_THIRD;
		else if (m_camera_mode == CAMERA_MODE_THIRD)
			m_camera_mode = CAMERA_MODE_THIRD_FRONT;
		else
			m_camera_mode = CAMERA_MODE_FIRST;
	}

	// Set the current camera mode
	inline void setCameraMode(CameraMode mode)
	{
		m_camera_mode = mode;
	}

	//read the current camera mode
	inline CameraMode getCameraMode()
	{
		return m_camera_mode;
	}

	Nametag *addNametag(scene::ISceneNode *parent_node,
		const std::string &nametag_text, video::SColor nametag_color,
		const v3f &pos);

	void removeNametag(Nametag *nametag);

	const std::list<Nametag *> &getNametags() { return m_nametags; }

	void drawNametags();

	inline void addArmInertia(f32 player_yaw);

private:
	// Nodes
	scene::ISceneNode *m_playernode = nullptr;
	scene::ISceneNode *m_headnode = nullptr;
	scene::ICameraSceneNode *m_cameranode = nullptr;

	scene::ISceneManager *m_wieldmgr = nullptr;
	WieldMeshSceneNode *m_wieldnode = nullptr;

	// draw control
	MapDrawControl& m_draw_control;

	Client *m_client;

	// Absolute camera position
	v3f m_camera_position;
	// Absolute camera direction
	v3f m_camera_direction;
	// Camera offset
	v3s16 m_camera_offset;

	v2f m_wieldmesh_offset = v2f(55.0f, -35.0f);
	v2f m_arm_dir;
	v2f m_cam_vel;
	v2f m_cam_vel_old;
	v2f m_last_cam_pos;

	// Field of view and aspect ratio stuff
	f32 m_aspect = 1.0f;
	f32 m_fov_x = 1.0f;
	f32 m_fov_y = 1.0f;

	// View bobbing animation frame (0 <= m_view_bobbing_anim < 1)
	f32 m_view_bobbing_anim = 0.0f;
	// If 0, view bobbing is off (e.g. player is standing).
	// If 1, view bobbing is on (player is walking).
	// If 2, view bobbing is getting switched off.
	s32 m_view_bobbing_state = 0;
	// Speed of view bobbing animation
	f32 m_view_bobbing_speed = 0.0f;
	// Fall view bobbing
	f32 m_view_bobbing_fall = 0.0f;

	// Digging animation frame (0 <= m_digging_anim < 1)
	f32 m_digging_anim = 0.0f;
	// If -1, no digging animation
	// If 0, left-click digging animation
	// If 1, right-click digging animation
	s32 m_digging_button = -1;

	// Animation when changing wielded item
	f32 m_wield_change_timer = 0.125f;
	ItemStack m_wield_item_next;

	CameraMode m_camera_mode = CAMERA_MODE_FIRST;

	f32 m_cache_fall_bobbing_amount;
	f32 m_cache_view_bobbing_amount;
	f32 m_cache_fov;
	f32 m_cache_zoom_fov;
	bool m_arm_inertia;

	std::list<Nametag *> m_nametags;
};
Coord function advtrains.maxN(list, expectstart) local n=expectstart or 0 while list[n] do n=n+1 end return n-1 end function advtrains.minN(list, expectstart) local n=expectstart or 0 while list[n] do n=n-1 end return n+1 end function atround(number) return math.floor(number+0.5) end atfloor = math.floor function advtrains.round_vector_floor_y(vec) return {x=math.floor(vec.x+0.5), y=math.floor(vec.y), z=math.floor(vec.z+0.5)} end function advtrains.yawToDirection(yaw, conn1, conn2) if not conn1 or not conn2 then error("given nil to yawToDirection: conn1="..(conn1 or "nil").." conn2="..(conn1 or "nil")) end local yaw1 = advtrains.dir_to_angle(conn1) local yaw2 = advtrains.dir_to_angle(conn2) local adiff1 = advtrains.minAngleDiffRad(yaw, yaw1) local adiff2 = advtrains.minAngleDiffRad(yaw, yaw2) if math.abs(adiff2)<math.abs(adiff1) then return conn2 else return conn1 end end function advtrains.yawToAnyDir(yaw) local min_conn, min_diff=0, 10 for conn, vec in pairs(advtrains.dir_trans_tbl) do local yaw1 = advtrains.dir_to_angle(conn) local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1)) if diff < min_diff then min_conn = conn min_diff = diff end end return min_conn end function advtrains.yawToClosestConn(yaw, conns) local min_connid, min_diff=1, 10 for connid, conn in ipairs(conns) do local yaw1 = advtrains.dir_to_angle(conn.c) local diff = math.abs(advtrains.minAngleDiffRad(yaw, yaw1)) if diff < min_diff then min_connid = connid min_diff = diff end end return min_connid end local pi, pi2 = math.pi, 2*math.pi function advtrains.minAngleDiffRad(r1, r2) while r1>pi2 do r1=r1-pi2 end while r1<0 do r1=r1+pi2 end while r2>pi2 do r2=r2-pi2 end while r1<0 do r2=r2+pi2 end local try1=r2-r1 local try2=r2+pi2-r1 local try3=r2-pi2-r1 local minabs = math.min(math.abs(try1), math.abs(try2), math.abs(try3)) if minabs==math.abs(try1) then return try1 end if minabs==math.abs(try2) then return try2 end if minabs==math.abs(try3) then return try3 end end -- Takes 2 connections (0...AT_CMAX) as argument -- Returns the angle median of those 2 positions from the pov -- of standing on the cdir1 side and looking towards cdir2 -- cdir1 - >NODE> - cdir2 function advtrains.conn_angle_median(cdir1, cdir2) local ang1 = advtrains.dir_to_angle(advtrains.oppd(cdir1)) local ang2 = advtrains.dir_to_angle(cdir2) return ang1 + advtrains.minAngleDiffRad(ang1, ang2)/2 end function advtrains.merge_tables(a, ...) local new={} for _,t in ipairs({a,...}) do for k,v in pairs(t) do new[k]=v end end return new end function advtrains.save_keys(tbl, keys) local new={} for _,key in ipairs(keys) do new[key] = tbl[key] end return new end function advtrains.get_real_index_position(path, index) if not path or not index then return end local first_pos=path[math.floor(index)] local second_pos=path[math.floor(index)+1] if not first_pos or not second_pos then return nil end local factor=index-math.floor(index) local actual_pos={x=first_pos.x-(first_pos.x-second_pos.x)*factor, y=first_pos.y-(first_pos.y-second_pos.y)*factor, z=first_pos.z-(first_pos.z-second_pos.z)*factor,} return actual_pos end function advtrains.pos_median(pos1, pos2) return {x=pos1.x-(pos1.x-pos2.x)*0.5, y=pos1.y-(pos1.y-pos2.y)*0.5, z=pos1.z-(pos1.z-pos2.z)*0.5} end function advtrains.abs_ceil(i) return math.ceil(math.abs(i))*math.sign(i) end function advtrains.serialize_inventory(inv) local ser={} local liszts=inv:get_lists() for lisztname, liszt in pairs(liszts) do ser[lisztname]={} for idx, item in ipairs(liszt) do local istring=item:to_string() if istring~="" then ser[lisztname][idx]=istring end end end return minetest.serialize(ser) end function advtrains.deserialize_inventory(sers, inv) local ser=minetest.deserialize(sers) if ser then inv:set_lists(ser) return true end return false end --is_protected wrapper that checks for protection_bypass privilege function advtrains.is_protected(pos, name) if not name then error("advtrains.is_protected() called without name parameter!") end if minetest.check_player_privs(name, {protection_bypass=true}) then --player can bypass protection return false end return minetest.is_protected(pos, name) end function advtrains.is_creative(name) if not name then error("advtrains.is_creative() called without name parameter!") end if minetest.check_player_privs(name, {creative=true}) then return true end return minetest.settings:get_bool("creative_mode") end function advtrains.is_damage_enabled(name) if not name then error("advtrains.is_damage_enabled() called without name parameter!") end if minetest.check_player_privs(name, "train_admin") then return false end return minetest.settings:get_bool("enable_damage") end function advtrains.ms_to_kmh(speed) return speed * 3.6 end -- 4 possible inputs: -- integer: just do that modulo calculation -- table with c set: rotate c -- table with tables: rotate each -- table with integers: rotate each (probably no use case) function advtrains.rotate_conn_by(conn, rotate) if tonumber(conn) then return (conn+rotate)%AT_CMAX elseif conn.c then return { c = (conn.c+rotate)%AT_CMAX, y = conn.y} end local tmp={} for connid, data in ipairs(conn) do tmp[connid]=advtrains.rotate_conn_by(data, rotate) end return tmp end function advtrains.oppd(dir) return advtrains.rotate_conn_by(dir, AT_CMAX/2) end --conn_to_match like rotate_conn_by --other_conns have to be a table of conn tables! function advtrains.conn_matches_to(conn, other_conns) if tonumber(conn) then for connid, data in ipairs(other_conns) do if advtrains.oppd(conn) == data.c then return connid end end return false elseif conn.c then for connid, data in ipairs(other_conns) do local cmp = advtrains.oppd(conn) if cmp.c == data.c and (cmp.y or 0) == (data.y or 0) then return connid end end return false end local tmp={} for connid, data in ipairs(conn) do local backmatch = advtrains.conn_matches_to(data, other_conns) if backmatch then return backmatch, connid end --returns <connid of other rail> <connid of this rail> end return false end -- Going from the rail at pos (does not need to be rounded) along connection with id conn_idx, if there is a matching rail, return it and the matching connid -- returns: <adjacent pos>, <conn index of adjacent>, <my conn index>, <railheight of adjacent> -- parameter this_conns_p is connection table of this rail and is optional, is determined by get_rail_info_at if not provided. function advtrains.get_adjacent_rail(this_posnr, this_conns_p, conn_idx, drives_on) local this_pos = advtrains.round_vector_floor_y(this_posnr) local this_conns = this_conns_p if not this_conns then _, this_conns = advtrains.get_rail_info_at(this_pos) end if not conn_idx then for coni, _ in ipairs(this_conns) do local adj_pos, adj_conn_idx, _, nry, nco = advtrains.get_adjacent_rail(this_pos, this_conns, coni) if adj_pos then return adj_pos,adj_conn_idx,coni,nry, nco end end return nil end local conn = this_conns[conn_idx] local conn_y = conn.y or 0 local adj_pos = advtrains.dirCoordSet(this_pos, conn.c); while conn_y>=1 do conn_y = conn_y - 1 adj_pos.y = adj_pos.y + 1 end local nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on) if not nextnode_ok then adj_pos.y = adj_pos.y - 1 conn_y = conn_y + 1 nextnode_ok, nextconns, nextrail_y=advtrains.get_rail_info_at(adj_pos, drives_on) if not nextnode_ok then return nil end end local adj_connid = advtrains.conn_matches_to({c=conn.c, y=conn_y}, nextconns) if adj_connid then return adj_pos, adj_connid, conn_idx, nextrail_y, nextconns end return nil end -- when a train enters a rail on connid 'conn', which connid will it go out? -- nconns: number of connections in connection table: -- 2 = straight rail; 3 = turnout, 4 = crossing, 5 = three-way turnout (5th entry is a stub) -- returns: connid_out local connlku={[2]={2,1}, [3]={2,1,1}, [4]={2,1,4,3}, [5]={2,1,1,1}} function advtrains.get_matching_conn(conn, nconns) return connlku[nconns][conn] end function advtrains.random_id() local idst="" for i=0,5 do idst=idst..(math.random(0,9)) end return idst end -- Shorthand for pos_to_string and round_vector_floor_y function advtrains.roundfloorpts(pos) return minetest.pos_to_string(advtrains.round_vector_floor_y(pos)) end -- insert an element into a table if it does not yet exist there -- equalfunc is a function to compare equality, defaults to == -- returns true if the element was inserted function advtrains.insert_once(tab, elem, equalfunc) for _,e in pairs(tab) do if equalfunc and equalfunc(elem, e) or e==elem then return false end end tab[#tab+1] = elem return true end local hext = { [0]="0",[1]="1",[2]="2",[3]="3",[4]="4",[5]="5",[6]="6",[7]="7",[8]="8",[9]="9",[10]="A",[11]="B",[12]="C",[13]="D",[14]="E",[15]="F"} local dect = { ["0"]=0,["1"]=1,["2"]=2,["3"]=3,["4"]=4,["5"]=5,["6"]=6,["7"]=7,["8"]=8,["9"]=9,["A"]=10,["B"]=11,["C"]=12,["D"]=13,["E"]=14,["F"]=15} local f = atfloor local function hex(i) local x=i+32768 local c4 = x % 16 x = f(x / 16) local c3 = x % 16 x = f(x / 16) local c2 = x % 16 x = f(x / 16) local c1 = x % 16 return (hext[c1]) .. (hext[c2]) .. (hext[c3]) .. (hext[c4]) end local function c(s,i) return dect[string.sub(s,i,i)] end local function dec(s) return (c(s,1)*4096 + c(s,2)*256 + c(s,3)*16 + c(s,4))-32768 end -- Takes a position vector and outputs a encoded value suitable as table index -- This is essentially a hexadecimal representation of the position (+32768) -- Order (YYY)YXXXXZZZZ function advtrains.encode_pos(pos) return hex(pos.y) .. hex(pos.x) .. hex(pos.z) end -- decodes a position encoded with encode_pos function advtrains.decode_pos(pts) if not pts or not #pts==6 then return nil end local stry = string.sub(pts, 1,4) local strx = string.sub(pts, 5,8) local strz = string.sub(pts, 9,12) return vector.new(dec(strx), dec(stry), dec(strz)) end --[[ Benchmarking code local tdt = {} local tlt = {} local tet = {} for i=1,1000000 do