<commit_before>/*
 * Copyright (c) 2013, IT Services, Stockholm University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of Stockholm University nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

describe('Info window view', function () {
  beforeEach(function () {
    $('#stage').replaceWith("

");
    $('#page-map').append(JST['map/infoWindow']({
      model: new Location(),
      itemText: "",
      displayDirections: true
    }));
  });

  afterEach(function () {
    $('#page-map').replaceWith("

");
  });

  describe('instantiation', function () {
    it('should add event handler on change of showingNonVisibleForLocation in AppModel ', function () {
      spyOn(AppModel.prototype, "on");

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });

      expect(AppModel.prototype.on).toHaveBeenCalledWith("change:showingNonVisibleForLocation", jasmine.any(Function), this.infoWindow);
    });

    it('should create google.maps.InfoWindow with maxWidth 260', function () {
      spyOn(google.maps, "InfoWindow");

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });

      expect(google.maps.InfoWindow).toHaveBeenCalledWith({ maxWidth: 260 });
    });
  });

  describe('remove', function () {

    it('should call close', function () {
      spyOn(InfoWindowView.prototype, "close");

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });
      this.infoWindow.remove();

      expect(InfoWindowView.prototype.close).toHaveBeenCalled();
    });

    it('should remove event handler from document for click on directions', function () {
      // Mock MapView and getDirections on it
      var MapView = function () {
      };
      MapView.prototype.getDirections = function () {
      };
      spyOn(MapView.prototype, "getDirections");

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel(),
        mapView: new MapView()
      });

      $(".dir-button").first().trigger("click");
      expect(MapView.prototype.getDirections.calls.length).toEqual(1);

      this.infoWindow.remove();
      $(".dir-button").first().trigger("click");
      expect(MapView.prototype.getDirections.calls.length).toEqual(1);
    });

  });

  describe('when clicking on a direction link in infowindow', function () {
    // Mock MapView and getDirections on it
    var MapView = function () {
    };
    MapView.prototype.getDirections = function () {
    };

    beforeEach(function () {
      spyOn(MapView.prototype, "getDirections");
      spyOn(InfoWindowView.prototype, "close");

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel(),
        mapView: new MapView()
      });
    });

    it('should close the info window', function () {
      $(".dir-button").first().trigger("click");
      expect(InfoWindowView.prototype.close).toHaveBeenCalled();
    });

    it('should set selected on clicked link and unselect others', function () {
      $(".dir-button").first().trigger("click");
      expect($(".dir-button.selected").attr("id")).toEqual("walking");

      $(".dir-button").last().trigger("click");
      expect($(".dir-button.selected").attr("id")).toEqual("driving");
      expect($(".dir-button.selected").length).toEqual(1);
    });

    it('should pass destination to getDirections', function () {
      this.infoWindow.setDestination("destination");
      $(".dir-button").first().trigger("click");
      expect(MapView.prototype.getDirections).toHaveBeenCalledWith("walking", "destination");
    });
  });

  describe('when clicking on a show related link in the infowindow', function () {

  });

  describe('when opening the infowindow', function () {

    it('should close previous infowindow', function () {
      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });

      spyOn(this.infoWindow.infoWindow, "close");
      this.infoWindow.open(new Location(), new google.maps.Marker(), new google.maps.LatLng(0, 0));
      expect(this.infoWindow.infoWindow.close).toHaveBeenCalled();
    });

    it('should call model (Location) getI18n method (fetching the text attribute)', function () {
      spyOn(Location.prototype, "getI18n");

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });

      var location = new Location({
        name: 'testName',
        directionAware: false
        });
      this.infoWindow.open(location, new google.maps.Marker(), new google.maps.LatLng(0, 0));

      expect(Location.prototype.getI18n).toHaveBeenCalledWith('text');
    });

    it('should call JST with correct values', function () {
      spyOn(JST, 'map/infoWindow').andReturn('');

      i18n.init({
        lng: 'sv-SE'
      });

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });

      var location = new Location({
        name: 'testName',
        directionAware: false,
        buildingName: 'testBuilding',
        text: 'testText',
        type: 'building'
      });

      this.infoWindow.appModel.locations = {
        byBuildingAndTypeAndHandicapAdapted: function (building, types, adapted) {
          return _([ new Location({
            floor: '1'
          }) ])
        }
      }

      this.infoWindow.open(location, new google.maps.Marker(), new google.maps.LatLng(0, 0));

      expect(JST['map/infoWindow']).toHaveBeenCalledWith({
        name: 'testName, testBuilding',
        displayDirections: false,
        model: location,
        itemText: 'testText',
        hasElevators: true,
        hasEntrances: true,
        tFloors: '1'
      });
    });

    it('should call JST with correct values when other language than swedish', function () {
      spyOn(JST, 'map/infoWindow').andReturn('');

      i18n.init({
        lng: 'fr-FR'
      });

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });

      var location = new Location({
        name: 'testName',
        nameEn: 'testName (en)',
        directionAware: false,
        text: 'testText',
        textEn: 'testText (en)',
        type: 'department'
      });

      this.infoWindow.open(location, new google.maps.Marker(), new google.maps.LatLng(0, 0));

      expect(JST['map/infoWindow']).toHaveBeenCalledWith({
        name: 'testName (en)',
        displayDirections: false,
        model: location,
        itemText: 'testText (en)'
      });
    });

  });

  describe('Info-window template', function () {
    it('should set hearing_loop for auditoriums when handicapAdapted', function () {
      // First expect to find no hearing loops
      expect($('#page-map').find('i[class="hearing_loop"]').size()).toEqual(0);

      var location = new Location({
        type: 'auditorium',
        handicapAdapted: true
      });

      $('#page-map').replaceWith("

");
      $('#page-map').append(JST['map/infoWindow']({
        model: location,
        itemText: '',
        displayDirections: false
      }));

      expect($('#page-map').find('i[class="hearing_loop"]').size()).toEqual(1);
    });

    it('should set hearing_loop for auditoriums when handicapAdpted', function () {
      // First expect to find no hearing loops
      expect($('#page-map').find('i[class="hearing_loop"]').size()).toEqual(0);

      var location = new Location({
        type: 'auditorium',
        handicapAdapted: false
      });

      $('#page-map').replaceWith("

");
      $('#page-map').append(JST['map/infoWindow']({
        model: location,
        itemText: '',
        displayDirections: false
      }));

      expect($('#page-map').find('i[class="hearing_loop"]').size()).toEqual(0);
      expect($('#page-map').text()).toMatch(/.*Hearing loop not available.*/);
    });

    it('should set correct building information when no accessible elevators, toilets or entrances exist', function () {

      var location = new Location({
        type: 'building',
        nonVisibleTypes: ["entrance", "elevator", "toilet"]
      });

      $('#page-map').append(JST['map/infoWindow']({
        model: location,
        itemText: '',
        hasElevators: false,
        tFloors: '',
        hasEntrances: false,
        displayDirections: false
      }));

      expect($('#page-map').find('i[class="elevator"]').size()).toEqual(0);
      expect($('#page-map').text()).toMatch(/.*Accessible lift not available.*/);

      expect($('#page-map').find('i[class="toilet"]').size()).toEqual(0);
      expect($('#page-map').text()).toMatch(/.*Accessible toilet not available.*/);

      expect($('#page-map').find('i[class="entrance"]').size()).toEqual(0);
      expect($('#page-map').text()).toMatch(/.*Accessible entrance not available.*/);
    });

    it('should set correct building information when accessible elevators, toilets or entrances exist', function () {

      var location = new Location({
        type: 'building',
        nonVisibleTypes: ["entrance", "elevator", "toilet"]
      });

      $('#page-map').append(JST['map/infoWindow']({
        model: location,
        itemText: '',
        hasElevators: true,
        tFloors: [1],
        hasEntrances: true,
        displayDirections: false
      }));

      expect($('#page-map').find('i[class="elevator"]').size()).toEqual(1);
      expect($('#page-map').text()).toMatch(/.*Accessible lift available.*/);

      expect($('#page-map').find('i[class="toilet"]').size()).toEqual(1);
      expect($('#page-map').text()).toMatch(/.*Accessible toilet available.*/);

      expect($('#page-map').find('i[class="entrance"]').size()).toEqual(1);

      expect($('#page-map').find('a[class="showRelated"]').size()).toEqual(1);
      expect($('#page-map').text()).toMatch(/.*Show accessible entrances.*/);

      expect($('#page-map').find('a[class="hideRelated"]').size()).toEqual(1);
      expect($('#page-map').text()).toMatch(/.*Hide accessible entrances.*/);
    });

  });
});
<commit_msg>MOB-167 Fixed some tests<commit_after>/*
 * Copyright (c) 2013, IT Services, Stockholm University
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of Stockholm University nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

describe('Info window view', function () {
  beforeEach(function () {
    $('#stage').replaceWith("

");
    $('#page-map').append(JST['map/infoWindow']({
      model: new Location(),
      itemText: "",
      displayDirections: true
    }));
  });

  afterEach(function () {
    $('#page-map').replaceWith("

");
  });

  describe('instantiation', function () {
    it('should add event handler on change of showingNonVisibleForLocation in AppModel ', function () {
      spyOn(AppModel.prototype, "on");

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });

      expect(AppModel.prototype.on).toHaveBeenCalledWith("change:showingNonVisibleForLocation", jasmine.any(Function), this.infoWindow);
    });

    it('should create google.maps.InfoWindow with maxWidth 260', function () {
      spyOn(google.maps, "InfoWindow");

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });

      expect(google.maps.InfoWindow).toHaveBeenCalledWith({ maxWidth: 260 });
    });
  });

  describe('remove', function () {

    it('should call close', function () {
      spyOn(InfoWindowView.prototype, "close");

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });
      this.infoWindow.remove();

      expect(InfoWindowView.prototype.close).toHaveBeenCalled();
    });

    it('should remove event handler from document for click on directions', function () {
      // Mock MapView and getDirections on it
      var MapView = function () {
      };
      MapView.prototype.getDirections = function () {
      };
      spyOn(MapView.prototype, "getDirections");

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel(),
        mapView: new MapView()
      });

      $(".dir-button").first().trigger("click");
      expect(MapView.prototype.getDirections.calls.length).toEqual(1);

      this.infoWindow.remove();
      $(".dir-button").first().trigger("click");
      expect(MapView.prototype.getDirections.calls.length).toEqual(1);
    });

  });

  describe('when clicking on a direction link in infowindow', function () {
    // Mock MapView and getDirections on it
    var MapView = function () {
    };
    MapView.prototype.getDirections = function () {
    };

    beforeEach(function () {
      spyOn(MapView.prototype, "getDirections");
      spyOn(InfoWindowView.prototype, "close");

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel(),
        mapView: new MapView()
      });
    });

    it('should close the info window', function () {
      $(".dir-button").first().trigger("click");
      expect(InfoWindowView.prototype.close).toHaveBeenCalled();
    });

    it('should set selected on clicked link and unselect others', function () {
      $(".dir-button").first().trigger("click");
      expect($(".dir-button.selected").attr("id")).toEqual("walking");

      $(".dir-button").last().trigger("click");
      expect($(".dir-button.selected").attr("id")).toEqual("driving");
      expect($(".dir-button.selected").length).toEqual(1);
    });

    it('should pass destination to getDirections', function () {
      this.infoWindow.setDestination("destination");
      $(".dir-button").first().trigger("click");
      expect(MapView.prototype.getDirections).toHaveBeenCalledWith("walking", "destination");
    });
  });

  describe('when clicking on a show related link in the infowindow', function () {

  });

  describe('when opening the infowindow', function () {

    it('should close previous infowindow', function () {
      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });

      spyOn(this.infoWindow.infoWindow, "close");
      this.infoWindow.open(new Location(), new google.maps.Marker(), new google.maps.LatLng(0, 0));
      expect(this.infoWindow.infoWindow.close).toHaveBeenCalled();
    });

    it('should call model (Location) getI18n method (fetching the text attribute)', function () {
      spyOn(Location.prototype, "getI18n");

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });

      var location = new Location({
        name: 'testName',
        directionAware: false
        });
      this.infoWindow.open(location, new google.maps.Marker(), new google.maps.LatLng(0, 0));

      expect(Location.prototype.getI18n).toHaveBeenCalledWith('text');
    });

    it('should call JST with correct values', function () {
      spyOn(JST, 'map/infoWindow').andReturn('');

      i18n.init({
        lng: 'sv-SE'
      });

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });

      var location = new Location({
        name: 'testName',
        directionAware: false,
        buildingName: 'testBuilding',
        text: 'testText',
        type: 'building'
      });

      this.infoWindow.appModel.locations = {
        byBuildingAndTypeAndHandicapAdapted: function (building, types, adapted) {
          return _([ new Location({
            floor: '1'
          }) ])
        }
      }

      this.infoWindow.open(location, new google.maps.Marker(), new google.maps.LatLng(0, 0));

      expect(JST['map/infoWindow']).toHaveBeenCalledWith({
        name: 'testName',
        displayDirections: false,
        model: location,
        itemText: 'testText',
        hasElevators: true,
        hasEntrances: true,
        tFloors: '1'
      });
    });

    it('should call JST with correct values when other language than swedish', function () {
      spyOn(JST, 'map/infoWindow').andReturn('');

      i18n.init({
        lng: 'fr-FR'
      });

      this.infoWindow = new InfoWindowView({
        appModel: new AppModel()
      });

      var location = new Location({
        name: 'testName',
        nameEn: 'testName (en)',
        directionAware: false,
        text: 'testText',
        textEn: 'testText (en)',
        type: 'department'
      });

      this.infoWindow.open(location, new google.maps.Marker(), new google.maps.LatLng(0, 0));

      expect(JST['map/infoWindow']).toHaveBeenCalledWith({
        name: 'testName (en)',
        displayDirections: false,
        model: location,
        itemText: 'testText (en)'
      });
    });

  });

  describe('Info-window template', function () {
    it('should set hearing_loop for auditoriums when handicapAdapted', function () {
      // First expect to find no hearing loops
      expect($('#page-map').find('div.hearing_loop').size()).toEqual(0);

      var location = new Location({
        type: 'auditorium',
        handicapAdapted: true
      });

      $('#page-map').replaceWith("

");
      $('#page-map').append(JST['map/infoWindow']({
        model: location,
        itemText: '',
        displayDirections: false
      }));

      expect($('#page-map').find('div.hearing_loop').size()).toEqual(1);
    });

    it('should set hearing_loop for auditoriums when handicapAdapted', function () {
      // First expect to find no hearing loops
      expect($('#page-map').find('i[class="hearing_loop"]').size()).toEqual(0);

      var location = new Location({
        type: 'auditorium',
        handicapAdapted: false
      });

      $('#page-map').replaceWith("

");
      $('#page-map').append(JST['map/infoWindow']({
        model: location,
        itemText: '',
        displayDirections: false
      }));

      expect($('#page-map').find('div[class="hearing_loop"]').size()).toEqual(0);
      expect($('#page-map').text()).toMatch(/.*Hearing loop not available.*/);
    });

    it('should set correct building information when no accessible elevators, toilets or entrances exist', function () {

      var location = new Location({
        type: 'building',
        nonVisibleTypes: ["entrance", "elevator", "toilet"]
      });

      $('#page-map').append(JST['map/infoWindow']({
        model: location,
        itemText: '',
        hasElevators: false,
        tFloors: '',
        hasEntrances: false,
        displayDirections: false
      }));

      expect($('#page-map').find('div[class="handicap-lift"]').size()).toEqual(0);
      expect($('#page-map').text()).toMatch(/.*Accessible lift not available.*/);

      expect($('#page-map').find('div[class="handicap-toilet"]').size()).toEqual(0);
      expect($('#page-map').text()).toMatch(/.*Accessible toilet not available.*/);

      expect($('#page-map').find('div[class="handicap-entrance"]').size()).toEqual(0);
      expect($('#page-map').text()).toMatch(/.*Accessible entrance not available.*/);
    });

    it('should set correct building information when accessible elevators, toilets or entrances exist', function () {

      var location = new Location({
        type: 'building',
        nonVisibleTypes: ["entrance", "elevator", "toilet"]
      });

      $('#page-map').append(JST['map/infoWindow']({
        model: location,
        itemText: '',
        hasElevators: true,
        tFloors: [1],
        hasEntrances: true,
        displayDirections: false
      }));

      expect($('#page-map').find('div[class="handicap-lift"]').size()).toEqual(1);
      expect($('#page-map').text()).toMatch(/.*Accessible lift available.*/);

      expect($('#page-map').find('div[class="handicap-toilet"]').size()).toEqual(1);
      expect($('#page-map').text()).toMatch(/.*Accessible toilet available.*/);

      expect($('#page-map').find('div[class="handicap-entrance"]').size()).toEqual(1);

      expect($('#page-map').find('a[class="showRelated"]').size()).toEqual(1);
      expect($('#page-map').text()).toMatch(/.*Show accessible entrances.*/);

      expect($('#page-map').find('a[class="hideRelated"]').size()).toEqual(1);
      expect($('#page-map').text()).toMatch(/.*Hide accessible entrances.*/);
    });

  });
});
<|endoftext|>


