Sfoglia il codice sorgente

Some work on the FPS sample

Panagiotis Christopoulos Charitos 2 mesi fa
parent
commit
4db1a801b1

+ 17 - 0
AnKi/Math/Euler.h

@@ -145,10 +145,27 @@ public:
 
 	/// @name Other
 	/// @{
+
+	/// Return lerp(this, v1, t)
+	[[nodiscard]] TEuler lerp(const TEuler& v1, T t) const
+	{
+		TEuler out;
+		for(U i = 0; i < 3; ++i)
+		{
+			out[i] = m_arr[i] * (T(1) - t) + v1.m_arr[i] * t;
+		}
+		return out;
+	}
+
 	String toString() const requires(std::is_floating_point<T>::value)
 	{
 		return String().sprintf("%f %f %f", m_vec.m_x, m_vec.m_y, m_vec.m_z);
 	}
+
+	String toStringDegrees() const requires(std::is_floating_point<T>::value)
+	{
+		return String().sprintf("%f %f %f", toDegrees(m_vec.m_x), toDegrees(m_vec.m_y), toDegrees(m_vec.m_z));
+	}
 	/// @}
 
 private:

+ 39 - 35
AnKi/Math/Quat.h

@@ -58,43 +58,42 @@ public:
 	{
 	}
 
-	explicit TQuat(const TMat<T, 3, 3>& m3)
+	/// http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
+	explicit TQuat(const TMat<T, 3, 3>& m)
 	{
-		const T trace = m3(0, 0) + m3(1, 1) + m3(2, 2) + T(1.0);
-		if(trace > kEpsilonf)
+		const T tr = m(0, 0) + m(1, 1) + m(2, 2);
+
+		if(tr > T(0))
+		{
+			const T S = sqrt<T>(tr + T(1)) * T(2); // S=4*qw
+			w() = T(0.25) * S;
+			x() = (m(2, 1) - m(1, 2)) / S;
+			y() = (m(0, 2) - m(2, 0)) / S;
+			z() = (m(1, 0) - m(0, 1)) / S;
+		}
+		else if((m(0, 0) > m(1, 1)) & (m(0, 0) > m(2, 2)))
+		{
+			const T S = sqrt<T>(T(1) + m(0, 0) - m(1, 1) - m(2, 2)) * T(2); // S=4*qx
+			w() = (m(2, 1) - m(1, 2)) / S;
+			x() = T(0.25) * S;
+			y() = (m(0, 1) + m(1, 0)) / S;
+			z() = (m(0, 2) + m(2, 0)) / S;
+		}
+		else if(m(1, 1) > m(2, 2))
 		{
-			T s = T(0.5) / sqrt<T>(trace);
-			w() = T(0.25) / s;
-			x() = (m3(2, 1) - m3(1, 2)) * s;
-			y() = (m3(0, 2) - m3(2, 0)) * s;
-			z() = (m3(1, 0) - m3(0, 1)) * s;
+			const T S = sqrt<T>(T(1) + m(1, 1) - m(0, 0) - m(2, 2)) * T(2); // S=4*qy
+			w() = (m(0, 2) - m(2, 0)) / S;
+			x() = (m(0, 1) + m(1, 0)) / S;
+			y() = T(0.25) * S;
+			z() = (m(1, 2) + m(2, 1)) / S;
 		}
 		else
 		{
-			if(m3(0, 0) > m3(1, 1) && m3(0, 0) > m3(2, 2))
-			{
-				T s = T(0.5) / sqrt<T>(T(1.0) + m3(0, 0) - m3(1, 1) - m3(2, 2));
-				w() = (m3(1, 2) - m3(2, 1)) * s;
-				x() = T(0.25) / s;
-				y() = (m3(0, 1) + m3(1, 0)) * s;
-				z() = (m3(0, 2) + m3(2, 0)) * s;
-			}
-			else if(m3(1, 1) > m3(2, 2))
-			{
-				T s = T(0.5) / sqrt<T>(T(1.0) + m3(1, 1) - m3(0, 0) - m3(2, 2));
-				w() = (m3(0, 2) - m3(2, 0)) * s;
-				x() = (m3(0, 1) + m3(1, 0)) * s;
-				y() = T(0.25) / s;
-				z() = (m3(1, 2) + m3(2, 1)) * s;
-			}
-			else
-			{
-				T s = T(0.5) / sqrt<T>(T(1.0) + m3(2, 2) - m3(0, 0) - m3(1, 1));
-				w() = (m3(0, 1) - m3(1, 0)) * s;
-				x() = (m3(0, 2) + m3(2, 0)) * s;
-				y() = (m3(1, 2) + m3(2, 1)) * s;
-				z() = T(0.25) / s;
-			}
+			const T S = sqrt<T>(T(1) + m(2, 2) - m(0, 0) - m(1, 1)) * T(2); // S=4*qz
+			w() = (m(1, 0) - m(0, 1)) / S;
+			x() = (m(0, 2) + m(2, 0)) / S;
+			y() = (m(1, 2) + m(2, 1)) / S;
+			z() = T(0.25) * S;
 		}
 	}
 
