<commit_before>// EMAIL NEWSLETTER SIGNUPS

// 1. Define all input fields
const emailBoxNames = [
	'hero',		// homepage hero email signup
	'event1',	// events pre-reg (top)
	'event2'	// events pre-reg (bottom)
]
const emailBoxFields = [
	'first_name',
	'last_name',
	'email',
	'questions',	// Only for homepage hero
	'phone'				// Only on events page
]

// 2. Assign enable button function to all relevant fields, and trigger submit if user presses enter
emailBoxNames.forEach(emailBoxName => {
	emailBoxFields.forEach(emailBoxField => {
		const field = getElementById(emailBoxName + '_' + emailBoxField)
		if (field) {
			field.addEventListener('keypress', e => {

				console.log(e.target.name)

				// Check to see if every field has been filled out correctly...
				const valid = emailBoxFields.every(checkEmailBoxField => {
					const field = getElementById(emailBoxName + '_' + checkEmailBoxField)
					// If field doesn’t exist, return true.
					if (!field)	return true
					// Make sure first and last names don’t contain spaces
					if (checkEmailBoxField === 'first_name' || checkEmailBoxField === 'last_name') {
						field.value = standardisationName(field.value)
					}
					// Set phone number according to national format
					else if (checkEmailBoxField === 'phone') {
						let iti = intlTelInput(field)
						console.log(iti)
						iti.setNumber(field.value)
						console.log(iti.getNumber())
						// Uses cleave.js
						// new Cleave(field, {
						// 	phone: true,
						// 	phoneRegionCode: 'US'
						// })
					}
					// return true if all fields have been filled out
					return (
						// First and last names have to have at least 2 characters each...
						((checkEmailBoxField === 'first_name' || checkEmailBoxField === 'last_name') && field.value.length > 1)
						// ...email has to be at least 4 characters and valid...
						|| (checkEmailBoxField === 'email' && field.value.length > 4 && standardisationEmail.test(field.value))
						// Any other fields have to be at least 1 character long.
						|| field.value.length > 0
					)
				})
				// Only show alert once every field has been touched...
				const everyFieldHasBeenFilledOut = emailBoxFields.every(checkEmailBoxField => !getElementById(emailBoxName + '_' + checkEmailBoxField) || getElementById(emailBoxName + '_' + checkEmailBoxField).value.length > 0)
				const buttonField = getElementById(emailBoxName + '_button')
				const alertField = getElementById(emailBoxName + '_alert')
				const emailBoxForm = getElementById(emailBoxName + '_form')
				// Only show alert if all fields have been filled out somewhat, but not yet validated
				if (!valid && everyFieldHasBeenFilledOut) {
					alertField.classList.remove('hidden')
					buttonField.disabled = true
					emailBoxForm.action = ''
					emailBoxForm.removeEventListener('click', () => null)
				}
				// ...and if all’s good to go, change button class to active, and submit emailBoxForm.
				else {
					alertField.classList.add('hidden')
					buttonField.disabled = false
					buttonField.addEventListener('click', () => {
						// If email box is used on homepage...
						if (emailBoxName === 'hero') {
							// ...collect email newsletter
							emailBoxForm.action = 'https://app.getresponse.com/add_subscriber.html'
							emailBoxForm.submit()
						}
						// ...otherwise, bring up reg form, since used in events page
						else {
							emailBoxFields.forEach(checkEmailBoxField => {
								// Transfer data onto reg form where it can be either used or cleared...
								const field = getElementById(emailBoxName + '_' + checkEmailBoxField)
								if (checkEmailBoxField === 'first_name') {
									eventFirstName.value = field.value
								}
								else if (checkEmailBoxField === 'last_name') {
									eventLastName.value = field.value
								}
								else if (checkEmailBoxField === 'email') {
									eventEmail.value = field.value
								}
								else if (checkEmailBoxField === 'phone') {
									eventMobile.value = field.value
								}
								// and clear it from email box for privacy.
								// if (field) {
								// 	field.value = ''
								// }
							})
							showRegForm()
						}
					})
				}
				const key = e.which || e.keyCode
				// If `enter` key is pressed, attempt to submit emailBoxForm
				if (key === 13) {
					const buttonField = getElementById(emailBoxName + '_button')
					buttonField.click()
				}
			})
		}
	})
})<commit_msg>032 - IntTel formatting<commit_after>// EMAIL NEWSLETTER SIGNUPS

// 1. Define all input fields
const emailBoxNames = [
	'hero',		// homepage hero email signup
	'event1',	// events pre-reg (top)
	'event2'	// events pre-reg (bottom)
]
const emailBoxFields = [
	'first_name',
	'last_name',
	'email',
	'questions',	// Only for homepage hero
	'phone'				// Only on events page
]

// 2. Assign enable button function to all relevant fields, and trigger submit if user presses enter
emailBoxNames.forEach(emailBoxName => {
	emailBoxFields.forEach(emailBoxField => {
		const field = getElementById(emailBoxName + '_' + emailBoxField)
		if (field) {
			field.addEventListener('keypress', e => {

				// Make sure first and last names don’t contain spaces
				if (e.target.name === 'first_name' || e.target.name === 'last_name') {
					field.value = standardisationName(field.value)
				}
				// Set phone number according to national format
				else if (e.target.name === 'phone') {
					let iti = intlTelInput(field)
					console.log(iti)
					iti.setNumber(field.value)
					console.log(iti.getNumber())
					// Uses cleave.js
					// new Cleave(field, {
					// 	phone: true,
					// 	phoneRegionCode: 'US'
					// })
				}

				// Check to see if every field has been filled out correctly...
				const valid = emailBoxFields.every(checkEmailBoxField => {
					const field = getElementById(emailBoxName + '_' + checkEmailBoxField)
					// If field doesn’t exist, return true.
					if (!field)	return true
					// return true if all fields have been filled out
					return (
						// First and last names have to have at least 2 characters each...
						((checkEmailBoxField === 'first_name' || checkEmailBoxField === 'last_name') && field.value.length > 1)
						// ...email has to be at least 4 characters and valid...
						|| (checkEmailBoxField === 'email' && field.value.length > 4 && standardisationEmail.test(field.value))
						// Any other fields have to be at least 1 character long.
						|| field.value.length > 0
					)
				})
				// Only show alert once every field has been touched...
				const everyFieldHasBeenFilledOut = emailBoxFields.every(checkEmailBoxField => !getElementById(emailBoxName + '_' + checkEmailBoxField) || getElementById(emailBoxName + '_' + checkEmailBoxField).value.length > 0)
				const buttonField = getElementById(emailBoxName + '_button')
				const alertField = getElementById(emailBoxName + '_alert')
				const emailBoxForm = getElementById(emailBoxName + '_form')
				// Only show alert if all fields have been filled out somewhat, but not yet validated
				if (!valid && everyFieldHasBeenFilledOut) {
					alertField.classList.remove('hidden')
					buttonField.disabled = true
					emailBoxForm.action = ''
					emailBoxForm.removeEventListener('click', () => null)
				}
				// ...and if all’s good to go, change button class to active, and submit emailBoxForm.
				else {
					alertField.classList.add('hidden')
					buttonField.disabled = false
					buttonField.addEventListener('click', () => {
						// If email box is used on homepage...
						if (emailBoxName === 'hero') {
							// ...collect email newsletter
							emailBoxForm.action = 'https://app.getresponse.com/add_subscriber.html'
							emailBoxForm.submit()
						}
						// ...otherwise, bring up reg form, since used in events page
						else {
							emailBoxFields.forEach(checkEmailBoxField => {
								// Transfer data onto reg form where it can be either used or cleared...
								const field = getElementById(emailBoxName + '_' + checkEmailBoxField)
								if (checkEmailBoxField === 'first_name') {
									eventFirstName.value = field.value
								}
								else if (checkEmailBoxField === 'last_name') {
									eventLastName.value = field.value
								}
								else if (checkEmailBoxField === 'email') {
									eventEmail.value = field.value
								}
								else if (checkEmailBoxField === 'phone') {
									eventMobile.value = field.value
								}
								// and clear it from email box for privacy.
								// if (field) {
								// 	field.value = ''
								// }
							})
							showRegForm()
						}
					})
				}
				const key = e.which || e.keyCode
				// If `enter` key is pressed, attempt to submit emailBoxForm
				if (key === 13) {
					const buttonField = getElementById(emailBoxName + '_button')
					buttonField.click()
				}
			})
		}
	})
})<|endoftext|>