<commit_before>(function () {
	'use strict';

	// The idea of getMatchStrength is that you have a regex like
	//          look at (.*)
	// And you have a string like
	//          look at thing
	// The strength is the length of the "fixed" bit of the string, in this case "look at ".
	// So we calculate this as the length of the input string, minus the length of the
	// text that matches the groups.

	var getMatchStrength = function (regex, input) {
		var lengthOfTextMatchedByGroups = 0;
		var matches = regex.exec(input);
		matches.shift();
		matches.forEach(function (group) {
			lengthOfTextMatchedByGroups += group.length;
		});
		return input.length - lengthOfTextMatchedByGroups;
	}

	questkit.handleCommand = function (input) {
		// TODO: Full conversion

		var parserContext = get('~parserContext');
		if (parserContext) {
			if (parserContext.options && !isNaN(input) && input > 0 && input <= parserContext.options.length) {
				finishDisambiguation(input);
				return;
			}
		}

		var bestMatch;
		var command;
		var maxStrength = -1;

		questkit.scopeCommands().forEach(function (cmd) {
			questkit.commandRegex(cmd).patterns.forEach(function (pattern) {
				var match = pattern.exec(input);
				if (match) {
					var matchStrength = getMatchStrength(pattern, input);

					// Core library commands are defined after game commands, so strength
					// has to be greater for a command to "win". An exception is if the current
					// winner has no parent, and the candidate does - a locally defined command
					// beats a global command.

					if (matchStrength > maxStrength || (command && !get(command, "parent") && get(cmd, "parent"))) {
						maxStrength = matchStrength;
						command = cmd;
						bestMatch = match;
					}
				}
			});
		});

		if (maxStrength > -1) {
			handleCommandPattern(command, bestMatch);
		}
		else {
			// TODO: Use game.unresolvedcommandhandler if it exists?
			msg(questkit.template('UnrecognisedCommand'));
		}
	};

	var handleCommandPattern = function (command, match) {
		if (questkit.commandRegex(command).groups.length > 0) {
			var args = match.slice(0);
			args.shift();
			var parserContext = {
				command: command,
				args: args,
				nextArgIndex: 0
			};
			set('~parserContext', parserContext);
			resolveNextName();
		}
		else {
			questkit.getscript(command + '.action')();
		}
	};

	var resolveNextName = function () {
		var parserContext = get('~parserContext');
		var index = parserContext.nextArgIndex;
		var command = parserContext.command;
		var group = questkit.commandRegex(command).groups[index];
		var args = parserContext.args;

		// TODO: resolve exits

		var resolveScope = null;
		var unresolvedTemplate = null;

		if (group.indexOf('object') === 0) {
			resolveScope = questkit.scopeVisible();
			unresolvedTemplate = 'UnresolvedObject';
		}
		else if (group.indexOf('exit') === 0) {
			resolveScope = questkit.scopeExits();
			unresolvedTemplate = 'UnresolvedExit';
		}
		else if (group.indexOf('walkthrough') === 0) {
			resolveScope = questkit.allWalkthroughs();
			unresolvedTemplate = 'UnresolvedWalkthrough';
		}

		if (resolveScope) {
			// Resolve object name

			var result = resolveName(args[index], resolveScope);

			if (result.resolved) {
				args[index] = result.value;
			}
			else if (result.pending) {
				startDisambiguation(args[index], result.value);
				return;
			}
			else {
				set('~parserContext', null);
				if (questkit.commandRegex(command).groups.length > 1) {
					// TODO: Unresolved template will depend on the scope we're searching
					msg(questkit.template('UnresolvedObjectMulti').format(args[index]));
				}
				else {
					msg(questkit.template(unresolvedTemplate));
				}
				return;
			}
		}

		finishedResolvingName();
	};

	var finishedResolvingName = function () {
		var parserContext = get('~parserContext');
		var command = parserContext.command;
		var args = parserContext.args;

		parserContext.nextArgIndex++;
		if (parserContext.nextArgIndex < questkit.commandRegex(command).groups.length) {
			set('~parserContext', parserContext);
			resolveNextName();
		}
		else {
			set('~parserContext', null);
			questkit.getscript(command + '.action').apply(this, args);
		}
	};

	var resolveName = function (name, scope) {
		// TODO: lists (e.g. "take all")
		// TODO: command metadata for non-disambiguating hyperlinks

		var fullMatches = [];
		var partialMatches = [];

		var result;
		var resolved = false;
		var pending = false;
		name = name.toLowerCase();

		scope.forEach(function (object) {
			compareNames(name, questkit.displayAlias(object).toLowerCase(), object, fullMatches, partialMatches);
			var alt = get(object, 'alt');
			if (alt) {
				alt.forEach(function (altName) {
					compareNames(name, altName.toLowerCase(), object, fullMatches, partialMatches);
				});
			}
		});

		if (fullMatches.length === 1) {
			resolved = true;
			result = fullMatches[0];
		}
		else if (fullMatches.length === 0 && partialMatches.length === 1) {
			resolved = true;
			result = partialMatches[0];
		}
		else if (fullMatches.length > 0 || partialMatches.length > 0) {
			pending = true;
			result = fullMatches.concat(partialMatches);
		}

		return {
			resolved: resolved,
			pending: pending,
			value: result
		};
	};

	var compareNames = function (enteredName, nameCandidate, object, fullMatches, partialMatches) {
		if (enteredName === nameCandidate) {
			if (fullMatches.indexOf(object) === -1) {
				fullMatches.push(object);
			}
			return;
		}

		// if any word of the name candidate starts with the entered name,
		// add it as a partial match
		if (nameCandidate.indexOf(enteredName) === 0 || nameCandidate.indexOf(' ' + enteredName) > -1) {
			if (partialMatches.indexOf(object) === -1) {
				partialMatches.push(object);
			}
			return;
		}
	};

	var startDisambiguation = function (name, options, prompt) {
		var parserContext = get('~parserContext');
		parserContext.options = options;
		set('~parserContext', parserContext);

		if (prompt) {
			msg(prompt + ':');
		}
		else {
			msg(questkit.template('DisambiguateMenu').format(name) + ':');
		}
		msg('');
		options.forEach(function (option, index) {
			msg((index + 1) + ': ' + questkit.displayAlias(option));
		});
	};

	var finishDisambiguation = function (index) {
		var parserContext = get('~parserContext');
		parserContext.args[parserContext.nextArgIndex] = parserContext.options[index - 1];
		parserContext.options = null;
		set('~parserContext', parserContext);
		finishedResolvingName();
	};

	questkit.runWalkthrough = function (walkthrough) {
		var steps = get(walkthrough, 'steps');
		steps.forEach(function (step) {
			msg('');
			msg('* ' + step);
			questkit.handleCommand(step);
		});
	};

	questkit.listWalkthroughs = function () {
		var parserContext = {
			command: 'walkthrough',
			args: [],
			nextArgIndex: 0,
		};
		set('~parserContext', parserContext);
		startDisambiguation(null, questkit.allWalkthroughs(), questkit.template('ChooseWalkthrough'));
	};
})();<commit_msg>Remove some todos<commit_after>(function () {
	'use strict';

	// The idea of getMatchStrength is that you have a regex like
	//          look at (.*)
	// And you have a string like
	//          look at thing
	// The strength is the length of the "fixed" bit of the string, in this case "look at ".
	// So we calculate this as the length of the input string, minus the length of the
	// text that matches the groups.

	var getMatchStrength = function (regex, input) {
		var lengthOfTextMatchedByGroups = 0;
		var matches = regex.exec(input);
		matches.shift();
		matches.forEach(function (group) {
			lengthOfTextMatchedByGroups += group.length;
		});
		return input.length - lengthOfTextMatchedByGroups;
	}

	questkit.handleCommand = function (input) {
		var parserContext = get('~parserContext');
		if (parserContext) {
			if (parserContext.options && !isNaN(input) && input > 0 && input <= parserContext.options.length) {
				finishDisambiguation(input);
				return;
			}
		}

		var bestMatch;
		var command;
		var maxStrength = -1;

		questkit.scopeCommands().forEach(function (cmd) {
			questkit.commandRegex(cmd).patterns.forEach(function (pattern) {
				var match = pattern.exec(input);
				if (match) {
					var matchStrength = getMatchStrength(pattern, input);

					// Core library commands are defined after game commands, so strength
					// has to be greater for a command to "win". An exception is if the current
					// winner has no parent, and the candidate does - a locally defined command
					// beats a global command.

					if (matchStrength > maxStrength || (command && !get(command, "parent") && get(cmd, "parent"))) {
						maxStrength = matchStrength;
						command = cmd;
						bestMatch = match;
					}
				}
			});
		});

		if (maxStrength > -1) {
			handleCommandPattern(command, bestMatch);
		}
		else {
			// TODO: Use game.unresolvedcommandhandler if it exists?
			msg(questkit.template('UnrecognisedCommand'));
		}
	};

	var handleCommandPattern = function (command, match) {
		if (questkit.commandRegex(command).groups.length > 0) {
			var args = match.slice(0);
			args.shift();
			var parserContext = {
				command: command,
				args: args,
				nextArgIndex: 0
			};
			set('~parserContext', parserContext);
			resolveNextName();
		}
		else {
			questkit.getscript(command + '.action')();
		}
	};

	var resolveNextName = function () {
		var parserContext = get('~parserContext');
		var index = parserContext.nextArgIndex;
		var command = parserContext.command;
		var group = questkit.commandRegex(command).groups[index];
		var args = parserContext.args;

		var resolveScope = null;
		var unresolvedTemplate = null;

		if (group.indexOf('object') === 0) {
			resolveScope = questkit.scopeVisible();
			unresolvedTemplate = 'UnresolvedObject';
		}
		else if (group.indexOf('exit') === 0) {
			resolveScope = questkit.scopeExits();
			unresolvedTemplate = 'UnresolvedExit';
		}
		else if (group.indexOf('walkthrough') === 0) {
			resolveScope = questkit.allWalkthroughs();
			unresolvedTemplate = 'UnresolvedWalkthrough';
		}

		if (resolveScope) {
			var result = resolveName(args[index], resolveScope);

			if (result.resolved) {
				args[index] = result.value;
			}
			else if (result.pending) {
				startDisambiguation(args[index], result.value);
				return;
			}
			else {
				set('~parserContext', null);
				if (questkit.commandRegex(command).groups.length > 1) {
					msg(questkit.template('UnresolvedObjectMulti').format(args[index]));
				}
				else {
					msg(questkit.template(unresolvedTemplate));
				}
				return;
			}
		}

		finishedResolvingName();
	};

	var finishedResolvingName = function () {
		var parserContext = get('~parserContext');
		var command = parserContext.command;
		var args = parserContext.args;

		parserContext.nextArgIndex++;
		if (parserContext.nextArgIndex < questkit.commandRegex(command).groups.length) {
			set('~parserContext', parserContext);
			resolveNextName();
		}
		else {
			set('~parserContext', null);
			questkit.getscript(command + '.action').apply(this, args);
		}
	};

	var resolveName = function (name, scope) {
		// TODO: lists (e.g. "take all")
		// TODO: command metadata for non-disambiguating hyperlinks

		var fullMatches = [];
		var partialMatches = [];

		var result;
		var resolved = false;
		var pending = false;
		name = name.toLowerCase();

		scope.forEach(function (object) {
			compareNames(name, questkit.displayAlias(object).toLowerCase(), object, fullMatches, partialMatches);
			var alt = get(object, 'alt');
			if (alt) {
				alt.forEach(function (altName) {
					compareNames(name, altName.toLowerCase(), object, fullMatches, partialMatches);
				});
			}
		});

		if (fullMatches.length === 1) {
			resolved = true;
			result = fullMatches[0];
		}
		else if (fullMatches.length === 0 && partialMatches.length === 1) {
			resolved = true;
			result = partialMatches[0];
		}
		else if (fullMatches.length > 0 || partialMatches.length > 0) {
			pending = true;
			result = fullMatches.concat(partialMatches);
		}

		return {
			resolved: resolved,
			pending: pending,
			value: result
		};
	};

	var compareNames = function (enteredName, nameCandidate, object, fullMatches, partialMatches) {
		if (enteredName === nameCandidate) {
			if (fullMatches.indexOf(object) === -1) {
				fullMatches.push(object);
			}
			return;
		}

		// if any word of the name candidate starts with the entered name,
		// add it as a partial match
		if (nameCandidate.indexOf(enteredName) === 0 || nameCandidate.indexOf(' ' + enteredName) > -1) {
			if (partialMatches.indexOf(object) === -1) {
				partialMatches.push(object);
			}
			return;
		}
	};

	var startDisambiguation = function (name, options, prompt) {
		var parserContext = get('~parserContext');
		parserContext.options = options;
		set('~parserContext', parserContext);

		if (prompt) {
			msg(prompt + ':');
		}
		else {
			msg(questkit.template('DisambiguateMenu').format(name) + ':');
		}
		msg('');
		options.forEach(function (option, index) {
			msg((index + 1) + ': ' + questkit.displayAlias(option));
		});
	};

	var finishDisambiguation = function (index) {
		var parserContext = get('~parserContext');
		parserContext.args[parserContext.nextArgIndex] = parserContext.options[index - 1];
		parserContext.options = null;
		set('~parserContext', parserContext);
		finishedResolvingName();
	};

	questkit.runWalkthrough = function (walkthrough) {
		var steps = get(walkthrough, 'steps');
		steps.forEach(function (step) {
			msg('');
			msg('* ' + step);
			questkit.handleCommand(step);
		});
	};

	questkit.listWalkthroughs = function () {
		var parserContext = {
			command: 'walkthrough',
			args: [],
			nextArgIndex: 0,
		};
		set('~parserContext', parserContext);
		startDisambiguation(null, questkit.allWalkthroughs(), questkit.template('ChooseWalkthrough'));
	};
})();<|endoftext|>


