From 2226fda19e32ef19f26e858ed71b51f6a9e66066 Mon Sep 17 00:00:00 2001
From: Tim Mickel <tim.mickel@gmail.com>
Date: Wed, 28 Sep 2016 17:09:04 -0400
Subject: [PATCH] Implement rotation style (#223)

---
 src/blocks/scratch3_motion.js |  5 +++
 src/import/sb2import.js       | 10 +++++
 src/sprites/clone.js          | 79 +++++++++++++++++++++++++++++++++--
 3 files changed, 90 insertions(+), 4 deletions(-)

diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js
index 10679750d..8b8e7356e 100644
--- a/src/blocks/scratch3_motion.js
+++ b/src/blocks/scratch3_motion.js
@@ -22,6 +22,7 @@ Scratch3MotionBlocks.prototype.getPrimitives = function() {
         'motion_turnleft': this.turnLeft,
         'motion_pointindirection': this.pointInDirection,
         'motion_glidesecstoxy': this.glide,
+        'motion_setrotationstyle': this.setRotationStyle,
         'motion_changexby': this.changeX,
         'motion_setx': this.setX,
         'motion_changeyby': this.changeY,
@@ -96,6 +97,10 @@ Scratch3MotionBlocks.prototype.glide = function (args, util) {
     }
 };
 
+Scratch3MotionBlocks.prototype.setRotationStyle = function (args, util) {
+    util.target.setRotationStyle(args.STYLE);
+};
+
 Scratch3MotionBlocks.prototype.changeX = function (args, util) {
     var dx = Cast.toNumber(args.DX);
     util.target.setXY(util.target.x + dx, util.target.y);
diff --git a/src/import/sb2import.js b/src/import/sb2import.js
index dfa6d25e2..79e135a7b 100644
--- a/src/import/sb2import.js
+++ b/src/import/sb2import.js
@@ -6,6 +6,7 @@
  */
 
 var Blocks = require('../engine/blocks');
+var Clone = require('../sprites/clone');
 var Sprite = require('../sprites/sprite');
 var Color = require('../util/color.js');
 var uid = require('../util/uid');
@@ -110,6 +111,15 @@ function parseScratchObject (object, runtime, topLevel) {
     if (object.hasOwnProperty('currentCostumeIndex')) {
         target.currentCostume = Math.round(object.currentCostumeIndex);
     }
+    if (object.hasOwnProperty('rotationStyle')) {
+        if (object.rotationStyle == 'none') {
+            target.rotationStyle = Clone.ROTATION_STYLE_NONE;
+        } else if (object.rotationStyle == 'leftRight') {
+            target.rotationStyle = Clone.ROTATION_STYLE_LEFT_RIGHT;
+        } else if (object.rotationStyle == 'normal') {
+            target.rotationStyle = Clone.ROTATION_STYLE_ALL_AROUND;
+        }
+    }
     target.isStage = topLevel;
     target.updateAllDrawableProperties();
     // The stage will have child objects; recursively process them.
diff --git a/src/sprites/clone.js b/src/sprites/clone.js
index f94015c64..14d7b366c 100644
--- a/src/sprites/clone.js
+++ b/src/sprites/clone.js
@@ -97,6 +97,30 @@ Clone.prototype.size = 100;
  */
 Clone.prototype.currentCostume = 0;
 
+/**
+ * Rotation style for "all around"/spinning.
+ * @enum
+ */
+Clone.ROTATION_STYLE_ALL_AROUND = 'all around';
+
+/**
+ * Rotation style for "left-right"/flipping.
+ * @enum
+ */
+Clone.ROTATION_STYLE_LEFT_RIGHT = 'left-right';
+
+/**
+ * Rotation style for "no rotation."
+ * @enum
+ */
+Clone.ROTATION_STYLE_NONE = 'don\'t rotate';
+
+/**
+ * Current rotation style.
+ * @type {!string}
+ */
+Clone.prototype.rotationStyle = Clone.ROTATION_STYLE_ALL_AROUND;
+
 /**
  * Map of current graphic effect values.
  * @type {!Object.<string, number>}
@@ -130,6 +154,26 @@ Clone.prototype.setXY = function (x, y) {
     }
 };
 
+/**
+ * Get the rendered direction and scale, after applying rotation style.
+ * @return {Object<string, number>} Direction and scale to render.
+ */
+Clone.prototype._getRenderedDirectionAndScale = function () {
+    // Default: no changes to `this.direction` or `this.scale`.
+    var finalDirection = this.direction;
+    var finalScale = [this.size, this.size];
+    if (this.rotationStyle == Clone.ROTATION_STYLE_NONE) {
+        // Force rendered direction to be 90.
+        finalDirection = 90;
+    } else if (this.rotationStyle === Clone.ROTATION_STYLE_LEFT_RIGHT) {
+        // Force rendered direction to be 90, and flip drawable if needed.
+        finalDirection = 90;
+        var scaleFlip = (this.direction < 0) ? -1 : 1;
+        finalScale = [scaleFlip * this.size, this.size];
+    }
+    return {direction: finalDirection, scale: finalScale};
+};
+
 /**
  * Set the direction of a clone.
  * @param {!number} direction New direction of clone.
@@ -141,8 +185,10 @@ Clone.prototype.setDirection = function (direction) {
     // Keep direction between -179 and +180.
     this.direction = MathUtil.wrapClamp(direction, -179, 180);
     if (this.renderer) {
+        var renderedDirectionScale = this._getRenderedDirectionAndScale();
         this.renderer.updateDrawableProperties(this.drawableID, {
-            direction: this.direction
+            direction: renderedDirectionScale.direction,
+            scale: renderedDirectionScale.scale
         });
     }
 };
@@ -191,8 +237,10 @@ Clone.prototype.setSize = function (size) {
     // Keep size between 5% and 535%.
     this.size = MathUtil.clamp(size, 5, 535);
     if (this.renderer) {
+        var renderedDirectionScale = this._getRenderedDirectionAndScale();
         this.renderer.updateDrawableProperties(this.drawableID, {
-            scale: [this.size, this.size]
+            direction: renderedDirectionScale.direction,
+            scale: renderedDirectionScale.scale
         });
     }
 };
@@ -241,6 +289,27 @@ Clone.prototype.setCostume = function (index) {
     }
 };
 
+/**
+ * Update the rotation style for this clone.
+ * @param {!string} rotationStyle New rotation style.
+ */
+Clone.prototype.setRotationStyle = function (rotationStyle) {
+    if (rotationStyle == Clone.ROTATION_STYLE_NONE) {
+        this.rotationStyle = Clone.ROTATION_STYLE_NONE;
+    } else if (rotationStyle == Clone.ROTATION_STYLE_ALL_AROUND) {
+        this.rotationStyle = Clone.ROTATION_STYLE_ALL_AROUND;
+    } else if (rotationStyle == Clone.ROTATION_STYLE_LEFT_RIGHT) {
+        this.rotationStyle = Clone.ROTATION_STYLE_LEFT_RIGHT;
+    }
+    if (this.renderer) {
+        var renderedDirectionScale = this._getRenderedDirectionAndScale();
+        this.renderer.updateDrawableProperties(this.drawableID, {
+            direction: renderedDirectionScale.direction,
+            scale: renderedDirectionScale.scale
+        });
+    }
+};
+
 /**
  * Get a costume index of this clone, by name of the costume.
  * @param {?string} costumeName Name of a costume.
@@ -261,10 +330,11 @@ Clone.prototype.getCostumeIndexByName = function (costumeName) {
  */
 Clone.prototype.updateAllDrawableProperties = function () {
     if (this.renderer) {
+        var renderedDirectionScale = this._getRenderedDirectionAndScale();
         this.renderer.updateDrawableProperties(this.drawableID, {
             position: [this.x, this.y],
-            direction: this.direction,
-            scale: [this.size, this.size],
+            direction: renderedDirectionScale.direction,
+            scale: renderedDirectionScale.scale,
             visible: this.visible,
             skin: this.sprite.costumes[this.currentCostume].skin
         });
@@ -326,6 +396,7 @@ Clone.prototype.makeClone = function () {
     newClone.visible = this.visible;
     newClone.size = this.size;
     newClone.currentCostume = this.currentCostume;
+    newClone.rotationStyle = this.rotationStyle;
     newClone.effects = JSON.parse(JSON.stringify(this.effects));
     newClone.variables = JSON.parse(JSON.stringify(this.variables));
     newClone.lists = JSON.parse(JSON.stringify(this.lists));