Index: /cpp/frams/model/geometry/modelgeoclass.cpp
===================================================================
--- /cpp/frams/model/geometry/modelgeoclass.cpp	(revision 241)
+++ /cpp/frams/model/geometry/modelgeoclass.cpp	(revision 242)
@@ -5,69 +5,101 @@
 
 #define FIELDSTRUCT ModelGeometry
-static ParamEntry modelgeo_paramtab[]=
+static ParamEntry modelgeo_paramtab[] =
 {
- {"Creature: Geometry",1,5,"ModelGeometry",
-  "Example usage:\n"
-  "Simulator.print(ModelGeometry.forModel(Model.newFromString(\"//0\\np:sh=1\\n\")).area());\n"
-  "ModelGeometry.geodensity refers to the global simulator parameter (also available in GUI).\n"
-  "Local geodensity of individual ModelGeometry objects can also be changed, like this:\n"
-  "var mg=ModelGeometry.forModel(GenePools[0][0].getModel()); mg.geodensity=2; GenePools[0][0].user1=mg.area();\n"},
- {"geodensity",0,0,"Density","f 0.01 100.0 1.0",FIELD(density),"Affects the geometry calculation precision"}, //note: geodensity instead of density to make the name more unique - because of the unfortunate sim_params merging all configuration fields in a single namespace
-{"forModel",0,PARAM_USERHIDDEN,"","p oModelGeometry(oModel)",PROCEDURE(p_formodel),"The returned ModelGeometry object can be used to calculate geometric properties of the associated model (volume, area, sizes). The density is copied from the current global ModelGeometry.geodensity on object creation."},
-{"volume",0,PARAM_NOSTATIC|PARAM_USERHIDDEN,"volume","p f()",PROCEDURE(p_volume),},
-{"area",0,PARAM_NOSTATIC|PARAM_USERHIDDEN,"area","p f()",PROCEDURE(p_area),},
-{"sizesAndAxes",0,PARAM_NOSTATIC|PARAM_USERHIDDEN,"sizesAndAxes","p oVector()",PROCEDURE(p_sizesandaxes),"The returned vector contains XYZ (sizes) and Orient (axes) objects."},
-
-{0,0,0,},
+	{ "Creature: Geometry", 1, 5, "ModelGeometry",
+	"Example usage:\n"
+	"Simulator.print(ModelGeometry.forModel(Model.newFromString(\"//0\\np:sh=1\\n\")).area());\n\n"
+	"ModelGeometry.geom_density refers to the global simulator parameter (also available in GUI).\n"
+	"To set geom_density for individual ModelGeometry objects:\n"
+	"var mg=ModelGeometry.forModel(GenePools[0][0].getModel()); mg.geom_density=2; GenePools[0][0].user1=mg.area();\n" },
+	{ "geom_density", 0, 0, "Density", "f 0.01 100.0 1.0", FIELD(density), "Affects the geometry calculation precision" }, //note: we used 'geom_density' instead of 'density' to make the name more unique - because sim_params merges all configuration fields in a single namespace.
+	{ "forModel", 0, PARAM_USERHIDDEN, "", "p oModelGeometry(oModel)", PROCEDURE(p_formodel), "The returned ModelGeometry object can be used to calculate geometric properties (volume, area, sizes) of the associated model. The density is copied from the current global ModelGeometry.geom_density on object creation." },
+	{ "volume", 0, PARAM_NOSTATIC | PARAM_USERHIDDEN, "volume", "p f()", PROCEDURE(p_volume), },
+	{ "area", 0, PARAM_NOSTATIC | PARAM_USERHIDDEN, "area", "p f()", PROCEDURE(p_area), },
+	{ "sizesAndAxes", 0, PARAM_NOSTATIC | PARAM_USERHIDDEN, "sizesAndAxes", "p oVector()", PROCEDURE(p_sizesandaxes), "The returned vector contains XYZ (sizes) and Orient (axes) objects." },
+	{ 0, 0, 0, },
 };
 #undef FIELDSTRUCT
 
 ExtObject ModelGeometry::makeDynamicObject(ModelGeometry* mg)
-{return ExtObject(&mg->par,mg);}
+{
+	return ExtObject(&mg->par, mg);
+}
 
 ModelGeometry::ModelGeometry(ModelObj *mo)
-	:par(modelgeo_paramtab,this)
+:par(modelgeo_paramtab, this)
 {
-model=mo;
-if (model!=NULL)
-	model->incref();
+	cached_for_density = -1; //invalid value, will be updated on first request
+	invalidateAllCached();
+	model = mo;
+	if (model != NULL)
+		model->incref();
 }
 
 ModelGeometry::~ModelGeometry()
 {
-if (model!=NULL)
-	model->decref();
+	if (model != NULL)
+		model->decref();
 }
 
