<commit_before>import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import { expandNotifications, scrollTopNotifications } from '../../actions/notifications';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import NotificationContainer from './containers/notification_container';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container';
import { createSelector } from 'reselect';
import { List as ImmutableList } from 'immutable';
import { debounce } from 'lodash';
import ScrollableList from '../../components/scrollable_list';

const messages = defineMessages({
  title: { id: 'column.notifications', defaultMessage: 'Notifications' },
});

const getNotifications = createSelector([
  state => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
  state => state.getIn(['notifications', 'items']),
], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type'))));

const mapStateToProps = state => ({
  notifications: getNotifications(state),
  isLoading: state.getIn(['notifications', 'isLoading'], true),
  isUnread: state.getIn(['notifications', 'unread']) > 0,
  hasMore: !!state.getIn(['notifications', 'next']),
});

@connect(mapStateToProps)
@injectIntl
export default class Notifications extends React.PureComponent {

  static propTypes = {
    columnId: PropTypes.string,
    notifications: ImmutablePropTypes.list.isRequired,
    dispatch: PropTypes.func.isRequired,
    shouldUpdateScroll: PropTypes.func,
    intl: PropTypes.object.isRequired,
    isLoading: PropTypes.bool,
    isUnread: PropTypes.bool,
    multiColumn: PropTypes.bool,
    hasMore: PropTypes.bool,
  };

  static defaultProps = {
    trackScroll: true,
  };

  handleScrollToBottom = debounce(() => {
    this.props.dispatch(scrollTopNotifications(false));
    this.props.dispatch(expandNotifications());
  }, 300, { leading: true });

  handleScrollToTop = debounce(() => {
    this.props.dispatch(scrollTopNotifications(true));
  }, 100);

  handleScroll = debounce(() => {
    this.props.dispatch(scrollTopNotifications(false));
  }, 100);

  handlePin = () => {
    const { columnId, dispatch } = this.props;

    if (columnId) {
      dispatch(removeColumn(columnId));
    } else {
      dispatch(addColumn('NOTIFICATIONS', {}));
    }
  }

  handleMove = (dir) => {
    const { columnId, dispatch } = this.props;
    dispatch(moveColumn(columnId, dir));
  }

  handleHeaderClick = () => {
    this.column.scrollTop();
  }

  setColumnRef = c => {
    this.column = c;
  }

  handleMoveUp = id => {
    const elementIndex = this.props.notifications.findIndex(item => item.get('id') === id) - 1;
    this._selectChild(elementIndex);
  }

  handleMoveDown = id => {
    const elementIndex = this.props.notifications.findIndex(item => item.get('id') === id) + 1;
    this._selectChild(elementIndex);
  }

  _selectChild (index) {
    const element = this.column.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);

    if (element) {
      element.focus();
    }
  }

  render () {
    const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore } = this.props;
    const pinned = !!columnId;
    const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;

    let scrollableContent = null;

    if (isLoading && this.scrollableContent) {
      scrollableContent = this.scrollableContent;
    } else if (notifications.size > 0 || hasMore) {
      scrollableContent = notifications.map((item) => (
        <NotificationContainer
          key={item.get('id')}
          notification={item}
          accountId={item.get('account')}
          onMoveUp={this.handleMoveUp}
          onMoveDown={this.handleMoveDown}
        />
      ));
    } else {
      scrollableContent = null;
    }

    this.scrollableContent = scrollableContent;

    const scrollContainer = (
      <ScrollableList
        scrollKey={`notifications-${columnId}`}
        trackScroll={!pinned}
        isLoading={isLoading}
        hasMore={hasMore}
        emptyMessage={emptyMessage}
        onScrollToBottom={this.handleScrollToBottom}
        onScrollToTop={this.handleScrollToTop}
        onScroll={this.handleScroll}
        shouldUpdateScroll={shouldUpdateScroll}
      >
        {scrollableContent}
      </ScrollableList>
    );

    return (
      <Column ref={this.setColumnRef}>
        <ColumnHeader
          icon='bell'
          active={isUnread}
          title={intl.formatMessage(messages.title)}
          onPin={this.handlePin}
          onMove={this.handleMove}
          onClick={this.handleHeaderClick}
          pinned={pinned}
          multiColumn={multiColumn}
        >
          <ColumnSettingsContainer />
        </ColumnHeader>

        {scrollContainer}
      </Column>
    );
  }

}
<commit_msg>Finalize location on scrollable notifications when unmounting (#6614)<commit_after>import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import { expandNotifications, scrollTopNotifications } from '../../actions/notifications';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import NotificationContainer from './containers/notification_container';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container';
import { createSelector } from 'reselect';
import { List as ImmutableList } from 'immutable';
import { debounce } from 'lodash';
import ScrollableList from '../../components/scrollable_list';

const messages = defineMessages({
  title: { id: 'column.notifications', defaultMessage: 'Notifications' },
});

const getNotifications = createSelector([
  state => ImmutableList(state.getIn(['settings', 'notifications', 'shows']).filter(item => !item).keys()),
  state => state.getIn(['notifications', 'items']),
], (excludedTypes, notifications) => notifications.filterNot(item => excludedTypes.includes(item.get('type'))));

const mapStateToProps = state => ({
  notifications: getNotifications(state),
  isLoading: state.getIn(['notifications', 'isLoading'], true),
  isUnread: state.getIn(['notifications', 'unread']) > 0,
  hasMore: !!state.getIn(['notifications', 'next']),
});

@connect(mapStateToProps)
@injectIntl
export default class Notifications extends React.PureComponent {

  static propTypes = {
    columnId: PropTypes.string,
    notifications: ImmutablePropTypes.list.isRequired,
    dispatch: PropTypes.func.isRequired,
    shouldUpdateScroll: PropTypes.func,
    intl: PropTypes.object.isRequired,
    isLoading: PropTypes.bool,
    isUnread: PropTypes.bool,
    multiColumn: PropTypes.bool,
    hasMore: PropTypes.bool,
  };

  static defaultProps = {
    trackScroll: true,
  };

  componentWillUnmount () {
    this.handleScrollToBottom.cancel();
    this.handleScrollToTop.cancel();
    this.handleScroll.cancel();
    this.props.dispatch(scrollTopNotifications(false));
  }

  handleScrollToBottom = debounce(() => {
    this.props.dispatch(scrollTopNotifications(false));
    this.props.dispatch(expandNotifications());
  }, 300, { leading: true });

  handleScrollToTop = debounce(() => {
    this.props.dispatch(scrollTopNotifications(true));
  }, 100);

  handleScroll = debounce(() => {
    this.props.dispatch(scrollTopNotifications(false));
  }, 100);

  handlePin = () => {
    const { columnId, dispatch } = this.props;

    if (columnId) {
      dispatch(removeColumn(columnId));
    } else {
      dispatch(addColumn('NOTIFICATIONS', {}));
    }
  }

  handleMove = (dir) => {
    const { columnId, dispatch } = this.props;
    dispatch(moveColumn(columnId, dir));
  }

  handleHeaderClick = () => {
    this.column.scrollTop();
  }

  setColumnRef = c => {
    this.column = c;
  }

  handleMoveUp = id => {
    const elementIndex = this.props.notifications.findIndex(item => item.get('id') === id) - 1;
    this._selectChild(elementIndex);
  }

  handleMoveDown = id => {
    const elementIndex = this.props.notifications.findIndex(item => item.get('id') === id) + 1;
    this._selectChild(elementIndex);
  }

  _selectChild (index) {
    const element = this.column.node.querySelector(`article:nth-of-type(${index + 1}) .focusable`);

    if (element) {
      element.focus();
    }
  }

  render () {
    const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore } = this.props;
    const pinned = !!columnId;
    const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. Interact with others to start the conversation." />;

    let scrollableContent = null;

    if (isLoading && this.scrollableContent) {
      scrollableContent = this.scrollableContent;
    } else if (notifications.size > 0 || hasMore) {
      scrollableContent = notifications.map((item) => (
        <NotificationContainer
          key={item.get('id')}
          notification={item}
          accountId={item.get('account')}
          onMoveUp={this.handleMoveUp}
          onMoveDown={this.handleMoveDown}
        />
      ));
    } else {
      scrollableContent = null;
    }

    this.scrollableContent = scrollableContent;

    const scrollContainer = (
      <ScrollableList
        scrollKey={`notifications-${columnId}`}
        trackScroll={!pinned}
        isLoading={isLoading}
        hasMore={hasMore}
        emptyMessage={emptyMessage}
        onScrollToBottom={this.handleScrollToBottom}
        onScrollToTop={this.handleScrollToTop}
        onScroll={this.handleScroll}
        shouldUpdateScroll={shouldUpdateScroll}
      >
        {scrollableContent}
      </ScrollableList>
    );

    return (
      <Column ref={this.setColumnRef}>
        <ColumnHeader
          icon='bell'
          active={isUnread}
          title={intl.formatMessage(messages.title)}
          onPin={this.handlePin}
          onMove={this.handleMove}
          onClick={this.handleHeaderClick}
          pinned={pinned}
          multiColumn={multiColumn}
        >
          <ColumnSettingsContainer />
        </ColumnHeader>

        {scrollContainer}
      </Column>
    );
  }

}
<|endoftext|>


<commit_before>import 'babel-polyfill';
import login from './login';
import { loginRequired } from './login';
import { getDate,cachedProperty } from './utility';
import Promise from 'Bluebird';
import _ from 'lodash';

const request = Promise.promisifyAll(require('request'));

const MODES = ['daily','weekly','monthly','rookie','male','female']
const RESTRICT_MODES = ['daily_r18','weekly_r18','r18g','male_r18','female_r18']
const PAGE = 'http://www.pixiv.net/ranking.php?format=json';

class Rank {
    constructor(mode='daily',date=null){
        if (!(MODES.includes(mode)) && !(RESTRICT_MODES.includes(mode))){
            throw Error('Unvalid mode.');
        }
        this.mode = mode;
        // default yesterday.
        this.date = getDate(date || new Date()-30*3600*1000);
    }

    get url(){
        return `${PAGE}&mode=${this.mode}&date=${this.date}`;
    }

    async getPage(page=1){
        if (MODES.includes(this.mode)) {
            return await this._getPage(page);
        } else if (RESTRICT_MODES.includes(this.mode)) {
            return await this._getRestrictPage(page);
        }
    }

    @loginRequired
    async _getRestrictPage(page=1) {
        return this._getPage(page);
    }

    async _getPage(page){
        let resp = await request.getAsync({
            url: `${this.url}&p=${page}`,
            jar: login.cookieJar
        });
        if (resp.statusCode !== 200) {
             throw resp;
        }
        return JSON.parse(resp.body);
    }

    @cachedProperty
    async get(page){
        return await this.getPage(page);
    }

    @cachedProperty
    async getAllContents(){
        let contents = [];
        let page = 1;
        while (page){
            let data = await this.getPage(page);
            let len = data && data.contents && data.contents.length;
            if (!len) {
                break;
            }
            page = data.next;
            contents = contents.concat(data.contents);
        }
        return contents;
    }
}

export default Rank;
<commit_msg>Change default value of date.<commit_after>import 'babel-polyfill';
import login from './login';
import { loginRequired } from './login';
import { getDate,cachedProperty } from './utility';
import Promise from 'Bluebird';
import _ from 'lodash';

const request = Promise.promisifyAll(require('request'));

const MODES = ['daily','weekly','monthly','rookie','male','female']
const RESTRICT_MODES = ['daily_r18','weekly_r18','r18g','male_r18','female_r18']
const PAGE = 'http://www.pixiv.net/ranking.php?format=json';

class Rank {
    constructor(mode='daily',date=null){
        if (!(MODES.includes(mode)) && !(RESTRICT_MODES.includes(mode))){
            throw Error('Unvalid mode.');
        }
        this.mode = mode;
        // default 30 hours before.
        this.date = getDate(date || new Date()-30*3600*1000);
    }

    get url(){
        return `${PAGE}&mode=${this.mode}&date=${this.date}`;
    }

    async getPage(page=1){
        if (MODES.includes(this.mode)) {
            return await this._getPage(page);
        } else if (RESTRICT_MODES.includes(this.mode)) {
            return await this._getRestrictPage(page);
        }
    }