<commit_before>var intervals = require('./intervals.js'),
	notes = require('./notes.js'),
	diatonic = require('./diatonic.js');

// A cache for composed triads
_triads_cache = {};
// A cache for composed sevenths
_sevenths_cache = {};

// A dictionairy that can be used to present
// lookup chord abbreviations. This dictionairy is also
// used in determine_seventh()
chord_shorthand_meaning = {
		// Triads
		"m" : " minor triad",
		"M" : " major triad",
		"" : " major triad",
		"dim" : " diminished triad",

		// Augmented chords
		"aug" : " augmented triad",
		"+" : " augmented triad",
		"7#5" : " augmented minor seventh",
		"M7+5" : " augmented minor seventh",
		"M7+" : " augmented major seventh",
		"m7+" : " augmented minor seventh",
		"7+" : " augmented major seventh",

		// Suspended chords
		"sus47" :" suspended seventh",
		"sus4" :" suspended fourth triad",
		"sus2" :" suspended second triad",
		"sus" :" suspended fourth triad",
		"11" :" eleventh",
		"sus4b9" :" suspended fourth ninth",
		"susb9" :" suspended fourth ninth",

		// Sevenths
		"m7" :" minor seventh",
		"M7" :" major seventh",
		"dom7" :" dominant seventh",
		"7" :" dominant seventh",
		"m7b5" :" half diminished seventh",
		"dim7" :" diminished seventh",
		"m/M7" :" minor/major seventh",
		"mM7" :" minor/major seventh",

		// Sixths
		"m6" : " minor sixth",
		"M6" : " major sixth",
		"6" : " major sixth",
		"6/7" : " dominant sixth",
		"67": " dominant sixth",
		"6/9" :" sixth ninth",
		"69" : " sixth ninth",

		// Ninths
		"9" : " dominant ninth",
		"7b9" : " dominant flat ninth",
		"7#9" :" dominant sharp ninth",
		"M9" :" major ninth",
		"m9" :" minor ninth",

		// Elevenths
		"7#11" :" lydian dominant seventh",
		"m11" :" minor eleventh",

		// Thirteenths
		"M13" :" major thirteenth",
		"m13" :" minor thirteenth",

		"13" :" dominant thirteenth",

		// Altered Chords
		"7b5" :" dominant flat five",

		// Special
		"hendrix" :" hendrix chord",
		"7b12" :" hendrix chord",
		"5" :" perfect fifth"
};

/*===================================================================
							Triads
===================================================================*/

// diatonic

function triad(note, key) {
	/*Returns the triad on note in key as a list.
	Examples:
	>>> triad("E", "C") /return ["E", "G", "B"]
	>>> triad("E", "B") // return ["E", "G#", "B"]
	*/
	return [note, intervals.third(note, key), intervals.fifth(note, key)]
}

function triads(key) {
	//Returns all the triads in key. Implemented using a cache.
	if(_triads_cache.hasOwnProperty(key)) {
		return _triads_cache[key];
	}
	var res = diatonic.get_notes(key).map(function (x) {
		return triad(x, key);
	});
	_triads_cache[key] = res;
	return res;
}

// absolute

function major_triad(note) {
	/*Builds a major triad on note.
	Example:
	{{{
	>>> major_triad("C")
	["C", "E", "G"]
	*/
	return [note, intervals.major_third(note), intervals.perfect_fifth(note)];
}

function minor_triad(note) {
	/* Builds a minor triad on note.
	Example:
	{{{
	>>> minor_triad("C")
	["C", "Eb", "G"]
	}}} */
	return [note, intervals.minor_third(note), intervals.perfect_fifth(note)];
}

function diminished_triad(note) {
	/* Builds a diminished triad on note.
	Example:
	{{{
	>>> diminished_triad("C")
	["C", "Eb", "Gb"]
	}}}*/
	return [note, intervals.minor_third(note), intervals.minor_fifth(note)];
}

function augmented_triad(note) {
	/*Builds an augmented triad on note.
	Example:
	{{{
	>>> augmented_triad("C")
	["C", "E", "G#"]
	}}}*/
	return [note, intervals.major_third(note), notes.augment(intervals.major_fifth(note))];
}

/*===================================================================
							Sevenths
===================================================================*/

// diatonic

function seventh(note, key) {
	/*Returns the seventh chord on note in key.
	Example:
	{{{
	>>> seventh("C", "C")
	["C", "E", "G", "B"]
	}}}*/
	var arr = triad(note, key);
	arr.push(intervals.seventh(note, key));
	return arr;
}

function sevenths(key) {
	/*Returns all the sevenths chords in key in a list*/
	if(_sevenths_cache.hasOwnProperty(key)) {
		return _sevenths_cache[key];
	}

	res = diatonic.get_notes(key).map(function (x) {
		return seventh(x, key);
	});
	_sevenths_cache[key] = res;
	return res;
}