@@ -107,13 +106,13 @@ public:
 	explicit TQuat(const TEuler<T>& eu)
 	{
 		T cx, sx;
-		sinCos(eu.y() * 0.5, sx, cx);
+		sinCos(eu.y() * T(0.5), sx, cx);
 
 		T cy, sy;
-		sinCos(eu.z() * 0.5, sy, cy);
+		sinCos(eu.z() * T(0.5), sy, cy);
 
 		T cz, sz;
-		sinCos(eu.x() * 0.5, sz, cz);
+		sinCos(eu.x() * T(0.5), sz, cz);
 
 		const T cxcy = cx * cy;
 		const T sxsy = sx * sy;
@@ -427,6 +426,11 @@ public:
 	{
 		return 4;
 	}
+
+	String toString() const requires(std::is_floating_point<T>::value)
+	{
+		return m_value.toString();
+	}
 	/// @}
 
 private:

+ 16 - 8
AnKi/Scene/SceneGraph.cpp

@@ -156,7 +156,7 @@ void SceneGraph::update(Second prevUpdateTime, Second crntTime)
 		SceneNode* node = m_nodesForRegistration.popFront();
 
 		// Add to dict if it has a name
-		if(node->getName())
+		if(node->getName() != "Unnamed")
 		{
 			if(tryFindSceneNode(node->getName()))
 			{
@@ -216,7 +216,7 @@ void SceneGraph::update(Second prevUpdateTime, Second crntTime)
 		}
 
 		// Remove from dict
-		if(node->getName())
+		if(node->getName() != "Unnamed")
 		{
 			auto it = m_nodesDict.find(node->getName());
 			ANKI_ASSERT(it != m_nodesDict.getEnd());
@@ -258,12 +258,6 @@ void SceneGraph::updateNode(Second prevTime, Second crntTime, SceneNode& node)
 		}
 	});
 
-	// Update children
-	node.visitChildrenMaxDepth(0, [&](SceneNode& child) {
-		updateNode(prevTime, crntTime, child);
-		return true;
-	});
-
 	// Frame update
 	{
 		if(sceneComponentUpdatedCount)
@@ -279,6 +273,12 @@ void SceneGraph::updateNode(Second prevTime, Second crntTime, SceneNode& node)
 
 		node.frameUpdate(prevTime, crntTime);
 	}
+
+	// Update children
+	node.visitChildrenMaxDepth(0, [&](SceneNode& child) {
+		updateNode(prevTime, crntTime, child);
+		return true;
+	});
 }
 
 void SceneGraph::updateNodes(UpdateSceneNodesCtx& ctx)
@@ -347,4 +347,12 @@ LightComponent* SceneGraph::getDirectionalLight() const
 	return out;
 }
 
+void SceneGraph::setActiveCameraNode(SceneNode* cam)
+{
+	if(ANKI_EXPECT(cam->hasComponent<CameraComponent>()))
+	{
+		m_mainCam = cam;
+	}
+}
+
 } // end namespace anki

+ 3 - 4
AnKi/Scene/SceneGraph.h

@@ -73,14 +73,13 @@ public:
 		ANKI_ASSERT(m_mainCam != nullptr);
 		return *m_mainCam;
 	}
+
 	const SceneNode& getActiveCameraNode() const
 	{
 		return *m_mainCam;
 	}
-	void setActiveCameraNode(SceneNode* cam)
-	{
-		m_mainCam = cam;
-	}
+
+	void setActiveCameraNode(SceneNode* cam);
 
 	U32 getSceneNodesCount() const
 	{

+ 1 - 1
AnKi/Scene/SceneNode.h

@@ -34,7 +34,7 @@ public:
 	/// Return the name. It may be empty for nodes that we don't want to track.
 	CString getName() const
 	{
-		return (!m_name.isEmpty()) ? m_name.toCString() : CString();
+		return (!m_name.isEmpty()) ? m_name.toCString() : "Unnamed";
 	}
 
 	U32 getUuid() const

+ 1 - 0
AnKi/Script/Common.h

@@ -18,6 +18,7 @@ class ScriptEnvironment;
 #define ANKI_SCRIPT_LOGE(...) ANKI_LOG("SCRI", kError, __VA_ARGS__)
 #define ANKI_SCRIPT_LOGW(...) ANKI_LOG("SCRI", kWarning, __VA_ARGS__)
 #define ANKI_SCRIPT_LOGF(...) ANKI_LOG("SCRI", kFatal, __VA_ARGS__)
+#define ANKI_SCRIPT_LOGV(...) ANKI_LOG("SCRI", kVerbose, __VA_ARGS__)
 
 class ScriptMemoryPool : public HeapMemoryPool, public MakeSingleton<ScriptMemoryPool>
 {

+ 39 - 0
AnKi/Script/Logger.cpp

@@ -123,12 +123,51 @@ static int wraplogw(lua_State* l)
 	return 0;
 }
 
+/// Pre-wrap function logv.
+static inline int pwraplogv(lua_State* l)
+{
+	[[maybe_unused]] LuaUserData* ud;
+	[[maybe_unused]] void* voidp;
+	[[maybe_unused]] PtrSize size;
+
+	if(LuaBinder::checkArgsCount(l, 1)) [[unlikely]]
+	{
+		return -1;
+	}
+
+	// Pop arguments
+	const char* arg0;
+	if(LuaBinder::checkString(l, 1, arg0)) [[unlikely]]
+	{
+		return -1;
+	}
+
+	// Call the function
+	ANKI_SCRIPT_LOGV("%s", arg0);
+
+	return 0;
+}
+
+/// Wrap function logv.
+static int wraplogv(lua_State* l)
+{
+	int res = pwraplogv(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
 /// Wrap the module.
 void wrapModuleLogger(lua_State* l)
 {
 	LuaBinder::pushLuaCFunc(l, "logi", wraplogi);
 	LuaBinder::pushLuaCFunc(l, "loge", wraploge);
 	LuaBinder::pushLuaCFunc(l, "logw", wraplogw);
+	LuaBinder::pushLuaCFunc(l, "logv", wraplogv);
 }
 
 } // end namespace anki

+ 6 - 0
AnKi/Script/Logger.xml

@@ -28,6 +28,12 @@ namespace anki {]]></head>
 				<arg>const char*</arg>
 			</args>
 		</function>
+		<function name="logv">
+			<overrideCall>ANKI_SCRIPT_LOGV("%s", arg0);</overrideCall>
+			<args>
+				<arg>const char*</arg>
+			</args>
+		</function>
 	</functions>
 	<tail><![CDATA[} // end namespace anki]]></tail>
 </glue>

+ 4 - 3
AnKi/Shaders/VolumetricLightingAccumulation.ankiprog

@@ -196,9 +196,10 @@ Vec4 accumulateLightsAndFog(Cluster cluster, Vec3 worldPos, F32 negativeZViewSpa
 			else
 			{
 				const Vec3 diff = worldPos - vol.m_aabbMinOrSphereCenter;
-				F32 distSq = dot(diff, diff) / (vol.m_aabbMaxOrSphereRadius.x * vol.m_aabbMaxOrSphereRadius.x);
-				distSq = min(1.0, distSq);
-				factor = 1.0 - distSq;
+				factor = dot(diff, diff) / (vol.m_aabbMaxOrSphereRadius.x * vol.m_aabbMaxOrSphereRadius.x);
+				factor = 1.0 - factor;
+				factor = max(0.0, factor);
+				factor *= factor;
 			}
 
 			fogDensity += vol.m_density * factor;

+ 9 - 0
AnKi/Util/StdTypes.h

@@ -329,6 +329,15 @@ inline constexpr F32 operator""_mm(long double x)
 }
 /// @}
 
+/// @name Other user literals
+/// @{
+inline constexpr F32 operator""_degrees(long double x)
+{
+	constexpr F32 kPi = 3.14159265358979323846f;
+	return F32(x) * (kPi / 180.0f);
+}
+/// @}
+
 /// Convenience macro that defines the type of a class.
 #define ANKI_DEFINE_CLASS_SELF(selfType) \
 	typedef auto _selfFn##selfType()->decltype(*this); \

+ 10 - 0
AnKi/Window/InputSdl.cpp

@@ -113,6 +113,11 @@ void Input::hideCursor(Bool hide)
 		{
 			ANKI_WIND_LOGE("SDL_HideCursor() failed: %s", SDL_GetError());
 		}
+
+		if(!SDL_SetWindowRelativeMouseMode(static_cast<NativeWindowSdl&>(NativeWindow::getSingleton()).m_sdlWindow, true))
+		{
+			ANKI_WIND_LOGE("SDL_SetWindowRelativeMouseMode() failed: %s", SDL_GetError());
+		}
 	}
 	else
 	{
@@ -120,6 +125,11 @@ void Input::hideCursor(Bool hide)
 		{
 			ANKI_WIND_LOGE("SDL_ShowCursor() failed: %s", SDL_GetError());
 		}
+
+		if(!SDL_SetWindowRelativeMouseMode(static_cast<NativeWindowSdl&>(NativeWindow::getSingleton()).m_sdlWindow, false))
+		{
+			ANKI_WIND_LOGE("SDL_SetWindowRelativeMouseMode() failed: %s", SDL_GetError());
+		}
 	}
 }
 