<commit_before>
export function PIXITextMetricsPlugIn() {

    let TextMetrics = PIXI.TextMetrics;

    let TextMetrics__orgWordWrap = PIXI.TextMetrics.wordWrap;
    TextMetrics.__breakAllWordWrap = __breakAllWordWrap;

    TextMetrics.wordWrap = function(text, style, canvas = TextMetrics._canvas) {
        return style.wordBreakAll ? TextMetrics.__breakAllWordWrap(text, style, canvas) : TextMetrics__orgWordWrap(text, style, canvas);
    };

    /**
     *
     * @param text
     * @param {PIXI.TextStyle | PIXITextStyle} style
     * @param canvas
     * @return {string}
     * @private
     */
    function __breakAllWordWrap(text, style, canvas) {
        const context = canvas.getContext('2d');

        let width = 0;
        let line = '';
        let lines = '';

        const cache = {};
        const { letterSpacing, whiteSpace } = style;

        // How to handle whitespaces
        const collapseSpaces = TextMetrics.collapseSpaces(whiteSpace);
        const collapseNewlines = TextMetrics.collapseNewlines(whiteSpace);

        // whether or not spaces may be added to the beginning of lines
        let canPrependSpaces = !collapseSpaces;

        // There is letterSpacing after every char except the last one
        // t_h_i_s_' '_i_s_' '_a_n_' '_e_x_a_m_p_l_e_' '_!
        // so for convenience the above needs to be compared to width + 1 extra letterSpace
        // t_h_i_s_' '_i_s_' '_a_n_' '_e_x_a_m_p_l_e_' '_!_
        // ________________________________________________
        // And then the final space is simply no appended to each line
        const wordWrapWidth = style.wordWrapWidth + letterSpacing;

        // break text into words, spaces and newline chars
        const tokens = TextMetrics.tokenize(text);

        for (let i = 0; i < tokens.length; i++) {
            // get the word, space or newlineChar
            let token = tokens[i];

            // if word is a new line
            if (TextMetrics.isNewline(token)) {
                // keep the new line
                if (!collapseNewlines) {
                    lines += TextMetrics.addLine(line);
                    canPrependSpaces = !collapseSpaces;
                    line = '';
                    width = 0;
                    continue;
                }

                // if we should collapse new lines
                // we simply convert it into a space
                token = ' ';
            }

            // if we should collapse repeated whitespaces
            if (collapseSpaces) {
                // check both this and the last tokens for spaces
                const currIsBreakingSpace = TextMetrics.isBreakingSpace(token);
                const lastIsBreakingSpace = TextMetrics.isBreakingSpace(line[line.length - 1]);

                if (currIsBreakingSpace && lastIsBreakingSpace) {
                    continue;
                }
            }

            // get word width from cache if possible
            const tokenWidth = TextMetrics.getFromCache(token, letterSpacing, cache, context);

            // word is longer than desired bounds
            if (width + tokenWidth > wordWrapWidth) {
                const characters = token.split('');
                for (let j = 0; j < characters.length; j++) {
                    let char = characters[j];
                    const characterWidth = TextMetrics.getFromCache(char, letterSpacing, cache, context);
                    if (width + characterWidth > wordWrapWidth) {
                        //newLine
                        lines += TextMetrics.addLine(line);
                        canPrependSpaces = false;
                        line = '';
                        width = 0;
                    }
                    line += char;
                    width += characterWidth;
                }
            }

            // word could fit
            else {
                // word won't fit because of existing words
                // start a new line
                if (tokenWidth + width > wordWrapWidth) {
                    // if its a space we don't want it
                    canPrependSpaces = false;

                    // add a new line
                    lines += TextMetrics.addLine(line);

                    // start a new line
                    line = '';
                    width = 0;
                }

                // don't add spaces to the beginning of lines
                if (line.length > 0 || !TextMetrics.isBreakingSpace(token) || canPrependSpaces) {
                    // add the word to the current line
                    line += token;

                    // update width counter
                    width += tokenWidth;
                }
            }
        }

        lines += TextMetrics.addLine(line, false);

        return lines;
    }

    //createjs와 동일한 글씨 크기 측정을 위해
    /**
     *
     * @param {PIXI.TextStyle} style
     * @param text
     * @return {number}
     * @private
     */
    function _getMeasuredWidth(font, text) {
        const ctx = TextMetrics._context;
        ctx.save();
        ctx.font = font;
        var w = ctx.measureText(text).width;
        ctx.restore();
        return w;
    }

    //createjs와 동일한 글씨 크기 측정을 위해
    TextMetrics.measureText = function(text, style, wordWrap, canvas = TextMetrics._canvas)
    {
        wordWrap = (wordWrap === undefined || wordWrap === null) ? style.wordWrap : wordWrap;
        const font = style.toFontString();
        // const fontProperties = TextMetrics.measureFont(font); //skip for performance
        const fontProperties = null;
        const context = canvas.getContext('2d');

        context.font = font;

        const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text;
        const lines = outputText.split(/(?:\r\n|\r|\n)/);
        const lineWidths = new Array(lines.length);
        let maxLineWidth = 0;

        for (let i = 0; i < lines.length; i++)
        {
            const lineWidth = context.measureText(lines[i]).width + ((lines[i].length - 1) * style.letterSpacing);

            lineWidths[i] = lineWidth;
            maxLineWidth = Math.max(maxLineWidth, lineWidth);
        }
        let width = maxLineWidth + style.strokeThickness;
        const lineHeight = style.lineHeight || _getMeasuredWidth(font, "M") * 1.2;
        let height = lineHeight * lines.length;

        return new TextMetrics(
            text,
            style,
            width,
            height,
            lines,
            lineWidths,
            lineHeight + style.leading,
            maxLineWidth,
            fontProperties
        );
    };

}<commit_msg>PIXITextMetricsPlugin.js getMeasuredWidth네이밍 변경<commit_after>export function PIXITextMetricsPlugIn() {
    const TextMetrics = PIXI.TextMetrics;

    const TextMetrics__orgWordWrap = PIXI.TextMetrics.wordWrap;
    TextMetrics.__breakAllWordWrap = __breakAllWordWrap;

    TextMetrics.wordWrap = function(text, style, canvas = TextMetrics._canvas) {
        return style.wordBreakAll
            ? TextMetrics.__breakAllWordWrap(text, style, canvas)
            : TextMetrics__orgWordWrap(text, style, canvas);
    };

    /**
     *
     * @param text
     * @param {PIXI.TextStyle | PIXITextStyle} style
     * @param canvas
     * @return {string}
     * @private
     */
    function __breakAllWordWrap(text, style, canvas) {
        const context = canvas.getContext('2d');

        let width = 0;
        let line = '';
        let lines = '';

        const cache = {};
        const { letterSpacing, whiteSpace } = style;

        // How to handle whitespaces
        const collapseSpaces = TextMetrics.collapseSpaces(whiteSpace);
        const collapseNewlines = TextMetrics.collapseNewlines(whiteSpace);

        // whether or not spaces may be added to the beginning of lines
        let canPrependSpaces = !collapseSpaces;

        // There is letterSpacing after every char except the last one
        // t_h_i_s_' '_i_s_' '_a_n_' '_e_x_a_m_p_l_e_' '_!
        // so for convenience the above needs to be compared to width + 1 extra letterSpace
        // t_h_i_s_' '_i_s_' '_a_n_' '_e_x_a_m_p_l_e_' '_!_
        // ________________________________________________
        // And then the final space is simply no appended to each line
        const wordWrapWidth = style.wordWrapWidth + letterSpacing;

        // break text into words, spaces and newline chars
        const tokens = TextMetrics.tokenize(text);

        for (let i = 0; i < tokens.length; i++) {
            // get the word, space or newlineChar
            let token = tokens[i];

            // if word is a new line
            if (TextMetrics.isNewline(token)) {
                // keep the new line
                if (!collapseNewlines) {
                    lines += TextMetrics.addLine(line);
                    canPrependSpaces = !collapseSpaces;
                    line = '';
                    width = 0;
                    continue;
                }

                // if we should collapse new lines
                // we simply convert it into a space
                token = ' ';
            }

            // if we should collapse repeated whitespaces
            if (collapseSpaces) {
                // check both this and the last tokens for spaces
                const currIsBreakingSpace = TextMetrics.isBreakingSpace(token);
                const lastIsBreakingSpace = TextMetrics.isBreakingSpace(line[line.length - 1]);

                if (currIsBreakingSpace && lastIsBreakingSpace) {
                    continue;
                }
            }

            // get word width from cache if possible
            const tokenWidth = TextMetrics.getFromCache(token, letterSpacing, cache, context);

            // word is longer than desired bounds
            if (width + tokenWidth > wordWrapWidth) {
                const characters = token.split('');
                for (let j = 0; j < characters.length; j++) {
                    const char = characters[j];
                    const characterWidth = TextMetrics.getFromCache(
                        char,
                        letterSpacing,
                        cache,
                        context
                    );
                    if (width + characterWidth > wordWrapWidth) {
                        //newLine
                        lines += TextMetrics.addLine(line);
                        canPrependSpaces = false;
                        line = '';
                        width = 0;
                    }
                    line += char;
                    width += characterWidth;
                }
            }

            // word could fit
            else {
                // word won't fit because of existing words
                // start a new line
                if (tokenWidth + width > wordWrapWidth) {
                    // if its a space we don't want it
                    canPrependSpaces = false;

                    // add a new line
                    lines += TextMetrics.addLine(line);

                    // start a new line
                    line = '';
                    width = 0;
                }

                // don't add spaces to the beginning of lines
                if (line.length > 0 || !TextMetrics.isBreakingSpace(token) || canPrependSpaces) {
                    // add the word to the current line
                    line += token;

                    // update width counter
                    width += tokenWidth;
                }
            }
        }

        lines += TextMetrics.addLine(line, false);

        return lines;
    }

    //createjs와 동일한 글씨 크기 측정을 위해
    /**
     *
     * @param {PIXI.TextStyle} style
     * @param text
     * @return {number}
     * @private
     */
    function getMeasuredWidth(font, text) {
        const ctx = TextMetrics._context;
        ctx.save();
        ctx.font = font;
        const w = ctx.measureText(text).width;
        ctx.restore();
        return w;
    }

    //createjs와 동일한 글씨 크기 측정을 위해
    TextMetrics.measureText = function(text, style, wordWrap, canvas = TextMetrics._canvas) {
        wordWrap = wordWrap === undefined || wordWrap === null ? style.wordWrap : wordWrap;
        const font = style.toFontString();
        // const fontProperties = TextMetrics.measureFont(font); //skip for performance
        const fontProperties = null;
        const context = canvas.getContext('2d');

        context.font = font;

        const outputText = wordWrap ? TextMetrics.wordWrap(text, style, canvas) : text;
        const lines = outputText.split(/(?:\r\n|\r|\n)/);
        const lineWidths = new Array(lines.length);
        let maxLineWidth = 0;

        for (let i = 0; i < lines.length; i++) {
            const lineWidth =
                context.measureText(lines[i]).width + (lines[i].length - 1) * style.letterSpacing;

            lineWidths[i] = lineWidth;
            maxLineWidth = Math.max(maxLineWidth, lineWidth);
        }
        const width = maxLineWidth + style.strokeThickness;
        const lineHeight = style.lineHeight || getMeasuredWidth(font, 'M') * 1.2;
        const height = lineHeight * lines.length;

        return new TextMetrics(
            text,
            style,
            width,
            height,
            lines,
            lineWidths,
            lineHeight + style.leading,
            maxLineWidth,
            fontProperties
        );
    };
}
<|endoftext|>


<commit_before>const URLBuilder = require('./url-builder.class');
const Request = require('./request.class');
const clone = require('./utils.class').clone;
const noop = require('./utils.class').noop;

/**
 * Creates an endpoint instance
 * @constructor
 */