//absolute

function major_seventh(note) {
	/*Builds a major seventh on note.
	Example:
	{{{
	>>> major_seventh("C")
	["C", "E", "G", "B"]
	}}}*/
	var arr = major_triad(note);
	arr.push(intervals.major_seventh(note));
	return arr;
}

function minor_seventh(note) {
	/*Builds a minor seventh on note.
	Example:
	{{{
	>>> minor_seventh("C")
	["C", "Eb", "G", "Bb"]
	}}}*/
	var arr = minor_triad(note);
	arr.push(intervals.minor_seventh(note));
	return arr;
}

function dominant_seventh(note) {
	/*Builds a dominant seventh on note.
	Example:
	{{{
	>>> dominant_seventh("C")
	["C", "E", "G", "Bb"]
	}}}*/
	var arr = major_triad(note);
	arr.push(intervals.minor_seventh(note));
	return arr;
}

function half_diminished_seventh(note) {
	/*Builds a half diminished seventh (=minor seventh flat five) \
	chord on note.
		Example:
	{{{
	>>> half_diminished_seventh("C")
	["C", "Eb", "Gb", "Bb"]
	}}}*/
	var arr = diminished_triad(note);
	arr.push(intervals.minor_seventh(note));
	return  arr;
}

function minor_seventh_flat_five(note) {
	/*See half_diminished_seventh(note) for docs*/
	return half_diminished_seventh(note);
}

function diminished_seventh(note) {
	/*Builds a diminished seventh chord on note.
	Example:
	{{{
	>>> diminished_seventh("C")
	["C", "Eb", "Gb", "Bbb"]
	}}}*/
	var arr = diminished_triad(note);
	arr.push(notes.diminish(intervals.minor_seventh(note)));
	return arr;
}

function minor_major_seventh(note) {
	/*Builds a minor major seventh chord on note.
	Example:
	{{{
	>>> minor_major_seventh("C")
	["C", "Eb", "G", "B"]
	}}}*/
	var arr = minor_triad(note);
	arr.push(intervals.major_seventh(note));
	return arr;
}

/*===================================================================
							Sixths
===================================================================*/

// absolute

function minor_sixth(note) {
	/*Builds a minor sixth chord on note.
	Example:
	{{{
	>>> minor_sixth("C")
	['C', 'Eb', 'G', 'A']
	}}}*/
	var arr = minor_triad(note);
	arr.push(intervals.major_sixth(note));
	return arr;
}

function major_sixth(note) {
	/*Builds a major sixth chord on note.
	Example:
	{{{
	>>> major_sixth("C")
	['C', 'E', 'G', 'A']
	}}}*/
	var arr = major_triad(note);
	arr.push(intervals.major_sixth(note));
	return arr;
}

function dominant_sixth(note) {
	/*Builds the altered chord 6/7 on note.
	Example:
	{{{
	>>> dominant_sixth("C")
	['C', 'E', 'G', 'A', 'Bb']
	}}}*/
	var arr = major_sixth(note);
	arr.push(intervals.minor_seventh(note));
	return arr;
}

function sixth_ninth(note) {
	/*Builds the sixth/ninth chord on note.
	Example:
	{{{
	>>> sixth_ninth('C')
	['C', 'E', 'G', 'A', 'D']
	}}}*/
	var arr = major_sixth(note);
	arr.push(intervals.major_second(note));
	return arr;
}

/*===================================================================
							Ninths
===================================================================*/

// absolute

function minor_ninth(note) {
	/*Builds a minor ninth chord on note.
	Example:
	{{{
	>>> minor_ninth("C")
	['C', 'Eb', 'G', 'Bb', 'D']
	}}}*/
	var arr = minor_seventh(note);
	arr.push(intervals.major_second(note));
	return arr;
}

function major_ninth(note) {
	/*Builds a major ninth chord on note.
	Example:
	{{{
	>>> major_ninth("C")
	['C', 'E', 'G', 'B', 'D']
	}}}*/
	var arr = major_seventh(note);
	arr.push(intervals.major_second(note));
	return arr;
}

function dominant_ninth(note) {
	/*Builds a dominant ninth chord on note.
	Example:
	{{{
	>>> dominant_ninth("C")
	['C', 'E', 'G', 'Bb', 'D']
	}}}*/
	var arr = dominant_seventh(note);
	arr.push(intervals.major_second(note));
	return arr;
}

function dominant_flat_ninth(note) {
	/*Builds a dominant flat ninth chord on note.
	Example:
	{{{
	>>> dominant_ninth("C")
	['C', 'E', 'G', 'Bb', 'Db']
	}}}*/
	var res = dominant_ninth(note);
	res[4] = intervals.minor_second(note);
	return res;
}

function dominant_sharp_ninth(note) {
	/*Builds a dominant sharp ninth chord on note.
	Example:
	{{{
	>>> dominant_ninth("C")
	['C', 'E', 'G', 'Bb', 'D#']
	}}}*/
	var res = dominant_ninth(note);
	res[4] = notes.augment(intervals.major_second(note));
	return res;
}
//export
exports._triads_cache = _triads_cache;
exports._sevenths_cache = _sevenths_cache;
exports.chord_shorthand_meaning = chord_shorthand_meaning;
exports.triad = triad;
exports.triads = triads;
exports.major_triad = major_triad;
exports.minor_triad = minor_triad;
exports.diminished_triad = diminished_triad;
exports.augmented_triad = augmented_triad;
exports.major_seventh = major_seventh;
exports.minor_seventh = minor_seventh;
exports.dominant_seventh = dominant_seventh;
exports.half_diminished_seventh = half_diminished_seventh;
exports.minor_seventh_flat_five = minor_seventh_flat_five;
exports.diminished_seventh = diminished_seventh;
exports.minor_major_seventh = minor_major_seventh;
exports.minor_sixth = minor_sixth;
exports.major_sixth = major_sixth;
exports.dominant_sixth = dominant_sixth;
exports.sixth_ninth = sixth_ninth;
exports.minor_ninth = minor_ninth;
exports.major_ninth = major_ninth;
exports.dominant_ninth = dominant_ninth;
exports.dominant_flat_ninth = dominant_flat_ninth
exports.dominant_sharp_ninth = dominant_sharp_ninth;<commit_msg>ADD: eleventh and minor_eleventh<commit_after>var intervals = require('./intervals.js'),
	notes = require('./notes.js'),
	diatonic = require('./diatonic.js');

// A cache for composed triads
_triads_cache = {};
// A cache for composed sevenths
_sevenths_cache = {};

// A dictionairy that can be used to present
// lookup chord abbreviations. This dictionairy is also
// used in determine_seventh()
chord_shorthand_meaning = {
		// Triads
		"m" : " minor triad",
		"M" : " major triad",
		"" : " major triad",
		"dim" : " diminished triad",

		// Augmented chords
		"aug" : " augmented triad",
		"+" : " augmented triad",
		"7#5" : " augmented minor seventh",
		"M7+5" : " augmented minor seventh",
		"M7+" : " augmented major seventh",
		"m7+" : " augmented minor seventh",
		"7+" : " augmented major seventh",

		// Suspended chords
		"sus47" :" suspended seventh",
		"sus4" :" suspended fourth triad",
		"sus2" :" suspended second triad",
		"sus" :" suspended fourth triad",
		"11" :" eleventh",
		"sus4b9" :" suspended fourth ninth",
		"susb9" :" suspended fourth ninth",

		// Sevenths
		"m7" :" minor seventh",
		"M7" :" major seventh",
		"dom7" :" dominant seventh",
		"7" :" dominant seventh",
		"m7b5" :" half diminished seventh",
		"dim7" :" diminished seventh",
		"m/M7" :" minor/major seventh",
		"mM7" :" minor/major seventh",

		// Sixths
		"m6" : " minor sixth",
		"M6" : " major sixth",
		"6" : " major sixth",
		"6/7" : " dominant sixth",
		"67": " dominant sixth",
		"6/9" :" sixth ninth",
		"69" : " sixth ninth",

		// Ninths
		"9" : " dominant ninth",
		"7b9" : " dominant flat ninth",
		"7#9" :" dominant sharp ninth",
		"M9" :" major ninth",
		"m9" :" minor ninth",

		// Elevenths
		"7#11" :" lydian dominant seventh",
		"m11" :" minor eleventh",

		// Thirteenths
		"M13" :" major thirteenth",
		"m13" :" minor thirteenth",

		"13" :" dominant thirteenth",

		// Altered Chords
		"7b5" :" dominant flat five",

		// Special
		"hendrix" :" hendrix chord",
		"7b12" :" hendrix chord",
		"5" :" perfect fifth"
};

/*===================================================================
							Triads
===================================================================*/

// diatonic

function triad(note, key) {
	/*Returns the triad on note in key as a list.
	Examples:
	>>> triad("E", "C") /return ["E", "G", "B"]
	>>> triad("E", "B") // return ["E", "G#", "B"]
	*/
	return [note, intervals.third(note, key), intervals.fifth(note, key)]
}

function triads(key) {
	//Returns all the triads in key. Implemented using a cache.
	if(_triads_cache.hasOwnProperty(key)) {
		return _triads_cache[key];
	}
	var res = diatonic.get_notes(key).map(function (x) {
		return triad(x, key);
	});
	_triads_cache[key] = res;
	return res;
}

// absolute

function major_triad(note) {
	/*Builds a major triad on note.
	Example:
	{{{
	>>> major_triad("C")
	["C", "E", "G"]
	*/
	return [note, intervals.major_third(note), intervals.perfect_fifth(note)];
}

function minor_triad(note) {
	/* Builds a minor triad on note.
	Example:
	{{{
	>>> minor_triad("C")
	["C", "Eb", "G"]
	}}} */
	return [note, intervals.minor_third(note), intervals.perfect_fifth(note)];
}

function diminished_triad(note) {
	/* Builds a diminished triad on note.
	Example:
	{{{
	>>> diminished_triad("C")
	["C", "Eb", "Gb"]
	}}}*/
	return [note, intervals.minor_third(note), intervals.minor_fifth(note)];
}

function augmented_triad(note) {
	/*Builds an augmented triad on note.
	Example:
	{{{
	>>> augmented_triad("C")
	["C", "E", "G#"]
	}}}*/
	return [note, intervals.major_third(note), notes.augment(intervals.major_fifth(note))];
}

/*===================================================================
							Sevenths
===================================================================*/

// diatonic

function seventh(note, key) {
	/*Returns the seventh chord on note in key.
	Example:
	{{{
	>>> seventh("C", "C")
	["C", "E", "G", "B"]
	}}}*/
	var arr = triad(note, key);
	arr.push(intervals.seventh(note, key));
	return arr;
}

function sevenths(key) {
	/*Returns all the sevenths chords in key in a list*/
	if(_sevenths_cache.hasOwnProperty(key)) {
		return _sevenths_cache[key];
	}

	res = diatonic.get_notes(key).map(function (x) {
		return seventh(x, key);
	});
	_sevenths_cache[key] = res;
	return res;
}

//absolute

function major_seventh(note) {
	/*Builds a major seventh on note.
	Example:
	{{{
	>>> major_seventh("C")
	["C", "E", "G", "B"]
	}}}*/
	var arr = major_triad(note);
	arr.push(intervals.major_seventh(note));
	return arr;
}

function minor_seventh(note) {
	/*Builds a minor seventh on note.
	Example:
	{{{
	>>> minor_seventh("C")
	["C", "Eb", "G", "Bb"]
	}}}*/
	var arr = minor_triad(note);
	arr.push(intervals.minor_seventh(note));
	return arr;
}

function dominant_seventh(note) {
	/*Builds a dominant seventh on note.
	Example:
	{{{
	>>> dominant_seventh("C")
	["C", "E", "G", "Bb"]
	}}}*/
	var arr = major_triad(note);
	arr.push(intervals.minor_seventh(note));
	return arr;
}