+ 1 - 1
README.md

@@ -76,7 +76,7 @@ To build the release version open `PowerShell` and type:
 	$cd path/to/anki
 	$mkdir build
 	$cd build
-	$cmake .. -G "Visual Studio 17 2022 Win64" -DCMAKE_BUILD_TYPE=Release
+	$cmake .. -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE=Release
 	$cmake --build . --config Release
 
 Alternatively, recent Visual Studio versions support building CMake projects from inside the IDE:

+ 1 - 1
Samples/PhysicsPlayground/CMakeLists.txt

@@ -1,2 +1,2 @@
-anki_new_executable(PhysicsPlayground Main.cpp ../Common/SampleApp.cpp)
+anki_new_executable(PhysicsPlayground Main.cpp ../Common/SampleApp.cpp FpsCharacterNode.cpp)
 target_link_libraries(PhysicsPlayground AnKi)

+ 26 - 0
Samples/PhysicsPlayground/Events.h

@@ -0,0 +1,26 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/AnKi.h>
+
+using namespace anki;
+
+inline void createDestructionEvent(SceneNode* node, Second timeToKill)
+{
+	constexpr CString script = R"(
+function update(event, prevTime, crntTime)
+	-- Do nothing
+end
+
+function onKilled(event, prevTime, crntTime)
+	logi(string.format("Will kill %s", event:getAssociatedSceneNodes():getAt(0):getName()))
+	event:getAssociatedSceneNodes():getAt(0):markForDeletion()
+end
+	)";
+	ScriptEvent* event = SceneGraph::getSingleton().getEventManager().newEvent<ScriptEvent>(-1.0, timeToKill, script);
+	event->addAssociatedSceneNode(node);
+}

+ 0 - 111
Samples/PhysicsPlayground/FpsCharacter.h

