1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 package com.badlogic.gdx.tools.flame; 17 18 import java.awt.BorderLayout; 19 import java.awt.Component; 20 import java.awt.EventQueue; 21 import java.awt.FileDialog; 22 import java.awt.Graphics; 23 import java.awt.GridBagConstraints; 24 import java.awt.GridBagLayout; 25 import java.awt.Insets; 26 import java.awt.event.ActionEvent; 27 import java.awt.event.ActionListener; 28 import java.awt.event.WindowAdapter; 29 import java.awt.event.WindowEvent; 30 import java.io.File; 31 import java.io.Writer; 32 33 import javax.swing.BorderFactory; 34 import javax.swing.DefaultComboBoxModel; 35 import javax.swing.JButton; 36 import javax.swing.JComboBox; 37 import javax.swing.JComponent; 38 import javax.swing.JFrame; 39 import javax.swing.JOptionPane; 40 import javax.swing.JPanel; 41 import javax.swing.JScrollPane; 42 import javax.swing.JSplitPane; 43 import javax.swing.UIManager; 44 import javax.swing.UIManager.LookAndFeelInfo; 45 import javax.swing.border.CompoundBorder; 46 import javax.swing.plaf.basic.BasicSplitPaneUI; 47 48 import com.badlogic.gdx.ApplicationListener; 49 import com.badlogic.gdx.Gdx; 50 import com.badlogic.gdx.InputMultiplexer; 51 import com.badlogic.gdx.assets.AssetDescriptor; 52 import com.badlogic.gdx.assets.AssetErrorListener; 53 import com.badlogic.gdx.assets.AssetLoaderParameters; 54 import com.badlogic.gdx.assets.AssetManager; 55 import com.badlogic.gdx.assets.loaders.AssetLoader; 56 import com.badlogic.gdx.assets.loaders.resolvers.AbsoluteFileHandleResolver; 57 import com.badlogic.gdx.assets.loaders.resolvers.InternalFileHandleResolver; 58 import com.badlogic.gdx.backends.lwjgl.LwjglCanvas; 59 import com.badlogic.gdx.files.FileHandle; 60 import com.badlogic.gdx.graphics.Color; 61 import com.badlogic.gdx.graphics.GL20; 62 import com.badlogic.gdx.graphics.PerspectiveCamera; 63 import com.badlogic.gdx.graphics.Texture; 64 import com.badlogic.gdx.graphics.VertexAttributes.Usage; 65 import com.badlogic.gdx.graphics.g2d.TextureAtlas; 66 import com.badlogic.gdx.graphics.g3d.Environment; 67 import com.badlogic.gdx.graphics.g3d.Material; 68 import com.badlogic.gdx.graphics.g3d.Model; 69 import com.badlogic.gdx.graphics.g3d.ModelBatch; 70 import com.badlogic.gdx.graphics.g3d.ModelInstance; 71 import com.badlogic.gdx.graphics.g3d.attributes.BlendingAttribute; 72 import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; 73 import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight; 74 import com.badlogic.gdx.graphics.g3d.particles.ParticleController; 75 import com.badlogic.gdx.graphics.g3d.particles.ParticleEffect; 76 import com.badlogic.gdx.graphics.g3d.particles.ParticleEffectLoader; 77 import com.badlogic.gdx.graphics.g3d.particles.ParticleEffectLoader.ParticleEffectSaveParameter; 78 import com.badlogic.gdx.graphics.g3d.particles.ParticleSystem; 79 import com.badlogic.gdx.graphics.g3d.particles.batches.BillboardParticleBatch; 80 import com.badlogic.gdx.graphics.g3d.particles.batches.ModelInstanceParticleBatch; 81 import com.badlogic.gdx.graphics.g3d.particles.batches.ParticleBatch; 82 import com.badlogic.gdx.graphics.g3d.particles.batches.PointSpriteParticleBatch; 83 import com.badlogic.gdx.graphics.g3d.particles.emitters.Emitter; 84 import com.badlogic.gdx.graphics.g3d.particles.emitters.RegularEmitter; 85 import com.badlogic.gdx.graphics.g3d.particles.influencers.ColorInfluencer; 86 import com.badlogic.gdx.graphics.g3d.particles.influencers.DynamicsInfluencer; 87 import com.badlogic.gdx.graphics.g3d.particles.influencers.Influencer; 88 import com.badlogic.gdx.graphics.g3d.particles.influencers.ModelInfluencer; 89 import com.badlogic.gdx.graphics.g3d.particles.influencers.ParticleControllerFinalizerInfluencer; 90 import com.badlogic.gdx.graphics.g3d.particles.influencers.ParticleControllerInfluencer; 91 import com.badlogic.gdx.graphics.g3d.particles.influencers.RegionInfluencer; 92 import com.badlogic.gdx.graphics.g3d.particles.influencers.ScaleInfluencer; 93 import com.badlogic.gdx.graphics.g3d.particles.influencers.SpawnInfluencer; 94 import com.badlogic.gdx.graphics.g3d.particles.renderers.BillboardRenderer; 95 import com.badlogic.gdx.graphics.g3d.particles.renderers.ModelInstanceRenderer; 96 import com.badlogic.gdx.graphics.g3d.particles.renderers.ParticleControllerControllerRenderer; 97 import com.badlogic.gdx.graphics.g3d.particles.renderers.PointSpriteRenderer; 98 import com.badlogic.gdx.graphics.g3d.particles.values.GradientColorValue; 99 import com.badlogic.gdx.graphics.g3d.particles.values.NumericValue; 100 import com.badlogic.gdx.graphics.g3d.utils.CameraInputController; 101 import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder; 102 import com.badlogic.gdx.math.MathUtils; 103 import com.badlogic.gdx.math.RandomXS128; 104 import com.badlogic.gdx.scenes.scene2d.InputEvent; 105 import com.badlogic.gdx.scenes.scene2d.Stage; 106 import com.badlogic.gdx.scenes.scene2d.ui.Label; 107 import com.badlogic.gdx.scenes.scene2d.ui.Skin; 108 import com.badlogic.gdx.scenes.scene2d.ui.Table; 109 import com.badlogic.gdx.scenes.scene2d.ui.TextButton; 110 import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; 111 import com.badlogic.gdx.utils.Array; 112 import com.badlogic.gdx.utils.StreamUtils; 113 import com.badlogic.gdx.utils.StringBuilder; 114 115 /** @author Inferno */ 116 public class FlameMain extends JFrame implements AssetErrorListener { 117 public static final String DEFAULT_FONT = "default.fnt", 118 DEFAULT_BILLBOARD_PARTICLE = "pre_particle.png", 119 DEFAULT_MODEL_PARTICLE = "monkey.g3db", 120 //DEFAULT_PFX = "default.pfx", 121 DEFAULT_TEMPLATE_PFX = "defaultTemplate.pfx", 122 DEFAULT_SKIN = "uiskin.json"; 123 124 public static final int EVT_ASSET_RELOADED = 0; 125 126 static class ControllerData { 127 public boolean enabled = true; 128 public ParticleController controller; 129 public ControllerData (ParticleController emitter) { 130 controller = emitter; 131 } 132 } 133 134 private static class InfluencerWrapper<T>{ 135 String string; 136 Class<Influencer> type; 137 public InfluencerWrapper(String string, Class<Influencer> type){ 138 this.string = string; 139 this.type = type; 140 } 141 142 @Override 143 public String toString () { 144 return string; 145 } 146 } 147 148 public enum ControllerType{ 149 Billboard( "Billboard", new InfluencerWrapper[]{ 150 new InfluencerWrapper("Single Color", ColorInfluencer.Single.class), 151 new InfluencerWrapper("Random Color", ColorInfluencer.Random.class), 152 new InfluencerWrapper("Single Region", RegionInfluencer.Single.class), 153 new InfluencerWrapper("Random Region", RegionInfluencer.Random.class), 154 new InfluencerWrapper("Animated Region", RegionInfluencer.Animated.class), 155 new InfluencerWrapper("Scale", ScaleInfluencer.class), 156 new InfluencerWrapper("Spawn", SpawnInfluencer.class), 157 new InfluencerWrapper("Dynamics", DynamicsInfluencer.class)}), 158 PointSprite("Point Sprite", new InfluencerWrapper[]{ 159 new InfluencerWrapper("Single Color", ColorInfluencer.Single.class), 160 new InfluencerWrapper("Random Color", ColorInfluencer.Random.class), 161 new InfluencerWrapper("Single Region", RegionInfluencer.Single.class), 162 new InfluencerWrapper("Random Region", RegionInfluencer.Random.class), 163 new InfluencerWrapper("Animated Region", RegionInfluencer.Animated.class), 164 new InfluencerWrapper("Scale", ScaleInfluencer.class), 165 new InfluencerWrapper("Spawn", SpawnInfluencer.class), 166 new InfluencerWrapper("Dynamics", DynamicsInfluencer.class)}), 167 ModelInstance( "Model Instance", new InfluencerWrapper[]{ 168 new InfluencerWrapper("Single Color", ColorInfluencer.Single.class), 169 new InfluencerWrapper("Random Color", ColorInfluencer.Random.class), 170 new InfluencerWrapper("Single Model", ModelInfluencer.Single.class), 171 new InfluencerWrapper("Random Model", ModelInfluencer.Random.class), 172 new InfluencerWrapper("Scale", ScaleInfluencer.class), 173 new InfluencerWrapper("Spawn", SpawnInfluencer.class), 174 new InfluencerWrapper("Dynamics", DynamicsInfluencer.class)}), 175 ParticleController("Particle Controller", new InfluencerWrapper[]{ 176 new InfluencerWrapper("Single Particle Controller", ParticleControllerInfluencer.Single.class), 177 new InfluencerWrapper("Random Particle Controller", ParticleControllerInfluencer.Random.class), 178 new InfluencerWrapper("Scale", ScaleInfluencer.class), 179 new InfluencerWrapper("Spawn", SpawnInfluencer.class), 180 new InfluencerWrapper("Dynamics", DynamicsInfluencer.class)}); 181 182 public String desc; 183 public InfluencerWrapper[] wrappers; 184 private ControllerType(String desc, InfluencerWrapper[] wrappers){ 185 this.desc = desc; 186 this.wrappers = wrappers; 187 } 188 } 189 190 LwjglCanvas lwjglCanvas; 191 JPanel controllerPropertiesPanel; 192 JPanel editorPropertiesPanel; 193 EffectPanel effectPanel; 194 JSplitPane splitPane; 195 NumericValue fovValue; 196 NumericValue deltaMultiplier; 197 GradientColorValue backgroundColor; 198 AppRenderer renderer; 199 AssetManager assetManager; 200 JComboBox influencerBox; 201 202 private ParticleEffect effect; 203 /** READ only */ 204 public Array<ControllerData> controllersData; 205 ParticleSystem particleSystem; 206 207 public FlameMain () { 208 super("Flame"); 209 MathUtils.random = new RandomXS128(); 210 particleSystem = ParticleSystem.get(); 211 effect = new ParticleEffect(); 212 particleSystem.add(effect); 213 assetManager = new AssetManager(); 214 assetManager.setErrorListener(this); 215 assetManager.setLoader(ParticleEffect.class, new ParticleEffectLoader(new InternalFileHandleResolver())); 216 controllersData = new Array<ControllerData>(); 217 218 lwjglCanvas = new LwjglCanvas(renderer = new AppRenderer()); 219 addWindowListener(new WindowAdapter() { 220 public void windowClosed (WindowEvent event) { 221 //System.exit(0); 222 Gdx.app.exit(); 223 } 224 }); 225 226 initializeComponents(); 227 228 setSize(1280, 950); 229 setLocationRelativeTo(null); 230 setDefaultCloseOperation(DISPOSE_ON_CLOSE); 231 setVisible(true); 232 } 233 234 public ControllerType getControllerType () { 235 ParticleController controller = getEmitter(); 236 ControllerType type = null; 237 if(controller.renderer instanceof BillboardRenderer) 238 type = ControllerType.Billboard; 239 else if(controller.renderer instanceof PointSpriteRenderer) 240 type = ControllerType.PointSprite; 241 else if(controller.renderer instanceof ModelInstanceRenderer) 242 type = ControllerType.ModelInstance; 243 else if(controller.renderer instanceof ParticleControllerControllerRenderer) 244 type = ControllerType.ParticleController; 245 return type; 246 } 247 248 void reloadRows () { 249 EventQueue.invokeLater(new Runnable() { 250 public void run () { 251 252 //Ensure no listener is left watching for events 253 EventManager.get().clear(); 254 255 //Clear 256 editorPropertiesPanel.removeAll(); 257 influencerBox.removeAllItems(); 258 controllerPropertiesPanel.removeAll(); 259 260 //Editor props 261 addRow(editorPropertiesPanel, new NumericPanel(FlameMain.this, fovValue, "Field of View", "")); 262 addRow(editorPropertiesPanel, new NumericPanel(FlameMain.this, deltaMultiplier, "Delta multiplier", "")); 263 addRow(editorPropertiesPanel, new GradientPanel(FlameMain.this,backgroundColor, "Background color", "", true)); 264 addRow(editorPropertiesPanel, new DrawPanel(FlameMain.this, "Draw", "")); 265 addRow(editorPropertiesPanel, new TextureLoaderPanel(FlameMain.this, "Texture", "")); 266 addRow(editorPropertiesPanel, new BillboardBatchPanel(FlameMain.this, renderer.billboardBatch), 1, 1); 267 editorPropertiesPanel.repaint(); 268 269 //Controller props 270 ParticleController controller = getEmitter(); 271 if(controller != null){ 272 //Reload available influencers 273 DefaultComboBoxModel model = (DefaultComboBoxModel)influencerBox.getModel(); 274 ControllerType type = getControllerType(); 275 if(type != null){ 276 for(Object value : type.wrappers) 277 model.addElement(value); 278 } 279 JPanel panel = null; 280 addRow(controllerPropertiesPanel, getPanel(controller.emitter)); 281 for(int i=0, c = controller.influencers.size; i < c; ++i){ 282 Influencer influencer = (Influencer)controller.influencers.get(i); 283 panel = getPanel(influencer); 284 if(panel != null) 285 addRow(controllerPropertiesPanel, panel, 1, i == c-1 ? 1 : 0); 286 } 287 for (Component component : controllerPropertiesPanel.getComponents()) 288 if (component instanceof EditorPanel) 289 ((EditorPanel)component).update(FlameMain.this); 290 } 291 controllerPropertiesPanel.repaint(); 292 } 293 }); 294 } 295 296 protected JPanel getPanel (Emitter emitter) { 297 if(emitter instanceof RegularEmitter){ 298 return new RegularEmitterPanel(this, (RegularEmitter) emitter); 299 } 300 return null; 301 } 302 303 protected JPanel getPanel (Influencer influencer) { 304 if(influencer instanceof ColorInfluencer.Single){ 305 return new ColorInfluencerPanel(this, (ColorInfluencer.Single) influencer); 306 } 307 if(influencer instanceof ColorInfluencer.Random){ 308 return new InfluencerPanel<ColorInfluencer.Random>(this, (ColorInfluencer.Random) influencer, 309 "Random Color Influencer", "Assign a random color to the particles") {}; 310 } 311 else if(influencer instanceof ScaleInfluencer){ 312 return new ScaleInfluencerPanel(this, (ScaleInfluencer)influencer); 313 } 314 else if(influencer instanceof SpawnInfluencer){ 315 return new SpawnInfluencerPanel(this, (SpawnInfluencer)influencer); 316 } 317 else if(influencer instanceof DynamicsInfluencer){ 318 return new DynamicsInfluencerPanel(this, (DynamicsInfluencer)influencer); 319 } 320 else if(influencer instanceof ModelInfluencer){ 321 boolean single = influencer instanceof ModelInfluencer.Single; 322 String name = single ? "Model Single Influencer" : "Model Random Influencer"; 323 return new ModelInfluencerPanel(this, (ModelInfluencer)influencer, single, name, "Defines what model will be used for the particles"); 324 } 325 else if(influencer instanceof ParticleControllerInfluencer){ 326 boolean single = influencer instanceof ParticleControllerInfluencer.Single; 327 String name = single ? "Particle Controller Single Influencer" : "Particle Controller Random Influencer"; 328 return new ParticleControllerInfluencerPanel(this, (ParticleControllerInfluencer)influencer, single, name, "Defines what controller will be used for the particles"); 329 } 330 else if(influencer instanceof RegionInfluencer.Single){ 331 return new RegionInfluencerPanel(this, "Billboard Single Region Influencer", 332 "Assign the chosen region to the particles", (RegionInfluencer.Single)influencer); 333 } 334 else if(influencer instanceof RegionInfluencer.Animated){ 335 return new RegionInfluencerPanel(this, "Billboard Animated Region Influencer", 336 "Animates the region of the particles", (RegionInfluencer.Animated)influencer); 337 } 338 else if(influencer instanceof RegionInfluencer.Random){ 339 return new RegionInfluencerPanel(this, "Billboard Random Region Influencer", 340 "Assigns a randomly picked (among those selected) region to the particles", (RegionInfluencer.Random)influencer); 341 } 342 else if(influencer instanceof ParticleControllerFinalizerInfluencer){ 343 return new InfluencerPanel<ParticleControllerFinalizerInfluencer>(this, (ParticleControllerFinalizerInfluencer) influencer, 344 "ParticleControllerFinalizer Influencer", "This is required when dealing with a controller of controllers, it will update the controller assigned to each particle, it MUST be the last influencer always.", 345 true, false) {}; 346 } 347 348 return null; 349 } 350 351 protected JPanel getPanel (ParticleBatch renderer) { 352 if(renderer instanceof PointSpriteParticleBatch){ 353 return new EmptyPanel(this, "Point Sprite Batch", "It renders particles as point sprites."); 354 } 355 if(renderer instanceof BillboardParticleBatch){ 356 return new BillboardBatchPanel(this, (BillboardParticleBatch) renderer); 357 } 358 else if(renderer instanceof ModelInstanceParticleBatch){ 359 return new EmptyPanel(this, "Model Instance Batch", "It renders particles as model instances."); 360 } 361 362 return null; 363 } 364 365 void addRow(JPanel panel, JPanel row) { 366 addRow(panel, row, 1, 0); 367 } 368 369 void addRow(JPanel panel, JPanel row, float wx, float wy) { 370 row.setBorder(BorderFactory.createMatteBorder(0, 0, 2, 0, java.awt.Color.black)); 371 panel.add(row, new GridBagConstraints(0, -1, 1, 1, wx, wy, GridBagConstraints.NORTH, GridBagConstraints.HORIZONTAL, 372 new Insets(0, 0, 0, 0), 0, 0)); 373 } 374 375 public void setVisible (String name, boolean visible) { 376 for (Component component : controllerPropertiesPanel.getComponents()) 377 if (component instanceof EditorPanel && ((EditorPanel)component).getName().equals(name)) component.setVisible(visible); 378 } 379 380 private void rebuildActiveControllers () { 381 //rebuild list 382 Array<ParticleController> effectControllers = effect.getControllers(); 383 effectControllers.clear(); 384 for(ControllerData controllerData : controllersData){ 385 if(controllerData.enabled) 386 effectControllers.add(controllerData.controller); 387 } 388 //System.out.println("rebuilding active controllers"); 389 390 effect.init(); 391 effect.start(); 392 } 393 394 public ParticleController getEmitter () { 395 return effectPanel.editIndex >=0 ? controllersData.get(effectPanel.editIndex).controller : null; 396 } 397 398 public void addEmitter (ParticleController emitter) { 399 controllersData.add(new ControllerData(emitter)); 400 rebuildActiveControllers(); 401 } 402 403 public void removeEmitter (int row) { 404 controllersData.removeIndex(row).controller.dispose(); 405 rebuildActiveControllers(); 406 } 407 408 public void setEnabled (int emitterIndex, boolean enabled) { 409 ControllerData data = controllersData.get(emitterIndex); 410 data.enabled = enabled; 411 rebuildActiveControllers(); 412 } 413 414 public boolean isEnabled (int emitterIndex) { 415 return controllersData.get(emitterIndex).enabled; 416 } 417 418 private void initializeComponents () { 419 splitPane = new JSplitPane(); 420 splitPane.setUI(new BasicSplitPaneUI() { 421 public void paint (Graphics g, JComponent jc) { 422 } 423 }); 424 splitPane.setDividerSize(4); 425 getContentPane().add(splitPane, BorderLayout.CENTER); 426 { 427 JSplitPane rightSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 428 rightSplit.setUI(new BasicSplitPaneUI() { 429 public void paint (Graphics g, JComponent jc) { 430 } 431 }); 432 rightSplit.setDividerSize(4); 433 splitPane.add(rightSplit, JSplitPane.RIGHT); 434 435 { 436 JPanel propertiesPanel = new JPanel(new GridBagLayout()); 437 rightSplit.add(propertiesPanel, JSplitPane.TOP); 438 propertiesPanel.setBorder(new CompoundBorder(BorderFactory.createEmptyBorder(3, 0, 6, 6), BorderFactory 439 .createTitledBorder("Editor Properties"))); 440 { 441 JScrollPane scroll = new JScrollPane(); 442 propertiesPanel.add(scroll, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTH, 443 GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); 444 scroll.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); 445 { 446 editorPropertiesPanel = new JPanel(new GridBagLayout()); 447 scroll.setViewportView(editorPropertiesPanel); 448 scroll.getVerticalScrollBar().setUnitIncrement(70); 449 } 450 } 451 } 452 453 { 454 JSplitPane rightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 455 rightSplitPane.setUI(new BasicSplitPaneUI() { 456 public void paint (Graphics g, JComponent jc) { 457 } 458 }); 459 rightSplitPane.setDividerSize(4); 460 rightSplitPane.setDividerLocation(100); 461 rightSplit.add(rightSplitPane, JSplitPane.BOTTOM); 462 463 JPanel propertiesPanel = new JPanel(new GridBagLayout()); 464 rightSplitPane.add(propertiesPanel, JSplitPane.TOP); 465 propertiesPanel.setBorder(new CompoundBorder(BorderFactory.createEmptyBorder(3, 0, 6, 6), BorderFactory 466 .createTitledBorder("Influencers"))); 467 { 468 JScrollPane scroll = new JScrollPane(); 469 propertiesPanel.add(scroll, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTH, 470 GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); 471 scroll.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); 472 { 473 JPanel influencersPanel = new JPanel(new GridBagLayout()); 474 influencerBox = new JComboBox(new DefaultComboBoxModel()); 475 JButton addInfluencerButton = new JButton("Add"); 476 addInfluencerButton.addActionListener(new ActionListener() { 477 @Override 478 public void actionPerformed (ActionEvent e) { 479 InfluencerWrapper wrapper = (InfluencerWrapper)influencerBox.getSelectedItem(); 480 ParticleController controller = getEmitter(); 481 if(controller != null) 482 addInfluencer(wrapper.type, controller); 483 484 } 485 }); 486 influencersPanel.add(influencerBox, new GridBagConstraints(0, 0, 1, 1, 0, 1, GridBagConstraints.NORTHWEST, 487 GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); 488 influencersPanel.add(addInfluencerButton, new GridBagConstraints(1, 0, 1, 1, 1, 1, GridBagConstraints.NORTHWEST, 489 GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0)); 490 scroll.setViewportView(influencersPanel); 491 scroll.getVerticalScrollBar().setUnitIncrement(70); 492 } 493 } 494 495 496 propertiesPanel = new JPanel(new GridBagLayout()); 497 rightSplitPane.add(propertiesPanel, JSplitPane.BOTTOM); 498 propertiesPanel.setBorder(new CompoundBorder(BorderFactory.createEmptyBorder(3, 0, 6, 6), BorderFactory 499 .createTitledBorder("Particle Controller Components"))); 500 { 501 JScrollPane scroll = new JScrollPane(); 502 propertiesPanel.add(scroll, new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTH, 503 GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); 504 scroll.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); 505 { 506 controllerPropertiesPanel = new JPanel(new GridBagLayout()); 507 scroll.setViewportView(controllerPropertiesPanel); 508 scroll.getVerticalScrollBar().setUnitIncrement(70); 509 } 510 } 511 } 512 513 rightSplit.setDividerLocation(250); 514 515 } 516 { 517 JSplitPane leftSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT); 518 leftSplit.setUI(new BasicSplitPaneUI() { 519 public void paint (Graphics g, JComponent jc) { 520 } 521 }); 522 leftSplit.setDividerSize(4); 523 splitPane.add(leftSplit, JSplitPane.LEFT); 524 { 525 JPanel spacer = new JPanel(new BorderLayout()); 526 leftSplit.add(spacer, JSplitPane.TOP); 527 spacer.add(lwjglCanvas.getCanvas()); 528 spacer.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 4)); 529 } 530 { 531 JPanel emittersPanel = new JPanel(new BorderLayout()); 532 leftSplit.add(emittersPanel, JSplitPane.BOTTOM); 533 emittersPanel.setBorder(new CompoundBorder(BorderFactory.createEmptyBorder(0, 6, 6, 0), BorderFactory 534 .createTitledBorder("Particle Controllers"))); 535 { 536 effectPanel = new EffectPanel(this); 537 emittersPanel.add(effectPanel); 538 } 539 } 540 leftSplit.setDividerLocation(625); 541 } 542 splitPane.setDividerLocation(500); 543 } 544 545 protected void addInfluencer (Class<Influencer> type, ParticleController controller) { 546 if(controller.findInfluencer(type) != null) return; 547 548 try { 549 controller.end(); 550 551 Influencer newInfluencer = type.newInstance(); 552 boolean replaced = false; 553 if(ColorInfluencer.class.isAssignableFrom(type)){ 554 replaced = controller.replaceInfluencer(ColorInfluencer.class, (ColorInfluencer)newInfluencer); 555 } 556 else if(RegionInfluencer.class.isAssignableFrom(type)){ 557 replaced = controller.replaceInfluencer(RegionInfluencer.class, (RegionInfluencer)newInfluencer); 558 } 559 else if(ModelInfluencer.class.isAssignableFrom(type)){ 560 ModelInfluencer newModelInfluencer = (ModelInfluencer) newInfluencer; 561 ModelInfluencer currentInfluencer = (ModelInfluencer)controller.findInfluencer(ModelInfluencer.class); 562 if(currentInfluencer != null){ 563 newModelInfluencer.models.add(currentInfluencer.models.first()); 564 } 565 replaced = controller.replaceInfluencer(ModelInfluencer.class, (ModelInfluencer)newInfluencer); 566 } 567 else if(ParticleControllerInfluencer.class.isAssignableFrom(type)){ 568 ParticleControllerInfluencer newModelInfluencer = (ParticleControllerInfluencer) newInfluencer; 569 ParticleControllerInfluencer currentInfluencer = (ParticleControllerInfluencer)controller.findInfluencer(ParticleControllerInfluencer.class); 570 if(currentInfluencer != null){ 571 newModelInfluencer.templates.add(currentInfluencer.templates.first()); 572 } 573 replaced = controller.replaceInfluencer(ParticleControllerInfluencer.class, (ParticleControllerInfluencer)newInfluencer); 574 } 575 576 if(!replaced){ 577 if(getControllerType() != ControllerType.ParticleController) 578 controller.influencers.add(newInfluencer); 579 else{ 580 Influencer finalizer = controller.influencers.pop(); 581 controller.influencers.add(newInfluencer); 582 controller.influencers.add(finalizer); 583 } 584 } 585 586 controller.init(); 587 effect.start(); 588 reloadRows(); 589 } catch (Exception e1) { 590 e1.printStackTrace(); 591 } 592 } 593 594 protected boolean canAddInfluencer (Class influencerType, ParticleController controller) { 595 boolean hasSameInfluencer = controller.findInfluencer(influencerType) != null; 596 if(!hasSameInfluencer){ 597 if( (ColorInfluencer.Single.class.isAssignableFrom(influencerType) && controller.findInfluencer(ColorInfluencer.Random.class) != null) || 598 (ColorInfluencer.Random.class.isAssignableFrom(influencerType) && controller.findInfluencer(ColorInfluencer.Single.class) != null) ){ 599 return false; 600 } 601 602 if(RegionInfluencer.class.isAssignableFrom(influencerType)){ 603 return controller.findInfluencer(RegionInfluencer.class) == null; 604 } 605 else if(ModelInfluencer.class.isAssignableFrom(influencerType)){ 606 return controller.findInfluencer(ModelInfluencer.class) == null; 607 } 608 else if(ParticleControllerInfluencer.class.isAssignableFrom(influencerType)){ 609 return controller.findInfluencer(ParticleControllerInfluencer.class) == null; 610 } 611 } 612 return !hasSameInfluencer; 613 } 614 615 class AppRenderer implements ApplicationListener { 616 //Stats 617 private float maxActiveTimer; 618 private int maxActive, lastMaxActive; 619 boolean isUpdate = true; 620 621 //Controls 622 private CameraInputController cameraInputController; 623 624 //UI 625 private Stage ui; 626 TextButton playPauseButton; 627 private Label fpsLabel, pointCountLabel, billboardCountLabel, modelInstanceCountLabel, maxLabel; 628 StringBuilder stringBuilder; 629 630 //Render 631 public PerspectiveCamera worldCamera; 632 private boolean isDrawXYZ, isDrawXZPlane, isDrawXYPlane; 633 private Array<Model> models; 634 private ModelInstance xyzInstance, xzPlaneInstance, xyPlaneInstance; 635 private Environment environment; 636 private ModelBatch modelBatch; 637 PointSpriteParticleBatch pointSpriteBatch; 638 BillboardParticleBatch billboardBatch; 639 ModelInstanceParticleBatch modelInstanceParticleBatch; 640 641 public void create () { 642 if (ui != null) return; 643 int w = Gdx.graphics.getWidth(), h = Gdx.graphics.getHeight(); 644 modelBatch = new ModelBatch(); 645 environment = new Environment(); 646 environment.add(new DirectionalLight().set(Color.WHITE, 0,0,-1)); 647 648 worldCamera = new PerspectiveCamera(67, w, h); 649 worldCamera.position.set(10, 10, 10); 650 worldCamera.lookAt(0,0,0); 651 worldCamera.near = 0.1f; 652 worldCamera.far = 300f; 653 worldCamera.update(); 654 655 cameraInputController = new CameraInputController(worldCamera); 656 657 //Batches 658 pointSpriteBatch = new PointSpriteParticleBatch(); 659 pointSpriteBatch.setCamera(worldCamera); 660 661 billboardBatch = new BillboardParticleBatch(); 662 billboardBatch.setCamera(worldCamera); 663 modelInstanceParticleBatch = new ModelInstanceParticleBatch(); 664 665 particleSystem.add(billboardBatch); 666 particleSystem.add(pointSpriteBatch); 667 particleSystem.add(modelInstanceParticleBatch); 668 669 fovValue = new NumericValue(); 670 fovValue.setValue(67); 671 fovValue.setActive(true); 672 673 deltaMultiplier = new NumericValue(); 674 deltaMultiplier.setValue(1.0f); 675 deltaMultiplier.setActive(true); 676 677 backgroundColor = new GradientColorValue(); 678 Color color = Color.valueOf("878787"); 679 backgroundColor.setColors(new float[] { color.r, color.g, color.b}); 680 681 models = new Array<Model>(); 682 ModelBuilder builder = new ModelBuilder(); 683 Model xyzModel = builder.createXYZCoordinates(10, new Material(), Usage.Position|Usage.ColorPacked), 684 planeModel = builder.createLineGrid(10, 10, 1, 1, new Material(ColorAttribute.createDiffuse(Color.WHITE)), Usage.Position); 685 models.add(xyzModel); 686 models.add(planeModel); 687 xyzInstance = new ModelInstance(xyzModel); 688 xzPlaneInstance = new ModelInstance(planeModel); 689 xyPlaneInstance = new ModelInstance(planeModel); 690 xyPlaneInstance.transform.rotate(1f, 0f, 0f, 90f); 691 692 setDrawXYZ(true); 693 setDrawXZPlane(true); 694 695 696 //Load default resources 697 ParticleEffectLoader.ParticleEffectLoadParameter params = new ParticleEffectLoader.ParticleEffectLoadParameter(particleSystem.getBatches()); 698 assetManager.load(DEFAULT_BILLBOARD_PARTICLE, Texture.class); 699 assetManager.load(DEFAULT_MODEL_PARTICLE, Model.class); 700 assetManager.load(DEFAULT_SKIN, Skin.class); 701 assetManager.load(DEFAULT_TEMPLATE_PFX, ParticleEffect.class, params); 702 703 assetManager.finishLoading(); 704 assetManager.setLoader(ParticleEffect.class, new ParticleEffectLoader(new AbsoluteFileHandleResolver())); 705 assetManager.get(DEFAULT_MODEL_PARTICLE, Model.class).materials.get(0).set(new BlendingAttribute(GL20.GL_ONE, GL20.GL_ONE_MINUS_SRC_ALPHA, 1)); 706 707 //Ui 708 stringBuilder = new StringBuilder(); 709 Skin skin = assetManager.get(DEFAULT_SKIN, Skin.class); 710 ui = new Stage(); 711 fpsLabel = new Label("", skin); 712 pointCountLabel = new Label("", skin); 713 billboardCountLabel = new Label("", skin); 714 modelInstanceCountLabel = new Label("", skin); 715 716 maxLabel = new Label("", skin); 717 playPauseButton = new TextButton("Pause", skin); 718 playPauseButton.addListener(new ClickListener(){ 719 @Override 720 public void clicked (InputEvent event, float x, float y) { 721 isUpdate = !isUpdate; 722 playPauseButton.setText(isUpdate ? "Pause" : "Play"); 723 } 724 }); 725 Table table = new Table(skin); 726 table.setFillParent(true); 727 table.pad(5); 728 table.add(fpsLabel).expandX().left().row(); 729 table.add(pointCountLabel).expandX().left().row(); 730 table.add(billboardCountLabel).expandX().left().row(); 731 table.add(modelInstanceCountLabel).expandX().left().row(); 732 table.add(maxLabel).expandX().left().row(); 733 table.add(playPauseButton).expand().bottom().left().row(); 734 ui.addActor(table); 735 736 setTexture((Texture)assetManager.get(DEFAULT_BILLBOARD_PARTICLE)); 737 effectPanel.createDefaultEmitter(ControllerType.Billboard, true, true); 738 } 739 740 @Override 741 public void resize (int width, int height) { 742 Gdx.input.setInputProcessor(new InputMultiplexer(ui, cameraInputController)); 743 Gdx.gl.glViewport(0, 0, width, height); 744 745 worldCamera.viewportWidth = width; 746 worldCamera.viewportHeight = height; 747 worldCamera.update(); 748 ui.getViewport().setWorldSize(width, height); 749 ui.getViewport().update(width, height, true); 750 } 751 752 public void render () { 753 //float delta = Math.max(0, Gdx.graphics.getDeltaTime() * deltaMultiplier.getValue()); 754 update(); 755 renderWorld(); 756 } 757 758 private void update () { 759 worldCamera.fieldOfView = fovValue.getValue(); 760 worldCamera.update(); 761 cameraInputController.update(); 762 if(isUpdate){ 763 particleSystem.update(); 764 //Update ui 765 stringBuilder.delete(0, stringBuilder.length); 766 stringBuilder.append("Point Sprites : ").append(pointSpriteBatch.getBufferedCount()); 767 pointCountLabel.setText(stringBuilder); 768 stringBuilder.delete(0, stringBuilder.length); 769 stringBuilder.append("Billboards : ").append(billboardBatch.getBufferedCount()); 770 billboardCountLabel.setText(stringBuilder); 771 stringBuilder.delete(0, stringBuilder.length); 772 stringBuilder.append("Model Instances : ").append(modelInstanceParticleBatch.getBufferedCount()); 773 modelInstanceCountLabel.setText(stringBuilder); 774 } 775 stringBuilder.delete(0, stringBuilder.length); 776 stringBuilder.append("FPS : ").append(Gdx.graphics.getFramesPerSecond()); 777 fpsLabel.setText(stringBuilder); 778 ui.act(Gdx.graphics.getDeltaTime()); 779 } 780 781 private void renderWorld () { 782 float[] colors = backgroundColor.getColors(); 783 Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); 784 Gdx.gl.glClearColor(colors[0], colors[1], colors[2], 0); 785 modelBatch.begin(worldCamera); 786 if(isDrawXYZ) modelBatch.render(xyzInstance); 787 if(isDrawXZPlane) modelBatch.render(xzPlaneInstance); 788 if(isDrawXYPlane) modelBatch.render(xyPlaneInstance); 789 particleSystem.begin(); 790 particleSystem.draw(); 791 particleSystem.end(); 792 793 //Draw 794 modelBatch.render(particleSystem, environment); 795 modelBatch.end(); 796 ui.draw(); 797 } 798 799 @Override 800 public void dispose () {} 801 802 @Override 803 public void pause () {} 804 805 @Override 806 public void resume () {} 807 808 public void setDrawXYZ(boolean isDraw) 809 { 810 isDrawXYZ = isDraw; 811 } 812 813 public boolean IsDrawXYZ() 814 { 815 return isDrawXYZ; 816 } 817 818 public void setDrawXZPlane(boolean isDraw) 819 { 820 isDrawXZPlane = isDraw; 821 } 822 823 public boolean IsDrawXZPlane() 824 { 825 return isDrawXZPlane; 826 } 827 828 public void setDrawXYPlane(boolean isDraw) 829 { 830 isDrawXYPlane = isDraw; 831 } 832 833 public boolean IsDrawXYPlane() 834 { 835 return isDrawXYPlane; 836 } 837 } 838 839 public static void main (String[] args) { 840 for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) { 841 if ("Nimbus".equals(info.getName())) { 842 try { 843 UIManager.setLookAndFeel(info.getClassName()); 844 } catch (Throwable ignored) { 845 } 846 break; 847 } 848 } 849 EventQueue.invokeLater(new Runnable() { 850 public void run () { 851 new FlameMain(); 852 } 853 }); 854 } 855 856 public AppRenderer getRenderer() { 857 return renderer; 858 } 859 860 String lastDir; 861 public File showFileLoadDialog () { 862 return showFileDialog("Open", FileDialog.LOAD); 863 } 864 865 public File showFileSaveDialog () { 866 return showFileDialog("Save", FileDialog.SAVE); 867 } 868 869 private File showFileDialog (String title, int mode ) { 870 FileDialog dialog = new FileDialog(this, title, mode); 871 if (lastDir != null) dialog.setDirectory(lastDir); 872 dialog.setVisible(true); 873 final String file = dialog.getFile(); 874 final String dir = dialog.getDirectory(); 875 if (dir == null || file == null || file.trim().length() == 0) 876 return null; 877 lastDir = dir; 878 return new File(dir, file); 879 } 880 881 @Override 882 public void error (AssetDescriptor asset, Throwable throwable) { 883 throwable.printStackTrace(); 884 } 885 886 public PointSpriteParticleBatch getPointSpriteBatch () { 887 return renderer.pointSpriteBatch; 888 } 889 890 public BillboardParticleBatch getBillboardBatch () { 891 return renderer.billboardBatch; 892 } 893 894 public ModelInstanceParticleBatch getModelInstanceParticleBatch () { 895 return renderer.modelInstanceParticleBatch; 896 } 897 898 public void setAtlas(TextureAtlas atlas){ 899 //currentAtlas = atlas; 900 setTexture(atlas.getTextures().first()); 901 } 902 903 public void setTexture(Texture texture){ 904 renderer.billboardBatch.setTexture(texture); 905 renderer.pointSpriteBatch.setTexture(texture); 906 } 907 908 public Texture getTexture(){ 909 return renderer.billboardBatch.getTexture(); 910 } 911 912 public TextureAtlas getAtlas(Texture texture){ 913 Array<TextureAtlas> atlases = assetManager.getAll(TextureAtlas.class, new Array<TextureAtlas>()); 914 for(TextureAtlas atlas : atlases){ 915 if(atlas.getTextures().contains(texture)) 916 return atlas; 917 } 918 return null; 919 } 920 921 public TextureAtlas getAtlas(){ 922 return getAtlas(renderer.billboardBatch.getTexture()); 923 } 924 925 public boolean isUsingDefaultTexture () { 926 return renderer.billboardBatch.getTexture() == assetManager.get(DEFAULT_BILLBOARD_PARTICLE, Texture.class); 927 } 928 929 public Array<ParticleEffect> getParticleEffects (Array<ParticleController> controllers, Array<ParticleEffect> out) { 930 out.clear(); 931 assetManager.getAll(ParticleEffect.class, out); 932 for(int i=0; i < out.size;){ 933 ParticleEffect effect = out.get(i); 934 Array<ParticleController> effectControllers = effect.getControllers(); 935 boolean remove = true; 936 for(ParticleController controller : controllers){ 937 if(effectControllers.contains(controller, true)){ 938 remove = false; 939 break; 940 } 941 } 942 943 if(remove){ 944 out.removeIndex(i); 945 continue; 946 } 947 948 ++i; 949 } 950 951 return out; 952 } 953 954 public void saveEffect (File file) { 955 Writer fileWriter = null; 956 try { 957 ParticleEffectLoader loader = (ParticleEffectLoader)assetManager.getLoader(ParticleEffect.class); 958 loader.save(effect, new ParticleEffectSaveParameter(new FileHandle(file.getAbsolutePath()), assetManager, particleSystem.getBatches())); 959 } catch (Exception ex) { 960 System.out.println("Error saving effect: " + file.getAbsolutePath()); 961 ex.printStackTrace(); 962 JOptionPane.showMessageDialog(this, "Error saving effect."); 963 } finally { 964 StreamUtils.closeQuietly(fileWriter); 965 } 966 } 967 968 public ParticleEffect openEffect (File file, boolean replaceCurrentWorkspace) { 969 try { 970 ParticleEffect loadedEffect = load(file.getAbsolutePath(), ParticleEffect.class, null, 971 new ParticleEffectLoader.ParticleEffectLoadParameter(particleSystem.getBatches())); 972 loadedEffect = loadedEffect.copy(); 973 loadedEffect.init(); 974 if(replaceCurrentWorkspace){ 975 effect = loadedEffect; 976 controllersData.clear(); 977 particleSystem.removeAll(); 978 particleSystem.add(effect); 979 for(ParticleController controller : effect.getControllers()) 980 controllersData.add(new ControllerData(controller)); 981 rebuildActiveControllers(); 982 } 983 reloadRows(); 984 return loadedEffect; 985 } catch (Exception ex) { 986 System.out.println("Error loading effect: " + file.getAbsolutePath()); 987 ex.printStackTrace(); 988 JOptionPane.showMessageDialog(this, "Error opening effect."); 989 } 990 return null; 991 } 992 993 public <T> T load (String resource, Class<T> type, AssetLoader loader, AssetLoaderParameters<T> params) { 994 String resolvedPath = new String(resource).replaceAll("\\\\", "/"); 995 boolean exist = assetManager.isLoaded(resolvedPath, type); 996 T oldAsset = null; 997 if(exist){ 998 oldAsset = assetManager.get(resolvedPath, type); 999 for(int i=assetManager.getReferenceCount(resolvedPath); i > 0; --i) 1000 assetManager.unload(resolvedPath); 1001 } 1002 1003 AssetLoader<T, AssetLoaderParameters<T>> currentLoader = assetManager.getLoader(type); 1004 if(loader != null) 1005 assetManager.setLoader(type, loader); 1006 1007 assetManager.load(resource, type, params); 1008 assetManager.finishLoading(); 1009 T res = assetManager.get(resolvedPath); 1010 if(currentLoader != null) 1011 assetManager.setLoader(type, currentLoader); 1012 1013 if(exist) 1014 EventManager.get().fire(EVT_ASSET_RELOADED, new Object[]{oldAsset, res}); 1015 1016 return res; 1017 } 1018 1019 public void restart () { 1020 effect.init(); 1021 effect.start(); 1022 } 1023 } 1024