Operator for estimating the maximum number of samples fitting on the
authorLukas Tönne <lukas.toenne@gmail.com>
Sat, 20 Dec 2014 13:45:25 +0000 (14:45 +0100)
committerLukas Tönne <lukas.toenne@gmail.com>
Sat, 20 Dec 2014 13:45:25 +0000 (14:45 +0100)
ground object.

This is based on the optimal circle packing density, which is an upper
bound for the number of samples.

This is a way to avoid gaps in the sampling from insufficient limit for
number of samples.

object_physics_meadow/meadow.py
object_physics_meadow/ui.py
object_physics_meadow/util.py

index f910e47..8765dab 100644 (file)
@@ -19,6 +19,7 @@
 # <pep8 compliant>
 
 import bpy, os, cProfile, pstats, io
+from math import *
 from mathutils import *
 
 from object_physics_meadow import settings as _settings
@@ -33,16 +34,35 @@ from object_physics_meadow.hierarchical_dart_throw import hierarchical_dart_thro
 
 use_profiling = False
 
+# estimate an upper bound on sample number based on optimal circle packing
+def estimate_max_samples(context, groundob, precision=3):
+    radius = groundob.meadow.sample_distance
+    area_circle = pi * radius*radius
+    
+    mat = groundob.matrix_world
+    bbmin = mat * Vector(tuple(min(p[i] for p in groundob.bound_box) for i in range(3)))
+    bbmax = mat * Vector(tuple(max(p[i] for p in groundob.bound_box) for i in range(3)))
+    area_bounds = (bbmax[0] - bbmin[0]) * (bbmax[1] - bbmin[1])
+    # optimal circle packing area ratio is pi/(2*sqrt(3)) ~= 0.9069
+    # http://en.wikipedia.org/wiki/Circle_packing
+    area_max = area_bounds * pi / (2.0*sqrt(3.0))
+
+    num = area_max / area_circle
+    # round to precision
+    num = int(round_sigfigs(num + 0.5, precision))
+
+    groundob.meadow.max_samples = num
+
 def make_samples(context, gridob, groundob):
     settings = _settings.get(context)
     
     mat = groundob.matrix_world
-    gmin = mat * Vector(tuple(min(p[i] for p in groundob.bound_box) for i in range(3)))
-    gmax = mat * Vector(tuple(max(p[i] for p in groundob.bound_box) for i in range(3)))
+    bbmin = mat * Vector(tuple(min(p[i] for p in groundob.bound_box) for i in range(3)))
+    bbmax = mat * Vector(tuple(max(p[i] for p in groundob.bound_box) for i in range(3)))
     
     # get a sample generator implementation
-    #gen = best_candidate_gen(groundob.meadow.sample_distance, gmin[0], gmax[0], gmin[1], gmax[1])
-    gen = hierarchical_dart_throw_gen(groundob.meadow.sample_distance, groundob.meadow.sampling_levels, gmin[0], gmax[0], gmin[1], gmax[1])
+    #gen = best_candidate_gen(groundob.meadow.sample_distance, bbmin[0], bbmax[0], bbmin[1], bbmax[1])
+    gen = hierarchical_dart_throw_gen(groundob.meadow.sample_distance, groundob.meadow.sampling_levels, bbmin[0], bbmax[0], bbmin[1], bbmax[1])
     
     loc2D = [p[0:2] for p in gen(groundob.meadow.seed, groundob.meadow.max_samples)]
     
index 81b96d9..dedf97d 100644 (file)
@@ -85,7 +85,9 @@ class OBJECT_PT_Meadow(Panel):
             sub.prop(meadow, "seed")
             col = sub.column(align=True)
             col.prop(meadow, "sample_distance")
-            col.prop(meadow, "max_samples")
+            sub2 = col.row(align=True)
+            sub2.prop(meadow, "max_samples")
+            sub2.operator("meadow.estimate_max_samples", text="Estimate")
             sub.prop(meadow, "sampling_levels")
             
             if has_samples:
@@ -133,6 +135,23 @@ class MeadowOperatorBase():
         return True, cache_dir
 
 
+class EstimateMaxSamplesOperator(MeadowOperatorBase, Operator):
+    """Estimate an upper bound for the number of samples fitting on the ground object"""
+    bl_idname = "meadow.estimate_max_samples"
+    bl_label = "Estimate Maximum Samples"
+    bl_options = {'REGISTER', 'UNDO'}
+    
+    def execute(self, context):
+        groundob = find_meadow_object(context, 'GROUND')
+        if not groundob:
+            self.report({'ERROR'}, "Could not find meadow Ground object")
+            return {'CANCELLED'}
+        
+        meadow.estimate_max_samples(context, groundob)
+        
+        return {'FINISHED'}
+
+
 class MakeBlobsOperator(MeadowOperatorBase, Operator):
     """Generate Blob objects storing dupli distribution"""
     bl_idname = "meadow.make_blobs"
@@ -265,6 +284,7 @@ def menu_generate_meadow(self, context):
 def register():
     bpy.utils.register_class(OBJECT_PT_Meadow)
     
+    bpy.utils.register_class(EstimateMaxSamplesOperator)
     bpy.utils.register_class(MakeBlobsOperator)
     bpy.utils.register_class(DeleteBlobsOperator)
     bpy.utils.register_class(MakePatchesOperator)
@@ -276,6 +296,7 @@ def register():
 def unregister():
     bpy.utils.unregister_class(OBJECT_PT_Meadow)
     
+    bpy.utils.unregister_class(EstimateMaxSamplesOperator)
     bpy.types.INFO_MT_add.remove(menu_generate_meadow)
     bpy.utils.unregister_class(MakeBlobsOperator)
     bpy.utils.unregister_class(DeleteBlobsOperator)
index d7dafe1..e909006 100644 (file)
@@ -24,6 +24,16 @@ from math import *
 def ifloor(x):
     return int(x) if x >= 0.0 else int(x) - 1
 
+def iceil(x):
+    return int(x) + 1 if x >= 0.0 else int(x)
+
+# based on http://code.activestate.com/recipes/578114-round-number-to-specified-number-of-significant-di/
+def round_sigfigs(num, sig_figs):
+    if num != 0:
+        return round(num, -int(floor(log10(abs(num))) - (sig_figs - 1)))
+    else:
+        return 0  # Can't take the log of 0
+
 class ObjectSelection():
     def __enter__(self):
         scene = bpy.context.scene