@@ -1,111 +0,0 @@
-// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#include <AnKi/AnKi.h>
-
-using namespace anki;
-
-class FpsCharacter : public SceneNode
-{
-public:
-	F32 m_mouseLookPower = toRad(7.0f);
-	F32 m_walkingSpeed = 8.5f;
-	F32 m_jumpSpeed = 8.0f;
-	Bool m_crouching = false;
-
-	FpsCharacter()
-		: SceneNode("FpsCharacter")
-	{
-		PlayerControllerComponent* playerc = newComponent<PlayerControllerComponent>();
-
-		SceneNode* cam = SceneGraph::getSingleton().newSceneNode<SceneNode>("FpsCharacterCam");
-		cam->setLocalTransform(Transform(Vec3(0.0f, 2.0f, 0.0f), Mat3::getIdentity(), Vec3(1.0f)));
-		addChild(cam);
-
-		SceneNode* shotgun = SceneGraph::getSingleton().newSceneNode<SceneNode>("Shotgun");
-		shotgun->setLocalTransform(Transform(Vec3(0.065f, -0.13f, -0.4f), Mat3(Euler(0.0f, kPi, 0.0f)), Vec3(1.0f)));
-		shotgun->newComponent<MeshComponent>()->setMeshFilename("sleevegloveLOW.001_893660395596b206.ankimesh");
-		shotgun->newComponent<MaterialComponent>()->setMaterialFilename("arms_3a4232ebbd425e7a.ankimtl").setSubmeshIndex(0);
-		shotgun->newComponent<MaterialComponent>()->setMaterialFilename("boomstick_89a614a521ace7fd.ankimtl").setSubmeshIndex(1);
-		cam->addChild(shotgun);
-	}
-
-	void frameUpdate([[maybe_unused]] Second prevUpdateTime, [[maybe_unused]] Second crntTime) override
-	{
-		PlayerControllerComponent& playerc = getFirstComponentOfType<PlayerControllerComponent>();
-
-		// Rotate
-		F32 y = Input::getSingleton().getMousePosition().y();
-		F32 x = Input::getSingleton().getMousePosition().x();
-		if(y != 0.0 || x != 0.0)
-		{
-			// Set rotation
-			Mat3 rot(Euler(m_mouseLookPower * y, m_mouseLookPower * x, 0.0f));
-
-			rot = getLocalRotation() * rot;
-
-			const Vec3 newz = rot.getColumn(2).normalize();
-			const Vec3 newx = Vec3(0.0, 1.0, 0.0).cross(newz);
-			const Vec3 newy = newz.cross(newx);
-			rot.setColumns(newx, newy, newz);
-			rot = rot.reorthogonalize();
-
-			// Update move
-			setLocalRotation(rot);
-		}
-
-		Vec3 moveVec(0.0);
-		if(Input::getSingleton().getKey(KeyCode::kW))
-		{
-			moveVec.z() += 1.0f;
-		}
-
-		if(Input::getSingleton().getKey(KeyCode::kA))
-		{
-			moveVec.x() += 1.0f;
-		}
-
-		if(Input::getSingleton().getKey(KeyCode::kS))
-		{
-			moveVec.z() -= 1.0f;
-		}
-
-		if(Input::getSingleton().getKey(KeyCode::kD))
-		{
-			moveVec.x() -= 1.0f;
-		}
-
-		F32 jumpSpeed = 0.0f;
-		if(Input::getSingleton().getKey(KeyCode::kSpace))
-		{
-			jumpSpeed += m_jumpSpeed;
-		}
-
-		Bool crouchChanged = false;
-		if(Input::getSingleton().getKey(KeyCode::kC))
-		{
-			m_crouching = !m_crouching;
-			crouchChanged = true;
-		}
-
-		if(moveVec != 0.0f || jumpSpeed != 0.0f || crouchChanged)
-		{
-			Vec3 dir;
-			if(moveVec != 0.0f)
-			{
-				dir = -(getLocalRotation() * moveVec);
-				dir.y() = 0.0f;
-				dir = dir.normalize();
-			}
-
-			F32 speed = m_walkingSpeed;
-			if(Input::getSingleton().getKey(KeyCode::kLeftShift))
-			{
-				speed *= 2.0f;
-			}
-			playerc.setVelocity(speed, jumpSpeed, dir, m_crouching);
-		}
-	}
-};

+ 276 - 0
Samples/PhysicsPlayground/FpsCharacterNode.cpp

