From 9cf20834075496e48c241637aba65945fc4ff730 Mon Sep 17 00:00:00 2001
From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54>
Date: Sun, 24 Oct 2010 19:27:59 +0000
Subject: [PATCH] test navier fluid particles

Apprentice mage can not be talked to if dead


git-svn-id: http://svn.net-core.org/repos/t-engine4@1616 51575b47-30f0-44d4-a5cc-537603b46e54
---
 game/engines/default/engine/Particles.lua     |   7 +-
 game/engines/default/engine/dialogs/Chat.lua  |   3 +-
 game/modules/tome/class/Game.lua              |   3 +
 .../tome/data/chats/mage-apprentice-quest.lua |   2 +-
 .../tome/data/gfx/particles/breath_fire.lua   |  50 +++
 .../tome/data/quests/mage-apprentice.lua      |   7 +-
 .../tome/data/talents/gifts/cold-drake.lua    |   2 +-
 .../tome/dialogs/LevelupStatsDialog.lua       |   4 +-
 .../tome/dialogs/LevelupTalentsDialog.lua     |   4 +-
 src/main.c                                    |   1 +
 src/particles_gas.c                           | 419 ++++++++++++++++++
 src/particles_gas.h                           |  50 +++
 12 files changed, 541 insertions(+), 11 deletions(-)
 create mode 100644 game/modules/tome/data/gfx/particles/breath_fire.lua
 create mode 100644 src/particles_gas.c
 create mode 100644 src/particles_gas.h

diff --git a/game/engines/default/engine/Particles.lua b/game/engines/default/engine/Particles.lua
index 574654b925..3843f506ac 100644
--- a/game/engines/default/engine/Particles.lua
+++ b/game/engines/default/engine/Particles.lua
@@ -70,5 +70,10 @@ function _M:loaded()
 	gl = self.__particles_gl[gl]
 
 	self.update = fct
-	self.ps = core.particles.newEmitter(max or 1000, no_stop, config.settings.particles_density or 100, def, gl)
+	-- Make a gas cloud
+	if def.gas then
+		self.ps = core.gas.newEmitter(def.gas.w, def.gas.h, config.settings.particles_density or 100, def, gl)
+	else
+		self.ps = core.particles.newEmitter(max or 1000, no_stop, config.settings.particles_density or 100, def, gl)
+	end
 end
diff --git a/game/engines/default/engine/dialogs/Chat.lua b/game/engines/default/engine/dialogs/Chat.lua
index 9912a37e70..a595837300 100644
--- a/game/engines/default/engine/dialogs/Chat.lua
+++ b/game/engines/default/engine/dialogs/Chat.lua
@@ -79,7 +79,7 @@ function _M:use(item, a)
 			return
 		end
 	end
-	if a.jump then
+	if a.jump and not self.killed then
 		self.cur_id = a.jump
 		self:regen()
 	else
@@ -92,6 +92,7 @@ function _M:regen()
 	local d = new(self.chat, self.cur_id)
 	d.__showup = false
 	game:replaceDialog(self, d)