class Endpoint {
  constructor(endpointConfig = {}) {
    if (!endpointConfig || 'function' !== typeof endpointConfig.model) {
      throw new Error('Invalid endpoint configuration.');
    }
    this.endpointConfig = clone(endpointConfig);
    this.config = {
      allowFromCache : true,
      method : 'get',
      target : '',
      query : {}
    };
    this.cache = this.endpointConfig.api().cache;
  }
  /**
   * Instantiates a new model instance and returns it
   * @param {object=} data - Initialization data for the new model instance
   * @returns {@link Model} New instance of Model
   */
  createNew(data = {}) {
    let root = this.endpointConfig.baseUrl() + this.endpointConfig.url();
    data['@root'] = root;
    let instantiator = this.endpointConfig.model();
    let instance = new instantiator(data);
    instance.config(this.endpointConfig);
    return instance;
  }
  /**
   * Gets / sets instantiator to use when creating a new model instance. Instantiator *should* inherit
   * from {@link Model}
   * @param {(function|class)=} instantiator - Function or class to use when instantiating model
   * @returns {(this|function)} Current instantiator function or this instance
   */
  model(instantiator) {
    if ('undefined' !== typeof instantiator) {
      this.endpointConfig.model(instantiator);
      return this;
    }
    return this.endpointConfig.model();
  }
  /**
   * Gets / sets the endpoint's relative url
   * @param {string=} url - The new url value
   * @returns {(this|string)} This instance or the current url
   */
  url(newUrl) {
    if ('undefined' !== typeof newUrl) {
      this.endpointConfig.url(newUrl);
      return this;
    }
    return this.endpointConfig.url();
  }
  /**
   * Builds a query to find an object with the specified identifier
   * @param {string} id - the unique model identifier
   * @returns {this}
   */
  findById(id) {
    if ('string' !== typeof id) {
      throw new Error('Invalid model identifier');
    }
    this.config.target = new URLBuilder([
      this.endpointConfig.baseUrl(),
      this.endpointConfig.url(),
      id
    ]);
    this.config.method = 'get';
    return this;
  }
  /**
   * Creates a query to find objects that match the optional query
   * @param {object=} query
   * @returns {this}
   */
  find(query) {
    this.config.target = new URLBuilder([
      this.endpointConfig.baseUrl(),
      this.endpointConfig.url()
    ]);
    this.config.method = 'get';
    this.config.query.search = JSON.stringify(query);
    return this;
  }

  /**
   * Creates a query to find a unique model with the specified id
   * and replaces it's data with the specified body object
   * @param {string} id - the unique model identifier
   * @param {object} body
   * @returns {this}
   */
  findByIdAndUpdate(id, body) {
    if ('string' !== typeof id) {
      throw new Error('Invalid model identifier');
    }
    if ('object' !== typeof body) {
      throw new Error('Invalid body object');
    }
    this.config.target = new URLBuilder([
      this.endpointConfig.baseUrl(),
      this.endpointConfig.url(),
      id
    ]);
    this.body = body;
    this.config.method = 'put';
    return this;
  }

  /**
   * Creates a query that finds a model with the specified id and
   * removes it from the database
   * @param {string} id - the unique model identifier
   * @returns {this}
   */
  findByIdAndRemove(id) {
    if ('string' !== typeof id) {
      throw new Error('Invalid model identifier');
    }
    this.config.target = new URLBuilder([
      this.endpointConfig.baseUrl(),
      this.endpointConfig.url(),
      id
    ]);
    this.config.method = 'delete';
    return this;
  }

  /**
   * Determines if the query request should allow objects from the cache
   * or require objects be fresh from the api
   * @param {boolean=} allow - Specify if using the cache is allowed
   * @returns {(this | boolean)} This instance or the current allow value
   */
  allowFromCache(allow) {
    if ('undefined' !== typeof allow) {
      if ('boolean' === typeof allow) {
        this.config.allowFromCache = allow;
      }
      return this;
    }
    return this.config.allowFromCache;
  }

  /**
   * Runs the query that has been created using the find/findBy.. calls
   * @param {function=} cb - Function to call on completion (success or failure)
   * @returns {Promise}
   */
  exec(cb = noop) {
    let allowFromCache = this.allowFromCache();
    let modelConstructor = this.endpointConfig.model();
    let endpointConfig = this.endpointConfig;

    let headers = {};
    try {
      headers = this.endpointConfig.api().commonHeaders();
    } catch(e) {
      // console.error('Unable to get common headers. Something went\'t wrong (unless you are unit testing). ');
    }
    Object.assign(headers, this.config.headers);

    let request = new Request()
      .method(this.config.method)
      .body(this.config.body || {})
      .query(this.config.query || {})
      .headers(headers || {})
      .url(this.config.target.toString());

    let Promise = require('./settings').getPromise();

    return new Promise((resolve, reject) => {
      if (this.hasCache()) {
        let cachedObject = cache.get(request.url());
        if (cachedObject && allowFromCache) {
          cb(null, cachedObject);
          return resolve(cachedObject);
        }
      }
      //do actual 'get'
      request.exec().then(response => {
        let data = response.data;
        let model;

        if (Array.isArray(data)) {
          model = data.map((item) => {
            let entry = new modelConstructor(item);
            entry.config(endpointConfig);
            Object.defineProperty(entry, '__request', { value : clone(request), enumerable : false });
            return entry;
          });
        } else {
          model = new modelConstructor(data);
          model.config(endpointConfig);
          Object.defineProperty(model, '__request', { value : clone(request), enumerable : false });
        }

        if (this.hasCache()) {
          cache.put(request.url(), model);
        }
        cb(null, model);
        return resolve(model);
      }).catch(err => {
        if (this.hasCache()) {
          cache.invalidate(request.url());
        }
        cb(err);
        return reject(err);
      });
    });
  }
  /**
   * Query helper to skip records returned from the api (if supported). Combined
   * with the .limit method, this function is great for pagination
   * @param {number} skipAmount
   * @returns this
   */
  skip(skipAmount = 0) {
    if ('number' === typeof skipAmount) {
      this.config.query.skip = skipAmount;
    }
    return this;
  }
 /**
  * Query helper to limit the number of results returned (provided the api
  * supports it)
  * @param {number} limitAmount
  * @returns this
  */
  limit(limitAmount = 0) {
    if ('number' === typeof limitAmount) {
      this.config.query.limit = limitAmount;
    }
    return this;
  }
  /**
   * Sets a list of fields to return from the api (if supported).
   * @param {(string|string[])} fields - a list of fields to return from the api
   * @returns this
   */
  select(fields = '') {
    if (Array.isArray(fields)) {
      fields = fields.join(' ');
    }
    if ('string' === typeof fields) {
      this.config.query.fields = fields;
    }
    return this;
  }

  /**
   * Checks to see if the cache object has been set and is valid
   * @access private
   * @returns {boolean}
   */
  hasCache() {
    return this.cache && 'function' === typeof this.cache;
  }
}

module.exports = Endpoint;
<commit_msg>fixed broken root path<commit_after>const URLBuilder = require('./url-builder.class');
const Request = require('./request.class');
const clone = require('./utils.class').clone;
const noop = require('./utils.class').noop;

/**
 * Creates an endpoint instance
 * @constructor
 */
class Endpoint {
  constructor(endpointConfig = {}) {
    if (!endpointConfig || 'function' !== typeof endpointConfig.model) {
      throw new Error('Invalid endpoint configuration.');
    }
    this.endpointConfig = clone(endpointConfig);
    this.config = {
      allowFromCache : true,
      method : 'get',
      target : '',
      query : {}
    };
    this.cache = this.endpointConfig.api().cache;
  }
  /**
   * Instantiates a new model instance and returns it
   * @param {object=} data - Initialization data for the new model instance
   * @returns {@link Model} New instance of Model
   */
  createNew(data = {}) {
    let root = new URLBuilder([
      this.endpointConfig.baseUrl(),
      this.endpointConfig.url()
    ]);
    data['@root'] = root;
    let instantiator = this.endpointConfig.model();
    let instance = new instantiator(data);
    instance.config(this.endpointConfig);
    return instance;
  }
  /**
   * Gets / sets instantiator to use when creating a new model instance. Instantiator *should* inherit
   * from {@link Model}
   * @param {(function|class)=} instantiator - Function or class to use when instantiating model
   * @returns {(this|function)} Current instantiator function or this instance
   */
  model(instantiator) {
    if ('undefined' !== typeof instantiator) {
      this.endpointConfig.model(instantiator);
      return this;
    }
    return this.endpointConfig.model();
  }
  /**
   * Gets / sets the endpoint's relative url
   * @param {string=} url - The new url value
   * @returns {(this|string)} This instance or the current url
   */
  url(newUrl) {
    if ('undefined' !== typeof newUrl) {
      this.endpointConfig.url(newUrl);
      return this;
    }
    return this.endpointConfig.url();
  }
  /**
   * Builds a query to find an object with the specified identifier
   * @param {string} id - the unique model identifier
   * @returns {this}
   */
  findById(id) {
    if ('string' !== typeof id) {
      throw new Error('Invalid model identifier');
    }
    this.config.target = new URLBuilder([
      this.endpointConfig.baseUrl(),
      this.endpointConfig.url(),
      id
    ]);
    this.config.method = 'get';
    return this;
  }
  /**
   * Creates a query to find objects that match the optional query
   * @param {object=} query
   * @returns {this}
   */
  find(query) {
    this.config.target = new URLBuilder([
      this.endpointConfig.baseUrl(),
      this.endpointConfig.url()
    ]);
    this.config.method = 'get';
    this.config.query.search = JSON.stringify(query);
    return this;
  }

  /**
   * Creates a query to find a unique model with the specified id
   * and replaces it's data with the specified body object
   * @param {string} id - the unique model identifier
   * @param {object} body
   * @returns {this}
   */
  findByIdAndUpdate(id, body) {
    if ('string' !== typeof id) {
      throw new Error('Invalid model identifier');
    }
    if ('object' !== typeof body) {
      throw new Error('Invalid body object');
    }
    this.config.target = new URLBuilder([
      this.endpointConfig.baseUrl(),
      this.endpointConfig.url(),
      id
    ]);
    this.body = body;
    this.config.method = 'put';
    return this;
  }

  /**
   * Creates a query that finds a model with the specified id and
   * removes it from the database
   * @param {string} id - the unique model identifier
   * @returns {this}
   */
  findByIdAndRemove(id) {
    if ('string' !== typeof id) {
      throw new Error('Invalid model identifier');
    }
    this.config.target = new URLBuilder([
      this.endpointConfig.baseUrl(),
      this.endpointConfig.url(),
      id
    ]);
    this.config.method = 'delete';
    return this;
  }

  /**
   * Determines if the query request should allow objects from the cache
   * or require objects be fresh from the api
   * @param {boolean=} allow - Specify if using the cache is allowed
   * @returns {(this | boolean)} This instance or the current allow value
   */
  allowFromCache(allow) {
    if ('undefined' !== typeof allow) {
      if ('boolean' === typeof allow) {
        this.config.allowFromCache = allow;
      }
      return this;
    }
    return this.config.allowFromCache;
  }