@@ -0,0 +1,276 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <Samples/PhysicsPlayground/FpsCharacterNode.h>
+#include <Samples/PhysicsPlayground/GrenadeNode.h>
+#include <Samples/PhysicsPlayground/Events.h>
+
+static void createFogVolumeFadeEvent(SceneNode* node)
+{
+	CString script = R"(
+density = 15
+radius = 3.5
+
+function update(event, prevTime, crntTime)
+	node = event:getAssociatedSceneNodes():getAt(0)
+	-- logi(string.format("Will fade fog for %s", node:getName()))
+	fogComponent = node:getFirstFogDensityComponent()
+
+	dt = crntTime - prevTime
+	density = density - 4.0 * dt
+	radius = radius + 0.5 * dt
+
+	pos = node:getLocalOrigin()
+	pos:setY(pos:getY() - 1.1 * dt)
+	node:setLocalOrigin(pos)
+
+	if density <= 0.0 or radius <= 0.0 then
+		node:markForDeletion()
+	else
+		node:setLocalScale(Vec3.new(radius))
+		fogComponent:setDensity(density)
+	end
+end
+
+function onKilled(event, prevTime, crntTime)
+	-- Nothing
+end
+	)";
+	ScriptEvent* event = SceneGraph::getSingleton().getEventManager().newEvent<ScriptEvent>(-1, 10.0, script);
+	event->addAssociatedSceneNode(node);
+}
+
+FpsCharacter::FpsCharacter(CString name)
+	: SceneNode(name)
+{
+	newComponent<PlayerControllerComponent>();
+
+	SceneNode* cam = SceneGraph::getSingleton().newSceneNode<SceneNode>("FpsCharacterCam");
+	cam->setLocalTransform(Transform(Vec3(0.0f, 2.0f, 0.0f), Mat3::getIdentity(), Vec3(1.0f)));
+	CameraComponent* camc = cam->newComponent<CameraComponent>();
+	camc->setPerspective(0.1f, 1000.0f, Renderer::getSingleton().getAspectRatio() * toRad<F32>(g_cvarGameFov), toRad<F32>(g_cvarGameFov));
+	addChild(cam);
+	m_cameraNode = cam;
+
+	SceneNode* shotgun = SceneGraph::getSingleton().newSceneNode<SceneNode>("Shotgun");
+	shotgun->setLocalTransform(Transform(m_shotgunRestingPosition, Mat3(m_shotgunRestingRotation), Vec3(1.0f, 1.0f, 0.45f)));
+	shotgun->newComponent<MeshComponent>()->setMeshFilename("Assets/sleevegloveLOW.001_893660395596b206.ankimesh");
+	shotgun->newComponent<MaterialComponent>()->setMaterialFilename("Assets/arms_3a4232ebbd425e7a.ankimtl").setSubmeshIndex(0);
+	shotgun->newComponent<MaterialComponent>()->setMaterialFilename("Assets/boomstick_89a614a521ace7fd.ankimtl").setSubmeshIndex(1);
+	cam->addChild(shotgun);
+	m_shotgunNode = shotgun;
+}
+
+void FpsCharacter::frameUpdate([[maybe_unused]] Second prevUpdateTime, [[maybe_unused]] Second crntTime)
+{
+	// Change FOV
+	CameraComponent& camc = m_cameraNode->getFirstComponentOfType<CameraComponent>();
+	if(toRad<F32>(g_cvarGameFov) != camc.getFovY())
+	{
+		camc.setFovX(Renderer::getSingleton().getAspectRatio() * toRad<F32>(g_cvarGameFov));
+		camc.setFovY(toRad<F32>(g_cvarGameFov));
+	}
+
+	// Mouselook
+	const Input& inp = Input::getSingleton();
+	const Vec2 mousePos = inp.getMousePosition();
+	if(mousePos != 0.0f)
+	{
+		Mat3 camRot = m_cameraNode->getLocalRotation();
+
+		Mat3 newRot(Euler(g_cvarGameMouseLookPower * mousePos.y(), g_cvarGameMouseLookPower * -mousePos.x(), 0.0f));
+		newRot = camRot * newRot;
+
+		const Vec3 newz = newRot.getColumn(2).normalize();
+		const Vec3 newx = Vec3(0.0, 1.0, 0.0).cross(newz);
+		const Vec3 newy = newz.cross(newx);
+		newRot.setColumns(newx, newy, newz);
+		newRot = newRot.reorthogonalize();
+
+		m_cameraNode->setLocalRotation(newRot);
+	}
+
+	// Movement
+	{
+		Vec3 moveVec(0.0);
+		if(inp.getKey(KeyCode::kW))
+		{
+			moveVec.z() += 1.0f;
+		}
+
+		if(inp.getKey(KeyCode::kA))
+		{
+			moveVec.x() += 1.0f;
+		}
+
+		if(inp.getKey(KeyCode::kS))
+		{
+			moveVec.z() -= 1.0f;
+		}
+
+		if(inp.getKey(KeyCode::kD))
+		{
+			moveVec.x() -= 1.0f;
+		}
+
+		F32 jumpSpeed = 0.0f;
+		if(inp.getKey(KeyCode::kSpace))
+		{
+			jumpSpeed += m_jumpSpeed;
+		}
+
+		Bool crouchChanged = false;
+		if(inp.getKey(KeyCode::kC))
+		{
+			m_crouching = !m_crouching;
+			crouchChanged = true;
+		}
+
+		if(moveVec != 0.0f || jumpSpeed != 0.0f || crouchChanged)
+		{
+			Vec3 dir;
+			if(moveVec != 0.0f)
+			{
+				dir = -(m_cameraNode->getLocalRotation() * moveVec);
+				dir.y() = 0.0f;
+				dir = dir.normalize();
+			}
+
+			F32 speed = m_walkingSpeed;
+			if(inp.getKey(KeyCode::kLeftShift))
+			{
+				speed *= 2.0f;
+			}
+
+			PlayerControllerComponent& playerc = getFirstComponentOfType<PlayerControllerComponent>();
+			playerc.setVelocity(speed, jumpSpeed, dir, m_crouching);
+		}
+	}
+
+	// Animate gun back to resting position
+	if(m_shotgunNode->getLocalOrigin() != m_shotgunRestingPosition)
+	{
+		const Vec3 gunPos = m_shotgunNode->getLocalOrigin();
+
+		Vec3 travelDir = m_shotgunRestingPosition - gunPos;
+		const F32 remainlingDist = travelDir.length();
+		travelDir = travelDir.normalize();
+
+		const F32 speed = getRandomRange(0.2f, 0.4f);
+		const F32 dist = speed * F32(crntTime - prevUpdateTime);
+
+		if(dist >= remainlingDist)
+		{
+			m_shotgunNode->setLocalOrigin(m_shotgunRestingPosition);
+			m_shotgunNode->setLocalRotation(Mat3(m_shotgunRestingRotation));
+		}
+		else
+		{
+			m_shotgunNode->setLocalOrigin(travelDir * dist + gunPos);
+
+			const F32 distFactor = dist / remainlingDist;
+			const Quat crntAngle(m_shotgunNode->getLocalRotation());
+			const Quat restingAngle(m_shotgunRestingRotation);
+			const Quat newAngle = crntAngle.slerp(restingAngle, distFactor);
+			m_shotgunNode->setLocalRotation(Mat3(newAngle));
+		}
+	}
+
+	// Shooting
+	if(inp.getMouseButton(MouseButton::kLeft) == 1)
+	{
+		fireShotgun();
+
+		const Vec3 newPosition(0.0f, getRandomRange(-0.03f, -0.05f), 0.15f);
+		m_shotgunNode->setLocalOrigin(m_shotgunRestingPosition + newPosition);
+		const Euler newRotation(getRandomRange(0.0_degrees, 10.0_degrees), getRandomRange(-10.0_degrees, 1.0_degrees), 0.0f);
+		m_shotgunNode->setLocalRotation(Mat3(newRotation) * Mat3(m_shotgunRestingRotation));
+	}
+
+	if(inp.getMouseButton(MouseButton::kRight) == 1)
+	{
+		fireGrenade();
+	}
+}
+
+void FpsCharacter::fireShotgun()
+{
+	for(U32 i = 0; i < 8; ++i)
+	{
+		F32 spreadAngle = getRandomRange(-m_shotgunSpreadAngle / 2.0f, m_shotgunSpreadAngle / 2.0f);
+		Mat3 randDirection(Axisang(spreadAngle, Vec3(1.0f, 0.0f, 0.0f)));
+
+		spreadAngle = getRandomRange(-m_shotgunSpreadAngle / 2.0f, m_shotgunSpreadAngle / 2.0f);
+		randDirection = randDirection * Mat3(Axisang(spreadAngle, Vec3(0.0f, 1.0f, 0.0f)));
+		randDirection = m_cameraNode->getLocalRotation().getRotationPart() * randDirection;
+
+		const Vec3 from = m_cameraNode->getWorldTransform().getOrigin().xyz();
+		const Vec3 to = from + -randDirection.getZAxis() * m_shotgunMaxLength;
+
+		RayHitResult result;
+		const Bool hit = PhysicsWorld::getSingleton().castRayClosestHit(from, to, PhysicsLayerBit::kStatic | PhysicsLayerBit::kMoving, result);
+
+		if(hit && result.m_object->getType() == PhysicsObjectType::kBody)
+		{
+			PhysicsBody& body = static_cast<PhysicsBody&>(*result.m_object);
+			const Bool isStatic = body.getMass() == 0.0f;
+
+			if(isStatic)
+			{
+				// Create rotation
+				const Vec3& zAxis = result.m_normal;
+				Vec3 yAxis = Vec3(0, 1, 0.5);
+				Vec3 xAxis = yAxis.cross(zAxis).normalize();
+				yAxis = zAxis.cross(xAxis);
+
+				Mat3 rot;
+				rot.setXAxis(xAxis);
+				rot.setYAxis(yAxis);
+				rot.setZAxis(zAxis);
+
+				const Transform trf(result.m_hitPosition, rot, Vec3(1.0f, 1.0f, 1.0f));
+
+				// Create an obj
+				SceneNode* bulletDecal = SceneGraph::getSingleton().newSceneNode<SceneNode>("");
+				bulletDecal->setLocalTransform(trf);
+				bulletDecal->setLocalScale(Vec3(0.1f, 0.1f, 0.3f));
+				DecalComponent* decalc = bulletDecal->newComponent<DecalComponent>();
+				decalc->loadDiffuseImageResource("Assets/bullet_hole_decal.ankitex", 1.0f);
+
+				createDestructionEvent(bulletDecal, 10.0_sec);
+			}
+			else
+			{
+				const Vec3 force = -result.m_normal * m_shotgunForce;
+				body.applyForce(force);
+			}
+
+			if(i == 0)
+			{
+				SceneNode* fogNode = SceneGraph::getSingleton().newSceneNode<SceneNode>("");
+				FogDensityComponent* fogComp = fogNode->newComponent<FogDensityComponent>();
+				fogNode->setLocalScale(Vec3(2.1f));
+				fogComp->setDensity(15.0f);
+
+				fogNode->setLocalOrigin(result.m_hitPosition);
+
+				createDestructionEvent(fogNode, 10.0_sec);
+				createFogVolumeFadeEvent(fogNode);
+			}
+		}
+	}
+}
+
+void FpsCharacter::fireGrenade()
+{
+	Transform camTrf = m_cameraNode->getWorldTransform();
+	const Vec3 newPos = camTrf.getOrigin().xyz() + camTrf.getRotation().getZAxis() * -3.0f;
+	camTrf.setOrigin(newPos.xyz0());
+
+	SceneNode* grenade = SceneGraph::getSingleton().newSceneNode<GrenadeNode>("");
+	BodyComponent& bodyc = grenade->getFirstComponentOfType<BodyComponent>();
+	bodyc.teleportTo(camTrf.getOrigin().xyz(), camTrf.getRotation().getRotationPart());
+	bodyc.applyForce(camTrf.getRotation().getZAxis().xyz() * -1200.0f, Vec3(0.0f, 0.0f, 0.0f));
+}