function half_diminished_seventh(note) {
	/*Builds a half diminished seventh (=minor seventh flat five) \
	chord on note.
		Example:
	{{{
	>>> half_diminished_seventh("C")
	["C", "Eb", "Gb", "Bb"]
	}}}*/
	var arr = diminished_triad(note);
	arr.push(intervals.minor_seventh(note));
	return  arr;
}

function minor_seventh_flat_five(note) {
	/*See half_diminished_seventh(note) for docs*/
	return half_diminished_seventh(note);
}

function diminished_seventh(note) {
	/*Builds a diminished seventh chord on note.
	Example:
	{{{
	>>> diminished_seventh("C")
	["C", "Eb", "Gb", "Bbb"]
	}}}*/
	var arr = diminished_triad(note);
	arr.push(notes.diminish(intervals.minor_seventh(note)));
	return arr;
}

function minor_major_seventh(note) {
	/*Builds a minor major seventh chord on note.
	Example:
	{{{
	>>> minor_major_seventh("C")
	["C", "Eb", "G", "B"]
	}}}*/
	var arr = minor_triad(note);
	arr.push(intervals.major_seventh(note));
	return arr;
}

/*===================================================================
							Sixths
===================================================================*/

// absolute

function minor_sixth(note) {
	/*Builds a minor sixth chord on note.
	Example:
	{{{
	>>> minor_sixth("C")
	['C', 'Eb', 'G', 'A']
	}}}*/
	var arr = minor_triad(note);
	arr.push(intervals.major_sixth(note));
	return arr;
}

function major_sixth(note) {
	/*Builds a major sixth chord on note.
	Example:
	{{{
	>>> major_sixth("C")
	['C', 'E', 'G', 'A']
	}}}*/
	var arr = major_triad(note);
	arr.push(intervals.major_sixth(note));
	return arr;
}

function dominant_sixth(note) {
	/*Builds the altered chord 6/7 on note.
	Example:
	{{{
	>>> dominant_sixth("C")
	['C', 'E', 'G', 'A', 'Bb']
	}}}*/
	var arr = major_sixth(note);
	arr.push(intervals.minor_seventh(note));
	return arr;
}

function sixth_ninth(note) {
	/*Builds the sixth/ninth chord on note.
	Example:
	{{{
	>>> sixth_ninth('C')
	['C', 'E', 'G', 'A', 'D']
	}}}*/
	var arr = major_sixth(note);
	arr.push(intervals.major_second(note));
	return arr;
}

/*===================================================================
							Ninths
===================================================================*/

// absolute

function minor_ninth(note) {
	/*Builds a minor ninth chord on note.
	Example:
	{{{
	>>> minor_ninth("C")
	['C', 'Eb', 'G', 'Bb', 'D']
	}}}*/
	var arr = minor_seventh(note);
	arr.push(intervals.major_second(note));
	return arr;
}

function major_ninth(note) {
	/*Builds a major ninth chord on note.
	Example:
	{{{
	>>> major_ninth("C")
	['C', 'E', 'G', 'B', 'D']
	}}}*/
	var arr = major_seventh(note);
	arr.push(intervals.major_second(note));
	return arr;
}

function dominant_ninth(note) {
	/*Builds a dominant ninth chord on note.
	Example:
	{{{
	>>> dominant_ninth("C")
	['C', 'E', 'G', 'Bb', 'D']
	}}}*/
	var arr = dominant_seventh(note);
	arr.push(intervals.major_second(note));
	return arr;
}

function dominant_flat_ninth(note) {
	/*Builds a dominant flat ninth chord on note.
	Example:
	{{{
	>>> dominant_ninth("C")
	['C', 'E', 'G', 'Bb', 'Db']
	}}}*/
	var res = dominant_ninth(note);
	res[4] = intervals.minor_second(note);
	return res;
}

function dominant_sharp_ninth(note) {
	/*Builds a dominant sharp ninth chord on note.
	Example:
	{{{
	>>> dominant_ninth("C")
	['C', 'E', 'G', 'Bb', 'D#']
	}}}*/
	var res = dominant_ninth(note);
	res[4] = notes.augment(intervals.major_second(note));
	return res;
}

/*===================================================================
							Elevenths
===================================================================*/

// diatonic

// absolute

function eleventh(note) {
	/*Builds an eleventh chord on note.
	Example:
	{{{
	>>> eleventh("C")
	['C', 'G', 'Bb', 'F']
	}}}*/
	return [note, intervals.perfect_fifth(note), intervals.minor_seventh(note), intervals.perfect_fourth(note)];
}

function minor_eleventh(note) {
	/*Builds a minor eleventh chord on note.
	Example:
	{{{
	>>> minor_eleventh("C")
	['C', 'Eb', 'G', 'Bb', 'F']
	}}}*/
	var arr = minor_seventh(note);
	arr.push(intervals.perfect_fourth(note));
	return arr;
}

/*===================================================================
							Thirteenths
===================================================================*/

// absolute

//export
exports._triads_cache = _triads_cache;
exports._sevenths_cache = _sevenths_cache;
exports.chord_shorthand_meaning = chord_shorthand_meaning;
exports.triad = triad;
exports.triads = triads;
exports.major_triad = major_triad;
exports.minor_triad = minor_triad;
exports.diminished_triad = diminished_triad;
exports.augmented_triad = augmented_triad;
exports.major_seventh = major_seventh;
exports.minor_seventh = minor_seventh;
exports.dominant_seventh = dominant_seventh;
exports.half_diminished_seventh = half_diminished_seventh;
exports.minor_seventh_flat_five = minor_seventh_flat_five;
exports.diminished_seventh = diminished_seventh;
exports.minor_major_seventh = minor_major_seventh;
exports.minor_sixth = minor_sixth;
exports.major_sixth = major_sixth;
exports.dominant_sixth = dominant_sixth;
exports.sixth_ninth = sixth_ninth;
exports.minor_ninth = minor_ninth;
exports.major_ninth = major_ninth;
exports.dominant_ninth = dominant_ninth;
exports.dominant_flat_ninth = dominant_flat_ninth
exports.dominant_sharp_ninth = dominant_sharp_ninth;
exports.eleventh = eleventh;
exports.minor_eleventh = minor_eleventh;<|endoftext|>


