deepcerulean/minotaur

View on GitHub
lib/minotaur/geometry/subdivider.rb

Summary

Maintainability
A
35 mins
Test Coverage
module Minotaur
  module Geometry
    #
    #   subdivides a region into smaller ones!
    #
    class Subdivider
      include Directions
      include Support::FateHelpers

      attr_accessor :count, :min_subdivision_length, :variance, :recursive

      def initialize(opts={})
        self.count                  = opts.delete(:count)                   { 2 }
        self.min_subdivision_length = opts.delete(:min_subdivision_length)  { 3 }
        self.variance               = opts.delete(:variance)                { 0 }
        self.recursive              = opts.delete(:recursive)               { true }
      end

      def depth
        @depth ||= 0
      end

      def reached_size_limit?(space)
        space.width < min_subdivision_length || space.height < min_subdivision_length
      end

      MAX_DEPTH = 25
      def subdivide(space,opts={})
        depth = opts.delete(:depth) { 0 }
        depth = depth + 1
        return [space] if reached_size_limit?(space) || (recursive && depth > MAX_DEPTH)

        direction  = opts.delete(:direction) do
          favorable_split_direction(space, min_subdivision_length)
        end

        resultant_subdivisions = if direction == 'horizontal'
          horizontal_subdivide(space)
        elsif direction == 'vertical'
          vertical_subdivide(space)
        end

        if recursive
          resultant_subdivisions.map! do |subdivision|
            subdivide(subdivision,opts.merge!({depth: depth}))
          end
        end

        resultant_subdivisions.flatten
      end

      private

      def favorable_split_direction(space,min_edge_length)
        if space.width < min_edge_length
          'horizontal'
        elsif space.height < min_edge_length
          'vertical'
        else
          coinflip? ? 'horizontal' : 'vertical'
        end
      end

      def vertical_subdivide(space)
        resultant_subdivisions = []
        total_width = 0
        split_and_mutate!(space.width).each do |next_width|
          resultant_subdivisions << space.class.new(
            location: space.location.translate(EAST,total_width),
            width:    next_width,
            height:   space.height
          )
          total_width = total_width + next_width
        end
        resultant_subdivisions
      end

      def horizontal_subdivide(space)
        resultant_subdivisions = []
        total_height = 0
        split_and_mutate!(space.height).each do |next_height|
          resultant_subdivisions << space.class.new(
            location: space.location.translate(SOUTH,total_height),
            width:    space.width,
            height:   next_height
          )
          total_height = total_height + next_height
        end
        resultant_subdivisions
      end

      def splitter
        @splitter ||= Geometry::Splitter.new({count: count, min_subdivision_length: min_subdivision_length})
      end

      def mutator
        @mutator ||= Geometry::Mutator.new({variance: variance, min_subdivision_length: min_subdivision_length})
      end

      def split_and_mutate!(magnitude)
        arr = splitter.split!(magnitude)
        mutator.mutate!(arr)
        arr
      end
    end
  end
end