    @loginRequired
    async _getRestrictPage(page=1) {
        return this._getPage(page);
    }

    async _getPage(page){
        let resp = await request.getAsync({
            url: `${this.url}&p=${page}`,
            jar: login.cookieJar
        });
        if (resp.statusCode !== 200) {
             throw resp;
        }
        return JSON.parse(resp.body);
    }

    @cachedProperty
    async get(page){
        return await this.getPage(page);
    }

    @cachedProperty
    async getAllContents(){
        let contents = [];
        let page = 1;
        while (page){
            let data = await this.getPage(page);
            let len = data && data.contents && data.contents.length;
            if (!len) {
                break;
            }
            page = data.next;
            contents = contents.concat(data.contents);
        }
        return contents;
    }
}

export default Rank;
<|endoftext|>


<commit_before>var fs = require('fs');
var nodePath = require('path');
var files = require('./files');
var objects = require('./objects');

var refs = module.exports = {
  isRef: function(ref) {
    return ref === "HEAD" || isLocalHeadRef(ref);
  },

  readTerminalRef: function(ref) {
    if (ref === "HEAD") {
      return readHead();
    } else if (isLocalHeadRef(ref)) {
      return ref;
    } else {
      return refs.nameToBranchRef(ref);
    }
  },

  readHash: function(refOrHash) {
    if (refOrHash !== undefined) {
      if (objects.readExists(refOrHash)) {
        return refOrHash;
      } else if (refs.readExists(refs.readTerminalRef(refOrHash))) {
        return files.read(nodePath.join(files.gitletDir(), refs.readTerminalRef(refOrHash)));
      } else if (refs.readExists(refs.nameToBranchRef(refOrHash))) {
        return files.read(nodePath.join(files.gitletDir(), refs.nameToBranchRef(refOrHash)));
      }
    }
  },

  nameToBranchRef: function(name) {
    return "refs/heads/" + name;
  },

  write: function(ref, content) {
    if (isLocalHeadRef(ref)) {
      files.write(nodePath.join(files.gitletDir(), ref), content);
    }
  },

  readLocalHeads: function() {
    return fs.readdirSync(nodePath.join(files.gitletDir(), "refs/heads/"));
  },

  readExists: function(ref) {
    return ref !== undefined &&
      isLocalHeadRef(ref) &&
      fs.existsSync(nodePath.join(files.gitletDir(), ref));
  },

  readCurrentBranchName: function() {
    if (readHead().match("refs")) {
      return readHead().match("refs/heads/(.+)")[1];
    }
  }
};

function readHead() {
  var content = files.read(nodePath.join(files.gitletDir(), "HEAD"));
  var refMatch = content.match("ref: (refs/heads/.+)");
  return refMatch ? refMatch[1] : content;
};

function isLocalHeadRef(ref) {
  return ref.match("refs/heads/[A-Za-z-]+");
};
<commit_msg>Allow refs.write() to write HEAD<commit_after>var fs = require('fs');
var nodePath = require('path');
var files = require('./files');
var objects = require('./objects');

var refs = module.exports = {
  isRef: function(ref) {
    return ref === "HEAD" || isLocalHeadRef(ref);
  },

  readTerminalRef: function(ref) {
    if (ref === "HEAD") {
      return readHead();
    } else if (isLocalHeadRef(ref)) {
      return ref;
    } else {
      return refs.nameToBranchRef(ref);
    }
  },

  readHash: function(refOrHash) {
    if (refOrHash !== undefined) {
      if (objects.readExists(refOrHash)) {
        return refOrHash;
      } else if (refs.readExists(refs.readTerminalRef(refOrHash))) {
        return files.read(nodePath.join(files.gitletDir(), refs.readTerminalRef(refOrHash)));
      } else if (refs.readExists(refs.nameToBranchRef(refOrHash))) {
        return files.read(nodePath.join(files.gitletDir(), refs.nameToBranchRef(refOrHash)));
      }
    }
  },

  nameToBranchRef: function(name) {
    return "refs/heads/" + name;
  },

  write: function(ref, content) {
    if (ref === "HEAD") {
      files.write(nodePath.join(files.gitletDir(), "HEAD"), content);
    } else if (isLocalHeadRef(ref)) {
      files.write(nodePath.join(files.gitletDir(), ref), content);
    }
  },

  readLocalHeads: function() {
    return fs.readdirSync(nodePath.join(files.gitletDir(), "refs/heads/"));
  },

  readExists: function(ref) {
    return ref !== undefined &&
      isLocalHeadRef(ref) &&
      fs.existsSync(nodePath.join(files.gitletDir(), ref));
  },

  readCurrentBranchName: function() {
    if (readHead().match("refs")) {
      return readHead().match("refs/heads/(.+)")[1];
    }
  }
};

function readHead() {
  var content = files.read(nodePath.join(files.gitletDir(), "HEAD"));
  var refMatch = content.match("ref: (refs/heads/.+)");
  return refMatch ? refMatch[1] : content;
};

function isLocalHeadRef(ref) {
  return ref.match("refs/heads/[A-Za-z-]+");
};
<|endoftext|>


<commit_before>
export const baseURL = 'https://moecoop.arukascloud.io'

export function getCall(url, callback) {
  const xhr = new XMLHttpRequest()
  xhr.onreadystatechange = function() {
    callback(xhr)
  }
  xhr.respenseType = 'json'
  xhr.open('GET', url, true)
  xhr.send()
}

export function postCall(url, param, callback) {
  const xhr = new XMLHttpRequest()
  xhr.onreadystatechange = function() {
    callback(xhr)
  }
  xhr.open('POST', url, true)
  xhr.setRequestHeader('Content-Type', 'application/json')
  xhr.send(JSON.stringify(param))
}
<commit_msg>知恵袋サーバーの URL を修正<commit_after>
export const baseURL = 'https://moecoop-server.herokuapp.com'

export function getCall(url, callback) {
  const xhr = new XMLHttpRequest()
  xhr.onreadystatechange = function() {
    callback(xhr)
  }
  xhr.respenseType = 'json'
  xhr.open('GET', url, true)
  xhr.send()
}

export function postCall(url, param, callback) {
  const xhr = new XMLHttpRequest()
  xhr.onreadystatechange = function() {
    callback(xhr)
  }
  xhr.open('POST', url, true)
  xhr.setRequestHeader('Content-Type', 'application/json')
  xhr.send(JSON.stringify(param))
}
<|endoftext|>


<commit_before>import * as THREE from './lib/three';

import props from './props';
import { tempQuaternion, tempVector, createInstancedMesh } from './utils/three';
import viewer from './viewer';
import settings from './settings';
import audio from './audio';
import storage from './storage';
import { getCostumeColor } from './theme/colors';

const num = 20;
let roomIndex = 0;
let roomMesh;
let roomMeshes;

const roomOffset = new THREE.Vector3(0, settings.roomHeight * 0.5, 0);

const transformMesh = (
  instancedMesh,
  index,
  [x, y, z, qx, qy, qz, qw],
  scale,
  offset,
) => {
  instancedMesh.setQuaternionAt(index, tempQuaternion(qx, qy, qz, qw));
  instancedMesh.setPositionAt(
    index,
    tempVector(x, y, z - settings.roomOffset).add(offset),
  );
  instancedMesh.setScaleAt(index, tempVector(scale, scale, scale));
  instancedMesh.needsUpdate();
};

props.on('loaded', () => {
  roomMeshes = {
    default: createInstancedMesh(
      num,
      getCostumeColor(0),
      props.room.geometry,
    ),
    ortographic: createInstancedMesh(
      num,
      getCostumeColor(0),
      props.ortographicRoom.geometry,
    ),
  };
  roomMesh = roomMeshes.default;
  viewer.scene.add(roomMesh);
});

export default class Room {
  constructor({ showHead, url }) {
    this.index = roomIndex;
    this.showHead = showHead;
    this.url = url;
    roomIndex += 1;
    this.position = new THREE.Vector3();

    const costumeColor = getCostumeColor(this.index);

    this.position.set(
      0,
      0,
      settings.roomOffset + (this.index * (settings.roomDepth + 0.001)),
    );
    this.updatePosition();

    this.handMesh = createInstancedMesh(
      200,
      costumeColor,
      props.hand.geometry,
    );
    viewer.scene.add(this.handMesh);

    if (showHead) {
      this.headMesh = createInstancedMesh(
        200,
        costumeColor,
        props.head.geometry,
      );
      viewer.scene.add(this.headMesh);
    }
  }

  load(callback) {
    storage.load(this.url, (error, performances) => {
      if (error) return callback(error);
      this.performances = performances;
      callback();
    });
  }

  updatePosition() {
    const position = tempVector(0, 0, 0).add(this.position).add(roomOffset);
    for (const i in roomMeshes) {
      roomMeshes[i].setPositionAt(this.index, position);
      roomMeshes[i].needsUpdate('position');
    }
  }

  gotoTime(seconds) {
    if (!this.performances) return;

    const frameNumber = Math.floor((seconds % (audio.loopDuration * 2)) * 90);
    // In orthographic mode, scale up the meshes:
    const scale = roomMesh === roomMeshes.ortographic ? 1.3 : 1;
    for (let i = 0; i < this.performances.length; i++) {
      const frames = this.performances[i];
      const [head, left, right] = frames[frameNumber % frames.length];
      if (this.showHead) {
        transformMesh(this.headMesh, i, head, scale, this.position);
      }
      transformMesh(this.handMesh, i * 2, left, scale, this.position);
      transformMesh(this.handMesh, (i * 2) + 1, right, scale, this.position);
    }
  }
}

Room.switchModel = (model) => {
  viewer.scene.remove(roomMesh);
  roomMesh = roomMeshes[model];
  viewer.scene.add(roomMesh);
};
<commit_msg>implement room#destroy<commit_after>import * as THREE from './lib/three';

import props from './props';
import { tempQuaternion, tempVector, createInstancedMesh } from './utils/three';
import viewer from './viewer';
import settings from './settings';
import audio from './audio';
import storage from './storage';
import { getCostumeColor } from './theme/colors';

const num = 20;
let roomIndex = 0;
let roomMesh;
let roomMeshes;

const roomOffset = new THREE.Vector3(0, settings.roomHeight * 0.5, 0);

const transformMesh = (
  instancedMesh,
  index,
  [x, y, z, qx, qy, qz, qw],
  scale,
  offset,
) => {
  instancedMesh.setQuaternionAt(index, tempQuaternion(qx, qy, qz, qw));
  instancedMesh.setPositionAt(
    index,
    tempVector(x, y, z - settings.roomOffset).add(offset),
  );
  instancedMesh.setScaleAt(index, tempVector(scale, scale, scale));
  instancedMesh.needsUpdate();
};

props.on('loaded', () => {
  roomMeshes = {
    default: createInstancedMesh(
      num,
      getCostumeColor(0),
      props.room.geometry,
    ),
    ortographic: createInstancedMesh(
      num,
      getCostumeColor(0),
      props.ortographicRoom.geometry,
    ),
  };
  roomMesh = roomMeshes.default;
  viewer.scene.add(roomMesh);
});

export default class Room {
  constructor({ showHead, url }) {
    this.index = roomIndex;
    this.showHead = showHead;
    this.url = url;
    roomIndex += 1;
    this.position = new THREE.Vector3();

    const costumeColor = getCostumeColor(this.index);

    this.position.set(
      0,
      0,
      settings.roomOffset + (this.index * (settings.roomDepth + 0.001)),
    );
    this.updatePosition();

    this.handMesh = createInstancedMesh(
      200,
      costumeColor,
      props.hand.geometry,
    );
    viewer.scene.add(this.handMesh);

    if (showHead) {
      this.headMesh = createInstancedMesh(
        200,
        costumeColor,
        props.head.geometry,
      );
      viewer.scene.add(this.headMesh);
    }
  }

  load(callback) {
    storage.load(this.url, (error, performances) => {
      if (error) return callback(error);
      this.performances = performances;
      callback();
    });
  }

  updatePosition() {
    const position = tempVector(0, 0, 0).add(this.position).add(roomOffset);
    for (const i in roomMeshes) {
      roomMeshes[i].setPositionAt(this.index, position);
      roomMeshes[i].needsUpdate('position');
    }
  }