  /**
   * Runs the query that has been created using the find/findBy.. calls
   * @param {function=} cb - Function to call on completion (success or failure)
   * @returns {Promise}
   */
  exec(cb = noop) {
    let allowFromCache = this.allowFromCache();
    let modelConstructor = this.endpointConfig.model();
    let endpointConfig = this.endpointConfig;

    let headers = {};
    try {
      headers = this.endpointConfig.api().commonHeaders();
    } catch(e) {
      // console.error('Unable to get common headers. Something went\'t wrong (unless you are unit testing). ');
    }
    Object.assign(headers, this.config.headers);

    let request = new Request()
      .method(this.config.method)
      .body(this.config.body || {})
      .query(this.config.query || {})
      .headers(headers || {})
      .url(this.config.target.toString());

    let Promise = require('./settings').getPromise();

    return new Promise((resolve, reject) => {
      if (this.hasCache()) {
        let cachedObject = cache.get(request.url());
        if (cachedObject && allowFromCache) {
          cb(null, cachedObject);
          return resolve(cachedObject);
        }
      }
      //do actual 'get'
      request.exec().then(response => {
        let data = response.data;
        let model;

        if (Array.isArray(data)) {
          model = data.map((item) => {
            let entry = new modelConstructor(item);
            entry.config(endpointConfig);
            Object.defineProperty(entry, '__request', { value : clone(request), enumerable : false });
            return entry;
          });
        } else {
          model = new modelConstructor(data);
          model.config(endpointConfig);
          Object.defineProperty(model, '__request', { value : clone(request), enumerable : false });
        }

        if (this.hasCache()) {
          cache.put(request.url(), model);
        }
        cb(null, model);
        return resolve(model);
      }).catch(err => {
        if (this.hasCache()) {
          cache.invalidate(request.url());
        }
        cb(err);
        return reject(err);
      });
    });
  }
  /**
   * Query helper to skip records returned from the api (if supported). Combined
   * with the .limit method, this function is great for pagination
   * @param {number} skipAmount
   * @returns this
   */
  skip(skipAmount = 0) {
    if ('number' === typeof skipAmount) {
      this.config.query.skip = skipAmount;
    }
    return this;
  }
 /**
  * Query helper to limit the number of results returned (provided the api
  * supports it)
  * @param {number} limitAmount
  * @returns this
  */
  limit(limitAmount = 0) {
    if ('number' === typeof limitAmount) {
      this.config.query.limit = limitAmount;
    }
    return this;
  }
  /**
   * Sets a list of fields to return from the api (if supported).
   * @param {(string|string[])} fields - a list of fields to return from the api
   * @returns this
   */
  select(fields = '') {
    if (Array.isArray(fields)) {
      fields = fields.join(' ');
    }
    if ('string' === typeof fields) {
      this.config.query.fields = fields;
    }
    return this;
  }

  /**
   * Checks to see if the cache object has been set and is valid
   * @access private
   * @returns {boolean}
   */
  hasCache() {
    return this.cache && 'function' === typeof this.cache;
  }
}

module.exports = Endpoint;
<|endoftext|>


<commit_before>var Thread = require('./thread');

/**
 * Utility function to determine if a value is a Promise.
 * @param {*} value Value to check for a Promise.
 * @return {Boolean} True if the value appears to be a Promise.
 */
var isPromise = function (value) {
    return value && value.then && typeof value.then === 'function';
};

/**
 * Execute a block.
 * @param {!Sequencer} sequencer Which sequencer is executing.
 * @param {!Thread} thread Thread which to read and execute.
 */
var execute = function (sequencer, thread) {
    var runtime = sequencer.runtime;
    var target = thread.target;

    // Current block to execute is the one on the top of the stack.
    var currentBlockId = thread.peekStack();
    var currentStackFrame = thread.peekStackFrame();

    // Verify that the block still exists.
    if (!target ||
        typeof target.blocks.getBlock(currentBlockId) === 'undefined') {
        // No block found: stop the thread; script no longer exists.
        sequencer.retireThread(thread);
        return;
    }
    // Query info about the block.
    var opcode = target.blocks.getOpcode(currentBlockId);
    var blockFunction = runtime.getOpcodeFunction(opcode);
    var isHat = runtime.getIsHat(opcode);
    var fields = target.blocks.getFields(currentBlockId);
    var inputs = target.blocks.getInputs(currentBlockId);

    if (!opcode) {
        console.warn('Could not get opcode for block: ' + currentBlockId);
        return;
    }

    /**
     * Handle any reported value from the primitive, either directly returned
     * or after a promise resolves.
     * @param {*} resolvedValue Value eventually returned from the primitive.
     */
    var handleReport = function (resolvedValue) {
        thread.pushReportedValue(resolvedValue);
        if (isHat) {
            // Hat predicate was evaluated.
            if (runtime.getIsEdgeActivatedHat(opcode)) {
                // If this is an edge-activated hat, only proceed if
                // the value is true and used to be false.
                var oldEdgeValue = runtime.updateEdgeActivatedValue(
                    currentBlockId,
                    resolvedValue
                );
                var edgeWasActivated = !oldEdgeValue && resolvedValue;
                if (!edgeWasActivated) {
                    sequencer.retireThread(thread);
                }
            } else {
                // Not an edge-activated hat: retire the thread
                // if predicate was false.
                if (!resolvedValue) {
                    sequencer.retireThread(thread);
                }
            }
        } else {
            // In a non-hat, report the value visually if necessary if
            // at the top of the thread stack.
            if (typeof resolvedValue !== 'undefined' && thread.atStackTop()) {
                runtime.visualReport(currentBlockId, resolvedValue);
            }
            // Finished any yields.
            thread.setStatus(Thread.STATUS_RUNNING);
        }
    };

    // Hats and single-field shadows are implemented slightly differently
    // from regular blocks.
    // For hats: if they have an associated block function,
    // it's treated as a predicate; if not, execution will proceed as a no-op.
    // For single-field shadows: If the block has a single field, and no inputs,
    // immediately return the value of the field.
    if (!blockFunction) {
        if (isHat) {
            // Skip through the block (hat with no predicate).
            return;
        } else {
            if (Object.keys(fields).length == 1 &&
                Object.keys(inputs).length == 0) {
                // One field and no inputs - treat as arg.
                for (var fieldKey in fields) { // One iteration.
                    handleReport(fields[fieldKey].value);
                }
            } else {
                console.warn('Could not get implementation for opcode: ' +
                    opcode);
            }
            thread.requestScriptGlowInFrame = true;
            return;
        }
    }

    // Generate values for arguments (inputs).

    var argValues = {};

    // Add all fields on this block to the argValues.
    for (var fieldName in fields) {
        argValues[fieldName] = fields[fieldName].value;
    }

    // Recursively evaluate input blocks.
    for (var inputName in inputs) {
        var input = inputs[inputName];
,        var inputBlockId = input.block;
        // Is there no value for this input waiting in the stack frame?
        if (typeof currentStackFrame.reported[inputName] === 'undefined') {
            // If there's not, we need to evaluate the block.
            var reporterYielded = (
                sequencer.stepToReporter(thread, inputBlockId, inputName)
            );
            // If the reporter yielded, return immediately;
            // it needs time to finish and report its value.
            if (reporterYielded) {
                return;
            }
        }
        argValues[inputName] = currentStackFrame.reported[inputName];
    }

    // Add any mutation to args (e.g., for procedures).
    var mutation = target.blocks.getMutation(currentBlockId);
    if (mutation) {
        argValues.mutation = mutation;
    }

    // If we've gotten this far, all of the input blocks are evaluated,
    // and `argValues` is fully populated. So, execute the block primitive.
    // First, clear `currentStackFrame.reported`, so any subsequent execution
    // (e.g., on return from a branch) gets fresh inputs.
    currentStackFrame.reported = {};

    var primitiveReportedValue = null;
    primitiveReportedValue = blockFunction(argValues, {
        stackFrame: currentStackFrame.executionContext,
        target: target,
        yield: function() {
            thread.setStatus(Thread.STATUS_YIELD);
        },
        yieldFrame: function() {
            thread.setStatus(Thread.STATUS_YIELD_FRAME);
        },
        done: function() {
            thread.setStatus(Thread.STATUS_RUNNING);
            sequencer.proceedThread(thread);
        },
        startBranch: function (branchNum) {
            sequencer.stepToBranch(thread, branchNum);
        },
        startProcedure: function (procedureName) {
            sequencer.stepToProcedure(thread, procedureName);
            return thread;
        },
        startHats: function(requestedHat, opt_matchFields, opt_target) {
            return (
                runtime.startHats(requestedHat, opt_matchFields, opt_target)
            );
        },
        ioQuery: function (device, func, args) {
            // Find the I/O device and execute the query/function call.
            if (runtime.ioDevices[device] && runtime.ioDevices[device][func]) {
                var devObject = runtime.ioDevices[device];
                return devObject[func].call(devObject, args);
            }
        },
        getThread: function () {
            return thread;
        }
    });

    if (typeof primitiveReportedValue === 'undefined') {
        // No value reported - potentially a command block.
        // Edge-activated hats don't request a glow; all commands do.
        thread.requestScriptGlowInFrame = true;
    }

    // If it's a promise, wait until promise resolves.
    if (isPromise(primitiveReportedValue)) {
        if (thread.status === Thread.STATUS_RUNNING) {
            // Primitive returned a promise; automatically yield thread.
            thread.setStatus(Thread.STATUS_YIELD);
        }
        // Promise handlers
        primitiveReportedValue.then(function(resolvedValue) {
            handleReport(resolvedValue);
            sequencer.proceedThread(thread);
        }, function(rejectionReason) {
            // Promise rejected: the primitive had some error.
            // Log it and proceed.
            console.warn('Primitive rejected promise: ', rejectionReason);
            thread.setStatus(Thread.STATUS_RUNNING);
            sequencer.proceedThread(thread);
        });
    } else if (thread.status === Thread.STATUS_RUNNING) {
        handleReport(primitiveReportedValue);
    }
};

module.exports = execute;
<commit_msg>Fix Typo<commit_after>var Thread = require('./thread');

/**
 * Utility function to determine if a value is a Promise.
 * @param {*} value Value to check for a Promise.
 * @return {Boolean} True if the value appears to be a Promise.
 */
var isPromise = function (value) {
    return value && value.then && typeof value.then === 'function';
};

/**
 * Execute a block.
 * @param {!Sequencer} sequencer Which sequencer is executing.
 * @param {!Thread} thread Thread which to read and execute.
 */