+ 41 - 0
Samples/PhysicsPlayground/FpsCharacterNode.h

@@ -0,0 +1,41 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/AnKi.h>
+#include <Samples/PhysicsPlayground/GrenadeNode.h>
+
+using namespace anki;
+
+ANKI_CVAR(NumericCVar<F32>, Game, Fov, 90.0f, 60.0f, 110.0f, "Field of view")
+ANKI_CVAR(NumericCVar<F32>, Game, MouseLookPower, 5.0f, 1.0f, 100.0f, "Mouselook")
+
+class FpsCharacter : public SceneNode
+{
+public:
+	F32 m_walkingSpeed = 8.5f;
+	F32 m_jumpSpeed = 8.0f;
+	Bool m_crouching = false;
+
+	U8 m_shotgunBulletCount = 8;
+	F32 m_shotgunSpreadAngle = 6.0_degrees;
+	F32 m_shotgunMaxLength = 100.0f;
+	F32 m_shotgunForce = 80.0f;
+	Vec3 m_shotgunRestingPosition = Vec3(0.1f, -0.18f, -0.19f);
+	Euler m_shotgunRestingRotation = Euler(0.0f, kPi, 0.0f);
+
+	SceneNode* m_cameraNode = nullptr;
+	SceneNode* m_shotgunNode = nullptr;
+
+	FpsCharacter(CString name);
+
+	void frameUpdate([[maybe_unused]] Second prevUpdateTime, [[maybe_unused]] Second crntTime) override;
+
+private:
+	void fireShotgun();
+
+	void fireGrenade();
+};

+ 30 - 0
Samples/PhysicsPlayground/GrenadeNode.h

@@ -0,0 +1,30 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/AnKi.h>
+#include <Samples/PhysicsPlayground/Events.h>
+
+using namespace anki;
+
+class GrenadeNode : public SceneNode
+{
+public:
+	GrenadeNode(CString name)
+		: SceneNode(name)
+	{
+		setLocalScale(Vec3(2.8f));
+
+		newComponent<MeshComponent>()->setMeshFilename("Assets/MESH_grenade_818651700502e14b.ankimesh");
+		newComponent<MaterialComponent>()->setMaterialFilename("Assets/MTL_grenade_4346150e31bdb957.ankimtl");
+
+		BodyComponent* bodyc = newComponent<BodyComponent>();
+		bodyc->setCollisionShapeType(BodyComponentCollisionShapeType::kFromMeshComponent);
+		bodyc->setMass(1.0f);
+
+		createDestructionEvent(this, 10.0_sec);
+	}
+};

+ 12 - 240
Samples/PhysicsPlayground/Main.cpp

@@ -5,65 +5,10 @@
 
 #include <cstdio>
 #include <Samples/Common/SampleApp.h>