  gotoTime(seconds) {
    if (!this.performances) return;

    const frameNumber = Math.floor((seconds % (audio.loopDuration * 2)) * 90);
    // In orthographic mode, scale up the meshes:
    const scale = roomMesh === roomMeshes.ortographic ? 1.3 : 1;
    for (let i = 0; i < this.performances.length; i++) {
      const frames = this.performances[i];
      const [head, left, right] = frames[frameNumber % frames.length];
      if (this.showHead) {
        transformMesh(this.headMesh, i, head, scale, this.position);
      }
      transformMesh(this.handMesh, i * 2, left, scale, this.position);
      transformMesh(this.handMesh, (i * 2) + 1, right, scale, this.position);
    }
  }

  destroy() {
    viewer.scene.remove(this.headMesh);
    viewer.scene.remove(this.handMesh);
    viewer.scene.remove(roomMesh);
  }
}

Room.switchModel = (model) => {
  viewer.scene.remove(roomMesh);
  roomMesh = roomMeshes[model];
  viewer.scene.add(roomMesh);
};
<|endoftext|>


<commit_before>import fs from 'fs';
import path from 'path';
import { Reader } from 'fstream';
import { Pack } from 'tar';

/** pose.save
 * @desc Save a template.
 * @param {Object} opts - Options.
 */
function save(opts = {}) {
  const name = opts._[1] || opts.name;

  const destPath = path.join(opts._templates, `${name}.tar`);
  const dest = fs.createWriteStream(destPath);

  const pack = new Pack({ noProprietary: true });

  const read = new Reader({ path: opts.entry, type: 'Directory' })
  .pipe(pack)
  .pipe(dest);

  pack.on('end', () => {
    console.log(`Saved ${name}.`);
  });

  pack.on('error', () => {
    console.log(`Failed to pack ${destPath}`);
  });

  read.on('error', () => {
    console.log(`Failed to read ${opts.entry}.`);
  });
}

export default save;
<commit_msg>Change entry for CLI layout<commit_after>import fs from 'fs';
import path from 'path';
import { Reader } from 'fstream';
import { Pack } from 'tar';

/** pose.save
 * @desc Save a template.
 * @param {Object} opts - Options.
 */
function save(opts = {}) {
  const name = opts._[1] || opts.name;
  const entry = opts._[2] || opts.entry;

  const destPath = path.join(opts._templates, `${name}.tar`);
  const dest = fs.createWriteStream(destPath);

  const pack = new Pack({ noProprietary: true });

  const read = new Reader({ path: entry, type: 'Directory' })
  .pipe(pack)
  .pipe(dest);

  pack.on('end', () => {
    console.log(`Saved ${name}.`);
  });

  pack.on('error', () => {
    console.log(`Failed to pack ${destPath}`);
  });

  read.on('error', () => {
    console.log(`Failed to read ${entry}.`);
  });
}

export default save;
<|endoftext|>


<commit_before>// 'use strict';
//
// var Turn = require('./models/turns.js');
//
// var turns = [
//   'Player 1s turn',
//   'Player 2s turn',
//   'Player 1s turn'
// ]
//
// turns.forEach(function(turn, index){
//   Turn.find({'name': turn}, function(err, turns){
//     if(!err && !turns.length){
//       Turn.create({timestamp: true, name: turn});
//     };
//   });
// });
<commit_msg>Delete seed.js<commit_after><|endoftext|>


<commit_before>var fs = require('fs')
var process = require('process')
var nodemailer = require('nodemailer')       // https://github.com/nodemailer/nodemailer
var markdown = require('markdown').markdown  // https://github.com/evilstreak/markdown-js
var path = require('path')

var util = require('./util.js')
var log = require('./log.js')
var config = require('./config.js')

var root = path.resolve(__dirname, '../')
var logDir = path.resolve(root, 'logs/')

var run = process.argv.indexOf('-r') > -1
var date = process.argv.indexOf('-d') > -1 && process.argv[process.argv.indexOf('-d')+1] || undefined

// config
config.contentOptions = config.contentOptions || {}
config.transporterOptions = config.transporterOptions || {}
config.mailOptions = config.mailOptions || {}