var execute = function (sequencer, thread) {
    var runtime = sequencer.runtime;
    var target = thread.target;

    // Current block to execute is the one on the top of the stack.
    var currentBlockId = thread.peekStack();
    var currentStackFrame = thread.peekStackFrame();

    // Verify that the block still exists.
    if (!target ||
        typeof target.blocks.getBlock(currentBlockId) === 'undefined') {
        // No block found: stop the thread; script no longer exists.
        sequencer.retireThread(thread);
        return;
    }
    // Query info about the block.
    var opcode = target.blocks.getOpcode(currentBlockId);
    var blockFunction = runtime.getOpcodeFunction(opcode);
    var isHat = runtime.getIsHat(opcode);
    var fields = target.blocks.getFields(currentBlockId);
    var inputs = target.blocks.getInputs(currentBlockId);

    if (!opcode) {
        console.warn('Could not get opcode for block: ' + currentBlockId);
        return;
    }

    /**
     * Handle any reported value from the primitive, either directly returned
     * or after a promise resolves.
     * @param {*} resolvedValue Value eventually returned from the primitive.
     */
    var handleReport = function (resolvedValue) {
        thread.pushReportedValue(resolvedValue);
        if (isHat) {
            // Hat predicate was evaluated.
            if (runtime.getIsEdgeActivatedHat(opcode)) {
                // If this is an edge-activated hat, only proceed if
                // the value is true and used to be false.
                var oldEdgeValue = runtime.updateEdgeActivatedValue(
                    currentBlockId,
                    resolvedValue
                );
                var edgeWasActivated = !oldEdgeValue && resolvedValue;
                if (!edgeWasActivated) {
                    sequencer.retireThread(thread);
                }
            } else {
                // Not an edge-activated hat: retire the thread
                // if predicate was false.
                if (!resolvedValue) {
                    sequencer.retireThread(thread);
                }
            }
        } else {
            // In a non-hat, report the value visually if necessary if
            // at the top of the thread stack.
            if (typeof resolvedValue !== 'undefined' && thread.atStackTop()) {
                runtime.visualReport(currentBlockId, resolvedValue);
            }
            // Finished any yields.
            thread.setStatus(Thread.STATUS_RUNNING);
        }
    };

    // Hats and single-field shadows are implemented slightly differently
    // from regular blocks.
    // For hats: if they have an associated block function,
    // it's treated as a predicate; if not, execution will proceed as a no-op.
    // For single-field shadows: If the block has a single field, and no inputs,
    // immediately return the value of the field.
    if (!blockFunction) {
        if (isHat) {
            // Skip through the block (hat with no predicate).
            return;
        } else {
            if (Object.keys(fields).length == 1 &&
                Object.keys(inputs).length == 0) {
                // One field and no inputs - treat as arg.
                for (var fieldKey in fields) { // One iteration.
                    handleReport(fields[fieldKey].value);
                }
            } else {
                console.warn('Could not get implementation for opcode: ' +
                    opcode);
            }
            thread.requestScriptGlowInFrame = true;
            return;
        }
    }

    // Generate values for arguments (inputs).

    var argValues = {};

    // Add all fields on this block to the argValues.
    for (var fieldName in fields) {
        argValues[fieldName] = fields[fieldName].value;
    }

    // Recursively evaluate input blocks.
    for (var inputName in inputs) {
        var input = inputs[inputName];
        var inputBlockId = input.block;
        // Is there no value for this input waiting in the stack frame?
        if (typeof currentStackFrame.reported[inputName] === 'undefined') {
            // If there's not, we need to evaluate the block.
            var reporterYielded = (
                sequencer.stepToReporter(thread, inputBlockId, inputName)
            );
            // If the reporter yielded, return immediately;
            // it needs time to finish and report its value.
            if (reporterYielded) {
                return;
            }
        }
        argValues[inputName] = currentStackFrame.reported[inputName];
    }

    // Add any mutation to args (e.g., for procedures).
    var mutation = target.blocks.getMutation(currentBlockId);
    if (mutation) {
        argValues.mutation = mutation;
    }

    // If we've gotten this far, all of the input blocks are evaluated,
    // and `argValues` is fully populated. So, execute the block primitive.
    // First, clear `currentStackFrame.reported`, so any subsequent execution
    // (e.g., on return from a branch) gets fresh inputs.
    currentStackFrame.reported = {};

    var primitiveReportedValue = null;
    primitiveReportedValue = blockFunction(argValues, {
        stackFrame: currentStackFrame.executionContext,
        target: target,
        yield: function() {
            thread.setStatus(Thread.STATUS_YIELD);
        },
        yieldFrame: function() {
            thread.setStatus(Thread.STATUS_YIELD_FRAME);
        },
        done: function() {
            thread.setStatus(Thread.STATUS_RUNNING);
            sequencer.proceedThread(thread);
        },
        startBranch: function (branchNum) {
            sequencer.stepToBranch(thread, branchNum);
        },
        startProcedure: function (procedureName) {
            sequencer.stepToProcedure(thread, procedureName);
            return thread;
        },
        startHats: function(requestedHat, opt_matchFields, opt_target) {
            return (
                runtime.startHats(requestedHat, opt_matchFields, opt_target)
            );
        },
        ioQuery: function (device, func, args) {
            // Find the I/O device and execute the query/function call.
            if (runtime.ioDevices[device] && runtime.ioDevices[device][func]) {
                var devObject = runtime.ioDevices[device];
                return devObject[func].call(devObject, args);
            }
        },
        getThread: function () {
            return thread;
        }
    });

    if (typeof primitiveReportedValue === 'undefined') {
        // No value reported - potentially a command block.
        // Edge-activated hats don't request a glow; all commands do.
        thread.requestScriptGlowInFrame = true;
    }

    // If it's a promise, wait until promise resolves.
    if (isPromise(primitiveReportedValue)) {
        if (thread.status === Thread.STATUS_RUNNING) {
            // Primitive returned a promise; automatically yield thread.
            thread.setStatus(Thread.STATUS_YIELD);
        }
        // Promise handlers
        primitiveReportedValue.then(function(resolvedValue) {
            handleReport(resolvedValue);
            sequencer.proceedThread(thread);
        }, function(rejectionReason) {
            // Promise rejected: the primitive had some error.
            // Log it and proceed.
            console.warn('Primitive rejected promise: ', rejectionReason);
            thread.setStatus(Thread.STATUS_RUNNING);
            sequencer.proceedThread(thread);
        });
    } else if (thread.status === Thread.STATUS_RUNNING) {
        handleReport(primitiveReportedValue);
    }
};

module.exports = execute;
<|endoftext|>