+	self.next_dialog = d
 end
 function _M:resolveAuto()
 --[[
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index a149de4d7b..e37c3a4452 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -641,6 +641,7 @@ function _M:setupCommands()
 		end,
 		[{"_g","ctrl"}] = function()
 			if config.settings.tome.cheat then
+--[[
 				local a = mod.class.NPC.new{}
 				a:replaceWith(self.player:cloneFull())
 				mod.class.NPC.castAs(a)
@@ -656,6 +657,8 @@ function _M:setupCommands()
 				a.ai_state = {talent_in=1}
 				a.faction = "enemies"
 				self.zone:addEntity(self.level, a, "actor", self.player.x+1, self.player.y)
+--]]
+				game.level.map:particleEmitter(self.player.x, self.player.y, 1, "breath_fire")
 			end
 		end,
 	}
diff --git a/game/modules/tome/data/chats/mage-apprentice-quest.lua b/game/modules/tome/data/chats/mage-apprentice-quest.lua
index 44e8d29ffa..1bccfcade8 100644
--- a/game/modules/tome/data/chats/mage-apprentice-quest.lua
+++ b/game/modules/tome/data/chats/mage-apprentice-quest.lua
@@ -25,7 +25,7 @@ Good day to you, fellow traveler!]],
 		{"I have something for you!",
 			jump="welcome",
 			cond=function(npc, player) return player:hasQuest("mage-apprentice") and player:hasQuest("mage-apprentice"):can_offer(player) end,
-			action=function(npc, player, dialog) player:hasQuest("mage-apprentice"):collect_staff(player, dialog) end
+			action=function(npc, player, dialog) player:hasQuest("mage-apprentice"):collect_staff(npc, player, dialog) end
 		},
 		{"I found this staff; it looks powerful. Maybe it would be enough?",
 			jump="angmar_fall",
diff --git a/game/modules/tome/data/gfx/particles/breath_fire.lua b/game/modules/tome/data/gfx/particles/breath_fire.lua
new file mode 100644
index 0000000000..10b39656b6
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/breath_fire.lua
@@ -0,0 +1,50 @@
+-- ToME - Tales of Middle-Earth
+-- Copyright (C) 2009, 2010 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 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 General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+local nb = 0
+
+return { gas = {w=60, h=60},
+generator = function()
+	if nb < 10 then
+		nb = nb + 1
+		return {
+--[[
+			{ sx = 3, sy = 29, dx = 3, dy = -3 },
+			{ sx = 3, sy = 30, dx = 3, dy = 0 },
+			{ sx = 3, sy = 31, dx = 3, dy = 3 },
+
+			{ sx = 4, sy = 29, dx = 3, dy = -3 },
+			{ sx = 4, sy = 30, dx = 3, dy = 0 },
+			{ sx = 4, sy = 31, dx = 3, dy = 3 },
+]]
+			{ sx = 29, sy = 29, dx = -3, dy = -3 },
+			{ sx = 31, sy = 29, dx =  3, dy = -3 },
+			{ sx = 29, sy = 31, dx = -3, dy =  3 },
+			{ sx = 31, sy = 31, dx =  3, dy =  3 },
+
+			{ sx = 30, sy = 29, dx =  0, dy = -3 },
+			{ sx = 30, sy = 31, dx =  0, dy =  3 },
+			{ sx = 29, sy = 30, dx = -3, dy =  0 },
+			{ sx = 31, sy = 30, dx =  3, dy =  0 },
+		}
+	else return {}
+	end
+end, },
+function(self)end,
+30*6
diff --git a/game/modules/tome/data/quests/mage-apprentice.lua b/game/modules/tome/data/quests/mage-apprentice.lua
index bb8ca2dc1b..ec4969cddc 100644
--- a/game/modules/tome/data/quests/mage-apprentice.lua
+++ b/game/modules/tome/data/quests/mage-apprentice.lua
@@ -41,7 +41,7 @@ on_status_change = function(self, who, status, sub)
 	end
 end
 
-collect_staff = function(self, who, dialog)
+collect_staff = function(self, npc, who, dialog)
 	who:showEquipInven("Offer which item?",
 		function(o) return (o.type == "weapon" and o.subtype == "staff" and (not o.define_as or o.define_as ~= "STAFF_ANGMAR")) or (o.type == "jewelry" and o.subtype == "ring") or (o.type == "jewelry" and o.subtype == "amulet") end,
 		function(o, inven, item)
@@ -49,7 +49,8 @@ collect_staff = function(self, who, dialog)
 			if o.define_as and o.define_as == "STAFF_ABSORPTION" then
 				game.logPlayer(who, "#LIGHT_RED#As the apprentice touches the staff he begins to consume, flames bursting out of his mouth, life seems to be drained away from him and in an instant he collapses in a lifeless husk.")
 				who:setQuestStatus(self, self.FAILED)
-				game:unregisterDialog(dialog)
+				game:unregisterDialog(dialog.next_dialog)
+				npc:die()
 				return true
 			end
 
@@ -58,7 +59,7 @@ collect_staff = function(self, who, dialog)
 			who:removeObject(who:getInven(inven), item)
 			game.log("You have no more %s", o:getName{no_count=true, do_color=true})
 			who:sortInven(who:getInven(inven))
-			dialog:regen()
+			dialog.next_dialog:regen()
 			return true
 		end
 	)
diff --git a/game/modules/tome/data/talents/gifts/cold-drake.lua b/game/modules/tome/data/talents/gifts/cold-drake.lua
index f99efcfdd0..6ef30cf79a 100644
--- a/game/modules/tome/data/talents/gifts/cold-drake.lua
+++ b/game/modules/tome/data/talents/gifts/cold-drake.lua
@@ -96,7 +96,7 @@ newTalent{
 			display = '#', color=colors.LIGHT_BLUE, back_color=colors.BLUE,
 			always_remember = true,
 			can_pass = {pass_wall=1},
-			does_block_move = true,
+			block_move = true,
 			block_sight = false,
 			temporary = 4 + self:getTalentLevel(t),
 			x = x, y = y,
diff --git a/game/modules/tome/dialogs/LevelupStatsDialog.lua b/game/modules/tome/dialogs/LevelupStatsDialog.lua
index aaccd1b413..53079867da 100644
--- a/game/modules/tome/dialogs/LevelupStatsDialog.lua
+++ b/game/modules/tome/dialogs/LevelupStatsDialog.lua
@@ -103,8 +103,8 @@ function _M:finish()
 		end
 	end
 	for i, tid in ipairs(reset) do
-		self.actor:forceUseTalent(tid, {ignore_energy=true, ingore_cd=true})
-		self.actor:forceUseTalent(tid, {ignore_energy=true, ingore_cd=true})
+		self.actor:forceUseTalent(tid, {ignore_energy=true, ignore_cd=true})
+		self.actor:forceUseTalent(tid, {ignore_energy=true, ignore_cd=true})
 	end
 end
 
diff --git a/game/modules/tome/dialogs/LevelupTalentsDialog.lua b/game/modules/tome/dialogs/LevelupTalentsDialog.lua
index 5bf2332966..3f11486671 100644
--- a/game/modules/tome/dialogs/LevelupTalentsDialog.lua
+++ b/game/modules/tome/dialogs/LevelupTalentsDialog.lua
@@ -212,8 +212,8 @@ function _M:finish()
 		end
 	end
 	for i, tid in ipairs(reset) do
-		self.actor:forceUseTalent(tid, {ignore_energy=true, ingore_cd=true})
-		self.actor:forceUseTalent(tid, {ignore_energy=true, ingore_cd=true})
+		self.actor:forceUseTalent(tid, {ignore_energy=true, ignore_cd=true})
+		self.actor:forceUseTalent(tid, {ignore_energy=true, ignore_cd=true})
 	end
 end
 
diff --git a/src/main.c b/src/main.c
index 6555cfbcb7..3d228789e4 100644
--- a/src/main.c
+++ b/src/main.c
@@ -641,6 +641,7 @@ void boot_lua(int state, bool rebooting, int argc, char *argv[])
 		luaopen_lxp(L);
 		luaopen_map(L);
 		luaopen_particles(L);
+		luaopen_gas(L);
 		luaopen_sound(L);
 		luaopen_noise(L);
 		luaopen_shaders(L);
diff --git a/src/particles_gas.c b/src/particles_gas.c
new file mode 100644
index 0000000000..125bd13075
--- /dev/null
+++ b/src/particles_gas.c
@@ -0,0 +1,419 @@
+/*
+    TE4 - T-Engine 4
+    Copyright (C) 2009, 2010 Nicolas Casalini
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+    Nicolas Casalini "DarkGod"
+    darkgod@te4.org
+*/
+#include "display.h"
+#include "lua.h"
+#include "lauxlib.h"
+#include "lualib.h"
+#include "auxiliar.h"
+#include "types.h"
+#include "particles_gas.h"
+#include "script.h"
+#include <math.h>
+#include "SFMT.h"
+
+#define rng(x, y) (x + rand_div(1 + y - x))
+
+static void getinitfield(lua_State *L, const char *key, int *min, int *max)
+{
+	lua_pushstring(L, key);
+	lua_gettable(L, -2);
+
+	lua_pushnumber(L, 1);
+	lua_gettable(L, -2);
+	*min = (int)lua_tonumber(L, -1);
+	lua_pop(L, 1);
+
+	lua_pushnumber(L, 2);
+	lua_gettable(L, -2);
+	*max = (int)lua_tonumber(L, -1);
+	lua_pop(L, 1);
+
+//	printf("%s :: %d %d\n", key, (int)*min, (int)*max);
+
+	lua_pop(L, 1);
+}
+
+static void getparticulefield(lua_State *L, const char *k, float *v)
+{
+	lua_pushstring(L, k);
+	lua_gettable(L, -2);
+	*v = (float)lua_tonumber(L, -1);
+//	printf("emit %s :: %f\n", k, *v);
+	lua_pop(L, 1);
+}
+
+static int gas_new(lua_State *L)
+{
+	int w = luaL_checknumber(L, 1);
+	int h = luaL_checknumber(L, 2);
+	int density = luaL_checknumber(L, 3);
+	GLuint *t = (GLuint*)auxiliar_checkclass(L, "gl{texture}", 5);
+	int t_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+	int p_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+	gaszone_type *gz = (gaszone_type*)lua_newuserdata(L, sizeof(gaszone_type));
+	auxiliar_setclass(L, "core{gas}", -1);
+
+	gz->last_tick = -1;
+	gz->w = w;
+	gz->h = h;
+	gz->n = (w*2 > h*2) ? w*2 : h*2;
+	gz->size = (gz->n + 2) * (gz->n + 2);
+
+	gz->visc = 1E-4f;
+	gz->diff = 1E-5f;
+	gz->force = 20.0f;
+	gz->source = 3000.0f;
+	gz->stepDelay  = 0.0f;
+
+	gz->texture = *t;
+	gz->texture_ref = t_ref;
+
+	gz->u = calloc(gz->size, sizeof(float));
+	gz->v = calloc(gz->size, sizeof(float));
+	gz->dens = calloc(gz->size, sizeof(float));
+	gz->u_prev = calloc(gz->size, sizeof(float));
+	gz->v_prev = calloc(gz->size, sizeof(float));
+	gz->dens_prev = calloc(gz->size, sizeof(float));
+
+	int i;
+	for (i=0; i < gz->size; i++)
+	{
+		gz->u[i] = 0.0f;
+		gz->u_prev[i] = 0.0f;
+		gz->v[i] = 0.0f;
+		gz->v_prev[i] = 0.0f;
+		gz->dens[i] = 0.0f;
+		gz->dens_prev[i] = 0.0f;
+	}
+
+	printf("Making gas emitter with size %dx%d\n", w, h);
+
+	// Grab all parameters
+	lua_rawgeti(L, LUA_REGISTRYINDEX, p_ref);
+
+	lua_pushstring(L, "generator");
+	lua_gettable(L, -2);
+	if (lua_isnil(L, -1))
+	{
+		lua_pop(L, 1);
+		gz->generator_ref = LUA_NOREF;
+	}
+	else
+		gz->generator_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+	if (gz->generator_ref == LUA_NOREF)
+	{
+		lua_pushstring(L, "Gas cloud created without a lua generator!");
+		lua_error(L);
+	}
+	lua_pop(L, 1);
+
+	luaL_unref(L, LUA_REGISTRYINDEX, p_ref);
+
+	return 1;
+}
+
+static int gas_free(lua_State *L)
+{
+	gaszone_type *gz = (gaszone_type*)auxiliar_checkclass(L, "core{gas}", 1);
+
+	free(gz->u);
+	free(gz->v);
+	free(gz->dens);
+	free(gz->u_prev);
+	free(gz->v_prev);
+	free(gz->dens_prev);
+
+	luaL_unref(L, LUA_REGISTRYINDEX, gz->texture_ref);
+	if (gz->generator_ref) luaL_unref(L, LUA_REGISTRYINDEX, gz->generator_ref);
+
+	lua_pushnumber(L, 1);
+	return 1;
+}
+
+#define IX(i,j) ((i)+(gz->n+2)*(j))
+#define SWAP(x0,x) {float *tmp=x0;x0=x;x=tmp;}
+
+// set boundary conditions
+void set_bnd (gaszone_type *gz, int b, float * x ) {
+	int i;
+	for ( i=1 ; i<=gz->n ; i++ ) {
+		// west and east walls
+		x[IX(0,i)] = b == 1 ? -x[IX(1,i)] : x[IX(1,i)];
+		x[IX(gz->n+1,i)] = b == 1 ? -x[IX(gz->n,i)] : x[IX(gz->n,i)];
+		// boundary doesn't work on north and south walls...
+		// dunno why...
+		x[IX(i,0)] = b == 1 ? -x[IX(i,1)] : x[IX(i,1)];
+		x[IX(i,gz->n+1)] = b == 1 ? -x[IX(i,gz->n)] : x[IX(i,gz->n)];
+	}
+	// boundary conditions at corners
+	x[IX(0  ,0  )] = 0.5*(x[IX(1,0  )]+x[IX(0  ,1)]);
+	x[IX(0  ,gz->n+1)] = 0.5*(x[IX(1,gz->n+1)]+x[IX(0  ,gz->n )]);
+	x[IX(gz->n+1,0  )] = 0.5*(x[IX(gz->n,0  )]+x[IX(gz->n+1,1)]);
+	x[IX(gz->n+1,gz->n+1)] = 0.5*(x[IX(gz->n,gz->n+1)]+x[IX(gz->n+1,gz->n )]);
+}
+
+
+// update density map according to density sources
+// x : density map
+// s : density source map
+// dt : elapsed time
+void add_source(gaszone_type *gz, float *x, float *s, float dt) {
+	int i;
+	for (i=0; i < gz->size; i++) {
+		x[i] += dt*s[i];
+	}
+}
+
+// update density or velocity map for diffusion
+// b : boundary width
+// x : current density map
+// x0 : previous density map
+// diff : diffusion coef
+// dt : elapsed time
+void diffuse(gaszone_type *gz,  int b, float *x, float *x0, float diff, float dt) {
+	float a = diff*dt*gz->n*gz->n;
+	int i, j, k;
+	for (k=0; k < 20; k++) {
+		for (i=1; i <= gz->n; i++ ) {
+			for (j=1; j<= gz->n; j++) {
+				x[IX(i,j)] = (x0[IX(i,j)] + a*(x[IX(i-1,j)]+x[IX(i+1,j)]
+					+x[IX(i,j-1)]+x[IX(i,j+1)]))/(1+4*a);
+			}
+		}
+		set_bnd(gz, b,x);
+	}
+}
+
+// update density map according to velocity map
+// b : boundary width
+// d : current density map
+// d0 : previous density map
+// u,v : current velocity map
+// dt : elapsed time
+void advect (gaszone_type *gz,  int b, float * d, float * d0, float * u, float * v, float dt ) {
+	int i0, j0, i1, j1;
+	float x, y, s0, t0, s1, t1, dt0;
+
+	dt0 = dt*gz->n;
+	int i, j;
+	for (i=1 ; i<=gz->n ; i++ ) {
+		for (j=1 ; j<=gz->n ; j++ ) {
+			x = i-dt0*u[IX(i,j)];
+			y = j-dt0*v[IX(i,j)];
+			if (x<0.5) x=0.5;
+			if (x>gz->n+0.5) x=gz->n+ 0.5;
+			i0=(int)x;
+			i1=i0+1;
+			if (y<0.5) y=0.5;
+			if (y>gz->n+0.5) y=gz->n+ 0.5;
+			j0=(int)y;
+			j1=j0+1;
+			s1 = x-i0;
+			s0 = 1-s1;
+			t1 = y-j0;
+			t0 = 1-t1;
+			d[IX(i,j)] = s0*(t0*d0[IX(i0,j0)]+t1*d0[IX(i0,j1)])+
+			s1*(t0*d0[IX(i1,j0)]+t1*d0[IX(i1,j1)]);
+		}
+	}
+	set_bnd (gz, b, d );
+}
+
+void project (gaszone_type *gz,  float * u, float * v, float * p, float * div ) {
+	int i, j, k;
+
+	float h = 1.0/gz->n;
+	for (i=1 ; i<=gz->n ; i++ ) {
+		for (j=1 ; j<=gz->n ; j++ ) {
+			div[IX(i,j)] = -0.5*h*(u[IX(i+1,j)]-u[IX(i-1,j)]+
+				v[IX(i,j+1)]-v[IX(i,j-1)]);
+			p[IX(i,j)] = 0;
+		}
+	}
+	set_bnd (gz, 0, div ); set_bnd (gz, 0, p );
+
+	for (k=0 ; k<19 ; k++ ) {
+		for (i=1 ; i<=gz->n ; i++ ) {
+			for (j=1 ; j<=gz->n ; j++ ) {
+				p[IX(i,j)] = (div[IX(i,j)] + p[IX(i-1,j)] + p[IX(i+1,j)] + p[IX(i,j-1)] + p[IX(i,j+1)]) / 4;
+			}
+		}
+		set_bnd (gz, 0, p );
+	}
+
+	for (i=1 ; i<=gz->n ; i++ ) {
+		for (j=1 ; j<=gz->n ; j++ ) {
+			u[IX(i,j)] -= 0.5*(p[IX(i+1,j)]-p[IX(i-1,j)])/h;
+			v[IX(i,j)] -= 0.5*(p[IX(i,j+1)]-p[IX(i,j-1)])/h;
+		}
+	}
+	set_bnd (gz, 1, u ); set_bnd (gz, 2, v );
+}
+
+// do all three density steps
+void update_density (gaszone_type *gz,  float * x, float * x0,  float * u, float * v, float diff,  float dt ) {
+	add_source (gz, x, x0, dt );
+	SWAP ( x0, x ); diffuse (gz, 0, x, x0, diff, dt );
+	SWAP ( x0, x ); advect (gz, 0, x, x0, u, v, dt );
+}
+
+void update_velocity(gaszone_type *gz, float * u, float * v, float * u0, float * v0,  float visc, float dt ) {
+	add_source (gz, u, u0, dt );
+	add_source (gz, v, v0, dt );
+	SWAP ( u0, u ); diffuse (gz, 1, u, u0, visc, dt );
+	SWAP ( v0, v ); diffuse (gz, 2, v, v0, visc, dt );
+	project (gz, u, v, u0, v0 );
+	SWAP ( u0, u ); SWAP ( v0, v );
+	advect (gz, 1, u, u0, u0, v0, dt ); advect (gz, 2, v, v0, u0, v0, dt );
+	project (gz, u, v, u0, v0 );
+}
+
+void add_data (gaszone_type *gz, float * d, float * u, float * v, float elapsed)
+{
+	int i;
+	for ( i=0 ; i < gz->size; i++ )
+	{
+		u[i] = v[i] = d[i] = 0.0f;
+	}
+
+	int px;
+	int py;
+	float dx;
+	float dy;
+
+	lua_rawgeti(L, LUA_REGISTRYINDEX, gz->generator_ref);
+	lua_call(L, 0, 1);
+	if (!lua_isnil(L, -1))
+	{
+		int len = lua_objlen(L, -1);
+		for (i = 1; i <= len; i++)
+		{
+			lua_pushnumber(L, i);
+			lua_gettable(L, -2);
+
+			float tmp;
+			getparticulefield(L, "sx", &tmp); px = tmp;
+			getparticulefield(L, "sy", &tmp); py = tmp;
+			getparticulefield(L, "dx", &dx);
+			getparticulefield(L, "dy", &dy);
+			float l = sqrt(dx*dx+dy*dy);
+			if (l > 0.0f)
+			{
+				l = 1.0f / l;
+				dx *= l;
+				dy *= l;
+				u[IX(px*2, py*2)] = gz->force * dx;
+				v[IX(px*2, py*2)] = gz->force * dy;
+				d[IX(px*2, py*2)] = gz->source;
+			}
+
+			lua_pop(L, 1);
+		}
+		lua_pop(L, 1);
+	}
+}
+
+void update(gaszone_type *gz, float elapsed) {
+	add_data(gz, gz->dens_prev, gz->u_prev, gz->v_prev, elapsed);
+	update_velocity(gz, gz->u, gz->v, gz->u_prev, gz->v_prev, gz->visc, elapsed);
+	update_density(gz, gz->dens, gz->dens_prev, gz->u, gz->v, gz->diff, elapsed);
+}
+
+#define CLAMP(a, b, x) ((x) < (a) ? (a) : ((x) > (b) ? (b) : (x)))
+
+static int gas_emit(lua_State *L)
+{
+	gaszone_type *gz = (gaszone_type*)auxiliar_checkclass(L, "core{gas}", 1);
+
+	return 0;
+}
+
+static int gas_to_screen(lua_State *L)
+{
+	gaszone_type *gz = (gaszone_type*)auxiliar_checkclass(L, "core{gas}", 1);
+	int x = luaL_checknumber(L, 2);
+	int y = luaL_checknumber(L, 3);
+	bool show = lua_toboolean(L, 4);
+	float zoom = luaL_checknumber(L, 5);
+	int w = 0;
+	int i, j, dx, dy;
+	bool alive = FALSE;
+
+	glBindTexture(GL_TEXTURE_2D, gz->texture);
+
+	if (gz->last_tick == -1) gz->last_tick = ((float)SDL_GetTicks()) / 1000.0f - 1;
+
+	float now = ((float)SDL_GetTicks()) / 1000.0f;
+	update(gz, now - gz->last_tick);
+	gz->last_tick = now;
+
+	for (dx=0; dx <= gz->n; dx++)
+	{
+		for (dy = 0; dy <= gz->n; dy++)
+		{
+			float coef = (float)(gz->dens[IX(dx, dy)] / 128.0f);
+			coef = CLAMP(0.0f, 1.0f, coef);
+			if (coef > 0.1)
+			{
+				glColor4f(coef, 0, 0, coef);
+
+				glBegin(GL_QUADS);
+				i = x + dx * 4;
+				j = y + dy * 4;
+				glTexCoord2f(0,0); glVertex3f(0 + i,	0 + j,		-97);
+				glTexCoord2f(1,0); glVertex3f(10 + i,	0 + j,		-97);
+				glTexCoord2f(1,1); glVertex3f(10 + i,	10 + j,		-97);
+				glTexCoord2f(0,1); glVertex3f(0 + i,	10 + j,		-97);
+				glEnd();
+			}
+		}
+	}
+
+	// Restore normal display
+	glColor4f(1, 1, 1, 1);
+
+	lua_pushboolean(L, 1);
+	return 1;
+}
+
+static const struct luaL_reg gaslib[] =
+{
+	{"newEmitter", gas_new},
+	{NULL, NULL},
+};
+
+static const struct luaL_reg gas_reg[] =
+{
+	{"__gc", gas_free},
+	{"close", gas_free},
+	{"emit", gas_emit},
+	{"toScreen", gas_to_screen},
+	{NULL, NULL},
+};
+
+int luaopen_gas(lua_State *L)
+{
+	auxiliar_newclass(L, "core{gas}", gas_reg);
+	luaL_openlib(L, "core.gas", gaslib, 0);
+	return 1;
+}
diff --git a/src/particles_gas.h b/src/particles_gas.h
new file mode 100644
index 0000000000..9529d5a689
--- /dev/null
+++ b/src/particles_gas.h
@@ -0,0 +1,50 @@
+/*
+    TE4 - T-Engine 4
+    Copyright (C) 2009, 2010 Nicolas Casalini
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 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 General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+    Nicolas Casalini "DarkGod"
+    darkgod@te4.org
+*/
+#ifndef _PARTICLES_H_
+#define _PARTICLES_H_
+
+#include "tgl.h"
+
+typedef struct {
+	GLuint texture;
+	int texture_ref;
+	int generator_ref;
+
+	float last_tick;
+
+	int w, h;
+	int size;
+	int n;
+
+	// 2D velocity maps (current and previous)
+	float *u, *v, *u_prev, *v_prev;
+
+	// density maps (current and previous)
+	float *dens, *dens_prev;
+
+	float visc;
+	float diff;
+	float force;
+	float source;
+	float stepDelay;
+} gaszone_type;
+
+#endif
-- 
GitLab