-#include <Samples/PhysicsPlayground/FpsCharacter.h>
+#include <Samples/PhysicsPlayground/FpsCharacterNode.h>
 
 using namespace anki;
 
-static Error createDestructionEvent(SceneNode* node)
-{
-	CString script = R"(
-function update(event, prevTime, crntTime)
-	-- Do nothing
-end
-
-function onKilled(event, prevTime, crntTime)
-	logi(string.format("Will kill %s", event:getAssociatedSceneNodes():getAt(0):getName()))
-	event:getAssociatedSceneNodes():getAt(0):markForDeletion()
-end
-	)";
-	ScriptEvent* event = SceneGraph::getSingleton().getEventManager().newEvent<ScriptEvent>(-1.0f, 10.0f, script);
-	event->addAssociatedSceneNode(node);
-
-	return Error::kNone;
-}
-
-static Error createFogVolumeFadeEvent(SceneNode* node)
-{
-	CString script = R"(
-density = 15
-radius = 3.5
-
-function update(event, prevTime, crntTime)
-	node = event:getAssociatedSceneNodes():getAt(0)
-	-- logi(string.format("Will fade fog for %s", node:getName()))
-	fogComponent = node:getFirstFogDensityComponent()
-
-	dt = crntTime - prevTime
-	density = density - 4.0 * dt
-	radius = radius + 0.5 * dt
-
-	pos = node:getLocalOrigin()
-	pos:setY(pos:getY() - 1.1 * dt)
-	node:setLocalOrigin(pos)
-
-	if density <= 0.0 or radius <= 0.0 then
-		node:markForDeletion()
-	else
-		node:setLocalScale(Vec3.new(radius))
-		fogComponent:setDensity(density)
-	end
-end
-
-function onKilled(event, prevTime, crntTime)
-	-- Nothing
-end
-	)";
-	ScriptEvent* event = SceneGraph::getSingleton().getEventManager().newEvent<ScriptEvent>(-1, 10.0, script);
-	event->addAssociatedSceneNode(node);
-
-	return Error::kNone;
-}
-
 class MyApp : public SampleApp
 {
 public:
@@ -80,7 +25,7 @@ Error MyApp::sampleExtraInit()
 	ANKI_CHECK(ScriptManager::getSingleton().evalString(script->getSource()));
 
 	// Create the player
-	if(1)
+	if(0)
 	{
 		SceneNode& cam = SceneGraph::getSingleton().getActiveCameraNode();
 		cam.setLocalTransform(Transform(Vec4(0.0, 2.0, 0.0, 0.0), Mat3x4::getIdentity(), Vec4(1.0f, 1.0f, 1.0f, 0.0f)));
@@ -96,6 +41,11 @@ Error MyApp::sampleExtraInit()
 		player->addChild(&cam);
 	}
 
+	{
+		FpsCharacter* c = SceneGraph::getSingleton().newSceneNode<FpsCharacter>("FpsCharacter");
+		SceneGraph::getSingleton().setActiveCameraNode(c->m_cameraNode);
+	}
+
 	// Create a body component with hinge joint
 	if(1)
 	{
@@ -194,6 +144,11 @@ Error MyApp::userMainLoop(Bool& quit, [[maybe_unused]] Second elapsedTime)
 	Renderer& renderer = Renderer::getSingleton();
 	Input& in = Input::getSingleton();
 
+	if(in.getKey(KeyCode::kGrave) == 1)
+	{
+		toggleDeveloperConsole();
+	}
+
 	if(Input::getSingleton().getKey(KeyCode::kEscape))
 	{
 		quit = true;
@@ -283,189 +238,6 @@ Error MyApp::userMainLoop(Bool& quit, [[maybe_unused]] Second elapsedTime)
 		renderer.getDbg().setDitheredDepthTestEnabled(false);
 	}
 
-	// Move player
-	{
-		SceneNode& player = SceneGraph::getSingleton().findSceneNode("player");
-		PlayerControllerComponent& playerc = player.getFirstComponentOfType<PlayerControllerComponent>();
-
-		if(Input::getSingleton().getKey(KeyCode::kR))
-		{
-			player.getFirstComponentOfType<PlayerControllerComponent>().moveToPosition(Vec3(0.0f, 2.0f, 0.0f));
-		}
-
-		constexpr F32 ang = toRad(7.0f);
-
-		F32 y = Input::getSingleton().getMousePosition().y();
-		F32 x = Input::getSingleton().getMousePosition().x();
-		if(y != 0.0 || x != 0.0)
-		{
-			// Set rotation
-			Mat3 rot(Euler(ang * y * 11.25f, ang * x * -20.0f, 0.0f));
-
-			rot = player.getLocalRotation() * rot;
-
-			Vec3 newz = rot.getColumn(2).normalize();
-			Vec3 newx = Vec3(0.0, 1.0, 0.0).cross(newz);
-			Vec3 newy = newz.cross(newx);
-			rot.setColumns(newx, newy, newz);
-			rot = rot.reorthogonalize();
-
-			// Update move
-			player.setLocalRotation(rot);
-		}
-
-		const F32 speed = 8.5;
-		Vec3 moveVec(0.0);
-		if(Input::getSingleton().getKey(KeyCode::kW))
-		{
-			moveVec.z() += 1.0f;
-		}
-
-		if(Input::getSingleton().getKey(KeyCode::kA))
-		{
-			moveVec.x() += 1.0f;
-		}
-
-		if(Input::getSingleton().getKey(KeyCode::kS))
-		{
-			moveVec.z() -= 1.0f;
-		}
-
-		if(Input::getSingleton().getKey(KeyCode::kD))
-		{
-			moveVec.x() -= 1.0f;
-		}
-
-		F32 jumpSpeed = 0.0f;
-		if(Input::getSingleton().getKey(KeyCode::kSpace))
-		{
-			jumpSpeed += 8.0f;
-		}
-
-		static Bool crouch = false;
-		Bool crouchChanged = false;
-		if(Input::getSingleton().getKey(KeyCode::kC))
-		{
-			crouch = !crouch;
-			crouchChanged = true;
-		}
-
-		if(moveVec != 0.0f || jumpSpeed != 0.0f || crouchChanged)
-		{
-			Vec3 dir;
-			if(moveVec != 0.0f)
-			{
-				dir = -(player.getLocalRotation() * moveVec);
-				dir.y() = 0.0f;
-				dir = dir.normalize();
-			}
-
-			F32 speed1 = speed;
-			if(Input::getSingleton().getKey(KeyCode::kLeftShift))
-			{
-				speed1 *= 2.0f;
-			}
-			playerc.setVelocity(speed1, jumpSpeed, dir, crouch);
-		}
-	}
-
-	if(Input::getSingleton().getMouseButton(MouseButton::kRight) == 1)
-	{
-		ANKI_LOGI("Firing a grenade");
-
-		static U32 instance = 0;
-
-		Transform camTrf = SceneGraph::getSingleton().getActiveCameraNode().getWorldTransform();
-		const Vec3 newPos = camTrf.getOrigin().xyz() + camTrf.getRotation().getZAxis() * -3.0f;
-		camTrf.setOrigin(newPos.xyz0());
-
-		SceneNode* grenade = SceneGraph::getSingleton().newSceneNode<SceneNode>(String().sprintf("Grenade%u", instance++).toCString());
-		grenade->setLocalScale(Vec3(2.8f));
-		grenade->newComponent<MeshComponent>()->setMeshFilename("Assets/MESH_grenade_818651700502e14b.ankimesh");
-		grenade->newComponent<MaterialComponent>()->setMaterialFilename("Assets/MTL_grenade_4346150e31bdb957.ankimtl");
-		// monkey->getFirstComponentOfType<MoveComponent>().setLocalTransform(camTrf);
-
-		BodyComponent* bodyc = grenade->newComponent<BodyComponent>();
-		bodyc->setCollisionShapeType(BodyComponentCollisionShapeType::kFromMeshComponent);
-		bodyc->teleportTo(camTrf.getOrigin().xyz(), camTrf.getRotation().getRotationPart());
-		bodyc->setMass(1.0f);
-
-		bodyc->applyForce(camTrf.getRotation().getZAxis().xyz() * -1200.0f, Vec3(0.0f, 0.0f, 0.0f));
-
-		// Create the destruction event
-		ANKI_CHECK(createDestructionEvent(grenade));
-	}
-
-	if(Input::getSingleton().getMouseButton(MouseButton::kLeft) == 1)
-	{
-		const Transform camTrf = SceneGraph::getSingleton().getActiveCameraNode().getWorldTransform();
-
-		for(U32 i = 0; i < 8; ++i)
-		{
-			F32 spredAngle = toRad(getRandomRange(-2.0f, 2.0f));
-			Mat3 randDirection(Axisang(spredAngle, Vec3(1.0f, 0.0f, 0.0f)));
-			spredAngle = toRad(getRandomRange(-2.0f, 2.0f));
-			randDirection = randDirection * Mat3(Axisang(spredAngle, Vec3(0.0f, 1.0f, 0.0f)));
-			randDirection = camTrf.getRotation().getRotationPart() * randDirection;
-
-			const Vec3 from = camTrf.getOrigin().xyz();
-			const Vec3 to = from + -randDirection.getZAxis() * 100.0f;
-
-			RayHitResult result;
-			const Bool hit = PhysicsWorld::getSingleton().castRayClosestHit(from, to, PhysicsLayerBit::kStatic, result);
-
-			if(hit)
-			{
-				// Create rotation
-				const Vec3& zAxis = result.m_normal;
-				Vec3 yAxis = Vec3(0, 1, 0.5);
-				Vec3 xAxis = yAxis.cross(zAxis).normalize();
-				yAxis = zAxis.cross(xAxis);
-
-				Mat3x4 rot = Mat3x4::getIdentity();
-				rot.setXAxis(xAxis);
-				rot.setYAxis(yAxis);
-				rot.setZAxis(zAxis);
-
-				Transform trf(result.m_hitPosition.xyz0(), rot, Vec4(1.0f, 1.0f, 1.0f, 0.0f));
-
-				// Create an obj
-				static U32 id = 0;
-				SceneNode* bulletDecal = SceneGraph::getSingleton().newSceneNode<SceneNode>(String().sprintf("decal%u", id++).toCString());
-				bulletDecal->setLocalTransform(trf);
-				bulletDecal->setLocalScale(Vec3(0.1f, 0.1f, 0.3f));
-				DecalComponent* decalc = bulletDecal->newComponent<DecalComponent>();
-				decalc->loadDiffuseImageResource("Assets/bullet_hole_decal.ankitex", 1.0f);
-
-				ANKI_CHECK(createDestructionEvent(bulletDecal));
-
-#if 0
-			// Create some particles
-			ParticleEmitterComponent* partc = monkey->newComponent<ParticleEmitterComponent>();
-			partc->loadParticleEmitterResource("Assets/Smoke.ankipart");
-#endif
-
-				// Create some fog volumes
-				if(i == 0)
-				{
-					static int id = 0;
-					String name;
-					name.sprintf("fog%u", id++);
-
-					SceneNode* fogNode = SceneGraph::getSingleton().newSceneNode<SceneNode>(name.toCString());
-					FogDensityComponent* fogComp = fogNode->newComponent<FogDensityComponent>();
-					fogNode->setLocalScale(Vec3(2.1f));
-					fogComp->setDensity(15.0f);
-
-					fogNode->setLocalTransform(trf);
-
-					ANKI_CHECK(createDestructionEvent(fogNode));
-					ANKI_CHECK(createFogVolumeFadeEvent(fogNode));
-				}
-			}
-		}
-	}
-
 	if(0)
 	{
 		SceneNode& node = SceneGraph::getSingleton().findSceneNode("trigger");