<commit_before>define('Enemy',

['Sprite', 'vent', 'util'],

function( Sprite, vent, util ){

    'use strict';

    var proto;

    /**
     * Enemy constructor (extends Sprite)
     */
    function Enemy(){
        Sprite.apply(this, arguments);
        this.fillStyle = util.randomColor();
        this.size = 15;
        this.physics = {
            speed: 2,
            friction: 0.01,
            velocity: util.random(0.5, 1),
            velocityGoal: 0.1
        };
        this.orbitAt = 0;
        this.orbitSpeed = 3;
        this.inc = 0.2;
        this.position = {
            x: Math.random() * this.canvas.width,
            y: 0 - this.size*2
        };
        this.displayProps = {
            // shadowBlur: 0,
            // shadowColor: 'rgba(221,113,8,1.0)'
        };
        this.gradients = {
            default: ['#800008', "#c3000b"],
            water: ['#255989', '#56b8ff'],
            air: ['#333', '#f1f1f1']
        };
        this.currentGradient = this.gradients.default;
        this.orbiterDisplayProps = { shadowBlur: 0 };
        this.effected = false;
    }

    proto = Enemy.prototype = Object.create(Sprite.prototype);

    /**
     * Creates the Enemy object in coordinate space, emits canvas event, and
     * sets any approprate elemental effects that may be active.
     * @param  {String} effect - "water"
     * @return {self}
     */
    proto.create = function( effect ){
        Sprite.prototype.create.call(this);
        this.setEffect(effect || null);
        return this;
    };

    /**
     * Binds all appropriate events to the Enemy, mostly how to react to
     * elemental effects.
     */
    proto.bindEvents = function(){
        Sprite.prototype.bindEvents.call(this);
        vent.on('elemental-water-on', this.waterEffect.bind(this, true));
        vent.on('elemental-air-on', this.airEffect.bind(this, true));
        vent.on('deactivate', this.deactivateAllEffects.bind(this));
        vent.on('elemental-fire-on', this.destroy.bind(this));
    };

    /**
     * Draw the Enemy object
     * @return {void}
     */
    proto.render = function(){
        if ( this.destroyed ) {
            return;
        }

        var inc = 0.02,
            curr = inc*0.5,
            alpha = 0.9,
            grd;

        // Render primary body
        grd = this.ctx.createRadialGradient(
            this.position.x, this.position.y, this.size*1.5,
            this.position.x + 10, this.position.y + 10, this.size/2
        );
        grd.addColorStop(0, this.currentGradient[0]);
        grd.addColorStop(1, this.currentGradient[1]);
        util.circle(this.ctx,
            this.position.x, this.position.y, this.size, grd, this.displayProps
        );

        // Render orbiter
        this.orbitAt = this.orbitAt >= 2 ? 0 : this.orbitAt;
        this.orbitAt += inc * this.orbitSpeed;

        util.circle(this.ctx,
          this.position.x + (this.size + 2.5) * Math.cos(this.orbitAt * Math.PI),
          this.position.y + (this.size + 2.5) * Math.sin(this.orbitAt * Math.PI),
          5, 'rgba(221,113,8,1.0)', this.orbiterDisplayProps
        );

        // Render trail
        for (var i = 20; i > 0; i--) {
            util.circle(this.ctx,
              this.position.x + (this.size + i/4) * Math.cos((this.orbitAt-curr) * Math.PI),
              this.position.y + (this.size + i/4) * Math.sin((this.orbitAt-curr) * Math.PI),
              i/4, 'rgba(221, 113, 8, ' + alpha + ')'
            );
            curr += inc * 1;
            alpha -= inc * 0.6;
        }
    };

    /**
     * Updates the positional coordinates of the object before render
     * @return {void}
     */
    proto.update = function(){
        if ( this.destroyed ) {
            return;
        }

        if ( this.position.y + this.physics.velocity >
            this.canvas.height + this.size ) {

            this.destroy();
            vent.emit('enemy-passed');

        } else {
            this.position.y = this.position.y + this.physics.velocity;
        }
    };

    /**
     * Destroys the Enemy object, allowing for explosion effect to occur. Emits
     * event to let canvas know not to render the object.
     * @param  {Boolean} shouldExplode
     * @return {void}
     */
    proto.destroy = function( shouldExplode ){
        if ( shouldExplode === true ) {
            vent.emit('kaboom!', this.position.x, this.position.y);
        }
        this.position = { x: 0, y: 0 };
        this.destroyed = true;
        this.deactivateAllEffects();
        vent.emit('entity-destroyed', this);
    };

    /**
     * This method is invoked when the object is being picked out of the sprite
     * pool, the purpose of which is to move the item out of a destroyed state
     * so that it can be reused in the canvas space.
     * @return {void}
     */
    proto.recycle = function(){
        this.position.x = Math.random() * this.canvas.width;
        this.position.y = 0 - this.size;
        this.physics.velocityGoal = util.random(0.1, 0.5);
        this.destroyed = false;
        this.fillStyle = util.randomColor();
    };

    /**
     * Sets the appropriate elemental effect on the enemy, or in the absence of
     * a parameter, will unset all effects.
     *
     * Note: since water is the only effect that works on new enemies and this
     * is invoked during Enemy#create, this can only set water effects.
     *
     * @param {String} effect
     * @return {void}
     */
    proto.setEffect = function( effect ){
        if ( effect === 'water' ) {
            this.waterEffect(true);
        } else {
            this.deactivateAllEffects();
        }
    };

    /**
     * Activates or deactivates the water elemental effect.
     * @param {Boolean} activate
     * @return {void}
     */
    proto.waterEffect = function( activate ){
        this.effected = !!activate;
        if ( activate ) {
            this.currentGradient = this.gradients.water;
            this.physics.velocityGoal = this.physics.velocity * 0.3;
        } else {
            this.currentGradient = this.gradients.default;
            this.physics.velocityGoal = util.random(0.5, 1);
        }
    };

    /**
     * Activates or deactivates the air elemental effect
     * @param {Boolean} activate
     * @return {void}
     */
    proto.airEffect = function( activate ){
        this.effected = activate;
        if ( activate ) {
            this.currentGradient = this.gradients.air;
            this.physics.velocityGoal = 0;
        } else {
            this.currentGradient = this.gradients.default;
            this.physics.velocityGoal = util.random(0.5, 1);
        }
    };

    /**
     * Deactivates all elemental effects on the Enemy
     * @return {void}
     */
    proto.deactivateAllEffects = function(){
        this.waterEffect(false);
        this.airEffect(false);
    };

    return Enemy;

});
<commit_msg>Fix the effect velocities<commit_after>define('Enemy',

['Sprite', 'vent', 'util'],

function( Sprite, vent, util ){

    'use strict';

    var proto;

    /**
     * Enemy constructor (extends Sprite)
     */
    function Enemy(){
        Sprite.apply(this, arguments);
        this.fillStyle = util.randomColor();
        this.size = 15;
        this.physics = {
            speed: 2,
            friction: 0.01,
            velocity: util.random(0.5, 1),
            velocityGoal: 0.1
        };
        this.orbitAt = 0;
        this.orbitSpeed = 3;
        this.inc = 0.2;
        this.position = {
            x: Math.random() * this.canvas.width,
            y: 0 - this.size*2
        };
        this.displayProps = {
            // shadowBlur: 0,
            // shadowColor: 'rgba(221,113,8,1.0)'
        };
        this.gradients = {
            default: ['#800008', "#c3000b"],
            water: ['#255989', '#56b8ff'],
            air: ['#333', '#f1f1f1']
        };
        this.currentGradient = this.gradients.default;
        this.orbiterDisplayProps = { shadowBlur: 0 };
        this.effected = false;
    }

    proto = Enemy.prototype = Object.create(Sprite.prototype);

    /**
     * Creates the Enemy object in coordinate space, emits canvas event, and
     * sets any approprate elemental effects that may be active.
     * @param  {String} effect - "water"
     * @return {self}
     */
    proto.create = function( effect ){
        Sprite.prototype.create.call(this);
        this.setEffect(effect || null);
        return this;
    };

    /**
     * Binds all appropriate events to the Enemy, mostly how to react to
     * elemental effects.
     */
    proto.bindEvents = function(){
        Sprite.prototype.bindEvents.call(this);
        vent.on('elemental-water-on', this.waterEffect.bind(this, true));
        vent.on('elemental-air-on', this.airEffect.bind(this, true));
        vent.on('deactivate', this.deactivateAllEffects.bind(this));
        vent.on('elemental-fire-on', this.destroy.bind(this));
    };

    /**
     * Draw the Enemy object
     * @return {void}
     */
    proto.render = function(){
        if ( this.destroyed ) {
            return;
        }

        var inc = 0.02,
            curr = inc*0.5,
            alpha = 0.9,
            grd;

        // Render primary body
        grd = this.ctx.createRadialGradient(
            this.position.x, this.position.y, this.size*1.5,
            this.position.x + 10, this.position.y + 10, this.size/2
        );
        grd.addColorStop(0, this.currentGradient[0]);
        grd.addColorStop(1, this.currentGradient[1]);
        util.circle(this.ctx,
            this.position.x, this.position.y, this.size, grd, this.displayProps
        );

        // Render orbiter
        this.orbitAt = this.orbitAt >= 2 ? 0 : this.orbitAt;
        this.orbitAt += inc * this.orbitSpeed;

        util.circle(this.ctx,
          this.position.x + (this.size + 2.5) * Math.cos(this.orbitAt * Math.PI),
          this.position.y + (this.size + 2.5) * Math.sin(this.orbitAt * Math.PI),
          5, 'rgba(221,113,8,1.0)', this.orbiterDisplayProps
        );

        // Render trail
        for (var i = 20; i > 0; i--) {
            util.circle(this.ctx,
              this.position.x + (this.size + i/4) * Math.cos((this.orbitAt-curr) * Math.PI),
              this.position.y + (this.size + i/4) * Math.sin((this.orbitAt-curr) * Math.PI),
              i/4, 'rgba(221, 113, 8, ' + alpha + ')'
            );
            curr += inc * 1;
            alpha -= inc * 0.6;
        }
    };

    /**
     * Updates the positional coordinates of the object before render
     * @return {void}
     */
    proto.update = function(){
        if ( this.destroyed ) {
            return;
        }

        if ( this.position.y + this.physics.velocity >
            this.canvas.height + this.size ) {

            this.destroy();
            vent.emit('enemy-passed');

        } else {
            this.position.y = this.position.y + this.physics.velocity;
        }
    };

    /**
     * Destroys the Enemy object, allowing for explosion effect to occur. Emits
     * event to let canvas know not to render the object.
     * @param  {Boolean} shouldExplode
     * @return {void}
     */
    proto.destroy = function( shouldExplode ){
        if ( shouldExplode === true ) {
            vent.emit('kaboom!', this.position.x, this.position.y);
        }
        this.position = { x: 0, y: 0 };
        this.destroyed = true;
        this.deactivateAllEffects();
        vent.emit('entity-destroyed', this);
    };

    /**
     * This method is invoked when the object is being picked out of the sprite
     * pool, the purpose of which is to move the item out of a destroyed state
     * so that it can be reused in the canvas space.
     * @return {void}
     */
    proto.recycle = function(){
        this.position.x = Math.random() * this.canvas.width;
        this.position.y = 0 - this.size;
        this.physics.velocityGoal = util.random(0.1, 0.5);
        this.destroyed = false;
        this.fillStyle = util.randomColor();
    };

    /**
     * Sets the appropriate elemental effect on the enemy, or in the absence of
     * a parameter, will unset all effects.
     *
     * Note: since water is the only effect that works on new enemies and this
     * is invoked during Enemy#create, this can only set water effects.
     *
     * @param {String} effect
     * @return {void}
     */
    proto.setEffect = function( effect ){
        if ( effect === 'water' ) {
            this.waterEffect(true);
        } else {
            this.deactivateAllEffects();
        }
    };

    /**
     * Activates or deactivates the water elemental effect.
     * @param {Boolean} activate
     * @return {void}
     */
    proto.waterEffect = function( activate ){
        this.effected = !!activate;
        if ( activate ) {
            this.currentGradient = this.gradients.water;
            this.physics.velocity = this.physics.velocity * 0.3;
        } else {
            this.currentGradient = this.gradients.default;
            this.physics.velocityGoal = util.random(0.5, 1);
        }
    };

    /**
     * Activates or deactivates the air elemental effect
     * @param {Boolean} activate
     * @return {void}
     */
    proto.airEffect = function( activate ){
        this.effected = activate;
        if ( activate ) {
            this.currentGradient = this.gradients.air;
            this.physics.velocity = 0;
        } else {
            this.currentGradient = this.gradients.default;
            this.physics.velocity = util.random(0.5, 1);
        }
    };

    /**
     * Deactivates all elemental effects on the Enemy
     * @return {void}
     */
    proto.deactivateAllEffects = function(){
        this.waterEffect(false);
        this.airEffect(false);
    };

    return Enemy;

});
<|endoftext|>


<commit_before>import React                   from 'react';
import ReactDOM                from 'react-dom';
import { Route, Switch }       from 'react-router';
import { BrowserRouter, NavLink } from 'react-router-dom';

import TimeTravelExample from './time_travel';
import StateTreeView     from './tree_view';
import ApiRouter         from './api';
import NonApiRouter      from './non_api';

const styles = {
  style: { marginRight: '8px' },
  activeStyle: { color: 'green' }
};

ReactDOM.render((

        <StateTreeView />
        <TimeTravelExample />

    <BrowserRouter>

        Api examples:

        <NavLink { ...styles } to="/api/collections">collections</NavLink>
        <NavLink { ...styles } to="/api/model">model</NavLink>
        <NavLink { ...styles } to="/api/search">search</NavLink>
        <NavLink { ...styles } to="/api/raw">raw api methods</NavLink>
        <NavLink { ...styles } to="/api/decorator">decorator</NavLink>

        Non-Api examples:

        <NavLink { ...styles } to="/non-api/subscribe">subscribe</NavLink>
        <NavLink { ...styles } to="/non-api/containers">containers</NavLink>

        <Switch>
          <Route path="/api" component={ ApiRouter } />
          <Route path="/non-api" component={ NonApiRouter } />
        </Switch>

    </BrowserRouter>

), document.getElementById( 'root' ));
<commit_msg>denest table<commit_after>import React                   from 'react';
import ReactDOM                from 'react-dom';
import { Route, Switch }       from 'react-router';
import { BrowserRouter, NavLink } from 'react-router-dom';

import TimeTravelExample from './time_travel';
import StateTreeView     from './tree_view';
import ApiRouter         from './api';
import NonApiRouter      from './non_api';

const styles = {
  style: { marginRight: '8px' },
  activeStyle: { color: 'green' }
};

ReactDOM.render((

          <StateTreeView />
          <TimeTravelExample />

    <BrowserRouter>

        Api examples:

        <NavLink { ...styles } to="/api/collections">collections</NavLink>
        <NavLink { ...styles } to="/api/model">model</NavLink>
        <NavLink { ...styles } to="/api/search">search</NavLink>
        <NavLink { ...styles } to="/api/raw">raw api methods</NavLink>
        <NavLink { ...styles } to="/api/decorator">decorator</NavLink>

        Non-Api examples:

        <NavLink { ...styles } to="/non-api/subscribe">subscribe</NavLink>
        <NavLink { ...styles } to="/non-api/containers">containers</NavLink>

        <Switch>
          <Route path="/api" component={ ApiRouter } />
          <Route path="/non-api" component={ NonApiRouter } />
        </Switch>

    </BrowserRouter>

), document.getElementById( 'root' ));
<|endoftext|>


