Prechádzať zdrojové kódy

Particle Effects Graph

This change adds a graph control that allows the editing of particle effect graph fields. It also updates the logo to use the universal torque logo.
Peter Robinson 4 rokov pred
rodič
commit
107a16bb91

+ 1 - 0
editor/AssetAdmin/AssetAdmin.cs

@@ -33,6 +33,7 @@ function AssetAdmin::create(%this)
 	exec("./NewParticleAssetDialog.cs");
 	exec("./NewFontAssetDialog.cs");
 	exec("./NewAudioAssetDialog.cs");
+	exec("./ParticleEditor/exec.cs");
 
 	%this.guiPage = EditorCore.RegisterEditor("Asset Manager", %this);
 	%this.guiPage.add(%this.buildAssetWindow());

+ 27 - 6
editor/AssetAdmin/AssetDictionaryButton.cs

@@ -130,6 +130,7 @@ function AssetDictionaryButton::buildIcon(%this)
 
 function AssetDictionaryButton::onClick(%this)
 {
+	%firstLoad = false;
 	if(AssetAdmin.chosenButton != %this)
 	{
 		if(isObject(AssetAdmin.chosenButton))
@@ -137,6 +138,8 @@ function AssetDictionaryButton::onClick(%this)
 			ThemeManager.setProfile(AssetAdmin.chosenButton, "itemSelectProfile");
 		}
 		ThemeManager.setProfile(%this, "itemSelectedProfile");
+		%firstLoad = true;
+		AssetAdmin.AssetWindow.resetCamera();
 	}
 
 	AssetAdmin.audioPlayButtonContainer.setVisible(false);
@@ -145,32 +148,50 @@ function AssetDictionaryButton::onClick(%this)
 	if(isObject(%this.AnimationAsset) && %this.AnimationAssetID !$= "")
 	{
 		AssetAdmin.AssetWindow.displayAnimationAsset(%this.imageAsset, %this.AnimationAsset, %this.AnimationAssetID);
-		AssetAdmin.inspector.loadAnimationAsset(%this.AnimationAsset, %this.AnimationAssetID);
+		if(%firstLoad)
+		{
+			AssetAdmin.inspector.loadAnimationAsset(%this.AnimationAsset, %this.AnimationAssetID);
+		}
 	}
 	else if(isObject(%this.ImageAsset) && %this.ImageAssetID !$= "")
 	{
 		AssetAdmin.AssetWindow.displayImageAsset(%this.ImageAsset, %this.ImageAssetID);
-		AssetAdmin.inspector.loadImageAsset(%this.ImageAsset, %this.ImageAssetID);
+		if(%firstLoad)
+		{
+			AssetAdmin.inspector.loadImageAsset(%this.ImageAsset, %this.ImageAssetID);
+		}
 	}
 	else if(isObject(%this.ParticleAsset) && %this.ParticleAssetID !$= "")
 	{
 		AssetAdmin.AssetWindow.displayParticleAsset(%this.ParticleAsset, %this.ParticleAssetID);
-		AssetAdmin.inspector.loadParticleAsset(%this.ParticleAsset, %this.ParticleAssetID);
+		if(%firstLoad)
+		{
+			AssetAdmin.inspector.loadParticleAsset(%this.ParticleAsset, %this.ParticleAssetID);
+		}
 	}
 	else if(isObject(%this.FontAsset) && %this.FontAssetID !$= "")
 	{
 		AssetAdmin.AssetWindow.displayFontAsset(%this.FontAsset, %this.FontAssetID);
-		AssetAdmin.inspector.loadFontAsset(%this.FontAsset, %this.FontAssetID);
+		if(%firstLoad)
+		{
+			AssetAdmin.inspector.loadFontAsset(%this.FontAsset, %this.FontAssetID);
+		}
 	}
 	else if(isObject(%this.AudioAsset) && %this.AudioAssetID !$= "")
 	{
 		AssetAdmin.AssetWindow.displayAudioAsset(%this.AudioAsset, %this.AudioAssetID);
-		AssetAdmin.inspector.loadAudioAsset(%this.AudioAsset, %this.AudioAssetID);
+		if(%firstLoad)
+		{
+			AssetAdmin.inspector.loadAudioAsset(%this.AudioAsset, %this.AudioAssetID);
+		}
 	}
 	else if(isObject(%this.SpineAsset) && %this.SpineAssetID !$= "")
 	{
 		AssetAdmin.AssetWindow.displaySpineAsset(%this.SpineAsset, %this.SpineAssetID);
-		AssetAdmin.inspector.loadSpineAsset(%this.SpineAsset, %this.SpineAssetID);
+		if(%firstLoad)
+		{
+			AssetAdmin.inspector.loadSpineAsset(%this.SpineAsset, %this.SpineAssetID);
+		}
 	}
 
 	AssetAdmin.chosenButton = %this;

+ 180 - 39
editor/AssetAdmin/AssetInspector.cs

@@ -34,60 +34,149 @@ function AssetInspector::onAdd(%this)
 	ThemeManager.setProfile(%this.titlebar, "panelProfile");
 	%this.add(%this.titlebar);
 
