scratch-html5/js/primitives/MotionAndPenPrims.js
2013-11-01 23:14:39 -04:00

326 lines
11 KiB
JavaScript

// Copyright (C) 2013 Massachusetts Institute of Technology
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 2,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
'use strict';
var MotionAndPenPrims = function() {};
MotionAndPenPrims.prototype.addPrimsTo = function(primTable) {
primTable['forward:'] = this.primMove;
primTable['turnLeft:'] = this.primTurnLeft;
primTable['turnRight:'] = this.primTurnRight;
primTable['heading:'] = this.primSetDirection;
primTable["pointTowards:"] = this.primPointTowards;
primTable["gotoX:y:"] = this.primGoTo;
primTable["gotoSpriteOrMouse:"] = this.primGoToSpriteOrMouse;
primTable["glideSecs:toX:y:elapsed:from:"] = this.primGlide;
primTable["changeXposBy:"] = this.primChangeX;
primTable["xpos:"] = this.primSetX;
primTable["changeYposBy:"] = this.primChangeY;
primTable["ypos:"] = this.primSetY;
primTable["bounceOffEdge"] = this.primBounceOffEdge;
primTable["setRotationStyle"] = this.primSetRotationStyle;
primTable["xpos"] = this.primXPosition;
primTable["ypos"] = this.primYPosition;
primTable["heading"] = this.primDirection;
primTable["clearPenTrails"] = this.primClear;
primTable["putPenDown"] = this.primPenDown;
primTable["putPenUp"] = this.primPenUp;
primTable["penColor:"] = this.primSetPenColor;
primTable["setPenHueTo:"] = this.primSetPenHue;
primTable["changePenHueBy:"] = this.primChangePenHue;
primTable["setPenShadeTo:"] = this.primSetPenShade;
primTable["changePenShadeBy:"] = this.primChangePenShade;
primTable["penSize:"] = this.primSetPenSize;
primTable["changePenSizeBy:"] = this.primChangePenSize;
primTable["stampCostume"] = this.primStamp;
primTable["stampTransparent"] = this.primStampTransparent;
};
MotionAndPenPrims.prototype.primMove = function(b) {
var s = interp.targetSprite();
var radians = (90 - s.direction) * Math.PI / 180;
var d = interp.arg(b, 0);
moveSpriteTo(s, s.scratchX + d * Math.cos(radians), s.scratchY + d * Math.sin(radians));
if (s.visible) interp.redraw();
};
MotionAndPenPrims.prototype.primTurnLeft = function(b) {
var s = interp.targetSprite();
var d = s.direction - interp.arg(b, 0);
s.setDirection(d);
if (s.visible) interp.redraw();
};
MotionAndPenPrims.prototype.primTurnRight = function(b) {
var s = interp.targetSprite();
var d = s.direction + interp.arg(b, 0);
s.setDirection(d);
if (s.visible) interp.redraw();
};
MotionAndPenPrims.prototype.primSetDirection = function(b) {
var s = interp.targetSprite();
s.setDirection(interp.arg(b, 0));
if (s.visible) interp.redraw();
};
MotionAndPenPrims.prototype.primPointTowards = function(b) {
var s = interp.targetSprite();
var p = mouseOrSpritePosition(interp.arg(b, 0));
if (s == null || p == null) return;
var dx = p.x - s.scratchX;
var dy = p.y - s.scratchY;
var angle = 90 - Math.atan2(dy, dx) * 180 / Math.PI;
s.setDirection(angle);
if (s.visible) interp.redraw();
};
MotionAndPenPrims.prototype.primGoTo = function(b) {
var s = interp.targetSprite();
if (s != null) moveSpriteTo(s, interp.arg(b, 0), interp.arg(b, 1));
};
MotionAndPenPrims.prototype.primGoToSpriteOrMouse = function(b) {
var s = interp.targetSprite();
var p = mouseOrSpritePosition(interp.arg(b, 0));
if (s == null || p == null) return;
moveSpriteTo(s, p.x, p.y);
};
MotionAndPenPrims.prototype.primGlide = function(b) {
var s = interp.targetSprite();
if (s == null) return;
if (interp.activeThread.firstTime) {
var secs = interp.arg(b, 0);
var destX = interp.arg(b, 1);
var destY = interp.arg(b, 2);
if (secs <= 0) {
moveSpriteTo(s, destX, destY);
return;
}
// record state: [0]start msecs, [1]duration, [2]startX, [3]startY, [4]endX, [5]endY
interp.activeThread.tmpObj = [interp.currentMSecs, 1000 * secs, s.scratchX, s.scratchY, destX, destY];
interp.startTimer(secs);
} else {
var state = interp.activeThread.tmpObj;
if (!interp.checkTimer()) {
// in progress: move to intermediate position along path
var frac = (interp.currentMSecs - state[0]) / state[1];
var newX = state[2] + frac * (state[4] - state[2]);
var newY = state[3] + frac * (state[5] - state[3]);
moveSpriteTo(s, newX, newY);
} else {
// finished: move to final position and clear state
moveSpriteTo(s, state[4], state[5]);
interp.activeThread.tmpObj = null;
}
}
};
MotionAndPenPrims.prototype.primChangeX = function(b) {
var s = interp.targetSprite();
if (s != null) moveSpriteTo(s, s.scratchX + interp.arg(b, 0), s.scratchY);
};
MotionAndPenPrims.prototype.primSetX = function(b) {
var s = interp.targetSprite();
if (s != null) moveSpriteTo(s, interp.arg(b, 0), s.scratchY);
};
MotionAndPenPrims.prototype.primChangeY = function(b) {
var s = interp.targetSprite();
if (s != null) moveSpriteTo(s, s.scratchX, s.scratchY + interp.arg(b, 0));
};
MotionAndPenPrims.prototype.primSetY = function(b) {
var s = interp.targetSprite();
if (s != null) moveSpriteTo(s, s.scratchX, interp.arg(b, 0));
};
MotionAndPenPrims.prototype.primBounceOffEdge = function(b) {
var s = interp.targetSprite();
if (s == null) return;
if (!turnAwayFromEdge(s)) return;
ensureOnStageOnBounce(s);
if (s.visible) interp.redraw();
};
MotionAndPenPrims.prototype.primSetRotationStyle = function(b) {
var s = interp.targetSprite();
if (s == null) return;
var request = interp.arg(b, 0);
var rotationStyle = 'normal';
if (request == 'all around') rotationStyle = 'normal';
else if (request == 'left-right') rotationStyle = 'leftRight';
else if (request == 'none') rotationStyle = 'none';
s.setRotationStyle(rotationStyle);
};
MotionAndPenPrims.prototype.primXPosition = function(b) {
var s = interp.targetSprite();
return s != null ? s.scratchX : 0;
};
MotionAndPenPrims.prototype.primYPosition = function(b) {
var s = interp.targetSprite();
return s != null ? s.scratchY : 0;
};
MotionAndPenPrims.prototype.primDirection = function(b) {
var s = interp.targetSprite();
return s != null ? s.direction : 0;
};
MotionAndPenPrims.prototype.primClear = function(b) {
runtime.stage.clearPenStrokes();
interp.redraw();
};
MotionAndPenPrims.prototype.primPenDown = function(b) {
var s = interp.targetSprite();
if (s != null) s.penIsDown = true;
stroke(s, s.scratchX, s.scratchY, s.scratchX + 0.2, s.scratchY + 0.2);
interp.redraw();
};
MotionAndPenPrims.prototype.primPenUp = function(b) {
var s = interp.targetSprite();
if (s != null) s.penIsDown = false;
};
MotionAndPenPrims.prototype.primSetPenColor = function(b) {
var s = interp.targetSprite();
if (s != null) s.setPenColor(interp.arg(b, 0));
};
MotionAndPenPrims.prototype.primSetPenHue = function(b) {
var s = interp.targetSprite();
if (s != null) s.setPenHue(interp.arg(b, 0));
};
MotionAndPenPrims.prototype.primChangePenHue = function(b) {
var s = interp.targetSprite();
if (s != null) s.setPenHue(s.penHue + interp.arg(b, 0));
};
MotionAndPenPrims.prototype.primSetPenShade = function(b) {
var s = interp.targetSprite();
if (s != null) s.setPenShade(interp.arg(b, 0));
};
MotionAndPenPrims.prototype.primChangePenShade = function(b) {
var s = interp.targetSprite();
if (s != null) s.setPenShade(s.penShade + interp.arg(b, 0));
};
MotionAndPenPrims.prototype.primSetPenSize = function(b) {
var s = interp.targetSprite();
var w = Math.max(0, Math.min(interp.arg(b, 0), 100));
if (s != null) s.penWidth = w;
};
MotionAndPenPrims.prototype.primChangePenSize = function(b) {
var s = interp.targetSprite();
var w = Math.max(0, Math.min(s.penWidth + interp.arg(b, 0), 100));
if (s != null) s.penWidth = w;
};
MotionAndPenPrims.prototype.primStamp = function(b) {
var s = interp.targetSprite();
s.stamp(runtime.stage.lineCache, 100);
};
MotionAndPenPrims.prototype.primStampTransparent = function(b) {
var s = interp.targetSprite();
var transparency = Math.max(0, Math.min(interp.arg(b, 0), 100));
var alpha = 100 - transparency;
s.stamp(runtime.stage.lineCache, alpha);
};
// Helpers
var stroke = function(s, oldX, oldY, newX, newY) {
runtime.stage.stroke([oldX, oldY], [newX, newY], s.penWidth, s.penColorCache);
interp.redraw();
};
var mouseOrSpritePosition = function(arg) {
if (arg == "_mouse_") {
var w = runtime.stage;
return new Point(runtime.mousePos[0], runtime.mousePos[1]);
} else {
var s = runtime.spriteNamed(arg);
if (s == null) return null;
return new Point(s.scratchX, s.scratchY);
}
return null;
};
var moveSpriteTo = function(s, newX, newY) {
var oldX = s.scratchX;
var oldY = s.scratchY;
s.setXY(newX, newY);
s.keepOnStage();
if (s.penIsDown) stroke(s, oldX, oldY, s.scratchX, s.scratchY);
if (s.penIsDown || s.visible) interp.redraw();
};
var turnAwayFromEdge = function(s) {
// turn away from the nearest edge if it's close enough; otherwise do nothing
// Note: comparisions are in the stage coordinates, with origin (0, 0)
// use bounding rect of the sprite to account for costume rotation and scale
var r = s.getRect();
// measure distance to edges
var d1 = Math.max(0, r.left);
var d2 = Math.max(0, r.top);
var d3 = Math.max(0, 480 - r.right);
var d4 = Math.max(0, 360 - r.bottom);
// find the nearest edge
var e = 0, minDist = 100000;
if (d1 < minDist) { minDist = d1; e = 1; }
if (d2 < minDist) { minDist = d2; e = 2; }
if (d3 < minDist) { minDist = d3; e = 3; }
if (d4 < minDist) { minDist = d4; e = 4; }
if (minDist > 0) return false; // not touching to any edge
// point away from nearest edge
var radians = (90 - s.direction) * Math.PI / 180;
var dx = Math.cos(radians);
var dy = -Math.sin(radians);
if (e == 1) { dx = Math.max(0.2, Math.abs(dx)); }
if (e == 2) { dy = Math.max(0.2, Math.abs(dy)); }
if (e == 3) { dx = 0 - Math.max(0.2, Math.abs(dx)); }
if (e == 4) { dy = 0 - Math.max(0.2, Math.abs(dy)); }
var newDir = Math.atan2(dy, dx) * 180 / Math.PI + 90;
s.direction = newDir;
return true;
};
var ensureOnStageOnBounce = function(s) {
var r = s.getRect();
if (r.left < 0) moveSpriteTo(s, s.scratchX - r.left, s.scratchY);
if (r.top < 0) moveSpriteTo(s, s.scratchX, s.scratchY + r.top);
if (r.right > 480) {
moveSpriteTo(s, s.scratchX - (r.right - 480), s.scratchY);
}
if (r.bottom > 360) {
moveSpriteTo(s, s.scratchX, s.scratchY + (r.bottom - 360));
}
};