<commit_before>/**
* The `Matter.Bodies` module contains factory methods for creating rigid body models
* with commonly used body configurations (such as rectangles, circles and other polygons).
*
* See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples).
*
* @class Bodies
*/

// TODO: true circle bodies

var Bodies = {};

module.exports = Bodies;

var Vertices = require('../geometry/Vertices');
var Common = require('../core/Common');
var Body = require('../body/Body');
var Bounds = require('../geometry/Bounds');
var Vector = require('../geometry/Vector');

(function() {

    /**
     * Creates a new rigid body model with a rectangle hull.
     * The options parameter is an object that specifies any properties you wish to override the defaults.
     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
     * @method rectangle
     * @param {number} x
     * @param {number} y
     * @param {number} width
     * @param {number} height
     * @param {object} [options]
     * @return {body} A new rectangle body
     */
    Bodies.rectangle = function(x, y, width, height, options) {
        options = options || {};

        var rectangle = {
            label: 'Rectangle Body',
            position: { x: x, y: y },
            vertices: Vertices.fromPath('L 0 0 L ' + width + ' 0 L ' + width + ' ' + height + ' L 0 ' + height)
        };

        if (options.chamfer) {
            var chamfer = options.chamfer;
            rectangle.vertices = Vertices.chamfer(rectangle.vertices, chamfer.radius,
                chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
            delete options.chamfer;
        }

        return Body.create(Common.extend({}, rectangle, options));
    };

    /**
     * Creates a new rigid body model with a trapezoid hull.
     * The options parameter is an object that specifies any properties you wish to override the defaults.
     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
     * @method trapezoid
     * @param {number} x
     * @param {number} y
     * @param {number} width
     * @param {number} height
     * @param {number} slope
     * @param {object} [options]
     * @return {body} A new trapezoid body
     */
    Bodies.trapezoid = function(x, y, width, height, slope, options) {
        options = options || {};

        slope *= 0.5;
        var roof = (1 - (slope * 2)) * width;

        var x1 = width * slope,
            x2 = x1 + roof,
            x3 = x2 + x1,
            verticesPath;

        if (slope < 0.5) {
            verticesPath = 'L 0 0 L ' + x1 + ' ' + (-height) + ' L ' + x2 + ' ' + (-height) + ' L ' + x3 + ' 0';
        } else {
            verticesPath = 'L 0 0 L ' + x2 + ' ' + (-height) + ' L ' + x3 + ' 0';
        }

        var trapezoid = {
            label: 'Trapezoid Body',
            position: { x: x, y: y },
            vertices: Vertices.fromPath(verticesPath)
        };

        if (options.chamfer) {
            var chamfer = options.chamfer;
            trapezoid.vertices = Vertices.chamfer(trapezoid.vertices, chamfer.radius,
                chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
            delete options.chamfer;
        }

        return Body.create(Common.extend({}, trapezoid, options));
    };

    /**
     * Creates a new rigid body model with a circle hull.
     * The options parameter is an object that specifies any properties you wish to override the defaults.
     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
     * @method circle
     * @param {number} x
     * @param {number} y
     * @param {number} radius
     * @param {object} [options]
     * @param {number} [maxSides]
     * @return {body} A new circle body
     */
    Bodies.circle = function(x, y, radius, options, maxSides) {
        options = options || {};

        var circle = {
            label: 'Circle Body',
            circleRadius: radius
        };

        // approximate circles with polygons until true circles implemented in SAT
        maxSides = maxSides || 25;
        var sides = Math.ceil(Math.max(10, Math.min(maxSides, radius)));

        // optimisation: always use even number of sides (half the number of unique axes)
        if (sides % 2 === 1)
            sides += 1;

        return Bodies.polygon(x, y, sides, radius, Common.extend({}, circle, options));
    };

    /**
     * Creates a new rigid body model with a regular polygon hull with the given number of sides.
     * The options parameter is an object that specifies any properties you wish to override the defaults.
     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
     * @method polygon
     * @param {number} x
     * @param {number} y
     * @param {number} sides
     * @param {number} radius
     * @param {object} [options]
     * @return {body} A new regular polygon body
     */
    Bodies.polygon = function(x, y, sides, radius, options) {
        options = options || {};

        if (sides < 3)
            return Bodies.circle(x, y, radius, options);

        var theta = 2 * Math.PI / sides,
            path = '',
            offset = theta * 0.5;

        for (var i = 0; i < sides; i += 1) {
            var angle = offset + (i * theta),
                xx = Math.cos(angle) * radius,
                yy = Math.sin(angle) * radius;

            path += 'L ' + xx.toFixed(3) + ' ' + yy.toFixed(3) + ' ';
        }

        var polygon = {
            label: 'Polygon Body',
            position: { x: x, y: y },
            vertices: Vertices.fromPath(path)
        };

        if (options.chamfer) {
            var chamfer = options.chamfer;
            polygon.vertices = Vertices.chamfer(polygon.vertices, chamfer.radius,
                chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
            delete options.chamfer;
        }

        return Body.create(Common.extend({}, polygon, options));
    };

    /**
     * Creates a body using the supplied vertices (or an array containing multiple sets of vertices).
     * If the vertices are convex, they will pass through as supplied.
     * Otherwise if the vertices are concave, they will be decomposed if [poly-decomp.js](https://github.com/schteppe/poly-decomp.js) is available.
     * Note that this process is not guaranteed to support complex sets of vertices (e.g. those with holes may fail).
     * By default the decomposition will discard collinear edges (to improve performance).
     * It can also optionally discard any parts that have an area less than `minimumArea`.
     * If the vertices can not be decomposed, the result will fall back to using the convex hull.
     * The options parameter is an object that specifies any `Matter.Body` properties you wish to override the defaults.
     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
     * @method fromVertices
     * @param {number} x
     * @param {number} y
     * @param [[vector]] vertexSets
     * @param {object} [options]
     * @param {bool} [flagInternal=false]
     * @param {number} [removeCollinear=0.01]
     * @param {number} [minimumArea=10]
     * @param {number} [removeDuplicatePoints=0.01]
     * @return {body}
     */
    Bodies.fromVertices = function(x, y, vertexSets, options, flagInternal, removeCollinear, minimumArea, removeDuplicatePoints) {
        var globals = typeof global !== 'undefined' ? global : window,
            decomp,
            body,
            parts,
            isConvex,
            vertices,
            i,
            j,
            k,
            v,
            z;

        try {
            decomp = globals.decomp || require('poly-decomp');
        } catch (e) {
            // decomp is undefined
        }

        options = options || {};
        parts = [];

        flagInternal = typeof flagInternal !== 'undefined' ? flagInternal : false;
        removeCollinear = typeof removeCollinear !== 'undefined' ? removeCollinear : 0.01;
        minimumArea = typeof minimumArea !== 'undefined' ? minimumArea : 10;
        removeDuplicatePoints = typeof removeDuplicatePoints !== 'undefined' ? removeDuplicatePoints : 0.01;

        if (!decomp) {
            Common.warn('Bodies.fromVertices: poly-decomp.js required. Could not decompose vertices. Fallback to convex hull.');
        }

        // ensure vertexSets is an array of arrays
        if (!Common.isArray(vertexSets[0])) {
            vertexSets = [vertexSets];
        }

        for (v = 0; v < vertexSets.length; v += 1) {
            vertices = vertexSets[v];
            isConvex = Vertices.isConvex(vertices);

            if (isConvex || !decomp) {
                if (isConvex) {
                    vertices = Vertices.clockwiseSort(vertices);
                } else {
                    // fallback to convex hull when decomposition is not possible
                    vertices = Vertices.hull(vertices);
                }

                parts.push({
                    position: { x: x, y: y },
                    vertices: vertices
                });
            } else {
                // initialise a decomposition
                var concave = vertices.map(function(vertex) {
                    return [vertex.x, vertex.y];
                });

                // vertices are concave and simple, we can decompose into parts
                decomp.makeCCW(concave);
                if (removeCollinear !== false)
                    decomp.removeCollinearPoints(concave, removeCollinear);
                if (removeDuplicatePoints !== false)
                    decomp.removeDuplicatePoints(concave, removeDuplicatePoints);

                // use the quick decomposition algorithm (Bayazit)
                var decomposed = decomp.quickDecomp(concave);

                // for each decomposed chunk
                for (i = 0; i < decomposed.length; i++) {
                    var chunk = decomposed[i];

                    // convert vertices into the correct structure
                    var chunkVertices = chunk.map(function(vertices) {
                        return {
                            x: vertices[0],
                            y: vertices[1]
                        };
                    });

                    // skip small chunks
                    if (minimumArea > 0 && Vertices.area(chunkVertices) < minimumArea)
                        continue;

                    // create a compound part
                    parts.push({
                        position: Vertices.centre(chunkVertices),
                        vertices: chunkVertices
                    });
                }
            }
        }

        // create body parts
        for (i = 0; i < parts.length; i++) {
            parts[i] = Body.create(Common.extend(parts[i], options));
        }

        // flag internal edges (coincident part edges)
        if (flagInternal) {
            var coincident_max_dist = 5;

            for (i = 0; i < parts.length; i++) {
                var partA = parts[i];

                for (j = i + 1; j < parts.length; j++) {
                    var partB = parts[j];

                    if (Bounds.overlaps(partA.bounds, partB.bounds)) {
                        var pav = partA.vertices,
                            pbv = partB.vertices;

                        // iterate vertices of both parts
                        for (k = 0; k < partA.vertices.length; k++) {
                            for (z = 0; z < partB.vertices.length; z++) {
                                // find distances between the vertices
                                var da = Vector.magnitudeSquared(Vector.sub(pav[(k + 1) % pav.length], pbv[z])),
                                    db = Vector.magnitudeSquared(Vector.sub(pav[k], pbv[(z + 1) % pbv.length]));

                                // if both vertices are very close, consider the edge concident (internal)
                                if (da < coincident_max_dist && db < coincident_max_dist) {
                                    pav[k].isInternal = true;
                                    pbv[z].isInternal = true;
                                }
                            }
                        }

                    }
                }
            }
        }

        if (parts.length > 1) {
            // create the parent body to be returned, that contains generated compound parts
            body = Body.create(Common.extend({ parts: parts.slice(0) }, options));
            Body.setPosition(body, { x: x, y: y });

            return body;
        } else {
            return parts[0];
        }
    };

})();
<commit_msg>add check for decomp removeDuplicatePoints feature<commit_after>/**
* The `Matter.Bodies` module contains factory methods for creating rigid body models
* with commonly used body configurations (such as rectangles, circles and other polygons).
*
* See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples).
*
* @class Bodies
*/

// TODO: true circle bodies

var Bodies = {};

module.exports = Bodies;

var Vertices = require('../geometry/Vertices');
var Common = require('../core/Common');
var Body = require('../body/Body');
var Bounds = require('../geometry/Bounds');
var Vector = require('../geometry/Vector');

(function() {

    /**
     * Creates a new rigid body model with a rectangle hull.
     * The options parameter is an object that specifies any properties you wish to override the defaults.
     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
     * @method rectangle
     * @param {number} x
     * @param {number} y
     * @param {number} width
     * @param {number} height
     * @param {object} [options]
     * @return {body} A new rectangle body
     */
    Bodies.rectangle = function(x, y, width, height, options) {
        options = options || {};

        var rectangle = {
            label: 'Rectangle Body',
            position: { x: x, y: y },
            vertices: Vertices.fromPath('L 0 0 L ' + width + ' 0 L ' + width + ' ' + height + ' L 0 ' + height)
        };

        if (options.chamfer) {
            var chamfer = options.chamfer;
            rectangle.vertices = Vertices.chamfer(rectangle.vertices, chamfer.radius,
                chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
            delete options.chamfer;
        }

        return Body.create(Common.extend({}, rectangle, options));
    };

    /**
     * Creates a new rigid body model with a trapezoid hull.
     * The options parameter is an object that specifies any properties you wish to override the defaults.
     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
     * @method trapezoid
     * @param {number} x
     * @param {number} y
     * @param {number} width
     * @param {number} height
     * @param {number} slope
     * @param {object} [options]
     * @return {body} A new trapezoid body
     */
    Bodies.trapezoid = function(x, y, width, height, slope, options) {
        options = options || {};

        slope *= 0.5;
        var roof = (1 - (slope * 2)) * width;

        var x1 = width * slope,
            x2 = x1 + roof,
            x3 = x2 + x1,
            verticesPath;

        if (slope < 0.5) {
            verticesPath = 'L 0 0 L ' + x1 + ' ' + (-height) + ' L ' + x2 + ' ' + (-height) + ' L ' + x3 + ' 0';
        } else {
            verticesPath = 'L 0 0 L ' + x2 + ' ' + (-height) + ' L ' + x3 + ' 0';
        }

        var trapezoid = {
            label: 'Trapezoid Body',
            position: { x: x, y: y },
            vertices: Vertices.fromPath(verticesPath)
        };

        if (options.chamfer) {
            var chamfer = options.chamfer;
            trapezoid.vertices = Vertices.chamfer(trapezoid.vertices, chamfer.radius,
                chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
            delete options.chamfer;
        }

        return Body.create(Common.extend({}, trapezoid, options));
    };

    /**
     * Creates a new rigid body model with a circle hull.
     * The options parameter is an object that specifies any properties you wish to override the defaults.
     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
     * @method circle
     * @param {number} x
     * @param {number} y
     * @param {number} radius
     * @param {object} [options]
     * @param {number} [maxSides]
     * @return {body} A new circle body
     */
    Bodies.circle = function(x, y, radius, options, maxSides) {
        options = options || {};

        var circle = {
            label: 'Circle Body',
            circleRadius: radius
        };

        // approximate circles with polygons until true circles implemented in SAT
        maxSides = maxSides || 25;
        var sides = Math.ceil(Math.max(10, Math.min(maxSides, radius)));

        // optimisation: always use even number of sides (half the number of unique axes)
        if (sides % 2 === 1)
            sides += 1;

        return Bodies.polygon(x, y, sides, radius, Common.extend({}, circle, options));
    };

    /**
     * Creates a new rigid body model with a regular polygon hull with the given number of sides.
     * The options parameter is an object that specifies any properties you wish to override the defaults.
     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
     * @method polygon
     * @param {number} x
     * @param {number} y
     * @param {number} sides
     * @param {number} radius
     * @param {object} [options]
     * @return {body} A new regular polygon body
     */
    Bodies.polygon = function(x, y, sides, radius, options) {
        options = options || {};

        if (sides < 3)
            return Bodies.circle(x, y, radius, options);

        var theta = 2 * Math.PI / sides,
            path = '',
            offset = theta * 0.5;

        for (var i = 0; i < sides; i += 1) {
            var angle = offset + (i * theta),
                xx = Math.cos(angle) * radius,
                yy = Math.sin(angle) * radius;

            path += 'L ' + xx.toFixed(3) + ' ' + yy.toFixed(3) + ' ';
        }

        var polygon = {
            label: 'Polygon Body',
            position: { x: x, y: y },
            vertices: Vertices.fromPath(path)
        };

        if (options.chamfer) {
            var chamfer = options.chamfer;
            polygon.vertices = Vertices.chamfer(polygon.vertices, chamfer.radius,
                chamfer.quality, chamfer.qualityMin, chamfer.qualityMax);
            delete options.chamfer;
        }

        return Body.create(Common.extend({}, polygon, options));
    };

    /**
     * Creates a body using the supplied vertices (or an array containing multiple sets of vertices).
     * If the vertices are convex, they will pass through as supplied.
     * Otherwise if the vertices are concave, they will be decomposed if [poly-decomp.js](https://github.com/schteppe/poly-decomp.js) is available.
     * Note that this process is not guaranteed to support complex sets of vertices (e.g. those with holes may fail).
     * By default the decomposition will discard collinear edges (to improve performance).
     * It can also optionally discard any parts that have an area less than `minimumArea`.
     * If the vertices can not be decomposed, the result will fall back to using the convex hull.
     * The options parameter is an object that specifies any `Matter.Body` properties you wish to override the defaults.
     * See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
     * @method fromVertices
     * @param {number} x
     * @param {number} y
     * @param [[vector]] vertexSets
     * @param {object} [options]
     * @param {bool} [flagInternal=false]
     * @param {number} [removeCollinear=0.01]
     * @param {number} [minimumArea=10]
     * @param {number} [removeDuplicatePoints=0.01]
     * @return {body}
     */
    Bodies.fromVertices = function(x, y, vertexSets, options, flagInternal, removeCollinear, minimumArea, removeDuplicatePoints) {
        var globals = typeof global !== 'undefined' ? global : window,
            decomp,
            body,
            parts,
            isConvex,
            vertices,
            i,
            j,
            k,
            v,
            z;

        try {
            decomp = globals.decomp || require('poly-decomp');
        } catch (e) {
            // decomp is undefined
        }

        options = options || {};
        parts = [];

        flagInternal = typeof flagInternal !== 'undefined' ? flagInternal : false;
        removeCollinear = typeof removeCollinear !== 'undefined' ? removeCollinear : 0.01;
        minimumArea = typeof minimumArea !== 'undefined' ? minimumArea : 10;
        removeDuplicatePoints = typeof removeDuplicatePoints !== 'undefined' ? removeDuplicatePoints : 0.01;

        if (!decomp) {
            Common.warn('Bodies.fromVertices: poly-decomp.js required. Could not decompose vertices. Fallback to convex hull.');
        }

        // ensure vertexSets is an array of arrays
        if (!Common.isArray(vertexSets[0])) {
            vertexSets = [vertexSets];
        }

        for (v = 0; v < vertexSets.length; v += 1) {
            vertices = vertexSets[v];
            isConvex = Vertices.isConvex(vertices);

            if (isConvex || !decomp) {
                if (isConvex) {
                    vertices = Vertices.clockwiseSort(vertices);
                } else {
                    // fallback to convex hull when decomposition is not possible
                    vertices = Vertices.hull(vertices);
                }

                parts.push({
                    position: { x: x, y: y },
                    vertices: vertices
                });
            } else {
                // initialise a decomposition
                var concave = vertices.map(function(vertex) {
                    return [vertex.x, vertex.y];
                });

                // vertices are concave and simple, we can decompose into parts
                decomp.makeCCW(concave);
                if (removeCollinear !== false)
                    decomp.removeCollinearPoints(concave, removeCollinear);
                if (removeDuplicatePoints !== false && decomp.removeDuplicatePoints)
                    decomp.removeDuplicatePoints(concave, removeDuplicatePoints);

                // use the quick decomposition algorithm (Bayazit)
                var decomposed = decomp.quickDecomp(concave);

                // for each decomposed chunk
                for (i = 0; i < decomposed.length; i++) {
                    var chunk = decomposed[i];

                    // convert vertices into the correct structure
                    var chunkVertices = chunk.map(function(vertices) {
                        return {
                            x: vertices[0],
                            y: vertices[1]
                        };
                    });

                    // skip small chunks
                    if (minimumArea > 0 && Vertices.area(chunkVertices) < minimumArea)
                        continue;

                    // create a compound part
                    parts.push({
                        position: Vertices.centre(chunkVertices),
                        vertices: chunkVertices
                    });
                }
            }
        }

        // create body parts
        for (i = 0; i < parts.length; i++) {
            parts[i] = Body.create(Common.extend(parts[i], options));
        }

        // flag internal edges (coincident part edges)
        if (flagInternal) {
            var coincident_max_dist = 5;

            for (i = 0; i < parts.length; i++) {
                var partA = parts[i];

                for (j = i + 1; j < parts.length; j++) {
                    var partB = parts[j];

                    if (Bounds.overlaps(partA.bounds, partB.bounds)) {
                        var pav = partA.vertices,
                            pbv = partB.vertices;

                        // iterate vertices of both parts
                        for (k = 0; k < partA.vertices.length; k++) {
                            for (z = 0; z < partB.vertices.length; z++) {
                                // find distances between the vertices
                                var da = Vector.magnitudeSquared(Vector.sub(pav[(k + 1) % pav.length], pbv[z])),
                                    db = Vector.magnitudeSquared(Vector.sub(pav[k], pbv[(z + 1) % pbv.length]));

                                // if both vertices are very close, consider the edge concident (internal)
                                if (da < coincident_max_dist && db < coincident_max_dist) {
                                    pav[k].isInternal = true;
                                    pbv[z].isInternal = true;
                                }
                            }
                        }

                    }
                }
            }
        }

        if (parts.length > 1) {
            // create the parent body to be returned, that contains generated compound parts
            body = Body.create(Common.extend({ parts: parts.slice(0) }, options));
            Body.setPosition(body, { x: x, y: y });

            return body;
        } else {
            return parts[0];
        }
    };

})();
<|endoftext|>