var send = function () {
    var filename = util.generateFile(date).filename
    var filepath = path.resolve(logDir, './' + filename)
    var content
    try {
        content = fs.readFileSync(filepath, 'utf8')
    } catch (error) {
        console.log(error)
        log(error.msg || error,'E')
        return
    }

    // verify
    var verifyReg = (function(){
        var reg = config.contentOptions.verifyReg
        if(typeof reg === 'object') return reg
        var regStr = reg || '^([\\s\\S]*?)周一([\\s\\S]*?)周二([\\s\\S]*?)周三([\\s\\S]*?)周四([\\s\\S]*?)周五([\\s\\S]*?)$'
        reg = new RegExp(regStr)
    })()
    var verify = verifyReg.test(content)
    var msg = `${filepath} verified success,wait to send...`
    if (run) {
        msg = `${filepath} skip verifing, wait to send...`
        log(msg, 'I')
        return
    }
    if (!verify) {
        msg = `${filepath} verified fail, abort sending...`
        log(msg, 'W')
        return
    }
    log(msg, 'I')

    // mail content
    var mailContent = (function () {
        var _header = config.contentOptions.mailHeader || ''
        var _content = content
        var _footer = config.contentOptions.mailFooter || ''
        var _rawStr = `${_header}\n\r${_content}\n\r${_footer}\n\r`
        return config.contentOptions.parseMD ? markdown.toHTML(_rawStr) : _rawStr
    }())

    // mail sending

    var transporterOptions = Object.assign({
        host: 'smtp.mxhichina.com',
        port: 25,
    }, config.transporterOptions)

    var mailOptions = Object.assign({
        subject: filename.replace(/^log_(\d+)_(\d+)_(\d+)_(\d+)_(\d+)\.md$/, '周报 $2月$3日 -- $4月$5日'),
        html: mailContent.replace(/[\n\r]+/g, '
'),
    }, (
        !config.mailOptions.subject && delete config.mailOptions.subject,
        config.mailOptions
    ))

    var transporter = nodemailer.createTransport(transporterOptions)

    transporter.verify(function (error, success) {
        if (error) {
            log(error, 'E');
        } else {
            log('Server is ready to take our messages', 'I');

            transporter.sendMail(mailOptions, function (error, info) {
                if (error) return log(error, 'E')
                log(`Message sent: \n\r${mailContent}`, 'I')

            })
        }
    });
}

if (run) {
    send()
} else {
    module.exports = send
} <commit_msg>fix some bug<commit_after>var fs = require('fs')
var process = require('process')
var nodemailer = require('nodemailer')       // https://github.com/nodemailer/nodemailer
var markdown = require('markdown').markdown  // https://github.com/evilstreak/markdown-js
var path = require('path')

var util = require('./util.js')
var log = require('./log.js')
var config = require('./config.js')

var root = path.resolve(__dirname, '../')
var logDir = path.resolve(root, 'logs/')

var run = process.argv.indexOf('-r') > -1
var date = process.argv.indexOf('-d') > -1 && process.argv[process.argv.indexOf('-d')+1] || undefined

// config
config.contentOptions = config.contentOptions || {}
config.transporterOptions = config.transporterOptions || {}
config.mailOptions = config.mailOptions || {}

var send = function () {
    var filename = util.generateFile(date).filename
    var filepath = path.resolve(logDir, './' + filename)
    var content
    try {
        content = fs.readFileSync(filepath, 'utf8')
    } catch (error) {
        log(error.msg || error,'E')
        return
    }

    // verify
    var verifyReg = (function(){
        var reg = config.contentOptions.verifyReg
        if(typeof reg === 'object') return reg
        var regStr = reg || '^([\\s\\S]*?)周一([\\s\\S]*?)周二([\\s\\S]*?)周三([\\s\\S]*?)周四([\\s\\S]*?)周五([\\s\\S]*?)$'
        reg = new RegExp(regStr)
    })()
    var verify = verifyReg.test(content)
    var msg = `${filepath} verified success,wait to send...`
    if (run) {
        msg = `${filepath} skip verifing, wait to send...`
    }else if (!verify) {
        msg = `${filepath} verified fail, abort sending...`
        log(msg, 'W')
        return
    }
    log(msg, 'I')

    // mail content
    var mailContent = (function () {
        var _header = config.contentOptions.mailHeader || ''
        var _content = content
        var _footer = config.contentOptions.mailFooter || ''
        var _rawStr = `${_header}\n\r${_content}\n\r${_footer}\n\r`
        return config.contentOptions.parseMD ? markdown.toHTML(_rawStr) : _rawStr
    }())

    // mail sending

    var transporterOptions = Object.assign({
        host: 'smtp.mxhichina.com',
        port: 25,
    }, config.transporterOptions)

    var mailOptions = Object.assign({
        subject: filename.replace(/^log_(\d+)_(\d+)_(\d+)_(\d+)_(\d+)\.md$/, '周报 $2月$3日 -- $4月$5日'),
        html: mailContent.replace(/[\n\r]+/g, '
'),
    }, (
        !config.mailOptions.subject && delete config.mailOptions.subject,
        config.mailOptions
    ))

    var transporter = nodemailer.createTransport(transporterOptions)

    transporter.verify(function (error, success) {
        if (error) {
            log(error, 'E');
        } else {
            log('Server is ready to take our messages', 'I');

            transporter.sendMail(mailOptions, function (error, info) {
                if (error) return log(error, 'E')
                log(`Message sent: \n\r${mailContent}`, 'I')

            })
        }
    });
}

if (run) {
    send()
} else {
    module.exports = send
} <|endoftext|>


<commit_before>// SKIN SPECIFIC CODE

// Create JSterminal.meta and JSterminal.eventHandlers namespaces if they do not exist
JSterminal.meta = JSterminal.meta || {};
JSterminal.eventHandlers = JSterminal.eventHandlers || {};

// Implement IO queue
JSterminal.ioQueue = (function() { // Queue of IO interfaces that claimed control of input/output
  var queue = [];
  return {
    push: function(io) {
      queue.push(io);
    },
    firstActive: function() {
      var io = queue[0];
      while (typeof io != "undefined" && !io.active) {
        queue.shift();
        io = queue[0];
      }
      return io;
    },
    noneActive: function() {
      return typeof this.firstActive() == "undefined";
    }
  }
})();

// Redefine IO interface
JSterminal.IO = function(opts) {
  var m = {
    prefixes: {
      input: "",
      output: ""
    },
    inputLog: [],
    inputLogCursor: -1,
    getsCallbacks: []
  }
  for (k in opts) { if (opts.hasOwnProperty(k)) { m[k] = opts[k]; } }
  return {
    puts: function(out) {
      jQuery("#JSterminal_in_wrap").before((this.meta.prefixes.output || "")+(out||"")+"\n");
      jQuery("#JSterminal_out").scrollTop(jQuery("#JSterminal_out").attr("scrollHeight"));
      jQuery("#JSterminal_in").focus();
    },
    gets: function(callback) {
      jQuery("#JSterminal_in_prefix").html(this.meta.prefixes.input || "");
      this.meta.getsCallbacks.push(callback);
    },
    flush: function(out) {
      jQuery("#JSterminal_out").html("");
    },
    claim: function() {
      this.active = true;
      JSterminal.ioQueue.push(this);
    },
    release: function() {
      this.active = false;
    },
    active: false,
    meta: m
  }
}

// Terminal input/output interface
JSterminal.meta.termIO = JSterminal.IO();
JSterminal.meta.termIO.meta.inputPrefix = "";

// Handle onkeydown event in #JSterminal_in
JSterminal.eventHandlers.keyPressed = function(e) {
  e = e || window.event;
  var keycode = e.keyCode || e.which;
  var termIO = JSterminal.meta.termIO;
  if (JSterminal.ioQueue.noneActive()) {
    termIO.claim();
    termIO.gets(function(s) {
      termIO.puts(s);
      termIO.release();
      JSterminal.interpret(s);
    });
  }
  // Handle input in the right scope
  var io = JSterminal.ioQueue.firstActive();
  if(keycode === 13) {
    jQuery("#JSterminal_in_prefix").html("");
    var i = jQuery("#JSterminal_in").val();
    jQuery("#JSterminal_in").val("");
    io.meta.inputLog.unshift(i);
    io.meta.inputLogCursor = -1;
    io.meta.getsCallbacks.shift()(i);
  } else if (keycode === 27) {
      JSterminal.quit();
  } else if (keycode === 38) {
    if(io.meta.inputLogCursor < io.meta.inputLog.length - 1) {
      jQuery("#JSterminal_in").val(io.meta.inputLog[++io.meta.inputLogCursor]);
      return false; // Avoid placing the cursor at the beginning
    }
  } else if (keycode === 40) {
    if(io.meta.inputLogCursor >= 0) {
      jQuery("#JSterminal_in").val(io.meta.inputLog[--io.meta.inputLogCursor]);
    }
  }
};

// Override launch and quit functions
JSterminal.launch = function() {
  if(jQuery("#JSterminal_container").length <= 0)
  {
    jQuery(document.body).prepend('

<input type="text" id="JSterminal_in" onkeydown="return JSterminal.eventHandlers.keyPressed(event)" />

');
    JSterminal.meta.termIO.puts("JSterminal version 0.0.3\n"+(new Date()).toLocaleString());
    jQuery("#JSterminal_in").focus();
  }
};

JSterminal.quit = function() {
  JSterminal.meta.inputQueue = [];
  jQuery("#JSterminal_container").remove();
}
<commit_msg>option for immediate IO control release<commit_after>// SKIN SPECIFIC CODE

// Create JSterminal.meta and JSterminal.eventHandlers namespaces if they do not exist
JSterminal.meta = JSterminal.meta || {};
JSterminal.eventHandlers = JSterminal.eventHandlers || {};

// Implement IO queue
JSterminal.ioQueue = (function() { // Queue of IO interfaces that claimed control of input/output
  var queue = [];
  return {
    push: function(io) {
      queue.push(io);
    },
    firstActive: function() {
      var io = queue[0];
      while (typeof io != "undefined" && !io.active) {
        queue.shift();
        io = queue[0];
      }
      return io;
    },
    noneActive: function() {
      return typeof this.firstActive() == "undefined";
    }
  }
})();

// Redefine IO interface
JSterminal.IO = function(opts) {
  var m = {
    prefixes: {
      input: "",
      output: ""
    },
    inputLog: [],
    inputLogCursor: -1,
    outputQueue: [],
    getsCallbacks: [],
  }
  for (k in opts) { if (opts.hasOwnProperty(k)) { m[k] = opts[k]; } }
  return {
    puts: function(out) {
      if (!this.active) {
        this.claim();
        this.immediateRelease = true;
      }
      if (this.hasControl()) {
        jQuery("#JSterminal_in_wrap").before((this.meta.prefixes.output || "")+(out||"")+"\n");
        jQuery("#JSterminal_out").scrollTop(jQuery("#JSterminal_out").attr("scrollHeight"));
        jQuery("#JSterminal_in").focus();
        if (this.immediateRelease) {
          this.release();
        }
      } else {
        this.meta.outputQueue.push(out);
      }
    },
    gets: function(callback) {
      jQuery("#JSterminal_in_prefix").html(this.meta.prefixes.input || "");
      this.meta.getsCallbacks.push(callback);
    },
    flush: function(out) {
      jQuery("#JSterminal_out").html("");
    },
    claim: function() {
      this.active = true;
      JSterminal.ioQueue.push(this);
    },
    release: function() {
      this.active = false;
      JSterminal.eventHandlers.ioReleased();
    },
    active: false,
    immediateRelease: false,
    hasControl: function() {
      return this == JSterminal.ioQueue.firstActive();
    },
    meta: m
  }
}

// Terminal input/output interface
JSterminal.meta.termIO = JSterminal.IO();

JSterminal.meta.printWaitingOutput = function() {
  var io = JSterminal.ioQueue.firstActive();
  if(!!io) {
    for (out in io.outputQueue) {
      jQuery("#JSterminal_in_wrap").before((this.meta.prefixes.output || "")+(io.outputQueue[out]||"")+"\n");
      jQuery("#JSterminal_out").scrollTop(jQuery("#JSterminal_out").attr("scrollHeight"));
      jQuery("#JSterminal_in").focus();
    }
  }
}

// Handle ioReleased
JSterminal.eventHandlers.ioReleased = function() {
  JSterminal.meta.printWaitingOutput();
}

// Handle onkeydown event in #JSterminal_in
JSterminal.eventHandlers.keyPressed = function(e) {
  e = e || window.event;
  var keycode = e.keyCode || e.which;
  var termIO = JSterminal.meta.termIO;
  if (JSterminal.ioQueue.noneActive()) {
    termIO.claim();
    termIO.gets(function(s) {
      termIO.puts(s);
      termIO.release();
      JSterminal.interpret(s);
    });
  }
  // Handle input in the right scope
  var io = JSterminal.ioQueue.firstActive();
  if(keycode === 13) {
    jQuery("#JSterminal_in_prefix").html("");
    var i = jQuery("#JSterminal_in").val();
    jQuery("#JSterminal_in").val("");
    io.meta.inputLog.unshift(i);
    io.meta.inputLogCursor = -1;
    io.meta.getsCallbacks.shift()(i);
  } else if (keycode === 27) {
      JSterminal.quit();
  } else if (keycode === 38) {
    if(io.meta.inputLogCursor < io.meta.inputLog.length - 1) {
      jQuery("#JSterminal_in").val(io.meta.inputLog[++io.meta.inputLogCursor]);
      return false; // Avoid placing the cursor at the beginning
    }
  } else if (keycode === 40) {
    if(io.meta.inputLogCursor >= 0) {
      jQuery("#JSterminal_in").val(io.meta.inputLog[--io.meta.inputLogCursor]);
    }
  }
};

// Override launch and quit functions
JSterminal.launch = function() {
  if(jQuery("#JSterminal_container").length <= 0)
  {
    jQuery(document.body).prepend('

<input type="text" id="JSterminal_in" onkeydown="return JSterminal.eventHandlers.keyPressed(event)" />

');
    JSterminal.meta.termIO.claim();
    JSterminal.meta.termIO.puts("JSterminal version 0.0.3\n"+(new Date()).toLocaleString());
    JSterminal.meta.termIO.release();
    jQuery("#JSterminal_in").focus();
  }
};

JSterminal.quit = function() {
  JSterminal.meta.inputQueue = [];
  jQuery("#JSterminal_container").remove();
}
<|endoftext|>


<commit_before>import metaDataManager from './metaDataManager.js';
import getPixelData from './getPixelData.js';
import createImage from '../createImage.js';

/**
 * Helper method to extract the transfer-syntax from the response of the server.
 * @param {string} contentType The value of the content-type header as returned by the WADO-RS server.
 * @return The transfer-syntax as announced by the server, or Implicit Little Endian by default.
 */
export function getTransferSyntaxForContentType(contentType) {
  const defaultTransferSyntax = '1.2.840.10008.1.2'; // Default is Implicit Little Endian.

  if (!contentType) {
    return defaultTransferSyntax;
  }

  // Browse through the content type parameters
  const parameters = contentType.split(';');
  const params = {};

  parameters.forEach(parameter => {
    // Look for a transfer-syntax=XXXX pair
    const parameterValues = parameter.split('=');

    if (parameterValues.length !== 2) {
      return;
    }

    const value = parameterValues[1].trim().replace(/"/g, '');

    params[parameterValues[0].trim()] = value;
  });

  // This is useful if the PACS doesn't respond with a syntax
  // in the content type.
  // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/chapter_6.html#table_6.1.1.8-3b
  const defaultTransferSyntaxByType = {
    'image/jpeg': '1.2.840.10008.1.2.4.70',
    'image/x-dicom-rle': '1.2.840.10008.1.2.5',
    'image/x-jls': '1.2.840.10008.1.2.4.80',
    'image/jp2': '1.2.840.10008.1.2.4.90',
    'image/jpx': '1.2.840.10008.1.2.4.92',
  };

  if (params['transfer-syntax']) {
    return params['transfer-syntax'];
  } else if (
    contentType &&
    !Object.keys(params).length &&
    defaultTransferSyntaxByType[contentType]
  ) {
    // dcm4che seems to be reporting the content type as just 'image/jp2'?
    return defaultTransferSyntaxByType[contentType];
  } else if (params.type && defaultTransferSyntaxByType[params.type]) {
    return defaultTransferSyntaxByType[params.type];
  }

  return defaultTransferSyntax;
}

function loadImage(imageId, options) {
  const start = new Date().getTime();
  const uri = imageId.substring(7);

  const promise = new Promise((resolve, reject) => {
    // TODO: load bulk data items that we might need
    const mediaType = 'multipart/related; type="application/octet-stream"'; // 'image/dicom+jp2';

    // get the pixel data from the server
    getPixelData(uri, imageId, mediaType)
      .then(result => {
        const transferSyntax = getTransferSyntaxForContentType(
          result.contentType
        );
        const pixelData = result.imageFrame.pixelData;

        const imagePromise = createImage(
          imageId,
          pixelData,
          transferSyntax,
          options
        );

        imagePromise.then(image => {
          // add the loadTimeInMS property
          const end = new Date().getTime();

          image.loadTimeInMS = end - start;
          resolve(image);
        }, reject);
      }, reject)
      .catch(error => {
        reject(error);
      });
  });

  return {
    promise,
    cancelFn: undefined,
  };
}

export default loadImage;
<commit_msg>feat: 🎸 Allow optional in-worker scaling and buffer redirection<commit_after>import metaDataManager from './metaDataManager.js';
import getPixelData from './getPixelData.js';
import createImage from '../createImage.js';

/**
 * Helper method to extract the transfer-syntax from the response of the server.
 * @param {string} contentType The value of the content-type header as returned by the WADO-RS server.
 * @return The transfer-syntax as announced by the server, or Implicit Little Endian by default.
 */
export function getTransferSyntaxForContentType(contentType) {
  const defaultTransferSyntax = '1.2.840.10008.1.2'; // Default is Implicit Little Endian.

  if (!contentType) {
    return defaultTransferSyntax;
  }

  // Browse through the content type parameters
  const parameters = contentType.split(';');
  const params = {};

  parameters.forEach(parameter => {
    // Look for a transfer-syntax=XXXX pair
    const parameterValues = parameter.split('=');

    if (parameterValues.length !== 2) {
      return;
    }

    const value = parameterValues[1].trim().replace(/"/g, '');

    params[parameterValues[0].trim()] = value;
  });

  // This is useful if the PACS doesn't respond with a syntax
  // in the content type.
  // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/chapter_6.html#table_6.1.1.8-3b
  const defaultTransferSyntaxByType = {
    'image/jpeg': '1.2.840.10008.1.2.4.70',
    'image/x-dicom-rle': '1.2.840.10008.1.2.5',
    'image/x-jls': '1.2.840.10008.1.2.4.80',
    'image/jp2': '1.2.840.10008.1.2.4.90',
    'image/jpx': '1.2.840.10008.1.2.4.92',
  };

  if (params['transfer-syntax']) {
    return params['transfer-syntax'];
  } else if (
    contentType &&
    !Object.keys(params).length &&
    defaultTransferSyntaxByType[contentType]
  ) {
    // dcm4che seems to be reporting the content type as just 'image/jp2'?
    return defaultTransferSyntaxByType[contentType];
  } else if (params.type && defaultTransferSyntaxByType[params.type]) {
    return defaultTransferSyntaxByType[params.type];
  }

  return defaultTransferSyntax;
}

function loadImage(imageId, options) {
  const start = new Date().getTime();
  const uri = imageId.substring(7);

  const promise = new Promise((resolve, reject) => {
    // TODO: load bulk data items that we might need
    const mediaType = 'multipart/related; type="application/octet-stream"'; // 'image/dicom+jp2';

    // get the pixel data from the server
    getPixelData(uri, imageId, mediaType)
      .then(result => {
        const transferSyntax = getTransferSyntaxForContentType(
          result.contentType
        );
        const pixelData = result.imageFrame.pixelData;
        const imagePromise = createImage(
          imageId,
          pixelData,
          transferSyntax,
          options
        );

        imagePromise.then(image => {
          // add the loadTimeInMS property
          const end = new Date().getTime();

          image.loadTimeInMS = end - start;
          resolve(image);
        }, reject);
      }, reject)
      .catch(error => {
        reject(error);
      });
  });

  return {
    promise,
    cancelFn: undefined,
  };
}

export default loadImage;
<|endoftext|>


<commit_before>// Docs at https://github.com/jigish/slate/wiki/JavaScript-Configs

slate.log('Loading config') // Good to know this file was found

var getKeystroke = function(key) {
    return key + ':shift,ctrl'
}

var getScreens = function() {
    var screens = {}
    var externalScreensCount = 0
    slate.eachScreen(function(screen) {
        // Screen IDs are ordered from left to right so external-0 stays on the left
        // of external-1
        var name = screen.isMain() ? 'main' : (function() {
            var id = 'external-' + externalScreensCount
            externalScreensCount++
            return id
        })()
        screens[name] = screen.id()
    })
    return screens
}

var getScreen = function(name) {
    var screens = getScreens()
    return name in screens ? screens[name] : screens.main
}

// Operations

var operations = {}
var windowMargin = 5

/**
 * Applies the operation or list of operations
 */
operations.apply = function(window, operation) {
    if (Array.isArray(operation)) {
        operation.forEach(function(subOperation) {
            operations.apply(window, subOperation)
        })
    } else {
        window.doOperation(operation)
    }
}

var leftRegionWidth = 'screenSizeX * 0.4'

operations.toLeftRegion = function() {
    return slate.operation('move', {
        x: 'screenOriginX',
        y: 'screenOriginY',
        width: leftRegionWidth,
        height: 'screenSizeY'
    })
}

operations.toRightRegion = function() {
    return slate.operation('move', {
        x: 'screenOriginX + ' + leftRegionWidth,
        y: 'screenOriginY',
        width: 'screenSizeX - (' + leftRegionWidth + ')',
        height: 'screenSizeY'
    })
}

operations.toTop = function() {
    return slate.operation('move', {
        x: 'screenOriginX',
        y: 'screenOriginY',
        width: 'screenSizeX',
        height: 'screenSizeY / 2'
    })
}

operations.toBottom = function() {
    return slate.operation('move', {
        x: 'screenOriginX',
        y: 'screenOriginY / 2',
        width: 'screenSizeX',
        height: 'screenSizeY / 2'
    })
}

operations.maximize = function() {
    return slate.operation('move', {
        x: 'screenOriginX',
        y: 'screenOriginY',
        width: 'screenSizeX',
        height: 'screenSizeY'
    })
}

operations.pad = function(top, right, bottom, left) {
    var xPadding = right + left
    var yPadding = top + bottom
    return [
        slate.operation('resize', {width: '-' + xPadding, height: '-' + yPadding}),
        slate.operation('nudge', {x: '+' + left, y: '+' + top})
    ]
}

var runAppOperations = function(app, appOperations) {
    app.eachWindow(function(window) { operations.apply(window, appOperations) })
}

var getAppByName = function(name) {
    var app
    slate.eachApp(function(currentApp) { if (currentApp.name() == name) app = currentApp })
    return app
}

var getAvailableAppsByName = function(names) {
    return names.map(getAppByName).filter(function(app) { return app })
}

var runAppOperationsByNames = function(names, appOperations) {
    getAvailableAppsByName(names).forEach(function(app) {
        runAppOperations(app, appOperations)
    })
}

var layout = function() {
    var screenCount = slate.screenCount()

    slate.log('Laying out (' + screenCount + ' screens)')

    runAppOperationsByNames(['iTerm', 'Terminal'], (function() {
        switch (screenCount) {
            case 1: return operations.maximize()
            case 2:
            case 3:
                return [
                    slate.operation('throw', {screen: getScreen('external-0')}),
                    operations.toLeftRegion(),
                    operations.pad(windowMargin, windowMargin / 2, windowMargin, windowMargin)
                ]
        }
    })())

    runAppOperationsByNames(['Google Chrome', 'Firefox', 'Safari', 'SourceTree'], (function() {
        switch (screenCount) {
            case 1: return operations.maximize()
            case 2:
            case 3: return [
                slate.operation('throw', {screen: getScreen('external-0')}),
                operations.toRightRegion(),
                operations.pad(windowMargin, windowMargin, windowMargin, windowMargin / 2)
            ]
        }
    })())

    runAppOperationsByNames(['PhpStorm', 'MacVim', 'Atom', 'Eclipse'], [
        slate.operation('throw', {screen: getScreen('main')}),
        operations.maximize()
    ])

    runAppOperationsByNames(['HipChat'], (function() {
        switch (screenCount) {
            case 1:
            case 2:
                return [
                    slate.operation('throw', {screen: getScreen('main')}),
                    operations.maximize()
                ]
            case 3:
                return [
                    slate.operation('throw', {screen: getScreen('external-1')}),
                    operations.toTop()
                ]
        }
    })())

    runAppOperationsByNames(['Spotify'], (function() {
        switch (screenCount) {
            case 1:
            case 2:
                return [
                    slate.operation('throw', {screen: getScreen('main')}),
                    operations.maximize()
                ]
            case 3:
                return [
                    slate.operation('throw', {screen: getScreen('external-1')}),
                    operations.toBottom()
                ]
        }
    })())
}

slate.default(1, layout)
slate.default(2, layout)
slate.default(3, layout)

// Keybindings

slate.bind(getKeystroke('r'), slate.operation('relaunch'))
slate.bind(getKeystroke('l'), layout)
slate.bind(getKeystroke('m'), operations.maximize())
slate.bind(getKeystroke('k'), operations.toTop())
slate.bind(getKeystroke('j'), operations.toBottom())

slate.log('Config loaded') // Good to know everything loaded ok
<commit_msg>getAppByName() -> getAppsByName()<commit_after>// Docs at https://github.com/jigish/slate/wiki/JavaScript-Configs

slate.log('Loading config') // Good to know this file was found

var getKeystroke = function(key) {
    return key + ':shift,ctrl'
}

var getScreens = function() {
    var screens = {}
    var externalScreensCount = 0
    slate.eachScreen(function(screen) {
        // Screen IDs are ordered from left to right so external-0 stays on the left
        // of external-1
        var name = screen.isMain() ? 'main' : (function() {
            var id = 'external-' + externalScreensCount
            externalScreensCount++
            return id
        })()
        screens[name] = screen.id()
    })
    return screens
}

var getScreen = function(name) {
    var screens = getScreens()
    return name in screens ? screens[name] : screens.main
}

// Operations

var operations = {}
var windowMargin = 5

/**
 * Applies the operation or list of operations
 */
operations.apply = function(window, operation) {
    if (Array.isArray(operation)) {
        operation.forEach(function(subOperation) {
            operations.apply(window, subOperation)
        })
    } else {
        window.doOperation(operation)
    }
}

var leftRegionWidth = 'screenSizeX * 0.4'

operations.toLeftRegion = function() {
    return slate.operation('move', {
        x: 'screenOriginX',
        y: 'screenOriginY',
        width: leftRegionWidth,
        height: 'screenSizeY'
    })
}

operations.toRightRegion = function() {
    return slate.operation('move', {
        x: 'screenOriginX + ' + leftRegionWidth,
        y: 'screenOriginY',
        width: 'screenSizeX - (' + leftRegionWidth + ')',
        height: 'screenSizeY'
    })
}

operations.toTop = function() {
    return slate.operation('move', {
        x: 'screenOriginX',
        y: 'screenOriginY',
        width: 'screenSizeX',
        height: 'screenSizeY / 2'
    })
}

operations.toBottom = function() {
    return slate.operation('move', {
        x: 'screenOriginX',
        y: 'screenOriginY / 2',
        width: 'screenSizeX',
        height: 'screenSizeY / 2'
    })
}

operations.maximize = function() {
    return slate.operation('move', {
        x: 'screenOriginX',
        y: 'screenOriginY',
        width: 'screenSizeX',
        height: 'screenSizeY'
    })
}

operations.pad = function(top, right, bottom, left) {
    var xPadding = right + left
    var yPadding = top + bottom
    return [
        slate.operation('resize', {width: '-' + xPadding, height: '-' + yPadding}),
        slate.operation('nudge', {x: '+' + left, y: '+' + top})
    ]
}

var runAppOperations = function(app, appOperations) {
    app.eachWindow(function(window) { operations.apply(window, appOperations) })
}

var getAppsByName = function(names) {
    var apps = []
    slate.eachApp(function(app) {
        if (names.some(function(name) { return name == app.name() })) {
            apps.push(app)
        }
    })
    return apps
}

var getAvailableAppsByName = function(names) {
    return getAppsByName(names).filter(function(app) { return app })
}

var runAppOperationsByNames = function(names, appOperations) {
    getAvailableAppsByName(names).forEach(function(app) {
        runAppOperations(app, appOperations)
    })
}

var layout = function() {
    var screenCount = slate.screenCount()

    slate.log('Laying out (' + screenCount + ' screens)')

    runAppOperationsByNames(['iTerm', 'Terminal'], (function() {
        switch (screenCount) {
            case 1: return operations.maximize()
            case 2:
            case 3:
                return [
                    slate.operation('throw', {screen: getScreen('external-0')}),
                    operations.toLeftRegion(),
                    operations.pad(windowMargin, windowMargin / 2, windowMargin, windowMargin)
                ]
        }
    })())

    runAppOperationsByNames(['Google Chrome', 'Firefox', 'Safari', 'SourceTree'], (function() {
        switch (screenCount) {
            case 1: return operations.maximize()
            case 2:
            case 3: return [
                slate.operation('throw', {screen: getScreen('external-0')}),
                operations.toRightRegion(),
                operations.pad(windowMargin, windowMargin, windowMargin, windowMargin / 2)
            ]
        }
    })())

    runAppOperationsByNames(['PhpStorm', 'MacVim', 'Atom', 'Eclipse'], [
        slate.operation('throw', {screen: getScreen('main')}),
        operations.maximize()
    ])

    runAppOperationsByNames(['HipChat'], (function() {
        switch (screenCount) {
            case 1:
            case 2:
                return [
                    slate.operation('throw', {screen: getScreen('main')}),
                    operations.maximize()
                ]
            case 3:
                return [
                    slate.operation('throw', {screen: getScreen('external-1')}),
                    operations.toTop()
                ]
        }
    })())

    runAppOperationsByNames(['Spotify'], (function() {
        switch (screenCount) {
            case 1:
            case 2:
                return [
                    slate.operation('throw', {screen: getScreen('main')}),
                    operations.maximize()
                ]
            case 3:
                return [
                    slate.operation('throw', {screen: getScreen('external-1')}),
                    operations.toBottom()
                ]
        }
    })())
}

slate.default(1, layout)
slate.default(2, layout)
slate.default(3, layout)

// Keybindings

slate.bind(getKeystroke('r'), slate.operation('relaunch'))
slate.bind(getKeystroke('l'), layout)
slate.bind(getKeystroke('m'), operations.maximize())
slate.bind(getKeystroke('k'), operations.toTop())
slate.bind(getKeystroke('j'), operations.toBottom())

slate.log('Config loaded') // Good to know everything loaded ok
<|endoftext|>


<commit_before>// Texture

UI.Texture = function ( position ) {

    UI.Element.call( this );

	var scope = this;

	var image = new Image();
	this.texture = null;

	this.dom = document.createElement( 'input' );
	this.dom.type = 'file';
	this.dom.style.position = position || 'relative';

	this.onChangeCallback = null;

	this.dom.addEventListener( 'change', function ( event ) {

		var file = event.target.files[ 0 ];

		if ( file.type.match( 'image.*' ) ) {

			var reader = new FileReader();
			reader.addEventListener( 'load', function ( event ) {

				var image = document.createElement( 'img' );
				image.addEventListener( 'load', function( event ) {

					scope.texture = new THREE.Texture( this );
					scope.texture.needsUpdate = true;

					// remember the original filename (including extension)
					// this is used for url field in the scene export

					scope.texture.sourceFile = file.name;

					// generate unique name per texture
					// based on source file name

					var chunks = file.name.split( '.' );
					var extension = chunks.pop().toLowerCase();
					var filename = chunks.join( '.' );

					if ( ! ( filename in scope.textureNameMap ) ) {

						scope.textureNameMap[ filename ] = true;
						scope.texture.name = filename;

					} else {

						scope.texture.name = filename + "_" + scope.texture.id;

					}

					if ( scope.onChangeCallback ) scope.onChangeCallback();

				}, false );

				image.src = event.target.result;

			}, false );

			reader.readAsDataURL( file );

		}

	}, false );

	return this;

};

UI.Texture.prototype = Object.create( UI.Element.prototype );

UI.Texture.prototype.textureNameMap = {};

UI.Texture.prototype.getValue = function () {

	return this.texture;

};

UI.Texture.prototype.setValue = function ( value ) {

	this.texture = value;

};

UI.Texture.prototype.onChange = function ( callback ) {

	this.onChangeCallback = callback;

	return this;

};

// CubeTexture

UI.CubeTexture = function ( position ) {

	UI.Element.call( this );

	var scope = this;

	this.texture = new THREE.Texture( [], new THREE.CubeReflectionMapping() );

	this.dom = document.createElement( 'input' );
	this.dom.type = 'file';
	this.dom.style.position = position || 'relative';

	this.onChangeCallback = null;

	this.dom.addEventListener( 'change', function ( event ) {

		var file = event.target.files[ 0 ];

		if ( file.type.match( 'image.*' ) ) {

			var reader = new FileReader();
			reader.addEventListener( 'load', function ( event ) {

				var image = document.createElement( 'img' );
				image.addEventListener( 'load', function( event ) {

					scope.texture.image = [ this, this, this, this, this, this ];
					scope.texture.needsUpdate = true;

					if ( scope.onChangeCallback ) scope.onChangeCallback();

				}, false );
				image.src = event.target.result;

			}, false );
			reader.readAsDataURL( file );

		}

	}, false );

	return this;

};

UI.CubeTexture.prototype = Object.create( UI.Element.prototype );

UI.CubeTexture.prototype.getValue = function () {

	return this.texture;

};

UI.CubeTexture.prototype.setValue = function ( value ) {

	this.texture = value;

};

UI.CubeTexture.prototype.onChange = function ( callback ) {

	this.onChangeCallback = callback;

	return this;

};
<commit_msg>Editor: Fixed cubemaps.<commit_after>// Texture

UI.Texture = function ( position ) {

    UI.Element.call( this );

	var scope = this;

	var image = new Image();
	this.texture = null;

	this.dom = document.createElement( 'input' );
	this.dom.type = 'file';
	this.dom.style.position = position || 'relative';

	this.onChangeCallback = null;

	this.dom.addEventListener( 'change', function ( event ) {

		var file = event.target.files[ 0 ];

		if ( file.type.match( 'image.*' ) ) {

			var reader = new FileReader();
			reader.addEventListener( 'load', function ( event ) {

				var image = document.createElement( 'img' );
				image.addEventListener( 'load', function( event ) {

					scope.texture = new THREE.Texture( this );
					scope.texture.needsUpdate = true;

					// remember the original filename (including extension)
					// this is used for url field in the scene export

					scope.texture.sourceFile = file.name;

					// generate unique name per texture
					// based on source file name

					var chunks = file.name.split( '.' );
					var extension = chunks.pop().toLowerCase();
					var filename = chunks.join( '.' );

					if ( ! ( filename in scope.textureNameMap ) ) {

						scope.textureNameMap[ filename ] = true;
						scope.texture.name = filename;

					} else {

						scope.texture.name = filename + "_" + scope.texture.id;

					}

					if ( scope.onChangeCallback ) scope.onChangeCallback();

				}, false );

				image.src = event.target.result;

			}, false );

			reader.readAsDataURL( file );

		}

	}, false );

	return this;

};

UI.Texture.prototype = Object.create( UI.Element.prototype );

UI.Texture.prototype.textureNameMap = {};

UI.Texture.prototype.getValue = function () {

	return this.texture;

};

UI.Texture.prototype.setValue = function ( value ) {

	this.texture = value;

};

UI.Texture.prototype.onChange = function ( callback ) {

	this.onChangeCallback = callback;

	return this;

};

// CubeTexture

UI.CubeTexture = function ( position ) {

	UI.Element.call( this );

	var scope = this;

	this.texture = null;

	this.dom = document.createElement( 'input' );
	this.dom.type = 'file';
	this.dom.style.position = position || 'relative';

	this.onChangeCallback = null;

	this.dom.addEventListener( 'change', function ( event ) {

		var file = event.target.files[ 0 ];

		if ( file.type.match( 'image.*' ) ) {

			var reader = new FileReader();
			reader.addEventListener( 'load', function ( event ) {

				var image = document.createElement( 'img' );
				image.addEventListener( 'load', function( event ) {

					scope.texture = new THREE.Texture( [ this, this, this, this, this, this ], new THREE.CubeReflectionMapping() )
					scope.texture.needsUpdate = true;

					if ( scope.onChangeCallback ) scope.onChangeCallback();

				}, false );
				image.src = event.target.result;

			}, false );
			reader.readAsDataURL( file );

		}

	}, false );

	return this;

};

UI.CubeTexture.prototype = Object.create( UI.Element.prototype );

UI.CubeTexture.prototype.getValue = function () {

	return this.texture;

};

UI.CubeTexture.prototype.setValue = function ( value ) {

	this.texture = value;

};

UI.CubeTexture.prototype.onChange = function ( callback ) {

	this.onChangeCallback = callback;

	return this;

};
<|endoftext|>


<commit_before>// Copyright 2014-2018, University of Colorado Boulder

/**
 * A static object that contains methods for redistributing a set of energy chunks within a shape in order to make them
 * spread out fairly evenly in a way that looks dynamic and real.  The basic approach is to simulate a set of particles
 * embedded in a fluid, and each particle repels all others as well as the edges of the containers.
 *
 * Reuse Notes: This could probably be generalized fairly easily to distribute any number items within a container of
 * arbitrary size.
 *
 * @author John Blanco
 * @author Jesse Greenberg
 */
define( function( require ) {
  'use strict';

  // modules
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var Rectangle = require( 'DOT/Rectangle' );
  var Vector2 = require( 'DOT/Vector2' );

  // constants
  var OUTSIDE_CONTAINER_FORCE = 0.01; // In Newtons, empirically determined.
  var ZERO_VECTOR = Vector2.ZERO;

  // parameters that can be adjusted to change the nature of the redistribution
  var MAX_TIME_STEP = ( 1 / 60 ) / 3; // in seconds, for algorithm that moves the points, best if a multiple of nominal frame rate
  var ENERGY_CHUNK_MASS = 1E-3; // in kilograms, chosen arbitrarily
  var FLUID_DENSITY = 1000; // in kg / m ^ 3, same as water, used for drag
  var ENERGY_CHUNK_DIAMETER = 1E-3; // in meters, chosen empirically

  // treat energy chunk as if it is shaped like a sphere
  var ENERGY_CHUNK_CROSS_SECTIONAL_AREA = Math.PI * Math.pow( ENERGY_CHUNK_DIAMETER, 2 );
  var DRAG_COEFFICIENT = 500; // unitless, empirically chosen

  // Thresholds for deciding whether or not to perform redistribution. These value should be chosen such that particles
  // spread out, then stop all movement.
  var REDISTRIBUTION_THRESHOLD_ENERGY = 1E-4; // in joules (I think)

  var EnergyChunkDistributor = {

    /**
     * Redistribute a set of energy chunks that are contained in energy chunk "slices".  The distribution is done taking
     * all nearby slices into account so that the chunks can be distributed in a way that looks good in the view, i.e.
     * the chunks don't overlap with each other.
     * @param {EnergyChunkContainerSlice[]} slices - set of slices that contain energy chunks
     * @param {number} dt - change in time
     * @public
     */
    updatePositions: function( slices, dt ) {

      var self = this;

      // determine a rectangle that bounds all of the slices
      var minX = Number.POSITIVE_INFINITY;
      var minY = Number.POSITIVE_INFINITY;
      var maxX = Number.NEGATIVE_INFINITY;
      var maxY = Number.NEGATIVE_INFINITY;

      // determine the collective bounds of all the slices
      slices.forEach( function( slice ) {
        minX = Math.min( slice.bounds.minX, minX );
        maxX = Math.max( slice.bounds.maxX, maxX );
        minY = Math.min( slice.bounds.minY, minY );
        maxY = Math.max( slice.bounds.maxY, maxY );
      } );

      var width = maxX - minX;
      var height = maxY - minY;
      var boundingRect = new Rectangle( minX, minY, width, height );

      // Create a map that tracks the force applied to each energy chunk and a map that tracks each energyChunk with a
      // unique ID.
      var chunkForces = {}; // map of chunkID (number) => force (Vector2)
      var chunkMap = {}; // map of chunkID (number) => chunk (EnergyChunk)
      var mapSize = 0;
      slices.forEach( function( slice ) {
        slice.energyChunkList.forEach( function( chunk ) {
          chunkForces[ chunk.id ] = Vector2.createFromPool( 0, 0 );
          chunkMap[ chunk.id ] = chunk;
          mapSize++;
        } );
      } );

      // make sure that there is actually something to distribute
      if ( mapSize === 0 ) {
        return false; // nothing to do - bail out
      }

      // Determine the minimum distance that is allowed to be used in the force calculations.  This prevents hitting
      // infinities that can cause run time issues or unreasonably large forces. Denominator empirically determined.
      var minDistance = Math.min( boundingRect.width, boundingRect.height ) / 20;

      // The particle repulsion force varies inversely with the density of particles so that we don't end up with hugely
      // repulsive forces that tend to push the particles out of the container.  This formula was made up, and can be
      // adjusted or even replaced if needed.
      var forceConstant = ENERGY_CHUNK_MASS * boundingRect.width * boundingRect.height * 0.1 / mapSize;

      // divide the time step up into the largest value known to work consistently for the algorithm
      var particlesRedistributed = false;
      var numForceCalcSteps = Math.floor( dt / MAX_TIME_STEP );
      var extraTime = dt - numForceCalcSteps * MAX_TIME_STEP;
      for ( var forceCalcStep = 0; forceCalcStep <= numForceCalcSteps; forceCalcStep++ ) {
        var timeStep = forceCalcStep < numForceCalcSteps ? MAX_TIME_STEP : extraTime;

        // update the forces acting on the particle due to its bounding container, other particles, and drag
        for ( var i = 0; i < slices.length; i++ ) {
          var slice = slices[ i ];
          var containerShapeBounds = slice.bounds;

          // determine the max possible distance to an edge
          var maxDistanceToEdge = Math.sqrt( Math.pow( containerShapeBounds.width, 2 ) +
                                             Math.pow( containerShapeBounds.height, 2 ) );

          // determine forces on each energy chunk
          slice.energyChunkList.forEach( function( chunk ) {

            // reset accumulated forces
            chunkForces[ chunk.id ].setXY( 0, 0 );
            if ( containerShapeBounds.containsCoordinates( chunk.positionProperty.value.x, chunk.positionProperty.value.y ) ) {
              self.computeEdgeForces( chunk, chunkForces, forceConstant, minDistance, maxDistanceToEdge, containerShapeBounds );
              self.updateForces( chunk, chunkMap, chunkForces, minDistance, forceConstant );
            }
            else {

              // point is outside container, move it towards center of shape
              chunkForces[ chunk.id ].freeToPool();
              var vectorToCenter = Vector2.createFromPool( boundingRect.centerX, boundingRect.centerY )
                .subtract( chunk.positionProperty.value );
              chunkForces[ chunk.id ] = vectorToCenter.setMagnitude( OUTSIDE_CONTAINER_FORCE );
            }
          } );
        }

        var maxEnergy = this.updateVelocities( chunkMap, chunkForces, timeStep );

        particlesRedistributed = maxEnergy > REDISTRIBUTION_THRESHOLD_ENERGY;

        if ( particlesRedistributed ) {
          this.stepChunks( chunkMap, timeStep );
        }
      }

      // free allocations
      _.values( chunkForces ).forEach( function( chunkForceVector ) { chunkForceVector.freeToPool(); } );
      return particlesRedistributed;
    },

    /**
     * Redistribute a set of energy chunks that are contained in energy chunk "slices" until they have reached a state
     * in which there is little or no movement of the chunks.
     * @param {EnergyChunkContainerSlice[]} slices - set of slices that contain energy chunks
     * @public
     */
    updatePositionsUntilStill: function( slices ) {

    },

    /**
     * compute the forces on an energy chunk based on the edges of the container in which it is contained
     * @param {EnergyChunk} chunk
     * @param {Vector2[]} chunkForces
     * @param {number} forceConstant
     * @param {number} minDistance
     * @param {number} maxDistance
     * @param {Bounds2} containerBounds
     * @private
     */
    computeEdgeForces: function( chunk, chunkForces, forceConstant, minDistance, maxDistance, containerBounds ) {

      // get the distance to the four different edges
      var rightLength = Math.max( containerBounds.maxX - chunk.positionProperty.value.x, minDistance );
      var bottomLength = Math.max( containerBounds.maxY - chunk.positionProperty.value.y, minDistance );
      var leftLength = Math.max( chunk.positionProperty.value.x - containerBounds.minX, minDistance );
      var topLength = Math.max( chunk.positionProperty.value.y - containerBounds.minY, minDistance );

      // calculate the force from the edge at the given angle
      var rightEdgeForce = Vector2.createFromPool( forceConstant / Math.pow( rightLength, 2 ), 0 ).rotate( Math.PI );
      var bottomEdgeForce = Vector2.createFromPool( forceConstant / Math.pow( bottomLength, 2 ), 0 ).rotate( Math.PI * 1.5 );
      var leftEdgeForce = Vector2.createFromPool( forceConstant / Math.pow( leftLength, 2 ), 0 ).rotate( 0 );
      var topEdgeForce = Vector2.createFromPool( forceConstant / Math.pow( topLength, 2 ), 0 ).rotate( Math.PI / 2 );

      // apply the forces
      chunkForces[ chunk.id ] = chunkForces[ chunk.id ]
        .add( rightEdgeForce )
        .add( bottomEdgeForce )
        .add( leftEdgeForce )
        .add( topEdgeForce );
      rightEdgeForce.freeToPool();
      bottomEdgeForce.freeToPool();
      leftEdgeForce.freeToPool();
      topEdgeForce.freeToPool();
    },

    // TODO: This was factored from updatePositions and requires further cleanup, probably more refactoring, bug fixes, and docs.
    updateForces: function( chunk, chunkMap, chunkForces, minDistance, forceConstant ) {

      // apply the force from each of the other particles, but set some limits on the max force that can be applied
      for ( var otherEnergyChunkID in chunkForces ) {

        if ( chunkForces.hasOwnProperty( otherEnergyChunkID ) ) {

          // skip self
          if ( chunk === chunkMap[ otherEnergyChunkID ] ) {
            continue;
          }

          // calculate force vector, but handle cases where too close
          var vectorFromOther = Vector2.createFromPool(
            chunk.positionProperty.value.x - chunkMap[ otherEnergyChunkID ].positionProperty.value.x,
            chunk.positionProperty.value.y - chunkMap[ otherEnergyChunkID ].positionProperty.value.y
          );
          if ( vectorFromOther.magnitude() < minDistance ) {
            if ( vectorFromOther.magnitude() === 0 ) {

              // create a random vector of min distance
              var randomAngle = phet.joist.random.nextDouble() * Math.PI * 2;
              vectorFromOther.setXY(
                minDistance * Math.cos( randomAngle ),
                minDistance * Math.sin( randomAngle )
              );
            }
            else {
              vectorFromOther = vectorFromOther.setMagnitude( minDistance );
            }
          }

          var forceToOther = Vector2.createFromPool( vectorFromOther.x, vectorFromOther.y )
            .setMagnitude( forceConstant / vectorFromOther.magnitudeSquared() );

          // add the force to the accumulated forces on this energy chunk
          chunkForces[ chunk.id ] = chunkForces[ chunk.id ].add( forceToOther );

          // free allocations
          vectorFromOther.freeToPool();
          forceToOther.freeToPool();
        }
      }
    },

    /**
     * Update energy chunk velocities and drag force, returning max total energy of chunks.
     * @param  {Object} chunkMap - Id => EnergyChunk pairs
     * @param  {Object} chunkForces - Id => Vector2 pairs
     * @param  {number} dt - timestep
     * @returns {number} maxEnergy - max total energy of all provided chunks
     * @private
     */
    updateVelocities: function( chunkMap, chunkForces, dt ) {

      // update energy chunk velocities, drag force, and position
      var maxEnergy = 0;
      for ( var id in chunkMap ) {
        if ( chunkMap.hasOwnProperty( id ) ) {

          // Calculate the energy chunk's velocity as a result of forces acting on it.

          // force on this chunk
          var force = chunkForces[ id ];
          assert && assert( !_.isNaN( force.x ) && !_.isNaN( force.y ), 'force contains NaN value' );

          // current velocity
          var velocity = chunkMap[ id ].velocity;
          assert && assert(
          velocity.x !== Infinity && !_.isNaN( velocity.x ) && typeof velocity.x === 'number',
            'velocity.x is ' + velocity.x
          );
          assert && assert(
          velocity.y !== Infinity && !_.isNaN( velocity.y ) && typeof velocity.y === 'number',
            'velocity.y is ' + velocity.y
          );

          // new velocity
          var forceMultiplier = dt / ENERGY_CHUNK_MASS;
          velocity.addXY( force.x * forceMultiplier, force.y * forceMultiplier );
          assert && assert( !_.isNaN( velocity.x ) && !_.isNaN( velocity.y ), 'New velocity contains NaN value' );

          var velocityMagnitudeSquared = velocity.magnitudeSquared();
          assert && assert(
          velocityMagnitudeSquared !== Infinity && !_.isNaN( velocityMagnitudeSquared ) && typeof velocityMagnitudeSquared === 'number',
            'velocity^2 is ' + velocityMagnitudeSquared
          );

          // calculate drag force using standard drag equation
          var dragMagnitude = 0.5 * FLUID_DENSITY * DRAG_COEFFICIENT * ENERGY_CHUNK_CROSS_SECTIONAL_AREA * velocityMagnitudeSquared;
          var dragForce = ZERO_VECTOR;
          if ( dragMagnitude > 0 ) {
            dragForce = Vector2.createFromPool(
              -velocity.x,
              -velocity.y
            );
            dragForce.setMagnitude( dragMagnitude );
          }
          assert && assert( !_.isNaN( dragForce.x ) && !_.isNaN( dragForce.y ), 'dragForce contains NaN value' );

          // update velocity based on drag force
          velocity.addXY( dragForce.x * forceMultiplier, dragForce.y * forceMultiplier );

          // free allocations
          dragForce.freeToPool();

          // update max energy
          var totalParticleEnergy = 0.5 * ENERGY_CHUNK_MASS * velocityMagnitudeSquared + force.magnitude() * Math.PI / 2;
          if ( totalParticleEnergy > maxEnergy ) {
            maxEnergy = totalParticleEnergy;
          }
        }
      }
      return maxEnergy;
    },

    /**
     * Update chunk positions. Mutates energy chunks in chunkMap.
     * @param  {Object} chunkMap - Id => EnergyChunk pairs
     * @param  {number} dt - time step in seconds
     */
    stepChunks: function( chunkMap, dt ) {
      for ( var id in chunkMap ) {
        if ( chunkMap.hasOwnProperty( id ) ) {

          var v = chunkMap[ id ].velocity;
          assert && assert( !_.isNaN( v.x ) && !_.isNaN( v.y ), 'v contains NaN value' );

          var position = chunkMap[ id ].positionProperty.value;
          assert && assert( !_.isNaN( position.x ) && !_.isNaN( position.y ), 'position contains NaN value' );

          chunkMap[ id ].positionProperty.set( position.plus( v.times( dt ) ) );
        }
      }
    },

    /**
     * Super simple alternative energy chunk distribution algorithm - just puts all energy chunks in center of slice.
     * This is useful for debugging. Rename it to substitute if for the 'real' algorithm.
     * @param {EnergyChunkContainerSlice[]} slices
     * @param {number} dt
     */
    updatePositionsDbg: function( slices, dt ) {

      // update the positions of the energy chunks
      slices.forEach( function( slice ) {
        slice.energyChunkList.forEach( function( energyChunk ) {
          energyChunk.setPosition( slice.bounds.centerX, slice.bounds.centerY );
        } );
      } );
    },

    /**
     * Generate a random location inside a rectangle.
     * @param {Rectangle} rect
     */
    generateRandomLocation: function( rect ) {
      return new Vector2(
        rect.x + ( phet.joist.random.nextDouble() * rect.width ),
        rect.y + ( phet.joist.random.nextDouble() * rect.height )
      );
    }
  };

  energyFormsAndChanges.register( 'EnergyChunkDistributor', EnergyChunkDistributor );

  return EnergyChunkDistributor;
} );<commit_msg>yMerge branch 'master' of https://github.com/phetsims/energy-forms-and-changes<commit_after>// Copyright 2014-2018, University of Colorado Boulder

/**
 * A static object that contains methods for redistributing a set of energy chunks within a shape in order to make them
 * spread out fairly evenly in a way that looks dynamic and real.  The basic approach is to simulate a set of particles
 * embedded in a fluid, and each particle repels all others as well as the edges of the containers.
 *
 * Reuse Notes: This could probably be generalized fairly easily to distribute any number items within a container of
 * arbitrary size.
 *
 * @author John Blanco
 * @author Jesse Greenberg
 */
define( function( require ) {
  'use strict';

  // modules
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var Rectangle = require( 'DOT/Rectangle' );
  var Vector2 = require( 'DOT/Vector2' );

  // constants
  var OUTSIDE_SLICE_FORCE = 0.01; // In Newtons, empirically determined.
  var ZERO_VECTOR = Vector2.ZERO;

  // parameters that can be adjusted to change the nature of the redistribution
  var MAX_TIME_STEP = ( 1 / 60 ) / 3; // in seconds, for algorithm that moves the points, best if a multiple of nominal frame rate
  var ENERGY_CHUNK_MASS = 1E-3; // in kilograms, chosen arbitrarily
  var FLUID_DENSITY = 1000; // in kg / m ^ 3, same as water, used for drag
  var ENERGY_CHUNK_DIAMETER = 1E-3; // in meters, chosen empirically

  // treat energy chunk as if it is shaped like a sphere
  var ENERGY_CHUNK_CROSS_SECTIONAL_AREA = Math.PI * Math.pow( ENERGY_CHUNK_DIAMETER, 2 );
  var DRAG_COEFFICIENT = 500; // unitless, empirically chosen

  // Thresholds for deciding whether or not to perform redistribution. These value should be chosen such that particles
  // spread out, then stop all movement.
  var REDISTRIBUTION_THRESHOLD_ENERGY = 1E-4; // in joules (I think)

  var EnergyChunkDistributor = {

    /**
     * Redistribute a set of energy chunks that are contained in energy chunk "slices".  The distribution is done taking
     * all nearby slices into account so that the chunks can be distributed in a way that looks good in the view, i.e.
     * the chunks don't overlap with each other.
     * @param {EnergyChunkContainerSlice[]} slices - set of slices that contain energy chunks
     * @param {number} dt - change in time
     * @public
     */
    updatePositions: function( slices, dt ) {

      var self = this;

      // determine a rectangle that bounds all of the slices
      var minX = Number.POSITIVE_INFINITY;
      var minY = Number.POSITIVE_INFINITY;
      var maxX = Number.NEGATIVE_INFINITY;
      var maxY = Number.NEGATIVE_INFINITY;

      // determine the collective bounds of all the slices
      slices.forEach( function( slice ) {
        minX = Math.min( slice.bounds.minX, minX );
        maxX = Math.max( slice.bounds.maxX, maxX );
        minY = Math.min( slice.bounds.minY, minY );
        maxY = Math.max( slice.bounds.maxY, maxY );
      } );

      var width = maxX - minX;
      var height = maxY - minY;
      var boundingRect = new Rectangle( minX, minY, width, height );

      // Create a map that tracks the force applied to each energy chunk and a map that tracks each energyChunk with a
      // unique ID.
      var chunkForces = {}; // map of chunkID (number) => force (Vector2)
      var chunkMap = {}; // map of chunkID (number) => chunk (EnergyChunk)
      var mapSize = 0;
      slices.forEach( function( slice ) {
        slice.energyChunkList.forEach( function( chunk ) {
          chunkForces[ chunk.id ] = Vector2.createFromPool( 0, 0 );
          chunkMap[ chunk.id ] = chunk;
          mapSize++;
        } );
      } );

      // make sure that there is actually something to distribute
      if ( mapSize === 0 ) {
        return false; // nothing to do - bail out
      }

      // Determine the minimum distance that is allowed to be used in the force calculations.  This prevents hitting
      // infinities that can cause run time issues or unreasonably large forces. Denominator empirically determined.
      var minDistance = Math.min( boundingRect.width, boundingRect.height ) / 20;

      // The particle repulsion force varies inversely with the density of particles so that we don't end up with hugely
      // repulsive forces that tend to push the particles out of the container.  This formula was made up, and can be
      // adjusted or even replaced if needed.
      var forceConstant = ENERGY_CHUNK_MASS * boundingRect.width * boundingRect.height * 0.1 / mapSize;

      // divide the time step up into the largest value known to work consistently for the algorithm
      var particlesRedistributed = false;
      var numForceCalcSteps = Math.floor( dt / MAX_TIME_STEP );
      var extraTime = dt - numForceCalcSteps * MAX_TIME_STEP;
      for ( var forceCalcStep = 0; forceCalcStep <= numForceCalcSteps; forceCalcStep++ ) {
        var timeStep = forceCalcStep < numForceCalcSteps ? MAX_TIME_STEP : extraTime;

        // update the forces acting on the particle due to its bounding container, other particles, and drag
        for ( var i = 0; i < slices.length; i++ ) {
          var slice = slices[ i ];
          var containerShapeBounds = slice.bounds;

          // determine the max possible distance to an edge
          var maxDistanceToEdge = Math.sqrt( Math.pow( containerShapeBounds.width, 2 ) +
                                             Math.pow( containerShapeBounds.height, 2 ) );

          // determine forces on each energy chunk
          slice.energyChunkList.forEach( function( chunk ) {

            // reset accumulated forces
            chunkForces[ chunk.id ].setXY( 0, 0 );
            if ( containerShapeBounds.containsPoint( chunk.positionProperty.value ) ) {
              self.computeEdgeForces( chunk, chunkForces, forceConstant, minDistance, maxDistanceToEdge, containerShapeBounds );
              self.updateForces( chunk, chunkMap, chunkForces, minDistance, forceConstant );
            }
            else {

              // point is outside container, move it towards center of shape
              chunkForces[ chunk.id ].setXY(
                containerShapeBounds.centerX - chunk.positionProperty.value.x,
                containerShapeBounds.centerY - chunk.positionProperty.value.y
              ).setMagnitude( OUTSIDE_SLICE_FORCE );
            }
          } );
        }

        var maxEnergy = this.updateVelocities( chunkMap, chunkForces, timeStep );

        particlesRedistributed = maxEnergy > REDISTRIBUTION_THRESHOLD_ENERGY;

        if ( particlesRedistributed ) {
          this.stepChunks( chunkMap, timeStep );
        }
      }

      // free allocations
      _.values( chunkForces ).forEach( function( chunkForceVector ) { chunkForceVector.freeToPool(); } );
      return particlesRedistributed;
    },

    /**
     * Redistribute a set of energy chunks that are contained in energy chunk "slices" until they have reached a state
     * in which there is little or no movement of the chunks.
     * @param {EnergyChunkContainerSlice[]} slices - set of slices that contain energy chunks
     * @public
     */
    updatePositionsUntilStill: function( slices ) {

    },

    /**
     * compute the forces on an energy chunk based on the edges of the container in which it is contained
     * @param {EnergyChunk} chunk
     * @param {Vector2[]} chunkForces
     * @param {number} forceConstant
     * @param {number} minDistance
     * @param {number} maxDistance
     * @param {Bounds2} containerBounds
     * @private
     */
    computeEdgeForces: function( chunk, chunkForces, forceConstant, minDistance, maxDistance, containerBounds ) {

      // this should only be called for chunks that are inside a container
      var chunkPosition = chunk.positionProperty.value;
      assert && assert( containerBounds.containsPoint( chunkPosition ) );

      // get the distance to the four different edges
      var distanceFromRightSide = Math.max( containerBounds.maxX - chunkPosition.x, minDistance );
      var distanceFromBottom = Math.max( chunkPosition.y - containerBounds.minY, minDistance );
      var distanceFromLeftSide = Math.max( chunkPosition.x - containerBounds.minX, minDistance );
      var distanceFromTop = Math.max( containerBounds.maxY - chunkPosition.y, minDistance );

      // calculate the force from the edge at the given angle
      var rightEdgeForce = Vector2.createFromPool( forceConstant / Math.pow( distanceFromRightSide, 2 ), 0 ).rotate( Math.PI );
      var bottomEdgeForce = Vector2.createFromPool( forceConstant / Math.pow( distanceFromBottom, 2 ), 0 ).rotate( Math.PI / 2 );
      var leftEdgeForce = Vector2.createFromPool( forceConstant / Math.pow( distanceFromLeftSide, 2 ), 0 ).rotate( 0 );
      var topEdgeForce = Vector2.createFromPool( forceConstant / Math.pow( distanceFromTop, 2 ), 0 ).rotate( Math.PI * 1.5 );

      // apply the forces
      chunkForces[ chunk.id ] = chunkForces[ chunk.id ]
        .add( rightEdgeForce )
        .add( bottomEdgeForce )
        .add( leftEdgeForce )
        .add( topEdgeForce );
      rightEdgeForce.freeToPool();
      bottomEdgeForce.freeToPool();
      leftEdgeForce.freeToPool();
      topEdgeForce.freeToPool();
    },

    // TODO: This was factored from updatePositions and requires further cleanup, probably more refactoring, bug fixes, and docs.
    updateForces: function( chunk, chunkMap, chunkForces, minDistance, forceConstant ) {

      // apply the force from each of the other particles, but set some limits on the max force that can be applied
      for ( var otherEnergyChunkID in chunkForces ) {

        if ( chunkForces.hasOwnProperty( otherEnergyChunkID ) ) {

          // skip self
          if ( chunk === chunkMap[ otherEnergyChunkID ] ) {
            continue;
          }

          // calculate force vector, but handle cases where too close
          var vectorFromOther = Vector2.createFromPool(
            chunk.positionProperty.value.x - chunkMap[ otherEnergyChunkID ].positionProperty.value.x,
            chunk.positionProperty.value.y - chunkMap[ otherEnergyChunkID ].positionProperty.value.y
          );
          if ( vectorFromOther.magnitude() < minDistance ) {
            if ( vectorFromOther.magnitude() === 0 ) {

              // create a random vector of min distance
              var randomAngle = phet.joist.random.nextDouble() * Math.PI * 2;
              vectorFromOther.setXY(
                minDistance * Math.cos( randomAngle ),
                minDistance * Math.sin( randomAngle )
              );
            }
            else {
              vectorFromOther = vectorFromOther.setMagnitude( minDistance );
            }
          }

          var forceToOther = Vector2.createFromPool( vectorFromOther.x, vectorFromOther.y )
            .setMagnitude( forceConstant / vectorFromOther.magnitudeSquared() );

          // add the force to the accumulated forces on this energy chunk
          chunkForces[ chunk.id ] = chunkForces[ chunk.id ].add( forceToOther );

          // free allocations
          vectorFromOther.freeToPool();
          forceToOther.freeToPool();
        }
      }
    },

    /**
     * Update energy chunk velocities and drag force, returning max total energy of chunks.
     * @param  {Object} chunkMap - Id => EnergyChunk pairs
     * @param  {Object} chunkForces - Id => Vector2 pairs
     * @param  {number} dt - timestep
     * @returns {number} maxEnergy - max total energy of all provided chunks
     * @private
     */
    updateVelocities: function( chunkMap, chunkForces, dt ) {

      // update energy chunk velocities, drag force, and position
      var maxEnergy = 0;
      for ( var id in chunkMap ) {
        if ( chunkMap.hasOwnProperty( id ) ) {

          // Calculate the energy chunk's velocity as a result of forces acting on it.

          // force on this chunk
          var force = chunkForces[ id ];
          assert && assert( !_.isNaN( force.x ) && !_.isNaN( force.y ), 'force contains NaN value' );

          // current velocity
          var velocity = chunkMap[ id ].velocity;
          assert && assert(
          velocity.x !== Infinity && !_.isNaN( velocity.x ) && typeof velocity.x === 'number',
            'velocity.x is ' + velocity.x
          );
          assert && assert(
          velocity.y !== Infinity && !_.isNaN( velocity.y ) && typeof velocity.y === 'number',
            'velocity.y is ' + velocity.y
          );

          // velocity change is based on the formula v = (F/m)* t, so pre-compute the t/m part for use later
          var forceMultiplier = dt / ENERGY_CHUNK_MASS;

          // calculate drag force using standard drag equation
          var velocityMagnitudeSquared = velocity.magnitudeSquared();
          assert && assert(
          velocityMagnitudeSquared !== Infinity && !_.isNaN( velocityMagnitudeSquared ) && typeof velocityMagnitudeSquared === 'number',
            'velocity^2 is ' + velocityMagnitudeSquared
          );
          var dragMagnitude = 0.5 * FLUID_DENSITY * DRAG_COEFFICIENT * ENERGY_CHUNK_CROSS_SECTIONAL_AREA * velocityMagnitudeSquared;
          var dragForce = ZERO_VECTOR;
          if ( dragMagnitude > 0 ) {
            dragForce = Vector2.createFromPool(
              -velocity.x,
              -velocity.y
            );
            dragForce.setMagnitude( dragMagnitude );
          }
          assert && assert( !_.isNaN( dragForce.x ) && !_.isNaN( dragForce.y ), 'dragForce contains NaN value' );

          // update velocity based on the sum of forces acting on the particle
          velocity.addXY( ( force.x + dragForce.x ) * forceMultiplier, ( force.y + dragForce.y ) * forceMultiplier );
          assert && assert( !_.isNaN( velocity.x ) && !_.isNaN( velocity.y ), 'New velocity contains NaN value' );

          // free allocations
          if ( dragForce !== ZERO_VECTOR ) {
            dragForce.freeToPool();
          }

          // update max energy
          var totalParticleEnergy = 0.5 * ENERGY_CHUNK_MASS * velocityMagnitudeSquared + force.magnitude() * Math.PI / 2;
          if ( totalParticleEnergy > maxEnergy ) {
            maxEnergy = totalParticleEnergy;
          }
        }
      }
      return maxEnergy;
    },

    /**
     * Update chunk positions. Mutates energy chunks in chunkMap.
     * @param  {Object} chunkMap - Id => EnergyChunk pairs
     * @param  {number} dt - time step in seconds
     */
    stepChunks: function( chunkMap, dt ) {
      for ( var id in chunkMap ) {
        if ( chunkMap.hasOwnProperty( id ) ) {

          var v = chunkMap[ id ].velocity;
          assert && assert( !_.isNaN( v.x ) && !_.isNaN( v.y ), 'v contains NaN value' );

          var position = chunkMap[ id ].positionProperty.value;
          assert && assert( !_.isNaN( position.x ) && !_.isNaN( position.y ), 'position contains NaN value' );

          chunkMap[ id ].positionProperty.set( position.plus( v.times( dt ) ) );
        }
      }
    },

    /**
     * Super simple alternative energy chunk distribution algorithm - just puts all energy chunks in center of slice.
     * This is useful for debugging. Rename it to substitute if for the 'real' algorithm.
     * @param {EnergyChunkContainerSlice[]} slices
     * @param {number} dt
     */
    updatePositionsDbg: function( slices, dt ) {

      // update the positions of the energy chunks
      slices.forEach( function( slice ) {
        slice.energyChunkList.forEach( function( energyChunk ) {
          energyChunk.setPosition( slice.bounds.centerX, slice.bounds.centerY );
        } );
      } );
    },

    /**
     * Generate a random location inside a rectangle.
     * @param {Rectangle} rect
     */
    generateRandomLocation: function( rect ) {
      return new Vector2(
        rect.x + ( phet.joist.random.nextDouble() * rect.width ),
        rect.y + ( phet.joist.random.nextDouble() * rect.height )
      );
    }
  };

  energyFormsAndChanges.register( 'EnergyChunkDistributor', EnergyChunkDistributor );

  return EnergyChunkDistributor;
} );<|endoftext|>