<commit_before>(function() {
    "use strict";

    var fn = timbre.fn;

    function BufferNode(_args) {
        timbre.Object.call(this, _args);
        fn.fixAR(this);

        var _ = this._;
        _.pitch      = timbre(1);
        _.buffer     = new Float32Array(0);
        _.isLooped   = false;
        _.isReversed = false;
        _.duration    = 0;
        _.currentTime = 0;
        _.currentTimeIncr = this.cell.length * 1000 / timbre.samplerate;
        _.samplerate  = 44100;
        _.phase = 0;
        _.phaseIncr = 0;

        this.on("play", onplay);
    }
    fn.extend(BufferNode);

    var onplay = function(value) {
        this._.isEnded = (value === false);
    };

    var $ = BufferNode.prototype;

    var setBuffer = function(value) {
        var _ = this._;
        if (typeof value === "object") {
            var buffer, samplerate;
            if (value instanceof Float32Array) {
                buffer = value;
            } else if (value.buffer instanceof Float32Array) {
                buffer = value.buffer;
                if (typeof value.samplerate === "number") {
                    samplerate = value.samplerate;
                }
            }
            if (buffer) {
                if (samplerate > 0) {
                    _.samplerate = value.samplerate;
                }
                _.buffer = buffer;
                _.phase     = 0;
                _.phaseIncr = _.samplerate / timbre.samplerate;
                _.duration  = _.buffer.length * 1000 / _.samplerate;
                _.currentTime = 0;
                _.plotFlush = true;
                this.isReversed = _.isReversed;
            }
        }
    };

    Object.defineProperties($, {
        buffer: {
            set: setBuffer,
            get: function() {
                return this._.buffer;
            }
        },
        pitch: {
            set: function(value) {
                this._.pitch = timbre(value);
            },
            get: function() {
                return this._.pitch;
            }
        },
        isLooped: {
            get: function() {
                return this._.isLooped;
            }
        },
        isReversed: {
            get: function() {
                return this._.isReversed;
            }
        },
        isEnded: {
            get: function() {
                return this._.isEnded;
            }
        },
        samplerate: {
            get: function() {
                return this._.samplerate;
            }
        },
        duration: {
            get: function() {
                return this._.duration;
            }
        },
        currentTime: {
            set: function(value) {
                if (typeof value === "number") {
                    var _ = this._;
                    if (0 <= value && value <= _.duration) {
                        _.phase = (value / 1000) * _.samplerate;
                        _.currentTime = value;
                    }
                }
            },
            get: function() {
                return this._.currentTime;
            }
        }
    });

    $.clone = function() {
        var _ = this._;
        var instance = timbre("buffer");

        if (_.buffer) {
            setBuffer.call(instance, {
                buffer    : _.buffer,
                samplerate: _.samplerate
            });
        }
        instance.isLooped   = this.isLooped;
        instance.isReversed = this.isReversed;

        return instance;
    };

    $.slice = function(begin, end) {
        var _ = this._;
        var instance = timbre(_.originkey);

        var isReversed = _.isReversed;
        if (typeof begin === "number" ){
            begin = (begin * 0.001 * _.samplerate)|0;
        } else {
            begin = 0;
        }
        if (typeof end === "number") {
            end   = (end   * 0.001 * _.samplerate)|0;
        } else {
            end = _.buffer.length;
        }
        if (begin > end) {
            var tmp = begin;
            begin = end;
            end   = tmp;
            isReversed = !isReversed;
        }

        if (_.buffer) {
            setBuffer.call(instance, {
                buffer    : _.buffer.subarray(begin, end),
                samplerate: _.samplerate
            });
            instance._.isEnded = false;
        }
        instance.isLooped   = this.isLooped;
        instance.isReversed = this.isReversed;

        return instance;
    };

    $.reverse = function(value) {
        var _ = this._;

        _.isReversed = !!value;
        if (_.isReversed) {
            if (_.phaseIncr > 0) {
                _.phaseIncr *= -1;
            }
            if (_.phase === 0 && _.buffer) {
                _.phase = _.buffer.length + _.phaseIncr;
            }
        } else {
            if (_.phaseIncr < 0) {
                _.phaseIncr *= -1;
            }
        }

        return this;
    };

    $.loop = function(value) {
        this._.isLooped = !!value;
        return this;
    };

    $.bang = function(value) {
        this._.phase   = 0;
        this._.isEnded = (value === false);
        this._.emit("bang");
        return this;
    };

    $.process = function(tickID) {
        var _ = this._;
        var cell = this.cell;

        if (_.isEnded && !_.buffer) {
            return cell;
        }

        if (this.tickID !== tickID) {
            this.tickID = tickID;

            var buffer = _.buffer;
            var phase  = _.phase;
            var mul = _.mul, add = _.add;
            var i, imax = cell.length;

            if (this.inputs.length) {
                fn.inputSignalAR(this);
                var t, sr = _.samplerate * 0.001;
                for (i = 0; i < imax; ++i) {
                    t = cell[i];
                    phase = t * sr;
                    cell[i] = (buffer[phase|0] || 0) * mul + add;
                }
                _.phase = phase;
                _.currentTime = t;
            } else {
                var pitch  = _.pitch.process(tickID)[0];
                var phaseIncr = _.phaseIncr * pitch;

                for (i = 0; i < imax; ++i) {
                    cell[i] = (buffer[phase|0] || 0) * mul + add;
                    phase += phaseIncr;
                }

                if (phase >= buffer.length) {
                    if (_.isLooped) {
                        fn.nextTick(onlooped.bind(this));
                    } else {
                        fn.nextTick(onended.bind(this));
                    }
                } else if (phase < 0) {
                    if (_.isLooped) {
                        fn.nextTick(onlooped.bind(this));
                    } else {
                        fn.nextTick(onended.bind(this));
                    }
                }
                _.phase = phase;
                _.currentTime += _.currentTimeIncr;
            }
        }

        return cell;
    };

    var onlooped = function() {
        var _ = this._;
        if (_.phase >= _.buffer.length) {
            _.phase = 0;
        } else if (_.phase < 0) {
            _.phase = _.buffer.length + _.phaseIncr;
        }
        this._.emit("looped");
    };

    var onended = function() {
        fn.onended(this, 0);
    };

    var super_plot = timbre.Object.prototype.plot;

    $.plot = function(opts) {
        var _ = this._;
        var buffer = _.buffer;
        if (_.plotFlush) {
            var data = new Float32Array(2048);
            var x = 0, xIncr = buffer.length / 2048;
            for (var i = 0; i < 2048; i++) {
                data[i] = buffer[x|0];
                x += xIncr;
            }
            _.plotData  = data;
            _.plotFlush = null;
        }
        return super_plot.call(this, opts);
    };

    fn.register("buffer", BufferNode);

})();
<commit_msg>fix loop, reverse<commit_after>(function() {
    "use strict";

    var fn = timbre.fn;

    function BufferNode(_args) {
        timbre.Object.call(this, _args);
        fn.fixAR(this);

        var _ = this._;
        _.pitch      = timbre(1);
        _.buffer     = new Float32Array(0);
        _.isLooped   = false;
        _.isReversed = false;
        _.duration    = 0;
        _.currentTime = 0;
        _.currentTimeIncr = this.cell.length * 1000 / timbre.samplerate;
        _.samplerate  = 44100;
        _.phase = 0;
        _.phaseIncr = 0;

        this.on("play", onplay);
    }
    fn.extend(BufferNode);

    var onplay = function(value) {
        this._.isEnded = (value === false);
    };

    var $ = BufferNode.prototype;

    var setBuffer = function(value) {
        var _ = this._;
        if (typeof value === "object") {
            var buffer, samplerate;
            if (value instanceof Float32Array) {
                buffer = value;
            } else if (value.buffer instanceof Float32Array) {
                buffer = value.buffer;
                if (typeof value.samplerate === "number") {
                    samplerate = value.samplerate;
                }
            }
            if (buffer) {
                if (samplerate > 0) {
                    _.samplerate = value.samplerate;
                }
                _.buffer = buffer;
                _.phase     = 0;
                _.phaseIncr = _.samplerate / timbre.samplerate;
                _.duration  = _.buffer.length * 1000 / _.samplerate;
                _.currentTime = 0;
                _.plotFlush = true;
                this.reverse(_.isReversed);
            }
        }
    };

    Object.defineProperties($, {
        buffer: {
            set: setBuffer,
            get: function() {
                return this._.buffer;
            }
        },
        pitch: {
            set: function(value) {
                this._.pitch = timbre(value);
            },
            get: function() {
                return this._.pitch;
            }
        },
        isLooped: {
            get: function() {
                return this._.isLooped;
            }
        },
        isReversed: {
            get: function() {
                return this._.isReversed;
            }
        },
        isEnded: {
            get: function() {
                return this._.isEnded;
            }
        },
        samplerate: {
            get: function() {
                return this._.samplerate;
            }
        },
        duration: {
            get: function() {
                return this._.duration;
            }
        },
        currentTime: {
            set: function(value) {
                if (typeof value === "number") {
                    var _ = this._;
                    if (0 <= value && value <= _.duration) {
                        _.phase = (value / 1000) * _.samplerate;
                        _.currentTime = value;
                    }
                }
            },
            get: function() {
                return this._.currentTime;
            }
        }
    });

    $.clone = function() {
        var _ = this._;
        var instance = timbre("buffer");

        if (_.buffer) {
            setBuffer.call(instance, {
                buffer    : _.buffer,
                samplerate: _.samplerate
            });
        }
        instance.loop(_.isLooped);
        instance.reverse(_.isReversed);

        return instance;
    };

    $.slice = function(begin, end) {
        var _ = this._;
        var instance = timbre(_.originkey);

        var isReversed = _.isReversed;
        if (typeof begin === "number" ){
            begin = (begin * 0.001 * _.samplerate)|0;
        } else {
            begin = 0;
        }
        if (typeof end === "number") {
            end   = (end   * 0.001 * _.samplerate)|0;
        } else {
            end = _.buffer.length;
        }
        if (begin > end) {
            var tmp = begin;
            begin = end;
            end   = tmp;
            isReversed = !isReversed;
        }

        if (_.buffer) {
            setBuffer.call(instance, {
                buffer    : _.buffer.subarray(begin, end),
                samplerate: _.samplerate
            });
            instance._.isEnded = false;
        }
        instance.loop(_.isLooped);
        instance.reverse(_.isReversed);

        return instance;
    };

    $.reverse = function(value) {
        var _ = this._;

        _.isReversed = !!value;
        if (_.isReversed) {
            if (_.phaseIncr > 0) {
                _.phaseIncr *= -1;
            }
            if (_.phase === 0 && _.buffer) {
                _.phase = _.buffer.length + _.phaseIncr;
            }
        } else {
            if (_.phaseIncr < 0) {
                _.phaseIncr *= -1;
            }
        }

        return this;
    };

    $.loop = function(value) {
        this._.isLooped = !!value;
        return this;
    };

    $.bang = function(value) {
        this._.phase   = 0;
        this._.isEnded = (value === false);
        this._.emit("bang");
        return this;
    };

    $.process = function(tickID) {
        var _ = this._;
        var cell = this.cell;

        if (_.isEnded && !_.buffer) {
            return cell;
        }

        if (this.tickID !== tickID) {
            this.tickID = tickID;

            var buffer = _.buffer;
            var phase  = _.phase;
            var mul = _.mul, add = _.add;
            var i, imax = cell.length;

            if (this.inputs.length) {
                fn.inputSignalAR(this);
                var t, sr = _.samplerate * 0.001;
                for (i = 0; i < imax; ++i) {
                    t = cell[i];
                    phase = t * sr;
                    cell[i] = (buffer[phase|0] || 0) * mul + add;
                }
                _.phase = phase;
                _.currentTime = t;
            } else {
                var pitch  = _.pitch.process(tickID)[0];
                var phaseIncr = _.phaseIncr * pitch;

                for (i = 0; i < imax; ++i) {
                    cell[i] = (buffer[phase|0] || 0) * mul + add;
                    phase += phaseIncr;
                }

                if (phase >= buffer.length) {
                    if (_.isLooped) {
                        fn.nextTick(onlooped.bind(this));
                    } else {
                        fn.nextTick(onended.bind(this));
                    }
                } else if (phase < 0) {
                    if (_.isLooped) {
                        fn.nextTick(onlooped.bind(this));
                    } else {
                        fn.nextTick(onended.bind(this));
                    }
                }
                _.phase = phase;
                _.currentTime += _.currentTimeIncr;
            }
        }

        return cell;
    };

    var onlooped = function() {
        var _ = this._;
        if (_.phase >= _.buffer.length) {
            _.phase = 0;
        } else if (_.phase < 0) {
            _.phase = _.buffer.length + _.phaseIncr;
        }
        this._.emit("looped");
    };

    var onended = function() {
        fn.onended(this, 0);
    };

    var super_plot = timbre.Object.prototype.plot;

    $.plot = function(opts) {
        var _ = this._;
        var buffer = _.buffer;
        if (_.plotFlush) {
            var data = new Float32Array(2048);
            var x = 0, xIncr = buffer.length / 2048;
            for (var i = 0; i < 2048; i++) {
                data[i] = buffer[x|0];
                x += xIncr;
            }
            _.plotData  = data;
            _.plotFlush = null;
        }
        return super_plot.call(this, opts);
    };

    fn.register("buffer", BufferNode);

})();
<|endoftext|>


<commit_before>goog.provide('og.node.Planet');

goog.require('og');
goog.require('og.node.RenderNode');
goog.require('og.math.Matrix4');
goog.require('og.math.Vector3');
goog.require('og.math.coder');
goog.require('og.quadTree');
goog.require('og.quadTree.QuadNode');
goog.require('og.bv.Sphere');
goog.require('og.planetSegment');
goog.require('og.shaderProgram.overlays');
goog.require('og.shaderProgram.single');
goog.require('og.shaderProgram.picking');
goog.require('og.layer');
goog.require('og.planetSegment.PlanetSegmentHelper');
goog.require('og.Extent');
goog.require('og.math.Ray');
goog.require('og.webgl.Framebuffer');
goog.require('og.Events');

og.node.Planet = function (name, ellipsoid) {
    og.base(this, name);
    this.ellipsoid = ellipsoid;
    this.quadTree;
    this.events = new og.Events();

    this.layers = [];
    this.visibleLayers = [];
    this.tcolorArr = new Float32Array(og.layer.MAX_OVERLAYS * 4);
    this.baseLayer;
    this.terrainProvider;
    this.emptyTexture = null;

    this.createdNodesCount = 0;
    this.renderedNodes = [];
    this.heightFactor = 1.0;

    this.mousePositionOnEarth = new og.math.Vector3();

    this.indexesBuffers = [];
    this.backbuffer;
    this._currentDistanceFromPixel = 0;
};

og.extend(og.node.Planet, og.node.RenderNode);

og.node.Planet.prototype.getLayerByName = function (name) {
    var i = this.layers.length;
    while (i--) {
        if (this.layers[i].name === name)
            return this.layers[i];
    }
    return null;
};

og.node.Planet.prototype.addLayer = function (layer) {
    layer.planet = this;
    this.layers.push(layer);
    this.updateVisibleLayers();
};

og.node.Planet.prototype.addLayers = function (layers) {
    for (var i = 0; i < layers.length; i++) {
        this.addLayer(layers[i]);
    }
};

og.node.Planet.prototype.setBaseLayer = function (layer) {
    if (this.baseLayer) {
        if (layer.id != this.baseLayer.id) {
            for (var i = 0; i < this.layers.length; i++) {
                if (this.layers[i].isBaseLayer) {
                    this.layers[i].visibility = false;
                }
            }
            layer.setVisibility(true);
            this.baseLayer.abortLoading();
            this.baseLayer = layer;
        }
    } else {
        this.baseLayer = layer;
    }
};

og.node.Planet.prototype.setHeightFactor = function (factor) {
    if (this.heightFactor !== factor) {
        this.heightFactor = factor;
        this.quadTree.reloadTerrain();
    }
};

og.node.Planet.prototype.setTerrainProvider = function (terrain) {
    this.terrainProvider = terrain;
};

og.node.Planet.prototype.removeLayer = function (layer) {
    //...
};

og.node.Planet.prototype.addEvent = function (name, sender, callback) {
    this.events.addEvent(name, sender, callback);
};

og.node.Planet.prototype.initialization = function () {
    //Initialization indexes table
    og.planetSegment.PlanetSegmentHelper.initIndexesTables(5);

    //Iniytialize indexes buffers array
    for (var i = 0; i <= 5; i++) {
        var gridSize = Math.pow(2, i);
        var indexes = og.planetSegment.PlanetSegmentHelper.createSegmentIndexes(gridSize, [gridSize, gridSize, gridSize, gridSize]);
        this.indexesBuffers[gridSize] = this.renderer.handler.createElementArrayBuffer(indexes, 1, indexes.length);
    }

    this.quadTree = og.quadTree.QuadNode.createNode(this, og.quadTree.NW, null, 0, 0, og.Extent.createFromArray([-20037508.34, -20037508.34, 20037508.34, 20037508.34]));
    this.drawMode = this.renderer.handler.gl.TRIANGLE_STRIP;
    this.setScale(new og.math.Vector3(1.0, this.ellipsoid._a / this.ellipsoid._b, 1.0));
    this.updateMatrices();
    this.loadEmptyTexture(og.RESOURCES_URL + "images/planet/empty.jpg");

    this.renderer.handler.addShaderProgram(og.shaderProgram.overlays);
    this.renderer.handler.addShaderProgram(og.shaderProgram.single);
    this.renderer.handler.addShaderProgram(og.shaderProgram.picking);

    this.backbuffer = new og.webgl.Framebuffer(this.renderer.handler.gl);
    this.backbuffer.initialize();
    this.updateVisibleLayers();
    this.events.registerNames([
        "ondraw"
    ]);

    this.renderer.addEvent("onresize", this.backbuffer, function (e) {
        this.setSize(e.width, e.height);
    });
};

