From de028fc056b26e03ee00324888f870f64e28c756 Mon Sep 17 00:00:00 2001
From: bigfoot547 <bigfoot547@users.noreply.github.com>
Date: Fri, 5 May 2017 15:07:36 -0500
Subject: [PATCH] [CSM] Add camera API (#5609)

* [CSM] Add camera API
roper rebase & squash

* Address nerzhul's review
---
 build/android/jni/Android.mk         |   1 +
 doc/client_lua_api.md                |  52 +++++--
 src/camera.cpp                       |   5 +
 src/camera.h                         |   8 +-
 src/script/lua_api/CMakeLists.txt    |   1 +
 src/script/lua_api/l_camera.cpp      | 206 +++++++++++++++++++++++++++
 src/script/lua_api/l_camera.h        |  46 ++++++
 src/script/lua_api/l_localplayer.cpp |  49 -------
 src/script/lua_api/l_localplayer.h   |   7 -
 src/script/scripting_client.cpp      |   7 +
 src/script/scripting_client.h        |   2 +
 11 files changed, 314 insertions(+), 70 deletions(-)
 create mode 100644 src/script/lua_api/l_camera.cpp
 create mode 100644 src/script/lua_api/l_camera.h

diff --git a/build/android/jni/Android.mk b/build/android/jni/Android.mk
index 6525942bd..d4a8d988f 100644
--- a/build/android/jni/Android.mk
+++ b/build/android/jni/Android.mk
@@ -305,6 +305,7 @@ LOCAL_SRC_FILES += \
 		jni/src/script/cpp_api/s_server.cpp       \
 		jni/src/script/lua_api/l_areastore.cpp    \
 		jni/src/script/lua_api/l_base.cpp         \
+		jni/src/script/lua_api/l_camera.cpp       \
 		jni/src/script/lua_api/l_client.cpp       \
 		jni/src/script/lua_api/l_craft.cpp        \
 		jni/src/script/lua_api/l_env.cpp          \
diff --git a/doc/client_lua_api.md b/doc/client_lua_api.md
index b76a406ea..a4293500f 100644
--- a/doc/client_lua_api.md
+++ b/doc/client_lua_api.md
@@ -694,7 +694,7 @@ Call these functions only at load time!
 * `minetest.get_wielded_item()`
     * Returns the itemstack the local player is holding
 * `minetest.localplayer`
-    * Reference to the LocalPlayer object. See `LocalPlayer` class reference for methods.
+    * Reference to the LocalPlayer object. See [`LocalPlayer`](#localplayer) class reference for methods.
 
 ### Client Environment
 * `minetest.get_player_names()`
@@ -750,7 +750,7 @@ Call these functions only at load time!
     * Encodes a string in base64.
 * `minetest.decode_base64(string)`: returns string
     * Decodes a string encoded in base64.
-* `minetest.gettext(string) : returns string
+* `minetest.gettext(string)` : returns string
     * look up the translation of a string in the gettext message catalog
 * `fgettext_ne(string, ...)`
     * call minetest.gettext(string), replace "$1"..."$9" with the given
@@ -762,7 +762,9 @@ Call these functions only at load time!
 
 ### UI
 * `minetest.ui.minimap`
-    * Reference to the minimap object. See `Minimap` class reference for methods.
+    * Reference to the minimap object. See [`Minimap`](#minimap) class reference for methods.
+* `minetest.camera`
+    * Reference to the camera object. See [`Camera`](#camera) class reference for methods.
 * `minetest.show_formspec(formname, formspec)` : returns true on success
 	* Shows a formspec to the player
 * `minetest.display_chat_message(message)` returns true on success
@@ -785,6 +787,40 @@ An interface to manipulate minimap on client UI
 * `set_shape(shape)`: Sets the minimap shape. (0 = square, 1 = round)
 * `get_shape()`: Gets the minimap shape. (0 = square, 1 = round)
 
+### Camera
+An interface to get or set information about the camera and cameranode.
+Please do not try to access the reference until the camera is initialized, otherwise the reference will be nil.
+
+#### Methods
+* `set_camera_mode(mode)`
+    * Pass `0` for first-person, `1` for third person, and `2` for third person front
+* `get_camera_mode()`
+    * Returns with same syntax as above
+* `get_fov()`
+    * Returns:
+    
+```lua
+     {
+         x = number,
+         y = number,
+         max = number,
+         actual = number
+     }
+```
+    
+* `get_pos()`
+    * Returns position of camera with view bobbing
+* `get_offset()`
+    * Returns eye offset vector
+* `get_look_dir()`
+    * Returns eye direction unit vector
+* `get_look_vertical()`
+    * Returns pitch in radians
+* `get_look_horizontal()`
+    * Returns yaw in radians
+* `get_aspect_ratio()`
+    * Returns aspect ratio of screen
+
 ### LocalPlayer
 An interface to retrieve information about the player. The player is
 not accessible until the client is fully done loading and therefore
@@ -844,16 +880,6 @@ Methods:
     * returns last player speed
 * `get_breath()`
     * returns the player's breath
-* `get_look_dir()`
-    * returns look direction vector
-* `get_look_horizontal()`
-    * returns look horizontal angle
-* `get_look_vertical()`
-    * returns look vertical angle
-* `get_eye_pos()`
-    * returns the player's eye position
-* `get_eye_offset()`
-    * returns the player's eye shift vector
 * `get_movement_acceleration()`
     * returns acceleration of the player in different environments:
 
diff --git a/src/camera.cpp b/src/camera.cpp
index b119bbfbb..949494b00 100644
--- a/src/camera.cpp
+++ b/src/camera.cpp
@@ -33,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "util/numeric.h"
 #include "constants.h"
 #include "fontengine.h"
+#include "script/scripting_client.h"
 
 #define CAMERA_OFFSET_STEP 200
 
@@ -125,6 +126,10 @@ bool Camera::successfullyCreated(std::string &error_message)
 	} else {
 		error_message.clear();
 	}
+	
+	if (g_settings->getBool("enable_client_modding")) {
+		m_client->getScript()->on_camera_ready(this);
+	}
 	return error_message.empty();
 }
 
diff --git a/src/camera.h b/src/camera.h
index e4c1d0b25..ca2e4ddcc 100644
--- a/src/camera.h
+++ b/src/camera.h
@@ -160,7 +160,13 @@ public:
 		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()
 	{
diff --git a/src/script/lua_api/CMakeLists.txt b/src/script/lua_api/CMakeLists.txt
index b03e94a09..1a78580e6 100644
--- a/src/script/lua_api/CMakeLists.txt
+++ b/src/script/lua_api/CMakeLists.txt
@@ -29,4 +29,5 @@ set(client_SCRIPT_LUA_API_SRCS
 	${CMAKE_CURRENT_SOURCE_DIR}/l_storage.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/l_sound.cpp
 	${CMAKE_CURRENT_SOURCE_DIR}/l_localplayer.cpp
+	${CMAKE_CURRENT_SOURCE_DIR}/l_camera.cpp
 	PARENT_SCOPE)
diff --git a/src/script/lua_api/l_camera.cpp b/src/script/lua_api/l_camera.cpp
new file mode 100644
index 000000000..e6229ffe8
--- /dev/null
+++ b/src/script/lua_api/l_camera.cpp
@@ -0,0 +1,206 @@
+#include "script/common/c_converter.h"
+#include "l_camera.h"
+#include "l_internal.h"
+#include "content_cao.h"
+#include "camera.h"
+
+LuaCamera::LuaCamera(Camera *m)
+{
+	m_camera = m;
+}
+
+void LuaCamera::create(lua_State *L, Camera *m)
+{
+	LuaCamera *o = new LuaCamera(m);
+	*(void **) (lua_newuserdata(L, sizeof(void *))) = o;
+	luaL_getmetatable(L, className);
+	lua_setmetatable(L, -2);
+	
+	int camera_object = lua_gettop(L);
+	
+	lua_getglobal(L, "core");
+	luaL_checktype(L, -1, LUA_TTABLE);
+	int coretable = lua_gettop(L);
+	
+	lua_pushvalue(L, camera_object);
+	lua_setfield(L, coretable, "camera");
+}
+
+int LuaCamera::l_set_camera_mode(lua_State *L)
+{
+	Camera *camera = getobject(L, 1);
+	GenericCAO *playercao = getClient(L)->getEnv().getLocalPlayer()->getCAO();
+	if (!camera)
+		return 0;
+	sanity_check(playercao);
+	if (!lua_isnumber(L, 2))
+		return 0;
+	
+	camera->setCameraMode((CameraMode)((int)lua_tonumber(L, 2)));
+	playercao->setVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
+	playercao->setChildrenVisible(camera->getCameraMode() > CAMERA_MODE_FIRST);
+	return 0;
+}
+
+int LuaCamera::l_get_camera_mode(lua_State *L)
+{
+	Camera *camera = getobject(L, 1);
+	if (!camera)
+		return 0;
+	
+	lua_pushnumber(L, (int)camera->getCameraMode());
+	
+	return 1;
+}
+
+int LuaCamera::l_get_fov(lua_State *L)
+{
+	Camera *camera = getobject(L, 1);
+	if (!camera)
+		return 0;
+	
+	lua_newtable(L);
+	lua_pushnumber(L, camera->getFovX() * core::DEGTORAD);
+	lua_setfield(L, -2, "x");
+	lua_pushnumber(L, camera->getFovY() * core::DEGTORAD);
+	lua_setfield(L, -2, "y");
+	lua_pushnumber(L, camera->getCameraNode()->getFOV() * core::RADTODEG);
+	lua_setfield(L, -2, "actual");
+	lua_pushnumber(L, camera->getFovMax() * core::RADTODEG);
+	lua_setfield(L, -2, "max");
+	return 1;
+}
+
+int LuaCamera::l_get_pos(lua_State *L)
+{
+	Camera *camera = getobject(L, 1);
+	if (!camera)
+		return 0;
+	
+	push_v3f(L, camera->getPosition());
+	return 1;
+}
+
+int LuaCamera::l_get_offset(lua_State *L)
+{
+	Camera *camera = getobject(L, 1);
+	if (!camera)
+		return 0;
+	
+	push_v3s16(L, camera->getOffset());
+	return 1;
+}
+
+int LuaCamera::l_get_look_dir(lua_State *L)
+{
+	LocalPlayer *player = getClient(L)->getEnv().getLocalPlayer();
+	sanity_check(player);
+
+	float pitch = -1.0 * player->getPitch() * core::DEGTORAD;
+	float yaw = (player->getYaw() + 90.) * core::DEGTORAD;
+	v3f v(cos(pitch) * cos(yaw), sin(pitch), cos(pitch) * sin(yaw));
+
+	push_v3f(L, v);
+	return 1;
+}
+
+int LuaCamera::l_get_look_horizontal(lua_State *L)
+{
+	LocalPlayer *player = getClient(L)->getEnv().getLocalPlayer();
+	sanity_check(player);
+
+	lua_pushnumber(L, (player->getYaw() + 90.) * core::DEGTORAD);
+	return 1;
+}
+
+int LuaCamera::l_get_look_vertical(lua_State *L)
+{
+	LocalPlayer *player = getClient(L)->getEnv().getLocalPlayer();
+	sanity_check(player);
+
+	lua_pushnumber(L, -1.0 * player->getPitch() * core::DEGTORAD);
+	return 1;
+}
+
+int LuaCamera::l_get_aspect_ratio(lua_State *L)
+{
+	Camera *camera = getobject(L, 1);
+	if (!camera)
+		return 0;
+	
+	lua_pushnumber(L, camera->getCameraNode()->getAspectRatio());
+	return 1;
+}
+
+LuaCamera *LuaCamera::checkobject(lua_State *L, int narg)
+{
+	luaL_checktype(L, narg, LUA_TUSERDATA);
+	
+	void *ud = luaL_checkudata(L, narg, className);
+	if (!ud)
+		luaL_typerror(L, narg, className);
+	
+	return *(LuaCamera **) ud;
+}
+
+Camera *LuaCamera::getobject(LuaCamera *ref)
+{
+	return ref->m_camera;
+}
+
+Camera *LuaCamera::getobject(lua_State *L, int narg)
+{
+	LuaCamera *ref = checkobject(L, narg);
+	assert(ref);
+	Camera *camera = getobject(ref);
+	if (!camera)
+		return NULL;
+	return camera;
+}
+
+int LuaCamera::gc_object(lua_State *L)
+{
+	LuaCamera *o = *(LuaCamera **) (lua_touserdata(L, 1));
+	delete o;
+	return 0;
+}
+
+void LuaCamera::Register(lua_State *L)
+{
+	lua_newtable(L);
+	int methodtable = lua_gettop(L);
+	luaL_newmetatable(L, className);
+	int metatable = lua_gettop(L);
+	
+	lua_pushliteral(L, "__metatable");
+	lua_pushvalue(L, methodtable);
+	lua_settable(L, metatable);
+	
+	lua_pushliteral(L, "__index");
+	lua_pushvalue(L, methodtable);
+	lua_settable(L, metatable);
+	
+	lua_pushliteral(L, "__gc");
+	lua_pushcfunction(L, gc_object);
+	lua_settable(L, metatable);
+	
+	lua_pop(L, 1);
+	
+	luaL_openlib(L, 0, methods, 0);
+	lua_pop(L, 1);
+}
+
+const char LuaCamera::className[] = "Camera";
+const luaL_reg LuaCamera::methods[] = {
+	luamethod(LuaCamera, set_camera_mode),
+	luamethod(LuaCamera, get_camera_mode),
+	luamethod(LuaCamera, get_fov),
+	luamethod(LuaCamera, get_pos),
+	luamethod(LuaCamera, get_offset),
+	luamethod(LuaCamera, get_look_dir),
+	luamethod(LuaCamera, get_look_vertical),
+	luamethod(LuaCamera, get_look_horizontal),
+	luamethod(LuaCamera, get_aspect_ratio),
+	
+	{0, 0}
+};
diff --git a/src/script/lua_api/l_camera.h b/src/script/lua_api/l_camera.h
new file mode 100644
index 000000000..82ab6a47f
--- /dev/null
+++ b/src/script/lua_api/l_camera.h
@@ -0,0 +1,46 @@
+#ifndef L_CAMERA_H
+#define L_CAMERA_H
+
+#include "l_base.h"
+
+class Camera;
+
+class LuaCamera : public ModApiBase {
+private: 
+
+	static const char className[];
+	static const luaL_Reg methods[];
+	
+	// garbage collector
+	static int gc_object(lua_State *L);
+	
+	static int l_set_camera_mode(lua_State *L);
+	static int l_get_camera_mode(lua_State *L);
+	
+	static int l_get_fov(lua_State *L);
+	
+	static int l_get_pos(lua_State *L);
+	static int l_get_offset(lua_State *L);
+	static int l_get_look_dir(lua_State *L);
+	static int l_get_look_vertical(lua_State *L);
+	static int l_get_look_horizontal(lua_State *L);
+	static int l_get_aspect_ratio(lua_State *L);
+	
+	Camera *m_camera;
+	
+public:
+
+	LuaCamera(Camera *m);
+	~LuaCamera() {}
+	
+	static void create(lua_State *L, Camera *m);
+	
+	static LuaCamera *checkobject(lua_State *L, int narg);
+	static Camera *getobject(LuaCamera *ref);
+	static Camera *getobject(lua_State *L, int narg);
+	
+	static void Register(lua_State *L);
+	
+};
+
+#endif // L_CAMERA_H
diff --git a/src/script/lua_api/l_localplayer.cpp b/src/script/lua_api/l_localplayer.cpp
index 177df55f3..7ec4eaa62 100644
--- a/src/script/lua_api/l_localplayer.cpp
+++ b/src/script/lua_api/l_localplayer.cpp
@@ -203,34 +203,6 @@ int LuaLocalPlayer::l_get_breath(lua_State *L)
 	return 1;
 }
 
-int LuaLocalPlayer::l_get_look_dir(lua_State *L)
-{
-	LocalPlayer *player = getobject(L, 1);
-
-	float pitch = -1.0 * player->getPitch() * core::DEGTORAD;
-	float yaw = (player->getYaw() + 90.) * core::DEGTORAD;
-	v3f v(cos(pitch) * cos(yaw), sin(pitch), cos(pitch) * sin(yaw));
-
-	push_v3f(L, v);
-	return 1;
-}
-
-int LuaLocalPlayer::l_get_look_horizontal(lua_State *L)
-{
-	LocalPlayer *player = getobject(L, 1);
-
-	lua_pushnumber(L, (player->getYaw() + 90.) * core::DEGTORAD);
-	return 1;
-}
-
-int LuaLocalPlayer::l_get_look_vertical(lua_State *L)
-{
-	LocalPlayer *player = getobject(L, 1);
-
-	lua_pushnumber(L, -1.0 * player->getPitch() * core::DEGTORAD);
-	return 1;
-}
-
 int LuaLocalPlayer::l_get_pos(lua_State *L)
 {
 	LocalPlayer *player = getobject(L, 1);
@@ -239,22 +211,6 @@ int LuaLocalPlayer::l_get_pos(lua_State *L)
 	return 1;
 }
 
-int LuaLocalPlayer::l_get_eye_pos(lua_State *L)
-{
-	LocalPlayer *player = getobject(L, 1);
-
-	push_v3f(L, player->getEyePosition());
-	return 1;
-}
-
-int LuaLocalPlayer::l_get_eye_offset(lua_State *L)
-{
-	LocalPlayer *player = getobject(L, 1);
-
-	push_v3f(L, player->getEyeOffset());
-	return 1;
-}
-
 int LuaLocalPlayer::l_get_movement_acceleration(lua_State *L)
 {
 	LocalPlayer *player = getobject(L, 1);
@@ -393,12 +349,7 @@ const luaL_Reg LuaLocalPlayer::methods[] = {
 		luamethod(LuaLocalPlayer, get_last_look_vertical),
 		luamethod(LuaLocalPlayer, get_key_pressed),
 		luamethod(LuaLocalPlayer, get_breath),
-		luamethod(LuaLocalPlayer, get_look_dir),
-		luamethod(LuaLocalPlayer, get_look_horizontal),
-		luamethod(LuaLocalPlayer, get_look_vertical),
 		luamethod(LuaLocalPlayer, get_pos),
-		luamethod(LuaLocalPlayer, get_eye_pos),
-		luamethod(LuaLocalPlayer, get_eye_offset),
 		luamethod(LuaLocalPlayer, get_movement_acceleration),
 		luamethod(LuaLocalPlayer, get_movement_speed),
 		luamethod(LuaLocalPlayer, get_movement),
diff --git a/src/script/lua_api/l_localplayer.h b/src/script/lua_api/l_localplayer.h
index 6ec3f4c09..e56ec808f 100644
--- a/src/script/lua_api/l_localplayer.h
+++ b/src/script/lua_api/l_localplayer.h
@@ -59,15 +59,8 @@ private:
 
 	static int l_get_breath(lua_State *L);
 
-	static int l_get_look_dir(lua_State *L);
-	static int l_get_look_horizontal(lua_State *L);
-	static int l_get_look_vertical(lua_State *L);
-
 	static int l_get_pos(lua_State *L);
 
-	static int l_get_eye_pos(lua_State *L);
-	static int l_get_eye_offset(lua_State *L);
-
 	static int l_get_movement_acceleration(lua_State *L);
 
 	static int l_get_movement_speed(lua_State *L);
diff --git a/src/script/scripting_client.cpp b/src/script/scripting_client.cpp
index 8ff5abcc4..b5a5085be 100644
--- a/src/script/scripting_client.cpp
+++ b/src/script/scripting_client.cpp
@@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "lua_api/l_item.h"
 #include "lua_api/l_nodemeta.h"
 #include "lua_api/l_localplayer.h"
+#include "lua_api/l_camera.h"
 
 ClientScripting::ClientScripting(Client *client):
 	ScriptApiBase()
@@ -71,6 +72,7 @@ void ClientScripting::InitializeModApi(lua_State *L, int top)
 	LuaMinimap::Register(L);
 	NodeMetaRef::RegisterClient(L);
 	LuaLocalPlayer::Register(L);
+	LuaCamera::Register(L);
 }
 
 void ClientScripting::on_client_ready(LocalPlayer *localplayer)
@@ -78,3 +80,8 @@ void ClientScripting::on_client_ready(LocalPlayer *localplayer)
 	lua_State *L = getStack();
 	LuaLocalPlayer::create(L, localplayer);
 }
+
+void ClientScripting::on_camera_ready(Camera *camera)
+{
+	LuaCamera::create(getStack(), camera);
+}
diff --git a/src/script/scripting_client.h b/src/script/scripting_client.h
index b3451e409..c13fde607 100644
--- a/src/script/scripting_client.h
+++ b/src/script/scripting_client.h
@@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 class Client;
 class LocalPlayer;
+class Camera;
 class ClientScripting:
 	virtual public ScriptApiBase,
 	public ScriptApiSecurity,
@@ -36,6 +37,7 @@ class ClientScripting:
 public:
 	ClientScripting(Client *client);
 	void on_client_ready(LocalPlayer *localplayer);
+	void on_camera_ready(Camera *camera);
 
 private:
 	virtual void InitializeModApi(lua_State *L, int top);
-- 
2.25.1