Path: blob/master/SLICK_HOME/src/org/newdawn/slick/particles/ParticleIO.java
1457 views
package org.newdawn.slick.particles;12import java.io.File;3import java.io.FileInputStream;4import java.io.FileOutputStream;5import java.io.IOException;6import java.io.InputStream;7import java.io.OutputStream;8import java.io.OutputStreamWriter;9import java.util.ArrayList;1011import javax.xml.parsers.DocumentBuilder;12import javax.xml.parsers.DocumentBuilderFactory;13import javax.xml.transform.OutputKeys;14import javax.xml.transform.Result;15import javax.xml.transform.Transformer;16import javax.xml.transform.TransformerFactory;17import javax.xml.transform.dom.DOMSource;18import javax.xml.transform.stream.StreamResult;1920import org.newdawn.slick.Color;21import org.newdawn.slick.geom.Vector2f;22import org.newdawn.slick.particles.ConfigurableEmitter.ColorRecord;23import org.newdawn.slick.particles.ConfigurableEmitter.LinearInterpolator;24import org.newdawn.slick.particles.ConfigurableEmitter.RandomValue;25import org.newdawn.slick.particles.ConfigurableEmitter.SimpleValue;26import org.newdawn.slick.util.Log;27import org.newdawn.slick.util.ResourceLoader;28import org.w3c.dom.Document;29import org.w3c.dom.Element;30import org.w3c.dom.NodeList;3132/**33* Utility methods to (de)serialize ConfigureEmitters to and from XML34*35* @author kevin36*/37public class ParticleIO {3839/**40* Load a set of configured emitters into a single system41*42* @param ref43* The reference to the XML file (file or classpath)44* @param mask45* @return A configured particle system46* @throws IOException47* Indicates a failure to find, read or parse the XML file48*/49public static ParticleSystem loadConfiguredSystem(String ref, Color mask)50throws IOException {51return loadConfiguredSystem(ResourceLoader.getResourceAsStream(ref),52null, null, mask);53}5455/**56* Load a set of configured emitters into a single system57*58* @param ref59* The reference to the XML file (file or classpath)60* @return A configured particle system61* @throws IOException62* Indicates a failure to find, read or parse the XML file63*/64public static ParticleSystem loadConfiguredSystem(String ref)65throws IOException {66return loadConfiguredSystem(ResourceLoader.getResourceAsStream(ref),67null, null, null);68}6970/**71* Load a set of configured emitters into a single system72*73* @param ref74* The XML file to read75* @return A configured particle system76* @throws IOException77* Indicates a failure to find, read or parse the XML file78*/79public static ParticleSystem loadConfiguredSystem(File ref)80throws IOException {81return loadConfiguredSystem(new FileInputStream(ref), null, null, null);82}8384/**85* Load a set of configured emitters into a single system86*87* @param ref88* The stream to read the XML from89* @param mask The mask used to make the particle image transparent90* @return A configured particle system91* @throws IOException92* Indicates a failure to find, read or parse the XML file93*/94public static ParticleSystem loadConfiguredSystem(InputStream ref, Color mask)95throws IOException {96return loadConfiguredSystem(ref, null, null, mask);97}9899/**100* Load a set of configured emitters into a single system101*102* @param ref103* The stream to read the XML from104* @return A configured particle system105* @throws IOException106* Indicates a failure to find, read or parse the XML file107*/108public static ParticleSystem loadConfiguredSystem(InputStream ref)109throws IOException {110return loadConfiguredSystem(ref, null, null, null);111}112113/**114* Load a set of configured emitters into a single system115*116* @param ref117* The reference to the XML file (file or classpath)118* @return A configured particle system119* @param factory120* The factory used to create the emitter than will be poulated121* with loaded data.122* @throws IOException123* Indicates a failure to find, read or parse the XML file124*/125public static ParticleSystem loadConfiguredSystem(String ref,126ConfigurableEmitterFactory factory) throws IOException {127return loadConfiguredSystem(ResourceLoader.getResourceAsStream(ref),128factory, null, null);129}130131/**132* Load a set of configured emitters into a single system133*134* @param ref135* The XML file to read136* @return A configured particle system137* @param factory138* The factory used to create the emitter than will be poulated139* with loaded data.140* @throws IOException141* Indicates a failure to find, read or parse the XML file142*/143public static ParticleSystem loadConfiguredSystem(File ref,144ConfigurableEmitterFactory factory) throws IOException {145return loadConfiguredSystem(new FileInputStream(ref), factory, null, null);146}147148/**149* Load a set of configured emitters into a single system150*151* @param ref152* The stream to read the XML from153* @return A configured particle system154* @param factory155* The factory used to create the emitter than will be poulated156* with loaded data.157* @throws IOException158* Indicates a failure to find, read or parse the XML file159*/160public static ParticleSystem loadConfiguredSystem(InputStream ref,161ConfigurableEmitterFactory factory) throws IOException {162return loadConfiguredSystem(ref, factory, null, null);163}164165/**166* Load a set of configured emitters into a single system167*168* @param ref169* The stream to read the XML from170* @param factory171* The factory used to create the emitter than will be poulated172* with loaded data.173* @param system The particle system that will be loaded into174* @param mask The mask used to make the image background transparent175* @return A configured particle system176* @throws IOException177* Indicates a failure to find, read or parse the XML file178*/179public static ParticleSystem loadConfiguredSystem(InputStream ref,180ConfigurableEmitterFactory factory, ParticleSystem system, Color mask) throws IOException {181if (factory == null) {182factory = new ConfigurableEmitterFactory() {183public ConfigurableEmitter createEmitter(String name) {184return new ConfigurableEmitter(name);185}186};187}188try {189DocumentBuilder builder = DocumentBuilderFactory.newInstance()190.newDocumentBuilder();191Document document = builder.parse(ref);192193Element element = document.getDocumentElement();194if (!element.getNodeName().equals("system")) {195throw new IOException("Not a particle system file");196}197198if (system == null) {199system = new ParticleSystem("org/newdawn/slick/data/particle.tga",2002000, mask);201}202boolean additive = "true".equals(element.getAttribute("additive"));203if (additive) {204system.setBlendingMode(ParticleSystem.BLEND_ADDITIVE);205} else {206system.setBlendingMode(ParticleSystem.BLEND_COMBINE);207}208boolean points = "true".equals(element.getAttribute("points"));209system.setUsePoints(points);210211NodeList list = element.getElementsByTagName("emitter");212for (int i = 0; i < list.getLength(); i++) {213Element em = (Element) list.item(i);214ConfigurableEmitter emitter = factory.createEmitter("new");215elementToEmitter(em, emitter);216217system.addEmitter(emitter);218}219220system.setRemoveCompletedEmitters(false);221return system;222} catch (IOException e) {223Log.error(e);224throw e;225} catch (Exception e) {226Log.error(e);227throw new IOException("Unable to load particle system config");228}229}230231/**232* Save a particle system with only ConfigurableEmitters in to an XML file233*234* @param file235* The file to save to236* @param system237* The system to store238* @throws IOException239* Indicates a failure to save or encode the system XML.240*/241public static void saveConfiguredSystem(File file, ParticleSystem system)242throws IOException {243saveConfiguredSystem(new FileOutputStream(file), system);244}245246/**247* Save a particle system with only ConfigurableEmitters in to an XML file248*249* @param out250* The location to which we'll save251* @param system252* The system to store253* @throws IOException254* Indicates a failure to save or encode the system XML.255*/256public static void saveConfiguredSystem(OutputStream out,257ParticleSystem system) throws IOException {258try {259DocumentBuilder builder = DocumentBuilderFactory.newInstance()260.newDocumentBuilder();261Document document = builder.newDocument();262263Element root = document.createElement("system");264root265.setAttribute(266"additive",267""268+ (system.getBlendingMode() == ParticleSystem.BLEND_ADDITIVE));269root.setAttribute("points", "" + (system.usePoints()));270271document.appendChild(root);272for (int i = 0; i < system.getEmitterCount(); i++) {273ParticleEmitter current = system.getEmitter(i);274if (current instanceof ConfigurableEmitter) {275Element element = emitterToElement(document,276(ConfigurableEmitter) current);277root.appendChild(element);278} else {279throw new RuntimeException(280"Only ConfigurableEmitter instances can be stored");281}282}283284Result result = new StreamResult(new OutputStreamWriter(out,285"utf-8"));286DOMSource source = new DOMSource(document);287288TransformerFactory factory = TransformerFactory.newInstance();289Transformer xformer = factory.newTransformer();290xformer.setOutputProperty(OutputKeys.INDENT, "yes");291292xformer.transform(source, result);293} catch (Exception e) {294Log.error(e);295throw new IOException("Unable to save configured particle system");296}297}298299/**300* Load a single emitter from an XML file301*302* @param ref303* The reference to the emitter XML file to load (classpath or304* file)305* @return The configured emitter306* @throws IOException307* Indicates a failure to find, read or parse the XML file308*/309public static ConfigurableEmitter loadEmitter(String ref)310throws IOException {311return loadEmitter(ResourceLoader.getResourceAsStream(ref), null);312}313314/**315* Load a single emitter from an XML file316*317* @param ref318* The XML file to read319* @return The configured emitter320* @throws IOException321* Indicates a failure to find, read or parse the XML file322*/323public static ConfigurableEmitter loadEmitter(File ref) throws IOException {324return loadEmitter(new FileInputStream(ref), null);325326}327328/**329* Load a single emitter from an XML file330*331* @param ref332* The stream to read the XML from333* @return The configured emitter334* @throws IOException335* Indicates a failure to find, read or parse the XML file336*/337public static ConfigurableEmitter loadEmitter(InputStream ref)338throws IOException {339return loadEmitter(ref, null);340}341342/**343* Load a single emitter from an XML file344*345* @param ref346* The reference to the emitter XML file to load (classpath or347* file)348* @return The configured emitter349* @param factory350* The factory used to create the emitter than will be poulated351* with loaded data.352* @throws IOException353* Indicates a failure to find, read or parse the XML file354*/355public static ConfigurableEmitter loadEmitter(String ref,356ConfigurableEmitterFactory factory) throws IOException {357return loadEmitter(ResourceLoader.getResourceAsStream(ref), factory);358}359360/**361* Load a single emitter from an XML file362*363* @param ref364* The XML file to read365* @return The configured emitter366* @param factory367* The factory used to create the emitter than will be poulated368* with loaded data.369* @throws IOException370* Indicates a failure to find, read or parse the XML file371*/372public static ConfigurableEmitter loadEmitter(File ref,373ConfigurableEmitterFactory factory) throws IOException {374return loadEmitter(new FileInputStream(ref), factory);375376}377378/**379* Load a single emitter from an XML file380*381* @param ref382* The stream to read the XML from383* @param factory384* The factory used to create the emitter than will be poulated385* with loaded data.386* @return The configured emitter387* @throws IOException388* Indicates a failure to find, read or parse the XML file389*/390public static ConfigurableEmitter loadEmitter(InputStream ref,391ConfigurableEmitterFactory factory) throws IOException {392if (factory == null) {393factory = new ConfigurableEmitterFactory() {394public ConfigurableEmitter createEmitter(String name) {395return new ConfigurableEmitter(name);396}397};398}399try {400DocumentBuilder builder = DocumentBuilderFactory.newInstance()401.newDocumentBuilder();402Document document = builder.parse(ref);403404if (!document.getDocumentElement().getNodeName().equals("emitter")) {405throw new IOException("Not a particle emitter file");406}407408ConfigurableEmitter emitter = factory.createEmitter("new");409elementToEmitter(document.getDocumentElement(), emitter);410411return emitter;412} catch (IOException e) {413Log.error(e);414throw e;415} catch (Exception e) {416Log.error(e);417throw new IOException("Unable to load emitter");418}419}420421/**422* Save a single emitter to the XML file423*424* @param file425* The file to save the emitter to426* @param emitter427* The emitter to store to the XML file428* @throws IOException429* Indicates a failure to write or encode the XML430*/431public static void saveEmitter(File file, ConfigurableEmitter emitter)432throws IOException {433saveEmitter(new FileOutputStream(file), emitter);434}435436/**437* Save a single emitter to the XML file438*439* @param out440* The location to which we should save441* @param emitter442* The emitter to store to the XML file443* @throws IOException444* Indicates a failure to write or encode the XML445*/446public static void saveEmitter(OutputStream out, ConfigurableEmitter emitter)447throws IOException {448try {449DocumentBuilder builder = DocumentBuilderFactory.newInstance()450.newDocumentBuilder();451Document document = builder.newDocument();452453document.appendChild(emitterToElement(document, emitter));454Result result = new StreamResult(new OutputStreamWriter(out,455"utf-8"));456DOMSource source = new DOMSource(document);457458TransformerFactory factory = TransformerFactory.newInstance();459Transformer xformer = factory.newTransformer();460xformer.setOutputProperty(OutputKeys.INDENT, "yes");461462xformer.transform(source, result);463} catch (Exception e) {464Log.error(e);465throw new IOException("Failed to save emitter");466}467}468469/**470* Get the first child named as specified from the passed XML element471*472* @param element473* The element whose children are interogated474* @param name475* The name of the element to retrieve476* @return The requested element477*/478private static Element getFirstNamedElement(Element element, String name) {479NodeList list = element.getElementsByTagName(name);480if (list.getLength() == 0) {481return null;482}483484return (Element) list.item(0);485}486487/**488* Convert from an XML element to an configured emitter489*490* @param element491* The XML element to convert492* @param emitter493* The emitter that will be configured based on the XML494*/495private static void elementToEmitter(Element element,496ConfigurableEmitter emitter) {497emitter.name = element.getAttribute("name");498emitter.setImageName(element.getAttribute("imageName"));499500String renderType = element.getAttribute("renderType");501emitter.usePoints = Particle.INHERIT_POINTS;502if (renderType.equals("quads")) {503emitter.usePoints = Particle.USE_QUADS;504}505if (renderType.equals("points")) {506emitter.usePoints = Particle.USE_POINTS;507}508509String useOriented = element.getAttribute("useOriented");510if (useOriented != null)511emitter.useOriented = "true".equals(useOriented);512513String useAdditive = element.getAttribute("useAdditive");514if (useAdditive != null)515emitter.useAdditive = "true".equals(useAdditive);516517parseRangeElement(getFirstNamedElement(element, "spawnInterval"),518emitter.spawnInterval);519parseRangeElement(getFirstNamedElement(element, "spawnCount"),520emitter.spawnCount);521parseRangeElement(getFirstNamedElement(element, "initialLife"),522emitter.initialLife);523parseRangeElement(getFirstNamedElement(element, "initialSize"),524emitter.initialSize);525parseRangeElement(getFirstNamedElement(element, "xOffset"),526emitter.xOffset);527parseRangeElement(getFirstNamedElement(element, "yOffset"),528emitter.yOffset);529parseRangeElement(getFirstNamedElement(element, "initialDistance"),530emitter.initialDistance);531parseRangeElement(getFirstNamedElement(element, "speed"), emitter.speed);532parseRangeElement(getFirstNamedElement(element, "length"),533emitter.length);534parseRangeElement(getFirstNamedElement(element, "emitCount"),535emitter.emitCount);536537parseValueElement(getFirstNamedElement(element, "spread"),538emitter.spread);539parseValueElement(getFirstNamedElement(element, "angularOffset"),540emitter.angularOffset);541parseValueElement(getFirstNamedElement(element, "growthFactor"),542emitter.growthFactor);543parseValueElement(getFirstNamedElement(element, "gravityFactor"),544emitter.gravityFactor);545parseValueElement(getFirstNamedElement(element, "windFactor"),546emitter.windFactor);547parseValueElement(getFirstNamedElement(element, "startAlpha"),548emitter.startAlpha);549parseValueElement(getFirstNamedElement(element, "endAlpha"),550emitter.endAlpha);551parseValueElement(getFirstNamedElement(element, "alpha"), emitter.alpha);552parseValueElement(getFirstNamedElement(element, "size"), emitter.size);553parseValueElement(getFirstNamedElement(element, "velocity"),554emitter.velocity);555parseValueElement(getFirstNamedElement(element, "scaleY"),556emitter.scaleY);557558Element color = getFirstNamedElement(element, "color");559NodeList steps = color.getElementsByTagName("step");560emitter.colors.clear();561for (int i = 0; i < steps.getLength(); i++) {562Element step = (Element) steps.item(i);563float offset = Float.parseFloat(step.getAttribute("offset"));564float r = Float.parseFloat(step.getAttribute("r"));565float g = Float.parseFloat(step.getAttribute("g"));566float b = Float.parseFloat(step.getAttribute("b"));567568emitter.addColorPoint(offset, new Color(r, g, b, 1));569}570571// generate new random play length572emitter.replay();573}574575/**576* Convert from an emitter to a XML element description577*578* @param document579* The document the element will be part of580* @param emitter581* The emitter to convert582* @return The XML element based on the configured emitter583*/584private static Element emitterToElement(Document document,585ConfigurableEmitter emitter) {586Element root = document.createElement("emitter");587root.setAttribute("name", emitter.name);588root.setAttribute("imageName", emitter.imageName == null ? ""589: emitter.imageName);590root591.setAttribute("useOriented", emitter.useOriented ? "true"592: "false");593root594.setAttribute("useAdditive", emitter.useAdditive ? "true"595: "false");596597if (emitter.usePoints == Particle.INHERIT_POINTS) {598root.setAttribute("renderType", "inherit");599}600if (emitter.usePoints == Particle.USE_POINTS) {601root.setAttribute("renderType", "points");602}603if (emitter.usePoints == Particle.USE_QUADS) {604root.setAttribute("renderType", "quads");605}606607root.appendChild(createRangeElement(document, "spawnInterval",608emitter.spawnInterval));609root.appendChild(createRangeElement(document, "spawnCount",610emitter.spawnCount));611root.appendChild(createRangeElement(document, "initialLife",612emitter.initialLife));613root.appendChild(createRangeElement(document, "initialSize",614emitter.initialSize));615root.appendChild(createRangeElement(document, "xOffset",616emitter.xOffset));617root.appendChild(createRangeElement(document, "yOffset",618emitter.yOffset));619root.appendChild(createRangeElement(document, "initialDistance",620emitter.initialDistance));621root.appendChild(createRangeElement(document, "speed", emitter.speed));622root623.appendChild(createRangeElement(document, "length",624emitter.length));625root.appendChild(createRangeElement(document, "emitCount",626emitter.emitCount));627628root629.appendChild(createValueElement(document, "spread",630emitter.spread));631root.appendChild(createValueElement(document, "angularOffset",632emitter.angularOffset));633root.appendChild(createValueElement(document, "growthFactor",634emitter.growthFactor));635root.appendChild(createValueElement(document, "gravityFactor",636emitter.gravityFactor));637root.appendChild(createValueElement(document, "windFactor",638emitter.windFactor));639root.appendChild(createValueElement(document, "startAlpha",640emitter.startAlpha));641root.appendChild(createValueElement(document, "endAlpha",642emitter.endAlpha));643root.appendChild(createValueElement(document, "alpha", emitter.alpha));644root.appendChild(createValueElement(document, "size", emitter.size));645root.appendChild(createValueElement(document, "velocity",646emitter.velocity));647root648.appendChild(createValueElement(document, "scaleY",649emitter.scaleY));650651Element color = document.createElement("color");652ArrayList list = emitter.colors;653for (int i = 0; i < list.size(); i++) {654ColorRecord record = (ColorRecord) list.get(i);655Element step = document.createElement("step");656step.setAttribute("offset", "" + record.pos);657step.setAttribute("r", "" + record.col.r);658step.setAttribute("g", "" + record.col.g);659step.setAttribute("b", "" + record.col.b);660661color.appendChild(step);662}663664root.appendChild(color);665666return root;667}668669/**670* Create an XML element based on a configured range671*672* @param document673* The document the element will be part of674* @param name675* The name to give the new element676* @param range677* The configured range678* @return A configured XML element on the range679*/680private static Element createRangeElement(Document document, String name,681ConfigurableEmitter.Range range) {682Element element = document.createElement(name);683element.setAttribute("min", "" + range.getMin());684element.setAttribute("max", "" + range.getMax());685element.setAttribute("enabled", "" + range.isEnabled());686687return element;688}689690/**691* Create an XML element based on a configured value692*693* @param document694* The document the element will be part of695* @param name696* The name to give the new element697* @param value698* The configured value699* @return A configure XML element based on the value700*/701private static Element createValueElement(Document document, String name,702ConfigurableEmitter.Value value) {703Element element = document.createElement(name);704705// void: now writes the value type706if (value instanceof SimpleValue) {707element.setAttribute("type", "simple");708element.setAttribute("value", "" + value.getValue(0));709} else if (value instanceof RandomValue) {710element.setAttribute("type", "random");711element712.setAttribute("value", ""713+ ((RandomValue) value).getValue());714} else if (value instanceof LinearInterpolator) {715element.setAttribute("type", "linear");716element.setAttribute("min", ""717+ ((LinearInterpolator) value).getMin());718element.setAttribute("max", ""719+ ((LinearInterpolator) value).getMax());720element.setAttribute("active", ""721+ ((LinearInterpolator) value).isActive());722723ArrayList curve = ((LinearInterpolator) value).getCurve();724for (int i = 0; i < curve.size(); i++) {725Vector2f point = (Vector2f) curve.get(i);726727Element pointElement = document.createElement("point");728pointElement.setAttribute("x", "" + point.x);729pointElement.setAttribute("y", "" + point.y);730731element.appendChild(pointElement);732}733} else {734Log.warn("unkown value type ignored: " + value.getClass());735}736737return element;738}739740/**741* Parse an XML element into a configured range742*743* @param element744* The XML element to parse745* @param range746* The range to configure based on the XML747*/748private static void parseRangeElement(Element element,749ConfigurableEmitter.Range range) {750if (element == null) {751return;752}753range.setMin(Float.parseFloat(element.getAttribute("min")));754range.setMax(Float.parseFloat(element.getAttribute("max")));755range.setEnabled("true".equals(element.getAttribute("enabled")));756}757758/**759* Parse an XML element into a configured value760*761* @param element762* The XML element to parse763* @param value764* The value to configure based on the XML765*/766private static void parseValueElement(Element element,767ConfigurableEmitter.Value value) {768if (element == null) {769return;770}771772String type = element.getAttribute("type");773String v = element.getAttribute("value");774775if (type == null || type.length() == 0) {776// support for old style which did not write the type777if (value instanceof SimpleValue) {778((SimpleValue) value).setValue(Float.parseFloat(v));779} else if (value instanceof RandomValue) {780((RandomValue) value).setValue(Float.parseFloat(v));781} else {782Log.warn("problems reading element, skipping: " + element);783}784} else {785// type given: this is the new style786if (type.equals("simple")) {787((SimpleValue) value).setValue(Float.parseFloat(v));788} else if (type.equals("random")) {789((RandomValue) value).setValue(Float.parseFloat(v));790} else if (type.equals("linear")) {791String min = element.getAttribute("min");792String max = element.getAttribute("max");793String active = element.getAttribute("active");794795NodeList points = element.getElementsByTagName("point");796797ArrayList curve = new ArrayList();798for (int i = 0; i < points.getLength(); i++) {799Element point = (Element) points.item(i);800801float x = Float.parseFloat(point.getAttribute("x"));802float y = Float.parseFloat(point.getAttribute("y"));803804curve.add(new Vector2f(x, y));805}806807((LinearInterpolator) value).setCurve(curve);808((LinearInterpolator) value).setMin(Integer.parseInt(min));809((LinearInterpolator) value).setMax(Integer.parseInt(max));810((LinearInterpolator) value).setActive("true".equals(active));811} else {812Log.warn("unkown type detected: " + type);813}814}815}816}817818819