og.node.Planet.prototype.loadEmptyTexture = function (url) {
    var that = this,
        img = new Image();
    img.onload = function () {
        that.emptyTexture = that.renderer.handler.createTextureFromImage(this);
    };
    img.src = url;
};

og.node.Planet.prototype.updateVisibleLayers = function () {
    this.visibleLayers.length = 0;
    for (var i = 0; i < this.layers.length; i++) {
        if (this.layers[i].visibility) {
            this.visibleLayers.push(this.layers[i]);
        }
    }
    this.sortVisibleLayersByZIndex();
};

og.node.Planet.prototype.sortVisibleLayersByZIndex = function () {
    this.visibleLayers.sort(function (a, b) {
        return a.isBaseLayer ? -1 : a.zIndex - b.zIndex;
    })
};

og.node.Planet.prototype.getAltitude = function (p) {
    var direction = new og.math.Vector3(-p.x, -p.y, -p.z);
    var intersection = this.getRayIntersectionEllipsoid(new og.math.Ray(this.renderer.activeCamera.eye, direction));
    return p.distance(intersection);
};

og.node.Planet.prototype.frame = function () {

    this.quadTree.renderTree();
    this.renderNodes();

    //re
    var r = this.renderer;
    var cam = r.activeCamera;
    this.mousePositionOnEarth = this.getCartesianFromPixelEllipsoid(r.mouseState);
    this.renderer.activeCamera.altitude = this.getAltitude(cam.eye);

    var ll = this.getLonLatFromPixelTerrain(new og.math.Pixel(this.renderer.mouseState.x, this.renderer.mouseState.y));
    var distance = 0;
    print2d("lbCoords", "distance = " + distance + ", latlon = " + ll.lat.toFixed(5) + "," + ll.lon.toFixed(5) + ", height = " + ll.height, 10, 10);

    //Here is the planet node dispatche a draw event before clearing.
    this.events.callEvents(this.events.ondraw, this);

    this.visitedNodesCount = 0;
    this.renderedNodesCount = 0;

    //NOT WORKING! BUG IS HERE!
    if (this.createdNodesCount > 140) {
        this.quadTree.clearTree();
        this.createdNodesCount = 0;
    }

    this.renderedNodes.length = 0;
};

og.node.Planet.prototype.renderNodes = function () {
    var sh, drawCallback;
    var renderer = this.renderer;
    var h = renderer.handler;

    if (this.visibleLayers.length > 1) {
        h.shaderPrograms.overlays.activate();
        sh = h.shaderPrograms.overlays._program;
        drawCallback = og.planetSegment.drawOverlays;
        var layers = this.visibleLayers;
        var i = layers.length;
        while (i--) {
            var ll = layers[i];
            var nt4 = i * 4;
            this.tcolorArr[nt4] = ll.transparentColor[0];
            this.tcolorArr[nt4 + 1] = ll.transparentColor[1];
            this.tcolorArr[nt4 + 2] = ll.transparentColor[2];
            this.tcolorArr[nt4 + 3] = ll.opacity;
        }
        h.gl.uniform1i(sh.uniforms.numTex._pName, layers.length);
        h.gl.uniform4fv(sh.uniforms.tcolorArr._pName, this.tcolorArr);
    } else {
        h.shaderPrograms.single.activate();
        sh = h.shaderPrograms.single._program;
        drawCallback = og.planetSegment.drawSingle;
    }

    h.gl.uniformMatrix4fv(sh.uniforms.uPMVMatrix._pName, false, renderer.activeCamera.pmvMatrix._m);

    var i = this.renderedNodes.length;
    while (i--) {
        drawCallback(sh, this.renderedNodes[i].planetSegment);
    }
};

og.node.Planet.prototype.getCartesianFromPixelEllipsoid = function (px) {
    var direction = this.renderer.activeCamera.unproject(px.x, px.y);
    var mxTr = this.transformationMatrix.transpose();
    var sx = new og.math.Ray(mxTr.mulVec3(this.renderer.activeCamera.eye),
        mxTr.mulVec3(direction)).hitSphere(new og.bv.Sphere(this.ellipsoid._a));
    if (sx) {
        return this.itransformationMatrix.mulVec3(sx);
    }
    return null;
};

og.node.Planet.prototype.getLonLatFromPixelEllipsoid = function (px) {
    var coords = this.getCartesianFromPixelEllipsoid(px);
    return this.ellipsoid.ECEF2LonLat(coords.z, coords.x, coords.y);
};

og.node.Planet.prototype.getRayIntersectionEllipsoid = function (ray) {
    var mxTr = this.transformationMatrix.transpose();
    var sx = new og.math.Ray(mxTr.mulVec3(ray.origin),
        mxTr.mulVec3(ray.direction)).hitSphere(new og.bv.Sphere(this.ellipsoid._a));
    if (sx) {
        return this.itransformationMatrix.mulVec3(sx);
    }
    return null;
};

og.node.Planet.prototype.getCartesianFromPixelTerrain = function (px) {
    var distance = this.getDistanceFromPixel(px);
    var direction = this.renderer.activeCamera.unproject(px.x, px.y);
    return direction.scaleTo(distance).add(this.renderer.activeCamera.eye);
};

og.node.Planet.prototype.getLonLatFromPixelTerrain = function (px) {
    var coords = this.getCartesianFromPixelTerrain(px);
    return this.ellipsoid.ECEF2LonLat(coords.z, coords.x, coords.y);
};

og.node.Planet.prototype.getPixelFromCartesian = function (coords) {
    return this.renderer.activeCamera.project(coords);
};

og.node.Planet.prototype.getPixelFromLonLat = function (lonlat) {
    var coords = this.ellipsoid.LonLat2ECEF(lonlat);
    return this.renderer.activeCamera.project(coords);
};

og.node.Planet.prototype.getDistanceFromPixel = function (px) {
    this.renderDistanceBackbufferPASS();
    var color = og.math.Vector4.fromVec(this.backbuffer.readPixels(px.x, this.backbuffer.height - px.y));
    return this._currentDistanceFromPixel = og.math.coder.decodeFloatFromRGBA(color);
};

og.node.Planet.prototype.renderDistanceBackbufferPASS = function () {
    var b = this.backbuffer,
        r = this.renderer;
    var h = r.handler;
    var pp = h.shaderPrograms.picking;
    b.activate();
    b.clear();
    pp.activate();
    h.gl.uniform3fv(pp._program.uniforms.camPos._pName, r.activeCamera.eye.toVec());
    var i = this.renderedNodes.length;
    while (i--) {
        this.renderedNodes[i].planetSegment.drawPicking();
    }
    b.deactivate();
};<commit_msg>Little distance retrieveal from framebuffer optimization.<commit_after>goog.provide('og.node.Planet');

goog.require('og');
goog.require('og.node.RenderNode');
goog.require('og.math.Matrix4');
goog.require('og.math.Vector3');
goog.require('og.math.coder');
goog.require('og.quadTree');
goog.require('og.quadTree.QuadNode');
goog.require('og.bv.Sphere');
goog.require('og.planetSegment');
goog.require('og.shaderProgram.overlays');
goog.require('og.shaderProgram.single');
goog.require('og.shaderProgram.picking');
goog.require('og.layer');
goog.require('og.planetSegment.PlanetSegmentHelper');
goog.require('og.Extent');
goog.require('og.math.Ray');
goog.require('og.webgl.Framebuffer');
goog.require('og.Events');

og.node.Planet = function (name, ellipsoid) {
    og.base(this, name);
    this.ellipsoid = ellipsoid;
    this.quadTree;
    this.events = new og.Events();

    this.layers = [];
    this.visibleLayers = [];
    this.tcolorArr = new Float32Array(og.layer.MAX_OVERLAYS * 4);
    this.baseLayer;
    this.terrainProvider;
    this.emptyTexture = null;

    this.createdNodesCount = 0;
    this.renderedNodes = [];
    this.heightFactor = 1.0;

    this.mousePositionOnEarth = new og.math.Vector3();

    this.indexesBuffers = [];
    this.backbuffer;
    this._currentDistanceFromPixel = 0;
    this._viewChanged = true;
};

og.extend(og.node.Planet, og.node.RenderNode);

og.node.Planet.prototype.getLayerByName = function (name) {
    var i = this.layers.length;
    while (i--) {
        if (this.layers[i].name === name)
            return this.layers[i];
    }
    return null;
};

og.node.Planet.prototype.addLayer = function (layer) {
    layer.planet = this;
    this.layers.push(layer);
    this.updateVisibleLayers();
};

og.node.Planet.prototype.addLayers = function (layers) {
    for (var i = 0; i < layers.length; i++) {
        this.addLayer(layers[i]);
    }
};

og.node.Planet.prototype.setBaseLayer = function (layer) {
    if (this.baseLayer) {
        if (layer.id != this.baseLayer.id) {
            for (var i = 0; i < this.layers.length; i++) {
                if (this.layers[i].isBaseLayer) {
                    this.layers[i].visibility = false;
                }
            }
            layer.setVisibility(true);
            this.baseLayer.abortLoading();
            this.baseLayer = layer;
        }
    } else {
        this.baseLayer = layer;
    }
};

og.node.Planet.prototype.setHeightFactor = function (factor) {
    if (this.heightFactor !== factor) {
        this.heightFactor = factor;
        this.quadTree.reloadTerrain();
    }
};

og.node.Planet.prototype.setTerrainProvider = function (terrain) {
    this.terrainProvider = terrain;
};

og.node.Planet.prototype.removeLayer = function (layer) {
    //...
};

og.node.Planet.prototype.addEvent = function (name, sender, callback) {
    this.events.addEvent(name, sender, callback);
};

og.node.Planet.prototype.initialization = function () {
    //Initialization indexes table
    og.planetSegment.PlanetSegmentHelper.initIndexesTables(5);

    //Iniytialize indexes buffers array
    for (var i = 0; i <= 5; i++) {
        var gridSize = Math.pow(2, i);
        var indexes = og.planetSegment.PlanetSegmentHelper.createSegmentIndexes(gridSize, [gridSize, gridSize, gridSize, gridSize]);
        this.indexesBuffers[gridSize] = this.renderer.handler.createElementArrayBuffer(indexes, 1, indexes.length);
    }

    this.quadTree = og.quadTree.QuadNode.createNode(this, og.quadTree.NW, null, 0, 0, og.Extent.createFromArray([-20037508.34, -20037508.34, 20037508.34, 20037508.34]));
    this.drawMode = this.renderer.handler.gl.TRIANGLE_STRIP;
    this.setScale(new og.math.Vector3(1.0, this.ellipsoid._a / this.ellipsoid._b, 1.0));
    this.updateMatrices();
    this.loadEmptyTexture(og.RESOURCES_URL + "images/planet/empty.jpg");

    this.renderer.handler.addShaderProgram(og.shaderProgram.overlays);
    this.renderer.handler.addShaderProgram(og.shaderProgram.single);
    this.renderer.handler.addShaderProgram(og.shaderProgram.picking);

    this.backbuffer = new og.webgl.Framebuffer(this.renderer.handler.gl);
    this.backbuffer.initialize();
    this.updateVisibleLayers();
    this.events.registerNames([
        "ondraw"
    ]);

    this.renderer.addEvent("onresize", this.backbuffer, function (e) {
        this.setSize(e.width, e.height);
    });

    this.renderer.addEvent("onmousemove", this, function (e) {
        this._viewChanged = true;
    });
};

og.node.Planet.prototype.loadEmptyTexture = function (url) {
    var that = this,
        img = new Image();
    img.onload = function () {
        that.emptyTexture = that.renderer.handler.createTextureFromImage(this);
    };
    img.src = url;
};