-void ModelGeometry::p_formodel(ExtValue *args,ExtValue *ret)
+// Mark all 3 results as invalid.
+// Validity of these 3 values must be maintained independently,
+// as each of them is calculated by an individual call.
+void ModelGeometry::invalidateAllCached()
 {
-Model *m=ModelObj::fromObject(*args);
-if (m!=NULL)
-	{
-	ModelGeometry *mg=new ModelGeometry((ModelObj*)m);
-	mg->density=density;
-	ret->setObject(ModelGeometry::makeDynamicObject(mg));
-	}
-else
-	ret->setEmpty();
+	cached_volume = -1;
+	cached_area = -1;
+	cached_sizes.x = -1;
 }
 
-void ModelGeometry::p_volume(ExtValue *args,ExtValue *ret)
+// Invalidates cached results if a new density is requested
+// (called in all geometry calculation functions)
+void ModelGeometry::onDensityChanged()
 {
-ret->setDouble(ModelGeometryInfo::volume(*model,density));
+	if (cached_for_density != density)
+	{
+		invalidateAllCached();
+		cached_for_density = density;
+	}
 }
 
-void ModelGeometry::p_area(ExtValue *args,ExtValue *ret)
+void ModelGeometry::p_formodel(ExtValue *args, ExtValue *ret)
 {
-ret->setDouble(ModelGeometryInfo::area(*model,density));
+	Model *m = ModelObj::fromObject(*args);
+	if (m != NULL)
+	{
+		ModelGeometry *mg = new ModelGeometry((ModelObj*)m);
+		mg->density = density;
+		ret->setObject(ModelGeometry::makeDynamicObject(mg));
+	}
+	else
+		ret->setEmpty();
 }
 
-void ModelGeometry::p_sizesandaxes(ExtValue *args,ExtValue *ret)
+void ModelGeometry::p_volume(ExtValue *args, ExtValue *ret)
 {
-Pt3D sizes; Orient axes;
-ModelGeometryInfo::findSizesAndAxesOfModel(*model,density,sizes,axes);
-VectorObject* n=new VectorObject;
-n->data+=new ExtValue(Pt3D_Ext::makeDynamicObject(sizes));
-n->data+=new ExtValue(Orient_Ext::makeDynamicObject(new Orient_Ext(axes)));
-ret->setObject(n->makeObject());
+	onDensityChanged();
+	if (cached_volume < 0) //calculate if invalid
+		cached_volume = ModelGeometryInfo::volume(*model, density);
+	ret->setDouble(cached_volume);
 }
+
+void ModelGeometry::p_area(ExtValue *args, ExtValue *ret)
+{
+	onDensityChanged();
+	if (cached_area < 0) //calculate if invalid
+		cached_area = ModelGeometryInfo::area(*model, density);
+	ret->setDouble(cached_area);
+}
+
+void ModelGeometry::p_sizesandaxes(ExtValue *args, ExtValue *ret)
+{
+	onDensityChanged();
+	if (cached_sizes.x < 0) //calculate if invalid
+		ModelGeometryInfo::findSizesAndAxesOfModel(*model, density, cached_sizes, cached_axes);
+
+	VectorObject* n = new VectorObject;
+	n->data += new ExtValue(Pt3D_Ext::makeDynamicObject(cached_sizes));
+	n->data += new ExtValue(Orient_Ext::makeDynamicObject(new Orient_Ext(cached_axes)));
+	ret->setObject(n->makeObject());
+}
Index: /cpp/frams/model/geometry/modelgeoclass.h
===================================================================
--- /cpp/frams/model/geometry/modelgeoclass.h	(revision 241)
+++ /cpp/frams/model/geometry/modelgeoclass.h	(revision 242)
@@ -4,22 +4,31 @@
 #include <frams/model/modelobj.h>
 
-class ModelGeometry: public DestrBase
+class ModelGeometry : public DestrBase
 {
-  public:
-ModelObj *model;
-double density;
-Param par;
+public:
+	ModelObj *model;
+	double density;
 
-ModelGeometry(ModelObj *mo=NULL);
-~ModelGeometry();
+	//"cached" fields let avoid redundant computations when asking for the same properties of the same model at the same density
+	double cached_for_density;
+	double cached_volume, cached_area;
+	Pt3D cached_sizes; Orient cached_axes;
+
+	Param par;
+
+	ModelGeometry(ModelObj *mo = NULL);
+	~ModelGeometry();
+
+	void invalidateAllCached();
+	void onDensityChanged();
 
 #define STATRICKCLASS ModelGeometry
-PARAMPROCDEF(p_formodel);
-PARAMPROCDEF(p_volume);
-PARAMPROCDEF(p_area);
-PARAMPROCDEF(p_sizesandaxes);
+	PARAMPROCDEF(p_formodel);
+	PARAMPROCDEF(p_volume);
+	PARAMPROCDEF(p_area);
+	PARAMPROCDEF(p_sizesandaxes);
 #undef STATRICKCLASS
 
-static ExtObject makeDynamicObject(ModelGeometry* mg);
+	static ExtObject makeDynamicObject(ModelGeometry* mg);
 };
 
