summary history branches tags files
commit:b03bdee67ce60308c5856ed64f0c25b3e18b1b1c
author:Trevor Bentley
committer:Trevor Bentley
date:Fri Mar 16 19:08:19 2012 -0400
parents:2bae658dc327b7aa444e0dd6e80bf9cf6cdc07ff
Added functions to define an 'oblong' level from a vector of angles.  Allows creation of fairly arbitrary symmetric levels.  Added tons of comments explaining the mess I've made.
diff --git a/tempest/levels.cljs b/tempest/levels.cljs
line changes: +189/-37
index f685924..d7f73f3
--- a/tempest/levels.cljs
+++ b/tempest/levels.cljs
@@ -5,8 +5,38 @@
             [goog.events.EventType :as event-type]
             [goog.math :as math]))
 
-(def *default-line-length* 80)
-(def *default-length-fn* #(* 4 %))
+;;;;
+;;;; Levels are defined by a vector of polar coordinates [r theta],
+;;;; which are used to build a vector of 'segments' that form a level.
+;;;;
+;;;; Levels can be manually specified by building a vector of lines
+;;;; manually.
+;;;;
+;;;; Some types of levels can be built automatically by calling helper
+;;;; functions in this module with various parameters.
+;;;;
+;;;; Levels are drawn radially, from the center point of the canvas.
+;;;;
+;;;; Levels are stored in the *levels* vector as a list of maps.
+;;;;
+;;;; Terminology in this module:
+;;;;
+;;;; "length" and "depth" both refer to how far from origin the inner
+;;;; line is drawn, in pixels.
+;;;;
+;;;; "length-fn" is a function to determine how long between inner and
+;;;; outer line.  Takes one argument 'r' to the inner line.  Returns
+;;;; 'r' to the outer line.  Default is 'inner r' multiplied by 4.
+;;;;
+;;;; "width" is how wide, in pixels, the outer line segment is.
+;;;;
+
+
+(def ^{:doc "Default length, in pixels, from origin to inner line."}
+  *default-line-length* 80)
+
+(def ^{:doc "Default length function, returns argument*4"}
+  *default-length-fn* #(* 4 %))
 
 (defn build-unlinked-segment-list [max-x]
   (vec ((fn [x segments]
@@ -29,32 +59,149 @@
 (defn deg-to-rad [deg]
   (/ (* deg 3.14159265358979) 180))
 
-(defn theta0 [width depth]
+
+
+;; "Flat" level functions follow:
+;;
+;; Functions for generating "flat" levels: levels where the edge appears as
+;; a straight line.  Something like this garbage:
+;;
+;;       ___________________________
+;;      /  /  / |  |  |  |  |   \   \
+;;     /  |  |  |  |  |  |   |   \   \
+;;    /  /  |  |   |  |   |   |   |   \
+;;   /  /  /   |  |   |   |   |    |   \
+;;  /  /  /   |   |   |   |    |    |   \
+;; ----------------------------------------
+;;
+;; Flat levels always start with a line dropped straight down, and build
+;; out symmetrically from there.  The width of segments at the "outer" edge
+;; (closer to the player) is uniform.
+;;
+
+(comment (defn theta0 [width depth]
   "Return theta0 in degrees."
-  (rad-to-deg (js/Math.atan (/ width depth))))
+  (rad-to-deg (js/Math.atan (/ width depth)))))
 
-(defn nth-theta [n width depth]
+(defn theta-flat [n width depth]
+  "Return theta for segment n.  Width is width of segment at the outer edge,
+   closest to the player, and depth is the distance to origin."
   (js/Math.round (rad-to-deg (js/Math.atan (/ (* (+ n 1) width) depth)))))
 
-(defn r-for-nth-theta [nth-theta depth]
-  (js/Math.round (/ depth (js/Math.cos (deg-to-rad nth-theta)))))
+(defn r-flat [theta depth]
+  "Return r for given theta (see theta-flat)."
+  (js/Math.round (/ depth (js/Math.cos (deg-to-rad theta)))))
 
-(defn nth-straight-line [n width depth angle-center angle-multiplier]
-  "Returns [r theta] for straight line segment n."
-  (let [th (nth-theta n width depth)]
-    [(r-for-nth-theta th depth) (+ angle-center (* th angle-multiplier))]))
+(defn r-theta-pair-flat [n width depth angle-center angle-multiplier]
+  "Returns [r theta] for nth straight line segment.  angle-center is the
+   angle that theta should be in reference to (probably 270 degrees, a line
+   straight down), and angle-multiplier should be -1 to built left or 1 to
+   build right."
+  (let [th (theta-flat n width depth)]
+    [(r-flat th depth) (+ angle-center (* th angle-multiplier))]))
 
 (defn flat-level [segment-count segment-width segment-depth]
-  (concat (reverse (map #(nth-straight-line % segment-width segment-depth 270 -1) (range segment-count)))
+  "Return a list of line segments representing a flat level with segment-count
+   segments ON EACH SIDE OF CENTER (2*segment-count total), width at the player
+   edge of segment-width, and distance from origin to inner-edge as
+   segment-depth"
+  (concat (reverse (map #(r-theta-pair-flat % segment-width segment-depth 270 -1) (range segment-count)))
           [[80 270]]
-          (map #(nth-straight-line % segment-width segment-depth 270 1) (range segment-count))))
+          (map #(r-theta-pair-flat % segment-width segment-depth 270 1) (range segment-count))))
+
+
+
+
+;; "Oblong" level functions follow:
+;;
+;; Functions for generating oblong triangles using Law of Cosines.
+;; Use to generate arbitrary levels from a list of angles, gamma(0)..gamma(N),
+;; where gamma is the angle between the previous line segment 'towards the player'
+;; and the line segment that makes the 'width' of the segment.
+;;             ____
+;;            /    / \
+;;           /    /   \ 
+;;          /    /     /
+;;         /    /     /
+;; gamma1-/->  /     /
+;;       /____/     /
+;;             \  </--- gamma0
+;;              \ /
+;;
+;; Named 'oblong' after the triangles formed when the lines are extended to origin.
+;; As opposed to the flat-levels, which are constructed of right triangles.
+;;
+;; This is a terrible non-obvious way to construct a level, but does let you
+;; construct complex, symmetric structures (both open and closed) with just
+;; a list of angles.
+;;
+
+(defn r-oblong [gamma width r0]
+  "Calculate the next radius of an oblong triangle.  Depends on the gamma
+   specified for this segment, the width of the segment, and the previous
+   radius r0.  For the first segment, r0 should be straight down
+   (270 degrees)."
+  (js/Math.sqrt (+ (js/Math.pow width 2)
+                   (js/Math.pow r0 2)
+                   (* -2 width r0 (js/Math.cos (deg-to-rad gamma)))
+                   )))
+
+(defn theta-oblong [width r1 r0 theta0 sumfn]
+  "Calculate the next theta, the angle (in degrees) in relation to origin,
+   for an oblong triangle.  This depends on the width of the segment, the
+   previous radius r0, the current radius r1 (see r-oblong), the previous
+   theta theta0.  Provide a function, either + or -, to determine the
+   direction.  - builds segments clockwise, + builds counterclockwise."
+  (sumfn theta0
+     (rad-to-deg (js/Math.acos
+                  (/  (+ (js/Math.pow r1 2)
+                         (js/Math.pow r0 2)
+                         (* -1 (js/Math.pow width 2)))
+                      (* 2 r1 r0))))))
+  
+(defn r-theta-pair-oblong [gamma width r0 theta0 sumfn]
+  "Return a vector [r theta] representing a line segment.  See
+   theta-oblong and r-oblong for parameters."
+  (let [r1 (r-oblong gamma width r0)]
+    (vec (map js/Math.round [r1 (theta-oblong width r1 r0 theta0 sumfn)]))
+    ))
 
+(defn oblong-half-level [gammas width height sumfn]
+  "Builds vector of line segments in relation to a line dropped straight down,
+   with the angles given in gammas.  Only builds in one direction."
+  ((fn [gammas r0 theta0 segments]
+     (if (= (count gammas) 0)
+       segments
+       (let [pair (r-theta-pair-oblong (first gammas) width r0 theta0 sumfn)]
+         (recur (rest gammas) (first pair) (last pair) (cons pair segments)))))
+   gammas height 270 []))
 
-;; short radius, angle in degrees
-;; straight lines: r = *default-line-length*/abs(cos(270-angle))
+(defn oblong-level [gammas width height]
+  "Builds a full level from the angles given in gammas.  Level is symmetric,
+   and can be open or closed.  Always starts with a straight down line."
+  (concat
+   (oblong-half-level gammas width height -)
+   [[height 270]]
+   (reverse (oblong-half-level gammas width height +))))
 
+
+
+
+
+
+;;
+;;
+;; BELOW HERE ARE LEVELS
+;;
+;;
+;;
+;;
+;;
+
+;; Flat level with 8 segments
 (def *level1_lines* (vec (flat-level 4 15 80)))
 
+;; Custom level, a circle
 (def *level2_lines*
   [[*default-line-length* 0]
    [*default-line-length* 18]
@@ -78,29 +225,34 @@
    [*default-line-length* 342]
    ])
 
+;; Flat level with 14 segments
 (def *level3_lines* (vec (flat-level 7 15 80)))
 
-(comment (def *level3_lines*
-  [[111 (- 270 44)]
-   [95 (- 270 33)]
-   [84 (- 270 18)]
-   [80 270]
-   [84 (+ 270 18)]
-   [95 (+ 270 33)]
-   [111 (+ 270 44)]
-   ]))
+;; Oblong level, an open "W"
+(def *level4_lines* (vec (oblong-level [135 105 90 33] 15 80)))
+
+;; Oblong level, open "eagle wings"
+(def *level5_lines* (vec (oblong-level [135 100 90 90 90 85 80 75] 15 80)))
+
+;; Oblong level, a closed, spikey flower
+(def *level6_lines* (vec (oblong-level [135 45 90 135 45 90 135 45 90 135 45 90
+                                        135 45 90 135 45 90 135 45 90 135 45 135] 15 80)))
+
+
+
+(defn make-level-entry [lines loops?]
+  "Make a map that defines a level.  A level contains a vector of lines, a
+   vector of segments constructed of pairs of lines, and a length function.
+   This function takes a vector of lines, and a boolean specifying whether
+   the level is a closed loop, or open."
+  {:lines lines
+   :segments (build-segment-list (- (count lines) 1) loops?)
+   :length-fn *default-length-fn*})
 
 (def *levels*
-  [
-   {:lines *level1_lines*
-    :segments (build-segment-list (- (count *level1_lines*) 1) false)
-    :length-fn *default-length-fn*}
-   
-   {:lines *level2_lines*
-    :segments (build-segment-list (- (count *level2_lines*) 1) true)
-    :length-fn *default-length-fn*}
-
-   {:lines *level3_lines*
-    :segments (build-segment-list (- (count *level3_lines*) 1) false)
-    :length-fn *default-length-fn*}
-])
+  [ (make-level-entry *level1_lines* false)
+    (make-level-entry *level2_lines* true)
+    (make-level-entry *level3_lines* false)
+    (make-level-entry *level4_lines* false)
+    (make-level-entry *level5_lines* false)
+    (make-level-entry *level6_lines* true)])