-	%this.insScroller = new GuiScrollCtrl()
+	%this.titleDropDown = new GuiDropDownCtrl()
+	{
+		Position = "5 3";
+		Extent = 320 SPC 26;
+		ConstantThumbHeight = false;
+		ScrollBarThickness = 12;
+		ShowArrowButtons = true;
+		Visible = false;
+	};
+	ThemeManager.setProfile(%this.titleDropDown, "dropDownProfile");
+	ThemeManager.setProfile(%this.titleDropDown, "dropDownItemProfile", "listBoxProfile");
+	ThemeManager.setProfile(%this.titleDropDown, "emptyProfile", "backgroundProfile");
+	ThemeManager.setProfile(%this.titleDropDown, "scrollingPanelProfile", "ScrollProfile");
+	ThemeManager.setProfile(%this.titleDropDown, "scrollingPanelThumbProfile", "ThumbProfile");
+	ThemeManager.setProfile(%this.titleDropDown, "scrollingPanelTrackProfile", "TrackProfile");
+	ThemeManager.setProfile(%this.titleDropDown, "scrollingPanelArrowProfile", "ArrowProfile");
+	%this.titlebar.add(%this.titleDropDown);
+
+	%this.tabBook = new GuiTabBookCtrl()
+	{
+		Class = AssetInspectorTabBook;
+		HorizSizing = width;
+		VertSizing = height;
+		Position = "0 34";
+		Extent = "700 290";
+		MinExtent="350 290";
+		TabPosition = top;
+		Visible = false;
+	};
+	ThemeManager.setProfile(%this.tabBook, "smallTabBookProfile");
+	ThemeManager.setProfile(%this.tabBook, "smallTabProfile", "TabProfile");
+	%this.add(%this.tabBook);
+
+	//Inspector Tab
+	%this.insPage = %this.createTabPage("Inspector", "");
+	%this.tabBook.add(%this.insPage);
+
+	%this.insScroller = %this.createScroller();
+	%this.insPage.add(%this.insScroller);
+
+	%this.inspector = %this.createInspector();
+	%this.insScroller.add(%this.inspector);
+
+	//Particle Graph Tool
+	%this.scaleGraphPage = %this.createTabPage("Scale Graph", "AssetParticleGraphTool", "");
+
+	//Emitter Graph Tool
+	%this.emitterGraphPage = %this.createTabPage("Emitter Graph", "AssetParticleGraphEmitterTool", "AssetParticleGraphTool");
+}
+
+function AssetInspector::createTabPage(%this, %name, %class, %superClass)
+{
+	%page = new GuiTabPageCtrl()
+	{
+		Class = %class;
+		SuperClass = %superClass;
+		HorizSizing = width;
+		VertSizing = height;
+		Position = "0 0";
+		Extent = "700 290";
+		Text = %name;
+	};
+	ThemeManager.setProfile(%page, "tabPageProfile");
+
+	return %page;
+}
+
+function AssetInspector::createScroller(%this)
+{
+	%scroller = new GuiScrollCtrl()
 	{
 		HorizSizing="width";
-		VertSizing="top";
-		Position="0 34";
+		VertSizing="height";
+		Position="0 0";
 		Extent="700 290";
-		MinExtent="350 145";
 		hScrollBar="alwaysOff";
 		vScrollBar="alwaysOn";
 		constantThumbHeight="0";
 		showArrowButtons="1";
 		scrollBarThickness="14";
 	};
-	ThemeManager.setProfile(%this.insScroller, "scrollingPanelProfile");
-	ThemeManager.setProfile(%this.insScroller, "scrollingPanelThumbProfile", "ThumbProfile");
-	ThemeManager.setProfile(%this.insScroller, "scrollingPanelTrackProfile", "TrackProfile");
-	ThemeManager.setProfile(%this.insScroller, "scrollingPanelArrowProfile", "ArrowProfile");
-	%this.add(%this.insScroller);
+	ThemeManager.setProfile(%scroller, "scrollingPanelProfile");
+	ThemeManager.setProfile(%scroller, "scrollingPanelThumbProfile", "ThumbProfile");
+	ThemeManager.setProfile(%scroller, "scrollingPanelTrackProfile", "TrackProfile");
+	ThemeManager.setProfile(%scroller, "scrollingPanelArrowProfile", "ArrowProfile");
 
-	%this.inspector = new GuiInspector()
+	return %scroller;
+}
+
+function AssetInspector::createInspector(%this)
+{
+	%inspector = new GuiInspector()
 	{
 		HorizSizing="width";
 		VertSizing="height";
 		Position="0 0";
 		Extent="686 290";
-		MinExtent="343 145";
 		FieldCellSize="300 40";
 		ControlOffset="10 18";
 		ConstantThumbHeight=false;
 		ScrollBarThickness=12;
 		ShowArrowButtons=true;
 	};
-	ThemeManager.setProfile(%this.inspector, "emptyProfile");
-	ThemeManager.setProfile(%this.inspector, "panelProfile", "GroupPanelProfile");
-	ThemeManager.setProfile(%this.inspector, "emptyProfile", "GroupGridProfile");
-	ThemeManager.setProfile(%this.inspector, "labelProfile", "LabelProfile");
-	ThemeManager.setProfile(%this.inspector, "textEditProfile", "textEditProfile");
-	ThemeManager.setProfile(%this.inspector, "dropDownProfile", "dropDownProfile");
-	ThemeManager.setProfile(%this.inspector, "dropDownItemProfile", "dropDownItemProfile");
-	ThemeManager.setProfile(%this.inspector, "emptyProfile", "backgroundProfile");
-	ThemeManager.setProfile(%this.inspector, "scrollingPanelProfile", "ScrollProfile");
-	ThemeManager.setProfile(%this.inspector, "scrollingPanelThumbProfile", "ThumbProfile");
-	ThemeManager.setProfile(%this.inspector, "scrollingPanelTrackProfile", "TrackProfile");
-	ThemeManager.setProfile(%this.inspector, "scrollingPanelArrowProfile", "ArrowProfile");
-	ThemeManager.setProfile(%this.inspector, "checkboxProfile", "checkboxProfile");
-	ThemeManager.setProfile(%this.inspector, "buttonProfile", "buttonProfile");
-	ThemeManager.setProfile(%this.inspector, "tipProfile", "tooltipProfile");
-	%this.insScroller.add(%this.inspector);
-	$ins = %this.inspector;
+	ThemeManager.setProfile(%inspector, "emptyProfile");
+	ThemeManager.setProfile(%inspector, "panelProfile", "GroupPanelProfile");
+	ThemeManager.setProfile(%inspector, "emptyProfile", "GroupGridProfile");
+	ThemeManager.setProfile(%inspector, "labelProfile", "LabelProfile");
+	ThemeManager.setProfile(%inspector, "textEditProfile", "textEditProfile");
+	ThemeManager.setProfile(%inspector, "dropDownProfile", "dropDownProfile");
+	ThemeManager.setProfile(%inspector, "dropDownItemProfile", "dropDownItemProfile");
+	ThemeManager.setProfile(%inspector, "emptyProfile", "backgroundProfile");
+	ThemeManager.setProfile(%inspector, "scrollingPanelProfile", "ScrollProfile");
+	ThemeManager.setProfile(%inspector, "scrollingPanelThumbProfile", "ThumbProfile");
+	ThemeManager.setProfile(%inspector, "scrollingPanelTrackProfile", "TrackProfile");
+	ThemeManager.setProfile(%inspector, "scrollingPanelArrowProfile", "ArrowProfile");
+	ThemeManager.setProfile(%inspector, "checkboxProfile", "checkboxProfile");
+	ThemeManager.setProfile(%inspector, "buttonProfile", "buttonProfile");
+	ThemeManager.setProfile(%inspector, "tipProfile", "tooltipProfile");
+
+	return %inspector;
+}
+
+function AssetInspector::resetInspector(%this)
+{
+	%this.titlebar.setText("");
+	%this.titleDropDown.visible = false;
+	%this.tabBook.Visible = true;
+	%this.tabBook.selectPage(0);
+	if(%this.tabBook.isMember(%this.scaleGraphPage))
+	{
+		%this.tabBook.remove(%this.scaleGraphPage);
+	}
+	if(%this.tabBook.isMember(%this.emitterGraphPage))
+	{
+		%this.tabBook.remove(%this.emitterGraphPage);
+	}
 }
 
 function AssetInspector::loadImageAsset(%this, %imageAsset, %assetID)
 {
-	%this.titlebar.setText("Image Asset");
+	%this.resetInspector();
+	%this.titlebar.setText("Image Asset:" SPC %imageAsset.AssetName);
+
 	%this.inspector.clearHiddenFields();
 	%this.inspector.addHiddenField("hidden");
 	%this.inspector.addHiddenField("locked");
@@ -100,7 +189,9 @@ function AssetInspector::loadImageAsset(%this, %imageAsset, %assetID)
 
 function AssetInspector::loadAnimationAsset(%this, %animationAsset, %assetID)
 {
-	%this.titlebar.setText("Animation Asset");
+	%this.resetInspector();
+	%this.titlebar.setText("Animation Asset:" SPC %animationAsset.AssetName);
+
 	%this.inspector.clearHiddenFields();
 	%this.inspector.addHiddenField("hidden");
 	%this.inspector.addHiddenField("locked");
@@ -112,19 +203,65 @@ function AssetInspector::loadAnimationAsset(%this, %animationAsset, %assetID)
 
 function AssetInspector::loadParticleAsset(%this, %particleAsset, %assetID)
 {
-	%this.titlebar.setText("Particle Asset");
+	%this.resetInspector();
+	%this.titleDropDown.visible = true;
+
+	%this.titleDropDown.clearItems();
+	%this.titleDropDown.addItem("Particle Asset:" SPC %particleAsset.AssetName);
+	for(%i = 0; %i < %particleAsset.getEmitterCount(); %i++)
+	{
+		%emitter = %particleAsset.getEmitter(%i);
+		%this.titleDropDown.addItem("Emitter:" SPC %emitter.EmitterName);
+		%this.titleDropDown.setItemColor(%i + 1, ThemeManager.activeTheme.color5);
+		%this.titleDropDown.Command = %this.getId() @ ".onChooseParticleAsset(" @ %particleAsset.getId() @ ");";
+	}
+	%this.titleDropDown.setCurSel(0);
+
+	%this.onChooseParticleAsset(%particleAsset);
+}
+
+function AssetInspector::onChooseParticleAsset(%this, %particleAsset)
+{
+	%index = %this.titleDropDown.getSelectedItem();
 	%this.inspector.clearHiddenFields();
-	%this.inspector.addHiddenField("hidden");
-	%this.inspector.addHiddenField("locked");
-	%this.inspector.addHiddenField("AssetInternal");
-	%this.inspector.addHiddenField("AssetPrivate");
-	%this.inspector.inspect(%particleAsset);
+	%curSel = %this.tabBook.getSelectedPage();
+	if(%index == 0)
+	{
+		%this.inspector.addHiddenField("hidden");
+		%this.inspector.addHiddenField("locked");
+		%this.inspector.addHiddenField("AssetInternal");
+		%this.inspector.addHiddenField("AssetPrivate");
+		%this.inspector.inspect(%particleAsset);
+
+		if(%this.tabBook.isMember(%this.emitterGraphPage))
+		{
+			%this.tabBook.remove(%this.emitterGraphPage);
+		}
+		%this.tabBook.add(%this.scaleGraphPage);
+		%this.scaleGraphPage.inspect(%particleAsset);
+	}
+	else if(%index > 0)
+	{
+		%this.inspector.addHiddenField("hidden");
+		%this.inspector.addHiddenField("locked");
+		%this.inspector.inspect(%particleAsset.getEmitter(%index - 1));
+
+		if(%this.tabBook.isMember(%this.scaleGraphPage))
+		{
+			%this.tabBook.remove(%this.scaleGraphPage);
+		}
+		%this.tabBook.add(%this.emitterGraphPage);
+		%this.emitterGraphPage.inspect(%particleAsset, %index - 1);
+	}
+	%this.tabBook.selectPage(%curSel);
 	%this.inspector.openGroupByIndex(0);
 }
 
 function AssetInspector::loadFontAsset(%this, %fontAsset, %assetID)
 {
-	%this.titlebar.setText("Font Asset");
+	%this.resetInspector();
+	%this.titlebar.setText("Font Asset:" SPC %fontAsset.AssetName);
+
 	%this.inspector.clearHiddenFields();
 	%this.inspector.addHiddenField("hidden");
 	%this.inspector.addHiddenField("locked");
@@ -136,7 +273,9 @@ function AssetInspector::loadFontAsset(%this, %fontAsset, %assetID)
 
 function AssetInspector::loadAudioAsset(%this, %audioAsset, %assetID)
 {
-	%this.titlebar.setText("Audio Asset");
+	%this.resetInspector();
+	%this.titlebar.setText("Audio Asset:" SPC %audioAsset.AssetName);
+
 	%this.inspector.clearHiddenFields();
 	%this.inspector.addHiddenField("hidden");
 	%this.inspector.addHiddenField("locked");
@@ -148,7 +287,9 @@ function AssetInspector::loadAudioAsset(%this, %audioAsset, %assetID)
 
 function AssetInspector::loadSpineAsset(%this, %spineAsset, %assetID)
 {
-	%this.titlebar.setText("Spine Asset");
+	%this.resetInspector();
+	%this.titlebar.setText("Spine Asset:" SPC %spineAsset.AssetName);
+
 	%this.inspector.clearHiddenFields();
 	%this.inspector.addHiddenField("hidden");
 	%this.inspector.addHiddenField("locked");

+ 6 - 8
editor/AssetAdmin/AssetWindow.cs

@@ -25,12 +25,16 @@ function AssetWindow::onAdd(%this)
 
 }
 
+function AssetWindow::resetCamera(%this)
+{
+	%this.setCameraPosition("0 0");
+	%this.setCameraZoom(1);
+}
+
 function AssetWindow::displayImageAsset(%this, %imageAsset, %assetID)
 {
 	AssetAdmin.AssetScene.clear(true);
 
-	%this.setCameraPosition("0 0");
-	%this.setCameraZoom(1);
 	%size = %this.getWorldSize(%imageAsset.getImageSize());
 	%sizeX = getWord(%size, 0);
 	%sizeY = getWord(%size, 1);
@@ -113,8 +117,6 @@ function AssetWindow::displayAnimationAsset(%this, %imageAsset, %animationAsset,
 {
 	AssetAdmin.AssetScene.clear(true);
 
-	%this.setCameraPosition("0 0");
-	%this.setCameraZoom(1);
 	%size = %this.getWorldSize(%imageAsset.getFrameSize(0));
 	new Sprite()
 	{
@@ -132,8 +134,6 @@ function AssetWindow::displayParticleAsset(%this, %particleAsset, %assetID)
 {
 	AssetAdmin.AssetScene.clear(true);
 
-	%this.setCameraPosition("0 0");
-	%this.setCameraZoom(5);
 	new ParticlePlayer()
 	{
 		Scene = AssetAdmin.AssetScene;
@@ -150,8 +150,6 @@ function AssetWindow::displayFontAsset(%this, %fontAsset, %assetID)
 {
 	AssetAdmin.AssetScene.clear(true);
 
-	%this.setCameraPosition("0 0");
-	%this.setCameraZoom(1);
 	%size = %this.getWorldSize("10 10");
 	new TextSprite()
 	{

+ 296 - 0
editor/AssetAdmin/ParticleEditor/AssetParticleGraphTool.cs

@@ -0,0 +1,296 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+function AssetParticleGraphTool::onAdd(%this)
+{
+	%this.init();
+	%this.addItem("Lifetime Scale");
+	%this.addItem("Quantity Scale");
+	%this.addItem("SizeX Scale");
+	%this.addItem("SizeY Scale");
+	%this.addItem("Speed Scale");
+	%this.addItem("Spin Scale");
+	%this.addItem("Fixed Force Scale");
+	%this.addItem("Random Motion Scale");
+	%this.addItem("Alpha Channel Scale");
+}
+
+function AssetParticleGraphEmitterTool::onAdd(%this)
+{
+	%this.init();
+	%this.initEmitter();
+	%this.addItem("Lifetime");
+	%this.addItem("Quantity");
+	%this.addItem("SizeX");
+	%this.addItem("SizeY");
+	%this.addItem("Speed");
+	%this.addItem("Spin");
+	%this.addItem("Fixed Force");
+	%this.addItem("Random Motion");
+	%this.addItem("Emission Force");
+	%this.addItem("Emission Angle");
+	%this.addItem("Emission Arc");
+	%this.addItem("Red Channel");
+	%this.addItem("Green Channel");
+	%this.addItem("Blue Channel");
+	%this.addItem("Alpha Channel");
+}
+
+function AssetParticleGraphTool::init(%this)
+{
+	%this.listScroll = new GuiScrollCtrl()
+	{
+		HorizSizing="right";
+		VertSizing="height";
+		Position="0 0";
+		Extent="200" SPC getWord(%this.extent, 1);
+		hScrollBar="alwaysOff";
+		vScrollBar="alwaysOn";
+		constantThumbHeight="0";
+		showArrowButtons="1";
+		scrollBarThickness="14";
+	};
+	ThemeManager.setProfile(%this.listScroll, "scrollingPanelProfile");
+	ThemeManager.setProfile(%this.listScroll, "scrollingPanelThumbProfile", "ThumbProfile");
+	ThemeManager.setProfile(%this.listScroll, "scrollingPanelTrackProfile", "TrackProfile");
+	ThemeManager.setProfile(%this.listScroll, "scrollingPanelArrowProfile", "ArrowProfile");
+	%this.add(%this.listScroll);
+
+	%this.baseList = new GuiListBoxCtrl()
+	{
+		Class = "AssetParticleGraphToolList";
+		HorizSizing="width";
+		VertSizing="height";
+		Position="0 0";
+		Extent= "200 100";
+		hScrollBar="dynamic";
+		vScrollBar="dynamic";
+	};
+	%this.startListening(%this.baseList);
+	ThemeManager.setProfile(%this.baseList, "listBoxProfile");
+	%this.listScroll.add(%this.baseList);
+
+	%this.toolScroll = new GuiScrollCtrl()
+	{
+		HorizSizing="width";
+		VertSizing="height";
+		Position="200 0";
+		Extent= (getWord(%this.extent, 0) - 200) SPC getWord(%this.extent, 1);
+		hScrollBar="dynamic";
+		vScrollBar="dynamic";
+		constantThumbHeight="0";
+		showArrowButtons="1";
+		scrollBarThickness="14";
+	};
+	ThemeManager.setProfile(%this.toolScroll, "scrollingPanelProfile");
+	ThemeManager.setProfile(%this.toolScroll, "scrollingPanelThumbProfile", "ThumbProfile");
+	ThemeManager.setProfile(%this.toolScroll, "scrollingPanelTrackProfile", "TrackProfile");
+	ThemeManager.setProfile(%this.toolScroll, "scrollingPanelArrowProfile", "ArrowProfile");
+	%this.add(%this.toolScroll);
+
+	%itemWidth = 360;
+	%this.toolGrid = new GuiGridCtrl()
+	{
+		HorizSizing="width";
+		VertSizing="height";
+		Position="0 0";
+		Extent = (getWord(%this.toolScroll.Extent, 0) - 14) SPC getWord(%this.extent, 1);
+		CellSizeX = %itemWidth;
+		CellSizeY = 0;
+		CellModeX = variable;
+		CellModeY = variable;
+		CellSpacingX = 4;
+		CellSpacingY = 4;
+		OrderMode = "LRTB";
+	};
+	ThemeManager.setProfile(%this.toolGrid, "emptyProfile");
+	%this.toolScroll.add(%this.toolGrid);
+
+	%this.baseGraph = new GuiControl()
+	{
+		Class = "AssetParticleGraphUnit";
+		HorizSizing="right";
+		VertSizing="bottom";
+		Position="0 0";
+		Extent= %itemWidth SPC (getWord(%this.extent, 1) - 30);
+		Text = "Base Value";
+	};
+	ThemeManager.setProfile(%this.baseGraph, "labelProfile");
+	%this.toolGrid.add(%this.baseGraph);
+}
+
+function AssetParticleGraphEmitterTool::initEmitter(%this)
+{
+	%this.variGraph = new GuiControl()
+	{
+		Class = "AssetParticleGraphUnit";
+		HorizSizing="right";
+		VertSizing="bottom";
+		Position="0 0";
+		Extent= %itemWidth SPC (getWord(%this.extent, 1) - 30);
+		Text = "Variation";
+		Tool = %this.toolGrid;
+	};
+	ThemeManager.setProfile(%this.variGraph, "labelProfile");
+	%this.toolGrid.add(%this.variGraph);
+
+	%this.lifeGraph = new GuiControl()
+	{
+		Class = "AssetParticleGraphUnit";
+		HorizSizing="right";
+		VertSizing="bottom";
+		Position="0 0";
+		Extent= %itemWidth SPC (getWord(%this.extent, 1) - 30);
+		Text = "Scale Over Particle Lifetime";
+		Tool = %this.toolGrid;
+	};
+	ThemeManager.setProfile(%this.lifeGraph, "labelProfile");
+	%this.toolGrid.add(%this.lifeGraph);
+}
+
+function AssetParticleGraphTool::addItem(%this, %item, %color)
+{
+	if(%color !$= "")
+	{
+		%this.baseList.addItem(%item, %color);
+	}
+	else
+	{
+		%this.baseList.addItem(%item);
+	}
+}
+
+function AssetParticleGraphTool::inspect(%this, %asset, %emitterID)
+{
+	%this.asset = %asset;
+	%this.baseGraph.graph.inspect(%asset);
+	if(isObject(%this.variGraph))
+	{
+		%this.variGraph.graph.inspect(%asset);
+	}
+	if(isObject(%this.lifeGraph))
+	{
+		%this.lifeGraph.graph.inspect(%asset);
+	}
+	%this.baseList.clearSelection();
+	%this.baseList.setCurSel(0);
+	%this.emitterID = %emitterID;
+}
+
+function AssetParticleGraphToolList::onSelect(%this, %index, %text, %id)
+{
+	%this.postEvent("Select", %index);
+}
+
+function AssetParticleGraphTool::onSelect(%this, %index)
+{
+	%i = 0;
+	%graphTable[%i] = "LifeTimeScale"; %i++;
+	%graphTable[%i] = "QuantityScale"; %i++;
+	%graphTable[%i] = "SizeXScale"; %i++;
+	%graphTable[%i] = "SizeYScale"; %i++;
+	%graphTable[%i] = "SpeedScale"; %i++;
+	%graphTable[%i] = "SpinScale"; %i++;
+	%graphTable[%i] = "FixedForceScale"; %i++;
+	%graphTable[%i] = "RandomMotionScale"; %i++;
+	%graphTable[%i] = "AlphaChannelScale";
+
+	%name = %graphTable[%index];
+	%this.baseGraph.setToScale(%name);
+	%this.baseGraph.setValueController(%this.getValueController(%name));
+	%this.baseGraph.setTimeController(%this.getTimeController(%name));
+}
+
+function AssetParticleGraphTool::getValueController(%this, %name)
+{
+	if(%name $= "")
+	{
+		return "";
+	}
+	return new ScriptObject()
+	{
+		class = ParticleGraphCameraController;
+		fieldName = %name;
+		isTime = false;
+		asset = %this.asset;
+	};
+}
+
+function AssetParticleGraphTool::getTimeController(%this, %name)
+{
+	if(%name $= "")
+	{
+		return "";
+	}
+	return new ScriptObject()
+	{
+		class = ParticleGraphCameraController;
+		fieldName = %name;
+		isTime = true;
+		asset = %this.asset;
+	};
+}
+
+function AssetParticleGraphEmitterTool::onSelect(%this, %index)
+{
+	%i = 0;
+	%graphTable[%i] = "Lifetime"; %i++;
+	%graphTable[%i] = "Quantity"; %i++;
+	%graphTable[%i] = "SizeX"; %i++;
+	%graphTable[%i] = "SizeY"; %i++;
+	%graphTable[%i] = "Speed"; %i++;
+	%graphTable[%i] = "Spin"; %i++;
+	%graphTable[%i] = "FixedForce"; %i++;
+	%graphTable[%i] = "RandomMotion"; %i++;
+	%graphTable[%i] = "EmissionForce"; %i++;
+	%graphTable[%i] = "EmissionAngle"; %i++;
+	%graphTable[%i] = "EmissionArc"; %i++;
+	%graphTable[%i] = "RedChannel"; %i++;
+	%graphTable[%i] = "GreenChannel"; %i++;
+	%graphTable[%i] = "BlueChannel"; %i++;
+	%graphTable[%i] = "AlphaChannel";
+
+	for(%i = 0; %i < 11; %i++)
+	{
+		%varTable[%i] = %graphTable[%i] @ "Variation";
+	}
+
+	for(%i = 2; %i < 8; %i++)
+	{
+		%lifeTable[%i] = %graphTable[%i] @ "Life";
+	}
+
+	%name = %graphTable[%index];
+	%this.baseGraph.setToBase(%name, %varTable[%index], %this.emitterID);
+	%this.baseGraph.setValueController(%this.getValueController(%name));
+	%this.baseGraph.setTimeController(%this.getTimeController(%name));
+
+	%name = %varTable[%index];
+	%this.variGraph.setToVari( %name, %this.emitterID);
+	%this.variGraph.setValueController(%this.getValueController(%name));
+	%this.variGraph.setTimeController(%this.getTimeController(%name));
+
+	%name = %lifeTable[%index];
+	%this.lifeGraph.setToLife( %name, %this.emitterID);
+	%this.lifeGraph.setValueController(%this.getValueController(%name));
+	%this.lifeGraph.setTimeController(%this.getTimeController(%name));
+}

+ 259 - 0
editor/AssetAdmin/ParticleEditor/AssetParticleGraphUnit.cs

@@ -0,0 +1,259 @@
+
+function AssetParticleGraphUnit::onAdd(%this)
+{
+	%this.graph = new GuiParticleGraphInspector()
+	{
+		HorizSizing="width";
+		VertSizing="height";
+		Position="30 18";
+		Extent= (getWord(%this.extent, 0) - 40) SPC (getWord(%this.extent, 1) - 60);
+	};
+	ThemeManager.setProfile(%this.graph, "graphProfile");
+	%this.add(%this.graph);
+
+	//Value zoom buttons
+	%center = 6 + mRound(getWord(%this.graph.extent, 1) / 2);
+	%this.valueZoomInButton = new GuiButtonCtrl()
+	{
+		Position = "2" SPC (%center + 13);
+		Extent = "24 24";
+		Text = "+";
+		Command = %this.getId() @ ".valueZoomIn();";
+	};
+	ThemeManager.setProfile(%this.valueZoomInButton, "buttonProfile");
+	%this.add(%this.valueZoomInButton);
+
+	%this.valueZoomOutButton = new GuiButtonCtrl()
+	{
+		Position = "2" SPC (%center - 13);
+		Extent = "24 24";
+		Text = "-";
+		Command = %this.getId() @ ".valueZoomOut();";
+	};
+	ThemeManager.setProfile(%this.valueZoomOutButton, "buttonProfile");
+	%this.add(%this.valueZoomOutButton);
+
+	//Value move buttons
+	%this.valueMoveUpButton = new GuiButtonCtrl()
+	{
+		Position = "2 18";
+		Extent = "24 24";
+		Text = "^";
+		Command = %this.getId() @ ".valueMoveUp();";
+	};
+	ThemeManager.setProfile(%this.valueMoveUpButton, "buttonProfile");
+	%this.add(%this.valueMoveUpButton);
+
+	%this.valueMoveDownButton = new GuiButtonCtrl()
+	{
+		Position = "2" SPC (getWord(%this.extent, 1) - 66);
+		Extent = "24 24";
+		Text = "v";
+		Command = %this.getId() @ ".valueMoveDown();";
+	};
+	ThemeManager.setProfile(%this.valueMoveDownButton, "buttonProfile");
+	%this.add(%this.valueMoveDownButton);
+
+	//time zoom buttons
+	%center = 18 + mRound(getWord(%this.graph.extent, 0));
+	%bottom = getWord(%this.extent, 1) - 38;
+	%this.timeZoomContainer = new GuiControl()
+	{
+		HorizSizing = "Center";
+		Position = "0" SPC %bottom;
+		Extent = "50 24";
+	};
+	ThemeManager.setProfile(%this.timeZoomContainer, "emptyProfile");
+	%this.add(%this.timeZoomContainer);
+
+	%this.timeZoomInButton = new GuiButtonCtrl()
+	{
+		Position = "0 0";
+		Extent = "24 24";
+		Text = "+";
+		Command = %this.getId() @ ".timeZoomIn();";
+	};
+	ThemeManager.setProfile(%this.timeZoomInButton, "buttonProfile");
+	%this.timeZoomContainer.add(%this.timeZoomInButton);
+
+	%this.timeZoomOutButton = new GuiButtonCtrl()
+	{
+		Position = "26 0";
+		Extent = "24 24";
+		Text = "-";
+		Command = %this.getId() @ ".timeZoomOut();";
+	};
+	ThemeManager.setProfile(%this.timeZoomOutButton, "buttonProfile");
+	%this.timeZoomContainer.add(%this.timeZoomOutButton);
+
+	//Time move buttons
+	%this.timeMoveBackButton = new GuiButtonCtrl()
+	{
+		HorizSizing = "right";
+		Position = "30" SPC %bottom;
+		Extent = "24 24";
+		Text = "<";
+		Command = %this.getId() @ ".timeMoveBack();";
+	};
+	ThemeManager.setProfile(%this.timeMoveBackButton, "buttonProfile");
+	%this.add(%this.timeMoveBackButton);
+
+	%this.timeMoveForwardButton = new GuiButtonCtrl()
+	{
+		HorizSizing = "left";
+		Position = (getWord(%this.graph.extent, 0) + 6) SPC %bottom;
+		Extent = "24 24";
+		Text = ">";
+		Command = %this.getId() @ ".timeMoveForward();";
+	};
+	ThemeManager.setProfile(%this.timeMoveForwardButton, "buttonProfile");
+	%this.add(%this.timeMoveForwardButton);
+}
+
+function AssetParticleGraphUnit::setToScale(%this, %scaleName)
+{
+	%this.graph.setDisplayLabels("Time", "Scale");
+	%this.graph.setDisplayField(%scaleName);
+}
+
+function AssetParticleGraphUnit::setToBase(%this, %baseName, %variName, %emitterID)
+{
+	%this.graph.setDisplayLabels("Time", "Base Value");
+	%this.graph.setDisplayField(%baseName, %emitterID);
+}
+
+function AssetParticleGraphUnit::setToVari(%this, %variName, %emitterID)
+{
+	if(%variName $= "")
+	{
+		if(%this.Tool.isMember(%this))
+		{
+			%this.Tool.remove(%this);
+		}
+		return;
+	}
+
+	if(!%this.Tool.isMember(%this))
+	{
+		%this.Tool.add(%this);
+	}
+	%this.graph.setDisplayLabels("Time", "Variation");
+	%this.graph.setDisplayField(%variName, %emitterID);
+}
+
+function AssetParticleGraphUnit::setToLife(%this, %lifeName, %emitterID)
+{
+	if(%lifeName $= "")
+	{
+		if(%this.Tool.isMember(%this))
+		{
+			%this.Tool.remove(%this);
+		}
+		return;
+	}
+
+	if(!%this.Tool.isMember(%this))
+	{
+		%this.Tool.add(%this);
+	}
+	%this.graph.setDisplayLabels("Time", "Scale");
+	%this.graph.setDisplayField(%lifeName, %emitterID);
+}
+
+function AssetParticleGraphUnit::setValueController(%this, %controller)
+{
+	if(!isObject(%controller))
+	{
+		return;
+	}
+	if(isObject(%this.valueController))
+	{
+		%this.valueController.delete();
+	}
+
+	%this.valueController = %controller;
+	%this.refreshCamera();
+}
+
+function AssetParticleGraphUnit::setTimeController(%this, %controller)
+{
+	if(!isObject(%controller))
+	{
+		return;
+	}
+	if(isObject(%this.timeController))
+	{
+		%this.timeController.delete();
+	}
+
+	%this.timeController = %controller;
+	%this.refreshCamera();
+}
+
+function AssetParticleGraphUnit::refreshCamera(%this)
+{
+	%xMin = %this.timeController.getCameraMin();
+	%xMax = %this.timeController.getCameraMax();
+	%yMin = %this.valueController.getCameraMin();
+	%yMax = %this.valueController.getCameraMax();
+
+	%this.graph.setDisplayArea(%xMin SPC %yMin SPC %xMax SPC %yMax);
+
+	%this.valueMoveUpButton.setActive(%this.valueController.getMoveUpEnabled());
+	%this.valueMoveDownButton.setActive(%this.valueController.getMoveDownEnabled());
+	%this.valueZoomInButton.setActive(%this.valueController.getZoomInEnabled());
+	%this.valueZoomOutButton.setActive(%this.valueController.getZoomOutEnabled());
+
+	%this.timeMoveForwardButton.setActive(%this.timeController.getMoveUpEnabled());
+	%this.timeMoveBackButton.setActive(%this.timeController.getMoveDownEnabled());
+	%this.timeZoomInButton.setActive(%this.timeController.getZoomInEnabled());
+	%this.timeZoomOutButton.setActive(%this.timeController.getZoomOutEnabled());
+}
+
+function AssetParticleGraphUnit::valueZoomIn(%this)
+{
+	%this.valueController.zoomIn();
+	%this.refreshCamera();
+}
+
+function AssetParticleGraphUnit::valueZoomOut(%this)
+{
+	%this.valueController.zoomOut();
+	%this.refreshCamera();
+}
+
+function AssetParticleGraphUnit::valueMoveUp(%this)
+{
+	%this.valueController.moveUp();
+	%this.refreshCamera();
+}
+
+function AssetParticleGraphUnit::valueMoveDown(%this)
+{
+	%this.valueController.moveDown();
+	%this.refreshCamera();
+}
+
+function AssetParticleGraphUnit::timeZoomIn(%this)
+{
+	%this.timeController.zoomIn();
+	%this.refreshCamera();
+}
+
+function AssetParticleGraphUnit::timeZoomOut(%this)
+{
+	%this.timeController.zoomOut();
+	%this.refreshCamera();
+}
+
+function AssetParticleGraphUnit::timeMoveBack(%this)
+{
+	%this.timeController.moveDown();
+	%this.refreshCamera();
+}
+
+function AssetParticleGraphUnit::timeMoveForward(%this)
+{
+	%this.timeController.moveUp();
+	%this.refreshCamera();
+}

+ 139 - 0
editor/AssetAdmin/ParticleEditor/ParticleGraphCameraController.cs

@@ -0,0 +1,139 @@
+
+function ParticleGraphCameraController::onAdd(%this)
+{
+	if(strstr(%this.fieldName, "Scale") > 0)
+	{
+		%this.asset.selectField(%this.fieldName);
+		if(%this.isTime)
+		{
+			%this.min = %this.asset.getMinTime();
+			%this.max = %this.asset.getMaxTime();
+		}
+		else
+		{
+			%this.min = %this.asset.getMinValue();
+			%this.max = %this.asset.getMaxValue();
+		}
+	}
+	else
+	{
+		%emitter = %this.asset.getEmitter(0);
+		%emitter.selectField(%this.fieldName);
+		if(%this.isTime)
+		{
+			%this.min = %emitter.getMinTime();
+			%this.max = %emitter.getMaxTime();
+		}
+		else
+		{
+			%this.min = %emitter.getMinValue();
+			%this.max = %emitter.getMaxValue();
+		}
+
+	}
+	%this.currentPosition = 0;
+
+	%this.zoomLevel[1] = 1;
+	%this.zoomCount = 1;
+	%this.currentZoomLevel = 1;
+
+	if(%this.max > 1)
+	{
+		%this.zoomLevel[2] = 10;
+		%this.zoomCount = 2;
+		%this.currentZoomLevel = 2;
+	}
+
+	if(%this.max > 10)
+	{
+		%this.zoomLevel[3] = 100;
+		%this.zoomCount = 3;
+	}
+
+	if(%this.max > 100)
+	{
+		%this.zoomLevel[4] = 1000;
+		%this.zoomCount = 4;
+	}
+
+	if(%this.max == 180 || %this.max == 360 || %this.max == 720)
+	{
+		%this.setupDegreeValue();
+	}
+}
+
+function ParticleGraphCameraController::setupDegreeValue(%this)
+{
+	%this.currentPosition = %this.min;
+
+	%this.zoomLevel[1] = 5;
+	%this.zoomLevel[2] = 30;
+	%this.zoomLevel[3] = 90;
+	%this.zoomLevel[4] = 360;
+	%this.zoomCount = 4;
+	%this.currentZoomLevel = 4;
+}
+
+function ParticleGraphCameraController::getCameraMin(%this)
+{
+	return %this.currentPosition;
+}
+
+function ParticleGraphCameraController::getCameraMax(%this)
+{
+	return %this.currentPosition + %this.zoomLevel[%this.currentZoomLevel];
+}
+
+function ParticleGraphCameraController::zoomIn(%this)
+{
+	%this.currentZoomLevel = mGetMax(%this.currentZoomLevel - 1, 1);
+}
+
+function ParticleGraphCameraController::zoomOut(%this)
+{
+	%this.currentZoomLevel = mGetMin(%this.currentZoomLevel + 1, %this.zoomCount);
+	%zoom = %this.zoomLevel[%this.currentZoomLevel];
+	%this.currentPosition = mFloor(%this.currentPosition / %zoom) * %zoom;
+
+	if((%this.currentPosition + %zoom) > %this.max)
+	{
+		%this.currentPosition = %this.max - %zoom;
+	}
+
+	if(%this.currentPosition < %this.min)
+	{
+		%this.currentPosition = %this.min;
+	}
+}
+
+function ParticleGraphCameraController::moveUp(%this)
+{
+	%zoom = %this.zoomLevel[%this.currentZoomLevel];
+	%this.currentPosition = mGetMin(%this.currentPosition + %zoom, %this.max - %zoom);
+}
+
+function ParticleGraphCameraController::moveDown(%this)
+{
+	%this.currentPosition = mGetMax(%this.currentPosition - %this.zoomLevel[%this.currentZoomLevel], %this.min);
+}
+
+function ParticleGraphCameraController::getMoveUpEnabled(%this)
+{
+	%zoom = %this.zoomLevel[%this.currentZoomLevel];
+	return (%this.currentPosition + %zoom) < %this.max;
+}
+
+function ParticleGraphCameraController::getMoveDownEnabled(%this)
+{
+	return %this.currentPosition > %this.min;
+}
+
+function ParticleGraphCameraController::getZoomInEnabled(%this)
+{
+	return %this.currentZoomLevel > 1;
+}
+
+function ParticleGraphCameraController::getZoomOutEnabled(%this)
+{
+	return %this.currentZoomLevel < %this.zoomCount;
+}

+ 3 - 0
editor/AssetAdmin/ParticleEditor/exec.cs

@@ -0,0 +1,3 @@
+exec("./AssetParticleGraphTool.cs");
+exec("./AssetParticleGraphUnit.cs");
+exec("./ParticleGraphCameraController.cs");

+ 28 - 0
editor/EditorCore/Themes/BaseTheme/BaseTheme.cs

@@ -61,6 +61,7 @@ function BaseTheme::onAdd(%this)
 	%this.makeDropDownProfile();
 	%this.makeWindowProfile();
 	%this.makeListBoxProfile();
+	%this.makeGraphProfile();
 }
 
 function BaseTheme::init(%this)
@@ -1607,6 +1608,33 @@ function BaseTheme::makeListBoxProfile(%this)
 	};
 }
 
+function BaseTheme::makeGraphProfile(%this)
+{
+	%border = new GuiBorderProfile()
+	{
+		padding = 4;
+		border = %this.borderSize;
+		borderColor = %this.color3; //Used for the border as normal
+	};
+
+	%this.graphProfile = new GuiControlProfile()
+	{
+		fillColor = %this.color2; //Used for the background of the control as normal
+		fillColorHL = %this.setAlpha(%this.color3, 220); //Used for the grid and labels
+		fillColorSL = %this.color5; //Used for the lines of the control
+		fillColorNA = %this.setAlpha(%this.color5, 100); //Used for the variance when it is displayed
+
+		fontType = %this.font[3];
+		fontDirectory = %this.fontDirectory;
+		fontSize = %this.fontSize - 2;
+		fontColor = %this.color4; //Used for the points
+		fontColorHL = %this.adjustValue(%this.color4, 10); //Used for the points when they are hovered over
+		fontColorSL = %this.adjustValue(%this.color5, 10); //Used for the selected points
+
+		borderDefault = %border;
+	};
+}
+
 //Positive values are brighter, negative are darker
 function BaseTheme::adjustValue(%this, %color, %percent)
 {

BIN
engine/Link/VC2012.Debug.Win32/Torque2D/RCa59600


BIN
engine/compilers/VisualStudio 2017/Torque 2D.aps


BIN
engine/compilers/VisualStudio 2017/Torque 2D.ico


+ 2 - 2
engine/compilers/VisualStudio 2017/Torque 2D.rc

@@ -78,10 +78,10 @@ BEGIN
         BLOCK "040904b0"
         BEGIN
             VALUE "CompanyName", "Torque Game Engines"
-            VALUE "FileDescription", "Torque 2D MIT"
+            VALUE "FileDescription", "Torque 2D: Rocket Edition"
             VALUE "FileVersion", "4, 0, 0, 0"
             VALUE "InternalName", "Torque 2D"
-            VALUE "LegalCopyright", "Copyright (c) 2020 Torque Game Engines"
+            VALUE "LegalCopyright", "Copyright (c) 2021 Torque Game Engines"
             VALUE "OriginalFilename", "Torque2D.exe"
             VALUE "ProductName", "Torque 2D"
             VALUE "ProductVersion", "4, 0, 0, 0"

+ 3 - 0
engine/compilers/VisualStudio 2017/Torque 2D.vcxproj

@@ -620,6 +620,7 @@
     <ClCompile Include="..\..\source\gui\containers\guiSceneScrollCtrl.cc" />
     <ClCompile Include="..\..\source\gui\containers\guiTabPageCtrl.cc" />
     <ClCompile Include="..\..\source\gui\editor\guiMenuBarCtrl.cc" />
+    <ClCompile Include="..\..\source\gui\editor\guiParticleGraphInspector.cc" />
     <ClCompile Include="..\..\source\gui\guiArrayCtrl.cc" />
     <ClCompile Include="..\..\source\gui\guiCanvas.cc" />
     <ClCompile Include="..\..\source\gui\guiColorPicker.cc" />
@@ -1159,6 +1160,8 @@
     <ClInclude Include="..\..\source\gui\containers\guiWindowCtrl_ScriptBinding.h" />
     <ClInclude Include="..\..\source\gui\editor\guiMenuBarCtrl.h" />
     <ClInclude Include="..\..\source\gui\editor\guiMenuBarCtrl_ScriptBinding.h" />
+    <ClInclude Include="..\..\source\gui\editor\guiParticleGraphInspector.h" />
+    <ClInclude Include="..\..\source\gui\editor\guiParticleGraphInspector_ScriptBinding.h" />
     <ClInclude Include="..\..\source\gui\guiArrayCtrl.h" />
     <ClInclude Include="..\..\source\gui\guiCanvas.h" />
     <ClInclude Include="..\..\source\gui\guiCanvas_ScriptBinding.h" />

+ 9 - 0
engine/compilers/VisualStudio 2017/Torque 2D.vcxproj.filters

@@ -1468,6 +1468,9 @@
     <ClCompile Include="..\..\source\gui\containers\guiSceneScrollCtrl.cc">
       <Filter>gui\containers</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\source\gui\editor\guiParticleGraphInspector.cc">
+      <Filter>gui\editor</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\source\audio\audio.h">
@@ -3328,6 +3331,12 @@
     <ClInclude Include="..\..\source\gui\containers\guiSceneScrollCtrl.h">
       <Filter>gui\containers</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\source\gui\editor\guiParticleGraphInspector.h">
+      <Filter>gui\editor</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\source\gui\editor\guiParticleGraphInspector_ScriptBinding.h">
+      <Filter>gui\editor</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="Torque 2D.rc" />

+ 2 - 2
engine/source/2d/assets/ParticleAssetEmitter.cc

@@ -223,8 +223,8 @@ ParticleAssetEmitter::ParticleAssetEmitter() :
     // Initialize particle fields.
     mParticleFields.addField( mParticleLife.getBase(), "Lifetime", 1000.0f, 0.0f, 10000.0f, 2.0f );
     mParticleFields.addField( mParticleLife.getVariation(), "LifetimeVariation", 1000.0f, 0.0f, 5000.0f, 0.0f  );
-    mParticleFields.addField( mQuantity.getBase(), "Quantity", 1000.0f, 0.0f, 100000.0f, 10.0f );
-    mParticleFields.addField( mQuantity.getVariation(), "QuantityVariation", 1000.0f, 0.0f, 100000.0f, 0.0f );
+    mParticleFields.addField( mQuantity.getBase(), "Quantity", 1000.0f, 0.0f, 1000.0f, 10.0f );
+    mParticleFields.addField( mQuantity.getVariation(), "QuantityVariation", 1000.0f, 0.0f, 1000.0f, 0.0f );
     mParticleFields.addField( mSizeX.getBase(), "SizeX",  1000.0f, 0.0f, 100.0f, 2.0f );
     mParticleFields.addField( mSizeX.getVariation(), "SizeXVariation", 1000.0f, 0.0f, 200.0f, 0.0f );
     mParticleFields.addField( mSizeX.getLife(), "SizeXLife", 1.0f, -100.0f, 100.0f, 1.0f );

+ 20 - 0
engine/source/2d/assets/ParticleAssetField.cc

@@ -251,6 +251,26 @@ S32 ParticleAssetField::setSingleDataKey( const F32 value )
 
 //-----------------------------------------------------------------------------
 
+bool ParticleAssetField::doesKeyExist(const F32 time)
+{
+	U32 index = 0;
+	for (index = 0; index < getDataKeyCount(); index++)
+	{
+		// Found Time?
+		if (mDataKeys[index].mTime == time)
+		{
+			return true;
+		}
+		// Past Time?
+		else if (mDataKeys[index].mTime > time)
+			// Finish search.
+			break;
+	}
+	return false;
+}
+
+//-----------------------------------------------------------------------------
+
 S32 ParticleAssetField::addDataKey( const F32 time, const F32 value )
 {
     // Check Max Time.

+ 1 - 0
engine/source/2d/assets/ParticleAssetField.h

@@ -95,6 +95,7 @@ public:
 
     void resetDataKeys( void );
     S32 setSingleDataKey( const F32 value );
+	bool doesKeyExist(const F32 time);
     S32 addDataKey( const F32 time, const F32 value );
     bool removeDataKey( const U32 index );
     void clearDataKeys( void );

+ 2 - 1
engine/source/2d/gui/SceneWindow.cc

@@ -1743,7 +1743,8 @@ void SceneWindow::resize(const Point2I &newPosition, const Point2I &newExtent)
     dSprintf( argBuffer, 64, "%d %d %d %d", newPosition.x, newPosition.y, newExtent.x, newExtent.y );
 
     // Resize Callback.
-    Con::executef( this, 2, "onExtentChange", argBuffer );
+	if(this->getNamespace())
+		Con::executef( this, 2, "onExtentChange", argBuffer );
 }
 
 //-----------------------------------------------------------------------------

+ 494 - 0
engine/source/gui/editor/guiParticleGraphInspector.cc

@@ -0,0 +1,494 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#include "console/console.h"
+#include "console/consoleTypes.h"
+#include "graphics/dgl.h"
+#include "gui/guiDefaultControlRender.h"
+#include "math/rectClipper.h"
+#include "gui/guiCanvas.h"
+
+#ifndef _PARTICLE_ASSET_H_
+#include "2d/assets/ParticleAsset.h"
+#endif
+
+#include "gui/editor/GuiParticleGraphInspector.h"
+
+#include "gui/editor/GuiParticleGraphInspector_ScriptBinding.h"
+
+IMPLEMENT_CONOBJECT(GuiParticleGraphInspector);
+
+GuiParticleGraphInspector::GuiParticleGraphInspector()
+{
+	mBounds.extent.set(300, 200);
+
+	mTargetAsset = NULL;
+	mTargetField = StringTable->insert("QuantityScale");
+	mEmitterIndex = 0;
+	mMinX = 0;
+	mMinXLabel = StringTable->insert("0");
+	mMaxX = 1;
+	mMaxXLabel = StringTable->insert("1");
+	mMinY = 0;
+	mMinYLabel = StringTable->insert("0");
+	mMaxY = 10;
+	mMaxYLabel = StringTable->insert("10");
+	mLabelX = StringTable->insert("Time", true);
+	mLabelY = StringTable->insert("Value", true);
+	mSelectedIndex = -1;
+	mDirty = true;
+	mPointList = Vector<GraphPoint>();
+
+	setField("profile", "GuiDefaultProfile");
+}
+
+void GuiParticleGraphInspector::initPersistFields()
+{
+	Parent::initPersistFields();
+}
+
+void GuiParticleGraphInspector::inspectObject(ParticleAsset* object)
+{
+	mTargetAsset = object;
+	mDirty = true;
+}
+
+void GuiParticleGraphInspector::setDisplayField(const char* fieldName)
+{
+	if (mTargetField != StringTable->insert(fieldName, true))
+	{
+		mSelectedIndex = -1;
+	}
+	mTargetField = StringTable->insert(fieldName, true);
+
+	mDirty = true;
+}
+
+void GuiParticleGraphInspector::setDisplayField(const char* fieldName, U16 index)
+{
+	mEmitterIndex = index;
+	setDisplayField(fieldName);
+}
+
+void GuiParticleGraphInspector::setDisplayArea(StringTableEntry minX, StringTableEntry minY, StringTableEntry maxX, StringTableEntry maxY)
+{
+	mMinXLabel = minX;
+	mMinX = dAtof(minX);
+
+	mMinYLabel = minY;
+	mMinY = dAtof(minY);
+
+	mMaxXLabel = maxX;
+	mMaxX = dAtof(maxX);
+
+	mMaxYLabel = maxY;
+	mMaxY = dAtof(maxY);
+
+	mDirty = true;
+}
+
+void GuiParticleGraphInspector::setDisplayLabels(const char* labelX, const char* labelY)
+{
+	mLabelX = StringTable->insert(labelX, true);
+	mLabelY = StringTable->insert(labelY, true);
+}
+
+ParticleAssetField* GuiParticleGraphInspector::getTargetField()
+{
+	ParticleAssetFieldCollection &collection = mTargetAsset->getParticleFields();
+	ParticleAssetField* field = collection.findField(mTargetField);
+
+	if (field == NULL)
+	{
+		ParticleAssetEmitter* emitter = mTargetAsset->getEmitter(mEmitterIndex);
+		ParticleAssetFieldCollection &emitterCollection = emitter->getParticleFields();
+		field = emitterCollection.findField(mTargetField);
+	}
+
+	AssertFatal(field != NULL, "GuiParticleGraphInspector::getTargetField() - Unable to find the requested field.");
+	
+	return field;
+}
+
+void GuiParticleGraphInspector::resize(const Point2I &newPosition, const Point2I &newExtent)
+{
+	GuiControl::resize(newPosition, newExtent);
+	mDirty = true;
+}
+
+void GuiParticleGraphInspector::setControlProfile(GuiControlProfile *prof)
+{
+	GuiControl::setControlProfile(prof);
+	mDirty = true;
+}
+
+void GuiParticleGraphInspector::onTouchUp(const GuiEvent &event)
+{
+	if(mTargetAsset)
+		mTargetAsset->refreshAsset();
+}
+
+void GuiParticleGraphInspector::onTouchDown(const GuiEvent &event)
+{
+	if(!mTargetAsset)
+		return;
+
+	mSelectedIndex = findHitGraphPoint(event.mousePoint);
+
+	if (mSelectedIndex != -1 && event.mouseClickCount == 2)
+	{
+		//remove the point
+		ParticleAssetField* field = getTargetField();
+		field->removeDataKey(mSelectedIndex);
+
+		mDirty = true;
+	}
+	else if (mSelectedIndex == -1 && mGridRect.pointInRect(event.mousePoint))
+	{
+		//Time to create a new point!
+		ParticleAssetField* field = getTargetField();
+		F32 time = getGraphTime(event.mousePoint.x);
+		F32 value = getGraphValue(event.mousePoint.y);
+		mSelectedIndex = field->addDataKey(time, value);
+
+		mDirty = true;
+	}
+}
+
+void GuiParticleGraphInspector::onTouchDragged(const GuiEvent &event)
+{
+	if (!mTargetAsset)
+		return;
+
+	Point2I point = Point2I(mClamp(event.mousePoint.x, mGridRect.point.x, mGridRect.point.x + mGridRect.extent.x), mClamp(event.mousePoint.y, mGridRect.point.y, mGridRect.point.y + mGridRect.extent.y));
+
+	if (mSelectedIndex == 0)
+	{
+		//Time to move the first point!
+		ParticleAssetField* field = getTargetField();
+		F32 value = getGraphValue(point.y);
+		field->setDataKeyValue(mSelectedIndex, value);
+
+		mDirty = true;
+	}
+	else if (mSelectedIndex > 0)
+	{
+		//Time to move a point!
+		ParticleAssetField* field = getTargetField();
+		F32 time = getGraphTime(point.x);
+		F32 value = getGraphValue(point.y);
+		if (time == field->getDataKeyTime(mSelectedIndex) || field->doesKeyExist(time))
+		{
+			//If we're not moving through time or we tried to drag it into a time with a different point, then just change the value.
+			field->setDataKeyValue(mSelectedIndex, value);
+		}
+		else
+		{
+			//Time travel! Destroy the old point. Recreate in a new time.
+			field->removeDataKey(mSelectedIndex);
+			mSelectedIndex = field->addDataKey(time, value);
+		}
+
+		mDirty = true;
+	}
+}
+
+U32 GuiParticleGraphInspector::findHitGraphPoint(const Point2I &point)
+{
+	for (U32 i = 0; i < mPointList.size(); i++)
+	{
+		F32 x = mPointList[i].mPoint.x - point.x;
+		F32 y = mPointList[i].mPoint.y - point.y;
+		F32 dist = mSqrt((x * x) + (y * y));
+		if (dist <= mRadius)
+		{
+			return i;
+		}
+	}
+	return -1;
+}
+
+F32 GuiParticleGraphInspector::getGraphValue(const F32 y)
+{
+	S32 len = mGridRect.extent.y;
+	F32 ratio = (F32)((y - mGridRect.point.y) / len);
+	ratio = mRound(ratio * 100) / 100; //Snaps to a grid of 100 possible values.
+	return mMinY + ((mMaxY - mMinY) * (1 - ratio));
+}
+
+F32 GuiParticleGraphInspector::getGraphTime(const F32 x)
+{
+	S32 len = mGridRect.extent.x;
+	F32 ratio = (F32)((x - mGridRect.point.x) / len);
+	ratio = mRound(ratio * 100) / 100; //Snaps to a grid of 100 possible values.
+	return mMinX + ((mMaxX - mMinX) * ratio);
+}
+
+void GuiParticleGraphInspector::onRender(Point2I offset, const RectI &updateRect)
+{
+	RectI ctrlRect = applyMargins(offset, mBounds.extent, NormalState, mProfile);
+	if (!ctrlRect.isValidRect())
+	{
+		return;
+	}
+	renderUniversalRect(ctrlRect, mProfile, NormalState);
+
+	RectI fillRect = applyBorders(ctrlRect.point, ctrlRect.extent, NormalState, mProfile);
+	RectI contentRect = applyPadding(fillRect.point, fillRect.extent, NormalState, mProfile);
+
+	//Make room for the graph labels
+	GFont *font = mProfile->mFont;
+	U32 fontHeight = font->getHeight();
+	contentRect.extent.y -= fontHeight;
+	U8 xReduction = getMax(getMax(fontHeight, font->getStrWidth(mMaxYLabel)), font->getStrWidth(mMinYLabel));
+	contentRect.extent.x -= xReduction;
+	contentRect.point.x += xReduction;
+
+	//reduce the contentRect to be divisible by 10
+	U32 modX = contentRect.len_x() % 10;
+	U32 modY = contentRect.len_y() % 10;
+	contentRect.extent.set(contentRect.len_x() - modX, contentRect.len_y() - modY);
+	contentRect.point.set(contentRect.point.x + mFloor(modX / 2), contentRect.point.y + mFloor(modY / 2));
+
+	//Draw the labels
+	ColorI gridColor = mProfile->getFillColor(HighlightState);
+	renderLabels(contentRect, gridColor);
+
+	if (contentRect.isValidRect())
+	{
+		renderGrid(contentRect, gridColor);
+
+		if (mCalculationOffset != offset)
+		{
+			mDirty = true;
+		}
+		renderPoints(contentRect, mProfile->getFillColor(SelectedState));
+		mCalculationOffset = offset;
+	}
+}
+
+void GuiParticleGraphInspector::renderLabels(const RectI &contentRect, const ColorI &labelColor)
+{
+	GFont *font = mProfile->mFont;
+	U32 fontHeight = font->getHeight();
+	U32 textWidth;
+	Point2I textPoint;
+
+	//Set the color used for the grid. This will also be used for the text.
+	dglSetBitmapModulation(labelColor);
+
+	//x label
+	textWidth = font->getStrWidth(mLabelX);
+	textPoint = Point2I(contentRect.point.x + (contentRect.extent.x / 2) - (textWidth / 2), contentRect.point.y + contentRect.extent.y + 2);
+	dglDrawText(font, textPoint, mLabelX, NULL, 0, 0);
+
+	//x min label
+	textWidth = font->getStrWidth(mMinXLabel);
+	textPoint = Point2I(contentRect.point.x + 1, contentRect.point.y + contentRect.extent.y + 2);
+	dglDrawText(font, textPoint, mMinXLabel, NULL, 0, 0);
+
+	//x max label
+	textWidth = font->getStrWidth(mMaxXLabel);
+	textPoint = Point2I((contentRect.point.x + contentRect.extent.x - 1) - textWidth, contentRect.point.y + contentRect.extent.y + 2);
+	dglDrawText(font, textPoint, mMaxXLabel, NULL, 0, 0);
+
+	//y label
+	textWidth = font->getStrWidth(mLabelY);
+	textPoint = Point2I(contentRect.point.x - (fontHeight + 2), contentRect.point.y + (contentRect.extent.y / 2) + (textWidth / 2));
+	dglDrawText(font, textPoint, mLabelY, NULL, 0, 90);
+
+	//y min label
+	textWidth = font->getStrWidth(mMinYLabel);
+	textPoint = Point2I(contentRect.point.x - (textWidth + 2), (contentRect.point.y + contentRect.extent.y - 2) - (fontHeight / 2));
+	dglDrawText(font, textPoint, mMinYLabel, NULL, 0, 0);
+
+	//y max label
+	textWidth = font->getStrWidth(mMaxYLabel);
+	textPoint = Point2I(contentRect.point.x - (textWidth + 2), (contentRect.point.y + 4) - (fontHeight / 2));
+	dglDrawText(font, textPoint, mMaxYLabel, NULL, 0, 0);
+}
+	
+void GuiParticleGraphInspector::renderGrid(const RectI &contentRect, const ColorI &gridColor)
+{
+	S32 x, y;
+	x = contentRect.len_x() / 10;
+	y = contentRect.len_y() / 10;
+
+	//horizontal lines
+	for (U8 i = 0; i < 11; i++)
+	{
+		if(i != 5)
+		{
+			dglDrawLine(Point2I(contentRect.point.x, contentRect.point.y + (y * i)), Point2I(contentRect.point.x + contentRect.extent.x, contentRect.point.y + (y * i)), gridColor);
+		}
+		else
+		{
+			Point2I p1 = Point2I(contentRect.point.x, contentRect.point.y + (y * i) - 1);
+			Point2I p2 = Point2I(contentRect.point.x + contentRect.extent.x, contentRect.point.y + (y * i) - 1);
+			Point2I p3 = Point2I(contentRect.point.x + contentRect.extent.x, contentRect.point.y + (y * i) + 1);
+			Point2I p4 = Point2I(contentRect.point.x, contentRect.point.y + (y * i) + 1);
+			dglDrawQuadFill(p1, p2, p3, p4, gridColor);
+		}
+	}
+
+	//vertical lines
+	for (U8 i = 0; i < 11; i++)
+	{
+		if (i != 5)
+		{
+			dglDrawLine(Point2I(contentRect.point.x + (x * i), contentRect.point.y), Point2I(contentRect.point.x + (x * i), contentRect.point.y + contentRect.extent.y), gridColor);
+		}
+		else
+		{
+			Point2I p1 = Point2I(contentRect.point.x + (x * i) - 1, contentRect.point.y);
+			Point2I p2 = Point2I(contentRect.point.x + (x * i) + 1, contentRect.point.y);
+			Point2I p3 = Point2I(contentRect.point.x + (x * i) + 1, contentRect.point.y + contentRect.extent.y);
+			Point2I p4 = Point2I(contentRect.point.x + (x * i) - 1, contentRect.point.y + contentRect.extent.y);
+			dglDrawQuadFill(p1, p2, p3, p4, gridColor);
+		}
+	}
+}
+
+void GuiParticleGraphInspector::calculatePoints(const RectI &contentRect)
+{
+	mGridRect = RectI(contentRect);
+
+	mPointList.clear();
+	ParticleAssetField* field = getTargetField();
+	
+	F32 time = 0;
+	U32 count = field->getDataKeyCount();
+	for (U32 i = 0; i < count; i++)
+	{
+		ParticleAssetField::DataKey key = field->getDataKey(i);
+
+		//force the first key to always be at time zero
+		if (i == 0 && key.mTime != 0)
+		{
+			field->addDataKey(0, key.mValue);
+			field->removeDataKey(1);
+			key = field->getDataKey(0);
+			count = field->getDataKeyCount();
+		}
+
+		//Remove the point if it has a bad time
+		if (i > 0 && key.mTime <= time)
+		{
+			field->removeDataKey(i);
+			count--;
+			continue;
+		}
+		time = key.mTime;
+	}
+
+	F32 width = mMaxX - mMinX;
+	F32 height = mMaxY - mMinY;
+	Point2I p;
+	for (U32 i = 0; i < count; i++)
+	{
+		ParticleAssetField::DataKey key = field->getDataKey(i);
+
+		F32 ratioX = (key.mTime - mMinX) / width;
+		F32 ratioY = (key.mValue - mMinY) / height;
+
+		p = Point2I(contentRect.point.x + (contentRect.extent.x * ratioX), contentRect.point.y + (contentRect.extent.y * (1 - ratioY)));
+
+		mPointList.push_back(GraphPoint(p, key.mTime, key.mValue, i));
+	}
+	mDirty = false;
+}
+
+void GuiParticleGraphInspector::renderPoints(const RectI &contentRect, const ColorI &lineColor)
+{
+	if (mTargetAsset)
+	{
+		if (mDirty)
+		{
+			calculatePoints(contentRect);
+		}
+
+		//get the cursor position
+		Point2I cursorPt = Point2I(0, 0);
+		GuiCanvas *root = getRoot();
+		if (root)
+		{
+			cursorPt = root->getCursorPos();
+		}
+
+		//Render the lines first
+		Point2I p1, p2;
+		U32 count = mPointList.size();
+		for(U32 i = 1; i < count; i++)
+		{
+			p1 = mPointList[i-1].mPoint;
+			p2 = mPointList[i].mPoint;
+
+			renderLine(contentRect, p1, p2, lineColor);
+			renderDot(contentRect, p1, cursorPt, mSelectedIndex == (i - 1));
+		}
+		p1 = mPointList[count - 1].mPoint;
+		p2 = Point2I(contentRect.point.x + contentRect.extent.x, p1.y);
+		if (p1.x < p2.x)
+		{
+			renderLine(contentRect, p1, p2, lineColor);
+		}
+		renderDot(contentRect, p1, cursorPt, mSelectedIndex == (count - 1));
+	}
+}
+
+void GuiParticleGraphInspector::renderDot(const RectI &contentRect, const Point2I &point, const Point2I &cursorPt, bool isSelected)
+{
+	if(point.x >= contentRect.point.x && point.x <= contentRect.point.x + contentRect.extent.x && point.y >= contentRect.point.y && point.y <= contentRect.point.y + contentRect.extent.y)
+	{
+		F32 x = cursorPt.x - point.x;
+		F32 y = cursorPt.y - point.y;
+		F32 dist = mSqrt((x * x) + (y * y));
+		ColorI color;
+		if (isSelected)
+		{
+			color = mProfile->getFontColor(SelectedState);
+		}
+		else if (dist <= mRadius)
+		{
+			color = mProfile->getFontColor(HighlightState);
+		} 
+		else
+		{
+			color = mProfile->getFontColor(NormalState);
+		}
+
+		dglDrawCircleFill(point, mRadius, ColorI(0, 0, 0, 100));
+		dglDrawCircleFill(point, mRadius - 2, color);
+	}
+}
+
+void GuiParticleGraphInspector::renderLine(const RectI &contentRect, const Point2I &point1, const Point2I &point2, const ColorI &lineColor)
+{
+	RectClipper clipper = RectClipper(contentRect);
+
+	Point2I p1;
+	Point2I p2;
+	if(clipper.clipLine(point1, point2, p1, p2))
+	{
+		dglDrawLine(p1, p2, ColorI(lineColor));
+	}
+}

+ 97 - 0
engine/source/gui/editor/guiParticleGraphInspector.h

@@ -0,0 +1,97 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+#ifndef _GUIPARTICLEGRAPHINSPECTOR_H_
+#define _GUIPARTICLEGRAPHINSPECTOR_H_
+
+#ifndef _GUICONTROL_H_
+#include "gui/guiControl.h"
+#endif
+
+class GuiParticleGraphInspector : public GuiControl
+{
+private:
+   typedef GuiControl Parent;
+   StringTableEntry mTargetField;
+   ParticleAsset* mTargetAsset;
+   U32 mEmitterIndex;
+
+   F32 mMinX, mMinY, mMaxX, mMaxY; //Display settings
+   StringTableEntry mLabelX, mLabelY; 
+   StringTableEntry mMaxYLabel, mMinYLabel, mMaxXLabel, mMinXLabel;
+
+   const F32 mRadius = 7; //size of a point
+
+   RectI mGridRect;
+   Point2I mCalculationOffset;
+
+public:
+	struct GraphPoint
+	{
+		GraphPoint() {}
+		GraphPoint(Point2I p, F32 time, F32 value, U32 index) { mTime = time; mValue = value; mPoint = p; mIndex = index; }
+
+		F32 mTime;
+		F32 mValue;
+		Point2I mPoint;
+		U32 mIndex;
+	};
+	S32 mSelectedIndex;
+	bool mDirty;
+	Vector<GraphPoint> mPointList;
+
+   //creation methods
+   DECLARE_CONOBJECT(GuiParticleGraphInspector);
+   GuiParticleGraphInspector();
+   static void initPersistFields();
+
+   virtual void inspectObject(ParticleAsset* object);
+   virtual void setDisplayField(const char* fieldName);
+   virtual void setDisplayField(const char* fieldName, U16 index);
+   virtual void setDisplayArea(StringTableEntry minX, StringTableEntry minY, StringTableEntry maxX, StringTableEntry maxY);
+   virtual void setDisplayLabels(const char* labelX, const char* labelY);
+
+   virtual void resize(const Point2I &newPosition, const Point2I &newExtent);
+   virtual void setControlProfile(GuiControlProfile *prof);
+
+   virtual void onTouchUp(const GuiEvent &event);
+   virtual void onTouchDown(const GuiEvent &event);
+   virtual void onTouchDragged(const GuiEvent &event);
+
+   void onRender(Point2I offset, const RectI &updateRect);
+
+protected:
+	U32 findHitGraphPoint(const Point2I &point);
+	F32 getGraphValue(const F32 y);
+	F32 getGraphTime(const F32 x);
+
+	void calculatePoints(const RectI &contentRect);
+	void renderLabels(const RectI &contentRect, const ColorI &labelColor);
+	void renderGrid(const RectI &contentRect, const ColorI &gridColor);
+	void renderPoints(const RectI &contentRect, const ColorI &lineColor);
+	void renderDot(const RectI &contentRect, const Point2I &point, const Point2I &cursorPt, bool isSelected);
+	void renderLine(const RectI &contentRect, const Point2I &point1, const Point2I &point2, const ColorI &lineColor);
+
+	ParticleAssetField* getTargetField();
+};
+
+#endif

+ 103 - 0
engine/source/gui/editor/guiParticleGraphInspector_ScriptBinding.h

@@ -0,0 +1,103 @@
+//-----------------------------------------------------------------------------
+// Copyright (c) 2013 GarageGames, LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//-----------------------------------------------------------------------------
+
+ConsoleMethodGroupBeginWithDocs(GuiParticleGraphInspector, GuiControl)
+
+/*! Sets the Particle Asset that will be used to draw graphs.
+	@param ParticleAsset The target of the graphs.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(GuiParticleGraphInspector, inspect, ConsoleVoid, 3, 3, "(ParticleAsset)")
+{
+	ParticleAsset* target = dynamic_cast<ParticleAsset*>(Sim::findObject(argv[2]));
+	if (!target)
+	{
+		if (dAtoi(argv[2]) > 0)
+			Con::warnf("%s::inspect(): Object is not a ParticleAsset: %s", argv[0], argv[2]);
+
+		return;
+	}
+
+	object->inspectObject(target);
+}
+
+/*! Sets the graph field to display.
+	@param FieldName The name of the field that should be displayed.
+	@param EmitterIndex If the field belongs to an emitter, include the index of the emitter to display.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(GuiParticleGraphInspector, setDisplayField, ConsoleVoid, 3, 4, "(FieldName, [EmitterIndex])")
+{
+	if (argc > 3)
+	{
+		object->setDisplayField(argv[2], dAtoi(argv[3]));
+	}
+	else
+	{
+		object->setDisplayField(argv[2]);
+	}
+}
+
+/*! Sets the area that will be displayed on the graph.
+	@param Area Four space-deliminated values representing left, bottom, right, top.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(GuiParticleGraphInspector, setDisplayArea, ConsoleVoid, 3, 3, "(area (xMin / yMin / xMax / yMax))")
+{
+	if (argc < 3)
+	{
+		Con::warnf("GuiParticleGraphInspector:setDisplayArea - Wrong number of arguments. Should be area(left / bottom / right / top).");
+		return;
+	}
+	
+	U32 count = Utility::mGetStringElementCount(argv[2]);
+	if (count != 4)
+	{
+		Con::warnf("GuiParticleGraphInspector:setDisplayArea - Area does not have four values. Should be area(left / bottom / right / top).");
+		return;
+	}
+
+	StringTableEntry s1 = StringTable->insert(Utility::mGetStringElement(argv[2], 0));
+	StringTableEntry s2 = StringTable->insert(Utility::mGetStringElement(argv[2], 1));
+	StringTableEntry s3 = StringTable->insert(Utility::mGetStringElement(argv[2], 2));
+	StringTableEntry s4 = StringTable->insert(Utility::mGetStringElement(argv[2], 3));
+
+	object->setDisplayArea(s1, s2, s3, s4);
+}
+
+/*! Sets the labels to display on the graph.
+	@param LabelX The label that appears on the bottom of the graph.
+	@param LabelY The label that appears on the left of the graph.
+	@return No return value.
+*/
+ConsoleMethodWithDocs(GuiParticleGraphInspector, setDisplayLabels, ConsoleVoid, 4, 4, "(LabelX, LabelY)")
+{
+	if (argc < 4)
+	{
+		Con::warnf("GuiParticleGraphInspector:setDisplayLabels - Wrong number of arguments. Should be LabelX and LabelY.");
+		return;
+	}
+
+	object->setDisplayLabels(argv[2], argv[3]);
+}
+
+ConsoleMethodGroupEndWithDocs(GuiParticleGraphInspector)

+ 0 - 1
engine/source/gui/guiControl.cc

@@ -1020,7 +1020,6 @@ void GuiControl::setControlProfile(GuiControlProfile *prof)
    mProfile = prof;
    if(mAwake)
       mProfile->incRefCount();
-
 }
 
 void GuiControl::onPreRender()

+ 1 - 1
engine/source/torqueConfig.h

@@ -29,7 +29,7 @@
 #define TORQUE_GAME_ENGINE          1000
 
 /// What's the name of your game? Used in a variety of places.
-#define TORQUE_GAME_NAME            "Torque 2D MIT"
+#define TORQUE_GAME_NAME            "Torque 2D 4.0: Rocket Edition"
 
 /// Human readable version string.
 #define TORQUE_GAME_VERSION_STRING  "Open Source"