og.node.Planet.prototype.updateVisibleLayers = function () {
    this.visibleLayers.length = 0;
    for (var i = 0; i < this.layers.length; i++) {
        if (this.layers[i].visibility) {
            this.visibleLayers.push(this.layers[i]);
        }
    }
    this.sortVisibleLayersByZIndex();
};

og.node.Planet.prototype.sortVisibleLayersByZIndex = function () {
    this.visibleLayers.sort(function (a, b) {
        return a.isBaseLayer ? -1 : a.zIndex - b.zIndex;
    })
};

og.node.Planet.prototype.getAltitude = function (p) {
    var direction = new og.math.Vector3(-p.x, -p.y, -p.z);
    var intersection = this.getRayIntersectionEllipsoid(new og.math.Ray(this.renderer.activeCamera.eye, direction));
    return p.distance(intersection);
};

og.node.Planet.prototype.frame = function () {

    this.quadTree.renderTree();
    this.renderNodes();

    //re
    var r = this.renderer;
    var cam = r.activeCamera;
    this.mousePositionOnEarth = this.getCartesianFromPixelEllipsoid(r.mouseState);
    this.renderer.activeCamera.altitude = this.getAltitude(cam.eye);

    var ll = this.getLonLatFromPixelTerrain(this.renderer.mouseState);
    var distance = this.getDistanceFromPixel(this.renderer.mouseState);
    print2d("lbCoords", "distance = " + distance + ", latlon = " + ll.lat.toFixed(5) + "," + ll.lon.toFixed(5) + ", height = " + ll.height, 10, 10);

    //Here is the planet node dispatche a draw event before clearing.
    this.events.callEvents(this.events.ondraw, this);

    this.visitedNodesCount = 0;
    this.renderedNodesCount = 0;

    //NOT WORKING! BUG IS HERE!
    if (this.createdNodesCount > 140) {
        this.quadTree.clearTree();
        this.createdNodesCount = 0;
    }

    this.renderedNodes.length = 0;
};

og.node.Planet.prototype.renderNodes = function () {
    var sh, drawCallback;
    var renderer = this.renderer;
    var h = renderer.handler;

    if (this.visibleLayers.length > 1) {
        h.shaderPrograms.overlays.activate();
        sh = h.shaderPrograms.overlays._program;
        drawCallback = og.planetSegment.drawOverlays;
        var layers = this.visibleLayers;
        var i = layers.length;
        while (i--) {
            var ll = layers[i];
            var nt4 = i * 4;
            this.tcolorArr[nt4] = ll.transparentColor[0];
            this.tcolorArr[nt4 + 1] = ll.transparentColor[1];
            this.tcolorArr[nt4 + 2] = ll.transparentColor[2];
            this.tcolorArr[nt4 + 3] = ll.opacity;
        }
        h.gl.uniform1i(sh.uniforms.numTex._pName, layers.length);
        h.gl.uniform4fv(sh.uniforms.tcolorArr._pName, this.tcolorArr);
    } else {
        h.shaderPrograms.single.activate();
        sh = h.shaderPrograms.single._program;
        drawCallback = og.planetSegment.drawSingle;
    }

    h.gl.uniformMatrix4fv(sh.uniforms.uPMVMatrix._pName, false, renderer.activeCamera.pmvMatrix._m);

    var i = this.renderedNodes.length;
    while (i--) {
        drawCallback(sh, this.renderedNodes[i].planetSegment);
    }
};

og.node.Planet.prototype.getCartesianFromPixelEllipsoid = function (px) {
    var direction = this.renderer.activeCamera.unproject(px.x, px.y);
    var mxTr = this.transformationMatrix.transpose();
    var sx = new og.math.Ray(mxTr.mulVec3(this.renderer.activeCamera.eye),
        mxTr.mulVec3(direction)).hitSphere(new og.bv.Sphere(this.ellipsoid._a));
    if (sx) {
        return this.itransformationMatrix.mulVec3(sx);
    }
    return null;
};

og.node.Planet.prototype.getLonLatFromPixelEllipsoid = function (px) {
    var coords = this.getCartesianFromPixelEllipsoid(px);
    return this.ellipsoid.ECEF2LonLat(coords.z, coords.x, coords.y);
};

og.node.Planet.prototype.getRayIntersectionEllipsoid = function (ray) {
    var mxTr = this.transformationMatrix.transpose();
    var sx = new og.math.Ray(mxTr.mulVec3(ray.origin),
        mxTr.mulVec3(ray.direction)).hitSphere(new og.bv.Sphere(this.ellipsoid._a));
    if (sx) {
        return this.itransformationMatrix.mulVec3(sx);
    }
    return null;
};

og.node.Planet.prototype.getCartesianFromPixelTerrain = function (px) {
    var distance = this.getDistanceFromPixel(px);
    var direction = this.renderer.activeCamera.unproject(px.x, px.y);
    return direction.scaleTo(distance).add(this.renderer.activeCamera.eye);
};

og.node.Planet.prototype.getLonLatFromPixelTerrain = function (px) {
    var coords = this.getCartesianFromPixelTerrain(px);
    return this.ellipsoid.ECEF2LonLat(coords.z, coords.x, coords.y);
};

og.node.Planet.prototype.getPixelFromCartesian = function (coords) {
    return this.renderer.activeCamera.project(coords);
};

og.node.Planet.prototype.getPixelFromLonLat = function (lonlat) {
    var coords = this.ellipsoid.LonLat2ECEF(lonlat);
    return this.renderer.activeCamera.project(coords);
};

og.node.Planet.prototype.getDistanceFromPixel = function (px) {
    if (this._viewChanged) {
        this._viewChanged = false;
        this.renderDistanceBackbufferPASS();
        var color = og.math.Vector4.fromVec(this.backbuffer.readPixels(px.x, this.backbuffer.height - px.y));
        return this._currentDistanceFromPixel = og.math.coder.decodeFloatFromRGBA(color);
    }
    return this._currentDistanceFromPixel;
};

og.node.Planet.prototype.renderDistanceBackbufferPASS = function () {
    var b = this.backbuffer,
        r = this.renderer;
    var h = r.handler;
    var pp = h.shaderPrograms.picking;
    b.activate();
    b.clear();
    pp.activate();
    h.gl.uniform3fv(pp._program.uniforms.camPos._pName, r.activeCamera.eye.toVec());
    var i = this.renderedNodes.length;
    while (i--) {
        this.renderedNodes[i].planetSegment.drawPicking();
    }
    b.deactivate();
};<|endoftext|>


<commit_before>/**
 * @module og/shaders/ray
 */

'use strict';

import { Program } from '../webgl/Program.js';

export function rayScreen() {
    return new Program("rayScreen", {
        uniforms: {
            projectionMatrix: "mat4",
            viewMatrix: "mat4",
            eyePositionHigh: "vec3",
            eyePositionLow: "vec3",
            resolution: "float",
            uOpacity: "float"
        },
        attributes: {
            a_vertices: "vec2",
            a_startPosHigh: "vec3",
            a_startPosLow: "vec3",
            a_endPosHigh: "vec3",
            a_endPosLow: "vec3",
            a_length: "float",
            a_thickness: "float",
            a_rgba: "vec4"
        },
        vertexShader:
            `precision highp float;
            attribute vec2 a_vertices;
            attribute vec3 a_startPosHigh;
            attribute vec3 a_startPosLow;
            attribute vec3 a_endPosHigh;
            attribute vec3 a_endPosLow;
            attribute float a_thickness;
            attribute float a_length;
            attribute vec4 a_rgba;

            varying vec4 v_rgba;

            uniform mat4 viewMatrix;
            uniform mat4 projectionMatrix;
            uniform vec3 eyePositionHigh;
            uniform vec3 eyePositionLow;
            uniform float resolution;

            void main() {

                v_rgba = a_rgba;

                vec3 camPos = eyePositionHigh + eyePositionLow;

                vec3 startPos = a_startPosHigh + a_startPosLow;
                vec3 direction = normalize((a_endPosHigh + a_endPosLow) - startPos);
                vec3 vertPos = startPos + a_vertices.y * direction * a_length;

                vec3 look = vertPos - camPos;
                vec3 up = normalize(direction);
                vec3 right = normalize(cross(look,up));

                float dist = dot(camPos - vertPos, vec3(viewMatrix[0][2], viewMatrix[1][2], viewMatrix[2][2]));
                float focalSize = 2.0 * dist * resolution;
                vec3 rr = right * a_thickness * focalSize * a_vertices.x + up * a_length * a_vertices.y;

                vec3 highDiff = a_startPosHigh - eyePositionHigh;
                vec3 lowDiff = a_startPosLow + rr - eyePositionLow;

                mat4 viewMatrixRTE = viewMatrix;
                viewMatrixRTE[3] = vec4(0.0, 0.0, 0.0, 1.0);

                vec4 pos = viewMatrixRTE * vec4(highDiff + lowDiff, 1.0);

                gl_Position = projectionMatrix * pos;
            }`,
        fragmentShader:
            `precision highp float;
            uniform float uOpacity;
            varying vec4 v_rgba;
            void main () {
                gl_FragColor = v_rgba * uOpacity;
            }`
    });
}
<commit_msg>Fixed ray global opacity<commit_after>/**
 * @module og/shaders/ray
 */

'use strict';

import { Program } from '../webgl/Program.js';

export function rayScreen() {
    return new Program("rayScreen", {
        uniforms: {
            projectionMatrix: "mat4",
            viewMatrix: "mat4",
            eyePositionHigh: "vec3",
            eyePositionLow: "vec3",
            resolution: "float",
            uOpacity: "float"
        },
        attributes: {
            a_vertices: "vec2",
            a_startPosHigh: "vec3",
            a_startPosLow: "vec3",
            a_endPosHigh: "vec3",
            a_endPosLow: "vec3",
            a_length: "float",
            a_thickness: "float",
            a_rgba: "vec4"
        },
        vertexShader:
            `precision highp float;
            attribute vec2 a_vertices;
            attribute vec3 a_startPosHigh;
            attribute vec3 a_startPosLow;
            attribute vec3 a_endPosHigh;
            attribute vec3 a_endPosLow;
            attribute float a_thickness;
            attribute float a_length;
            attribute vec4 a_rgba;

            varying vec4 v_rgba;

            uniform mat4 viewMatrix;
            uniform mat4 projectionMatrix;
            uniform vec3 eyePositionHigh;
            uniform vec3 eyePositionLow;
            uniform float resolution;
            uniform float uOpacity;

            void main() {

                v_rgba = vec4(a_rgba.rgb, a_rgba.a * uOpacity);

                vec3 camPos = eyePositionHigh + eyePositionLow;

                vec3 startPos = a_startPosHigh + a_startPosLow;
                vec3 direction = normalize((a_endPosHigh + a_endPosLow) - startPos);
                vec3 vertPos = startPos + a_vertices.y * direction * a_length;

                vec3 look = vertPos - camPos;
                vec3 up = normalize(direction);
                vec3 right = normalize(cross(look,up));

                float dist = dot(camPos - vertPos, vec3(viewMatrix[0][2], viewMatrix[1][2], viewMatrix[2][2]));
                float focalSize = 2.0 * dist * resolution;
                vec3 rr = right * a_thickness * focalSize * a_vertices.x + up * a_length * a_vertices.y;

                vec3 highDiff = a_startPosHigh - eyePositionHigh;
                vec3 lowDiff = a_startPosLow + rr - eyePositionLow;

                mat4 viewMatrixRTE = viewMatrix;
                viewMatrixRTE[3] = vec4(0.0, 0.0, 0.0, 1.0);

                vec4 pos = viewMatrixRTE * vec4(highDiff + lowDiff, 1.0);

                gl_Position = projectionMatrix * pos;
            }`,
        fragmentShader:
            `precision highp float;
            varying vec4 v_rgba;
            void main () {
                gl_FragColor = v_rgba;
            }`
    });
}
<|endoftext|>